implement proof-based signatures using plonky2 proofs (#112)
* implement proof-based signatures using plonky2 proofs * proof-based sigs: polish & document
This commit is contained in:
parent
42c1f0b0f7
commit
ef3bf26533
4 changed files with 200 additions and 1 deletions
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
|
@ -19,4 +19,4 @@ jobs:
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test
|
run: cargo test --release
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use plonky2::field::types::{Field, PrimeField64};
|
||||||
use plonky2::hash::poseidon::PoseidonHash;
|
use plonky2::hash::poseidon::PoseidonHash;
|
||||||
use plonky2::plonk::config::Hasher;
|
use plonky2::plonk::config::Hasher;
|
||||||
use plonky2::plonk::config::PoseidonGoldilocksConfig;
|
use plonky2::plonk::config::PoseidonGoldilocksConfig;
|
||||||
|
use plonky2::plonk::proof::Proof as Plonky2Proof;
|
||||||
use std::cmp::{Ord, Ordering};
|
use std::cmp::{Ord, Ordering};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
|
@ -21,6 +22,9 @@ pub type C = PoseidonGoldilocksConfig;
|
||||||
/// D defines the extension degree of the field used in the Plonky2 proofs (quadratic extension).
|
/// D defines the extension degree of the field used in the Plonky2 proofs (quadratic extension).
|
||||||
pub const D: usize = 2;
|
pub const D: usize = 2;
|
||||||
|
|
||||||
|
/// proof system proof
|
||||||
|
pub type Proof = Plonky2Proof<F, PoseidonGoldilocksConfig, D>;
|
||||||
|
|
||||||
pub const HASH_SIZE: usize = 4;
|
pub const HASH_SIZE: usize = 4;
|
||||||
pub const VALUE_SIZE: usize = 4;
|
pub const VALUE_SIZE: usize = 4;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
pub mod merkletree;
|
pub mod merkletree;
|
||||||
|
pub mod signature;
|
||||||
|
|
|
||||||
194
src/backends/plonky2/primitives/signature.rs
Normal file
194
src/backends/plonky2/primitives/signature.rs
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
//! Proof-based signatures using Plonky2 proofs, following
|
||||||
|
//! https://eprint.iacr.org/2024/1553 .
|
||||||
|
use anyhow::Result;
|
||||||
|
use plonky2::{
|
||||||
|
field::types::Sample,
|
||||||
|
hash::{
|
||||||
|
hash_types::{HashOut, HashOutTarget},
|
||||||
|
poseidon::PoseidonHash,
|
||||||
|
},
|
||||||
|
iop::{
|
||||||
|
target::Target,
|
||||||
|
witness::{PartialWitness, WitnessWrite},
|
||||||
|
},
|
||||||
|
plonk::{
|
||||||
|
circuit_builder::CircuitBuilder,
|
||||||
|
circuit_data::{CircuitConfig, ProverCircuitData, VerifierCircuitData},
|
||||||
|
config::Hasher,
|
||||||
|
proof::ProofWithPublicInputs,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::backends::plonky2::basetypes::{Proof, Value, C, D, F, VALUE_SIZE};
|
||||||
|
|
||||||
|
pub struct ProverParams {
|
||||||
|
prover: ProverCircuitData<F, C, D>,
|
||||||
|
circuit: SignatureCircuit,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VerifierParams(VerifierCircuitData<F, C, D>);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SecretKey(Value);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PublicKey(Value);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Signature(Proof);
|
||||||
|
|
||||||
|
/// Implements the key generation and the computation of proof-based signatures.
|
||||||
|
impl SecretKey {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
// note: the `F::rand()` internally uses `rand::rngs::OsRng`
|
||||||
|
Self(Value(std::array::from_fn(|_| F::rand())))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn public_key(&self) -> PublicKey {
|
||||||
|
PublicKey(Value(PoseidonHash::hash_no_pad(&self.0 .0).elements))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign(&self, pp: &ProverParams, msg: Value) -> Result<Signature> {
|
||||||
|
let pk = self.public_key();
|
||||||
|
let s = Value(PoseidonHash::hash_no_pad(&[pk.0 .0, msg.0].concat()).elements);
|
||||||
|
|
||||||
|
let mut pw = PartialWitness::<F>::new();
|
||||||
|
pp.circuit.set_targets(&mut pw, self.clone(), pk, msg, s)?;
|
||||||
|
|
||||||
|
let proof = pp.prover.prove(pw)?;
|
||||||
|
|
||||||
|
Ok(Signature(proof.proof))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements the parameters generation and the verification of proof-based
|
||||||
|
/// signatures.
|
||||||
|
impl Signature {
|
||||||
|
pub fn params() -> Result<(ProverParams, VerifierParams)> {
|
||||||
|
let (builder, circuit) = Self::builder()?;
|
||||||
|
let prover = builder.build_prover::<C>();
|
||||||
|
|
||||||
|
let (builder, _) = Self::builder()?;
|
||||||
|
let circuit_data = builder.build::<C>();
|
||||||
|
let vp = circuit_data.verifier_data();
|
||||||
|
|
||||||
|
Ok((ProverParams { prover, circuit }, VerifierParams(vp)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn builder() -> Result<(CircuitBuilder<F, D>, SignatureCircuit)> {
|
||||||
|
// notice that we use the 'zk' config
|
||||||
|
let config = CircuitConfig::standard_recursion_zk_config();
|
||||||
|
|
||||||
|
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||||
|
let circuit = SignatureCircuit::add_targets(&mut builder)?;
|
||||||
|
|
||||||
|
Ok((builder, circuit))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(&self, vp: &VerifierParams, pk: &PublicKey, msg: Value) -> Result<()> {
|
||||||
|
// prepare public inputs as [pk, msg, s]
|
||||||
|
let s = Value(PoseidonHash::hash_no_pad(&[pk.0 .0, msg.0].concat()).elements);
|
||||||
|
let public_inputs: Vec<F> = [pk.0 .0, msg.0, s.0].concat();
|
||||||
|
|
||||||
|
// verify plonky2 proof
|
||||||
|
vp.0.verify(ProofWithPublicInputs {
|
||||||
|
proof: self.0.clone(),
|
||||||
|
public_inputs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The SignatureCircuit implements the circuit used for the proof of the
|
||||||
|
/// argument described at https://eprint.iacr.org/2024/1553.
|
||||||
|
///
|
||||||
|
/// The circuit proves that for the given public inputs (pk, msg, s), the Prover
|
||||||
|
/// knows the secret (sk) such that:
|
||||||
|
/// i) pk == H(sk)
|
||||||
|
/// ii) s == H(pk, msg)
|
||||||
|
struct SignatureCircuit {
|
||||||
|
sk_targ: Vec<Target>,
|
||||||
|
pk_targ: HashOutTarget,
|
||||||
|
msg_targ: Vec<Target>,
|
||||||
|
s_targ: HashOutTarget,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignatureCircuit {
|
||||||
|
/// creates the targets and defines the logic of the circuit
|
||||||
|
fn add_targets(builder: &mut CircuitBuilder<F, D>) -> Result<Self> {
|
||||||
|
// create the targets
|
||||||
|
let sk_targ = builder.add_virtual_targets(VALUE_SIZE);
|
||||||
|
let pk_targ = builder.add_virtual_hash();
|
||||||
|
let msg_targ = builder.add_virtual_targets(VALUE_SIZE);
|
||||||
|
let s_targ = builder.add_virtual_hash();
|
||||||
|
|
||||||
|
// define the public inputs
|
||||||
|
builder.register_public_inputs(&pk_targ.elements);
|
||||||
|
builder.register_public_inputs(&msg_targ);
|
||||||
|
builder.register_public_inputs(&s_targ.elements);
|
||||||
|
|
||||||
|
// define the logic
|
||||||
|
let computed_pk_targ = builder.hash_n_to_hash_no_pad::<PoseidonHash>(sk_targ.clone());
|
||||||
|
builder.connect_array::<VALUE_SIZE>(computed_pk_targ.elements, pk_targ.elements);
|
||||||
|
|
||||||
|
let inp: Vec<Target> = [pk_targ.elements.to_vec(), msg_targ.clone()].concat();
|
||||||
|
let computed_s_targ = builder.hash_n_to_hash_no_pad::<PoseidonHash>(inp);
|
||||||
|
builder.connect_array::<VALUE_SIZE>(computed_s_targ.elements, s_targ.elements);
|
||||||
|
|
||||||
|
// return the targets
|
||||||
|
Ok(Self {
|
||||||
|
sk_targ,
|
||||||
|
pk_targ,
|
||||||
|
msg_targ,
|
||||||
|
s_targ,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// assigns the given values to the targets
|
||||||
|
fn set_targets(
|
||||||
|
&self,
|
||||||
|
pw: &mut PartialWitness<F>,
|
||||||
|
sk: SecretKey,
|
||||||
|
pk: PublicKey,
|
||||||
|
msg: Value,
|
||||||
|
s: Value,
|
||||||
|
) -> Result<()> {
|
||||||
|
pw.set_target_arr(&self.sk_targ, &sk.0 .0.to_vec())?;
|
||||||
|
pw.set_hash_target(self.pk_targ, HashOut::<F>::from_vec(pk.0 .0.to_vec()))?;
|
||||||
|
pw.set_target_arr(&self.msg_targ, &msg.0.to_vec())?;
|
||||||
|
pw.set_hash_target(self.s_targ, HashOut::<F>::from_vec(s.0.to_vec()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use crate::backends::plonky2::basetypes::Hash;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Note: this test must be run with the `--release` flag.
|
||||||
|
#[test]
|
||||||
|
fn test_signature() -> Result<()> {
|
||||||
|
let (pp, vp) = Signature::params()?;
|
||||||
|
|
||||||
|
let sk = SecretKey::new();
|
||||||
|
let pk = sk.public_key();
|
||||||
|
|
||||||
|
let msg = Value::from(42);
|
||||||
|
let sig = sk.sign(&pp, msg)?;
|
||||||
|
sig.verify(&vp, &pk, msg)?;
|
||||||
|
|
||||||
|
// expect the signature verification to fail when using a different msg
|
||||||
|
let v = sig.verify(&vp, &pk, Value::from(24));
|
||||||
|
assert!(v.is_err(), "should fail to verify");
|
||||||
|
|
||||||
|
// perform a 2nd signature over another msg and verify it
|
||||||
|
let msg_2 = Value::from(Hash::from("message"));
|
||||||
|
let sig2 = sk.sign(&pp, msg_2)?;
|
||||||
|
sig2.verify(&vp, &pk, msg_2)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue