Small fixes, trying to get test vector sig share verification working
This commit is contained in:
parent
15706f0b48
commit
bf8b138595
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
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"),
|
||||
pub struct SignatureResponse {
|
||||
pub(crate) R_share: RistrettoPoint,
|
||||
pub(crate) z_share: Scalar,
|
||||
}
|
||||
|
||||
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(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue