use std::{string::String, vec::Vec};
use halo2_proofs::{
pasta::EqAffine,
plonk::{self, keygen_pk, keygen_vk},
poly::commitment::Params,
};
use super::circuit::{Circuit, Instance, K};
use crate::{
prove_error::{create_proof_bytes, verify_proof_bytes},
ProveError,
};
pub type DelegationKeys = (
Params<EqAffine>,
plonk::ProvingKey<EqAffine>,
plonk::VerifyingKey<EqAffine>,
);
static DELEGATION_PK_CACHE: std::sync::OnceLock<Result<DelegationKeys, String>> =
std::sync::OnceLock::new();
pub fn delegation_params() -> Params<EqAffine> {
Params::new(K)
}
pub fn delegation_proving_key(
params: &Params<EqAffine>,
) -> Result<(plonk::ProvingKey<EqAffine>, plonk::VerifyingKey<EqAffine>), ProveError> {
let empty_circuit = Circuit::default();
let vk = keygen_vk(params, &empty_circuit).map_err(ProveError::KeygenVk)?;
let pk = keygen_pk(params, vk.clone(), &empty_circuit).map_err(ProveError::KeygenPk)?;
Ok((pk, vk))
}
pub fn delegation_cached_keys() -> Result<&'static DelegationKeys, ProveError> {
match DELEGATION_PK_CACHE.get_or_init(|| {
let params = delegation_params();
delegation_proving_key(¶ms)
.map(|(pk, vk)| (params, pk, vk))
.map_err(|error| error.to_string())
}) {
Ok(keys) => Ok(keys),
Err(error) => Err(ProveError::CachedKeygen(error.clone())),
}
}
pub fn warm_delegation_keys() -> Result<(), ProveError> {
delegation_cached_keys().map(|_| ())
}
pub fn create_delegation_proof(
circuit: Circuit,
instance: &Instance,
) -> Result<Vec<u8>, ProveError> {
let (params, pk, _vk) = delegation_cached_keys()?;
let public_inputs = instance.to_halo2_instance();
create_proof_bytes(params, pk, circuit, &public_inputs)
}
pub fn verify_delegation_proof(proof: &[u8], instance: &Instance) -> Result<(), String> {
let (params, _pk, vk) = delegation_cached_keys().map_err(|error| error.to_string())?;
let public_inputs = instance.to_halo2_instance();
verify_proof_bytes("delegation", params, vk, proof, &public_inputs)
}
#[cfg(test)]
mod prove_tests {
use super::*;
use crate::delegation::builder::{build_delegation_bundle, RealNoteInput};
use crate::delegation::imt::{ImtProvider, SpacedLeafImtProvider};
use crate::ProveError;
use ff::Field;
use halo2_proofs::plonk;
use incrementalmerkletree::{Hashable, Level};
use orchard::{
keys::{FullViewingKey, Scope, SpendValidatingKey, SpendingKey},
note::{commitment::ExtractedNoteCommitment, nullifier::Nullifier, Note, Rho},
tree::{MerkleHashOrchard, MerklePath},
value::NoteValue,
};
use pasta_curves::pallas;
use rand::rngs::OsRng;
fn minimal_instance() -> Instance {
let mut rng = OsRng;
let sk = SpendingKey::random(&mut rng);
let fvk: FullViewingKey = (&sk).into();
let ak: SpendValidatingKey = fvk.into();
let rk = ak.randomize(&pallas::Scalar::from(1));
Instance::from_parts(
Nullifier::from_inner(pallas::Base::from(1)),
rk,
pallas::Base::from(2),
pallas::Base::from(3),
pallas::Base::from(4),
pallas::Base::from(5),
pallas::Base::from(6),
[pallas::Base::from(7); 5],
pallas::Base::from(8),
)
.expect("test rk must be non-identity")
}
#[test]
fn create_delegation_proof_signature_returns_result() {
let _: fn(Circuit, &Instance) -> Result<Vec<u8>, ProveError> = create_delegation_proof;
}
#[test]
#[ignore = "long-running K=14 proof keygen; run when touching delegation proof creation"]
fn create_delegation_proof_returns_err_for_missing_witnesses() {
let instance = minimal_instance();
let err = create_delegation_proof(Circuit::default(), &instance).unwrap_err();
assert!(matches!(err, ProveError::Halo2(plonk::Error::Synthesis)));
}
#[test]
#[ignore = "TODO(sean): runs K=14 keygen; run with `cargo test -- --ignored vk_fingerprint_unchanged`"]
fn vk_fingerprint_unchanged() {
let (_, _, vk) = delegation_cached_keys().expect("delegation keys");
let pinned = format!("{:?}", vk.pinned());
let fingerprint = blake2b_simd::Params::new()
.hash_length(32)
.hash(pinned.as_bytes());
let actual: &[u8] = fingerprint.as_bytes();
let expected: [u8; 32] = [
0xc4, 0xfc, 0xa7, 0xc2, 0x23, 0x3c, 0x48, 0x65, 0x86, 0x25, 0x4a, 0x4c, 0xca, 0xf5,
0x03, 0x98, 0xd3, 0x72, 0x1b, 0x49, 0xe1, 0x49, 0xa5, 0x5b, 0xf2, 0x10, 0x93, 0xcf,
0xb6, 0xb1, 0x34, 0x7c,
];
assert_eq!(
actual,
expected.as_slice(),
"delegation VK fingerprint changed; if intentional, update `expected` to:\n{:02x?}",
actual,
);
}
#[test]
#[ignore = "long-running real proof roundtrip; run with `cargo test -- --ignored`"]
fn real_proof_roundtrip() {
let mut rng = OsRng;
let sk = SpendingKey::random(&mut rng);
let fvk: FullViewingKey = (&sk).into();
let output_recipient = fvk.address_at(1u32, Scope::External);
let vote_round_id = pallas::Base::random(&mut rng);
let van_comm_rand = pallas::Base::random(&mut rng);
let alpha = pallas::Scalar::random(&mut rng);
let recipient = fvk.address_at(0u32, Scope::External);
let (_, _, dummy) = Note::dummy(&mut rng, None);
let note = Note::new(
recipient,
NoteValue::from_raw(13_000_000),
Rho::from_nf_old(dummy.nullifier(&fvk)),
&mut rng,
);
let cmx = ExtractedNoteCommitment::from(note.commitment());
let leaf = MerkleHashOrchard::from_cmx(&cmx);
let empty = MerkleHashOrchard::empty_leaf();
let mut leaves = [empty; 2];
leaves[0] = leaf;
let l1 = MerkleHashOrchard::combine(Level::from(0), &leaves[0], &leaves[1]);
let mut current = l1;
for level in 1..32u8 {
current = MerkleHashOrchard::combine(
Level::from(level),
¤t,
&MerkleHashOrchard::empty_root(Level::from(level)),
);
}
let nc_root = current.inner();
let mut auth_path = [empty; 32];
auth_path[0] = leaves[1];
for level in 1..32u8 {
auth_path[level as usize] = MerkleHashOrchard::empty_root(Level::from(level));
}
let merkle_path = MerklePath::from_parts(0u32, auth_path);
let imt = SpacedLeafImtProvider::new();
let real_nf = note.nullifier(&fvk);
let imt_proof = imt.non_membership_proof(real_nf.inner()).unwrap();
let input = RealNoteInput {
note,
fvk: fvk.clone(),
merkle_path,
imt_proof,
scope: Scope::External,
};
let bundle = build_delegation_bundle(
vec![input],
&fvk,
alpha,
output_recipient,
vote_round_id,
nc_root,
van_comm_rand,
&imt,
&mut rng,
None,
)
.unwrap();
let proof = create_delegation_proof(bundle.circuit, &bundle.instance)
.expect("delegation proof creation should succeed");
verify_delegation_proof(&proof, &bundle.instance).expect("real proof roundtrip failed");
}
}