refactor(core/monero): Monero code cleanup

* remove support for HF12 and below
* remove MLSAG support
* clean up monero cryptography naming
* get rid of "optional first argument" pattern, in favor of mandatory argument that is allowed to be None
  (and fix several bugs related to this feature)

Co-authored-by: grdddj <jiri.musil06@seznam.cz>
Co-authored-by: Martin Milata <martin@martinmilata.cz>
Co-authored-by: matejcik <ja@matejcik.cz>
pull/2227/head
Dusan Klinec 2 years ago committed by matejcik
parent 99c817bad4
commit 33c174491f

@ -5,6 +5,13 @@ package hw.trezor.messages.monero;
option java_package = "com.satoshilabs.trezor.lib.protobuf"; option java_package = "com.satoshilabs.trezor.lib.protobuf";
option java_outer_classname = "TrezorMessageMonero"; option java_outer_classname = "TrezorMessageMonero";
enum MoneroNetworkType {
MAINNET = 0;
TESTNET = 1;
STAGENET = 2;
FAKECHAIN = 3;
}
/** /**
* Structure representing Monero transaction source entry, UTXO * Structure representing Monero transaction source entry, UTXO
* @embed * @embed
@ -24,8 +31,8 @@ message MoneroTransactionSourceEntry {
optional uint64 idx = 1; optional uint64 idx = 1;
optional MoneroRctKeyPublic key = 2; optional MoneroRctKeyPublic key = 2;
message MoneroRctKeyPublic { message MoneroRctKeyPublic {
optional bytes dest = 1; required bytes dest = 1;
optional bytes commitment = 2; required bytes commitment = 2;
} }
} }
message MoneroMultisigKLRki { message MoneroMultisigKLRki {
@ -79,7 +86,7 @@ message MoneroTransactionRsigData {
message MoneroGetAddress { message MoneroGetAddress {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
optional bool show_display = 2; // Optionally show on display before sending the result optional bool show_display = 2; // Optionally show on display before sending the result
optional uint32 network_type = 3; // Main-net / testnet / stagenet optional MoneroNetworkType network_type = 3 [default=MAINNET]; // Network type
optional uint32 account = 4; // Major subaddr index optional uint32 account = 4; // Major subaddr index
optional uint32 minor = 5; // Minor subaddr index optional uint32 minor = 5; // Minor subaddr index
optional bytes payment_id = 6; // Payment ID for integrated address optional bytes payment_id = 6; // Payment ID for integrated address
@ -101,7 +108,7 @@ message MoneroAddress {
*/ */
message MoneroGetWatchKey { message MoneroGetWatchKey {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
optional uint32 network_type = 2; // Main-net / testnet / stagenet optional MoneroNetworkType network_type = 2 [default=MAINNET]; // Network type
} }
/** /**
@ -121,7 +128,7 @@ message MoneroWatchKey {
message MoneroTransactionInitRequest { message MoneroTransactionInitRequest {
optional uint32 version = 1; optional uint32 version = 1;
repeated uint32 address_n = 2; repeated uint32 address_n = 2;
optional uint32 network_type = 3; // Main-net / testnet / stagenet optional MoneroNetworkType network_type = 3 [default=MAINNET]; // Network type
optional MoneroTransactionData tsx_data = 4; optional MoneroTransactionData tsx_data = 4;
/** /**
* Structure representing Monero initial transaction information * Structure representing Monero initial transaction information
@ -329,16 +336,16 @@ message MoneroTransactionFinalAck {
* @next MoneroKeyImageExportInitAck * @next MoneroKeyImageExportInitAck
*/ */
message MoneroKeyImageExportInitRequest { message MoneroKeyImageExportInitRequest {
optional uint64 num = 1; required uint64 num = 1;
optional bytes hash = 2; required bytes hash = 2;
repeated uint32 address_n = 3; // BIP-32 path to derive the key from master node repeated uint32 address_n = 3; // BIP-32 path to derive the key from master node
optional uint32 network_type = 4; // Main-net / testnet / stagenet optional MoneroNetworkType network_type = 4 [default=MAINNET]; // network type
repeated MoneroSubAddressIndicesList subs = 5; repeated MoneroSubAddressIndicesList subs = 5;
/** /**
* Structure representing Monero list of sub-addresses * Structure representing Monero list of sub-addresses
*/ */
message MoneroSubAddressIndicesList { message MoneroSubAddressIndicesList {
optional uint32 account = 1; required uint32 account = 1;
repeated uint32 minor_indices = 2; repeated uint32 minor_indices = 2;
} }
} }
@ -360,10 +367,10 @@ message MoneroKeyImageSyncStepRequest {
* Structure representing Monero UTXO for key image sync * Structure representing Monero UTXO for key image sync
*/ */
message MoneroTransferDetails { message MoneroTransferDetails {
optional bytes out_key = 1; required bytes out_key = 1;
optional bytes tx_pub_key = 2; required bytes tx_pub_key = 2;
repeated bytes additional_tx_pub_keys = 3; repeated bytes additional_tx_pub_keys = 3;
optional uint64 internal_output_index = 4; required uint64 internal_output_index = 4;
optional uint32 sub_addr_major = 5; optional uint32 sub_addr_major = 5;
optional uint32 sub_addr_minor = 6; optional uint32 sub_addr_minor = 6;
} }
@ -406,12 +413,12 @@ message MoneroKeyImageSyncFinalAck {
*/ */
message MoneroGetTxKeyRequest { message MoneroGetTxKeyRequest {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
optional uint32 network_type = 2; // Main-net / testnet / stagenet optional MoneroNetworkType network_type = 2 [default=MAINNET]; // network type
optional bytes salt1 = 3; required bytes salt1 = 3;
optional bytes salt2 = 4; required bytes salt2 = 4;
optional bytes tx_enc_keys = 5; required bytes tx_enc_keys = 5;
optional bytes tx_prefix_hash = 6; required bytes tx_prefix_hash = 6;
optional uint32 reason = 7; // reason to display for user. e.g., tx_proof optional uint32 reason = 7; // reason to display for user. e.g., tx_proof
optional bytes view_public_key = 8; // addr for derivation optional bytes view_public_key = 8; // addr for derivation
} }
@ -432,7 +439,7 @@ message MoneroGetTxKeyAck {
*/ */
message MoneroLiveRefreshStartRequest { message MoneroLiveRefreshStartRequest {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
optional uint32 network_type = 2; // Main-net / testnet / stagenet optional MoneroNetworkType network_type = 2 [default=MAINNET]; // network type
} }
/** /**
@ -449,11 +456,11 @@ message MoneroLiveRefreshStartAck {
* @next MoneroLiveRefreshStepAck * @next MoneroLiveRefreshStepAck
*/ */
message MoneroLiveRefreshStepRequest { message MoneroLiveRefreshStepRequest {
optional bytes out_key = 1; required bytes out_key = 1;
optional bytes recv_deriv = 2; required bytes recv_deriv = 2;
optional uint64 real_out_idx = 3; required uint64 real_out_idx = 3;
optional uint32 sub_addr_major = 4; required uint32 sub_addr_major = 4;
optional uint32 sub_addr_minor = 5; required uint32 sub_addr_minor = 5;
} }
/** /**

@ -0,0 +1 @@
Refactor and cleanup of Monero code.

@ -0,0 +1 @@
Removed support for obsolete Monero hardfork 12 and below

File diff suppressed because it is too large Load Diff

@ -2,22 +2,22 @@ from typing import *
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
class Ge25519: class Point:
""" """
EC point on ED25519 EC point on ED25519
""" """
def __init__(self, x: Ge25519 | bytes | None = None): def __init__(self, x: Point | bytes | None = None):
""" """
Constructor Constructor
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
class Sc25519: class Scalar:
""" """
EC scalar on SC25519 EC scalar on SC25519
""" """
def __init__(self, x: Sc25519 | bytes | int | None = None): def __init__(self, x: Scalar | bytes | int | None = None):
""" """
Constructor Constructor
""" """
@ -47,92 +47,84 @@ class Hasher:
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def init256_modm( def sc_copy(
dst: Sc25519 | None, val: int | bytes | Sc25519 dst: Scalar | None, val: int | bytes | Scalar
) -> Sc25519: ) -> Scalar:
""" """
Initializes Sc25519 scalar Initializes a scalar
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def check256_modm(val: Sc25519) -> None: def sc_check(val: Scalar) -> None:
""" """
Throws exception if scalar is invalid Throws exception if scalar is invalid
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def iszero256_modm(val: Sc25519) -> bool: def sc_iszero(val: Scalar) -> bool:
""" """
Returns False if the scalar is zero Returns False if the scalar is zero
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def eq256_modm(a: Sc25519, b: Sc25519) -> int: def sc_eq(a: Scalar, b: Scalar) -> int:
""" """
Compares scalars, returns 1 on the same value Compares scalars, returns 1 on the same value
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def get256_modm(a: Sc25519) -> int: def sc_add_into(r: Scalar | None, a: Scalar, b: Scalar) -> Scalar:
"""
Extracts 64bit integer from the scalar. Raises exception if scalar is
bigger than 2^64
"""
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def add256_modm(r: Sc25519 | None, a: Sc25519, b: Sc25519) -> Sc25519:
""" """
Scalar addition Scalar addition
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def sub256_modm(r: Sc25519 | None, a: Sc25519, b: Sc25519) -> Sc25519: def sc_sub_into(r: Scalar | None, a: Scalar, b: Scalar) -> Scalar:
""" """
Scalar subtraction Scalar subtraction
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def mul256_modm(r: Sc25519 | None, a: Sc25519, b: Sc25519) -> Sc25519: def sc_mul_into(r: Scalar | None, a: Scalar, b: Scalar) -> Scalar:
""" """
Scalar multiplication Scalar multiplication
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def mulsub256_modm( def sc_mulsub_into(
r: Sc25519 | None, a: Sc25519, b: Sc25519, c: Sc25519 r: Scalar | None, a: Scalar, b: Scalar, c: Scalar
) -> Sc25519: ) -> Scalar:
""" """
c - a*b c - a*b
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def muladd256_modm( def sc_muladd_into(
r: Sc25519 | None, a: Sc25519, b: Sc25519, c: Sc25519 r: Scalar | None, a: Scalar, b: Scalar, c: Scalar
) -> Sc25519: ) -> Scalar:
""" """
c + a*b c + a*b
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def inv256_modm(r: Sc25519 | None, a: Sc25519) -> Sc25519: def sc_inv_into(r: Scalar | None, a: Scalar) -> Scalar:
""" """
Scalar modular inversion Scalar modular inversion
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def pack256_modm( def encodeint_into(
r: bytes | None, a: Sc25519, offset: int | None = 0 r: bytes | None, a: Scalar, offset: int | None = 0
) -> bytes: ) -> bytes:
""" """
Scalar compression Scalar compression
@ -140,144 +132,124 @@ def pack256_modm(
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def unpack256_modm( def decodeint_into(
r: Sc25519 | None, a: bytes, offset: int = 0 r: Scalar | None, a: bytes, offset: int = 0
) -> Sc25519: ) -> Scalar:
""" """
Scalar decompression Scalar decompression
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def unpack256_modm_noreduce( def decodeint_into_noreduce(
r: Sc25519 | None, a: bytes, offset: int = 0 r: Scalar | None, a: bytes, offset: int = 0
) -> Sc25519: ) -> Scalar:
""" """
Scalar decompression, raw, without modular reduction Scalar decompression, raw, without modular reduction
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_set_neutral(r: Ge25519 | None) -> Ge25519: def identity_into(r: Point | None = None) -> Point:
""" """
Sets neutral point Sets neutral point
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_set_xmr_h(r: Ge25519 | None) -> Ge25519: def xmr_H(r: Point | None = None) -> Point:
""" """
Sets H point Sets H point
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_check(r: Ge25519) -> None: def ge25519_check(r: Point) -> None:
""" """
Checks point, throws if not on curve Checks point, throws if not on curve
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_eq(a: Ge25519, b: Ge25519) -> bool: def point_eq(a: Point, b: Point) -> bool:
""" """
Compares EC points Compares EC points
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_add(r: Ge25519 | None, a: Ge25519, b: Ge25519) -> Ge25519: def point_add_into(r: Point | None, a: Point, b: Point) -> Point:
""" """
Adds EC points Adds EC points
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_sub(r: Ge25519 | None, a: Ge25519, b: Ge25519) -> Ge25519: def point_sub_into(r: Point | None, a: Point, b: Point) -> Point:
""" """
Subtracts EC points Subtracts EC points
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_double(r: Ge25519 | None, p: Ge25519) -> Ge25519: def ge25519_mul8(r: Point | None, p: Point) -> Point:
"""
EC point doubling
"""
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_mul8(r: Ge25519 | None, p: Ge25519) -> Ge25519:
""" """
EC point * 8 EC point * 8
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_double_scalarmult_vartime( def ge25519_double_scalarmult_vartime_into(
r: Ge25519 | None, p1: Ge25519, s1: Sc25519, s2: Sc25519 r: Point | None, p1: Point, s1: Scalar, s2: Scalar
) -> Ge25519: ) -> Point:
""" """
s1 * G + s2 * p1 s1 * G + s2 * p1
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_double_scalarmult_vartime2( def scalarmult_base_into(
r: Ge25519 | None, r: Point | None, s: Scalar | int
p1: Ge25519, ) -> Point:
s1: Sc25519,
p2: Ge25519,
s2: Sc25519,
) -> Ge25519:
"""
s1 * p1 + s2 * p2
"""
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_scalarmult_base(
r: Ge25519 | None, s: Sc25519 | int
) -> Ge25519:
""" """
s * G s * G
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_scalarmult( def scalarmult_into(
r: Ge25519 | None, p: Ge25519, s: Sc25519 | int r: Point | None, p: Point, s: Scalar | int
) -> Ge25519: ) -> Point:
""" """
s * p s * p
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_pack(r: bytes, p: Ge25519, offset: int = 0) -> bytes: def encodepoint_into(r: bytes | None, p: Point, offset: int = 0) -> bytes:
""" """
Point compression Point compression
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_unpack_vartime( def decodepoint_into(
r: Ge25519 | None, buff: bytes, offset: int = 0 r: Point | None, buff: bytes, offset: int = 0
) -> Ge25519: ) -> Point:
""" """
Point decompression Point decompression
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def base58_addr_encode_check(tag: int, buff: bytes) -> bytes: def xmr_base58_addr_encode_check(tag: int, buff: bytes) -> str:
""" """
Monero block base 58 encoding Monero block base 58 encoding
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def base58_addr_decode_check(buff: bytes) -> tuple[bytes, int]: def xmr_base58_addr_decode_check(buff: bytes) -> tuple[bytes, int]:
""" """
Monero block base 58 decoding, returning (decoded, tag) or raising on Monero block base 58 decoding, returning (decoded, tag) or raising on
error. error.
@ -285,30 +257,43 @@ def base58_addr_decode_check(buff: bytes) -> tuple[bytes, int]:
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_random_scalar(r: Sc25519 | None = None) -> Sc25519: def random_scalar(r: Scalar | None = None) -> Scalar:
""" """
Generates a random scalar Generates a random scalar
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_fast_hash(r: bytes | None, buff: bytes, length: int, offset: int) -> bytes: def fast_hash_into(
r: bytes | None,
buff: bytes,
length: int | None = None,
offset: int = 0,
) -> bytes:
""" """
XMR fast hash XMR fast hash
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_hash_to_ec(r: Ge25519 | None, buff: bytes, length: int, offset: def hash_to_point_into(
int) -> Ge25519: r: Point | None,
buff: bytes,
length: int | None = None,
offset: int = 0,
) -> Point:
""" """
XMR hashing to EC point XMR hashing to EC point
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_hash_to_scalar(r: Sc25519 | None, buff: bytes, length: int, def hash_to_scalar_into(
offset: int) -> Sc25519: r: Scalar | None,
buff: bytes,
length: int | None = None,
offset: int = 0,
) -> Scalar:
""" """
XMR hashing to EC scalar XMR hashing to EC scalar
""" """
@ -316,8 +301,8 @@ offset: int) -> Sc25519:
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_derivation_to_scalar( def xmr_derivation_to_scalar(
r: Sc25519 | None, p: Ge25519, output_index: int r: Scalar | None, p: Point, output_index: int
) -> Sc25519: ) -> Scalar:
""" """
H_s(derivation || varint(output_index)) H_s(derivation || varint(output_index))
""" """
@ -325,8 +310,8 @@ def xmr_derivation_to_scalar(
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_generate_key_derivation( def xmr_generate_key_derivation(
r: Ge25519 | None, A: Ge25519, b: Sc25519 r: Point | None, A: Point, b: Scalar
) -> Ge25519: ) -> Point:
""" """
8*(key2*key1) 8*(key2*key1)
""" """
@ -334,8 +319,8 @@ def xmr_generate_key_derivation(
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_derive_private_key( def xmr_derive_private_key(
r: Sc25519 | None, deriv: Ge25519, idx: int, base: Sc25519 r: Scalar | None, deriv: Point, idx: int, base: Scalar
) -> Sc25519: ) -> Scalar:
""" """
base + H_s(derivation || varint(output_index)) base + H_s(derivation || varint(output_index))
""" """
@ -343,44 +328,26 @@ def xmr_derive_private_key(
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_derive_public_key( def xmr_derive_public_key(
r: Ge25519 | None, deriv: Ge25519, idx: int, base: Ge25519 r: Point | None, deriv: Point, idx: int, base: Point
) -> Ge25519: ) -> Point:
""" """
H_s(derivation || varint(output_index))G + base H_s(derivation || varint(output_index))G + base
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_add_keys2( def add_keys2_into(
r: Ge25519 | None, a: Sc25519, b: Sc25519, B: Ge25519 r: Point | None, a: Scalar, b: Scalar, B: Point
) -> Ge25519: ) -> Point:
"""
aG + bB, G is basepoint
"""
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_add_keys2_vartime(
r: Ge25519 | None, a: Sc25519, b: Sc25519, B: Ge25519
) -> Ge25519:
""" """
aG + bB, G is basepoint aG + bB, G is basepoint
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_add_keys3( def add_keys3_into(
r: Ge25519 | None, a: Sc25519, A: Ge25519, b: Sc25519, B: Ge25519 r: Point | None, a: Scalar, A: Point, b: Scalar, B: Point
) -> Ge25519: ) -> Point:
"""
aA + bB
"""
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_add_keys3_vartime(
r: Ge25519 | None, a: Sc25519, A: Ge25519, b: Sc25519, B: Ge25519
) -> Ge25519:
""" """
aA + bB aA + bB
""" """
@ -388,15 +355,15 @@ def xmr_add_keys3_vartime(
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_get_subaddress_secret_key( def xmr_get_subaddress_secret_key(
r: Sc25519 | None, major: int, minor: int, m: Sc25519 r: Scalar | None, major: int, minor: int, m: Scalar
) -> Sc25519: ) -> Scalar:
""" """
Hs(SubAddr || a || index_major || index_minor) Hs(SubAddr || a || index_major || index_minor)
""" """
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h # extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_gen_c(r: Ge25519 | None, a: Sc25519, amount: int) -> Ge25519: def gen_commitment_into(r: Point | None, a: Scalar, amount: int) -> Point:
""" """
aG + amount * H aG + amount * H
""" """
@ -407,3 +374,6 @@ def ct_equals(a: bytes, b: bytes) -> bool:
""" """
Constant time buffer comparison Constant time buffer comparison
""" """
BP_GI_PRE: bytes
BP_HI_PRE: bytes
BP_TWO_N: bytes

@ -3,7 +3,8 @@ from typing import TypeVar
C = TypeVar("C", bound=int) C = TypeVar("C", bound=int)
def const(c: C) -> C: ... def const(c: C) -> C: ...
def mem_info(verbose: bool | None = None) -> None: ... def mem_info(verbose: bool | int | None = None) -> None: ...
def mem_current() -> int: ... def mem_current() -> int: ...
def mem_total() -> int: ... def mem_total() -> int: ...
def mem_peak() -> int: ... def mem_peak() -> int: ...
def stack_use() -> int: ...

@ -434,6 +434,8 @@ if not utils.BITCOIN_ONLY:
import trezor.enums.CardanoTxWitnessType import trezor.enums.CardanoTxWitnessType
trezor.enums.EthereumDataType trezor.enums.EthereumDataType
import trezor.enums.EthereumDataType import trezor.enums.EthereumDataType
trezor.enums.MoneroNetworkType
import trezor.enums.MoneroNetworkType
trezor.enums.NEMImportanceTransferMode trezor.enums.NEMImportanceTransferMode
import trezor.enums.NEMImportanceTransferMode import trezor.enums.NEMImportanceTransferMode
trezor.enums.NEMModificationType trezor.enums.NEMModificationType
@ -592,8 +594,6 @@ if not utils.BITCOIN_ONLY:
import apps.monero.signing.step_01_init_transaction import apps.monero.signing.step_01_init_transaction
apps.monero.signing.step_02_set_input apps.monero.signing.step_02_set_input
import apps.monero.signing.step_02_set_input import apps.monero.signing.step_02_set_input
apps.monero.signing.step_03_inputs_permutation
import apps.monero.signing.step_03_inputs_permutation
apps.monero.signing.step_04_input_vini apps.monero.signing.step_04_input_vini
import apps.monero.signing.step_04_input_vini import apps.monero.signing.step_04_input_vini
apps.monero.signing.step_05_all_inputs_set apps.monero.signing.step_05_all_inputs_set
@ -606,22 +606,24 @@ if not utils.BITCOIN_ONLY:
import apps.monero.signing.step_09_sign_input import apps.monero.signing.step_09_sign_input
apps.monero.signing.step_10_sign_final apps.monero.signing.step_10_sign_final
import apps.monero.signing.step_10_sign_final import apps.monero.signing.step_10_sign_final
apps.monero.xmr
import apps.monero.xmr
apps.monero.xmr.addresses apps.monero.xmr.addresses
import apps.monero.xmr.addresses import apps.monero.xmr.addresses
apps.monero.xmr.bulletproof apps.monero.xmr.bulletproof
import apps.monero.xmr.bulletproof import apps.monero.xmr.bulletproof
apps.monero.xmr.chacha_poly
import apps.monero.xmr.chacha_poly
apps.monero.xmr.clsag
import apps.monero.xmr.clsag
apps.monero.xmr.credentials apps.monero.xmr.credentials
import apps.monero.xmr.credentials import apps.monero.xmr.credentials
apps.monero.xmr.crypto apps.monero.xmr.crypto_helpers
import apps.monero.xmr.crypto import apps.monero.xmr.crypto_helpers
apps.monero.xmr.crypto.chacha_poly
import apps.monero.xmr.crypto.chacha_poly
apps.monero.xmr.keccak_hasher apps.monero.xmr.keccak_hasher
import apps.monero.xmr.keccak_hasher import apps.monero.xmr.keccak_hasher
apps.monero.xmr.key_image apps.monero.xmr.key_image
import apps.monero.xmr.key_image import apps.monero.xmr.key_image
apps.monero.xmr.mlsag
import apps.monero.xmr.mlsag
apps.monero.xmr.mlsag_hasher apps.monero.xmr.mlsag_hasher
import apps.monero.xmr.mlsag_hasher import apps.monero.xmr.mlsag_hasher
apps.monero.xmr.monero apps.monero.xmr.monero
@ -650,8 +652,6 @@ if not utils.BITCOIN_ONLY:
import apps.monero.xmr.serialize_messages.tx_prefix import apps.monero.xmr.serialize_messages.tx_prefix
apps.monero.xmr.serialize_messages.tx_rsig_bulletproof apps.monero.xmr.serialize_messages.tx_rsig_bulletproof
import apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import apps.monero.xmr.serialize_messages.tx_rsig_bulletproof
apps.monero.xmr.types
import apps.monero.xmr.types
apps.nem apps.nem
import apps.nem import apps.nem
apps.nem.get_address apps.nem.get_address

@ -1,3 +1,9 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from trezor.messages import Failure
if __debug__: if __debug__:
import gc import gc
import micropython import micropython
@ -8,7 +14,7 @@ if __debug__:
PREV_MEM = gc.mem_free() PREV_MEM = gc.mem_free()
CUR_MES = 0 CUR_MES = 0
def log_trace(x=None): def log_trace(x=None) -> None:
log.debug( log.debug(
__name__, __name__,
"Log trace %s, ... F: %s A: %s, S: %s", "Log trace %s, ... F: %s A: %s, S: %s",
@ -18,7 +24,7 @@ if __debug__:
micropython.stack_use(), micropython.stack_use(),
) )
def check_mem(x=""): def check_mem(x: str | int = "") -> None:
global PREV_MEM, CUR_MES global PREV_MEM, CUR_MES
gc.collect() gc.collect()
@ -33,12 +39,12 @@ if __debug__:
CUR_MES += 1 CUR_MES += 1
PREV_MEM = free PREV_MEM = free
def retit(**kwargs): def retit(**kwargs) -> Failure:
from trezor.messages import Failure from trezor.messages import Failure
return Failure(**kwargs) return Failure(**kwargs)
async def diag(ctx, msg, **kwargs): async def diag(ctx, msg, **kwargs) -> Failure:
log.debug(__name__, "----diagnostics") log.debug(__name__, "----diagnostics")
gc.collect() gc.collect()
@ -85,7 +91,7 @@ if __debug__:
bpi.gc_fnc = gc.collect bpi.gc_fnc = gc.collect
bpi.gc_trace = log_trace bpi.gc_trace = log_trace
vals = [crypto.sc_init((1 << 30) - 1 + 16), crypto.sc_init(22222)] vals = [crypto.Scalar((1 << 30) - 1 + 16), crypto.Scalar(22222)]
masks = [crypto.random_scalar(), crypto.random_scalar()] masks = [crypto.random_scalar(), crypto.random_scalar()]
check_mem("BP pre input") check_mem("BP pre input")

@ -1,33 +1,53 @@
from typing import TYPE_CHECKING
from trezor import wire
from trezor.messages import MoneroAddress from trezor.messages import MoneroAddress
from trezor.ui.layouts import show_address from trezor.ui.layouts import show_address
from apps.common import paths from apps.common import paths
from apps.common.keychain import auto_keychain from apps.common.keychain import auto_keychain
from apps.monero import misc from apps.monero import misc
from apps.monero.xmr import addresses, crypto, monero from apps.monero.xmr import addresses, crypto_helpers, monero
from apps.monero.xmr.networks import net_version from apps.monero.xmr.networks import net_version
if TYPE_CHECKING:
from trezor.messages import MoneroGetAddress
from apps.common.keychain import Keychain
@auto_keychain(__name__) @auto_keychain(__name__)
async def get_address(ctx, msg, keychain): async def get_address(
ctx: wire.Context, msg: MoneroGetAddress, keychain: Keychain
) -> MoneroAddress:
await paths.validate_path(ctx, keychain, msg.address_n) await paths.validate_path(ctx, keychain, msg.address_n)
creds = misc.get_creds(keychain, msg.address_n, msg.network_type) creds = misc.get_creds(keychain, msg.address_n, msg.network_type)
addr = creds.address addr = creds.address
if msg.payment_id: have_subaddress = msg.account is not None and msg.minor is not None
have_payment_id = msg.payment_id is not None
if (msg.account is None) != (msg.minor is None):
raise wire.ProcessError("Invalid subaddress indexes")
if have_payment_id and have_subaddress:
raise wire.DataError("Subaddress cannot be integrated")
if have_payment_id:
assert msg.payment_id is not None
if len(msg.payment_id) != 8: if len(msg.payment_id) != 8:
raise ValueError("Invalid payment ID length") raise ValueError("Invalid payment ID length")
addr = addresses.encode_addr( addr = addresses.encode_addr(
net_version(msg.network_type, False, True), net_version(msg.network_type, False, True),
crypto.encodepoint(creds.spend_key_public), crypto_helpers.encodepoint(creds.spend_key_public),
crypto.encodepoint(creds.view_key_public), crypto_helpers.encodepoint(creds.view_key_public),
msg.payment_id, msg.payment_id,
) )
if msg.account or msg.minor: if have_subaddress:
if msg.payment_id: assert msg.account is not None
raise ValueError("Subaddress cannot be integrated") assert msg.minor is not None
pub_spend, pub_view = monero.generate_sub_address_keys( pub_spend, pub_view = monero.generate_sub_address_keys(
creds.view_key_private, creds.spend_key_public, msg.account, msg.minor creds.view_key_private, creds.spend_key_public, msg.account, msg.minor
@ -35,17 +55,17 @@ async def get_address(ctx, msg, keychain):
addr = addresses.encode_addr( addr = addresses.encode_addr(
net_version(msg.network_type, True, False), net_version(msg.network_type, True, False),
crypto.encodepoint(pub_spend), crypto_helpers.encodepoint(pub_spend),
crypto.encodepoint(pub_view), crypto_helpers.encodepoint(pub_view),
) )
if msg.show_display: if msg.show_display:
title = paths.address_n_to_str(msg.address_n) title = paths.address_n_to_str(msg.address_n)
await show_address( await show_address(
ctx, ctx,
address=addr.decode(), address=addr,
address_qr="monero:" + addr.decode(), address_qr="monero:" + addr,
title=title, title=title,
) )
return MoneroAddress(address=addr) return MoneroAddress(address=addr.encode())

@ -14,22 +14,26 @@ encrypted using the private spend key. Here the host sends it back
in `MoneroGetTxKeyRequest.tx_enc_keys` to be decrypted and yet again encrypted in `MoneroGetTxKeyRequest.tx_enc_keys` to be decrypted and yet again encrypted
using the view key, which the host possess. using the view key, which the host possess.
""" """
from typing import TYPE_CHECKING
from trezor import utils from trezor import utils, wire
from trezor.messages import MoneroGetTxKeyAck, MoneroGetTxKeyRequest from trezor.messages import MoneroGetTxKeyAck, MoneroGetTxKeyRequest
from apps.common import paths from apps.common import paths
from apps.common.keychain import auto_keychain from apps.common.keychain import auto_keychain
from apps.monero import layout, misc from apps.monero import layout, misc
from apps.monero.xmr import crypto from apps.monero.xmr import chacha_poly, crypto, crypto_helpers
from apps.monero.xmr.crypto import chacha_poly
_GET_TX_KEY_REASON_TX_KEY = 0
_GET_TX_KEY_REASON_TX_DERIVATION = 1 _GET_TX_KEY_REASON_TX_DERIVATION = 1
if TYPE_CHECKING:
from apps.common.keychain import Keychain
@auto_keychain(__name__) @auto_keychain(__name__)
async def get_tx_keys(ctx, msg: MoneroGetTxKeyRequest, keychain): async def get_tx_keys(
ctx: wire.Context, msg: MoneroGetTxKeyRequest, keychain: Keychain
) -> MoneroGetTxKeyAck:
await paths.validate_path(ctx, keychain, msg.address_n) await paths.validate_path(ctx, keychain, msg.address_n)
do_deriv = msg.reason == _GET_TX_KEY_REASON_TX_DERIVATION do_deriv = msg.reason == _GET_TX_KEY_REASON_TX_DERIVATION
@ -41,7 +45,7 @@ async def get_tx_keys(ctx, msg: MoneroGetTxKeyRequest, keychain):
creds.spend_key_private, creds.spend_key_private,
msg.tx_prefix_hash, msg.tx_prefix_hash,
msg.salt1, msg.salt1,
crypto.decodeint(msg.salt2), crypto_helpers.decodeint(msg.salt2),
) )
# the plain_buff first stores the tx_priv_keys as decrypted here # the plain_buff first stores the tx_priv_keys as decrypted here
@ -52,10 +56,13 @@ async def get_tx_keys(ctx, msg: MoneroGetTxKeyRequest, keychain):
# If return only derivations do tx_priv * view_pub # If return only derivations do tx_priv * view_pub
if do_deriv: if do_deriv:
if msg.view_public_key is None:
raise wire.DataError("Missing view public key")
plain_buff = bytearray(plain_buff) plain_buff = bytearray(plain_buff)
view_pub = crypto.decodepoint(msg.view_public_key) view_pub = crypto_helpers.decodepoint(msg.view_public_key)
tx_priv = crypto.new_scalar() tx_priv = crypto.Scalar()
derivation = crypto.new_point() derivation = crypto.Point()
n_keys = len(plain_buff) // 32 n_keys = len(plain_buff) // 32
for c in range(n_keys): for c in range(n_keys):
crypto.decodeint_into(tx_priv, plain_buff, 32 * c) crypto.decodeint_into(tx_priv, plain_buff, 32 * c)

@ -1,19 +1,29 @@
from trezor.messages import MoneroGetWatchKey, MoneroWatchKey from typing import TYPE_CHECKING
from trezor.messages import MoneroWatchKey
from apps.common import paths from apps.common import paths
from apps.common.keychain import auto_keychain from apps.common.keychain import auto_keychain
from apps.monero import layout, misc from apps.monero import layout, misc
from apps.monero.xmr import crypto from apps.monero.xmr import crypto_helpers
if TYPE_CHECKING:
from trezor.wire import Context
from trezor.messages import MoneroGetWatchKey
from apps.common.keychain import Keychain
@auto_keychain(__name__) @auto_keychain(__name__)
async def get_watch_only(ctx, msg: MoneroGetWatchKey, keychain): async def get_watch_only(
ctx: Context, msg: MoneroGetWatchKey, keychain: Keychain
) -> MoneroWatchKey:
await paths.validate_path(ctx, keychain, msg.address_n) await paths.validate_path(ctx, keychain, msg.address_n)
await layout.require_confirm_watchkey(ctx) await layout.require_confirm_watchkey(ctx)
creds = misc.get_creds(keychain, msg.address_n, msg.network_type) creds = misc.get_creds(keychain, msg.address_n, msg.network_type)
address = creds.address address = creds.address
watch_key = crypto.encodeint(creds.view_key_private) watch_key = crypto_helpers.encodeint(creds.view_key_private)
return MoneroWatchKey(watch_key=watch_key, address=address) return MoneroWatchKey(watch_key=watch_key, address=address.encode())

@ -1,6 +1,8 @@
import gc import gc
from typing import TYPE_CHECKING
from trezor import log, wire from trezor import log, wire
from trezor.crypto import random
from trezor.messages import ( from trezor.messages import (
MoneroExportedKeyImage, MoneroExportedKeyImage,
MoneroKeyImageExportInitAck, MoneroKeyImageExportInitAck,
@ -13,20 +15,28 @@ from trezor.messages import (
from apps.common import paths from apps.common import paths
from apps.common.keychain import auto_keychain from apps.common.keychain import auto_keychain
from apps.monero import layout, misc from apps.monero import layout, misc
from apps.monero.xmr import crypto, key_image, monero from apps.monero.xmr import chacha_poly, crypto, crypto_helpers, key_image, monero
from apps.monero.xmr.crypto import chacha_poly
if TYPE_CHECKING:
from trezor.messages import MoneroKeyImageExportInitRequest
from apps.common.keychain import Keychain
from .xmr.credentials import AccountCreds
@auto_keychain(__name__) @auto_keychain(__name__)
async def key_image_sync(ctx, msg, keychain): async def key_image_sync(
ctx: wire.Context, msg: MoneroKeyImageExportInitRequest, keychain: Keychain
) -> MoneroKeyImageSyncFinalAck:
state = KeyImageSync() state = KeyImageSync()
res = await _init_step(state, ctx, msg, keychain) res = await _init_step(state, ctx, msg, keychain)
while state.current_output + 1 < state.num_outputs: while state.current_output + 1 < state.num_outputs:
msg = await ctx.call(res, MoneroKeyImageSyncStepRequest) step = await ctx.call(res, MoneroKeyImageSyncStepRequest)
res = await _sync_step(state, ctx, msg) res = await _sync_step(state, ctx, step)
gc.collect() gc.collect()
msg = await ctx.call(res, MoneroKeyImageSyncFinalRequest) await ctx.call(res, MoneroKeyImageSyncFinalRequest)
res = await _final_step(state, ctx) res = await _final_step(state, ctx)
return res return res
@ -36,14 +46,19 @@ class KeyImageSync:
def __init__(self): def __init__(self):
self.current_output = -1 self.current_output = -1
self.num_outputs = 0 self.num_outputs = 0
self.expected_hash = None self.expected_hash = b""
self.enc_key = None self.enc_key = b""
self.creds = None self.creds: AccountCreds | None = None
self.subaddresses = {} self.subaddresses = {}
self.hasher = crypto.get_keccak() self.hasher = crypto_helpers.get_keccak()
async def _init_step(s, ctx, msg, keychain): async def _init_step(
s: KeyImageSync,
ctx: wire.Context,
msg: MoneroKeyImageExportInitRequest,
keychain: Keychain,
) -> MoneroKeyImageExportInitAck:
await paths.validate_path(ctx, keychain, msg.address_n) await paths.validate_path(ctx, keychain, msg.address_n)
s.creds = misc.get_creds(keychain, msg.address_n, msg.network_type) s.creds = misc.get_creds(keychain, msg.address_n, msg.network_type)
@ -52,7 +67,7 @@ async def _init_step(s, ctx, msg, keychain):
s.num_outputs = msg.num s.num_outputs = msg.num
s.expected_hash = msg.hash s.expected_hash = msg.hash
s.enc_key = crypto.random_bytes(32) s.enc_key = random.bytes(32)
for sub in msg.subs: for sub in msg.subs:
monero.compute_subaddresses( monero.compute_subaddresses(
@ -62,7 +77,11 @@ async def _init_step(s, ctx, msg, keychain):
return MoneroKeyImageExportInitAck() return MoneroKeyImageExportInitAck()
async def _sync_step(s, ctx, tds): async def _sync_step(
s: KeyImageSync, ctx: wire.Context, tds: MoneroKeyImageSyncStepRequest
) -> MoneroKeyImageSyncStepAck:
assert s.creds is not None
if not tds.tdis: if not tds.tdis:
raise wire.DataError("Empty") raise wire.DataError("Empty")
@ -99,7 +118,7 @@ async def _sync_step(s, ctx, tds):
return MoneroKeyImageSyncStepAck(kis=kis) return MoneroKeyImageSyncStepAck(kis=kis)
async def _final_step(s, ctx): async def _final_step(s, ctx: wire.Context) -> MoneroKeyImageSyncFinalAck:
if s.current_output + 1 != s.num_outputs: if s.current_output + 1 != s.num_outputs:
raise wire.DataError("Invalid number of outputs") raise wire.DataError("Invalid number of outputs")

@ -14,18 +14,21 @@ DUMMY_PAYMENT_ID = b"\x00\x00\x00\x00\x00\x00\x00\x00"
if TYPE_CHECKING: if TYPE_CHECKING:
from apps.monero.signing.state import State from trezor.enums import MoneroNetworkType
from trezor.messages import ( from trezor.messages import (
MoneroTransactionData, MoneroTransactionData,
MoneroTransactionDestinationEntry, MoneroTransactionDestinationEntry,
) )
from trezor.wire import Context
from .signing.state import State
def _format_amount(value):
def _format_amount(value: int) -> str:
return f"{strings.format_amount(value, 12)} XMR" return f"{strings.format_amount(value, 12)} XMR"
async def require_confirm_watchkey(ctx): async def require_confirm_watchkey(ctx: Context) -> None:
await confirm_action( await confirm_action(
ctx, ctx,
"get_watchkey", "get_watchkey",
@ -37,7 +40,7 @@ async def require_confirm_watchkey(ctx):
) )
async def require_confirm_keyimage_sync(ctx): async def require_confirm_keyimage_sync(ctx: Context) -> None:
await confirm_action( await confirm_action(
ctx, ctx,
"key_image_sync", "key_image_sync",
@ -49,7 +52,7 @@ async def require_confirm_keyimage_sync(ctx):
) )
async def require_confirm_live_refresh(ctx): async def require_confirm_live_refresh(ctx: Context) -> None:
await confirm_action( await confirm_action(
ctx, ctx,
"live_refresh", "live_refresh",
@ -61,7 +64,7 @@ async def require_confirm_live_refresh(ctx):
) )
async def require_confirm_tx_key(ctx, export_key=False): async def require_confirm_tx_key(ctx: Context, export_key: bool = False) -> None:
if export_key: if export_key:
description = "Do you really want to export tx_key?" description = "Do you really want to export tx_key?"
else: else:
@ -78,8 +81,11 @@ async def require_confirm_tx_key(ctx, export_key=False):
async def require_confirm_transaction( async def require_confirm_transaction(
ctx, state: State, tsx_data: MoneroTransactionData, network_type: int ctx: Context,
): state: State,
tsx_data: MoneroTransactionData,
network_type: MoneroNetworkType,
) -> None:
""" """
Ask for confirmation from user. Ask for confirmation from user.
""" """
@ -87,8 +93,6 @@ async def require_confirm_transaction(
outputs = tsx_data.outputs outputs = tsx_data.outputs
change_idx = get_change_addr_idx(outputs, tsx_data.change_dts) change_idx = get_change_addr_idx(outputs, tsx_data.change_dts)
has_integrated = bool(tsx_data.integrated_indices)
has_payment = bool(tsx_data.payment_id)
if tsx_data.unlock_time != 0: if tsx_data.unlock_time != 0:
await _require_confirm_unlock_time(ctx, tsx_data.unlock_time) await _require_confirm_unlock_time(ctx, tsx_data.unlock_time)
@ -100,13 +104,17 @@ async def require_confirm_transaction(
is_dummy = change_idx is None and dst.amount == 0 and len(outputs) == 2 is_dummy = change_idx is None and dst.amount == 0 and len(outputs) == 2
if is_dummy: if is_dummy:
continue # Dummy output does not need confirmation continue # Dummy output does not need confirmation
if has_integrated and idx in tsx_data.integrated_indices: if tsx_data.integrated_indices and idx in tsx_data.integrated_indices:
cur_payment = tsx_data.payment_id cur_payment = tsx_data.payment_id
else: else:
cur_payment = None cur_payment = None
await _require_confirm_output(ctx, dst, network_type, cur_payment) await _require_confirm_output(ctx, dst, network_type, cur_payment)
if has_payment and not has_integrated and tsx_data.payment_id != DUMMY_PAYMENT_ID: if (
tsx_data.payment_id
and not tsx_data.integrated_indices
and tsx_data.payment_id != DUMMY_PAYMENT_ID
):
await _require_confirm_payment_id(ctx, tsx_data.payment_id) await _require_confirm_payment_id(ctx, tsx_data.payment_id)
await _require_confirm_fee(ctx, tsx_data.fee) await _require_confirm_fee(ctx, tsx_data.fee)
@ -114,8 +122,11 @@ async def require_confirm_transaction(
async def _require_confirm_output( async def _require_confirm_output(
ctx, dst: MoneroTransactionDestinationEntry, network_type: int, payment_id: bytes ctx: Context,
): dst: MoneroTransactionDestinationEntry,
network_type: MoneroNetworkType,
payment_id: bytes | None,
) -> None:
""" """
Single transaction destination confirmation Single transaction destination confirmation
""" """
@ -129,14 +140,14 @@ async def _require_confirm_output(
await confirm_output( await confirm_output(
ctx, ctx,
address=addr.decode(), address=addr,
amount=_format_amount(dst.amount), amount=_format_amount(dst.amount),
font_amount=ui.BOLD, font_amount=ui.BOLD,
br_code=ButtonRequestType.SignTx, br_code=ButtonRequestType.SignTx,
) )
async def _require_confirm_payment_id(ctx, payment_id: bytes): async def _require_confirm_payment_id(ctx: Context, payment_id: bytes) -> None:
await confirm_blob( await confirm_blob(
ctx, ctx,
"confirm_payment_id", "confirm_payment_id",
@ -146,7 +157,7 @@ async def _require_confirm_payment_id(ctx, payment_id: bytes):
) )
async def _require_confirm_fee(ctx, fee): async def _require_confirm_fee(ctx: Context, fee: int) -> None:
await confirm_metadata( await confirm_metadata(
ctx, ctx,
"confirm_final", "confirm_final",
@ -158,7 +169,7 @@ async def _require_confirm_fee(ctx, fee):
) )
async def _require_confirm_unlock_time(ctx, unlock_time): async def _require_confirm_unlock_time(ctx: Context, unlock_time: int) -> None:
await confirm_metadata( await confirm_metadata(
ctx, ctx,
"confirm_locktime", "confirm_locktime",
@ -170,12 +181,12 @@ async def _require_confirm_unlock_time(ctx, unlock_time):
class TransactionStep(ui.Component): class TransactionStep(ui.Component):
def __init__(self, state, info): def __init__(self, state: State, info: list[str]) -> None:
super().__init__() super().__init__()
self.state = state self.state = state
self.info = info self.info = info
def on_render(self): def on_render(self) -> None:
state = self.state state = self.state
info = self.info info = self.info
ui.header("Signing transaction", ui.ICON_SEND, ui.TITLE_GREY, ui.BG, ui.BLUE) ui.header("Signing transaction", ui.ICON_SEND, ui.TITLE_GREY, ui.BG, ui.BLUE)
@ -187,12 +198,12 @@ class TransactionStep(ui.Component):
class KeyImageSyncStep(ui.Component): class KeyImageSyncStep(ui.Component):
def __init__(self, current, total_num): def __init__(self, current: int, total_num: int) -> None:
super().__init__() super().__init__()
self.current = current self.current = current
self.total_num = total_num self.total_num = total_num
def on_render(self): def on_render(self) -> None:
current = self.current current = self.current
total_num = self.total_num total_num = self.total_num
ui.header("Syncing", ui.ICON_SEND, ui.TITLE_GREY, ui.BG, ui.BLUE) ui.header("Syncing", ui.ICON_SEND, ui.TITLE_GREY, ui.BG, ui.BLUE)
@ -201,11 +212,11 @@ class KeyImageSyncStep(ui.Component):
class LiveRefreshStep(ui.Component): class LiveRefreshStep(ui.Component):
def __init__(self, current): def __init__(self, current: int) -> None:
super().__init__() super().__init__()
self.current = current self.current = current
def on_render(self): def on_render(self) -> None:
current = self.current current = self.current
ui.header("Refreshing", ui.ICON_SEND, ui.TITLE_GREY, ui.BG, ui.BLUE) ui.header("Refreshing", ui.ICON_SEND, ui.TITLE_GREY, ui.BG, ui.BLUE)
p = (1000 * current // 8) % 1000 p = (1000 * current // 8) % 1000
@ -215,13 +226,11 @@ class LiveRefreshStep(ui.Component):
) )
async def transaction_step(state: State, step: int, sub_step: int | None = None): async def transaction_step(state: State, step: int, sub_step: int = 0) -> None:
if step == 0: if step == 0:
info = ["Signing..."] info = ["Signing..."]
elif step == state.STEP_INP: elif step == state.STEP_INP:
info = ["Processing inputs", f"{sub_step + 1}/{state.input_count}"] info = ["Processing inputs", f"{sub_step + 1}/{state.input_count}"]
elif step == state.STEP_PERM:
info = ["Sorting..."]
elif step == state.STEP_VINI: elif step == state.STEP_VINI:
info = ["Hashing inputs", f"{sub_step + 1}/{state.input_count}"] info = ["Hashing inputs", f"{sub_step + 1}/{state.input_count}"]
elif step == state.STEP_ALL_IN: elif step == state.STEP_ALL_IN:
@ -239,13 +248,13 @@ async def transaction_step(state: State, step: int, sub_step: int | None = None)
await Popup(TransactionStep(state, info)) await Popup(TransactionStep(state, info))
async def keyimage_sync_step(ctx, current, total_num): async def keyimage_sync_step(ctx: Context, current: int | None, total_num: int) -> None:
if current is None: if current is None:
return return
await Popup(KeyImageSyncStep(current, total_num)) await Popup(KeyImageSyncStep(current, total_num))
async def live_refresh_step(ctx, current): async def live_refresh_step(ctx: Context, current: int | None) -> None:
if current is None: if current is None:
return return
await Popup(LiveRefreshStep(current)) await Popup(LiveRefreshStep(current))

@ -1,4 +1,5 @@
import gc import gc
from typing import TYPE_CHECKING
import storage.cache import storage.cache
from trezor import log from trezor import log
@ -6,7 +7,6 @@ from trezor.enums import MessageType
from trezor.messages import ( from trezor.messages import (
MoneroLiveRefreshFinalAck, MoneroLiveRefreshFinalAck,
MoneroLiveRefreshStartAck, MoneroLiveRefreshStartAck,
MoneroLiveRefreshStartRequest,
MoneroLiveRefreshStepAck, MoneroLiveRefreshStepAck,
MoneroLiveRefreshStepRequest, MoneroLiveRefreshStepRequest,
) )
@ -14,40 +14,49 @@ from trezor.messages import (
from apps.common import paths from apps.common import paths
from apps.common.keychain import auto_keychain from apps.common.keychain import auto_keychain
from apps.monero import layout, misc from apps.monero import layout, misc
from apps.monero.xmr import crypto, key_image, monero from apps.monero.xmr import chacha_poly, crypto, crypto_helpers, key_image, monero
from apps.monero.xmr.crypto import chacha_poly
if TYPE_CHECKING:
from trezor.messages import MoneroLiveRefreshStartRequest
from trezor.wire import Context
from apps.common.keychain import Keychain
from .xmr.credentials import AccountCreds
@auto_keychain(__name__) @auto_keychain(__name__)
async def live_refresh(ctx, msg: MoneroLiveRefreshStartRequest, keychain): async def live_refresh(
ctx: Context, msg: MoneroLiveRefreshStartRequest, keychain: Keychain
) -> MoneroLiveRefreshFinalAck:
state = LiveRefreshState() state = LiveRefreshState()
res = await _init_step(state, ctx, msg, keychain) res = await _init_step(state, ctx, msg, keychain)
while True: while True:
msg = await ctx.call_any( step = await ctx.call_any(
res, res,
MessageType.MoneroLiveRefreshStepRequest, MessageType.MoneroLiveRefreshStepRequest,
MessageType.MoneroLiveRefreshFinalRequest, MessageType.MoneroLiveRefreshFinalRequest,
) )
del res del res
if msg.MESSAGE_WIRE_TYPE == MessageType.MoneroLiveRefreshStepRequest: if MoneroLiveRefreshStepRequest.is_type_of(step):
res = await _refresh_step(state, ctx, msg) res = await _refresh_step(state, ctx, step)
else: else:
return MoneroLiveRefreshFinalAck() return MoneroLiveRefreshFinalAck()
gc.collect() gc.collect()
return res
class LiveRefreshState: class LiveRefreshState:
def __init__(self): def __init__(self) -> None:
self.current_output = 0 self.current_output = 0
self.creds = None self.creds: AccountCreds | None = None
async def _init_step( async def _init_step(
s: LiveRefreshState, ctx, msg: MoneroLiveRefreshStartRequest, keychain s: LiveRefreshState,
): ctx: Context,
msg: MoneroLiveRefreshStartRequest,
keychain: Keychain,
) -> MoneroLiveRefreshStartAck:
await paths.validate_path(ctx, keychain, msg.address_n) await paths.validate_path(ctx, keychain, msg.address_n)
if not storage.cache.get(storage.cache.APP_MONERO_LIVE_REFRESH): if not storage.cache.get(storage.cache.APP_MONERO_LIVE_REFRESH):
@ -59,7 +68,11 @@ async def _init_step(
return MoneroLiveRefreshStartAck() return MoneroLiveRefreshStartAck()
async def _refresh_step(s: LiveRefreshState, ctx, msg: MoneroLiveRefreshStepRequest): async def _refresh_step(
s: LiveRefreshState, ctx: Context, msg: MoneroLiveRefreshStepRequest
) -> MoneroLiveRefreshStepAck:
assert s.creds is not None
buff = bytearray(32 * 3) buff = bytearray(32 * 3)
buff_mv = memoryview(buff) buff_mv = memoryview(buff)
@ -74,14 +87,14 @@ async def _refresh_step(s: LiveRefreshState, ctx, msg: MoneroLiveRefreshStepRequ
# If subaddr: # If subaddr:
# spend_priv += Hs("SubAddr" || view_key_private || major || minor) # spend_priv += Hs("SubAddr" || view_key_private || major || minor)
# out_key = spend_priv * G, KI: spend_priv * Hp(out_key) # out_key = spend_priv * G, KI: spend_priv * Hp(out_key)
out_key = crypto.decodepoint(msg.out_key) out_key = crypto_helpers.decodepoint(msg.out_key)
recv_deriv = crypto.decodepoint(msg.recv_deriv) recv_deriv = crypto_helpers.decodepoint(msg.recv_deriv)
received_index = msg.sub_addr_major, msg.sub_addr_minor received_index = msg.sub_addr_major, msg.sub_addr_minor
spend_priv, ki = monero.generate_tx_spend_and_key_image( spend_priv, ki = monero.generate_tx_spend_and_key_image(
s.creds, out_key, recv_deriv, msg.real_out_idx, received_index s.creds, out_key, recv_deriv, msg.real_out_idx, received_index
) )
ki_enc = crypto.encodepoint(ki) ki_enc = crypto_helpers.encodepoint(ki)
sig = key_image.generate_ring_signature(ki_enc, ki, [out_key], spend_priv, 0, False) sig = key_image.generate_ring_signature(ki_enc, ki, [out_key], spend_priv, 0, False)
del spend_priv # spend_priv never leaves the device del spend_priv # spend_priv never leaves the device

@ -1,10 +1,18 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from apps.monero.xmr.types import Sc25519 from apps.common.keychain import Keychain
from apps.common.paths import Bip32Path
from trezor.enums import MoneroNetworkType
def get_creds(keychain, address_n=None, network_type=None): from .xmr.crypto import Scalar
from .xmr.credentials import AccountCreds
def get_creds(
keychain: Keychain, address_n: Bip32Path, network_type: MoneroNetworkType
) -> AccountCreds:
from apps.monero.xmr import monero from apps.monero.xmr import monero
from apps.monero.xmr.credentials import AccountCreds from apps.monero.xmr.credentials import AccountCreds
@ -18,25 +26,30 @@ def get_creds(keychain, address_n=None, network_type=None):
def compute_tx_key( def compute_tx_key(
spend_key_private: Sc25519, spend_key_private: Scalar,
tx_prefix_hash: bytes, tx_prefix_hash: bytes,
salt: bytes, salt: bytes,
rand_mult_num: Sc25519, rand_mult_num: Scalar,
) -> bytes: ) -> bytes:
from apps.monero.xmr import crypto from apps.monero.xmr import crypto, crypto_helpers
rand_inp = crypto.sc_add(spend_key_private, rand_mult_num) rand_inp = crypto.sc_add_into(None, spend_key_private, rand_mult_num)
passwd = crypto.keccak_2hash(crypto.encodeint(rand_inp) + tx_prefix_hash) passwd = crypto_helpers.keccak_2hash(
tx_key = crypto.compute_hmac(salt, passwd) crypto_helpers.encodeint(rand_inp) + tx_prefix_hash
)
tx_key = crypto_helpers.compute_hmac(salt, passwd)
return tx_key return tx_key
def compute_enc_key_host( def compute_enc_key_host(
view_key_private: Sc25519, tx_prefix_hash: bytes view_key_private: Scalar, tx_prefix_hash: bytes
) -> tuple[bytes, bytes]: ) -> tuple[bytes, bytes]:
from apps.monero.xmr import crypto from trezor.crypto import random
from apps.monero.xmr import crypto_helpers
salt = crypto.random_bytes(32)
passwd = crypto.keccak_2hash(crypto.encodeint(view_key_private) + tx_prefix_hash) salt = random.bytes(32)
tx_key = crypto.compute_hmac(salt, passwd) passwd = crypto_helpers.keccak_2hash(
crypto_helpers.encodeint(view_key_private) + tx_prefix_hash
)
tx_key = crypto_helpers.compute_hmac(salt, passwd)
return tx_key, salt return tx_key, salt

@ -1,4 +1,5 @@
import gc import gc
from typing import TYPE_CHECKING
from trezor import log, utils, wire from trezor import log, utils, wire
from trezor.enums import MessageType from trezor.enums import MessageType
@ -6,9 +7,15 @@ from trezor.enums import MessageType
from apps.common.keychain import auto_keychain from apps.common.keychain import auto_keychain
from apps.monero.signing.state import State from apps.monero.signing.state import State
if TYPE_CHECKING:
from trezor.messages import MoneroTransactionFinalAck
from apps.common.keychain import Keychain
@auto_keychain(__name__) @auto_keychain(__name__)
async def sign_tx(ctx, received_msg, keychain): async def sign_tx(
ctx: wire.Context, received_msg, keychain: Keychain
) -> MoneroTransactionFinalAck:
state = State(ctx) state = State(ctx)
mods = utils.unimport_begin() mods = utils.unimport_begin()
@ -34,7 +41,7 @@ async def sign_tx(ctx, received_msg, keychain):
return result_msg return result_msg
async def sign_tx_dispatch(state, msg, keychain): async def sign_tx_dispatch(state: State, msg, keychain: Keychain) -> tuple:
if msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInitRequest: if msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInitRequest:
from apps.monero.signing import step_01_init_transaction from apps.monero.signing import step_01_init_transaction
@ -57,14 +64,6 @@ async def sign_tx_dispatch(state, msg, keychain):
), ),
) )
elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInputsPermutationRequest:
from apps.monero.signing import step_03_inputs_permutation
return (
await step_03_inputs_permutation.tsx_inputs_permutation(state, msg.perm),
(MessageType.MoneroTransactionInputViniRequest,),
)
elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInputViniRequest: elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInputViniRequest:
from apps.monero.signing import step_04_input_vini from apps.monero.signing import step_04_input_vini

@ -16,8 +16,7 @@ class NotEnoughOutputsError(wire.DataError):
class RctType: 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 Bulletproof2 like RCTTypeFull and RCTTypeSimple but currently we use only CLSAG
""" """
Bulletproof2 = 4
CLSAG = 5 CLSAG = 5

@ -3,10 +3,9 @@ from typing import TYPE_CHECKING
from trezor import utils from trezor import utils
from apps.monero.xmr import crypto from apps.monero.xmr import crypto, crypto_helpers
if TYPE_CHECKING: if TYPE_CHECKING:
from apps.monero.xmr.types import Sc25519
from trezor.messages import ( from trezor.messages import (
MoneroTransactionDestinationEntry, MoneroTransactionDestinationEntry,
MoneroTransactionSourceEntry, MoneroTransactionSourceEntry,
@ -20,7 +19,10 @@ _BUILD_KEY_BUFFER = bytearray(_SECRET_LENGTH + _DISCRIMINATOR_LENGTH + _INDEX_LE
def _build_key( def _build_key(
secret, discriminator=None, index: int = None, out: bytes = None secret: bytes,
discriminator: bytes,
index: int | None = None,
out: bytes | None = None,
) -> bytes: ) -> bytes:
""" """
Creates an unique-purpose key Creates an unique-purpose key
@ -48,81 +50,81 @@ def _build_key(
offset += 1 offset += 1
index = shifted index = shifted
return crypto.keccak_2hash(key_buff, out) return crypto_helpers.keccak_2hash(key_buff, out)
def hmac_key_txin(key_hmac, idx: int) -> bytes: def hmac_key_txin(key_hmac: bytes, idx: int) -> bytes:
""" """
(TxSourceEntry[i] || tx.vin[i]) hmac key (TxSourceEntry[i] || tx.vin[i]) hmac key
""" """
return _build_key(key_hmac, b"txin", idx) return _build_key(key_hmac, b"txin", idx)
def hmac_key_txin_comm(key_hmac, idx: int) -> bytes: def hmac_key_txin_comm(key_hmac: bytes, idx: int) -> bytes:
""" """
pseudo_outputs[i] hmac key. Pedersen commitment for inputs. pseudo_outputs[i] hmac key. Pedersen commitment for inputs.
""" """
return _build_key(key_hmac, b"txin-comm", idx) return _build_key(key_hmac, b"txin-comm", idx)
def hmac_key_txdst(key_hmac, idx: int) -> bytes: def hmac_key_txdst(key_hmac: bytes, idx: int) -> bytes:
""" """
TxDestinationEntry[i] hmac key TxDestinationEntry[i] hmac key
""" """
return _build_key(key_hmac, b"txdest", idx) return _build_key(key_hmac, b"txdest", idx)
def hmac_key_txout(key_hmac, idx: int) -> bytes: def hmac_key_txout(key_hmac: bytes, idx: int) -> bytes:
""" """
(TxDestinationEntry[i] || tx.vout[i]) hmac key (TxDestinationEntry[i] || tx.vout[i]) hmac key
""" """
return _build_key(key_hmac, b"txout", idx) return _build_key(key_hmac, b"txout", idx)
def hmac_key_txout_asig(key_hmac, idx: int) -> bytes: def hmac_key_txout_asig(key_hmac: bytes, idx: int) -> bytes:
""" """
rsig[i] hmac key. Range signature HMAC rsig[i] hmac key. Range signature HMAC
""" """
return _build_key(key_hmac, b"txout-asig", idx) return _build_key(key_hmac, b"txout-asig", idx)
def enc_key_txin_alpha(key_enc, idx: int) -> bytes: def enc_key_txin_alpha(key_enc: bytes, idx: int) -> bytes:
""" """
Chacha20Poly1305 encryption key for alpha[i] used in Pedersen commitment in pseudo_outs[i] Chacha20Poly1305 encryption key for alpha[i] used in Pedersen commitment in pseudo_outs[i]
""" """
return _build_key(key_enc, b"txin-alpha", idx) return _build_key(key_enc, b"txin-alpha", idx)
def enc_key_spend(key_enc, idx: int) -> bytes: def enc_key_spend(key_enc: bytes, idx: int) -> bytes:
""" """
Chacha20Poly1305 encryption key for alpha[i] used in Pedersen commitment in pseudo_outs[i] Chacha20Poly1305 encryption key for alpha[i] used in Pedersen commitment in pseudo_outs[i]
""" """
return _build_key(key_enc, b"txin-spend", idx) return _build_key(key_enc, b"txin-spend", idx)
def enc_key_cout(key_enc, idx: int = None) -> bytes: def enc_key_cout(key_enc: bytes, idx: int | None = None) -> bytes:
""" """
Chacha20Poly1305 encryption key for multisig C values from MLASG. Chacha20Poly1305 encryption key for multisig C values from MLASG.
""" """
return _build_key(key_enc, b"cout", idx) return _build_key(key_enc, b"cout", idx)
def key_signature(master, idx: int, is_iv=False) -> bytes: def key_signature(master: bytes, idx: int, is_iv: bool = False) -> bytes:
""" """
Generates signature offloading related offloading keys Generates signature offloading related offloading keys
""" """
return _build_key(master, b"sig-iv" if is_iv else b"sig-key", idx) return _build_key(master, b"sig-iv" if is_iv else b"sig-key", idx)
def det_comm_masks(key_enc, idx: int) -> Sc25519: def det_comm_masks(key_enc: bytes, idx: int) -> crypto.Scalar:
""" """
Deterministic output commitment masks Deterministic output commitment masks
""" """
return crypto.decodeint(_build_key(key_enc, b"out-mask", idx)) return crypto_helpers.decodeint(_build_key(key_enc, b"out-mask", idx))
def gen_hmac_vini( def gen_hmac_vini(
key, src_entr: MoneroTransactionSourceEntry, vini_bin: bytes, idx: int key: bytes, src_entr: MoneroTransactionSourceEntry, vini_bin: bytes, idx: int
) -> bytes: ) -> bytes:
""" """
Computes hmac (TxSourceEntry[i] || tx.vin[i]) Computes hmac (TxSourceEntry[i] || tx.vin[i])
@ -151,12 +153,12 @@ def gen_hmac_vini(
kwriter.write(vini_bin) kwriter.write(vini_bin)
hmac_key_vini = hmac_key_txin(key, idx) hmac_key_vini = hmac_key_txin(key, idx)
hmac_vini = crypto.compute_hmac(hmac_key_vini, kwriter.get_digest()) hmac_vini = crypto_helpers.compute_hmac(hmac_key_vini, kwriter.get_digest())
return hmac_vini return hmac_vini
def gen_hmac_vouti( def gen_hmac_vouti(
key, dst_entr: MoneroTransactionDestinationEntry, tx_out_bin: bytes, idx: int key: bytes, dst_entr: MoneroTransactionDestinationEntry, tx_out_bin: bytes, idx: int
) -> bytes: ) -> bytes:
""" """
Generates HMAC for (TxDestinationEntry[i] || tx.vout[i]) Generates HMAC for (TxDestinationEntry[i] || tx.vout[i])
@ -169,12 +171,12 @@ def gen_hmac_vouti(
kwriter.write(tx_out_bin) kwriter.write(tx_out_bin)
hmac_key_vouti = hmac_key_txout(key, idx) hmac_key_vouti = hmac_key_txout(key, idx)
hmac_vouti = crypto.compute_hmac(hmac_key_vouti, kwriter.get_digest()) hmac_vouti = crypto_helpers.compute_hmac(hmac_key_vouti, kwriter.get_digest())
return hmac_vouti return hmac_vouti
def gen_hmac_tsxdest( def gen_hmac_tsxdest(
key, dst_entr: MoneroTransactionDestinationEntry, idx: int key: bytes, dst_entr: MoneroTransactionDestinationEntry, idx: int
) -> bytes: ) -> bytes:
""" """
Generates HMAC for TxDestinationEntry[i] Generates HMAC for TxDestinationEntry[i]
@ -186,7 +188,7 @@ def gen_hmac_tsxdest(
kwriter.write(protobuf.dump_message_buffer(dst_entr)) kwriter.write(protobuf.dump_message_buffer(dst_entr))
hmac_key = hmac_key_txdst(key, idx) hmac_key = hmac_key_txdst(key, idx)
hmac_tsxdest = crypto.compute_hmac(hmac_key, kwriter.get_digest()) hmac_tsxdest = crypto_helpers.compute_hmac(hmac_key, kwriter.get_digest())
return hmac_tsxdest return hmac_tsxdest

@ -7,8 +7,10 @@ from trezor import log
from apps.monero.xmr import crypto from apps.monero.xmr import crypto
if TYPE_CHECKING: if TYPE_CHECKING:
from apps.monero.xmr.types import Ge25519, Sc25519 from trezor.wire import Context
from apps.monero.xmr.crypto import Point, Scalar
from apps.monero.xmr.credentials import AccountCreds from apps.monero.xmr.credentials import AccountCreds
from trezor.messages import MoneroTransactionDestinationEntry
Subaddresses = dict[bytes, tuple[int, int]] Subaddresses = dict[bytes, tuple[int, int]]
@ -17,14 +19,13 @@ class State:
STEP_INIT = const(0) STEP_INIT = const(0)
STEP_INP = const(100) STEP_INP = const(100)
STEP_PERM = const(200)
STEP_VINI = const(300) STEP_VINI = const(300)
STEP_ALL_IN = const(350) STEP_ALL_IN = const(350)
STEP_OUT = const(400) STEP_OUT = const(400)
STEP_ALL_OUT = const(500) STEP_ALL_OUT = const(500)
STEP_SIGN = const(600) STEP_SIGN = const(600)
def __init__(self, ctx): def __init__(self, ctx: Context) -> None:
from apps.monero.xmr.keccak_hasher import KeccakXmrArchive from apps.monero.xmr.keccak_hasher import KeccakXmrArchive
from apps.monero.xmr.mlsag_hasher import PreMlsagHasher from apps.monero.xmr.mlsag_hasher import PreMlsagHasher
@ -51,8 +52,8 @@ class State:
- for subaddresses the `r` is commonly denoted as `s`, however it is still just a random number - for subaddresses the `r` is commonly denoted as `s`, however it is still just a random number
- the keys are used to derive the one time address and its keys (P = H(A*r)*G + B) - the keys are used to derive the one time address and its keys (P = H(A*r)*G + B)
""" """
self.tx_priv: Sc25519 = None self.tx_priv: Scalar | None = None
self.tx_pub: Ge25519 = None self.tx_pub: Point | None = None
""" """
In some cases when subaddresses are used we need more tx_keys In some cases when subaddresses are used we need more tx_keys
@ -64,58 +65,54 @@ class State:
self.client_version = 0 self.client_version = 0
self.hard_fork = 12 self.hard_fork = 12
self.input_count = 0 self.input_count: int | None = 0
self.output_count = 0 self.output_count = 0
self.progress_total = 0 self.progress_total = 0
self.progress_cur = 0 self.progress_cur = 0
self.output_change = None self.output_change: "MoneroTransactionDestinationEntry" | None = None
self.fee = 0 self.fee: int | None = 0
self.tx_type = 0 self.tx_type = 0
# wallet sub-address major index # wallet sub-address major index
self.account_idx = 0 self.account_idx: int | None = 0
# contains additional tx keys if need_additional_tx_keys is True # contains additional tx keys if need_additional_tx_keys is True
self.additional_tx_private_keys: list[Sc25519] = [] self.additional_tx_private_keys: list[Scalar] = []
self.additional_tx_public_keys: list[bytes] = [] self.additional_tx_public_keys: list[bytes] | None = []
# currently processed input/output index # currently processed input/output index
self.current_input_index = -1 self.current_input_index = -1
self.current_output_index = -1 self.current_output_index: int | None = -1
self.is_processing_offloaded = False self.is_processing_offloaded = False
# for pseudo_out recomputation from new mask # for pseudo_out recomputation from new mask
self.input_last_amount = 0 self.input_last_amount: int | None = 0
self.summary_inputs_money = 0 self.summary_inputs_money: int | None = 0
self.summary_outs_money = 0 self.summary_outs_money: int | None = 0
# output commitments # output commitments
self.output_pk_commitments: list[bytes] = [] self.output_pk_commitments: list[bytes] | None = []
self.output_amounts: list[int] = [] self.output_amounts: list[int] | None = []
# output *range proof* masks. HP10+ makes them deterministic. # output *range proof* masks. HP10+ makes them deterministic.
self.output_masks: list[Sc25519] = [] self.output_masks: list[Scalar] | None = []
# the range proofs are calculated in batches, this denotes the grouping # the range proofs are calculated in batches, this denotes the grouping
self.rsig_grouping: list[int] = [] self.rsig_grouping: list[int] | None = []
# is range proof computing offloaded or not # is range proof computing offloaded or not
self.rsig_offload = False self.rsig_offload: bool | None = False
# sum of all inputs' pseudo out masks # sum of all inputs' pseudo out masks
self.sumpouts_alphas: Sc25519 = crypto.sc_0() self.sumpouts_alphas: Scalar = crypto.Scalar(0)
# sum of all output' pseudo out masks # sum of all output' pseudo out masks
self.sumout: Sc25519 = crypto.sc_0() self.sumout: Scalar = crypto.Scalar(0)
self.subaddresses: Subaddresses = {} self.subaddresses: Subaddresses | None = {}
# TX_EXTRA_NONCE extra field for tx.extra, due to sort_tx_extra() # TX_EXTRA_NONCE extra field for tx.extra, due to sort_tx_extra()
self.extra_nonce = None self.extra_nonce: bytes | None = None
# contains an array where each item denotes the input's position
# (inputs are sorted by key images)
self.source_permutation: list[int] = []
# Last key image seen. Used for input permutation correctness check # Last key image seen. Used for input permutation correctness check
self.last_ki: bytes | None = None self.last_ki: bytes | None = None
@ -124,14 +121,14 @@ class State:
self.opening_key: bytes | None = None self.opening_key: bytes | None = None
# Step transition automaton # Step transition automaton
self.last_step = self.STEP_INIT self.last_step: int | None = self.STEP_INIT
""" """
Tx prefix hasher/hash. We use the hasher to incrementally hash and then Tx prefix hasher/hash. We use the hasher to incrementally hash and then
store the final hash in tx_prefix_hash. store the final hash in tx_prefix_hash.
See Monero-Trezor documentation section 3.3 for more details. See Monero-Trezor documentation section 3.3 for more details.
""" """
self.tx_prefix_hasher = KeccakXmrArchive() self.tx_prefix_hasher: KeccakXmrArchive | None = KeccakXmrArchive()
self.tx_prefix_hash: bytes | None = None self.tx_prefix_hash: bytes | None = None
""" """
@ -139,10 +136,10 @@ class State:
Contains tx_prefix_hash. Contains tx_prefix_hash.
See Monero-Trezor documentation section 3.3 for more details. See Monero-Trezor documentation section 3.3 for more details.
""" """
self.full_message_hasher = PreMlsagHasher() self.full_message_hasher: PreMlsagHasher | None = PreMlsagHasher()
self.full_message: bytes | None = None self.full_message: bytes | None = None
def mem_trace(self, x=None, collect=False): def mem_trace(self, x=None, collect: bool = False) -> None:
if __debug__: if __debug__:
log.debug( log.debug(
__name__, __name__,

@ -7,10 +7,9 @@ from typing import TYPE_CHECKING
from apps.monero import layout, misc, signing from apps.monero import layout, misc, signing
from apps.monero.signing.state import State from apps.monero.signing.state import State
from apps.monero.xmr import crypto, monero from apps.monero.xmr import crypto, crypto_helpers, monero
if TYPE_CHECKING: if TYPE_CHECKING:
from apps.monero.xmr.types import Sc25519, Ge25519
from trezor.messages import ( from trezor.messages import (
MoneroAccountPublicAddress, MoneroAccountPublicAddress,
MoneroTransactionData, MoneroTransactionData,
@ -34,16 +33,17 @@ async def init_transaction(
state.creds = misc.get_creds(keychain, address_n, network_type) state.creds = misc.get_creds(keychain, address_n, network_type)
state.client_version = tsx_data.client_version or 0 state.client_version = tsx_data.client_version or 0
if state.client_version == 0: if state.client_version < 3:
raise ValueError("Client version not supported") raise ValueError("Client version not supported")
state.fee = state.fee if state.fee > 0 else 0 state.fee = state.fee if state.fee > 0 else 0
state.tx_priv = crypto.random_scalar() state.tx_priv = crypto.random_scalar()
state.tx_pub = crypto.scalarmult_base(state.tx_priv) state.tx_pub = crypto.scalarmult_base_into(None, state.tx_priv)
state.mem_trace(1) state.mem_trace(1)
state.input_count = tsx_data.num_inputs state.input_count = tsx_data.num_inputs
state.output_count = len(tsx_data.outputs) state.output_count = len(tsx_data.outputs)
assert state.input_count is not None
state.progress_total = 4 + 3 * state.input_count + state.output_count state.progress_total = 4 + 3 * state.input_count + state.output_count
state.progress_cur = 0 state.progress_cur = 0
@ -58,16 +58,14 @@ async def init_transaction(
# Basic transaction parameters # Basic transaction parameters
state.output_change = tsx_data.change_dts state.output_change = tsx_data.change_dts
state.mixin = tsx_data.mixin
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:
state.tx_type = ( raise ValueError("Unsupported hard-fork version")
signing.RctType.CLSAG if state.hard_fork >= 13 else signing.RctType.Bulletproof2
)
# Ensure change is correct # Ensure change is correct
_check_change(state, tsx_data.outputs) _check_change(state, tsx_data.outputs)
@ -123,7 +121,9 @@ async def init_transaction(
return MoneroTransactionInitAck(hmacs=hmacs, rsig_data=rsig_data) return MoneroTransactionInitAck(hmacs=hmacs, rsig_data=rsig_data)
def _check_subaddresses(state: State, outputs: list[MoneroTransactionDestinationEntry]): def _check_subaddresses(
state: State, outputs: list[MoneroTransactionDestinationEntry]
) -> None:
""" """
Using subaddresses leads to a few poorly documented exceptions. Using subaddresses leads to a few poorly documented exceptions.
@ -156,8 +156,10 @@ def _check_subaddresses(state: State, outputs: list[MoneroTransactionDestination
# we set (override) the tx pubkey to R=r*D and no additional # we set (override) the tx pubkey to R=r*D and no additional
# tx keys are needed # tx keys are needed
if num_stdaddresses == 0 and num_subaddresses == 1: if num_stdaddresses == 0 and num_subaddresses == 1:
state.tx_pub = crypto.scalarmult( state.tx_pub = crypto.scalarmult_into(
crypto.decodepoint(single_dest_subaddress.spend_public_key), state.tx_priv None,
crypto_helpers.decodepoint(single_dest_subaddress.spend_public_key),
state.tx_priv,
) )
# if a subaddress is used and either standard address is as well # if a subaddress is used and either standard address is as well
@ -178,11 +180,12 @@ def _get_primary_change_address(state: State) -> MoneroAccountPublicAddress:
state.creds.view_key_private, state.creds.spend_key_public, state.account_idx, 0 state.creds.view_key_private, state.creds.spend_key_public, state.account_idx, 0
) )
return MoneroAccountPublicAddress( return MoneroAccountPublicAddress(
view_public_key=crypto.encodepoint(C), spend_public_key=crypto.encodepoint(D) view_public_key=crypto_helpers.encodepoint(C),
spend_public_key=crypto_helpers.encodepoint(D),
) )
def _check_rsig_data(state: State, rsig_data: MoneroTransactionRsigData): def _check_rsig_data(state: State, rsig_data: MoneroTransactionRsigData) -> None:
""" """
There are two types of monero ring confidential transactions: There are two types of monero ring confidential transactions:
1. RCTTypeFull = 1 (used if num_inputs == 1 && Borromean) 1. RCTTypeFull = 1 (used if num_inputs == 1 && Borromean)
@ -211,7 +214,7 @@ def _check_rsig_data(state: State, rsig_data: MoneroTransactionRsigData):
_check_grouping(state) _check_grouping(state)
def _check_grouping(state: State): def _check_grouping(state: State) -> None:
acc = 0 acc = 0
for x in state.rsig_grouping: for x in state.rsig_grouping:
if x is None or x <= 0: if x is None or x <= 0:
@ -222,7 +225,9 @@ def _check_grouping(state: State):
raise ValueError("Invalid grouping") raise ValueError("Invalid grouping")
def _check_change(state: State, outputs: list[MoneroTransactionDestinationEntry]): def _check_change(
state: State, outputs: list[MoneroTransactionDestinationEntry]
) -> None:
""" """
Check if the change address in state.output_change (from `tsx_data.outputs`) is Check if the change address in state.output_change (from `tsx_data.outputs`) is
a) among tx outputs a) among tx outputs
@ -269,7 +274,7 @@ def _check_change(state: State, outputs: list[MoneroTransactionDestinationEntry]
raise signing.ChangeAddressError("Change address differs from ours") raise signing.ChangeAddressError("Change address differs from ours")
def _compute_sec_keys(state: State, tsx_data: MoneroTransactionData): def _compute_sec_keys(state: State, tsx_data: MoneroTransactionData) -> None:
""" """
Generate master key H( H(TsxData || tx_priv) || rand ) Generate master key H( H(TsxData || tx_priv) || rand )
""" """
@ -278,16 +283,16 @@ def _compute_sec_keys(state: State, tsx_data: MoneroTransactionData):
writer = get_keccak_writer() writer = get_keccak_writer()
writer.write(protobuf.dump_message_buffer(tsx_data)) writer.write(protobuf.dump_message_buffer(tsx_data))
writer.write(crypto.encodeint(state.tx_priv)) writer.write(crypto_helpers.encodeint(state.tx_priv))
master_key = crypto.keccak_2hash( master_key = crypto_helpers.keccak_2hash(
writer.get_digest() + crypto.encodeint(crypto.random_scalar()) writer.get_digest() + crypto_helpers.encodeint(crypto.random_scalar())
) )
state.key_hmac = crypto.keccak_2hash(b"hmac" + master_key) state.key_hmac = crypto_helpers.keccak_2hash(b"hmac" + master_key)
state.key_enc = crypto.keccak_2hash(b"enc" + master_key) state.key_enc = crypto_helpers.keccak_2hash(b"enc" + master_key)
def _precompute_subaddr(state: State, account: int, indices: list[int]): def _precompute_subaddr(state: State, account: int, indices: list[int]) -> None:
""" """
Precomputes subaddresses for account (major) and list of indices (minors) Precomputes subaddresses for account (major) and list of indices (minors)
Subaddresses have to be stored in encoded form - unique representation. Subaddresses have to be stored in encoded form - unique representation.
@ -296,7 +301,7 @@ def _precompute_subaddr(state: State, account: int, indices: list[int]):
monero.compute_subaddresses(state.creds, account, indices, state.subaddresses) monero.compute_subaddresses(state.creds, account, indices, state.subaddresses)
def _process_payment_id(state: State, tsx_data: MoneroTransactionData): def _process_payment_id(state: State, tsx_data: MoneroTransactionData) -> None:
""" """
Writes payment id to the `extra` field under the TX_EXTRA_NONCE = 0x02 tag. Writes payment id to the `extra` field under the TX_EXTRA_NONCE = 0x02 tag.
@ -316,14 +321,14 @@ def _process_payment_id(state: State, tsx_data: MoneroTransactionData):
if not tsx_data.payment_id or len(tsx_data.payment_id) == 8: if not tsx_data.payment_id or len(tsx_data.payment_id) == 8:
view_key_pub_enc = _get_key_for_payment_id_encryption( view_key_pub_enc = _get_key_for_payment_id_encryption(
tsx_data, state.change_address(), state.client_version > 0 tsx_data, state.change_address(), True
) )
if not tsx_data.payment_id: if not tsx_data.payment_id:
return return
elif len(tsx_data.payment_id) == 8: elif len(tsx_data.payment_id) == 8:
view_key_pub = crypto.decodepoint(view_key_pub_enc) view_key_pub = crypto_helpers.decodepoint(view_key_pub_enc)
payment_id_encr = _encrypt_payment_id( payment_id_encr = _encrypt_payment_id(
tsx_data.payment_id, view_key_pub, state.tx_priv tsx_data.payment_id, view_key_pub, state.tx_priv
) )
@ -354,7 +359,7 @@ def _process_payment_id(state: State, tsx_data: MoneroTransactionData):
def _get_key_for_payment_id_encryption( def _get_key_for_payment_id_encryption(
tsx_data: MoneroTransactionData, tsx_data: MoneroTransactionData,
change_addr=None, change_addr: MoneroAccountPublicAddress | None = None,
add_dummy_payment_id: bool = False, add_dummy_payment_id: bool = False,
) -> bytes: ) -> bytes:
""" """
@ -366,7 +371,8 @@ def _get_key_for_payment_id_encryption(
from trezor.messages import MoneroAccountPublicAddress from trezor.messages import MoneroAccountPublicAddress
addr = MoneroAccountPublicAddress( addr = MoneroAccountPublicAddress(
spend_public_key=crypto.NULL_KEY_ENC, view_public_key=crypto.NULL_KEY_ENC spend_public_key=crypto_helpers.NULL_KEY_ENC,
view_public_key=crypto_helpers.NULL_KEY_ENC,
) )
count = 0 count = 0
for dest in tsx_data.outputs: for dest in tsx_data.outputs:
@ -390,23 +396,23 @@ def _get_key_for_payment_id_encryption(
if count == 0 and change_addr: if count == 0 and change_addr:
return change_addr.view_public_key return change_addr.view_public_key
if addr.view_public_key == crypto.NULL_KEY_ENC: if addr.view_public_key == crypto_helpers.NULL_KEY_ENC:
raise ValueError("Invalid key") raise ValueError("Invalid key")
return addr.view_public_key return addr.view_public_key
def _encrypt_payment_id( def _encrypt_payment_id(
payment_id: bytes, public_key: Ge25519, secret_key: Sc25519 payment_id: bytes, public_key: crypto.Point, secret_key: crypto.Scalar
) -> bytes: ) -> bytes:
""" """
Encrypts payment_id hex. Encrypts payment_id hex.
Used in the transaction extra. Only recipient is able to decrypt. Used in the transaction extra. Only recipient is able to decrypt.
""" """
derivation_p = crypto.generate_key_derivation(public_key, secret_key) derivation_p = crypto_helpers.generate_key_derivation(public_key, secret_key)
derivation = bytearray(33) derivation = bytearray(33)
derivation = crypto.encodepoint_into(derivation, derivation_p) derivation = crypto.encodepoint_into(derivation, derivation_p)
derivation[32] = 0x8D # ENCRYPTED_PAYMENT_ID_TAIL derivation[32] = 0x8D # ENCRYPTED_PAYMENT_ID_TAIL
hash = crypto.cn_fast_hash(derivation) hash = crypto.fast_hash_into(None, derivation)
pm_copy = bytearray(payment_id) pm_copy = bytearray(payment_id)
return crypto.xor8(pm_copy, hash) return crypto_helpers.xor8(pm_copy, hash)

@ -14,12 +14,11 @@ key derived for exactly this purpose.
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from apps.monero import layout from apps.monero import layout
from apps.monero.xmr import crypto, monero, serialize from apps.monero.xmr import crypto, crypto_helpers, monero, serialize
from .state import State from .state import State
if TYPE_CHECKING: if TYPE_CHECKING:
from apps.monero.xmr.types import Sc25519, Ge25519
from trezor.messages import MoneroTransactionSourceEntry from trezor.messages import MoneroTransactionSourceEntry
from trezor.messages import MoneroTransactionSetInputAck from trezor.messages import MoneroTransactionSetInputAck
@ -28,7 +27,7 @@ async def set_input(
state: State, src_entr: MoneroTransactionSourceEntry state: State, src_entr: MoneroTransactionSourceEntry
) -> MoneroTransactionSetInputAck: ) -> MoneroTransactionSetInputAck:
from trezor.messages import MoneroTransactionSetInputAck from trezor.messages import MoneroTransactionSetInputAck
from apps.monero.xmr.crypto import chacha_poly from apps.monero.xmr import chacha_poly
from apps.monero.xmr.serialize_messages.tx_prefix import TxinToKey from apps.monero.xmr.serialize_messages.tx_prefix import TxinToKey
from apps.monero.signing import offloading_keys from apps.monero.signing import offloading_keys
@ -49,9 +48,11 @@ async def set_input(
# Secrets derivation # Secrets derivation
# the UTXO's one-time address P # the UTXO's one-time address P
out_key = crypto.decodepoint(src_entr.outputs[src_entr.real_output].key.dest) out_key = crypto_helpers.decodepoint(
src_entr.outputs[src_entr.real_output].key.dest
)
# the tx_pub of our UTXO stored inside its transaction # the tx_pub of our UTXO stored inside its transaction
tx_key = crypto.decodepoint(src_entr.real_out_tx_key) tx_key = crypto_helpers.decodepoint(src_entr.real_out_tx_key)
additional_tx_pub_key = _get_additional_public_key(src_entr) additional_tx_pub_key = _get_additional_public_key(src_entr)
# Calculates `derivation = Ra`, private spend key `x = H(Ra||i) + b` to be able # Calculates `derivation = Ra`, private spend key `x = H(Ra||i) + b` to be able
@ -70,7 +71,7 @@ async def set_input(
# Construct tx.vin # Construct tx.vin
# If multisig is used then ki in vini should be src_entr.multisig_kLRki.ki # If multisig is used then ki in vini should be src_entr.multisig_kLRki.ki
vini = TxinToKey(amount=src_entr.amount, k_image=crypto.encodepoint(ki)) vini = TxinToKey(amount=src_entr.amount, k_image=crypto_helpers.encodepoint(ki))
vini.key_offsets = _absolute_output_offsets_to_relative( vini.key_offsets = _absolute_output_offsets_to_relative(
[x.idx for x in src_entr.outputs] [x.idx for x in src_entr.outputs]
) )
@ -92,22 +93,22 @@ async def set_input(
# PseudoOuts commitment, alphas stored to state # PseudoOuts commitment, alphas stored to state
alpha, pseudo_out = _gen_commitment(state, src_entr.amount) alpha, pseudo_out = _gen_commitment(state, src_entr.amount)
pseudo_out = crypto.encodepoint(pseudo_out) pseudo_out = crypto_helpers.encodepoint(pseudo_out)
# The alpha is encrypted and passed back for storage # The alpha is encrypted and passed back for storage
pseudo_out_hmac = crypto.compute_hmac( pseudo_out_hmac = crypto_helpers.compute_hmac(
offloading_keys.hmac_key_txin_comm(state.key_hmac, state.current_input_index), offloading_keys.hmac_key_txin_comm(state.key_hmac, state.current_input_index),
pseudo_out, pseudo_out,
) )
alpha_enc = chacha_poly.encrypt_pack( alpha_enc = chacha_poly.encrypt_pack(
offloading_keys.enc_key_txin_alpha(state.key_enc, state.current_input_index), offloading_keys.enc_key_txin_alpha(state.key_enc, state.current_input_index),
crypto.encodeint(alpha), crypto_helpers.encodeint(alpha),
) )
spend_enc = chacha_poly.encrypt_pack( spend_enc = chacha_poly.encrypt_pack(
offloading_keys.enc_key_spend(state.key_enc, state.current_input_index), offloading_keys.enc_key_spend(state.key_enc, state.current_input_index),
crypto.encodeint(xi), crypto_helpers.encodeint(xi),
) )
state.last_step = state.STEP_INP state.last_step = state.STEP_INP
@ -127,7 +128,7 @@ async def set_input(
) )
def _gen_commitment(state: State, in_amount: int) -> tuple[Sc25519, Ge25519]: def _gen_commitment(state: State, in_amount: int) -> tuple[crypto.Scalar, crypto.Point]:
""" """
Computes Pedersen commitment - pseudo outs Computes Pedersen commitment - pseudo outs
Here is slight deviation from the original protocol. Here is slight deviation from the original protocol.
@ -139,8 +140,8 @@ def _gen_commitment(state: State, in_amount: int) -> tuple[Sc25519, Ge25519]:
Returns pseudo_out Returns pseudo_out
""" """
alpha = crypto.random_scalar() alpha = crypto.random_scalar()
state.sumpouts_alphas = crypto.sc_add(state.sumpouts_alphas, alpha) state.sumpouts_alphas = crypto.sc_add_into(None, state.sumpouts_alphas, alpha)
return alpha, crypto.gen_commitment(alpha, in_amount) return alpha, crypto.gen_commitment_into(None, alpha, in_amount)
def _absolute_output_offsets_to_relative(off: list[int]) -> list[int]: def _absolute_output_offsets_to_relative(off: list[int]) -> list[int]:
@ -161,10 +162,10 @@ def _absolute_output_offsets_to_relative(off: list[int]) -> list[int]:
def _get_additional_public_key( def _get_additional_public_key(
src_entr: MoneroTransactionSourceEntry, src_entr: MoneroTransactionSourceEntry,
) -> Ge25519 | None: ) -> crypto.Point | None:
additional_tx_pub_key = None additional_tx_pub_key = None
if len(src_entr.real_out_additional_tx_keys) == 1: # compression if len(src_entr.real_out_additional_tx_keys) == 1: # compression
additional_tx_pub_key = crypto.decodepoint( additional_tx_pub_key = crypto_helpers.decodepoint(
src_entr.real_out_additional_tx_keys[0] src_entr.real_out_additional_tx_keys[0]
) )
elif src_entr.real_out_additional_tx_keys: elif src_entr.real_out_additional_tx_keys:
@ -172,7 +173,7 @@ def _get_additional_public_key(
src_entr.real_out_additional_tx_keys src_entr.real_out_additional_tx_keys
): ):
raise ValueError("Wrong number of additional derivations") raise ValueError("Wrong number of additional derivations")
additional_tx_pub_key = crypto.decodepoint( additional_tx_pub_key = crypto_helpers.decodepoint(
src_entr.real_out_additional_tx_keys[src_entr.real_output_in_tx_index] src_entr.real_out_additional_tx_keys[src_entr.real_output_in_tx_index]
) )
return additional_tx_pub_key return additional_tx_pub_key

@ -1,57 +0,0 @@
"""
Inputs in transaction need to be sorted by their key image, otherwise the
transaction is rejected. The sorting is done on host and then sent here in
the MoneroTransactionInputsPermutationRequest message.
The message contains just a simple array where each item stands for the
input's position in the transaction.
We do not do the actual sorting here (we do not store the complete input
data anyway, so we can't) we just save the array to the state and use
it later when needed.
New protocol version (CL3) does not store the permutation. The permutation
correctness is checked by checking the number of elements,
HMAC correctness (host sends original sort idx) and ordering check
on the key images. This step is skipped.
"""
from typing import TYPE_CHECKING
from apps.monero.layout import transaction_step
from .state import State
if TYPE_CHECKING:
from trezor.messages import MoneroTransactionInputsPermutationAck
async def tsx_inputs_permutation(
state: State, permutation: list[int]
) -> MoneroTransactionInputsPermutationAck:
"""
Set permutation on the inputs - sorted by key image on host.
"""
from trezor.messages import MoneroTransactionInputsPermutationAck
await transaction_step(state, state.STEP_PERM)
if state.last_step != state.STEP_INP:
raise ValueError("Invalid state transition")
if len(permutation) != state.input_count:
raise ValueError("Invalid permutation size")
if state.current_input_index != state.input_count - 1:
raise ValueError("Invalid input count")
_check_permutation(permutation)
state.source_permutation = permutation
state.current_input_index = -1
state.last_step = state.STEP_PERM
return MoneroTransactionInputsPermutationAck()
def _check_permutation(permutation: list[int]):
for n in range(len(permutation)):
if n not in permutation:
raise ValueError("Invalid permutation")

@ -28,12 +28,12 @@ async def input_vini(
from trezor.messages import MoneroTransactionInputViniAck from trezor.messages import MoneroTransactionInputViniAck
await layout.transaction_step(state, state.STEP_VINI, state.current_input_index + 1) await layout.transaction_step(state, state.STEP_VINI, state.current_input_index + 1)
if state.last_step not in (state.STEP_INP, state.STEP_PERM, state.STEP_VINI): if state.last_step not in (state.STEP_INP, state.STEP_VINI):
raise ValueError("Invalid state transition") raise ValueError("Invalid state transition")
if state.current_input_index >= state.input_count: if state.current_input_index >= state.input_count:
raise ValueError("Too many inputs") raise ValueError("Too many inputs")
if state.client_version >= 2 and state.last_step < state.STEP_VINI: if state.last_step < state.STEP_VINI:
state.current_input_index = -1 state.current_input_index = -1
state.last_ki = None state.last_ki = None
@ -44,9 +44,7 @@ async def input_vini(
state.key_hmac, state.key_hmac,
src_entr, src_entr,
vini_bin, vini_bin,
state.source_permutation[state.current_input_index] orig_idx,
if state.client_version <= 1
else orig_idx,
) )
if not crypto.ct_equals(hmac_vini_comp, vini_hmac): if not crypto.ct_equals(hmac_vini_comp, vini_hmac):
raise ValueError("HMAC is not correct") raise ValueError("HMAC is not correct")

@ -27,7 +27,7 @@ async def all_inputs_set(state: State) -> MoneroTransactionAllInputsSetAck:
raise ValueError("Invalid input count") raise ValueError("Invalid input count")
# The sum of the masks must match the input masks sum. # The sum of the masks must match the input masks sum.
state.sumout = crypto.sc_init(0) state.sumout = crypto.Scalar()
state.last_step = state.STEP_ALL_IN state.last_step = state.STEP_ALL_IN
resp = MoneroTransactionAllInputsSetAck() resp = MoneroTransactionAllInputsSetAck()
return resp return resp

@ -9,12 +9,11 @@ from trezor import utils
from apps.monero import layout, signing from apps.monero import layout, signing
from apps.monero.signing import offloading_keys from apps.monero.signing import offloading_keys
from apps.monero.xmr import crypto, serialize from apps.monero.xmr import crypto, crypto_helpers, serialize
from .state import State from .state import State
if TYPE_CHECKING: if TYPE_CHECKING:
from apps.monero.xmr.types import Sc25519, Ge25519
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
from trezor.messages import ( from trezor.messages import (
@ -170,7 +169,7 @@ def _validate(
def _compute_tx_keys( def _compute_tx_keys(
state: State, dst_entr: MoneroTransactionDestinationEntry state: State, dst_entr: MoneroTransactionDestinationEntry
) -> tuple[Ge25519, Sc25519]: ) -> tuple[crypto.Point, crypto.Scalar]:
"""Computes tx_out_key, amount_key""" """Computes tx_out_key, amount_key"""
if state.is_processing_offloaded: if state.is_processing_offloaded:
@ -181,24 +180,26 @@ def _compute_tx_keys(
# derivation = a*R or r*A or s*C # derivation = a*R or r*A or s*C
derivation = _set_out_derivation(state, dst_entr, additional_txkey_priv) derivation = _set_out_derivation(state, dst_entr, additional_txkey_priv)
# amount key = H_s(derivation || i) # amount key = H_s(derivation || i)
amount_key = crypto.derivation_to_scalar(derivation, state.current_output_index) amount_key = crypto_helpers.derivation_to_scalar(
derivation, state.current_output_index
)
# one-time destination address P = H_s(derivation || i)*G + B # one-time destination address P = H_s(derivation || i)*G + B
tx_out_key = crypto.derive_public_key( tx_out_key = crypto_helpers.derive_public_key(
derivation, derivation,
state.current_output_index, state.current_output_index,
crypto.decodepoint(dst_entr.addr.spend_public_key), crypto_helpers.decodepoint(dst_entr.addr.spend_public_key),
) )
del (derivation, additional_txkey_priv) del (derivation, additional_txkey_priv)
from apps.monero.xmr import monero from apps.monero.xmr import monero
mask = monero.commitment_mask(crypto.encodeint(amount_key)) mask = monero.commitment_mask(crypto_helpers.encodeint(amount_key))
state.output_masks.append(mask) state.output_masks.append(mask)
return tx_out_key, amount_key return tx_out_key, amount_key
def _set_out_tx_out( def _set_out_tx_out(
state: State, dst_entr: MoneroTransactionDestinationEntry, tx_out_key: Ge25519 state: State, dst_entr: MoneroTransactionDestinationEntry, tx_out_key: crypto.Point
) -> tuple[bytes, bytes]: ) -> tuple[bytes, bytes]:
""" """
Manually serializes TxOut(0, TxoutToKey(key)) and calculates hmac. Manually serializes TxOut(0, TxoutToKey(key)) and calculates hmac.
@ -223,7 +224,7 @@ def _set_out_tx_out(
def _range_proof( def _range_proof(
state: State, rsig_data: MoneroTransactionRsigData state: State, rsig_data: MoneroTransactionRsigData
) -> tuple[MoneroTransactionRsigData, Sc25519]: ) -> tuple[MoneroTransactionRsigData, crypto.Scalar]:
""" """
Computes rangeproof and handles range proof offloading logic. Computes rangeproof and handles range proof offloading logic.
@ -257,7 +258,9 @@ def _range_proof(
# If not last, do not proceed to the BP processing. # If not last, do not proceed to the BP processing.
if not last_in_batch: if not last_in_batch:
rsig_data_new = ( rsig_data_new = (
_return_rsig_data(mask=crypto.encodeint(mask)) if offload_mask else None _return_rsig_data(mask=crypto_helpers.encodeint(mask))
if offload_mask
else None
) )
return rsig_data_new, mask return rsig_data_new, mask
@ -282,7 +285,7 @@ def _range_proof(
# Construct new rsig data to send back to the host. # Construct new rsig data to send back to the host.
rsig_data_new = _return_rsig_data( rsig_data_new = _return_rsig_data(
rsig, crypto.encodeint(mask) if offload_mask else None rsig, crypto_helpers.encodeint(mask) if offload_mask else None
) )
if state.current_output_index + 1 == state.output_count and ( if state.current_output_index + 1 == state.output_count and (
@ -386,7 +389,7 @@ def _dump_rsig_bp(rsig: Bulletproof) -> bytes:
def _return_rsig_data( def _return_rsig_data(
rsig: bytes = None, mask: bytes = None rsig: bytes | None = None, mask: bytes | None = None
) -> MoneroTransactionRsigData: ) -> MoneroTransactionRsigData:
if rsig is None and mask is None: if rsig is None and mask is None:
return None return None
@ -405,17 +408,23 @@ def _return_rsig_data(
def _get_ecdh_info_and_out_pk( def _get_ecdh_info_and_out_pk(
state: State, tx_out_key: Ge25519, amount: int, mask: Sc25519, amount_key: Sc25519 state: State,
tx_out_key: crypto.Point,
amount: int,
mask: crypto.Scalar,
amount_key: crypto.Scalar,
) -> tuple[bytes, bytes, bytes]: ) -> tuple[bytes, bytes, bytes]:
""" """
Calculates the Pedersen commitment C = aG + bH and returns it as CtKey. Calculates the Pedersen commitment C = aG + bH and returns it as CtKey.
Also encodes the two items - `mask` and `amount` - into ecdh info, Also encodes the two items - `mask` and `amount` - into ecdh info,
so the recipient is able to reconstruct the commitment. so the recipient is able to reconstruct the commitment.
""" """
out_pk_dest = crypto.encodepoint(tx_out_key) out_pk_dest = crypto_helpers.encodepoint(tx_out_key)
out_pk_commitment = crypto.encodepoint(crypto.gen_commitment(mask, amount)) out_pk_commitment = crypto_helpers.encodepoint(
crypto.gen_commitment_into(None, mask, amount)
)
crypto.sc_add_into(state.sumout, state.sumout, mask) crypto.sc_add_into(state.sumout, state.sumout, mask)
ecdh_info = _ecdh_encode(amount, crypto.encodeint(amount_key)) ecdh_info = _ecdh_encode(amount, crypto_helpers.encodeint(amount_key))
# Manual ECDH info serialization # Manual ECDH info serialization
ecdh_info_bin = _serialize_ecdh(ecdh_info) ecdh_info_bin = _serialize_ecdh(ecdh_info)
@ -442,7 +451,7 @@ def _ecdh_hash(shared_sec: bytes) -> bytes:
data = bytearray(38) data = bytearray(38)
data[0:6] = b"amount" data[0:6] = b"amount"
data[6:] = shared_sec data[6:] = shared_sec
return crypto.cn_fast_hash(data) return crypto.fast_hash_into(None, data)
def _ecdh_encode(amount: int, amount_key: bytes) -> EcdhTuple: def _ecdh_encode(amount: int, amount_key: bytes) -> EcdhTuple:
@ -451,16 +460,16 @@ def _ecdh_encode(amount: int, amount_key: bytes) -> EcdhTuple:
""" """
from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple
ecdh_info = EcdhTuple(mask=crypto.NULL_KEY_ENC, amount=bytearray(32)) ecdh_info = EcdhTuple(mask=crypto_helpers.NULL_KEY_ENC, amount=bytearray(32))
amnt = crypto.sc_init(amount) amnt = crypto.Scalar(amount)
crypto.encodeint_into(ecdh_info.amount, amnt) crypto.encodeint_into(ecdh_info.amount, amnt)
crypto.xor8(ecdh_info.amount, _ecdh_hash(amount_key)) crypto_helpers.xor8(ecdh_info.amount, _ecdh_hash(amount_key))
return ecdh_info return ecdh_info
def _set_out_additional_keys( def _set_out_additional_keys(
state: State, dst_entr: MoneroTransactionDestinationEntry state: State, dst_entr: MoneroTransactionDestinationEntry
) -> Sc25519: ) -> crypto.Scalar:
""" """
If needed (decided in step 1), additional tx keys are calculated If needed (decided in step 1), additional tx keys are calculated
for this particular output. for this particular output.
@ -472,15 +481,15 @@ def _set_out_additional_keys(
if dst_entr.is_subaddress: if dst_entr.is_subaddress:
# R=r*D # R=r*D
additional_txkey = crypto.decodepoint(dst_entr.addr.spend_public_key) additional_txkey = crypto_helpers.decodepoint(dst_entr.addr.spend_public_key)
crypto.scalarmult_into( crypto.scalarmult_into(
additional_txkey, additional_txkey, additional_txkey_priv additional_txkey, additional_txkey, additional_txkey_priv
) )
else: else:
# R=r*G # R=r*G
additional_txkey = crypto.scalarmult_base(additional_txkey_priv) additional_txkey = crypto.scalarmult_base_into(None, additional_txkey_priv)
state.additional_tx_public_keys.append(crypto.encodepoint(additional_txkey)) state.additional_tx_public_keys.append(crypto_helpers.encodepoint(additional_txkey))
state.additional_tx_private_keys.append(additional_txkey_priv) state.additional_tx_private_keys.append(additional_txkey_priv)
return additional_txkey_priv return additional_txkey_priv
@ -488,8 +497,8 @@ def _set_out_additional_keys(
def _set_out_derivation( def _set_out_derivation(
state: State, state: State,
dst_entr: MoneroTransactionDestinationEntry, dst_entr: MoneroTransactionDestinationEntry,
additional_txkey_priv: Sc25519, additional_txkey_priv: crypto.Scalar,
) -> Ge25519: ) -> crypto.Point:
""" """
Calculates derivation which is then used in the one-time address as Calculates derivation which is then used in the one-time address as
`P = H(derivation)*G + B`. `P = H(derivation)*G + B`.
@ -504,7 +513,7 @@ def _set_out_derivation(
change_addr = state.change_address() change_addr = state.change_address()
if change_addr and addr_eq(dst_entr.addr, change_addr): if change_addr and addr_eq(dst_entr.addr, change_addr):
# sending change to yourself; derivation = a*R # sending change to yourself; derivation = a*R
derivation = crypto.generate_key_derivation( derivation = crypto_helpers.generate_key_derivation(
state.tx_pub, state.creds.view_key_private state.tx_pub, state.creds.view_key_private
) )
@ -514,8 +523,8 @@ def _set_out_derivation(
deriv_priv = additional_txkey_priv deriv_priv = additional_txkey_priv
else: else:
deriv_priv = state.tx_priv deriv_priv = state.tx_priv
derivation = crypto.generate_key_derivation( derivation = crypto_helpers.generate_key_derivation(
crypto.decodepoint(dst_entr.addr.view_public_key), deriv_priv crypto_helpers.decodepoint(dst_entr.addr.view_public_key), deriv_priv
) )
return derivation return derivation

@ -62,6 +62,7 @@ async def all_outputs_set(state: State) -> MoneroTransactionAllOutSetAck:
state.current_output_index = None state.current_output_index = None
state.current_input_index = -1 state.current_input_index = -1
assert state.full_message_hasher is not None
state.full_message = state.full_message_hasher.get_digest() state.full_message = state.full_message_hasher.get_digest()
state.full_message_hasher = None state.full_message_hasher = None
state.output_pk_commitments = None state.output_pk_commitments = None
@ -79,7 +80,7 @@ async def all_outputs_set(state: State) -> MoneroTransactionAllOutSetAck:
) )
def _validate(state: State): def _validate(state: State) -> None:
if state.last_step != state.STEP_OUT: if state.last_step != state.STEP_OUT:
raise ValueError("Invalid state transition") raise ValueError("Invalid state transition")
if state.current_output_index + 1 != state.output_count: if state.current_output_index + 1 != state.output_count:
@ -143,7 +144,7 @@ def _set_tx_extra(state: State) -> bytes:
return extra return extra
def _set_tx_prefix(state: State, extra: bytes): def _set_tx_prefix(state: State, extra: bytes) -> None:
""" """
Adds `extra` to the tx_prefix_hash, which is the last needed item, Adds `extra` to the tx_prefix_hash, which is the last needed item,
so the tx_prefix_hash is now complete and can be incorporated so the tx_prefix_hash is now complete and can be incorporated
@ -160,7 +161,7 @@ def _set_tx_prefix(state: State, extra: bytes):
state.full_message_hasher.set_message(state.tx_prefix_hash) state.full_message_hasher.set_message(state.tx_prefix_hash)
def _out_pk(state: State): def _out_pk(state: State) -> None:
""" """
Hashes out_pk into the full message. Hashes out_pk into the full message.
""" """

@ -1,5 +1,5 @@
""" """
Generates a MLSAG signature for one input. Generates a clsag signature for one input.
Mask Balancing. Mask Balancing.
Sum of input masks has to be equal to the sum of output masks. Sum of input masks has to be equal to the sum of output masks.
@ -16,7 +16,7 @@ from typing import TYPE_CHECKING
from trezor import utils from trezor import utils
from apps.monero import layout from apps.monero import layout
from apps.monero.xmr import crypto from apps.monero.xmr import crypto, crypto_helpers
from .state import State from .state import State
@ -61,11 +61,7 @@ async def sign_input(
if pseudo_out_alpha_enc is None: if pseudo_out_alpha_enc is None:
raise ValueError("SimpleRCT requires pseudo_out's mask but none provided") raise ValueError("SimpleRCT requires pseudo_out's mask but none provided")
input_position = ( input_position = orig_idx
state.source_permutation[state.current_input_index]
if state.client_version <= 1
else orig_idx
)
mods = utils.unimport_begin() mods = utils.unimport_begin()
# Check input's HMAC # Check input's HMAC
@ -88,9 +84,9 @@ async def sign_input(
gc.collect() gc.collect()
state.mem_trace(1, True) state.mem_trace(1, True)
from apps.monero.xmr.crypto import chacha_poly from apps.monero.xmr import chacha_poly
pseudo_out_alpha = crypto.decodeint( pseudo_out_alpha = crypto_helpers.decodeint(
chacha_poly.decrypt_pack( chacha_poly.decrypt_pack(
offloading_keys.enc_key_txin_alpha(state.key_enc, input_position), offloading_keys.enc_key_txin_alpha(state.key_enc, input_position),
bytes(pseudo_out_alpha_enc), bytes(pseudo_out_alpha_enc),
@ -101,31 +97,33 @@ async def sign_input(
if input_position + 1 == state.input_count: if input_position + 1 == state.input_count:
# Recompute the lash alpha so the sum holds # Recompute the lash alpha so the sum holds
state.mem_trace("Correcting alpha") state.mem_trace("Correcting alpha")
alpha_diff = crypto.sc_sub(state.sumout, state.sumpouts_alphas) alpha_diff = crypto.sc_sub_into(None, state.sumout, state.sumpouts_alphas)
crypto.sc_add_into(pseudo_out_alpha, pseudo_out_alpha, alpha_diff) crypto.sc_add_into(pseudo_out_alpha, pseudo_out_alpha, alpha_diff)
pseudo_out_c = crypto.gen_commitment(pseudo_out_alpha, state.input_last_amount) pseudo_out_c = crypto.gen_commitment_into(
None, pseudo_out_alpha, state.input_last_amount
)
else: else:
if input_position + 1 == state.input_count: if input_position + 1 == state.input_count:
utils.ensure( utils.ensure(
crypto.sc_eq(state.sumpouts_alphas, state.sumout), "Sum eq error" crypto.sc_eq(state.sumpouts_alphas, state.sumout) != 0, "Sum eq error"
) )
# both pseudo_out and its mask were offloaded so we need to # both pseudo_out and its mask were offloaded so we need to
# validate pseudo_out's HMAC and decrypt the alpha # validate pseudo_out's HMAC and decrypt the alpha
pseudo_out_hmac_comp = crypto.compute_hmac( pseudo_out_hmac_comp = crypto_helpers.compute_hmac(
offloading_keys.hmac_key_txin_comm(state.key_hmac, input_position), offloading_keys.hmac_key_txin_comm(state.key_hmac, input_position),
pseudo_out, pseudo_out,
) )
if not crypto.ct_equals(pseudo_out_hmac_comp, pseudo_out_hmac): if not crypto.ct_equals(pseudo_out_hmac_comp, pseudo_out_hmac):
raise ValueError("HMAC is not correct") raise ValueError("HMAC is not correct")
pseudo_out_c = crypto.decodepoint(pseudo_out) pseudo_out_c = crypto_helpers.decodepoint(pseudo_out)
state.mem_trace(2, True) state.mem_trace(2, True)
# Spending secret # Spending secret
spend_key = crypto.decodeint( spend_key = crypto_helpers.decodeint(
chacha_poly.decrypt_pack( chacha_poly.decrypt_pack(
offloading_keys.enc_key_spend(state.key_enc, input_position), offloading_keys.enc_key_spend(state.key_enc, input_position),
bytes(spend_enc), bytes(spend_enc),
@ -147,28 +145,29 @@ async def sign_input(
from apps.monero.xmr.serialize_messages.tx_ct_key import CtKey from apps.monero.xmr.serialize_messages.tx_ct_key import CtKey
index = src_entr.real_output index = src_entr.real_output
input_secret_key = CtKey(spend_key, crypto.decodeint(src_entr.mask)) input_secret_key = CtKey(spend_key, crypto_helpers.decodeint(src_entr.mask))
# Private key correctness test # Private key correctness test
utils.ensure( utils.ensure(
crypto.point_eq( crypto.point_eq(
crypto.decodepoint(src_entr.outputs[src_entr.real_output].key.dest), crypto_helpers.decodepoint(src_entr.outputs[src_entr.real_output].key.dest),
crypto.scalarmult_base(input_secret_key.dest), crypto.scalarmult_base_into(None, input_secret_key.dest),
), ),
"Real source entry's destination does not equal spend key's", "Real source entry's destination does not equal spend key's",
) )
utils.ensure( utils.ensure(
crypto.point_eq( crypto.point_eq(
crypto.decodepoint(src_entr.outputs[src_entr.real_output].key.commitment), crypto_helpers.decodepoint(
crypto.gen_commitment(input_secret_key.mask, src_entr.amount), src_entr.outputs[src_entr.real_output].key.commitment
),
crypto.gen_commitment_into(None, input_secret_key.mask, src_entr.amount),
), ),
"Real source entry's mask does not equal spend key's", "Real source entry's mask does not equal spend key's",
) )
state.mem_trace(4, True) state.mem_trace(4, True)
from apps.monero.xmr import mlsag from apps.monero.xmr import clsag
from apps.monero import signing
mg_buffer = [] mg_buffer = []
ring_pubkeys = [x.key for x in src_entr.outputs if x] ring_pubkeys = [x.key for x in src_entr.outputs if x]
@ -177,43 +176,32 @@ async def sign_input(
state.mem_trace(5, True) state.mem_trace(5, True)
if state.tx_type == signing.RctType.CLSAG: assert state.full_message is not None
state.mem_trace("CLSAG") state.mem_trace("CLSAG")
mlsag.generate_clsag_simple( clsag.generate_clsag_simple(
state.full_message, state.full_message,
ring_pubkeys, ring_pubkeys,
input_secret_key, input_secret_key,
pseudo_out_alpha, pseudo_out_alpha,
pseudo_out_c, pseudo_out_c,
index, index,
mg_buffer, mg_buffer,
) )
else:
mlsag.generate_mlsag_simple(
state.full_message,
ring_pubkeys,
input_secret_key,
pseudo_out_alpha,
pseudo_out_c,
index,
mg_buffer,
)
del (CtKey, input_secret_key, pseudo_out_alpha, mlsag, ring_pubkeys) del (CtKey, input_secret_key, pseudo_out_alpha, clsag, ring_pubkeys)
state.mem_trace(6, True) state.mem_trace(6, True)
from trezor.messages import MoneroTransactionSignInputAck from trezor.messages import MoneroTransactionSignInputAck
# Encrypt signature, reveal once protocol finishes OK # Encrypt signature, reveal once protocol finishes OK
if state.client_version >= 3: utils.unimport_end(mods)
utils.unimport_end(mods) state.mem_trace(7, True)
state.mem_trace(7, True) mg_buffer = _protect_signature(state, mg_buffer)
mg_buffer = _protect_signature(state, mg_buffer)
state.mem_trace(8, True) state.mem_trace(8, True)
state.last_step = state.STEP_SIGN state.last_step = state.STEP_SIGN
return MoneroTransactionSignInputAck( return MoneroTransactionSignInputAck(
signature=mg_buffer, pseudo_out=crypto.encodepoint(pseudo_out_c) signature=mg_buffer, pseudo_out=crypto_helpers.encodepoint(pseudo_out_c)
) )

@ -8,19 +8,14 @@ so we can recover it just from the transaction and the spend key.
The private tx keys are used in other numerous Monero features. The private tx keys are used in other numerous Monero features.
""" """
from typing import TYPE_CHECKING from trezor.crypto import random
from trezor.messages import MoneroTransactionFinalAck from trezor.messages import MoneroTransactionFinalAck
from apps.monero import misc from apps.monero import misc
from apps.monero.xmr import crypto from apps.monero.xmr import chacha_poly, crypto, crypto_helpers
from apps.monero.xmr.crypto import chacha_poly
from .state import State from .state import State
if TYPE_CHECKING:
from apps.monero.xmr.types import Sc25519
def final_msg(state: State) -> MoneroTransactionFinalAck: def final_msg(state: State) -> MoneroTransactionFinalAck:
if state.last_step != state.STEP_SIGN: if state.last_step != state.STEP_SIGN:
@ -32,8 +27,8 @@ def final_msg(state: State) -> MoneroTransactionFinalAck:
state.creds.spend_key_private, state.tx_prefix_hash state.creds.spend_key_private, state.tx_prefix_hash
) )
key_buff = crypto.encodeint(state.tx_priv) + b"".join( key_buff = crypto_helpers.encodeint(state.tx_priv) + b"".join(
[crypto.encodeint(x) for x in state.additional_tx_private_keys] [crypto_helpers.encodeint(x) for x in state.additional_tx_private_keys]
) )
tx_enc_keys = chacha_poly.encrypt_pack(tx_key, key_buff) tx_enc_keys = chacha_poly.encrypt_pack(tx_key, key_buff)
state.last_step = None state.last_step = None
@ -48,12 +43,12 @@ def final_msg(state: State) -> MoneroTransactionFinalAck:
def _compute_tx_key( def _compute_tx_key(
spend_key_private: Sc25519, tx_prefix_hash: bytes spend_key_private: crypto.Scalar, tx_prefix_hash: bytes
) -> tuple[bytes, bytes, bytes]: ) -> tuple[bytes, bytes, bytes]:
salt = crypto.random_bytes(32) salt = random.bytes(32)
rand_mult_num = crypto.random_scalar() rand_mult_num = crypto.random_scalar()
rand_mult = crypto.encodeint(rand_mult_num) rand_mult = crypto_helpers.encodeint(rand_mult_num)
tx_key = misc.compute_tx_key(spend_key_private, tx_prefix_hash, salt, rand_mult_num) tx_key = misc.compute_tx_key(spend_key_private, tx_prefix_hash, salt, rand_mult_num)
return tx_key, salt, rand_mult return tx_key, salt, rand_mult

@ -0,0 +1 @@
from trezor.crypto import monero as crypto # noqa: F401

@ -1,11 +1,11 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from trezor.crypto import monero as tcry from trezor.crypto import monero as tcry
from trezor.enums import MoneroNetworkType
from apps.monero.xmr.networks import NetworkTypes, net_version from apps.monero.xmr.networks import net_version
if TYPE_CHECKING: if TYPE_CHECKING:
from apps.monero.xmr.types import Ge25519
from trezor.messages import MoneroAccountPublicAddress from trezor.messages import MoneroAccountPublicAddress
from trezor.messages import MoneroTransactionDestinationEntry from trezor.messages import MoneroTransactionDestinationEntry
@ -18,7 +18,7 @@ def addr_to_hash(addr: MoneroAccountPublicAddress) -> bytes:
def encode_addr( def encode_addr(
version, spend_pub: Ge25519, view_pub: Ge25519, payment_id: bytes | None = None version, spend_pub: bytes, view_pub: bytes, payment_id: bytes | None = None
) -> str: ) -> str:
""" """
Builds Monero address from public keys Builds Monero address from public keys
@ -40,8 +40,10 @@ def decode_addr(addr: bytes) -> tuple[int, bytes, bytes]:
def public_addr_encode( def public_addr_encode(
pub_addr: MoneroAccountPublicAddress, is_sub=False, net=NetworkTypes.MAINNET pub_addr: MoneroAccountPublicAddress,
): is_sub: bool = False,
net: MoneroNetworkType = MoneroNetworkType.MAINNET,
) -> str:
""" """
Encodes public address to Monero address Encodes public address to Monero address
""" """
@ -52,13 +54,13 @@ def public_addr_encode(
def classify_subaddresses( def classify_subaddresses(
tx_dests: list[MoneroTransactionDestinationEntry], tx_dests: list[MoneroTransactionDestinationEntry],
change_addr: MoneroAccountPublicAddress, change_addr: MoneroAccountPublicAddress,
) -> tuple[int, int, int]: ) -> tuple[int, int, MoneroAccountPublicAddress | None]:
""" """
Classify destination subaddresses Classify destination subaddresses
""" """
num_stdaddresses = 0 num_stdaddresses = 0
num_subaddresses = 0 num_subaddresses = 0
single_dest_subaddress = None single_dest_subaddress: MoneroAccountPublicAddress | None = None
addr_set = set() addr_set = set()
for tx in tx_dests: for tx in tx_dests:
if change_addr and addr_eq(change_addr, tx.addr): if change_addr and addr_eq(change_addr, tx.addr):
@ -75,7 +77,7 @@ def classify_subaddresses(
return num_stdaddresses, num_subaddresses, single_dest_subaddress return num_stdaddresses, num_subaddresses, single_dest_subaddress
def addr_eq(a: MoneroAccountPublicAddress, b: MoneroAccountPublicAddress): def addr_eq(a: MoneroAccountPublicAddress, b: MoneroAccountPublicAddress) -> bool:
return ( return (
a.spend_public_key == b.spend_public_key a.spend_public_key == b.spend_public_key
and a.view_public_key == b.view_public_key and a.view_public_key == b.view_public_key
@ -84,8 +86,8 @@ def addr_eq(a: MoneroAccountPublicAddress, b: MoneroAccountPublicAddress):
def get_change_addr_idx( def get_change_addr_idx(
outputs: list[MoneroTransactionDestinationEntry], outputs: list[MoneroTransactionDestinationEntry],
change_dts: MoneroTransactionDestinationEntry, change_dts: MoneroTransactionDestinationEntry | None,
) -> int: ) -> int | None:
""" """
Returns ID of the change output from the change_dts and outputs Returns ID of the change output from the change_dts and outputs
""" """

File diff suppressed because it is too large Load Diff

@ -1,7 +1,7 @@
from trezor.crypto import chacha20poly1305 as ChaCha20Poly1305, monero, random from trezor.crypto import chacha20poly1305 as ChaCha20Poly1305, monero, random
def encrypt(key, plaintext, associated_data=None): def encrypt(key: bytes, plaintext: bytes, associated_data: bytes | None = None):
""" """
Uses ChaCha20Poly1305 for encryption Uses ChaCha20Poly1305 for encryption
""" """
@ -14,7 +14,13 @@ def encrypt(key, plaintext, associated_data=None):
return nonce, ciphertext + tag, b"" return nonce, ciphertext + tag, b""
def decrypt(key, iv, ciphertext, tag=None, associated_data=None): def decrypt(
key: bytes,
iv: bytes,
ciphertext: bytes,
tag: bytes | None = None,
associated_data: bytes | None = None,
):
""" """
ChaCha20Poly1305 decryption ChaCha20Poly1305 decryption
""" """
@ -30,11 +36,11 @@ def decrypt(key, iv, ciphertext, tag=None, associated_data=None):
return plaintext return plaintext
def encrypt_pack(key, plaintext, associated_data=None): def encrypt_pack(key: bytes, plaintext: bytes, associated_data: bytes | None = None):
b = encrypt(key, plaintext, associated_data) b = encrypt(key, plaintext, associated_data)
return b[0] + b[1] return b[0] + b[1]
def decrypt_pack(key, ciphertext): def decrypt_pack(key: bytes, ciphertext: bytes):
cp = memoryview(ciphertext) cp = memoryview(ciphertext)
return decrypt(key, cp[:12], cp[12:], None) return decrypt(key, cp[:12], cp[12:], None)

@ -0,0 +1,243 @@
"""
Multilayer Linkable Spontaneous Anonymous Group (MLSAG)
Optimized versions with incremental hashing.
See https://eprint.iacr.org/2015/1098.pdf for details.
Also explained in From Zero to Monero section 3.3 and 5.
----------
Please note, that the MLSAG code is written in a generic manner,
where it is designed for multiple public keys (aka inputs). In another
words, MLSAG should be used to sign multiple inputs, but that is currently
not the case of Monero, where the inputs are signed one by one.
So the public keys matrix has always two rows (one for public keys,
one for commitments), although the algorithm is designed for `n` rows.
This has one unfortunate effect where `rows` is always equal to 2 and
dsRows always to 1, but the algorithm is still written as the numbers
are arbitrary. That's why there are loops such as `for i in range(dsRows)`
where it is run only once currently.
----------
Also note, that the matrix of public keys is indexed by columns first.
This is because the code was ported from the official Monero client,
which is written in C++ and where it does have some memory advantages.
For ring size = 3 and one input the matrix M will look like this:
|------------------------|------------------------|------------------------|
| public key 0 | public key 1 | public key 2 |
| cmt 0 - pseudo_out cmt | cmt 1 - pseudo_out cmt | cmt 2 - pseudo_out cmt |
and `sk` is equal to:
|--------------|-----------------------------------------------------|
| private key* | input secret key's mask - pseudo_out's mask (alpha) |
* corresponding to one of the public keys (`index` denotes which one)
----------
Mostly ported from official Monero client, but also inspired by Mininero.
Author: Dusan Klinec, ph4r05, 2018
"""
import gc
from typing import TYPE_CHECKING
from apps.monero.xmr import crypto, crypto_helpers
from apps.monero.xmr.serialize import int_serialize
if TYPE_CHECKING:
from typing import Any, TypeGuard, TypeVar
from .serialize_messages.tx_ct_key import CtKey
from trezor.messages import MoneroRctKeyPublic
KeyM = list[list[bytes]]
T = TypeVar("T")
def list_of_type(lst: list[Any], typ: type[T]) -> TypeGuard[list[T]]:
...
_HASH_KEY_CLSAG_ROUND = b"CLSAG_round\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
_HASH_KEY_CLSAG_AGG_0 = b"CLSAG_agg_0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
_HASH_KEY_CLSAG_AGG_1 = b"CLSAG_agg_1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
def generate_clsag_simple(
message: bytes,
pubs: list[MoneroRctKeyPublic],
in_sk: CtKey,
a: crypto.Scalar,
cout: crypto.Point,
index: int,
mg_buff: list[bytearray],
) -> list[bytes]:
"""
CLSAG for RctType.Simple
https://eprint.iacr.org/2019/654.pdf
Corresponds to proveRctCLSAGSimple in rctSigs.cpp
:param message: the full message to be signed (actually its hash)
:param pubs: vector of MoneroRctKey; this forms the ring; point values in encoded form; (dest, mask) = (P, C)
:param in_sk: CtKey; spending private key with input commitment mask (original); better_name: input_secret_key
:param a: mask from the pseudo output commitment; better name: pseudo_out_alpha
:param cout: pseudo output commitment; point, decoded; better name: pseudo_out_c
:param index: specifies corresponding public key to the `in_sk` in the pubs array
:param mg_buff: buffer to store the signature to
"""
cols = len(pubs)
if cols == 0:
raise ValueError("Empty pubs")
P = _key_vector(cols)
C_nonzero = _key_vector(cols)
p = in_sk.dest
z = crypto.sc_sub_into(None, in_sk.mask, a)
for i in range(cols):
P[i] = pubs[i].dest
C_nonzero[i] = pubs[i].commitment
pubs[i] = None # type: ignore
del pubs
gc.collect()
return _generate_clsag(message, P, p, C_nonzero, z, cout, index, mg_buff)
def _generate_clsag(
message: bytes,
P: list[bytes],
p: crypto.Scalar,
C_nonzero: list[bytes],
z: crypto.Scalar,
Cout: crypto.Point,
index: int,
mg_buff: list[bytearray],
) -> list[bytes]:
sI = crypto.Point() # sig.I
sD = crypto.Point() # sig.D
sc1 = crypto.Scalar() # sig.c1
a = crypto.random_scalar()
H = crypto.Point()
D = crypto.Point()
Cout_bf = crypto_helpers.encodepoint(Cout)
tmp_sc = crypto.Scalar()
tmp = crypto.Point()
tmp_bf = bytearray(32)
crypto.hash_to_point_into(H, P[index])
crypto.scalarmult_into(sI, H, p) # I = p*H
crypto.scalarmult_into(D, H, z) # D = z*H
crypto.sc_mul_into(tmp_sc, z, crypto_helpers.INV_EIGHT_SC) # 1/8*z
crypto.scalarmult_into(sD, H, tmp_sc) # sig.D = 1/8*z*H
sD = crypto_helpers.encodepoint(sD)
hsh_P = crypto_helpers.get_keccak() # domain, I, D, P, C, C_offset
hsh_C = crypto_helpers.get_keccak() # domain, I, D, P, C, C_offset
hsh_P.update(_HASH_KEY_CLSAG_AGG_0)
hsh_C.update(_HASH_KEY_CLSAG_AGG_1)
def hsh_PC(x):
nonlocal hsh_P, hsh_C
hsh_P.update(x)
hsh_C.update(x)
for x in P:
hsh_PC(x)
for x in C_nonzero:
hsh_PC(x)
hsh_PC(crypto.encodepoint_into(tmp_bf, sI))
hsh_PC(sD)
hsh_PC(Cout_bf)
mu_P = crypto_helpers.decodeint(hsh_P.digest())
mu_C = crypto_helpers.decodeint(hsh_C.digest())
del (hsh_PC, hsh_P, hsh_C)
c_to_hash = crypto_helpers.get_keccak() # domain, P, C, C_offset, message, aG, aH
c_to_hash.update(_HASH_KEY_CLSAG_ROUND)
for i in range(len(P)):
c_to_hash.update(P[i])
for i in range(len(P)):
c_to_hash.update(C_nonzero[i])
c_to_hash.update(Cout_bf)
c_to_hash.update(message)
chasher = c_to_hash.copy()
crypto.scalarmult_base_into(tmp, a)
chasher.update(crypto.encodepoint_into(tmp_bf, tmp)) # aG
crypto.scalarmult_into(tmp, H, a)
chasher.update(crypto.encodepoint_into(tmp_bf, tmp)) # aH
c = crypto_helpers.decodeint(chasher.digest())
del (chasher, H)
L = crypto.Point()
R = crypto.Point()
c_p = crypto.Scalar()
c_c = crypto.Scalar()
i = (index + 1) % len(P)
if i == 0:
crypto.sc_copy(sc1, c)
mg_buff.append(int_serialize.dump_uvarint_b(len(P)))
for _ in range(len(P)):
mg_buff.append(bytearray(32))
while i != index:
crypto.random_scalar(tmp_sc)
crypto.encodeint_into(mg_buff[i + 1], tmp_sc)
crypto.sc_mul_into(c_p, mu_P, c)
crypto.sc_mul_into(c_c, mu_C, c)
# L = tmp_sc * G + c_P * P[i] + c_c * C[i]
crypto.add_keys2_into(L, tmp_sc, c_p, crypto.decodepoint_into(tmp, P[i]))
crypto.decodepoint_into(tmp, C_nonzero[i]) # C = C_nonzero - Cout
crypto.point_sub_into(tmp, tmp, Cout)
crypto.scalarmult_into(tmp, tmp, c_c)
crypto.point_add_into(L, L, tmp)
# R = tmp_sc * HP + c_p * I + c_c * D
crypto.hash_to_point_into(tmp, P[i])
crypto.add_keys3_into(R, tmp_sc, tmp, c_p, sI)
crypto.point_add_into(R, R, crypto.scalarmult_into(tmp, D, c_c))
chasher = c_to_hash.copy()
chasher.update(crypto.encodepoint_into(tmp_bf, L))
chasher.update(crypto.encodepoint_into(tmp_bf, R))
crypto.decodeint_into(c, chasher.digest())
P[i] = None # type: ignore
C_nonzero[i] = None # type: ignore
i = (i + 1) % len(P)
if i == 0:
crypto.sc_copy(sc1, c)
if i & 3 == 0:
gc.collect()
# Final scalar = a - c * (mu_P * p + mu_c * Z)
crypto.sc_mul_into(tmp_sc, mu_P, p)
crypto.sc_muladd_into(tmp_sc, mu_C, z, tmp_sc)
crypto.sc_mulsub_into(tmp_sc, c, tmp_sc, a)
crypto.encodeint_into(mg_buff[index + 1], tmp_sc)
if TYPE_CHECKING:
assert list_of_type(mg_buff, bytes)
mg_buff.append(crypto_helpers.encodeint(sc1))
mg_buff.append(sD)
return mg_buff
def _key_vector(rows: int) -> list[Any]:
return [None] * rows

@ -1,11 +1,8 @@
from typing import TYPE_CHECKING from trezor.enums import MoneroNetworkType
from apps.monero.xmr import crypto from apps.monero.xmr import crypto, crypto_helpers
from apps.monero.xmr.addresses import encode_addr from apps.monero.xmr.addresses import encode_addr
from apps.monero.xmr.networks import NetworkTypes, net_version from apps.monero.xmr.networks import net_version
if TYPE_CHECKING:
from apps.monero.xmr.types import Sc25519, Ge25519
class AccountCreds: class AccountCreds:
@ -15,33 +12,33 @@ class AccountCreds:
def __init__( def __init__(
self, self,
view_key_private: Sc25519 | None = None, view_key_private: crypto.Scalar,
spend_key_private: Sc25519 | None = None, spend_key_private: crypto.Scalar,
view_key_public: Ge25519 | None = None, view_key_public: crypto.Point,
spend_key_public: Ge25519 | None = None, spend_key_public: crypto.Point,
address: str | None = None, address: str,
network_type=NetworkTypes.MAINNET, network_type: MoneroNetworkType,
): ) -> None:
self.view_key_private = view_key_private self.view_key_private = view_key_private
self.view_key_public = view_key_public self.view_key_public = view_key_public
self.spend_key_private = spend_key_private self.spend_key_private = spend_key_private
self.spend_key_public = spend_key_public self.spend_key_public = spend_key_public
self.address = address self.address: str | None = address
self.network_type = network_type self.network_type: MoneroNetworkType | None = network_type
@classmethod @classmethod
def new_wallet( def new_wallet(
cls, cls,
priv_view_key: Sc25519, priv_view_key: crypto.Scalar,
priv_spend_key: Sc25519, priv_spend_key: crypto.Scalar,
network_type=NetworkTypes.MAINNET, network_type: MoneroNetworkType = MoneroNetworkType.MAINNET,
): ) -> "AccountCreds":
pub_view_key = crypto.scalarmult_base(priv_view_key) pub_view_key = crypto.scalarmult_base_into(None, priv_view_key)
pub_spend_key = crypto.scalarmult_base(priv_spend_key) pub_spend_key = crypto.scalarmult_base_into(None, priv_spend_key)
addr = encode_addr( addr = encode_addr(
net_version(network_type), net_version(network_type),
crypto.encodepoint(pub_spend_key), crypto_helpers.encodepoint(pub_spend_key),
crypto.encodepoint(pub_view_key), crypto_helpers.encodepoint(pub_view_key),
) )
return cls( return cls(
view_key_private=priv_view_key, view_key_private=priv_view_key,

@ -1,325 +0,0 @@
# Author: Dusan Klinec, ph4r05, 2018
#
# Resources:
# https://cr.yp.to
# https://github.com/monero-project/mininero
# https://godoc.org/github.com/agl/ed25519/edwards25519
# https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-00#section-4
# https://github.com/monero-project/research-lab
from typing import TYPE_CHECKING
from trezor.crypto import monero as tcry, random
from trezor.crypto.hashlib import sha3_256
if TYPE_CHECKING:
from apps.monero.xmr.types import Sc25519, Ge25519
NULL_KEY_ENC = b"\x00" * 32
random_bytes = random.bytes
ct_equals = tcry.ct_equals
def keccak_factory(data=None):
return sha3_256(data=data, keccak=True)
get_keccak = keccak_factory
keccak_hash = tcry.xmr_fast_hash
keccak_hash_into = tcry.xmr_fast_hash
def keccak_2hash(inp, buff=None):
buff = buff if buff else bytearray(32)
keccak_hash_into(buff, inp)
keccak_hash_into(buff, buff)
return buff
def compute_hmac(key, msg):
digestmod = keccak_factory
inner = digestmod()
block_size = inner.block_size
if len(key) > block_size:
key = digestmod(key).digest()
key_block = bytearray(block_size)
for i in range(block_size):
key_block[i] = 0x36
for i in range(len(key)):
key_block[i] ^= key[i]
inner.update(key_block)
inner.update(msg)
outer = digestmod()
for i in range(block_size):
key_block[i] = 0x5C
for i in range(len(key)):
key_block[i] ^= key[i]
outer.update(key_block)
outer.update(inner.digest())
return outer.digest()
#
# EC
#
new_point = tcry.ge25519_set_neutral
def new_scalar() -> Sc25519:
return tcry.init256_modm(0)
decodepoint = tcry.ge25519_unpack_vartime
decodepoint_into = tcry.ge25519_unpack_vartime
encodepoint = tcry.ge25519_pack
encodepoint_into = tcry.ge25519_pack
decodeint = tcry.unpack256_modm
decodeint_into_noreduce = tcry.unpack256_modm_noreduce
decodeint_into = tcry.unpack256_modm
encodeint = tcry.pack256_modm
encodeint_into = tcry.pack256_modm
check_ed25519point = tcry.ge25519_check
scalarmult_base = tcry.ge25519_scalarmult_base
scalarmult_base_into = tcry.ge25519_scalarmult_base
scalarmult = tcry.ge25519_scalarmult
scalarmult_into = tcry.ge25519_scalarmult
point_add = tcry.ge25519_add
point_add_into = tcry.ge25519_add
point_sub = tcry.ge25519_sub
point_sub_into = tcry.ge25519_sub
point_eq = tcry.ge25519_eq
point_double = tcry.ge25519_double
point_double_into = tcry.ge25519_double
point_mul8 = tcry.ge25519_mul8
point_mul8_into = tcry.ge25519_mul8
INV_EIGHT = b"\x79\x2f\xdc\xe2\x29\xe5\x06\x61\xd0\xda\x1c\x7d\xb3\x9d\xd3\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06"
INV_EIGHT_SC = decodeint(INV_EIGHT)
def sc_inv_eight() -> Sc25519:
return INV_EIGHT_SC
#
# Zmod(order), scalar values field
#
def sc_0() -> Sc25519:
return tcry.init256_modm(0)
def sc_0_into(r: Sc25519) -> Sc25519:
return tcry.init256_modm(r, 0)
def sc_init(x: int) -> Sc25519:
if x >= (1 << 64):
raise ValueError("Initialization works up to 64-bit only")
return tcry.init256_modm(x)
def sc_init_into(r: Sc25519, x: int) -> Sc25519:
if x >= (1 << 64):
raise ValueError("Initialization works up to 64-bit only")
return tcry.init256_modm(r, x)
sc_copy = tcry.init256_modm
sc_get64 = tcry.get256_modm
sc_check = tcry.check256_modm
check_sc = tcry.check256_modm
sc_add = tcry.add256_modm
sc_add_into = tcry.add256_modm
sc_sub = tcry.sub256_modm
sc_sub_into = tcry.sub256_modm
sc_mul = tcry.mul256_modm
sc_mul_into = tcry.mul256_modm
def sc_isnonzero(c: Sc25519) -> bool:
"""
Returns true if scalar is non-zero
"""
return not tcry.iszero256_modm(c)
sc_eq = tcry.eq256_modm
sc_mulsub = tcry.mulsub256_modm
sc_mulsub_into = tcry.mulsub256_modm
sc_muladd = tcry.muladd256_modm
sc_muladd_into = tcry.muladd256_modm
sc_inv_into = tcry.inv256_modm
def random_scalar(r=None) -> Sc25519:
return tcry.xmr_random_scalar(r if r is not None else new_scalar())
#
# GE - ed25519 group
#
def ge25519_double_scalarmult_base_vartime(a, A, b) -> Ge25519:
"""
void ge25519_double_scalarmult_vartime(ge25519 *r, const ge25519 *p1, const bignum256modm s1, const bignum256modm s2);
r = a * A + b * B
"""
R = tcry.ge25519_double_scalarmult_vartime(A, a, b)
return R
ge25519_double_scalarmult_vartime2 = tcry.xmr_add_keys3
def identity(byte_enc=False) -> Ge25519 | bytes:
idd = tcry.ge25519_set_neutral()
return idd if not byte_enc else encodepoint(idd)
identity_into = tcry.ge25519_set_neutral
"""
https://www.imperialviolet.org/2013/12/25/elligator.html
http://elligator.cr.yp.to/
http://elligator.cr.yp.to/elligator-20130828.pdf
"""
#
# Monero specific
#
cn_fast_hash = keccak_hash
def hash_to_scalar(data: bytes, length: int | None = None):
"""
H_s(P)
"""
dt = data[:length] if length else data
return tcry.xmr_hash_to_scalar(dt)
def hash_to_scalar_into(r: Sc25519, data: bytes, length: int | None = None):
dt = data[:length] if length else data
return tcry.xmr_hash_to_scalar(r, dt)
# H_p(buf)
#
# Code adapted from MiniNero: https://github.com/monero-project/mininero
# https://github.com/monero-project/research-lab/blob/master/whitepaper/ge_fromfe_writeup/ge_fromfe.pdf
# http://archive.is/yfINb
hash_to_point = tcry.xmr_hash_to_ec
hash_to_point_into = tcry.xmr_hash_to_ec
#
# XMR
#
xmr_H = tcry.ge25519_set_h
def scalarmult_h(i) -> Ge25519:
return scalarmult(xmr_H(), sc_init(i) if isinstance(i, int) else i)
add_keys2 = tcry.xmr_add_keys2_vartime
add_keys2_into = tcry.xmr_add_keys2_vartime
add_keys3 = tcry.xmr_add_keys3_vartime
add_keys3_into = tcry.xmr_add_keys3_vartime
gen_commitment = tcry.xmr_gen_c
def generate_key_derivation(pub: Ge25519, sec: Sc25519) -> Ge25519:
"""
Key derivation: 8*(key2*key1)
"""
sc_check(sec) # checks that the secret key is uniform enough...
check_ed25519point(pub)
return tcry.xmr_generate_key_derivation(pub, sec)
def derivation_to_scalar(derivation: Ge25519, output_index: int) -> Sc25519:
"""
H_s(derivation || varint(output_index))
"""
check_ed25519point(derivation)
return tcry.xmr_derivation_to_scalar(derivation, output_index)
def derive_public_key(derivation: Ge25519, output_index: int, B: Ge25519) -> Ge25519:
"""
H_s(derivation || varint(output_index))G + B
"""
check_ed25519point(B)
return tcry.xmr_derive_public_key(derivation, output_index, B)
def derive_secret_key(derivation: Ge25519, output_index: int, base: Sc25519) -> Sc25519:
"""
base + H_s(derivation || varint(output_index))
"""
sc_check(base)
return tcry.xmr_derive_private_key(derivation, output_index, base)
def get_subaddress_secret_key(
secret_key: Sc25519, major: int = 0, minor: int = 0
) -> Sc25519:
"""
Builds subaddress secret key from the subaddress index
Hs(SubAddr || a || index_major || index_minor)
"""
return tcry.xmr_get_subaddress_secret_key(major, minor, secret_key)
def generate_signature(data: bytes, priv: Sc25519) -> tuple[Sc25519, Sc25519, Ge25519]:
"""
Generate EC signature
crypto_ops::generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig)
"""
pub = scalarmult_base(priv)
k = random_scalar()
comm = scalarmult_base(k)
buff = data + encodepoint(pub) + encodepoint(comm)
c = hash_to_scalar(buff)
r = sc_mulsub(priv, c, k)
return c, r, pub
def check_signature(data: bytes, c: Sc25519, r: Sc25519, pub: Ge25519) -> bool:
"""
EC signature verification
"""
check_ed25519point(pub)
if sc_check(c) != 0 or sc_check(r) != 0:
raise ValueError("Signature error")
tmp2 = point_add(scalarmult(pub, c), scalarmult_base(r))
buff = data + encodepoint(pub) + encodepoint(tmp2)
tmp_c = hash_to_scalar(buff)
res = sc_sub(tmp_c, c)
return not sc_isnonzero(res)
def xor8(buff: bytes, key: bytes) -> bytes:
for i in range(8):
buff[i] ^= key[i]
return buff

@ -0,0 +1,162 @@
# Author: Dusan Klinec, ph4r05, 2018
#
# Resources:
# https://cr.yp.to
# https://github.com/monero-project/mininero
# https://godoc.org/github.com/agl/ed25519/edwards25519
# https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-00#section-4
# https://github.com/monero-project/research-lab
from trezor.crypto import monero as tcry
from trezor.crypto.hashlib import sha3_256
NULL_KEY_ENC = b"\x00" * 32
def get_keccak(data: bytes | None = None) -> sha3_256:
return sha3_256(data=data, keccak=True)
def keccak_2hash(inp: bytes, buff: bytes | None = None) -> bytes:
buff = buff if buff else bytearray(32)
tcry.fast_hash_into(buff, inp)
tcry.fast_hash_into(buff, buff)
return buff
def compute_hmac(key: bytes, msg: bytes) -> bytes:
digestmod = get_keccak
inner = digestmod()
block_size = inner.block_size
if len(key) > block_size:
key = digestmod(key).digest()
key_block = bytearray(block_size)
for i in range(block_size):
key_block[i] = 0x36
for i in range(len(key)):
key_block[i] ^= key[i]
inner.update(key_block)
inner.update(msg)
outer = digestmod()
for i in range(block_size):
key_block[i] = 0x5C
for i in range(len(key)):
key_block[i] ^= key[i]
outer.update(key_block)
outer.update(inner.digest())
return outer.digest()
#
# EC
#
def decodepoint(x: bytes) -> tcry.Point:
return tcry.decodepoint_into(None, x)
def encodepoint(x: tcry.Point, offset: int = 0) -> bytes:
return tcry.encodepoint_into(None, x, offset)
def encodeint(x: tcry.Scalar, offset: int = 0) -> bytes:
return tcry.encodeint_into(None, x, offset)
def decodeint(x: bytes) -> tcry.Scalar:
return tcry.decodeint_into(None, x)
INV_EIGHT = b"\x79\x2f\xdc\xe2\x29\xe5\x06\x61\xd0\xda\x1c\x7d\xb3\x9d\xd3\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06"
INV_EIGHT_SC = decodeint(INV_EIGHT)
def generate_key_derivation(pub: tcry.Point, sec: tcry.Scalar) -> tcry.Point:
"""
Key derivation: 8*(key2*key1)
"""
tcry.sc_check(sec) # checks that the secret key is uniform enough...
tcry.ge25519_check(pub)
return tcry.xmr_generate_key_derivation(None, pub, sec)
def derivation_to_scalar(derivation: tcry.Point, output_index: int) -> tcry.Scalar:
"""
H_s(derivation || varint(output_index))
"""
tcry.ge25519_check(derivation)
return tcry.xmr_derivation_to_scalar(None, derivation, output_index)
def derive_public_key(
derivation: tcry.Point, output_index: int, B: tcry.Point
) -> tcry.Point:
"""
H_s(derivation || varint(output_index))G + B
"""
tcry.ge25519_check(B)
return tcry.xmr_derive_public_key(None, derivation, output_index, B)
def derive_secret_key(
derivation: tcry.Point, output_index: int, base: tcry.Scalar
) -> tcry.Scalar:
"""
base + H_s(derivation || varint(output_index))
"""
tcry.sc_check(base)
return tcry.xmr_derive_private_key(None, derivation, output_index, base)
def get_subaddress_secret_key(
secret_key: tcry.Scalar, major: int = 0, minor: int = 0
) -> tcry.Scalar:
"""
Builds subaddress secret key from the subaddress index
Hs(SubAddr || a || index_major || index_minor)
"""
return tcry.xmr_get_subaddress_secret_key(None, major, minor, secret_key)
def generate_signature(
data: bytes, priv: tcry.Scalar
) -> tuple[tcry.Scalar, tcry.Scalar, tcry.Point]:
"""
Generate EC signature
crypto_ops::generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig)
"""
pub = tcry.scalarmult_base_into(None, priv)
k = tcry.random_scalar()
comm = tcry.scalarmult_base_into(None, k)
buff = data + encodepoint(pub) + encodepoint(comm)
c = tcry.hash_to_scalar_into(None, buff)
r = tcry.sc_mulsub_into(None, priv, c, k)
return c, r, pub
def check_signature(
data: bytes, c: tcry.Scalar, r: tcry.Scalar, pub: tcry.Point
) -> bool:
"""
EC signature verification
"""
tcry.ge25519_check(pub)
if tcry.sc_check(c) != 0 or tcry.sc_check(r) != 0:
raise ValueError("Signature error")
tmp2 = tcry.point_add_into(
None, tcry.scalarmult_into(None, pub, c), tcry.scalarmult_base_into(None, r)
)
buff = data + encodepoint(pub) + encodepoint(tmp2)
tmp_c = tcry.hash_to_scalar_into(None, buff)
res = tcry.sc_sub_into(None, tmp_c, c)
return tcry.sc_iszero(res)
def xor8(buff: bytearray, key: bytes) -> bytes:
for i in range(8):
buff[i] ^= key[i]
return buff

@ -1,27 +1,32 @@
from typing import TYPE_CHECKING
from trezor.utils import HashWriter from trezor.utils import HashWriter
from apps.monero.xmr import crypto from apps.monero.xmr import crypto_helpers
from apps.monero.xmr.serialize import int_serialize from apps.monero.xmr.serialize import int_serialize
if TYPE_CHECKING:
from trezor.utils import HashContext
class KeccakXmrArchive: class KeccakXmrArchive:
def __init__(self, ctx=None): def __init__(self, ctx: HashContext | None = None) -> None:
self.kwriter = get_keccak_writer(ctx) self.kwriter = get_keccak_writer(ctx)
def get_digest(self): def get_digest(self) -> bytes:
return self.kwriter.get_digest() return self.kwriter.get_digest()
def buffer(self, buf): def buffer(self, buf: bytes) -> None:
return self.kwriter.write(buf) return self.kwriter.write(buf)
def uvarint(self, i): def uvarint(self, i: int) -> None:
int_serialize.dump_uvarint(self.kwriter, i) int_serialize.dump_uvarint(self.kwriter, i)
def uint(self, i, width): def uint(self, i: int, width: int) -> None:
int_serialize.dump_uint(self.kwriter, i, width) int_serialize.dump_uint(self.kwriter, i, width)
def get_keccak_writer(ctx=None): def get_keccak_writer(ctx: HashContext | None = None) -> HashWriter:
if ctx is None: if ctx is None:
ctx = crypto.get_keccak() ctx = crypto_helpers.get_keccak()
return HashWriter(ctx) return HashWriter(ctx)

@ -1,19 +1,18 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from apps.monero.xmr import crypto, monero from apps.monero.xmr import crypto, crypto_helpers, monero
from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b
if TYPE_CHECKING: if TYPE_CHECKING:
from apps.monero.xmr.types import Ge25519, Sc25519
from apps.monero.xmr.credentials import AccountCreds from apps.monero.xmr.credentials import AccountCreds
from trezor.messages import MoneroTransferDetails from trezor.messages import MoneroTransferDetails
Subaddresses = dict[bytes, tuple[int, int]] Subaddresses = dict[bytes, tuple[int, int]]
Sig = list[list[Sc25519]] Sig = list[list[crypto.Scalar]]
def compute_hash(rr: MoneroTransferDetails) -> bytes: def compute_hash(rr: MoneroTransferDetails) -> bytes:
kck = crypto.get_keccak() kck = crypto_helpers.get_keccak()
kck.update(rr.out_key) kck.update(rr.out_key)
kck.update(rr.tx_pub_key) kck.update(rr.tx_pub_key)
if rr.additional_tx_pub_keys: if rr.additional_tx_pub_keys:
@ -25,17 +24,17 @@ def compute_hash(rr: MoneroTransferDetails) -> bytes:
def export_key_image( def export_key_image(
creds: AccountCreds, subaddresses: Subaddresses, td: MoneroTransferDetails creds: AccountCreds, subaddresses: Subaddresses, td: MoneroTransferDetails
) -> tuple[Ge25519, Sig]: ) -> tuple[crypto.Point, Sig]:
out_key = crypto.decodepoint(td.out_key) out_key = crypto_helpers.decodepoint(td.out_key)
tx_pub_key = crypto.decodepoint(td.tx_pub_key) tx_pub_key = crypto_helpers.decodepoint(td.tx_pub_key)
additional_tx_pub_key = None additional_tx_pub_key = None
if len(td.additional_tx_pub_keys) == 1: # compression if len(td.additional_tx_pub_keys) == 1: # compression
additional_tx_pub_key = crypto.decodepoint(td.additional_tx_pub_keys[0]) additional_tx_pub_key = crypto_helpers.decodepoint(td.additional_tx_pub_keys[0])
elif td.additional_tx_pub_keys: elif td.additional_tx_pub_keys:
if td.internal_output_index >= len(td.additional_tx_pub_keys): if td.internal_output_index >= len(td.additional_tx_pub_keys):
raise ValueError("Wrong number of additional derivations") raise ValueError("Wrong number of additional derivations")
additional_tx_pub_key = crypto.decodepoint( additional_tx_pub_key = crypto_helpers.decodepoint(
td.additional_tx_pub_keys[td.internal_output_index] td.additional_tx_pub_keys[td.internal_output_index]
) )
@ -56,14 +55,14 @@ def export_key_image(
def _export_key_image( def _export_key_image(
creds: AccountCreds, creds: AccountCreds,
subaddresses: Subaddresses, subaddresses: Subaddresses,
pkey: Ge25519, pkey: crypto.Point,
tx_pub_key: Ge25519, tx_pub_key: crypto.Point,
additional_tx_pub_key: Ge25519 | None, additional_tx_pub_key: crypto.Point | None,
out_idx: int, out_idx: int,
test: bool = True, test: bool = True,
sub_addr_major: int = None, sub_addr_major: int | None = None,
sub_addr_minor: int = None, sub_addr_minor: int | None = None,
) -> tuple[Ge25519, Sig]: ) -> tuple[crypto.Point, Sig]:
""" """
Generates key image for the TXO + signature for the key image Generates key image for the TXO + signature for the key image
""" """
@ -79,7 +78,7 @@ def _export_key_image(
) )
xi, ki, _ = r[:3] xi, ki, _ = r[:3]
phash = crypto.encodepoint(ki) phash = crypto_helpers.encodepoint(ki)
sig = generate_ring_signature(phash, ki, [pkey], xi, 0, test) sig = generate_ring_signature(phash, ki, [pkey], xi, 0, test)
return ki, sig return ki, sig
@ -87,9 +86,9 @@ def _export_key_image(
def generate_ring_signature( def generate_ring_signature(
prefix_hash: bytes, prefix_hash: bytes,
image: Ge25519, image: crypto.Point,
pubs: list[Ge25519], pubs: list[crypto.Point],
sec: Sc25519, sec: crypto.Scalar,
sec_idx: int, sec_idx: int,
test: bool = False, test: bool = False,
) -> Sig: ) -> Sig:
@ -100,59 +99,57 @@ def generate_ring_signature(
from trezor.utils import memcpy from trezor.utils import memcpy
if test: if test:
t = crypto.scalarmult_base(sec) t = crypto.scalarmult_base_into(None, sec)
if not crypto.point_eq(t, pubs[sec_idx]): if not crypto.point_eq(t, pubs[sec_idx]):
raise ValueError("Invalid sec key") raise ValueError("Invalid sec key")
k_i = monero.generate_key_image(crypto.encodepoint(pubs[sec_idx]), sec) k_i = monero.generate_key_image(crypto_helpers.encodepoint(pubs[sec_idx]), sec)
if not crypto.point_eq(k_i, image): if not crypto.point_eq(k_i, image):
raise ValueError("Key image invalid") raise ValueError("Key image invalid")
for k in pubs: for k in pubs:
crypto.check_ed25519point(k) crypto.ge25519_check(k)
buff_off = len(prefix_hash) buff_off = len(prefix_hash)
buff = bytearray(buff_off + 2 * 32 * len(pubs)) buff = bytearray(buff_off + 2 * 32 * len(pubs))
memcpy(buff, 0, prefix_hash, 0, buff_off) memcpy(buff, 0, prefix_hash, 0, buff_off)
mvbuff = memoryview(buff) mvbuff = memoryview(buff)
sum = crypto.sc_0() sum = crypto.Scalar(0)
k = crypto.sc_0() k = crypto.Scalar(0)
sig = [] sig = []
for _ in range(len(pubs)): for _ in range(len(pubs)):
sig.append([crypto.sc_0(), crypto.sc_0()]) # c, r sig.append([crypto.Scalar(0), crypto.Scalar(0)]) # c, r
for i in range(len(pubs)): for i in range(len(pubs)):
if i == sec_idx: if i == sec_idx:
k = crypto.random_scalar() k = crypto.random_scalar()
tmp3 = crypto.scalarmult_base(k) tmp3 = crypto.scalarmult_base_into(None, k)
crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp3) crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp3)
buff_off += 32 buff_off += 32
tmp3 = crypto.hash_to_point(crypto.encodepoint(pubs[i])) tmp3 = crypto.hash_to_point_into(None, crypto_helpers.encodepoint(pubs[i]))
tmp2 = crypto.scalarmult(tmp3, k) tmp2 = crypto.scalarmult_into(None, tmp3, k)
crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2) crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2)
buff_off += 32 buff_off += 32
else: else:
sig[i] = [crypto.random_scalar(), crypto.random_scalar()] sig[i] = [crypto.random_scalar(), crypto.random_scalar()]
tmp3 = pubs[i] tmp3 = pubs[i]
tmp2 = crypto.ge25519_double_scalarmult_base_vartime( tmp2 = crypto.ge25519_double_scalarmult_vartime_into(
sig[i][0], tmp3, sig[i][1] None, tmp3, sig[i][0], sig[i][1]
) )
crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2) crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2)
buff_off += 32 buff_off += 32
tmp3 = crypto.hash_to_point(crypto.encodepoint(tmp3)) tmp3 = crypto.hash_to_point_into(None, crypto_helpers.encodepoint(tmp3))
tmp2 = crypto.ge25519_double_scalarmult_vartime2( tmp2 = crypto.add_keys3_into(None, sig[i][1], tmp3, sig[i][0], image)
sig[i][1], tmp3, sig[i][0], image
)
crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2) crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2)
buff_off += 32 buff_off += 32
sum = crypto.sc_add(sum, sig[i][0]) crypto.sc_add_into(sum, sum, sig[i][0])
h = crypto.hash_to_scalar(buff) h = crypto.hash_to_scalar_into(None, buff)
sig[sec_idx][0] = crypto.sc_sub(h, sum) sig[sec_idx][0] = crypto.sc_sub_into(None, h, sum)
sig[sec_idx][1] = crypto.sc_mulsub(sig[sec_idx][0], sec, k) sig[sec_idx][1] = crypto.sc_mulsub_into(None, sig[sec_idx][0], sec, k)
return sig return sig

@ -1,493 +0,0 @@
"""
Multilayer Linkable Spontaneous Anonymous Group (MLSAG)
Optimized versions with incremental hashing.
See https://eprint.iacr.org/2015/1098.pdf for details.
Also explained in From Zero to Monero section 3.3 and 5.
----------
Please note, that the MLSAG code is written in a generic manner,
where it is designed for multiple public keys (aka inputs). In another
words, MLSAG should be used to sign multiple inputs, but that is currently
not the case of Monero, where the inputs are signed one by one.
So the public keys matrix has always two rows (one for public keys,
one for commitments), although the algorithm is designed for `n` rows.
This has one unfortunate effect where `rows` is always equal to 2 and
dsRows always to 1, but the algorithm is still written as the numbers
are arbitrary. That's why there are loops such as `for i in range(dsRows)`
where it is run only once currently.
----------
Also note, that the matrix of public keys is indexed by columns first.
This is because the code was ported from the official Monero client,
which is written in C++ and where it does have some memory advantages.
For ring size = 3 and one input the matrix M will look like this:
|------------------------|------------------------|------------------------|
| public key 0 | public key 1 | public key 2 |
| cmt 0 - pseudo_out cmt | cmt 1 - pseudo_out cmt | cmt 2 - pseudo_out cmt |
and `sk` is equal to:
|--------------|-----------------------------------------------------|
| private key* | input secret key's mask - pseudo_out's mask (alpha) |
* corresponding to one of the public keys (`index` denotes which one)
----------
Mostly ported from official Monero client, but also inspired by Mininero.
Author: Dusan Klinec, ph4r05, 2018
"""
import gc
from typing import TYPE_CHECKING
from apps.monero.xmr import crypto
from apps.monero.xmr.serialize import int_serialize
if TYPE_CHECKING:
from apps.monero.xmr.types import Ge25519, Sc25519
from apps.monero.xmr.serialize_messages.tx_ct_key import CtKey
from trezor.messages import MoneroRctKeyPublic
KeyM = list[list[bytes]]
_HASH_KEY_CLSAG_ROUND = b"CLSAG_round\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
_HASH_KEY_CLSAG_AGG_0 = b"CLSAG_agg_0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
_HASH_KEY_CLSAG_AGG_1 = b"CLSAG_agg_1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
def generate_mlsag_simple(
message: bytes,
pubs: list[MoneroRctKeyPublic],
in_sk: CtKey,
a: Sc25519,
cout: Ge25519,
index: int,
mg_buff: list[bytes],
) -> list[bytes]:
"""
MLSAG for RctType.Simple
:param message: the full message to be signed (actually its hash)
:param pubs: vector of MoneroRctKey; this forms the ring; point values in encoded form; (dest, mask) = (P, C)
:param in_sk: CtKey; spending private key with input commitment mask (original); better_name: input_secret_key
:param a: mask from the pseudo output commitment; better name: pseudo_out_alpha
:param cout: pseudo output commitment; point, decoded; better name: pseudo_out_c
:param index: specifies corresponding public key to the `in_sk` in the pubs array
:param mg_buff: buffer to store the signature to
"""
# Monero signs inputs separately, so `rows` always equals 2 (pubkey, commitment)
# and `dsRows` is always 1 (denotes where the pubkeys "end")
rows = 2
dsRows = 1
cols = len(pubs)
if cols == 0:
raise ValueError("Empty pubs")
sk = _key_vector(rows)
M = _key_matrix(rows, cols)
sk[0] = in_sk.dest
sk[1] = crypto.sc_sub(in_sk.mask, a)
tmp_pt = crypto.new_point()
for i in range(cols):
crypto.point_sub_into(
tmp_pt, crypto.decodepoint_into(tmp_pt, pubs[i].commitment), cout
)
M[i][0] = pubs[i].dest
M[i][1] = crypto.encodepoint(tmp_pt)
pubs[i] = None
del pubs
gc.collect()
return generate_mlsag(message, M, sk, index, dsRows, mg_buff)
def gen_mlsag_assert(pk: KeyM, xx: list[Sc25519], index: int, dsRows: int):
"""
Conditions check
"""
cols = len(pk)
if cols <= 1:
raise ValueError("Cols == 1")
if index >= cols:
raise ValueError("Index out of range")
rows = len(pk[0])
if rows == 0:
raise ValueError("Empty pk")
for i in range(cols):
if len(pk[i]) != rows:
raise ValueError("pk is not rectangular")
if len(xx) != rows:
raise ValueError("Bad xx size")
if dsRows > rows:
raise ValueError("Bad dsRows size")
return rows, cols
def generate_first_c_and_key_images(
message: bytes,
pk: KeyM,
xx: list[Sc25519],
index: int,
dsRows: int,
rows: int,
cols: int,
) -> tuple[Sc25519, list[Ge25519], list[Ge25519]]:
"""
MLSAG computation - the part with secret keys
:param message: the full message to be signed (actually its hash)
:param pk: matrix of public keys and commitments
:param xx: input secret array composed of a private key and commitment mask
:param index: specifies corresponding public key to the `xx`'s private key in the `pk` array
:param dsRows: row number where the pubkeys "end" (and commitments follow)
:param rows: total number of rows
:param cols: size of ring
"""
II = _key_vector(dsRows)
alpha = _key_vector(rows)
tmp_buff = bytearray(32)
Hi = crypto.new_point()
aGi = crypto.new_point()
aHPi = crypto.new_point()
hasher = _hasher_message(message)
for i in range(dsRows):
# this is somewhat extra as compared to the Ring Confidential Tx paper
# see footnote in From Zero to Monero section 3.3
hasher.update(pk[index][i])
crypto.hash_to_point_into(Hi, pk[index][i])
alpha[i] = crypto.random_scalar()
# L = alpha_i * G
crypto.scalarmult_base_into(aGi, alpha[i])
# Ri = alpha_i * H(P_i)
crypto.scalarmult_into(aHPi, Hi, alpha[i])
# key image
II[i] = crypto.scalarmult(Hi, xx[i])
_hash_point(hasher, aGi, tmp_buff)
_hash_point(hasher, aHPi, tmp_buff)
for i in range(dsRows, rows):
alpha[i] = crypto.random_scalar()
# L = alpha_i * G
crypto.scalarmult_base_into(aGi, alpha[i])
# for some reasons we omit calculating R here, which seems
# contrary to the paper, but it is in the Monero official client
# see https://github.com/monero-project/monero/blob/636153b2050aa0642ba86842c69ac55a5d81618d/src/ringct/rctSigs.cpp#L191
hasher.update(pk[index][i])
_hash_point(hasher, aGi, tmp_buff)
# the first c
c_old = hasher.digest()
c_old = crypto.decodeint(c_old)
return c_old, II, alpha
def generate_mlsag(
message: bytes,
pk: KeyM,
xx: list[Sc25519],
index: int,
dsRows: int,
mg_buff: list[bytes],
) -> list[bytes]:
"""
Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures)
:param message: the full message to be signed (actually its hash)
:param pk: matrix of public keys and commitments
:param xx: input secret array composed of a private key and commitment mask
:param index: specifies corresponding public key to the `xx`'s private key in the `pk` array
:param dsRows: separates pubkeys from commitment
:param mg_buff: mg signature buffer
"""
rows, cols = gen_mlsag_assert(pk, xx, index, dsRows)
rows_b_size = int_serialize.uvarint_size(rows)
# Preallocation of the chunked buffer, len + cols + cc
for _ in range(1 + cols + 1):
mg_buff.append(None)
mg_buff[0] = int_serialize.dump_uvarint_b(cols)
cc = crypto.new_scalar() # rv.cc
c = crypto.new_scalar()
L = crypto.new_point()
R = crypto.new_point()
Hi = crypto.new_point()
# calculates the "first" c, key images and random scalars alpha
c_old, II, alpha = generate_first_c_and_key_images(
message, pk, xx, index, dsRows, rows, cols
)
i = (index + 1) % cols
if i == 0:
crypto.sc_copy(cc, c_old)
ss = [crypto.new_scalar() for _ in range(rows)]
tmp_buff = bytearray(32)
while i != index:
hasher = _hasher_message(message)
# Serialize size of the row
mg_buff[i + 1] = bytearray(rows_b_size + 32 * rows)
int_serialize.dump_uvarint_b_into(rows, mg_buff[i + 1])
for x in ss:
crypto.random_scalar(x)
for j in range(dsRows):
# L = rv.ss[i][j] * G + c_old * pk[i][j]
crypto.add_keys2_into(
L, ss[j], c_old, crypto.decodepoint_into(Hi, pk[i][j])
)
crypto.hash_to_point_into(Hi, pk[i][j])
# R = rv.ss[i][j] * H(pk[i][j]) + c_old * Ip[j]
crypto.add_keys3_into(R, ss[j], Hi, c_old, II[j])
hasher.update(pk[i][j])
_hash_point(hasher, L, tmp_buff)
_hash_point(hasher, R, tmp_buff)
for j in range(dsRows, rows):
# again, omitting R here as discussed above
crypto.add_keys2_into(
L, ss[j], c_old, crypto.decodepoint_into(Hi, pk[i][j])
)
hasher.update(pk[i][j])
_hash_point(hasher, L, tmp_buff)
for si in range(rows):
crypto.encodeint_into(mg_buff[i + 1], ss[si], rows_b_size + 32 * si)
crypto.decodeint_into(c, hasher.digest())
crypto.sc_copy(c_old, c)
pk[i] = None
i = (i + 1) % cols
if i == 0:
crypto.sc_copy(cc, c_old)
gc.collect()
del II
# Finalizing rv.ss by processing rv.ss[index]
mg_buff[index + 1] = bytearray(rows_b_size + 32 * rows)
int_serialize.dump_uvarint_b_into(rows, mg_buff[index + 1])
for j in range(rows):
crypto.sc_mulsub_into(ss[j], c, xx[j], alpha[j])
crypto.encodeint_into(mg_buff[index + 1], ss[j], rows_b_size + 32 * j)
# rv.cc
mg_buff[-1] = crypto.encodeint(cc)
return mg_buff
def generate_clsag_simple(
message: bytes,
pubs: list[MoneroRctKeyPublic],
in_sk: CtKey,
a: Sc25519,
cout: Ge25519,
index: int,
mg_buff: list[bytes],
) -> list[bytes]:
"""
CLSAG for RctType.Simple
https://eprint.iacr.org/2019/654.pdf
Corresponds to proveRctCLSAGSimple in rctSigs.cpp
:param message: the full message to be signed (actually its hash)
:param pubs: vector of MoneroRctKey; this forms the ring; point values in encoded form; (dest, mask) = (P, C)
:param in_sk: CtKey; spending private key with input commitment mask (original); better_name: input_secret_key
:param a: mask from the pseudo output commitment; better name: pseudo_out_alpha
:param cout: pseudo output commitment; point, decoded; better name: pseudo_out_c
:param index: specifies corresponding public key to the `in_sk` in the pubs array
:param mg_buff: buffer to store the signature to
"""
cols = len(pubs)
if cols == 0:
raise ValueError("Empty pubs")
P = _key_vector(cols)
C_nonzero = _key_vector(cols)
p = in_sk.dest
z = crypto.sc_sub(in_sk.mask, a)
for i in range(cols):
P[i] = pubs[i].dest
C_nonzero[i] = pubs[i].commitment
pubs[i] = None
del pubs
gc.collect()
return _generate_clsag(message, P, p, C_nonzero, z, cout, index, mg_buff)
def _generate_clsag(
message: bytes,
P: list[bytes],
p: Sc25519,
C_nonzero: list[bytes],
z: Sc25519,
Cout: Ge25519,
index: int,
mg_buff: list[bytes],
) -> list[bytes]:
sI = crypto.new_point() # sig.I
sD = crypto.new_point() # sig.D
sc1 = crypto.new_scalar() # sig.c1
a = crypto.random_scalar()
H = crypto.new_point()
D = crypto.new_point()
Cout_bf = crypto.encodepoint(Cout)
tmp_sc = crypto.new_scalar()
tmp = crypto.new_point()
tmp_bf = bytearray(32)
crypto.hash_to_point_into(H, P[index])
crypto.scalarmult_into(sI, H, p) # I = p*H
crypto.scalarmult_into(D, H, z) # D = z*H
crypto.sc_mul_into(tmp_sc, z, crypto.sc_inv_eight()) # 1/8*z
crypto.scalarmult_into(sD, H, tmp_sc) # sig.D = 1/8*z*H
sD = crypto.encodepoint(sD)
hsh_P = crypto.get_keccak() # domain, I, D, P, C, C_offset
hsh_C = crypto.get_keccak() # domain, I, D, P, C, C_offset
hsh_P.update(_HASH_KEY_CLSAG_AGG_0)
hsh_C.update(_HASH_KEY_CLSAG_AGG_1)
def hsh_PC(x):
nonlocal hsh_P, hsh_C
hsh_P.update(x)
hsh_C.update(x)
for x in P:
hsh_PC(x)
for x in C_nonzero:
hsh_PC(x)
hsh_PC(crypto.encodepoint_into(tmp_bf, sI))
hsh_PC(sD)
hsh_PC(Cout_bf)
mu_P = crypto.decodeint(hsh_P.digest())
mu_C = crypto.decodeint(hsh_C.digest())
del (hsh_PC, hsh_P, hsh_C)
c_to_hash = crypto.get_keccak() # domain, P, C, C_offset, message, aG, aH
c_to_hash.update(_HASH_KEY_CLSAG_ROUND)
for i in range(len(P)):
c_to_hash.update(P[i])
for i in range(len(P)):
c_to_hash.update(C_nonzero[i])
c_to_hash.update(Cout_bf)
c_to_hash.update(message)
chasher = c_to_hash.copy()
crypto.scalarmult_base_into(tmp, a)
chasher.update(crypto.encodepoint_into(tmp_bf, tmp)) # aG
crypto.scalarmult_into(tmp, H, a)
chasher.update(crypto.encodepoint_into(tmp_bf, tmp)) # aH
c = crypto.decodeint(chasher.digest())
del (chasher, H)
L = crypto.new_point()
R = crypto.new_point()
c_p = crypto.new_scalar()
c_c = crypto.new_scalar()
i = (index + 1) % len(P)
if i == 0:
crypto.sc_copy(sc1, c)
mg_buff.append(int_serialize.dump_uvarint_b(len(P)))
for _ in range(len(P)):
mg_buff.append(bytearray(32))
while i != index:
crypto.random_scalar(tmp_sc)
crypto.encodeint_into(mg_buff[i + 1], tmp_sc)
crypto.sc_mul_into(c_p, mu_P, c)
crypto.sc_mul_into(c_c, mu_C, c)
# L = tmp_sc * G + c_P * P[i] + c_c * C[i]
crypto.add_keys2_into(L, tmp_sc, c_p, crypto.decodepoint_into(tmp, P[i]))
crypto.decodepoint_into(tmp, C_nonzero[i]) # C = C_nonzero - Cout
crypto.point_sub_into(tmp, tmp, Cout)
crypto.scalarmult_into(tmp, tmp, c_c)
crypto.point_add_into(L, L, tmp)
# R = tmp_sc * HP + c_p * I + c_c * D
crypto.hash_to_point_into(tmp, P[i])
crypto.add_keys3_into(R, tmp_sc, tmp, c_p, sI)
crypto.point_add_into(R, R, crypto.scalarmult_into(tmp, D, c_c))
chasher = c_to_hash.copy()
chasher.update(crypto.encodepoint_into(tmp_bf, L))
chasher.update(crypto.encodepoint_into(tmp_bf, R))
crypto.decodeint_into(c, chasher.digest())
P[i] = None
C_nonzero[i] = None
i = (i + 1) % len(P)
if i == 0:
crypto.sc_copy(sc1, c)
if i & 3 == 0:
gc.collect()
# Final scalar = a - c * (mu_P * p + mu_c * Z)
crypto.sc_mul_into(tmp_sc, mu_P, p)
crypto.sc_muladd_into(tmp_sc, mu_C, z, tmp_sc)
crypto.sc_mulsub_into(tmp_sc, c, tmp_sc, a)
crypto.encodeint_into(mg_buff[index + 1], tmp_sc)
mg_buff.append(crypto.encodeint(sc1))
mg_buff.append(sD)
return mg_buff
def _key_vector(rows):
return [None] * rows
def _key_matrix(rows, cols):
"""
first index is columns (so slightly backward from math)
"""
rv = [None] * cols
for i in range(0, cols):
rv[i] = _key_vector(rows)
return rv
def _hasher_message(message):
"""
Returns incremental hasher for MLSAG
"""
ctx = crypto.get_keccak()
ctx.update(message)
return ctx
def _hash_point(hasher, point, tmp_buff):
crypto.encodepoint_into(tmp_buff, point)
hasher.update(tmp_buff)

@ -1,10 +1,12 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from apps.monero.xmr import crypto 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 apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof from trezor.utils import HashContext
class PreMlsagHasher: class PreMlsagHasher:
@ -12,50 +14,50 @@ class PreMlsagHasher:
Iterative construction of the pre_mlsag_hash Iterative construction of the pre_mlsag_hash
""" """
def __init__(self): def __init__(self) -> None:
self.state = 0 self.state = 0
self.kc_master = crypto.get_keccak() self.kc_master: HashContext = crypto_helpers.get_keccak()
self.rsig_hasher = crypto.get_keccak() self.rsig_hasher: HashContext = crypto_helpers.get_keccak()
self.rtcsig_hasher = KeccakXmrArchive() self.rtcsig_hasher: KeccakXmrArchive = KeccakXmrArchive()
def init(self): def init(self) -> None:
if self.state != 0: if self.state != 0:
raise ValueError("State error") raise ValueError("State error")
self.state = 1 self.state = 1
def set_message(self, message: bytes): def set_message(self, message: bytes) -> None:
self.kc_master.update(message) self.kc_master.update(message)
def set_type_fee(self, rv_type: int, fee: int): def set_type_fee(self, rv_type: int, fee: int) -> None:
if self.state != 1: if self.state != 1:
raise ValueError("State error") raise ValueError("State error")
self.state = 2 self.state = 2
self.rtcsig_hasher.uint(rv_type, 1) # UInt8 self.rtcsig_hasher.uint(rv_type, 1) # UInt8
self.rtcsig_hasher.uvarint(fee) # UVarintType self.rtcsig_hasher.uvarint(fee) # UVarintType
def set_ecdh(self, ecdh: bytes): def set_ecdh(self, ecdh: bytes) -> None:
if self.state not in (2, 3, 4): if self.state not in (2, 3, 4):
raise ValueError("State error") raise ValueError("State error")
self.state = 4 self.state = 4
self.rtcsig_hasher.buffer(ecdh) self.rtcsig_hasher.buffer(ecdh)
def set_out_pk_commitment(self, out_pk_commitment: bytes): def set_out_pk_commitment(self, out_pk_commitment: bytes) -> None:
if self.state not in (4, 5): if self.state not in (4, 5):
raise ValueError("State error") raise ValueError("State error")
self.state = 5 self.state = 5
self.rtcsig_hasher.buffer(out_pk_commitment) # ECKey self.rtcsig_hasher.buffer(out_pk_commitment) # ECKey
def rctsig_base_done(self): def rctsig_base_done(self) -> None:
if self.state != 5: if self.state != 5:
raise ValueError("State error") raise ValueError("State error")
self.state = 6 self.state = 6
c_hash = self.rtcsig_hasher.get_digest() c_hash = self.rtcsig_hasher.get_digest()
self.kc_master.update(c_hash) self.kc_master.update(c_hash)
self.rtcsig_hasher = None self.rtcsig_hasher = None # type: ignore
def rsig_val(self, p: bytes | list[bytes] | Bulletproof, raw: bool = False): def rsig_val(self, p: bytes | list[bytes] | Bulletproof, raw: bool = False) -> None:
if self.state == 8: if self.state == 8:
raise ValueError("State error") raise ValueError("State error")
@ -68,9 +70,12 @@ class PreMlsagHasher:
for x in p: for x in p:
self.rsig_hasher.update(x) self.rsig_hasher.update(x)
else: else:
assert isinstance(p, bytes)
self.rsig_hasher.update(p) self.rsig_hasher.update(p)
return return
assert isinstance(p, Bulletproof)
# Hash Bulletproof # Hash Bulletproof
self.rsig_hasher.update(p.A) self.rsig_hasher.update(p.A)
self.rsig_hasher.update(p.S) self.rsig_hasher.update(p.S)
@ -92,7 +97,7 @@ class PreMlsagHasher:
self.state = 8 self.state = 8
c_hash = self.rsig_hasher.digest() c_hash = self.rsig_hasher.digest()
self.rsig_hasher = None self.rsig_hasher = None # type: ignore
self.kc_master.update(c_hash) self.kc_master.update(c_hash)
return self.kc_master.digest() return self.kc_master.digest()

@ -1,9 +1,8 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from apps.monero.xmr import crypto from apps.monero.xmr import crypto, crypto_helpers
if TYPE_CHECKING: if TYPE_CHECKING:
from apps.monero.xmr.types import Ge25519, Sc25519
from apps.monero.xmr.credentials import AccountCreds from apps.monero.xmr.credentials import AccountCreds
Subaddresses = dict[bytes, tuple[int, int]] Subaddresses = dict[bytes, tuple[int, int]]
@ -17,7 +16,9 @@ class XmrNoSuchAddressException(XmrException):
pass pass
def get_subaddress_secret_key(secret_key: Sc25519, major: int = 0, minor: int = 0): def get_subaddress_secret_key(
secret_key: crypto.Scalar, major: int = 0, minor: int = 0
) -> crypto.Scalar:
""" """
Builds subaddress secret key from the subaddress index Builds subaddress secret key from the subaddress index
Hs(SubAddr || a || index_major || index_minor) Hs(SubAddr || a || index_major || index_minor)
@ -25,12 +26,12 @@ def get_subaddress_secret_key(secret_key: Sc25519, major: int = 0, minor: int =
if major == 0 and minor == 0: if major == 0 and minor == 0:
return secret_key return secret_key
return crypto.get_subaddress_secret_key(secret_key, major, minor) return crypto_helpers.get_subaddress_secret_key(secret_key, major, minor)
def get_subaddress_spend_public_key( def get_subaddress_spend_public_key(
view_private: Sc25519, spend_public: Ge25519, major: int, minor: int view_private: crypto.Scalar, spend_public: crypto.Point, major: int, minor: int
) -> Ge25519: ) -> crypto.Point:
""" """
Generates subaddress spend public key D_{major, minor} Generates subaddress spend public key D_{major, minor}
""" """
@ -38,43 +39,43 @@ def get_subaddress_spend_public_key(
return spend_public return spend_public
m = get_subaddress_secret_key(view_private, major=major, minor=minor) m = get_subaddress_secret_key(view_private, major=major, minor=minor)
M = crypto.scalarmult_base(m) M = crypto.scalarmult_base_into(None, m)
D = crypto.point_add(spend_public, M) D = crypto.point_add_into(None, spend_public, M)
return D return D
def derive_subaddress_public_key( def derive_subaddress_public_key(
out_key: Ge25519, derivation: Ge25519, output_index: int out_key: crypto.Point, derivation: crypto.Point, output_index: int
) -> Ge25519: ) -> crypto.Point:
""" """
out_key - H_s(derivation || varint(output_index))G out_key - H_s(derivation || varint(output_index))G
""" """
crypto.check_ed25519point(out_key) crypto.ge25519_check(out_key)
scalar = crypto.derivation_to_scalar(derivation, output_index) scalar = crypto_helpers.derivation_to_scalar(derivation, output_index)
point2 = crypto.scalarmult_base(scalar) point2 = crypto.scalarmult_base_into(None, scalar)
point4 = crypto.point_sub(out_key, point2) point4 = crypto.point_sub_into(None, out_key, point2)
return point4 return point4
def generate_key_image(public_key: bytes, secret_key: Sc25519) -> Ge25519: def generate_key_image(public_key: bytes, secret_key: crypto.Scalar) -> crypto.Point:
""" """
Key image: secret_key * H_p(pub_key) Key image: secret_key * H_p(pub_key)
""" """
point = crypto.hash_to_point(public_key) point = crypto.hash_to_point_into(None, public_key)
point2 = crypto.scalarmult(point, secret_key) point2 = crypto.scalarmult_into(None, point, secret_key)
return point2 return point2
def is_out_to_account( def is_out_to_account(
subaddresses: Subaddresses, subaddresses: Subaddresses,
out_key: Ge25519, out_key: crypto.Point,
derivation: Ge25519, derivation: crypto.Point,
additional_derivation: Ge25519, additional_derivation: crypto.Point | None,
output_index: int, output_index: int,
creds: AccountCreds | None = None, creds: AccountCreds | None,
sub_addr_major: int = None, sub_addr_major: int | None,
sub_addr_minor: int = None, sub_addr_minor: int | None,
): ) -> tuple[tuple[int, int], crypto.Point] | None:
""" """
Checks whether the given transaction is sent to the account. Checks whether the given transaction is sent to the account.
Searches subaddresses for the computed subaddress_spendkey. Searches subaddresses for the computed subaddress_spendkey.
@ -101,7 +102,7 @@ def is_out_to_account(
return (sub_addr_major, sub_addr_minor), derivation return (sub_addr_major, sub_addr_minor), derivation
if subaddresses: if subaddresses:
subaddress_spendkey = crypto.encodepoint(subaddress_spendkey_obj) subaddress_spendkey = crypto_helpers.encodepoint(subaddress_spendkey_obj)
if subaddress_spendkey in subaddresses: if subaddress_spendkey in subaddresses:
return subaddresses[subaddress_spendkey], derivation return subaddresses[subaddress_spendkey], derivation
@ -111,10 +112,12 @@ def is_out_to_account(
) )
if sub_pub_key and crypto.point_eq(subaddress_spendkey_obj, sub_pub_key): if sub_pub_key and crypto.point_eq(subaddress_spendkey_obj, sub_pub_key):
# sub_pub_key is only set if sub_addr_{major, minor} are set
assert sub_addr_major is not None and sub_addr_minor is not None
return (sub_addr_major, sub_addr_minor), additional_derivation return (sub_addr_major, sub_addr_minor), additional_derivation
if subaddresses: if subaddresses:
subaddress_spendkey = crypto.encodepoint(subaddress_spendkey_obj) subaddress_spendkey = crypto_helpers.encodepoint(subaddress_spendkey_obj)
if subaddress_spendkey in subaddresses: if subaddress_spendkey in subaddresses:
return subaddresses[subaddress_spendkey], additional_derivation return subaddresses[subaddress_spendkey], additional_derivation
@ -123,11 +126,11 @@ def is_out_to_account(
def generate_tx_spend_and_key_image( def generate_tx_spend_and_key_image(
ack: AccountCreds, ack: AccountCreds,
out_key: Ge25519, out_key: crypto.Point,
recv_derivation: Ge25519, recv_derivation: crypto.Point,
real_output_index: int, real_output_index: int,
received_index: tuple[int, int], received_index: tuple[int, int],
) -> tuple[Sc25519, Ge25519] | None: ) -> tuple[crypto.Scalar, crypto.Point]:
""" """
Generates UTXO spending key and key image. Generates UTXO spending key and key image.
Corresponds to generate_key_image_helper_precomp() in the Monero codebase. Corresponds to generate_key_image_helper_precomp() in the Monero codebase.
@ -140,11 +143,11 @@ def generate_tx_spend_and_key_image(
:param received_index: subaddress index this payment was received to :param received_index: subaddress index this payment was received to
:return: :return:
""" """
if not crypto.sc_isnonzero(ack.spend_key_private): if crypto.sc_iszero(ack.spend_key_private):
raise ValueError("Watch-only wallet not supported") raise ValueError("Watch-only wallet not supported")
# derive secret key with subaddress - step 1: original CN derivation # derive secret key with subaddress - step 1: original CN derivation
scalar_step1 = crypto.derive_secret_key( scalar_step1 = crypto_helpers.derive_secret_key(
recv_derivation, real_output_index, ack.spend_key_private recv_derivation, real_output_index, ack.spend_key_private
) )
@ -156,10 +159,10 @@ def generate_tx_spend_and_key_image(
subaddr_sk = get_subaddress_secret_key( subaddr_sk = get_subaddress_secret_key(
ack.view_key_private, major=received_index[0], minor=received_index[1] ack.view_key_private, major=received_index[0], minor=received_index[1]
) )
scalar_step2 = crypto.sc_add(scalar_step1, subaddr_sk) scalar_step2 = crypto.sc_add_into(None, scalar_step1, subaddr_sk)
# When not in multisig, we know the full spend secret key, so the output pubkey can be obtained by scalarmultBase # When not in multisig, we know the full spend secret key, so the output pubkey can be obtained by scalarmultBase
pub_ver = crypto.scalarmult_base(scalar_step2) pub_ver = crypto.scalarmult_base_into(None, scalar_step2)
# <Multisig>, branch deactivated until implemented # <Multisig>, branch deactivated until implemented
# # When in multisig, we only know the partial spend secret key. But we do know the full spend public key, # # When in multisig, we only know the partial spend secret key. But we do know the full spend public key,
@ -179,20 +182,20 @@ def generate_tx_spend_and_key_image(
"key image helper precomp: given output pubkey doesn't match the derived one" "key image helper precomp: given output pubkey doesn't match the derived one"
) )
ki = generate_key_image(crypto.encodepoint(pub_ver), scalar_step2) ki = generate_key_image(crypto_helpers.encodepoint(pub_ver), scalar_step2)
return scalar_step2, ki return scalar_step2, ki
def generate_tx_spend_and_key_image_and_derivation( def generate_tx_spend_and_key_image_and_derivation(
creds: AccountCreds, creds: AccountCreds,
subaddresses: Subaddresses, subaddresses: Subaddresses,
out_key: Ge25519, out_key: crypto.Point,
tx_public_key: Ge25519, tx_public_key: crypto.Point,
additional_tx_public_key: Ge25519, additional_tx_public_key: crypto.Point | None,
real_output_index: int, real_output_index: int | None,
sub_addr_major: int = None, sub_addr_major: int | None,
sub_addr_minor: int = None, sub_addr_minor: int | None,
) -> tuple[Sc25519, Ge25519, Ge25519]: ) -> tuple[crypto.Scalar, crypto.Point, crypto.Point]:
""" """
Generates UTXO spending key and key image and corresponding derivation. Generates UTXO spending key and key image and corresponding derivation.
Supports subaddresses. Supports subaddresses.
@ -208,12 +211,14 @@ def generate_tx_spend_and_key_image_and_derivation(
:param sub_addr_minor: subaddress minor index :param sub_addr_minor: subaddress minor index
:return: :return:
""" """
recv_derivation = crypto.generate_key_derivation( recv_derivation = crypto_helpers.generate_key_derivation(
tx_public_key, creds.view_key_private tx_public_key, creds.view_key_private
) )
additional_recv_derivation = ( additional_recv_derivation = (
crypto.generate_key_derivation(additional_tx_public_key, creds.view_key_private) crypto_helpers.generate_key_derivation(
additional_tx_public_key, creds.view_key_private
)
if additional_tx_public_key if additional_tx_public_key
else None else None
) )
@ -257,56 +262,55 @@ def compute_subaddresses(
for idx in indices: for idx in indices:
if account == 0 and idx == 0: if account == 0 and idx == 0:
subaddresses[crypto.encodepoint(creds.spend_key_public)] = (0, 0) subaddresses[crypto_helpers.encodepoint(creds.spend_key_public)] = (0, 0)
continue continue
pub = get_subaddress_spend_public_key( pub = get_subaddress_spend_public_key(
creds.view_key_private, creds.spend_key_public, major=account, minor=idx creds.view_key_private, creds.spend_key_public, major=account, minor=idx
) )
pub = crypto.encodepoint(pub) pub = crypto_helpers.encodepoint(pub)
subaddresses[pub] = (account, idx) subaddresses[pub] = (account, idx)
return subaddresses return subaddresses
def generate_keys(recovery_key: Sc25519) -> tuple[Sc25519, Ge25519]: def generate_keys(recovery_key: crypto.Scalar) -> tuple[crypto.Scalar, crypto.Point]:
pub = crypto.scalarmult_base(recovery_key) pub = crypto.scalarmult_base_into(None, recovery_key)
return recovery_key, pub return recovery_key, pub
def generate_monero_keys(seed: bytes) -> tuple[Sc25519, Ge25519, Sc25519, Ge25519]: def generate_monero_keys(
seed: bytes,
) -> tuple[crypto.Scalar, crypto.Point, crypto.Scalar, crypto.Point]:
""" """
Generates spend key / view key from the seed in the same manner as Monero code does. Generates spend key / view key from the seed in the same manner as Monero code does.
account.cpp: account.cpp:
crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random). crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random).
""" """
spend_sec, spend_pub = generate_keys(crypto.decodeint(seed)) spend_sec, spend_pub = generate_keys(crypto_helpers.decodeint(seed))
hash = crypto.cn_fast_hash(crypto.encodeint(spend_sec)) hash = crypto.fast_hash_into(None, crypto_helpers.encodeint(spend_sec))
view_sec, view_pub = generate_keys(crypto.decodeint(hash)) view_sec, view_pub = generate_keys(crypto_helpers.decodeint(hash))
return spend_sec, spend_pub, view_sec, view_pub return spend_sec, spend_pub, view_sec, view_pub
def generate_sub_address_keys( def generate_sub_address_keys(
view_sec: Sc25519, spend_pub: Ge25519, major: int, minor: int view_sec: crypto.Scalar, spend_pub: crypto.Point, major: int, minor: int
) -> tuple[Ge25519, Ge25519]: ) -> tuple[crypto.Point, crypto.Point]:
if major == 0 and minor == 0: # special case, Monero-defined if major == 0 and minor == 0: # special case, Monero-defined
return spend_pub, crypto.scalarmult_base(view_sec) return spend_pub, crypto.scalarmult_base_into(None, view_sec)
m = get_subaddress_secret_key(view_sec, major=major, minor=minor) m = get_subaddress_secret_key(view_sec, major=major, minor=minor)
M = crypto.scalarmult_base(m) M = crypto.scalarmult_base_into(None, m)
D = crypto.point_add(spend_pub, M) D = crypto.point_add_into(None, spend_pub, M)
C = crypto.scalarmult(D, view_sec) C = crypto.scalarmult_into(None, D, view_sec)
return D, C return D, C
def commitment_mask(key: bytes, buff: Sc25519 | None = None) -> Sc25519: def commitment_mask(key: bytes, buff: crypto.Scalar | None = None) -> crypto.Scalar:
""" """
Generates deterministic commitment mask for Bulletproof2 Generates deterministic commitment mask for Bulletproof2
""" """
data = bytearray(15 + 32) data = bytearray(15 + 32)
data[0:15] = b"commitment_mask" data[0:15] = b"commitment_mask"
data[15:] = key data[15:] = key
if buff: return crypto.hash_to_scalar_into(buff, data)
return crypto.hash_to_scalar_into(buff, data)
else:
return crypto.hash_to_scalar(data)

@ -1,8 +1,4 @@
class NetworkTypes: from trezor.enums import MoneroNetworkType
MAINNET = 0
TESTNET = 1
STAGENET = 2
FAKECHAIN = 3
class MainNet: class MainNet:
@ -24,8 +20,10 @@ class StageNet:
def net_version( def net_version(
network_type=NetworkTypes.MAINNET, is_subaddr=False, is_integrated=False network_type: MoneroNetworkType = MoneroNetworkType.MAINNET,
): is_subaddr: bool = False,
is_integrated: bool = False,
) -> bytes:
""" """
Network version bytes used for address construction Network version bytes used for address construction
""" """
@ -33,11 +31,11 @@ def net_version(
raise ValueError("Subaddress cannot be integrated") raise ValueError("Subaddress cannot be integrated")
c_net = None c_net = None
if network_type is None or network_type == NetworkTypes.MAINNET: if network_type == MoneroNetworkType.MAINNET:
c_net = MainNet c_net = MainNet
elif network_type == NetworkTypes.TESTNET: elif network_type == MoneroNetworkType.TESTNET:
c_net = TestNet c_net = TestNet
elif network_type == NetworkTypes.STAGENET: elif network_type == MoneroNetworkType.STAGENET:
c_net = StageNet c_net = StageNet
else: else:
raise ValueError(f"Unknown network type: {network_type}") raise ValueError(f"Unknown network type: {network_type}")

@ -11,35 +11,36 @@ Author: Dusan Klinec, ph4r05, 2018
import gc import gc
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from apps.monero.xmr import crypto from apps.monero.xmr import crypto, crypto_helpers
if TYPE_CHECKING: if TYPE_CHECKING:
from apps.monero.xmr.types import Sc25519
from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof
def prove_range_bp_batch(amounts: list[int], masks: list[Sc25519]) -> Bulletproof: def prove_range_bp_batch(amounts: list[int], masks: list[crypto.Scalar]) -> Bulletproof:
"""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.BulletProofBuilder()
bp_proof = bpi.prove_batch([crypto.sc_init(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()
return bp_proof return bp_proof
def verify_bp(bp_proof: Bulletproof, amounts: list[int], masks: list[Sc25519]) -> bool: def verify_bp(
bp_proof: Bulletproof, amounts: list[int], masks: list[crypto.Scalar]
) -> bool:
"""Verifies Bulletproof""" """Verifies Bulletproof"""
from apps.monero.xmr import bulletproof as bp from apps.monero.xmr import bulletproof as bp
if amounts: if amounts:
bp_proof.V = [] bp_proof.V = []
for i in range(len(amounts)): for i in range(len(amounts)):
C = crypto.gen_commitment(masks[i], amounts[i]) C = crypto.gen_commitment_into(None, masks[i], amounts[i])
crypto.scalarmult_into(C, C, crypto.sc_inv_eight()) crypto.scalarmult_into(C, C, crypto_helpers.INV_EIGHT_SC)
bp_proof.V.append(crypto.encodepoint(C)) bp_proof.V.append(crypto_helpers.encodepoint(C))
bpi = bp.BulletProofBuilder() bpi = bp.BulletProofBuilder()
res = bpi.verify(bp_proof) res = bpi.verify(bp_proof)

@ -1,14 +1,24 @@
import gc import gc
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import TypeVar
def parse_msg(buf: bytes, msg_type): from .message_types import MessageType
T = TypeVar("T", bound=MessageType)
def parse_msg(buf: bytes, msg_type: type[T]) -> T:
from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter
reader = MemoryReaderWriter(memoryview(buf)) reader = MemoryReaderWriter(memoryview(buf))
return msg_type.load(reader) return msg_type.load(reader)
def dump_msg(msg, preallocate: int = None, prefix: bytes = None) -> bytes: def dump_msg(
msg: MessageType, preallocate: int | None = None, prefix: bytes | None = None
) -> bytes:
from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter
writer = MemoryReaderWriter(preallocate=preallocate) writer = MemoryReaderWriter(preallocate=preallocate)
@ -20,7 +30,9 @@ def dump_msg(msg, preallocate: int = None, prefix: bytes = None) -> bytes:
return writer.get_buffer() return writer.get_buffer()
def dump_msg_gc(msg, preallocate: int = None, prefix: bytes = None) -> bytes: def dump_msg_gc(
msg: MessageType, preallocate: int | None = None, prefix: bytes | None = None
) -> bytes:
buf = dump_msg(msg, preallocate, prefix) buf = dump_msg(msg, preallocate, prefix)
del msg del msg
gc.collect() gc.collect()

@ -1,3 +1,5 @@
from typing import TYPE_CHECKING
from apps.monero.xmr.serialize.int_serialize import ( from apps.monero.xmr.serialize.int_serialize import (
dump_uint, dump_uint,
dump_uvarint, dump_uvarint,
@ -5,30 +7,60 @@ from apps.monero.xmr.serialize.int_serialize import (
load_uvarint, load_uvarint,
) )
if TYPE_CHECKING:
from typing import Protocol, TypeVar, Union
T = TypeVar("T")
XT = TypeVar("XT", bound="XmrType")
ST = TypeVar("ST", bound="XmrStructuredType")
XmrFieldType = Union[
tuple[str, XT],
tuple[str, ST, XT],
]
XmrFspec = tuple[XmrFieldType, ...]
class Writer(Protocol):
def write(self, __data: bytes) -> None:
...
class Reader(Protocol):
def readinto(self, __buffer: bytearray | memoryview) -> int:
...
class XmrType(Protocol[T]):
def load(self, __reader: Reader) -> T:
...
def dump(self, __writer: Writer, __value: T) -> None:
...
class XmrType: class XmrStructuredType(XmrType):
pass def f_specs(self) -> XmrFspec:
...
class UVarintType(XmrType): class UVarintType:
@staticmethod @staticmethod
def load(reader) -> int: def load(reader: Reader) -> int:
return load_uvarint(reader) return load_uvarint(reader)
@staticmethod @staticmethod
def dump(writer, n: int): def dump(writer: Writer, n: int) -> None:
return dump_uvarint(writer, n) return dump_uvarint(writer, n)
class IntType(XmrType): class IntType:
WIDTH = 0 WIDTH = 0
@classmethod @classmethod
def load(cls, reader) -> int: def load(cls, reader: Reader) -> int:
return load_uint(reader, cls.WIDTH) return load_uint(reader, cls.WIDTH)
@classmethod @classmethod
def dump(cls, writer, n: int): def dump(cls, writer: Writer, n: int):
return dump_uint(writer, n, cls.WIDTH) return dump_uint(writer, n, cls.WIDTH)

@ -1,7 +1,12 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .base_types import Reader, Writer
_UINT_BUFFER = bytearray(1) _UINT_BUFFER = bytearray(1)
def load_uint(reader, width): def load_uint(reader: Reader, width: int) -> int:
""" """
Constant-width integer serialization Constant-width integer serialization
""" """
@ -15,7 +20,7 @@ def load_uint(reader, width):
return result return result
def dump_uint(writer, n, width): def dump_uint(writer: Writer, n: int, width: int) -> None:
""" """
Constant-width integer serialization Constant-width integer serialization
""" """
@ -26,7 +31,7 @@ def dump_uint(writer, n, width):
n >>= 8 n >>= 8
def uvarint_size(n): def uvarint_size(n: int) -> int:
""" """
Returns size in bytes n would occupy serialized as varint Returns size in bytes n would occupy serialized as varint
""" """
@ -37,7 +42,7 @@ def uvarint_size(n):
return bts return bts
def load_uvarint_b(buffer): def load_uvarint_b(buffer: bytes) -> int:
""" """
Variable int deserialization, synchronous from buffer. Variable int deserialization, synchronous from buffer.
""" """
@ -51,7 +56,7 @@ def load_uvarint_b(buffer):
return result return result
def dump_uvarint_b(n): def dump_uvarint_b(n: int) -> bytearray:
""" """
Serializes uvarint to the buffer Serializes uvarint to the buffer
""" """
@ -59,7 +64,7 @@ def dump_uvarint_b(n):
return dump_uvarint_b_into(n, buffer, 0) return dump_uvarint_b_into(n, buffer, 0)
def dump_uvarint_b_into(n, buffer, offset=0): def dump_uvarint_b_into(n: int, buffer: bytearray, offset: int = 0) -> bytearray:
""" """
Serializes n as variable size integer to the provided buffer. Serializes n as variable size integer to the provided buffer.
""" """
@ -74,7 +79,9 @@ def dump_uvarint_b_into(n, buffer, offset=0):
return buffer return buffer
def dump_uint_b_into(n, width, buffer, offset=0): def dump_uint_b_into(
n: int, width: int, buffer: bytearray, offset: int = 0
) -> bytearray:
""" """
Serializes fixed size integer to the buffer Serializes fixed size integer to the buffer
""" """
@ -84,7 +91,7 @@ def dump_uint_b_into(n, width, buffer, offset=0):
return buffer return buffer
def load_uvarint(reader): def load_uvarint(reader: Reader) -> int:
buffer = _UINT_BUFFER buffer = _UINT_BUFFER
result = 0 result = 0
shift = 0 shift = 0
@ -97,7 +104,7 @@ def load_uvarint(reader):
return result return result
def dump_uvarint(writer, n): def dump_uvarint(writer: Writer, n: int) -> None:
if n < 0: if n < 0:
raise ValueError("Cannot dump signed value, convert it to unsigned first.") raise ValueError("Cannot dump signed value, convert it to unsigned first.")
buffer = _UINT_BUFFER buffer = _UINT_BUFFER

@ -1,33 +1,41 @@
from typing import TYPE_CHECKING
from trezor.utils import obj_eq, obj_repr from trezor.utils import obj_eq, obj_repr
from apps.monero.xmr.serialize.base_types import XmrType from apps.monero.xmr.serialize.int_serialize import dump_uvarint, load_uvarint
from apps.monero.xmr.serialize.int_serialize import (
dump_uint, if TYPE_CHECKING:
dump_uvarint, from typing import TypeVar, Generic
load_uint,
load_uvarint, from .base_types import XmrType, XmrFspec, Reader, Writer
)
T = TypeVar("T", bound=XmrType)
MT = TypeVar("MT", bound="MessageType")
else:
Generic = XmrType = [object]
T = 0
class UnicodeType(XmrType): class UnicodeType:
""" """
Unicode data in UTF-8 encoding. Unicode data in UTF-8 encoding.
""" """
@staticmethod @staticmethod
def dump(writer, s): def dump(writer: Writer, s: str) -> None:
dump_uvarint(writer, len(s)) dump_uvarint(writer, len(s))
writer.write(bytes(s)) writer.write(s.encode())
@staticmethod @staticmethod
def load(reader): def load(reader: Reader) -> str:
ivalue = load_uvarint(reader) ivalue = load_uvarint(reader)
fvalue = bytearray(ivalue) fvalue = bytearray(ivalue)
reader.readinto(fvalue) reader.readinto(fvalue)
return str(fvalue) return str(fvalue)
class BlobType(XmrType): class BlobType:
""" """
Binary data, represented as bytearray. BlobType is only a scheme Binary data, represented as bytearray. BlobType is only a scheme
descriptor. Behaves in the same way as primitive types. descriptor. Behaves in the same way as primitive types.
@ -37,7 +45,7 @@ class BlobType(XmrType):
SIZE = 0 SIZE = 0
@classmethod @classmethod
def dump(cls, writer, elem: bytes): def dump(cls, writer: Writer, elem: bytes) -> None:
if cls.FIX_SIZE: if cls.FIX_SIZE:
if cls.SIZE != len(elem): if cls.SIZE != len(elem):
raise ValueError("Size mismatch") raise ValueError("Size mismatch")
@ -46,7 +54,7 @@ class BlobType(XmrType):
writer.write(elem) writer.write(elem)
@classmethod @classmethod
def load(cls, reader) -> bytearray: def load(cls, reader: Reader) -> bytes:
if cls.FIX_SIZE: if cls.FIX_SIZE:
size = cls.SIZE size = cls.SIZE
else: else:
@ -56,7 +64,7 @@ class BlobType(XmrType):
return elem return elem
class ContainerType(XmrType): class ContainerType(Generic[T]):
""" """
Array of elements, represented as a list of items. ContainerType is only a Array of elements, represented as a list of items. ContainerType is only a
scheme descriptor. scheme descriptor.
@ -64,10 +72,12 @@ class ContainerType(XmrType):
FIX_SIZE = 0 FIX_SIZE = 0
SIZE = 0 SIZE = 0
ELEM_TYPE = None ELEM_TYPE: XmrType[T]
@classmethod @classmethod
def dump(cls, writer, elems, elem_type=None): def dump(
cls, writer: Writer, elems: list[T], elem_type: XmrType[T] | None = None
) -> None:
if elem_type is None: if elem_type is None:
elem_type = cls.ELEM_TYPE elem_type = cls.ELEM_TYPE
if cls.FIX_SIZE: if cls.FIX_SIZE:
@ -79,7 +89,7 @@ class ContainerType(XmrType):
elem_type.dump(writer, elem) elem_type.dump(writer, elem)
@classmethod @classmethod
def load(cls, reader, elem_type=None): def load(cls, reader: Reader, elem_type: XmrType[T] | None = None) -> list[T]:
if elem_type is None: if elem_type is None:
elem_type = cls.ELEM_TYPE elem_type = cls.ELEM_TYPE
if cls.FIX_SIZE: if cls.FIX_SIZE:
@ -93,42 +103,7 @@ class ContainerType(XmrType):
return elems return elems
class VariantType(XmrType): class MessageType:
"""
Union of types, differentiated by variant tags. VariantType is only a scheme
descriptor.
"""
@classmethod
def dump(cls, writer, elem):
for field in cls.f_specs():
ftype = field[1]
if isinstance(elem, ftype):
break
else:
raise ValueError(f"Unrecognized variant: {elem}")
dump_uint(writer, ftype.VARIANT_CODE, 1)
ftype.dump(writer, elem)
@classmethod
def load(cls, reader):
tag = load_uint(reader, 1)
for field in cls.f_specs():
ftype = field[1]
if ftype.VARIANT_CODE == tag:
fvalue = ftype.load(reader)
break
else:
raise ValueError(f"Unknown tag: {tag}")
return fvalue
@classmethod
def f_specs(cls):
return ()
class MessageType(XmrType):
""" """
Message composed of fields with specific types. Message composed of fields with specific types.
""" """
@ -141,7 +116,7 @@ class MessageType(XmrType):
__repr__ = obj_repr __repr__ = obj_repr
@classmethod @classmethod
def dump(cls, writer, msg): def dump(cls: type[MT], writer: Writer, msg: MT) -> None:
defs = cls.f_specs() defs = cls.f_specs()
for field in defs: for field in defs:
fname, ftype, *fparams = field fname, ftype, *fparams = field
@ -149,7 +124,7 @@ class MessageType(XmrType):
ftype.dump(writer, fvalue, *fparams) ftype.dump(writer, fvalue, *fparams)
@classmethod @classmethod
def load(cls, reader): def load(cls: type[MT], reader: Reader) -> MT:
msg = cls() msg = cls()
defs = cls.f_specs() defs = cls.f_specs()
for field in defs: for field in defs:
@ -159,5 +134,5 @@ class MessageType(XmrType):
return msg return msg
@classmethod @classmethod
def f_specs(cls): def f_specs(cls) -> XmrFspec:
return () return ()

@ -4,14 +4,12 @@ import gc
class MemoryReaderWriter: class MemoryReaderWriter:
def __init__( def __init__(
self, self,
buffer=None, buffer: bytearray | memoryview | None = None,
read_empty=False, read_empty: bool = False,
threshold=None, threshold: int | None = None,
do_gc=False, do_gc: bool = False,
preallocate=None, preallocate: int | None = None,
**kwargs ) -> None:
):
self.buffer = buffer
self.nread = 0 self.nread = 0
self.nwritten = 0 self.nwritten = 0
@ -25,20 +23,21 @@ class MemoryReaderWriter:
if preallocate is not None: if preallocate is not None:
self.preallocate(preallocate) self.preallocate(preallocate)
elif self.buffer is None: elif buffer is None:
self.buffer = bytearray(0) self.buffer = bytearray(0)
else: else:
self.buffer = buffer
self.woffset = len(buffer) self.woffset = len(buffer)
def is_empty(self): def is_empty(self) -> bool:
return self.offset == len(self.buffer) or self.offset == self.woffset return self.offset == len(self.buffer) or self.offset == self.woffset
def preallocate(self, size): def preallocate(self, size: int) -> None:
self.buffer = bytearray(size) self.buffer = bytearray(size)
self.offset = 0 self.offset = 0
self.woffset = 0 self.woffset = 0
def readinto(self, buf): def readinto(self, buf: bytearray | memoryview) -> int:
ln = len(buf) ln = len(buf)
if not self.read_empty and ln > 0 and self.offset == len(self.buffer): if not self.read_empty and ln > 0 and self.offset == len(self.buffer):
raise EOFError raise EOFError
@ -62,7 +61,8 @@ class MemoryReaderWriter:
return nread return nread
def write(self, buf): def write(self, buf: bytes) -> None:
assert isinstance(self.buffer, bytearray)
nwritten = len(buf) nwritten = len(buf)
nall = len(self.buffer) nall = len(self.buffer)
towrite = nwritten towrite = nwritten
@ -93,8 +93,8 @@ class MemoryReaderWriter:
self.nwritten += nwritten self.nwritten += nwritten
self.ndata += nwritten self.ndata += nwritten
return nwritten # return nwritten
def get_buffer(self): def get_buffer(self) -> bytes:
mv = memoryview(self.buffer) mv = memoryview(self.buffer)
return mv[self.offset : self.woffset] return mv[self.offset : self.woffset]

@ -1,6 +1,12 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .. import crypto
class CtKey: class CtKey:
__slots__ = ("dest", "mask") __slots__ = ("dest", "mask")
def __init__(self, dest, mask): def __init__(self, dest: crypto.Scalar, mask: crypto.Scalar) -> None:
self.dest = dest self.dest = dest
self.mask = mask self.mask = mask

@ -10,7 +10,7 @@ class TxinToKey(MessageType):
VARIANT_CODE = const(0x2) VARIANT_CODE = const(0x2)
@classmethod @classmethod
def f_specs(cls): def f_specs(cls) -> tuple:
return ( return (
("amount", UVarintType), ("amount", UVarintType),
("key_offsets", ContainerType, UVarintType), ("key_offsets", ContainerType, UVarintType),

@ -1,19 +1,23 @@
from micropython import const from micropython import const
from typing import TYPE_CHECKING
from apps.monero.xmr.serialize.message_types import ContainerType, MessageType from apps.monero.xmr.serialize.message_types import ContainerType, MessageType
from apps.monero.xmr.serialize_messages.base import ECKey from apps.monero.xmr.serialize_messages.base import ECKey
if TYPE_CHECKING:
from ..serialize.base_types import XmrType
class _KeyV(ContainerType): class _KeyV(ContainerType):
FIX_SIZE = const(0) FIX_SIZE = const(0)
ELEM_TYPE = ECKey ELEM_TYPE: XmrType[bytes] = ECKey
class Bulletproof(MessageType): class Bulletproof(MessageType):
__slots__ = ("A", "S", "T1", "T2", "taux", "mu", "L", "R", "a", "b", "t") __slots__ = ("A", "S", "T1", "T2", "taux", "mu", "L", "R", "a", "b", "t", "V")
@classmethod @classmethod
def f_specs(cls): def f_specs(cls) -> tuple:
return ( return (
("A", ECKey), ("A", ECKey),
("S", ECKey), ("S", ECKey),

@ -1,7 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from trezor.crypto import monero as tcry
Ge25519 = tcry.ge25519
Sc25519 = tcry.bignum256modm

@ -0,0 +1,8 @@
# Automatically generated by pb2py
# fmt: off
# isort:skip_file
MAINNET = 0
TESTNET = 1
STAGENET = 2
FAKECHAIN = 3

@ -456,6 +456,12 @@ if TYPE_CHECKING:
ARRAY = 7 ARRAY = 7
STRUCT = 8 STRUCT = 8
class MoneroNetworkType(IntEnum):
MAINNET = 0
TESTNET = 1
STAGENET = 2
FAKECHAIN = 3
class NEMMosaicLevy(IntEnum): class NEMMosaicLevy(IntEnum):
MosaicLevy_Absolute = 1 MosaicLevy_Absolute = 1
MosaicLevy_Percentile = 2 MosaicLevy_Percentile = 2

@ -39,6 +39,7 @@ if TYPE_CHECKING:
from trezor.enums import FailureType # noqa: F401 from trezor.enums import FailureType # noqa: F401
from trezor.enums import InputScriptType # noqa: F401 from trezor.enums import InputScriptType # noqa: F401
from trezor.enums import MessageType # noqa: F401 from trezor.enums import MessageType # noqa: F401
from trezor.enums import MoneroNetworkType # noqa: F401
from trezor.enums import NEMImportanceTransferMode # noqa: F401 from trezor.enums import NEMImportanceTransferMode # noqa: F401
from trezor.enums import NEMModificationType # noqa: F401 from trezor.enums import NEMModificationType # noqa: F401
from trezor.enums import NEMMosaicLevy # noqa: F401 from trezor.enums import NEMMosaicLevy # noqa: F401
@ -3691,7 +3692,7 @@ if TYPE_CHECKING:
class MoneroGetAddress(protobuf.MessageType): class MoneroGetAddress(protobuf.MessageType):
address_n: "list[int]" address_n: "list[int]"
show_display: "bool | None" show_display: "bool | None"
network_type: "int | None" network_type: "MoneroNetworkType"
account: "int | None" account: "int | None"
minor: "int | None" minor: "int | None"
payment_id: "bytes | None" payment_id: "bytes | None"
@ -3701,7 +3702,7 @@ if TYPE_CHECKING:
*, *,
address_n: "list[int] | None" = None, address_n: "list[int] | None" = None,
show_display: "bool | None" = None, show_display: "bool | None" = None,
network_type: "int | None" = None, network_type: "MoneroNetworkType | None" = None,
account: "int | None" = None, account: "int | None" = None,
minor: "int | None" = None, minor: "int | None" = None,
payment_id: "bytes | None" = None, payment_id: "bytes | None" = None,
@ -3728,13 +3729,13 @@ if TYPE_CHECKING:
class MoneroGetWatchKey(protobuf.MessageType): class MoneroGetWatchKey(protobuf.MessageType):
address_n: "list[int]" address_n: "list[int]"
network_type: "int | None" network_type: "MoneroNetworkType"
def __init__( def __init__(
self, self,
*, *,
address_n: "list[int] | None" = None, address_n: "list[int] | None" = None,
network_type: "int | None" = None, network_type: "MoneroNetworkType | None" = None,
) -> None: ) -> None:
pass pass
@ -3761,7 +3762,7 @@ if TYPE_CHECKING:
class MoneroTransactionInitRequest(protobuf.MessageType): class MoneroTransactionInitRequest(protobuf.MessageType):
version: "int | None" version: "int | None"
address_n: "list[int]" address_n: "list[int]"
network_type: "int | None" network_type: "MoneroNetworkType"
tsx_data: "MoneroTransactionData | None" tsx_data: "MoneroTransactionData | None"
def __init__( def __init__(
@ -3769,7 +3770,7 @@ if TYPE_CHECKING:
*, *,
address_n: "list[int] | None" = None, address_n: "list[int] | None" = None,
version: "int | None" = None, version: "int | None" = None,
network_type: "int | None" = None, network_type: "MoneroNetworkType | None" = None,
tsx_data: "MoneroTransactionData | None" = None, tsx_data: "MoneroTransactionData | None" = None,
) -> None: ) -> None:
pass pass
@ -4051,20 +4052,20 @@ if TYPE_CHECKING:
return isinstance(msg, cls) return isinstance(msg, cls)
class MoneroKeyImageExportInitRequest(protobuf.MessageType): class MoneroKeyImageExportInitRequest(protobuf.MessageType):
num: "int | None" num: "int"
hash: "bytes | None" hash: "bytes"
address_n: "list[int]" address_n: "list[int]"
network_type: "int | None" network_type: "MoneroNetworkType"
subs: "list[MoneroSubAddressIndicesList]" subs: "list[MoneroSubAddressIndicesList]"
def __init__( def __init__(
self, self,
*, *,
num: "int",
hash: "bytes",
address_n: "list[int] | None" = None, address_n: "list[int] | None" = None,
subs: "list[MoneroSubAddressIndicesList] | None" = None, subs: "list[MoneroSubAddressIndicesList] | None" = None,
num: "int | None" = None, network_type: "MoneroNetworkType | None" = None,
hash: "bytes | None" = None,
network_type: "int | None" = None,
) -> None: ) -> None:
pass pass
@ -4128,23 +4129,23 @@ if TYPE_CHECKING:
class MoneroGetTxKeyRequest(protobuf.MessageType): class MoneroGetTxKeyRequest(protobuf.MessageType):
address_n: "list[int]" address_n: "list[int]"
network_type: "int | None" network_type: "MoneroNetworkType"
salt1: "bytes | None" salt1: "bytes"
salt2: "bytes | None" salt2: "bytes"
tx_enc_keys: "bytes | None" tx_enc_keys: "bytes"
tx_prefix_hash: "bytes | None" tx_prefix_hash: "bytes"
reason: "int | None" reason: "int | None"
view_public_key: "bytes | None" view_public_key: "bytes | None"
def __init__( def __init__(
self, self,
*, *,
salt1: "bytes",
salt2: "bytes",
tx_enc_keys: "bytes",
tx_prefix_hash: "bytes",
address_n: "list[int] | None" = None, address_n: "list[int] | None" = None,
network_type: "int | None" = None, network_type: "MoneroNetworkType | None" = None,
salt1: "bytes | None" = None,
salt2: "bytes | None" = None,
tx_enc_keys: "bytes | None" = None,
tx_prefix_hash: "bytes | None" = None,
reason: "int | None" = None, reason: "int | None" = None,
view_public_key: "bytes | None" = None, view_public_key: "bytes | None" = None,
) -> None: ) -> None:
@ -4174,13 +4175,13 @@ if TYPE_CHECKING:
class MoneroLiveRefreshStartRequest(protobuf.MessageType): class MoneroLiveRefreshStartRequest(protobuf.MessageType):
address_n: "list[int]" address_n: "list[int]"
network_type: "int | None" network_type: "MoneroNetworkType"
def __init__( def __init__(
self, self,
*, *,
address_n: "list[int] | None" = None, address_n: "list[int] | None" = None,
network_type: "int | None" = None, network_type: "MoneroNetworkType | None" = None,
) -> None: ) -> None:
pass pass
@ -4195,20 +4196,20 @@ if TYPE_CHECKING:
return isinstance(msg, cls) return isinstance(msg, cls)
class MoneroLiveRefreshStepRequest(protobuf.MessageType): class MoneroLiveRefreshStepRequest(protobuf.MessageType):
out_key: "bytes | None" out_key: "bytes"
recv_deriv: "bytes | None" recv_deriv: "bytes"
real_out_idx: "int | None" real_out_idx: "int"
sub_addr_major: "int | None" sub_addr_major: "int"
sub_addr_minor: "int | None" sub_addr_minor: "int"
def __init__( def __init__(
self, self,
*, *,
out_key: "bytes | None" = None, out_key: "bytes",
recv_deriv: "bytes | None" = None, recv_deriv: "bytes",
real_out_idx: "int | None" = None, real_out_idx: "int",
sub_addr_major: "int | None" = None, sub_addr_major: "int",
sub_addr_minor: "int | None" = None, sub_addr_minor: "int",
) -> None: ) -> None:
pass pass
@ -4329,14 +4330,14 @@ if TYPE_CHECKING:
return isinstance(msg, cls) return isinstance(msg, cls)
class MoneroRctKeyPublic(protobuf.MessageType): class MoneroRctKeyPublic(protobuf.MessageType):
dest: "bytes | None" dest: "bytes"
commitment: "bytes | None" commitment: "bytes"
def __init__( def __init__(
self, self,
*, *,
dest: "bytes | None" = None, dest: "bytes",
commitment: "bytes | None" = None, commitment: "bytes",
) -> None: ) -> None:
pass pass
@ -4421,14 +4422,14 @@ if TYPE_CHECKING:
return isinstance(msg, cls) return isinstance(msg, cls)
class MoneroSubAddressIndicesList(protobuf.MessageType): class MoneroSubAddressIndicesList(protobuf.MessageType):
account: "int | None" account: "int"
minor_indices: "list[int]" minor_indices: "list[int]"
def __init__( def __init__(
self, self,
*, *,
account: "int",
minor_indices: "list[int] | None" = None, minor_indices: "list[int] | None" = None,
account: "int | None" = None,
) -> None: ) -> None:
pass pass
@ -4437,20 +4438,20 @@ if TYPE_CHECKING:
return isinstance(msg, cls) return isinstance(msg, cls)
class MoneroTransferDetails(protobuf.MessageType): class MoneroTransferDetails(protobuf.MessageType):
out_key: "bytes | None" out_key: "bytes"
tx_pub_key: "bytes | None" tx_pub_key: "bytes"
additional_tx_pub_keys: "list[bytes]" additional_tx_pub_keys: "list[bytes]"
internal_output_index: "int | None" internal_output_index: "int"
sub_addr_major: "int | None" sub_addr_major: "int | None"
sub_addr_minor: "int | None" sub_addr_minor: "int | None"
def __init__( def __init__(
self, self,
*, *,
out_key: "bytes",
tx_pub_key: "bytes",
internal_output_index: "int",
additional_tx_pub_keys: "list[bytes] | None" = None, additional_tx_pub_keys: "list[bytes] | None" = None,
out_key: "bytes | None" = None,
tx_pub_key: "bytes | None" = None,
internal_output_index: "int | None" = None,
sub_addr_major: "int | None" = None, sub_addr_major: "int | None" = None,
sub_addr_minor: "int | None" = None, sub_addr_minor: "int | None" = None,
) -> None: ) -> None:

@ -12,8 +12,8 @@ class TestMoneroBulletproof(unittest.TestCase):
pass pass
def mask_consistency_check(self, bpi): def mask_consistency_check(self, bpi):
sv = [crypto.sc_init(123)] sv = [crypto.Scalar(123)]
gamma = [crypto.sc_init(432)] gamma = [crypto.Scalar(432)]
M, logM, aL, aR, V, gamma = bpi.prove_setup(sv, gamma) M, logM, aL, aR, V, gamma = bpi.prove_setup(sv, gamma)
x = bp._ensure_dst_key() x = bp._ensure_dst_key()
@ -293,15 +293,15 @@ class TestMoneroBulletproof(unittest.TestCase):
def test_prove(self): def test_prove(self):
bpi = bp.BulletProofBuilder() bpi = bp.BulletProofBuilder()
val = crypto.sc_init(123) val = crypto.Scalar(123)
mask = crypto.sc_init(432) mask = crypto.Scalar(432)
bp_res = bpi.prove(val, mask) bp_res = bpi.prove(val, mask)
bpi.verify(bp_res) bpi.verify(bp_res)
def test_prove_2(self): def test_prove_2(self):
bpi = bp.BulletProofBuilder() bpi = bp.BulletProofBuilder()
val = crypto.sc_init((1 << 30) - 1 + 16) val = crypto.Scalar((1 << 30) - 1 + 16)
mask = crypto.random_scalar() mask = crypto.random_scalar()
bp_res = bpi.prove(val, mask) bp_res = bpi.prove(val, mask)
@ -322,7 +322,7 @@ class TestMoneroBulletproof(unittest.TestCase):
def test_prove_random_masks(self): def test_prove_random_masks(self):
bpi = bp.BulletProofBuilder() bpi = bp.BulletProofBuilder()
bpi.use_det_masks = False # trully randomly generated mask vectors bpi.use_det_masks = False # trully randomly generated mask vectors
val = crypto.sc_init((1 << 30) - 1 + 16) val = crypto.Scalar((1 << 30) - 1 + 16)
mask = crypto.random_scalar() mask = crypto.random_scalar()
bp_res = bpi.prove(val, mask) bp_res = bpi.prove(val, mask)
@ -331,8 +331,8 @@ class TestMoneroBulletproof(unittest.TestCase):
def ctest_multiexp(self): def ctest_multiexp(self):
scalars = [0, 1, 2, 3, 4, 99] scalars = [0, 1, 2, 3, 4, 99]
point_base = [0, 2, 4, 7, 12, 18] point_base = [0, 2, 4, 7, 12, 18]
scalar_sc = [crypto.sc_init(x) for x in scalars] scalar_sc = [crypto.Scalar(x) for x in scalars]
points = [crypto.scalarmult_base(crypto.sc_init(x)) for x in point_base] points = [crypto.scalarmult_base_into(None, crypto.Scalar(x)) for x in point_base]
muex = bp.MultiExp(scalars=[crypto.encodeint(x) for x in scalar_sc], muex = bp.MultiExp(scalars=[crypto.encodeint(x) for x in scalar_sc],
point_fnc=lambda i, d: crypto.encodepoint(points[i])) point_fnc=lambda i, d: crypto.encodepoint(points[i]))
@ -340,24 +340,24 @@ class TestMoneroBulletproof(unittest.TestCase):
self.assertEqual(len(muex), len(scalars)) self.assertEqual(len(muex), len(scalars))
res = bp.multiexp(None, muex) res = bp.multiexp(None, muex)
res2 = bp.vector_exponent_custom( res2 = bp.vector_exponent_custom(
A=bp.KeyVEval(3, lambda i, d: crypto.encodepoint_into(crypto.scalarmult_base(crypto.sc_init(point_base[i])), d)), A=bp.KeyVEval(3, lambda i, d: crypto.encodepoint_into(crypto.scalarmult_base_into(None, crypto.Scalar(point_base[i])), d)),
B=bp.KeyVEval(3, lambda i, d: crypto.encodepoint_into(crypto.scalarmult_base(crypto.sc_init(point_base[3+i])), d)), B=bp.KeyVEval(3, lambda i, d: crypto.encodepoint_into(crypto.scalarmult_base_into(None, crypto.Scalar(point_base[3+i])), d)),
a=bp.KeyVEval(3, lambda i, d: crypto.encodeint_into(crypto.sc_init(scalars[i]), d),), a=bp.KeyVEval(3, lambda i, d: crypto.encodeint_into(crypto.Scalar(scalars[i]), d),),
b=bp.KeyVEval(3, lambda i, d: crypto.encodeint_into(crypto.sc_init(scalars[i+3]), d)), b=bp.KeyVEval(3, lambda i, d: crypto.encodeint_into(crypto.Scalar(scalars[i+3]), d)),
) )
self.assertEqual(res, res2) self.assertEqual(res, res2)
def test_prove_batch(self): def test_prove_batch(self):
bpi = bp.BulletProofBuilder() bpi = bp.BulletProofBuilder()
sv = [crypto.sc_init(123), crypto.sc_init(768)] sv = [crypto.Scalar(123), crypto.Scalar(768)]
gamma = [crypto.sc_init(456), crypto.sc_init(901)] gamma = [crypto.Scalar(456), crypto.Scalar(901)]
proof = bpi.prove_batch(sv, gamma) proof = bpi.prove_batch(sv, gamma)
bpi.verify_batch([proof]) bpi.verify_batch([proof])
def test_prove_batch16(self): def test_prove_batch16(self):
bpi = bp.BulletProofBuilder() bpi = bp.BulletProofBuilder()
sv = [crypto.sc_init(137*i) for i in range(16)] sv = [crypto.Scalar(137*i) for i in range(16)]
gamma = [crypto.sc_init(991*i) for i in range(16)] gamma = [crypto.Scalar(991*i) 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])

@ -1,11 +1,14 @@
from common import * from common import *
if not utils.BITCOIN_ONLY: if not utils.BITCOIN_ONLY:
from apps.monero.xmr import crypto, mlsag from apps.monero.xmr import crypto, crypto_helpers, clsag
from apps.monero.xmr.serialize_messages.tx_ct_key import CtKey from apps.monero.xmr.serialize_messages.tx_ct_key import CtKey
from trezor.crypto import monero as tcry
from trezor.crypto import random from trezor.crypto import random
import ubinascii import ubinascii
point_mul8_into = tcry.ge25519_mul8
class TmpKey: class TmpKey:
def __init__(self, d, c): def __init__(self, d, c):
@ -17,18 +20,18 @@ class TmpKey:
class TestMoneroClsag(unittest.TestCase): class TestMoneroClsag(unittest.TestCase):
def verify_clsag(self, msg, ss, sc1, sI, sD, pubs, C_offset): def verify_clsag(self, msg, ss, sc1, sI, sD, pubs, C_offset):
n = len(pubs) n = len(pubs)
c = crypto.new_scalar() c = crypto.Scalar()
D_8 = crypto.new_point() D_8 = crypto.Point()
tmp_bf = bytearray(32) tmp_bf = bytearray(32)
C_offset_bf = crypto.encodepoint(C_offset) C_offset_bf = crypto_helpers.encodepoint(C_offset)
crypto.sc_copy(c, sc1) crypto.sc_copy(c, sc1)
crypto.point_mul8_into(D_8, sD) point_mul8_into(D_8, sD)
hsh_P = crypto.get_keccak() # domain, I, D, P, C, C_offset hsh_P = crypto_helpers.get_keccak() # domain, I, D, P, C, C_offset
hsh_C = crypto.get_keccak() # domain, I, D, P, C, C_offset hsh_C = crypto_helpers.get_keccak() # domain, I, D, P, C, C_offset
hsh_P.update(mlsag._HASH_KEY_CLSAG_AGG_0) hsh_P.update(clsag._HASH_KEY_CLSAG_AGG_0)
hsh_C.update(mlsag._HASH_KEY_CLSAG_AGG_1) hsh_C.update(clsag._HASH_KEY_CLSAG_AGG_1)
def hsh_PC(x): def hsh_PC(x):
hsh_P.update(x) hsh_P.update(x)
@ -43,11 +46,11 @@ class TestMoneroClsag(unittest.TestCase):
hsh_PC(crypto.encodepoint_into(tmp_bf, sI)) hsh_PC(crypto.encodepoint_into(tmp_bf, sI))
hsh_PC(crypto.encodepoint_into(tmp_bf, sD)) hsh_PC(crypto.encodepoint_into(tmp_bf, sD))
hsh_PC(C_offset_bf) hsh_PC(C_offset_bf)
mu_P = crypto.decodeint(hsh_P.digest()) mu_P = crypto_helpers.decodeint(hsh_P.digest())
mu_C = crypto.decodeint(hsh_C.digest()) mu_C = crypto_helpers.decodeint(hsh_C.digest())
c_to_hash = crypto.get_keccak() # domain, P, C, C_offset, message, L, R c_to_hash = crypto_helpers.get_keccak() # domain, P, C, C_offset, message, L, R
c_to_hash.update(mlsag._HASH_KEY_CLSAG_ROUND) c_to_hash.update(clsag._HASH_KEY_CLSAG_ROUND)
for i in range(len(pubs)): for i in range(len(pubs)):
c_to_hash.update(pubs[i].dest) c_to_hash.update(pubs[i].dest)
for i in range(len(pubs)): for i in range(len(pubs)):
@ -55,25 +58,25 @@ class TestMoneroClsag(unittest.TestCase):
c_to_hash.update(C_offset_bf) c_to_hash.update(C_offset_bf)
c_to_hash.update(msg) c_to_hash.update(msg)
c_p = crypto.new_scalar() c_p = crypto.Scalar()
c_c = crypto.new_scalar() c_c = crypto.Scalar()
L = crypto.new_point() L = crypto.Point()
R = crypto.new_point() R = crypto.Point()
tmp_pt = crypto.new_point() tmp_pt = crypto.Point()
i = 0 i = 0
while i < n: while i < n:
crypto.sc_mul_into(c_p, mu_P, c) crypto.sc_mul_into(c_p, mu_P, c)
crypto.sc_mul_into(c_c, mu_C, c) crypto.sc_mul_into(c_c, mu_C, c)
C_P = crypto.point_sub( C_P = crypto.point_sub_into(
crypto.decodepoint_into(tmp_pt, pubs[i].commitment), C_offset None, crypto.decodepoint_into(tmp_pt, pubs[i].commitment), C_offset
) )
crypto.add_keys2_into( crypto.add_keys2_into(
L, ss[i], c_p, crypto.decodepoint_into(tmp_pt, pubs[i].dest) L, ss[i], c_p, crypto.decodepoint_into(tmp_pt, pubs[i].dest)
) )
crypto.point_add_into(L, L, crypto.scalarmult_into(tmp_pt, C_P, c_c)) crypto.point_add_into(L, L, crypto.scalarmult_into(tmp_pt, C_P, c_c))
HP = crypto.hash_to_point(pubs[i].dest) HP = crypto.hash_to_point_into(None, pubs[i].dest)
crypto.add_keys3_into(R, ss[i], HP, c_p, sI) crypto.add_keys3_into(R, ss[i], HP, c_p, sI)
crypto.point_add_into(R, R, crypto.scalarmult_into(tmp_pt, D_8, c_c)) crypto.point_add_into(R, R, crypto.scalarmult_into(tmp_pt, D_8, c_c))
@ -82,8 +85,8 @@ class TestMoneroClsag(unittest.TestCase):
chasher.update(crypto.encodepoint_into(tmp_bf, R)) chasher.update(crypto.encodepoint_into(tmp_bf, R))
crypto.decodeint_into(c, chasher.digest()) crypto.decodeint_into(c, chasher.digest())
i += 1 i += 1
res = crypto.sc_sub(c, sc1) res = crypto.sc_sub_into(None, c, sc1)
if not crypto.sc_eq(res, crypto.sc_0()): if not crypto.sc_eq(res, crypto.Scalar(0)):
raise ValueError("Signature error") raise ValueError("Signature error")
def gen_clsag_test(self, ring_size=11, index=None): def gen_clsag_test(self, ring_size=11, index=None):
@ -93,60 +96,67 @@ class TestMoneroClsag(unittest.TestCase):
def gen_clsag_sig(self, ring_size=11, index=None): def gen_clsag_sig(self, ring_size=11, index=None):
msg = random.bytes(32) msg = random.bytes(32)
amnt = crypto.sc_init(random.uniform(0xFFFFFF) + 12) amnt = crypto.Scalar(random.uniform(0xFFFFFF) + 12)
priv = crypto.random_scalar() priv = crypto.random_scalar()
msk = crypto.random_scalar() msk = crypto.random_scalar()
alpha = crypto.random_scalar() alpha = crypto.random_scalar()
P = crypto.scalarmult_base(priv) P = crypto.scalarmult_base_into(None, priv)
C = crypto.add_keys2(msk, amnt, crypto.xmr_H()) C = crypto.add_keys2_into(None, msk, amnt, crypto.xmr_H())
Cp = crypto.add_keys2(alpha, amnt, crypto.xmr_H()) Cp = crypto.add_keys2_into(None, alpha, amnt, crypto.xmr_H())
ring = [] ring = []
for i in range(ring_size - 1): for i in range(ring_size - 1):
tk = TmpKey( tk = TmpKey(
crypto.encodepoint(crypto.scalarmult_base(crypto.random_scalar())), crypto_helpers.encodepoint(
crypto.encodepoint(crypto.scalarmult_base(crypto.random_scalar())), crypto.scalarmult_base_into(None, crypto.random_scalar())
),
crypto_helpers.encodepoint(
crypto.scalarmult_base_into(None, crypto.random_scalar())
),
) )
ring.append(tk) ring.append(tk)
index = index if index is not None else random.uniform(len(ring)) index = index if index is not None else random.uniform(len(ring))
ring.insert(index, TmpKey(crypto.encodepoint(P), crypto.encodepoint(C))) ring.insert(index, TmpKey(crypto_helpers.encodepoint(P), crypto_helpers.encodepoint(C)))
ring2 = list(ring) ring2 = list(ring)
mg_buffer = [] mg_buffer = []
self.assertTrue( self.assertTrue(
crypto.point_eq( crypto.point_eq(
crypto.scalarmult_base(priv), crypto.decodepoint(ring[index].dest) crypto.scalarmult_base_into(None, priv),
crypto_helpers.decodepoint(ring[index].dest),
) )
) )
self.assertTrue( self.assertTrue(
crypto.point_eq( crypto.point_eq(
crypto.scalarmult_base(crypto.sc_sub(msk, alpha)), crypto.scalarmult_base_into(None, crypto.sc_sub_into(None, msk, alpha)),
crypto.point_sub(crypto.decodepoint(ring[index].commitment), Cp), crypto.point_sub_into(
None, crypto_helpers.decodepoint(ring[index].commitment), Cp
),
) )
) )
mlsag.generate_clsag_simple( clsag.generate_clsag_simple(
msg, ring, CtKey(priv, msk), alpha, Cp, index, mg_buffer, msg, ring, CtKey(priv, msk), alpha, Cp, index, mg_buffer,
) )
sD = crypto.decodepoint(mg_buffer[-1]) sD = crypto_helpers.decodepoint(mg_buffer[-1])
sc1 = crypto.decodeint(mg_buffer[-2]) sc1 = crypto_helpers.decodeint(mg_buffer[-2])
scalars = [crypto.decodeint(x) for x in mg_buffer[1:-2]] scalars = [crypto_helpers.decodeint(x) for x in mg_buffer[1:-2]]
H = crypto.new_point() H = crypto.Point()
sI = crypto.new_point() sI = crypto.Point()
crypto.hash_to_point_into(H, crypto.encodepoint(P)) crypto.hash_to_point_into(H, crypto_helpers.encodepoint(P))
crypto.scalarmult_into(sI, H, priv) # I = p*H crypto.scalarmult_into(sI, H, priv) # I = p*H
return msg, scalars, sc1, sI, sD, ring2, Cp return msg, scalars, sc1, sI, sD, ring2, Cp
def verify_monero_generated(self, clsag): def verify_monero_generated(self, clsag):
msg = ubinascii.unhexlify(clsag["msg"]) msg = ubinascii.unhexlify(clsag["msg"])
sI = crypto.decodepoint(ubinascii.unhexlify(clsag["sI"])) sI = crypto_helpers.decodepoint(ubinascii.unhexlify(clsag["sI"]))
sD = crypto.decodepoint(ubinascii.unhexlify(clsag["sD"])) sD = crypto_helpers.decodepoint(ubinascii.unhexlify(clsag["sD"]))
sc1 = crypto.decodeint(ubinascii.unhexlify(clsag["sc1"])) sc1 = crypto_helpers.decodeint(ubinascii.unhexlify(clsag["sc1"]))
Cout = crypto.decodepoint(ubinascii.unhexlify(clsag["cout"])) Cout = crypto_helpers.decodepoint(ubinascii.unhexlify(clsag["cout"]))
scalars = [crypto.decodeint(ubinascii.unhexlify(x)) for x in clsag["ss"]] scalars = [crypto_helpers.decodeint(ubinascii.unhexlify(x)) for x in clsag["ss"]]
ring = [] ring = []
for e in clsag["ring"]: for e in clsag["ring"]:
ring.append(TmpKey(ubinascii.unhexlify(e[0]), ubinascii.unhexlify(e[1]))) ring.append(TmpKey(ubinascii.unhexlify(e[0]), ubinascii.unhexlify(e[1])))
@ -160,43 +170,65 @@ class TestMoneroClsag(unittest.TestCase):
"sI": "a1c7f4a316ddd16374fe495d402be60566047ae5a1352554e98ebff118705303", "sI": "a1c7f4a316ddd16374fe495d402be60566047ae5a1352554e98ebff118705303",
"sD": "cd80b5c7f3f597de6e20bcef669a4ba9eb3eb89ead12ab1c24c92acd609afcb2", "sD": "cd80b5c7f3f597de6e20bcef669a4ba9eb3eb89ead12ab1c24c92acd609afcb2",
"sc1": "cf4f48ed60771d4e8d02e9e0af37281ceeb66573bd528ac256a7e17794a75602", "sc1": "cf4f48ed60771d4e8d02e9e0af37281ceeb66573bd528ac256a7e17794a75602",
"ss": "ss": [
["aaeffa564b5b0ff1e4ed72c9b595cd0241ac64eeb41b902a35688e369922d704" "aaeffa564b5b0ff1e4ed72c9b595cd0241ac64eeb41b902a35688e369922d704",
, "1defc134a853252d734d19b29d8f2fabc85a8ae24ebcf8f050d4daf8a335e901" "1defc134a853252d734d19b29d8f2fabc85a8ae24ebcf8f050d4daf8a335e901",
, "cdf9ac576f0c7ceb7eb22c1a1254a801d0d2915e59870be8b1ab68cd1281120d" "cdf9ac576f0c7ceb7eb22c1a1254a801d0d2915e59870be8b1ab68cd1281120d",
, "d1973493d8224aaa9732878b9a88d448ea16185f94e5bafd82816277682fa108" "d1973493d8224aaa9732878b9a88d448ea16185f94e5bafd82816277682fa108",
, "a130e076845e512687575942bf3694bcb44eb19eb1181af9a1fc2254949b7c0f" "a130e076845e512687575942bf3694bcb44eb19eb1181af9a1fc2254949b7c0f",
, "26f5b6ea154d6bd4a969c742563d75f1bfcd5ded3af78669e45ba95e76c48605" "26f5b6ea154d6bd4a969c742563d75f1bfcd5ded3af78669e45ba95e76c48605",
, "5b695d3be46b826fd11e043028dee2aa25cf36910e86537fcd1cd3f5cb49650e" "5b695d3be46b826fd11e043028dee2aa25cf36910e86537fcd1cd3f5cb49650e",
, "37e811ebb4a2b9c35556b4af911a03a93468f599956c034092c3ece9e1169208" "37e811ebb4a2b9c35556b4af911a03a93468f599956c034092c3ece9e1169208",
, "a361ceec9aacd65da6d3e686fbcd0c1aef26096321be7f01653157ee6096a201" "a361ceec9aacd65da6d3e686fbcd0c1aef26096321be7f01653157ee6096a201",
, "f9b762ef1df69bb12ca76a97dce11f7840b8ec63c3dc2683f7ae71cb79c49103" "f9b762ef1df69bb12ca76a97dce11f7840b8ec63c3dc2683f7ae71cb79c49103",
, "ea010fa6a35f3bd3d7899a7a2a8df4d3ef9c9dfbbd56fe43ff5c7442821d3508" "ea010fa6a35f3bd3d7899a7a2a8df4d3ef9c9dfbbd56fe43ff5c7442821d3508",
] ],
, "ring": [ "ring": [
["241c0295b4c3a149e5ac7997963e125d0fc6cc8adad9349df3b01ff611936c87", [
"3a24a4c418ccb2ceb83672d01534a73ff1e9f548937d5ddd7f1971c9b398868c"], "241c0295b4c3a149e5ac7997963e125d0fc6cc8adad9349df3b01ff611936c87",
["ec432ccfbf730077cb2d8c59968e2796148a590eec7928ecf268d883ced0de5b", "3a24a4c418ccb2ceb83672d01534a73ff1e9f548937d5ddd7f1971c9b398868c",
"2973d6e9c27538fd0f7c003e014311e9403dcb6e7d86b66df65176a579943bda"], ],
["0cfeafc313a6a2e60110778d53d61fa1705e9049b8afba0f51c1127f6855c07f", [
"ffa4d4c77202907832294243a96886920017b67fbe5b3800bcc1457c4a4a1ff0"], "ec432ccfbf730077cb2d8c59968e2796148a590eec7928ecf268d883ced0de5b",
["bd4eca22dc010a214524901b88bdda27e427217ff784c47520ee76743caba036", "2973d6e9c27538fd0f7c003e014311e9403dcb6e7d86b66df65176a579943bda",
"e07135f8398459133c2969184e70610b9b995f73e44acf54b6eaed6227e68bbc"], ],
["73c8d57d0128c99fc2ab0be8cee5fe5c1288b98e51822a6681846035fcc53fea", [
"2987499fde3f4353013206d89fe2d7c6ad3cd9a66c9a36d17749e39112513572"], "0cfeafc313a6a2e60110778d53d61fa1705e9049b8afba0f51c1127f6855c07f",
["385c538901b79c6bd2ddea5191e808b1414c9dfdcaf424841d843dd788cb89ad", "ffa4d4c77202907832294243a96886920017b67fbe5b3800bcc1457c4a4a1ff0",
"ec5f987fe138c6cb1d47ff75d77852b7c0a94ba1f0b93d22c0463f75986605bd"], ],
["fed06cb761745a6f087d1af13f84670ecbf1523d72b46e8bd0698d1cdfb398bc", [
"5d81df981fb885f947b9404cb63cb06fe4e001be281f2bdfb3c638d54ec6e49e"], "bd4eca22dc010a214524901b88bdda27e427217ff784c47520ee76743caba036",
["667d1edfb83a17bd81fcf7831362b6c9038f26340ee1fe56d41f62cb0b32e989", "e07135f8398459133c2969184e70610b9b995f73e44acf54b6eaed6227e68bbc",
"e9ceba97867b43cd5420c94fa61cc5f11e440e261df74dfc8b1c07ec4b13aa3c"], ],
["e1e76da5bd52fc065f9af40efde5f733f9673974d14c6af8d200d8576ac3a90d", [
"97358d6ddad38b2707fb864bfcaaab935851af66d50bcbac569d159d740bdf71"], "73c8d57d0128c99fc2ab0be8cee5fe5c1288b98e51822a6681846035fcc53fea",
["4fd5d0db88283c63905d5095a76b11a75337e43f403f8469175ba9c49741552e", "2987499fde3f4353013206d89fe2d7c6ad3cd9a66c9a36d17749e39112513572",
"af0ab85872a6355d5c82c1f9a2a41488146e19b272887a1f7385cc26bef3f1d8"], ],
["37e1a4c49a22340fa5ac2c22c1b7a891e7191cdc53911700a317c0d8b92bbf4e", [
"5c89d29dad77de7d76ece8bb81c7c8cd15008f63c5a14ab1c984b3833e7bbce3"] "385c538901b79c6bd2ddea5191e808b1414c9dfdcaf424841d843dd788cb89ad",
] "ec5f987fe138c6cb1d47ff75d77852b7c0a94ba1f0b93d22c0463f75986605bd",
],
[
"fed06cb761745a6f087d1af13f84670ecbf1523d72b46e8bd0698d1cdfb398bc",
"5d81df981fb885f947b9404cb63cb06fe4e001be281f2bdfb3c638d54ec6e49e",
],
[
"667d1edfb83a17bd81fcf7831362b6c9038f26340ee1fe56d41f62cb0b32e989",
"e9ceba97867b43cd5420c94fa61cc5f11e440e261df74dfc8b1c07ec4b13aa3c",
],
[
"e1e76da5bd52fc065f9af40efde5f733f9673974d14c6af8d200d8576ac3a90d",
"97358d6ddad38b2707fb864bfcaaab935851af66d50bcbac569d159d740bdf71",
],
[
"4fd5d0db88283c63905d5095a76b11a75337e43f403f8469175ba9c49741552e",
"af0ab85872a6355d5c82c1f9a2a41488146e19b272887a1f7385cc26bef3f1d8",
],
[
"37e1a4c49a22340fa5ac2c22c1b7a891e7191cdc53911700a317c0d8b92bbf4e",
"5c89d29dad77de7d76ece8bb81c7c8cd15008f63c5a14ab1c984b3833e7bbce3",
],
],
} }
self.verify_monero_generated(clsag) self.verify_monero_generated(clsag)
@ -207,43 +239,65 @@ class TestMoneroClsag(unittest.TestCase):
"sI": "917fdd3086c056503ffdb1840f03c78d48bfe6d9d60b4efb194bd9798d03acaa", "sI": "917fdd3086c056503ffdb1840f03c78d48bfe6d9d60b4efb194bd9798d03acaa",
"sD": "769d0ca9b272ac02c5efad7df6b5c00f2995c99ca80f4597136decba9a0dd36f", "sD": "769d0ca9b272ac02c5efad7df6b5c00f2995c99ca80f4597136decba9a0dd36f",
"sc1": "fe5c7eb39a32d2aea12e6d127d847b72ea810bfbf3d5bbe23c40e7abdd12900e", "sc1": "fe5c7eb39a32d2aea12e6d127d847b72ea810bfbf3d5bbe23c40e7abdd12900e",
"ss": "ss": [
["da2940c66cc2405032d959325c8804e216f76b36e71b2ae6b76417ed9c10a80a" "da2940c66cc2405032d959325c8804e216f76b36e71b2ae6b76417ed9c10a80a",
, "ca763505c2e5ebacf72098f8cba89ea6826aa448501f03d439c7a838a88bba0e" "ca763505c2e5ebacf72098f8cba89ea6826aa448501f03d439c7a838a88bba0e",
, "b2eadee4c121e85b0c2a09d56c665ba19ee8ebc451f1e9e96cf72c874f945104" "b2eadee4c121e85b0c2a09d56c665ba19ee8ebc451f1e9e96cf72c874f945104",
, "5a79523fdc0df9a54ab3937c878bd5a02e62bff77efc338728deb060ecda4509" "5a79523fdc0df9a54ab3937c878bd5a02e62bff77efc338728deb060ecda4509",
, "dfadddc51866cde5206269270f44ca2f6350ca0b1328a968773fcacf57031502" "dfadddc51866cde5206269270f44ca2f6350ca0b1328a968773fcacf57031502",
, "a964f3549a10fc8bdb2f8217df0e9b08e90477be19a665b94b73ce417622450b" "a964f3549a10fc8bdb2f8217df0e9b08e90477be19a665b94b73ce417622450b",
, "48e805427109268b04bf378c869501dbebb79c0cbe664bf7eb0ca222376d1c0f" "48e805427109268b04bf378c869501dbebb79c0cbe664bf7eb0ca222376d1c0f",
, "33f36d9a699e92a66d4b9fdf6c1123ae99701b117fbe8f0af9faec51e45eb409" "33f36d9a699e92a66d4b9fdf6c1123ae99701b117fbe8f0af9faec51e45eb409",
, "25ef746a03aaf59701d1d47ea3b9e9f092662cebc9d44902ce18e81cc5035f01" "25ef746a03aaf59701d1d47ea3b9e9f092662cebc9d44902ce18e81cc5035f01",
, "2ba3022d4f9b57da7429499715592073f1608cf270318840a5fd3890bbf5950a" "2ba3022d4f9b57da7429499715592073f1608cf270318840a5fd3890bbf5950a",
, "8149ec0d965c9881d6a4adedca7d3c9090359dbfae56dbab526be102722aab09" "8149ec0d965c9881d6a4adedca7d3c9090359dbfae56dbab526be102722aab09",
] ],
, "ring": [ "ring": [
["081b048be784e1ff6f3b7ebe602690c27723b5d9952405bcdcbed31d16125067", [
"6090eccb73d2e1fc7bc7644a4fad04e5fe93d953a1258307c44d5b23cd636bf9"], "081b048be784e1ff6f3b7ebe602690c27723b5d9952405bcdcbed31d16125067",
["e2f0f100f1634d7c7dd5a09bc6dd7ee53506d73536aa743e8ea049528e4cb2aa", "6090eccb73d2e1fc7bc7644a4fad04e5fe93d953a1258307c44d5b23cd636bf9",
"632438f9aeda72eb9c6c434391cf9fa2f71788bea598a5d5729a5d502865932a"], ],
["6744197cfde37ad1901d518f112c0f4d820c23122a016949e300eec2ab88916c", [
"1b251d5b32e22de29a4f99a0ed1de32754636175075e21b25d7283036eb85541"], "e2f0f100f1634d7c7dd5a09bc6dd7ee53506d73536aa743e8ea049528e4cb2aa",
["0e86bb7ee0b4728f2fedde7ac5019b54de7b2bb19b44d1864e6346dac6c171ab", "632438f9aeda72eb9c6c434391cf9fa2f71788bea598a5d5729a5d502865932a",
"5a3c85e93890f802d4148140733dcdcd676353fce1bd774ce28034fc2ec00253"], ],
["1847ce49d9552651395b2fa80637c131a31036f0bfc5abb63526701cd1a32320", [
"a9cb55bc24e6e1fb894c511f2edd4b7bda4c75a608657d952e85bab83ec98a52"], "6744197cfde37ad1901d518f112c0f4d820c23122a016949e300eec2ab88916c",
["5c5d0b678f5045b0304e3c48027bd7e9ccaee1dac4449ed1f34b204868ca5651", "1b251d5b32e22de29a4f99a0ed1de32754636175075e21b25d7283036eb85541",
"badf83ccba38f2194f924a4f7fb7c2fd966b1e16c1fddeb3658033aa009febe0"], ],
["81961aa4c241a91d498d8f3057b31373d9fc72b6e7d7f98bf497e3dfe705eeaa", [
"a0e632fbb801d6bce99ef97d7bb6acd945aff5cd7fab56c0e6fec6900a3babd7"], "0e86bb7ee0b4728f2fedde7ac5019b54de7b2bb19b44d1864e6346dac6c171ab",
["cbd89f10ddf152bd9c756d145ef4cda1d56a31f1e1936759bee04b7a8a815c76", "5a3c85e93890f802d4148140733dcdcd676353fce1bd774ce28034fc2ec00253",
"8b835b8180f36e79ba79528e0d3401f439cc1c7f99e4bcfb3cb4aa2b60b1afc1"], ],
["a7bc55e955a825730f5dcdc3f8126717d7647cbca8a6b90e08b77269aeed3533", [
"8da31e80698c9b5181b2e8d9773136083a34e3e72c92134d8201d9c368d89284"], "1847ce49d9552651395b2fa80637c131a31036f0bfc5abb63526701cd1a32320",
["a7902cec90d3f2de25c8ddc87075159fd00f219a51a1e7dcac17c2b8a91887e9", "a9cb55bc24e6e1fb894c511f2edd4b7bda4c75a608657d952e85bab83ec98a52",
"2b1e848b6649abefbd6b399504a169252358e7ff6bde8fa7a773b9cf0a167069"], ],
["9fc3d5fb7de8cfc59982f7b20f3f5c145ad191088e7f59c10908dc5d55863bee", [
"b8de2bc9bb46d475007230a92af14afb6f9dd2804b5c31355a282b40ccdadc92"] "5c5d0b678f5045b0304e3c48027bd7e9ccaee1dac4449ed1f34b204868ca5651",
] "badf83ccba38f2194f924a4f7fb7c2fd966b1e16c1fddeb3658033aa009febe0",
],
[
"81961aa4c241a91d498d8f3057b31373d9fc72b6e7d7f98bf497e3dfe705eeaa",
"a0e632fbb801d6bce99ef97d7bb6acd945aff5cd7fab56c0e6fec6900a3babd7",
],
[
"cbd89f10ddf152bd9c756d145ef4cda1d56a31f1e1936759bee04b7a8a815c76",
"8b835b8180f36e79ba79528e0d3401f439cc1c7f99e4bcfb3cb4aa2b60b1afc1",
],
[
"a7bc55e955a825730f5dcdc3f8126717d7647cbca8a6b90e08b77269aeed3533",
"8da31e80698c9b5181b2e8d9773136083a34e3e72c92134d8201d9c368d89284",
],
[
"a7902cec90d3f2de25c8ddc87075159fd00f219a51a1e7dcac17c2b8a91887e9",
"2b1e848b6649abefbd6b399504a169252358e7ff6bde8fa7a773b9cf0a167069",
],
[
"9fc3d5fb7de8cfc59982f7b20f3f5c145ad191088e7f59c10908dc5d55863bee",
"b8de2bc9bb46d475007230a92af14afb6f9dd2804b5c31355a282b40ccdadc92",
],
],
} }
self.verify_monero_generated(clsag) self.verify_monero_generated(clsag)
@ -261,22 +315,22 @@ class TestMoneroClsag(unittest.TestCase):
res = self.gen_clsag_sig(ring_size=11, index=5) res = self.gen_clsag_sig(ring_size=11, index=5)
msg, scalars, sc1, sI, sD, ring2, Cp = res msg, scalars, sc1, sI, sD, ring2, Cp = res
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
sI = crypto.point_mul8(sI) sI = point_mul8_into(None, sI)
self.verify_clsag(msg, scalars, sc1, sI, sD, ring2, Cp) self.verify_clsag(msg, scalars, sc1, sI, sD, ring2, Cp)
def test_clsag_invalid_sD(self): def test_clsag_invalid_sD(self):
res = self.gen_clsag_sig(ring_size=11, index=5) res = self.gen_clsag_sig(ring_size=11, index=5)
msg, scalars, sc1, sI, sD, ring2, Cp = res msg, scalars, sc1, sI, sD, ring2, Cp = res
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
sD = crypto.scalarmult_base(crypto.random_scalar()) sD = crypto.scalarmult_base_into(None, crypto.random_scalar())
self.verify_clsag(msg, scalars, sc1, sI, sD, ring2, Cp) self.verify_clsag(msg, scalars, sc1, sI, sD, ring2, Cp)
def test_clsag_invalid_P(self): def test_clsag_invalid_P(self):
res = self.gen_clsag_sig(ring_size=11, index=5) res = self.gen_clsag_sig(ring_size=11, index=5)
msg, scalars, sc1, sI, sD, ring2, Cp = res msg, scalars, sc1, sI, sD, ring2, Cp = res
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
ring2[5].dest = crypto.encodepoint( ring2[5].dest = crypto_helpers.encodepoint(
crypto.point_mul8(crypto.decodepoint(ring2[5].dest)) point_mul8_into(None, crypto_helpers.decodepoint(ring2[5].dest))
) )
self.verify_clsag(msg, scalars, sc1, sI, sD, ring2, Cp) self.verify_clsag(msg, scalars, sc1, sI, sD, ring2, Cp)
@ -284,8 +338,8 @@ class TestMoneroClsag(unittest.TestCase):
res = self.gen_clsag_sig(ring_size=11, index=5) res = self.gen_clsag_sig(ring_size=11, index=5)
msg, scalars, sc1, sI, sD, ring2, Cp = res msg, scalars, sc1, sI, sD, ring2, Cp = res
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
ring2[5].commitment = crypto.encodepoint( ring2[5].commitment = crypto_helpers.encodepoint(
crypto.point_mul8(crypto.decodepoint(ring2[5].dest)) point_mul8_into(None, crypto_helpers.decodepoint(ring2[5].dest))
) )
self.verify_clsag(msg, scalars, sc1, sI, sD, ring2, Cp) self.verify_clsag(msg, scalars, sc1, sI, sD, ring2, Cp)
@ -293,7 +347,9 @@ class TestMoneroClsag(unittest.TestCase):
res = self.gen_clsag_sig(ring_size=11, index=5) res = self.gen_clsag_sig(ring_size=11, index=5)
msg, scalars, sc1, sI, sD, ring2, Cp = res msg, scalars, sc1, sI, sD, ring2, Cp = res
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
Cp = crypto.point_add(Cp, crypto.scalarmult_base(crypto.sc_init(1))) Cp = crypto.point_add_into(
None, Cp, crypto.scalarmult_base_into(None, crypto.Scalar(1))
)
self.verify_clsag(msg, scalars, sc1, sI, sD, ring2, Cp) self.verify_clsag(msg, scalars, sc1, sI, sD, ring2, Cp)
def test_clsag_invalid_index(self): def test_clsag_invalid_index(self):

@ -1,29 +1,29 @@
from common import * from common import *
if not utils.BITCOIN_ONLY: if not utils.BITCOIN_ONLY:
from apps.monero.xmr import crypto, monero from trezor.enums import MoneroNetworkType
from apps.monero.xmr import crypto, crypto_helpers, monero
from apps.monero.xmr.addresses import encode_addr from apps.monero.xmr.addresses import encode_addr
from apps.monero.xmr.credentials import AccountCreds from apps.monero.xmr.credentials import AccountCreds
from apps.monero.xmr.networks import NetworkTypes, net_version from apps.monero.xmr.networks import net_version
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestMoneroCrypto(unittest.TestCase): class TestMoneroCrypto(unittest.TestCase):
def test_encoding(self): def test_encoding(self):
point = unhexlify( point = unhexlify(
b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0" b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0"
) )
self.assertEqual(point, crypto.encodepoint(crypto.decodepoint(point))) self.assertEqual(point, crypto_helpers.encodepoint(crypto_helpers.decodepoint(point)))
self.assertTrue( self.assertTrue(
crypto.point_eq( crypto.point_eq(
crypto.decodepoint(point), crypto_helpers.decodepoint(point),
crypto.decodepoint(crypto.encodepoint(crypto.decodepoint(point))), crypto_helpers.decodepoint(crypto_helpers.encodepoint(crypto_helpers.decodepoint(point))),
) )
) )
def test_scalarmult_base(self): def test_scalarmult_base(self):
scalar = crypto.decodeint( scalar = crypto_helpers.decodeint(
unhexlify( unhexlify(
b"a0eea49140a3b036da30eacf64bd9d56ce3ef68ba82ef13571ec511edbcf8303" b"a0eea49140a3b036da30eacf64bd9d56ce3ef68ba82ef13571ec511edbcf8303"
) )
@ -32,11 +32,11 @@ class TestMoneroCrypto(unittest.TestCase):
b"16bb4a3c44e2ced511fc0d4cd86b13b3af21efc99fb0356199fac489f2544c09" b"16bb4a3c44e2ced511fc0d4cd86b13b3af21efc99fb0356199fac489f2544c09"
) )
res = crypto.scalarmult_base(scalar) res = crypto.scalarmult_base_into(None, scalar)
self.assertEqual(exp, crypto.encodepoint(res)) self.assertEqual(exp, crypto_helpers.encodepoint(res))
self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res)) self.assertTrue(crypto.point_eq(crypto_helpers.decodepoint(exp), res))
scalar = crypto.decodeint( scalar = crypto_helpers.decodeint(
unhexlify( unhexlify(
b"fd290dce39f781aebbdbd24584ed6d48bd300de19d9c3decfda0a6e2c6751d0f" b"fd290dce39f781aebbdbd24584ed6d48bd300de19d9c3decfda0a6e2c6751d0f"
) )
@ -45,9 +45,9 @@ class TestMoneroCrypto(unittest.TestCase):
b"123daf90fc26f13c6529e6b49bfed498995ac383ef19c0db6771143f24ba8dd5" b"123daf90fc26f13c6529e6b49bfed498995ac383ef19c0db6771143f24ba8dd5"
) )
res = crypto.scalarmult_base(scalar) res = crypto.scalarmult_base_into(None, scalar)
self.assertEqual(exp, crypto.encodepoint(res)) self.assertEqual(exp, crypto_helpers.encodepoint(res))
self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res)) self.assertTrue(crypto.point_eq(crypto_helpers.decodepoint(exp), res))
def test_scalarmult(self): def test_scalarmult(self):
priv = unhexlify( priv = unhexlify(
@ -60,15 +60,15 @@ class TestMoneroCrypto(unittest.TestCase):
b"adcd1f5881f46f254900a03c654e71950a88a0236fa0a3a946c9b8daed6ef43d" b"adcd1f5881f46f254900a03c654e71950a88a0236fa0a3a946c9b8daed6ef43d"
) )
res = crypto.scalarmult(crypto.decodepoint(pub), crypto.decodeint(priv)) res = crypto.scalarmult_into(None, crypto_helpers.decodepoint(pub), crypto_helpers.decodeint(priv))
self.assertEqual(exp, crypto.encodepoint(res)) self.assertEqual(exp, crypto_helpers.encodepoint(res))
self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res)) self.assertTrue(crypto.point_eq(crypto_helpers.decodepoint(exp), res))
def test_cn_fast_hash(self): def test_cn_fast_hash(self):
inp = unhexlify( inp = unhexlify(
b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405" b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405"
) )
res = crypto.cn_fast_hash(inp) res = crypto.fast_hash_into(None, inp)
self.assertEqual( self.assertEqual(
res, res,
unhexlify( unhexlify(
@ -81,8 +81,8 @@ class TestMoneroCrypto(unittest.TestCase):
b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405" b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405"
) )
res = crypto.hash_to_scalar(inp) res = crypto.hash_to_scalar_into(None, inp)
exp = crypto.decodeint( exp = crypto_helpers.decodeint(
unhexlify( unhexlify(
b"9907925b254e12162609fc0dfd0fef2aa4d605b0d10e6507cac253dd31a3ec06" b"9907925b254e12162609fc0dfd0fef2aa4d605b0d10e6507cac253dd31a3ec06"
) )
@ -93,8 +93,8 @@ class TestMoneroCrypto(unittest.TestCase):
data = unhexlify( data = unhexlify(
b"42f6835bf83114a1f5f6076fe79bdfa0bd67c74b88f127d54572d3910dd09201" b"42f6835bf83114a1f5f6076fe79bdfa0bd67c74b88f127d54572d3910dd09201"
) )
res = crypto.hash_to_point(data) res = crypto.hash_to_point_into(None, data)
res_p = crypto.encodepoint(res) res_p = crypto_helpers.encodepoint(res)
self.assertEqual( self.assertEqual(
res_p, res_p,
unhexlify( unhexlify(
@ -110,16 +110,16 @@ class TestMoneroCrypto(unittest.TestCase):
b"25d08763414c379aa9cf989cdcb3cadd36bd5193b500107d6bf5f921f18e470e" b"25d08763414c379aa9cf989cdcb3cadd36bd5193b500107d6bf5f921f18e470e"
) )
sc_int = crypto.derivation_to_scalar(crypto.decodepoint(derivation), 0) sc_int = crypto_helpers.derivation_to_scalar(crypto_helpers.decodepoint(derivation), 0)
self.assertEqual(scalar, crypto.encodeint(sc_int)) self.assertEqual(scalar, crypto_helpers.encodeint(sc_int))
def test_generate_key_derivation(self): def test_generate_key_derivation(self):
key_pub = crypto.decodepoint( key_pub = crypto_helpers.decodepoint(
unhexlify( unhexlify(
b"7739c95d3298e2f87362dba9e0e0b3980a692ae8e2f16796b0e382098cd6bd83" b"7739c95d3298e2f87362dba9e0e0b3980a692ae8e2f16796b0e382098cd6bd83"
) )
) )
key_priv = crypto.decodeint( key_priv = crypto_helpers.decodeint(
unhexlify( unhexlify(
b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a" b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a"
) )
@ -130,18 +130,18 @@ class TestMoneroCrypto(unittest.TestCase):
self.assertEqual( self.assertEqual(
deriv_exp, deriv_exp,
crypto.encodepoint(crypto.generate_key_derivation(key_pub, key_priv)), crypto_helpers.encodepoint(crypto_helpers.generate_key_derivation(key_pub, key_priv)),
) )
def test_h(self): def test_h(self):
H = unhexlify( H = unhexlify(
b"8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94" b"8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94"
) )
self.assertEqual(crypto.encodepoint(crypto.xmr_H()), H) self.assertEqual(crypto_helpers.encodepoint(crypto.xmr_H()), H)
def test_sc_inversion(self): def test_sc_inversion(self):
res = crypto.new_scalar() res = crypto.Scalar()
inp = crypto.decodeint( inp = crypto_helpers.decodeint(
unhexlify( unhexlify(
b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a" b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a"
) )
@ -149,7 +149,7 @@ class TestMoneroCrypto(unittest.TestCase):
crypto.sc_inv_into(res, inp) crypto.sc_inv_into(res, inp)
self.assertEqual( self.assertEqual(
hexlify(crypto.encodeint(res)), hexlify(crypto_helpers.encodeint(res)),
b"bcf365a551e6358f3f281a6241d4a25eded60230b60a1d48c67b51a85e33d70e", b"bcf365a551e6358f3f281a6241d4a25eded60230b60a1d48c67b51a85e33d70e",
) )
@ -166,39 +166,39 @@ class TestMoneroCrypto(unittest.TestCase):
self.assertEqual( self.assertEqual(
addr, addr,
b"43tpGG9PKbwCpjRvNLn1jwXPpnacw2uVUcszAtgmDiVcZK4VgHwjJT9BJz1WGF9eMxSYASp8yNMkuLjeQfWqJn3CNWdWfzV", "43tpGG9PKbwCpjRvNLn1jwXPpnacw2uVUcszAtgmDiVcZK4VgHwjJT9BJz1WGF9eMxSYASp8yNMkuLjeQfWqJn3CNWdWfzV",
) )
w = AccountCreds.new_wallet( w = AccountCreds.new_wallet(
crypto.decodeint( crypto_helpers.decodeint(
unhexlify( unhexlify(
b"4ce88c168e0f5f8d6524f712d5f8d7d83233b1e7a2a60b5aba5206cc0ea2bc08" b"4ce88c168e0f5f8d6524f712d5f8d7d83233b1e7a2a60b5aba5206cc0ea2bc08"
) )
), ),
crypto.decodeint( crypto_helpers.decodeint(
unhexlify( unhexlify(
b"f2644a3dd97d43e87887e74d1691d52baa0614206ad1b0c239ff4aa3b501750a" b"f2644a3dd97d43e87887e74d1691d52baa0614206ad1b0c239ff4aa3b501750a"
) )
), ),
network_type=NetworkTypes.TESTNET, network_type=MoneroNetworkType.TESTNET,
) )
self.assertEqual( self.assertEqual(
w.address, w.address,
b"9vacMKaj8JJV6MnwDzh2oNVdwTLJfTDyNRiB6NzV9TT7fqvzLivH2dB8Tv7VYR3ncn8vCb3KdNMJzQWrPAF1otYJ9cPKpkr", "9vacMKaj8JJV6MnwDzh2oNVdwTLJfTDyNRiB6NzV9TT7fqvzLivH2dB8Tv7VYR3ncn8vCb3KdNMJzQWrPAF1otYJ9cPKpkr",
) )
def test_derive_subaddress_public_key(self): def test_derive_subaddress_public_key(self):
out_key = crypto.decodepoint( out_key = crypto_helpers.decodepoint(
unhexlify( unhexlify(
b"f4efc29da4ccd6bc6e81f52a6f47b2952966442a7efb49901cce06a7a3bef3e5" b"f4efc29da4ccd6bc6e81f52a6f47b2952966442a7efb49901cce06a7a3bef3e5"
) )
) )
deriv = crypto.decodepoint( deriv = crypto_helpers.decodepoint(
unhexlify( unhexlify(
b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff64" b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff64"
) )
) )
res = crypto.encodepoint(monero.derive_subaddress_public_key(out_key, deriv, 5)) res = crypto_helpers.encodepoint(monero.derive_subaddress_public_key(out_key, deriv, 5))
self.assertEqual( self.assertEqual(
res, res,
unhexlify( unhexlify(
@ -207,14 +207,14 @@ class TestMoneroCrypto(unittest.TestCase):
) )
def test_get_subaddress_secret_key(self): def test_get_subaddress_secret_key(self):
a = crypto.decodeint( a = crypto_helpers.decodeint(
unhexlify( unhexlify(
b"4ce88c168e0f5f8d6524f712d5f8d7d83233b1e7a2a60b5aba5206cc0ea2bc08" b"4ce88c168e0f5f8d6524f712d5f8d7d83233b1e7a2a60b5aba5206cc0ea2bc08"
) )
) )
m = monero.get_subaddress_secret_key(secret_key=a, major=0, minor=1) m = monero.get_subaddress_secret_key(secret_key=a, major=0, minor=1)
self.assertEqual( self.assertEqual(
crypto.encodeint(m), crypto_helpers.encodeint(m),
unhexlify( unhexlify(
b"b6ff4d689b95e3310efbf683850c075bcde46361923054e42ef30016b287ff0c" b"b6ff4d689b95e3310efbf683850c075bcde46361923054e42ef30016b287ff0c"
), ),
@ -231,10 +231,10 @@ class TestMoneroCrypto(unittest.TestCase):
b"0846cae7405077b6b7800f0b932c10a186448370b6db318f8c9e13f781dab546" b"0846cae7405077b6b7800f0b932c10a186448370b6db318f8c9e13f781dab546"
) )
pkey_comp = crypto.derive_public_key( pkey_comp = crypto_helpers.derive_public_key(
crypto.decodepoint(derivation), 0, crypto.decodepoint(base) crypto_helpers.decodepoint(derivation), 0, crypto_helpers.decodepoint(base)
) )
self.assertEqual(pkey_ex, crypto.encodepoint(pkey_comp)) self.assertEqual(pkey_ex, crypto_helpers.encodepoint(pkey_comp))
if __name__ == "__main__": if __name__ == "__main__":

@ -11,33 +11,53 @@ if not utils.BITCOIN_ONLY:
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestMoneroProto(unittest.TestCase): class TestMoneroProto(unittest.TestCase):
def test_sign_keys(self): def test_sign_keys(self):
mst = ubinascii.unhexlify(b"ca3bbe08a178a4508c3992a47ba775799e7626a365ed136e803fe5f2df2ce01c") mst = ubinascii.unhexlify(
self.assertEqual(offloading_keys.key_signature(mst, 0, True)[:12], ubinascii.unhexlify(b'bb665d97ac7c77995578e352')) b"ca3bbe08a178a4508c3992a47ba775799e7626a365ed136e803fe5f2df2ce01c"
self.assertEqual(offloading_keys.key_signature(mst, 0, False), ubinascii.unhexlify(b'87bb70af81bb7325f73e8b962167579454d126ff8ee51472922d7c103fc60f5f')) )
self.assertEqual(offloading_keys.key_signature(mst, 3, True)[:12], ubinascii.unhexlify(b'b2ef8e4e4eec72ce3096622a')) self.assertEqual(
self.assertEqual(offloading_keys.key_signature(mst, 3, False), ubinascii.unhexlify(b'e4331602a83a68c892a83693a1b961564048d9349111b85b8b4b52a1adcf36da')) offloading_keys.key_signature(mst, 0, True)[:12],
ubinascii.unhexlify(b"bb665d97ac7c77995578e352"),
)
self.assertEqual(
offloading_keys.key_signature(mst, 0, False),
ubinascii.unhexlify(
b"87bb70af81bb7325f73e8b962167579454d126ff8ee51472922d7c103fc60f5f"
),
)
self.assertEqual(
offloading_keys.key_signature(mst, 3, True)[:12],
ubinascii.unhexlify(b"b2ef8e4e4eec72ce3096622a"),
)
self.assertEqual(
offloading_keys.key_signature(mst, 3, False),
ubinascii.unhexlify(
b"e4331602a83a68c892a83693a1b961564048d9349111b85b8b4b52a1adcf36da"
),
)
def test_sig_seal(self): def test_sig_seal(self):
mst = ubinascii.unhexlify(b"ca3bbe08a178a4508c3992a47ba775799e7626a365ed136e803fe5f2df2ce01c") mst = ubinascii.unhexlify(
b"ca3bbe08a178a4508c3992a47ba775799e7626a365ed136e803fe5f2df2ce01c"
)
st = State(None) st = State(None)
st.last_step = st.STEP_SIGN st.last_step = st.STEP_SIGN
st.opening_key = mst st.opening_key = mst
st.current_input_index = 3 st.current_input_index = 3
mg_buff = [ mg_buff = [
'0b', "0b",
'02fe9ee789007254215b41351109f186620624a3c1ad2ba89628194528672adf04f900ebf9ad3b0cc1ac9ae1f03167f74d6e04175df5001c91d09d29dbefd6bc0b', "02fe9ee789007254215b41351109f186620624a3c1ad2ba89628194528672adf04f900ebf9ad3b0cc1ac9ae1f03167f74d6e04175df5001c91d09d29dbefd6bc0b",
'021d46f6db8a349caca48a4dfee155b9dee927d0f25cdf5bcd724358c611b47906de6cedad47fd26070927f3954bcaf7a0e126699bf961ca4e8124abefe8aaeb05', "021d46f6db8a349caca48a4dfee155b9dee927d0f25cdf5bcd724358c611b47906de6cedad47fd26070927f3954bcaf7a0e126699bf961ca4e8124abefe8aaeb05",
'02ae933994effe2b348b09bfab783bf9adb58b09659d8f5bd058cca252d763b600541807dcb0ea9fe253e59f23ce36cc811d627acae5e2abdc00b7ed155f3e6b0f', "02ae933994effe2b348b09bfab783bf9adb58b09659d8f5bd058cca252d763b600541807dcb0ea9fe253e59f23ce36cc811d627acae5e2abdc00b7ed155f3e6b0f",
'0203dd7138c7378444fe3c1b1572a351f88505aeab2d9f8ed4a8f67d66e76983072d8ae6e496b3953a8603543c2dc64749ee15fe3575e4505b502bfe696f06690e', "0203dd7138c7378444fe3c1b1572a351f88505aeab2d9f8ed4a8f67d66e76983072d8ae6e496b3953a8603543c2dc64749ee15fe3575e4505b502bfe696f06690e",
'0287b572b6c096bc11a8c10fe1fc4ba2085633f8e1bdd2e39df8f46c9bf733ca068261d8006f22ee2bfaf4366e26d42b00befdddd9058a5c87a0f39c757f121909', "0287b572b6c096bc11a8c10fe1fc4ba2085633f8e1bdd2e39df8f46c9bf733ca068261d8006f22ee2bfaf4366e26d42b00befdddd9058a5c87a0f39c757f121909",
'021e2ea38aa07601e07a3d7623a97e68d3251525304d2a748548c7b46d07c20b0c78506b19cae49d569d0a8c4979c74f7d8d19f7e595d307ddf00faf3d8f621c0d', "021e2ea38aa07601e07a3d7623a97e68d3251525304d2a748548c7b46d07c20b0c78506b19cae49d569d0a8c4979c74f7d8d19f7e595d307ddf00faf3d8f621c0d",
'0214f758c8fb4a521a1e3d25b9fb535974f6aab1c1dda5988e986dda7e17140909a7b7bdb3d5e17a2ebd5deb3530d10c6f5d6966f525c1cbca408059949ff65304', "0214f758c8fb4a521a1e3d25b9fb535974f6aab1c1dda5988e986dda7e17140909a7b7bdb3d5e17a2ebd5deb3530d10c6f5d6966f525c1cbca408059949ff65304",
'02f707c4a37066a692986ddfdd2ca71f68c6f45a956d45eaf6e8e7a2e5272ac3033eb26ca2b55bf86e90ab8ddcdbad88a82ded88deb552614190440169afcee004', "02f707c4a37066a692986ddfdd2ca71f68c6f45a956d45eaf6e8e7a2e5272ac3033eb26ca2b55bf86e90ab8ddcdbad88a82ded88deb552614190440169afcee004",
'02edb8a5b8cc02a2e03b95ea068084ae2496f21d4dfd0842c63836137e37047b06d5a0160994396c98630d8b47878e9c18fea4fb824588c143e05c4b18bfea2301', "02edb8a5b8cc02a2e03b95ea068084ae2496f21d4dfd0842c63836137e37047b06d5a0160994396c98630d8b47878e9c18fea4fb824588c143e05c4b18bfea2301",
'02aa59c2ef76ac97c261279a1c6ed3724d66a437fe8df0b85e8858703947a2b10f04e49912a0626c09849c3b4a3ea46166cd909b9fd561257730c91cbccf4abe07', "02aa59c2ef76ac97c261279a1c6ed3724d66a437fe8df0b85e8858703947a2b10f04e49912a0626c09849c3b4a3ea46166cd909b9fd561257730c91cbccf4abe07",
'02c64a98c59c4a3d7c583de65404c5a54b350a25011dfca70cd84e3f6e570428026236028fce31bfd8d9fc5401867ab5349eb0859c65df05b380899a7bdfee9003', "02c64a98c59c4a3d7c583de65404c5a54b350a25011dfca70cd84e3f6e570428026236028fce31bfd8d9fc5401867ab5349eb0859c65df05b380899a7bdfee9003",
'03da465e27f7feec31353cb668f0e8965391f983b06c0684b35b00af38533603', "03da465e27f7feec31353cb668f0e8965391f983b06c0684b35b00af38533603",
] ]
mg_buff = [ubinascii.unhexlify(x) for x in mg_buff] mg_buff = [ubinascii.unhexlify(x) for x in mg_buff]

@ -10,9 +10,7 @@ if not utils.BITCOIN_ONLY:
) )
from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter
from apps.monero.xmr.serialize_messages.base import ECPoint from apps.monero.xmr.serialize_messages.base import ECPoint
from apps.monero.xmr.serialize_messages.tx_prefix import ( from apps.monero.xmr.serialize_messages.tx_prefix import TxinToKey
TxinToKey,
)
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
@ -66,7 +64,8 @@ class TestMoneroSerializer(unittest.TestCase):
test_deser = TxinToKey.load(MemoryReaderWriter(writer.get_buffer())) test_deser = TxinToKey.load(MemoryReaderWriter(writer.get_buffer()))
self.assertEqual(msg.amount, test_deser.amount) self.assertEqual(msg.amount, test_deser.amount)
self.assertEqual(msg, test_deser) self.assertEqual(msg.k_image, test_deser.k_image)
self.assertEqual(msg.key_offsets, test_deser.key_offsets)
if __name__ == "__main__": if __name__ == "__main__":

@ -491,6 +491,13 @@ class EthereumDataType(IntEnum):
STRUCT = 8 STRUCT = 8
class MoneroNetworkType(IntEnum):
MAINNET = 0
TESTNET = 1
STAGENET = 2
FAKECHAIN = 3
class NEMMosaicLevy(IntEnum): class NEMMosaicLevy(IntEnum):
MosaicLevy_Absolute = 1 MosaicLevy_Absolute = 1
MosaicLevy_Percentile = 2 MosaicLevy_Percentile = 2
@ -5148,7 +5155,7 @@ class MoneroGetAddress(protobuf.MessageType):
FIELDS = { FIELDS = {
1: protobuf.Field("address_n", "uint32", repeated=True, required=False), 1: protobuf.Field("address_n", "uint32", repeated=True, required=False),
2: protobuf.Field("show_display", "bool", repeated=False, required=False), 2: protobuf.Field("show_display", "bool", repeated=False, required=False),
3: protobuf.Field("network_type", "uint32", repeated=False, required=False), 3: protobuf.Field("network_type", "MoneroNetworkType", repeated=False, required=False),
4: protobuf.Field("account", "uint32", repeated=False, required=False), 4: protobuf.Field("account", "uint32", repeated=False, required=False),
5: protobuf.Field("minor", "uint32", repeated=False, required=False), 5: protobuf.Field("minor", "uint32", repeated=False, required=False),
6: protobuf.Field("payment_id", "bytes", repeated=False, required=False), 6: protobuf.Field("payment_id", "bytes", repeated=False, required=False),
@ -5159,7 +5166,7 @@ class MoneroGetAddress(protobuf.MessageType):
*, *,
address_n: Optional[Sequence["int"]] = None, address_n: Optional[Sequence["int"]] = None,
show_display: Optional["bool"] = None, show_display: Optional["bool"] = None,
network_type: Optional["int"] = None, network_type: Optional["MoneroNetworkType"] = MoneroNetworkType.MAINNET,
account: Optional["int"] = None, account: Optional["int"] = None,
minor: Optional["int"] = None, minor: Optional["int"] = None,
payment_id: Optional["bytes"] = None, payment_id: Optional["bytes"] = None,
@ -5190,14 +5197,14 @@ class MoneroGetWatchKey(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 542 MESSAGE_WIRE_TYPE = 542
FIELDS = { FIELDS = {
1: protobuf.Field("address_n", "uint32", repeated=True, required=False), 1: protobuf.Field("address_n", "uint32", repeated=True, required=False),
2: protobuf.Field("network_type", "uint32", repeated=False, required=False), 2: protobuf.Field("network_type", "MoneroNetworkType", repeated=False, required=False),
} }
def __init__( def __init__(
self, self,
*, *,
address_n: Optional[Sequence["int"]] = None, address_n: Optional[Sequence["int"]] = None,
network_type: Optional["int"] = None, network_type: Optional["MoneroNetworkType"] = MoneroNetworkType.MAINNET,
) -> None: ) -> None:
self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.address_n: Sequence["int"] = address_n if address_n is not None else []
self.network_type = network_type self.network_type = network_type
@ -5225,7 +5232,7 @@ class MoneroTransactionInitRequest(protobuf.MessageType):
FIELDS = { FIELDS = {
1: protobuf.Field("version", "uint32", repeated=False, required=False), 1: protobuf.Field("version", "uint32", repeated=False, required=False),
2: protobuf.Field("address_n", "uint32", repeated=True, required=False), 2: protobuf.Field("address_n", "uint32", repeated=True, required=False),
3: protobuf.Field("network_type", "uint32", repeated=False, required=False), 3: protobuf.Field("network_type", "MoneroNetworkType", repeated=False, required=False),
4: protobuf.Field("tsx_data", "MoneroTransactionData", repeated=False, required=False), 4: protobuf.Field("tsx_data", "MoneroTransactionData", repeated=False, required=False),
} }
@ -5234,7 +5241,7 @@ class MoneroTransactionInitRequest(protobuf.MessageType):
*, *,
address_n: Optional[Sequence["int"]] = None, address_n: Optional[Sequence["int"]] = None,
version: Optional["int"] = None, version: Optional["int"] = None,
network_type: Optional["int"] = None, network_type: Optional["MoneroNetworkType"] = MoneroNetworkType.MAINNET,
tsx_data: Optional["MoneroTransactionData"] = None, tsx_data: Optional["MoneroTransactionData"] = None,
) -> None: ) -> None:
self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.address_n: Sequence["int"] = address_n if address_n is not None else []
@ -5543,21 +5550,21 @@ class MoneroTransactionFinalAck(protobuf.MessageType):
class MoneroKeyImageExportInitRequest(protobuf.MessageType): class MoneroKeyImageExportInitRequest(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 530 MESSAGE_WIRE_TYPE = 530
FIELDS = { FIELDS = {
1: protobuf.Field("num", "uint64", repeated=False, required=False), 1: protobuf.Field("num", "uint64", repeated=False, required=True),
2: protobuf.Field("hash", "bytes", repeated=False, required=False), 2: protobuf.Field("hash", "bytes", repeated=False, required=True),
3: protobuf.Field("address_n", "uint32", repeated=True, required=False), 3: protobuf.Field("address_n", "uint32", repeated=True, required=False),
4: protobuf.Field("network_type", "uint32", repeated=False, required=False), 4: protobuf.Field("network_type", "MoneroNetworkType", repeated=False, required=False),
5: protobuf.Field("subs", "MoneroSubAddressIndicesList", repeated=True, required=False), 5: protobuf.Field("subs", "MoneroSubAddressIndicesList", repeated=True, required=False),
} }
def __init__( def __init__(
self, self,
*, *,
num: "int",
hash: "bytes",
address_n: Optional[Sequence["int"]] = None, address_n: Optional[Sequence["int"]] = None,
subs: Optional[Sequence["MoneroSubAddressIndicesList"]] = None, subs: Optional[Sequence["MoneroSubAddressIndicesList"]] = None,
num: Optional["int"] = None, network_type: Optional["MoneroNetworkType"] = MoneroNetworkType.MAINNET,
hash: Optional["bytes"] = None,
network_type: Optional["int"] = None,
) -> None: ) -> None:
self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.address_n: Sequence["int"] = address_n if address_n is not None else []
self.subs: Sequence["MoneroSubAddressIndicesList"] = subs if subs is not None else [] self.subs: Sequence["MoneroSubAddressIndicesList"] = subs if subs is not None else []
@ -5620,11 +5627,11 @@ class MoneroGetTxKeyRequest(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 550 MESSAGE_WIRE_TYPE = 550
FIELDS = { FIELDS = {
1: protobuf.Field("address_n", "uint32", repeated=True, required=False), 1: protobuf.Field("address_n", "uint32", repeated=True, required=False),
2: protobuf.Field("network_type", "uint32", repeated=False, required=False), 2: protobuf.Field("network_type", "MoneroNetworkType", repeated=False, required=False),
3: protobuf.Field("salt1", "bytes", repeated=False, required=False), 3: protobuf.Field("salt1", "bytes", repeated=False, required=True),
4: protobuf.Field("salt2", "bytes", repeated=False, required=False), 4: protobuf.Field("salt2", "bytes", repeated=False, required=True),
5: protobuf.Field("tx_enc_keys", "bytes", repeated=False, required=False), 5: protobuf.Field("tx_enc_keys", "bytes", repeated=False, required=True),
6: protobuf.Field("tx_prefix_hash", "bytes", repeated=False, required=False), 6: protobuf.Field("tx_prefix_hash", "bytes", repeated=False, required=True),
7: protobuf.Field("reason", "uint32", repeated=False, required=False), 7: protobuf.Field("reason", "uint32", repeated=False, required=False),
8: protobuf.Field("view_public_key", "bytes", repeated=False, required=False), 8: protobuf.Field("view_public_key", "bytes", repeated=False, required=False),
} }
@ -5632,21 +5639,21 @@ class MoneroGetTxKeyRequest(protobuf.MessageType):
def __init__( def __init__(
self, self,
*, *,
salt1: "bytes",
salt2: "bytes",
tx_enc_keys: "bytes",
tx_prefix_hash: "bytes",
address_n: Optional[Sequence["int"]] = None, address_n: Optional[Sequence["int"]] = None,
network_type: Optional["int"] = None, network_type: Optional["MoneroNetworkType"] = MoneroNetworkType.MAINNET,
salt1: Optional["bytes"] = None,
salt2: Optional["bytes"] = None,
tx_enc_keys: Optional["bytes"] = None,
tx_prefix_hash: Optional["bytes"] = None,
reason: Optional["int"] = None, reason: Optional["int"] = None,
view_public_key: Optional["bytes"] = None, view_public_key: Optional["bytes"] = None,
) -> None: ) -> None:
self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.address_n: Sequence["int"] = address_n if address_n is not None else []
self.network_type = network_type
self.salt1 = salt1 self.salt1 = salt1
self.salt2 = salt2 self.salt2 = salt2
self.tx_enc_keys = tx_enc_keys self.tx_enc_keys = tx_enc_keys
self.tx_prefix_hash = tx_prefix_hash self.tx_prefix_hash = tx_prefix_hash
self.network_type = network_type
self.reason = reason self.reason = reason
self.view_public_key = view_public_key self.view_public_key = view_public_key
@ -5675,14 +5682,14 @@ class MoneroLiveRefreshStartRequest(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 552 MESSAGE_WIRE_TYPE = 552
FIELDS = { FIELDS = {
1: protobuf.Field("address_n", "uint32", repeated=True, required=False), 1: protobuf.Field("address_n", "uint32", repeated=True, required=False),
2: protobuf.Field("network_type", "uint32", repeated=False, required=False), 2: protobuf.Field("network_type", "MoneroNetworkType", repeated=False, required=False),
} }
def __init__( def __init__(
self, self,
*, *,
address_n: Optional[Sequence["int"]] = None, address_n: Optional[Sequence["int"]] = None,
network_type: Optional["int"] = None, network_type: Optional["MoneroNetworkType"] = MoneroNetworkType.MAINNET,
) -> None: ) -> None:
self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.address_n: Sequence["int"] = address_n if address_n is not None else []
self.network_type = network_type self.network_type = network_type
@ -5695,21 +5702,21 @@ class MoneroLiveRefreshStartAck(protobuf.MessageType):
class MoneroLiveRefreshStepRequest(protobuf.MessageType): class MoneroLiveRefreshStepRequest(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 554 MESSAGE_WIRE_TYPE = 554
FIELDS = { FIELDS = {
1: protobuf.Field("out_key", "bytes", repeated=False, required=False), 1: protobuf.Field("out_key", "bytes", repeated=False, required=True),
2: protobuf.Field("recv_deriv", "bytes", repeated=False, required=False), 2: protobuf.Field("recv_deriv", "bytes", repeated=False, required=True),
3: protobuf.Field("real_out_idx", "uint64", repeated=False, required=False), 3: protobuf.Field("real_out_idx", "uint64", repeated=False, required=True),
4: protobuf.Field("sub_addr_major", "uint32", repeated=False, required=False), 4: protobuf.Field("sub_addr_major", "uint32", repeated=False, required=True),
5: protobuf.Field("sub_addr_minor", "uint32", repeated=False, required=False), 5: protobuf.Field("sub_addr_minor", "uint32", repeated=False, required=True),
} }
def __init__( def __init__(
self, self,
*, *,
out_key: Optional["bytes"] = None, out_key: "bytes",
recv_deriv: Optional["bytes"] = None, recv_deriv: "bytes",
real_out_idx: Optional["int"] = None, real_out_idx: "int",
sub_addr_major: Optional["int"] = None, sub_addr_major: "int",
sub_addr_minor: Optional["int"] = None, sub_addr_minor: "int",
) -> None: ) -> None:
self.out_key = out_key self.out_key = out_key
self.recv_deriv = recv_deriv self.recv_deriv = recv_deriv
@ -5844,15 +5851,15 @@ class MoneroMultisigKLRki(protobuf.MessageType):
class MoneroRctKeyPublic(protobuf.MessageType): class MoneroRctKeyPublic(protobuf.MessageType):
MESSAGE_WIRE_TYPE = None MESSAGE_WIRE_TYPE = None
FIELDS = { FIELDS = {
1: protobuf.Field("dest", "bytes", repeated=False, required=False), 1: protobuf.Field("dest", "bytes", repeated=False, required=True),
2: protobuf.Field("commitment", "bytes", repeated=False, required=False), 2: protobuf.Field("commitment", "bytes", repeated=False, required=True),
} }
def __init__( def __init__(
self, self,
*, *,
dest: Optional["bytes"] = None, dest: "bytes",
commitment: Optional["bytes"] = None, commitment: "bytes",
) -> None: ) -> None:
self.dest = dest self.dest = dest
self.commitment = commitment self.commitment = commitment
@ -5954,15 +5961,15 @@ class MoneroRingCtSig(protobuf.MessageType):
class MoneroSubAddressIndicesList(protobuf.MessageType): class MoneroSubAddressIndicesList(protobuf.MessageType):
MESSAGE_WIRE_TYPE = None MESSAGE_WIRE_TYPE = None
FIELDS = { FIELDS = {
1: protobuf.Field("account", "uint32", repeated=False, required=False), 1: protobuf.Field("account", "uint32", repeated=False, required=True),
2: protobuf.Field("minor_indices", "uint32", repeated=True, required=False), 2: protobuf.Field("minor_indices", "uint32", repeated=True, required=False),
} }
def __init__( def __init__(
self, self,
*, *,
account: "int",
minor_indices: Optional[Sequence["int"]] = None, minor_indices: Optional[Sequence["int"]] = None,
account: Optional["int"] = None,
) -> None: ) -> None:
self.minor_indices: Sequence["int"] = minor_indices if minor_indices is not None else [] self.minor_indices: Sequence["int"] = minor_indices if minor_indices is not None else []
self.account = account self.account = account
@ -5971,10 +5978,10 @@ class MoneroSubAddressIndicesList(protobuf.MessageType):
class MoneroTransferDetails(protobuf.MessageType): class MoneroTransferDetails(protobuf.MessageType):
MESSAGE_WIRE_TYPE = None MESSAGE_WIRE_TYPE = None
FIELDS = { FIELDS = {
1: protobuf.Field("out_key", "bytes", repeated=False, required=False), 1: protobuf.Field("out_key", "bytes", repeated=False, required=True),
2: protobuf.Field("tx_pub_key", "bytes", repeated=False, required=False), 2: protobuf.Field("tx_pub_key", "bytes", repeated=False, required=True),
3: protobuf.Field("additional_tx_pub_keys", "bytes", repeated=True, required=False), 3: protobuf.Field("additional_tx_pub_keys", "bytes", repeated=True, required=False),
4: protobuf.Field("internal_output_index", "uint64", repeated=False, required=False), 4: protobuf.Field("internal_output_index", "uint64", repeated=False, required=True),
5: protobuf.Field("sub_addr_major", "uint32", repeated=False, required=False), 5: protobuf.Field("sub_addr_major", "uint32", repeated=False, required=False),
6: protobuf.Field("sub_addr_minor", "uint32", repeated=False, required=False), 6: protobuf.Field("sub_addr_minor", "uint32", repeated=False, required=False),
} }
@ -5982,10 +5989,10 @@ class MoneroTransferDetails(protobuf.MessageType):
def __init__( def __init__(
self, self,
*, *,
out_key: "bytes",
tx_pub_key: "bytes",
internal_output_index: "int",
additional_tx_pub_keys: Optional[Sequence["bytes"]] = None, additional_tx_pub_keys: Optional[Sequence["bytes"]] = None,
out_key: Optional["bytes"] = None,
tx_pub_key: Optional["bytes"] = None,
internal_output_index: Optional["int"] = None,
sub_addr_major: Optional["int"] = None, sub_addr_major: Optional["int"] = None,
sub_addr_minor: Optional["int"] = None, sub_addr_minor: Optional["int"] = None,
) -> None: ) -> None:

Loading…
Cancel
Save