def _ecdsa_pubkey_recovery(ec: EC, e: int, sig: ECDS) -> List[Point]: """Private function provided for testing purposes only.""" # ECDSA public key recovery operation according to SEC 1 # http://www.secg.org/sec1-v2.pdf # See SEC 1 v.2 section 4.1.6 r, s = to_dsasig(ec, sig) # precomputations r1 = mod_inv(r, ec.n) r1s = r1 * s r1e = -r1 * e keys = [] for j in range(ec.h): # 1 x = r + j * ec.n # 1.1 try: R = (x % ec._p, ec.yOdd(x, 1)) # 1.2, 1.3, and 1.4 # 1.5 already taken care outside this for loop Q = DblScalarMult(ec, r1s, R, r1e, ec.G) # 1.6.1 if Q[1] != 0 and _ecdsa_verhlp(ec, e, Q, sig): # 1.6.2 keys.append(Q) R = ec.opposite(R) # 1.6.3 Q = DblScalarMult(ec, r1s, R, r1e, ec.G) if Q[1] != 0 and _ecdsa_verhlp(ec, e, Q, sig): # 1.6.2 keys.append(Q) # 1.6.2 except Exception: # R is not a curve point pass return keys
def borromean_sign( msg: bytes, k: List[int], sign_key_idx: List[int], sign_keys: List[int], pubk_rings: Dict[int, List[Point]]) -> Tuple[bytes, Dict[int, List[int]]]: """ Borromean ring signature - signing algorithm inputs: - msg: msg 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 lists where internal lists represent single rings of pubkeys """ s: Dict[int, List[int]] = defaultdict(list) e: Dict[int, List[int]] = defaultdict(list) m = get_msg_format(msg, pubk_rings) e0bytes = m ring_size = len(pubk_rings) # step 1 for i in range(ring_size): keys_size = len(pubk_rings[i]) s[i] = [0] * keys_size e[i] = [0] * keys_size j_star = sign_key_idx[i] start_idx = (j_star + 1) % keys_size R = point2octets(ec, pointMult(ec, k[i], ec.G), True) if start_idx != 0: for j in range(start_idx, keys_size): s[i][j] = random.getrandbits(256) temp = borromean_hash(m, R, i, j) e[i][j] = bits2int(ec, temp) assert e[i][j] != 0 and e[i][j] < ec.n, "sign fail" T = DblScalarMult(ec, s[i][j], ec.G, -e[i][j], pubk_rings[i][j]) R = point2octets(ec, T, True) e0bytes += R e0 = sha256(e0bytes).digest() # step 2 for i in range(ring_size): temp = borromean_hash(m, e0, i, 0) e[i][0] = bits2int(ec, temp) assert e[i][0] != 0 and e[i][0] < ec.n, "sign fail" j_star = sign_key_idx[i] for j in range(1, j_star + 1): s[i][j - 1] = random.getrandbits(256) T = DblScalarMult(ec, s[i][j - 1], ec.G, -e[i][j - 1], pubk_rings[i][j - 1]) R = point2octets(ec, T, True) e[i][j] = bits2int(ec, borromean_hash(m, R, i, j)) assert e[i][j] != 0 and e[i][j] < ec.n, "sign fail" s[i][j_star] = k[i] + sign_keys[i] * e[i][j_star] return e0, s
def _ecssa_pubkey_recovery(ec: EC, hf, e: int, sig: ECSS) -> Point: """Private function provided for testing purposes only.""" r, s = to_ssasig(ec, sig) # could be obtained from to_ssasig... K = r, ec.yQuadraticResidue(r, True) if e == 0: raise ValueError("invalid (zero) challenge e") e1 = mod_inv(e, ec.n) P = DblScalarMult(ec, e1*s, ec.G, -e1, K) assert P[1] != 0, "how did you do that?!?" return P
def _ecssa_verify(ec: EC, hf, m: bytes, P: Point, sig: ECSS) -> bool: """Private function provided for testing purposes only. It raises Errors, while verify should always return True or False https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki """ # the bitcoin proposed standard is only valid for curves # whose prime p = 3 % 4 if not ec.pIsThreeModFour: errmsg = 'curve prime p must be equal to 3 (mod 4)' raise ValueError(errmsg) # Let r = int(sig[ 0:32]); fail if r is not [0, p-1]. # Let s = int(sig[32:64]); fail if s is not [0, n-1]. r, s = to_ssasig(ec, sig) # The message m: a 32-byte array if len(m) != hf().digest_size: errmsg = f'message of wrong size: {len(m)}' errmsg += f' instead of {hf().digest_size}' raise ValueError(errmsg) # Let P = point(pk); fail if point(pk) fails. ec.requireOnCurve(P) if P[1] == 0: raise ValueError("public key is infinite") # Let e = int(hf(bytes(r) || bytes(P) || m)) mod n. e = _ecssa_e(ec, hf, r, P, m) # Let R = sG - eP. R = DblScalarMult(ec, s, ec.G, -e, P) # Fail if infinite(R). if R[1] == 0: raise ValueError("sG - eP is infinite") # Fail if jacobi(y(R)) ≠ 1. if legendre_symbol(R[1], ec._p) != 1: raise ValueError("y(sG - eP) is not a quadratic residue") # Fail if x(R) ≠ r. return R[0] == r
def _ecdsa_verhlp(ec: EC, e: int, P: Point, sig: ECDS) -> bool: """Private function provided for testing purposes only.""" # Fail if r is not [1, n-1] # Fail if s is not [1, n-1] r, s = to_dsasig(ec, sig) # 1 # Let P = point(pk); fail if point(pk) fails. ec.requireOnCurve(P) if P[1] == 0: raise ValueError("public key is infinite") s1 = mod_inv(s, ec.n) u = e * s1 v = r * s1 # 4 # Let R = u*G + v*P. R = DblScalarMult(ec, u, ec.G, v, P) # 5 # Fail if infinite(R). assert R[1] != 0, "how did you do that?!?" # 5 v = R[0] % ec.n # 6, 7 # Fail if r ≠ x(R) %n. return r == v # 8
def _borromean_verify(msg: bytes, e0: bytes, s: Dict[int, List[int]], pubk_rings: Dict[int, List[Point]]) -> bool: ring_size = len(pubk_rings) m = get_msg_format(msg, pubk_rings) e: Dict[int, List[int]] = defaultdict(list) e0bytes = m for i in range(ring_size): keys_size = len(pubk_rings[i]) e[i] = [0] * keys_size e[i][0] = bits2int(ec, borromean_hash(m, e0, i, 0)) assert e[i][0] != 0, "how did you do that?!?" R = b'\0x00' for j in range(keys_size): T = DblScalarMult(ec, s[i][j], ec.G, -e[i][j], pubk_rings[i][j]) R = point2octets(ec, T, True) if j != len(pubk_rings[i]) - 1: e[i][j + 1] = bits2int(ec, borromean_hash(m, R, i, j + 1)) assert e[i][j + 1] != 0, "how did you do that?!?" else: e0bytes += R e0_prime = sha256(e0bytes).digest() return e0_prime == e0
def pedersen_commit(r: int, v: int, ec: EC, hf) -> Point: """Return rG + vH, with H being second (NUMS) generator of the curve""" H = secondGenerator(ec, hf) Q = DblScalarMult(ec, r, ec.G, v, H) assert Q[1] != 0, "how did you do that?!?" return Q