Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frost-core/src/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ where

for item in self.signatures.iter() {
let z = item.sig.z;
let R = <C>::effective_nonce_element(item.sig.R);
let R = item.sig.R;
let vk = <C>::effective_pubkey_element(&item.vk, &item.sig_params);

let blind = <<C::Group as Group>::Field>::random(&mut rng);
Expand Down
32 changes: 22 additions & 10 deletions frost-core/src/keys/dkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use rand_core::{CryptoRng, RngCore};

use crate::{
Challenge, Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, Signature,
SigningKey, VerifyingKey,
SigningKey,
};

use super::{
Expand Down Expand Up @@ -314,7 +314,7 @@ pub fn part1<C: Ciphersuite, R: RngCore + CryptoRng>(
/// Generates the challenge for the proof of knowledge to a secret for the DKG.
fn challenge<C>(
identifier: Identifier<C>,
verifying_key: &VerifyingKey<C>,
verifying_key: &Element<C>,
R: &Element<C>,
) -> Option<Challenge<C>>
where
Expand All @@ -323,7 +323,7 @@ where
let mut preimage = vec![];

preimage.extend_from_slice(identifier.serialize().as_ref());
preimage.extend_from_slice(<C::Group>::serialize(&verifying_key.element).as_ref());
preimage.extend_from_slice(<C::Group>::serialize(&verifying_key).as_ref());
preimage.extend_from_slice(<C::Group>::serialize(R).as_ref());

Some(Challenge(C::HDKG(&preimage[..])?))
Expand All @@ -344,14 +344,23 @@ pub(crate) fn compute_proof_of_knowledge<C: Ciphersuite, R: RngCore + CryptoRng>
// > a_{i0} by calculating σ_i = (R_i, μ_i), such that k ← Z_q, R_i = g^k,
// > c_i = H(i, Φ, g^{a_{i0}} , R_i), μ_i = k + a_{i0} · c_i, with Φ being
// > a context string to prevent replay attacks.
let k = <<C::Group as Group>::Field>::random(&mut rng);
let R_i = <C::Group>::generator() * k;
let c_i = challenge::<C>(identifier, &commitment.verifying_key()?, &R_i)
.ok_or(Error::DKGNotSupported)?;
let mut k = <<C::Group as Group>::Field>::random(&mut rng);
let mut R_i = <C::Group>::generator() * k;
k = <C>::effective_nonce_secret(k, &R_i);
R_i = <C>::effective_nonce_element(R_i);

let verifying_key = commitment.verifying_key()?;
let sig_params = Default::default();

let phi_ell0 = <C>::effective_pubkey_element(&verifying_key, &sig_params);

let c_i = challenge::<C>(identifier, &phi_ell0, &R_i).ok_or(Error::DKGNotSupported)?;
let a_i0 = *coefficients
.first()
.expect("coefficients must have at least one element");
let mu_i = k + a_i0 * c_i.0;
let a_i0_effective = <C>::effective_secret_key(a_i0, &verifying_key, &sig_params);

let mu_i = k + a_i0_effective * c_i.0;
Ok(Signature { R: R_i, z: mu_i })
}

Expand All @@ -371,9 +380,12 @@ pub(crate) fn verify_proof_of_knowledge<C: Ciphersuite>(
let ell = identifier;
let R_ell = proof_of_knowledge.R;
let mu_ell = proof_of_knowledge.z;
let phi_ell0 = commitment.verifying_key()?;

let verifying_key = commitment.verifying_key()?;
let phi_ell0 = <C>::effective_pubkey_element(&verifying_key, &Default::default());
let c_ell = challenge::<C>(ell, &phi_ell0, &R_ell).ok_or(Error::DKGNotSupported)?;
if R_ell != <C::Group>::generator() * mu_ell - phi_ell0.element * c_ell.0 {

if R_ell != <C::Group>::generator() * mu_ell - phi_ell0 * c_ell.0 {
return Err(Error::InvalidProofOfKnowledge { culprit: ell });
}
Ok(())
Expand Down
15 changes: 4 additions & 11 deletions frost-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ where

// Compute the group commitment from signing commitments produced in round one.
let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?;
let R = <C>::effective_nonce_element(group_commitment.0);

// The aggregation of the signature shares by summing them up, resulting in
// a plain Schnorr signature.
Expand All @@ -636,12 +637,8 @@ where
z = z + signature_share.share;
}

let signature: Signature<C> = <C>::aggregate_sig_finalize(
z,
group_commitment.0,
&pubkeys.verifying_key,
&signing_package.sig_target,
);
let signature: Signature<C> =
<C>::aggregate_sig_finalize(z, R, &pubkeys.verifying_key, &signing_package.sig_target);

// Verify the aggregate signature
let verification_result = pubkeys
Expand All @@ -654,11 +651,7 @@ where
#[cfg(feature = "cheater-detection")]
if let Err(err) = verification_result {
// Compute the per-message challenge.
let challenge = <C>::challenge(
&group_commitment.0,
&pubkeys.verifying_key,
&signing_package.sig_target,
);
let challenge = <C>::challenge(&R, &pubkeys.verifying_key, &signing_package.sig_target);

// Verify the signature shares.
for (signature_share_identifier, signature_share) in signature_shares {
Expand Down
46 changes: 2 additions & 44 deletions frost-core/src/signature.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Schnorr signatures over prime order groups (or subgroups)

use debugless_unwrap::DebuglessUnwrap;
use derive_getters::Getters;

use crate::{Ciphersuite, Element, Error, Field, Group, Scalar};
Expand Down Expand Up @@ -32,53 +31,12 @@ where

/// Converts bytes as [`Ciphersuite::SignatureSerialization`] into a `Signature<C>`.
pub fn deserialize(bytes: C::SignatureSerialization) -> Result<Self, Error<C>> {
// To compute the expected length of the encoded point, encode the generator
// and get its length. Note that we can't use the identity because it can be encoded
// shorter in some cases (e.g. P-256, which uses SEC1 encoding).
let generator = <C::Group>::generator();
let mut R_bytes = Vec::from(<C::Group>::serialize(&generator).as_ref());

let R_bytes_len = R_bytes.len();

R_bytes[..].copy_from_slice(
bytes
.as_ref()
.get(0..R_bytes_len)
.ok_or(Error::MalformedSignature)?,
);

let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?;

let one = <<C::Group as Group>::Field as Field>::zero();
let mut z_bytes =
Vec::from(<<C::Group as Group>::Field as Field>::serialize(&one).as_ref());

let z_bytes_len = z_bytes.len();

// We extract the exact length of bytes we expect, not just the remaining bytes with `bytes[R_bytes_len..]`
z_bytes[..].copy_from_slice(
bytes
.as_ref()
.get(R_bytes_len..R_bytes_len + z_bytes_len)
.ok_or(Error::MalformedSignature)?,
);

let z_serialization = &z_bytes.try_into().map_err(|_| Error::MalformedSignature)?;

Ok(Self {
R: <C::Group>::deserialize(R_serialization)?,
z: <<C::Group as Group>::Field>::deserialize(z_serialization)?,
})
C::deserialize_signature(bytes)
}

/// Converts this signature to its [`Ciphersuite::SignatureSerialization`] in bytes.
pub fn serialize(&self) -> C::SignatureSerialization {
let mut bytes = vec![];

bytes.extend(<C::Group>::serialize(&self.R).as_ref());
bytes.extend(<<C::Group as Group>::Field>::serialize(&self.z).as_ref());

bytes.try_into().debugless_unwrap()
C::serialize_signature(self)
}
}

Expand Down
3 changes: 2 additions & 1 deletion frost-core/src/signing_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ where
let secret = <C>::effective_secret_key(self.scalar, &public, &sig_target.sig_params);

let mut k = random_nonzero::<C, R>(&mut rng);
let R = <C::Group>::generator() * k;
let mut R = <C::Group>::generator() * k;
k = <C>::effective_nonce_secret(k, &R);
R = <C>::effective_nonce_element(R);

// Generate Schnorr challenge
let c: Challenge<C> = <C>::challenge(&R, &public, &sig_target);
Expand Down
58 changes: 58 additions & 0 deletions frost-core/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{
ops::{Add, Mul, Sub},
};

use debugless_unwrap::DebuglessUnwrap;
use rand_core::{CryptoRng, RngCore};

use crate::{
Expand Down Expand Up @@ -310,6 +311,63 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug {
Signature { R, z }
}

/// Converts a signature to its [`Ciphersuite::SignatureSerialization`] in bytes.
///
/// The default implementation serializes a signature by serializing its `R` point and
/// `z` component independently, and then concatenating them.
fn serialize_signature(signature: &Signature<Self>) -> Self::SignatureSerialization {
let mut bytes = vec![];
bytes.extend(<Self::Group>::serialize(&signature.R).as_ref());
bytes.extend(<<Self::Group as Group>::Field>::serialize(&signature.z).as_ref());
bytes.try_into().debugless_unwrap()
}

/// Converts bytes as [`Ciphersuite::SignatureSerialization`] into a `Signature<C>`.
///
/// The default implementation assumes the serialization is a serialized `R` point
/// followed by a serialized `z` component with no padding or extra fields.
fn deserialize_signature(
bytes: Self::SignatureSerialization,
) -> Result<Signature<Self>, Error<Self>> {
// To compute the expected length of the encoded point, encode the generator
// and get its length. Note that we can't use the identity because it can be encoded
// shorter in some cases (e.g. P-256, which uses SEC1 encoding).
let generator = <Self::Group>::generator();
let mut R_bytes = Vec::from(<Self::Group>::serialize(&generator).as_ref());

let R_bytes_len = R_bytes.len();

R_bytes[..].copy_from_slice(
bytes
.as_ref()
.get(0..R_bytes_len)
.ok_or(Error::MalformedSignature)?,
);

let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?;

let one = <<Self::Group as Group>::Field as Field>::zero();
let mut z_bytes =
Vec::from(<<Self::Group as Group>::Field as Field>::serialize(&one).as_ref());

let z_bytes_len = z_bytes.len();

// We extract the exact length of bytes we expect, not just the remaining bytes with `bytes[R_bytes_len..]`
z_bytes[..].copy_from_slice(
bytes
.as_ref()
.get(R_bytes_len..R_bytes_len + z_bytes_len)
.ok_or(Error::MalformedSignature)?,
);

let z_serialization = &z_bytes.try_into().map_err(|_| Error::MalformedSignature)?;

Ok(Signature {
R: <Self::Group>::deserialize(R_serialization)?,
z: <<Self::Group as Group>::Field>::deserialize(z_serialization)?,
})
}

/// Compute the signature share for a particular signer on a given challenge.
fn compute_signature_share(
signer_nonces: &round1::SigningNonces<Self>,
Expand Down
2 changes: 1 addition & 1 deletion frost-core/src/verifying_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ where
// h * ( z * B - c * A - R) == 0
//
// where h is the cofactor
let R = C::effective_nonce_element(signature.R);
let R = signature.R;
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this PR, a Signature's R point could be either even or odd parity. When verifying, we were coercing R to be even, so that BIP340 verification logic would work.

After this PR, the Signature's R point is always even parity (unless someone manually constructs one with an odd-parity outside the scope of this library). We no longer need to do this coercion during verification.

let vk = C::effective_pubkey_element(&self, sig_params);

let zB = C::Group::generator() * signature.z;
Expand Down
3 changes: 2 additions & 1 deletion frost-ed25519/tests/helpers/samples.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"identifier": "2a00000000000000000000000000000000000000000000000000000000000000",
"proof_of_knowledge": "5866666666666666666666666666666666666666666666666666666666666666498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a",
"element1": "5866666666666666666666666666666666666666666666666666666666666666",
"element2": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022",
"scalar1": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a"
}
}
13 changes: 3 additions & 10 deletions frost-ed25519/tests/helpers/samples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,19 +107,12 @@ pub fn public_key_package() -> PublicKeyPackage {

/// Generate a sample round1::Package.
pub fn round1_package() -> round1::Package {
let serialized_scalar = <<C as Ciphersuite>::Group as Group>::Field::serialize(&scalar1());
let serialized_signature = Signature::new(element1(), scalar1()).serialize();
let signature = Signature::deserialize(serialized_signature).unwrap();

let serialized_element = <C as Ciphersuite>::Group::serialize(&element1());
let serialized_signature = serialized_element
.as_ref()
.iter()
.chain(serialized_scalar.as_ref().iter())
.cloned()
.collect::<Vec<u8>>()
.try_into()
.unwrap();
let vss_commitment =
VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap();
let signature = Signature::deserialize(serialized_signature).unwrap();

round1::Package::new(vss_commitment, signature)
}
Expand Down
3 changes: 2 additions & 1 deletion frost-ed448/tests/helpers/samples.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"proof_of_knowledge": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f69004d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00",
"element1": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900",
"element2": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80",
"scalar1": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00"
}
}
13 changes: 3 additions & 10 deletions frost-ed448/tests/helpers/samples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,19 +107,12 @@ pub fn public_key_package() -> PublicKeyPackage {

/// Generate a sample round1::Package.
pub fn round1_package() -> round1::Package {
let serialized_scalar = <<C as Ciphersuite>::Group as Group>::Field::serialize(&scalar1());
let serialized_signature = Signature::new(element1(), scalar1()).serialize();
let signature = Signature::deserialize(serialized_signature).unwrap();

let serialized_element = <C as Ciphersuite>::Group::serialize(&element1());
let serialized_signature = serialized_element
.as_ref()
.iter()
.chain(serialized_scalar.as_ref().iter())
.cloned()
.collect::<Vec<u8>>()
.try_into()
.unwrap();
let vss_commitment =
VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap();
let signature = Signature::deserialize(serialized_signature).unwrap();

round1::Package::new(vss_commitment, signature)
}
Expand Down
3 changes: 2 additions & 1 deletion frost-p256/tests/helpers/samples.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"identifier": "000000000000000000000000000000000000000000000000000000000000002a",
"proof_of_knowledge": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1",
"element1": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296",
"element2": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978",
"scalar1": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1"
}
}
13 changes: 3 additions & 10 deletions frost-p256/tests/helpers/samples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,19 +107,12 @@ pub fn public_key_package() -> PublicKeyPackage {

/// Generate a sample round1::Package.
pub fn round1_package() -> round1::Package {
let serialized_scalar = <<C as Ciphersuite>::Group as Group>::Field::serialize(&scalar1());
let serialized_signature = Signature::new(element1(), scalar1()).serialize();
let signature = Signature::deserialize(serialized_signature).unwrap();

let serialized_element = <C as Ciphersuite>::Group::serialize(&element1());
let serialized_signature = serialized_element
.as_ref()
.iter()
.chain(serialized_scalar.as_ref().iter())
.cloned()
.collect::<Vec<u8>>()
.try_into()
.unwrap();
let vss_commitment =
VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap();
let signature = Signature::deserialize(serialized_signature).unwrap();

round1::Package::new(vss_commitment, signature)
}
Expand Down
3 changes: 2 additions & 1 deletion frost-ristretto255/tests/helpers/samples.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"identifier": "2a00000000000000000000000000000000000000000000000000000000000000",
"proof_of_knowledge": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a",
"element1": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76",
"element2": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919",
"scalar1": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a"
}
}
Loading