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_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 address_from_pubkey(Q: Point, compressed: bool, version: bytes = b'\x00') -> bytes: """Public key to (bytes) address""" pubkey = point2octets(ec, Q, compressed) # FIXME: this is mainnet only vh160 = version + h160(pubkey) return b58encode_check(vh160)
def get_msg_format(msg: bytes, pubk_rings: Dict[int, List[Point]]) -> bytes: m = msg rings = len(pubk_rings) for i in range(rings): keys = len(pubk_rings[i]) for j in range(keys): P = pubk_rings[i][j] Pbytes = point2octets(ec, P, True) m += Pbytes return sha256(m).digest()
def _tweak(c: bytes, ec: EC, hf, k: int) -> Tuple[Point, int]: """tweak kG returns: - point kG to tweak - tweaked private key k + h(kG||c), the corresponding pubkey is a commitment to kG, c """ R = pointMult(ec, k, ec.G) e = hf(point2octets(ec, R, True) + c).digest() e = int.from_bytes(e, 'big') return R, (e + k) % ec.n
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 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 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
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 bip32_xpub_from_xprv(xprv: octets) -> bytes: """Neutered Derivation (ND) Computation of the extended public key corresponding to an extended private key (“neutered” as it removes the ability to sign transactions) """ xprv = b58decode_check(xprv, 78) assert xprv[45] == 0, "extended key is not a private one" i = PRIVATE.index(xprv[:4]) # serialization data xpub = PUBLIC[i] # version # unchanged serialization data xpub += xprv[4:5] # depth xpub += xprv[5:9] # parent pubkey fingerprint xpub += xprv[9:13] # child index xpub += xprv[13:45] # chain code p = octets2int(xprv[46:]) P = pointMult(ec, p, ec.G) xpub += point2octets(ec, P, True) # public key return b58encode_check(xpub)
def bip32_ckd(xparentkey: octets, index: Union[octets, int]) -> bytes: """Child Key Derivation (CDK) Key derivation is normal if the extended parent key is public or child_index is less than 0x80000000. Key derivation is hardened if the extended parent key is private and child_index is not less than 0x80000000. """ if isinstance(index, int): index = index.to_bytes(4, 'big') elif isinstance(index, str): # hex string index = bytes.fromhex(index) if len(index) != 4: raise ValueError(f"a 4 bytes int is required, not {len(index)}") xparent = b58decode_check(xparentkey, 78) version = xparent[:4] # serialization data xkey = version # version xkey += (xparent[4] + 1).to_bytes(1, 'big') # (increased) depth if (version in PUBLIC): if xparent[45] not in (2, 3): # not a compressed public key raise ValueError("version/key mismatch in extended parent key") Parent_bytes = xparent[45:] Parent = octets2point(ec, Parent_bytes) xkey += h160(Parent_bytes)[:4] # parent pubkey fingerprint if index[0] >= 0x80: raise ValueError("no private/hardened derivation from pubkey") xkey += index # child index parent_chain_code = xparent[13:45] # normal derivation # actual extended key (key + chain code) derivation h = HMAC(parent_chain_code, Parent_bytes + index, sha512).digest() offset = int.from_bytes(h[:32], 'big') Offset = pointMult(ec, offset, ec.G) Child = ec.add(Parent, Offset) Child_bytes = point2octets(ec, Child, True) xkey += h[32:] # chain code xkey += Child_bytes # public key elif (version in PRIVATE): if xparent[45] != 0: # not a private key raise ValueError("version/key mismatch in extended parent key") parent = int.from_bytes(xparent[46:], 'big') Parent = pointMult(ec, parent, ec.G) Parent_bytes = point2octets(ec, Parent, True) xkey += h160(Parent_bytes)[:4] # parent pubkey fingerprint xkey += index # child index # actual extended key (key + chain code) derivation parent_chain_code = xparent[13:45] if (index[0] < 0x80): # normal derivation h = HMAC(parent_chain_code, Parent_bytes + index, sha512).digest() else: # hardened derivation h = HMAC(parent_chain_code, xparent[45:] + index, sha512).digest() offset = int.from_bytes(h[:32], 'big') child = (parent + offset) % ec.n child_bytes = b'\x00' + child.to_bytes(32, 'big') xkey += h[32:] # chain code xkey += child_bytes # private key else: raise ValueError("invalid extended key version") return b58encode_check(xkey)