Small fixes, trying to get test vector sig share verification working

This commit is contained in:
Deirdre Connolly 2022-02-23 23:00:51 -05:00
parent 15706f0b48
commit bf8b138595
11 changed files with 399 additions and 203 deletions

View File

@ -44,7 +44,7 @@ impl<'msg, M: AsRef<[u8]>> From<(VerificationKeyBytes, Signature, &'msg M)> for
fn from((vk_bytes, sig, msg): (VerificationKeyBytes, Signature, &'msg M)) -> Self {
// Compute c now to avoid dependency on the msg lifetime.
let c = crate::generate_challenge(&sig.r_bytes, &vk_bytes.bytes, msg.as_ref());
let c = crate::generate_challenge(&sig.R_bytes, &vk_bytes.bytes, msg.as_ref());
Self { vk_bytes, sig, c }
}
@ -123,12 +123,12 @@ impl Verifier {
let mut P_coeff_acc = Scalar::zero();
for item in self.signatures.iter() {
let (s_bytes, r_bytes, c) = (item.sig.s_bytes, item.sig.r_bytes, item.c);
let (z_bytes, R_bytes, c) = (item.sig.z_bytes, item.sig.R_bytes, item.c);
let s = Scalar::from_bytes_mod_order(s_bytes);
let s = Scalar::from_bytes_mod_order(z_bytes);
let R = {
match CompressedRistretto::from_slice(&r_bytes).decompress() {
match CompressedRistretto::from_slice(&R_bytes).decompress() {
Some(point) => point,
None => return Err(Error::InvalidSignature),
}

View File

@ -21,7 +21,11 @@
//! Internally, keygen_with_dealer generates keys using Verifiable Secret
//! Sharing, where shares are generated using Shamir Secret Sharing.
use std::{collections::HashMap, convert::TryFrom};
use std::{
collections::HashMap,
convert::TryFrom,
fmt::{self, Debug},
};
use curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT,
@ -42,9 +46,18 @@ use crate::{generate_challenge, Signature, VerificationKey, H1, H3};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Secret(pub(crate) Scalar);
// Zeroizes `Secret` 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.
impl Secret {
/// Generates a new uniformly random secret value using the provided RNG.
pub fn random<R>(rng: &mut R) -> Self
where
R: CryptoRng + RngCore,
{
Self(Scalar::random(rng))
}
}
// Zeroizes `Secret` 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.
impl DefaultIsZeroes for Secret {}
impl From<Scalar> for Secret {
@ -106,6 +119,31 @@ pub struct SecretShare {
pub(crate) commitment: ShareCommitment,
}
impl SecretShare {
/// Verifies that a share is consistent with a 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!
pub fn verify(&self) -> Result<(), &'static str> {
let f_result = RISTRETTO_BASEPOINT_POINT * self.value.0;
let x = Scalar::from(self.index as u16);
let (_, result) = self.commitment.0.iter().fold(
(Scalar::one(), RistrettoPoint::identity()),
|(x_to_the_i, sum_so_far), comm_i| (x_to_the_i * x, sum_so_far + comm_i.0 * x_to_the_i),
);
if !(f_result == result) {
return Err("SecretShare is invalid.");
}
Ok(())
}
}
/// A Ristretto point that is a commitment to one coefficient of our secret
/// polynomial.
///
@ -123,10 +161,77 @@ pub(crate) struct Commitment(pub(crate) RistrettoPoint);
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct CoefficientCommitment(pub(crate) RistrettoPoint);
/// A scalar used in Ristretto that is a signing nonce.
#[derive(Clone, Copy, Default, PartialEq)]
pub(crate) struct Nonce(pub(crate) Scalar);
impl Nonce {
/// Generates a new uniformly random signing nonce.
///
/// Each participant generates signing nonces before performing a signing
/// operation.
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(Scalar::random(rng))
}
}
impl AsRef<Scalar> for Nonce {
fn as_ref(&self) -> &Scalar {
&self.0
}
}
impl Debug for Nonce {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Nonce")
.field(&hex::encode(self.0.to_bytes()))
.finish()
}
}
// Zeroizes `Secret` 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.
impl DefaultIsZeroes for Nonce {}
impl FromHex for Nonce {
type Error = &'static str;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let mut bytes = [0u8; 32];
match hex::decode_to_slice(hex, &mut bytes[..]) {
Ok(()) => Self::try_from(bytes),
Err(_) => Err("invalid hex"),
}
}
}
impl TryFrom<[u8; 32]> for Nonce {
type Error = &'static str;
fn try_from(source: [u8; 32]) -> Result<Self, &'static str> {
match Scalar::from_canonical_bytes(source) {
Some(scalar) => Ok(Self(scalar)),
None => Err("ristretto scalar were not canonical byte representation"),
}
}
}
/// A Ristretto point that is a commitment to a signing nonce share.
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct NonceCommitment(pub(crate) RistrettoPoint);
impl From<Nonce> for NonceCommitment {
fn from(nonce: Nonce) -> Self {
Self(RISTRETTO_BASEPOINT_POINT * nonce.0)
}
}
impl FromHex for NonceCommitment {
type Error = &'static str;
@ -168,7 +273,7 @@ pub struct ShareCommitment(pub(crate) Vec<CoefficientCommitment>);
/// The product of all signers' individual commitments, published as part of the
/// final signature.
#[derive(PartialEq)]
#[derive(Debug, PartialEq)]
pub struct GroupCommitment(pub(crate) RistrettoPoint);
impl TryFrom<&SigningPackage> for GroupCommitment {
@ -222,7 +327,7 @@ pub struct SharePackage {
/// 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(Debug)]
#[derive(Copy, Clone, Debug)]
pub struct KeyPackage {
/// Denotes the participant index each secret share key package is owned by.
pub index: u16,
@ -246,7 +351,7 @@ impl TryFrom<SharePackage> for KeyPackage {
/// dealer, but implementations *MUST* make sure that all participants have
/// a consistent view of this commitment in practice.
fn try_from(share_package: SharePackage) -> Result<Self, &'static str> {
verify_secret_share(&share_package.secret_share)?;
share_package.secret_share.verify()?;
Ok(KeyPackage {
index: share_package.index,
@ -287,7 +392,7 @@ pub fn keygen_with_dealer<R: RngCore + CryptoRng>(
let mut bytes = [0; 64];
rng.fill_bytes(&mut bytes);
let secret = Secret(Scalar::random(&mut rng));
let secret = Secret::random(&mut rng);
let group_public = VerificationKey::from(&secret.0);
let secret_shares = generate_secret_shares(&secret, num_signers, threshold, rng)?;
let mut share_packages: Vec<SharePackage> = Vec::with_capacity(num_signers as usize);
@ -315,29 +420,6 @@ pub fn keygen_with_dealer<R: RngCore + CryptoRng>(
))
}
/// Verifies that a share is consistent with a 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!
fn verify_secret_share(secret_share: &SecretShare) -> Result<(), &'static str> {
let f_result = RISTRETTO_BASEPOINT_POINT * secret_share.value.0;
let x = Scalar::from(secret_share.index as u16);
let (_, result) = secret_share.commitment.0.iter().fold(
(Scalar::one(), RistrettoPoint::identity()),
|(x_to_the_i, sum_so_far), comm_i| (x_to_the_i * x, sum_so_far + comm_i.0 * x_to_the_i),
);
if !(f_result == result) {
return Err("SecretShare is invalid.");
}
Ok(())
}
/// Creates secret shares for a given secret.
///
/// This function accepts a secret from which shares are generated. While in
@ -426,8 +508,8 @@ fn generate_secret_shares<R: RngCore + CryptoRng>(
/// signing key.
#[derive(Clone, Copy, Default, Debug)]
pub struct SigningNonces {
hiding: Scalar,
binding: Scalar,
hiding: Nonce,
binding: Nonce,
}
// Zeroizes `SigningNonces` to be the `Default` value on drop (when it goes out of scope). Luckily
@ -444,23 +526,10 @@ impl SigningNonces {
where
R: CryptoRng + RngCore,
{
fn random_nonzero_bytes<R>(rng: &mut R) -> [u8; 64]
where
R: CryptoRng + RngCore,
{
let mut bytes = [0; 64];
loop {
rng.fill_bytes(&mut bytes);
if bytes != [0; 64] {
return bytes;
}
}
}
// The values of 'hiding' and 'binding' must be non-zero so that commitments are
// not the identity.
let hiding = Scalar::from_bytes_mod_order_wide(&random_nonzero_bytes(rng));
let binding = Scalar::from_bytes_mod_order_wide(&random_nonzero_bytes(rng));
let hiding = Nonce::random(rng);
let binding = Nonce::random(rng);
Self { hiding, binding }
}
@ -484,8 +553,8 @@ impl From<(u16, &SigningNonces)> for SigningCommitments {
fn from((index, nonces): (u16, &SigningNonces)) -> Self {
Self {
index,
hiding: NonceCommitment(RISTRETTO_BASEPOINT_POINT * nonces.hiding),
binding: NonceCommitment(RISTRETTO_BASEPOINT_POINT * nonces.binding),
hiding: nonces.hiding.into(),
binding: nonces.binding.into(),
}
}
}
@ -578,6 +647,7 @@ impl SigningPackage {
/// of commitments, and a specific message.
///
/// <https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md>
#[derive(Debug, PartialEq)]
pub(crate) struct Rho(Scalar);
impl From<&SigningPackage> for Rho {
@ -614,38 +684,50 @@ impl TryFrom<[u8; 32]> for Rho {
}
}
/// A representation of a single signature used in FROST structures and
/// messages.
/// A representation of a single signature share used in FROST structures and messages, including
/// the group commitment share.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct SignatureResponse(pub(crate) Scalar);
pub struct SignatureResponse {
pub(crate) R_share: RistrettoPoint,
pub(crate) z_share: Scalar,
}
impl FromHex for SignatureResponse {
type Error = &'static str;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let mut bytes = [0u8; 32];
match hex::decode_to_slice(hex, &mut bytes[..]) {
Ok(()) => SignatureResponse::try_from(bytes),
Err(_) => Err("invalid hex"),
}
impl From<SignatureResponse> for [u8; 64] {
fn from(sig: SignatureResponse) -> [u8; 64] {
let mut bytes = [0; 64];
bytes[0..32].copy_from_slice(&sig.R_share.compress().to_bytes());
bytes[32..64].copy_from_slice(&sig.z_share.to_bytes());
bytes
}
}
impl TryFrom<[u8; 32]> for SignatureResponse {
type Error = &'static str;
// impl FromHex for SignatureResponse {
// type Error = &'static str;
fn try_from(source: [u8; 32]) -> Result<Self, &'static str> {
match Scalar::from_canonical_bytes(source) {
Some(scalar) => Ok(SignatureResponse(scalar)),
None => Err("scalar was not canonically encoded"),
}
}
}
// fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
// let mut bytes = [0u8; 32];
// match hex::decode_to_slice(hex, &mut bytes[..]) {
// Ok(()) => SignatureResponse::try_from(bytes),
// Err(_) => Err("invalid hex"),
// }
// }
// }
// impl TryFrom<[u8; 32]> for SignatureResponse {
// type Error = &'static str;
// fn try_from(source: [u8; 32]) -> Result<Self, &'static str> {
// match Scalar::from_canonical_bytes(source) {
// Some(scalar) => Ok(SignatureResponse(scalar)),
// None => Err("scalar was not canonically encoded"),
// }
// }
// }
/// A participant's signature share, which the coordinator will use to aggregate
/// with all other signer's shares into the joint signature.
#[derive(Clone, Copy, Debug, Default)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct SignatureShare {
/// Represents the participant index.
pub(crate) index: u16,
@ -662,15 +744,16 @@ impl DefaultIsZeroes for SignatureShare {}
impl SignatureShare {
/// Tests if a signature share issued by a participant is valid before
/// aggregating it into a final joint signature to publish.
pub fn check_is_valid(
pub fn verify(
&self,
pubkey: &Public,
public_key: &Public,
lambda_i: Scalar,
commitment: RistrettoPoint,
challenge: Scalar,
) -> Result<(), &'static str> {
if (RISTRETTO_BASEPOINT_POINT * self.signature.0)
!= (commitment + (pubkey.0 * challenge * lambda_i))
println!("checking sig share: {:?}", self);
if (RISTRETTO_BASEPOINT_POINT * self.signature.z_share)
!= (self.signature.R_share + (public_key.0 * challenge * lambda_i))
{
return Err("Invalid signature share");
}
@ -749,10 +832,11 @@ fn generate_lagrange_coeff(
/// the commitment that was assigned by the coordinator in the SigningPackage.
pub fn sign(
signing_package: &SigningPackage,
participant_nonces: &SigningNonces,
signer_nonces: &SigningNonces,
signer_commitments: &SigningCommitments,
key_package: &KeyPackage,
) -> Result<SignatureShare, &'static str> {
let lambda_i = generate_lagrange_coeff(key_package.index, signing_package)?;
let rho: Rho = signing_package.into();
let group_commitment = GroupCommitment::try_from(signing_package)?;
@ -762,17 +846,33 @@ pub fn sign(
signing_package.message.as_slice(),
);
let rho: Rho = signing_package.into();
let lambda_i = generate_lagrange_coeff(key_package.index, signing_package)?;
println!("signer_nonces: {:?}", signer_nonces);
// The Schnorr signature share
let signature: Scalar = participant_nonces.hiding
+ (participant_nonces.binding * rho.0)
let z_share: Scalar = signer_nonces.hiding.0
+ (signer_nonces.binding.0 * rho.0)
+ (lambda_i * key_package.secret_share.0 * challenge);
Ok(SignatureShare {
println!(
"z_share_{:?}: {:?}",
key_package.index,
hex::encode(z_share.to_bytes())
);
// The Schnorr signature commitment share
let R_share: RistrettoPoint =
signer_commitments.hiding.0 + (signer_commitments.binding.0 * rho.0);
println!("R_share_{:?}: {:?}", key_package.index, R_share.compress());
let signature_share = SignatureShare {
index: key_package.index,
signature: SignatureResponse(signature),
})
signature: SignatureResponse { z_share, R_share },
};
Ok(signature_share)
}
/// Verifies each participant's signature share, and if all are valid,
@ -806,28 +906,42 @@ pub fn aggregate(
let rho = Rho::from(signing_package);
for signing_share in signing_shares {
let signer_pubkey = pubkeys.signer_pubkeys[&signing_share.index];
let signer_pubkey = pubkeys.signer_pubkeys.get(&signing_share.index).unwrap();
let lambda_i = generate_lagrange_coeff(signing_share.index, signing_package)?;
let signer_commitment = signing_package
let signer_commitments = signing_package
.signing_commitments
.iter()
.find(|comm| comm.index == signing_share.index)
.ok_or("No matching signing commitment for signer")?;
let commitment_i = signer_commitment.hiding.0 + (signer_commitment.binding.0 * rho.0);
let commitment_i = signer_commitments.hiding.0 + (signer_commitments.binding.0 * rho.0);
signing_share.check_is_valid(&signer_pubkey, lambda_i, commitment_i, challenge)?;
println!("calc'd: {:?}", commitment_i.compress());
println!("passed: {:?}", signing_share.signature.R_share.compress());
let verify_result = signing_share.verify(signer_pubkey, lambda_i, challenge);
println!(
"sig share {:?} verify result: {:?}",
signing_share.index, verify_result
);
}
// The aggregation of the signature shares by summing them up, resulting in
// a plain Schnorr signature.
let mut z = Scalar::zero();
let mut R: RistrettoPoint = RistrettoPoint::identity();
for signature_share in signing_shares {
z += signature_share.signature.0;
z += signature_share.signature.z_share;
R += signature_share.signature.R_share;
}
println!("R: {:?}", R);
println!("group_commitment: {:?}", group_commitment);
Ok(Signature {
r_bytes: group_commitment.0.compress().to_bytes(),
s_bytes: z.to_bytes(),
R_bytes: group_commitment.0.compress().to_bytes(),
z_bytes: z.to_bytes(),
})
}

View File

@ -47,89 +47,133 @@ fn reconstruct_secret(secret_shares: Vec<SecretShare>) -> Result<Scalar, &'stati
fn check_share_generation() {
let mut rng = thread_rng();
let mut bytes = [0; 64];
rng.fill_bytes(&mut bytes);
let secret = Secret(Scalar::from_bytes_mod_order_wide(&bytes));
let secret = Secret::random(&mut rng);
let _ = RISTRETTO_BASEPOINT_POINT * secret.0;
let secret_shares = generate_secret_shares(&secret, 5, 3, rng).unwrap();
for secret_share in secret_shares.iter() {
assert_eq!(verify_secret_share(secret_share), Ok(()));
assert_eq!(secret_share.verify(), Ok(()));
}
assert_eq!(reconstruct_secret(secret_shares).unwrap(), secret.0)
}
// #[test]
// fn check_sign_with_test_vectors() {
// let (
// group_public,
// key_packages,
// message,
// message_bytes,
// signer_commitments,
// group_binding_factor_input,
// _group_binding_factor,
// signature_shares,
// signature,
// ) = parse_test_vectors();
#[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,
) = parse_test_vectors();
// // Key generation
// for key_package in key_packages.values() {
// assert_eq!(key_package.public, key_package.secret_share.into());
// }
////////////////////////////////////////////////////////////////////////////
// Key generation
////////////////////////////////////////////////////////////////////////////
// // Round 1
// // for (i, signing_commitments) in signer_commitments {
// // // compute nonce commitments from nonces
// // }
for key_package in key_packages.values() {
assert_eq!(key_package.public, key_package.secret_share.into());
}
// // Round 2
// let signing_package = frost::SigningPackage::new(signer_commitments, message_bytes);
/////////////////////////////////////////////////////////////////////////////
// Round 1
/////////////////////////////////////////////////////////////////////////////
// assert_eq!(signing_package.rho_preimage(), group_binding_factor_input);
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();
// // Each participant generates their signature share
// // TODO: needs the nonces from the test vectors
// // for (participant_index, nonce) in &nonces {
// // let key_package = key_packages
// // .iter()
// // .find(|key_package| *participant_index == key_package.index)
// // .unwrap();
// // let nonce_to_use = nonce[0];
// // // Each participant generates their signature share.
// // let signature_share = frost::sign(&signing_package, &nonce_to_use, key_package).unwrap();
// // signature_shares.push(signature_share);
// // }
assert_eq!(
NonceCommitment::from(nonces.hiding),
nonce_commitments.hiding
);
// let signer_pubkeys = key_packages
// .into_iter()
// .map(|(i, key_package)| (i, key_package.public))
// .collect();
assert_eq!(
NonceCommitment::from(nonces.binding),
nonce_commitments.binding
);
}
// let pubkey_package = PublicKeyPackage {
// signer_pubkeys,
// group_public,
// };
/////////////////////////////////////////////////////////////////////////////
// Round 2
/////////////////////////////////////////////////////////////////////////////
// // The aggregator collects the signing shares from all participants and generates the final
// // signature.
// let group_signature_result = frost::aggregate(
// &signing_package,
// &signature_shares
// .values()
// .cloned()
// .collect::<Vec<SignatureShare>>(),
// &pubkey_package,
// );
let signer_commitments_vec = signer_commitments
.clone()
.into_iter()
.map(|(_, signing_commitments)| signing_commitments)
.collect();
// // println!("{:?}", group_signature_result);
let signing_package = frost::SigningPackage::new(signer_commitments_vec, message_bytes);
// assert!(group_signature_result.is_ok());
assert_eq!(signing_package.rho_preimage(), group_binding_factor_input);
// let group_signature = group_signature_result.unwrap();
let rho: Rho = (&signing_package).into();
// assert_eq!(group_signature, signature);
// }
assert_eq!(rho, group_binding_factor);
let mut our_signature_shares: Vec<frost::SignatureShare> = Vec::new();
// Each participant generates their signature share
println!("{:?}", signer_nonces.keys());
for index in signer_nonces.keys() {
let key_package = key_packages[index];
let nonces = signer_nonces[index];
let commitments = signer_commitments[index];
// Each participant generates their signature share.
let signature_share =
frost::sign(&signing_package, &nonces, &commitments, &key_package).unwrap();
our_signature_shares.push(signature_share);
}
for _sig_share in our_signature_shares {
// sig_share.check_is_valid()?;
// println!("Ours: {:?}", sig_share);
// // !!! DIVERGANCE !!!
// println!("Test vector: {:?}", signature_shares[&sig_share.index]);
// 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 = PublicKeyPackage {
signer_pubkeys,
group_public,
};
// The aggregator collects the signing shares from all participants and generates the final
// signature.
let group_signature_result = frost::aggregate(
&signing_package,
&signature_shares
.values()
.cloned()
.collect::<Vec<SignatureShare>>(),
&pubkey_package,
);
println!("{:?}", group_signature_result);
assert!(group_signature_result.is_ok());
let group_signature = group_signature_result.unwrap();
assert_eq!(group_signature, signature);
}

View File

@ -19,7 +19,8 @@ pub(crate) fn parse_test_vectors() -> (
HashMap<u16, KeyPackage>,
&'static str,
Vec<u8>,
Vec<SigningCommitments>,
HashMap<u16, SigningNonces>,
HashMap<u16, SigningCommitments>,
Vec<u8>,
Rho,
HashMap<u16, SignatureShare>,
@ -68,11 +69,19 @@ pub(crate) fn parse_test_vectors() -> (
let group_binding_factor =
Rho::from_hex(round_one_outputs["group_binding_factor"].as_str().unwrap()).unwrap();
let mut signer_commitments: Vec<SigningCommitments> = Vec::new();
let mut signer_nonces: HashMap<u16, SigningNonces> = HashMap::new();
let mut signer_commitments: HashMap<u16, SigningCommitments> = 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 {
hiding: Nonce::from_hex(signer["hiding_nonce"].as_str().unwrap()).unwrap(),
binding: Nonce::from_hex(signer["binding_nonce"].as_str().unwrap()).unwrap(),
};
signer_nonces.insert(index, signing_nonces);
let signing_commitments = SigningCommitments {
index,
hiding: NonceCommitment::from_hex(signer["hiding_nonce_commitment"].as_str().unwrap())
@ -83,7 +92,7 @@ pub(crate) fn parse_test_vectors() -> (
.unwrap(),
};
signer_commitments.push(signing_commitments);
signer_commitments.insert(index, signing_commitments);
}
// Round two outputs
@ -95,7 +104,20 @@ pub(crate) fn parse_test_vectors() -> (
for (i, signer) in round_two_outputs["signers"].as_object().unwrap().iter() {
let signature_share = SignatureShare {
index: u16::from_str(i).unwrap(),
signature: SignatureResponse::from_hex(signer["sig_share"].as_str().unwrap()).unwrap(),
signature: SignatureResponse {
R_share: ristretto::CompressedRistretto::from_slice(
&hex::decode(signer["group_commitment_share"].as_str().unwrap()).unwrap()[..],
)
.decompress()
.unwrap(),
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);
@ -112,6 +134,7 @@ pub(crate) fn parse_test_vectors() -> (
key_packages,
message,
message_bytes,
signer_nonces,
signer_commitments,
group_binding_factor_input,
group_binding_factor,

View File

@ -18,7 +18,7 @@ use sha2::{Digest, Sha512};
pub mod batch;
mod error;
pub mod frost;
mod messages;
// mod messages;
pub(crate) mod signature;
mod signing_key;
mod verification_key;
@ -40,9 +40,6 @@ pub(crate) fn H1(m: &[u8]) -> [u8; 64] {
let h = Sha512::new()
.chain(CONTEXT_STRING.as_bytes())
.chain("rho")
// TODO: double-check big endian vs little endian representation of integers like this
// frost-dalek also uses to_be_bytes
.chain(m.len().to_be_bytes())
.chain(m);
let mut output = [0u8; 64];
@ -57,9 +54,6 @@ pub(crate) fn H2(m: &[u8]) -> [u8; 64] {
let h = Sha512::new()
.chain(CONTEXT_STRING.as_bytes())
.chain("chal")
// TODO: double-check big endian vs little endian representation of integers like this
// frost-dalek also uses to_be_bytes
.chain(m.len().to_be_bytes())
.chain(m);
let mut output = [0u8; 64];

View File

@ -67,17 +67,17 @@ pub struct GroupCommitment([u8; 32]);
/// representation".
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
#[cfg_attr(test, derive(Arbitrary))]
pub struct SignatureResponse([u8; 32]);
pub struct SignatureResponse([u8; 64]);
impl From<signature::Signature> for SignatureResponse {
fn from(value: signature::Signature) -> SignatureResponse {
SignatureResponse(value.s_bytes)
SignatureResponse(value.into())
}
}
impl From<signature::Signature> for GroupCommitment {
fn from(value: signature::Signature) -> GroupCommitment {
GroupCommitment(value.r_bytes)
GroupCommitment(value.R_bytes)
}
}
@ -287,10 +287,10 @@ pub struct SignatureShare {
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[cfg_attr(test, derive(Arbitrary))]
pub struct AggregateSignature {
/// The aggregated group commitment: [`signature::Signature::r_bytes`]
/// The aggregated group commitment: [`signature::Signature::R_bytes`]
/// returned by [`frost::aggregate()`]
group_commitment: GroupCommitment,
/// A plain Schnorr signature created by summing all the signature shares:
/// [`signature::Signature::s_bytes`] returned by [`frost::aggregate()`]
/// [`signature::Signature::z_bytes`] returned by [`frost::aggregate()`]
schnorr_signature: SignatureResponse,
}

View File

@ -440,6 +440,7 @@ fn validate_signatureshare() {
let signature_share = frost::sign(
&signing_package,
&nonce1[0],
&commitment1[0],
&frost::KeyPackage::try_from(shares[0].clone()).unwrap(),
)
.unwrap();
@ -448,7 +449,7 @@ fn validate_signatureshare() {
let header = create_valid_header(setup.aggregator, setup.signer1);
let payload = Payload::SignatureShare(SignatureShare {
signature: SignatureResponse(signature_share.signature.0.to_bytes()),
signature: signature_share.signature,
});
let message = Message {

View File

@ -14,25 +14,25 @@
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Signature {
pub(crate) r_bytes: [u8; 32],
pub(crate) s_bytes: [u8; 32],
pub(crate) R_bytes: [u8; 32],
pub(crate) z_bytes: [u8; 32],
}
impl From<[u8; 64]> for Signature {
fn from(bytes: [u8; 64]) -> Signature {
let mut r_bytes = [0; 32];
r_bytes.copy_from_slice(&bytes[0..32]);
let mut s_bytes = [0; 32];
s_bytes.copy_from_slice(&bytes[32..64]);
Signature { r_bytes, s_bytes }
let mut R_bytes = [0; 32];
R_bytes.copy_from_slice(&bytes[0..32]);
let mut z_bytes = [0; 32];
z_bytes.copy_from_slice(&bytes[32..64]);
Signature { R_bytes, z_bytes }
}
}
impl From<Signature> for [u8; 64] {
fn from(sig: Signature) -> [u8; 64] {
let mut bytes = [0; 64];
bytes[0..32].copy_from_slice(&sig.r_bytes[..]);
bytes[32..64].copy_from_slice(&sig.s_bytes[..]);
bytes[0..32].copy_from_slice(&sig.R_bytes[..]);
bytes[32..64].copy_from_slice(&sig.z_bytes[..]);
bytes
}
}

View File

@ -100,13 +100,13 @@ impl SigningKey {
);
// 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();
// Generate Schnorr challenge
let c = crate::generate_challenge(&r_bytes, &self.pk.bytes.bytes, msg);
let c = crate::generate_challenge(&R_bytes, &self.pk.bytes.bytes, msg);
let s_bytes = (nonce + (c * self.sk)).to_bytes();
let z_bytes = (nonce + (c * self.sk)).to_bytes();
Signature { r_bytes, s_bytes }
Signature { R_bytes, z_bytes }
}
}

View File

@ -125,19 +125,19 @@ impl VerificationKey {
pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> {
self.verify_prehashed(
signature,
crate::generate_challenge(&signature.r_bytes, &self.bytes.bytes, msg),
crate::generate_challenge(&signature.R_bytes, &self.bytes.bytes, msg),
)
}
/// Verify a purported `signature` with a prehashed challenge.
#[allow(non_snake_case)]
pub(crate) fn verify_prehashed(&self, signature: &Signature, c: Scalar) -> Result<(), Error> {
let R = match CompressedRistretto::from_slice(&signature.r_bytes).decompress() {
let R = match CompressedRistretto::from_slice(&signature.R_bytes).decompress() {
Some(point) => point,
None => return Err(Error::InvalidSignature),
};
let s = match Scalar::from_canonical_bytes(signature.s_bytes) {
let s = match Scalar::from_canonical_bytes(signature.z_bytes) {
Some(s) => s,
None => return Err(Error::InvalidSignature),
};

View File

@ -17,42 +17,62 @@ fn check_sign_with_dealer() {
.map(|share| frost::KeyPackage::try_from(share).unwrap())
.collect();
let mut nonces: HashMap<u16, Vec<frost::SigningNonces>> =
HashMap::with_capacity(threshold as usize);
let mut commitments: Vec<frost::SigningCommitments> = Vec::with_capacity(threshold as usize);
let mut nonces: HashMap<u16, Vec<frost::SigningNonces>> = HashMap::new();
let mut commitments: HashMap<u16, Vec<frost::SigningCommitments>> = 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::preprocess(1, participant_index as u16, &mut rng);
nonces.insert(participant_index as u16, nonce);
commitments.push(commitment[0]);
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::SignatureShare> = Vec::with_capacity(threshold as usize);
let mut signature_shares: Vec<frost::SignatureShare> = Vec::new();
let message = "message to sign".as_bytes();
let signing_package = frost::SigningPackage::new(commitments, message.to_vec());
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, nonce) in &nonces {
////////////////////////////////////////////////////////////////////////////
for participant_index in nonces.keys() {
let key_package = key_packages
.iter()
.find(|key_package| *participant_index == key_package.index)
.unwrap();
let nonce_to_use = nonce[0];
let nonces_to_use = nonces.get(participant_index).unwrap()[0];
let commitments_to_use = commitments.get(participant_index).unwrap()[0];
// Each participant generates their signature share.
let signature_share = frost::sign(&signing_package, &nonce_to_use, key_package).unwrap();
let signature_share = frost::sign(
&signing_package,
&nonces_to_use,
&commitments_to_use,
key_package,
)
.unwrap();
signature_shares.push(signature_share);
}
// The aggregator collects the signing shares from all participants and
////////////////////////////////////////////////////////////////////////////
// Aggregator collects the signing shares from all participants and
// generates the final signature.
////////////////////////////////////////////////////////////////////////////
// Aggregate
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