mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-18 02:22:01 +00:00
feat(xmr): add support for HF15, BP+
This commit is contained in:
parent
14e08ca9db
commit
5e6582a3fe
@ -172,7 +172,7 @@ message MoneroTransactionSetInputRequest {
|
|||||||
/**
|
/**
|
||||||
* Response: Response to setting UTXO for signature. Contains sealed values needed for further protocol steps.
|
* Response: Response to setting UTXO for signature. Contains sealed values needed for further protocol steps.
|
||||||
* @next MoneroTransactionSetInputAck
|
* @next MoneroTransactionSetInputAck
|
||||||
* @next MoneroTransactionInputsPermutationRequest
|
* @next MoneroTransactionInputViniRequest
|
||||||
*/
|
*/
|
||||||
message MoneroTransactionSetInputAck {
|
message MoneroTransactionSetInputAck {
|
||||||
optional bytes vini = 1; // xmrtypes.TxinToKey
|
optional bytes vini = 1; // xmrtypes.TxinToKey
|
||||||
@ -183,21 +183,6 @@ message MoneroTransactionSetInputAck {
|
|||||||
optional bytes spend_key = 6;
|
optional bytes spend_key = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Request: Sub request of MoneroTransactionSign. Permutation on key images.
|
|
||||||
* @next MoneroTransactionInputsPermutationAck
|
|
||||||
*/
|
|
||||||
message MoneroTransactionInputsPermutationRequest {
|
|
||||||
repeated uint32 perm = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response: Response to setting permutation on key images
|
|
||||||
* @next MoneroTransactionInputViniRequest
|
|
||||||
*/
|
|
||||||
message MoneroTransactionInputsPermutationAck {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request: Sub request of MoneroTransactionSign. Sends one UTXO to device together with sealed values.
|
* Request: Sub request of MoneroTransactionSign. Sends one UTXO to device together with sealed values.
|
||||||
* @next MoneroTransactionInputViniAck
|
* @next MoneroTransactionInputViniAck
|
||||||
@ -327,7 +312,7 @@ message MoneroTransactionFinalAck {
|
|||||||
optional bytes salt = 2;
|
optional bytes salt = 2;
|
||||||
optional bytes rand_mult = 3;
|
optional bytes rand_mult = 3;
|
||||||
optional bytes tx_enc_keys = 4;
|
optional bytes tx_enc_keys = 4;
|
||||||
optional bytes opening_key = 5; // enc master key to decrypt MLSAGs after protocol finishes correctly
|
optional bytes opening_key = 5; // enc master key to decrypt CLSAGs after protocol finishes correctly
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -297,8 +297,6 @@ enum MessageType {
|
|||||||
MessageType_MoneroTransactionInitAck = 502 [(wire_out) = true];
|
MessageType_MoneroTransactionInitAck = 502 [(wire_out) = true];
|
||||||
MessageType_MoneroTransactionSetInputRequest = 503 [(wire_out) = true];
|
MessageType_MoneroTransactionSetInputRequest = 503 [(wire_out) = true];
|
||||||
MessageType_MoneroTransactionSetInputAck = 504 [(wire_out) = true];
|
MessageType_MoneroTransactionSetInputAck = 504 [(wire_out) = true];
|
||||||
MessageType_MoneroTransactionInputsPermutationRequest = 505 [(wire_out) = true];
|
|
||||||
MessageType_MoneroTransactionInputsPermutationAck = 506 [(wire_out) = true];
|
|
||||||
MessageType_MoneroTransactionInputViniRequest = 507 [(wire_out) = true];
|
MessageType_MoneroTransactionInputViniRequest = 507 [(wire_out) = true];
|
||||||
MessageType_MoneroTransactionInputViniAck = 508 [(wire_out) = true];
|
MessageType_MoneroTransactionInputViniAck = 508 [(wire_out) = true];
|
||||||
MessageType_MoneroTransactionAllInputsSetRequest = 509 [(wire_out) = true];
|
MessageType_MoneroTransactionAllInputsSetRequest = 509 [(wire_out) = true];
|
||||||
|
@ -192,15 +192,6 @@ After receiving this message:
|
|||||||
|
|
||||||
Trezor computes spending keys, `TxinToKey`, `pseudo_out`, HMACs for offloaded data
|
Trezor computes spending keys, `TxinToKey`, `pseudo_out`, HMACs for offloaded data
|
||||||
|
|
||||||
### `MoneroTransactionInputsPermutationRequest` (Deprecated)
|
|
||||||
|
|
||||||
UTXOs have to be sorted by the key image in the valid blockchain transaction.
|
|
||||||
This message caries permutation on the key images so they are sorted in the desired way.
|
|
||||||
|
|
||||||
In Client version 3+ sending the permutation is deprecated. Original sort index is sent from the host
|
|
||||||
when needed (to verify HMACs built on the original ordering). Moreover, permutation correctness is checked by
|
|
||||||
the set size, HMAC validity and strict ordering on the key images.
|
|
||||||
|
|
||||||
### `MoneroTransactionInputViniRequest`
|
### `MoneroTransactionInputViniRequest`
|
||||||
|
|
||||||
- Step needed to correctly hash all transaction inputs, in the right order (permutation computed in the previous step).
|
- Step needed to correctly hash all transaction inputs, in the right order (permutation computed in the previous step).
|
||||||
|
@ -59,7 +59,6 @@ async def sign_tx_dispatch(state: State, msg, keychain: Keychain) -> tuple:
|
|||||||
await step_02_set_input.set_input(state, msg.src_entr),
|
await step_02_set_input.set_input(state, msg.src_entr),
|
||||||
(
|
(
|
||||||
MessageType.MoneroTransactionSetInputRequest,
|
MessageType.MoneroTransactionSetInputRequest,
|
||||||
MessageType.MoneroTransactionInputsPermutationRequest,
|
|
||||||
MessageType.MoneroTransactionInputViniRequest,
|
MessageType.MoneroTransactionInputViniRequest,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -17,6 +17,8 @@ class RctType:
|
|||||||
"""
|
"""
|
||||||
There are several types of monero Ring Confidential Transactions
|
There are several types of monero Ring Confidential Transactions
|
||||||
like RCTTypeFull and RCTTypeSimple but currently we use only CLSAG
|
like RCTTypeFull and RCTTypeSimple but currently we use only CLSAG
|
||||||
|
and RCTTypeBulletproofPlus
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CLSAG = 5
|
CLSAG = 5
|
||||||
|
RCTTypeBulletproofPlus = 6
|
||||||
|
@ -103,6 +103,7 @@ class State:
|
|||||||
self.rsig_grouping: list[int] | None = []
|
self.rsig_grouping: list[int] | None = []
|
||||||
# is range proof computing offloaded or not
|
# is range proof computing offloaded or not
|
||||||
self.rsig_offload: bool | None = False
|
self.rsig_offload: bool | None = False
|
||||||
|
self.rsig_is_bp_plus: bool | None = False
|
||||||
|
|
||||||
# sum of all inputs' pseudo out masks
|
# sum of all inputs' pseudo out masks
|
||||||
self.sumpouts_alphas: Scalar = crypto.Scalar(0)
|
self.sumpouts_alphas: Scalar = crypto.Scalar(0)
|
||||||
|
@ -61,7 +61,6 @@ async def init_transaction(
|
|||||||
state.fee = tsx_data.fee
|
state.fee = tsx_data.fee
|
||||||
state.account_idx = tsx_data.account
|
state.account_idx = tsx_data.account
|
||||||
state.last_step = state.STEP_INIT
|
state.last_step = state.STEP_INIT
|
||||||
state.tx_type = signing.RctType.CLSAG
|
|
||||||
if tsx_data.hard_fork:
|
if tsx_data.hard_fork:
|
||||||
state.hard_fork = tsx_data.hard_fork
|
state.hard_fork = tsx_data.hard_fork
|
||||||
if state.hard_fork < 13:
|
if state.hard_fork < 13:
|
||||||
@ -208,6 +207,11 @@ def _check_rsig_data(state: State, rsig_data: MoneroTransactionRsigData) -> None
|
|||||||
elif rsig_data.rsig_type not in (1, 2, 3):
|
elif rsig_data.rsig_type not in (1, 2, 3):
|
||||||
raise ValueError("Unknown rsig type")
|
raise ValueError("Unknown rsig type")
|
||||||
|
|
||||||
|
state.tx_type = signing.RctType.CLSAG
|
||||||
|
if rsig_data.bp_version == 4:
|
||||||
|
state.rsig_is_bp_plus = True
|
||||||
|
state.tx_type = signing.RctType.RCTTypeBulletproofPlus
|
||||||
|
|
||||||
if state.output_count > 2:
|
if state.output_count > 2:
|
||||||
state.rsig_offload = True
|
state.rsig_offload = True
|
||||||
|
|
||||||
|
@ -15,7 +15,10 @@ from .state import State
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple
|
from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple
|
||||||
from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof
|
from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import (
|
||||||
|
Bulletproof,
|
||||||
|
BulletproofPlus,
|
||||||
|
)
|
||||||
from trezor.messages import (
|
from trezor.messages import (
|
||||||
MoneroTransactionDestinationEntry,
|
MoneroTransactionDestinationEntry,
|
||||||
MoneroTransactionSetOutputAck,
|
MoneroTransactionSetOutputAck,
|
||||||
@ -303,7 +306,7 @@ def _rsig_bp(state: State) -> bytes:
|
|||||||
from apps.monero.xmr import range_signatures
|
from apps.monero.xmr import range_signatures
|
||||||
|
|
||||||
rsig = range_signatures.prove_range_bp_batch(
|
rsig = range_signatures.prove_range_bp_batch(
|
||||||
state.output_amounts, state.output_masks
|
state.output_amounts, state.output_masks, state.rsig_is_bp_plus
|
||||||
)
|
)
|
||||||
state.mem_trace("post-bp" if __debug__ else None, collect=True)
|
state.mem_trace("post-bp" if __debug__ else None, collect=True)
|
||||||
|
|
||||||
@ -314,7 +317,7 @@ def _rsig_bp(state: State) -> bytes:
|
|||||||
state.full_message_hasher.rsig_val(rsig, raw=False)
|
state.full_message_hasher.rsig_val(rsig, raw=False)
|
||||||
state.mem_trace("post-bp-hash" if __debug__ else None, collect=True)
|
state.mem_trace("post-bp-hash" if __debug__ else None, collect=True)
|
||||||
|
|
||||||
rsig = _dump_rsig_bp(rsig)
|
rsig = _dump_rsig_bp_plus(rsig) if state.rsig_is_bp_plus else _dump_rsig_bp(rsig)
|
||||||
state.mem_trace(
|
state.mem_trace(
|
||||||
f"post-bp-ser, size: {len(rsig)}" if __debug__ else None, collect=True
|
f"post-bp-ser, size: {len(rsig)}" if __debug__ else None, collect=True
|
||||||
)
|
)
|
||||||
@ -327,8 +330,14 @@ def _rsig_bp(state: State) -> bytes:
|
|||||||
|
|
||||||
def _rsig_process_bp(state: State, rsig_data: MoneroTransactionRsigData):
|
def _rsig_process_bp(state: State, rsig_data: MoneroTransactionRsigData):
|
||||||
from apps.monero.xmr import range_signatures
|
from apps.monero.xmr import range_signatures
|
||||||
from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof
|
from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import (
|
||||||
|
Bulletproof,
|
||||||
|
BulletproofPlus,
|
||||||
|
)
|
||||||
|
|
||||||
|
if state.rsig_is_bp_plus:
|
||||||
|
bp_obj = serialize.parse_msg(rsig_data.rsig, BulletproofPlus)
|
||||||
|
else:
|
||||||
bp_obj = serialize.parse_msg(rsig_data.rsig, Bulletproof)
|
bp_obj = serialize.parse_msg(rsig_data.rsig, Bulletproof)
|
||||||
rsig_data.rsig = None
|
rsig_data.rsig = None
|
||||||
|
|
||||||
@ -366,8 +375,45 @@ def _dump_rsig_bp(rsig: Bulletproof) -> bytes:
|
|||||||
utils.memcpy(buff, 32 * 4, rsig.taux, 0, 32)
|
utils.memcpy(buff, 32 * 4, rsig.taux, 0, 32)
|
||||||
utils.memcpy(buff, 32 * 5, rsig.mu, 0, 32)
|
utils.memcpy(buff, 32 * 5, rsig.mu, 0, 32)
|
||||||
|
|
||||||
buff[32 * 6] = len(rsig.L)
|
offset = _dump_rsig_lr(buff, 32 * 6, rsig)
|
||||||
offset = 32 * 6 + 1
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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_lr(
|
||||||
|
buff: bytearray, offset: int, rsig: Bulletproof | BulletproofPlus
|
||||||
|
) -> int:
|
||||||
|
buff[offset] = len(rsig.L)
|
||||||
|
offset += 1
|
||||||
|
|
||||||
for x in rsig.L:
|
for x in rsig.L:
|
||||||
utils.memcpy(buff, offset, x, 0, 32)
|
utils.memcpy(buff, offset, x, 0, 32)
|
||||||
@ -379,18 +425,12 @@ def _dump_rsig_bp(rsig: Bulletproof) -> bytes:
|
|||||||
for x in rsig.R:
|
for x in rsig.R:
|
||||||
utils.memcpy(buff, offset, x, 0, 32)
|
utils.memcpy(buff, offset, x, 0, 32)
|
||||||
offset += 32
|
offset += 32
|
||||||
|
return offset
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def _return_rsig_data(
|
def _return_rsig_data(
|
||||||
rsig: bytes | None = None, mask: bytes | None = None
|
rsig: bytes | None = None, mask: bytes | None = None
|
||||||
) -> MoneroTransactionRsigData:
|
) -> MoneroTransactionRsigData | None:
|
||||||
if rsig is None and mask is None:
|
if rsig is None and mask is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -420,9 +460,18 @@ def _get_ecdh_info_and_out_pk(
|
|||||||
so the recipient is able to reconstruct the commitment.
|
so the recipient is able to reconstruct the commitment.
|
||||||
"""
|
"""
|
||||||
out_pk_dest = crypto_helpers.encodepoint(tx_out_key)
|
out_pk_dest = crypto_helpers.encodepoint(tx_out_key)
|
||||||
out_pk_commitment = crypto_helpers.encodepoint(
|
if state.rsig_is_bp_plus:
|
||||||
crypto.gen_commitment_into(None, mask, amount)
|
# HF15+ stores commitment multiplied by 8**-1
|
||||||
)
|
inv8 = crypto.decodeint_into_noreduce(None, crypto_helpers.INV_EIGHT)
|
||||||
|
mask8 = crypto.sc_mul_into(None, mask, inv8)
|
||||||
|
amnt8 = crypto.Scalar(amount)
|
||||||
|
amnt8 = crypto.sc_mul_into(amnt8, amnt8, inv8)
|
||||||
|
out_pk_commitment = crypto.add_keys2_into(None, mask8, amnt8, crypto.xmr_H())
|
||||||
|
del (inv8, mask8, amnt8)
|
||||||
|
else:
|
||||||
|
out_pk_commitment = crypto.gen_commitment_into(None, mask, amount)
|
||||||
|
|
||||||
|
out_pk_commitment = crypto_helpers.encodepoint(out_pk_commitment)
|
||||||
crypto.sc_add_into(state.sumout, state.sumout, mask)
|
crypto.sc_add_into(state.sumout, state.sumout, mask)
|
||||||
ecdh_info = _ecdh_encode(amount, crypto_helpers.encodeint(amount_key))
|
ecdh_info = _ecdh_encode(amount, crypto_helpers.encodeint(amount_key))
|
||||||
|
|
||||||
|
@ -347,7 +347,7 @@ def _invert_batch(x):
|
|||||||
|
|
||||||
for i in range(len(x) - 1, -1, -1):
|
for i in range(len(x) - 1, -1, -1):
|
||||||
_sc_mul(tmp, acc, x[i])
|
_sc_mul(tmp, acc, x[i])
|
||||||
_sc_mul(x[i], acc, scratch[i])
|
x[i] = _sc_mul(x[i], acc, scratch[i])
|
||||||
memcpy(acc, 0, tmp, 0, 32)
|
memcpy(acc, 0, tmp, 0, 32)
|
||||||
return x
|
return x
|
||||||
|
|
||||||
@ -1161,6 +1161,55 @@ class KeyPow2Vct(KeyVBase):
|
|||||||
return self.cur_sc if self.raw else self.cur
|
return self.cur_sc if self.raw else self.cur
|
||||||
|
|
||||||
|
|
||||||
|
class KeyChallengeCacheVct(KeyVBase):
|
||||||
|
"""
|
||||||
|
Challenge cache vector for BP+ verification
|
||||||
|
More on this in the verification code, near "challenge_cache" definition
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
"nbits",
|
||||||
|
"ch_",
|
||||||
|
"chi",
|
||||||
|
"precomp",
|
||||||
|
"precomp_depth",
|
||||||
|
"cur",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, nbits: int, ch_: KeyVBase, chi: KeyVBase, precomputed: KeyVBase | None
|
||||||
|
):
|
||||||
|
super().__init__(1 << nbits)
|
||||||
|
self.nbits = nbits
|
||||||
|
self.ch_ = ch_
|
||||||
|
self.chi = chi
|
||||||
|
self.precomp = precomputed
|
||||||
|
self.precomp_depth = 0
|
||||||
|
self.cur = _ensure_dst_key()
|
||||||
|
if not precomputed:
|
||||||
|
return
|
||||||
|
|
||||||
|
while (1 << self.precomp_depth) < len(precomputed):
|
||||||
|
self.precomp_depth += 1
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
i = self.idxize(item)
|
||||||
|
bits_done = 0
|
||||||
|
|
||||||
|
if self.precomp:
|
||||||
|
_copy_key(self.cur, self.precomp[i >> (self.nbits - self.precomp_depth)])
|
||||||
|
bits_done += self.precomp_depth
|
||||||
|
else:
|
||||||
|
_copy_key(self.cur, _ONE)
|
||||||
|
|
||||||
|
for j in range(self.nbits - 1, bits_done - 1, -1):
|
||||||
|
if i & (1 << (self.nbits - 1 - j)) > 0:
|
||||||
|
_sc_mul(self.cur, self.cur, self.ch_[j])
|
||||||
|
else:
|
||||||
|
_sc_mul(self.cur, self.cur, self.chi[j])
|
||||||
|
return self.cur
|
||||||
|
|
||||||
|
|
||||||
class KeyR0(KeyVBase):
|
class KeyR0(KeyVBase):
|
||||||
"""
|
"""
|
||||||
Vector r0. Allows only sequential access (no jumping). Resets on [0,1] access.
|
Vector r0. Allows only sequential access (no jumping). Resets on [0,1] access.
|
||||||
@ -2736,6 +2785,9 @@ class BulletProofPlusBuilder:
|
|||||||
|
|
||||||
return BulletproofPlus(V=V, A=A, A1=A1, B=B, r1=r1, s1=s1, d1=d1, L=L, R=R)
|
return BulletproofPlus(V=V, A=A, A1=A1, B=B, r1=r1, s1=s1, d1=d1, L=L, R=R)
|
||||||
|
|
||||||
|
def verify(self, proof: BulletproofPlus) -> bool:
|
||||||
|
return self.verify_batch([proof])
|
||||||
|
|
||||||
def verify_batch(self, proofs: list[BulletproofPlus]):
|
def verify_batch(self, proofs: list[BulletproofPlus]):
|
||||||
"""
|
"""
|
||||||
BP+ batch verification
|
BP+ batch verification
|
||||||
@ -2763,7 +2815,8 @@ class BulletProofPlusBuilder:
|
|||||||
max_logm = 0
|
max_logm = 0
|
||||||
|
|
||||||
proof_data = []
|
proof_data = []
|
||||||
to_invert = []
|
to_invert_offset = 0
|
||||||
|
to_invert = _ensure_dst_keyvect(None, 11 * len(proofs))
|
||||||
for proof in proofs:
|
for proof in proofs:
|
||||||
max_length = max(max_length, len(proof.L))
|
max_length = max(max_length, len(proof.L))
|
||||||
nV += len(proof.V)
|
nV += len(proof.V)
|
||||||
@ -2806,11 +2859,17 @@ class BulletProofPlusBuilder:
|
|||||||
# batch scalar inversions
|
# batch scalar inversions
|
||||||
pd.inv_offset = inv_offset
|
pd.inv_offset = inv_offset
|
||||||
for j in range(rounds): # max rounds is 10 = lg(16*64) = lg(1024)
|
for j in range(rounds): # max rounds is 10 = lg(16*64) = lg(1024)
|
||||||
to_invert.append(bytearray(pd.challenges[j]))
|
to_invert.read(to_invert_offset, pd.challenges[j])
|
||||||
to_invert.append(bytearray(pd.y))
|
to_invert_offset += 1
|
||||||
|
|
||||||
|
to_invert.read(to_invert_offset, pd.y)
|
||||||
|
to_invert_offset += 1
|
||||||
inv_offset += rounds + 1
|
inv_offset += rounds + 1
|
||||||
self.gc(2)
|
self.gc(2)
|
||||||
|
|
||||||
|
to_invert.resize(inv_offset)
|
||||||
|
self.gc(2)
|
||||||
|
|
||||||
utils.ensure(max_length < 32, "At least one proof is too large")
|
utils.ensure(max_length < 32, "At least one proof is too large")
|
||||||
maxMN = 1 << max_length
|
maxMN = 1 << max_length
|
||||||
tmp2 = _ensure_dst_key()
|
tmp2 = _ensure_dst_key()
|
||||||
@ -2937,32 +2996,55 @@ class BulletProofPlusBuilder:
|
|||||||
yinv = inverses[pd.inv_offset + rounds]
|
yinv = inverses[pd.inv_offset + rounds]
|
||||||
self.gc(6)
|
self.gc(6)
|
||||||
|
|
||||||
# Compute challenge products
|
# Description of challenges_cache:
|
||||||
challenges_cache = _ensure_dst_keyvect(
|
# Let define ch_[i] = pd.challenges[i] and
|
||||||
None, 1 << rounds
|
# chi[i] = pd.challenges[i]^{-1}
|
||||||
) # [_ZERO] * (1 << rounds)
|
# Also define b_j[i] = i-th bit of integer j, 0 is MSB
|
||||||
|
# encoded in {rounds} bits
|
||||||
|
#
|
||||||
|
# challenges_cache[i] contains multiplication ch_ or chi depending on the b_i
|
||||||
|
# i.e., its binary representation. chi is for 0, ch_ for 1 in the b_i repr.
|
||||||
|
#
|
||||||
|
# challenges_cache[i] = \\mult_{j \in [0, rounds)} (b_i[j] * ch_[j]) +
|
||||||
|
# (1-b_i[j]) * chi[j]
|
||||||
|
# Originally, it is constructed iteratively, starting with 1 bit, 2 bits.
|
||||||
|
# We cannot afford having it all precomputed, thus we precompute it up to
|
||||||
|
# a threshold challenges_cache_depth_lim bits, the rest is evaluated on the fly
|
||||||
|
challenges_cache_depth_lim = const(8)
|
||||||
|
challenges_cache_depth = min(rounds, challenges_cache_depth_lim)
|
||||||
|
challenges_cache = _ensure_dst_keyvect(None, 1 << challenges_cache_depth)
|
||||||
|
|
||||||
challenges_cache[0] = inverses[pd.inv_offset]
|
challenges_cache[0] = inverses[pd.inv_offset]
|
||||||
challenges_cache[1] = pd.challenges[0]
|
challenges_cache[1] = pd.challenges[0]
|
||||||
for j in range(1, rounds):
|
|
||||||
|
for j in range(1, challenges_cache_depth):
|
||||||
slots = 1 << (j + 1)
|
slots = 1 << (j + 1)
|
||||||
for s in range(slots - 1, -1, -2):
|
for s in range(slots - 1, -1, -2):
|
||||||
challenges_cache.read(
|
challenges_cache.read(
|
||||||
s,
|
s,
|
||||||
_sc_mul(
|
_sc_mul(
|
||||||
challenges_cache[s],
|
_tmp_bf_0,
|
||||||
challenges_cache[s // 2],
|
challenges_cache[s // 2],
|
||||||
pd.challenges[j],
|
pd.challenges[j], # even s
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
challenges_cache.read(
|
challenges_cache.read(
|
||||||
s - 1,
|
s - 1,
|
||||||
_sc_mul(
|
_sc_mul(
|
||||||
challenges_cache[s - 1],
|
_tmp_bf_0,
|
||||||
challenges_cache[s // 2],
|
challenges_cache[s // 2],
|
||||||
inverses[pd.inv_offset + j],
|
inverses[pd.inv_offset + j], # odd s
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if rounds > challenges_cache_depth:
|
||||||
|
challenges_cache = KeyChallengeCacheVct(
|
||||||
|
rounds,
|
||||||
|
pd.challenges,
|
||||||
|
inverses.slice_view(pd.inv_offset, pd.inv_offset + rounds + 1),
|
||||||
|
challenges_cache,
|
||||||
|
)
|
||||||
|
|
||||||
# Gi and Hi
|
# Gi and Hi
|
||||||
self.gc(7)
|
self.gc(7)
|
||||||
e_r1_w_y = _ensure_dst_key()
|
e_r1_w_y = _ensure_dst_key()
|
||||||
|
@ -3,10 +3,9 @@ from typing import TYPE_CHECKING
|
|||||||
from apps.monero.xmr import crypto_helpers
|
from apps.monero.xmr import crypto_helpers
|
||||||
from apps.monero.xmr.keccak_hasher import KeccakXmrArchive
|
from apps.monero.xmr.keccak_hasher import KeccakXmrArchive
|
||||||
|
|
||||||
from .serialize_messages.tx_rsig_bulletproof import Bulletproof
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from trezor.utils import HashContext
|
from trezor.utils import HashContext
|
||||||
|
from .serialize_messages.tx_rsig_bulletproof import Bulletproof, BulletproofPlus
|
||||||
|
|
||||||
|
|
||||||
class PreMlsagHasher:
|
class PreMlsagHasher:
|
||||||
@ -57,7 +56,9 @@ class PreMlsagHasher:
|
|||||||
self.kc_master.update(c_hash)
|
self.kc_master.update(c_hash)
|
||||||
self.rtcsig_hasher = None # type: ignore
|
self.rtcsig_hasher = None # type: ignore
|
||||||
|
|
||||||
def rsig_val(self, p: bytes | list[bytes] | Bulletproof, raw: bool = False) -> None:
|
def rsig_val(
|
||||||
|
self, p: bytes | list[bytes] | Bulletproof | BulletproofPlus, raw: bool = False
|
||||||
|
) -> None:
|
||||||
if self.state == 8:
|
if self.state == 8:
|
||||||
raise ValueError("State error")
|
raise ValueError("State error")
|
||||||
|
|
||||||
@ -74,19 +75,27 @@ class PreMlsagHasher:
|
|||||||
self.rsig_hasher.update(p)
|
self.rsig_hasher.update(p)
|
||||||
return
|
return
|
||||||
|
|
||||||
assert isinstance(p, Bulletproof)
|
from .serialize_messages.tx_rsig_bulletproof import Bulletproof, BulletproofPlus
|
||||||
|
|
||||||
|
is_plus = isinstance(p, BulletproofPlus)
|
||||||
|
assert isinstance(p, Bulletproof) or is_plus
|
||||||
|
|
||||||
# Hash Bulletproof
|
# Hash Bulletproof
|
||||||
self.rsig_hasher.update(p.A)
|
fields = (
|
||||||
self.rsig_hasher.update(p.S)
|
(p.A, p.A1, p.B, p.r1, p.s1, p.d1)
|
||||||
self.rsig_hasher.update(p.T1)
|
if is_plus
|
||||||
self.rsig_hasher.update(p.T2)
|
else (p.A, p.S, p.T1, p.T2, p.taux, p.mu)
|
||||||
self.rsig_hasher.update(p.taux)
|
)
|
||||||
self.rsig_hasher.update(p.mu)
|
for fld in fields:
|
||||||
|
self.rsig_hasher.update(fld)
|
||||||
|
|
||||||
|
del (fields,)
|
||||||
for i in range(len(p.L)):
|
for i in range(len(p.L)):
|
||||||
self.rsig_hasher.update(p.L[i])
|
self.rsig_hasher.update(p.L[i])
|
||||||
for i in range(len(p.R)):
|
for i in range(len(p.R)):
|
||||||
self.rsig_hasher.update(p.R[i])
|
self.rsig_hasher.update(p.R[i])
|
||||||
|
|
||||||
|
if not is_plus:
|
||||||
self.rsig_hasher.update(p.a)
|
self.rsig_hasher.update(p.a)
|
||||||
self.rsig_hasher.update(p.b)
|
self.rsig_hasher.update(p.b)
|
||||||
self.rsig_hasher.update(p.t)
|
self.rsig_hasher.update(p.t)
|
||||||
|
@ -14,14 +14,19 @@ from typing import TYPE_CHECKING
|
|||||||
from apps.monero.xmr import crypto, crypto_helpers
|
from apps.monero.xmr import crypto, crypto_helpers
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof
|
from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import (
|
||||||
|
Bulletproof,
|
||||||
|
BulletproofPlus,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def prove_range_bp_batch(amounts: list[int], masks: list[crypto.Scalar]) -> Bulletproof:
|
def prove_range_bp_batch(
|
||||||
|
amounts: list[int], masks: list[crypto.Scalar], bp_plus: bool = False
|
||||||
|
) -> Bulletproof | BulletproofPlus:
|
||||||
"""Calculates Bulletproof in batches"""
|
"""Calculates Bulletproof in batches"""
|
||||||
from apps.monero.xmr import bulletproof as bp
|
from apps.monero.xmr import bulletproof as bp
|
||||||
|
|
||||||
bpi = bp.BulletProofBuilder()
|
bpi = bp.BulletProofPlusBuilder() if bp_plus else bp.BulletProofBuilder()
|
||||||
bp_proof = bpi.prove_batch([crypto.Scalar(a) for a in amounts], masks)
|
bp_proof = bpi.prove_batch([crypto.Scalar(a) for a in amounts], masks)
|
||||||
del (bpi, bp)
|
del (bpi, bp)
|
||||||
gc.collect()
|
gc.collect()
|
||||||
@ -30,7 +35,9 @@ def prove_range_bp_batch(amounts: list[int], masks: list[crypto.Scalar]) -> Bull
|
|||||||
|
|
||||||
|
|
||||||
def verify_bp(
|
def verify_bp(
|
||||||
bp_proof: Bulletproof, amounts: list[int], masks: list[crypto.Scalar]
|
bp_proof: Bulletproof | BulletproofPlus,
|
||||||
|
amounts: list[int],
|
||||||
|
masks: list[crypto.Scalar],
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Verifies Bulletproof"""
|
"""Verifies Bulletproof"""
|
||||||
from apps.monero.xmr import bulletproof as bp
|
from apps.monero.xmr import bulletproof as bp
|
||||||
@ -42,7 +49,13 @@ def verify_bp(
|
|||||||
crypto.scalarmult_into(C, C, crypto_helpers.INV_EIGHT_SC)
|
crypto.scalarmult_into(C, C, crypto_helpers.INV_EIGHT_SC)
|
||||||
bp_proof.V.append(crypto_helpers.encodepoint(C))
|
bp_proof.V.append(crypto_helpers.encodepoint(C))
|
||||||
|
|
||||||
bpi = bp.BulletProofBuilder()
|
from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import BulletproofPlus
|
||||||
|
|
||||||
|
bpi = (
|
||||||
|
bp.BulletProofPlusBuilder()
|
||||||
|
if isinstance(bp_proof, BulletproofPlus)
|
||||||
|
else bp.BulletProofBuilder()
|
||||||
|
)
|
||||||
res = bpi.verify(bp_proof)
|
res = bpi.verify(bp_proof)
|
||||||
gc.collect()
|
gc.collect()
|
||||||
return res
|
return res
|
||||||
|
@ -45,7 +45,6 @@ class BulletproofPlus(MessageType):
|
|||||||
("r1", ECKey),
|
("r1", ECKey),
|
||||||
("s1", ECKey),
|
("s1", ECKey),
|
||||||
("d1", ECKey),
|
("d1", ECKey),
|
||||||
("V", _KeyV),
|
|
||||||
("L", _KeyV),
|
("L", _KeyV),
|
||||||
("R", _KeyV),
|
("R", _KeyV),
|
||||||
)
|
)
|
||||||
|
@ -180,8 +180,6 @@ if not utils.BITCOIN_ONLY:
|
|||||||
MoneroTransactionInitAck = 502
|
MoneroTransactionInitAck = 502
|
||||||
MoneroTransactionSetInputRequest = 503
|
MoneroTransactionSetInputRequest = 503
|
||||||
MoneroTransactionSetInputAck = 504
|
MoneroTransactionSetInputAck = 504
|
||||||
MoneroTransactionInputsPermutationRequest = 505
|
|
||||||
MoneroTransactionInputsPermutationAck = 506
|
|
||||||
MoneroTransactionInputViniRequest = 507
|
MoneroTransactionInputViniRequest = 507
|
||||||
MoneroTransactionInputViniAck = 508
|
MoneroTransactionInputViniAck = 508
|
||||||
MoneroTransactionAllInputsSetRequest = 509
|
MoneroTransactionAllInputsSetRequest = 509
|
||||||
|
@ -198,8 +198,6 @@ if TYPE_CHECKING:
|
|||||||
MoneroTransactionInitAck = 502
|
MoneroTransactionInitAck = 502
|
||||||
MoneroTransactionSetInputRequest = 503
|
MoneroTransactionSetInputRequest = 503
|
||||||
MoneroTransactionSetInputAck = 504
|
MoneroTransactionSetInputAck = 504
|
||||||
MoneroTransactionInputsPermutationRequest = 505
|
|
||||||
MoneroTransactionInputsPermutationAck = 506
|
|
||||||
MoneroTransactionInputViniRequest = 507
|
MoneroTransactionInputViniRequest = 507
|
||||||
MoneroTransactionInputViniAck = 508
|
MoneroTransactionInputViniAck = 508
|
||||||
MoneroTransactionAllInputsSetRequest = 509
|
MoneroTransactionAllInputsSetRequest = 509
|
||||||
|
@ -3833,26 +3833,6 @@ if TYPE_CHECKING:
|
|||||||
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["MoneroTransactionSetInputAck"]:
|
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["MoneroTransactionSetInputAck"]:
|
||||||
return isinstance(msg, cls)
|
return isinstance(msg, cls)
|
||||||
|
|
||||||
class MoneroTransactionInputsPermutationRequest(protobuf.MessageType):
|
|
||||||
perm: "list[int]"
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
perm: "list[int] | None" = None,
|
|
||||||
) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["MoneroTransactionInputsPermutationRequest"]:
|
|
||||||
return isinstance(msg, cls)
|
|
||||||
|
|
||||||
class MoneroTransactionInputsPermutationAck(protobuf.MessageType):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["MoneroTransactionInputsPermutationAck"]:
|
|
||||||
return isinstance(msg, cls)
|
|
||||||
|
|
||||||
class MoneroTransactionInputViniRequest(protobuf.MessageType):
|
class MoneroTransactionInputViniRequest(protobuf.MessageType):
|
||||||
src_entr: "MoneroTransactionSourceEntry | None"
|
src_entr: "MoneroTransactionSourceEntry | None"
|
||||||
vini: "bytes | None"
|
vini: "bytes | None"
|
||||||
|
@ -407,7 +407,7 @@ class TestMoneroBulletproof(unittest.TestCase):
|
|||||||
t=unhexlify(b"0de43b393686af8dd0d89f4832a2995cda14e6288de9ecd2b4bf2fa39baba408")
|
t=unhexlify(b"0de43b393686af8dd0d89f4832a2995cda14e6288de9ecd2b4bf2fa39baba408")
|
||||||
)
|
)
|
||||||
|
|
||||||
def bproof_plus_1(self):
|
def bproof_plus_2(self):
|
||||||
return BulletproofPlus(
|
return BulletproofPlus(
|
||||||
V=[
|
V=[
|
||||||
unhexlify(b"e0dae61095ac728a15d4d9754f1f9f956c22d4fa2deee2c0ff1def031b083e02"),
|
unhexlify(b"e0dae61095ac728a15d4d9754f1f9f956c22d4fa2deee2c0ff1def031b083e02"),
|
||||||
@ -484,9 +484,9 @@ class TestMoneroBulletproof(unittest.TestCase):
|
|||||||
|
|
||||||
def test_verify_plus(self):
|
def test_verify_plus(self):
|
||||||
bpi = bp.BulletProofPlusBuilder()
|
bpi = bp.BulletProofPlusBuilder()
|
||||||
bpi.verify_batch([self.bproof_plus_1()])
|
bpi.verify_batch([self.bproof_plus_2()])
|
||||||
|
|
||||||
def test_prove_plus_(self):
|
def test_prove_plus_1(self):
|
||||||
bpi = bp.BulletProofPlusBuilder()
|
bpi = bp.BulletProofPlusBuilder()
|
||||||
sv = [crypto.Scalar(123)]
|
sv = [crypto.Scalar(123)]
|
||||||
gamma = [crypto.Scalar(456)]
|
gamma = [crypto.Scalar(456)]
|
||||||
@ -500,10 +500,10 @@ class TestMoneroBulletproof(unittest.TestCase):
|
|||||||
proof = bpi.prove_batch(sv, gamma)
|
proof = bpi.prove_batch(sv, gamma)
|
||||||
bpi.verify_batch([proof])
|
bpi.verify_batch([proof])
|
||||||
|
|
||||||
def test_prove_plus_8(self):
|
def test_prove_plus_16(self):
|
||||||
bpi = bp.BulletProofPlusBuilder()
|
bpi = bp.BulletProofPlusBuilder()
|
||||||
sv = [crypto.Scalar(i*123 + 45) for i in range(8)]
|
sv = [crypto.Scalar(i*123 + 45) for i in range(16)]
|
||||||
gamma = [crypto.Scalar(i*456 * 17) for i in range(8)]
|
gamma = [crypto.Scalar(i*456 * 17) for i in range(16)]
|
||||||
proof = bpi.prove_batch(sv, gamma)
|
proof = bpi.prove_batch(sv, gamma)
|
||||||
bpi.verify_batch([proof])
|
bpi.verify_batch([proof])
|
||||||
|
|
||||||
|
@ -206,8 +206,6 @@ class MessageType(IntEnum):
|
|||||||
MoneroTransactionInitAck = 502
|
MoneroTransactionInitAck = 502
|
||||||
MoneroTransactionSetInputRequest = 503
|
MoneroTransactionSetInputRequest = 503
|
||||||
MoneroTransactionSetInputAck = 504
|
MoneroTransactionSetInputAck = 504
|
||||||
MoneroTransactionInputsPermutationRequest = 505
|
|
||||||
MoneroTransactionInputsPermutationAck = 506
|
|
||||||
MoneroTransactionInputViniRequest = 507
|
MoneroTransactionInputViniRequest = 507
|
||||||
MoneroTransactionInputViniAck = 508
|
MoneroTransactionInputViniAck = 508
|
||||||
MoneroTransactionAllInputsSetRequest = 509
|
MoneroTransactionAllInputsSetRequest = 509
|
||||||
@ -5310,24 +5308,6 @@ class MoneroTransactionSetInputAck(protobuf.MessageType):
|
|||||||
self.spend_key = spend_key
|
self.spend_key = spend_key
|
||||||
|
|
||||||
|
|
||||||
class MoneroTransactionInputsPermutationRequest(protobuf.MessageType):
|
|
||||||
MESSAGE_WIRE_TYPE = 505
|
|
||||||
FIELDS = {
|
|
||||||
1: protobuf.Field("perm", "uint32", repeated=True, required=False),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
perm: Optional[Sequence["int"]] = None,
|
|
||||||
) -> None:
|
|
||||||
self.perm: Sequence["int"] = perm if perm is not None else []
|
|
||||||
|
|
||||||
|
|
||||||
class MoneroTransactionInputsPermutationAck(protobuf.MessageType):
|
|
||||||
MESSAGE_WIRE_TYPE = 506
|
|
||||||
|
|
||||||
|
|
||||||
class MoneroTransactionInputViniRequest(protobuf.MessageType):
|
class MoneroTransactionInputViniRequest(protobuf.MessageType):
|
||||||
MESSAGE_WIRE_TYPE = 507
|
MESSAGE_WIRE_TYPE = 507
|
||||||
FIELDS = {
|
FIELDS = {
|
||||||
|
Loading…
Reference in New Issue
Block a user