def ecdh_encdec(masked, receiver_sk=None, derivation=None, v2=False, enc=True, dest=None): """ Elliptic Curve Diffie-Helman: encodes and decodes the amount b and mask a where C= aG + bH """ rv = xmrtypes.EcdhTuple() if dest is None else dest if derivation is None: derivation = crypto.scalarmult(masked.senderPk, receiver_sk) if v2: amnt = masked.amount rv.mask = monero.commitment_mask(derivation) rv.amount = bytearray(32) crypto.encodeint_into(rv.amount, amnt) crypto.xor8(rv.amount, monero.ecdh_hash(derivation)) rv.amount = crypto.decodeint(rv.amount) return rv else: amount_key_hash_single = crypto.hash_to_scalar(derivation) amount_key_hash_double = crypto.hash_to_scalar( crypto.encodeint(amount_key_hash_single) ) sc_fnc = crypto.sc_add if enc else crypto.sc_sub rv.mask = sc_fnc(masked.mask, amount_key_hash_single) rv.amount = sc_fnc(masked.amount, amount_key_hash_double) return rv
def ver_asnl(P1, P2, L1, s2, s): """ Aggregate Schnorr Non-Linkable :param P1: :param P2: :param L1: :param s2: :param s: :return: """ logger.info("Verifying Aggregate Schnorr Non-linkable Ring Signature") n = len(P1) LHS = crypto.scalarmult_base(crypto.sc_0()) RHS = crypto.scalarmult_base(s) for j in range(0, n): c2 = crypto.hash_to_scalar(crypto.encodepoint(L1[j])) L2 = crypto.point_add(crypto.scalarmult_base(s2[j]), crypto.scalarmult(P2[j], c2)) LHS = crypto.point_add(LHS, L1[j]) c1 = crypto.hash_to_scalar(crypto.encodepoint(L2)) RHS = crypto.point_add(RHS, crypto.scalarmult(P1[j], c1)) if crypto.point_eq(LHS, RHS): return 1 else: logger.warning("Didn't verify L1: %s, L1p: %s" % (crypto.encodepoint(LHS), crypto.encodepoint(RHS))) return 0
def unblind_int(self, v, k, AKout): AKout = crypto.hash_to_scalar(AKout) k = crypto.sc_sub(crypto.decodeint(k), AKout) AKout = crypto.hash_to_scalar(crypto.encodeint(AKout)) v = crypto.sc_sub(crypto.decodeint(v), AKout) return v, k, AKout
def ecdh_encode_into(dst, unmasked, derivation=None): """ Elliptic Curve Diffie-Helman: encodes and decodes the amount b and mask a where C= aG + bH """ sec1 = crypto.hash_to_scalar(derivation) sec2 = crypto.hash_to_scalar(crypto.encodeint(sec1)) dst.mask = crypto.sc_add(unmasked.mask, sec1) dst.amount = crypto.sc_add(unmasked.amount, sec2) return dst
def poc2(self): print('[+] PoC Ledger-app-Monero 1.4.2 spend key extraction, v2') self.reset() self.set_mode() self.open_tx() # 1. get A, find x, s.t.: [8*a*x*G]_pt == [8*a*x*G]_sc A = self.scalarmult(self.fake_a) Apt = crypto.decodepoint(A) x, A8x = self.find_confusion(Apt) Gx = crypto.encodepoint(crypto.scalarmult_base(crypto.sc_init(x))) print(' 1. Confusion found, x: %d' % (x, )) print(' 8xA: %s' % binascii.hexlify(A8x).decode('ascii')) print(' A: %s' % binascii.hexlify(A).decode('ascii')) print(' xG: %s' % binascii.hexlify(Gx).decode('ascii')) # 2. gen_deriv (8*a*x*G) = enc(8x*A) = enc(P); we know {P, enc(P)}; # It holds that P=8xA is also a valid scalar value, from the step above. P = self.gen_derivation(Gx, self.fake_a) print(' 2. P: %s' % (self.fmtkey(P).decode('ascii'), )) # 3. get_secret_key: s1 = Hs(P||0) + s sp = self.derive_secret_key(P, 0, self.fake_b) print(' 3. sp: %s' % (self.fmtkey(sp).decode('ascii'), )) # 4. mlsag_hash(p2=1, opt=0x80) = c c = self.mlsag_hash() print(' 4. c: %s' % (binascii.hexlify(c).decode('ascii'), )) # 5. mlsag_sign(s1, enc(P)), r1 = enc(s1 - Pc) = enc(Hs(P||0) + s - Pc); # We have R = Hs(P||0) + s - Pc -> R - Hs(P||0) + Pc = s r = self.mlsag_sign_s(P, sp) print(' 5. r: %s' % (binascii.hexlify(r[0]).decode('ascii'), )) # Extract the spend key hs0 = crypto.hash_to_scalar(bytearray(A8x) + bytearray(1)) rsc = crypto.decodeint(r[0]) rsc = crypto.sc_sub(rsc, hs0) bsc = crypto.sc_add( rsc, crypto.sc_mul(crypto.decodeint(c), crypto.decodeint(A8x))) b = crypto.encodeint(bsc) print(' 5. b: %s' % binascii.hexlify(b).decode('ascii')) B = crypto.scalarmult_base(bsc) print(' 5. B: %s' % binascii.hexlify(crypto.encodepoint(B)).decode('ascii')) # 6. Verify BB = self.scalarmult(self.fake_b) print(' 6. bG: %s\n' % binascii.hexlify(BB).decode('ascii')) if BB == crypto.encodepoint(B): print('[+] PoC successful') else: print('[-] PoC not working') print('\nCommands: ') for x in self.commands: print(' %s' % x)
def check_ring_singature(prefix_hash, image, pubs, sig): """ Checks ring signature generated with generate_ring_signature :param prefix_hash: :param image: :param pubs: :param sig: :return: """ image_unp = crypto.ge_frombytes_vartime(image) image_pre = crypto.ge_dsm_precomp(image_unp) buff = b"" + prefix_hash sum = crypto.sc_0() for i in range(len(pubs)): if crypto.sc_check(sig[i][0]) != 0 or crypto.sc_check(sig[i][1]) != 0: return False tmp3 = crypto.ge_frombytes_vartime(pubs[i]) tmp2 = crypto.ge_double_scalarmult_base_vartime( sig[i][0], tmp3, sig[i][1]) buff += crypto.encodepoint(tmp2) tmp3 = crypto.hash_to_ec(crypto.encodepoint(pubs[i])) tmp2 = crypto.ge_double_scalarmult_precomp_vartime( sig[i][1], tmp3, sig[i][0], image_pre) buff += crypto.encodepoint(tmp2) sum = crypto.sc_add(sum, sig[i][0]) h = crypto.hash_to_scalar(buff) h = crypto.sc_sub(h, sum) return crypto.sc_isnonzero(h) == 0
def check_ring_singature(prefix_hash, image, pubs, sig): """ Checks ring signature generated with generate_ring_signature """ image_unp = crypto.ge_frombytes_vartime(image) image_pre = crypto.ge_dsm_precomp(image_unp) buff_off = len(prefix_hash) buff = bytearray(buff_off + 2 * 32 * len(pubs)) memcpy(buff, 0, prefix_hash, 0, buff_off) mvbuff = memoryview(buff) sum = crypto.sc_0() for i in range(len(pubs)): if crypto.sc_check(sig[i][0]) != 0 or crypto.sc_check(sig[i][1]) != 0: return False tmp3 = crypto.ge_frombytes_vartime(pubs[i]) tmp2 = crypto.ge_double_scalarmult_base_vartime( sig[i][0], tmp3, sig[i][1]) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2) buff_off += 32 tmp3 = crypto.hash_to_point(crypto.encodepoint(pubs[i])) tmp2 = crypto.ge_double_scalarmult_precomp_vartime( sig[i][1], tmp3, sig[i][0], image_pre) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2) buff_off += 32 sum = crypto.sc_add(sum, sig[i][0]) h = crypto.hash_to_scalar(buff) h = crypto.sc_sub(h, sum) return crypto.sc_isnonzero(h) == 0
def ecdh_decode(masked, receiver_sk=None, derivation=None): """ Elliptic Curve Diffie-Helman: encodes and decodes the amount b and mask a where C= aG + bH """ rv = xmrtypes.EcdhTuple() if derivation is None: derivation = crypto.scalarmult(masked.senderPk, receiver_sk) sharedSec1 = crypto.hash_to_scalar(derivation) sharedSec2 = crypto.hash_to_scalar(crypto.encodeint(sharedSec1)) rv.mask = crypto.sc_sub(masked.mask, sharedSec1) rv.amount = crypto.sc_sub(masked.amount, sharedSec2) return rv
def ver_borromean(P1, P2, s0, s1, ee): """ Verify range proof signature, Borromean (c.f. gmax/andytoshi's paper) :param P1: :param P2: :param s0: :param s1: :param ee: :return: """ n = len(P1) Lv1 = key_vector(n) for ii in range(n): LL = crypto.add_keys2(s0[ii], ee, P1[ii]) chash = crypto.hash_to_scalar(crypto.encodepoint(LL)) Lv1[ii] = crypto.add_keys2(s1[ii], chash, P2[ii]) kck = crypto.get_keccak() for ii in range(n): kck.update(crypto.encodepoint(Lv1[ii])) # ee_computed = crypto.hash_to_scalar(crypto.encodepoint(Lv1)) ee_computed = crypto.decodeint(kck.digest()) return crypto.sc_eq(ee_computed, ee)
async def blind(self): AKout = self._fetch_decrypt(32) k = self._fetch(32) v = self._fetch(32) self.ctx_amount.update(AKout) self.ctx_amount.update(k) self.ctx_amount.update(v) AKout = crypto.hash_to_scalar(AKout) k = crypto.sc_add(crypto.decodeint(k), AKout) AKout = crypto.hash_to_scalar(crypto.encodeint(AKout)) v = crypto.sc_add(crypto.decodeint(v), AKout) self._insert(crypto.encodeint(v)) self._insert(crypto.encodeint(k)) return SW_OK
def test_hash_to_scalar(self): inp = unhexlify( b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405" ) res = crypto.hash_to_scalar(inp) exp = crypto.decodeint( binascii.unhexlify( b"9907925b254e12162609fc0dfd0fef2aa4d605b0d10e6507cac253dd31a3ec06" )) self.assertTrue(crypto.sc_eq(res, exp))
def ecdh_encode(unmasked, receiver_pk=None, derivation=None): """ Elliptic Curve Diffie-Helman: encodes and decodes the amount b and mask a where C= aG + bH :param unmasked: :param receiver_pk: :param derivation: :return: """ rv = xmrtypes.EcdhTuple() if derivation is None: esk = crypto.random_scalar() rv.senderPk = crypto.scalarmult_base(esk) derivation = crypto.encodepoint(crypto.scalarmult(receiver_pk, esk)) sec1 = crypto.hash_to_scalar(derivation) sec2 = crypto.hash_to_scalar(crypto.encodeint(sec1)) rv.mask = crypto.sc_add(unmasked.mask, sec1) rv.amount = crypto.sc_add(unmasked.amount, sec2) return rv
def gen_borromean(x, P1, P2, indices): """ Generates Borromean signature for range proofs. :return: """ n = len(P1) alpha = key_zero_vector(n) s1 = key_zero_vector(n) kck = crypto.get_keccak() # ee computation for ii in range(n): alpha[ii] = crypto.random_scalar() L = crypto.scalarmult_base(alpha[ii]) if indices[ii] == 0: s1[ii] = crypto.random_scalar() c = crypto.hash_to_scalar(crypto.encodepoint(L)) L = crypto.add_keys2(s1[ii], c, P2[ii]) kck.update(crypto.encodepoint(L)) else: kck.update(crypto.encodepoint(L)) ee = crypto.decodeint(kck.digest()) del kck s0 = key_zero_vector(n) for jj in range(n): if not indices[jj]: s0[jj] = crypto.sc_mulsub(x[jj], ee, alpha[jj]) else: s0[jj] = crypto.random_scalar() LL = crypto.add_keys2(s0[jj], ee, P1[jj]) cc = crypto.hash_to_scalar(crypto.encodepoint(LL)) s1[jj] = crypto.sc_mulsub(x[jj], cc, alpha[jj]) return s0, s1, ee
def poc(self): print('[+] PoC Ledger-app-Monero 1.4.2 spend key extraction') self.reset() self.set_mode() self.open_tx() # 1. call `monero_apdu_generate_keypair()`, obtain `{enc(x), hmac(enx(x))}`, where `x` is unknown. _, sec = self.gen_kp() print(' 1. rsec: %s' % self.fmtkey(sec).decode('ascii')) # 2. call `sc_sub(enc(x), hmac(enc(x)), enc(x), hmac(enc(x)))`, obtain `{enc(0), hmac(enc(0))}` zero = self.sc_sub(sec, sec) print(' 2. zero: %s' % self.fmtkey(zero).decode('ascii')) # 3. call `monero_apdu_derive_secret_key( # enc(0), hmac(enc(0)), 0, C_FAKE_SEC_SPEND_KEY, hmac(C_FAKE_SEC_SPEND_KEY))`, # obtain `{enc(r), hmac(enc(r))}` encr = self.derive_secret_key(zero, 0, self.fake_b) print(' 3. encr: %s' % self.fmtkey(encr).decode('ascii')) # 4. call `monero_apdu_mlsag_sign(enc(0), hmac(enc(0)), enc(r), hmac(enc(r)))`, obtain `r` r = self.mlsag_sign_s(zero, encr) print(' 4. r: %s' % self.fmtkey(r).decode('ascii')) # 5. compute b: `b = r - H_s(00....00 || varint(0))` hs0 = crypto.hash_to_scalar(bytearray(33)) rsc = crypto.decodeint(r[0]) bsc = crypto.sc_sub(rsc, hs0) b = crypto.encodeint(bsc) print(' 5. b: %s' % binascii.hexlify(b).decode('ascii')) B = crypto.scalarmult_base(bsc) print(' 5. B: %s' % binascii.hexlify(crypto.encodepoint(B)).decode('ascii')) # 6. Verify BB = self.scalarmult(self.fake_b) print(' 6. bG: %s' % binascii.hexlify(BB).decode('ascii')) if BB == crypto.encodepoint(B): print('[+] PoC successful') else: print('[-] PoC not working') if self.lite: return self.extra_poc(zero, hs0, B)
def test_hash_to_scalar(self): inp = bytes( [ 0x25, 0x9e, 0xf2, 0xab, 0xa8, 0xfe, 0xb4, 0x73, 0xcf, 0x39, 0x05, 0x8a, 0x0f, 0xe3, 0x0b, 0x9f, 0xf6, 0xd2, 0x45, 0xb4, 0x2b, 0x68, 0x26, 0x68, 0x7e, 0xbd, 0x6b, 0x63, 0x12, 0x8a, 0xff, 0x64, 0x05, ] ) res = crypto.hash_to_scalar(inp) exp = self.init_sc( 0x6eca331dd53c2ca07650ed1b005d6a42aef0ffd0dfc092616124e255b920799 ) self.assertTrue(crypto.sc_eq(res, exp))
def get_subaddress_secret_key( secret_key, index=None, major=None, minor=None, little_endian=True ): """ Builds subaddress secret key from the subaddress index Hs(SubAddr || a || index_major || index_minor) UPDATE: Monero team fixed this problem. Always use little endian. Note: need to handle endianity in the index C-code simply does: memcpy(data + sizeof(prefix) + sizeof(crypto::secret_key), &index, sizeof(subaddress_index)); Where the index has the following form: struct subaddress_index { uint32_t major; uint32_t minor; } https://docs.python.org/3/library/struct.html#byte-order-size-and-alignment :param secret_key: :param index: :param major: :param minor: :param little_endian: :return: """ if index: major = index.major minor = index.minor endianity = "<" if little_endian else ">" prefix = b"SubAddr" buffer = bytearray(len(prefix) + 1 + 32 + 4 + 4) struct.pack_into( "%s7sb32sLL" % endianity, buffer, 0, prefix, 0, crypto.encodeint(secret_key), major, minor, ) return crypto.hash_to_scalar(buffer)
def generate_ring_signature(prefix_hash, image, pubs, sec, sec_idx, test=False): """ Generates ring signature with key image. void crypto_ops::generate_ring_signature() """ if test: t = crypto.scalarmult_base(sec) if not crypto.point_eq(t, pubs[sec_idx]): raise ValueError("Invalid sec key") k_i = monero.generate_key_image(crypto.encodepoint(pubs[sec_idx]), sec) if not crypto.point_eq(k_i, image): raise ValueError("Key image invalid") for k in pubs: crypto.ge_frombytes_vartime_check(k) image_unp = crypto.ge_frombytes_vartime(image) image_pre = crypto.ge_dsm_precomp(image_unp) buff_off = len(prefix_hash) buff = bytearray(buff_off + 2 * 32 * len(pubs)) memcpy(buff, 0, prefix_hash, 0, buff_off) mvbuff = memoryview(buff) sum = crypto.sc_0() k = crypto.sc_0() sig = [] for i in range(len(pubs)): sig.append([crypto.sc_0(), crypto.sc_0()]) # c, r for i in range(len(pubs)): if i == sec_idx: k = crypto.random_scalar() tmp3 = crypto.scalarmult_base(k) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp3) buff_off += 32 tmp3 = crypto.hash_to_point(crypto.encodepoint(pubs[i])) tmp2 = crypto.scalarmult(tmp3, k) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2) buff_off += 32 else: sig[i] = [crypto.random_scalar(), crypto.random_scalar()] tmp3 = crypto.ge_frombytes_vartime(pubs[i]) tmp2 = crypto.ge_double_scalarmult_base_vartime( sig[i][0], tmp3, sig[i][1]) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2) buff_off += 32 tmp3 = crypto.hash_to_point(crypto.encodepoint(tmp3)) tmp2 = crypto.ge_double_scalarmult_precomp_vartime( sig[i][1], tmp3, sig[i][0], image_pre) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2) buff_off += 32 sum = crypto.sc_add(sum, sig[i][0]) h = crypto.hash_to_scalar(buff) sig[sec_idx][0] = crypto.sc_sub(h, sum) sig[sec_idx][1] = crypto.sc_mulsub(sig[sec_idx][0], sec, k) return sig
def prove_range_mem(amount, last_mask=None): """ Memory optimized range proof. Gives C, and mask such that \sumCi = C c.f. http:#eprint.iacr.org/2015/1098 section 5.1 Ci is a commitment to either 0 or 2^i, i=0,...,63 thus this proves that "amount" is in [0, 2^ATOMS] mask is a such that C = aG + bH, and b = amount :param amount: :param last_mask: ai[ATOMS-1] will be computed as \sum_{i=0}^{ATOMS-2} a_i - last_mask :return: sumCi, mask, RangeSig. sumCi is Pedersen commitment on the amount value. sumCi = aG + amount*H mask is "a" from the Pedersent commitment above. """ n = ATOMS bb = d2b(amount, n) # gives binary form of bb in "digits" binary digits ai = [None] * len(bb) Ci = [None] * len(bb) a = crypto.sc_0() C = crypto.identity() alpha = mlsag2.key_zero_vector(n) s1 = mlsag2.key_zero_vector(n) c_H = crypto.xmr_H() kck = crypto.get_keccak() # ee computation # First pass, generates: ai, alpha, Ci, ee, s1 for ii in range(n): ai[ii] = crypto.random_scalar() if last_mask is not None and ii == ATOMS - 1: ai[ii] = crypto.sc_sub(last_mask, a) a = crypto.sc_add( a, ai[ii] ) # creating the total mask since you have to pass this to receiver... alpha[ii] = crypto.random_scalar() L = crypto.scalarmult_base(alpha[ii]) if bb[ii] == 0: Ci[ii] = crypto.scalarmult_base(ai[ii]) else: Ci[ii] = crypto.point_add(crypto.scalarmult_base(ai[ii]), c_H) C = crypto.point_add(C, Ci[ii]) if bb[ii] == 0: s1[ii] = crypto.random_scalar() c = crypto.hash_to_scalar(crypto.encodepoint(L)) L = crypto.add_keys2(s1[ii], c, crypto.point_sub(Ci[ii], c_H)) kck.update(crypto.encodepoint(L)) else: kck.update(crypto.encodepoint(L)) c_H = crypto.point_double(c_H) # Compute ee, memory cleanup ee = crypto.decodeint(kck.digest()) del kck # Second phase computes: s0, s1 c_H = crypto.xmr_H() s0 = mlsag2.key_zero_vector(n) for jj in range(n): if not bb[jj]: s0[jj] = crypto.sc_mulsub(ai[jj], ee, alpha[jj]) else: s0[jj] = crypto.random_scalar() LL = crypto.add_keys2(s0[jj], ee, Ci[jj]) cc = crypto.hash_to_scalar(crypto.encodepoint(LL)) s1[jj] = crypto.sc_mulsub(ai[jj], cc, alpha[jj]) c_H = crypto.point_double(c_H) A = xmrtypes.BoroSig() A.s0, A.s1, A.ee = s0, s1, ee R = xmrtypes.RangeSig() R.asig = A R.Ci = Ci return C, a, R
def generate_ring_signature(prefix_hash, image, pubs, sec, sec_idx, test=False): """ Generates ring signature with key image. void crypto_ops::generate_ring_signature() :param prefix_hash: :param image: :param pubs: :param sec: :param sec_idx: :param test: :return: """ if test: t = crypto.scalarmult_base(sec) if not crypto.point_eq(t, pubs[sec_idx]): raise ValueError("Invalid sec key") k_i = monero.generate_key_image(crypto.encodepoint(pubs[sec_idx]), sec) if not crypto.point_eq(k_i, image): raise ValueError("Key image invalid") for k in pubs: crypto.ge_frombytes_vartime_check(k) image_unp = crypto.ge_frombytes_vartime(image) image_pre = crypto.ge_dsm_precomp(image_unp) buff = b"" + prefix_hash sum = crypto.sc_0() k = crypto.sc_0() sig = [] for i in range(len(pubs)): sig.append([crypto.sc_0(), crypto.sc_0()]) # c, r for i in range(len(pubs)): if i == sec_idx: k = crypto.random_scalar() tmp3 = crypto.scalarmult_base(k) buff += crypto.encodepoint(tmp3) tmp3 = crypto.hash_to_ec(crypto.encodepoint(pubs[i])) tmp2 = crypto.scalarmult(tmp3, k) buff += crypto.encodepoint(tmp2) else: sig[i] = [crypto.random_scalar(), crypto.random_scalar()] tmp3 = crypto.ge_frombytes_vartime(pubs[i]) tmp2 = crypto.ge_double_scalarmult_base_vartime( sig[i][0], tmp3, sig[i][1]) buff += crypto.encodepoint(tmp2) tmp3 = crypto.hash_to_ec(crypto.encodepoint(tmp3)) tmp2 = crypto.ge_double_scalarmult_precomp_vartime( sig[i][1], tmp3, sig[i][0], image_pre) buff += crypto.encodepoint(tmp2) sum = crypto.sc_add(sum, sig[i][0]) h = crypto.hash_to_scalar(buff) sig[sec_idx][0] = crypto.sc_sub(h, sum) sig[sec_idx][1] = crypto.sc_mulsub(sig[sec_idx][0], sec, k) return sig