* 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
parent
99c817bad4
commit
33c174491f
@ -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
@ -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.keychain import auto_keychain
|
||||
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__)
|
||||
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 layout.require_confirm_watchkey(ctx)
|
||||
|
||||
creds = misc.get_creds(keychain, msg.address_n, msg.network_type)
|
||||
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,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")
|
@ -0,0 +1 @@
|
||||
from trezor.crypto import monero as crypto # noqa: F401
|
File diff suppressed because it is too large
Load Diff
@ -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,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 apps.monero.xmr import crypto
|
||||
from apps.monero.xmr import crypto_helpers
|
||||
from apps.monero.xmr.serialize import int_serialize
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from trezor.utils import HashContext
|
||||
|
||||
|
||||
class KeccakXmrArchive:
|
||||
def __init__(self, ctx=None):
|
||||
def __init__(self, ctx: HashContext | None = None) -> None:
|
||||
self.kwriter = get_keccak_writer(ctx)
|
||||
|
||||
def get_digest(self):
|
||||
def get_digest(self) -> bytes:
|
||||
return self.kwriter.get_digest()
|
||||
|
||||
def buffer(self, buf):
|
||||
def buffer(self, buf: bytes) -> None:
|
||||
return self.kwriter.write(buf)
|
||||
|
||||
def uvarint(self, i):
|
||||
def uvarint(self, i: int) -> None:
|
||||
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)
|
||||
|
||||
|
||||
def get_keccak_writer(ctx=None):
|
||||
def get_keccak_writer(ctx: HashContext | None = None) -> HashWriter:
|
||||
if ctx is None:
|
||||
ctx = crypto.get_keccak()
|
||||
ctx = crypto_helpers.get_keccak()
|
||||
return HashWriter(ctx)
|
||||
|
@ -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,6 +1,12 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import crypto
|
||||
|
||||
|
||||
class CtKey:
|
||||
__slots__ = ("dest", "mask")
|
||||
|
||||
def __init__(self, dest, mask):
|
||||
def __init__(self, dest: crypto.Scalar, mask: crypto.Scalar) -> None:
|
||||
self.dest = dest
|
||||
self.mask = mask
|
||||
|
@ -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
|
Loading…
Reference in new issue