mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-05-30 04:38:44 +00:00
feat(core): implement CoSi module and CoSi verification
This commit is contained in:
parent
e6a7b9ccaa
commit
dc5e4c1c8f
@ -75,6 +75,8 @@ trezor.crypto.bech32
|
|||||||
import trezor.crypto.bech32
|
import trezor.crypto.bech32
|
||||||
trezor.crypto.cashaddr
|
trezor.crypto.cashaddr
|
||||||
import trezor.crypto.cashaddr
|
import trezor.crypto.cashaddr
|
||||||
|
trezor.crypto.cosi
|
||||||
|
import trezor.crypto.cosi
|
||||||
trezor.crypto.curve
|
trezor.crypto.curve
|
||||||
import trezor.crypto.curve
|
import trezor.crypto.curve
|
||||||
trezor.crypto.der
|
trezor.crypto.der
|
||||||
|
@ -26,6 +26,7 @@ SCHEMA_SLIP26 = PathSchema.parse("m/10026'/[0-2139062143]'/[0-4]'/0'", slip44_id
|
|||||||
|
|
||||||
async def cosi_commit(ctx: Context, msg: CosiCommit) -> CosiSignature:
|
async def cosi_commit(ctx: Context, msg: CosiCommit) -> CosiSignature:
|
||||||
import storage.cache as storage_cache
|
import storage.cache as storage_cache
|
||||||
|
from trezor.crypto import cosi
|
||||||
from trezor.crypto.curve import ed25519
|
from trezor.crypto.curve import ed25519
|
||||||
from trezor.ui.layouts import confirm_blob
|
from trezor.ui.layouts import confirm_blob
|
||||||
from apps.common import paths
|
from apps.common import paths
|
||||||
@ -39,7 +40,7 @@ async def cosi_commit(ctx: Context, msg: CosiCommit) -> CosiSignature:
|
|||||||
pubkey = ed25519.publickey(seckey)
|
pubkey = ed25519.publickey(seckey)
|
||||||
|
|
||||||
if not storage_cache.is_set(storage_cache.APP_MISC_COSI_COMMITMENT):
|
if not storage_cache.is_set(storage_cache.APP_MISC_COSI_COMMITMENT):
|
||||||
nonce, commitment = ed25519.cosi_commit()
|
nonce, commitment = cosi.commit()
|
||||||
storage_cache.set(storage_cache.APP_MISC_COSI_NONCE, nonce)
|
storage_cache.set(storage_cache.APP_MISC_COSI_NONCE, nonce)
|
||||||
storage_cache.set(storage_cache.APP_MISC_COSI_COMMITMENT, commitment)
|
storage_cache.set(storage_cache.APP_MISC_COSI_COMMITMENT, commitment)
|
||||||
commitment = storage_cache.get(storage_cache.APP_MISC_COSI_COMMITMENT)
|
commitment = storage_cache.get(storage_cache.APP_MISC_COSI_COMMITMENT)
|
||||||
@ -69,7 +70,7 @@ async def cosi_commit(ctx: Context, msg: CosiCommit) -> CosiSignature:
|
|||||||
if nonce is None:
|
if nonce is None:
|
||||||
raise RuntimeError
|
raise RuntimeError
|
||||||
|
|
||||||
signature = ed25519.cosi_sign(
|
signature = cosi.sign(
|
||||||
seckey, sign_msg.data, nonce, sign_msg.global_commitment, sign_msg.global_pubkey
|
seckey, sign_msg.data, nonce, sign_msg.global_commitment, sign_msg.global_pubkey
|
||||||
)
|
)
|
||||||
return CosiSignature(signature=signature)
|
return CosiSignature(signature=signature)
|
||||||
|
37
core/src/trezor/crypto/cosi.py
Normal file
37
core/src/trezor/crypto/cosi.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
from .curve import ed25519
|
||||||
|
|
||||||
|
commit = ed25519.cosi_commit
|
||||||
|
sign = ed25519.cosi_sign
|
||||||
|
combine_publickeys = ed25519.cosi_combine_publickeys
|
||||||
|
combine_signatures = ed25519.cosi_combine_signatures
|
||||||
|
|
||||||
|
|
||||||
|
def select_keys(sigmask: int, keys: Sequence[bytes]) -> list[bytes]:
|
||||||
|
selected_keys = []
|
||||||
|
for key in keys:
|
||||||
|
if sigmask & 1:
|
||||||
|
selected_keys.append(key)
|
||||||
|
sigmask >>= 1
|
||||||
|
if sigmask:
|
||||||
|
raise ValueError # sigmask specifies more public keys than provided
|
||||||
|
return selected_keys
|
||||||
|
|
||||||
|
|
||||||
|
def verify(
|
||||||
|
signature: bytes,
|
||||||
|
data: bytes,
|
||||||
|
threshold: int,
|
||||||
|
keys: Sequence[bytes],
|
||||||
|
sigmask: int,
|
||||||
|
) -> bool:
|
||||||
|
if threshold < 1:
|
||||||
|
raise ValueError # at least one signer is required
|
||||||
|
|
||||||
|
selected_keys = select_keys(sigmask, keys)
|
||||||
|
if len(selected_keys) < threshold:
|
||||||
|
return False # insufficient number of signatures
|
||||||
|
|
||||||
|
global_pk = combine_publickeys(selected_keys)
|
||||||
|
return ed25519.verify(global_pk, signature, data)
|
95
core/tests/test_trezor.crypto.cosi.py
Normal file
95
core/tests/test_trezor.crypto.cosi.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
from common import *
|
||||||
|
|
||||||
|
from trezor.crypto import cosi, random
|
||||||
|
from trezor.crypto.curve import ed25519
|
||||||
|
|
||||||
|
|
||||||
|
class TestCryptoEd25519Cosi(unittest.TestCase):
|
||||||
|
def random_signers(self, N: int) -> tuple[list[bytes], list[bytes]]:
|
||||||
|
keys = []
|
||||||
|
pubkeys = []
|
||||||
|
for j in range(N):
|
||||||
|
keys.append(ed25519.generate_secret())
|
||||||
|
pubkeys.append(ed25519.publickey(keys[j]))
|
||||||
|
return keys, pubkeys
|
||||||
|
|
||||||
|
def cosign(
|
||||||
|
self, message: bytes, keys: list[bytes], pubkeys: list[bytes]
|
||||||
|
) -> tuple[bytes, bytes]:
|
||||||
|
# phase 0: combine pubkeys
|
||||||
|
pubkey = cosi.combine_publickeys(pubkeys)
|
||||||
|
|
||||||
|
# phase 1: create nonces, commitments (R values) and combine commitments
|
||||||
|
nonces = []
|
||||||
|
Rs = []
|
||||||
|
for _ in range(len(keys)):
|
||||||
|
nonce, R = cosi.commit()
|
||||||
|
nonces.append(nonce)
|
||||||
|
Rs.append(R)
|
||||||
|
|
||||||
|
R = ed25519.cosi_combine_publickeys(Rs)
|
||||||
|
|
||||||
|
# phase 2: sign and combine signatures
|
||||||
|
sigs = []
|
||||||
|
for key, nonce in zip(keys, nonces):
|
||||||
|
sigs.append(cosi.sign(key, message, nonce, R, pubkey))
|
||||||
|
|
||||||
|
return pubkey, cosi.combine_signatures(R, sigs)
|
||||||
|
|
||||||
|
def test_cosi(self):
|
||||||
|
for N in range(1, 11):
|
||||||
|
# generate random inputs
|
||||||
|
msg = random.bytes(128)
|
||||||
|
keys, pubkeys = self.random_signers(N)
|
||||||
|
|
||||||
|
# generate combined signature
|
||||||
|
pubkey, sig = self.cosign(msg, keys, pubkeys)
|
||||||
|
|
||||||
|
# check signature using normal ed25519.verify
|
||||||
|
res = ed25519.verify(pubkey, sig, msg)
|
||||||
|
self.assertTrue(res)
|
||||||
|
|
||||||
|
def test_select_keys(self):
|
||||||
|
keys, pubkeys = self.random_signers(5)
|
||||||
|
# try all possible combinations of signers = all 5-bit sigmasks
|
||||||
|
for sigmask in range(1, 32):
|
||||||
|
selected_keys = cosi.select_keys(sigmask, keys)
|
||||||
|
selected_pubkeys = cosi.select_keys(sigmask, pubkeys)
|
||||||
|
|
||||||
|
# check that the selection picks the right key combinations
|
||||||
|
for key, pubkey in zip(selected_keys, selected_pubkeys):
|
||||||
|
self.assertEqual(ed25519.publickey(key), pubkey)
|
||||||
|
|
||||||
|
# count number of ones
|
||||||
|
count = 0
|
||||||
|
for i in range(5):
|
||||||
|
if sigmask & (1 << i):
|
||||||
|
count += 1
|
||||||
|
self.assertEqual(len(selected_keys), count)
|
||||||
|
self.assertEqual(len(selected_pubkeys), count)
|
||||||
|
|
||||||
|
def test_verify(self):
|
||||||
|
keys, all_pubkeys = self.random_signers(5)
|
||||||
|
# try all possible combinations of signers = all 5-bit sigmasks
|
||||||
|
for sigmask in range(1, 32):
|
||||||
|
message = random.bytes(128)
|
||||||
|
|
||||||
|
selected_keys = cosi.select_keys(sigmask, keys)
|
||||||
|
selected_pubkeys = cosi.select_keys(sigmask, all_pubkeys)
|
||||||
|
|
||||||
|
_, sig = self.cosign(message, selected_keys, selected_pubkeys)
|
||||||
|
|
||||||
|
res = cosi.verify(sig, message, len(selected_keys), all_pubkeys, sigmask)
|
||||||
|
self.assertTrue(res)
|
||||||
|
|
||||||
|
res = cosi.verify(sig, message, 1, all_pubkeys, sigmask)
|
||||||
|
self.assertTrue(res)
|
||||||
|
|
||||||
|
res = cosi.verify(
|
||||||
|
sig, message, len(selected_keys) + 1, all_pubkeys, sigmask
|
||||||
|
)
|
||||||
|
self.assertFalse(res)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
@ -1,46 +0,0 @@
|
|||||||
from common import *
|
|
||||||
|
|
||||||
from trezor.crypto import random
|
|
||||||
from trezor.crypto.curve import ed25519
|
|
||||||
|
|
||||||
|
|
||||||
class TestCryptoEd25519Cosi(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_cosi(self):
|
|
||||||
|
|
||||||
for N in range(1, 11):
|
|
||||||
|
|
||||||
# generate random message to be signed
|
|
||||||
msg = random.bytes(128)
|
|
||||||
|
|
||||||
# phase 0: create priv/pubkeys and combine pubkeys
|
|
||||||
keys = [None] * N
|
|
||||||
pubkeys = [None] * N
|
|
||||||
for j in range(N):
|
|
||||||
keys[j] = ed25519.generate_secret()
|
|
||||||
pubkeys[j] = ed25519.publickey(keys[j])
|
|
||||||
|
|
||||||
pubkey = ed25519.cosi_combine_publickeys(pubkeys)
|
|
||||||
|
|
||||||
# phase 1: create nonces, commitments (R values) and combine commitments
|
|
||||||
nonces = [None] * N
|
|
||||||
Rs = [None] * N
|
|
||||||
for j in range(N):
|
|
||||||
nonces[j], Rs[j] = ed25519.cosi_commit()
|
|
||||||
|
|
||||||
R = ed25519.cosi_combine_publickeys(Rs)
|
|
||||||
|
|
||||||
# phase 2: sign and combine signatures
|
|
||||||
sigs = [None] * N
|
|
||||||
for j in range(N):
|
|
||||||
sigs[j] = ed25519.cosi_sign(keys[j], msg, nonces[j], R, pubkey)
|
|
||||||
|
|
||||||
sig = ed25519.cosi_combine_signatures(R, sigs)
|
|
||||||
|
|
||||||
# check signature using normal ed25519.verify
|
|
||||||
res = ed25519.verify(pubkey, sig, msg)
|
|
||||||
self.assertTrue(res)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
Loading…
Reference in New Issue
Block a user