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 _sign_(c: int, q: int, nonce: int, lower_s: bool, ec: Curve) -> Sig: # Private function for testing purposes: it allows to explore all # possible value of the challenge c (for low-cardinality curves). # It assume that c is in [0, n-1], while q and nonce are in [1, n-1] # Steps numbering follows SEC 1 v.2 section 4.1.3 KJ = _mult(nonce, ec.GJ, ec) # 1 # affine x_K-coordinate of K (field element) x_K = (KJ[0] * mod_inv(KJ[2] * KJ[2], ec.p)) % ec.p # mod n makes it a scalar r = x_K % ec.n # 2, 3 if r == 0: # r≠0 required as it multiplies the public key raise BTClibRuntimeError("failed to sign: r = 0") s = mod_inv(nonce, ec.n) * (c + r * q) % ec.n # 6 if s == 0: # s≠0 required as verify will need the inverse of s raise BTClibRuntimeError("failed to sign: s = 0") # bitcoin canonical 'low-s' encoding for ECDSA signatures # it removes signature malleability as cause of transaction malleability # see https://github.com/bitcoin/bitcoin/pull/6769 if lower_s and s > ec.n / 2: s = ec.n - s # s = - s % ec.n return Sig(r, s, ec)
def parse(stream: BinaryData, forbid_zero_size: bool = False) -> bytes: """Return the variable-length octets read from a stream.""" stream = bytesio_from_binarydata(stream) i = var_int.parse(stream) if forbid_zero_size and i == 0: raise BTClibRuntimeError("zero size") result = stream.read(i) if len(result) != i: raise BTClibRuntimeError("not enough binary data") return result
def _recover_pub_key_(c: int, r: int, s: int, ec: Curve) -> int: # Private function provided for testing purposes only. if c == 0: raise BTClibRuntimeError("invalid zero challenge") KJ = r, ec.y_even(r), 1 e1 = mod_inv(c, ec.n) QJ = _double_mult(ec.n - e1, KJ, e1 * s, ec.GJ, ec) # edge case that cannot be reproduced in the test suite if QJ[2] == 0: err_msg = "invalid (INF) key" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover return ec.x_aff_from_jac(QJ)
def _assert_as_valid_(c: int, QJ: JacPoint, r: int, s: int, ec: Curve) -> None: # Private function for test/dev purposes # It raises Errors, while verify should always return True or False # Let K = sG - eQ. # in Jacobian coordinates KJ = _double_mult(ec.n - c, QJ, s, ec.GJ, ec) # Fail if infinite(KJ). # Fail if y_K is odd. if ec.y_aff_from_jac(KJ) % 2: raise BTClibRuntimeError("y_K is odd") # Fail if x_K ≠ r if KJ[0] != KJ[2] * KJ[2] * r % ec.p: raise BTClibRuntimeError("signature verification failed")
def _sign_(c: int, q: int, nonce: int, r: int, ec: Curve) -> Sig: # Private function for testing purposes: it allows to explore all # possible value of the challenge c (for low-cardinality curves). # It assume that c is in [1, n-1], while q and nonce are in [1, n-1] if c == 0: # c≠0 required as it multiplies the private key raise BTClibRuntimeError("invalid zero challenge") # s=0 is ok: in verification there is no inverse of s s = (nonce + c * q) % ec.n return Sig(r, s, ec)
def commit(r: int, v: int, ec: Curve = secp256k1, hf: HashF = sha256) -> Point: """Commit to r, returning rG+vH. Commit to r, returning rG+vH. H is the second Nothing-Up-My-Sleeve (NUMS) generator of the curve. """ H = second_generator(ec, hf) Q = double_mult(v, H, r, ec.G, ec) # edge case that cannot be reproduced in the test suite if Q[1] == 0: err_msg = "invalid (INF) key" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover return Q
def _assert_as_valid_(c: int, QJ: JacPoint, r: int, s: int, lower_s: bool, ec: Curve) -> None: # Private function for test/dev purposes if lower_s and s > ec.n / 2: raise BTClibValueError("not a low s") w = mod_inv(s, ec.n) u = c * w % ec.n v = r * w % ec.n # 4 # Let K = u*G + v*Q. KJ = _double_mult(v, QJ, u, ec.GJ, ec) # 5 # Fail if infinite(K). # edge case that cannot be reproduced in the test suite if KJ[2] == 0: # 5 err_msg = "invalid (INF) key" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover # affine x_K-coordinate of K x_K = (KJ[0] * mod_inv(KJ[2] * KJ[2], ec.p)) % ec.p # Fail if r ≠ x_K %n. if r != x_K % ec.n: # 6, 7, 8 raise BTClibRuntimeError("signature verification failed")
def challenge_(msg_hash: Octets, x_Q: int, x_K: int, ec: Curve, hf: HashF) -> int: # the message msg_hash: a hf_len array hf_len = hf().digest_size msg_hash = bytes_from_octets(msg_hash, hf_len) t = b"".join([ x_K.to_bytes(ec.p_size, byteorder="big", signed=False), x_Q.to_bytes(ec.p_size, byteorder="big", signed=False), msg_hash, ]) t = tagged_hash("BIP0340/challenge".encode(), t, hf) c = int_from_bits(t, ec.nlen) % ec.n if c == 0: raise BTClibRuntimeError("invalid zero challenge") # pragma: no cover return c
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 assert_batch_as_valid_( m_hashes: Sequence[Octets], Qs: Sequence[BIP340PubKey], sigs: Sequence[Sig], hf: HashF = sha256, ) -> None: batch_size = len(Qs) if batch_size == 0: raise BTClibValueError("no signatures provided") if len(m_hashes) != batch_size: err_msg = f"mismatch between number of pub_keys ({batch_size}) " err_msg += f"and number of messages ({len(m_hashes)})" raise BTClibValueError(err_msg) if len(sigs) != batch_size: err_msg = f"mismatch between number of pub_keys ({batch_size}) " err_msg += f"and number of signatures ({len(sigs)})" raise BTClibValueError(err_msg) if batch_size == 1: assert_as_valid_(m_hashes[0], Qs[0], sigs[0], hf) return None ec = sigs[0].ec if any(sig.ec != ec for sig in sigs): raise BTClibValueError("not the same curve for all signatures") t = 0 scalars: List[int] = [] points: List[JacPoint] = [] for i, (msg_hash, Q, sig) in enumerate(zip(m_hashes, Qs, sigs)): msg_hash = bytes_from_octets(msg_hash, hf().digest_size) KJ = sig.r, ec.y_even(sig.r), 1 x_Q, y_Q = point_from_bip340pub_key(Q, ec) QJ = x_Q, y_Q, 1 c = challenge_(msg_hash, x_Q, sig.r, ec, hf) # rand in [1, n-1] # deterministically generated using a CSPRNG seeded by a # cryptographic hash (e.g., SHA256) of all inputs of the # algorithm, or randomly generated independently for each # run of the batch verification algorithm rand = 1 if i == 0 else 1 + secrets.randbelow(ec.n - 1) scalars.append(rand) points.append(KJ) scalars.append(rand * c % ec.n) points.append(QJ) t += rand * sig.s TJ = _mult(t, ec.GJ, ec) RHSJ = _multi_mult(scalars, points, ec) # return T == RHS, checked in Jacobian coordinates RHSZ2 = RHSJ[2] * RHSJ[2] TZ2 = TJ[2] * TJ[2] if (TJ[0] * RHSZ2 % ec.p != RHSJ[0] * TZ2 % ec.p) or ( TJ[1] * RHSZ2 * RHSJ[2] % ec.p != RHSJ[1] * TZ2 * TJ[2] % ec.p): raise BTClibRuntimeError("signature verification failed") return None
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