def point_from_key(key: Key, ec: Curve = secp256k1) -> Point: """Return a point tuple from any possible key representation. It supports: - BIP32 extended keys (bytes, string, or BIP32KeyData) - SEC Octets (bytes or hex-string, with 02, 03, or 04 prefix) - native tuple """ if isinstance(key, tuple): return point_from_pub_key(key, ec) if isinstance(key, int): q, _, _ = prv_keyinfo_from_prv_key(key) return mult(q, ec.G, ec) try: q, net, _ = prv_keyinfo_from_prv_key(key) except BTClibValueError: pass else: if ec != NETWORKS[net].curve: raise BTClibValueError("Curve mismatch") return mult(q, ec.G, ec) return point_from_pub_key(key, ec)
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_taproot_key_tweaking() -> None: prvkey = 123456 pubkey = mult(prvkey) script_trees = [ None, [(0xC0, ["OP_1"])], [[(0xC0, ["OP_2"])], [(0xC0, ["OP_3"])]], ] for script_tree in script_trees: tweaked_prvkey = output_prvkey(prvkey, script_tree) tweaked_pubkey = output_pubkey(pubkey, script_tree)[0] assert tweaked_pubkey == mult(tweaked_prvkey)[0].to_bytes(32, "big")
def test_assorted_mult() -> None: ec = ec23_31 H = second_generator(ec) for k1 in range(-ec.n + 1, ec.n): K1 = mult(k1, ec.G, ec) for k2 in range(ec.n): K2 = mult(k2, H, ec) shamir = double_mult(k1, ec.G, k2, ec.G, ec) assert shamir == mult(k1 + k2, ec.G, ec) shamir = double_mult(k1, INF, k2, H, ec) assert ec.is_on_curve(shamir) assert shamir == K2 shamir = double_mult(k1, ec.G, k2, INF, ec) assert ec.is_on_curve(shamir) assert shamir == K1 shamir = double_mult(k1, ec.G, k2, H, ec) assert ec.is_on_curve(shamir) K1K2 = ec.add(K1, K2) assert K1K2 == shamir k3 = 1 + secrets.randbelow(ec.n - 1) K3 = mult(k3, ec.G, ec) K1K2K3 = ec.add(K1K2, K3) assert ec.is_on_curve(K1K2K3) boscoster = multi_mult([k1, k2, k3], [ec.G, H, ec.G], ec) assert ec.is_on_curve(boscoster) assert K1K2K3 == boscoster, k3 k4 = 1 + secrets.randbelow(ec.n - 1) K4 = mult(k4, H, ec) K1K2K3K4 = ec.add(K1K2K3, K4) assert ec.is_on_curve(K1K2K3K4) points = [ec.G, H, ec.G, H] boscoster = multi_mult([k1, k2, k3, k4], points, ec) assert ec.is_on_curve(boscoster) assert K1K2K3K4 == boscoster, k4 assert K1K2K3 == multi_mult([k1, k2, k3, 0], points, ec) assert K1K2 == multi_mult([k1, k2, 0, 0], points, ec) assert K1 == multi_mult([k1, 0, 0, 0], points, ec) assert INF == multi_mult([0, 0, 0, 0], points, ec) err_msg = "mismatch between number of scalars and points: " with pytest.raises(BTClibValueError, match=err_msg): multi_mult([k1, k2, k3, k4], [ec.G, H, ec.G], ec)
def pubkey_bytes_from_prvkey(prvkey, compressed=True): PubKey = mult(prvkey) if compressed: prefix = b"\x02" if (PubKey[1] % 2 == 0) else b"\x03" return prefix + PubKey[0].to_bytes(32, byteorder="big") return (b"\x04" + PubKey[0].to_bytes(32, byteorder="big") + PubKey[1].to_bytes(32, byteorder="big"))
def test_negate() -> None: for ec in all_curves.values(): # just a random point, not INF q = 1 + secrets.randbelow(ec.n - 1) Q = mult(q, ec.G, ec) minus_Q = ec.negate(Q) assert ec.add(Q, minus_Q) == INF # Jacobian coordinates QJ = jac_from_aff(Q) minus_QJ = ec.negate_jac(QJ) assert ec.jac_equality(ec.add_jac(QJ, minus_QJ), INFJ) # negate of INF is INF minus_INF = ec.negate(INF) assert minus_INF == INF # negate of INFJ is INFJ minus_INFJ = ec.negate_jac(INFJ) assert ec.jac_equality(minus_INFJ, INFJ) with pytest.raises(BTClibTypeError, match="not a point"): ec.negate(ec.GJ) # type: ignore with pytest.raises(BTClibTypeError, match="not a Jacobian point"): ec.negate_jac(ec.G) # type: ignore
def check_output_pubkey(q: Octets, script: Octets, control: Octets, ec: Curve = secp256k1) -> bool: q = bytes_from_octets(q) script = bytes_from_octets(script) control = bytes_from_octets(control) if len(control) > 4129: # 33 + 32 * 128 raise BTClibValueError("Control block too long") m = (len(control) - 33) // 32 if len(control) != 33 + 32 * m: raise BTClibValueError("Invalid control block length") leaf_version = control[0] & 0xFE preimage = leaf_version.to_bytes(1, "big") + var_bytes.serialize(script) k = tagged_hash(b"TapLeaf", preimage) for j in range(m): e = control[33 + 32 * j:65 + 32 * j] if k < e: k = tagged_hash(b"TapBranch", k + e) else: k = tagged_hash(b"TapBranch", e + k) p_bytes = control[1:33] t_bytes = tagged_hash(b"TapTweak", p_bytes + k) p = int.from_bytes(p_bytes, "big") t = int.from_bytes(t_bytes, "big") # edge case that cannot be reproduced in the test suite if t >= ec.n: raise BTClibValueError("Invalid script tree hash") # pragma: no cover P = (p, secp256k1.y_even(p)) Q = secp256k1.add(P, mult(t)) return Q[0] == int.from_bytes(q, "big") and control[0] & 1 == Q[1] % 2
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 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 test_ecdh() -> None: ec = CURVES["secp256k1"] hf = sha256 a, A = dsa.gen_keys() # Alice b, B = dsa.gen_keys() # Bob # Alice computes the shared secret using Bob's public key shared_secret_a = mult(a, B) # Bob computes the shared secret using Alice's public key shared_secret_b = mult(b, A) assert shared_secret_a == shared_secret_b assert shared_secret_a == mult(a * b, ec.G) # hash the shared secret to remove weak bits shared_secret_field_element = shared_secret_a[0] z = shared_secret_field_element.to_bytes(ec.p_size, byteorder="big", signed=False) shared_info = b"deadbeef" hf_size = hf().digest_size for size in (hf_size - 1, hf_size, hf_size + 1): shared_key = ansi_x9_63_kdf(z, size, hf, None) assert len(shared_key) == size assert shared_key == diffie_hellman(a, B, size, None, ec, hf) assert shared_key == diffie_hellman(b, A, size, None, ec, hf) shared_key = ansi_x9_63_kdf(z, size, hf, shared_info) assert len(shared_key) == size assert shared_key == diffie_hellman(a, B, size, shared_info, ec, hf) assert shared_key == diffie_hellman(b, A, size, shared_info, ec, hf) max_size = hf_size * (2**32 - 1) size = max_size + 1 with pytest.raises(BTClibValueError, match="cannot derive a key larger than "): ansi_x9_63_kdf(z, size, hf, None)
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 test_control_block() -> None: script_tree = [[(0xC0, ["OP_2"])], [(0xC0, ["OP_3"])]] pubkey = output_pubkey(None, script_tree)[0] script, control = input_script_sig(None, script_tree, 0) assert check_output_pubkey(pubkey, serialize(script), control) prvkey = 123456 internal_pubkey = mult(prvkey) script_tree = [[(0xC0, ["OP_2"])], [(0xC0, ["OP_3"])]] pubkey = output_pubkey(internal_pubkey, script_tree)[0] script, control = input_script_sig(internal_pubkey, script_tree, 0) assert check_output_pubkey(pubkey, serialize(script), control)
def ssa_verify_commit_( commit_hash: Octets, R: Point, msg_hash: Octets, pub_key: ssa.BIP340PubKey, sig: ssa.Sig, hf: HashF = sha256, ) -> bool: "Open the commitment associated to an EC SSA signature." tweak = _tweak(commit_hash, R, sig.ec, hf) W = sig.ec.add(R, mult(tweak, sig.ec.G, sig.ec)) # sig.r is in [1..p-1] return (sig.r == W[0]) and ssa.verify_(msg_hash, pub_key, sig, hf)
def test_is_on_curve() -> None: for ec in all_curves.values(): with pytest.raises(BTClibValueError, match="point must be a tuple"): ec.is_on_curve("not a point") # type: ignore with pytest.raises(BTClibValueError, match="x-coordinate not in 0..p-1: "): ec.y(ec.p) # just a random point, not INF q = 1 + secrets.randbelow(ec.n - 1) Q = mult(q, ec.G, ec) with pytest.raises(BTClibValueError, match="y-coordinate not in 1..p-1: "): ec.is_on_curve((Q[0], ec.p))
def dsa_verify_commit_( commit_hash: Octets, R: Point, msg_hash: Octets, key: dsa.Key, sig: dsa.Sig, lower_s: bool = True, hf: HashF = sha256, ) -> bool: "Open the commitment associated to an EC DSA signature." tweak = _tweak(commit_hash, R, sig.ec, hf) W = sig.ec.add(R, mult(tweak, sig.ec.G, sig.ec)) # sig.r is in [1..n-1] return (sig.r == W[0] % sig.ec.n) and dsa.verify_(msg_hash, key, sig, lower_s, hf)
def test_pub_key_recovery() -> None: ec = CURVES["secp112r2"] q = 0x10 Q = mult(q, ec.G, ec) msg = "Satoshi Nakamoto".encode() sig = dsa.sign(msg, q, ec=ec) dsa.assert_as_valid(msg, Q, sig) assert dsa.verify(msg, Q, sig) keys = dsa.recover_pub_keys(msg, sig) assert len(keys) == 4 assert Q in keys for Q in keys: assert dsa.verify(msg, Q, sig)
def ssa_commit_sign_( commit_hash: Octets, msg_hash: Octets, prv_key: PrvKey, nonce: Optional[PrvKey] = None, ec: Curve = secp256k1, hf: HashF = sha256, ) -> Tuple[ssa.Sig, Point]: "Include a commitment inside an EC SSA signature." nonce = (ssa.det_nonce_(msg_hash, prv_key, aux=None, ec=ec, hf=hf) if nonce is None else int_from_prv_key(nonce, ec)) R = mult(nonce, ec.G, ec) tweaked_nonce = (nonce + _tweak(commit_hash, R, ec, hf)) % ec.n tweaked_sig = ssa.sign_(msg_hash, prv_key, tweaked_nonce, ec, hf) return tweaked_sig, R
def output_prvkey( internal_prvkey: PrvKey, script_tree: Optional[TaprootScriptTree] = None, ec: Curve = secp256k1, ) -> int: internal_prvkey = int_from_prv_key(internal_prvkey) P = mult(internal_prvkey) if script_tree: _, h = tree_helper(script_tree) else: h = b"" has_even_y = ec.y_even(P[0]) == P[1] internal_prvkey = internal_prvkey if has_even_y else ec.n - internal_prvkey t = int.from_bytes(tagged_hash(b"TapTweak", P[0].to_bytes(32, "big") + h), "big") # edge case that cannot be reproduced in the test suite if t >= ec.n: raise BTClibValueError("Invalid script tree hash") # pragma: no cover return (internal_prvkey + t) % ec.n
def test_aff_jac_conversions() -> None: for ec in all_curves.values(): # just a random point, not INF q = 1 + secrets.randbelow(ec.n - 1) Q = mult(q, ec.G, ec) QJ = jac_from_aff(Q) assert Q == ec.aff_from_jac(QJ) x_Q = ec.x_aff_from_jac(QJ) assert Q[0] == x_Q y_Q = ec.y_aff_from_jac(QJ) assert Q[1] == y_Q assert INF == ec.aff_from_jac(jac_from_aff(INF)) with pytest.raises(BTClibValueError, match="INF has no x-coordinate"): ec.x_aff_from_jac(INFJ) with pytest.raises(BTClibValueError, match="INF has no y-coordinate"): ec.y_aff_from_jac(INFJ)
def test_add_double_aff_jac() -> None: "Test consistency between affine and Jacobian add/double methods." for ec in all_curves.values(): # just a random point, not INF q = 1 + secrets.randbelow(ec.n - 1) Q = mult(q, ec.G, ec) QJ = jac_from_aff(Q) # add Q and G R = ec.add_aff(Q, ec.G) RJ = ec.add_jac(QJ, ec.GJ) assert R == ec.aff_from_jac(RJ) # double Q R = ec.double_aff(Q) RJ = ec.double_jac(QJ) assert R == ec.aff_from_jac(RJ) assert R == ec.add_aff(Q, Q) assert ec.jac_equality(RJ, ec.add_jac(QJ, QJ))
def output_pubkey( internal_pubkey: Optional[Key] = None, script_tree: Optional[TaprootScriptTree] = None, ec: Curve = secp256k1, ) -> Tuple[bytes, int]: if not internal_pubkey and not script_tree: raise BTClibValueError("Missing data") if internal_pubkey: pubkey = pub_keyinfo_from_key(internal_pubkey, compressed=True)[0][1:] else: h_str = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" pubkey = bytes.fromhex(h_str) if script_tree: _, h = tree_helper(script_tree) else: h = tagged_hash(b"TapTweak", pubkey) t = int.from_bytes(tagged_hash(b"TapTweak", pubkey + h), "big") # edge case that cannot be reproduced in the test suite if t >= ec.n: raise BTClibValueError("Invalid script tree hash") # pragma: no cover x = int.from_bytes(pubkey, "big") Q = ec.add((x, ec.y_even(x)), mult(t)) return Q[0].to_bytes(32, "big"), Q[1] % 2
def diffie_hellman( dU: int, QV: Point, size: int, shared_info: Optional[bytes] = None, ec: Curve = secp256k1, hf: HashF = sha256, ) -> bytes: """Diffie-Hellman elliptic curve key agreement scheme. http://www.secg.org/sec1-v2.pdf, section 6.1 """ shared_secret_point = mult(dU, QV, ec) # edge case that cannot be reproduced in the test suite if shared_secret_point[1] == 0: err_msg = "invalid (INF) key" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover shared_secret_field_element = shared_secret_point[0] z = shared_secret_field_element.to_bytes(ec.p_size, byteorder="big", signed=False) return ansi_x9_63_kdf(z, size, hf, shared_info)
def test_rfc6979_tv() -> None: fname = "rfc6979.json" filename = path.join(path.dirname(__file__), "_data", fname) with open(filename, "r") as file_: test_dict = json.load(file_) lower_s = False for ec_name in test_dict: ec = CURVES[ec_name] test_vectors = test_dict[ec_name] for x, x_U, y_U, hf, msg, k, r, s in test_vectors: x = int(x, 16) msg = msg.encode() m = reduce_to_hlen(msg, hf=getattr(hashlib, hf)) # test RFC6979 implementation k2 = rfc6979_(m, x, ec, getattr(hashlib, hf)) assert int(k, 16) == k2 # test RFC6979 usage in DSA sig = dsa.sign_(m, x, k2, lower_s, ec=ec, hf=getattr(hashlib, hf)) assert int(r, 16) == sig.r assert int(s, 16) == sig.s # test that RFC6979 is the default nonce for DSA sig = dsa.sign_(m, x, None, lower_s, ec=ec, hf=getattr(hashlib, hf)) assert int(r, 16) == sig.r assert int(s, 16) == sig.s # test key-pair coherence U = mult(x, ec.G, ec) assert int(x_U, 16), int(y_U, 16) == U # test signature validity dsa.assert_as_valid(msg, U, sig, lower_s, getattr(hashlib, hf))
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 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 test_double_mult() -> None: H = second_generator(secp256k1) G = secp256k1.G # 0*G + 1*H T = double_mult(1, H, 0, G) assert T == H T = multi_mult([1, 0], [H, G]) assert T == H # 0*G + 2*H exp = mult(2, H) T = double_mult(2, H, 0, G) assert T == exp T = multi_mult([2, 0], [H, G]) assert T == exp # 0*G + 3*H exp = mult(3, H) T = double_mult(3, H, 0, G) assert T == exp T = multi_mult([3, 0], [H, G]) assert T == exp # 1*G + 0*H T = double_mult(0, H, 1, G) assert T == G T = multi_mult([0, 1], [H, G]) assert T == G # 2*G + 0*H exp = mult(2, G) T = double_mult(0, H, 2, G) assert T == exp T = multi_mult([0, 2], [H, G]) assert T == exp # 3*G + 0*H exp = mult(3, G) T = double_mult(0, H, 3, G) assert T == exp T = multi_mult([0, 3], [H, G]) assert T == exp # 0*G + 5*H exp = mult(5, H) T = double_mult(5, H, 0, G) assert T == exp T = multi_mult([5, 0], [H, G]) assert T == exp # 0*G - 5*H exp = mult(-5, H) T = double_mult(-5, H, 0, G) assert T == exp T = multi_mult([-5, 0], [H, G]) assert T == exp # 1*G - 5*H exp = secp256k1.add(G, T) T = double_mult(-5, H, 1, G) assert T == exp
def test_symmetry() -> None: "Methods to break simmetry: quadratic residue, even/odd, low/high." for ec in low_card_curves.values(): # just a random point, not INF q = 1 + secrets.randbelow(ec.n - 1) Q = mult(q, ec.G, ec) x_Q = Q[0] assert not ec.y_even(x_Q) % 2 assert ec.y_low(x_Q) <= ec.p // 2 # compute quadratic residues hasRoot = {1} for i in range(2, ec.p): hasRoot.add(i * i % ec.p) if ec.p % 4 == 3: quad_res = ec.y_quadratic_residue(x_Q) # in this case only quad_res is a quadratic residue assert quad_res in hasRoot root = mod_sqrt(quad_res, ec.p) assert quad_res == (root * root) % ec.p root = ec.p - root assert quad_res == (root * root) % ec.p assert ec.p - quad_res not in hasRoot with pytest.raises(BTClibValueError, match="no root for "): mod_sqrt(ec.p - quad_res, ec.p) else: assert ec.p % 4 == 1 # cannot use y_quadratic_residue in this case err_msg = "field prime is not equal to 3 mod 4: " with pytest.raises(BTClibValueError, match=err_msg): ec.y_quadratic_residue(x_Q) y_even = ec.y_even(x_Q) y_odd = ec.p - y_even # in this case neither or both y_Q are quadratic residues neither = y_odd not in hasRoot and y_even not in hasRoot both = y_odd in hasRoot and y_even in hasRoot assert neither or both if y_odd in hasRoot: # both have roots root = mod_sqrt(y_odd, ec.p) assert y_odd == (root * root) % ec.p root = ec.p - root assert y_odd == (root * root) % ec.p root = mod_sqrt(y_even, ec.p) assert y_even == (root * root) % ec.p root = ec.p - root assert y_even == (root * root) % ec.p else: err_msg = "no root for " with pytest.raises(BTClibValueError, match=err_msg): mod_sqrt(y_odd, ec.p) with pytest.raises(BTClibValueError, match=err_msg): mod_sqrt(y_even, ec.p) with pytest.raises(BTClibValueError): secp256k1.y_even(INF[0]) with pytest.raises(BTClibValueError): secp256k1.y_low(INF[0]) with pytest.raises(BTClibValueError): secp256k1.y_quadratic_residue(INF[0])
# or distributed except according to the terms contained in the LICENSE file. """ Deterministic Key Sequence (Type-1)""" import secrets from hashlib import sha256 as hf from btclib.ecc.curve import mult from btclib.ecc.curve import secp256k1 as ec from btclib.utils import int_from_bits # master prvkey in [1, n-1] mprvkey = 1 + secrets.randbelow(ec.n - 1) print(f"\nmaster prvkey: {hex(mprvkey).upper()}") # Master Pubkey: mpubkey = mult(mprvkey, ec.G) print(f"Master Pubkey: {hex(mpubkey[0]).upper()}") print(f" {hex(mpubkey[1]).upper()}") r = secrets.randbits(ec.nlen) print(f"\npublic random number: {hex(r).upper()}") rbytes = r.to_bytes(ec.nsize, "big") nKeys = 3 for i in range(nKeys): ibytes = i.to_bytes(ec.nsize, "big") hd = hf(ibytes + rbytes).digest() offset = int_from_bits(hd, ec.nlen) % ec.n q = (mprvkey + offset) % ec.n Q = mult(q, ec.G, ec) print(f"\nprvkey #{i}: {hex(q).upper()}")
# # No part of btclib including this file, may be copied, modified, propagated, # or distributed except according to the terms contained in the LICENSE file. from btclib.ecc.curve import mult from btclib.ecc.curve import secp256k1 as ec from btclib.ecc.dsa import recover_pub_keys, sign, verify from btclib.ecc.der import Sig print("\n*** EC:") print(ec) print("0. Key generation") q = 0x18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725 q %= ec.n Q = mult(q, ec.G) print(f"prvkey: {hex(q).upper()}") print(f"PubKey: {'02' if Q[1] % 2 == 0 else '03'} {hex(Q[0]).upper()}") print("\n1. Message to be signed") msg1 = "Paolo is afraid of ephemeral random numbers".encode() print(msg1.decode()) print("2. Sign message") sig1 = sign(msg1, q) print(f" r1: {hex(sig1.r).upper()}") print(f" s1: {hex(sig1.s).upper()}") print("3. Verify signature") print(verify(msg1, Q, sig1))
from hashlib import sha256 as hf from btclib import dsa from btclib.ecc.curve import mult from btclib.ecc.curve import secp256k1 as ec from btclib.dh import ansi_x9_63_kdf # Diffie-Hellman print("\n Diffie-Hellman") a, A = dsa.gen_keys() # Alice b, B = dsa.gen_keys() # Bob # Alice calculates the shared secret using Bob's public key shared_secret_a = mult(a, B) # Bob calculates the shared secret using Alice's public key shared_secret_b = mult(b, A) print("same shared secret:", shared_secret_a == shared_secret_b) print("as expected:", shared_secret_a == mult(a * b, ec.G)) # hash the shared secret to remove weak bits shared_secret_field_element = shared_secret_a[0] z = shared_secret_field_element.to_bytes(ec.psize, "big") shared_info = None shared_key = ansi_x9_63_kdf(z, 32, hf, shared_info) print("shared key:", shared_key.hex())