def recode_rangesig(rsig, encode=True, copy=False): """ In - place rsig recoding :param rsig: :param encode: if true encodes to byte representation, otherwise decodes from byte representation :param copy: :return: """ recode_int = crypto.encodeint if encode else crypto.decodeint recode_point = crypto.encodepoint if encode else crypto.decodepoint nrsig = rsig if copy: nrsig = xmrtypes.RangeSig() nrsig.Ci = [None] * 64 nrsig.asig = xmrtypes.BoroSig() nrsig.asig.s0 = [None] * 64 nrsig.asig.s1 = [None] * 64 for i in range(len(rsig.Ci)): nrsig.Ci[i] = recode_point(rsig.Ci[i]) for i in range(len(rsig.asig.s0)): nrsig.asig.s0[i] = recode_int(rsig.asig.s0[i]) for i in range(len(rsig.asig.s1)): nrsig.asig.s1[i] = recode_int(rsig.asig.s1[i]) nrsig.asig.ee = recode_int(rsig.asig.ee) return nrsig
async def _on_set_outputs_ack(self, idx, dst, t_res): self.ct.tx.vout.append(await tmisc.parse_msg(t_res.tx_out, xmrtypes.TxOut())) self.ct.tx_out_hmacs.append(t_res.vouti_hmac) self.ct.tx_out_pk.append(await tmisc.parse_msg(t_res.out_pk, xmrtypes.CtKey())) # hf10 encodes only 8B amount if self.hf == 9: self.ct.tx_out_ecdh.append(await tmisc.parse_msg(t_res.ecdh_info, xmrtypes.EcdhTuple())) else: cur_ecdh = xmrtypes.EcdhTuple(mask=crypto.NULL_KEY_ENC, amount=bytearray( crypto.NULL_KEY_ENC)) cur_ecdh.amount[0:8] = bytearray(t_res.ecdh_info) self.ct.tx_out_ecdh.append(cur_ecdh) rsig_buff = None if t_res.rsig_data: tsig_data = t_res.rsig_data if tsig_data.rsig and len(tsig_data.rsig) > 0: rsig_buff = tsig_data.rsig elif tsig_data.rsig_parts and len(tsig_data.rsig_parts) > 0: rsig_buff = b"".join(tsig_data.rsig_parts) if self.client_version >= 1 and tsig_data.mask: logger.debug("MASK received") self.ct.rsig_gamma.append(tsig_data.mask) # Client1: send generated BP if offloading if self.client_version >= 1 and self._is_offloading(): proc = await self._v1_offloading(idx, dst, t_res) if not proc: return # no state advance else: rsig = None if rsig_buff and not self._is_req_bulletproof(): rsig = await tmisc.parse_msg(rsig_buff, xmrtypes.RangeSig()) elif rsig_buff: rsig = await tmisc.parse_msg(rsig_buff, xmrtypes.Bulletproof()) else: return # no state change await self._out_proc_range_proof(rsig) # batch state advance self.ct.cur_batch_idx += 1 self.ct.cur_output_in_batch_idx = 0
async def _on_set_outputs_ack(self, t_res): self.ct.tx.vout.append(await tmisc.parse_msg(t_res.tx_out, xmrtypes.TxOut())) self.ct.tx_out_hmacs.append(t_res.vouti_hmac) self.ct.tx_out_pk.append(await tmisc.parse_msg(t_res.out_pk, xmrtypes.CtKey())) self.ct.tx_out_ecdh.append(await tmisc.parse_msg(t_res.ecdh_info, xmrtypes.EcdhTuple())) rsig_buff = None if t_res.rsig_data: tsig_data = t_res.rsig_data if tsig_data.rsig and len(tsig_data.rsig) > 0: rsig_buff = tsig_data.rsig elif tsig_data.rsig_parts and len(tsig_data.rsig_parts) > 0: rsig_buff = b"".join(tsig_data.rsig_parts) if rsig_buff and not self._is_req_bulletproof(): rsig = await tmisc.parse_msg(rsig_buff, xmrtypes.RangeSig()) elif rsig_buff: rsig = await tmisc.parse_msg(rsig_buff, xmrtypes.Bulletproof()) else: return if self._is_req_bulletproof(): rsig.V = [] batch_size = self.ct.rsig_batches[self.ct.cur_batch_idx] for i in range(batch_size): commitment = self.ct.tx_out_pk[1 + self.ct.cur_output_idx - batch_size + i].mask commitment = crypto.scalarmult(crypto.decodepoint(commitment), crypto.sc_inv_eight()) rsig.V.append(crypto.encodepoint(commitment)) self.ct.tx_out_rsigs.append(rsig) # Rsig verification try: if not ring_ct.ver_range( C=crypto.decodepoint(self.ct.tx_out_pk[-1].mask), rsig=rsig, use_bulletproof=self._is_req_bulletproof(), ): logger.warning("Rsing not valid") except Exception as e: logger.error("Exception rsig: %s" % e) traceback.print_exc() self.ct.cur_batch_idx += 1 self.ct.cur_output_in_batch_idx = 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
def inflate_rsig(buff, rsig=None): """ Rsig binary repr -> byte encoded repr :param rsig: :return: """ if rsig is None: rsig = xmrtypes.RangeSig() rsig.Ci = [None] * 64 rsig.asig = xmrtypes.BoroSig() rsig.asig.s0 = [None] * 64 rsig.asig.s1 = [None] * 64 for i in range(64): rsig.asig.s0[i], buff = buff[:32], buff[32:] for i in range(64): rsig.asig.s1[i], buff = buff[:32], buff[32:] rsig.asig.ee, buff = buff[:32], buff[32:] for i in range(64): rsig.Ci[i], buff = buff[:32], buff[32:] return rsig
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
async def sign_transaction_data(self, tx, multisig=False, exp_tx_prefix_hash=None, use_tx_keys=None): """ Uses Trezor to sign the transaction :param tx: :type tx: xmrtypes.TxConstructionData :param multisig: :param exp_tx_prefix_hash: :param use_tx_keys: :return: """ self.ct = TData() self.ct.tx_data = tx payment_id = [] extras = await monero.parse_extra_fields(tx.extra) extra_nonce = monero.find_tx_extra_field_by_type( extras, xmrtypes.TxExtraNonce) if extra_nonce and monero.has_encrypted_payment_id(extra_nonce.nonce): payment_id = monero.get_encrypted_payment_id_from_tx_extra_nonce( extra_nonce.nonce) elif extra_nonce and monero.has_payment_id(extra_nonce.nonce): payment_id = monero.get_payment_id_from_tx_extra_nonce( extra_nonce.nonce) # Init transaction tsx_data = TsxData() tsx_data.version = 1 tsx_data.payment_id = payment_id tsx_data.unlock_time = tx.unlock_time tsx_data.outputs = tx.splitted_dsts tsx_data.change_dts = tx.change_dts tsx_data.num_inputs = len(tx.sources) tsx_data.mixin = len(tx.sources[0].outputs) tsx_data.fee = sum([x.amount for x in tx.sources]) - sum( [x.amount for x in tx.splitted_dsts]) tsx_data.account = tx.subaddr_account tsx_data.minor_indices = tx.subaddr_indices tsx_data.is_multisig = multisig tsx_data.is_bulletproof = False tsx_data.exp_tx_prefix_hash = common.defval(exp_tx_prefix_hash, b"") tsx_data.use_tx_keys = common.defval(use_tx_keys, []) self.ct.tx.unlock_time = tx.unlock_time self.ct.tsx_data = tsx_data tsx_data_pb = await tmisc.translate_tsx_data_pb(tsx_data) init_msg = MoneroTransactionInitRequest( version=0, address_n=self.address_n, network_type=self.network_type, tsx_data=tsx_data_pb, ) t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest(init=init_msg) ) # type: MoneroTransactionInitAck self.handle_error(t_res) in_memory = t_res.in_memory self.ct.tx_out_entr_hmacs = t_res.hmacs # Set transaction inputs for idx, src in enumerate(tx.sources): src_bin = await tmisc.dump_msg(src) msg = MoneroTransactionSetInputRequest(src_entr=src_bin) t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest(set_input=msg) ) # type: MoneroTransactionSetInputAck self.handle_error(t_res) vini = await tmisc.parse_msg(t_res.vini, xmrtypes.TxinToKey()) self.ct.tx.vin.append(vini) self.ct.tx_in_hmacs.append(t_res.vini_hmac) self.ct.pseudo_outs.append( (t_res.pseudo_out, t_res.pseudo_out_hmac)) self.ct.alphas.append(t_res.alpha_enc) self.ct.spend_encs.append(t_res.spend_enc) # Sort key image self.ct.source_permutation = list(range(len(tx.sources))) self.ct.source_permutation.sort( key=lambda x: self.ct.tx.vin[x].k_image, reverse=True) def swapper(x, y): self.ct.tx.vin[x], self.ct.tx.vin[y] = self.ct.tx.vin[ y], self.ct.tx.vin[x] self.ct.tx_in_hmacs[x], self.ct.tx_in_hmacs[y] = ( self.ct.tx_in_hmacs[y], self.ct.tx_in_hmacs[x], ) self.ct.pseudo_outs[x], self.ct.pseudo_outs[y] = ( self.ct.pseudo_outs[y], self.ct.pseudo_outs[x], ) self.ct.alphas[x], self.ct.alphas[y] = self.ct.alphas[ y], self.ct.alphas[x] self.ct.spend_encs[x], self.ct.spend_encs[y] = ( self.ct.spend_encs[y], self.ct.spend_encs[x], ) tx.sources[x], tx.sources[y] = tx.sources[y], tx.sources[x] common.apply_permutation(self.ct.source_permutation, swapper) if not in_memory: msg = MoneroTransactionInputsPermutationRequest( perm=self.ct.source_permutation) t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest(input_permutation=msg)) self.handle_error(t_res) # Set vin_i back - tx prefix hashing # Done only if not in-memory. if not in_memory: for idx in range(len(self.ct.tx.vin)): msg = MoneroTransactionInputViniRequest( src_entr=await tmisc.dump_msg(tx.sources[idx]), vini=await tmisc.dump_msg(self.ct.tx.vin[idx]), vini_hmac=self.ct.tx_in_hmacs[idx], pseudo_out=self.ct.pseudo_outs[idx][0] if not in_memory else None, pseudo_out_hmac=self.ct.pseudo_outs[idx][1] if not in_memory else None, ) t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest(input_vini=msg)) self.handle_error(t_res) # Set transaction outputs for idx, dst in enumerate(tx.splitted_dsts): msg = MoneroTransactionSetOutputRequest( dst_entr=await tmisc.dump_msg(dst), dst_entr_hmac=self.ct.tx_out_entr_hmacs[idx], ) t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest(set_output=msg) ) # type: MoneroTransactionSetOutputAck self.handle_error(t_res) self.ct.tx.vout.append(await tmisc.parse_msg(t_res.tx_out, xmrtypes.TxOut())) self.ct.tx_out_hmacs.append(t_res.vouti_hmac) self.ct.tx_out_rsigs.append(await tmisc.parse_msg( t_res.rsig, xmrtypes.RangeSig())) self.ct.tx_out_pk.append(await tmisc.parse_msg(t_res.out_pk, xmrtypes.CtKey())) self.ct.tx_out_ecdh.append(await tmisc.parse_msg(t_res.ecdh_info, xmrtypes.EcdhTuple())) # Rsig verification try: rsig = self.ct.tx_out_rsigs[-1] if not ring_ct.ver_range(C=None, rsig=rsig): logger.warning("Rsing not valid") except Exception as e: logger.error("Exception rsig: %s" % e) traceback.print_exc() t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest( all_out_set=MoneroTransactionAllOutSetRequest()) ) # type: MoneroTransactionAllOutSetAck self.handle_error(t_res) rv = xmrtypes.RctSig() rv.p = xmrtypes.RctSigPrunable() rv.txnFee = t_res.rv.txn_fee rv.message = t_res.rv.message rv.type = t_res.rv.rv_type self.ct.tx.extra = list(bytearray(t_res.extra)) # Verify transaction prefix hash correctness, tx hash in one pass self.ct.tx_prefix_hash = await monero.get_transaction_prefix_hash( self.ct.tx) if not common.ct_equal(t_res.tx_prefix_hash, self.ct.tx_prefix_hash): raise ValueError("Transaction prefix has does not match") # RctSig if self.is_simple(rv): if self.is_bulletproof(rv): rv.p.pseudoOuts = [x[0] for x in self.ct.pseudo_outs] else: rv.pseudoOuts = [x[0] for x in self.ct.pseudo_outs] # Range proof rv.p.rangeSigs = [] rv.outPk = [] rv.ecdhInfo = [] for idx in range(len(self.ct.tx_out_rsigs)): rv.p.rangeSigs.append(self.ct.tx_out_rsigs[idx]) rv.outPk.append(self.ct.tx_out_pk[idx]) rv.ecdhInfo.append(self.ct.tx_out_ecdh[idx]) # MLSAG message check t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest( mlsag_done=MoneroTransactionMlsagDoneRequest()) ) # type: MoneroTransactionMlsagDoneAck self.handle_error(t_res) mlsag_hash = t_res.full_message_hash mlsag_hash_computed = await monero.get_pre_mlsag_hash(rv) if not common.ct_equal(mlsag_hash, mlsag_hash_computed): raise ValueError("Pre MLSAG hash has does not match") # Sign each input couts = [] rv.p.MGs = [] for idx, src in enumerate(tx.sources): msg = MoneroTransactionSignInputRequest( await tmisc.dump_msg(src), await tmisc.dump_msg(self.ct.tx.vin[idx]), self.ct.tx_in_hmacs[idx], self.ct.pseudo_outs[idx][0] if not in_memory else None, self.ct.pseudo_outs[idx][1] if not in_memory else None, self.ct.alphas[idx], self.ct.spend_encs[idx], ) t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest(sign_input=msg) ) # type: MoneroTransactionSignInputAck self.handle_error(t_res) mg = await tmisc.parse_msg(t_res.signature, xmrtypes.MgSig()) rv.p.MGs.append(mg) couts.append(t_res.cout) self.ct.tx.signatures = [] self.ct.tx.rct_signatures = rv t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest( final_msg=MoneroTransactionFinalRequest()) ) # type: MoneroTransactionFinalAck self.handle_error(t_res) if multisig: cout_key = t_res.cout_key for ccout in couts: self.ct.couts.append( chacha_poly.decrypt(cout_key, ccout[0], ccout[1], ccout[2])) self.ct.enc_salt1, self.ct.enc_salt2 = t_res.salt, t_res.rand_mult self.ct.enc_keys = t_res.tx_enc_keys return self.ct.tx