Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 234 additions & 7 deletions src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1188,20 +1188,24 @@ mod tests {
use core::iter;

use ff::Field;
use group::{Curve, Group, GroupEncoding};
use halo2_proofs::{circuit::Value, dev::MockProver};
use pasta_curves::pallas;
use rand::{rngs::OsRng, RngCore};

use super::{Circuit, Instance, Proof, ProvingKey, VerifyingKey, K};
use crate::note::AssetBase;
use crate::builder::SpendInfo;
use crate::note::commitment::NoteCommitTrapdoor;
use crate::note::{AssetBase, Nullifier};
use crate::primitives::redpallas::VerificationKey;
use crate::{
keys::SpendValidatingKey,
note::Note,
keys::{FullViewingKey, Scope, SpendValidatingKey, SpendingKey},
note::{Note, NoteCommitment},
tree::MerklePath,
value::{ValueCommitTrapdoor, ValueCommitment},
value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
};

fn generate_circuit_instance<R: RngCore>(mut rng: R) -> (Circuit, Instance) {
fn generate_dummy_circuit_instance<R: RngCore>(mut rng: R) -> (Circuit, Instance) {
let (_, fvk, spent_note) = Note::dummy(&mut rng, None, AssetBase::native());

let sender_address = spent_note.recipient();
Expand Down Expand Up @@ -1264,7 +1268,7 @@ mod tests {
let mut rng = OsRng;

let (circuits, instances): (Vec<_>, Vec<_>) = iter::once(())
.map(|()| generate_circuit_instance(&mut rng))
.map(|()| generate_dummy_circuit_instance(&mut rng))
.unzip();

let vk = VerifyingKey::build();
Expand Down Expand Up @@ -1378,7 +1382,7 @@ mod tests {
let create_proof = || -> std::io::Result<()> {
let mut rng = OsRng;

let (circuit, instance) = generate_circuit_instance(OsRng);
let (circuit, instance) = generate_dummy_circuit_instance(OsRng);
let instances = &[instance.clone()];

let pk = ProvingKey::build();
Expand Down Expand Up @@ -1441,4 +1445,227 @@ mod tests {
.render(K, &circuit, &root)
.unwrap();
}

fn check_proof_of_orchard_circuit(circuit: &Circuit, instance: &Instance, should_pass: bool) {
let proof_verify = MockProver::run(
K,
circuit,
instance
.to_halo2_instance()
.iter()
.map(|p| p.to_vec())
.collect(),
)
.unwrap()
.verify();
if should_pass {
assert!(proof_verify.is_ok());
} else {
assert!(proof_verify.is_err());
}
}

fn generate_circuit_instance<R: RngCore>(
is_native_asset: bool,
split_flag: bool,
mut rng: R,
) -> (Circuit, Instance) {
// Create asset
let asset_base = if is_native_asset {
AssetBase::native()
} else {
AssetBase::random(&mut rng)
};

// Create spent_note
let (spent_note_fvk, spent_note) = {
let sk = SpendingKey::random(&mut rng);
let fvk: FullViewingKey = (&sk).into();
let sender_address = fvk.address_at(0u32, Scope::External);
let rho_old = Nullifier::dummy(&mut rng);
let spent_note = Note::new(
sender_address,
NoteValue::from_raw(40),
asset_base,
rho_old,
&mut rng,
);
(fvk, spent_note)
};

let output_value = NoteValue::from_raw(10);

let (dummy_sk, fvk, scope, nf_old, v_net) = if split_flag {
let sk = SpendingKey::random(&mut rng);
let fvk: FullViewingKey = (&sk).into();
(
Some(sk),
fvk.clone(),
Scope::External,
spent_note.nullifier(&fvk),
// Split notes do not contribute to v_net.
// Therefore, if split_flag is true, v_net = - output_value
NoteValue::zero() - output_value,
)
} else {
(
None,
spent_note_fvk.clone(),
spent_note_fvk
.scope_for_address(&spent_note.recipient())
.unwrap(),
spent_note.nullifier(&spent_note_fvk),
spent_note.value() - output_value,
)
};
let ak: SpendValidatingKey = fvk.clone().into();
let alpha = pallas::Scalar::random(&mut rng);
let rk = ak.randomize(&alpha);

let output_note = {
let sk = SpendingKey::random(&mut rng);
let fvk: FullViewingKey = (&sk).into();
let sender_address = fvk.address_at(0u32, Scope::External);

Note::new(sender_address, output_value, asset_base, nf_old, &mut rng)
};

let cmx = output_note.commitment().into();

let rcv = ValueCommitTrapdoor::random(&mut rng);
let cv_net = ValueCommitment::derive(v_net, rcv, asset_base);

let path = MerklePath::dummy(&mut rng);
let anchor = path.root(spent_note.commitment().into());

let spend_info = SpendInfo {
dummy_sk,
fvk,
scope,
note: spent_note,
merkle_path: path,
split_flag,
};

(
Circuit::from_action_context_unchecked(spend_info, output_note, alpha, rcv),
Instance {
anchor,
cv_net,
nf_old,
rk,
cmx,
enable_spend: true,
enable_output: true,
},
)
}

fn random_note_commitment(mut rng: impl RngCore) -> NoteCommitment {
NoteCommitment::derive(
pallas::Point::random(&mut rng).to_affine().to_bytes(),
pallas::Point::random(&mut rng).to_affine().to_bytes(),
NoteValue::from_raw(rng.next_u64()),
AssetBase::random(&mut rng),
pallas::Base::random(&mut rng),
pallas::Base::random(&mut rng),
NoteCommitTrapdoor(pallas::Scalar::random(&mut rng)),
)
.unwrap()
}

#[test]
fn orchard_circuit_negative_test() {
let mut rng = OsRng;

for is_native_asset in [true, false] {
for split_flag in [true, false] {
let (circuit, instance) =
generate_circuit_instance(is_native_asset, split_flag, &mut rng);

check_proof_of_orchard_circuit(&circuit, &instance, true);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice.


// Set cv_net to zero
// The proof should fail
let instance_wrong_cv_net = Instance {
anchor: instance.anchor,
cv_net: ValueCommitment::from_bytes(&[0u8; 32]).unwrap(),
nf_old: instance.nf_old,
rk: instance.rk.clone(),
cmx: instance.cmx,
enable_spend: instance.enable_spend,
enable_output: instance.enable_output,
};
check_proof_of_orchard_circuit(&circuit, &instance_wrong_cv_net, false);

// Set rk_pub to dummy VerificationKey
// The proof should fail
let instance_wrong_rk = Instance {
anchor: instance.anchor,
cv_net: instance.cv_net.clone(),
nf_old: instance.nf_old,
rk: VerificationKey::dummy(),
cmx: instance.cmx,
enable_spend: instance.enable_spend,
enable_output: instance.enable_output,
};
check_proof_of_orchard_circuit(&circuit, &instance_wrong_rk, false);

// Set cm_old to random NoteCommitment
// The proof should fail
let circuit_wrong_cm_old = Circuit {
path: circuit.path,
pos: circuit.pos,
g_d_old: circuit.g_d_old,
pk_d_old: circuit.pk_d_old,
v_old: circuit.v_old,
rho_old: circuit.rho_old,
psi_old: circuit.psi_old,
rcm_old: circuit.rcm_old.clone(),
cm_old: Value::known(random_note_commitment(&mut rng)),
alpha: circuit.alpha,
ak: circuit.ak.clone(),
nk: circuit.nk,
rivk: circuit.rivk,
g_d_new: circuit.g_d_new,
pk_d_new: circuit.pk_d_new,
v_new: circuit.v_new,
psi_new: circuit.psi_new,
rcm_new: circuit.rcm_new.clone(),
rcv: circuit.rcv,
asset: circuit.asset,
split_flag: circuit.split_flag,
};
check_proof_of_orchard_circuit(&circuit_wrong_cm_old, &instance, false);

// Set cmx_pub to random NoteCommitment
// The proof should fail
let instance_wrong_cmx_pub = Instance {
anchor: instance.anchor,
cv_net: instance.cv_net.clone(),
nf_old: instance.nf_old,
rk: instance.rk.clone(),
cmx: random_note_commitment(&mut rng).into(),
enable_spend: instance.enable_spend,
enable_output: instance.enable_output,
};
check_proof_of_orchard_circuit(&circuit, &instance_wrong_cmx_pub, false);

// If split_flag=0, set nf_old_pub to random Nullifier
// The proof should fail
if !split_flag {
let instance_wrong_nf_old_pub = Instance {
anchor: instance.anchor,
cv_net: instance.cv_net,
nf_old: Nullifier::dummy(&mut rng),
rk: instance.rk,
cmx: instance.cmx,
enable_spend: instance.enable_spend,
enable_output: instance.enable_output,
};
check_proof_of_orchard_circuit(&circuit, &instance_wrong_nf_old_pub, false);
}
}
}
}
}