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

View file

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

View file

@ -1,11 +1,11 @@
use core::{ops::Mul, fmt::Debug}; use core::fmt::Debug;
use thiserror::Error; use thiserror::Error;
use rand_core::{RngCore, CryptoRng}; use rand_core::{RngCore, CryptoRng};
use ff::{PrimeField, PrimeFieldBits}; use ff::{PrimeField, PrimeFieldBits};
use group::{Group, GroupOps, prime::PrimeGroup}; use group::{Group, GroupOps, GroupEncoding, prime::PrimeGroup};
#[cfg(any(test, feature = "dalek"))] #[cfg(any(test, feature = "dalek"))]
mod dalek; mod dalek;
@ -44,20 +44,14 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug {
type F: PrimeField + PrimeFieldBits; type F: PrimeField + PrimeFieldBits;
/// Group element type /// Group element type
type G: Group<Scalar = Self::F> + GroupOps + PrimeGroup; type G: Group<Scalar = Self::F> + GroupOps + PrimeGroup;
/// Precomputed table type
type T: Mul<Self::F, Output = Self::G>;
/// ID for this curve /// ID for this curve
const ID: &'static [u8]; const ID: &'static [u8];
/// Generator for the group /// 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; 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 /// Securely generate a random nonce. H4 from the IETF draft
fn random_nonce<R: RngCore + CryptoRng>(secret: Self::F, rng: &mut R) -> Self::F; 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 // hash_msg and hash_binding_factor
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F; fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F;
}
/// Constant size of a serialized scalar field element #[allow(non_snake_case)]
// The alternative way to grab this would be either serializing a junk element and getting its pub(crate) fn F_len<C: Curve>() -> usize {
// length or doing a naive division of its BITS property by 8 and assuming a lack of padding <C::F as PrimeField>::Repr::default().as_ref().len()
#[allow(non_snake_case)] }
fn F_len() -> usize;
/// Constant size of a serialized group element #[allow(non_snake_case)]
// We could grab the serialization as described above yet a naive developer may use a pub(crate) fn G_len<C: Curve>() -> usize {
// non-constant size encoding, proving yet another reason to force this to be a provided constant <C::G as GroupEncoding>::Repr::default().as_ref().len()
// 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;
} }
/// Field element from slice /// Field element from slice

View file

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

View file

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

View file

@ -4,7 +4,7 @@ use group::{ff::{Field, PrimeField}, GroupEncoding};
use multiexp::BatchVerifier; use multiexp::BatchVerifier;
use crate::Curve; use crate::{Curve, F_len, G_len};
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
@ -15,7 +15,7 @@ pub struct SchnorrSignature<C: Curve> {
impl<C: Curve> SchnorrSignature<C> { impl<C: Curve> SchnorrSignature<C> {
pub fn serialize(&self) -> Vec<u8> { 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.R.to_bytes().as_ref());
res.extend(self.s.to_repr().as_ref()); res.extend(self.s.to_repr().as_ref());
res res
@ -28,7 +28,7 @@ pub(crate) fn sign<C: Curve>(
challenge: C::F challenge: C::F
) -> SchnorrSignature<C> { ) -> SchnorrSignature<C> {
SchnorrSignature { SchnorrSignature {
R: C::GENERATOR_TABLE * nonce, R: C::GENERATOR * nonce,
s: nonce + (private_key * challenge) s: nonce + (private_key * challenge)
} }
} }
@ -38,7 +38,7 @@ pub(crate) fn verify<C: Curve>(
challenge: C::F, challenge: C::F,
signature: &SchnorrSignature<C> signature: &SchnorrSignature<C>
) -> bool { ) -> 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>( 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 transcript::Transcript;
use crate::{ use crate::{
curve::{Curve, F_from_slice, G_from_slice}, curve::{Curve, G_len, F_from_slice, G_from_slice},
FrostError, FrostError,
FrostParams, FrostKeys, FrostView, FrostParams, FrostKeys, FrostView,
algorithm::Algorithm, 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),
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(); let mut serialized = commitments[0].to_bytes().as_ref().to_vec();
serialized.extend(commitments[1].to_bytes().as_ref()); 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 commitments = commitments.remove(l).unwrap();
let mut read_commitment = |c, label| { 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); transcript.append_message(label, commitment);
G_from_slice::<C::G>(commitment).map_err(|_| FrostError::InvalidCommitment(*l)) G_from_slice::<C::G>(commitment).map_err(|_| FrostError::InvalidCommitment(*l))
}; };
#[allow(non_snake_case)] #[allow(non_snake_case)]
let mut read_D_E = || Ok( 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()?); 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 // 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(), C::F::zero(),
|accum, (i, keys)| accum + (keys.secret_share() * lagrange::<C::F>(*i, &included)) |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 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 let challenge = C::F::random(rng); // Doesn't bother to craft an HRAM
assert!( assert!(
schnorr::verify::<C>( schnorr::verify::<C>(
C::GENERATOR_TABLE * private_key, C::GENERATOR * private_key,
challenge, challenge,
&schnorr::sign(private_key, nonce, 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) { pub(crate) fn core_verify<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
assert!( assert!(
!schnorr::verify::<C>( !schnorr::verify::<C>(
C::GENERATOR_TABLE * C::F::random(&mut *rng), C::GENERATOR * C::F::random(&mut *rng),
C::F::random(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 // Batch verify
let triplets = (0 .. 5).map( 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<_>>(); ).collect::<Vec<_>>();
schnorr::batch_verify(rng, &triplets).unwrap(); 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() { for i in 1 ..= u16::try_from(keys.len()).unwrap() {
keys.insert(i, Arc::new(keys[&i].offset(offset))); 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); sign_core(rng, offset_key, &keys);
} }