mirror of https://github.com/zcash/orchard.git
Merge pull request #318 from zcash/str4d/circuit-review
Changes from circuit review
This commit is contained in:
commit
93ad4a6952
|
@ -13,6 +13,9 @@ and this project adheres to Rust's notion of
|
||||||
`Bundle::{try_}map_authorization`.
|
`Bundle::{try_}map_authorization`.
|
||||||
- `Flags::from_byte` now returns `Option<Flags>` instead of
|
- `Flags::from_byte` now returns `Option<Flags>` instead of
|
||||||
`io::Result<Flags>`.
|
`io::Result<Flags>`.
|
||||||
|
- `impl Sub for orchard::value::NoteValue` now returns `ValueSum` instead of
|
||||||
|
`Option<ValueSum>`, as the result is guaranteed to be within the valid range
|
||||||
|
of `ValueSum`.
|
||||||
|
|
||||||
## [0.1.0-beta.3] - 2022-04-06
|
## [0.1.0-beta.3] - 2022-04-06
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -82,6 +82,6 @@ debug = true
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
halo2_gadgets = { git = "https://github.com/zcash/halo2.git", rev = "0c33fa4e6e41464884765c8fb4cefebafd300ca2" }
|
halo2_gadgets = { git = "https://github.com/zcash/halo2.git", rev = "3800de59188a73b4e04f689c8bcc855a2fc7fdcf" }
|
||||||
halo2_proofs = { git = "https://github.com/zcash/halo2.git", rev = "0c33fa4e6e41464884765c8fb4cefebafd300ca2" }
|
halo2_proofs = { git = "https://github.com/zcash/halo2.git", rev = "3800de59188a73b4e04f689c8bcc855a2fc7fdcf" }
|
||||||
incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "f23e3d89507849a24543121839eea6f40b141aff" }
|
incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "f23e3d89507849a24543121839eea6f40b141aff" }
|
||||||
|
|
|
@ -18,10 +18,9 @@ bundle of actions, where each action is both a spend and an output. This provide
|
||||||
inherent arity-hiding as multi-JoinSplit Sprout, but using Sapling value commitments to
|
inherent arity-hiding as multi-JoinSplit Sprout, but using Sapling value commitments to
|
||||||
balance the transaction without doubling its size.
|
balance the transaction without doubling its size.
|
||||||
|
|
||||||
TODO: Depending on the circuit cost, we _may_ switch to having an action internally
|
|
||||||
represent either a spend or an output. Externally spends and outputs would still be
|
|
||||||
indistinguishable, but the transaction would be larger.
|
|
||||||
|
|
||||||
## Memo fields
|
## Memo fields
|
||||||
|
|
||||||
TODO: One memo per tx vs one memo per output
|
Each Orchard action has a memo field for its corresponding output, as with Sprout and
|
||||||
|
Sapling. We did at one point consider having a single Orchard memo field per transaction,
|
||||||
|
and/or having a mechanism for enabling multiple recipients to decrypt the same memo, but
|
||||||
|
these were decided against in order to keep the overall design simpler.
|
||||||
|
|
|
@ -149,7 +149,7 @@ pub(crate) mod testing {
|
||||||
) -> Action<()> {
|
) -> Action<()> {
|
||||||
let cmx = ExtractedNoteCommitment::from(note.commitment());
|
let cmx = ExtractedNoteCommitment::from(note.commitment());
|
||||||
let cv_net = ValueCommitment::derive(
|
let cv_net = ValueCommitment::derive(
|
||||||
(spend_value - output_value).unwrap(),
|
spend_value - output_value,
|
||||||
ValueCommitTrapdoor::zero()
|
ValueCommitTrapdoor::zero()
|
||||||
);
|
);
|
||||||
// FIXME: make a real one from the note.
|
// FIXME: make a real one from the note.
|
||||||
|
@ -180,7 +180,7 @@ pub(crate) mod testing {
|
||||||
) -> Action<redpallas::Signature<SpendAuth>> {
|
) -> Action<redpallas::Signature<SpendAuth>> {
|
||||||
let cmx = ExtractedNoteCommitment::from(note.commitment());
|
let cmx = ExtractedNoteCommitment::from(note.commitment());
|
||||||
let cv_net = ValueCommitment::derive(
|
let cv_net = ValueCommitment::derive(
|
||||||
(spend_value - output_value).unwrap(),
|
spend_value - output_value,
|
||||||
ValueCommitTrapdoor::zero()
|
ValueCommitTrapdoor::zero()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ use core::fmt;
|
||||||
use core::iter;
|
use core::iter;
|
||||||
|
|
||||||
use ff::Field;
|
use ff::Field;
|
||||||
use group::GroupEncoding;
|
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
use pasta_curves::pallas;
|
use pasta_curves::pallas;
|
||||||
use rand::{prelude::SliceRandom, CryptoRng, RngCore};
|
use rand::{prelude::SliceRandom, CryptoRng, RngCore};
|
||||||
|
@ -125,7 +124,7 @@ impl ActionInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the value sum for this action.
|
/// Returns the value sum for this action.
|
||||||
fn value_sum(&self) -> Option<ValueSum> {
|
fn value_sum(&self) -> ValueSum {
|
||||||
self.spend.note.value() - self.output.value
|
self.spend.note.value() - self.output.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +134,7 @@ impl ActionInfo {
|
||||||
///
|
///
|
||||||
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
||||||
fn build(self, mut rng: impl RngCore) -> (Action<SigningMetadata>, Circuit) {
|
fn build(self, mut rng: impl RngCore) -> (Action<SigningMetadata>, Circuit) {
|
||||||
let v_net = self.value_sum().expect("already checked this");
|
let v_net = self.value_sum();
|
||||||
let cv_net = ValueCommitment::derive(v_net, self.rcv.clone());
|
let cv_net = ValueCommitment::derive(v_net, self.rcv.clone());
|
||||||
|
|
||||||
let nf_old = self.spend.note.nullifier(&self.spend.fvk);
|
let nf_old = self.spend.note.nullifier(&self.spend.fvk);
|
||||||
|
@ -197,8 +196,8 @@ impl ActionInfo {
|
||||||
ak: Some(ak),
|
ak: Some(ak),
|
||||||
nk: Some(*self.spend.fvk.nk()),
|
nk: Some(*self.spend.fvk.nk()),
|
||||||
rivk: Some(self.spend.fvk.rivk(self.spend.scope)),
|
rivk: Some(self.spend.fvk.rivk(self.spend.scope)),
|
||||||
g_d_new_star: Some((*note.recipient().g_d()).to_bytes()),
|
g_d_new: Some(note.recipient().g_d()),
|
||||||
pk_d_new_star: Some(note.recipient().pk_d().to_bytes()),
|
pk_d_new: Some(*note.recipient().pk_d()),
|
||||||
v_new: Some(note.value()),
|
v_new: Some(note.value()),
|
||||||
psi_new: Some(note.rseed().psi(¬e.rho())),
|
psi_new: Some(note.rseed().psi(¬e.rho())),
|
||||||
rcm_new: Some(note.rseed().rcm(¬e.rho())),
|
rcm_new: Some(note.rseed().rcm(¬e.rho())),
|
||||||
|
@ -336,7 +335,7 @@ impl Builder {
|
||||||
let value_balance = pre_actions
|
let value_balance = pre_actions
|
||||||
.iter()
|
.iter()
|
||||||
.fold(Some(ValueSum::zero()), |acc, action| {
|
.fold(Some(ValueSum::zero()), |acc, action| {
|
||||||
acc? + action.value_sum()?
|
acc? + action.value_sum()
|
||||||
})
|
})
|
||||||
.ok_or(OverflowError)?;
|
.ok_or(OverflowError)?;
|
||||||
|
|
||||||
|
|
|
@ -522,7 +522,7 @@ pub mod testing {
|
||||||
|
|
||||||
output_value_gen.prop_flat_map(move |output_value| {
|
output_value_gen.prop_flat_map(move |output_value| {
|
||||||
arb_unauthorized_action(spend_value, output_value)
|
arb_unauthorized_action(spend_value, output_value)
|
||||||
.prop_map(move |a| ((spend_value - output_value).unwrap(), a))
|
.prop_map(move |a| (spend_value - output_value, a))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -547,7 +547,7 @@ pub mod testing {
|
||||||
|
|
||||||
output_value_gen.prop_flat_map(move |output_value| {
|
output_value_gen.prop_flat_map(move |output_value| {
|
||||||
arb_action(spend_value, output_value)
|
arb_action(spend_value, output_value)
|
||||||
.prop_map(move |a| ((spend_value - output_value).unwrap(), a))
|
.prop_map(move |a| (spend_value - output_value, a))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
305
src/circuit.rs
305
src/circuit.rs
|
@ -4,7 +4,7 @@ use core::fmt;
|
||||||
|
|
||||||
use group::{Curve, GroupEncoding};
|
use group::{Curve, GroupEncoding};
|
||||||
use halo2_proofs::{
|
use halo2_proofs::{
|
||||||
circuit::{floor_planner, AssignedCell, Layouter},
|
circuit::{floor_planner, Layouter},
|
||||||
plonk::{
|
plonk::{
|
||||||
self, Advice, Column, Constraints, Expression, Instance as InstanceColumn, Selector,
|
self, Advice, Column, Constraints, Expression, Instance as InstanceColumn, Selector,
|
||||||
SingleVerifier,
|
SingleVerifier,
|
||||||
|
@ -16,12 +16,18 @@ use memuse::DynamicUsage;
|
||||||
use pasta_curves::{arithmetic::CurveAffine, pallas, vesta};
|
use pasta_curves::{arithmetic::CurveAffine, pallas, vesta};
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
|
|
||||||
use self::commit_ivk::CommitIvkConfig;
|
use self::{
|
||||||
use self::note_commit::NoteCommitConfig;
|
commit_ivk::{CommitIvkChip, CommitIvkConfig},
|
||||||
|
gadget::{
|
||||||
|
add_chip::{AddChip, AddConfig},
|
||||||
|
assign_free_advice,
|
||||||
|
},
|
||||||
|
note_commit::{NoteCommitChip, NoteCommitConfig},
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::{
|
constants::{
|
||||||
util::gen_const_array, NullifierK, OrchardCommitDomains, OrchardFixedBases,
|
OrchardCommitDomains, OrchardFixedBases, OrchardFixedBasesFull, OrchardHashDomains,
|
||||||
OrchardFixedBasesFull, OrchardHashDomains, ValueCommitV, MERKLE_DEPTH_ORCHARD,
|
MERKLE_DEPTH_ORCHARD,
|
||||||
},
|
},
|
||||||
keys::{
|
keys::{
|
||||||
CommitIvkRandomness, DiversifiedTransmissionKey, NullifierDerivingKey, SpendValidatingKey,
|
CommitIvkRandomness, DiversifiedTransmissionKey, NullifierDerivingKey, SpendValidatingKey,
|
||||||
|
@ -39,10 +45,10 @@ use crate::{
|
||||||
use halo2_gadgets::{
|
use halo2_gadgets::{
|
||||||
ecc::{
|
ecc::{
|
||||||
chip::{EccChip, EccConfig},
|
chip::{EccChip, EccConfig},
|
||||||
FixedPoint, FixedPointBaseField, FixedPointShort, NonIdentityPoint, Point,
|
FixedPoint, NonIdentityPoint, Point,
|
||||||
},
|
},
|
||||||
poseidon::{Hash as PoseidonHash, Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig},
|
poseidon::{Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig},
|
||||||
primitives::poseidon::{self, ConstantLength},
|
primitives::poseidon,
|
||||||
sinsemilla::{
|
sinsemilla::{
|
||||||
chip::{SinsemillaChip, SinsemillaConfig},
|
chip::{SinsemillaChip, SinsemillaConfig},
|
||||||
merkle::{
|
merkle::{
|
||||||
|
@ -50,7 +56,7 @@ use halo2_gadgets::{
|
||||||
MerklePath,
|
MerklePath,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
|
utilities::lookup_range_check::LookupRangeCheckConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod commit_ivk;
|
mod commit_ivk;
|
||||||
|
@ -76,9 +82,8 @@ const ENABLE_OUTPUT: usize = 8;
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
primary: Column<InstanceColumn>,
|
primary: Column<InstanceColumn>,
|
||||||
q_orchard: Selector,
|
q_orchard: Selector,
|
||||||
// Selector for the field addition gate poseidon_hash(nk, rho_old) + psi_old.
|
|
||||||
q_add: Selector,
|
|
||||||
advices: [Column<Advice>; 10],
|
advices: [Column<Advice>; 10],
|
||||||
|
add_config: AddConfig,
|
||||||
ecc_config: EccConfig<OrchardFixedBases>,
|
ecc_config: EccConfig<OrchardFixedBases>,
|
||||||
poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
|
poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
|
||||||
merkle_config_1: MerkleConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
|
merkle_config_1: MerkleConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
|
||||||
|
@ -108,18 +113,14 @@ pub struct Circuit {
|
||||||
pub(crate) ak: Option<SpendValidatingKey>,
|
pub(crate) ak: Option<SpendValidatingKey>,
|
||||||
pub(crate) nk: Option<NullifierDerivingKey>,
|
pub(crate) nk: Option<NullifierDerivingKey>,
|
||||||
pub(crate) rivk: Option<CommitIvkRandomness>,
|
pub(crate) rivk: Option<CommitIvkRandomness>,
|
||||||
pub(crate) g_d_new_star: Option<[u8; 32]>,
|
pub(crate) g_d_new: Option<NonIdentityPallasPoint>,
|
||||||
pub(crate) pk_d_new_star: Option<[u8; 32]>,
|
pub(crate) pk_d_new: Option<DiversifiedTransmissionKey>,
|
||||||
pub(crate) v_new: Option<NoteValue>,
|
pub(crate) v_new: Option<NoteValue>,
|
||||||
pub(crate) psi_new: Option<pallas::Base>,
|
pub(crate) psi_new: Option<pallas::Base>,
|
||||||
pub(crate) rcm_new: Option<NoteCommitTrapdoor>,
|
pub(crate) rcm_new: Option<NoteCommitTrapdoor>,
|
||||||
pub(crate) rcv: Option<ValueCommitTrapdoor>,
|
pub(crate) rcv: Option<ValueCommitTrapdoor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UtilitiesInstructions<pallas::Base> for Circuit {
|
|
||||||
type Var = AssignedCell<pallas::Base, pallas::Base>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl plonk::Circuit<pallas::Base> for Circuit {
|
impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
type Config = Config;
|
type Config = Config;
|
||||||
type FloorPlanner = floor_planner::V1;
|
type FloorPlanner = floor_planner::V1;
|
||||||
|
@ -144,7 +145,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
];
|
];
|
||||||
|
|
||||||
// Constrain v_old - v_new = magnitude * sign
|
// Constrain v_old - v_new = magnitude * sign
|
||||||
// Either v_old = 0, or anchor equals public input
|
// Either v_old = 0, or calculated root = anchor
|
||||||
// Constrain v_old = 0 or enable_spends = 1.
|
// Constrain v_old = 0 or enable_spends = 1.
|
||||||
// Constrain v_new = 0 or enable_outputs = 1.
|
// Constrain v_new = 0 or enable_outputs = 1.
|
||||||
let q_orchard = meta.selector();
|
let q_orchard = meta.selector();
|
||||||
|
@ -155,12 +156,13 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
let magnitude = meta.query_advice(advices[2], Rotation::cur());
|
let magnitude = meta.query_advice(advices[2], Rotation::cur());
|
||||||
let sign = meta.query_advice(advices[3], Rotation::cur());
|
let sign = meta.query_advice(advices[3], Rotation::cur());
|
||||||
|
|
||||||
let anchor = meta.query_advice(advices[4], Rotation::cur());
|
let root = meta.query_advice(advices[4], Rotation::cur());
|
||||||
let pub_input_anchor = meta.query_advice(advices[5], Rotation::cur());
|
let anchor = meta.query_advice(advices[5], Rotation::cur());
|
||||||
|
|
||||||
|
let enable_spends = meta.query_advice(advices[6], Rotation::cur());
|
||||||
|
let enable_outputs = meta.query_advice(advices[7], Rotation::cur());
|
||||||
|
|
||||||
let one = Expression::Constant(pallas::Base::one());
|
let one = Expression::Constant(pallas::Base::one());
|
||||||
let not_enable_spends = one.clone() - meta.query_advice(advices[6], Rotation::cur());
|
|
||||||
let not_enable_outputs = one - meta.query_advice(advices[7], Rotation::cur());
|
|
||||||
|
|
||||||
Constraints::with_selector(
|
Constraints::with_selector(
|
||||||
q_orchard,
|
q_orchard,
|
||||||
|
@ -170,28 +172,23 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
v_old.clone() - v_new.clone() - magnitude * sign,
|
v_old.clone() - v_new.clone() - magnitude * sign,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"Either v_old = 0, or anchor equals public input",
|
"Either v_old = 0, or root = anchor",
|
||||||
v_old.clone() * (anchor - pub_input_anchor),
|
v_old.clone() * (root - anchor),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"v_old = 0 or enable_spends = 1",
|
||||||
|
v_old * (one.clone() - enable_spends),
|
||||||
),
|
),
|
||||||
("v_old = 0 or enable_spends = 1", v_old * not_enable_spends),
|
|
||||||
(
|
(
|
||||||
"v_new = 0 or enable_outputs = 1",
|
"v_new = 0 or enable_outputs = 1",
|
||||||
v_new * not_enable_outputs,
|
v_new * (one - enable_outputs),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Addition of two field elements poseidon_hash(nk, rho_old) + psi_old.
|
// Addition of two field elements.
|
||||||
let q_add = meta.selector();
|
let add_config = AddChip::configure(meta, advices[7], advices[8], advices[6]);
|
||||||
meta.create_gate("poseidon_hash(nk, rho_old) + psi_old", |meta| {
|
|
||||||
let q_add = meta.query_selector(q_add);
|
|
||||||
let sum = meta.query_advice(advices[6], Rotation::cur());
|
|
||||||
let hash_old = meta.query_advice(advices[7], Rotation::cur());
|
|
||||||
let psi_old = meta.query_advice(advices[8], Rotation::cur());
|
|
||||||
|
|
||||||
Constraints::with_selector(q_add, Some(hash_old + psi_old - sum))
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fixed columns for the Sinsemilla generator lookup table
|
// Fixed columns for the Sinsemilla generator lookup table
|
||||||
let table_idx = meta.lookup_table_column();
|
let table_idx = meta.lookup_table_column();
|
||||||
|
@ -290,24 +287,23 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
|
|
||||||
// Configuration to handle decomposition and canonicity checking
|
// Configuration to handle decomposition and canonicity checking
|
||||||
// for CommitIvk.
|
// for CommitIvk.
|
||||||
let commit_ivk_config =
|
let commit_ivk_config = CommitIvkChip::configure(meta, advices);
|
||||||
CommitIvkConfig::configure(meta, advices, sinsemilla_config_1.clone());
|
|
||||||
|
|
||||||
// Configuration to handle decomposition and canonicity checking
|
// Configuration to handle decomposition and canonicity checking
|
||||||
// for NoteCommit_old.
|
// for NoteCommit_old.
|
||||||
let old_note_commit_config =
|
let old_note_commit_config =
|
||||||
NoteCommitConfig::configure(meta, advices, sinsemilla_config_1.clone());
|
NoteCommitChip::configure(meta, advices, sinsemilla_config_1.clone());
|
||||||
|
|
||||||
// Configuration to handle decomposition and canonicity checking
|
// Configuration to handle decomposition and canonicity checking
|
||||||
// for NoteCommit_new.
|
// for NoteCommit_new.
|
||||||
let new_note_commit_config =
|
let new_note_commit_config =
|
||||||
NoteCommitConfig::configure(meta, advices, sinsemilla_config_2.clone());
|
NoteCommitChip::configure(meta, advices, sinsemilla_config_2.clone());
|
||||||
|
|
||||||
Config {
|
Config {
|
||||||
primary,
|
primary,
|
||||||
q_orchard,
|
q_orchard,
|
||||||
q_add,
|
|
||||||
advices,
|
advices,
|
||||||
|
add_config,
|
||||||
ecc_config,
|
ecc_config,
|
||||||
poseidon_config,
|
poseidon_config,
|
||||||
merkle_config_1,
|
merkle_config_1,
|
||||||
|
@ -320,6 +316,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
fn synthesize(
|
fn synthesize(
|
||||||
&self,
|
&self,
|
||||||
config: Self::Config,
|
config: Self::Config,
|
||||||
|
@ -332,16 +329,16 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
let ecc_chip = config.ecc_chip();
|
let ecc_chip = config.ecc_chip();
|
||||||
|
|
||||||
// Witness private inputs that are used across multiple checks.
|
// Witness private inputs that are used across multiple checks.
|
||||||
let (psi_old, rho_old, cm_old, g_d_old, ak, nk, v_old, v_new) = {
|
let (psi_old, rho_old, cm_old, g_d_old, ak_P, nk, v_old, v_new) = {
|
||||||
// Witness psi_old
|
// Witness psi_old
|
||||||
let psi_old = self.load_private(
|
let psi_old = assign_free_advice(
|
||||||
layouter.namespace(|| "witness psi_old"),
|
layouter.namespace(|| "witness psi_old"),
|
||||||
config.advices[0],
|
config.advices[0],
|
||||||
self.psi_old,
|
self.psi_old,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Witness rho_old
|
// Witness rho_old
|
||||||
let rho_old = self.load_private(
|
let rho_old = assign_free_advice(
|
||||||
layouter.namespace(|| "witness rho_old"),
|
layouter.namespace(|| "witness rho_old"),
|
||||||
config.advices[0],
|
config.advices[0],
|
||||||
self.rho_old.map(|rho| rho.0),
|
self.rho_old.map(|rho| rho.0),
|
||||||
|
@ -361,44 +358,43 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
self.g_d_old.as_ref().map(|gd| gd.to_affine()),
|
self.g_d_old.as_ref().map(|gd| gd.to_affine()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Witness ak.
|
// Witness ak_P.
|
||||||
let ak: Option<pallas::Point> = self.ak.as_ref().map(|ak| ak.into());
|
let ak_P: Option<pallas::Point> = self.ak.as_ref().map(|ak| ak.into());
|
||||||
let ak = NonIdentityPoint::new(
|
let ak_P = NonIdentityPoint::new(
|
||||||
ecc_chip.clone(),
|
ecc_chip.clone(),
|
||||||
layouter.namespace(|| "ak"),
|
layouter.namespace(|| "witness ak_P"),
|
||||||
ak.map(|ak| ak.to_affine()),
|
ak_P.map(|ak_P| ak_P.to_affine()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Witness nk.
|
// Witness nk.
|
||||||
let nk = self.load_private(
|
let nk = assign_free_advice(
|
||||||
layouter.namespace(|| "witness nk"),
|
layouter.namespace(|| "witness nk"),
|
||||||
config.advices[0],
|
config.advices[0],
|
||||||
self.nk.map(|nk| nk.inner()),
|
self.nk.map(|nk| nk.inner()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Witness v_old.
|
// Witness v_old.
|
||||||
let v_old = self.load_private(
|
let v_old = assign_free_advice(
|
||||||
layouter.namespace(|| "witness v_old"),
|
layouter.namespace(|| "witness v_old"),
|
||||||
config.advices[0],
|
config.advices[0],
|
||||||
self.v_old.map(|v_old| pallas::Base::from(v_old.inner())),
|
self.v_old,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Witness v_new.
|
// Witness v_new.
|
||||||
let v_new = self.load_private(
|
let v_new = assign_free_advice(
|
||||||
layouter.namespace(|| "witness v_new"),
|
layouter.namespace(|| "witness v_new"),
|
||||||
config.advices[0],
|
config.advices[0],
|
||||||
self.v_new.map(|v_new| pallas::Base::from(v_new.inner())),
|
self.v_new,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
(psi_old, rho_old, cm_old, g_d_old, ak, nk, v_old, v_new)
|
(psi_old, rho_old, cm_old, g_d_old, ak_P, nk, v_old, v_new)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Merkle path validity check.
|
// Merkle path validity check.
|
||||||
let anchor = {
|
let root = {
|
||||||
let path: Option<[pallas::Base; MERKLE_DEPTH_ORCHARD]> = self.path.map(|typed_path| {
|
let path = self
|
||||||
// TODO: Replace with array::map once MSRV is 1.55.0.
|
.path
|
||||||
gen_const_array(|i| typed_path[i].inner())
|
.map(|typed_path| typed_path.map(|node| node.inner()));
|
||||||
});
|
|
||||||
let merkle_inputs = MerklePath::construct(
|
let merkle_inputs = MerklePath::construct(
|
||||||
config.merkle_chip_1(),
|
config.merkle_chip_1(),
|
||||||
config.merkle_chip_2(),
|
config.merkle_chip_2(),
|
||||||
|
@ -407,39 +403,34 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
path,
|
path,
|
||||||
);
|
);
|
||||||
let leaf = cm_old.extract_p().inner().clone();
|
let leaf = cm_old.extract_p().inner().clone();
|
||||||
merkle_inputs.calculate_root(layouter.namespace(|| "MerkleCRH"), leaf)?
|
merkle_inputs.calculate_root(layouter.namespace(|| "Merkle path"), leaf)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Value commitment integrity.
|
// Value commitment integrity.
|
||||||
let v_net = {
|
let v_net = {
|
||||||
// v_net = v_old - v_new
|
// Witness the magnitude and sign of v_net = v_old - v_new
|
||||||
let v_net = {
|
let v_net = {
|
||||||
// v_old, v_new are guaranteed to be 64-bit values. Therefore, we can
|
let magnitude_sign = self.v_old.zip(self.v_new).map(|(v_old, v_new)| {
|
||||||
// move them into the base field.
|
let v_net = v_old - v_new;
|
||||||
let v_old = self.v_old.map(|v_old| pallas::Base::from(v_old.inner()));
|
let (magnitude, sign) = v_net.magnitude_sign();
|
||||||
let v_new = self.v_new.map(|v_new| pallas::Base::from(v_new.inner()));
|
|
||||||
|
|
||||||
let magnitude_sign = v_old.zip(v_new).map(|(v_old, v_new)| {
|
(
|
||||||
let is_negative = v_old < v_new;
|
// magnitude is guaranteed to be an unsigned 64-bit value.
|
||||||
let magnitude = if is_negative {
|
// Therefore, we can move it into the base field.
|
||||||
v_new - v_old
|
pallas::Base::from(magnitude),
|
||||||
} else {
|
match sign {
|
||||||
v_old - v_new
|
crate::value::Sign::Positive => pallas::Base::one(),
|
||||||
};
|
crate::value::Sign::Negative => -pallas::Base::one(),
|
||||||
let sign = if is_negative {
|
},
|
||||||
-pallas::Base::one()
|
)
|
||||||
} else {
|
|
||||||
pallas::Base::one()
|
|
||||||
};
|
|
||||||
(magnitude, sign)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let magnitude = self.load_private(
|
let magnitude = assign_free_advice(
|
||||||
layouter.namespace(|| "v_net magnitude"),
|
layouter.namespace(|| "v_net magnitude"),
|
||||||
config.advices[9],
|
config.advices[9],
|
||||||
magnitude_sign.map(|m_s| m_s.0),
|
magnitude_sign.map(|m_s| m_s.0),
|
||||||
)?;
|
)?;
|
||||||
let sign = self.load_private(
|
let sign = assign_free_advice(
|
||||||
layouter.namespace(|| "v_net sign"),
|
layouter.namespace(|| "v_net sign"),
|
||||||
config.advices[9],
|
config.advices[9],
|
||||||
magnitude_sign.map(|m_s| m_s.1),
|
magnitude_sign.map(|m_s| m_s.1),
|
||||||
|
@ -447,25 +438,12 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
(magnitude, sign)
|
(magnitude, sign)
|
||||||
};
|
};
|
||||||
|
|
||||||
// commitment = [v_net] ValueCommitV
|
let cv_net = gadget::value_commit_orchard(
|
||||||
let (commitment, _) = {
|
layouter.namespace(|| "cv_net = ValueCommit^Orchard_rcv(v_net)"),
|
||||||
let value_commit_v = ValueCommitV;
|
ecc_chip.clone(),
|
||||||
let value_commit_v = FixedPointShort::from_inner(ecc_chip.clone(), value_commit_v);
|
v_net.clone(),
|
||||||
value_commit_v.mul(layouter.namespace(|| "[v_net] ValueCommitV"), v_net.clone())?
|
self.rcv.as_ref().map(|rcv| rcv.inner()),
|
||||||
};
|
)?;
|
||||||
|
|
||||||
// blind = [rcv] ValueCommitR
|
|
||||||
let (blind, _rcv) = {
|
|
||||||
let rcv = self.rcv.as_ref().map(|rcv| rcv.inner());
|
|
||||||
let value_commit_r = OrchardFixedBasesFull::ValueCommitR;
|
|
||||||
let value_commit_r = FixedPoint::from_inner(ecc_chip.clone(), value_commit_r);
|
|
||||||
|
|
||||||
// [rcv] ValueCommitR
|
|
||||||
value_commit_r.mul(layouter.namespace(|| "[rcv] ValueCommitR"), rcv)?
|
|
||||||
};
|
|
||||||
|
|
||||||
// [v_net] ValueCommitV + [rcv] ValueCommitR
|
|
||||||
let cv_net = commitment.add(layouter.namespace(|| "cv_net"), &blind)?;
|
|
||||||
|
|
||||||
// Constrain cv_net to equal public input
|
// Constrain cv_net to equal public input
|
||||||
layouter.constrain_instance(cv_net.inner().x().cell(), config.primary, CV_NET_X)?;
|
layouter.constrain_instance(cv_net.inner().x().cell(), config.primary, CV_NET_X)?;
|
||||||
|
@ -476,61 +454,17 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
|
|
||||||
// Nullifier integrity
|
// Nullifier integrity
|
||||||
let nf_old = {
|
let nf_old = {
|
||||||
// hash_old = poseidon_hash(nk, rho_old)
|
let nf_old = gadget::derive_nullifier(
|
||||||
let hash_old = {
|
layouter.namespace(|| "nf_old = DeriveNullifier_nk(rho_old, psi_old, cm_old)"),
|
||||||
let poseidon_message = [nk.clone(), rho_old.clone()];
|
config.poseidon_chip(),
|
||||||
let poseidon_hasher =
|
config.add_chip(),
|
||||||
PoseidonHash::<_, _, poseidon::P128Pow5T3, ConstantLength<2>, 3, 2>::init(
|
ecc_chip.clone(),
|
||||||
config.poseidon_chip(),
|
rho_old.clone(),
|
||||||
layouter.namespace(|| "Poseidon init"),
|
&psi_old,
|
||||||
)?;
|
&cm_old,
|
||||||
poseidon_hasher.hash(
|
nk.clone(),
|
||||||
layouter.namespace(|| "Poseidon hash (nk, rho_old)"),
|
|
||||||
poseidon_message,
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add hash output to psi.
|
|
||||||
// `scalar` = poseidon_hash(nk, rho_old) + psi_old.
|
|
||||||
//
|
|
||||||
let scalar = layouter.assign_region(
|
|
||||||
|| " `scalar` = poseidon_hash(nk, rho_old) + psi_old",
|
|
||||||
|mut region| {
|
|
||||||
config.q_add.enable(&mut region, 0)?;
|
|
||||||
|
|
||||||
hash_old.copy_advice(|| "copy hash_old", &mut region, config.advices[7], 0)?;
|
|
||||||
psi_old.copy_advice(|| "copy psi_old", &mut region, config.advices[8], 0)?;
|
|
||||||
|
|
||||||
let scalar_val = hash_old
|
|
||||||
.value()
|
|
||||||
.zip(psi_old.value())
|
|
||||||
.map(|(hash_old, psi_old)| hash_old + psi_old);
|
|
||||||
region.assign_advice(
|
|
||||||
|| "poseidon_hash(nk, rho_old) + psi_old",
|
|
||||||
config.advices[6],
|
|
||||||
0,
|
|
||||||
|| scalar_val.ok_or(plonk::Error::Synthesis),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Multiply scalar by NullifierK
|
|
||||||
// `product` = [poseidon_hash(nk, rho_old) + psi_old] NullifierK.
|
|
||||||
//
|
|
||||||
let product = {
|
|
||||||
let nullifier_k = FixedPointBaseField::from_inner(ecc_chip.clone(), NullifierK);
|
|
||||||
nullifier_k.mul(
|
|
||||||
layouter.namespace(|| "[poseidon_output + psi_old] NullifierK"),
|
|
||||||
scalar,
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add cm_old to multiplied fixed base to get nf_old
|
|
||||||
// cm_old + [poseidon_output + psi_old] NullifierK
|
|
||||||
let nf_old = cm_old
|
|
||||||
.add(layouter.namespace(|| "nf_old"), &product)?
|
|
||||||
.extract_p();
|
|
||||||
|
|
||||||
// Constrain nf_old to equal public input
|
// Constrain nf_old to equal public input
|
||||||
layouter.constrain_instance(nf_old.inner().cell(), config.primary, NF_OLD)?;
|
layouter.constrain_instance(nf_old.inner().cell(), config.primary, NF_OLD)?;
|
||||||
|
|
||||||
|
@ -546,8 +480,8 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
spend_auth_g.mul(layouter.namespace(|| "[alpha] SpendAuthG"), self.alpha)?
|
spend_auth_g.mul(layouter.namespace(|| "[alpha] SpendAuthG"), self.alpha)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// [alpha] SpendAuthG + ak
|
// [alpha] SpendAuthG + ak_P
|
||||||
let rk = alpha_commitment.add(layouter.namespace(|| "rk"), &ak)?;
|
let rk = alpha_commitment.add(layouter.namespace(|| "rk"), &ak_P)?;
|
||||||
|
|
||||||
// Constrain rk to equal public input
|
// Constrain rk to equal public input
|
||||||
layouter.constrain_instance(rk.inner().x().cell(), config.primary, RK_X)?;
|
layouter.constrain_instance(rk.inner().x().cell(), config.primary, RK_X)?;
|
||||||
|
@ -556,16 +490,16 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
|
|
||||||
// Diversified address integrity.
|
// Diversified address integrity.
|
||||||
let pk_d_old = {
|
let pk_d_old = {
|
||||||
let commit_ivk_config = config.commit_ivk_config.clone();
|
|
||||||
|
|
||||||
let ivk = {
|
let ivk = {
|
||||||
|
let ak = ak_P.extract_p().inner().clone();
|
||||||
let rivk = self.rivk.map(|rivk| rivk.inner());
|
let rivk = self.rivk.map(|rivk| rivk.inner());
|
||||||
|
|
||||||
commit_ivk_config.assign_region(
|
gadget::commit_ivk(
|
||||||
config.sinsemilla_chip_1(),
|
config.sinsemilla_chip_1(),
|
||||||
ecc_chip.clone(),
|
ecc_chip.clone(),
|
||||||
|
config.commit_ivk_chip(),
|
||||||
layouter.namespace(|| "CommitIvk"),
|
layouter.namespace(|| "CommitIvk"),
|
||||||
ak.extract_p().inner().clone(),
|
ak,
|
||||||
nk,
|
nk,
|
||||||
rivk,
|
rivk,
|
||||||
)?
|
)?
|
||||||
|
@ -577,6 +511,12 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
g_d_old.mul(layouter.namespace(|| "[ivk] g_d_old"), ivk.inner())?;
|
g_d_old.mul(layouter.namespace(|| "[ivk] g_d_old"), ivk.inner())?;
|
||||||
|
|
||||||
// Constrain derived pk_d_old to equal witnessed pk_d_old
|
// Constrain derived pk_d_old to equal witnessed pk_d_old
|
||||||
|
//
|
||||||
|
// This equality constraint is technically superfluous, because the assigned
|
||||||
|
// value of `derived_pk_d_old` is an equivalent witness. But it's nice to see
|
||||||
|
// an explicit connection between circuit-synthesized values, and explicit
|
||||||
|
// prover witnesses. We could get the best of both worlds with a write-on-copy
|
||||||
|
// abstraction (https://github.com/zcash/halo2/issues/334).
|
||||||
let pk_d_old = NonIdentityPoint::new(
|
let pk_d_old = NonIdentityPoint::new(
|
||||||
ecc_chip.clone(),
|
ecc_chip.clone(),
|
||||||
layouter.namespace(|| "witness pk_d_old"),
|
layouter.namespace(|| "witness pk_d_old"),
|
||||||
|
@ -590,17 +530,16 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
|
|
||||||
// Old note commitment integrity.
|
// Old note commitment integrity.
|
||||||
{
|
{
|
||||||
let old_note_commit_config = config.old_note_commit_config.clone();
|
|
||||||
|
|
||||||
let rcm_old = self.rcm_old.as_ref().map(|rcm_old| rcm_old.inner());
|
let rcm_old = self.rcm_old.as_ref().map(|rcm_old| rcm_old.inner());
|
||||||
|
|
||||||
// g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)
|
// g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)
|
||||||
let derived_cm_old = old_note_commit_config.assign_region(
|
let derived_cm_old = gadget::note_commit(
|
||||||
layouter.namespace(|| {
|
layouter.namespace(|| {
|
||||||
"g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)"
|
"g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)"
|
||||||
}),
|
}),
|
||||||
config.sinsemilla_chip_1(),
|
config.sinsemilla_chip_1(),
|
||||||
config.ecc_chip(),
|
config.ecc_chip(),
|
||||||
|
config.note_commit_chip_old(),
|
||||||
g_d_old.inner(),
|
g_d_old.inner(),
|
||||||
pk_d_old.inner(),
|
pk_d_old.inner(),
|
||||||
v_old.clone(),
|
v_old.clone(),
|
||||||
|
@ -615,13 +554,9 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
|
|
||||||
// New note commitment integrity.
|
// New note commitment integrity.
|
||||||
{
|
{
|
||||||
let new_note_commit_config = config.new_note_commit_config.clone();
|
// Witness g_d_new
|
||||||
|
|
||||||
// Witness g_d_new_star
|
|
||||||
let g_d_new = {
|
let g_d_new = {
|
||||||
let g_d_new = self
|
let g_d_new = self.g_d_new.map(|g_d_new| g_d_new.to_affine());
|
||||||
.g_d_new_star
|
|
||||||
.map(|bytes| pallas::Affine::from_bytes(&bytes).unwrap());
|
|
||||||
NonIdentityPoint::new(
|
NonIdentityPoint::new(
|
||||||
ecc_chip.clone(),
|
ecc_chip.clone(),
|
||||||
layouter.namespace(|| "witness g_d_new_star"),
|
layouter.namespace(|| "witness g_d_new_star"),
|
||||||
|
@ -629,11 +564,9 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Witness pk_d_new_star
|
// Witness pk_d_new
|
||||||
let pk_d_new = {
|
let pk_d_new = {
|
||||||
let pk_d_new = self
|
let pk_d_new = self.pk_d_new.map(|pk_d_new| pk_d_new.inner().to_affine());
|
||||||
.pk_d_new_star
|
|
||||||
.map(|bytes| pallas::Affine::from_bytes(&bytes).unwrap());
|
|
||||||
NonIdentityPoint::new(
|
NonIdentityPoint::new(
|
||||||
ecc_chip,
|
ecc_chip,
|
||||||
layouter.namespace(|| "witness pk_d_new"),
|
layouter.namespace(|| "witness pk_d_new"),
|
||||||
|
@ -641,8 +574,11 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ρ^new = nf^old
|
||||||
|
let rho_new = nf_old.inner().clone();
|
||||||
|
|
||||||
// Witness psi_new
|
// Witness psi_new
|
||||||
let psi_new = self.load_private(
|
let psi_new = assign_free_advice(
|
||||||
layouter.namespace(|| "witness psi_new"),
|
layouter.namespace(|| "witness psi_new"),
|
||||||
config.advices[0],
|
config.advices[0],
|
||||||
self.psi_new,
|
self.psi_new,
|
||||||
|
@ -651,16 +587,17 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
let rcm_new = self.rcm_new.as_ref().map(|rcm_new| rcm_new.inner());
|
let rcm_new = self.rcm_new.as_ref().map(|rcm_new| rcm_new.inner());
|
||||||
|
|
||||||
// g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)
|
// g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)
|
||||||
let cm_new = new_note_commit_config.assign_region(
|
let cm_new = gadget::note_commit(
|
||||||
layouter.namespace(|| {
|
layouter.namespace(|| {
|
||||||
"g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)"
|
"g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)"
|
||||||
}),
|
}),
|
||||||
config.sinsemilla_chip_2(),
|
config.sinsemilla_chip_2(),
|
||||||
config.ecc_chip(),
|
config.ecc_chip(),
|
||||||
|
config.note_commit_chip_new(),
|
||||||
g_d_new.inner(),
|
g_d_new.inner(),
|
||||||
pk_d_new.inner(),
|
pk_d_new.inner(),
|
||||||
v_new.clone(),
|
v_new.clone(),
|
||||||
nf_old.inner().clone(),
|
rho_new,
|
||||||
psi_new,
|
psi_new,
|
||||||
rcm_new,
|
rcm_new,
|
||||||
)?;
|
)?;
|
||||||
|
@ -671,10 +608,9 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
layouter.constrain_instance(cmx.inner().cell(), config.primary, CMX)?;
|
layouter.constrain_instance(cmx.inner().cell(), config.primary, CMX)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constrain v_old - v_new = magnitude * sign
|
// Constrain the remaining Orchard circuit checks.
|
||||||
// Either v_old = 0, or anchor equals public input
|
|
||||||
layouter.assign_region(
|
layouter.assign_region(
|
||||||
|| "v_old - v_new = magnitude * sign",
|
|| "Orchard circuit checks",
|
||||||
|mut region| {
|
|mut region| {
|
||||||
v_old.copy_advice(|| "v_old", &mut region, config.advices[0], 0)?;
|
v_old.copy_advice(|| "v_old", &mut region, config.advices[0], 0)?;
|
||||||
v_new.copy_advice(|| "v_new", &mut region, config.advices[1], 0)?;
|
v_new.copy_advice(|| "v_new", &mut region, config.advices[1], 0)?;
|
||||||
|
@ -682,7 +618,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
||||||
magnitude.copy_advice(|| "v_net magnitude", &mut region, config.advices[2], 0)?;
|
magnitude.copy_advice(|| "v_net magnitude", &mut region, config.advices[2], 0)?;
|
||||||
sign.copy_advice(|| "v_net sign", &mut region, config.advices[3], 0)?;
|
sign.copy_advice(|| "v_net sign", &mut region, config.advices[3], 0)?;
|
||||||
|
|
||||||
anchor.copy_advice(|| "anchor", &mut region, config.advices[4], 0)?;
|
root.copy_advice(|| "calculated root", &mut region, config.advices[4], 0)?;
|
||||||
region.assign_advice_from_instance(
|
region.assign_advice_from_instance(
|
||||||
|| "pub input anchor",
|
|| "pub input anchor",
|
||||||
config.primary,
|
config.primary,
|
||||||
|
@ -905,7 +841,6 @@ mod tests {
|
||||||
use core::iter;
|
use core::iter;
|
||||||
|
|
||||||
use ff::Field;
|
use ff::Field;
|
||||||
use group::GroupEncoding;
|
|
||||||
use halo2_proofs::dev::MockProver;
|
use halo2_proofs::dev::MockProver;
|
||||||
use pasta_curves::pallas;
|
use pasta_curves::pallas;
|
||||||
use rand::{rngs::OsRng, RngCore};
|
use rand::{rngs::OsRng, RngCore};
|
||||||
|
@ -934,7 +869,7 @@ mod tests {
|
||||||
|
|
||||||
let value = spent_note.value() - output_note.value();
|
let value = spent_note.value() - output_note.value();
|
||||||
let rcv = ValueCommitTrapdoor::random(&mut rng);
|
let rcv = ValueCommitTrapdoor::random(&mut rng);
|
||||||
let cv_net = ValueCommitment::derive(value.unwrap(), rcv.clone());
|
let cv_net = ValueCommitment::derive(value, rcv.clone());
|
||||||
|
|
||||||
let path = MerklePath::dummy(&mut rng);
|
let path = MerklePath::dummy(&mut rng);
|
||||||
let anchor = path.root(spent_note.commitment().into());
|
let anchor = path.root(spent_note.commitment().into());
|
||||||
|
@ -954,8 +889,8 @@ mod tests {
|
||||||
ak: Some(ak),
|
ak: Some(ak),
|
||||||
nk: Some(nk),
|
nk: Some(nk),
|
||||||
rivk: Some(rivk),
|
rivk: Some(rivk),
|
||||||
g_d_new_star: Some((*output_note.recipient().g_d()).to_bytes()),
|
g_d_new: Some(output_note.recipient().g_d()),
|
||||||
pk_d_new_star: Some(output_note.recipient().pk_d().to_bytes()),
|
pk_d_new: Some(*output_note.recipient().pk_d()),
|
||||||
v_new: Some(output_note.value()),
|
v_new: Some(output_note.value()),
|
||||||
psi_new: Some(output_note.rseed().psi(&output_note.rho())),
|
psi_new: Some(output_note.rseed().psi(&output_note.rho())),
|
||||||
rcm_new: Some(output_note.rseed().rcm(&output_note.rho())),
|
rcm_new: Some(output_note.rseed().rcm(&output_note.rho())),
|
||||||
|
@ -1140,8 +1075,8 @@ mod tests {
|
||||||
ak: None,
|
ak: None,
|
||||||
nk: None,
|
nk: None,
|
||||||
rivk: None,
|
rivk: None,
|
||||||
g_d_new_star: None,
|
g_d_new: None,
|
||||||
pk_d_new_star: None,
|
pk_d_new: None,
|
||||||
v_new: None,
|
v_new: None,
|
||||||
psi_new: None,
|
psi_new: None,
|
||||||
rcm_new: None,
|
rcm_new: None,
|
||||||
|
|
|
@ -10,37 +10,31 @@ use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||||
use crate::constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains, T_P};
|
use crate::constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains, T_P};
|
||||||
use halo2_gadgets::{
|
use halo2_gadgets::{
|
||||||
ecc::{chip::EccChip, X},
|
ecc::{chip::EccChip, X},
|
||||||
sinsemilla::{
|
sinsemilla::{chip::SinsemillaChip, CommitDomain, Message, MessagePiece},
|
||||||
chip::{SinsemillaChip, SinsemillaConfig},
|
utilities::{bool_check, RangeConstrained},
|
||||||
CommitDomain, Message, MessagePiece,
|
|
||||||
},
|
|
||||||
utilities::{bitrange_subset, bool_check},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CommitIvkConfig {
|
pub struct CommitIvkConfig {
|
||||||
q_commit_ivk: Selector,
|
q_commit_ivk: Selector,
|
||||||
advices: [Column<Advice>; 10],
|
advices: [Column<Advice>; 10],
|
||||||
sinsemilla_config:
|
|
||||||
SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommitIvkConfig {
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct CommitIvkChip {
|
||||||
|
config: CommitIvkConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommitIvkChip {
|
||||||
pub(in crate::circuit) fn configure(
|
pub(in crate::circuit) fn configure(
|
||||||
meta: &mut ConstraintSystem<pallas::Base>,
|
meta: &mut ConstraintSystem<pallas::Base>,
|
||||||
advices: [Column<Advice>; 10],
|
advices: [Column<Advice>; 10],
|
||||||
sinsemilla_config: SinsemillaConfig<
|
) -> CommitIvkConfig {
|
||||||
OrchardHashDomains,
|
|
||||||
OrchardCommitDomains,
|
|
||||||
OrchardFixedBases,
|
|
||||||
>,
|
|
||||||
) -> Self {
|
|
||||||
let q_commit_ivk = meta.selector();
|
let q_commit_ivk = meta.selector();
|
||||||
|
|
||||||
let config = Self {
|
let config = CommitIvkConfig {
|
||||||
q_commit_ivk,
|
q_commit_ivk,
|
||||||
advices,
|
advices,
|
||||||
sinsemilla_config,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// <https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit>
|
// <https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit>
|
||||||
|
@ -111,10 +105,8 @@ impl CommitIvkConfig {
|
||||||
// Check that d_whole is consistent with the witnessed subpieces.
|
// Check that d_whole is consistent with the witnessed subpieces.
|
||||||
let d_decomposition_check = d_whole - (d_0.clone() + d_1.clone() * two_pow_9);
|
let d_decomposition_check = d_whole - (d_0.clone() + d_1.clone() * two_pow_9);
|
||||||
|
|
||||||
// Check `b_1` is a single-bit value
|
// Check `b_1` and `d_1` are each a single-bit value.
|
||||||
let b1_bool_check = bool_check(b_1.clone());
|
let b1_bool_check = bool_check(b_1.clone());
|
||||||
|
|
||||||
// Check `d_1` is a single-bit value
|
|
||||||
let d1_bool_check = bool_check(d_1.clone());
|
let d1_bool_check = bool_check(d_1.clone());
|
||||||
|
|
||||||
// Check that ak = a (250 bits) || b_0 (4 bits) || b_1 (1 bit)
|
// Check that ak = a (250 bits) || b_0 (4 bits) || b_1 (1 bit)
|
||||||
|
@ -224,22 +216,37 @@ impl CommitIvkConfig {
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(in crate::circuit) fn construct(config: CommitIvkConfig) -> Self {
|
||||||
|
Self { config }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::circuit) mod gadgets {
|
||||||
|
use halo2_gadgets::utilities::{lookup_range_check::LookupRangeCheckConfig, RangeConstrained};
|
||||||
|
use halo2_proofs::circuit::Chip;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// `Commit^ivk` from [Section 5.4.8.4 Sinsemilla commitments].
|
||||||
|
///
|
||||||
|
/// [Section 5.4.8.4 Sinsemilla commitments]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillacommit
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub(in crate::circuit) fn assign_region(
|
pub(in crate::circuit) fn commit_ivk(
|
||||||
&self,
|
|
||||||
sinsemilla_chip: SinsemillaChip<
|
sinsemilla_chip: SinsemillaChip<
|
||||||
OrchardHashDomains,
|
OrchardHashDomains,
|
||||||
OrchardCommitDomains,
|
OrchardCommitDomains,
|
||||||
OrchardFixedBases,
|
OrchardFixedBases,
|
||||||
>,
|
>,
|
||||||
ecc_chip: EccChip<OrchardFixedBases>,
|
ecc_chip: EccChip<OrchardFixedBases>,
|
||||||
|
commit_ivk_chip: CommitIvkChip,
|
||||||
mut layouter: impl Layouter<pallas::Base>,
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
ak: AssignedCell<pallas::Base, pallas::Base>,
|
ak: AssignedCell<pallas::Base, pallas::Base>,
|
||||||
nk: AssignedCell<pallas::Base, pallas::Base>,
|
nk: AssignedCell<pallas::Base, pallas::Base>,
|
||||||
rivk: Option<pallas::Scalar>,
|
rivk: Option<pallas::Scalar>,
|
||||||
) -> Result<X<pallas::Affine, EccChip<OrchardFixedBases>>, Error> {
|
) -> Result<X<pallas::Affine, EccChip<OrchardFixedBases>>, Error> {
|
||||||
// <https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit>
|
let lookup_config = sinsemilla_chip.config().lookup_config();
|
||||||
|
|
||||||
// We need to hash `ak || nk` where each of `ak`, `nk` is a field element (255 bits).
|
// We need to hash `ak || nk` where each of `ak`, `nk` is a field element (255 bits).
|
||||||
//
|
//
|
||||||
// a = bits 0..=249 of `ak`
|
// a = bits 0..=249 of `ak`
|
||||||
|
@ -247,93 +254,79 @@ impl CommitIvkConfig {
|
||||||
// = (bits 250..=253 of `ak`) || (bit 254 of `ak`) || (bits 0..=4 of `nk`)
|
// = (bits 250..=253 of `ak`) || (bit 254 of `ak`) || (bits 0..=4 of `nk`)
|
||||||
// c = bits 5..=244 of `nk`
|
// c = bits 5..=244 of `nk`
|
||||||
// d = d_0||d_1` = (bits 245..=253 of `nk`) || (bit 254 of `nk`)
|
// d = d_0||d_1` = (bits 245..=253 of `nk`) || (bit 254 of `nk`)
|
||||||
|
//
|
||||||
|
// We start by witnessing all of the individual pieces, and range-constraining
|
||||||
|
// the short pieces b_0, b_2, and d_0.
|
||||||
|
|
||||||
// `a` = bits 0..=249 of `ak`
|
// `a` = bits 0..=249 of `ak`
|
||||||
let a = {
|
let a = MessagePiece::from_subpieces(
|
||||||
let a = ak.value().map(|value| bitrange_subset(value, 0..250));
|
sinsemilla_chip.clone(),
|
||||||
MessagePiece::from_field_elem(
|
layouter.namespace(|| "a"),
|
||||||
sinsemilla_chip.clone(),
|
[RangeConstrained::bitrange_of(ak.value(), 0..250)],
|
||||||
layouter.namespace(|| "a"),
|
)?;
|
||||||
a,
|
|
||||||
25,
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
// `b = b_0||b_1||b_2`
|
// `b = b_0||b_1||b_2`
|
||||||
// = (bits 250..=253 of `ak`) || (bit 254 of `ak`) || (bits 0..=4 of `nk`)
|
// = (bits 250..=253 of `ak`) || (bit 254 of `ak`) || (bits 0..=4 of `nk`)
|
||||||
let (b_0, b_1, b_2, b) = {
|
let (b_0, b_1, b_2, b) = {
|
||||||
let b_0 = ak.value().map(|value| bitrange_subset(value, 250..254));
|
|
||||||
let b_1 = ak.value().map(|value| bitrange_subset(value, 254..255));
|
|
||||||
let b_2 = nk.value().map(|value| bitrange_subset(value, 0..5));
|
|
||||||
|
|
||||||
let b = b_0.zip(b_1).zip(b_2).map(|((b_0, b_1), b_2)| {
|
|
||||||
let b1_shifted = b_1 * pallas::Base::from(1 << 4);
|
|
||||||
let b2_shifted = b_2 * pallas::Base::from(1 << 5);
|
|
||||||
b_0 + b1_shifted + b2_shifted
|
|
||||||
});
|
|
||||||
|
|
||||||
// Constrain b_0 to be 4 bits.
|
// Constrain b_0 to be 4 bits.
|
||||||
let b_0 = self.sinsemilla_config.lookup_config().witness_short_check(
|
let b_0 = RangeConstrained::witness_short(
|
||||||
layouter.namespace(|| "b_0 is 4 bits"),
|
&lookup_config,
|
||||||
b_0,
|
layouter.namespace(|| "b_0"),
|
||||||
4,
|
ak.value(),
|
||||||
)?;
|
250..254,
|
||||||
// Constrain b_2 to be 5 bits.
|
|
||||||
let b_2 = self.sinsemilla_config.lookup_config().witness_short_check(
|
|
||||||
layouter.namespace(|| "b_2 is 5 bits"),
|
|
||||||
b_2,
|
|
||||||
5,
|
|
||||||
)?;
|
)?;
|
||||||
// b_1 will be boolean-constrained in the custom gate.
|
// b_1 will be boolean-constrained in the custom gate.
|
||||||
|
let b_1 = RangeConstrained::bitrange_of(ak.value(), 254..255);
|
||||||
|
// Constrain b_2 to be 5 bits.
|
||||||
|
let b_2 = RangeConstrained::witness_short(
|
||||||
|
&lookup_config,
|
||||||
|
layouter.namespace(|| "b_2"),
|
||||||
|
nk.value(),
|
||||||
|
0..5,
|
||||||
|
)?;
|
||||||
|
|
||||||
let b = MessagePiece::from_field_elem(
|
let b = MessagePiece::from_subpieces(
|
||||||
sinsemilla_chip.clone(),
|
sinsemilla_chip.clone(),
|
||||||
layouter.namespace(|| "b = b_0 || b_1 || b_2"),
|
layouter.namespace(|| "b = b_0 || b_1 || b_2"),
|
||||||
b,
|
[b_0.value(), b_1, b_2.value()],
|
||||||
1,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
(b_0, b_1, b_2, b)
|
(b_0, b_1, b_2, b)
|
||||||
};
|
};
|
||||||
|
|
||||||
// c = bits 5..=244 of `nk`
|
// c = bits 5..=244 of `nk`
|
||||||
let c = {
|
let c = MessagePiece::from_subpieces(
|
||||||
let c = nk.value().map(|value| bitrange_subset(value, 5..245));
|
sinsemilla_chip.clone(),
|
||||||
MessagePiece::from_field_elem(
|
layouter.namespace(|| "c"),
|
||||||
sinsemilla_chip.clone(),
|
[RangeConstrained::bitrange_of(nk.value(), 5..245)],
|
||||||
layouter.namespace(|| "c"),
|
)?;
|
||||||
c,
|
|
||||||
24,
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
// `d = d_0||d_1` = (bits 245..=253 of `nk`) || (bit 254 of `nk`)
|
// `d = d_0||d_1` = (bits 245..=253 of `nk`) || (bit 254 of `nk`)
|
||||||
let (d_0, d_1, d) = {
|
let (d_0, d_1, d) = {
|
||||||
let d_0 = nk.value().map(|value| bitrange_subset(value, 245..254));
|
|
||||||
let d_1 = nk.value().map(|value| bitrange_subset(value, 254..255));
|
|
||||||
|
|
||||||
let d = d_0
|
|
||||||
.zip(d_1)
|
|
||||||
.map(|(d_0, d_1)| d_0 + d_1 * pallas::Base::from(1 << 9));
|
|
||||||
|
|
||||||
// Constrain d_0 to be 9 bits.
|
// Constrain d_0 to be 9 bits.
|
||||||
let d_0 = self.sinsemilla_config.lookup_config().witness_short_check(
|
let d_0 = RangeConstrained::witness_short(
|
||||||
layouter.namespace(|| "d_0 is 9 bits"),
|
&lookup_config,
|
||||||
d_0,
|
layouter.namespace(|| "d_0"),
|
||||||
9,
|
nk.value(),
|
||||||
|
245..254,
|
||||||
)?;
|
)?;
|
||||||
// d_1 will be boolean-constrained in the custom gate.
|
// d_1 will be boolean-constrained in the custom gate.
|
||||||
|
let d_1 = RangeConstrained::bitrange_of(nk.value(), 254..255);
|
||||||
|
|
||||||
let d = MessagePiece::from_field_elem(
|
let d = MessagePiece::from_subpieces(
|
||||||
sinsemilla_chip.clone(),
|
sinsemilla_chip.clone(),
|
||||||
layouter.namespace(|| "d = d_0 || d_1"),
|
layouter.namespace(|| "d = d_0 || d_1"),
|
||||||
d,
|
[d_0.value(), d_1],
|
||||||
1,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
(d_0, d_1, d)
|
(d_0, d_1, d)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ivk = Commit^ivk_rivk(I2LEBSP_255(ak) || I2LEBSP_255(nk))
|
||||||
|
//
|
||||||
|
// `ivk = ⊥` is handled internally to `CommitDomain::short_commit`: incomplete
|
||||||
|
// addition constraints allows ⊥ to occur, and then during synthesis it detects
|
||||||
|
// these edge cases and raises an error (aborting proof creation).
|
||||||
let (ivk, zs) = {
|
let (ivk, zs) = {
|
||||||
let message = Message::from_pieces(
|
let message = Message::from_pieces(
|
||||||
sinsemilla_chip.clone(),
|
sinsemilla_chip.clone(),
|
||||||
|
@ -344,17 +337,22 @@ impl CommitIvkConfig {
|
||||||
domain.short_commit(layouter.namespace(|| "Hash ak||nk"), message, rivk)?
|
domain.short_commit(layouter.namespace(|| "Hash ak||nk"), message, rivk)?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// `CommitDomain::short_commit` returns the running sum for each `MessagePiece`.
|
||||||
|
// Grab the outputs for pieces `a` and `c` that we will need for canonicity checks
|
||||||
|
// on `ak` and `nk`.
|
||||||
let z13_a = zs[0][13].clone();
|
let z13_a = zs[0][13].clone();
|
||||||
let z13_c = zs[2][13].clone();
|
let z13_c = zs[2][13].clone();
|
||||||
|
|
||||||
let (a_prime, z13_a_prime) = self.ak_canonicity(
|
let (a_prime, z13_a_prime) = ak_canonicity(
|
||||||
|
&lookup_config,
|
||||||
layouter.namespace(|| "ak canonicity"),
|
layouter.namespace(|| "ak canonicity"),
|
||||||
a.inner().cell_value(),
|
a.inner().cell_value(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let (b2_c_prime, z14_b2_c_prime) = self.nk_canonicity(
|
let (b2_c_prime, z14_b2_c_prime) = nk_canonicity(
|
||||||
|
&lookup_config,
|
||||||
layouter.namespace(|| "nk canonicity"),
|
layouter.namespace(|| "nk canonicity"),
|
||||||
b_2.clone(),
|
&b_2,
|
||||||
c.inner().cell_value(),
|
c.inner().cell_value(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -378,7 +376,7 @@ impl CommitIvkConfig {
|
||||||
z14_b2_c_prime,
|
z14_b2_c_prime,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.assign_gate(
|
commit_ivk_chip.config.assign_gate(
|
||||||
layouter.namespace(|| "Assign cells used in canonicity gate"),
|
layouter.namespace(|| "Assign cells used in canonicity gate"),
|
||||||
gate_cells,
|
gate_cells,
|
||||||
)?;
|
)?;
|
||||||
|
@ -386,10 +384,10 @@ impl CommitIvkConfig {
|
||||||
Ok(ivk)
|
Ok(ivk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Witnesses and decomposes the `a'` value we need to check the canonicity of `ak`.
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
// Check canonicity of `ak` encoding
|
|
||||||
fn ak_canonicity(
|
fn ak_canonicity(
|
||||||
&self,
|
lookup_config: &LookupRangeCheckConfig<pallas::Base, 10>,
|
||||||
mut layouter: impl Layouter<pallas::Base>,
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
a: AssignedCell<pallas::Base, pallas::Base>,
|
a: AssignedCell<pallas::Base, pallas::Base>,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
|
@ -413,24 +411,24 @@ impl CommitIvkConfig {
|
||||||
let t_p = pallas::Base::from_u128(T_P);
|
let t_p = pallas::Base::from_u128(T_P);
|
||||||
a + two_pow_130 - t_p
|
a + two_pow_130 - t_p
|
||||||
});
|
});
|
||||||
let zs = self.sinsemilla_config.lookup_config().witness_check(
|
let zs = lookup_config.witness_check(
|
||||||
layouter.namespace(|| "Decompose low 130 bits of (a + 2^130 - t_P)"),
|
layouter.namespace(|| "Decompose low 130 bits of (a + 2^130 - t_P)"),
|
||||||
a_prime,
|
a_prime,
|
||||||
13,
|
13,
|
||||||
false,
|
false,
|
||||||
)?;
|
)?;
|
||||||
let a_prime = zs[0].clone();
|
let a_prime = zs[0].clone();
|
||||||
assert_eq!(zs.len(), 14); // [z_0, z_1, ..., z13_a]
|
assert_eq!(zs.len(), 14); // [z_0, z_1, ..., z13]
|
||||||
|
|
||||||
Ok((a_prime, zs[13].clone()))
|
Ok((a_prime, zs[13].clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Witnesses and decomposes the `b2c'` value we need to check the canonicity of `nk`.
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
// Check canonicity of `nk` encoding
|
|
||||||
fn nk_canonicity(
|
fn nk_canonicity(
|
||||||
&self,
|
lookup_config: &LookupRangeCheckConfig<pallas::Base, 10>,
|
||||||
mut layouter: impl Layouter<pallas::Base>,
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
b_2: AssignedCell<pallas::Base, pallas::Base>,
|
b_2: &RangeConstrained<pallas::Base, AssignedCell<pallas::Base, pallas::Base>>,
|
||||||
c: AssignedCell<pallas::Base, pallas::Base>,
|
c: AssignedCell<pallas::Base, pallas::Base>,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
(
|
(
|
||||||
|
@ -450,13 +448,13 @@ impl CommitIvkConfig {
|
||||||
|
|
||||||
// Decompose the low 140 bits of b2_c_prime = b_2 + c * 2^5 + 2^140 - t_P, and output
|
// Decompose the low 140 bits of b2_c_prime = b_2 + c * 2^5 + 2^140 - t_P, and output
|
||||||
// the running sum at the end of it. If b2_c_prime < 2^140, the running sum will be 0.
|
// the running sum at the end of it. If b2_c_prime < 2^140, the running sum will be 0.
|
||||||
let b2_c_prime = b_2.value().zip(c.value()).map(|(b_2, c)| {
|
let b2_c_prime = b_2.inner().value().zip(c.value()).map(|(b_2, c)| {
|
||||||
let two_pow_5 = pallas::Base::from(1 << 5);
|
let two_pow_5 = pallas::Base::from(1 << 5);
|
||||||
let two_pow_140 = pallas::Base::from_u128(1u128 << 70).square();
|
let two_pow_140 = pallas::Base::from_u128(1u128 << 70).square();
|
||||||
let t_p = pallas::Base::from_u128(T_P);
|
let t_p = pallas::Base::from_u128(T_P);
|
||||||
b_2 + c * two_pow_5 + two_pow_140 - t_p
|
b_2 + c * two_pow_5 + two_pow_140 - t_p
|
||||||
});
|
});
|
||||||
let zs = self.sinsemilla_config.lookup_config().witness_check(
|
let zs = lookup_config.witness_check(
|
||||||
layouter.namespace(|| "Decompose low 140 bits of (b_2 + c * 2^5 + 2^140 - t_P)"),
|
layouter.namespace(|| "Decompose low 140 bits of (b_2 + c * 2^5 + 2^140 - t_P)"),
|
||||||
b2_c_prime,
|
b2_c_prime,
|
||||||
14,
|
14,
|
||||||
|
@ -467,8 +465,10 @@ impl CommitIvkConfig {
|
||||||
|
|
||||||
Ok((b2_c_prime, zs[14].clone()))
|
Ok((b2_c_prime, zs[14].clone()))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Assign cells for the canonicity gate.
|
impl CommitIvkConfig {
|
||||||
|
/// Assign cells for the canonicity gate.
|
||||||
/*
|
/*
|
||||||
The pieces are laid out in this configuration:
|
The pieces are laid out in this configuration:
|
||||||
|
|
||||||
|
@ -508,22 +508,28 @@ impl CommitIvkConfig {
|
||||||
.copy_advice(|| "b", &mut region, self.advices[2], offset)?;
|
.copy_advice(|| "b", &mut region, self.advices[2], offset)?;
|
||||||
|
|
||||||
// Copy in `b_0`
|
// Copy in `b_0`
|
||||||
gate_cells
|
gate_cells.b_0.inner().copy_advice(
|
||||||
.b_0
|
|| "b_0",
|
||||||
.copy_advice(|| "b_0", &mut region, self.advices[3], offset)?;
|
&mut region,
|
||||||
|
self.advices[3],
|
||||||
|
offset,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Witness `b_1`
|
// Witness `b_1`
|
||||||
region.assign_advice(
|
region.assign_advice(
|
||||||
|| "Witness b_1",
|
|| "Witness b_1",
|
||||||
self.advices[4],
|
self.advices[4],
|
||||||
offset,
|
offset,
|
||||||
|| gate_cells.b_1.ok_or(Error::Synthesis),
|
|| gate_cells.b_1.inner().ok_or(Error::Synthesis),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Copy in `b_2`
|
// Copy in `b_2`
|
||||||
gate_cells
|
gate_cells.b_2.inner().copy_advice(
|
||||||
.b_2
|
|| "b_2",
|
||||||
.copy_advice(|| "b_2", &mut region, self.advices[5], offset)?;
|
&mut region,
|
||||||
|
self.advices[5],
|
||||||
|
offset,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Copy in z13_a
|
// Copy in z13_a
|
||||||
gate_cells.z13_a.copy_advice(
|
gate_cells.z13_a.copy_advice(
|
||||||
|
@ -570,16 +576,19 @@ impl CommitIvkConfig {
|
||||||
.copy_advice(|| "d", &mut region, self.advices[2], offset)?;
|
.copy_advice(|| "d", &mut region, self.advices[2], offset)?;
|
||||||
|
|
||||||
// Copy in `d_0`
|
// Copy in `d_0`
|
||||||
gate_cells
|
gate_cells.d_0.inner().copy_advice(
|
||||||
.d_0
|
|| "d_0",
|
||||||
.copy_advice(|| "d_0", &mut region, self.advices[3], offset)?;
|
&mut region,
|
||||||
|
self.advices[3],
|
||||||
|
offset,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Witness `d_1`
|
// Witness `d_1`
|
||||||
region.assign_advice(
|
region.assign_advice(
|
||||||
|| "Witness d_1",
|
|| "Witness d_1",
|
||||||
self.advices[4],
|
self.advices[4],
|
||||||
offset,
|
offset,
|
||||||
|| gate_cells.d_1.ok_or(Error::Synthesis),
|
|| gate_cells.d_1.inner().ok_or(Error::Synthesis),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Copy in z13_c
|
// Copy in z13_c
|
||||||
|
@ -621,11 +630,11 @@ struct GateCells {
|
||||||
d: AssignedCell<pallas::Base, pallas::Base>,
|
d: AssignedCell<pallas::Base, pallas::Base>,
|
||||||
ak: AssignedCell<pallas::Base, pallas::Base>,
|
ak: AssignedCell<pallas::Base, pallas::Base>,
|
||||||
nk: AssignedCell<pallas::Base, pallas::Base>,
|
nk: AssignedCell<pallas::Base, pallas::Base>,
|
||||||
b_0: AssignedCell<pallas::Base, pallas::Base>,
|
b_0: RangeConstrained<pallas::Base, AssignedCell<pallas::Base, pallas::Base>>,
|
||||||
b_1: Option<pallas::Base>,
|
b_1: RangeConstrained<pallas::Base, Option<pallas::Base>>,
|
||||||
b_2: AssignedCell<pallas::Base, pallas::Base>,
|
b_2: RangeConstrained<pallas::Base, AssignedCell<pallas::Base, pallas::Base>>,
|
||||||
d_0: AssignedCell<pallas::Base, pallas::Base>,
|
d_0: RangeConstrained<pallas::Base, AssignedCell<pallas::Base, pallas::Base>>,
|
||||||
d_1: Option<pallas::Base>,
|
d_1: RangeConstrained<pallas::Base, Option<pallas::Base>>,
|
||||||
z13_a: AssignedCell<pallas::Base, pallas::Base>,
|
z13_a: AssignedCell<pallas::Base, pallas::Base>,
|
||||||
a_prime: AssignedCell<pallas::Base, pallas::Base>,
|
a_prime: AssignedCell<pallas::Base, pallas::Base>,
|
||||||
z13_a_prime: AssignedCell<pallas::Base, pallas::Base>,
|
z13_a_prime: AssignedCell<pallas::Base, pallas::Base>,
|
||||||
|
@ -638,7 +647,7 @@ struct GateCells {
|
||||||
mod tests {
|
mod tests {
|
||||||
use core::iter;
|
use core::iter;
|
||||||
|
|
||||||
use super::CommitIvkConfig;
|
use super::{gadgets, CommitIvkChip, CommitIvkConfig};
|
||||||
use crate::constants::{
|
use crate::constants::{
|
||||||
fixed_bases::COMMIT_IVK_PERSONALIZATION, OrchardCommitDomains, OrchardFixedBases,
|
fixed_bases::COMMIT_IVK_PERSONALIZATION, OrchardCommitDomains, OrchardFixedBases,
|
||||||
OrchardHashDomains, L_ORCHARD_BASE, T_Q,
|
OrchardHashDomains, L_ORCHARD_BASE, T_Q,
|
||||||
|
@ -647,7 +656,7 @@ mod tests {
|
||||||
use halo2_gadgets::{
|
use halo2_gadgets::{
|
||||||
ecc::chip::{EccChip, EccConfig},
|
ecc::chip::{EccChip, EccConfig},
|
||||||
primitives::sinsemilla::CommitDomain,
|
primitives::sinsemilla::CommitDomain,
|
||||||
sinsemilla::chip::SinsemillaChip,
|
sinsemilla::chip::{SinsemillaChip, SinsemillaConfig},
|
||||||
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
|
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
|
||||||
};
|
};
|
||||||
use halo2_proofs::{
|
use halo2_proofs::{
|
||||||
|
@ -671,7 +680,11 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Circuit<pallas::Base> for MyCircuit {
|
impl Circuit<pallas::Base> for MyCircuit {
|
||||||
type Config = (CommitIvkConfig, EccConfig<OrchardFixedBases>);
|
type Config = (
|
||||||
|
SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
|
||||||
|
CommitIvkConfig,
|
||||||
|
EccConfig<OrchardFixedBases>,
|
||||||
|
);
|
||||||
type FloorPlanner = SimpleFloorPlanner;
|
type FloorPlanner = SimpleFloorPlanner;
|
||||||
|
|
||||||
fn without_witnesses(&self) -> Self {
|
fn without_witnesses(&self) -> Self {
|
||||||
|
@ -730,8 +743,7 @@ mod tests {
|
||||||
range_check,
|
range_check,
|
||||||
);
|
);
|
||||||
|
|
||||||
let commit_ivk_config =
|
let commit_ivk_config = CommitIvkChip::configure(meta, advices);
|
||||||
CommitIvkConfig::configure(meta, advices, sinsemilla_config);
|
|
||||||
|
|
||||||
let ecc_config = EccChip::<OrchardFixedBases>::configure(
|
let ecc_config = EccChip::<OrchardFixedBases>::configure(
|
||||||
meta,
|
meta,
|
||||||
|
@ -740,7 +752,7 @@ mod tests {
|
||||||
range_check,
|
range_check,
|
||||||
);
|
);
|
||||||
|
|
||||||
(commit_ivk_config, ecc_config)
|
(sinsemilla_config, commit_ivk_config, ecc_config)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn synthesize(
|
fn synthesize(
|
||||||
|
@ -748,18 +760,19 @@ mod tests {
|
||||||
config: Self::Config,
|
config: Self::Config,
|
||||||
mut layouter: impl Layouter<pallas::Base>,
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let (commit_ivk_config, ecc_config) = config;
|
let (sinsemilla_config, commit_ivk_config, ecc_config) = config;
|
||||||
|
|
||||||
// Load the Sinsemilla generator lookup table used by the whole circuit.
|
// Load the Sinsemilla generator lookup table used by the whole circuit.
|
||||||
SinsemillaChip::<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>::load(commit_ivk_config.sinsemilla_config.clone(), &mut layouter)?;
|
SinsemillaChip::<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>::load(sinsemilla_config.clone(), &mut layouter)?;
|
||||||
|
|
||||||
// Construct a Sinsemilla chip
|
// Construct a Sinsemilla chip
|
||||||
let sinsemilla_chip =
|
let sinsemilla_chip = SinsemillaChip::construct(sinsemilla_config);
|
||||||
SinsemillaChip::construct(commit_ivk_config.sinsemilla_config.clone());
|
|
||||||
|
|
||||||
// Construct an ECC chip
|
// Construct an ECC chip
|
||||||
let ecc_chip = EccChip::construct(ecc_config);
|
let ecc_chip = EccChip::construct(ecc_config);
|
||||||
|
|
||||||
|
let commit_ivk_chip = CommitIvkChip::construct(commit_ivk_config.clone());
|
||||||
|
|
||||||
// Witness ak
|
// Witness ak
|
||||||
let ak = self.load_private(
|
let ak = self.load_private(
|
||||||
layouter.namespace(|| "load ak"),
|
layouter.namespace(|| "load ak"),
|
||||||
|
@ -777,9 +790,10 @@ mod tests {
|
||||||
// Use a random scalar for rivk
|
// Use a random scalar for rivk
|
||||||
let rivk = pallas::Scalar::random(OsRng);
|
let rivk = pallas::Scalar::random(OsRng);
|
||||||
|
|
||||||
let ivk = commit_ivk_config.assign_region(
|
let ivk = gadgets::commit_ivk(
|
||||||
sinsemilla_chip,
|
sinsemilla_chip,
|
||||||
ecc_chip,
|
ecc_chip,
|
||||||
|
commit_ivk_chip,
|
||||||
layouter.namespace(|| "CommitIvk"),
|
layouter.namespace(|| "CommitIvk"),
|
||||||
ak,
|
ak,
|
||||||
nk,
|
nk,
|
||||||
|
|
|
@ -1,15 +1,38 @@
|
||||||
//! Gadgets used in the Orchard circuit.
|
//! Gadgets used in the Orchard circuit.
|
||||||
|
|
||||||
|
use ff::Field;
|
||||||
use pasta_curves::pallas;
|
use pasta_curves::pallas;
|
||||||
|
|
||||||
use crate::constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains};
|
use super::{commit_ivk::CommitIvkChip, note_commit::NoteCommitChip};
|
||||||
|
use crate::constants::{
|
||||||
|
NullifierK, OrchardCommitDomains, OrchardFixedBases, OrchardFixedBasesFull, OrchardHashDomains,
|
||||||
|
ValueCommitV,
|
||||||
|
};
|
||||||
use halo2_gadgets::{
|
use halo2_gadgets::{
|
||||||
ecc::chip::EccChip,
|
ecc::{
|
||||||
poseidon::Pow5Chip as PoseidonChip,
|
chip::EccChip, EccInstructions, FixedPoint, FixedPointBaseField, FixedPointShort, Point, X,
|
||||||
|
},
|
||||||
|
poseidon::{Hash as PoseidonHash, PoseidonSpongeInstructions, Pow5Chip as PoseidonChip},
|
||||||
|
primitives::poseidon::{self, ConstantLength},
|
||||||
sinsemilla::{chip::SinsemillaChip, merkle::chip::MerkleChip},
|
sinsemilla::{chip::SinsemillaChip, merkle::chip::MerkleChip},
|
||||||
};
|
};
|
||||||
|
use halo2_proofs::{
|
||||||
|
arithmetic::FieldExt,
|
||||||
|
circuit::{AssignedCell, Chip, Layouter},
|
||||||
|
plonk::{self, Advice, Assigned, Column},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(in crate::circuit) mod add_chip;
|
||||||
|
|
||||||
impl super::Config {
|
impl super::Config {
|
||||||
|
pub(super) fn add_chip(&self) -> add_chip::AddChip {
|
||||||
|
add_chip::AddChip::construct(self.add_config.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn commit_ivk_chip(&self) -> CommitIvkChip {
|
||||||
|
CommitIvkChip::construct(self.commit_ivk_config.clone())
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn ecc_chip(&self) -> EccChip<OrchardFixedBases> {
|
pub(super) fn ecc_chip(&self) -> EccChip<OrchardFixedBases> {
|
||||||
EccChip::construct(self.ecc_config.clone())
|
EccChip::construct(self.ecc_config.clone())
|
||||||
}
|
}
|
||||||
|
@ -41,4 +64,148 @@ impl super::Config {
|
||||||
pub(super) fn poseidon_chip(&self) -> PoseidonChip<pallas::Base, 3, 2> {
|
pub(super) fn poseidon_chip(&self) -> PoseidonChip<pallas::Base, 3, 2> {
|
||||||
PoseidonChip::construct(self.poseidon_config.clone())
|
PoseidonChip::construct(self.poseidon_config.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn note_commit_chip_new(&self) -> NoteCommitChip {
|
||||||
|
NoteCommitChip::construct(self.new_note_commit_config.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn note_commit_chip_old(&self) -> NoteCommitChip {
|
||||||
|
NoteCommitChip::construct(self.old_note_commit_config.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An instruction set for adding two circuit words (field elements).
|
||||||
|
pub(in crate::circuit) trait AddInstruction<F: FieldExt>: Chip<F> {
|
||||||
|
/// Constraints `a + b` and returns the sum.
|
||||||
|
fn add(
|
||||||
|
&self,
|
||||||
|
layouter: impl Layouter<F>,
|
||||||
|
a: &AssignedCell<F, F>,
|
||||||
|
b: &AssignedCell<F, F>,
|
||||||
|
) -> Result<AssignedCell<F, F>, plonk::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Witnesses the given value in a standalone region.
|
||||||
|
///
|
||||||
|
/// Usages of this helper are technically superfluous, as the single-cell region is only
|
||||||
|
/// ever used in equality constraints. We could eliminate them with a write-on-copy
|
||||||
|
/// abstraction (https://github.com/zcash/halo2/issues/334).
|
||||||
|
pub(in crate::circuit) fn assign_free_advice<F: Field, V: Copy>(
|
||||||
|
mut layouter: impl Layouter<F>,
|
||||||
|
column: Column<Advice>,
|
||||||
|
value: Option<V>,
|
||||||
|
) -> Result<AssignedCell<V, F>, plonk::Error>
|
||||||
|
where
|
||||||
|
for<'v> Assigned<F>: From<&'v V>,
|
||||||
|
{
|
||||||
|
layouter.assign_region(
|
||||||
|
|| "load private",
|
||||||
|
|mut region| {
|
||||||
|
region.assign_advice(
|
||||||
|
|| "load private",
|
||||||
|
column,
|
||||||
|
0,
|
||||||
|
|| value.ok_or(plonk::Error::Synthesis),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `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: (
|
||||||
|
AssignedCell<pallas::Base, pallas::Base>,
|
||||||
|
AssignedCell<pallas::Base, pallas::Base>,
|
||||||
|
),
|
||||||
|
rcv: Option<pallas::Scalar>,
|
||||||
|
) -> 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
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub(in crate::circuit) fn derive_nullifier<
|
||||||
|
PoseidonChip: PoseidonSpongeInstructions<pallas::Base, poseidon::P128Pow5T3, ConstantLength<2>, 3, 2>,
|
||||||
|
AddChip: AddInstruction<pallas::Base>,
|
||||||
|
EccChip: EccInstructions<
|
||||||
|
pallas::Affine,
|
||||||
|
FixedPoints = OrchardFixedBases,
|
||||||
|
Var = AssignedCell<pallas::Base, pallas::Base>,
|
||||||
|
>,
|
||||||
|
>(
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
poseidon_chip: PoseidonChip,
|
||||||
|
add_chip: AddChip,
|
||||||
|
ecc_chip: EccChip,
|
||||||
|
rho: AssignedCell<pallas::Base, pallas::Base>,
|
||||||
|
psi: &AssignedCell<pallas::Base, pallas::Base>,
|
||||||
|
cm: &Point<pallas::Affine, EccChip>,
|
||||||
|
nk: AssignedCell<pallas::Base, pallas::Base>,
|
||||||
|
) -> Result<X<pallas::Affine, EccChip>, plonk::Error> {
|
||||||
|
// hash = poseidon_hash(nk, rho)
|
||||||
|
let hash = {
|
||||||
|
let poseidon_message = [nk, rho];
|
||||||
|
let poseidon_hasher =
|
||||||
|
PoseidonHash::init(poseidon_chip, layouter.namespace(|| "Poseidon init"))?;
|
||||||
|
poseidon_hasher.hash(
|
||||||
|
layouter.namespace(|| "Poseidon hash (nk, rho)"),
|
||||||
|
poseidon_message,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add hash output to psi.
|
||||||
|
// `scalar` = poseidon_hash(nk, rho) + psi.
|
||||||
|
let scalar = add_chip.add(
|
||||||
|
layouter.namespace(|| "scalar = poseidon_hash(nk, rho) + psi"),
|
||||||
|
&hash,
|
||||||
|
psi,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Multiply scalar by NullifierK
|
||||||
|
// `product` = [poseidon_hash(nk, rho) + psi] NullifierK.
|
||||||
|
//
|
||||||
|
let product = {
|
||||||
|
let nullifier_k = FixedPointBaseField::from_inner(ecc_chip, NullifierK);
|
||||||
|
nullifier_k.mul(
|
||||||
|
layouter.namespace(|| "[poseidon_output + psi] NullifierK"),
|
||||||
|
scalar,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add cm to multiplied fixed base to get nf
|
||||||
|
// cm + [poseidon_output + psi] NullifierK
|
||||||
|
cm.add(layouter.namespace(|| "nf"), &product)
|
||||||
|
.map(|res| res.extract_p())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::circuit) use crate::circuit::commit_ivk::gadgets::commit_ivk;
|
||||||
|
pub(in crate::circuit) use crate::circuit::note_commit::gadgets::note_commit;
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
use halo2_proofs::{
|
||||||
|
circuit::{AssignedCell, Chip, Layouter},
|
||||||
|
plonk::{self, Advice, Column, ConstraintSystem, Constraints, Selector},
|
||||||
|
poly::Rotation,
|
||||||
|
};
|
||||||
|
use pasta_curves::pallas;
|
||||||
|
|
||||||
|
use super::AddInstruction;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(in crate::circuit) struct AddConfig {
|
||||||
|
a: Column<Advice>,
|
||||||
|
b: Column<Advice>,
|
||||||
|
c: Column<Advice>,
|
||||||
|
q_add: Selector,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A chip implementing a single addition constraint `c = a + b` on a single row.
|
||||||
|
pub(in crate::circuit) struct AddChip {
|
||||||
|
config: AddConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chip<pallas::Base> for AddChip {
|
||||||
|
type Config = AddConfig;
|
||||||
|
type Loaded = ();
|
||||||
|
|
||||||
|
fn config(&self) -> &Self::Config {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loaded(&self) -> &Self::Loaded {
|
||||||
|
&()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddChip {
|
||||||
|
pub(in crate::circuit) fn configure(
|
||||||
|
meta: &mut ConstraintSystem<pallas::Base>,
|
||||||
|
a: Column<Advice>,
|
||||||
|
b: Column<Advice>,
|
||||||
|
c: Column<Advice>,
|
||||||
|
) -> AddConfig {
|
||||||
|
let q_add = meta.selector();
|
||||||
|
meta.create_gate("Field element addition: c = a + b", |meta| {
|
||||||
|
let q_add = meta.query_selector(q_add);
|
||||||
|
let a = meta.query_advice(a, Rotation::cur());
|
||||||
|
let b = meta.query_advice(b, Rotation::cur());
|
||||||
|
let c = meta.query_advice(c, Rotation::cur());
|
||||||
|
|
||||||
|
Constraints::with_selector(q_add, Some(a + b - c))
|
||||||
|
});
|
||||||
|
|
||||||
|
AddConfig { a, b, c, q_add }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::circuit) fn construct(config: AddConfig) -> Self {
|
||||||
|
Self { config }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddInstruction<pallas::Base> for AddChip {
|
||||||
|
fn add(
|
||||||
|
&self,
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
a: &AssignedCell<pallas::Base, pallas::Base>,
|
||||||
|
b: &AssignedCell<pallas::Base, pallas::Base>,
|
||||||
|
) -> Result<AssignedCell<pallas::Base, pallas::Base>, plonk::Error> {
|
||||||
|
layouter.assign_region(
|
||||||
|
|| "c = a + b",
|
||||||
|
|mut region| {
|
||||||
|
self.config.q_add.enable(&mut region, 0)?;
|
||||||
|
|
||||||
|
a.copy_advice(|| "copy a", &mut region, self.config.a, 0)?;
|
||||||
|
b.copy_advice(|| "copy b", &mut region, self.config.b, 0)?;
|
||||||
|
|
||||||
|
let scalar_val = a.value().zip(b.value()).map(|(a, b)| a + b);
|
||||||
|
region.assign_advice(
|
||||||
|
|| "c",
|
||||||
|
self.config.c,
|
||||||
|
0,
|
||||||
|
|| scalar_val.ok_or(plonk::Error::Synthesis),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
13
src/tree.rs
13
src/tree.rs
|
@ -5,7 +5,6 @@ use core::iter;
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::{
|
constants::{
|
||||||
sinsemilla::{i2lebsp_k, L_ORCHARD_MERKLE, MERKLE_CRH_PERSONALIZATION},
|
sinsemilla::{i2lebsp_k, L_ORCHARD_MERKLE, MERKLE_CRH_PERSONALIZATION},
|
||||||
util::gen_const_array_with_default,
|
|
||||||
MERKLE_DEPTH_ORCHARD,
|
MERKLE_DEPTH_ORCHARD,
|
||||||
},
|
},
|
||||||
note::commitment::ExtractedNoteCommitment,
|
note::commitment::ExtractedNoteCommitment,
|
||||||
|
@ -100,20 +99,14 @@ impl MerklePath {
|
||||||
pub(crate) fn dummy(mut rng: &mut impl RngCore) -> Self {
|
pub(crate) fn dummy(mut rng: &mut impl RngCore) -> Self {
|
||||||
MerklePath {
|
MerklePath {
|
||||||
position: rng.next_u32(),
|
position: rng.next_u32(),
|
||||||
auth_path: gen_const_array_with_default(MerkleHashOrchard::empty_leaf(), |_| {
|
auth_path: [(); MERKLE_DEPTH_ORCHARD]
|
||||||
MerkleHashOrchard(pallas::Base::random(&mut rng))
|
.map(|_| MerkleHashOrchard(pallas::Base::random(&mut rng))),
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Instantiates a new Merkle path given a leaf position and authentication path.
|
/// Instantiates a new Merkle path given a leaf position and authentication path.
|
||||||
pub(crate) fn new(position: u32, auth_path: [pallas::Base; MERKLE_DEPTH_ORCHARD]) -> Self {
|
pub(crate) fn new(position: u32, auth_path: [pallas::Base; MERKLE_DEPTH_ORCHARD]) -> Self {
|
||||||
Self::from_parts(
|
Self::from_parts(position, auth_path.map(MerkleHashOrchard))
|
||||||
position,
|
|
||||||
gen_const_array_with_default(MerkleHashOrchard::empty_leaf(), |i| {
|
|
||||||
MerkleHashOrchard(auth_path[i])
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Instantiates a new Merkle path given a leaf position and authentication path.
|
/// Instantiates a new Merkle path given a leaf position and authentication path.
|
||||||
|
|
33
src/value.rs
33
src/value.rs
|
@ -42,6 +42,7 @@ use core::ops::{Add, RangeInclusive, Sub};
|
||||||
use bitvec::{array::BitArray, order::Lsb0};
|
use bitvec::{array::BitArray, order::Lsb0};
|
||||||
use ff::{Field, PrimeField};
|
use ff::{Field, PrimeField};
|
||||||
use group::{Curve, Group, GroupEncoding};
|
use group::{Curve, Group, GroupEncoding};
|
||||||
|
use halo2_proofs::plonk::Assigned;
|
||||||
use pasta_curves::{
|
use pasta_curves::{
|
||||||
arithmetic::{CurveAffine, CurveExt},
|
arithmetic::{CurveAffine, CurveExt},
|
||||||
pallas,
|
pallas,
|
||||||
|
@ -115,8 +116,14 @@ impl NoteValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&NoteValue> for Assigned<pallas::Base> {
|
||||||
|
fn from(v: &NoteValue) -> Self {
|
||||||
|
pallas::Base::from(v.inner()).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Sub for NoteValue {
|
impl Sub for NoteValue {
|
||||||
type Output = Option<ValueSum>;
|
type Output = ValueSum;
|
||||||
|
|
||||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||||
fn sub(self, rhs: Self) -> Self::Output {
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
@ -125,20 +132,26 @@ impl Sub for NoteValue {
|
||||||
a.checked_sub(b)
|
a.checked_sub(b)
|
||||||
.filter(|v| VALUE_SUM_RANGE.contains(v))
|
.filter(|v| VALUE_SUM_RANGE.contains(v))
|
||||||
.map(ValueSum)
|
.map(ValueSum)
|
||||||
|
.expect("u64 - u64 result is always in VALUE_SUM_RANGE")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) enum Sign {
|
||||||
|
Positive,
|
||||||
|
Negative,
|
||||||
|
}
|
||||||
|
|
||||||
/// A sum of Orchard note values.
|
/// A sum of Orchard note values.
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
pub struct ValueSum(i128);
|
pub struct ValueSum(i128);
|
||||||
|
|
||||||
impl ValueSum {
|
impl ValueSum {
|
||||||
pub(crate) fn zero() -> Self {
|
pub(crate) fn zero() -> Self {
|
||||||
// Default for i64 is zero.
|
// Default for i128 is zero.
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a value sum from its raw numeric value.
|
/// Creates a value sum from a raw i64 (which is always in range for this type).
|
||||||
///
|
///
|
||||||
/// This only enforces that the value is a signed 63-bit integer. We use it internally
|
/// This only enforces that the value is a signed 63-bit integer. We use it internally
|
||||||
/// in `Bundle::binding_validating_key`, where we are converting from the user-defined
|
/// in `Bundle::binding_validating_key`, where we are converting from the user-defined
|
||||||
|
@ -147,6 +160,20 @@ impl ValueSum {
|
||||||
pub(crate) fn from_raw(value: i64) -> Self {
|
pub(crate) fn from_raw(value: i64) -> Self {
|
||||||
ValueSum(value as i128)
|
ValueSum(value as i128)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Splits this value sum into its magnitude and sign.
|
||||||
|
pub(crate) fn magnitude_sign(&self) -> (u64, Sign) {
|
||||||
|
let (magnitude, sign) = if self.0.is_negative() {
|
||||||
|
(-self.0, Sign::Negative)
|
||||||
|
} else {
|
||||||
|
(self.0, Sign::Positive)
|
||||||
|
};
|
||||||
|
(
|
||||||
|
u64::try_from(magnitude)
|
||||||
|
.expect("ValueSum magnitude is in range for u64 by construction"),
|
||||||
|
sign,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add for ValueSum {
|
impl Add for ValueSum {
|
||||||
|
|
Loading…
Reference in New Issue