feat(xmr): add support for HF15, BP+

pull/2227/head
Dusan Klinec 2 years ago committed by matejcik
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.
* @next MoneroTransactionSetInputAck
* @next MoneroTransactionInputsPermutationRequest
* @next MoneroTransactionInputViniRequest
*/
message MoneroTransactionSetInputAck {
optional bytes vini = 1; // xmrtypes.TxinToKey
@ -183,21 +183,6 @@ message MoneroTransactionSetInputAck {
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.
* @next MoneroTransactionInputViniAck
@ -327,7 +312,7 @@ message MoneroTransactionFinalAck {
optional bytes salt = 2;
optional bytes rand_mult = 3;
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_MoneroTransactionSetInputRequest = 503 [(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_MoneroTransactionInputViniAck = 508 [(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
### `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`
- 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),
(
MessageType.MoneroTransactionSetInputRequest,
MessageType.MoneroTransactionInputsPermutationRequest,
MessageType.MoneroTransactionInputViniRequest,
),
)

@ -17,6 +17,8 @@ class RctType:
"""
There are several types of monero Ring Confidential Transactions
like RCTTypeFull and RCTTypeSimple but currently we use only CLSAG
and RCTTypeBulletproofPlus
"""
CLSAG = 5
RCTTypeBulletproofPlus = 6

@ -103,6 +103,7 @@ class State:
self.rsig_grouping: list[int] | None = []
# is range proof computing offloaded or not
self.rsig_offload: bool | None = False
self.rsig_is_bp_plus: bool | None = False
# sum of all inputs' pseudo out masks
self.sumpouts_alphas: Scalar = crypto.Scalar(0)

@ -61,7 +61,6 @@ async def init_transaction(
state.fee = tsx_data.fee
state.account_idx = tsx_data.account
state.last_step = state.STEP_INIT
state.tx_type = signing.RctType.CLSAG
if tsx_data.hard_fork:
state.hard_fork = tsx_data.hard_fork
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):
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:
state.rsig_offload = True

@ -15,7 +15,10 @@ from .state import State
if TYPE_CHECKING:
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 (
MoneroTransactionDestinationEntry,
MoneroTransactionSetOutputAck,
@ -303,7 +306,7 @@ def _rsig_bp(state: State) -> bytes:
from apps.monero.xmr import range_signatures
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)
@ -314,7 +317,7 @@ def _rsig_bp(state: State) -> bytes:
state.full_message_hasher.rsig_val(rsig, raw=False)
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(
f"post-bp-ser, size: {len(rsig)}" if __debug__ else None, collect=True
)
@ -327,9 +330,15 @@ def _rsig_bp(state: State) -> bytes:
def _rsig_process_bp(state: State, rsig_data: MoneroTransactionRsigData):
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,
)
bp_obj = serialize.parse_msg(rsig_data.rsig, Bulletproof)
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)
rsig_data.rsig = None
# BP is hashed with raw=False as hash does not contain L, R
@ -366,8 +375,45 @@ def _dump_rsig_bp(rsig: Bulletproof) -> bytes:
utils.memcpy(buff, 32 * 4, rsig.taux, 0, 32)
utils.memcpy(buff, 32 * 5, rsig.mu, 0, 32)
buff[32 * 6] = len(rsig.L)
offset = 32 * 6 + 1
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
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:
utils.memcpy(buff, offset, x, 0, 32)
@ -379,18 +425,12 @@ def _dump_rsig_bp(rsig: Bulletproof) -> bytes:
for x in rsig.R:
utils.memcpy(buff, offset, x, 0, 32)
offset += 32
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
return offset
def _return_rsig_data(
rsig: bytes | None = None, mask: bytes | None = None
) -> MoneroTransactionRsigData:
) -> MoneroTransactionRsigData | None:
if rsig is None and mask is None:
return None
@ -420,9 +460,18 @@ def _get_ecdh_info_and_out_pk(
so the recipient is able to reconstruct the commitment.
"""
out_pk_dest = crypto_helpers.encodepoint(tx_out_key)
out_pk_commitment = crypto_helpers.encodepoint(
crypto.gen_commitment_into(None, mask, amount)
)
if state.rsig_is_bp_plus:
# 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)
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):
_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)
return x
@ -1161,6 +1161,55 @@ class KeyPow2Vct(KeyVBase):
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):
"""
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)
def verify(self, proof: BulletproofPlus) -> bool:
return self.verify_batch([proof])
def verify_batch(self, proofs: list[BulletproofPlus]):
"""
BP+ batch verification
@ -2763,7 +2815,8 @@ class BulletProofPlusBuilder:
max_logm = 0
proof_data = []
to_invert = []
to_invert_offset = 0
to_invert = _ensure_dst_keyvect(None, 11 * len(proofs))
for proof in proofs:
max_length = max(max_length, len(proof.L))
nV += len(proof.V)
@ -2806,11 +2859,17 @@ class BulletProofPlusBuilder:
# batch scalar inversions
pd.inv_offset = inv_offset
for j in range(rounds): # max rounds is 10 = lg(16*64) = lg(1024)
to_invert.append(bytearray(pd.challenges[j]))
to_invert.append(bytearray(pd.y))
to_invert.read(to_invert_offset, pd.challenges[j])
to_invert_offset += 1
to_invert.read(to_invert_offset, pd.y)
to_invert_offset += 1
inv_offset += rounds + 1
self.gc(2)
to_invert.resize(inv_offset)
self.gc(2)
utils.ensure(max_length < 32, "At least one proof is too large")
maxMN = 1 << max_length
tmp2 = _ensure_dst_key()
@ -2937,32 +2996,55 @@ class BulletProofPlusBuilder:
yinv = inverses[pd.inv_offset + rounds]
self.gc(6)
# Compute challenge products
challenges_cache = _ensure_dst_keyvect(
None, 1 << rounds
) # [_ZERO] * (1 << rounds)
# Description of challenges_cache:
# Let define ch_[i] = pd.challenges[i] and
# chi[i] = pd.challenges[i]^{-1}
# 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[1] = pd.challenges[0]
for j in range(1, rounds):
for j in range(1, challenges_cache_depth):
slots = 1 << (j + 1)
for s in range(slots - 1, -1, -2):
challenges_cache.read(
s,
_sc_mul(
challenges_cache[s],
_tmp_bf_0,
challenges_cache[s // 2],
pd.challenges[j],
pd.challenges[j], # even s
),
)
challenges_cache.read(
s - 1,
_sc_mul(
challenges_cache[s - 1],
_tmp_bf_0,
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
self.gc(7)
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.keccak_hasher import KeccakXmrArchive
from .serialize_messages.tx_rsig_bulletproof import Bulletproof
if TYPE_CHECKING:
from trezor.utils import HashContext
from .serialize_messages.tx_rsig_bulletproof import Bulletproof, BulletproofPlus
class PreMlsagHasher:
@ -57,7 +56,9 @@ class PreMlsagHasher:
self.kc_master.update(c_hash)
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:
raise ValueError("State error")
@ -74,22 +75,30 @@ class PreMlsagHasher:
self.rsig_hasher.update(p)
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
self.rsig_hasher.update(p.A)
self.rsig_hasher.update(p.S)
self.rsig_hasher.update(p.T1)
self.rsig_hasher.update(p.T2)
self.rsig_hasher.update(p.taux)
self.rsig_hasher.update(p.mu)
fields = (
(p.A, p.A1, p.B, p.r1, p.s1, p.d1)
if is_plus
else (p.A, p.S, p.T1, p.T2, p.taux, p.mu)
)
for fld in fields:
self.rsig_hasher.update(fld)
del (fields,)
for i in range(len(p.L)):
self.rsig_hasher.update(p.L[i])
for i in range(len(p.R)):
self.rsig_hasher.update(p.R[i])
self.rsig_hasher.update(p.a)
self.rsig_hasher.update(p.b)
self.rsig_hasher.update(p.t)
if not is_plus:
self.rsig_hasher.update(p.a)
self.rsig_hasher.update(p.b)
self.rsig_hasher.update(p.t)
def get_digest(self) -> bytes:
if self.state != 6:

@ -14,14 +14,19 @@ from typing import TYPE_CHECKING
from apps.monero.xmr import crypto, crypto_helpers
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"""
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)
del (bpi, bp)
gc.collect()
@ -30,7 +35,9 @@ def prove_range_bp_batch(amounts: list[int], masks: list[crypto.Scalar]) -> Bull
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:
"""Verifies Bulletproof"""
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)
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)
gc.collect()
return res

