def checkTemplate(rpc: RPC, mnemonic: str, req: Dict[str, Any], inputs: List[Dict[str, Any]], outputs: List[Dict[str, Any]]) -> None: template: Dict[str, Any] = rpc.call("personal", "getTransactionTemplate", req) if template["type"] != "Send": raise TestError("Template isn't of type Send.") if sortUTXOs(template["inputs"]) != sortUTXOs(inputs): raise TestError("Template inputs aren't as expected.") if template["outputs"] != outputs: raise TestError("Template outputs are incorrect.") keys: List[bytes] = [] for inputJSON in template["inputs"]: key: bytes if inputJSON["change"]: key = getChangePublicKey(mnemonic, "", inputJSON["index"]) else: key = getPublicKey(mnemonic, "", inputJSON["index"]) if key not in keys: keys.append(key) if template["publicKey"] != Ristretto.aggregate( [Ristretto.RistrettoPoint(key) for key in keys]).serialize().hex().upper(): if len(keys) == 1: raise TestError( "Template public key isn't correct when only a single key is present." ) raise TestError("Public key aggregation isn't correct.")
def deriveKeyAndChainCode( secret: bytes, path: List[int] ) -> Tuple[bytes, bytes]: k: bytes = hashlib.sha512(secret).digest() kL: bytes = Ristretto.RistrettoScalar(k[:32]).serialize() kR: bytes = k[32:] k = kL + kR #Parent public key/chain code. A: bytes = Ristretto.RistrettoScalar(kL).toPoint().serialize() c: bytes = hashlib.sha256(bytes([1]) + secret).digest() #Derive each child. for i in path: iBytes: bytes = i.to_bytes(4, "little") Z: bytes if i < HARDENED_THRESHOLD: Z = hmac512(c, bytes([2]) + A + iBytes) c = hmac512(c, bytes([3]) + A + iBytes)[32:] else: Z = hmac512(c, bytes([0]) + k + iBytes) c = hmac512(c, bytes([1]) + k + iBytes)[32:] zR: bytes = Z[32:] #This performs a mod l on kL which the BIP32-Ed25519 doesn't specify. That said, it's required to form a valid private key. scalar: Ristretto.Scalar = ( Ristretto.RistrettoScalar(int.from_bytes(Z[:32], "little")) + Ristretto.RistrettoScalar(int.from_bytes(kL, "little")) ) if scalar.underlying == mpz(0): raise Exception("Invalid child.") kL = scalar.serialize() kR = ((int.from_bytes(zR, "little") + int.from_bytes(kR, "little")) % (1 << 256)).to_bytes(32, "little") k = kL + kR A = Ristretto.RistrettoPoint(scalar.toPoint()).serialize() return (k, c)