Remove C::F_len, C::G_len for F_len<C> and G_len<C>

Relies on the ff/group API, instead of the custom Curve type.

Also removes GENERATOR_TABLE, only used by dalek, as we should provide 
our own API for that over ff/group instead. This slows down the FROST 
tests, under debug, by about 0.2-0.3s. Ed25519 and Ristretto together 
take ~2.15 seconds now.
This commit is contained in:
Luke Parker 2022-06-30 18:46:18 -04:00
parent 4eafbe2a09
commit 133c1222ad
No known key found for this signature in database
GPG key ID: F9F1386DB1E119B6
9 changed files with 46 additions and 84 deletions

View file

@ -11,29 +11,24 @@ macro_rules! dalek_curve {
$Curve: ident,
$Hram: ident,
$Point: ident,
$Table: ident,
$POINT: ident,
$TABLE: ident,
$ID: literal,
$CONTEXT: literal,
$chal: literal,
$digest: literal,
) => {
use dalek_ff_group::{$Point, $Table, $POINT, $TABLE};
use dalek_ff_group::{$Point, $POINT};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct $Curve;
impl Curve for $Curve {
type F = Scalar;
type G = $Point;
type T = &'static $Table;
const ID: &'static [u8] = $ID;
const GENERATOR: Self::G = $POINT;
const GENERATOR_TABLE: Self::T = &$TABLE;
fn random_nonce<R: RngCore + CryptoRng>(secret: Self::F, rng: &mut R) -> Self::F {
let mut seed = vec![0; 32];
@ -58,14 +53,6 @@ macro_rules! dalek_curve {
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
Scalar::from_hash(Sha512::new().chain_update($CONTEXT).chain_update(dst).chain_update(msg))
}
fn F_len() -> usize {
32
}
fn G_len() -> usize {
32
}
}
#[derive(Copy, Clone)]
@ -84,9 +71,7 @@ dalek_curve!(
Ristretto,
IetfRistrettoHram,
RistrettoPoint,
RistrettoBasepointTable,
RISTRETTO_BASEPOINT_POINT,
RISTRETTO_BASEPOINT_TABLE,
b"ristretto",
b"FROST-RISTRETTO255-SHA512-v5",
b"chal",
@ -98,9 +83,7 @@ dalek_curve!(
Ed25519,
IetfEd25519Hram,
EdwardsPoint,
EdwardsBasepointTable,
ED25519_BASEPOINT_POINT,
ED25519_BASEPOINT_TABLE,
b"edwards25519",
b"",
b"",

View file

@ -22,12 +22,9 @@ macro_rules! kp_curve {
impl Curve for $Curve {
type F = $lib::Scalar;
type G = $lib::ProjectivePoint;
type T = $lib::ProjectivePoint;
const ID: &'static [u8] = $ID;
const GENERATOR: Self::G = $lib::ProjectivePoint::GENERATOR;
const GENERATOR_TABLE: Self::G = $lib::ProjectivePoint::GENERATOR;
fn random_nonce<R: RngCore + CryptoRng>(secret: Self::F, rng: &mut R) -> Self::F {
let mut seed = vec![0; 32];
@ -73,14 +70,6 @@ macro_rules! kp_curve {
}).reduce(&modulus).unwrap().to_be_bytes()[16 ..]
).unwrap()
}
fn F_len() -> usize {
32
}
fn G_len() -> usize {
33
}
}
#[derive(Clone)]

View file

@ -1,11 +1,11 @@
use core::{ops::Mul, fmt::Debug};
use core::fmt::Debug;
use thiserror::Error;
use rand_core::{RngCore, CryptoRng};
use ff::{PrimeField, PrimeFieldBits};
use group::{Group, GroupOps, prime::PrimeGroup};
use group::{Group, GroupOps, GroupEncoding, prime::PrimeGroup};
#[cfg(any(test, feature = "dalek"))]
mod dalek;
@ -44,20 +44,14 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug {
type F: PrimeField + PrimeFieldBits;
/// Group element type
type G: Group<Scalar = Self::F> + GroupOps + PrimeGroup;
/// Precomputed table type
type T: Mul<Self::F, Output = Self::G>;
/// ID for this curve
const ID: &'static [u8];
/// Generator for the group
// While group does provide this in its API, privacy coins will want to use a custom basepoint
// While group does provide this in its API, privacy coins may want to use a custom basepoint
const GENERATOR: Self::G;
/// Table for the generator for the group
/// If there isn't a precomputed table available, the generator itself should be used
const GENERATOR_TABLE: Self::T;
/// Securely generate a random nonce. H4 from the IETF draft
fn random_nonce<R: RngCore + CryptoRng>(secret: Self::F, rng: &mut R) -> Self::F;
@ -83,20 +77,16 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug {
// hash_msg and hash_binding_factor
#[allow(non_snake_case)]
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F;
}
/// Constant size of a serialized scalar field element
// The alternative way to grab this would be either serializing a junk element and getting its
// length or doing a naive division of its BITS property by 8 and assuming a lack of padding
#[allow(non_snake_case)]
fn F_len() -> usize;
#[allow(non_snake_case)]
pub(crate) fn F_len<C: Curve>() -> usize {
<C::F as PrimeField>::Repr::default().as_ref().len()
}
/// Constant size of a serialized group element
// We could grab the serialization as described above yet a naive developer may use a
// non-constant size encoding, proving yet another reason to force this to be a provided constant
// A naive developer could still provide a constant for a variable length encoding, yet at least
// that is on them
#[allow(non_snake_case)]
fn G_len() -> usize;
#[allow(non_snake_case)]
pub(crate) fn G_len<C: Curve>() -> usize {
<C::G as GroupEncoding>::Repr::default().as_ref().len()
}
/// Field element from slice

View file

@ -7,7 +7,7 @@ use group::{ff::{Field, PrimeField}, GroupEncoding};
use multiexp::{multiexp_vartime, BatchVerifier};
use crate::{
curve::{Curve, F_from_slice, G_from_slice},
curve::{Curve, F_len, G_len, F_from_slice, G_from_slice},
FrostError, FrostParams, FrostKeys,
schnorr::{self, SchnorrSignature},
validate_map
@ -35,13 +35,13 @@ fn generate_key_r1<R: RngCore + CryptoRng, C: Curve>(
let t = usize::from(params.t);
let mut coefficients = Vec::with_capacity(t);
let mut commitments = Vec::with_capacity(t);
let mut serialized = Vec::with_capacity((C::G_len() * t) + C::G_len() + C::F_len());
let mut serialized = Vec::with_capacity((G_len::<C>() * t) + G_len::<C>() + F_len::<C>());
for i in 0 .. t {
// Step 1: Generate t random values to form a polynomial with
coefficients.push(C::F::random(&mut *rng));
// Step 3: Generate public commitments
commitments.push(C::GENERATOR_TABLE * coefficients[i]);
commitments.push(C::GENERATOR * coefficients[i]);
// Serialize them for publication
serialized.extend(commitments[i].to_bytes().as_ref());
}
@ -59,7 +59,7 @@ fn generate_key_r1<R: RngCore + CryptoRng, C: Curve>(
challenge::<C>(
context,
params.i(),
(C::GENERATOR_TABLE * r).to_bytes().as_ref(),
(C::GENERATOR * r).to_bytes().as_ref(),
&serialized
)
).serialize()
@ -83,19 +83,19 @@ fn verify_r1<R: RngCore + CryptoRng, C: Curve>(
(params.i(), our_commitments)
)?;
let commitments_len = usize::from(params.t()) * C::G_len();
let commitments_len = usize::from(params.t()) * G_len::<C>();
let mut commitments = HashMap::new();
#[allow(non_snake_case)]
let R_bytes = |l| &serialized[&l][commitments_len .. commitments_len + C::G_len()];
let R_bytes = |l| &serialized[&l][commitments_len .. commitments_len + G_len::<C>()];
#[allow(non_snake_case)]
let R = |l| G_from_slice::<C::G>(R_bytes(l)).map_err(|_| FrostError::InvalidProofOfKnowledge(l));
#[allow(non_snake_case)]
let Am = |l| &serialized[&l][0 .. commitments_len];
let s = |l| F_from_slice::<C::F>(
&serialized[&l][commitments_len + C::G_len() ..]
&serialized[&l][commitments_len + G_len::<C>() ..]
).map_err(|_| FrostError::InvalidProofOfKnowledge(l));
let mut signatures = Vec::with_capacity(usize::from(params.n() - 1));
@ -104,7 +104,7 @@ fn verify_r1<R: RngCore + CryptoRng, C: Curve>(
for c in 0 .. usize::from(params.t()) {
these_commitments.push(
G_from_slice::<C::G>(
&serialized[&l][(c * C::G_len()) .. ((c + 1) * C::G_len())]
&serialized[&l][(c * G_len::<C>()) .. ((c + 1) * G_len::<C>())]
).map_err(|_| FrostError::InvalidCommitment(l.try_into().unwrap()))?
);
}
@ -257,7 +257,7 @@ fn complete_r2<R: RngCore + CryptoRng, C: Curve>(
verification_shares.insert(i, multiexp_vartime(&exponential(i, &stripes)));
}
// Removing this check would enable optimizing the above from t + (n * t) to t + ((n - 1) * t)
debug_assert_eq!(C::GENERATOR_TABLE * secret_share, verification_shares[&params.i()]);
debug_assert_eq!(C::GENERATOR * secret_share, verification_shares[&params.i()]);
// TODO: Clear serialized and shares

View file

@ -8,7 +8,7 @@ use group::{ff::{Field, PrimeField}, GroupEncoding};
mod schnorr;
pub mod curve;
use curve::{Curve, F_from_slice, G_from_slice};
use curve::{Curve, F_len, G_len, F_from_slice, G_from_slice};
pub mod key_gen;
pub mod algorithm;
pub mod sign;
@ -160,7 +160,7 @@ impl<C: Curve> FrostKeys<C> {
// Enables schemes like Monero's subaddresses which have a per-subaddress offset and then a
// one-time-key offset
res.offset = Some(offset + res.offset.unwrap_or(C::F::zero()));
res.group_key += C::GENERATOR_TABLE * offset;
res.group_key += C::GENERATOR * offset;
res
}
@ -195,7 +195,7 @@ impl<C: Curve> FrostKeys<C> {
verification_shares: self.verification_shares.iter().map(
|(l, share)| (
*l,
(*share * lagrange::<C::F>(*l, &included)) + (C::GENERATOR_TABLE * offset_share)
(*share * lagrange::<C::F>(*l, &included)) + (C::GENERATOR * offset_share)
)
).collect(),
included: included.to_vec(),
@ -203,7 +203,7 @@ impl<C: Curve> FrostKeys<C> {
}
pub fn serialized_len(n: u16) -> usize {
8 + C::ID.len() + (3 * 2) + C::F_len() + C::G_len() + (usize::from(n) * C::G_len())
8 + C::ID.len() + (3 * 2) + F_len::<C>() + G_len::<C>() + (usize::from(n) * G_len::<C>())
}
pub fn serialize(&self) -> Vec<u8> {
@ -253,21 +253,21 @@ impl<C: Curve> FrostKeys<C> {
let i = u16::from_be_bytes(serialized[cursor .. (cursor + 2)].try_into().unwrap());
cursor += 2;
let secret_share = F_from_slice::<C::F>(&serialized[cursor .. (cursor + C::F_len())])
let secret_share = F_from_slice::<C::F>(&serialized[cursor .. (cursor + F_len::<C>())])
.map_err(|_| FrostError::InternalError("invalid secret share".to_string()))?;
cursor += C::F_len();
let group_key = G_from_slice::<C::G>(&serialized[cursor .. (cursor + C::G_len())])
cursor += F_len::<C>();
let group_key = G_from_slice::<C::G>(&serialized[cursor .. (cursor + G_len::<C>())])
.map_err(|_| FrostError::InternalError("invalid group key".to_string()))?;
cursor += C::G_len();
cursor += G_len::<C>();
let mut verification_shares = HashMap::new();
for l in 1 ..= n {
verification_shares.insert(
l,
G_from_slice::<C::G>(&serialized[cursor .. (cursor + C::G_len())])
G_from_slice::<C::G>(&serialized[cursor .. (cursor + G_len::<C>())])
.map_err(|_| FrostError::InternalError("invalid verification share".to_string()))?
);
cursor += C::G_len();
cursor += G_len::<C>();
}
Ok(

View file

@ -4,7 +4,7 @@ use group::{ff::{Field, PrimeField}, GroupEncoding};
use multiexp::BatchVerifier;
use crate::Curve;
use crate::{Curve, F_len, G_len};
#[allow(non_snake_case)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
@ -15,7 +15,7 @@ pub struct SchnorrSignature<C: Curve> {
impl<C: Curve> SchnorrSignature<C> {
pub fn serialize(&self) -> Vec<u8> {
let mut res = Vec::with_capacity(C::G_len() + C::F_len());
let mut res = Vec::with_capacity(G_len::<C>() + F_len::<C>());
res.extend(self.R.to_bytes().as_ref());
res.extend(self.s.to_repr().as_ref());
res
@ -28,7 +28,7 @@ pub(crate) fn sign<C: Curve>(
challenge: C::F
) -> SchnorrSignature<C> {
SchnorrSignature {
R: C::GENERATOR_TABLE * nonce,
R: C::GENERATOR * nonce,
s: nonce + (private_key * challenge)
}
}
@ -38,7 +38,7 @@ pub(crate) fn verify<C: Curve>(
challenge: C::F,
signature: &SchnorrSignature<C>
) -> bool {
(C::GENERATOR_TABLE * signature.s) == (signature.R + (public_key * challenge))
(C::GENERATOR * signature.s) == (signature.R + (public_key * challenge))
}
pub(crate) fn batch_verify<C: Curve, R: RngCore + CryptoRng>(

View file

@ -8,7 +8,7 @@ use group::{ff::{Field, PrimeField}, GroupEncoding};
use transcript::Transcript;
use crate::{
curve::{Curve, F_from_slice, G_from_slice},
curve::{Curve, G_len, F_from_slice, G_from_slice},
FrostError,
FrostParams, FrostKeys, FrostView,
algorithm::Algorithm,
@ -84,7 +84,7 @@ fn preprocess<R: RngCore + CryptoRng, C: Curve, A: Algorithm<C>>(
C::random_nonce(params.view().secret_share(), &mut *rng),
C::random_nonce(params.view().secret_share(), &mut *rng)
];
let commitments = [C::GENERATOR_TABLE * nonces[0], C::GENERATOR_TABLE * nonces[1]];
let commitments = [C::GENERATOR * nonces[0], C::GENERATOR * nonces[1]];
let mut serialized = commitments[0].to_bytes().as_ref().to_vec();
serialized.extend(commitments[1].to_bytes().as_ref());
@ -146,18 +146,18 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
let commitments = commitments.remove(l).unwrap();
let mut read_commitment = |c, label| {
let commitment = &commitments[c .. (c + C::G_len())];
let commitment = &commitments[c .. (c + G_len::<C>())];
transcript.append_message(label, commitment);
G_from_slice::<C::G>(commitment).map_err(|_| FrostError::InvalidCommitment(*l))
};
#[allow(non_snake_case)]
let mut read_D_E = || Ok(
[read_commitment(0, b"commitment_D")?, read_commitment(C::G_len(), b"commitment_E")?]
[read_commitment(0, b"commitment_D")?, read_commitment(G_len::<C>(), b"commitment_E")?]
);
B.insert(*l, read_D_E()?);
addendums.insert(*l, commitments[(C::G_len() * 2) ..].to_vec());
addendums.insert(*l, commitments[(G_len::<C>() * 2) ..].to_vec());
}
// Append the message to the transcript

View file

@ -98,7 +98,7 @@ pub fn recover<C: Curve>(keys: &HashMap<u16, FrostKeys<C>>) -> C::F {
C::F::zero(),
|accum, (i, keys)| accum + (keys.secret_share() * lagrange::<C::F>(*i, &included))
);
assert_eq!(C::GENERATOR_TABLE * group_private, first.group_key(), "failed to recover keys");
assert_eq!(C::GENERATOR * group_private, first.group_key(), "failed to recover keys");
group_private
}

View file

@ -15,7 +15,7 @@ pub(crate) fn core_sign<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
let challenge = C::F::random(rng); // Doesn't bother to craft an HRAM
assert!(
schnorr::verify::<C>(
C::GENERATOR_TABLE * private_key,
C::GENERATOR * private_key,
challenge,
&schnorr::sign(private_key, nonce, challenge)
)
@ -28,9 +28,9 @@ pub(crate) fn core_sign<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
pub(crate) fn core_verify<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
assert!(
!schnorr::verify::<C>(
C::GENERATOR_TABLE * C::F::random(&mut *rng),
C::GENERATOR * C::F::random(&mut *rng),
C::F::random(rng),
&SchnorrSignature { R: C::GENERATOR_TABLE * C::F::zero(), s: C::F::zero() }
&SchnorrSignature { R: C::GENERATOR * C::F::zero(), s: C::F::zero() }
)
);
}
@ -48,7 +48,7 @@ pub(crate) fn core_batch_verify<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
// Batch verify
let triplets = (0 .. 5).map(
|i| (u16::try_from(i + 1).unwrap(), C::GENERATOR_TABLE * keys[i], challenges[i], sigs[i])
|i| (u16::try_from(i + 1).unwrap(), C::GENERATOR * keys[i], challenges[i], sigs[i])
).collect::<Vec<_>>();
schnorr::batch_verify(rng, &triplets).unwrap();
@ -113,7 +113,7 @@ fn sign_with_offset<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
for i in 1 ..= u16::try_from(keys.len()).unwrap() {
keys.insert(i, Arc::new(keys[&i].offset(offset)));
}
let offset_key = group_key + (C::GENERATOR_TABLE * offset);
let offset_key = group_key + (C::GENERATOR * offset);
sign_core(rng, offset_key, &keys);
}