2018-06-21 14:28:34 +00:00
|
|
|
# This file is part of the Trezor project.
|
|
|
|
#
|
|
|
|
# Copyright (C) 2012-2018 SatoshiLabs and contributors
|
|
|
|
#
|
|
|
|
# This library is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Lesser General Public License version 3
|
|
|
|
# as published by the Free Software Foundation.
|
|
|
|
#
|
|
|
|
# This library is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Lesser General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the License along with this library.
|
|
|
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
|
|
|
|
2018-08-13 16:21:24 +00:00
|
|
|
from functools import reduce
|
2018-10-12 10:20:41 +00:00
|
|
|
from typing import Iterable, List, Tuple
|
2017-10-03 22:37:45 +00:00
|
|
|
|
2018-08-13 16:21:24 +00:00
|
|
|
from . import _ed25519, messages
|
2018-06-13 17:04:18 +00:00
|
|
|
from .tools import expect
|
|
|
|
|
2018-05-28 12:20:26 +00:00
|
|
|
# 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
|
2017-10-03 22:37:45 +00:00
|
|
|
|
2018-05-28 12:20:26 +00:00
|
|
|
|
|
|
|
def combine_keys(pks: Iterable[Ed25519PublicPoint]) -> Ed25519PublicPoint:
|
|
|
|
"""Combine a list of Ed25519 points into a "global" CoSi key."""
|
2018-05-25 11:07:02 +00:00
|
|
|
P = [_ed25519.decodepoint(pk) for pk in pks]
|
2018-10-12 10:20:41 +00:00
|
|
|
combine = reduce(_ed25519.edwards_add, P)
|
2018-05-28 12:20:26 +00:00
|
|
|
return Ed25519PublicPoint(_ed25519.encodepoint(combine))
|
2017-10-03 22:37:45 +00:00
|
|
|
|
|
|
|
|
2018-08-13 16:21:24 +00:00
|
|
|
def combine_sig(
|
|
|
|
global_R: Ed25519PublicPoint, sigs: Iterable[Ed25519Signature]
|
|
|
|
) -> Ed25519Signature:
|
2018-05-28 12:20:26 +00:00
|
|
|
"""Combine a list of signatures into a single CoSi signature."""
|
2018-05-25 11:07:02 +00:00
|
|
|
S = [_ed25519.decodeint(si) for si in sigs]
|
|
|
|
s = sum(S) % _ed25519.l
|
2018-05-28 12:20:26 +00:00
|
|
|
sig = global_R + _ed25519.encodeint(s)
|
|
|
|
return Ed25519Signature(sig)
|
|
|
|
|
|
|
|
|
2018-08-13 16:21:24 +00:00
|
|
|
def get_nonce(
|
|
|
|
sk: Ed25519PrivateKey, data: bytes, ctr: int = 0
|
|
|
|
) -> Tuple[int, Ed25519PublicPoint]:
|
2018-05-28 12:20:26 +00:00
|
|
|
"""Calculate CoSi nonces for given data.
|
|
|
|
These differ from Ed25519 deterministic nonces in that there is a counter appended at end.
|
2017-10-03 22:37:45 +00:00
|
|
|
|
2018-05-28 12:20:26 +00:00
|
|
|
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.
|
2017-10-03 22:37:45 +00:00
|
|
|
|
2018-05-28 12:20:26 +00:00
|
|
|
`R` should be combined with other partial signatures through :func:`combine_keys`
|
|
|
|
to obtain a "global commitment".
|
|
|
|
"""
|
2018-09-27 14:49:17 +00:00
|
|
|
# r = hash(hash(sk)[b .. 2b] + M + ctr)
|
|
|
|
# R = rB
|
2018-05-25 11:07:02 +00:00
|
|
|
h = _ed25519.H(sk)
|
2018-09-27 14:49:17 +00:00
|
|
|
bytesize = _ed25519.b // 8
|
|
|
|
assert len(h) == bytesize * 2
|
|
|
|
r = _ed25519.Hint(h[bytesize:] + data + ctr.to_bytes(4, "big"))
|
2018-05-25 11:07:02 +00:00
|
|
|
R = _ed25519.scalarmult(_ed25519.B, r)
|
2018-05-28 12:20:26 +00:00
|
|
|
return r, Ed25519PublicPoint(_ed25519.encodepoint(R))
|
2017-10-03 22:37:45 +00:00
|
|
|
|
|
|
|
|
2018-08-13 16:21:24 +00:00
|
|
|
def verify(
|
|
|
|
signature: Ed25519Signature, digest: bytes, pub_key: Ed25519PublicPoint
|
|
|
|
) -> None:
|
2018-05-28 12:20:26 +00:00
|
|
|
"""Verify Ed25519 signature. Raise exception if the signature is invalid."""
|
|
|
|
# XXX this *might* change to bool function
|
2018-05-25 11:07:02 +00:00
|
|
|
_ed25519.checkvalid(signature, digest, pub_key)
|
2018-05-17 10:53:01 +00:00
|
|
|
|
|
|
|
|
2018-10-12 10:20:41 +00:00
|
|
|
def verify_m_of_n(
|
|
|
|
signature: Ed25519Signature,
|
|
|
|
digest: bytes,
|
|
|
|
m: int,
|
|
|
|
n: int,
|
|
|
|
mask: int,
|
|
|
|
keys: List[Ed25519PublicPoint],
|
|
|
|
) -> None:
|
2018-10-12 10:49:40 +00:00
|
|
|
if m < 1:
|
|
|
|
raise ValueError("At least 1 signer must be specified")
|
2018-10-12 10:20:41 +00:00
|
|
|
selected_keys = [keys[i] for i in range(n) if mask & (1 << i)]
|
|
|
|
if len(selected_keys) < m:
|
|
|
|
raise ValueError(
|
|
|
|
"Not enough signers ({} required, {} found)".format(m, len(selected_keys))
|
|
|
|
)
|
|
|
|
global_pk = combine_keys(selected_keys)
|
|
|
|
return verify(signature, digest, global_pk)
|
|
|
|
|
|
|
|
|
2018-05-28 12:20:26 +00:00
|
|
|
def pubkey_from_privkey(privkey: Ed25519PrivateKey) -> Ed25519PublicPoint:
|
|
|
|
"""Interpret 32 bytes of data as an Ed25519 private key.
|
|
|
|
Calculate and return the corresponding public key.
|
|
|
|
"""
|
2018-10-12 10:20:41 +00:00
|
|
|
return Ed25519PublicPoint(_ed25519.publickey_unsafe(privkey))
|
2018-05-25 11:12:42 +00:00
|
|
|
|
|
|
|
|
2018-08-13 16:21:24 +00:00
|
|
|
def sign_with_privkey(
|
|
|
|
digest: bytes,
|
|
|
|
privkey: Ed25519PrivateKey,
|
|
|
|
global_pubkey: Ed25519PublicPoint,
|
|
|
|
nonce: int,
|
|
|
|
global_commit: Ed25519PublicPoint,
|
|
|
|
) -> Ed25519Signature:
|
2018-05-28 12:20:26 +00:00
|
|
|
"""Create a CoSi signature of `digest` with the supplied private key.
|
|
|
|
This function needs to know the global public key and global commitment.
|
|
|
|
"""
|
2018-09-27 14:49:17 +00:00
|
|
|
h = _ed25519.H(privkey)
|
2018-10-12 10:20:41 +00:00
|
|
|
a = _ed25519.decodecoord(h)
|
2018-09-27 14:49:17 +00:00
|
|
|
|
2018-05-25 11:07:02 +00:00
|
|
|
S = (nonce + _ed25519.Hint(global_commit + global_pubkey + digest) * a) % _ed25519.l
|
2018-05-28 12:20:26 +00:00
|
|
|
return Ed25519Signature(_ed25519.encodeint(S))
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
|
2018-08-10 14:05:14 +00:00
|
|
|
# ====== Client functions ====== #
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
@expect(messages.CosiCommitment)
|
|
|
|
def commit(client, n, data):
|
|
|
|
return client.call(messages.CosiCommit(address_n=n, data=data))
|
|
|
|
|
|
|
|
|
|
|
|
@expect(messages.CosiSignature)
|
|
|
|
def sign(client, n, data, global_commitment, global_pubkey):
|
2018-08-13 16:21:24 +00:00
|
|
|
return client.call(
|
|
|
|
messages.CosiSign(
|
|
|
|
address_n=n,
|
|
|
|
data=data,
|
|
|
|
global_commitment=global_commitment,
|
|
|
|
global_pubkey=global_pubkey,
|
|
|
|
)
|
|
|
|
)
|