@ -45,7 +45,6 @@ class BulletproofPlus(MessageType):
("r1", ECKey),
("s1", ECKey),
("d1", ECKey),
("V", _KeyV),
("L", _KeyV),
("R", _KeyV),
)

@ -180,8 +180,6 @@ if not utils.BITCOIN_ONLY:
MoneroTransactionInitAck = 502
MoneroTransactionSetInputRequest = 503
MoneroTransactionSetInputAck = 504
MoneroTransactionInputsPermutationRequest = 505
MoneroTransactionInputsPermutationAck = 506
MoneroTransactionInputViniRequest = 507
MoneroTransactionInputViniAck = 508
MoneroTransactionAllInputsSetRequest = 509

@ -198,8 +198,6 @@ if TYPE_CHECKING:
MoneroTransactionInitAck = 502
MoneroTransactionSetInputRequest = 503
MoneroTransactionSetInputAck = 504
MoneroTransactionInputsPermutationRequest = 505
MoneroTransactionInputsPermutationAck = 506
MoneroTransactionInputViniRequest = 507
MoneroTransactionInputViniAck = 508
MoneroTransactionAllInputsSetRequest = 509

@ -3833,26 +3833,6 @@ if TYPE_CHECKING:
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["MoneroTransactionSetInputAck"]:
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):
src_entr: "MoneroTransactionSourceEntry | None"
vini: "bytes | None"

