def _build_key( secret: bytes, discriminator: bytes, index: int | None = None, out: bytes | None = None, ) -> bytes: """ Creates an unique-purpose key """ key_buff = _BUILD_KEY_BUFFER utils.ensure(len(secret) == _SECRET_LENGTH, "Invalid key length") utils.ensure(len(discriminator) <= _DISCRIMINATOR_LENGTH, "Disc too long") offset = _SECRET_LENGTH utils.memcpy(key_buff, 0, secret, 0, _SECRET_LENGTH) for i in range(_SECRET_LENGTH, len(key_buff)): key_buff[i] = 0 utils.memcpy(key_buff, offset, discriminator, 0, len(discriminator)) offset += _DISCRIMINATOR_LENGTH # fixed domain separator size if index is not None: # dump_uvarint_b_into, saving import shifted = True while shifted: shifted = index >> 7 key_buff[offset] = (index & 0x7F) | (0x80 if shifted else 0x00) offset += 1 index = shifted return crypto_helpers.keccak_2hash(key_buff, out)
def _build_key(secret, discriminator=None, index: int = None, out: bytes = None) -> bytes: """ Creates an unique-purpose key """ key_buff = BUILD_KEY_BUFFER # bytearray(32 + 12 + 4) # key + disc + index utils.ensure(len(secret) == 32, "Invalid key length") utils.ensure(len(discriminator) <= 12, "Disc too long") offset = 32 utils.memcpy(key_buff, 0, secret, 0, 32) for i in range(32, len(key_buff)): key_buff[i] = 0 if discriminator is not None: utils.memcpy(key_buff, offset, discriminator, 0, len(discriminator)) offset += len(discriminator) if index is not None: # dump_uvarint_b_into, saving import shifted = True while shifted: shifted = index >> 7 key_buff[offset] = (index & 0x7F) | (0x80 if shifted else 0x00) offset += 1 index = shifted return crypto.keccak_2hash(key_buff, out)
async def send_cmd(cmd: Cmd, iface: io.HID) -> None: init_desc = frame_init() cont_desc = frame_cont() offset = 0 seq = 0 datalen = len(cmd.data) buf, frm = make_struct(init_desc) frm.cid = cmd.cid frm.cmd = cmd.cmd frm.bcnt = datalen offset += utils.memcpy(frm.data, 0, cmd.data, offset, datalen) iface.write(buf) if offset < datalen: frm = overlay_struct(buf, cont_desc) write = loop.wait(iface.iface_num() | io.POLL_WRITE) while offset < datalen: frm.seq = seq offset += utils.memcpy(frm.data, 0, cmd.data, offset, datalen) while True: await write if iface.write(buf) > 0: break seq += 1
def msg_authenticate_sign(challenge: bytes, app_id: bytes, privkey: bytes) -> bytes: flags = bytes([_AUTH_FLAG_TUP]) # get next counter ctr = storage.next_u2f_counter() ctrbuf = ustruct.pack(">L", ctr) # hash input data together with counter dig = hashlib.sha256() dig.update(app_id) # uint8_t appId[32]; dig.update(flags) # uint8_t flags; dig.update(ctrbuf) # uint8_t ctr[4]; dig.update(challenge) # uint8_t chal[32]; dig = dig.digest() # sign the digest and convert to der sig = nist256p1.sign(privkey, dig, False) sig = der.encode_seq((sig[1:33], sig[33:])) # pack to a response buf, resp = make_struct(resp_cmd_authenticate(len(sig))) resp.flags = flags[0] resp.ctr = ctr utils.memcpy(resp.sig, 0, sig, 0, len(sig)) resp.status = _SW_NO_ERROR return buf
def send_cmd(cmd: Cmd, iface: io.HID) -> None: init_desc = frame_init() cont_desc = frame_cont() offset = 0 seq = 0 datalen = len(cmd.data) buf, frm = make_struct(init_desc) frm.cid = cmd.cid frm.cmd = cmd.cmd frm.bcnt = datalen offset += utils.memcpy(frm.data, 0, cmd.data, offset, datalen) iface.write(buf) # log.debug(__name__, 'send init %s', buf) if offset < datalen: frm = overlay_struct(buf, cont_desc) while offset < datalen: frm.seq = seq offset += utils.memcpy(frm.data, 0, cmd.data, offset, datalen) utime.sleep_ms(1) # FIXME: async write iface.write(buf) # log.debug(__name__, 'send cont %s', buf) seq += 1
async def read_cmd(iface: io.HID) -> Cmd: desc_init = frame_init() desc_cont = frame_cont() read = loop.wait(iface.iface_num() | io.POLL_READ) buf = await read ifrm = overlay_struct(buf, desc_init) bcnt = ifrm.bcnt data = ifrm.data datalen = len(data) seq = 0 if ifrm.cmd & _TYPE_MASK == _TYPE_CONT: # unexpected cont packet, abort current msg if __debug__: log.warning(__name__, "_TYPE_CONT") return None if datalen < bcnt: databuf = bytearray(bcnt) utils.memcpy(databuf, 0, data, 0, bcnt) data = databuf else: data = data[:bcnt] while datalen < bcnt: buf = await read cfrm = overlay_struct(buf, desc_cont) if cfrm.seq == _CMD_INIT: # _CMD_INIT frame, cancels current channel ifrm = overlay_struct(buf, desc_init) data = ifrm.data[: ifrm.bcnt] break if cfrm.cid != ifrm.cid: # cont frame for a different channel, reply with BUSY and skip if __debug__: log.warning(__name__, "_ERR_CHANNEL_BUSY") await send_cmd(cmd_error(cfrm.cid, _ERR_CHANNEL_BUSY), iface) continue if cfrm.seq != seq: # cont frame for this channel, but incorrect seq number, abort # current msg if __debug__: log.warning(__name__, "_ERR_INVALID_SEQ") await send_cmd(cmd_error(cfrm.cid, _ERR_INVALID_SEQ), iface) return None datalen += utils.memcpy(data, datalen, cfrm.data, 0, bcnt - datalen) seq += 1 return Cmd(ifrm.cid, ifrm.cmd, data)
def msg_register_sign(challenge: bytes, app_id: bytes) -> bytes: from apps.common import seed # derivation path is m/U2F'/r'/r'/r'/r'/r'/r'/r'/r' keypath = [0x80000000 | random.uniform(0xf0000000) for _ in range(0, 8)] nodepath = [_U2F_KEY_PATH] + keypath # prepare signing key from random path, compute decompressed public key node = seed.get_root_without_passphrase('nist256p1') node.derive_path(nodepath) pubkey = nist256p1.publickey(node.private_key(), False) # first half of keyhandle is keypath keybuf = ustruct.pack('>8L', *keypath) # second half of keyhandle is a hmac of app_id and keypath keybase = hmac.Hmac(node.private_key(), app_id, hashlib.sha256) keybase.update(keybuf) keybase = keybase.digest() # hash the request data together with keyhandle and pubkey dig = hashlib.sha256() dig.update(b'\x00') # uint8_t reserved; dig.update(app_id) # uint8_t appId[32]; dig.update(challenge) # uint8_t chal[32]; dig.update(keybuf) # uint8_t keyHandle[64]; dig.update(keybase) dig.update(pubkey) # uint8_t pubKey[65]; dig = dig.digest() # sign the digest and convert to der sig = nist256p1.sign(_U2F_ATT_PRIV_KEY, dig, False) sig = der.encode_seq((sig[1:33], sig[33:])) # pack to a response buf, resp = make_struct( resp_cmd_register( len(keybuf) + len(keybase), len(_U2F_ATT_CERT), len(sig))) resp.registerId = _U2F_REGISTER_ID utils.memcpy(resp.pubKey, 0, pubkey, 0, len(pubkey)) resp.keyHandleLen = len(keybuf) + len(keybase) utils.memcpy(resp.keyHandle, 0, keybuf, 0, len(keybuf)) utils.memcpy(resp.keyHandle, len(keybuf), keybase, 0, len(keybase)) utils.memcpy(resp.cert, 0, _U2F_ATT_CERT, 0, len(_U2F_ATT_CERT)) utils.memcpy(resp.sig, 0, sig, 0, len(sig)) resp.status = _SW_NO_ERROR return buf
async def write_message(iface: WireInterface, mtype: int, mdata: bytes) -> None: write = loop.wait(iface.iface_num() | io.POLL_WRITE) # gather data from msg msize = len(mdata) # prepare the report buffer with header data report = bytearray(_REP_LEN) repofs = _REP_INIT_DATA ustruct.pack_into(_REP_INIT, report, 0, _REP_MARKER, _REP_MAGIC, _REP_MAGIC, mtype, msize) nwritten = 0 while True: # copy as much as possible to the report buffer nwritten += utils.memcpy(report, repofs, mdata, nwritten) # write the report while True: await write n = iface.write(report) if n == len(report): break # if we have more data to write, use continuation reports for it if nwritten < msize: repofs = _REP_CONT_DATA else: break
def _serialize_ecdh(ecdh_info, v2=False): """ Serializes ECDH according to the current format defined by the hard fork version or the signature format respectively. """ if v2: # In HF10 the amount is serialized to 8B and mask is deterministic ecdh_info_bin = bytearray(8) ecdh_info_bin[:] = ecdh_info.amount[0:8] return ecdh_info_bin else: ecdh_info_bin = bytearray(64) utils.memcpy(ecdh_info_bin, 0, ecdh_info.mask, 0, 32) utils.memcpy(ecdh_info_bin, 32, ecdh_info.amount, 0, 32) return ecdh_info_bin
async def areadinto(self, buf): ''' Read exactly `len(buf)` bytes into `buf`, waiting for additional reports, if needed. Raises `EOFError` if end-of-message is encountered before the full read can be completed. ''' if self.size < len(buf): raise EOFError read = loop.select(self.iface.iface_num() | io.POLL_READ) nread = 0 while nread < len(buf): if self.ofs == len(self.data): # we are at the end of received data # wait for continuation report while True: report = await read marker = report[0] if marker == _REP_MARKER: break self.data = report[_REP_CONT_DATA:_REP_CONT_DATA + self.size] self.ofs = 0 # copy as much as possible to target buffer nbytes = utils.memcpy(buf, nread, self.data, self.ofs, len(buf)) nread += nbytes self.ofs += nbytes self.size -= nbytes return nread
def _dump_rsig_lr(buff: bytearray, offset: int, rsig: Bulletproof | BulletproofPlus) -> int: buff[offset] = len(rsig.L) offset += 1 for x in rsig.L: utils.memcpy(buff, offset, x, 0, 32) offset += 32 buff[offset] = len(rsig.R) offset += 1 for x in rsig.R: utils.memcpy(buff, offset, x, 0, 32) offset += 32 return offset
async def awrite(self, buf): ''' Encode and write every byte from `buf`. Does not need to be called in case message has zero length. Raises `EOFError` if the length of `buf` exceeds the remaining message length. ''' if self.size < len(buf): raise EOFError write = loop.select(self.iface.iface_num() | io.POLL_WRITE) nwritten = 0 while nwritten < len(buf): # copy as much as possible to report buffer nbytes = utils.memcpy(self.data, self.ofs, buf, nwritten, len(buf)) nwritten += nbytes self.ofs += nbytes self.size -= nbytes if self.ofs == _REP_LEN: # we are at the end of the report, flush it await write self.iface.write(self.data) self.ofs = _REP_CONT_DATA return nwritten
async def read_into(self, dst): ''' Read exactly `len(dst)` bytes into writable buffer-like `dst`. Raises `EOFError` if the internal limit was reached or the backing IO strategy signalled an EOF. ''' n = len(dst) if self._limit is not None: if self._limit < n: raise EOFError() self._limit -= n buf = self._buffer ofs = self._ofs i = 0 while i < n: if ofs >= len(buf): buf = yield ofs = 0 # memcpy caps on the buffer lengths, no need for exact byte count nb = memcpy(dst, i, buf, ofs, n) ofs += nb i += nb self._buffer = buf self._ofs = ofs
async def read_message(iface: WireInterface, buffer: utils.BufferType) -> Message: read = loop.wait(iface.iface_num() | io.POLL_READ) # wait for initial report report = await read if report[0] != _REP_MARKER: raise CodecError("Invalid magic") _, magic1, magic2, mtype, msize = ustruct.unpack(_REP_INIT, report) if magic1 != _REP_MAGIC or magic2 != _REP_MAGIC: raise CodecError("Invalid magic") read_and_throw_away = False with buffer_lock: if msize > len(buffer): # allocate a new buffer to fit the message try: mdata: utils.BufferType = bytearray(msize) except MemoryError: mdata = bytearray(_REP_LEN) read_and_throw_away = True else: # reuse a part of the supplied buffer mdata = memoryview(buffer)[:msize] # buffer the initial data nread = utils.memcpy(mdata, 0, report, _REP_INIT_DATA) while nread < msize: # wait for continuation report report = await read if report[0] != _REP_MARKER: raise CodecError("Invalid magic") # buffer the continuation data if read_and_throw_away: nread += len(report) - 1 else: nread += utils.memcpy(mdata, nread, report, _REP_CONT_DATA) if read_and_throw_away: raise CodecError("Message too large") return Message(mtype, mdata)
def _set_tx_extra(state: State) -> bytes: """ Sets tx public keys into transaction's extra. Extra field is supposed to be sorted (by sort_tx_extra() in the Monero) Tag ordering: TX_EXTRA_TAG_PUBKEY, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS, TX_EXTRA_NONCE """ from apps.monero.xmr.serialize import int_serialize # Extra buffer length computation # TX_EXTRA_TAG_PUBKEY (1B) | tx_pub_key (32B) extra_size = 33 offset = 0 num_keys = 0 len_size = 0 if state.need_additional_txkeys: num_keys = len(state.additional_tx_public_keys) len_size = int_serialize.uvarint_size(num_keys) # TX_EXTRA_TAG_ADDITIONAL_PUBKEYS (1B) | varint | keys extra_size += 1 + len_size + 32 * num_keys if state.extra_nonce: extra_size += len(state.extra_nonce) extra = bytearray(extra_size) extra[0] = 1 # TX_EXTRA_TAG_PUBKEY crypto.encodepoint_into(memoryview(extra)[1:], state.tx_pub) offset += 33 if state.need_additional_txkeys: extra[offset] = 0x4 # TX_EXTRA_TAG_ADDITIONAL_PUBKEYS int_serialize.dump_uvarint_b_into(num_keys, extra, offset + 1) offset += 1 + len_size for idx in range(num_keys): extra[offset:offset + 32] = state.additional_tx_public_keys[idx] offset += 32 if state.extra_nonce: utils.memcpy(extra, offset, state.extra_nonce, 0, len(state.extra_nonce)) state.extra_nonce = None return extra
def cmd_init(req: Cmd) -> Cmd: if req.cid == 0: return cmd_error(req.cid, _ERR_INVALID_CID) elif req.cid == _CID_BROADCAST: # uint32_t except 0 and 0xffffffff resp_cid = random.uniform(0xFFFFFFFE) + 1 else: resp_cid = req.cid buf, resp = make_struct(resp_cmd_init()) utils.memcpy(resp.nonce, 0, req.data, 0, len(req.data)) resp.cid = resp_cid resp.versionInterface = _U2FHID_IF_VERSION resp.versionMajor = 2 resp.versionMinor = 0 resp.versionBuild = 0 resp.capFlags = _CAPFLAG_WINK return Cmd(req.cid, req.cmd, buf)
async def all_inputs_set(state: State): state.mem_trace(0) await confirms.transaction_step(state.ctx, state.STEP_ALL_IN) from trezor.messages.MoneroTransactionAllInputsSetAck import ( MoneroTransactionAllInputsSetAck, ) from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData # Generate random commitment masks to be used in range proofs. # If SimpleRCT is used the sum of the masks must match the input masks sum. state.sumout = crypto.sc_init(0) for i in range(state.output_count): cur_mask = crypto.new_scalar() # new mask for each output is_last = i + 1 == state.output_count if is_last and state.rct_type == RctType.Simple: # in SimpleRCT the last mask needs to be calculated as an offset of the sum crypto.sc_sub_into(cur_mask, state.sumpouts_alphas, state.sumout) else: crypto.random_scalar(cur_mask) crypto.sc_add_into(state.sumout, state.sumout, cur_mask) state.output_masks.append(cur_mask) if state.rct_type == RctType.Simple: utils.ensure(crypto.sc_eq(state.sumout, state.sumpouts_alphas), "Invalid masks sum") # sum check state.sumout = crypto.sc_init(0) rsig_data = MoneroTransactionRsigData() resp = MoneroTransactionAllInputsSetAck(rsig_data=rsig_data) # If range proofs are being offloaded, we send the masks to the host, which uses them # to create the range proof. If not, we do not send any and we use them in the following step. if state.rsig_offload: tmp_buff = bytearray(32) rsig_data.mask = bytearray(32 * state.output_count) for i in range(state.output_count): crypto.encodeint_into(tmp_buff, state.output_masks[i]) utils.memcpy(rsig_data.mask, 32 * i, tmp_buff, 0, 32) return resp
def _build_key(secret, discriminator=None, index: int = None) -> bytes: """ Creates an unique-purpose key """ key_buff = bytearray(32 + 12 + 4) # key + disc + index offset = 32 utils.memcpy(key_buff, 0, secret, 0, len(secret)) if discriminator is not None: utils.memcpy(key_buff, offset, discriminator, 0, len(discriminator)) offset += len(discriminator) if index is not None: # dump_uvarint_b_into, saving import shifted = True while shifted: shifted = index >> 7 key_buff[offset] = (index & 0x7F) | (0x80 if shifted else 0x00) offset += 1 index = shifted return crypto.keccak_2hash(key_buff)
def _get_ecdh_info_and_out_pk(state: State, tx_out_key, amount, mask, amount_key): """ Calculates the Pedersen commitment C = aG + bH and returns it as CtKey. Also encodes the two items - `mask` and `amount` - into ecdh info, so the recipient is able to reconstruct the commitment. """ out_pk_dest = crypto.encodepoint(tx_out_key) out_pk_commitment = crypto.encodepoint(crypto.gen_commitment(mask, amount)) state.sumout = crypto.sc_add(state.sumout, mask) state.output_sk_masks.append(mask) # masking of mask and amount ecdh_info = _ecdh_encode(mask, amount, crypto.encodeint(amount_key)) # Manual ECDH info serialization ecdh_info_bin = bytearray(64) utils.memcpy(ecdh_info_bin, 0, ecdh_info.mask, 0, 32) utils.memcpy(ecdh_info_bin, 32, ecdh_info.amount, 0, 32) gc.collect() return out_pk_dest, out_pk_commitment, ecdh_info_bin
def _dump_rsig_bp_plus(rsig: BulletproofPlus) -> bytes: if len(rsig.L) > 127: raise ValueError("Too large") # Manual serialization as the generic purpose serialize.dump_msg_gc # is more memory intensive which is not desired in the range proof section. # BP: "V", "A", "A1", "B", "r1", "s1", "d1", "V", "L", "R" # Commitment vector V is not serialized # Vector size under 127 thus varint occupies 1 B buff_size = 32 * (6 + 2 * (len(rsig.L))) + 2 buff = bytearray(buff_size) utils.memcpy(buff, 0, rsig.A, 0, 32) utils.memcpy(buff, 32, rsig.A1, 0, 32) utils.memcpy(buff, 32 * 2, rsig.B, 0, 32) utils.memcpy(buff, 32 * 3, rsig.r1, 0, 32) utils.memcpy(buff, 32 * 4, rsig.s1, 0, 32) utils.memcpy(buff, 32 * 5, rsig.d1, 0, 32) _dump_rsig_lr(buff, 32 * 6, rsig) return buff
def _dump_rsig_bp(rsig: Bulletproof) -> bytes: if len(rsig.L) > 127: raise ValueError("Too large") # Manual serialization as the generic purpose serialize.dump_msg_gc # is more memory intensive which is not desired in the range proof section. # BP: V, A, S, T1, T2, taux, mu, L, R, a, b, t # Commitment vector V is not serialized # Vector size under 127 thus varint occupies 1 B buff_size = 32 * (9 + 2 * (len(rsig.L))) + 2 buff = bytearray(buff_size) utils.memcpy(buff, 0, rsig.A, 0, 32) utils.memcpy(buff, 32, rsig.S, 0, 32) utils.memcpy(buff, 32 * 2, rsig.T1, 0, 32) utils.memcpy(buff, 32 * 3, rsig.T2, 0, 32) utils.memcpy(buff, 32 * 4, rsig.taux, 0, 32) utils.memcpy(buff, 32 * 5, rsig.mu, 0, 32) offset = _dump_rsig_lr(buff, 32 * 6, rsig) utils.memcpy(buff, offset, rsig.a, 0, 32) offset += 32 utils.memcpy(buff, offset, rsig.b, 0, 32) offset += 32 utils.memcpy(buff, offset, rsig.t, 0, 32) return buff
async def set_output( state: State, dst_entr: MoneroTransactionDestinationEntry, dst_entr_hmac: bytes, rsig_data: MoneroTransactionRsigData, is_offloaded_bp=False, ) -> MoneroTransactionSetOutputAck: state.mem_trace(0, True) mods = utils.unimport_begin() # Progress update only for master message (skip for offloaded BP msg) if not is_offloaded_bp: await layout.transaction_step(state, state.STEP_OUT, state.current_output_index + 1) state.mem_trace(1, True) dst_entr = _validate(state, dst_entr, dst_entr_hmac, is_offloaded_bp) state.mem_trace(2, True) if not state.is_processing_offloaded: # First output - we include the size of the container into the tx prefix hasher if state.current_output_index == 0: state.tx_prefix_hasher.uvarint(state.output_count) state.mem_trace(4, True) state.output_amounts.append(dst_entr.amount) state.summary_outs_money += dst_entr.amount utils.unimport_end(mods) state.mem_trace(5, True) # Compute tx keys and masks if applicable tx_out_key, amount_key, derivation = _compute_tx_keys(state, dst_entr) utils.unimport_end(mods) state.mem_trace(6, True) # Range proof first, memory intensive (fragmentation) rsig_data_new, mask = _range_proof(state, rsig_data) utils.unimport_end(mods) state.mem_trace(7, True) # If det masks & offloading, return as we are handling offloaded BP. if state.is_processing_offloaded: from trezor.messages import MoneroTransactionSetOutputAck return MoneroTransactionSetOutputAck() # Tx header prefix hashing, hmac dst_entr tx_out_bin, hmac_vouti = _set_out_tx_out( state, dst_entr, tx_out_key, derivation, ) del (derivation, ) state.mem_trace(11, True) out_pk_dest, out_pk_commitment, ecdh_info_bin = _get_ecdh_info_and_out_pk( state=state, tx_out_key=tx_out_key, amount=dst_entr.amount, mask=mask, amount_key=amount_key, ) del (dst_entr, mask, amount_key, tx_out_key) state.mem_trace(12, True) # Incremental hashing of the ECDH info. # RctSigBase allows to hash only one of the (ecdh, out_pk) as they are serialized # as whole vectors. We choose to hash ECDH first, because it saves state space. state.full_message_hasher.set_ecdh(ecdh_info_bin) state.mem_trace(13, True) # output_pk_commitment is stored to the state as it is used during the signature and hashed to the # RctSigBase later. No need to store amount, it was already stored. state.output_pk_commitments.append(out_pk_commitment) state.last_step = state.STEP_OUT state.mem_trace(14, True) from trezor.messages import MoneroTransactionSetOutputAck out_pk_bin = bytearray(64) utils.memcpy(out_pk_bin, 0, out_pk_dest, 0, 32) utils.memcpy(out_pk_bin, 32, out_pk_commitment, 0, 32) return MoneroTransactionSetOutputAck( tx_out=tx_out_bin, vouti_hmac=hmac_vouti, rsig_data=rsig_data_new, out_pk=out_pk_bin, ecdh_info=ecdh_info_bin, )
def generate_ring_signature( prefix_hash: bytes, image: Ge25519, pubs: list[Ge25519], sec: Sc25519, sec_idx: int, test: bool = False, ) -> Sig: """ Generates ring signature with key image. void crypto_ops::generate_ring_signature() """ from trezor.utils import memcpy 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.check_ed25519point(k) 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 _ 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 = pubs[i] tmp2 = crypto.ge25519_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.ge25519_double_scalarmult_vartime2( sig[i][1], tmp3, sig[i][0], image) 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 set_output(state: State, dst_entr, dst_entr_hmac, rsig_data): state.mem_trace(0, True) mods = utils.unimport_begin() await confirms.transaction_step(state.ctx, state.STEP_OUT, state.current_output_index + 1, state.output_count) state.mem_trace(1) state.current_output_index += 1 state.mem_trace(2, True) await _validate(state, dst_entr, dst_entr_hmac) # First output - we include the size of the container into the tx prefix hasher if state.current_output_index == 0: state.tx_prefix_hasher.uvarint(state.output_count) state.mem_trace(4, True) state.output_amounts.append(dst_entr.amount) state.summary_outs_money += dst_entr.amount utils.unimport_end(mods) state.mem_trace(5, True) # Range proof first, memory intensive rsig, mask = _range_proof(state, dst_entr.amount, rsig_data) utils.unimport_end(mods) state.mem_trace(6, True) # additional tx key if applicable additional_txkey_priv = _set_out_additional_keys(state, dst_entr) # derivation = a*R or r*A or s*C derivation = _set_out_derivation(state, dst_entr, additional_txkey_priv) # amount key = H_s(derivation || i) amount_key = crypto.derivation_to_scalar(derivation, state.current_output_index) # one-time destination address P = H_s(derivation || i)*G + B tx_out_key = crypto.derive_public_key( derivation, state.current_output_index, crypto.decodepoint(dst_entr.addr.spend_public_key), ) del (derivation, additional_txkey_priv) state.mem_trace(7, True) # Tx header prefix hashing, hmac dst_entr tx_out_bin, hmac_vouti = await _set_out_tx_out(state, dst_entr, tx_out_key) state.mem_trace(11, True) out_pk_dest, out_pk_commitment, ecdh_info_bin = _get_ecdh_info_and_out_pk( state=state, tx_out_key=tx_out_key, amount=dst_entr.amount, mask=mask, amount_key=amount_key, ) del (dst_entr, mask, amount_key, tx_out_key) state.mem_trace(12, True) # Incremental hashing of the ECDH info. # RctSigBase allows to hash only one of the (ecdh, out_pk) as they are serialized # as whole vectors. We choose to hash ECDH first, because it saves state space. state.full_message_hasher.set_ecdh(ecdh_info_bin) state.mem_trace(13, True) # output_pk_commitment is stored to the state as it is used during the signature and hashed to the # RctSigBase later. No need to store amount, it was already stored. state.output_pk_commitments.append(out_pk_commitment) state.mem_trace(14, True) from trezor.messages.MoneroTransactionSetOutputAck import ( MoneroTransactionSetOutputAck, ) out_pk_bin = bytearray(64) utils.memcpy(out_pk_bin, 0, out_pk_dest, 0, 32) utils.memcpy(out_pk_bin, 32, out_pk_commitment, 0, 32) return MoneroTransactionSetOutputAck( tx_out=tx_out_bin, vouti_hmac=hmac_vouti, rsig_data=_return_rsig_data(rsig), out_pk=out_pk_bin, ecdh_info=ecdh_info_bin, )