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:
parent
2e8509837c
commit
c551cd56eb
|
@ -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![] }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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!(),
|
||||
};
|
Loading…
Reference in New Issue