mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-04-19 16:49:02 +00:00
refactor(core,crypto): rename sign() to sign_recoverable()
[no changelog]
This commit is contained in:
parent
8b4936926c
commit
fe777474dd
@ -87,14 +87,14 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(
|
||||
mod_trezorcrypto_nist256p1_publickey_obj, 1, 2,
|
||||
mod_trezorcrypto_nist256p1_publickey);
|
||||
|
||||
/// def sign(
|
||||
/// def sign_recoverable(
|
||||
/// secret_key: bytes, digest: bytes, compressed: bool = True
|
||||
/// ) -> bytes:
|
||||
/// """
|
||||
/// Uses secret key to produce the signature of the digest.
|
||||
/// """
|
||||
STATIC mp_obj_t mod_trezorcrypto_nist256p1_sign(size_t n_args,
|
||||
const mp_obj_t *args) {
|
||||
STATIC mp_obj_t mod_trezorcrypto_nist256p1_sign_recoverable(
|
||||
size_t n_args, const mp_obj_t *args) {
|
||||
mp_buffer_info_t sk = {0};
|
||||
mp_buffer_info_t dig = {0};
|
||||
mp_get_buffer_raise(args[0], &sk, MP_BUFFER_READ);
|
||||
@ -118,9 +118,9 @@ STATIC mp_obj_t mod_trezorcrypto_nist256p1_sign(size_t n_args,
|
||||
sig.buf[0] = 27 + pby + compressed * 4;
|
||||
return mp_obj_new_str_from_vstr(&mp_type_bytes, &sig);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_nist256p1_sign_obj,
|
||||
2, 3,
|
||||
mod_trezorcrypto_nist256p1_sign);
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(
|
||||
mod_trezorcrypto_nist256p1_sign_recoverable_obj, 2, 3,
|
||||
mod_trezorcrypto_nist256p1_sign_recoverable);
|
||||
|
||||
/// def verify(public_key: bytes, signature: bytes, digest: bytes) -> bool:
|
||||
/// """
|
||||
@ -225,8 +225,8 @@ STATIC const mp_rom_map_elem_t mod_trezorcrypto_nist256p1_globals_table[] = {
|
||||
MP_ROM_PTR(&mod_trezorcrypto_nist256p1_generate_secret_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_publickey),
|
||||
MP_ROM_PTR(&mod_trezorcrypto_nist256p1_publickey_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_sign),
|
||||
MP_ROM_PTR(&mod_trezorcrypto_nist256p1_sign_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_sign_recoverable),
|
||||
MP_ROM_PTR(&mod_trezorcrypto_nist256p1_sign_recoverable_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_verify),
|
||||
MP_ROM_PTR(&mod_trezorcrypto_nist256p1_verify_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_verify_recover),
|
||||
|
@ -114,7 +114,7 @@ enum {
|
||||
|
||||
#endif
|
||||
|
||||
/// def sign(
|
||||
/// def sign_recoverable(
|
||||
/// secret_key: bytes,
|
||||
/// digest: bytes,
|
||||
/// compressed: bool = True,
|
||||
@ -123,8 +123,8 @@ enum {
|
||||
/// """
|
||||
/// Uses secret key to produce the signature of the digest.
|
||||
/// """
|
||||
STATIC mp_obj_t mod_trezorcrypto_secp256k1_sign(size_t n_args,
|
||||
const mp_obj_t *args) {
|
||||
STATIC mp_obj_t mod_trezorcrypto_secp256k1_sign_recoverable(
|
||||
size_t n_args, const mp_obj_t *args) {
|
||||
mp_buffer_info_t sk = {0};
|
||||
mp_buffer_info_t dig = {0};
|
||||
mp_get_buffer_raise(args[0], &sk, MP_BUFFER_READ);
|
||||
@ -160,9 +160,9 @@ STATIC mp_obj_t mod_trezorcrypto_secp256k1_sign(size_t n_args,
|
||||
sig.buf[0] = 27 + pby + compressed * 4;
|
||||
return mp_obj_new_str_from_vstr(&mp_type_bytes, &sig);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_secp256k1_sign_obj,
|
||||
2, 4,
|
||||
mod_trezorcrypto_secp256k1_sign);
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(
|
||||
mod_trezorcrypto_secp256k1_sign_recoverable_obj, 2, 4,
|
||||
mod_trezorcrypto_secp256k1_sign_recoverable);
|
||||
|
||||
/// def verify(public_key: bytes, signature: bytes, digest: bytes) -> bool:
|
||||
/// """
|
||||
@ -267,8 +267,8 @@ STATIC const mp_rom_map_elem_t mod_trezorcrypto_secp256k1_globals_table[] = {
|
||||
MP_ROM_PTR(&mod_trezorcrypto_secp256k1_generate_secret_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_publickey),
|
||||
MP_ROM_PTR(&mod_trezorcrypto_secp256k1_publickey_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_sign),
|
||||
MP_ROM_PTR(&mod_trezorcrypto_secp256k1_sign_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_sign_recoverable),
|
||||
MP_ROM_PTR(&mod_trezorcrypto_secp256k1_sign_recoverable_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_verify),
|
||||
MP_ROM_PTR(&mod_trezorcrypto_secp256k1_verify_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_verify_recover),
|
||||
|
@ -16,7 +16,7 @@ def publickey(secret_key: bytes, compressed: bool = True) -> bytes:
|
||||
|
||||
|
||||
# upymod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
|
||||
def sign(
|
||||
def sign_recoverable(
|
||||
secret_key: bytes, digest: bytes, compressed: bool = True
|
||||
) -> bytes:
|
||||
"""
|
||||
|
@ -18,7 +18,7 @@ CANONICAL_SIG_EOS: int = 2
|
||||
|
||||
|
||||
# upymod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
|
||||
def sign(
|
||||
def sign_recoverable(
|
||||
secret_key: bytes,
|
||||
digest: bytes,
|
||||
compressed: bool = True,
|
||||
|
@ -24,7 +24,7 @@ from .curve_benchmark import (
|
||||
from .hash_benchmark import HashBenchmark
|
||||
|
||||
|
||||
# This is a wrapper above the trezor.crypto.curve.ed25519 module that satisfies SignCurve protocol, the modules uses `message` instead of `digest` in `sign()` and `verify()`
|
||||
# This is a wrapper above the trezor.crypto.curve.ed25519 module that satisfies SignCurve protocol, the wrapper maps `digest` to `message` in `sign()` and `verify()`
|
||||
class Ed25519:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
@ -44,6 +44,48 @@ class Ed25519:
|
||||
return ed25519.verify(public_key, signature, digest)
|
||||
|
||||
|
||||
# This is a wrapper above the trezor.crypto.curve.secp256k1 module that satisfies SignCurve and MultiplyCurve protocol, the wrapper maps `sign()` to `sign_recoverable()`
|
||||
class Secp256k1:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def generate_secret(self) -> bytes:
|
||||
return secp256k1.generate_secret()
|
||||
|
||||
def publickey(self, secret_key: bytes) -> bytes:
|
||||
return secp256k1.publickey(secret_key)
|
||||
|
||||
def multiply(self, secret_key: bytes, public_key: bytes) -> bytes:
|
||||
return secp256k1.multiply(secret_key, public_key)
|
||||
|
||||
def sign(self, secret_key: bytes, digest: bytes) -> bytes:
|
||||
return secp256k1.sign_recoverable(secret_key, digest)
|
||||
|
||||
def verify(self, public_key: bytes, signature: bytes, digest: bytes) -> bool:
|
||||
return secp256k1.verify(public_key, signature, digest)
|
||||
|
||||
|
||||
# This is a wrapper above the trezor.crypto.curve.nist256p1 module that satisfies SignCurve and MultiplyCurve protocol, the wrapper maps `sign()` to `sign_recoverable()`
|
||||
class Nist256p1:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def generate_secret(self) -> bytes:
|
||||
return nist256p1.generate_secret()
|
||||
|
||||
def publickey(self, secret_key: bytes) -> bytes:
|
||||
return nist256p1.publickey(secret_key)
|
||||
|
||||
def multiply(self, secret_key: bytes, public_key: bytes) -> bytes:
|
||||
return nist256p1.multiply(secret_key, public_key)
|
||||
|
||||
def sign(self, secret_key: bytes, digest: bytes) -> bytes:
|
||||
return nist256p1.sign_recoverable(secret_key, digest)
|
||||
|
||||
def verify(self, public_key: bytes, signature: bytes, digest: bytes) -> bool:
|
||||
return nist256p1.verify(public_key, signature, digest)
|
||||
|
||||
|
||||
benchmarks = {
|
||||
"crypto/hash/blake2b": HashBenchmark(lambda: blake2b()),
|
||||
"crypto/hash/blake2s": HashBenchmark(lambda: blake2s()),
|
||||
@ -79,17 +121,17 @@ benchmarks = {
|
||||
"crypto/cipher/chacha20poly1305/decrypt": DecryptBenchmark(
|
||||
lambda: chacha20poly1305(random_bytes(32), random_bytes(12)), 64
|
||||
),
|
||||
"crypto/curve/secp256k1/sign": SignBenchmark(secp256k1),
|
||||
"crypto/curve/secp256k1/verify": VerifyBenchmark(secp256k1),
|
||||
"crypto/curve/secp256k1/publickey": PublickeyBenchmark(secp256k1),
|
||||
"crypto/curve/secp256k1/multiply": MultiplyBenchmark(secp256k1),
|
||||
"crypto/curve/nist256p1/sign": SignBenchmark(nist256p1),
|
||||
"crypto/curve/nist256p1/verify": VerifyBenchmark(nist256p1),
|
||||
"crypto/curve/nist256p1/publickey": PublickeyBenchmark(nist256p1),
|
||||
"crypto/curve/nist256p1/multiply": MultiplyBenchmark(nist256p1),
|
||||
"crypto/curve/secp256k1/sign": SignBenchmark(Secp256k1()),
|
||||
"crypto/curve/secp256k1/verify": VerifyBenchmark(Secp256k1()),
|
||||
"crypto/curve/secp256k1/publickey": PublickeyBenchmark(Secp256k1()),
|
||||
"crypto/curve/secp256k1/multiply": MultiplyBenchmark(Secp256k1()),
|
||||
"crypto/curve/nist256p1/sign": SignBenchmark(Nist256p1()),
|
||||
"crypto/curve/nist256p1/verify": VerifyBenchmark(Nist256p1()),
|
||||
"crypto/curve/nist256p1/publickey": PublickeyBenchmark(Nist256p1()),
|
||||
"crypto/curve/nist256p1/multiply": MultiplyBenchmark(Nist256p1()),
|
||||
"crypto/curve/ed25519/sign": SignBenchmark(Ed25519()),
|
||||
"crypto/curve/ed25519/verify": VerifyBenchmark(Ed25519()),
|
||||
"crypto/curve/ed25519/publickey": PublickeyBenchmark(ed25519),
|
||||
"crypto/curve/ed25519/publickey": PublickeyBenchmark(Ed25519()),
|
||||
"crypto/curve/curve25519/publickey": PublickeyBenchmark(curve25519),
|
||||
"crypto/curve/curve25519/multiply": MultiplyBenchmark(curve25519),
|
||||
}
|
||||
|
@ -59,6 +59,6 @@ async def sign_tx(envelope: BinanceSignTx, keychain: Keychain) -> BinanceSignedT
|
||||
|
||||
# generate_content_signature
|
||||
msghash = sha256(msg_json.encode()).digest()
|
||||
signature_bytes = secp256k1.sign(node.private_key(), msghash)[1:65]
|
||||
signature_bytes = secp256k1.sign_recoverable(node.private_key(), msghash)[1:65]
|
||||
|
||||
return BinanceSignedTx(signature=signature_bytes, public_key=node.public_key())
|
||||
|
@ -108,7 +108,7 @@ def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes:
|
||||
from trezor.crypto import der
|
||||
from trezor.crypto.curve import secp256k1
|
||||
|
||||
sig = secp256k1.sign(node.private_key(), digest)
|
||||
sig = secp256k1.sign_recoverable(node.private_key(), digest)
|
||||
sigder = der.encode_seq((sig[1:33], sig[33:65]))
|
||||
return sigder
|
||||
|
||||
|
@ -52,7 +52,7 @@ async def sign_message(
|
||||
seckey = node.private_key()
|
||||
|
||||
digest = message_digest(coin, message)
|
||||
signature = secp256k1.sign(seckey, digest)
|
||||
signature = secp256k1.sign_recoverable(seckey, digest)
|
||||
|
||||
if script_type == InputScriptType.SPENDADDRESS:
|
||||
script_type_info = 0
|
||||
|
@ -51,7 +51,7 @@ async def sign_tx(msg: EosSignTx, keychain: Keychain) -> EosSignedTx:
|
||||
write_bytes_fixed(sha, bytearray(32), 32)
|
||||
|
||||
digest = sha.get_digest()
|
||||
signature = secp256k1.sign(
|
||||
signature = secp256k1.sign_recoverable(
|
||||
node.private_key(), digest, True, secp256k1.CANONICAL_SIG_EOS
|
||||
)
|
||||
|
||||
|
@ -46,7 +46,7 @@ async def sign_message(
|
||||
decode_message(msg.message), address, account="ETH", path=path, verify=False
|
||||
)
|
||||
|
||||
signature = secp256k1.sign(
|
||||
signature = secp256k1.sign_recoverable(
|
||||
node.private_key(),
|
||||
message_digest(msg.message),
|
||||
False,
|
||||
|
@ -262,7 +262,7 @@ def _sign_digest(
|
||||
from trezor.crypto.curve import secp256k1
|
||||
|
||||
node = keychain.derive(msg.address_n)
|
||||
signature = secp256k1.sign(
|
||||
signature = secp256k1.sign_recoverable(
|
||||
node.private_key(), digest, False, secp256k1.CANONICAL_SIG_ETHEREUM
|
||||
)
|
||||
|
||||
|
@ -158,7 +158,7 @@ def _sign_digest(
|
||||
from trezor.messages import EthereumTxRequest
|
||||
|
||||
node = keychain.derive(msg.address_n)
|
||||
signature = secp256k1.sign(
|
||||
signature = secp256k1.sign_recoverable(
|
||||
node.private_key(), digest, False, secp256k1.CANONICAL_SIG_ETHEREUM
|
||||
)
|
||||
|
||||
|
@ -48,7 +48,7 @@ async def sign_typed_data(
|
||||
msg.primary_type, msg.metamask_v4_compat
|
||||
)
|
||||
|
||||
signature = secp256k1.sign(
|
||||
signature = secp256k1.sign_recoverable(
|
||||
node.private_key(), data_hash, False, secp256k1.CANONICAL_SIG_ETHEREUM
|
||||
)
|
||||
|
||||
|
@ -133,11 +133,11 @@ def sign_challenge(
|
||||
if curve == "secp256k1":
|
||||
from trezor.crypto.curve import secp256k1
|
||||
|
||||
signature = secp256k1.sign(seckey, data)
|
||||
signature = secp256k1.sign_recoverable(seckey, data)
|
||||
elif curve == "nist256p1":
|
||||
from trezor.crypto.curve import nist256p1
|
||||
|
||||
signature = nist256p1.sign(seckey, data)
|
||||
signature = nist256p1.sign_recoverable(seckey, data)
|
||||
elif curve == "ed25519":
|
||||
from trezor.crypto.curve import ed25519
|
||||
|
||||
|
@ -54,7 +54,7 @@ async def sign_tx(msg: RippleSignTx, keychain: Keychain) -> RippleSignedTx:
|
||||
|
||||
# Signs and encodes signature into DER format
|
||||
first_half_of_sha512 = sha512(to_sign).digest()[:32]
|
||||
sig = secp256k1.sign(node.private_key(), first_half_of_sha512)
|
||||
sig = secp256k1.sign_recoverable(node.private_key(), first_half_of_sha512)
|
||||
sig_encoded = der.encode_seq((sig[1:33], sig[33:65]))
|
||||
|
||||
tx = serialize(msg, source_address, node.public_key(), sig_encoded)
|
||||
|
@ -94,7 +94,7 @@ class Credential:
|
||||
dig = hashlib.sha256()
|
||||
for segment in data:
|
||||
dig.update(segment)
|
||||
sig = nist256p1.sign(self._private_key(), dig.digest(), False)
|
||||
sig = nist256p1.sign_recoverable(self._private_key(), dig.digest(), False)
|
||||
return der.encode_seq((sig[1:33], sig[33:]))
|
||||
|
||||
def bogus_signature(self) -> bytes:
|
||||
|
@ -1309,7 +1309,7 @@ def basic_attestation_sign(data: Iterable[bytes]) -> bytes:
|
||||
dig = hashlib.sha256()
|
||||
for segment in data:
|
||||
dig.update(segment)
|
||||
sig = nist256p1.sign(_FIDO_ATT_PRIV_KEY, dig.digest(), False)
|
||||
sig = nist256p1.sign_recoverable(_FIDO_ATT_PRIV_KEY, dig.digest(), False)
|
||||
return der.encode_seq((sig[1:33], sig[33:]))
|
||||
|
||||
|
||||
|
@ -20,7 +20,7 @@ if not utils.BITCOIN_ONLY:
|
||||
# NOTE: copy-pasted from apps.binance.sign_tx
|
||||
def generate_content_signature(json: bytes, private_key: bytes) -> bytes:
|
||||
msghash = sha256(json).digest()
|
||||
return secp256k1.sign(private_key, msghash)[1:65]
|
||||
return secp256k1.sign_recoverable(private_key, msghash)[1:65]
|
||||
|
||||
|
||||
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
|
||||
|
@ -250,17 +250,17 @@ class TestCryptoNist256p1(unittest.TestCase):
|
||||
pk = nist256p1.publickey(sk)
|
||||
|
||||
dig = bytes([1] + [0] * 31)
|
||||
sig = nist256p1.sign(sk, dig)
|
||||
sig = nist256p1.sign_recoverable(sk, dig)
|
||||
self.assertTrue(nist256p1.verify(pk, sig, dig))
|
||||
self.assertTrue(nist256p1.verify(pk, sig[1:], dig))
|
||||
|
||||
dig = bytes([0] * 31 + [1])
|
||||
sig = nist256p1.sign(sk, dig)
|
||||
sig = nist256p1.sign_recoverable(sk, dig)
|
||||
self.assertTrue(nist256p1.verify(pk, sig, dig))
|
||||
self.assertTrue(nist256p1.verify(pk, sig[1:], dig))
|
||||
|
||||
dig = bytes([0xFF] * 32)
|
||||
sig = nist256p1.sign(sk, dig)
|
||||
sig = nist256p1.sign_recoverable(sk, dig)
|
||||
self.assertTrue(nist256p1.verify(pk, sig, dig))
|
||||
self.assertTrue(nist256p1.verify(pk, sig[1:], dig))
|
||||
|
||||
@ -269,7 +269,7 @@ class TestCryptoNist256p1(unittest.TestCase):
|
||||
sk = nist256p1.generate_secret()
|
||||
pk = nist256p1.publickey(sk)
|
||||
dig = random.bytes(32)
|
||||
sig = nist256p1.sign(sk, dig)
|
||||
sig = nist256p1.sign_recoverable(sk, dig)
|
||||
self.assertTrue(nist256p1.verify(pk, sig, dig))
|
||||
self.assertTrue(nist256p1.verify(pk, sig[1:], dig))
|
||||
|
||||
@ -279,7 +279,7 @@ class TestCryptoNist256p1(unittest.TestCase):
|
||||
sk = nist256p1.generate_secret()
|
||||
pk = nist256p1.publickey(sk, compressed)
|
||||
dig = random.bytes(32)
|
||||
sig = nist256p1.sign(sk, dig, compressed)
|
||||
sig = nist256p1.sign_recoverable(sk, dig, compressed)
|
||||
pk2 = nist256p1.verify_recover(sig, dig)
|
||||
self.assertEqual(pk, pk2)
|
||||
|
||||
|
@ -221,15 +221,15 @@ class TestCryptoSecp256k1(unittest.TestCase):
|
||||
pk = secp256k1.publickey(sk)
|
||||
|
||||
dig = bytes([1] + [0] * 31)
|
||||
sig = secp256k1.sign(sk, dig)
|
||||
sig = secp256k1.sign_recoverable(sk, dig)
|
||||
self.assertTrue(secp256k1.verify(pk, sig, dig))
|
||||
|
||||
dig = bytes([0] * 31 + [1])
|
||||
sig = secp256k1.sign(sk, dig)
|
||||
sig = secp256k1.sign_recoverable(sk, dig)
|
||||
self.assertTrue(secp256k1.verify(pk, sig, dig))
|
||||
|
||||
dig = bytes([0xFF] * 32)
|
||||
sig = secp256k1.sign(sk, dig)
|
||||
sig = secp256k1.sign_recoverable(sk, dig)
|
||||
self.assertTrue(secp256k1.verify(pk, sig, dig))
|
||||
|
||||
def test_sign_verify_random(self):
|
||||
@ -237,7 +237,7 @@ class TestCryptoSecp256k1(unittest.TestCase):
|
||||
sk = secp256k1.generate_secret()
|
||||
pk = secp256k1.publickey(sk)
|
||||
dig = random.bytes(32)
|
||||
sig = secp256k1.sign(sk, dig)
|
||||
sig = secp256k1.sign_recoverable(sk, dig)
|
||||
self.assertTrue(secp256k1.verify(pk, sig, dig))
|
||||
|
||||
def test_verify_recover(self):
|
||||
@ -246,7 +246,7 @@ class TestCryptoSecp256k1(unittest.TestCase):
|
||||
sk = secp256k1.generate_secret()
|
||||
pk = secp256k1.publickey(sk, compressed)
|
||||
dig = random.bytes(32)
|
||||
sig = secp256k1.sign(sk, dig, compressed)
|
||||
sig = secp256k1.sign_recoverable(sk, dig, compressed)
|
||||
pk2 = secp256k1.verify_recover(sig, dig)
|
||||
self.assertEqual(pk, pk2)
|
||||
|
||||
|
@ -295,7 +295,9 @@ def test_sign_native(curve, r):
|
||||
digest = r.randbytes(32)
|
||||
sig = r.randbytes(64)
|
||||
|
||||
lib.tc_ecdsa_sign_digest(curve.ptr, priv, digest, sig, c.c_void_p(0), c.c_void_p(0))
|
||||
lib.tc_ecdsa_sign_digest_recoverable(
|
||||
curve.ptr, priv, digest, sig, c.c_void_p(0), c.c_void_p(0)
|
||||
)
|
||||
|
||||
exp = bytes2num(priv)
|
||||
sk = ecdsa.SigningKey.from_secret_exponent(exp, curve, hashfunc=hashlib.sha256)
|
||||
@ -316,7 +318,7 @@ def test_sign_zkp(r):
|
||||
digest = r.randbytes(32)
|
||||
sig = r.randbytes(64)
|
||||
|
||||
lib.zkp_ecdsa_sign_digest(
|
||||
lib.zkp_ecdsa_sign_digest_recoverable(
|
||||
curve.ptr, priv, digest, sig, c.c_void_p(0), c.c_void_p(0)
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user