mirror of https://github.com/zcash/orchard.git
Circuit: Update value_commit_orchard to take into account asset (#50)
In the circuit, we update value_commit_orchard to take into account asset. Previously, value_commit_orchard returns cv_net = [v_net] ValueCommitV + [rcv] ValueCommitR.. Now, value_commit_orchard returns cv_net = [v_net] asset + [rcv] ValueCommitR. ValueCommitV and ValueCommitR are constants v_net is equal to sign * magnitude where sign is in {-1, 1} and magnitude is an unsigned integer on 64 bits. To evaluate [v_net] asset where v_net = sign * magnitude, we perform the following steps 1. verify that magnitude is on 64 bits 2. evaluate commitment=[magnitude]asset with the variable-base long-scalar multiplication 3. evaluate result=[sign]commitment with the new mul_sign gate
This commit is contained in:
parent
f0b794896d
commit
563b4e5502
|
@ -29,8 +29,8 @@ blake2b_simd = "1"
|
|||
ff = "0.12"
|
||||
fpe = "0.5"
|
||||
group = { version = "0.12.1", features = ["wnaf-memuse"] }
|
||||
halo2_gadgets = "0.2"
|
||||
halo2_proofs = "0.2"
|
||||
halo2_gadgets = { git = "https://github.com/QED-it/halo2", branch = "zsa1" }
|
||||
halo2_proofs = { git = "https://github.com/QED-it/halo2", branch = "zsa1" }
|
||||
hex = "0.4"
|
||||
lazy_static = "1"
|
||||
memuse = { version = "0.2.1", features = ["nonempty"] }
|
||||
|
@ -52,7 +52,7 @@ plotters = { version = "0.3.0", optional = true }
|
|||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
halo2_gadgets = { version = "0.2", features = ["test-dependencies"] }
|
||||
halo2_gadgets = { git = "https://github.com/QED-it/halo2", branch = "zsa1" , features = ["test-dependencies"] }
|
||||
hex = "0.4"
|
||||
proptest = "1.0.0"
|
||||
zcash_note_encryption = { version = "0.2", features = ["pre-zip-212"] }
|
||||
|
|
126
src/circuit.rs
126
src/circuit.rs
|
@ -46,7 +46,7 @@ use crate::{
|
|||
use halo2_gadgets::{
|
||||
ecc::{
|
||||
chip::{EccChip, EccConfig},
|
||||
FixedPoint, NonIdentityPoint, Point, ScalarFixed, ScalarFixedShort, ScalarVar,
|
||||
FixedPoint, NonIdentityPoint, Point, ScalarFixed, ScalarVar,
|
||||
},
|
||||
poseidon::{primitives as poseidon, Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig},
|
||||
sinsemilla::{
|
||||
|
@ -62,6 +62,7 @@ use halo2_gadgets::{
|
|||
mod commit_ivk;
|
||||
pub mod gadget;
|
||||
mod note_commit;
|
||||
mod value_commit_orchard;
|
||||
|
||||
/// Size of the Orchard circuit.
|
||||
const K: u32 = 11;
|
||||
|
@ -109,7 +110,6 @@ pub struct Circuit {
|
|||
pub(crate) psi_old: Value<pallas::Base>,
|
||||
pub(crate) rcm_old: Value<NoteCommitTrapdoor>,
|
||||
pub(crate) cm_old: Value<NoteCommitment>,
|
||||
pub(crate) asset_old: Value<AssetBase>,
|
||||
pub(crate) alpha: Value<pallas::Scalar>,
|
||||
pub(crate) ak: Value<SpendValidatingKey>,
|
||||
pub(crate) nk: Value<NullifierDerivingKey>,
|
||||
|
@ -119,8 +119,8 @@ pub struct Circuit {
|
|||
pub(crate) v_new: Value<NoteValue>,
|
||||
pub(crate) psi_new: Value<pallas::Base>,
|
||||
pub(crate) rcm_new: Value<NoteCommitTrapdoor>,
|
||||
pub(crate) asset_new: Value<AssetBase>,
|
||||
pub(crate) rcv: Value<ValueCommitTrapdoor>,
|
||||
pub(crate) asset: Value<AssetBase>,
|
||||
pub(crate) split_flag: Value<bool>,
|
||||
}
|
||||
|
||||
|
@ -175,7 +175,6 @@ impl Circuit {
|
|||
psi_old: Value::known(psi_old),
|
||||
rcm_old: Value::known(rcm_old),
|
||||
cm_old: Value::known(spend.note.commitment()),
|
||||
asset_old: Value::known(spend.note.asset()),
|
||||
alpha: Value::known(alpha),
|
||||
ak: Value::known(spend.fvk.clone().into()),
|
||||
nk: Value::known(*spend.fvk.nk()),
|
||||
|
@ -185,8 +184,8 @@ impl Circuit {
|
|||
v_new: Value::known(output_note.value()),
|
||||
psi_new: Value::known(psi_new),
|
||||
rcm_new: Value::known(rcm_new),
|
||||
asset_new: Value::known(output_note.asset()),
|
||||
rcv: Value::known(rcv),
|
||||
asset: Value::known(spend.note.asset()),
|
||||
split_flag: Value::known(spend.split_flag),
|
||||
}
|
||||
}
|
||||
|
@ -475,23 +474,6 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
(psi_old, rho_old, cm_old, g_d_old, ak_P, nk, v_old, v_new)
|
||||
};
|
||||
|
||||
// Verify that asset_old and asset_new are equals
|
||||
{
|
||||
let asset_old = NonIdentityPoint::new(
|
||||
ecc_chip.clone(),
|
||||
layouter.namespace(|| "witness asset_old"),
|
||||
self.asset_old
|
||||
.map(|asset_old| asset_old.cv_base().to_affine()),
|
||||
)?;
|
||||
let asset_new = NonIdentityPoint::new(
|
||||
ecc_chip.clone(),
|
||||
layouter.namespace(|| "asset equality"),
|
||||
self.asset_new
|
||||
.map(|asset_new| asset_new.cv_base().to_affine()),
|
||||
)?;
|
||||
asset_old.constrain_equal(layouter.namespace(|| "asset equality"), &asset_new)?;
|
||||
}
|
||||
|
||||
// Merkle path validity check (https://p.z.cash/ZKS:action-merkle-path-validity?partial).
|
||||
let root = {
|
||||
let path = self
|
||||
|
@ -549,22 +531,25 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
(magnitude, sign)
|
||||
};
|
||||
|
||||
let v_net = ScalarFixedShort::new(
|
||||
ecc_chip.clone(),
|
||||
layouter.namespace(|| "v_net"),
|
||||
v_net_magnitude_sign.clone(),
|
||||
)?;
|
||||
let rcv = ScalarFixed::new(
|
||||
ecc_chip.clone(),
|
||||
layouter.namespace(|| "rcv"),
|
||||
self.rcv.as_ref().map(|rcv| rcv.inner()),
|
||||
)?;
|
||||
|
||||
let cv_net = gadget::value_commit_orchard(
|
||||
layouter.namespace(|| "cv_net = ValueCommit^Orchard_rcv(v_net)"),
|
||||
let asset = NonIdentityPoint::new(
|
||||
ecc_chip.clone(),
|
||||
v_net,
|
||||
layouter.namespace(|| "witness asset"),
|
||||
self.asset.map(|asset| asset.cv_base().to_affine()),
|
||||
)?;
|
||||
|
||||
let cv_net = gadget::value_commit_orchard(
|
||||
layouter.namespace(|| "cv_net = ValueCommit^Orchard_rcv(v_net_magnitude_sign)"),
|
||||
config.sinsemilla_chip_1(),
|
||||
ecc_chip.clone(),
|
||||
v_net_magnitude_sign.clone(),
|
||||
rcv,
|
||||
asset,
|
||||
)?;
|
||||
|
||||
// Constrain cv_net to equal public input
|
||||
|
@ -1033,7 +1018,6 @@ mod tests {
|
|||
use rand::{rngs::OsRng, RngCore};
|
||||
|
||||
use super::{Circuit, Instance, Proof, ProvingKey, VerifyingKey, K};
|
||||
use crate::keys::{IssuanceAuthorizingKey, IssuanceValidatingKey, SpendingKey};
|
||||
use crate::note::AssetBase;
|
||||
use crate::{
|
||||
keys::SpendValidatingKey,
|
||||
|
@ -1074,7 +1058,6 @@ mod tests {
|
|||
psi_old: Value::known(spent_note.rseed().psi(&spent_note.rho())),
|
||||
rcm_old: Value::known(spent_note.rseed().rcm(&spent_note.rho())),
|
||||
cm_old: Value::known(spent_note.commitment()),
|
||||
asset_old: Value::known(spent_note.asset()),
|
||||
alpha: Value::known(alpha),
|
||||
ak: Value::known(ak),
|
||||
nk: Value::known(nk),
|
||||
|
@ -1084,8 +1067,8 @@ mod tests {
|
|||
v_new: Value::known(output_note.value()),
|
||||
psi_new: Value::known(output_note.rseed().psi(&output_note.rho())),
|
||||
rcm_new: Value::known(output_note.rseed().rcm(&output_note.rho())),
|
||||
asset_new: Value::known(output_note.asset()),
|
||||
rcv: Value::known(rcv),
|
||||
asset: Value::known(spent_note.asset()),
|
||||
split_flag: Value::known(false),
|
||||
},
|
||||
Instance {
|
||||
|
@ -1128,8 +1111,8 @@ mod tests {
|
|||
K as usize,
|
||||
&circuits[0],
|
||||
);
|
||||
assert_eq!(usize::from(circuit_cost.proof_size(1)), 4992);
|
||||
assert_eq!(usize::from(circuit_cost.proof_size(2)), 7264);
|
||||
assert_eq!(usize::from(circuit_cost.proof_size(1)), 5024);
|
||||
assert_eq!(usize::from(circuit_cost.proof_size(2)), 7296);
|
||||
usize::from(circuit_cost.proof_size(instances.len()))
|
||||
};
|
||||
|
||||
|
@ -1156,74 +1139,6 @@ mod tests {
|
|||
assert_eq!(proof.0.len(), expected_proof_size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_equal_asset_ids() {
|
||||
use halo2_proofs::dev::{
|
||||
metadata::Column, metadata::Region, FailureLocation, VerifyFailure,
|
||||
};
|
||||
use halo2_proofs::plonk::Any::Advice;
|
||||
|
||||
let mut rng = OsRng;
|
||||
|
||||
let (mut circuit, instance) = generate_circuit_instance(&mut rng);
|
||||
|
||||
// We would like to test that if the asset of the spent note (called asset_old) and the
|
||||
// asset of the output note (called asset_new) are not equal, the proof is not verified.
|
||||
// To do that, we attribute a random value to asset_new.
|
||||
let random_asset_id = {
|
||||
let sk = SpendingKey::random(&mut rng);
|
||||
let isk = IssuanceAuthorizingKey::from(&sk);
|
||||
let ik = IssuanceValidatingKey::from(&isk);
|
||||
let asset_descr = "zsa_asset";
|
||||
AssetBase::derive(&ik, asset_descr)
|
||||
};
|
||||
circuit.asset_new = Value::known(random_asset_id);
|
||||
|
||||
assert_eq!(
|
||||
MockProver::run(
|
||||
K,
|
||||
&circuit,
|
||||
instance
|
||||
.to_halo2_instance()
|
||||
.iter()
|
||||
.map(|p| p.to_vec())
|
||||
.collect()
|
||||
)
|
||||
.unwrap()
|
||||
.verify(),
|
||||
Err(vec![
|
||||
VerifyFailure::Permutation {
|
||||
column: Column::from((Advice, 0)),
|
||||
location: FailureLocation::InRegion {
|
||||
region: Region::from((9, "witness non-identity point".to_string())),
|
||||
offset: 0
|
||||
}
|
||||
},
|
||||
VerifyFailure::Permutation {
|
||||
column: Column::from((Advice, 0)),
|
||||
location: FailureLocation::InRegion {
|
||||
region: Region::from((10, "witness non-identity point".to_string())),
|
||||
offset: 0
|
||||
}
|
||||
},
|
||||
VerifyFailure::Permutation {
|
||||
column: Column::from((Advice, 1)),
|
||||
location: FailureLocation::InRegion {
|
||||
region: Region::from((9, "witness non-identity point".to_string())),
|
||||
offset: 0
|
||||
}
|
||||
},
|
||||
VerifyFailure::Permutation {
|
||||
column: Column::from((Advice, 1)),
|
||||
location: FailureLocation::InRegion {
|
||||
region: Region::from((10, "witness non-identity point".to_string())),
|
||||
offset: 0
|
||||
}
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialized_proof_test_case() {
|
||||
use std::io::{Read, Write};
|
||||
|
@ -1305,7 +1220,7 @@ mod tests {
|
|||
let test_case_bytes = include_bytes!("circuit_proof_test_case.bin");
|
||||
read_test_case(&test_case_bytes[..]).expect("proof must be valid")
|
||||
};
|
||||
assert_eq!(proof.0.len(), 4992);
|
||||
assert_eq!(proof.0.len(), 5024);
|
||||
|
||||
assert!(proof.verify(&vk, &[instance]).is_ok());
|
||||
}
|
||||
|
@ -1331,7 +1246,6 @@ mod tests {
|
|||
psi_old: Value::unknown(),
|
||||
rcm_old: Value::unknown(),
|
||||
cm_old: Value::unknown(),
|
||||
asset_old: Value::unknown(),
|
||||
alpha: Value::unknown(),
|
||||
ak: Value::unknown(),
|
||||
nk: Value::unknown(),
|
||||
|
@ -1341,8 +1255,8 @@ mod tests {
|
|||
v_new: Value::unknown(),
|
||||
psi_new: Value::unknown(),
|
||||
rcm_new: Value::unknown(),
|
||||
asset_new: Value::unknown(),
|
||||
rcv: Value::unknown(),
|
||||
asset: Value::unknown(),
|
||||
split_flag: Value::unknown(),
|
||||
};
|
||||
halo2_proofs::dev::CircuitLayout::default()
|
||||
|
|
|
@ -4,15 +4,9 @@ use ff::Field;
|
|||
use pasta_curves::pallas;
|
||||
|
||||
use super::{commit_ivk::CommitIvkChip, note_commit::NoteCommitChip};
|
||||
use crate::constants::{
|
||||
NullifierK, OrchardCommitDomains, OrchardFixedBases, OrchardFixedBasesFull, OrchardHashDomains,
|
||||
ValueCommitV,
|
||||
};
|
||||
use crate::constants::{NullifierK, OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains};
|
||||
use halo2_gadgets::{
|
||||
ecc::{
|
||||
chip::EccChip, EccInstructions, FixedPoint, FixedPointBaseField, FixedPointShort, Point,
|
||||
ScalarFixed, ScalarFixedShort, X,
|
||||
},
|
||||
ecc::{chip::EccChip, EccInstructions, FixedPointBaseField, Point, X},
|
||||
poseidon::{
|
||||
primitives::{self as poseidon, ConstantLength},
|
||||
Hash as PoseidonHash, PoseidonSpongeInstructions, Pow5Chip as PoseidonChip,
|
||||
|
@ -107,41 +101,6 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
/// `ValueCommit^Orchard` from [Section 5.4.8.3 Homomorphic Pedersen commitments (Sapling and Orchard)].
|
||||
///
|
||||
/// [Section 5.4.8.3 Homomorphic Pedersen commitments (Sapling and Orchard)]: https://zips.z.cash/protocol/protocol.pdf#concretehomomorphiccommit
|
||||
pub(in crate::circuit) fn value_commit_orchard<
|
||||
EccChip: EccInstructions<
|
||||
pallas::Affine,
|
||||
FixedPoints = OrchardFixedBases,
|
||||
Var = AssignedCell<pallas::Base, pallas::Base>,
|
||||
>,
|
||||
>(
|
||||
mut layouter: impl Layouter<pallas::Base>,
|
||||
ecc_chip: EccChip,
|
||||
v: ScalarFixedShort<pallas::Affine, EccChip>,
|
||||
rcv: ScalarFixed<pallas::Affine, EccChip>,
|
||||
) -> Result<Point<pallas::Affine, EccChip>, plonk::Error> {
|
||||
// commitment = [v] ValueCommitV
|
||||
let (commitment, _) = {
|
||||
let value_commit_v = ValueCommitV;
|
||||
let value_commit_v = FixedPointShort::from_inner(ecc_chip.clone(), value_commit_v);
|
||||
value_commit_v.mul(layouter.namespace(|| "[v] ValueCommitV"), v)?
|
||||
};
|
||||
|
||||
// blind = [rcv] ValueCommitR
|
||||
let (blind, _rcv) = {
|
||||
let value_commit_r = OrchardFixedBasesFull::ValueCommitR;
|
||||
let value_commit_r = FixedPoint::from_inner(ecc_chip, value_commit_r);
|
||||
|
||||
// [rcv] ValueCommitR
|
||||
value_commit_r.mul(layouter.namespace(|| "[rcv] ValueCommitR"), rcv)?
|
||||
};
|
||||
|
||||
// [v] ValueCommitV + [rcv] ValueCommitR
|
||||
commitment.add(layouter.namespace(|| "cv"), &blind)
|
||||
}
|
||||
|
||||
/// `DeriveNullifier` from [Section 4.16: Note Commitments and Nullifiers].
|
||||
///
|
||||
/// [Section 4.16: Note Commitments and Nullifiers]: https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers
|
||||
|
@ -202,3 +161,4 @@ pub(in crate::circuit) fn derive_nullifier<
|
|||
|
||||
pub(in crate::circuit) use crate::circuit::commit_ivk::gadgets::commit_ivk;
|
||||
pub(in crate::circuit) use crate::circuit::note_commit::gadgets::note_commit;
|
||||
pub(in crate::circuit) use crate::circuit::value_commit_orchard::gadgets::value_commit_orchard;
|
||||
|
|
|
@ -0,0 +1,341 @@
|
|||
pub(in crate::circuit) mod gadgets {
|
||||
use pasta_curves::pallas;
|
||||
|
||||
use crate::constants::{
|
||||
OrchardCommitDomains, OrchardFixedBases, OrchardFixedBasesFull, OrchardHashDomains,
|
||||
};
|
||||
use halo2_gadgets::{
|
||||
ecc::{chip::EccChip, FixedPoint, NonIdentityPoint, Point, ScalarFixed, ScalarVar},
|
||||
sinsemilla::{self, chip::SinsemillaChip},
|
||||
};
|
||||
use halo2_proofs::{
|
||||
circuit::{AssignedCell, Chip, Layouter},
|
||||
plonk,
|
||||
};
|
||||
|
||||
/// `ValueCommit^Orchard` from [Section 5.4.8.3 Homomorphic Pedersen commitments (Sapling and Orchard)].
|
||||
///
|
||||
/// [Section 5.4.8.3 Homomorphic Pedersen commitments (Sapling and Orchard)]: https://zips.z.cash/protocol/protocol.pdf#concretehomomorphiccommit
|
||||
pub(in crate::circuit) fn value_commit_orchard(
|
||||
mut layouter: impl Layouter<pallas::Base>,
|
||||
sinsemilla_chip: SinsemillaChip<
|
||||
OrchardHashDomains,
|
||||
OrchardCommitDomains,
|
||||
OrchardFixedBases,
|
||||
>,
|
||||
ecc_chip: EccChip<OrchardFixedBases>,
|
||||
v_net_magnitude_sign: (
|
||||
AssignedCell<pallas::Base, pallas::Base>,
|
||||
AssignedCell<pallas::Base, pallas::Base>,
|
||||
),
|
||||
rcv: ScalarFixed<pallas::Affine, EccChip<OrchardFixedBases>>,
|
||||
asset: NonIdentityPoint<pallas::Affine, EccChip<OrchardFixedBases>>,
|
||||
) -> Result<Point<pallas::Affine, EccChip<OrchardFixedBases>>, plonk::Error> {
|
||||
// Check that magnitude is 64 bits.
|
||||
{
|
||||
let lookup_config = sinsemilla_chip.config().lookup_config();
|
||||
let (magnitude_words, magnitude_extra_bits) = (6, 4);
|
||||
assert_eq!(
|
||||
magnitude_words * sinsemilla::primitives::K + magnitude_extra_bits,
|
||||
64
|
||||
);
|
||||
let magnitude_zs = lookup_config.copy_check(
|
||||
layouter.namespace(|| "magnitude lowest 60 bits"),
|
||||
v_net_magnitude_sign.0.clone(),
|
||||
magnitude_words, // 6 windows of 10 bits.
|
||||
false, // Do not constrain the result here.
|
||||
)?;
|
||||
assert_eq!(magnitude_zs.len(), magnitude_words + 1);
|
||||
lookup_config.copy_short_check(
|
||||
layouter.namespace(|| "magnitude highest 4 bits"),
|
||||
magnitude_zs[magnitude_words].clone(),
|
||||
magnitude_extra_bits, // The 7th window must be a 4 bits value.
|
||||
)?;
|
||||
}
|
||||
|
||||
// Multiply asset by magnitude, using the long scalar mul.
|
||||
// TODO: implement a new variable base multiplication which is optimized for 64-bit scalar
|
||||
// (the long scalar mul is optimized for pallas::Base scalar (~255-bits))
|
||||
//
|
||||
// commitment = [magnitude] asset
|
||||
let commitment = {
|
||||
let magnitude_scalar = ScalarVar::from_base(
|
||||
ecc_chip.clone(),
|
||||
layouter.namespace(|| "magnitude"),
|
||||
&v_net_magnitude_sign.0,
|
||||
)?;
|
||||
let (commitment, _) =
|
||||
asset.mul(layouter.namespace(|| "[magnitude] asset"), magnitude_scalar)?;
|
||||
commitment
|
||||
};
|
||||
|
||||
// signed_commitment = [sign] commitment = [v_net_magnitude_sign] asset
|
||||
let signed_commitment = commitment.mul_sign(
|
||||
layouter.namespace(|| "[sign] commitment"),
|
||||
&v_net_magnitude_sign.1,
|
||||
)?;
|
||||
|
||||
// blind = [rcv] ValueCommitR
|
||||
let (blind, _rcv) = {
|
||||
let value_commit_r = OrchardFixedBasesFull::ValueCommitR;
|
||||
let value_commit_r = FixedPoint::from_inner(ecc_chip, value_commit_r);
|
||||
|
||||
// [rcv] ValueCommitR
|
||||
value_commit_r.mul(layouter.namespace(|| "[rcv] ValueCommitR"), rcv)?
|
||||
};
|
||||
|
||||
// [v] ValueCommitV + [rcv] ValueCommitR
|
||||
signed_commitment.add(layouter.namespace(|| "cv"), &blind)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
circuit::gadget::{assign_free_advice, value_commit_orchard},
|
||||
circuit::K,
|
||||
constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains},
|
||||
keys::{IssuanceAuthorizingKey, IssuanceValidatingKey, SpendingKey},
|
||||
note::AssetBase,
|
||||
value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
|
||||
};
|
||||
use halo2_gadgets::{
|
||||
ecc::{
|
||||
chip::{EccChip, EccConfig},
|
||||
NonIdentityPoint, ScalarFixed,
|
||||
},
|
||||
sinsemilla::chip::{SinsemillaChip, SinsemillaConfig},
|
||||
utilities::lookup_range_check::LookupRangeCheckConfig,
|
||||
};
|
||||
|
||||
use group::Curve;
|
||||
use halo2_proofs::{
|
||||
circuit::{Layouter, SimpleFloorPlanner, Value},
|
||||
dev::MockProver,
|
||||
plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance},
|
||||
};
|
||||
use pasta_curves::pallas;
|
||||
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
|
||||
#[test]
|
||||
fn test_value_commit_orchard() {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MyConfig {
|
||||
primary: Column<Instance>,
|
||||
advices: [Column<Advice>; 10],
|
||||
ecc_config: EccConfig<OrchardFixedBases>,
|
||||
// Sinsemilla config is only used to initialize the table_idx lookup table in the same
|
||||
// way as in the Orchard circuit
|
||||
sinsemilla_config:
|
||||
SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
|
||||
}
|
||||
#[derive(Default)]
|
||||
struct MyCircuit {
|
||||
v_old: Value<NoteValue>,
|
||||
v_new: Value<NoteValue>,
|
||||
rcv: Value<ValueCommitTrapdoor>,
|
||||
asset: Value<AssetBase>,
|
||||
split_flag: Value<bool>,
|
||||
}
|
||||
|
||||
impl Circuit<pallas::Base> for MyCircuit {
|
||||
type Config = MyConfig;
|
||||
type FloorPlanner = SimpleFloorPlanner;
|
||||
|
||||
fn without_witnesses(&self) -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
|
||||
let advices = [
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
];
|
||||
|
||||
for advice in advices.iter() {
|
||||
meta.enable_equality(*advice);
|
||||
}
|
||||
|
||||
// Instance column used for public inputs
|
||||
let primary = meta.instance_column();
|
||||
meta.enable_equality(primary);
|
||||
|
||||
let table_idx = meta.lookup_table_column();
|
||||
let lookup = (
|
||||
table_idx,
|
||||
meta.lookup_table_column(),
|
||||
meta.lookup_table_column(),
|
||||
);
|
||||
|
||||
let lagrange_coeffs = [
|
||||
meta.fixed_column(),
|
||||
meta.fixed_column(),
|
||||
meta.fixed_column(),
|
||||
meta.fixed_column(),
|
||||
meta.fixed_column(),
|
||||
meta.fixed_column(),
|
||||
meta.fixed_column(),
|
||||
meta.fixed_column(),
|
||||
];
|
||||
meta.enable_constant(lagrange_coeffs[0]);
|
||||
|
||||
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
|
||||
|
||||
let sinsemilla_config = SinsemillaChip::configure(
|
||||
meta,
|
||||
advices[..5].try_into().unwrap(),
|
||||
advices[6],
|
||||
lagrange_coeffs[0],
|
||||
lookup,
|
||||
range_check,
|
||||
);
|
||||
|
||||
MyConfig {
|
||||
primary,
|
||||
advices,
|
||||
ecc_config: EccChip::<OrchardFixedBases>::configure(
|
||||
meta,
|
||||
advices,
|
||||
lagrange_coeffs,
|
||||
range_check,
|
||||
),
|
||||
sinsemilla_config,
|
||||
}
|
||||
}
|
||||
|
||||
fn synthesize(
|
||||
&self,
|
||||
config: Self::Config,
|
||||
mut layouter: impl Layouter<pallas::Base>,
|
||||
) -> Result<(), Error> {
|
||||
// Load the Sinsemilla generator lookup table.
|
||||
SinsemillaChip::load(config.sinsemilla_config.clone(), &mut layouter)?;
|
||||
|
||||
// Construct an ECC chip
|
||||
let ecc_chip = EccChip::construct(config.ecc_config);
|
||||
|
||||
let sinsemilla_chip = SinsemillaChip::construct(config.sinsemilla_config.clone());
|
||||
|
||||
// Witness the magnitude and sign of v_net = v_old - v_new
|
||||
let v_net_magnitude_sign = {
|
||||
// v_net is equal to
|
||||
// (-v_new) if split_flag = true
|
||||
// v_old - v_new if split_flag = false
|
||||
let v_net = self.split_flag.and_then(|split_flag| {
|
||||
if split_flag {
|
||||
Value::known(crate::value::NoteValue::zero()) - self.v_new
|
||||
} else {
|
||||
self.v_old - self.v_new
|
||||
}
|
||||
});
|
||||
|
||||
let magnitude_sign = v_net.map(|v_net| {
|
||||
let (magnitude, sign) = v_net.magnitude_sign();
|
||||
(
|
||||
// magnitude is guaranteed to be an unsigned 64-bit value.
|
||||
// Therefore, we can move it into the base field.
|
||||
pallas::Base::from(magnitude),
|
||||
match sign {
|
||||
crate::value::Sign::Positive => pallas::Base::one(),
|
||||
crate::value::Sign::Negative => -pallas::Base::one(),
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
let magnitude = assign_free_advice(
|
||||
layouter.namespace(|| "v_net magnitude"),
|
||||
config.advices[9],
|
||||
magnitude_sign.map(|m_s| m_s.0),
|
||||
)?;
|
||||
let sign = assign_free_advice(
|
||||
layouter.namespace(|| "v_net sign"),
|
||||
config.advices[9],
|
||||
magnitude_sign.map(|m_s| m_s.1),
|
||||
)?;
|
||||
(magnitude, sign)
|
||||
};
|
||||
|
||||
// Witness rcv
|
||||
let rcv = ScalarFixed::new(
|
||||
ecc_chip.clone(),
|
||||
layouter.namespace(|| "rcv"),
|
||||
self.rcv.as_ref().map(|rcv| rcv.inner()),
|
||||
)?;
|
||||
|
||||
// Witness asset
|
||||
let asset = NonIdentityPoint::new(
|
||||
ecc_chip.clone(),
|
||||
layouter.namespace(|| "witness asset"),
|
||||
self.asset.map(|asset| asset.cv_base().to_affine()),
|
||||
)?;
|
||||
|
||||
// Evaluate cv_net with value_commit_orchard
|
||||
let cv_net = value_commit_orchard(
|
||||
layouter.namespace(|| "cv_net = ValueCommit^Orchard_rcv(v_net)"),
|
||||
sinsemilla_chip,
|
||||
ecc_chip,
|
||||
v_net_magnitude_sign,
|
||||
rcv,
|
||||
asset,
|
||||
)?;
|
||||
|
||||
// Constrain cv_net to equal public input
|
||||
layouter.constrain_instance(cv_net.inner().x().cell(), config.primary, 0)?;
|
||||
layouter.constrain_instance(cv_net.inner().y().cell(), config.primary, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Test different circuits
|
||||
let mut rng = OsRng;
|
||||
let mut circuits = vec![];
|
||||
let mut instances = vec![];
|
||||
let native_asset = AssetBase::native();
|
||||
let random_asset = {
|
||||
let sk = SpendingKey::random(&mut rng);
|
||||
let isk = IssuanceAuthorizingKey::from(&sk);
|
||||
let ik = IssuanceValidatingKey::from(&isk);
|
||||
let asset_descr = "zsa_asset";
|
||||
AssetBase::derive(&ik, asset_descr)
|
||||
};
|
||||
for split_flag in [false, true] {
|
||||
for asset in [native_asset, random_asset] {
|
||||
let v_old = NoteValue::from_raw(rng.next_u64());
|
||||
let v_new = NoteValue::from_raw(rng.next_u64());
|
||||
let rcv = ValueCommitTrapdoor::random(&mut rng);
|
||||
let v_net = if split_flag {
|
||||
NoteValue::zero() - v_new
|
||||
} else {
|
||||
v_old - v_new
|
||||
};
|
||||
circuits.push(MyCircuit {
|
||||
v_old: Value::known(v_old),
|
||||
v_new: Value::known(v_new),
|
||||
rcv: Value::known(rcv),
|
||||
asset: Value::known(asset),
|
||||
split_flag: Value::known(split_flag),
|
||||
});
|
||||
let expected_cv_net = ValueCommitment::derive(v_net, rcv, asset);
|
||||
instances.push([[expected_cv_net.x(), expected_cv_net.y()]]);
|
||||
}
|
||||
}
|
||||
|
||||
for (circuit, instance) in circuits.iter().zip(instances.iter()) {
|
||||
let prover = MockProver::<pallas::Base>::run(
|
||||
K,
|
||||
circuit,
|
||||
instance.iter().map(|p| p.to_vec()).collect(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(prover.verify(), Ok(()));
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Loading…
Reference in New Issue