@ -407,7 +407,7 @@ class TestMoneroBulletproof(unittest.TestCase):
t=unhexlify(b"0de43b393686af8dd0d89f4832a2995cda14e6288de9ecd2b4bf2fa39baba408")
)
def bproof_plus_1(self):
def bproof_plus_2(self):
return BulletproofPlus(
V=[
unhexlify(b"e0dae61095ac728a15d4d9754f1f9f956c22d4fa2deee2c0ff1def031b083e02"),
@ -484,9 +484,9 @@ class TestMoneroBulletproof(unittest.TestCase):
def test_verify_plus(self):
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()
sv = [crypto.Scalar(123)]
gamma = [crypto.Scalar(456)]
@ -500,10 +500,10 @@ class TestMoneroBulletproof(unittest.TestCase):
proof = bpi.prove_batch(sv, gamma)
bpi.verify_batch([proof])
def test_prove_plus_8(self):
def test_prove_plus_16(self):
bpi = bp.BulletProofPlusBuilder()
sv = [crypto.Scalar(i*123 + 45) for i in range(8)]
gamma = [crypto.Scalar(i*456 * 17) 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(16)]
proof = bpi.prove_batch(sv, gamma)
bpi.verify_batch([proof])

@ -206,8 +206,6 @@ class MessageType(IntEnum):
MoneroTransactionInitAck = 502
MoneroTransactionSetInputRequest = 503
MoneroTransactionSetInputAck = 504
MoneroTransactionInputsPermutationRequest = 505
MoneroTransactionInputsPermutationAck = 506
MoneroTransactionInputViniRequest = 507
MoneroTransactionInputViniAck = 508
MoneroTransactionAllInputsSetRequest = 509
@ -5310,24 +5308,6 @@ class MoneroTransactionSetInputAck(protobuf.MessageType):
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):
MESSAGE_WIRE_TYPE = 507
FIELDS = {

Loading…
Cancel
Save