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 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 back_encrypt(self, authenticated=True): for i in range(5): priv_key = crypto.random_scalar() data = crypto.cn_fast_hash(crypto.encodeint( crypto.random_scalar())) * (i + 1) blob = chacha.encrypt_xmr(priv_key, data, authenticated=authenticated) plaintext = chacha.decrypt_xmr(priv_key, blob, authenticated=authenticated) self.assertEqual(data, plaintext) try: plaintext2 = chacha.decrypt_xmr( crypto.sc_add(priv_key, crypto.sc_init(1)), blob, authenticated=authenticated, ) if authenticated: self.fail("Signature error expected") else: self.assertNotEqual(data, plaintext2) except: if not authenticated: raise
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 gen_asnl(x, P1, P2, indices): """ Aggregate Schnorr Non-Linkable x, P1, P2, are key vectors here, but actually you indices specifices which column of the given row of the key vector you sign. the key vector with the first or second key :param x: :param P1: :param P2: :param indices: :return: """ n = len(x) logger.info("Generating Aggregate Schnorr Non-linkable Ring Signature") L1 = [None] * n s1 = [None] * n s2 = [None] * n s = crypto.sc_0() for j in range(0, n): L1[j], s1[j], s2[j] = gen_schnorr_non_linkable(x[j], P1[j], P2[j], indices[j]) s = crypto.sc_add(s, s1[j]) return L1, s2, s
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 compute_tx_key(spend_key_private, tx_prefix_hash, salt=None, rand_mult=None): """ :param spend_key_private: :param tx_prefix_hash: :param salt: :param rand_mult: :return: """ if not salt: salt = crypto.random_bytes(32) if not rand_mult: rand_mult_num = crypto.random_scalar() rand_mult = crypto.encodeint(rand_mult_num) else: rand_mult_num = crypto.decodeint(rand_mult) rand_inp = crypto.sc_add(spend_key_private, rand_mult_num) passwd = crypto.keccak_2hash(crypto.encodeint(rand_inp) + tx_prefix_hash) tx_key = crypto.compute_hmac(salt, passwd) # tx_key = crypto.pbkdf2(passwd, salt, count=100) return tx_key, salt, rand_mult
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 generate_key_image_helper_precomp( ack, out_key, recv_derivation, real_output_index, received_index ): """ Generates UTXO spending key and key image. :param ack: sender credentials :type ack: AccountCreds :param out_key: real output (from input RCT) destination key :param recv_derivation: :param real_output_index: :param received_index: subaddress index this payment was received to :return: """ if ack.spend_key_private == 0: raise ValueError("Watch-only wallet not supported") # derive secret key with subaddress - step 1: original CN derivation scalar_step1 = crypto.derive_secret_key( recv_derivation, real_output_index, ack.spend_key_private ) # step 2: add Hs(SubAddr || a || index_major || index_minor) subaddr_sk = None scalar_step2 = None if received_index == (0, 0): scalar_step2 = scalar_step1 else: subaddr_sk = get_subaddress_secret_key( ack.view_key_private, major=received_index[0], minor=received_index[1] ) scalar_step2 = crypto.sc_add(scalar_step1, subaddr_sk) # when not in multisig, we know the full spend secret key, so the output pubkey can be obtained by scalarmultBase if len(ack.multisig_keys) == 0: pub_ver = crypto.scalarmult_base(scalar_step2) else: # When in multisig, we only know the partial spend secret key. But we do know the full spend public key, # so the output pubkey can be obtained by using the standard CN key derivation. pub_ver = crypto.derive_public_key( recv_derivation, real_output_index, ack.spend_key_public ) # Add the contribution from the subaddress part if received_index != (0, 0): subaddr_pk = crypto.scalarmult_base(subaddr_sk) pub_ver = crypto.point_add(pub_ver, subaddr_pk) if not crypto.point_eq(pub_ver, out_key): raise ValueError( "key image helper precomp: given output pubkey doesn't match the derived one" ) ki = generate_key_image(crypto.encodepoint(pub_ver), scalar_step2) return scalar_step2, ki
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 test_signature(self): for i in range(10): priv = crypto.random_scalar() data = crypto.cn_fast_hash(bytes(bytearray([i]))) c, r, pub = crypto.generate_signature(data, priv) res = crypto.check_signature(data, c, r, pub) self.assertEqual(res, 1) res2 = crypto.check_signature(data, crypto.sc_add(c, crypto.sc_init(1)), r, pub) self.assertEqual(res2, 0)
def prove_range_orig(amount, last_mask=None, use_asnl=False): """ 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 :param use_asnl: use ASNL, used before Borromean, insecure :return: sumCi, mask, RangeSig. sumCi is Pedersen commitment on the amount value. sumCi = aG + amount*H mask is "a" from the Pedersent commitment above. """ bb = d2b(amount, ATOMS) # gives binary form of bb in "digits" binary digits logger.info("amount, amount in binary %s %s" % (amount, bb)) ai = [None] * len(bb) Ci = [None] * len(bb) CiH = [None] * len(bb) # this is like Ci - 2^i H H2 = crypto.gen_Hpow(ATOMS) a = crypto.sc_0() for i in range(0, ATOMS): ai[i] = crypto.random_scalar() if last_mask is not None and i == ATOMS - 1: ai[i] = crypto.sc_sub(last_mask, a) a = crypto.sc_add( a, ai[i] ) # creating the total mask since you have to pass this to receiver... if bb[i] == 0: Ci[i] = crypto.scalarmult_base(ai[i]) if bb[i] == 1: Ci[i] = crypto.point_add(crypto.scalarmult_base(ai[i]), H2[i]) CiH[i] = crypto.point_sub(Ci[i], H2[i]) A = xmrtypes.BoroSig() if use_asnl: A.s0, A.s1, A.ee = asnl.gen_asnl(ai, Ci, CiH, bb) else: A.s0, A.s1, A.ee = mlsag2.gen_borromean(ai, Ci, CiH, bb) R = xmrtypes.RangeSig() R.asig = A R.Ci = Ci C = sum_Ci(Ci) return C, a, R
async def commitment(self, in_amount): """ Computes Pedersen commitment - pseudo outs Here is slight deviation from the original protocol. We want that \\sum Alpha = \\sum A_{i,j} where A_{i,j} is a mask from range proof for output i, bit j. Previously this was computed in such a way that Alpha_{last} = \\sum A{i,j} - \\sum_{i=0}^{last-1} Alpha But we would prefer to compute commitment before range proofs so alphas are generated completely randomly and the last A mask is computed in this special way. Returns pseudo_out :return: """ alpha = crypto.random_scalar() self.sumpouts_alphas = crypto.sc_add(self.sumpouts_alphas, alpha) return alpha, crypto.gen_c(alpha, in_amount)
async def test_live_refresh(self): if self.should_test_only_tx() or not int( os.getenv("TREZOR_TEST_LIVE_REFRESH", '1')): self.skipTest("Live refresh skipped") creds = self.get_trezor_creds(0) await self.agent.live_refresh_start() for att in range(22): r = crypto.random_scalar() R = crypto.scalarmult_base(r) D = crypto.scalarmult(R, creds.view_key_private) subaddr = 0, att scalar_step1 = crypto.derive_secret_key(D, att, creds.spend_key_private) # step 2: add Hs(SubAddr || a || index_major || index_minor) if subaddr == (0, 0): scalar_step2 = scalar_step1 else: subaddr_sk = monero.get_subaddress_secret_key( creds.view_key_private, major=0, minor=att) scalar_step2 = crypto.sc_add(scalar_step1, subaddr_sk) pub_ver = crypto.scalarmult_base(scalar_step2) ki = monero.generate_key_image(crypto.encodepoint(pub_ver), scalar_step2) ki2 = await self.agent.live_refresh(creds.view_key_private, crypto.encodepoint(pub_ver), crypto.encodepoint(D), att, 0, att) if not crypto.point_eq(ki, ki2): raise ValueError("Key image inconsistent") time.sleep(0.1) await self.agent.live_refresh_final()
async def gen_rct_simple( self, in_sk, destinations, inamounts, outamounts, txn_fee, mix_ring, kLRki, msout, index, ): """ Generate simple RCT signature. :param in_sk: :param destinations: :param inamounts: :param outamounts: :param txn_fee: :param mix_ring: :param kLRki: :param msout: :param index: :param out_sk: :return: """ if len(inamounts) == 0: raise ValueError("Empty inamounts") if len(inamounts) != len(in_sk): raise ValueError("Different number of inamounts/inSk") if len(outamounts) != len(destinations): raise ValueError("Different number of amounts/destinations") if len(self.output_secrets) != len(destinations): raise ValueError("Different number of amount_keys/destinations") if len(index) != len(in_sk): raise ValueError("Different number of index/inSk") if len(mix_ring) != len(in_sk): raise ValueError("Different number of mixRing/inSk") for idx in range(len(mix_ring)): if index[idx] >= len(mix_ring[idx]): raise ValueError("Bad index into mixRing") rv, sumout, out_sk = await self.gen_rct_header(destinations, outamounts) rv.type = ( xmrtypes.RctType.SimpleBulletproof if self.use_bulletproof else xmrtypes.RctType.Simple ) rv.txnFee = txn_fee rv.mixRing = mix_ring # Pseudooutputs pseudo_outs = [None] * len(inamounts) rv.p.MGs = [None] * len(inamounts) sumpouts = crypto.sc_0() a = [] for idx in range(len(inamounts) - 1): a.append(crypto.random_scalar()) sumpouts = crypto.sc_add(sumpouts, a[idx]) pseudo_outs[idx] = crypto.gen_c(a[idx], inamounts[idx]) a.append(crypto.sc_sub(sumout, sumpouts)) pseudo_outs[-1] = crypto.gen_c(a[-1], inamounts[-1]) if self.use_bulletproof: rv.p.pseudoOuts = [crypto.encodepoint(x) for x in pseudo_outs] else: rv.pseudoOuts = [crypto.encodepoint(x) for x in pseudo_outs] full_message = await monero.get_pre_mlsag_hash(rv) # TODO: msout multisig for i in range(len(inamounts)): rv.p.MGs[i], msc = mlsag2.prove_rct_mg_simple( full_message, rv.mixRing[i], in_sk[i], a[i], pseudo_outs[i], kLRki[i] if kLRki else None, None, index[i], ) if __debug__: assert mlsag2.ver_rct_mg_simple( full_message, rv.p.MGs[i], rv.mixRing[i], pseudo_outs[i] ) return rv
def rekey_input(self, inp, keys, subs=None, new_keys=None, new_subs=None, mixin_change=None): subs = subs if subs else {} real_out_key = inp.outputs[inp.real_output][1] out_key = crypto.decodepoint(real_out_key.dest) tx_key = crypto.decodepoint(inp.real_out_tx_key) additional_keys = [ crypto.decodepoint(x) for x in inp.real_out_additional_tx_keys ] logger.debug("Current out key: %s" % binascii.hexlify(real_out_key.dest)) secs = monero.generate_key_image_helper( keys, subs, out_key, tx_key, additional_keys, inp.real_output_in_tx_index ) xi, ki, di = secs need_additional = additional_keys is not None and len(additional_keys) > 0 is_dst_sub = self.dest_sub_major != 0 and ( self.args.minors[0] != 0 or len(self.args.minors) > 1 ) logger.debug( "Is dst sub: %s, need additional: %s" % (is_dst_sub, need_additional) ) if is_dst_sub and self.add_additionals: need_additional = True if is_dst_sub: rand_minor = random.choice(self.args.minors) m = monero.get_subaddress_secret_key( new_keys.view_key_private, major=self.dest_sub_major, minor=rand_minor ) M = crypto.scalarmult_base(m) d = crypto.sc_add(m, new_keys.spend_key_private) D = crypto.point_add(new_keys.spend_key_public, M) C = crypto.scalarmult(D, new_keys.view_key_private) if not need_additional and not is_dst_sub: # real_out_key.dst = Hs(R*new_a || idx)G + newB r = crypto.random_scalar() tx_key = crypto.scalarmult_base(r) new_deriv = crypto.generate_key_derivation(new_keys.view_key_public, r) new_out_pr = crypto.derive_secret_key( new_deriv, inp.real_output_in_tx_index, new_keys.spend_key_private ) new_out = crypto.scalarmult_base(new_out_pr) real_out_key.dest = crypto.encodepoint(new_out) elif not need_additional and is_dst_sub: # real_out_key.dst = Hs(r*C || idx)G + newB, R=rD r = crypto.random_scalar() tx_key = crypto.scalarmult(D, r) new_deriv = crypto.generate_key_derivation(C, r) new_out_pr = crypto.derive_secret_key( new_deriv, inp.real_output_in_tx_index, d ) new_out = crypto.scalarmult_base(new_out_pr) real_out_key.dest = crypto.encodepoint(new_out) else: r = crypto.random_scalar() tx_key = crypto.scalarmult_base(r) gen_additionals = min(2, inp.real_output_in_tx_index + 1) if additional_keys is None or len(additional_keys) < gen_additionals: additional_keys = [ crypto.scalarmult_base(crypto.random_scalar()) for _ in range(gen_additionals) ] ri = crypto.random_scalar() if is_dst_sub: add_tx = crypto.scalarmult(D, ri) new_deriv = crypto.generate_key_derivation(C, ri) new_out_pr = crypto.derive_secret_key( new_deriv, inp.real_output_in_tx_index, d ) new_out = crypto.scalarmult_base(new_out_pr) if not crypto.point_eq( new_out, crypto.derive_public_key(new_deriv, inp.real_output_in_tx_index, D), ): raise ValueError("Invalid txout computation") else: add_tx = crypto.scalarmult_base(ri) new_deriv = crypto.generate_key_derivation(new_keys.view_key_public, r) new_out_pr = crypto.derive_secret_key( new_deriv, inp.real_output_in_tx_index, new_keys.spend_key_private ) new_out = crypto.scalarmult_base(new_out_pr) additional_keys[inp.real_output_in_tx_index] = add_tx real_out_key.dest = crypto.encodepoint(new_out) # Increasing the size of the mixin if mixin_change and len(inp.outputs) < mixin_change: for i in range(mixin_change - len(inp.outputs)): inp.outputs.append((0, CtKey( mask=crypto.encodepoint(self.random_pub()), dest=crypto.encodepoint(self.random_pub())))) if additional_keys: additional_keys.append(self.random_pub()) inp.real_out_tx_key = crypto.encodepoint(tx_key) inp.real_out_additional_tx_keys = [ crypto.encodepoint(x) for x in additional_keys ] logger.debug("New pub: %s" % binascii.hexlify(real_out_key.dest)) # Self-check self.check_input(inp, new_keys, new_subs) return inp
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
async def gen_input(self, value=1, sub_major=None, sub_minor=0, additionals=False): creds = self.src_keys r = self.random_scalar() R = crypto.scalarmult_base(r) additional_keys = [] Additional = None sub_major = sub_major if sub_major is not None else self.account_idx is_sub = sub_major != 0 or sub_minor != 0 if sub_major != self.account_idx: logger.warning( "Generating input with different major subindex, cannot be spent in the resulting " "transaction") kssec = monero.get_subaddress_secret_key(creds.view_key_private, major=sub_major, minor=sub_minor) kssub = crypto.sc_add( kssec, creds.spend_key_private) if is_sub else creds.spend_key_private kvsub = crypto.sc_mul(creds.view_key_private, kssub) if is_sub else creds.view_key_private KSSUB, KVSUB = monero.generate_sub_address_keys( creds.view_key_private, creds.spend_key_public, sub_major, sub_minor) if not crypto.point_eq(KSSUB, crypto.scalarmult_base(kssub)): raise ValueError("Invariant error") oidx = self.prng.randint(0, 12) additionals_cnt = self.prng.randint(oidx + 1, 2 * oidx + 1) deriv = crypto.generate_key_derivation(KVSUB, r) kout = crypto.derive_secret_key(deriv, oidx, kssub) KOUT = crypto.derive_public_key(deriv, oidx, KSSUB) if not crypto.point_eq(KOUT, crypto.scalarmult_base(kout)): raise ValueError("Invariant error") if additionals: if is_sub: Additional = crypto.scalarmult(KSSUB, r) else: Additional = crypto.scalarmult_base(r) else: if is_sub: R = crypto.scalarmult(KSSUB, r) amnt = crypto.sc_init(value) msk = self.random_scalar() # commitment mask C = crypto.add_keys2(msk, amnt, crypto.xmr_H()) ring = [] for i in range(self.ring_size - 1): tk = CtKey( dest=crypto.encodepoint(self.random_pub()), mask=crypto.encodepoint(self.random_pub()), ) ring.append(tk) index = self.prng.randint(0, len(ring)) ring.insert( index, CtKey(dest=crypto.encodepoint(KOUT), mask=crypto.encodepoint(C))) if additionals: additional_keys = [ self.random_pub() for _ in range(additionals_cnt) ] additional_keys[oidx] = Additional src = TxSourceEntry() src.outputs = [(self.random_glob_idx(), x) for x in ring] src.real_output = index src.real_out_tx_key = crypto.encodepoint(R) src.real_out_additional_tx_keys = [ crypto.encodepoint(x) for x in additional_keys ] src.real_output_in_tx_index = oidx src.amount = value src.rct = True src.mask = crypto.encodeint(msk) src.multisig_kLRki = MultisigKLRki(K=crypto.ZERO, L=crypto.ZERO, R=crypto.ZERO, ki=crypto.ZERO) td = TransferDetails() td.m_internal_output_index = oidx td.m_global_output_index = src.outputs[index][0] td.m_mask = src.mask td.m_amount = value td.m_subaddr_index = SubaddressIndex(major=sub_major, minor=sub_minor) td.m_rct = True td.m_txid = self.random_bytes(32) td.m_block_height = self.prng.randint(0, 0xFFFF) td.m_spent = False td.m_spent_height = 0 td.m_key_image_known = True td.m_key_image_requested = False td.m_key_image_partial = False td.m_multisig_k = [] td.m_multisig_info = [] td.m_uses = [] td.m_pk_index = 0 td.m_tx = self.gen_tx_prefix(self.prng.randint(1, 10), additionals_cnt) td.m_tx.vout[oidx].target.key = crypto.encodepoint(KOUT) extras = [] extras.append(TxExtraNonce(nonce=self.random_bytes(8))) extras.append(TxExtraPubKey(pub_key=src.real_out_tx_key)) if src.real_out_additional_tx_keys: extras.append( TxExtraAdditionalPubKeys(data=src.real_out_additional_tx_keys)) td.m_tx.extra = await self.dump_extra_fields(extras) tmpsubs = {} monero.compute_subaddresses(creds, sub_major, [sub_minor], tmpsubs) xi, ki, rderiv = self.check_input(src, creds, tmpsubs) if not crypto.sc_eq(xi, kout): raise ValueError("Invariant error") td.m_key_image = crypto.encodepoint(ki) self.sources.append(src) self.selected_transfers.append(len(self.transfers)) self.transfers.append(td) self.sources_creds.append(creds) if not crypto.point_eq( crypto.decodepoint(src.outputs[src.real_output][1].dest), crypto.scalarmult_base(kout)): raise ValueError("Invariant error") return self
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
def prove_rct_mg(message, pubs, in_sk, out_sk, out_pk, kLRki, mscout, index, txn_fee_key): """ c.f. http://eprint.iacr.org/2015/1098 section 4. definition 10. This does the MG sig on the "dest" part of the given key matrix, and the last row is the sum of input commitments from that column - sum output commitments this shows that sum inputs = sum outputs :param message: :param pubs: matrix of CtKeys. points are encoded. :param in_sk: :param out_sk: :param out_pk: :param kLRki: :param mscout: :param index: :param txn_fee_key: :return: """ cols = len(pubs) if cols == 0: raise ValueError("Empty pubs") rows = len(pubs[0]) if rows == 0: raise ValueError("Empty pub row") for i in range(cols): if len(pubs[i]) != rows: raise ValueError("pub is not rectangular") if len(in_sk) != rows: raise ValueError("Bad inSk size") if len(out_sk) != len(out_pk): raise ValueError("Bad outsk/putpk size") if (not kLRki or not mscout) and (kLRki and mscout): raise ValueError("Only one of kLRki/mscout is present") sk = key_vector(rows + 1) M = key_matrix(rows + 1, cols) for i in range(rows + 1): sk[i] = crypto.sc_0() for i in range(cols): M[i][rows] = crypto.identity() for j in range(rows): M[i][j] = crypto.decodepoint(pubs[i][j].dest) M[i][rows] = crypto.point_add( M[i][rows], crypto.decodepoint(pubs[i][j].commitment)) sk[rows] = crypto.sc_0() for j in range(rows): sk[j] = in_sk[j].dest sk[rows] = crypto.sc_add(sk[rows], in_sk[j].mask) # add masks in last row for i in range(cols): for j in range(len(out_pk)): M[i][rows] = crypto.point_sub( M[i][rows], crypto.decodepoint( out_pk[j].mask)) # subtract output Ci's in last row # Subtract txn fee output in last row M[i][rows] = crypto.point_sub(M[i][rows], txn_fee_key) for j in range(len(out_pk)): sk[rows] = crypto.sc_sub( sk[rows], out_sk[j].mask) # subtract output masks in last row return gen_mlsag_ext(message, M, sk, kLRki, mscout, index, rows)
async def sc_add(self): s1 = crypto.decodeint(self._fetch()) s2 = crypto.decodeint(self._fetch()) self._insert(crypto.encodeint(crypto.sc_add(s1, s2))) return SW_OK
async def gen_rct_header(self, destinations, outamounts): """ Initializes RV RctSig structure, processes outputs, computes range proofs, ecdh info masking. Common to gen_rct and gen_rct_simple. :param destinations: :param outamounts: :return: """ rv = xmrtypes.RctSig() rv.p = xmrtypes.RctSigPrunable() rv.message = self.tx_prefix_hash rv.outPk = [None] * len(destinations) if self.use_bulletproof: rv.p.bulletproofs = [None] * len(destinations) else: rv.p.rangeSigs = [None] * len(destinations) rv.ecdhInfo = [None] * len(destinations) # Output processing sumout = crypto.sc_0() out_sk = [None] * len(destinations) for idx in range(len(destinations)): rv.outPk[idx] = xmrtypes.CtKey(dest=crypto.encodepoint(destinations[idx])) C, mask, rsig = None, 0, None # Rangeproof if self.use_bulletproof: raise ValueError("Bulletproof not yet supported") else: C, mask, rsig = ring_ct.prove_range(outamounts[idx]) rv.p.rangeSigs[idx] = rsig if __debug__: assert ring_ct.ver_range(C, rsig) assert crypto.point_eq( C, crypto.point_add( crypto.scalarmult_base(mask), crypto.scalarmult_h(outamounts[idx]), ), ) # Mask sum rv.outPk[idx].mask = crypto.encodepoint(C) sumout = crypto.sc_add(sumout, mask) out_sk[idx] = xmrtypes.CtKey(mask=mask) # ECDH masking amount_key = crypto.encodeint(self.output_secrets[idx][0]) rv.ecdhInfo[idx] = xmrtypes.EcdhTuple( mask=mask, amount=crypto.sc_init(outamounts[idx]) ) rv.ecdhInfo[idx] = ring_ct.ecdh_encode( rv.ecdhInfo[idx], derivation=amount_key ) monero.recode_ecdh(rv.ecdhInfo[idx], encode=True) return rv, sumout, out_sk
async def range_proof(self, idx, dest_pub_key, amount, amount_key): """ Computes rangeproof and related information - out_sk, out_pk, ecdh_info. In order to optimize incremental transaction build, the mask computation is changed compared to the official Monero code. In the official code, the input pedersen commitments are computed after range proof in such a way summed masks for commitments (alpha) and rangeproofs (ai) are equal. In order to save roundtrips we compute commitments randomly and then for the last rangeproof a[63] = (\\sum_{i=0}^{num_inp}alpha_i - \\sum_{i=0}^{num_outs-1} amasks_i) - \\sum_{i=0}^{62}a_i The range proof is incrementally hashed to the final_message. :param idx: :param dest_pub_key: :param amount: :param amount_key: :return: """ from monero_glue.xmr import ring_ct rsig = bytearray(32 * (64 + 64 + 64 + 1)) rsig_mv = memoryview(rsig) out_pk = misc.StdObj(dest=dest_pub_key, mask=None) is_last = idx + 1 == self.num_dests() last_mask = ( None if not is_last or not self.use_simple_rct else crypto.sc_sub(self.sumpouts_alphas, self.sumout) ) # Pedersen commitment on the value, mask from the commitment, range signature. C, mask, rsig = None, 0, None # Rangeproof gc.collect() if self.use_bulletproof: raise ValueError("Bulletproof not yet supported") else: C, mask, rsig = ring_ct.prove_range( amount, last_mask, backend_impl=True, byte_enc=True, rsig=rsig_mv ) rsig = memoryview(rsig) if __debug__: rsig_bytes = monero.inflate_rsig(rsig) self.assrt(ring_ct.ver_range(C, rsig_bytes)) self.assrt( crypto.point_eq( C, crypto.point_add( crypto.scalarmult_base(mask), crypto.scalarmult_h(amount) ), ), "rproof", ) # Incremental hashing await self.full_message_hasher.rsig_val( rsig, self.use_bulletproof, raw=True ) gc.collect() self._log_trace("rproof") # Mask sum out_pk.mask = crypto.encodepoint(C) self.sumout = crypto.sc_add(self.sumout, mask) self.output_sk.append(misc.StdObj(mask=mask)) # ECDH masking from monero_glue.xmr.sub.recode import recode_ecdh from monero_serialize.xmrtypes import EcdhTuple ecdh_info = EcdhTuple(mask=mask, amount=crypto.sc_init(amount)) ecdh_info = ring_ct.ecdh_encode( ecdh_info, derivation=crypto.encodeint(amount_key) ) recode_ecdh(ecdh_info, encode=True) gc.collect() return rsig, out_pk, ecdh_info