Merge pull request #50 from ZcashFoundation/frost-core
* frost-core Cargo.toml * Ciphersuite trait * Signature trait * Copy stub ristretto impl for now * First stab at making signing and verifying generic over frost-core::Ciphersuite * Update signing * Nice const generics and stuff for frost-core::Ciphersuite * Have to implement traits for the pre-parameterized types inside the module * Ciphersuite::Group::Field * Make frost/keys generic over Ciphersuite * frost-core genericization mostly done, modulo batch * Move tests around * Remove internal test module * Lots of tidies, including type refinement of Scalar, Challenge * More genericization and tidy'ing * Test vectors working against Ristretto impl in the frost-core integration tests * clippy fix * Fix generic params for full frost example integration test using ristretto * Genericize proptests * clippy --fix * Doc comment identifier module * In-flight batch and multiscalar mul * Stop using Scalar::from_hash() as it expects impl Digest which sha2 0.10+ isn't doing anymore * run cargo udeps * Update frost-core/src/frost/round1.rs * Update frost-core/src/frost.rs * Update frost-core/src/frost/keys.rs
This commit is contained in:
parent
2c86646e80
commit
e6d5afdfb2
|
@ -1,8 +1,40 @@
|
||||||
[package]
|
[package]
|
||||||
name = "frost-core"
|
name = "frost-core"
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
# When releasing to crates.io:
|
||||||
|
# - Update html_root_url
|
||||||
|
# - Update CHANGELOG.md
|
||||||
|
# - Create git tag.
|
||||||
|
version = "0.1.0-alpha.1"
|
||||||
|
authors = ["Deirdre Connolly <durumcrustulum@gmail.com>", "Chelsea Komlo <me@chelseakomlo.com>"]
|
||||||
|
readme = "README.md"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
repository = "https://github.com/ZcashFoundation/frost"
|
||||||
|
categories = ["cryptography"]
|
||||||
|
keywords = ["cryptography", "crypto", "ristretto", "threshold", "signature", "schnorr"]
|
||||||
|
description = "Types and traits to support implementing Flexible Round-Optimized Schnorr Threshold signature schemes (FROST)."
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
[package.metadata.docs.rs]
|
||||||
|
features = ["nightly"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
byteorder = "1.4"
|
||||||
|
digest = "0.9"
|
||||||
|
hex = { version = "0.4.3", features = ["serde"] }
|
||||||
|
rand_core = "0.6"
|
||||||
|
serde = { version = "1", optional = true, features = ["derive"] }
|
||||||
|
thiserror = "1.0"
|
||||||
|
zeroize = { version = "1.5.4", default-features = false, features = ["derive"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
curve25519-dalek = { version = "4.0.0-pre.1", features = ["serde"] }
|
||||||
|
lazy_static = "1.4"
|
||||||
|
proptest = "1.0"
|
||||||
|
rand = "0.8"
|
||||||
|
rand_chacha = "0.3"
|
||||||
|
serde_json = "1.0"
|
||||||
|
sha2 = "0.10.2"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
nightly = []
|
||||||
|
default = ["serde"]
|
||||||
|
|
|
@ -21,9 +21,6 @@ dependency, such as `frost-ristretto255`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
//! Performs batch Schnorr signature verification.
|
||||||
|
//!
|
||||||
|
//! Batch verification asks whether *all* signatures in some set are valid,
|
||||||
|
//! rather than asking whether *each* of them is valid. This allows sharing
|
||||||
|
//! computations among all signature verifications, performing less work overall
|
||||||
|
//! at the cost of higher latency (the entire batch must complete), complexity
|
||||||
|
//! of caller code (which must assemble a batch of signatures across
|
||||||
|
//! work-items), and loss of the ability to easily pinpoint failing signatures.
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
frost::{self, *},
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A batch verification item.
|
||||||
|
///
|
||||||
|
/// This struct exists to allow batch processing to be decoupled from the
|
||||||
|
/// lifetime of the message. This is useful when using the batch verification
|
||||||
|
/// API in an async context.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Item<C: Ciphersuite> {
|
||||||
|
vk: VerifyingKey<C>,
|
||||||
|
sig: Signature<C>,
|
||||||
|
c: Challenge<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'msg, C, M> From<(VerifyingKey<C>, Signature<C>, &'msg M)> for Item<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
M: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
fn from((vk, sig, msg): (VerifyingKey<C>, Signature<C>, &'msg M)) -> Self {
|
||||||
|
// Compute c now to avoid dependency on the msg lifetime.
|
||||||
|
|
||||||
|
let c = crate::challenge(&sig.R, &vk.element, msg.as_ref());
|
||||||
|
|
||||||
|
Self { vk, sig, c }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Item<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Perform non-batched verification of this `Item`.
|
||||||
|
///
|
||||||
|
/// This is useful (in combination with `Item::clone`) for implementing
|
||||||
|
/// fallback logic when batch verification fails. In contrast to
|
||||||
|
/// [`VerifyingKey::verify`](crate::VerifyingKey::verify), which
|
||||||
|
/// requires borrowing the message data, the `Item` type is unlinked
|
||||||
|
/// from the lifetime of the message.
|
||||||
|
pub fn verify_single(self) -> Result<(), Error> {
|
||||||
|
VerifyingKey::try_from(self.vk_bytes).and_then(|vk| vk.verify_prehashed(&self.sig, self.c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
/// A batch verification context.
|
||||||
|
pub struct Verifier<C: Ciphersuite> {
|
||||||
|
/// Signature data queued for verification.
|
||||||
|
signatures: Vec<Item<C>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Verifier<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Constructs a new batch verifier.
|
||||||
|
pub fn new() -> Verifier<C> {
|
||||||
|
Verifier::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Queues an Item for verification.
|
||||||
|
pub fn queue<I: Into<Item<C>>>(&mut self, item: I) {
|
||||||
|
self.signatures.push(item.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs batch verification, returning `Ok(())` if all signatures were
|
||||||
|
/// valid and `Err` otherwise.
|
||||||
|
///
|
||||||
|
/// The batch verification equation is:
|
||||||
|
///
|
||||||
|
/// h_G * -[sum(z_i * s_i)]P_G + sum(\[z_i\]R_i + [z_i * c_i]VK_i) = 0_G
|
||||||
|
///
|
||||||
|
/// which we split out into:
|
||||||
|
///
|
||||||
|
/// h_G * -[sum(z_i * s_i)]P_G + sum(\[z_i\]R_i) + sum([z_i * c_i]VK_i) =
|
||||||
|
/// 0_G
|
||||||
|
///
|
||||||
|
/// so that we can use multiscalar multiplication speedups.
|
||||||
|
///
|
||||||
|
/// where for each signature i,
|
||||||
|
/// - VK_i is the verification key;
|
||||||
|
/// - R_i is the signature's R value;
|
||||||
|
/// - s_i is the signature's s value;
|
||||||
|
/// - c_i is the hash of the message and other data;
|
||||||
|
/// - z_i is a random 128-bit Scalar;
|
||||||
|
/// - h_G is the cofactor of the group;
|
||||||
|
/// - P_G is the generator of the subgroup;
|
||||||
|
///
|
||||||
|
/// As follows elliptic curve scalar multiplication convention,
|
||||||
|
/// scalar variables are lowercase and group point variables
|
||||||
|
/// are uppercase. This does not exactly match the RedDSA
|
||||||
|
/// notation in the [protocol specification §B.1][ps].
|
||||||
|
///
|
||||||
|
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#reddsabatchverify
|
||||||
|
pub fn verify<R: RngCore + CryptoRng>(self, mut rng: R) -> Result<(), Error> {
|
||||||
|
let n = self.signatures.len();
|
||||||
|
|
||||||
|
let mut VK_coeffs = Vec::with_capacity(n);
|
||||||
|
let mut VKs = Vec::with_capacity(n);
|
||||||
|
let mut R_coeffs = Vec::with_capacity(self.signatures.len());
|
||||||
|
let mut Rs = Vec::with_capacity(self.signatures.len());
|
||||||
|
let mut P_coeff_acc = Scalar::zero();
|
||||||
|
|
||||||
|
for item in self.signatures.iter() {
|
||||||
|
let (z_bytes, R_bytes, c) = (item.sig.z_bytes, item.sig.R_bytes, item.c);
|
||||||
|
|
||||||
|
let s = Scalar::from_bytes_mod_order(z_bytes);
|
||||||
|
|
||||||
|
let R = {
|
||||||
|
match CompressedRistretto::from_slice(&R_bytes).decompress() {
|
||||||
|
Some(point) => point,
|
||||||
|
None => return Err(Error::InvalidSignature),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let VK = VerifyingKey::try_from(item.vk_bytes.bytes)?.point;
|
||||||
|
|
||||||
|
let z = Scalar::random(&mut rng);
|
||||||
|
|
||||||
|
let P_coeff = z * s;
|
||||||
|
P_coeff_acc -= P_coeff;
|
||||||
|
|
||||||
|
R_coeffs.push(z);
|
||||||
|
Rs.push(R);
|
||||||
|
|
||||||
|
VK_coeffs.push(Scalar::zero() + (z * c));
|
||||||
|
VKs.push(VK);
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
let scalars = once(&P_coeff_acc)
|
||||||
|
.chain(VK_coeffs.iter())
|
||||||
|
.chain(R_coeffs.iter());
|
||||||
|
|
||||||
|
let basepoints = [curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT];
|
||||||
|
let points = basepoints.iter().chain(VKs.iter()).chain(Rs.iter());
|
||||||
|
|
||||||
|
let check = RistrettoPoint::vartime_multiscalar_mul(scalars, points);
|
||||||
|
|
||||||
|
if check == RistrettoPoint::identity() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidSignature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
//! FROST Error types
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// An error related to FROST.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum Error {
|
||||||
|
/// This identifier is unserializable.
|
||||||
|
#[error("Malformed identifier is unserializable.")]
|
||||||
|
MalformedIdentifier,
|
||||||
|
/// The encoding of a group scalar was malformed.
|
||||||
|
#[error("Malformed scalar encoding.")]
|
||||||
|
MalformedScalar,
|
||||||
|
/// The encoding of a group element was malformed.
|
||||||
|
#[error("Malformed group element encoding.")]
|
||||||
|
MalformedElement,
|
||||||
|
/// The encoding of a signing key was malformed.
|
||||||
|
#[error("Malformed signing key encoding.")]
|
||||||
|
MalformedSigningKey,
|
||||||
|
/// The encoding of a verifying key was malformed.
|
||||||
|
#[error("Malformed verifying key encoding.")]
|
||||||
|
MalformedVerifyingKey,
|
||||||
|
/// The encoding of a signature was malformed.
|
||||||
|
#[error("Malformed signature encoding.")]
|
||||||
|
MalformedSignature,
|
||||||
|
/// Signature verification failed.
|
||||||
|
#[error("Invalid signature.")]
|
||||||
|
InvalidSignature,
|
||||||
|
/// This scalar MUST NOT be zero.
|
||||||
|
#[error("Invalid for this scalar to be zero.")]
|
||||||
|
InvalidZeroScalar,
|
||||||
|
}
|
|
@ -0,0 +1,343 @@
|
||||||
|
//! An implementation of FROST (Flexible Round-Optimized Schnorr Threshold)
|
||||||
|
//! signatures.
|
||||||
|
//!
|
||||||
|
//! If you are interested in deploying FROST, please do not hesitate to consult the FROST authors.
|
||||||
|
//!
|
||||||
|
//! This implementation currently only supports key generation using a central
|
||||||
|
//! dealer. In the future, we will add support for key generation via a DKG,
|
||||||
|
//! as specified in the FROST paper.
|
||||||
|
//!
|
||||||
|
//! Internally, keygen_with_dealer generates keys using Verifiable Secret
|
||||||
|
//! Sharing, where shares are generated using Shamir Secret Sharing.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
convert::TryFrom,
|
||||||
|
fmt::{self, Debug},
|
||||||
|
};
|
||||||
|
|
||||||
|
use hex::FromHex;
|
||||||
|
|
||||||
|
mod identifier;
|
||||||
|
pub mod keys;
|
||||||
|
pub mod round1;
|
||||||
|
pub mod round2;
|
||||||
|
|
||||||
|
use crate::{Ciphersuite, Error, Field, Group, Signature};
|
||||||
|
|
||||||
|
pub use self::identifier::Identifier;
|
||||||
|
|
||||||
|
/// The binding factor, also known as _rho_ (ρ)
|
||||||
|
///
|
||||||
|
/// Ensures each signature share is strongly bound to a signing set, specific set
|
||||||
|
/// of commitments, and a specific message.
|
||||||
|
///
|
||||||
|
/// <https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md>
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct Rho<C: Ciphersuite>(<<C::Group as Group>::Field as Field>::Scalar);
|
||||||
|
|
||||||
|
impl<C> Rho<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Deserializes [`Rho`] from bytes.
|
||||||
|
pub fn from_bytes(
|
||||||
|
bytes: <<C::Group as Group>::Field as Field>::Serialization,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
<<C::Group as Group>::Field as Field>::deserialize(&bytes).map(|scalar| Self(scalar))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serializes [`Rho`] to bytes.
|
||||||
|
pub fn to_bytes(&self) -> <<C::Group as Group>::Field as Field>::Serialization {
|
||||||
|
<<C::Group as Group>::Field as Field>::serialize(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Debug for Rho<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_tuple("Rho")
|
||||||
|
.field(&hex::encode(self.to_bytes()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> From<&SigningPackage<C>> for Rho<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
// [`compute_binding_factor`] in the spec
|
||||||
|
//
|
||||||
|
// [`compute_binding_factor`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-4.4
|
||||||
|
fn from(signing_package: &SigningPackage<C>) -> Rho<C> {
|
||||||
|
let preimage = signing_package.rho_preimage();
|
||||||
|
|
||||||
|
let binding_factor = C::H1(&preimage[..]);
|
||||||
|
|
||||||
|
Rho(binding_factor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> FromHex for Rho<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
|
||||||
|
match FromHex::from_hex(hex) {
|
||||||
|
Ok(bytes) => Self::from_bytes(bytes).map_err(|_| "malformed scalar encoding"),
|
||||||
|
Err(_) => Err("invalid hex"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: pub struct Lagrange<C: Ciphersuite>(Scalar);
|
||||||
|
|
||||||
|
/// Generates the lagrange coefficient for the i'th participant.
|
||||||
|
fn derive_lagrange_coeff<C: Ciphersuite>(
|
||||||
|
signer_id: u16,
|
||||||
|
signing_package: &SigningPackage<C>,
|
||||||
|
) -> Result<<<C::Group as Group>::Field as Field>::Scalar, &'static str> {
|
||||||
|
// This should fail and panic if signer_id_scalar is 0 in the scalar field.
|
||||||
|
let signer_id_scalar = Identifier::<C>::try_from(signer_id).unwrap();
|
||||||
|
|
||||||
|
let zero = <<C::Group as Group>::Field as Field>::zero();
|
||||||
|
|
||||||
|
// TODO: This is redundant
|
||||||
|
if signer_id_scalar.0 == zero {
|
||||||
|
return Err("Invalid parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
if signing_package
|
||||||
|
.signing_commitments()
|
||||||
|
.iter()
|
||||||
|
.any(|commitment| {
|
||||||
|
let commitment_id_scalar = Identifier::<C>::try_from(commitment.index).unwrap();
|
||||||
|
|
||||||
|
*commitment_id_scalar == zero
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return Err("Invalid parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut num = <<C::Group as Group>::Field as Field>::one();
|
||||||
|
let mut den = <<C::Group as Group>::Field as Field>::one();
|
||||||
|
|
||||||
|
// Ala the sorting of B, just always sort by index in ascending order
|
||||||
|
//
|
||||||
|
// https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#encoding-operations-dep-encoding
|
||||||
|
for commitment in signing_package.signing_commitments() {
|
||||||
|
if commitment.index == signer_id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let commitment_id_scalar = Identifier::<C>::try_from(commitment.index).unwrap();
|
||||||
|
|
||||||
|
num = num * *commitment_id_scalar;
|
||||||
|
den = den * (*commitment_id_scalar - *signer_id_scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
if den == zero {
|
||||||
|
return Err("Duplicate shares provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(dconnolly): return this error if the inversion result == zero
|
||||||
|
let lagrange_coeff = num * <<C::Group as Group>::Field as Field>::invert(&den).unwrap();
|
||||||
|
|
||||||
|
Ok(lagrange_coeff)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generated by the coordinator of the signing operation and distributed to
|
||||||
|
/// each signing party
|
||||||
|
pub struct SigningPackage<C: Ciphersuite> {
|
||||||
|
/// The set of commitments participants published in the first round of the
|
||||||
|
/// protocol.
|
||||||
|
signing_commitments: HashMap<u16, round1::SigningCommitments<C>>,
|
||||||
|
/// Message which each participant will sign.
|
||||||
|
///
|
||||||
|
/// Each signer should perform protocol-specific verification on the
|
||||||
|
/// message.
|
||||||
|
message: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> SigningPackage<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Create a new `SigingPackage`
|
||||||
|
///
|
||||||
|
/// The `signing_commitments` are sorted by participant `index`.
|
||||||
|
pub fn new(
|
||||||
|
mut signing_commitments: Vec<round1::SigningCommitments<C>>,
|
||||||
|
message: Vec<u8>,
|
||||||
|
) -> SigningPackage<C> {
|
||||||
|
signing_commitments.sort_by_key(|a| a.index);
|
||||||
|
|
||||||
|
SigningPackage {
|
||||||
|
signing_commitments: signing_commitments
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| (s.index, s))
|
||||||
|
.collect(),
|
||||||
|
message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a signing commitment by its participant index.
|
||||||
|
pub fn signing_commitment(&self, index: &u16) -> round1::SigningCommitments<C> {
|
||||||
|
self.signing_commitments[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the signing commitments, sorted by the participant indices
|
||||||
|
pub fn signing_commitments(&self) -> Vec<round1::SigningCommitments<C>> {
|
||||||
|
let mut signing_commitments: Vec<round1::SigningCommitments<C>> =
|
||||||
|
self.signing_commitments.values().cloned().collect();
|
||||||
|
signing_commitments.sort_by_key(|a| a.index);
|
||||||
|
signing_commitments
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the message to be signed
|
||||||
|
pub fn message(&self) -> &Vec<u8> {
|
||||||
|
&self.message
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the preimage to H3 to compute rho
|
||||||
|
// We separate this out into its own method so it can be tested
|
||||||
|
pub fn rho_preimage(&self) -> Vec<u8> {
|
||||||
|
let mut preimage = vec![];
|
||||||
|
|
||||||
|
preimage
|
||||||
|
.extend_from_slice(&round1::encode_group_commitments(self.signing_commitments())[..]);
|
||||||
|
preimage.extend_from_slice(C::H3(self.message.as_slice()).as_ref());
|
||||||
|
|
||||||
|
preimage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The product of all signers' individual commitments, published as part of the
|
||||||
|
/// final signature.
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub struct GroupCommitment<C: Ciphersuite>(pub(super) <C::Group as Group>::Element);
|
||||||
|
|
||||||
|
// impl<C> Debug for GroupCommitment<C> where C: Ciphersuite {
|
||||||
|
// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// f.debug_tuple("GroupCommitment")
|
||||||
|
// .field(&hex::encode(self.0.compress().to_bytes()))
|
||||||
|
// .finish()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
impl<C> TryFrom<&SigningPackage<C>> for GroupCommitment<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
/// Generates the group commitment which is published as part of the joint
|
||||||
|
/// Schnorr signature.
|
||||||
|
///
|
||||||
|
/// Implements [`compute_group_commitment`] from the spec.
|
||||||
|
///
|
||||||
|
/// [`compute_group_commitment`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-4.4
|
||||||
|
fn try_from(signing_package: &SigningPackage<C>) -> Result<GroupCommitment<C>, &'static str> {
|
||||||
|
let rho: Rho<C> = signing_package.into();
|
||||||
|
|
||||||
|
let identity = <C::Group as Group>::identity();
|
||||||
|
|
||||||
|
let mut accumulator = <C::Group as Group>::identity();
|
||||||
|
|
||||||
|
// Ala the sorting of B, just always sort by index in ascending order
|
||||||
|
//
|
||||||
|
// https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#encoding-operations-dep-encoding
|
||||||
|
for commitment in signing_package.signing_commitments() {
|
||||||
|
// The following check prevents a party from accidentally revealing their share.
|
||||||
|
// Note that the '&&' operator would be sufficient.
|
||||||
|
if identity == commitment.binding.0 || identity == commitment.hiding.0 {
|
||||||
|
return Err("Commitment equals the identity.");
|
||||||
|
}
|
||||||
|
|
||||||
|
accumulator = accumulator + (commitment.hiding.0 + (commitment.binding.0 * rho.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(GroupCommitment(accumulator))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Aggregation
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Verifies each participant's signature share, and if all are valid,
|
||||||
|
/// aggregates the shares into a signature to publish.
|
||||||
|
///
|
||||||
|
/// Resulting signature is compatible with verification of a plain SpendAuth
|
||||||
|
/// signature.
|
||||||
|
///
|
||||||
|
/// This operation is performed by a coordinator that can communicate with all
|
||||||
|
/// the signing participants before publishing the final signature. The
|
||||||
|
/// coordinator can be one of the participants or a semi-trusted third party
|
||||||
|
/// (who is trusted to not perform denial of service attacks, but does not learn
|
||||||
|
/// any secret information). Note that because the coordinator is trusted to
|
||||||
|
/// report misbehaving parties in order to avoid publishing an invalid
|
||||||
|
/// signature, if the coordinator themselves is a signer and misbehaves, they
|
||||||
|
/// can avoid that step. However, at worst, this results in a denial of
|
||||||
|
/// service attack due to publishing an invalid signature.
|
||||||
|
pub fn aggregate<C>(
|
||||||
|
signing_package: &SigningPackage<C>,
|
||||||
|
signature_shares: &[round2::SignatureShare<C>],
|
||||||
|
pubkeys: &keys::PublicKeyPackage<C>,
|
||||||
|
) -> Result<Signature<C>, &'static str>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
// Encodes the signing commitment list produced in round one as part of generating [`Rho`], the
|
||||||
|
// binding factor.
|
||||||
|
let rho: Rho<C> = signing_package.into();
|
||||||
|
|
||||||
|
// Compute the group commitment from signing commitments produced in round one.
|
||||||
|
let group_commitment = GroupCommitment::<C>::try_from(signing_package)?;
|
||||||
|
|
||||||
|
// Compute the per-message challenge.
|
||||||
|
let challenge = crate::challenge::<C>(
|
||||||
|
&group_commitment.0,
|
||||||
|
&pubkeys.group_public.element,
|
||||||
|
signing_package.message().as_slice(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the signature shares.
|
||||||
|
for signature_share in signature_shares {
|
||||||
|
// Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_,
|
||||||
|
// and where s[i] is a secret share of the constant term of _f_, the secret polynomial.
|
||||||
|
let signer_pubkey = pubkeys.signer_pubkeys.get(&signature_share.index).unwrap();
|
||||||
|
|
||||||
|
// Compute Lagrange coefficient.
|
||||||
|
let lambda_i = derive_lagrange_coeff(signature_share.index, signing_package)?;
|
||||||
|
|
||||||
|
// Compute the commitment share.
|
||||||
|
let R_share = signing_package
|
||||||
|
.signing_commitment(&signature_share.index)
|
||||||
|
.to_group_commitment_share(&rho);
|
||||||
|
|
||||||
|
// Compute relation values to verify this signature share.
|
||||||
|
signature_share.verify(&R_share, signer_pubkey, lambda_i, &challenge)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The aggregation of the signature shares by summing them up, resulting in
|
||||||
|
// a plain Schnorr signature.
|
||||||
|
//
|
||||||
|
// Implements [`frost_aggregate`] from the spec.
|
||||||
|
//
|
||||||
|
// [`frost_aggregate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-5.3-4
|
||||||
|
let mut z = <<C::Group as Group>::Field as Field>::zero();
|
||||||
|
|
||||||
|
for signature_share in signature_shares {
|
||||||
|
z = z + signature_share.signature.z_share;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Signature {
|
||||||
|
R: group_commitment.0,
|
||||||
|
z,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
//! FROST participant identifiers
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fmt::{self, Debug},
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
ops::{Deref, Index},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{Ciphersuite, Error, Field, Group, Scalar};
|
||||||
|
|
||||||
|
/// A FROST participant identifier.
|
||||||
|
///
|
||||||
|
/// The identifier is a field element in the scalar field that the secret polynomial is defined
|
||||||
|
/// over, corresponding to some x-coordinate for a polynomial f(x) = y. MUST NOT be zero in the
|
||||||
|
/// field, as f(0) = the shared secret.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Identifier<C: Ciphersuite>(pub(crate) Scalar<C>);
|
||||||
|
|
||||||
|
impl<C> AsRef<Scalar<C>> for Identifier<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn as_ref(&self) -> &Scalar<C> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Debug for Identifier<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_tuple("Identifier")
|
||||||
|
.field(&usize::from(*self))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Deref for Identifier<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
type Target = Scalar<C>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl<C> Deref for &Identifier<C>
|
||||||
|
// where
|
||||||
|
// C: Ciphersuite,
|
||||||
|
// {
|
||||||
|
// type Target = Scalar<C>;
|
||||||
|
|
||||||
|
// fn deref(&self) -> &Self::Target {
|
||||||
|
// &self.0
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
impl<C> Eq for Identifier<C> where C: Ciphersuite {}
|
||||||
|
|
||||||
|
impl<C> From<Identifier<C>> for usize
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
// TODO: this feels janky, are we confident we aren't clamping off the higher byte values?
|
||||||
|
fn from(id: Identifier<C>) -> usize {
|
||||||
|
// This is 8 bytes because usize is up to 8 bytes depending on the platform.
|
||||||
|
//
|
||||||
|
// https://doc.rust-lang.org/stable/std/primitive.usize.html#method.from_le_bytes
|
||||||
|
let mut bytes = [0u8; 8];
|
||||||
|
|
||||||
|
let serialized = <<C::Group as Group>::Field as Field>::serialize(&id.0);
|
||||||
|
|
||||||
|
for i in 0..bytes.len() {
|
||||||
|
bytes[i] = serialized.as_ref()[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
usize::from_le_bytes(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Hash for Identifier<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
<<C::Group as Group>::Field as Field>::serialize(&self.0)
|
||||||
|
.as_ref()
|
||||||
|
.hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, T> Index<Identifier<C>> for Vec<T>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn index(&self, id: Identifier<C>) -> &Self::Output {
|
||||||
|
&self[usize::from(id)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl<C> std::ops::Mul for Identifier<C>
|
||||||
|
// where
|
||||||
|
// C: Ciphersuite,
|
||||||
|
// {
|
||||||
|
// type Output = Self;
|
||||||
|
|
||||||
|
// fn mul(self, rhs: Identifier<C>) -> Self::Output {
|
||||||
|
// Self(self.0 * rhs.0)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl<C> std::ops::Mul<Scalar<C>> for Identifier<C>
|
||||||
|
// where
|
||||||
|
// C: Ciphersuite,
|
||||||
|
// {
|
||||||
|
// type Output = Scalar<C>;
|
||||||
|
|
||||||
|
// fn mul(self, scalar: Scalar<C>) -> Scalar<C> {
|
||||||
|
// self.0 * scalar
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl<'a, 'b, C> std::ops::Mul<&'b Identifier<C>> for &'a Scalar<C>
|
||||||
|
// where
|
||||||
|
// C: Ciphersuite,
|
||||||
|
// {
|
||||||
|
// type Output = Scalar<C>;
|
||||||
|
|
||||||
|
// fn mul(self, id: &'b Identifier<C>) -> Scalar<C> {
|
||||||
|
// self * id.0
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
impl<C> PartialEq for Identifier<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.0 == other.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl<C> std::ops::Sub for Identifier<C>
|
||||||
|
// where
|
||||||
|
// C: Ciphersuite,
|
||||||
|
// {
|
||||||
|
// type Output = Self;
|
||||||
|
|
||||||
|
// fn sub(self, rhs: Identifier<C>) -> Self::Output {
|
||||||
|
// Self(self.0 - rhs.0)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl<C> std::ops::Sub<Scalar<C>> for Identifier<C>
|
||||||
|
// where
|
||||||
|
// C: Ciphersuite,
|
||||||
|
// {
|
||||||
|
// type Output = Scalar<C>;
|
||||||
|
|
||||||
|
// fn sub(self, scalar: Scalar<C>) -> Scalar<C> {
|
||||||
|
// self.0 - scalar
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
impl<C> TryFrom<u16> for Identifier<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
// TODO: this feels like a cluster. Improve?
|
||||||
|
fn try_from(n: u16) -> Result<Identifier<C>, Self::Error> {
|
||||||
|
let mut bytes =
|
||||||
|
Vec::from(<<C::Group as Group>::Field as Field>::Serialization::default().as_ref());
|
||||||
|
|
||||||
|
for (i, byte) in n.to_le_bytes().iter().enumerate() {
|
||||||
|
bytes[i] = *byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
let serialization = bytes
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Self::Error::MalformedIdentifier)?;
|
||||||
|
|
||||||
|
let scalar = <<C::Group as Group>::Field as Field>::deserialize(&serialization)?;
|
||||||
|
|
||||||
|
// Participant identifiers are public, so this comparison doesn't need to be constant-time.
|
||||||
|
if scalar == <<C::Group as Group>::Field as Field>::zero() {
|
||||||
|
Err(Self::Error::InvalidZeroScalar)
|
||||||
|
} else {
|
||||||
|
Ok(Self(scalar))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,513 @@
|
||||||
|
//! FROST keys, keygen, key shares
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
convert::TryFrom,
|
||||||
|
default::Default,
|
||||||
|
fmt::{self, Debug},
|
||||||
|
};
|
||||||
|
|
||||||
|
use hex::FromHex;
|
||||||
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
use zeroize::{DefaultIsZeroes, Zeroize};
|
||||||
|
|
||||||
|
use crate::{frost::Identifier, Ciphersuite, Error, Field, Group, Scalar, VerifyingKey};
|
||||||
|
|
||||||
|
/// A secret scalar value representing a signer's secret key.
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub struct Secret<C: Ciphersuite>(pub(crate) Scalar<C>);
|
||||||
|
|
||||||
|
impl<C> Secret<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Deserialize [`Secret`] from bytes
|
||||||
|
pub fn from_bytes(
|
||||||
|
bytes: <<C::Group as Group>::Field as Field>::Serialization,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
<<C::Group as Group>::Field as Field>::deserialize(&bytes).map(|scalar| Self(scalar))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize [`Secret`] to bytes
|
||||||
|
pub fn to_bytes(&self) -> <<C::Group as Group>::Field as Field>::Serialization {
|
||||||
|
<<C::Group as Group>::Field as Field>::serialize(&self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a new uniformly random secret value using the provided RNG.
|
||||||
|
// TODO: should this only be behind test?
|
||||||
|
pub fn random<R>(mut rng: R) -> Self
|
||||||
|
where
|
||||||
|
R: CryptoRng + RngCore,
|
||||||
|
{
|
||||||
|
Self(<<C::Group as Group>::Field as Field>::random_nonzero(
|
||||||
|
&mut rng,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Debug for Secret<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_tuple("Secret")
|
||||||
|
.field(&hex::encode(self.to_bytes()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Default for Secret<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(<<C::Group as Group>::Field as Field>::zero())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements [`Zeroize`] by overwriting a value with the [`Default::default()`] value
|
||||||
|
impl<C> DefaultIsZeroes for Secret<C> where C: Ciphersuite {}
|
||||||
|
|
||||||
|
// impl<C> Drop for Secret<C>
|
||||||
|
// where
|
||||||
|
// C: Ciphersuite,
|
||||||
|
// {
|
||||||
|
// fn drop(&mut self) {
|
||||||
|
// self.zeroize()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
impl<C> From<&Secret<C>> for VerifyingKey<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn from(secret: &Secret<C>) -> Self {
|
||||||
|
let element = <C::Group as Group>::generator() * secret.0;
|
||||||
|
|
||||||
|
VerifyingKey { element }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> FromHex for Secret<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
|
||||||
|
match FromHex::from_hex(hex) {
|
||||||
|
Ok(bytes) => Self::from_bytes(bytes).map_err(|_| "malformed secret encoding"),
|
||||||
|
Err(_) => Err("invalid hex"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A public group element that represents a single signer's public key.
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
pub struct Public<C>(pub(super) <C::Group as Group>::Element)
|
||||||
|
where
|
||||||
|
C: Ciphersuite;
|
||||||
|
|
||||||
|
impl<C> Public<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Deserialize from bytes
|
||||||
|
pub fn from_bytes(bytes: <C::Group as Group>::Serialization) -> Result<Self, Error> {
|
||||||
|
<C::Group as Group>::deserialize(&bytes).map(|element| Self(element))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize [`Public`] to bytes
|
||||||
|
pub fn to_bytes(&self) -> <C::Group as Group>::Serialization {
|
||||||
|
<C::Group as Group>::serialize(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Debug for Public<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_tuple("Public")
|
||||||
|
.field(&hex::encode(self.to_bytes()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> From<Secret<C>> for Public<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn from(secret: Secret<C>) -> Public<C> {
|
||||||
|
Public(<C::Group as Group>::generator() * secret.0 as Scalar<C>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [`Group::Element`] that is a commitment to one coefficient of our secret polynomial.
|
||||||
|
///
|
||||||
|
/// This is a (public) commitment to one coefficient of a secret polynomial used for performing
|
||||||
|
/// verifiable secret sharing for a Shamir secret share.
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub(super) struct CoefficientCommitment<C: Ciphersuite>(pub(super) <C::Group as Group>::Element);
|
||||||
|
|
||||||
|
/// Contains the commitments to the coefficients for our secret polynomial _f_,
|
||||||
|
/// used to generate participants' key shares.
|
||||||
|
///
|
||||||
|
/// [`VerifiableSecretSharingCommitment`] contains a set of commitments to the coefficients (which
|
||||||
|
/// themselves are scalars) for a secret polynomial f, where f is used to
|
||||||
|
/// generate each ith participant's key share f(i). Participants use this set of
|
||||||
|
/// commitments to perform verifiable secret sharing.
|
||||||
|
///
|
||||||
|
/// Note that participants MUST be assured that they have the *same*
|
||||||
|
/// [`VerifiableSecretSharingCommitment`], either by performing pairwise comparison, or by using
|
||||||
|
/// some agreed-upon public location for publication, where each participant can
|
||||||
|
/// ensure that they received the correct (and same) value.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct VerifiableSecretSharingCommitment<C: Ciphersuite>(
|
||||||
|
pub(super) Vec<CoefficientCommitment<C>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// A secret share generated by performing a (t-out-of-n) secret sharing scheme.
|
||||||
|
///
|
||||||
|
/// `n` is the total number of shares and `t` is the threshold required to reconstruct the secret;
|
||||||
|
/// in this case we use Shamir's secret sharing.
|
||||||
|
///
|
||||||
|
/// As a solution to the secret polynomial _f_ (a 'point'), the `index` is the x-coordinate, and the
|
||||||
|
/// `value` is the y-coordinate.
|
||||||
|
#[derive(Clone, Zeroize)]
|
||||||
|
pub struct SecretShare<C: Ciphersuite> {
|
||||||
|
/// The participant index of this [`SecretShare`].
|
||||||
|
pub index: u16,
|
||||||
|
/// Secret Key.
|
||||||
|
pub value: Secret<C>,
|
||||||
|
/// The commitments to be distributed among signers.
|
||||||
|
pub commitment: VerifiableSecretSharingCommitment<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> SecretShare<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Gets the inner [`Secret`] share value.
|
||||||
|
pub fn secret(&self) -> &Secret<C> {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verifies that a secret share is consistent with a verifiable secret sharing commitment.
|
||||||
|
///
|
||||||
|
/// This ensures that this participant's share has been generated using the same
|
||||||
|
/// mechanism as all other signing participants. Note that participants *MUST*
|
||||||
|
/// ensure that they have the same view as all other participants of the
|
||||||
|
/// commitment!
|
||||||
|
///
|
||||||
|
/// An implementation of `vss_verify()` from the [spec].
|
||||||
|
///
|
||||||
|
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#appendix-B.2-4
|
||||||
|
pub fn verify(&self) -> Result<(), &'static str> {
|
||||||
|
let f_result = <C::Group as Group>::generator() * self.value.0;
|
||||||
|
|
||||||
|
let x = Identifier::<C>::try_from(self.index).unwrap();
|
||||||
|
|
||||||
|
let (_, result) = self.commitment.0.iter().fold(
|
||||||
|
(
|
||||||
|
<<C::Group as Group>::Field as Field>::one(),
|
||||||
|
<C::Group as Group>::identity(),
|
||||||
|
),
|
||||||
|
|(x_to_the_i, sum_so_far), comm_i| {
|
||||||
|
(*x * x_to_the_i, sum_so_far + comm_i.0 * x_to_the_i)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if !(f_result == result) {
|
||||||
|
return Err("SecretShare is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Secret and public key material generated by a dealer performing
|
||||||
|
/// [`keygen_with_dealer`].
|
||||||
|
///
|
||||||
|
/// To derive a FROST keypair, the receiver of the [`SharePackage`] *must* call
|
||||||
|
/// .into(), which under the hood also performs validation.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SharePackage<C: Ciphersuite> {
|
||||||
|
/// Denotes the participant index each share is owned by.
|
||||||
|
pub index: u16,
|
||||||
|
/// This participant's secret share.
|
||||||
|
pub secret_share: SecretShare<C>,
|
||||||
|
/// This participant's public key.
|
||||||
|
pub public: Public<C>,
|
||||||
|
/// The public signing key that represents the entire group.
|
||||||
|
pub group_public: VerifyingKey<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows all participants' keys to be generated using a central, trusted
|
||||||
|
/// dealer.
|
||||||
|
///
|
||||||
|
/// Under the hood, this performs verifiable secret sharing, which itself uses
|
||||||
|
/// Shamir secret sharing, from which each share becomes a participant's secret
|
||||||
|
/// key. The output from this function is a set of shares along with one single
|
||||||
|
/// commitment that participants use to verify the integrity of the share. The
|
||||||
|
/// number of signers is limited to 255.
|
||||||
|
///
|
||||||
|
/// Implements [`trusted_dealer_keygen`] from the spec.
|
||||||
|
///
|
||||||
|
/// [`trusted_dealer_keygen`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#appendix-B
|
||||||
|
pub fn keygen_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>(
|
||||||
|
num_signers: u8,
|
||||||
|
threshold: u8,
|
||||||
|
mut rng: R,
|
||||||
|
) -> Result<(Vec<SharePackage<C>>, PublicKeyPackage<C>), &'static str> {
|
||||||
|
let mut bytes = [0; 64];
|
||||||
|
rng.fill_bytes(&mut bytes);
|
||||||
|
|
||||||
|
let secret = Secret::random(&mut rng);
|
||||||
|
let group_public = VerifyingKey::from(&secret);
|
||||||
|
let secret_shares = generate_secret_shares(&secret, num_signers, threshold, rng)?;
|
||||||
|
let mut share_packages: Vec<SharePackage<C>> = Vec::with_capacity(num_signers as usize);
|
||||||
|
let mut signer_pubkeys: HashMap<u16, Public<C>> = HashMap::with_capacity(num_signers as usize);
|
||||||
|
|
||||||
|
for secret_share in secret_shares {
|
||||||
|
let signer_public = secret_share.value.into();
|
||||||
|
|
||||||
|
share_packages.push(SharePackage {
|
||||||
|
index: secret_share.index,
|
||||||
|
secret_share: secret_share.clone(),
|
||||||
|
public: signer_public,
|
||||||
|
group_public,
|
||||||
|
});
|
||||||
|
|
||||||
|
signer_pubkeys.insert(secret_share.index, signer_public);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
share_packages,
|
||||||
|
PublicKeyPackage {
|
||||||
|
signer_pubkeys,
|
||||||
|
group_public,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A FROST keypair, which can be generated either by a trusted dealer or using
|
||||||
|
/// a DKG.
|
||||||
|
///
|
||||||
|
/// When using a central dealer, [`SharePackage`]s are distributed to
|
||||||
|
/// participants, who then perform verification, before deriving
|
||||||
|
/// [`KeyPackage`]s, which they store to later use during signing.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct KeyPackage<C: Ciphersuite> {
|
||||||
|
/// Denotes the participant index each secret share key package is owned by.
|
||||||
|
pub index: u16,
|
||||||
|
/// This participant's secret share.
|
||||||
|
pub secret_share: Secret<C>,
|
||||||
|
/// This participant's public key.
|
||||||
|
pub public: Public<C>,
|
||||||
|
/// The public signing key that represents the entire group.
|
||||||
|
pub group_public: VerifyingKey<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> KeyPackage<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Gets the participant index associated with this [`KeyPackage`].
|
||||||
|
pub fn index(&self) -> &u16 {
|
||||||
|
&self.index
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the participant's [`Secret`] share associated with this [`KeyPackage`].
|
||||||
|
pub fn secret_share(&self) -> &Secret<C> {
|
||||||
|
&self.secret_share
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the participant's [`Public`] key associated with this [`Secret`] share in this [`KeyPackage`].
|
||||||
|
pub fn public(&self) -> &Public<C> {
|
||||||
|
&self.public
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the group [`VerifyingKey`] associated with the entire group in this [`KeyPackage`].
|
||||||
|
pub fn group_public(&self) -> &VerifyingKey<C> {
|
||||||
|
&self.group_public
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> TryFrom<SharePackage<C>> for KeyPackage<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
/// Tries to verify a share and construct a [`KeyPackage`] from it.
|
||||||
|
///
|
||||||
|
/// When participants receive a [`SharePackage`] from the dealer, they
|
||||||
|
/// *MUST* verify the integrity of the share before continuing on to
|
||||||
|
/// transform it into a signing/verification keypair. Here, we assume that
|
||||||
|
/// every participant has the same view of the commitment issued by the
|
||||||
|
/// dealer, but implementations *MUST* make sure that all participants have
|
||||||
|
/// a consistent view of this commitment in practice.
|
||||||
|
fn try_from(share_package: SharePackage<C>) -> Result<Self, &'static str> {
|
||||||
|
share_package.secret_share.verify()?;
|
||||||
|
|
||||||
|
Ok(KeyPackage {
|
||||||
|
index: share_package.index,
|
||||||
|
secret_share: share_package.secret_share.value,
|
||||||
|
public: share_package.public,
|
||||||
|
group_public: share_package.group_public,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Public data that contains all the signers' public keys as well as the
|
||||||
|
/// group public key.
|
||||||
|
///
|
||||||
|
/// Used for verification purposes before publishing a signature.
|
||||||
|
pub struct PublicKeyPackage<C: Ciphersuite> {
|
||||||
|
/// When performing signing, the coordinator must ensure that they have the
|
||||||
|
/// correct view of participants' public keys to perform verification before
|
||||||
|
/// publishing a signature. `signer_pubkeys` represents all signers for a
|
||||||
|
/// signing operation.
|
||||||
|
pub signer_pubkeys: HashMap<u16, Public<C>>,
|
||||||
|
/// The joint public key for the entire group.
|
||||||
|
pub group_public: VerifyingKey<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates secret shares for a given secret.
|
||||||
|
///
|
||||||
|
/// This function accepts a secret from which shares are generated. While in
|
||||||
|
/// FROST this secret should always be generated randomly, we allow this secret
|
||||||
|
/// to be specified for this internal function for testability.
|
||||||
|
///
|
||||||
|
/// Internally, [`generate_secret_shares`] performs verifiable secret sharing, which
|
||||||
|
/// generates shares via Shamir Secret Sharing, and then generates public
|
||||||
|
/// commitments to those shares.
|
||||||
|
///
|
||||||
|
/// More specifically, [`generate_secret_shares`]:
|
||||||
|
/// - Randomly samples of coefficients [a, b, c], this represents a secret
|
||||||
|
/// polynomial f
|
||||||
|
/// - For each participant i, their secret share is f(i)
|
||||||
|
/// - The commitment to the secret polynomial f is [g^a, g^b, g^c]
|
||||||
|
///
|
||||||
|
/// Implements [`secret_key_shard`] from the spec.
|
||||||
|
///
|
||||||
|
/// [`secret_key_shard`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#appendix-B.1
|
||||||
|
pub fn generate_secret_shares<C: Ciphersuite, R: RngCore + CryptoRng>(
|
||||||
|
secret: &Secret<C>,
|
||||||
|
numshares: u8,
|
||||||
|
threshold: u8,
|
||||||
|
mut rng: R,
|
||||||
|
) -> Result<Vec<SecretShare<C>>, &'static str> {
|
||||||
|
if threshold < 2 {
|
||||||
|
return Err("Threshold cannot be less than 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
if numshares < 2 {
|
||||||
|
return Err("Number of shares cannot be less than the minimum threshold 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
if threshold > numshares {
|
||||||
|
return Err("Threshold cannot exceed numshares");
|
||||||
|
}
|
||||||
|
|
||||||
|
let numcoeffs = threshold - 1;
|
||||||
|
|
||||||
|
let mut coefficients: Vec<Scalar<C>> = Vec::with_capacity(threshold as usize);
|
||||||
|
|
||||||
|
let mut secret_shares: Vec<SecretShare<C>> = Vec::with_capacity(numshares as usize);
|
||||||
|
|
||||||
|
let mut commitment: VerifiableSecretSharingCommitment<C> =
|
||||||
|
VerifiableSecretSharingCommitment(Vec::with_capacity(threshold as usize));
|
||||||
|
|
||||||
|
for _ in 0..numcoeffs {
|
||||||
|
coefficients.push(<<C::Group as Group>::Field as Field>::random(&mut rng));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifiable secret sharing, to make sure that participants can ensure their
|
||||||
|
// secret is consistent with every other participant's.
|
||||||
|
commitment.0.push(CoefficientCommitment(
|
||||||
|
<C::Group as Group>::generator() * secret.0,
|
||||||
|
));
|
||||||
|
|
||||||
|
for c in &coefficients {
|
||||||
|
commitment
|
||||||
|
.0
|
||||||
|
.push(CoefficientCommitment(<C::Group as Group>::generator() * *c));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate the polynomial with `secret` as the constant term
|
||||||
|
// and `coeffs` as the other coefficients at the point x=share_index,
|
||||||
|
// using Horner's method.
|
||||||
|
for id in (1..=numshares as u16).map_while(|i| Identifier::<C>::try_from(i).ok()) {
|
||||||
|
let mut value = <<C::Group as Group>::Field as Field>::zero();
|
||||||
|
|
||||||
|
// Polynomial evaluation, for this index
|
||||||
|
//
|
||||||
|
// We rely only on `Add` and `Mul` here so as to not require `AddAssign` and `MulAssign`
|
||||||
|
//
|
||||||
|
// Note that this is from the 'last' coefficient to the 'first'.
|
||||||
|
for i in (0..numcoeffs).rev() {
|
||||||
|
value = value + coefficients[i as usize];
|
||||||
|
value = *id * value;
|
||||||
|
}
|
||||||
|
value = value + secret.0;
|
||||||
|
|
||||||
|
secret_shares.push(SecretShare {
|
||||||
|
index: usize::from(id) as u16,
|
||||||
|
value: Secret(value),
|
||||||
|
commitment: commitment.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(secret_shares)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recompute the secret from t-of-n secret shares using Lagrange interpolation.
|
||||||
|
pub fn reconstruct_secret<C: Ciphersuite>(
|
||||||
|
secret_shares: Vec<SecretShare<C>>,
|
||||||
|
) -> Result<Secret<C>, &'static str> {
|
||||||
|
if secret_shares.is_empty() {
|
||||||
|
return Err("No secret_shares provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
let secret_share_map: HashMap<Identifier<C>, SecretShare<C>> = secret_shares
|
||||||
|
.into_iter()
|
||||||
|
.map(|share| (Identifier::<C>::try_from(share.index).unwrap(), share))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut secret = <<C::Group as Group>::Field as Field>::zero();
|
||||||
|
|
||||||
|
// Compute the Lagrange coefficients
|
||||||
|
for (i, secret_share) in secret_share_map.clone() {
|
||||||
|
let mut num = <<C::Group as Group>::Field as Field>::one();
|
||||||
|
let mut den = <<C::Group as Group>::Field as Field>::one();
|
||||||
|
|
||||||
|
for j in secret_share_map.clone().into_keys() {
|
||||||
|
if j == i {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// numerator *= j
|
||||||
|
num = num * *j;
|
||||||
|
|
||||||
|
// denominator *= j - i
|
||||||
|
den = den * (*j - *i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If at this step, the denominator is zero in the scalar field, there must be a duplicate
|
||||||
|
// secret share.
|
||||||
|
if den == <<C::Group as Group>::Field as Field>::zero() {
|
||||||
|
return Err("Duplicate shares provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save numerator * 1/denomintor in the scalar field
|
||||||
|
let lagrange_coefficient =
|
||||||
|
num * <<C::Group as Group>::Field as Field>::invert(&den).unwrap();
|
||||||
|
|
||||||
|
// Compute y = f(0) via polynomial interpolation of these t-of-n solutions ('points) of f
|
||||||
|
secret = secret + (lagrange_coefficient * secret_share.value.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Secret::from_bytes(<<C::Group as Group>::Field as Field>::serialize(&secret)).unwrap())
|
||||||
|
}
|
|
@ -0,0 +1,303 @@
|
||||||
|
//! FROST Round 1 functionality and types
|
||||||
|
|
||||||
|
use std::fmt::{self, Debug};
|
||||||
|
|
||||||
|
use hex::FromHex;
|
||||||
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use crate::{frost, Ciphersuite, Error, Field, Group};
|
||||||
|
|
||||||
|
/// A scalar that is a signing nonce.
|
||||||
|
#[derive(Clone, PartialEq, Zeroize)]
|
||||||
|
pub struct Nonce<C: Ciphersuite>(pub(super) <<C::Group as Group>::Field as Field>::Scalar);
|
||||||
|
|
||||||
|
impl<C> Nonce<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Generates a new uniformly random signing nonce.
|
||||||
|
///
|
||||||
|
/// Each participant generates signing nonces before performing a signing
|
||||||
|
/// operation.
|
||||||
|
///
|
||||||
|
/// An implementation of `RandomNonzeroScalar()` from the [spec].
|
||||||
|
///
|
||||||
|
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-3.1-3.4
|
||||||
|
pub fn random<R>(rng: &mut R) -> Self
|
||||||
|
where
|
||||||
|
R: CryptoRng + RngCore,
|
||||||
|
{
|
||||||
|
// The values of 'hiding' and 'binding' nonces must be non-zero so that commitments are
|
||||||
|
// not the identity.
|
||||||
|
Self(<<C::Group as Group>::Field as Field>::random_nonzero(rng))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize [`Nonce`] from bytes
|
||||||
|
pub fn from_bytes(
|
||||||
|
bytes: <<C::Group as Group>::Field as Field>::Serialization,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
<<C::Group as Group>::Field as Field>::deserialize(&bytes).map(|scalar| Self(scalar))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize [`Nonce`] to bytes
|
||||||
|
pub fn to_bytes(&self) -> <<C::Group as Group>::Field as Field>::Serialization {
|
||||||
|
<<C::Group as Group>::Field as Field>::serialize(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl<C> Drop for Nonce<C>
|
||||||
|
// where
|
||||||
|
// C: Ciphersuite,
|
||||||
|
// {
|
||||||
|
// fn drop(&mut self) {
|
||||||
|
// self.zeroize()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
impl<C> FromHex for Nonce<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
|
||||||
|
match FromHex::from_hex(hex) {
|
||||||
|
Ok(bytes) => Self::from_bytes(bytes).map_err(|_| "malformed nonce encoding"),
|
||||||
|
Err(_) => Err("invalid hex"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Ristretto point that is a commitment to a signing nonce share.
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub struct NonceCommitment<C: Ciphersuite>(pub(super) <C::Group as Group>::Element);
|
||||||
|
|
||||||
|
impl<C> NonceCommitment<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Deserialize [`NonceCommitment`] from bytes
|
||||||
|
pub fn from_bytes(bytes: <C::Group as Group>::Serialization) -> Result<Self, Error> {
|
||||||
|
<C::Group as Group>::deserialize(&bytes).map(|element| Self(element))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize [`NonceCommitment`] to bytes
|
||||||
|
pub fn to_bytes(&self) -> <C::Group as Group>::Serialization {
|
||||||
|
<C::Group as Group>::serialize(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Debug for NonceCommitment<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_tuple("NonceCommitment")
|
||||||
|
.field(&hex::encode(self.to_bytes()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> From<Nonce<C>> for NonceCommitment<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn from(nonce: Nonce<C>) -> Self {
|
||||||
|
From::from(&nonce)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> From<&Nonce<C>> for NonceCommitment<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn from(nonce: &Nonce<C>) -> Self {
|
||||||
|
Self(<C::Group as Group>::generator() * nonce.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> FromHex for NonceCommitment<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
|
||||||
|
match FromHex::from_hex(hex) {
|
||||||
|
Ok(bytes) => Self::from_bytes(bytes).map_err(|_| "malformed nonce commitment encoding"),
|
||||||
|
Err(_) => Err("invalid hex"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Comprised of hiding and binding nonces.
|
||||||
|
///
|
||||||
|
/// Note that [`SigningNonces`] must be used *only once* for a signing
|
||||||
|
/// operation; re-using nonces will result in leakage of a signer's long-lived
|
||||||
|
/// signing key.
|
||||||
|
#[derive(Clone, Zeroize)]
|
||||||
|
pub struct SigningNonces<C: Ciphersuite> {
|
||||||
|
/// The hiding [`Nonce`].
|
||||||
|
pub hiding: Nonce<C>,
|
||||||
|
/// The binding [`Nonce`].
|
||||||
|
pub binding: Nonce<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> SigningNonces<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Generates a new signing nonce.
|
||||||
|
///
|
||||||
|
/// Each participant generates signing nonces before performing a signing
|
||||||
|
/// operation.
|
||||||
|
pub fn new<R>(rng: &mut R) -> Self
|
||||||
|
where
|
||||||
|
R: CryptoRng + RngCore,
|
||||||
|
{
|
||||||
|
// The values of 'hiding' and 'binding' must be non-zero so that commitments are
|
||||||
|
// not the identity.
|
||||||
|
let hiding = Nonce::<C>::random(rng);
|
||||||
|
let binding = Nonce::<C>::random(rng);
|
||||||
|
|
||||||
|
Self { hiding, binding }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the hiding [`Nonce`]
|
||||||
|
pub fn hiding(&self) -> &Nonce<C> {
|
||||||
|
&self.hiding
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the binding [`Nonce`]
|
||||||
|
pub fn binding(&self) -> &Nonce<C> {
|
||||||
|
&self.binding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Published by each participant in the first round of the signing protocol.
|
||||||
|
///
|
||||||
|
/// This step can be batched if desired by the implementation. Each
|
||||||
|
/// SigningCommitment can be used for exactly *one* signature.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct SigningCommitments<C: Ciphersuite> {
|
||||||
|
/// The participant index.
|
||||||
|
pub index: u16,
|
||||||
|
/// Commitment to the hiding [`Nonce`].
|
||||||
|
pub hiding: NonceCommitment<C>,
|
||||||
|
/// Commitment to the binding [`Nonce`].
|
||||||
|
pub binding: NonceCommitment<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> SigningCommitments<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Computes the [signature commitment share] from these round one signing commitments.
|
||||||
|
///
|
||||||
|
/// [signature commitment share]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#name-signature-share-verificatio
|
||||||
|
pub(super) fn to_group_commitment_share(
|
||||||
|
self,
|
||||||
|
binding_factor: &frost::Rho<C>,
|
||||||
|
) -> GroupCommitmentShare<C> {
|
||||||
|
GroupCommitmentShare::<C>(self.hiding.0 + (self.binding.0 * binding_factor.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the hiding [`NonceCommitment`].
|
||||||
|
pub fn hiding(&self) -> &NonceCommitment<C> {
|
||||||
|
&self.hiding
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the binding [`NonceCommitment`].
|
||||||
|
pub fn binding(&self) -> &NonceCommitment<C> {
|
||||||
|
&self.binding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> From<(u16, &SigningNonces<C>)> for SigningCommitments<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn from((index, nonces): (u16, &SigningNonces<C>)) -> Self {
|
||||||
|
Self {
|
||||||
|
index,
|
||||||
|
hiding: nonces.hiding.clone().into(),
|
||||||
|
binding: nonces.binding.clone().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One signer's share of the group commitment, derived from their individual signing commitments
|
||||||
|
/// and the binding factor _rho_.
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub struct GroupCommitmentShare<C: Ciphersuite>(pub(super) <C::Group as Group>::Element);
|
||||||
|
|
||||||
|
/// Encode the list of group signing commitments.
|
||||||
|
///
|
||||||
|
/// Implements [`encode_group_commitment_list()`] from the spec.
|
||||||
|
///
|
||||||
|
/// Inputs:
|
||||||
|
/// - commitment_list = [(j, D_j, E_j), ...], a list of commitments issued by each signer,
|
||||||
|
/// where each element in the list indicates the signer index and their
|
||||||
|
/// two commitment Element values. B MUST be sorted in ascending order
|
||||||
|
/// by signer index.
|
||||||
|
///
|
||||||
|
/// Outputs:
|
||||||
|
/// - A byte string containing the serialized representation of B.
|
||||||
|
///
|
||||||
|
/// [`encode_group_commitment_list()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-4.3
|
||||||
|
pub(super) fn encode_group_commitments<C: Ciphersuite>(
|
||||||
|
signing_commitments: Vec<SigningCommitments<C>>,
|
||||||
|
) -> Vec<u8> {
|
||||||
|
// B MUST be sorted in ascending order by signer index.
|
||||||
|
//
|
||||||
|
// https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#encoding-operations-dep-encoding
|
||||||
|
//
|
||||||
|
// TODO: AtLeastOne or other explicitly Sorted wrapper types?
|
||||||
|
let mut sorted_signing_commitments = signing_commitments;
|
||||||
|
sorted_signing_commitments.sort_by_key(|a| a.index);
|
||||||
|
|
||||||
|
let mut bytes = vec![];
|
||||||
|
|
||||||
|
for item in sorted_signing_commitments {
|
||||||
|
bytes.extend_from_slice(&item.index.to_be_bytes()); // TODO: 2-bytes until spec moves off u16
|
||||||
|
bytes.extend_from_slice(<C::Group as Group>::serialize(&item.hiding.0).as_ref());
|
||||||
|
bytes.extend_from_slice(<C::Group as Group>::serialize(&item.binding.0).as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Done once by each participant, to generate _their_ nonces and commitments
|
||||||
|
/// that are then used during signing.
|
||||||
|
///
|
||||||
|
/// When performing signing using two rounds, num_nonces would equal 1, to
|
||||||
|
/// perform the first round. Batching entails generating more than one
|
||||||
|
/// nonce/commitment pair at a time. Nonces should be stored in secret storage
|
||||||
|
/// for later use, whereas the commitments are published.
|
||||||
|
///
|
||||||
|
/// The number of nonces is limited to 255. This limit can be increased if it
|
||||||
|
/// turns out to be too conservative.
|
||||||
|
// TODO: Make sure the above is a correct statement, fix if needed in:
|
||||||
|
// https://github.com/ZcashFoundation/redjubjub/issues/111
|
||||||
|
pub fn preprocess<C, R>(
|
||||||
|
num_nonces: u8,
|
||||||
|
participant_index: u16,
|
||||||
|
rng: &mut R,
|
||||||
|
) -> (Vec<SigningNonces<C>>, Vec<SigningCommitments<C>>)
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
R: CryptoRng + RngCore,
|
||||||
|
{
|
||||||
|
let mut signing_nonces: Vec<SigningNonces<C>> = Vec::with_capacity(num_nonces as usize);
|
||||||
|
let mut signing_commitments: Vec<SigningCommitments<C>> =
|
||||||
|
Vec::with_capacity(num_nonces as usize);
|
||||||
|
|
||||||
|
for _ in 0..num_nonces {
|
||||||
|
let nonces = SigningNonces::new(rng);
|
||||||
|
signing_commitments.push(SigningCommitments::from((participant_index, &nonces)));
|
||||||
|
signing_nonces.push(nonces);
|
||||||
|
}
|
||||||
|
|
||||||
|
(signing_nonces, signing_commitments)
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
//! FROST Round 2 functionality and types, for signature share generation
|
||||||
|
|
||||||
|
use std::fmt::{self, Debug};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
challenge,
|
||||||
|
frost::{self, round1, *},
|
||||||
|
Challenge, Ciphersuite, Error, Field, Group,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A representation of a single signature share used in FROST structures and messages.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct SignatureResponse<C: Ciphersuite> {
|
||||||
|
/// The [`Scalar`] contribution to the group signature.
|
||||||
|
pub z_share: <<C::Group as Group>::Field as Field>::Scalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> SignatureResponse<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Deserialize [`SignatureResponse`] from bytes
|
||||||
|
pub fn from_bytes(
|
||||||
|
bytes: <<C::Group as Group>::Field as Field>::Serialization,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
<<C::Group as Group>::Field as Field>::deserialize(&bytes)
|
||||||
|
.map(|scalar| Self { z_share: scalar })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize [`SignatureResponse`] to bytes
|
||||||
|
pub fn to_bytes(&self) -> <<C::Group as Group>::Field as Field>::Serialization {
|
||||||
|
<<C::Group as Group>::Field as Field>::serialize(&self.z_share)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Debug for SignatureResponse<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("SignatureResponse")
|
||||||
|
.field("z_share", &hex::encode(self.to_bytes()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Eq for SignatureResponse<C> where C: Ciphersuite {}
|
||||||
|
|
||||||
|
impl<C> PartialEq for SignatureResponse<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
// TODO: should this have any constant-time guarantees? I think signature shares are public.
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.z_share == other.z_share
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A participant's signature share, which the coordinator will aggregate with all other signer's
|
||||||
|
/// shares into the joint signature.
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub struct SignatureShare<C: Ciphersuite> {
|
||||||
|
/// Represents the participant index.
|
||||||
|
pub index: u16,
|
||||||
|
/// This participant's signature over the message.
|
||||||
|
pub signature: SignatureResponse<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> SignatureShare<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Gets the participant index associated with this [`SignatureShare`].
|
||||||
|
pub fn index(&self) -> &u16 {
|
||||||
|
&self.index
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests if a signature share issued by a participant is valid before
|
||||||
|
/// aggregating it into a final joint signature to publish.
|
||||||
|
///
|
||||||
|
/// This is the final step of [`verify_signature_share`] from the spec.
|
||||||
|
///
|
||||||
|
/// [`verify_signature_share`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-5.3
|
||||||
|
pub fn verify(
|
||||||
|
&self,
|
||||||
|
group_commitment_share: &round1::GroupCommitmentShare<C>,
|
||||||
|
public_key: &frost::keys::Public<C>,
|
||||||
|
lambda_i: <<C::Group as Group>::Field as Field>::Scalar,
|
||||||
|
challenge: &Challenge<C>,
|
||||||
|
) -> Result<(), &'static str> {
|
||||||
|
if (<C::Group as Group>::generator() * self.signature.z_share)
|
||||||
|
!= (group_commitment_share.0 + (public_key.0 * challenge.0 * lambda_i))
|
||||||
|
{
|
||||||
|
return Err("Invalid signature share");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Debug for SignatureShare<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("SignatureShare")
|
||||||
|
.field("index", &self.index)
|
||||||
|
.field("signature", &self.signature)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Zeroizes `SignatureShare` to be the `Default` value on drop (when it goes out
|
||||||
|
// // of scope). Luckily the derived `Default` includes the `Default` impl of
|
||||||
|
// // Scalar, which is four 0u64's under the hood, and u16, which is
|
||||||
|
// // 0u16.
|
||||||
|
// impl DefaultIsZeroes for SignatureShare {}
|
||||||
|
|
||||||
|
/// Performed once by each participant selected for the signing operation.
|
||||||
|
///
|
||||||
|
/// Implements [`sign`] from the spec.
|
||||||
|
///
|
||||||
|
/// Receives the message to be signed and a set of signing commitments and a set
|
||||||
|
/// of randomizing commitments to be used in that signing operation, including
|
||||||
|
/// that for this participant.
|
||||||
|
///
|
||||||
|
/// Assumes the participant has already determined which nonce corresponds with
|
||||||
|
/// the commitment that was assigned by the coordinator in the SigningPackage.
|
||||||
|
///
|
||||||
|
/// [`sign`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-5.2
|
||||||
|
pub fn sign<C: Ciphersuite>(
|
||||||
|
signing_package: &SigningPackage<C>,
|
||||||
|
signer_nonces: &round1::SigningNonces<C>,
|
||||||
|
key_package: &frost::keys::KeyPackage<C>,
|
||||||
|
) -> Result<SignatureShare<C>, &'static str> {
|
||||||
|
// Encodes the signing commitment list produced in round one as part of generating [`Rho`], the
|
||||||
|
// binding factor.
|
||||||
|
let rho: frost::Rho<C> = signing_package.into();
|
||||||
|
|
||||||
|
// Compute the group commitment from signing commitments produced in round one.
|
||||||
|
let group_commitment = GroupCommitment::<C>::try_from(signing_package)?;
|
||||||
|
|
||||||
|
// Compute Lagrange coefficient.
|
||||||
|
let lambda_i = frost::derive_lagrange_coeff(*key_package.index(), signing_package)?;
|
||||||
|
|
||||||
|
// Compute the per-message challenge.
|
||||||
|
let challenge = challenge::<C>(
|
||||||
|
&group_commitment.0,
|
||||||
|
&key_package.group_public.element,
|
||||||
|
signing_package.message.as_slice(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compute the Schnorr signature share.
|
||||||
|
let z_share: <<C::Group as Group>::Field as Field>::Scalar = signer_nonces.hiding.0
|
||||||
|
+ (signer_nonces.binding.0 * rho.0)
|
||||||
|
+ (lambda_i * key_package.secret_share.0 * challenge.0);
|
||||||
|
|
||||||
|
let signature_share = SignatureShare::<C> {
|
||||||
|
index: *key_package.index(),
|
||||||
|
signature: SignatureResponse::<C> { z_share },
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(signature_share)
|
||||||
|
}
|
|
@ -1,8 +1,240 @@
|
||||||
#[cfg(test)]
|
#![allow(non_snake_case)]
|
||||||
mod tests {
|
#![deny(missing_docs)]
|
||||||
#[test]
|
#![doc = include_str!("../README.md")]
|
||||||
fn it_works() {
|
#![forbid(unsafe_code)]
|
||||||
let result = 2 + 2;
|
|
||||||
assert_eq!(result, 4);
|
use std::{
|
||||||
|
default::Default,
|
||||||
|
fmt::Debug,
|
||||||
|
ops::{Add, Mul, Sub},
|
||||||
|
};
|
||||||
|
|
||||||
|
use hex::FromHex;
|
||||||
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
|
||||||
|
// pub mod batch;
|
||||||
|
mod error;
|
||||||
|
pub mod frost;
|
||||||
|
// mod scalar_mul;
|
||||||
|
mod signature;
|
||||||
|
mod signing_key;
|
||||||
|
mod verifying_key;
|
||||||
|
|
||||||
|
pub use error::Error;
|
||||||
|
pub use signature::Signature;
|
||||||
|
pub use signing_key::SigningKey;
|
||||||
|
pub use verifying_key::VerifyingKey;
|
||||||
|
|
||||||
|
/// A prime order finite field GF(q) over which all scalar values for our prime order group can be
|
||||||
|
/// multiplied are defined.
|
||||||
|
///
|
||||||
|
/// This trait does not have to be implemented for a finite field scalar itself, it can be a
|
||||||
|
/// pass-through, implemented for a type just for the ciphersuite, and calls through to another
|
||||||
|
/// implementation underneath, so that this trait does not have to be implemented for types you
|
||||||
|
/// don't own.
|
||||||
|
pub trait Field: Copy + Clone {
|
||||||
|
/// An element of the scalar field GF(p).
|
||||||
|
type Scalar: Add<Output = Self::Scalar>
|
||||||
|
+ Copy
|
||||||
|
+ Clone
|
||||||
|
+ Eq
|
||||||
|
+ Mul<Output = Self::Scalar>
|
||||||
|
+ PartialEq
|
||||||
|
+ Sub<Output = Self::Scalar>;
|
||||||
|
|
||||||
|
/// A unique byte array buf of fixed length N.
|
||||||
|
///
|
||||||
|
/// Little-endian!
|
||||||
|
type Serialization: AsRef<[u8]> + AsMut<[u8]> + Debug + Default + FromHex + TryFrom<Vec<u8>>;
|
||||||
|
|
||||||
|
/// Returns the zero element of the field, the additive identity.
|
||||||
|
fn zero() -> Self::Scalar;
|
||||||
|
|
||||||
|
/// Returns the one element of the field, the multiplicative identity.
|
||||||
|
fn one() -> Self::Scalar;
|
||||||
|
|
||||||
|
/// Computes the multiplicative inverse of an element of the scalar field, failing if the
|
||||||
|
/// element is zero.
|
||||||
|
fn invert(scalar: &Self::Scalar) -> Result<Self::Scalar, Error>;
|
||||||
|
|
||||||
|
/// Generate a random scalar from the entire space [0, l-1]
|
||||||
|
///
|
||||||
|
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-3.1-3.3>
|
||||||
|
fn random<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
|
||||||
|
|
||||||
|
/// Generate a random scalar from the entire space [1, l-1]
|
||||||
|
///
|
||||||
|
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-3.1-3.4>
|
||||||
|
fn random_nonzero<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
|
||||||
|
|
||||||
|
/// A member function of a group _G_ that maps an [`Element`] to a unique byte array buf of
|
||||||
|
/// fixed length Ne.
|
||||||
|
///
|
||||||
|
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-3.1-3.5>
|
||||||
|
fn serialize(scalar: &Self::Scalar) -> Self::Serialization;
|
||||||
|
|
||||||
|
/// A member function of a [`Group`] that attempts to map a byte array `buf` to an [`Element`].
|
||||||
|
///
|
||||||
|
/// Fails if the input is not a valid byte representation of an [`Element`] of the
|
||||||
|
/// [`Group`]. This function can raise a [`DeserializeError`] if deserialization fails or if the
|
||||||
|
/// resulting [`Element`] is the identity element of the group
|
||||||
|
///
|
||||||
|
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-3.1-3.6>
|
||||||
|
fn deserialize(buf: &Self::Serialization) -> Result<Self::Scalar, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An element of the [`Ciphersuite`] `C`'s [`Group`]'s scalar [`Field`].
|
||||||
|
pub type Scalar<C> = <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar;
|
||||||
|
|
||||||
|
/// A prime-order group (or subgroup) that provides everything we need to create and verify Schnorr
|
||||||
|
/// signatures.
|
||||||
|
///
|
||||||
|
/// This trait does not have to be implemented for the curve/element/point itself, it can be a
|
||||||
|
/// pass-through, implemented for a type just for the ciphersuite, and calls through to another
|
||||||
|
/// implementation underneath, so that this trait does not have to be implemented for types you
|
||||||
|
/// don't own.
|
||||||
|
pub trait Group: Copy + Clone {
|
||||||
|
/// A prime order finite field GF(q) over which all scalar values for our prime order group can
|
||||||
|
/// be multiplied are defined.
|
||||||
|
type Field: Field;
|
||||||
|
|
||||||
|
/// An element of our group that we will be computing over.
|
||||||
|
type Element: Add<Output = Self::Element>
|
||||||
|
+ Copy
|
||||||
|
+ Clone
|
||||||
|
+ Eq
|
||||||
|
+ Mul<<Self::Field as Field>::Scalar, Output = Self::Element>
|
||||||
|
+ PartialEq
|
||||||
|
+ Sub<Output = Self::Element>;
|
||||||
|
|
||||||
|
/// A unique byte array buf of fixed length N.
|
||||||
|
///
|
||||||
|
/// Little-endian!
|
||||||
|
type Serialization: AsRef<[u8]> + AsMut<[u8]> + Default + FromHex + TryFrom<Vec<u8>>;
|
||||||
|
|
||||||
|
/// Outputs the order of G (i.e. p)
|
||||||
|
///
|
||||||
|
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-3.1-3.1>
|
||||||
|
fn order() -> <Self::Field as Field>::Scalar;
|
||||||
|
|
||||||
|
/// The order of the the quotient group when the prime order subgroup divides the order of the
|
||||||
|
/// full curve group.
|
||||||
|
///
|
||||||
|
/// If using a prime order elliptic curve, the cofactor should be 1 in the scalar field.
|
||||||
|
///
|
||||||
|
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-4.1-3>
|
||||||
|
fn cofactor() -> <Self::Field as Field>::Scalar;
|
||||||
|
|
||||||
|
/// Additive [identity] of the prime order group.
|
||||||
|
///
|
||||||
|
/// [identity]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-3.1-3.2
|
||||||
|
fn identity() -> Self::Element;
|
||||||
|
|
||||||
|
/// The fixed generator element of the prime order group.
|
||||||
|
///
|
||||||
|
/// The 'base' of [`ScalarBaseMult()`] from the spec.
|
||||||
|
/// [`ScalarBaseMult()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-3.1
|
||||||
|
fn generator() -> Self::Element;
|
||||||
|
|
||||||
|
/// A member function of a group _G_ that maps an [`Element`] to a unique byte array buf of
|
||||||
|
/// fixed length Ne.
|
||||||
|
///
|
||||||
|
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-3.1-3.5>
|
||||||
|
fn serialize(element: &Self::Element) -> Self::Serialization;
|
||||||
|
|
||||||
|
/// A member function of a [`Group`] that attempts to map a byte array `buf` to an [`Element`].
|
||||||
|
///
|
||||||
|
/// Fails if the input is not a valid byte representation of an [`Element`] of the
|
||||||
|
/// [`Group`]. This function can raise a [`DeserializeError`] if deserialization fails or if the
|
||||||
|
/// resulting [`Element`] is the identity element of the group
|
||||||
|
///
|
||||||
|
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-3.1-3.6>
|
||||||
|
fn deserialize(buf: &Self::Serialization) -> Result<Self::Element, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An element of the [`Ciphersuite`] `C`'s [`Group`].
|
||||||
|
pub type Element<C> = <<C as Ciphersuite>::Group as Group>::Element;
|
||||||
|
|
||||||
|
/// A [FROST ciphersuite] specifies the underlying prime-order group details and cryptographic hash
|
||||||
|
/// function.
|
||||||
|
///
|
||||||
|
/// [FROST ciphersuite]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#name-ciphersuites
|
||||||
|
pub trait Ciphersuite: Copy + Clone {
|
||||||
|
/// The prime order group (or subgroup) that this ciphersuite operates over.
|
||||||
|
type Group: Group;
|
||||||
|
|
||||||
|
/// A unique byte array of fixed length.
|
||||||
|
type HashOutput: AsRef<[u8]>;
|
||||||
|
|
||||||
|
/// A unique byte array of fixed length that is the `Group::ElementSerialization` +
|
||||||
|
/// `Group::ScalarSerialization`
|
||||||
|
type SignatureSerialization: AsRef<[u8]> + FromHex + TryFrom<Vec<u8>>;
|
||||||
|
|
||||||
|
/// [H1] for a FROST ciphersuite.
|
||||||
|
///
|
||||||
|
/// Maps arbitrary inputs to non-zero `Self::Scalar` elements of the prime-order group scalar field.
|
||||||
|
///
|
||||||
|
/// [H1]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash
|
||||||
|
fn H1(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar;
|
||||||
|
|
||||||
|
/// [H2] for a FROST ciphersuite.
|
||||||
|
///
|
||||||
|
/// Maps arbitrary inputs to non-zero `Self::Scalar` elements of the prime-order group scalar field.
|
||||||
|
///
|
||||||
|
/// [H2]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash
|
||||||
|
fn H2(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar;
|
||||||
|
|
||||||
|
/// H3 for a FROST ciphersuite.
|
||||||
|
///
|
||||||
|
/// Usually an an alias for the ciphersuite hash function _H_ with domain separation applied.
|
||||||
|
///
|
||||||
|
/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash
|
||||||
|
fn H3(m: &[u8]) -> Self::HashOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type refinement for the scalar field element representing the per-message _[challenge]_.
|
||||||
|
///
|
||||||
|
/// [challenge]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#name-signature-challenge-computa
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Challenge<C: Ciphersuite>(
|
||||||
|
pub(crate) <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<C> Debug for Challenge<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_tuple("Secret")
|
||||||
|
.field(&hex::encode(
|
||||||
|
<<<C as Ciphersuite>::Group as Group>::Field as Field>::serialize(&self.0),
|
||||||
|
))
|
||||||
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates the challenge as is required for Schnorr signatures.
|
||||||
|
///
|
||||||
|
/// Deals in bytes, so that [FROST] and singleton signing and verification can use it with different
|
||||||
|
/// types.
|
||||||
|
///
|
||||||
|
/// This is the only invocation of the H2 hash function from the [RFC].
|
||||||
|
///
|
||||||
|
/// [FROST]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-4.6
|
||||||
|
/// [RFC]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-3.2
|
||||||
|
fn challenge<C>(
|
||||||
|
R: &<C::Group as Group>::Element,
|
||||||
|
verifying_key: &<C::Group as Group>::Element,
|
||||||
|
msg: &[u8],
|
||||||
|
) -> Challenge<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
let mut preimage = vec![];
|
||||||
|
|
||||||
|
preimage.extend_from_slice(<C::Group as Group>::serialize(R).as_ref());
|
||||||
|
preimage.extend_from_slice(<C::Group as Group>::serialize(verifying_key).as_ref());
|
||||||
|
preimage.extend_from_slice(msg);
|
||||||
|
|
||||||
|
Challenge(C::H2(&preimage[..]))
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,190 @@
|
||||||
|
use std::{
|
||||||
|
borrow::Borrow,
|
||||||
|
fmt::{Debug, Result},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait NonAdjacentForm {
|
||||||
|
fn non_adjacent_form(&self, w: usize) -> [i8; 256];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> NonAdjacentForm for Scalar<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Computes a width-\\(w\\) "Non-Adjacent Form" of this scalar.
|
||||||
|
///
|
||||||
|
/// Thanks to curve25519-dalek for the original implementation that informed this one.
|
||||||
|
///
|
||||||
|
/// The full scalar field MUST fit in 256 bits in this implementation.
|
||||||
|
fn non_adjacent_form(&self, w: usize) -> [i8; 256] {
|
||||||
|
// required by the NAF definition
|
||||||
|
debug_assert!(w >= 2);
|
||||||
|
// required so that the NAF digits fit in i8
|
||||||
|
debug_assert!(w <= 8);
|
||||||
|
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
|
||||||
|
// NB: Assumes a scalar that fits in 256 bits.
|
||||||
|
let mut naf = [0i8; 256];
|
||||||
|
|
||||||
|
let mut x_u64 = [0u64; 5];
|
||||||
|
LittleEndian::read_u64_into(&self.to_bytes(), &mut x_u64[0..4]);
|
||||||
|
|
||||||
|
let width = 1 << w;
|
||||||
|
let window_mask = width - 1;
|
||||||
|
|
||||||
|
let mut pos = 0;
|
||||||
|
let mut carry = 0;
|
||||||
|
while pos < 256 {
|
||||||
|
// Construct a buffer of bits of the scalar, starting at bit `pos`
|
||||||
|
let u64_idx = pos / 64;
|
||||||
|
let bit_idx = pos % 64;
|
||||||
|
let bit_buf: u64;
|
||||||
|
if bit_idx < 64 - w {
|
||||||
|
// This window's bits are contained in a single u64
|
||||||
|
bit_buf = x_u64[u64_idx] >> bit_idx;
|
||||||
|
} else {
|
||||||
|
// Combine the current u64's bits with the bits from the next u64
|
||||||
|
bit_buf = (x_u64[u64_idx] >> bit_idx) | (x_u64[1 + u64_idx] << (64 - bit_idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the carry into the current window
|
||||||
|
let window = carry + (bit_buf & window_mask);
|
||||||
|
|
||||||
|
if window & 1 == 0 {
|
||||||
|
// If the window value is even, preserve the carry and continue.
|
||||||
|
// Why is the carry preserved?
|
||||||
|
// If carry == 0 and window & 1 == 0, then the next carry should be 0
|
||||||
|
// If carry == 1 and window & 1 == 0, then bit_buf & 1 == 1 so the next carry should be 1
|
||||||
|
pos += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if window < width / 2 {
|
||||||
|
carry = 0;
|
||||||
|
naf[pos] = window as i8;
|
||||||
|
} else {
|
||||||
|
carry = 1;
|
||||||
|
naf[pos] = (window as i8).wrapping_sub(width as i8);
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += w;
|
||||||
|
}
|
||||||
|
|
||||||
|
naf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for variable-time multiscalar multiplication without precomputation.
|
||||||
|
///
|
||||||
|
/// Implement for a group element.
|
||||||
|
pub trait VartimeMultiscalarMul: Clone {
|
||||||
|
/// Given an iterator of public scalars and an iterator of
|
||||||
|
/// `Option`s of group elements, compute either `Some(Q)`, where
|
||||||
|
/// $$
|
||||||
|
/// Q = c\_1 E\_1 + \cdots + c\_n E\_n,
|
||||||
|
/// $$
|
||||||
|
/// if all points were `Some(E_i)`, or else return `None`.
|
||||||
|
fn optional_multiscalar_mul<I, J>(scalars: I, elements: J) -> Option<Self>
|
||||||
|
where
|
||||||
|
I: IntoIterator,
|
||||||
|
I::Item: Borrow<Scalar>,
|
||||||
|
J: IntoIterator<Item = Option<Self>>;
|
||||||
|
|
||||||
|
/// Given an iterator of public scalars and an iterator of
|
||||||
|
/// public group elements, compute
|
||||||
|
/// $$
|
||||||
|
/// Q = c\_1 E\_1 + \cdots + c\_n E\_n,
|
||||||
|
/// $$
|
||||||
|
/// using variable-time operations.
|
||||||
|
///
|
||||||
|
/// It is an error to call this function with two iterators of different lengths.
|
||||||
|
fn vartime_multiscalar_mul<I, J>(scalars: I, elements: J) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator,
|
||||||
|
I::Item: Borrow<Scalar>,
|
||||||
|
J: IntoIterator,
|
||||||
|
J::Item: Borrow<Self>,
|
||||||
|
{
|
||||||
|
Self::optional_multiscalar_mul(
|
||||||
|
scalars,
|
||||||
|
elements.into_iter().map(|e| Some(e.borrow().clone())),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> VartimeMultiscalarMul for Element<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn optional_multiscalar_mul<I, J>(scalars: I, elements: J) -> Option<Element<C>>
|
||||||
|
where
|
||||||
|
I: IntoIterator,
|
||||||
|
I::Item: Borrow<Scalar>,
|
||||||
|
J: IntoIterator<Item = Option<Element<C>>>,
|
||||||
|
{
|
||||||
|
let nafs: Vec<_> = scalars
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| c.borrow().non_adjacent_form(5))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let lookup_tables = elements
|
||||||
|
.into_iter()
|
||||||
|
.map(|P_opt| P_opt.map(|P| LookupTable5::<Element<C>>::from(&P)))
|
||||||
|
.collect::<Option<Vec<_>>>()?;
|
||||||
|
|
||||||
|
let mut r = <C::Group as Group>::identity();
|
||||||
|
|
||||||
|
for i in (0..256).rev() {
|
||||||
|
let mut t = r + r.clone();
|
||||||
|
|
||||||
|
for (naf, lookup_table) in nafs.iter().zip(lookup_tables.iter()) {
|
||||||
|
if naf[i] > 0 {
|
||||||
|
t = &t + &lookup_table.select(naf[i] as usize);
|
||||||
|
} else if naf[i] < 0 {
|
||||||
|
t = &t - &lookup_table.select(-naf[i] as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Holds odd multiples 1A, 3A, ..., 15A of a point A.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) struct LookupTable5<T>(pub(crate) [T; 8]);
|
||||||
|
|
||||||
|
impl<T: Copy> LookupTable5<T> {
|
||||||
|
/// Given public, odd \\( x \\) with \\( 0 < x < 2^4 \\), return \\(xA\\).
|
||||||
|
pub fn select(&self, x: usize) -> T {
|
||||||
|
debug_assert_eq!(x & 1, 1);
|
||||||
|
debug_assert!(x < 16);
|
||||||
|
|
||||||
|
self.0[x / 2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Debug> Debug for LookupTable5<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result {
|
||||||
|
write!(f, "LookupTable5({:?})", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, C> From<&'a <C::Group·as·Group>::Element> for LookupTable5<<C::Group·as·Group>::Element>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn from(A: &'a <C::Group·as·Group>::Element) -> Self {
|
||||||
|
let mut Ai = [A; 8];
|
||||||
|
let A2 = A * A.clone();
|
||||||
|
for i in 0..7 {
|
||||||
|
Ai[i + 1] = (&A2 + &Ai[i]);
|
||||||
|
}
|
||||||
|
// Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A]
|
||||||
|
LookupTable5(Ai)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
//! Schnorr signatures over prime order groups (or subgroups)
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
// use hex::FromHex;
|
||||||
|
|
||||||
|
use crate::{Ciphersuite, Error, Field, Group};
|
||||||
|
|
||||||
|
/// A Schnorr signature over some prime order group (or subgroup).
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct Signature<C: Ciphersuite> {
|
||||||
|
/// The commitment `R` to the signature nonce.
|
||||||
|
pub(crate) R: <C::Group as Group>::Element,
|
||||||
|
/// The response `z` to the challenge computed from the commitment `R`, the verifying key, and
|
||||||
|
/// the message.
|
||||||
|
pub(crate) z: <<C::Group as Group>::Field as Field>::Scalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Signature<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
C::Group: Group,
|
||||||
|
<C::Group as Group>::Field: Field,
|
||||||
|
{
|
||||||
|
/// Converts bytes as [`C::SignatureSerialization`] into a `Signature<C>`.
|
||||||
|
pub fn from_bytes(bytes: C::SignatureSerialization) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
<<C::Group as Group>::Serialization as TryFrom<Vec<u8>>>::Error: Debug,
|
||||||
|
<<<C::Group as Group>::Field as Field>::Serialization as TryFrom<Vec<u8>>>::Error: Debug,
|
||||||
|
{
|
||||||
|
let mut R_bytes = Vec::from(<C::Group as Group>::Serialization::default().as_ref());
|
||||||
|
|
||||||
|
let R_bytes_len = R_bytes.len();
|
||||||
|
|
||||||
|
R_bytes[..].copy_from_slice(&bytes.as_ref()[0..R_bytes_len]);
|
||||||
|
|
||||||
|
println!("{:?}", R_bytes);
|
||||||
|
|
||||||
|
let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?;
|
||||||
|
|
||||||
|
let mut z_bytes =
|
||||||
|
Vec::from(<<C::Group as Group>::Field as Field>::Serialization::default().as_ref());
|
||||||
|
|
||||||
|
let z_bytes_len = z_bytes.len();
|
||||||
|
|
||||||
|
z_bytes[..].copy_from_slice(&bytes.as_ref()[R_bytes_len..z_bytes_len]);
|
||||||
|
|
||||||
|
println!("{:?}", z_bytes);
|
||||||
|
|
||||||
|
let z_serialization = &z_bytes.try_into().map_err(|_| Error::MalformedSignature)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
R: <C::Group as Group>::deserialize(R_serialization)?,
|
||||||
|
z: <<C::Group as Group>::Field as Field>::deserialize(z_serialization)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this signature to its [`C::SignatureSerialization`] in bytes.
|
||||||
|
pub fn to_bytes(&self) -> C::SignatureSerialization
|
||||||
|
where
|
||||||
|
<<C as Ciphersuite>::SignatureSerialization as TryFrom<Vec<u8>>>::Error: Debug,
|
||||||
|
{
|
||||||
|
let mut bytes = vec![];
|
||||||
|
|
||||||
|
bytes.extend(<C::Group as Group>::serialize(&self.R).as_ref());
|
||||||
|
bytes.extend(<<C::Group as Group>::Field as Field>::serialize(&self.z).as_ref());
|
||||||
|
|
||||||
|
bytes.try_into().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Ciphersuite> std::fmt::Debug for Signature<C> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Signature")
|
||||||
|
.field(
|
||||||
|
"R",
|
||||||
|
&hex::encode(<C::Group as Group>::serialize(&self.R).as_ref()),
|
||||||
|
)
|
||||||
|
.field(
|
||||||
|
"z",
|
||||||
|
&hex::encode(<<C::Group as Group>::Field as Field>::serialize(&self.z).as_ref()),
|
||||||
|
)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl<C> FromHex for Signature<C>
|
||||||
|
// where
|
||||||
|
// C: Ciphersuite,
|
||||||
|
// {
|
||||||
|
// type Error = &'static str;
|
||||||
|
|
||||||
|
// fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
|
||||||
|
// match FromHex::from_hex(hex) {
|
||||||
|
// Ok(bytes) => Self::from_bytes(bytes).map_err(|_| "malformed signature encoding"),
|
||||||
|
// Err(_) => Err("invalid hex"),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
|
@ -0,0 +1,73 @@
|
||||||
|
//! Schnorr signature signing keys
|
||||||
|
|
||||||
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
|
||||||
|
use crate::{Ciphersuite, Error, Field, Group, Signature, VerifyingKey};
|
||||||
|
|
||||||
|
/// A signing key for a Schnorr signature on a FROST [`Ciphersuite::Group`].
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct SigningKey<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
scalar: <<C::Group as Group>::Field as Field>::Scalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> SigningKey<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
/// Generate a new signing key.
|
||||||
|
pub fn new<R: RngCore + CryptoRng>(mut rng: R) -> SigningKey<C> {
|
||||||
|
let scalar = <<C::Group as Group>::Field as Field>::random_nonzero(&mut rng);
|
||||||
|
|
||||||
|
SigningKey { scalar }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize from bytes
|
||||||
|
pub fn from_bytes(
|
||||||
|
bytes: <<C::Group as Group>::Field as Field>::Serialization,
|
||||||
|
) -> Result<SigningKey<C>, Error> {
|
||||||
|
<<C::Group as Group>::Field as Field>::deserialize(&bytes)
|
||||||
|
.map(|scalar| SigningKey { scalar })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize `SigningKey` to bytes
|
||||||
|
pub fn to_bytes(&self) -> <<C::Group as Group>::Field as Field>::Serialization {
|
||||||
|
<<C::Group as Group>::Field as Field>::serialize(&self.scalar)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a signature `msg` using this `SigningKey`.
|
||||||
|
pub fn sign<R: RngCore + CryptoRng>(&self, mut rng: R, msg: &[u8]) -> Signature<C> {
|
||||||
|
let k = <<C::Group as Group>::Field as Field>::random_nonzero(&mut rng);
|
||||||
|
|
||||||
|
let R = <C::Group as Group>::generator() * k;
|
||||||
|
|
||||||
|
// Generate Schnorr challenge
|
||||||
|
let c = crate::challenge::<C>(&R, &VerifyingKey::<C>::from(*self).element, msg);
|
||||||
|
|
||||||
|
let z = k + (c.0 * self.scalar);
|
||||||
|
|
||||||
|
Signature { R, z }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> From<&SigningKey<C>> for VerifyingKey<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn from(signing_key: &SigningKey<C>) -> Self {
|
||||||
|
VerifyingKey {
|
||||||
|
element: C::Group::generator() * signing_key.scalar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> From<SigningKey<C>> for VerifyingKey<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn from(signing_key: SigningKey<C>) -> Self {
|
||||||
|
VerifyingKey::<C>::from(&signing_key)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
use std::fmt::{self, Debug};
|
||||||
|
|
||||||
|
use hex::FromHex;
|
||||||
|
|
||||||
|
use crate::{Ciphersuite, Error, Group, Signature};
|
||||||
|
|
||||||
|
/// A valid verifying key for Schnorr signatures over a FROST [`Ciphersuite::Group`].
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
pub struct VerifyingKey<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
pub(crate) element: <C::Group as Group>::Element,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> VerifyingKey<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
// pub(crate) fn from(scalar: &<<C::Group as Group>::Field as Field>::Scalar) -> Self {
|
||||||
|
// let element = <C::Group as Group>::generator() * *scalar;
|
||||||
|
|
||||||
|
// VerifyingKey { element }
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// Deserialize from bytes
|
||||||
|
pub fn from_bytes(bytes: <C::Group as Group>::Serialization) -> Result<VerifyingKey<C>, Error> {
|
||||||
|
<C::Group as Group>::deserialize(&bytes).map(|element| VerifyingKey { element })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize `VerifyingKey` to bytes
|
||||||
|
pub fn to_bytes(&self) -> <C::Group as Group>::Serialization {
|
||||||
|
<C::Group as Group>::serialize(&self.element)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify a purported `signature` over `msg` made by this verification key.
|
||||||
|
pub fn verify(&self, msg: &[u8], signature: &Signature<C>) -> Result<(), Error> {
|
||||||
|
let c = crate::challenge::<C>(&signature.R, &self.element, msg);
|
||||||
|
|
||||||
|
// Verify check is h * ( - z * B + R + c * A) == 0
|
||||||
|
// h * ( z * B - c * A - R) == 0
|
||||||
|
//
|
||||||
|
// where h is the cofactor
|
||||||
|
let zB = C::Group::generator() * signature.z;
|
||||||
|
let cA = self.element * c.0;
|
||||||
|
let check = (zB - cA - signature.R) * C::Group::cofactor();
|
||||||
|
|
||||||
|
if check == C::Group::identity() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidSignature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Debug for VerifyingKey<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_tuple("VerifyingKey")
|
||||||
|
.field(&hex::encode(self.to_bytes()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> FromHex for VerifyingKey<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
|
||||||
|
match FromHex::from_hex(hex) {
|
||||||
|
Ok(bytes) => Self::from_bytes(bytes).map_err(|_| "malformed verifying key encoding"),
|
||||||
|
Err(_) => Err("invalid hex"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl<C: Ciphersuite> From<VerifyingKey<C>> for <C::Group as Group>::ElementSerialization {
|
||||||
|
// fn from(pk: VerifyingKey<C>) -> <C::Group as Group>::ElementSerialization {
|
||||||
|
// pk.bytes.bytes
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl<C: Ciphersuite> TryFrom<<C::Group as Group>::ElementSerialization> for VerifyingKey<C> {
|
||||||
|
// type Error = Error;
|
||||||
|
|
||||||
|
// fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
|
||||||
|
// VerifyingKeyBytes::from(bytes).try_into()
|
||||||
|
// }
|
||||||
|
// }
|
|
@ -0,0 +1,62 @@
|
||||||
|
use rand::thread_rng;
|
||||||
|
|
||||||
|
use frost_core::*;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
use common::ciphersuite::Ristretto255Sha512 as R;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn batch_verify() {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let mut batch = batch::Verifier::<R>::new();
|
||||||
|
for _ in 0..32 {
|
||||||
|
let sk = SigningKey::new(&mut rng);
|
||||||
|
let vk = VerifyingKey::<R>::from(&sk);
|
||||||
|
let msg = b"BatchVerifyTest";
|
||||||
|
let sig = sk.sign(&mut rng, &msg[..]);
|
||||||
|
batch.queue((vk.into(), sig, msg));
|
||||||
|
}
|
||||||
|
assert!(batch.verify(rng).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bad_batch_verify() {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let bad_index = 4; // must be even
|
||||||
|
let mut batch = batch::Verifier::<R>::new();
|
||||||
|
let mut items = Vec::new();
|
||||||
|
for i in 0..32 {
|
||||||
|
let item: batch::Item<R> = match i % 2 {
|
||||||
|
0 => {
|
||||||
|
let sk = SigningKey::new(&mut rng);
|
||||||
|
let vk = VerifyingKey::<R>::from(&sk);
|
||||||
|
let msg = b"BatchVerifyTest";
|
||||||
|
let sig = if i != bad_index {
|
||||||
|
sk.sign(&mut rng, &msg[..])
|
||||||
|
} else {
|
||||||
|
sk.sign(&mut rng, b"bad")
|
||||||
|
};
|
||||||
|
(vk.into(), sig, msg).into()
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let sk = SigningKey::new(&mut rng);
|
||||||
|
let vk = VerifyingKey::<R>::from(&sk);
|
||||||
|
let msg = b"BatchVerifyTest";
|
||||||
|
let sig = sk.sign(&mut rng, &msg[..]);
|
||||||
|
(vk.into(), sig, msg).into()
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
items.push(item.clone());
|
||||||
|
batch.queue(item);
|
||||||
|
}
|
||||||
|
assert!(batch.verify(rng).is_err());
|
||||||
|
for (i, item) in items.drain(..).enumerate() {
|
||||||
|
if i != bad_index {
|
||||||
|
assert!(item.verify_single().is_ok());
|
||||||
|
} else {
|
||||||
|
assert!(item.verify_single().is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use curve25519_dalek::{
|
||||||
|
constants::{BASEPOINT_ORDER, RISTRETTO_BASEPOINT_POINT},
|
||||||
|
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||||
|
scalar::Scalar,
|
||||||
|
traits::Identity,
|
||||||
|
};
|
||||||
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
use sha2::{digest::Update, Digest, Sha512};
|
||||||
|
|
||||||
|
use frost_core::{Ciphersuite, Error, Field, Group};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct RistrettoScalarField;
|
||||||
|
|
||||||
|
impl Field for RistrettoScalarField {
|
||||||
|
type Scalar = Scalar;
|
||||||
|
|
||||||
|
type Serialization = [u8; 32];
|
||||||
|
|
||||||
|
fn zero() -> Self::Scalar {
|
||||||
|
Scalar::zero()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn one() -> Self::Scalar {
|
||||||
|
Scalar::one()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invert(scalar: &Self::Scalar) -> Result<Self::Scalar, Error> {
|
||||||
|
// [`curve25519_dalek::scalar::Scalar`]'s Eq/PartialEq does a constant-time comparison using
|
||||||
|
// `ConstantTimeEq`
|
||||||
|
if *scalar == <Self as Field>::zero() {
|
||||||
|
Err(Error::InvalidZeroScalar)
|
||||||
|
} else {
|
||||||
|
Ok(scalar.invert())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
|
||||||
|
Scalar::random(rng)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random_nonzero<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
|
||||||
|
loop {
|
||||||
|
let scalar = Scalar::random(rng);
|
||||||
|
|
||||||
|
// This impl of `Eq` calls to `ConstantTimeEq` under the hood
|
||||||
|
if scalar != Scalar::zero() {
|
||||||
|
return scalar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize(scalar: &Self::Scalar) -> Self::Serialization {
|
||||||
|
scalar.to_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize(buf: &Self::Serialization) -> Result<Self::Scalar, Error> {
|
||||||
|
match Scalar::from_canonical_bytes(*buf) {
|
||||||
|
Some(s) => Ok(s),
|
||||||
|
None => Err(Error::MalformedScalar),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub struct RistrettoGroup;
|
||||||
|
|
||||||
|
impl Group for RistrettoGroup {
|
||||||
|
type Field = RistrettoScalarField;
|
||||||
|
|
||||||
|
type Element = RistrettoPoint;
|
||||||
|
|
||||||
|
type Serialization = [u8; 32];
|
||||||
|
|
||||||
|
fn order() -> <Self::Field as Field>::Scalar {
|
||||||
|
BASEPOINT_ORDER
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cofactor() -> <Self::Field as Field>::Scalar {
|
||||||
|
Scalar::one()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn identity() -> Self::Element {
|
||||||
|
RistrettoPoint::identity()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generator() -> Self::Element {
|
||||||
|
RISTRETTO_BASEPOINT_POINT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize(element: &Self::Element) -> Self::Serialization {
|
||||||
|
element.compress().to_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize(buf: &Self::Serialization) -> Result<Self::Element, Error> {
|
||||||
|
match CompressedRistretto::from_slice(buf.as_ref()).decompress() {
|
||||||
|
Some(point) => Ok(point),
|
||||||
|
None => Err(Error::MalformedElement),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Context string 'FROST-RISTRETTO255-SHA512' from the ciphersuite in the [spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.txt
|
||||||
|
const CONTEXT_STRING: &str = "FROST-RISTRETTO255-SHA512";
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub struct Ristretto255Sha512;
|
||||||
|
|
||||||
|
impl Ciphersuite for Ristretto255Sha512 {
|
||||||
|
type Group = RistrettoGroup;
|
||||||
|
|
||||||
|
type HashOutput = [u8; 64];
|
||||||
|
|
||||||
|
type SignatureSerialization = [u8; 64];
|
||||||
|
|
||||||
|
/// H1 for FROST(ristretto255, SHA-512)
|
||||||
|
///
|
||||||
|
/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash
|
||||||
|
fn H1(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
|
||||||
|
let h = Sha512::new()
|
||||||
|
.chain(CONTEXT_STRING.as_bytes())
|
||||||
|
.chain("rho")
|
||||||
|
.chain(m);
|
||||||
|
|
||||||
|
let mut output = [0u8; 64];
|
||||||
|
output.copy_from_slice(h.finalize().as_slice());
|
||||||
|
<<Self::Group as Group>::Field as Field>::Scalar::from_bytes_mod_order_wide(&output)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// H2 for FROST(ristretto255, SHA-512)
|
||||||
|
///
|
||||||
|
/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash-function-dep-hash
|
||||||
|
fn H2(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
|
||||||
|
let h = Sha512::new()
|
||||||
|
.chain(CONTEXT_STRING.as_bytes())
|
||||||
|
.chain("chal")
|
||||||
|
.chain(m);
|
||||||
|
|
||||||
|
let mut output = [0u8; 64];
|
||||||
|
output.copy_from_slice(h.finalize().as_slice());
|
||||||
|
<<Self::Group as Group>::Field as Field>::Scalar::from_bytes_mod_order_wide(&output)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// H3 for FROST(ristretto255, SHA-512)
|
||||||
|
///
|
||||||
|
/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash-function-dep-hash
|
||||||
|
fn H3(m: &[u8]) -> Self::HashOutput {
|
||||||
|
let h = Sha512::new()
|
||||||
|
.chain(CONTEXT_STRING.as_bytes())
|
||||||
|
.chain("digest")
|
||||||
|
.chain(m);
|
||||||
|
|
||||||
|
let mut output = [0u8; 64];
|
||||||
|
output.copy_from_slice(h.finalize().as_slice());
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_parameterized_types() {
|
||||||
|
let h3_image = Ristretto255Sha512::H3(b"test_message");
|
||||||
|
|
||||||
|
println!("h3_image: {:?}", h3_image);
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
//! Shared code for `frost-ristretto255` integration tests.
|
||||||
|
//!
|
||||||
|
//! # Warning
|
||||||
|
//!
|
||||||
|
//! Test functions in this file and its submodules will not be run.
|
||||||
|
//! This file is only for test library code.
|
||||||
|
//!
|
||||||
|
//! This module uses the legacy directory structure,
|
||||||
|
//! to avoid compiling an empty "common" test binary:
|
||||||
|
//! <https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-tests>
|
||||||
|
|
||||||
|
pub mod ciphersuite;
|
||||||
|
pub mod vectors;
|
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"MAX_SIGNERS": "3",
|
||||||
|
"NUM_SIGNERS": "2",
|
||||||
|
"THRESHOLD_LIMIT": "2",
|
||||||
|
"name": "FROST(ristretto255, SHA-512)",
|
||||||
|
"group": "ristretto255",
|
||||||
|
"hash": "SHA-512"
|
||||||
|
},
|
||||||
|
"inputs": {
|
||||||
|
"group_secret_key": "b120be204b5e758960458ca9c4675b56b12a8faff2be9c94891d5e1cd75c880e",
|
||||||
|
"group_public_key": "563b80013f337deaa2a282af7b281bd70d2f501928a89c1aa48b379a5ac4202b",
|
||||||
|
"message": "74657374",
|
||||||
|
"signers": {
|
||||||
|
"1": {
|
||||||
|
"signer_share": "94ae65bb90030a89507fa00fff08dfed841cf996de5a0c574f1f4693ddcb6705"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"signer_share": "641003b3f00bb1e01656ac1818a4419a580e637ecaf67b1915212e0ae43a470c"
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"signer_share": "479eaa4d36b145e00690c07e5245c5312c00cd65b692ebdbda221681eaa92603"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"round_one_outputs": {
|
||||||
|
"participants": "1,2",
|
||||||
|
"group_binding_factor_input": "0001824e9eddddf02b2a9caf5859825e999d791ca094f65b814a8bca6013d9cc312774c7e1271d2939a84a9a867e3a06579b4d25659b427439ccf0d745b43f75b76600028013834ff4d48e7d6b76c2e732bc611f54720ef8933c4ca4de7eaaa77ff5cd125e056ecc4f7c4657d3a742354430d768f945db229c335d258e9622ad99f3e7582d07b35bd9849ce4af6ad403090d69a7d0eb88bba669a9f985175d70cd15ad5f1ef5b734c98a32b4aab7b43a57e93fc09281f2e7a207076b31e416ba63f53d9d",
|
||||||
|
"group_binding_factor": "f00ae6007f2d74a1507c962cf30006be77596106db28f2d5443fd66d755e780c",
|
||||||
|
"signers": {
|
||||||
|
"1": {
|
||||||
|
"hiding_nonce": "349b3bb8464a1d87f7d6b56f4559a3f9a6335261a3266089a9b12d9d6f6ce209",
|
||||||
|
"binding_nonce": "ce7406016a854be4291f03e7d24fe30e77994c3465de031515a4c116f22ca901",
|
||||||
|
"hiding_nonce_commitment": "824e9eddddf02b2a9caf5859825e999d791ca094f65b814a8bca6013d9cc3127",
|
||||||
|
"binding_nonce_commitment": "74c7e1271d2939a84a9a867e3a06579b4d25659b427439ccf0d745b43f75b766"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"hiding_nonce": "4d66d319f20a728ec3d491cbf260cc6be687bd87cc2b5fdb4d5f528f65fd650d",
|
||||||
|
"binding_nonce": "278b9b1e04632e6af3f1a3c144d07922ffcf5efd3a341b47abc19c43f48ce306",
|
||||||
|
"hiding_nonce_commitment": "8013834ff4d48e7d6b76c2e732bc611f54720ef8933c4ca4de7eaaa77ff5cd12",
|
||||||
|
"binding_nonce_commitment": "5e056ecc4f7c4657d3a742354430d768f945db229c335d258e9622ad99f3e758"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"round_two_outputs": {
|
||||||
|
"participants": "1,2",
|
||||||
|
"signers": {
|
||||||
|
"1": {
|
||||||
|
"sig_share": "ec6b075f17c5670e80b1fda8f6de1cfe3c79db06a852f8d5650fb71eaad69501"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"sig_share": "87ceccc477069aa9b751b307f25955daaf943a3abc51f214a114781de0f58e03"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"final_output": {
|
||||||
|
"sig": "7e92309bf40993141acd5f2c7680a302cc5aa5dd291a833906da8e35bc39b03e733ad4238fcb01b83703b1b0e83872d8ec0d164164a4eaea06242f3c8acc2405"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
|
use curve25519_dalek::scalar::Scalar;
|
||||||
|
use hex::{self, FromHex};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use frost_core::{
|
||||||
|
frost::{keys::*, round1::*, round2::*, *},
|
||||||
|
VerifyingKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::ciphersuite::Ristretto255Sha512;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref RISTRETTO255_SHA512: Value =
|
||||||
|
serde_json::from_str(include_str!("vectors.json").trim())
|
||||||
|
.expect("Test vector is valid JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn parse_test_vectors() -> (
|
||||||
|
VerifyingKey<Ristretto255Sha512>,
|
||||||
|
HashMap<u16, KeyPackage<Ristretto255Sha512>>,
|
||||||
|
&'static str,
|
||||||
|
Vec<u8>,
|
||||||
|
HashMap<u16, SigningNonces<Ristretto255Sha512>>,
|
||||||
|
HashMap<u16, SigningCommitments<Ristretto255Sha512>>,
|
||||||
|
Vec<u8>,
|
||||||
|
Rho<Ristretto255Sha512>,
|
||||||
|
HashMap<u16, SignatureShare<Ristretto255Sha512>>,
|
||||||
|
Vec<u8>, // Signature<Ristretto255Sha512>,
|
||||||
|
) {
|
||||||
|
type R = Ristretto255Sha512;
|
||||||
|
|
||||||
|
let inputs = &RISTRETTO255_SHA512["inputs"];
|
||||||
|
|
||||||
|
let message = inputs["message"].as_str().unwrap();
|
||||||
|
let message_bytes = hex::decode(message).unwrap();
|
||||||
|
|
||||||
|
let mut key_packages: HashMap<u16, KeyPackage<R>> = HashMap::new();
|
||||||
|
|
||||||
|
let possible_signers = RISTRETTO255_SHA512["inputs"]["signers"]
|
||||||
|
.as_object()
|
||||||
|
.unwrap()
|
||||||
|
.iter();
|
||||||
|
|
||||||
|
let group_public =
|
||||||
|
VerifyingKey::<R>::from_hex(inputs["group_public_key"].as_str().unwrap()).unwrap();
|
||||||
|
|
||||||
|
for (i, secret_share) in possible_signers {
|
||||||
|
let secret = Secret::<R>::from_hex(secret_share["signer_share"].as_str().unwrap()).unwrap();
|
||||||
|
let signer_public = secret.into();
|
||||||
|
|
||||||
|
let key_package = KeyPackage::<R> {
|
||||||
|
index: u16::from_str(i).unwrap(),
|
||||||
|
secret_share: secret,
|
||||||
|
public: signer_public,
|
||||||
|
group_public,
|
||||||
|
};
|
||||||
|
|
||||||
|
key_packages.insert(*key_package.index(), key_package);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round one outputs
|
||||||
|
|
||||||
|
let round_one_outputs = &RISTRETTO255_SHA512["round_one_outputs"];
|
||||||
|
|
||||||
|
let group_binding_factor_input = Vec::<u8>::from_hex(
|
||||||
|
round_one_outputs["group_binding_factor_input"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let group_binding_factor =
|
||||||
|
Rho::<R>::from_hex(round_one_outputs["group_binding_factor"].as_str().unwrap()).unwrap();
|
||||||
|
|
||||||
|
let mut signer_nonces: HashMap<u16, SigningNonces<R>> = HashMap::new();
|
||||||
|
let mut signer_commitments: HashMap<u16, SigningCommitments<R>> = HashMap::new();
|
||||||
|
|
||||||
|
for (i, signer) in round_one_outputs["signers"].as_object().unwrap().iter() {
|
||||||
|
let index = u16::from_str(i).unwrap();
|
||||||
|
|
||||||
|
let signing_nonces = SigningNonces::<R> {
|
||||||
|
hiding: Nonce::<R>::from_hex(signer["hiding_nonce"].as_str().unwrap()).unwrap(),
|
||||||
|
binding: Nonce::<R>::from_hex(signer["binding_nonce"].as_str().unwrap()).unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
signer_nonces.insert(index, signing_nonces);
|
||||||
|
|
||||||
|
let signing_commitments = SigningCommitments::<R> {
|
||||||
|
index,
|
||||||
|
hiding: NonceCommitment::from_hex(signer["hiding_nonce_commitment"].as_str().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
binding: NonceCommitment::from_hex(
|
||||||
|
signer["binding_nonce_commitment"].as_str().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
signer_commitments.insert(index, signing_commitments);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round two outputs
|
||||||
|
|
||||||
|
let round_two_outputs = &RISTRETTO255_SHA512["round_two_outputs"];
|
||||||
|
|
||||||
|
let mut signature_shares: HashMap<u16, SignatureShare<R>> = HashMap::new();
|
||||||
|
|
||||||
|
for (i, signer) in round_two_outputs["signers"].as_object().unwrap().iter() {
|
||||||
|
let signature_share = SignatureShare::<R> {
|
||||||
|
index: u16::from_str(i).unwrap(),
|
||||||
|
signature: SignatureResponse {
|
||||||
|
z_share: Scalar::from_canonical_bytes(
|
||||||
|
hex::decode(signer["sig_share"].as_str().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
signature_shares.insert(u16::from_str(i).unwrap(), signature_share);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final output
|
||||||
|
|
||||||
|
let final_output = &RISTRETTO255_SHA512["final_output"];
|
||||||
|
|
||||||
|
let signature_bytes = FromHex::from_hex(final_output["sig"].as_str().unwrap()).unwrap();
|
||||||
|
|
||||||
|
(
|
||||||
|
group_public,
|
||||||
|
key_packages,
|
||||||
|
message,
|
||||||
|
message_bytes,
|
||||||
|
signer_nonces,
|
||||||
|
signer_commitments,
|
||||||
|
group_binding_factor_input,
|
||||||
|
group_binding_factor,
|
||||||
|
signature_shares,
|
||||||
|
signature_bytes,
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
use std::{collections::HashMap, convert::TryFrom};
|
||||||
|
|
||||||
|
use frost_core::frost;
|
||||||
|
use rand::thread_rng;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
use common::ciphersuite::Ristretto255Sha512 as R;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_sign_with_dealer() {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Key generation
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
let numsigners = 5;
|
||||||
|
let threshold = 3;
|
||||||
|
let (shares, pubkeys) =
|
||||||
|
frost::keys::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap();
|
||||||
|
|
||||||
|
// Verifies the secret shares from the dealer
|
||||||
|
let key_packages: Vec<frost::keys::KeyPackage<R>> = shares
|
||||||
|
.into_iter()
|
||||||
|
.map(|share| frost::keys::KeyPackage::try_from(share).unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut nonces: HashMap<u16, Vec<frost::round1::SigningNonces<R>>> = HashMap::new();
|
||||||
|
let mut commitments: HashMap<u16, Vec<frost::round1::SigningCommitments<R>>> = HashMap::new();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Round 1: generating nonces and signing commitments for each participant
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
for participant_index in 1..(threshold + 1) {
|
||||||
|
// Generate one (1) nonce and one SigningCommitments instance for each
|
||||||
|
// participant, up to _threshold_.
|
||||||
|
let (nonce, commitment) = frost::round1::preprocess(1, participant_index as u16, &mut rng);
|
||||||
|
nonces.insert(participant_index as u16, nonce);
|
||||||
|
commitments.insert(participant_index as u16, commitment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is what the signature aggregator / coordinator needs to do:
|
||||||
|
// - decide what message to sign
|
||||||
|
// - take one (unused) commitment per signing participant
|
||||||
|
let mut signature_shares: Vec<frost::round2::SignatureShare<R>> = Vec::new();
|
||||||
|
let message = "message to sign".as_bytes();
|
||||||
|
let comms = commitments.clone().into_values().flatten().collect();
|
||||||
|
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Round 2: each participant generates their signature share
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
for participant_index in nonces.keys() {
|
||||||
|
let key_package = key_packages
|
||||||
|
.iter()
|
||||||
|
.find(|key_package| *participant_index == key_package.index)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let nonces_to_use = &nonces.get(participant_index).unwrap()[0];
|
||||||
|
|
||||||
|
// Each participant generates their signature share.
|
||||||
|
let signature_share =
|
||||||
|
frost::round2::sign(&signing_package, nonces_to_use, key_package).unwrap();
|
||||||
|
signature_shares.push(signature_share);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Aggregation: collects the signing shares from all participants,
|
||||||
|
// generates the final signature.
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Aggregate (also verifies the signature shares)
|
||||||
|
let group_signature_res = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys);
|
||||||
|
|
||||||
|
assert!(group_signature_res.is_ok());
|
||||||
|
|
||||||
|
let group_signature = group_signature_res.unwrap();
|
||||||
|
|
||||||
|
// Check that the threshold signature can be verified by the group public
|
||||||
|
// key (the verification key).
|
||||||
|
assert!(pubkeys
|
||||||
|
.group_public
|
||||||
|
.verify(message, &group_signature)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
// Check that the threshold signature can be verified by the group public
|
||||||
|
// key (the verification key) from SharePackage.group_public
|
||||||
|
for (participant_index, _) in nonces.clone() {
|
||||||
|
let key_package = key_packages.get(participant_index as usize).unwrap();
|
||||||
|
|
||||||
|
assert!(key_package
|
||||||
|
.group_public
|
||||||
|
.verify(message, &group_signature)
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
use frost_core::*;
|
||||||
|
use proptest::prelude::*;
|
||||||
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
use common::ciphersuite::Ristretto255Sha512 as R;
|
||||||
|
|
||||||
|
/// A signature test-case, containing signature data and expected validity.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct SignatureCase<C: Ciphersuite> {
|
||||||
|
msg: Vec<u8>,
|
||||||
|
sig: Signature<C>,
|
||||||
|
vk: VerifyingKey<C>,
|
||||||
|
invalid_vk: VerifyingKey<C>,
|
||||||
|
is_valid: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A modification to a test-case.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
enum Tweak {
|
||||||
|
/// No-op, used to check that unchanged cases verify.
|
||||||
|
None,
|
||||||
|
/// Change the message the signature is defined for, invalidating the signature.
|
||||||
|
ChangeMessage,
|
||||||
|
/// Change the public key the signature is defined for, invalidating the signature.
|
||||||
|
ChangePubkey,
|
||||||
|
/* XXX implement this -- needs to regenerate a custom signature because the
|
||||||
|
nonce commitment is fed into the hash, so it has to have torsion at signing
|
||||||
|
time.
|
||||||
|
/// Change the case to have a torsion component in the signature's `r` value.
|
||||||
|
AddTorsion,
|
||||||
|
*/
|
||||||
|
/* XXX implement this -- needs custom handling of field arithmetic.
|
||||||
|
/// Change the signature's `s` scalar to be unreduced (mod L), invalidating the signature.
|
||||||
|
UnreducedScalar,
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> SignatureCase<C>
|
||||||
|
where
|
||||||
|
C: Ciphersuite,
|
||||||
|
{
|
||||||
|
fn new<R: RngCore + CryptoRng>(mut rng: R, msg: Vec<u8>) -> Self {
|
||||||
|
let sk = SigningKey::<C>::new(&mut rng);
|
||||||
|
let sig = sk.sign(&mut rng, &msg);
|
||||||
|
let vk = VerifyingKey::<C>::from(&sk);
|
||||||
|
let invalid_vk = VerifyingKey::<C>::from(&SigningKey::new(&mut rng));
|
||||||
|
Self {
|
||||||
|
msg,
|
||||||
|
sig,
|
||||||
|
vk,
|
||||||
|
invalid_vk,
|
||||||
|
is_valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that signature verification succeeds or fails, as expected.
|
||||||
|
fn check(&self) -> bool {
|
||||||
|
// // The signature data is stored in (refined) byte types, but do a round trip
|
||||||
|
// // conversion to raw bytes to exercise those code paths.
|
||||||
|
// let sig = {
|
||||||
|
// let bytes: [u8; 64] = self.sig.into();
|
||||||
|
// Signature::<C>::from_bytes(bytes)
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // Check that the verification key is a valid key.
|
||||||
|
// let pub_key = VerifyingKey::<C>::from_bytes(pk_bytes)
|
||||||
|
// .expect("The test verification key to be well-formed.");
|
||||||
|
|
||||||
|
// Check that signature validation has the expected result.
|
||||||
|
self.is_valid == self.vk.verify(&self.msg, &self.sig).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_tweak(&mut self, tweak: &Tweak) {
|
||||||
|
match tweak {
|
||||||
|
Tweak::None => {}
|
||||||
|
Tweak::ChangeMessage => {
|
||||||
|
// Changing the message makes the signature invalid.
|
||||||
|
self.msg.push(90);
|
||||||
|
self.is_valid = false;
|
||||||
|
}
|
||||||
|
Tweak::ChangePubkey => {
|
||||||
|
// Changing the public key makes the signature invalid.
|
||||||
|
self.vk = self.invalid_vk;
|
||||||
|
self.is_valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tweak_strategy() -> impl Strategy<Value = Tweak> {
|
||||||
|
prop_oneof![
|
||||||
|
10 => Just(Tweak::None),
|
||||||
|
1 => Just(Tweak::ChangeMessage),
|
||||||
|
1 => Just(Tweak::ChangePubkey),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
use rand_chacha::ChaChaRng;
|
||||||
|
use rand_core::SeedableRng;
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tweak_signature(
|
||||||
|
tweaks in prop::collection::vec(tweak_strategy(), (0,5)),
|
||||||
|
rng_seed in prop::array::uniform32(any::<u8>()),
|
||||||
|
) {
|
||||||
|
// Use a deterministic RNG so that test failures can be reproduced.
|
||||||
|
// Seeding with 64 bits of entropy is INSECURE and this code should
|
||||||
|
// not be copied outside of this test!
|
||||||
|
let mut rng = ChaChaRng::from_seed(rng_seed);
|
||||||
|
|
||||||
|
// Create a test case for each signature type.
|
||||||
|
let msg = b"test message for proptests";
|
||||||
|
let mut sig = SignatureCase::<R>::new(&mut rng, msg.to_vec());
|
||||||
|
|
||||||
|
|
||||||
|
// Apply tweaks to each case.
|
||||||
|
for t in &tweaks {
|
||||||
|
sig.apply_tweak(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(sig.check());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
use frost_core::frost;
|
||||||
|
use rand::thread_rng;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
use common::{ciphersuite::*, vectors::*};
|
||||||
|
|
||||||
|
/// This is testing that Shamir's secret sharing to compute and arbitrary
|
||||||
|
/// value is working.
|
||||||
|
#[test]
|
||||||
|
fn check_share_generation_ristretto255_sha512() {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
|
let secret = frost::keys::Secret::<Ristretto255Sha512>::random(&mut rng);
|
||||||
|
|
||||||
|
let secret_shares = frost::keys::generate_secret_shares(&secret, 5, 3, rng).unwrap();
|
||||||
|
|
||||||
|
for secret_share in secret_shares.iter() {
|
||||||
|
assert_eq!(secret_share.verify(), Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
frost::keys::reconstruct_secret::<Ristretto255Sha512>(secret_shares).unwrap(),
|
||||||
|
secret
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_sign_with_test_vectors() {
|
||||||
|
let (
|
||||||
|
group_public,
|
||||||
|
key_packages,
|
||||||
|
_message,
|
||||||
|
message_bytes,
|
||||||
|
signer_nonces,
|
||||||
|
signer_commitments,
|
||||||
|
group_binding_factor_input,
|
||||||
|
group_binding_factor,
|
||||||
|
signature_shares,
|
||||||
|
signature_bytes,
|
||||||
|
) = parse_test_vectors();
|
||||||
|
|
||||||
|
type R = Ristretto255Sha512;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Key generation
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
for key_package in key_packages.values() {
|
||||||
|
assert_eq!(
|
||||||
|
*key_package.public(),
|
||||||
|
frost::keys::Public::from(*key_package.secret_share())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Round 1: generating nonces and signing commitments for each participant
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
for (i, _) in signer_commitments.clone() {
|
||||||
|
// compute nonce commitments from nonces
|
||||||
|
let nonces = signer_nonces.get(&i).unwrap();
|
||||||
|
let nonce_commitments = signer_commitments.get(&i).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
&frost::round1::NonceCommitment::from(nonces.hiding()),
|
||||||
|
nonce_commitments.hiding()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
&frost::round1::NonceCommitment::from(nonces.binding()),
|
||||||
|
nonce_commitments.binding()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Round 2: each participant generates their signature share
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
let signer_commitments_vec = signer_commitments
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, signing_commitments)| signing_commitments)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let signing_package = frost::SigningPackage::new(signer_commitments_vec, message_bytes);
|
||||||
|
|
||||||
|
assert_eq!(signing_package.rho_preimage(), group_binding_factor_input);
|
||||||
|
|
||||||
|
let rho: frost::Rho<R> = (&signing_package).into();
|
||||||
|
|
||||||
|
assert_eq!(rho, group_binding_factor);
|
||||||
|
|
||||||
|
let mut our_signature_shares: Vec<frost::round2::SignatureShare<R>> = Vec::new();
|
||||||
|
|
||||||
|
// Each participant generates their signature share
|
||||||
|
for index in signer_nonces.keys() {
|
||||||
|
let key_package = &key_packages[index];
|
||||||
|
let nonces = &signer_nonces[index];
|
||||||
|
|
||||||
|
// Each participant generates their signature share.
|
||||||
|
let signature_share = frost::round2::sign(&signing_package, nonces, key_package).unwrap();
|
||||||
|
|
||||||
|
our_signature_shares.push(signature_share);
|
||||||
|
}
|
||||||
|
|
||||||
|
for sig_share in our_signature_shares.clone() {
|
||||||
|
assert_eq!(sig_share, signature_shares[sig_share.index()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let signer_pubkeys = key_packages
|
||||||
|
.into_iter()
|
||||||
|
.map(|(i, key_package)| (i, *key_package.public()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let pubkey_package = frost::keys::PublicKeyPackage {
|
||||||
|
signer_pubkeys,
|
||||||
|
group_public,
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Aggregation: collects the signing shares from all participants,
|
||||||
|
// generates the final signature.
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Aggregate the FROST signature from test vector sig shares
|
||||||
|
let group_signature_result = frost::aggregate(
|
||||||
|
&signing_package,
|
||||||
|
&signature_shares
|
||||||
|
.values()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<frost::round2::SignatureShare<R>>>(),
|
||||||
|
&pubkey_package,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that the aggregation passed signature share verification and generation
|
||||||
|
assert!(group_signature_result.is_ok());
|
||||||
|
|
||||||
|
// Check that the generated signature matches the test vector signature
|
||||||
|
let group_signature = group_signature_result.unwrap();
|
||||||
|
assert_eq!(group_signature.to_bytes().to_vec(), signature_bytes);
|
||||||
|
|
||||||
|
// Aggregate the FROST signature from our signature shares
|
||||||
|
let group_signature_result =
|
||||||
|
frost::aggregate(&signing_package, &our_signature_shares, &pubkey_package);
|
||||||
|
|
||||||
|
// Check that the aggregation passed signature share verification and generation
|
||||||
|
assert!(group_signature_result.is_ok());
|
||||||
|
|
||||||
|
// Check that the generated signature matches the test vector signature
|
||||||
|
let group_signature = group_signature_result.unwrap();
|
||||||
|
assert_eq!(group_signature.to_bytes().to_vec(), signature_bytes);
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ edition = "2021"
|
||||||
# - Update CHANGELOG.md
|
# - Update CHANGELOG.md
|
||||||
# - Create git tag.
|
# - Create git tag.
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Henry de Valence <hdevalence@hdevalence.ca>", "Deirdre Connolly <durumcrustulum@gmail.com>", "Chelsea Komlo <me@chelseakomlo.com>"]
|
authors = [ "Deirdre Connolly <durumcrustulum@gmail.com>", "Chelsea Komlo <me@chelseakomlo.com>", "Henry de Valence <hdevalence@hdevalence.ca>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/ZcashFoundation/frost"
|
repository = "https://github.com/ZcashFoundation/frost"
|
||||||
|
@ -20,7 +20,7 @@ features = ["nightly"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.4"
|
byteorder = "1.4"
|
||||||
curve25519-dalek = { version = "4.0.0-pre.1", features = ["serde"] }
|
curve25519-dalek = { version = "4.0.0-pre.1", features = ["serde"] }
|
||||||
digest = "0.9"
|
# digest = "0.9"
|
||||||
hex = { version = "0.4.3", features = ["serde"] }
|
hex = { version = "0.4.3", features = ["serde"] }
|
||||||
rand_core = "0.6"
|
rand_core = "0.6"
|
||||||
serde = { version = "1", optional = true, features = ["derive"] }
|
serde = { version = "1", optional = true, features = ["derive"] }
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
use curve25519_dalek::{digest::Update, scalar::Scalar};
|
use curve25519_dalek::scalar::Scalar;
|
||||||
use sha2::{Digest, Sha512};
|
use sha2::{digest::Update, Digest, Sha512};
|
||||||
|
|
||||||
pub mod batch;
|
pub mod batch;
|
||||||
mod error;
|
mod error;
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, digest::Update, scalar::Scalar};
|
use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, scalar::Scalar};
|
||||||
use rand_core::{CryptoRng, RngCore};
|
use rand_core::{CryptoRng, RngCore};
|
||||||
use sha2::{Digest, Sha512};
|
use sha2::{Digest, Sha512};
|
||||||
|
|
||||||
|
@ -92,12 +92,15 @@ impl SigningKey {
|
||||||
bytes
|
bytes
|
||||||
};
|
};
|
||||||
|
|
||||||
let nonce = Scalar::from_hash(
|
let mut hasher = Sha512::new();
|
||||||
Sha512::new()
|
hasher.update(&random_bytes[..]);
|
||||||
.chain(&random_bytes[..])
|
hasher.update(&self.pk.bytes.bytes[..]);
|
||||||
.chain(&self.pk.bytes.bytes[..]) // XXX ugly
|
hasher.update(msg);
|
||||||
.chain(msg),
|
|
||||||
);
|
let mut hash_bytes = [0u8; 64];
|
||||||
|
hash_bytes.copy_from_slice(hasher.finalize().as_slice());
|
||||||
|
|
||||||
|
let nonce = Scalar::from_bytes_mod_order_wide(&hash_bytes);
|
||||||
|
|
||||||
// XXX: does this need `RistrettoPoint::from_uniform_bytes()` ?
|
// XXX: does this need `RistrettoPoint::from_uniform_bytes()` ?
|
||||||
let R_bytes = (RISTRETTO_BASEPOINT_POINT * nonce).compress().to_bytes();
|
let R_bytes = (RISTRETTO_BASEPOINT_POINT * nonce).compress().to_bytes();
|
||||||
|
|
Loading…
Reference in New Issue