def test_point_from_bip340pub_key() -> None: q, x_Q = ssa.gen_keys() Q = mult(q) # Integer (int) assert ssa.point_from_bip340pub_key(x_Q) == Q # Integer (bytes) x_Q_bytes = x_Q.to_bytes(32, "big", signed=False) assert ssa.point_from_bip340pub_key(x_Q_bytes) == Q # Integer (hex-str) assert ssa.point_from_bip340pub_key(x_Q_bytes.hex()) == Q # tuple Point assert ssa.point_from_bip340pub_key(Q) == Q # 33 bytes assert ssa.point_from_bip340pub_key(bytes_from_point(Q)) == Q # 33 bytes hex-string assert ssa.point_from_bip340pub_key(bytes_from_point(Q).hex()) == Q # 65 bytes assert ssa.point_from_bip340pub_key(bytes_from_point( Q, compressed=False)) == Q # 65 bytes hex-string assert (ssa.point_from_bip340pub_key( bytes_from_point(Q, compressed=False).hex()) == Q) xpub_data = BIP32KeyData.b58decode( "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy" ) xpub_data.key = bytes_from_point(Q) # BIP32KeyData assert ssa.point_from_bip340pub_key(xpub_data) == Q # BIP32Key encoded str xpub = xpub_data.b58encode() assert ssa.point_from_bip340pub_key(xpub) == Q # BIP32Key str assert ssa.point_from_bip340pub_key(xpub.encode("ascii")) == Q
def test_gec_2() -> None: """GEC 2: Test Vectors for SEC 1, section 4.1 http://read.pudn.com/downloads168/doc/772358/TestVectorsforSEC%201-gec2.pdf """ # 4.1.1 ec = CURVES["secp160r1"] hf = sha1 # 4.1.2 dU = 971761939728640320549601132085879836204587084162 assert dU == 0xAA374FFC3CE144E6B073307972CB6D57B2A4E982 QU = mult(dU, ec.G, ec) assert QU == ( 466448783855397898016055842232266600516272889280, 1110706324081757720403272427311003102474457754220, ) assert ( bytes_from_point(QU, ec).hex() == "0251b4496fecc406ed0e75a24a3c03206251419dc0" ) # 4.1.3 dV = 399525573676508631577122671218044116107572676710 assert dV == 0x45FB58A92A17AD4B15101C66E74F277E2B460866 QV = mult(dV, ec.G, ec) assert QV == ( 420773078745784176406965940076771545932416607676, 221937774842090227911893783570676792435918278531, ) assert ( bytes_from_point(QV, ec).hex() == "0349b41e0e9c0369c2328739d90f63d56707c6e5bc" ) # expected results z_exp = 1155982782519895915997745984453282631351432623114 assert z_exp == 0xCA7C0F8C3FFA87A96E1B74AC8E6AF594347BB40A size = 20 # 4.1.4 z, _ = mult(dU, QV, ec) # x coordinate only assert z == z_exp keyingdata = ansi_x9_63_kdf( z.to_bytes(ec.p_size, byteorder="big", signed=False), size, hf, None ) assert keyingdata.hex() == "744ab703f5bc082e59185f6d049d2d367db245c2" # 4.1.5 z, _ = mult(dV, QU, ec) # x coordinate only assert z == z_exp keyingdata = ansi_x9_63_kdf( z.to_bytes(ec.p_size, byteorder="big", signed=False), size, hf, None ) assert keyingdata.hex() == "744ab703f5bc082e59185f6d049d2d367db245c2"
def test_gec() -> None: """GEC 2: Test Vectors for SEC 1, section 2 http://read.pudn.com/downloads168/doc/772358/TestVectorsforSEC%201-gec2.pdf """ # 2.1.1 Scheme setup ec = CURVES["secp160r1"] hf = sha1 # 2.1.2 Key Deployment for U dU = 971761939728640320549601132085879836204587084162 dU, QU = dsa.gen_keys(dU, ec) assert (format(dU, str(ec.n_size) + "x") == "aa374ffc3ce144e6b073307972cb6d57b2a4e982") assert QU == ( 466448783855397898016055842232266600516272889280, 1110706324081757720403272427311003102474457754220, ) assert (bytes_from_point( QU, ec).hex() == "0251b4496fecc406ed0e75a24a3c03206251419dc0") # 2.1.3 Signing Operation for U msg = b"abc" k = 702232148019446860144825009548118511996283736794 lower_s = False sig = dsa.sign_(reduce_to_hlen(msg, hf), dU, k, lower_s, ec, hf) assert sig.r == 0xCE2873E5BE449563391FEB47DDCBA2DC16379191 assert sig.s == 0x3480EC1371A091A464B31CE47DF0CB8AA2D98B54 assert sig.ec == ec # 2.1.4 Verifying Operation for V dsa.assert_as_valid(msg, QU, sig, lower_s, hf) assert dsa.verify(msg, QU, sig, lower_s, hf)
def test_p2pkh_from_pub_key() -> None: # https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses pub_key = "02 50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352" address = "1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs" assert address == b58.p2pkh(pub_key) assert address == b58.p2pkh(pub_key, compressed=True) _, h160, _ = b58.h160_from_address(address) assert h160 == hash160(pub_key) # trailing/leading spaces in address string assert address == b58.p2pkh(" " + pub_key) assert h160 == hash160(" " + pub_key) assert address == b58.p2pkh(pub_key + " ") assert h160 == hash160(pub_key + " ") uncompr_pub_key = bytes_from_point(point_from_octets(pub_key), compressed=False) uncompr_address = "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM" assert uncompr_address == b58.p2pkh(uncompr_pub_key, compressed=False) assert uncompr_address == b58.p2pkh(uncompr_pub_key) _, uncompr_h160, _ = b58.h160_from_address(uncompr_address) assert uncompr_h160 == hash160(uncompr_pub_key) err_msg = "not a private or uncompressed public key: " with pytest.raises(BTClibValueError, match=err_msg): assert uncompr_address == b58.p2pkh(pub_key, compressed=False) err_msg = "not a private or compressed public key: " with pytest.raises(BTClibValueError, match=err_msg): assert address == b58.p2pkh(uncompr_pub_key, compressed=True)
def _get_msg_format(msg: bytes, pubk_rings: PubkeyRing) -> bytes: t = b"".join( b"".join(bytes_from_point(Q, ec) for Q in pubk_ring) for pubk_ring in pubk_rings.values() ) return hf(msg + t).digest()
def assert_as_valid(msg: Octets, e0: bytes, s: SValues, pubk_rings: PubkeyRing) -> bool: msg = bytes_from_octets(msg) m = _get_msg_format(msg, pubk_rings) ring_size = len(pubk_rings) e: SValues = defaultdict(list) e0bytes = m for i in range(ring_size): keys_size = len(pubk_rings[i]) e[i] = [0] * keys_size e[i][0] = int_from_bits(_hash(m, e0, i, 0), ec.nlen) % ec.n # edge case that cannot be reproduced in the test suite if e[i][0] == 0: err_msg = "implausibile signature failure" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover r = b"\0x00" for j in range(keys_size): t = double_mult(-e[i][j], pubk_rings[i][j], s[i][j], ec.G) r = bytes_from_point(t, ec) if j != len(pubk_rings[i]) - 1: h = _hash(m, r, i, j + 1) e[i][j + 1] = int_from_bits(h, ec.nlen) % ec.n # edge case that cannot be reproduced in the test suite if e[i][j + 1] == 0: err_msg = "implausibile signature failure" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover else: e0bytes += r e0_prime = hf(e0bytes).digest() return e0_prime == e0
def second_generator(ec: Curve = secp256k1, hf: HashF = sha256) -> Point: """Second (with respect to G) elliptic curve generator. Second (with respect to G) Nothing-Up-My-Sleeve (NUMS) elliptic curve generator. The hash of G is coerced it to a point (x_H, y_H). If the resulting point is not on the curve, keep on incrementing x_H until a valid curve point (x_H, y_H) is obtained. idea: https://crypto.stackexchange.com/questions/25581/second-generator-for-secp256k1-curve source: https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/rangeproof/main_impl.h """ G_bytes = bytes_from_point(ec.G, ec, compressed=False) hash_ = hf() hash_.update(G_bytes) hash_digest = hash_.digest() x_H = int_from_bits(hash_digest, ec.nlen) % ec.n while True: try: y_H = ec.y_even(x_H) return x_H, y_H except BTClibValueError: x_H += 1 x_H %= ec.p
def test_p2wpkh() -> None: # https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki # leading/trailing spaces should be tolerated pub = " 02 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" addr = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" assert addr == b32.p2wpkh(pub) addr = "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx" assert addr == b32.p2wpkh(pub, "testnet") # http://bitcoinscri.pt/pages/segwit_native_p2wpkh pub = "02 530c548d402670b13ad8887ff99c294e67fc18097d236d57880c69261b42def7" addr = "bc1qg9stkxrszkdqsuj92lm4c7akvk36zvhqw7p6ck" assert addr == b32.p2wpkh(pub) _, wit_prg, _ = b32.witness_from_address(addr) assert wit_prg == hash160(pub) uncompr_pub = bytes_from_point(point_from_octets(pub), compressed=False) err_msg = "not a private or compressed public key: " with pytest.raises(BTClibValueError, match=err_msg): b32.p2wpkh(uncompr_pub) with pytest.raises(BTClibValueError, match=err_msg): b32.p2wpkh(pub + "00") err_msg = "invalid size: " with pytest.raises(BTClibValueError, match=err_msg): b32.address_from_witness(0, hash160(pub) + b"\x00")
def pub_keyinfo_from_prv_key(prv_key: PrvKey, network: Optional[str] = None, compressed: Optional[bool] = None) -> PubkeyInfo: "Return the pub key tuple (SEC-bytes, network) from a private key." q, net, compr = prv_keyinfo_from_prv_key(prv_key, network, compressed) ec = NETWORKS[net].curve pub_key = mult(q, ec.G, ec) return bytes_from_point(pub_key, ec, compr), net
def assert_as_valid(msg: Octets, addr: String, sig: Union[Sig, String], lower_s: bool = True) -> None: # Private function for test/dev purposes # It raises Errors, while verify should always return True or False if isinstance(sig, Sig): sig.assert_valid() else: sig = Sig.b64decode(sig) # first two bits in rf are reserved for key_id # key_id = 00; key_id = 01; key_id = 10; key_id = 11 # 27-27 = 000000; 28-27 = 000001; 29-27 = 000010; 30-27 = 000011 # 31-27 = 000100; 32-27 = 000101; 33-27 = 000110; 34-27 = 000111 # 35-27 = 001000; 36-27 = 001001; 37-27 = 001010; 38-27 = 001011 # 39-27 = 001100; 40-27 = 001101; 41-27 = 001110; 42-27 = 001111 key_id = sig.rf - 27 & 0b11 magic_msg = magic_message(msg) Q = dsa.recover_pub_key(key_id, magic_msg, sig.dsa_sig, lower_s, sha256) compressed = sig.rf > 30 # signature is valid only if the provided address is matched pub_key = bytes_from_point(Q, compressed=compressed) if has_segwit_prefix(addr): wit_ver, h160, _ = witness_from_address(addr) if wit_ver != 0 or len(h160) != 20: raise BTClibValueError(f"not a p2wpkh address: {addr!r}") if not (30 < sig.rf < 35 or sig.rf > 38): raise BTClibValueError( f"invalid p2wpkh address recovery flag: {sig.rf}") if hash160(pub_key) != h160: raise BTClibValueError(f"invalid p2wpkh address: {addr!r}") return script_type, h160, _ = h160_from_address(addr) if script_type == "p2pkh": if sig.rf > 34: raise BTClibValueError( f"invalid p2pkh address recovery flag: {sig.rf}") if hash160(pub_key) != h160: raise BTClibValueError(f"invalid p2pkh address: {addr!r}") return # must be P2WPKH-P2SH if not 30 < sig.rf < 39: raise BTClibValueError( f"invalid p2wpkh-p2sh address recovery flag: {sig.rf}") script_pk = b"\x00\x14" + hash160(pub_key) if hash160(script_pk) != h160: raise BTClibValueError(f"invalid p2wpkh-p2sh address: {addr!r}")
def __ckd(xkey: _ExtendedBIP32KeyData, index: int) -> None: xkey.depth += 1 xkey.index = index if xkey.is_private: Q_bytes = bytes_from_point(mult(xkey.prv_key_int)) xkey.parent_fingerprint = hash160(Q_bytes)[:4] if xkey.is_hardened: # hardened derivation hmac_ = hmac.new( xkey.chain_code, xkey.key + index.to_bytes(4, byteorder="big", signed=False), "sha512", ).digest() else: # normal derivation hmac_ = hmac.new( xkey.chain_code, Q_bytes + index.to_bytes(4, byteorder="big", signed=False), "sha512", ).digest() xkey.chain_code = hmac_[32:] offset = int.from_bytes(hmac_[:32], byteorder="big", signed=False) xkey.prv_key_int = (xkey.prv_key_int + offset) % ec.n xkey.key = b"\x00" + xkey.prv_key_int.to_bytes( 32, byteorder="big", signed=False ) xkey.pub_key_point = INF else: # public key xkey.parent_fingerprint = hash160(xkey.key)[:4] if xkey.is_hardened: raise BTClibValueError("invalid hardened derivation from public key") hmac_ = hmac.new( xkey.chain_code, xkey.key + index.to_bytes(4, byteorder="big", signed=False), "sha512", ).digest() xkey.chain_code = hmac_[32:] offset = int.from_bytes(hmac_[:32], byteorder="big", signed=False) xkey.pub_key_point = ec.add(xkey.pub_key_point, mult(offset)) xkey.key = bytes_from_point(xkey.pub_key_point) xkey.prv_key_int = 0
def pub_keyinfo_from_pub_key(pub_key: PubKey, network: Optional[str] = None, compressed: Optional[bool] = None) -> PubkeyInfo: "Return the pub key tuple (SEC-bytes, network) from a public key." compr = True if compressed is None else compressed net = "mainnet" if network is None else network ec = NETWORKS[net].curve if isinstance(pub_key, tuple): return bytes_from_point(pub_key, ec, compr), net if isinstance(pub_key, BIP32KeyData): return _pub_keyinfo_from_xpub(pub_key, network, compressed) try: return _pub_keyinfo_from_xpub(pub_key, network, compressed) except (TypeError, BTClibValueError): pass # it must be octets try: if compressed is None: pub_key = bytes_from_octets(pub_key, (ec.p_size + 1, 2 * ec.p_size + 1)) compr = False if len(pub_key) == ec.p_size + 1: compr = True else: size = ec.p_size + 1 if compressed else 2 * ec.p_size + 1 pub_key = bytes_from_octets(pub_key, size) compr = compressed except (TypeError, ValueError) as e: err_msg = f"not a public key: {pub_key!r}" raise BTClibValueError(err_msg) from e # verify that it is a valid point Q = point_from_octets(pub_key, ec) return bytes_from_point(Q, ec, compr), net
def test_libsecp256k1() -> None: try: import btclib_libsecp256k1.dsa # pylint: disable=import-outside-toplevel except ImportError: # pragma: no cover pytest.skip() prvkey, Q = dsa.gen_keys(0x1) pubkey_bytes = bytes_from_point(Q) msg = "Satoshi Nakamoto".encode() msg_hash = reduce_to_hlen(msg) libsecp256k1_sig = btclib_libsecp256k1.dsa.sign(msg_hash, prvkey) btclib_sig = dsa.sign_(msg_hash, prvkey) assert btclib_libsecp256k1.dsa.verify(msg_hash, pubkey_bytes, btclib_sig.serialize()) assert dsa.verify(msg, prvkey, libsecp256k1_sig)
def _tweak(commit_hash: Octets, R: Point, ec: Curve, hf: HashF) -> int: "Return the hash(R||commit_hash) tweak for the provided R." t = bytes_from_point(R, ec) + bytes_from_octets(commit_hash) while True: h = hf() h.update(t) t = h.digest() # The following lines would introduce a bias # nonce = int.from_bytes(t, 'big') % ec.n # nonce = int_from_bits(t, ec.nlen) % ec.n # In general, taking a uniformly random integer (like those # obtained from a hash function in the random oracle model) # modulo the curve order n would produce a biased result. # However, if the order n is sufficiently close to 2^hf_len, # then the bias is not observable: e.g. # for secp256k1 and sha256 1-n/2^256 it is about 1.27*2^-128 tweak = int_from_bits(t, ec.nlen) # candidate tweak if 0 < tweak < ec.n: # acceptable value for tweak return tweak # successful candidate
def sign(msg: Octets, prv_key: PrvKey, addr: Optional[String] = None) -> Sig: "Generate address-based compact signature for the provided message." # first sign the message magic_msg = magic_message(msg) q, network, compressed = prv_keyinfo_from_prv_key(prv_key) dsa_sig = dsa.sign(magic_msg, q) # now calculate the key_id # TODO do the match in Jacobian coordinates avoiding mod_inv pub_keys = dsa.recover_pub_keys(magic_msg, dsa_sig) Q = mult(q) # key_id is in [0, 3] # first two bits in rf are reserved for it key_id = pub_keys.index(Q) pub_key = bytes_from_point(Q, compressed=compressed) if isinstance(addr, str): addr = addr.strip() elif isinstance(addr, bytes): addr = addr.decode("ascii") # finally, calculate the recovery flag if addr is None or addr == p2pkh(pub_key, network, compressed): rf = key_id + 27 # third bit in rf is reserved for the 'compressed' boolean rf += 4 if compressed else 0 # BIP137 elif addr == p2wpkh_p2sh(pub_key, network): rf = key_id + 35 elif addr == p2wpkh(pub_key, network): rf = key_id + 39 else: raise BTClibValueError("mismatch between private key and address") return Sig(rf, dsa_sig)
def _xpub_from_xprv(xprv: BIP32Key) -> BIP32KeyData: """Neutered Derivation (ND). Derivation of the extended public key corresponding to an extended private key (“neutered” as it removes the ability to sign transactions). """ if isinstance(xprv, BIP32KeyData): xkey = copy.copy(xprv) else: xkey = BIP32KeyData.b58decode(xprv) if xkey.key[0] != 0: err_msg = f"not a private key: {xkey.b58encode()}" raise BTClibValueError(err_msg) i = XPRV_VERSIONS_ALL.index(xkey.version) xkey.version = XPUB_VERSIONS_ALL[i] q = int.from_bytes(xkey.key[1:], byteorder="big", signed=False) Q = mult(q) xkey.key = bytes_from_point(Q) return xkey
def test_infinity_point_bytes() -> None: with pytest.raises(BTClibValueError, match="no bytes representation for infinity point"): bytes_from_point(INF)
def test_exceptions() -> None: # from creator example psbt_str = "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAAAAAA=" psbt = Psbt.b64decode(psbt_str) psbt.outputs[0].redeem_script = "bad script" # type: ignore with pytest.raises(TypeError): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.inputs[0].witness_script = "bad script" # type: ignore with pytest.raises(TypeError): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.outputs[0].unknown = {"bad key": b""} # type: ignore with pytest.raises(TypeError): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.outputs[0].unknown = {b"deadbeef": "bad value"} # type: ignore with pytest.raises(TypeError): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.inputs[0].sig_hash_type = 101 with pytest.raises(BTClibValueError, match="invalid sign_hash type: "): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.inputs[0].final_script_sig = "bad script" # type: ignore with pytest.raises(TypeError): psbt.serialize() psbt = Psbt.b64decode(psbt_str) _, Q = dsa.gen_keys() pub_key = sec_point.bytes_from_point(Q) r = s = int.from_bytes(bytes.fromhex("FF" * 32), byteorder="big", signed=False) sig_bytes = der.Sig(r, s, check_validity=False).serialize(check_validity=False) psbt.inputs[0].partial_sigs = {pub_key: sig_bytes} with pytest.raises(BTClibValueError, match="invalid partial signature: "): psbt.serialize() pub_key = bytes.fromhex("02" + 31 * "00" + "07") psbt.inputs[0].partial_sigs = {pub_key: sig_bytes} with pytest.raises(BTClibValueError, match="invalid partial signature pub_key: "): psbt.serialize() psbt = Psbt.b64decode(psbt_str) err_msg = "invalid version: " psbt.version = -1 with pytest.raises(BTClibValueError, match=err_msg): psbt.serialize() psbt.version = 0xFFFFFFFF + 1 with pytest.raises(BTClibValueError, match=err_msg): psbt.serialize() psbt.version = 1 # TODO: add to test vectors with pytest.raises(BTClibValueError, match="invalid non-zero version: "): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt_bin = psbt.serialize() psbt_bin = psbt_bin.replace(PSBT_SEPARATOR, PSBT_DELIMITER) # TODO: add to test vectors with pytest.raises(BTClibValueError, match="malformed psbt: missing separator"): Psbt.parse(psbt_bin) psbt = Psbt.b64decode(psbt_str) psbt.inputs.pop() err_msg = "mismatched number of psb.tx.vin and psb.inputs: " # TODO: add to test vectors with pytest.raises(BTClibValueError, match=err_msg): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.tx.vin[0].script_witness = Witness([b""]) err_msg = "non empty script_sig or witness" # TODO: add to test vectors with pytest.raises(BTClibValueError, match=err_msg): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.outputs.pop() err_msg = "mismatched number of psb.tx.vout and psbt.outputs: " # TODO: add to test vectors with pytest.raises(BTClibValueError, match=err_msg): psbt.serialize() psbt_str = "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA" psbt = Psbt.b64decode(psbt_str) psbt.tx.vin[0].prev_out.tx_id, psbt.tx.vin[1].prev_out.tx_id = ( psbt.tx.vin[1].prev_out.tx_id, psbt.tx.vin[0].prev_out.tx_id, ) err_msg = "mismatched non-witness utxo / outpoint tx_id" with pytest.raises(BTClibValueError, match=err_msg): psbt.assert_valid()
def test_from_key() -> None: secp256r1 = CURVES["secp256r1"] m_c = bytes_from_point(Q, compressed=True), "mainnet" m_unc = bytes_from_point(Q, compressed=False), "mainnet" t_c = bytes_from_point(Q, compressed=True), "testnet" t_unc = bytes_from_point(Q, compressed=False), "testnet" for pub_key in [Q, *plain_pub_keys]: assert Q == point_from_pub_key(pub_key) with pytest.raises(BTClibValueError): point_from_pub_key(pub_key, secp256r1) assert m_c == pub_keyinfo_from_pub_key(pub_key) assert m_c == pub_keyinfo_from_pub_key(pub_key, "mainnet") assert m_c == pub_keyinfo_from_pub_key(pub_key, "mainnet", compressed=True) assert m_c == pub_keyinfo_from_pub_key(pub_key, compressed=True) assert m_unc == pub_keyinfo_from_pub_key(pub_key, "mainnet", compressed=False) assert m_unc == pub_keyinfo_from_pub_key(pub_key, compressed=False) assert t_c == pub_keyinfo_from_pub_key(pub_key, "testnet") assert t_c == pub_keyinfo_from_pub_key(pub_key, "testnet", compressed=True) assert t_unc == pub_keyinfo_from_pub_key(pub_key, "testnet", compressed=False) for prv_key2 in [xpub_data, *compressed_pub_keys]: assert Q == point_from_pub_key(prv_key2) with pytest.raises(BTClibValueError): point_from_pub_key(prv_key2, secp256r1) assert m_c == pub_keyinfo_from_pub_key(prv_key2) assert m_c == pub_keyinfo_from_pub_key(prv_key2, "mainnet") assert m_c == pub_keyinfo_from_pub_key(prv_key2, "mainnet", compressed=True) assert m_c == pub_keyinfo_from_pub_key(prv_key2, compressed=True) with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key2, "mainnet", compressed=False) with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key2, compressed=False) with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key2, "testnet", compressed=False) for prv_key3 in uncompressed_pub_keys: assert Q == point_from_pub_key(prv_key3) with pytest.raises(BTClibValueError): point_from_pub_key(prv_key3, secp256r1) assert m_unc == pub_keyinfo_from_pub_key(prv_key3) assert m_unc == pub_keyinfo_from_pub_key(prv_key3, "mainnet") with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key3, "mainnet", compressed=True) with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key3, compressed=True) assert m_unc == pub_keyinfo_from_pub_key(prv_key3, "mainnet", compressed=False) assert m_unc == pub_keyinfo_from_pub_key(prv_key3, compressed=False) with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key3, "testnet", compressed=True) for prv_key4 in [xpub_data, *net_aware_pub_keys]: assert Q == point_from_pub_key(prv_key4) with pytest.raises(BTClibValueError): point_from_pub_key(prv_key4, secp256r1) assert pub_keyinfo_from_pub_key(prv_key4) in (m_c, m_unc) assert pub_keyinfo_from_pub_key(prv_key4, "mainnet") in (m_c, m_unc) with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(prv_key4, "testnet") for prv_key5 in net_unaware_pub_keys: assert Q == point_from_pub_key(prv_key5) with pytest.raises(BTClibValueError): point_from_pub_key(prv_key5, secp256r1) assert pub_keyinfo_from_pub_key(prv_key5) in (m_c, m_unc) assert pub_keyinfo_from_pub_key(prv_key5, "mainnet") in (m_c, m_unc) assert pub_keyinfo_from_pub_key(prv_key5, "testnet") in (t_c, t_unc) for invalid_pub_key in [INF, INF_xpub_data, *invalid_pub_keys]: with pytest.raises(BTClibValueError): point_from_pub_key(invalid_pub_key) # type: ignore with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(invalid_pub_key) # type: ignore for not_a_pub_key in [ INF, INF_xpub_data, *not_a_pub_keys, q, q0, qn, *plain_prv_keys, xprv_data, xprv0_data, xprvn_data, *compressed_prv_keys, *uncompressed_prv_keys, ]: with pytest.raises(BTClibValueError): point_from_pub_key(not_a_pub_key) # type: ignore with pytest.raises(BTClibValueError): pub_keyinfo_from_pub_key(not_a_pub_key) # type: ignore for key in [Q, *plain_pub_keys, q, *plain_prv_keys]: assert Q == point_from_key(key) assert m_c == pub_keyinfo_from_key(key) assert m_c == pub_keyinfo_from_key(key, "mainnet") assert m_c == pub_keyinfo_from_key(key, "mainnet", compressed=True) assert m_c == pub_keyinfo_from_key(key, compressed=True) assert m_unc == pub_keyinfo_from_key(key, "mainnet", compressed=False) assert m_unc == pub_keyinfo_from_key(key, compressed=False) assert t_c == pub_keyinfo_from_key(key, "testnet") assert t_c == pub_keyinfo_from_key(key, "testnet", compressed=True) assert t_unc == pub_keyinfo_from_key(key, "testnet", compressed=False) for key2 in [ *compressed_pub_keys, xpub_data, xprv_data, *compressed_prv_keys ]: assert Q == point_from_key(key2) with pytest.raises(BTClibValueError): point_from_key(key2, secp256r1) assert m_c == pub_keyinfo_from_key(key2) assert m_c == pub_keyinfo_from_key(key2, "mainnet") assert m_c == pub_keyinfo_from_key(key2, "mainnet", compressed=True) assert m_c == pub_keyinfo_from_key(key2, compressed=True) with pytest.raises(BTClibValueError): pub_keyinfo_from_key(key2, "mainnet", compressed=False) with pytest.raises(BTClibValueError): pub_keyinfo_from_key(key2, compressed=False) for key3 in [*uncompressed_pub_keys, *uncompressed_prv_keys]: assert Q == point_from_key(key3) with pytest.raises(BTClibValueError): point_from_key(key3, secp256r1) assert m_unc == pub_keyinfo_from_key(key3) assert m_unc == pub_keyinfo_from_key(key3, "mainnet") with pytest.raises(BTClibValueError): pub_keyinfo_from_key(key3, "mainnet", compressed=True) with pytest.raises(BTClibValueError): pub_keyinfo_from_key(key3, compressed=True) assert m_unc == pub_keyinfo_from_key(key3, "mainnet", compressed=False) assert m_unc == pub_keyinfo_from_key(key3, compressed=False) for key4 in [ *net_aware_pub_keys, xpub_data, xprv_data, *net_aware_prv_keys ]: assert Q == point_from_key(key4) with pytest.raises(BTClibValueError): point_from_key(key4, secp256r1) assert pub_keyinfo_from_key(key4) in (m_c, m_unc) assert pub_keyinfo_from_key(key4, "mainnet") in (m_c, m_unc) with pytest.raises(BTClibValueError): pub_keyinfo_from_key(key4, "testnet") for key5 in [q, *net_unaware_prv_keys, *net_unaware_pub_keys]: assert Q == point_from_key(key5) assert pub_keyinfo_from_key(key5) in (m_c, m_unc) assert pub_keyinfo_from_key(key5, "mainnet") in (m_c, m_unc) assert pub_keyinfo_from_key(key5, "testnet") in (t_c, t_unc) for invalid_key in [ INF, INF_xpub_data, *invalid_pub_keys, q0, qn, xprv0_data, xprvn_data, *invalid_prv_keys, ]: with pytest.raises(BTClibValueError): point_from_key(invalid_key) # type: ignore with pytest.raises(BTClibValueError): pub_keyinfo_from_key(invalid_key) # type: ignore for not_a_key in [ q0, qn, xprv0_data, xprvn_data, INF, INF_xpub_data, *not_a_pub_keys, ]: with pytest.raises(BTClibValueError): point_from_key(not_a_key) # type: ignore with pytest.raises(BTClibValueError): pub_keyinfo_from_key(not_a_key) # type: ignore
def test_octets2point() -> None: for ec in all_curves.values(): G_bytes = bytes_from_point(ec.G, ec) G_point = point_from_octets(G_bytes, ec) assert ec.G == G_point G_bytes = bytes_from_point(ec.G, ec, False) G_point = point_from_octets(G_bytes, ec) assert ec.G == G_point # just a random point, not INF q = 1 + secrets.randbelow(ec.n - 1) Q = mult(q, ec.G, ec) Q_bytes = b"\x03" if Q[1] & 1 else b"\x02" Q_bytes += Q[0].to_bytes(ec.p_size, byteorder="big", signed=False) Q_point = point_from_octets(Q_bytes, ec) assert Q_point == Q assert bytes_from_point(Q_point, ec) == Q_bytes Q_hex_str = Q_bytes.hex() Q_point = point_from_octets(Q_hex_str, ec) assert Q_point == Q Q_bytes = b"\x04" + Q[0].to_bytes( ec.p_size, byteorder="big", signed=False) Q_bytes += Q[1].to_bytes(ec.p_size, byteorder="big", signed=False) Q_point = point_from_octets(Q_bytes, ec) assert Q_point == Q assert bytes_from_point(Q_point, ec, False) == Q_bytes Q_hex_str = Q_bytes.hex() Q_point = point_from_octets(Q_hex_str, ec) assert Q_point == Q Q_bytes = b"\x01" + b"\x01" * ec.p_size with pytest.raises(BTClibValueError, match="not a point: "): point_from_octets(Q_bytes, ec) Q_bytes = b"\x01" + b"\x01" * 2 * ec.p_size with pytest.raises(BTClibValueError, match="not a point: "): point_from_octets(Q_bytes, ec) Q_bytes = b"\x04" + b"\x01" * ec.p_size with pytest.raises(BTClibValueError, match="invalid size for uncompressed point: "): point_from_octets(Q_bytes, ec) Q_bytes = b"\x02" + b"\x01" * 2 * ec.p_size with pytest.raises(BTClibValueError, match="invalid size for compressed point: "): point_from_octets(Q_bytes, ec) Q_bytes = b"\x03" + b"\x01" * 2 * ec.p_size with pytest.raises(BTClibValueError, match="invalid size for compressed point: "): point_from_octets(Q_bytes, ec) # invalid x_Q coordinate ec = CURVES["secp256k1"] x_Q = 0xEEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34 xstr = format(x_Q, "32X") with pytest.raises(BTClibValueError, match="invalid x-coordinate: "): point_from_octets("03" + xstr, ec) with pytest.raises(BTClibValueError, match="point not on curve: "): point_from_octets("04" + 2 * xstr, ec) with pytest.raises(BTClibValueError, match="point not on curve"): bytes_from_point((x_Q, x_Q), ec) with pytest.raises(BTClibValueError, match="point not on curve"): bytes_from_point((x_Q, x_Q), ec, False)
def sign( msg: Octets, ks: Sequence[int], sign_key_idx: Sequence[int], sign_keys: Sequence[int], pubk_rings: PubkeyRing, ) -> Tuple[bytes, SValues]: """Borromean ring signature - signing algorithm https://github.com/ElementsProject/borromean-signatures-writeup https://github.com/Blockstream/borromean_paper/blob/master/borromean_draft_0.01_9ade1e49.pdf inputs: - msg: message to be signed (bytes) - sign_key_idx: list of indexes representing each signing key per ring - sign_keys: list containing the whole set of signing keys (one per ring) - pubk_rings: dictionary of sequences representing single rings of pub_keys """ msg = bytes_from_octets(msg) m = _get_msg_format(msg, pubk_rings) e0bytes = m s: SValues = defaultdict(list) e: SValues = defaultdict(list) # step 1 for i, (pubk_ring, j_star, k) in enumerate( zip(pubk_rings.values(), sign_key_idx, ks) ): keys_size = len(pubk_ring) s[i] = [0] * keys_size e[i] = [0] * keys_size start_idx = (j_star + 1) % keys_size r = bytes_from_point(mult(k), ec) if start_idx != 0: for j in range(start_idx, keys_size): s[i][j] = secrets.randbits(256) e[i][j] = int_from_bits(_hash(m, r, i, j), ec.nlen) % ec.n # edge case that cannot be reproduced in the test suite if not 0 < e[i][j] < ec.n: err_msg = "implausibile signature failure" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover t = double_mult(-e[i][j], pubk_ring[j], s[i][j], ec.G) r = bytes_from_point(t, ec) e0bytes += r e0 = hf(e0bytes).digest() # step 2 for i, (j_star, k) in enumerate(zip(sign_key_idx, ks)): e[i][0] = int_from_bits(_hash(m, e0, i, 0), ec.nlen) % ec.n # edge case that cannot be reproduced in the test suite if not 0 < e[i][0] < ec.n: err_msg = "implausibile signature failure" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover for j in range(1, j_star + 1): s[i][j - 1] = secrets.randbits(256) t = double_mult(-e[i][j - 1], pubk_rings[i][j - 1], s[i][j - 1], ec.G) r = bytes_from_point(t, ec) e[i][j] = int_from_bits(_hash(m, r, i, j), ec.nlen) % ec.n # edge case that cannot be reproduced in the test suite if not 0 < e[i][j] < ec.n: err_msg = "implausibile signature failure" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover s[i][j_star] = k + sign_keys[i] * e[i][j_star] return e0, s
# ==master ext private key== # depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ... depth = b"\x00" # This is ser32(i) for i in xi = xpar/i, with xi the key being serialized. (0x00000000 if master key) child_number = b"\x00\x00\x00\x00" # the fingerprint of the parent's public key (0x00000000 if master key) fingerprint = b"\x00\x00\x00\x00" idf = depth + fingerprint + child_number # master private key, master public key, chain code hd = hmac.digest(b"Bitcoin seed", seed.to_bytes(seed_bytes, "big"), "sha512") qbytes = hd[:32] p = int(qbytes.hex(), 16) % ec.n qbytes = b"\x00" + p.to_bytes(32, "big") Q = mult(p, ec.G) Qbytes = bytes_from_point(Q) chain_code = hd[32:] # extended keys ext_prv = b58encode(xprv + idf + chain_code + qbytes) print("\nm") print(ext_prv) ext_pub = b58encode(xpub + idf + chain_code + Qbytes) print("M") print(ext_pub) assert ( ext_prv == b"xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6" ), "failure" assert ( ext_pub