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 ecdsa_sign(ec: EC, hf, M: bytes, d: int, k: Optional[int] = None) -> Tuple[int, int]: """ECDSA signing operation according to SEC 1 http://www.secg.org/sec1-v2.pdf Steps numbering follows SEC 1 v.2 section 4.1.3 """ # https://tools.ietf.org/html/rfc6979#section-3.2 # The message M is first processed by hf, yielding the value hd(m), # a sequence of bits of length hlen. Normally, hf is chosen such that # its output length hlen is roughly equal to nlen, since the overall # security of the signature scheme will depend on the smallest of hlen # and nlen; however, the (EC)DSA standard support all combinations of # hlen and nlen. hd = hf(M).digest() # 4 # H(m) is transformed into an integer modulo ec.n using bits2int: e = bits2int(ec, hd) # 5 if k is None: k = rfc6979(ec, hf, hd, d) # 1 # second part delegated to helper function used in testing return _ecdsa_sign(ec, e, d, k)
def _ecssa_e(ec: EC, hf, r: int, P: Point, m: bytes) -> int: # Let e = int(hf(bytes(x(R)) || bytes(dG) || m)) mod n. ebytes = int2octets(r, ec.psize) # FIXME: hsize, nsize ? ebytes += point2octets(ec, P, True) ebytes += m ebytes = hf(ebytes).digest() e = bits2int(ec, ebytes) return e
def ecdsa_pubkey_recovery(ec: EC, hf, M: bytes, sig: ECDS) -> List[Point]: """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 """ # The message digest m: a 32-byte array hd = hf(M).digest() # 1.5 e = bits2int(ec, hd) # 1.5 return _ecdsa_pubkey_recovery(ec, e, sig)
def ecssa_batch_validation(ec: EC, hf, ms: List[bytes], P: List[Point], a: List[int], sig: List[ECSS]) -> bool: # initialization mult = 0 points = list() factors = list() u = len(P) for i in range(u): r, s = to_ssasig(ec, sig[i]) ebytes = r.to_bytes(32, byteorder="big") ebytes += point2octets(ec, P[i], True) ebytes += ms[i] ebytes = hf(ebytes).digest() e = bits2int(ec, ebytes) y = ec.y(r) # raises an error if y does not exist mult += a[i] * s % ec.n points.append(_jac_from_aff((r, y))) factors.append(a[i]) points.append(_jac_from_aff(P[i])) factors.append(a[i] * e % ec.n) # Bos-coster's algorithm, source: # https://cr.yp.to/badbatch/boscoster2.py boscoster = list(zip([-n for n in factors], points)) heapq.heapify(boscoster) while len(boscoster) > 1: aK1 = heapq.heappop(boscoster) aK2 = heapq.heappop(boscoster) a1, K1 = -aK1[0], aK1[1] a2, K2 = -aK2[0], aK2[1] K2 = ec._addJacobian(K1, K2) a1 -= a2 if a1 > 0: heapq.heappush(boscoster, (-a1, K1)) heapq.heappush(boscoster, (-a2, K2)) aK = heapq.heappop(boscoster) RHSJ = _pointMultJacobian(ec, -aK[0], aK[1]) TJ = _pointMultJacobian(ec, mult, _jac_from_aff(ec.G)) RHS = ec._affine_from_jac(RHSJ) T = ec._affine_from_jac(TJ) return T == RHS
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 verify_commit(c: bytes, ec: EC, hf, receipt: Receipt) -> bool: w, R = receipt # w in [1..n-1] dsa # w in [1..p-1] ssa # different verify functions? # verify R is a good point? ch = hf(c).digest() e = hf(point2octets(ec, R, True) + ch).digest() e = bits2int(ec, e) W = ec.add(R, pointMult(ec, e, ec.G)) # different verify functions? # return w == W[0] # ECSS return w == W[0] % ec.n # ECDS
def _ecdsa_verify(ec: EC, hf, M: bytes, P: Point, sig: ECDS) -> bool: """Private function provided for testing purposes only. It raises Errors, while verify should always return True or False See SEC 1 v.2 section 4.1.4 http://www.secg.org/sec1-v2.pdf """ # The message digest m: a 32-byte array hd = hf(M).digest() # 2 e = bits2int(ec, hd) # 3 # Let P = point(pk); fail if point(pk) fails. # P on point will be checked below by DblScalarMult # second part delegated to helper function used in testing return _ecdsa_verhlp(ec, e, P, sig)
def secondGenerator(ec: EC, hf) -> Point: """Nothing-Up-My-Sleeve (NUMS) second generator H wrt ec.G source: https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/rangeproof/main_impl.h idea: https://crypto.stackexchange.com/questions/25581/second-generator-for-secp256k1-curve Get the hash of G, then coerce it to a point (hx, hy). The resulting point could not be a curvepoint: in this case keep on incrementing hx until a valid curve point (hx, hy) is obtained. """ G_bytes = point2octets(ec, ec.G, False) hd = hf(G_bytes).digest() hx = bits2int(ec, hd) isCurvePoint = False while not isCurvePoint: try: hy = ec.yOdd(hx, False) isCurvePoint = True except: hx += 1 return hx, hy