//! Demonstrates how to use Nova to produce a recursive proof of an ECDSA signature.
|
|
//! This example proves the knowledge of a sequence of ECDSA signatures with different public keys on different messages,
|
|
//! but the example can be adapted to other settings (e.g., proving the validity of the certificate chain with a well-known root public key)
|
|
//! Scheme borrowed from https://github.com/filecoin-project/bellperson-gadgets/blob/main/src/eddsa.rs
|
|
//! Sign using G1 curve, and prove using G2 curve.
|
|
|
|
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg};
|
|
use ff::{
|
|
derive::byteorder::{ByteOrder, LittleEndian},
|
|
Field, PrimeField, PrimeFieldBits,
|
|
};
|
|
use nova_snark::{
|
|
traits::{circuit::TrivialTestCircuit, Group as Nova_Group},
|
|
CompressedSNARK, PublicParams, RecursiveSNARK,
|
|
};
|
|
use num_bigint::BigUint;
|
|
use pasta_curves::{
|
|
arithmetic::CurveAffine,
|
|
group::{Curve, Group},
|
|
};
|
|
use rand::{rngs::OsRng, RngCore};
|
|
use sha3::{Digest, Sha3_512};
|
|
use subtle::Choice;
|
|
|
|
mod circuit;
|
|
mod utils;
|
|
|
|
use crate::circuit::{Coordinate, EcdsaCircuit, EcdsaSignature};
|
|
use crate::utils::BitIterator;
|
|
|
|
type G1 = pasta_curves::pallas::Point;
|
|
type G2 = pasta_curves::vesta::Point;
|
|
type S1 = nova_snark::spartan_with_ipa_pc::RelaxedR1CSSNARK<G2>;
|
|
type S2 = nova_snark::spartan_with_ipa_pc::RelaxedR1CSSNARK<G1>;
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct SecretKey(pub <G1 as Group>::Scalar);
|
|
|
|
impl SecretKey {
|
|
pub fn random(mut rng: impl RngCore) -> Self {
|
|
let secret = <G1 as Group>::Scalar::random(&mut rng);
|
|
Self(secret)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct PublicKey(pub G1);
|
|
|
|
impl PublicKey {
|
|
pub fn from_secret_key(s: &SecretKey) -> Self {
|
|
let point = G1::generator() * s.0;
|
|
Self(point)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct Signature {
|
|
pub r: G1,
|
|
pub s: <G1 as Group>::Scalar,
|
|
}
|
|
|
|
impl SecretKey {
|
|
pub fn sign(self, c: <G1 as Group>::Scalar, mut rng: impl RngCore) -> Signature {
|
|
// T
|
|
let mut t = [0u8; 80];
|
|
rng.fill_bytes(&mut t[..]);
|
|
|
|
// h = H*(T || M)
|
|
let h = Self::hash_to_scalar(b"Nova_Ecdsa_Hash", &t[..], &c.to_repr());
|
|
|
|
// R = [h]G
|
|
let r = G1::generator().mul(h);
|
|
|
|
// s = h + c * sk
|
|
let mut s = c;
|
|
|
|
s.mul_assign(&self.0);
|
|
s.add_assign(&h);
|
|
|
|
Signature { r, s }
|
|
}
|
|
|
|
fn mul_bits<B: AsRef<[u64]>>(
|
|
s: &<G1 as Group>::Scalar,
|
|
bits: BitIterator<B>,
|
|
) -> <G1 as Group>::Scalar {
|
|
let mut x = <G1 as Group>::Scalar::zero();
|
|
for bit in bits {
|
|
x.double();
|
|
|
|
if bit {
|
|
x.add_assign(s)
|
|
}
|
|
}
|
|
x
|
|
}
|
|
|
|
fn to_uniform(digest: &[u8]) -> <G1 as Group>::Scalar {
|
|
assert_eq!(digest.len(), 64);
|
|
let mut bits: [u64; 8] = [0; 8];
|
|
LittleEndian::read_u64_into(digest, &mut bits);
|
|
Self::mul_bits(&<G1 as Group>::Scalar::one(), BitIterator::new(bits))
|
|
}
|
|
|
|
pub fn to_uniform_32(digest: &[u8]) -> <G1 as Group>::Scalar {
|
|
assert_eq!(digest.len(), 32);
|
|
let mut bits: [u64; 4] = [0; 4];
|
|
LittleEndian::read_u64_into(digest, &mut bits);
|
|
Self::mul_bits(&<G1 as Group>::Scalar::one(), BitIterator::new(bits))
|
|
}
|
|
|
|
pub fn hash_to_scalar(persona: &[u8], a: &[u8], b: &[u8]) -> <G1 as Group>::Scalar {
|
|
let mut hasher = Sha3_512::new();
|
|
hasher.input(persona);
|
|
hasher.input(a);
|
|
hasher.input(b);
|
|
let digest = hasher.result();
|
|
Self::to_uniform(digest.as_ref())
|
|
}
|
|
}
|
|
|
|
impl PublicKey {
|
|
pub fn verify(&self, c: <G1 as Group>::Scalar, signature: &Signature) -> bool {
|
|
let modulus = Self::modulus_as_scalar();
|
|
let order_check_pk = self.0.mul(modulus);
|
|
if !order_check_pk.eq(&G1::identity()) {
|
|
return false;
|
|
}
|
|
|
|
let order_check_r = signature.r.mul(modulus);
|
|
if !order_check_r.eq(&G1::identity()) {
|
|
return false;
|
|
}
|
|
|
|
// 0 = [-s]G + R + [c]PK
|
|
self
|
|
.0
|
|
.mul(c)
|
|
.add(&signature.r)
|
|
.add(G1::generator().mul(signature.s).neg())
|
|
.eq(&G1::identity())
|
|
}
|
|
|
|
fn modulus_as_scalar() -> <G1 as Group>::Scalar {
|
|
let mut bits = <G1 as Group>::Scalar::char_le_bits().to_bitvec();
|
|
let mut acc = BigUint::new(Vec::<u32>::new());
|
|
while let Some(b) = bits.pop() {
|
|
acc <<= 1_i32;
|
|
acc += b as u8;
|
|
}
|
|
let modulus = acc.to_str_radix(10);
|
|
<G1 as Group>::Scalar::from_str_vartime(&modulus).unwrap()
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
// In a VERY LIMITED case of messages known to be unique due to application level
|
|
// and being less than the group order when interpreted as integer, one can sign
|
|
// the message directly without hashing
|
|
pub const MAX_MESSAGE_LEN: usize = 16;
|
|
assert!(MAX_MESSAGE_LEN * 8 <= <G1 as Group>::Scalar::CAPACITY as usize);
|
|
|
|
// produce public parameters
|
|
println!("Generating public parameters...");
|
|
|
|
let circuit_primary = EcdsaCircuit::<<G2 as Nova_Group>::Scalar> {
|
|
r: Coordinate::new(
|
|
<G2 as Nova_Group>::Scalar::zero(),
|
|
<G2 as Nova_Group>::Scalar::zero(),
|
|
),
|
|
g: Coordinate::new(
|
|
<G2 as Nova_Group>::Scalar::zero(),
|
|
<G2 as Nova_Group>::Scalar::zero(),
|
|
),
|
|
pk: Coordinate::new(
|
|
<G2 as Nova_Group>::Scalar::zero(),
|
|
<G2 as Nova_Group>::Scalar::zero(),
|
|
),
|
|
c: <G2 as Nova_Group>::Scalar::zero(),
|
|
s: <G2 as Nova_Group>::Scalar::zero(),
|
|
c_bits: vec![Choice::from(0u8); 256],
|
|
s_bits: vec![Choice::from(0u8); 256],
|
|
};
|
|
|
|
let circuit_secondary = TrivialTestCircuit::default();
|
|
|
|
let pp = PublicParams::<
|
|
G2,
|
|
G1,
|
|
EcdsaCircuit<<G2 as Group>::Scalar>,
|
|
TrivialTestCircuit<<G1 as Group>::Scalar>,
|
|
>::setup(circuit_primary, circuit_secondary.clone());
|
|
|
|
// produce non-deterministic advice
|
|
println!("Generating non-deterministic advice...");
|
|
|
|
let num_steps = 3;
|
|
|
|
let signatures = || {
|
|
let mut signatures = Vec::new();
|
|
for i in 0..num_steps {
|
|
let sk = SecretKey::random(&mut OsRng);
|
|
let pk = PublicKey::from_secret_key(&sk);
|
|
|
|
let message = format!("MESSAGE{}", i).as_bytes().to_owned();
|
|
assert!(message.len() <= MAX_MESSAGE_LEN);
|
|
|
|
let mut digest: Vec<u8> = message.to_vec();
|
|
for _ in 0..(32 - message.len() as u32) {
|
|
digest.extend(&[0u8; 1]);
|
|
}
|
|
|
|
let c = SecretKey::to_uniform_32(digest.as_ref());
|
|
|
|
let signature_primary = sk.sign(c, &mut OsRng);
|
|
let result = pk.verify(c, &signature_primary);
|
|
assert!(result);
|
|
|
|
// Affine coordinates guaranteed to be on the curve
|
|
let rxy = signature_primary.r.to_affine().coordinates().unwrap();
|
|
let gxy = G1::generator().to_affine().coordinates().unwrap();
|
|
let pkxy = pk.0.to_affine().coordinates().unwrap();
|
|
|
|
let s = signature_primary.s;
|
|
|
|
signatures.push(EcdsaSignature::<
|
|
<G1 as Nova_Group>::Base,
|
|
<G1 as Nova_Group>::Scalar,
|
|
>::new(
|
|
Coordinate::<<G1 as Nova_Group>::Base>::new(*pkxy.x(), *pkxy.y()),
|
|
Coordinate::<<G1 as Nova_Group>::Base>::new(*rxy.x(), *rxy.y()),
|
|
s,
|
|
c,
|
|
Coordinate::<<G1 as Nova_Group>::Base>::new(*gxy.x(), *gxy.y()),
|
|
));
|
|
}
|
|
signatures
|
|
};
|
|
|
|
let (z0_primary, circuits_primary) = EcdsaCircuit::<<G2 as Nova_Group>::Scalar>::new::<
|
|
<G1 as Nova_Group>::Base,
|
|
<G1 as Nova_Group>::Scalar,
|
|
>(num_steps, &signatures());
|
|
|
|
// Secondary circuit
|
|
let z0_secondary = vec![<G1 as Group>::Scalar::zero()];
|
|
|
|
// produce a recursive SNARK
|
|
println!("Generating a RecursiveSNARK...");
|
|
|
|
type C1 = EcdsaCircuit<<G2 as Nova_Group>::Scalar>;
|
|
type C2 = TrivialTestCircuit<<G1 as Nova_Group>::Scalar>;
|
|
|
|
let mut recursive_snark: Option<RecursiveSNARK<G2, G1, C1, C2>> = None;
|
|
|
|
for (i, circuit_primary) in circuits_primary.iter().take(num_steps).enumerate() {
|
|
let result = RecursiveSNARK::prove_step(
|
|
&pp,
|
|
recursive_snark,
|
|
circuit_primary.clone(),
|
|
circuit_secondary.clone(),
|
|
z0_primary.clone(),
|
|
z0_secondary.clone(),
|
|
);
|
|
assert!(result.is_ok());
|
|
println!("RecursiveSNARK::prove_step {}: {:?}", i, result.is_ok());
|
|
recursive_snark = Some(result.unwrap());
|
|
}
|
|
|
|
assert!(recursive_snark.is_some());
|
|
let recursive_snark = recursive_snark.unwrap();
|
|
|
|
// verify the recursive SNARK
|
|
println!("Verifying the RecursiveSNARK...");
|
|
let res = recursive_snark.verify(&pp, num_steps, z0_primary.clone(), z0_secondary.clone());
|
|
println!("RecursiveSNARK::verify: {:?}", res.is_ok());
|
|
assert!(res.is_ok());
|
|
|
|
// produce a compressed SNARK
|
|
println!("Generating a CompressedSNARK...");
|
|
let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &recursive_snark);
|
|
println!("CompressedSNARK::prove: {:?}", res.is_ok());
|
|
assert!(res.is_ok());
|
|
let compressed_snark = res.unwrap();
|
|
|
|
// verify the compressed SNARK
|
|
println!("Verifying a CompressedSNARK...");
|
|
let res = compressed_snark.verify(&pp, num_steps, z0_primary, z0_secondary);
|
|
println!("CompressedSNARK::verify: {:?}", res.is_ok());
|
|
assert!(res.is_ok());
|
|
}
|