import sys from functools import reduce import binascii from typing import Iterable, Tuple from trezorlib import _ed25519 # XXX, these could be NewType's, but that would infect users of the cosi module with these types as well. # Unsure if we want that. Ed25519PrivateKey = bytes Ed25519PublicPoint = bytes Ed25519Signature = bytes def combine_keys(pks: Iterable[Ed25519PublicPoint]) -> Ed25519PublicPoint: """Combine a list of Ed25519 points into a "global" CoSi key.""" P = [_ed25519.decodepoint(pk) for pk in pks] combine = reduce(_ed25519.edwards, P) return Ed25519PublicPoint(_ed25519.encodepoint(combine)) def combine_sig(global_R: Ed25519PublicPoint, sigs: Iterable[Ed25519Signature]) -> Ed25519Signature: """Combine a list of signatures into a single CoSi signature.""" S = [_ed25519.decodeint(si) for si in sigs] s = sum(S) % _ed25519.l sig = global_R + _ed25519.encodeint(s) return Ed25519Signature(sig) def get_nonce(sk: Ed25519PrivateKey, data: bytes, ctr: int = 0) -> Tuple[int, Ed25519PublicPoint]: """Calculate CoSi nonces for given data. These differ from Ed25519 deterministic nonces in that there is a counter appended at end. Returns both the private point `r` and the partial signature `R`. `r` is returned for performance reasons: :func:`sign_with_privkey` takes it as its `nonce` argument so that it doesn't repeat the `get_nonce` call. `R` should be combined with other partial signatures through :func:`combine_keys` to obtain a "global commitment". """ h = _ed25519.H(sk) b = _ed25519.b r = _ed25519.Hint(bytes([h[i] for i in range(b >> 3, b >> 2)]) + data + binascii.unhexlify('%08x' % ctr)) R = _ed25519.scalarmult(_ed25519.B, r) return r, Ed25519PublicPoint(_ed25519.encodepoint(R)) def verify(signature: Ed25519Signature, digest: bytes, pub_key: Ed25519PublicPoint) -> None: """Verify Ed25519 signature. Raise exception if the signature is invalid.""" # XXX this *might* change to bool function _ed25519.checkvalid(signature, digest, pub_key) def pubkey_from_privkey(privkey: Ed25519PrivateKey) -> Ed25519PublicPoint: """Interpret 32 bytes of data as an Ed25519 private key. Calculate and return the corresponding public key. """ return Ed25519PublicPoint(_ed25519.publickey(privkey)) def sign_with_privkey(digest: bytes, privkey: Ed25519PrivateKey, global_pubkey: Ed25519PublicPoint, nonce: int, global_commit: Ed25519PublicPoint) -> Ed25519Signature: """Create a CoSi signature of `digest` with the supplied private key. This function needs to know the global public key and global commitment. """ h = _ed25519.H(privkey) b = _ed25519.b a = 2 ** (b - 2) + sum(2 ** i * _ed25519.bit(h, i) for i in range(3, b - 2)) S = (nonce + _ed25519.Hint(global_commit + global_pubkey + digest) * a) % _ed25519.l return Ed25519Signature(_ed25519.encodeint(S))