def child(self, i: int) -> 'Xpub': hardened = i >= 1 << 31 if hardened: raise KeyDerivationError( 'Cannot derive a hardened key from an extended public key') I = hmac.new(key=self.code, msg=self.keydata() + int_to_bytes(i).rjust(4, b'\x00'), digestmod=hashlib.sha512).digest() I_L, I_R = I[:32], I[32:] key = PrivateKey(I_L).to_public().point + self.key.point ret_code = I_R path = self.path + f'/{i}' # TODO add point at infinity check return Xpub(PublicKey(key), ret_code, depth=self.depth + 1, i=i, parent=self.fingerprint(), path=path, addresstype=self.type.value)
def OP_CHECKSIG(self): """https://en.bitcoin.it/wiki/OP_CHECKSIG""" pub = PublicKey.decode(self.pop()) extended_sig = self.pop() sig = Signature.decode(extended_sig[:-1]) hashcode = SIGHASH(extended_sig[-1]) sighash = self.tx.sighash(i=self.index, hashcode=hashcode) self.push(sig.verify_hash(sighash, pub))
def test_verification(self): for vector in self.vectors: try: sig = Schnorr.from_hex(vector['sig']) pubkey = PublicKey.from_hex(vector['pub']) result = vector['msg'].verify(signature=sig, public=pubkey) except AssertionError: result = False self.assertEqual(result, vector['result'])
def test_p2wsh(self): """https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#examples""" pubkey = PublicKey.from_hex( '0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798' ) script = push(pubkey.encode( compressed=True)) + OP.CHECKSIG.byte # <key> <OP_CHECKSIG> address = script_to_address(script, 'P2WSH') self.assertEqual( address, 'bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3') self.assertEqual(address_type(address), ADDRESS.P2WSH)
def test_encoding(self): for vector in self.vectors: prv_hex = vector['prv'] if not prv_hex: continue prv = PrivateKey.from_hex(prv_hex) pubkey_actual = prv.to_public() pubkey = PublicKey.from_hex(vector['pub']) self.assertEqual(pubkey_actual.hex(compact=True), pubkey.hex(compact=True)) msg = vector['msg'] aux = vector['aux'] signature = msg.sign_schnorr(prv, aux=aux) self.assertEqual(signature.hex(), vector['sig'].lower())
def OP_CHECKMULTISIG(self): # Multisig m out of n # The stack at this point should look something like this # ['', # '3045022100c38f1d0e340f4308b7f6e4bef0c8668e84793370924844a1076cc986f37047af02207cc29b61e85dc580ce85e01858e2e47eb3b8a80472ad784eb74538045e8172e801', # '30450221009a6abea495730976b69f255282ee0c488e49769138b7048e749dd5215bdf8120022069f690fcaf5dba05f0537911b16b2868087440eb55a19dc6e89bcb83f1f35c6501', # 2, # '02d271610ba72d9b0948ea0821fac77e0e6d10234a266b4828671a86a59073bb30', # '0359446555d1c389782468191250c007a98393eb6e9db64649cd7ed1e7f9ca0cf3', # '023779ee80b4a940503b1d630e7a3934503eecba5d571111f30841cdfbce0e8397', # 3] n = self.pop() keys = [PublicKey.decode(self.pop()) for _ in range(n)] m = self.pop() raw_signatures = [self.pop() for _ in range(m)] _ = self.pop( ) # extra bytes in stack due to original implementation bug valid_signatures = [] for raw_sig in raw_signatures: sig, hashcode = Signature.decode(raw_sig[:-1]), SIGHASH( raw_sig[-1]) sighash = self.tx.sighash(self.index, script=self.scriptPubKey, hashcode=hashcode) for pub in keys: valid = sig.verify_hash(sighash, pub) if valid: valid_signatures.append(valid) keys.remove( pub ) # Drop public key, to avoid allowing multiple signatures with same key break else: valid_signatures.append(False) self.push(sum(valid_signatures) >= m)
def deserialize(cls, bts: bytes) -> 'ExtendedKey': def read(n): nonlocal bts data, bts = bts[:n], bts[n:] return data net = read(4) is_private = net in network('extended_prv').values() is_public = net in network('extended_pub').values() assert is_public ^ is_private, f'Invalid network bytes : {bytes_to_hex(net)}' address_lookup = { val: key for key, val in (network('extended_prv') if is_private else network('extended_pub')).items() } constructor = Xprv if is_private else Xpub depth = bytes_to_int(read(1)) assert depth in range(256), f'Invalid depth : {depth}' fingerprint = read(4) i = bytes_to_int(read(4)) if depth == 0: i = None path = None else: ih = f'{i}' if i < 2**31 else f"{i - 2**31}h" path = '/'.join([constructor.root_path] + ['x' for _ in range(depth - 1)] + [ih]) code = read(32) key = read(33) key = PrivateKey(key) if is_private else PublicKey.decode(key) assert not bts, 'Leftover bytes' return constructor(key, code, depth=depth, i=i, parent=fingerprint, path=path, addresstype=address_lookup[net])