Support naive batch verification (#73)

* Support naive batch verification

* Generic batch verification compiles, but the batch verify test in core fails 😭

* The 'z' in old impl is now 'blind', as we call the response 'z' in our signatures

Co-authored-by: Conrado Gouvea <conradoplg@gmail.com>

* Do the last scalar mul by the Ciphersuite::Group::cofactor() with the check

* Make VerifyingKey::verify_prehashed() pub(crate)

Co-authored-by: Conrado Gouvea <conradoplg@gmail.com>
This commit is contained in:
Deirdre Connolly 2022-08-02 09:50:13 -04:00 committed by GitHub
parent 2e8509837c
commit c551cd56eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 71 deletions

View File

@ -7,14 +7,11 @@
//! of caller code (which must assemble a batch of signatures across
//! work-items), and loss of the ability to easily pinpoint failing signatures.
use std::convert::TryFrom;
use std::iter::once;
use rand_core::{CryptoRng, RngCore};
use crate::{
frost::{self, *},
*,
};
use crate::{scalar_mul::VartimeMultiscalarMul, Ciphersuite, Element, *};
/// A batch verification item.
///
@ -35,7 +32,6 @@ where
{
fn from((vk, sig, msg): (VerifyingKey<C>, Signature<C>, &'msg M)) -> Self {
// Compute c now to avoid dependency on the msg lifetime.
let c = crate::challenge(&sig.R, &vk.element, msg.as_ref());
Self { vk, sig, c }
@ -54,11 +50,10 @@ where
/// requires borrowing the message data, the `Item` type is unlinked
/// from the lifetime of the message.
pub fn verify_single(self) -> Result<(), Error> {
VerifyingKey::try_from(self.vk_bytes).and_then(|vk| vk.verify_prehashed(&self.sig, self.c))
self.vk.verify_prehashed(&self.sig, self.c)
}
}
#[derive(Default)]
/// A batch verification context.
pub struct Verifier<C: Ciphersuite> {
/// Signature data queued for verification.
@ -115,49 +110,47 @@ where
let mut VKs = Vec::with_capacity(n);
let mut R_coeffs = Vec::with_capacity(self.signatures.len());
let mut Rs = Vec::with_capacity(self.signatures.len());
let mut P_coeff_acc = Scalar::zero();
let mut P_coeff_acc = <<C::Group as Group>::Field as Field>::zero();
for item in self.signatures.iter() {
let (z_bytes, R_bytes, c) = (item.sig.z_bytes, item.sig.R_bytes, item.c);
let z = item.sig.z;
let R = item.sig.R;
let s = Scalar::from_bytes_mod_order(z_bytes);
let blind = <<C::Group as Group>::Field as Field>::random(&mut rng);
let R = {
match CompressedRistretto::from_slice(&R_bytes).decompress() {
Some(point) => point,
None => return Err(Error::InvalidSignature),
}
};
let P_coeff = blind * z;
P_coeff_acc = P_coeff_acc - P_coeff;
let VK = VerifyingKey::try_from(item.vk_bytes.bytes)?.point;
let z = Scalar::random(&mut rng);
let P_coeff = z * s;
P_coeff_acc -= P_coeff;
R_coeffs.push(z);
R_coeffs.push(blind);
Rs.push(R);
VK_coeffs.push(Scalar::zero() + (z * c));
VKs.push(VK);
VK_coeffs.push(<<C::Group as Group>::Field as Field>::zero() + (blind * item.c.0));
VKs.push(item.vk.element);
}
use std::iter::once;
let scalars = once(&P_coeff_acc)
.chain(VK_coeffs.iter())
.chain(R_coeffs.iter());
let basepoints = [curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT];
let basepoints = [<C::Group as Group>::generator()];
let points = basepoints.iter().chain(VKs.iter()).chain(Rs.iter());
let check = RistrettoPoint::vartime_multiscalar_mul(scalars, points);
let check: Element<C> =
VartimeMultiscalarMul::<C>::vartime_multiscalar_mul(scalars, points);
if check == RistrettoPoint::identity() {
if (check * <C::Group as Group>::cofactor()) == <C::Group as Group>::identity() {
Ok(())
} else {
Err(Error::InvalidSignature)
}
}
}
impl<C> Default for Verifier<C>
where
C: Ciphersuite,
{
fn default() -> Self {
Self { signatures: vec![] }
}
}

View File

@ -11,10 +11,10 @@ use std::{
use rand_core::{CryptoRng, RngCore};
// pub mod batch;
pub mod batch;
mod error;
pub mod frost;
// mod scalar_mul;
mod scalar_mul;
mod signature;
mod signing_key;
mod verifying_key;
@ -66,17 +66,17 @@ pub trait Field: Copy + Clone {
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-05.html#section-3.1-3.4>
fn random_nonzero<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
/// A member function of a group _G_ that maps an [`Element`] to a unique byte array buf of
/// A member function of a [`Field`] that maps a [`Scalar`] to a unique byte array buf of
/// fixed length Ne.
///
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-05.html#section-3.1-3.7>
fn serialize(scalar: &Self::Scalar) -> Self::Serialization;
/// A member function of a [`Group`] that attempts to map a byte array `buf` to an [`Element`].
/// A member function of a [`Field`] that attempts to map a byte array `buf` to a [`Scalar`].
///
/// Fails if the input is not a valid byte representation of an [`Element`] of the
/// [`Group`]. This function can raise a [`DeserializeError`] if deserialization fails or if the
/// resulting [`Element`] is the identity element of the group
/// Fails if the input is not a valid byte representation of an [`Scalar`] of the
/// [`Field`]. This function can raise a [`DeserializeError`] if deserialization fails or if the
/// resulting [`Scalar`] is zero
///
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-05.html#section-3.1-3.8>
fn deserialize(buf: &Self::Serialization) -> Result<Self::Scalar, Error>;
@ -109,7 +109,7 @@ pub trait Group: Copy + Clone {
/// A unique byte array buf of fixed length N.
///
/// Little-endian!
type Serialization: AsRef<[u8]> + TryFrom<Vec<u8>>;
type Serialization: AsRef<[u8]> + Debug + TryFrom<Vec<u8>>;
/// The order of the the quotient group when the prime order subgroup divides the order of the
/// full curve group.

View File

@ -1,21 +1,30 @@
//! Non-adjacent form (NAF) implementations for fast batch scalar multiplcation
use std::{
borrow::Borrow,
fmt::{Debug, Result},
marker::PhantomData,
};
pub trait NonAdjacentForm {
use crate::{Ciphersuite, Element, Field, Group, Scalar};
/// A trait for transforming a scalar generic over a ciphersuite to a non-adjacent form (NAF).
pub trait NonAdjacentForm<C: Ciphersuite> {
fn non_adjacent_form(&self, w: usize) -> [i8; 256];
}
impl<C> NonAdjacentForm for Scalar<C>
impl<C> NonAdjacentForm<C> for Scalar<C>
where
C: Ciphersuite,
{
/// Computes a width-\\(w\\) "Non-Adjacent Form" of this scalar.
/// Computes a width-(w) "Non-Adjacent Form" of this scalar.
///
/// Thanks to curve25519-dalek for the original implementation that informed this one.
///
/// # Safety
///
/// The full scalar field MUST fit in 256 bits in this implementation.
/// Assumes that a little-endian representations of the scalar in NAF work.
fn non_adjacent_form(&self, w: usize) -> [i8; 256] {
// required by the NAF definition
debug_assert!(w >= 2);
@ -24,11 +33,14 @@ where
use byteorder::{ByteOrder, LittleEndian};
// NB: Assumes a scalar that fits in 256 bits.
// Safety: assumes a scalar that fits in 256 bits.
let mut naf = [0i8; 256];
let mut x_u64 = [0u64; 5];
LittleEndian::read_u64_into(&self.to_bytes(), &mut x_u64[0..4]);
LittleEndian::read_u64_into(
<<C::Group as Group>::Field as Field>::serialize(self).as_ref(),
&mut x_u64[0..4],
);
let width = 1 << w;
let window_mask = width - 1;
@ -78,7 +90,7 @@ where
/// A trait for variable-time multiscalar multiplication without precomputation.
///
/// Implement for a group element.
pub trait VartimeMultiscalarMul: Clone {
pub trait VartimeMultiscalarMul<C: Ciphersuite>: Clone {
/// Given an iterator of public scalars and an iterator of
/// `Option`s of group elements, compute either `Some(Q)`, where
/// $$
@ -88,7 +100,7 @@ pub trait VartimeMultiscalarMul: Clone {
fn optional_multiscalar_mul<I, J>(scalars: I, elements: J) -> Option<Self>
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
I::Item: Borrow<Scalar<C>>,
J: IntoIterator<Item = Option<Self>>;
/// Given an iterator of public scalars and an iterator of
@ -102,7 +114,7 @@ pub trait VartimeMultiscalarMul: Clone {
fn vartime_multiscalar_mul<I, J>(scalars: I, elements: J) -> Self
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
I::Item: Borrow<Scalar<C>>,
J: IntoIterator,
J::Item: Borrow<Self>,
{
@ -114,36 +126,36 @@ pub trait VartimeMultiscalarMul: Clone {
}
}
impl<C> VartimeMultiscalarMul for Element<C>
impl<C> VartimeMultiscalarMul<C> for Element<C>
where
C: Ciphersuite,
{
fn optional_multiscalar_mul<I, J>(scalars: I, elements: J) -> Option<Element<C>>
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
I::Item: Borrow<Scalar<C>>,
J: IntoIterator<Item = Option<Element<C>>>,
{
let nafs: Vec<_> = scalars
.into_iter()
.map(|c| c.borrow().non_adjacent_form(5))
.map(|c| NonAdjacentForm::<C>::non_adjacent_form(c.borrow(), 5))
.collect();
let lookup_tables = elements
.into_iter()
.map(|P_opt| P_opt.map(|P| LookupTable5::<Element<C>>::from(&P)))
.map(|P_opt| P_opt.map(|P| LookupTable5::<C, Element<C>>::from(&P)))
.collect::<Option<Vec<_>>>()?;
let mut r = <C::Group as Group>::identity();
for i in (0..256).rev() {
let mut t = r + r.clone();
let mut t = r + r;
for (naf, lookup_table) in nafs.iter().zip(lookup_tables.iter()) {
if naf[i] > 0 {
t = &t + &lookup_table.select(naf[i] as usize);
t = t + lookup_table.select(naf[i] as usize);
} else if naf[i] < 0 {
t = &t - &lookup_table.select(-naf[i] as usize);
t = t - lookup_table.select(-naf[i] as usize);
}
}
@ -156,35 +168,42 @@ where
/// Holds odd multiples 1A, 3A, ..., 15A of a point A.
#[derive(Copy, Clone)]
pub(crate) struct LookupTable5<T>(pub(crate) [T; 8]);
pub(crate) struct LookupTable5<C, T> {
pub(crate) bytes: [T; 8],
pub(crate) _marker: PhantomData<C>,
}
impl<T: Copy> LookupTable5<T> {
impl<C: Ciphersuite, T: Copy> LookupTable5<C, T> {
/// Given public, odd \\( x \\) with \\( 0 < x < 2^4 \\), return \\(xA\\).
pub fn select(&self, x: usize) -> T {
debug_assert_eq!(x & 1, 1);
debug_assert!(x < 16);
self.0[x / 2]
self.bytes[x / 2]
}
}
impl<T: Debug> Debug for LookupTable5<T> {
impl<C: Ciphersuite, T: Debug> Debug for LookupTable5<C, T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result {
write!(f, "LookupTable5({:?})", self.0)
write!(f, "LookupTable5({:?})", self.bytes)
}
}
impl<'a, C> From<&'a <C::Group·as·Group>::Element> for LookupTable5<<C::Group·as·Group>::Element>
impl<'a, C> From<&'a Element<C>> for LookupTable5<C, Element<C>>
where
C: Ciphersuite,
{
fn from(A: &'a <C::Group·as·Group>::Element) -> Self {
let mut Ai = [A; 8];
let A2 = A * A.clone();
fn from(A: &'a Element<C>) -> Self {
let mut Ai = [*A; 8];
let A2 = *A + *A;
for i in 0..7 {
Ai[i + 1] = (&A2 + &Ai[i]);
Ai[i + 1] = A2 + Ai[i];
}
// Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A]
LookupTable5(Ai)
LookupTable5 {
bytes: Ai,
_marker: PhantomData,
}
}
}

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Debug};
use hex::FromHex;
use crate::{Ciphersuite, Error, Group, Signature};
use crate::{Challenge, Ciphersuite, Error, Group, Signature};
/// A valid verifying key for Schnorr signatures over a FROST [`Ciphersuite::Group`].
#[derive(Copy, Clone, PartialEq)]
@ -37,12 +37,22 @@ where
pub fn verify(&self, msg: &[u8], signature: &Signature<C>) -> Result<(), Error> {
let c = crate::challenge::<C>(&signature.R, &self.element, msg);
self.verify_prehashed(signature, c)
}
/// Verify a purported `signature` with a pre-hashed [`Challenge`] made by this verification
/// key.
pub(crate) fn verify_prehashed(
&self,
signature: &Signature<C>,
challenge: Challenge<C>,
) -> Result<(), Error> {
// Verify check is h * ( - z * B + R + c * A) == 0
// h * ( z * B - c * A - R) == 0
//
// where h is the cofactor
let zB = C::Group::generator() * signature.z;
let cA = self.element * c.0;
let cA = self.element * challenge.0;
let check = (zB - cA - signature.R) * C::Group::cofactor();
if check == C::Group::identity() {

View File

@ -15,7 +15,8 @@ fn batch_verify() {
let vk = VerifyingKey::<R>::from(&sk);
let msg = b"BatchVerifyTest";
let sig = sk.sign(&mut rng, &msg[..]);
batch.queue((vk.into(), sig, msg));
assert!(vk.verify(msg, &sig).is_ok());
batch.queue((vk, sig, msg));
}
assert!(batch.verify(rng).is_ok());
}
@ -37,14 +38,14 @@ fn bad_batch_verify() {
} else {
sk.sign(&mut rng, b"bad")
};
(vk.into(), sig, msg).into()
(vk, sig, msg).into()
}
1 => {
let sk = SigningKey::new(&mut rng);
let vk = VerifyingKey::<R>::from(&sk);
let msg = b"BatchVerifyTest";
let sig = sk.sign(&mut rng, &msg[..]);
(vk.into(), sig, msg).into()
(vk, sig, msg).into()
}
_ => unreachable!(),
};