mirror of https://github.com/zcash/orchard.git
Update random nullifier for split notes and circuit (#76)
To be secure against roadblock attacks, we update the process to obtain a random nullifier for split notes. Now we have the following formula to evaluate nf_old - for non split_notes, nf_old = Extract_P([PRF^{nfOrchard}_{nk}(rho_old) + psi_nf) mod q_P] NullifierK + cm_old) - for split notes, nf_old = Extract_P([PRF^{nfOrchard}_{nk}(rho_old) + psi_nf) mod q_P] NullifierK + cm_old + NullifierL) where psi_nf is equal to - psi_old for non split notes - a random pallas Base element for split notes The following constraints have been updated into the circuit - nf_old = nf_old_pub for all notes - derived_pk_d_old = pk_d_old for all notes - if split_flag=0, then psi_old = psi_new
This commit is contained in:
parent
daf6269e89
commit
477f949bd2
|
@ -167,26 +167,20 @@ impl SpendInfo {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a split spend, which is identical to origin normal spend except that we use a random
|
||||
/// fvk to generate a different nullifier. In addition, the split_flag is raised.
|
||||
/// Creates a split spend, which is identical to origin normal spend except that
|
||||
/// `rseed_split_note` contains a random seed. In addition, the split_flag is raised.
|
||||
///
|
||||
/// Defined in [Transfer and Burn of Zcash Shielded Assets ZIP-0226 § Split Notes (DRAFT PR)][TransferZSA].
|
||||
///
|
||||
/// [TransferZSA]: https://qed-it.github.io/zips/zip-0226.html#split-notes
|
||||
fn create_split_spend(&self, rng: &mut impl RngCore) -> Self {
|
||||
let note = self.note;
|
||||
let merkle_path = self.merkle_path.clone();
|
||||
|
||||
let sk = SpendingKey::random(rng);
|
||||
let fvk: FullViewingKey = (&sk).into();
|
||||
|
||||
SpendInfo {
|
||||
dummy_sk: Some(sk),
|
||||
fvk,
|
||||
dummy_sk: None,
|
||||
fvk: self.fvk.clone(),
|
||||
// We use external scope to avoid unnecessary derivations
|
||||
scope: Scope::External,
|
||||
note,
|
||||
merkle_path,
|
||||
note: self.note.create_split_note(rng),
|
||||
merkle_path: self.merkle_path.clone(),
|
||||
split_flag: true,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,7 +141,6 @@ pub struct Bundle<T: Authorization, V> {
|
|||
/// This is the sum of Orchard spends minus the sum of Orchard outputs.
|
||||
value_balance: V,
|
||||
/// Assets intended for burning
|
||||
/// TODO We need to add a consensus check to make sure that it is impossible to burn ZEC.
|
||||
burn: Vec<(AssetBase, V)>,
|
||||
/// The root of the Orchard commitment tree that this bundle commits to.
|
||||
anchor: Anchor,
|
||||
|
|
192
src/circuit.rs
192
src/circuit.rs
|
@ -21,7 +21,7 @@ use self::{
|
|||
commit_ivk::{CommitIvkChip, CommitIvkConfig},
|
||||
gadget::{
|
||||
add_chip::{AddChip, AddConfig},
|
||||
assign_free_advice, assign_is_native_asset,
|
||||
assign_free_advice, assign_is_native_asset, assign_split_flag,
|
||||
},
|
||||
note_commit::{NoteCommitChip, NoteCommitConfig},
|
||||
};
|
||||
|
@ -113,6 +113,7 @@ pub struct Circuit {
|
|||
pub(crate) psi_old: Value<pallas::Base>,
|
||||
pub(crate) rcm_old: Value<NoteCommitTrapdoor>,
|
||||
pub(crate) cm_old: Value<NoteCommitment>,
|
||||
pub(crate) psi_nf: Value<pallas::Base>,
|
||||
pub(crate) alpha: Value<pallas::Scalar>,
|
||||
pub(crate) ak: Value<SpendValidatingKey>,
|
||||
pub(crate) nk: Value<NullifierDerivingKey>,
|
||||
|
@ -164,6 +165,9 @@ impl Circuit {
|
|||
let psi_old = spend.note.rseed().psi(&rho_old);
|
||||
let rcm_old = spend.note.rseed().rcm(&rho_old);
|
||||
|
||||
let nf_rseed = spend.note.rseed_split_note().unwrap_or(*spend.note.rseed());
|
||||
let psi_nf = nf_rseed.psi(&rho_old);
|
||||
|
||||
let rho_new = output_note.rho();
|
||||
let psi_new = output_note.rseed().psi(&rho_new);
|
||||
let rcm_new = output_note.rseed().rcm(&rho_new);
|
||||
|
@ -178,6 +182,7 @@ impl Circuit {
|
|||
psi_old: Value::known(psi_old),
|
||||
rcm_old: Value::known(rcm_old),
|
||||
cm_old: Value::known(spend.note.commitment()),
|
||||
psi_nf: Value::known(psi_nf),
|
||||
alpha: Value::known(alpha),
|
||||
ak: Value::known(spend.fvk.clone().into()),
|
||||
nk: Value::known(*spend.fvk.nk()),
|
||||
|
@ -222,10 +227,9 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
// Constrain v_old = 0 or calculated root = anchor (https://p.z.cash/ZKS:action-merkle-path-validity?partial).
|
||||
// Constrain v_old = 0 or enable_spends = 1 (https://p.z.cash/ZKS:action-enable-spend).
|
||||
// Constrain v_new = 0 or enable_outputs = 1 (https://p.z.cash/ZKS:action-enable-output).
|
||||
// Constrain split_flag = 1 or nf_old = nf_old_pub
|
||||
// Constrain is_native_asset to be boolean
|
||||
// Constraint if is_native_asset = 1 then asset = native_asset else asset != native_asset
|
||||
// Constraint split_flag = 1 or derived_pk_d_old = pk_d_old
|
||||
// Constraint if split_flag = 0 then psi_old = psi_nf
|
||||
let q_orchard = meta.selector();
|
||||
meta.create_gate("Orchard circuit checks", |meta| {
|
||||
let q_orchard = meta.query_selector(q_orchard);
|
||||
|
@ -242,14 +246,11 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
|
||||
let split_flag = meta.query_advice(advices[8], Rotation::cur());
|
||||
|
||||
let nf_old = meta.query_advice(advices[9], Rotation::cur());
|
||||
let nf_old_pub = meta.query_advice(advices[0], Rotation::next());
|
||||
|
||||
let is_native_asset = meta.query_advice(advices[1], Rotation::next());
|
||||
let asset_x = meta.query_advice(advices[2], Rotation::next());
|
||||
let asset_y = meta.query_advice(advices[3], Rotation::next());
|
||||
let diff_asset_x_inv = meta.query_advice(advices[4], Rotation::next());
|
||||
let diff_asset_y_inv = meta.query_advice(advices[5], Rotation::next());
|
||||
let is_native_asset = meta.query_advice(advices[9], Rotation::cur());
|
||||
let asset_x = meta.query_advice(advices[0], Rotation::next());
|
||||
let asset_y = meta.query_advice(advices[1], Rotation::next());
|
||||
let diff_asset_x_inv = meta.query_advice(advices[2], Rotation::next());
|
||||
let diff_asset_y_inv = meta.query_advice(advices[3], Rotation::next());
|
||||
|
||||
let one = Expression::Constant(pallas::Base::one());
|
||||
|
||||
|
@ -262,10 +263,8 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
let diff_asset_x = asset_x - Expression::Constant(*native_asset.x());
|
||||
let diff_asset_y = asset_y - Expression::Constant(*native_asset.y());
|
||||
|
||||
let pk_d_old_x = meta.query_advice(advices[6], Rotation::next());
|
||||
let pk_d_old_y = meta.query_advice(advices[7], Rotation::next());
|
||||
let derived_pk_d_old_x = meta.query_advice(advices[8], Rotation::next());
|
||||
let derived_pk_d_old_y = meta.query_advice(advices[9], Rotation::next());
|
||||
let psi_old = meta.query_advice(advices[4], Rotation::next());
|
||||
let psi_nf = meta.query_advice(advices[5], Rotation::next());
|
||||
|
||||
Constraints::with_selector(
|
||||
q_orchard,
|
||||
|
@ -289,10 +288,6 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
"v_new = 0 or enable_outputs = 1",
|
||||
v_new * (one.clone() - enable_outputs),
|
||||
),
|
||||
(
|
||||
"split_flag = 1 or nf_old = nf_old_pub",
|
||||
(one.clone() - split_flag.clone()) * (nf_old - nf_old_pub),
|
||||
),
|
||||
(
|
||||
"bool_check is_native_asset",
|
||||
bool_check(is_native_asset.clone()),
|
||||
|
@ -316,20 +311,9 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
* (diff_asset_x * diff_asset_x_inv - one.clone())
|
||||
* (diff_asset_y * diff_asset_y_inv - one.clone()),
|
||||
),
|
||||
// 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).
|
||||
(
|
||||
"split_flag = 1 or pk_d_old_x = derived_pk_d_old_x",
|
||||
(one.clone() - split_flag.clone()) * (pk_d_old_x - derived_pk_d_old_x),
|
||||
),
|
||||
(
|
||||
"split_flag = 1 or pk_d_old_y = derived_pk_d_old_y",
|
||||
(one - split_flag) * (pk_d_old_y - derived_pk_d_old_y),
|
||||
"(split_flag = 0) => (psi_old = psi_nf)",
|
||||
(one - split_flag) * (psi_old - psi_nf),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
@ -480,7 +464,14 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
let ecc_chip = config.ecc_chip();
|
||||
|
||||
// Witness private inputs that are used across multiple checks.
|
||||
let (psi_old, rho_old, cm_old, g_d_old, ak_P, nk, v_old, v_new, asset, nf_old_pub) = {
|
||||
let (psi_nf, psi_old, rho_old, cm_old, g_d_old, ak_P, nk, v_old, v_new, asset) = {
|
||||
// Witness psi_nf
|
||||
let psi_nf = assign_free_advice(
|
||||
layouter.namespace(|| "witness psi_nf"),
|
||||
config.advices[0],
|
||||
self.psi_nf,
|
||||
)?;
|
||||
|
||||
// Witness psi_old
|
||||
let psi_old = assign_free_advice(
|
||||
layouter.namespace(|| "witness psi_old"),
|
||||
|
@ -545,25 +536,18 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
self.asset.map(|asset| asset.cv_base().to_affine()),
|
||||
)?;
|
||||
|
||||
// Witness nf_old_pub
|
||||
let nf_old_pub = layouter.assign_region(
|
||||
|| "load nf_old pub",
|
||||
|mut region| {
|
||||
region.assign_advice_from_instance(
|
||||
|| "load nf_old pub",
|
||||
config.primary,
|
||||
NF_OLD,
|
||||
config.advices[0],
|
||||
1,
|
||||
)
|
||||
},
|
||||
)?;
|
||||
|
||||
(
|
||||
psi_old, rho_old, cm_old, g_d_old, ak_P, nk, v_old, v_new, asset, nf_old_pub,
|
||||
psi_nf, psi_old, rho_old, cm_old, g_d_old, ak_P, nk, v_old, v_new, asset,
|
||||
)
|
||||
};
|
||||
|
||||
// Witness split_flag
|
||||
let split_flag = assign_split_flag(
|
||||
layouter.namespace(|| "witness split_flag"),
|
||||
config.advices[0],
|
||||
self.split_flag,
|
||||
)?;
|
||||
|
||||
// Witness is_native_asset which is equal to
|
||||
// 1 if asset is equal to native asset, and
|
||||
// 0 if asset is not equal to native asset.
|
||||
|
@ -656,16 +640,21 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
// Nullifier integrity (https://p.z.cash/ZKS:action-nullifier-integrity).
|
||||
let nf_old = {
|
||||
let nf_old = gadget::derive_nullifier(
|
||||
layouter.namespace(|| "nf_old = DeriveNullifier_nk(rho_old, psi_old, cm_old)"),
|
||||
layouter.namespace(|| "nf_old = DeriveNullifier_nk(rho_old, psi_nf, cm_old)"),
|
||||
config.poseidon_chip(),
|
||||
config.add_chip(),
|
||||
ecc_chip.clone(),
|
||||
config.mux_chip(),
|
||||
rho_old.clone(),
|
||||
&psi_old,
|
||||
&psi_nf,
|
||||
&cm_old,
|
||||
nk.clone(),
|
||||
split_flag.clone(),
|
||||
)?;
|
||||
|
||||
// Constrain nf_old to equal public input
|
||||
layouter.constrain_instance(nf_old.inner().cell(), config.primary, NF_OLD)?;
|
||||
|
||||
nf_old
|
||||
};
|
||||
|
||||
|
@ -690,7 +679,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
}
|
||||
|
||||
// Diversified address integrity (https://p.z.cash/ZKS:action-addr-integrity?partial).
|
||||
let (derived_pk_d_old, pk_d_old) = {
|
||||
let pk_d_old = {
|
||||
let ivk = {
|
||||
let ak = ak_P.extract_p().inner().clone();
|
||||
let rivk = ScalarFixed::new(
|
||||
|
@ -717,13 +706,22 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
let (derived_pk_d_old, _ivk) =
|
||||
g_d_old.mul(layouter.namespace(|| "[ivk] g_d_old"), ivk)?;
|
||||
|
||||
// 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(
|
||||
ecc_chip.clone(),
|
||||
layouter.namespace(|| "witness pk_d_old"),
|
||||
self.pk_d_old.map(|pk_d_old| pk_d_old.inner().to_affine()),
|
||||
)?;
|
||||
derived_pk_d_old
|
||||
.constrain_equal(layouter.namespace(|| "pk_d_old equality"), &pk_d_old)?;
|
||||
|
||||
(derived_pk_d_old, pk_d_old)
|
||||
pk_d_old
|
||||
};
|
||||
|
||||
// Old note commitment integrity (https://p.z.cash/ZKS:action-cm-old-integrity?partial).
|
||||
|
@ -747,7 +745,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
pk_d_old.inner(),
|
||||
v_old.clone(),
|
||||
rho_old,
|
||||
psi_old,
|
||||
psi_old.clone(),
|
||||
asset.inner(),
|
||||
rcm_old,
|
||||
is_native_asset.clone(),
|
||||
|
@ -780,7 +778,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
};
|
||||
|
||||
// ρ^new = nf^old
|
||||
let rho_new = nf_old_pub.clone();
|
||||
let rho_new = nf_old.inner().clone();
|
||||
|
||||
// Witness psi_new
|
||||
let psi_new = assign_free_advice(
|
||||
|
@ -864,41 +862,28 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
0,
|
||||
)?;
|
||||
|
||||
region.assign_advice(
|
||||
|| "split_flag",
|
||||
config.advices[8],
|
||||
0,
|
||||
|| {
|
||||
self.split_flag
|
||||
.map(|split_flag| pallas::Base::from(split_flag as u64))
|
||||
},
|
||||
)?;
|
||||
|
||||
nf_old
|
||||
.inner()
|
||||
.copy_advice(|| "nf_old", &mut region, config.advices[9], 0)?;
|
||||
nf_old_pub.copy_advice(|| "nf_old", &mut region, config.advices[0], 1)?;
|
||||
split_flag.copy_advice(|| "split_flag", &mut region, config.advices[8], 0)?;
|
||||
|
||||
is_native_asset.copy_advice(
|
||||
|| "is_native_asset",
|
||||
&mut region,
|
||||
config.advices[1],
|
||||
1,
|
||||
config.advices[9],
|
||||
0,
|
||||
)?;
|
||||
asset
|
||||
.inner()
|
||||
.x()
|
||||
.copy_advice(|| "asset_x", &mut region, config.advices[2], 1)?;
|
||||
.copy_advice(|| "asset_x", &mut region, config.advices[0], 1)?;
|
||||
asset
|
||||
.inner()
|
||||
.y()
|
||||
.copy_advice(|| "asset_y", &mut region, config.advices[3], 1)?;
|
||||
.copy_advice(|| "asset_y", &mut region, config.advices[1], 1)?;
|
||||
|
||||
// `diff_asset_x_inv` and `diff_asset_y_inv` will be used to prove that
|
||||
// if is_native_asset = 0, then asset != native_asset.
|
||||
region.assign_advice(
|
||||
|| "diff_asset_x_inv",
|
||||
config.advices[4],
|
||||
config.advices[2],
|
||||
1,
|
||||
|| {
|
||||
self.asset.map(|asset| {
|
||||
|
@ -922,7 +907,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
)?;
|
||||
region.assign_advice(
|
||||
|| "diff_asset_y_inv",
|
||||
config.advices[5],
|
||||
config.advices[3],
|
||||
1,
|
||||
|| {
|
||||
self.asset.map(|asset| {
|
||||
|
@ -945,30 +930,8 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|
|||
},
|
||||
)?;
|
||||
|
||||
pk_d_old.inner().x().copy_advice(
|
||||
|| "pk_d_old_x",
|
||||
&mut region,
|
||||
config.advices[6],
|
||||
1,
|
||||
)?;
|
||||
pk_d_old.inner().y().copy_advice(
|
||||
|| "pk_d_old_y",
|
||||
&mut region,
|
||||
config.advices[7],
|
||||
1,
|
||||
)?;
|
||||
derived_pk_d_old.inner().x().copy_advice(
|
||||
|| "derived_pk_d_old_x",
|
||||
&mut region,
|
||||
config.advices[8],
|
||||
1,
|
||||
)?;
|
||||
derived_pk_d_old.inner().y().copy_advice(
|
||||
|| "derived_pk_d_old_y",
|
||||
&mut region,
|
||||
config.advices[9],
|
||||
1,
|
||||
)?;
|
||||
psi_old.copy_advice(|| "psi_old", &mut region, config.advices[4], 1)?;
|
||||
psi_nf.copy_advice(|| "psi_nf", &mut region, config.advices[5], 1)?;
|
||||
|
||||
config.q_orchard.enable(&mut region, 0)
|
||||
},
|
||||
|
@ -1226,6 +1189,8 @@ mod tests {
|
|||
let path = MerklePath::dummy(&mut rng);
|
||||
let anchor = path.root(spent_note.commitment().into());
|
||||
|
||||
let psi_old = spent_note.rseed().psi(&spent_note.rho());
|
||||
|
||||
(
|
||||
Circuit {
|
||||
path: Value::known(path.auth_path()),
|
||||
|
@ -1234,9 +1199,11 @@ mod tests {
|
|||
pk_d_old: Value::known(*sender_address.pk_d()),
|
||||
v_old: Value::known(spent_note.value()),
|
||||
rho_old: Value::known(spent_note.rho()),
|
||||
psi_old: Value::known(spent_note.rseed().psi(&spent_note.rho())),
|
||||
psi_old: Value::known(psi_old),
|
||||
rcm_old: Value::known(spent_note.rseed().rcm(&spent_note.rho())),
|
||||
cm_old: Value::known(spent_note.commitment()),
|
||||
// For non split note, psi_nf is equal to psi_old
|
||||
psi_nf: Value::known(psi_old),
|
||||
alpha: Value::known(alpha),
|
||||
ak: Value::known(ak),
|
||||
nk: Value::known(nk),
|
||||
|
@ -1426,6 +1393,7 @@ mod tests {
|
|||
psi_old: Value::unknown(),
|
||||
rcm_old: Value::unknown(),
|
||||
cm_old: Value::unknown(),
|
||||
psi_nf: Value::unknown(),
|
||||
alpha: Value::unknown(),
|
||||
ak: Value::unknown(),
|
||||
nk: Value::unknown(),
|
||||
|
@ -1483,42 +1451,41 @@ mod tests {
|
|||
let fvk: FullViewingKey = (&sk).into();
|
||||
let sender_address = fvk.address_at(0u32, Scope::External);
|
||||
let rho_old = Nullifier::dummy(&mut rng);
|
||||
let spent_note = Note::new(
|
||||
let note = Note::new(
|
||||
sender_address,
|
||||
NoteValue::from_raw(40),
|
||||
asset_base,
|
||||
rho_old,
|
||||
&mut rng,
|
||||
);
|
||||
let spent_note = if split_flag {
|
||||
note.create_split_note(&mut rng)
|
||||
} else {
|
||||
note
|
||||
};
|
||||
(fvk, spent_note)
|
||||
};
|
||||
|
||||
let output_value = NoteValue::from_raw(10);
|
||||
|
||||
let (dummy_sk, fvk, scope, nf_old, v_net) = if split_flag {
|
||||
let sk = SpendingKey::random(&mut rng);
|
||||
let fvk: FullViewingKey = (&sk).into();
|
||||
let (scope, v_net) = if split_flag {
|
||||
(
|
||||
Some(sk),
|
||||
fvk.clone(),
|
||||
Scope::External,
|
||||
spent_note.nullifier(&fvk),
|
||||
// Split notes do not contribute to v_net.
|
||||
// Therefore, if split_flag is true, v_net = - output_value
|
||||
NoteValue::zero() - output_value,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
None,
|
||||
spent_note_fvk.clone(),
|
||||
spent_note_fvk
|
||||
.scope_for_address(&spent_note.recipient())
|
||||
.unwrap(),
|
||||
spent_note.nullifier(&spent_note_fvk),
|
||||
spent_note.value() - output_value,
|
||||
)
|
||||
};
|
||||
let ak: SpendValidatingKey = fvk.clone().into();
|
||||
|
||||
let nf_old = spent_note.nullifier(&spent_note_fvk);
|
||||
let ak: SpendValidatingKey = spent_note_fvk.clone().into();
|
||||
let alpha = pallas::Scalar::random(&mut rng);
|
||||
let rk = ak.randomize(&alpha);
|
||||
|
||||
|
@ -1539,8 +1506,8 @@ mod tests {
|
|||
let anchor = path.root(spent_note.commitment().into());
|
||||
|
||||
let spend_info = SpendInfo {
|
||||
dummy_sk,
|
||||
fvk,
|
||||
dummy_sk: None,
|
||||
fvk: spent_note_fvk,
|
||||
scope,
|
||||
note: spent_note,
|
||||
merkle_path: path,
|
||||
|
@ -1623,6 +1590,7 @@ mod tests {
|
|||
psi_old: circuit.psi_old,
|
||||
rcm_old: circuit.rcm_old.clone(),
|
||||
cm_old: Value::known(random_note_commitment(&mut rng)),
|
||||
psi_nf: circuit.psi_nf,
|
||||
alpha: circuit.alpha,
|
||||
ak: circuit.ak.clone(),
|
||||
nk: circuit.nk,
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
//! Gadgets used in the Orchard circuit.
|
||||
|
||||
use ff::Field;
|
||||
use group::Curve;
|
||||
use pasta_curves::arithmetic::CurveExt;
|
||||
use pasta_curves::pallas;
|
||||
|
||||
use super::{commit_ivk::CommitIvkChip, note_commit::NoteCommitChip};
|
||||
use crate::circuit::gadget::mux_chip::{MuxChip, MuxInstructions};
|
||||
use crate::constants::{NullifierK, OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains};
|
||||
use crate::note::AssetBase;
|
||||
use halo2_gadgets::{
|
||||
ecc::{chip::EccChip, EccInstructions, FixedPointBaseField, Point, X},
|
||||
ecc::{chip::EccChip, chip::EccPoint, EccInstructions, FixedPointBaseField, Point, X},
|
||||
poseidon::{
|
||||
primitives::{self as poseidon, ConstantLength},
|
||||
Hash as PoseidonHash, PoseidonSpongeInstructions, Pow5Chip as PoseidonChip,
|
||||
|
@ -128,6 +131,28 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
/// Witnesses split_flag.
|
||||
pub(in crate::circuit) fn assign_split_flag<F: Field>(
|
||||
layouter: impl Layouter<F>,
|
||||
column: Column<Advice>,
|
||||
split_flag: Value<bool>,
|
||||
) -> Result<AssignedCell<pasta_curves::Fp, F>, plonk::Error>
|
||||
where
|
||||
Assigned<F>: for<'v> From<&'v pasta_curves::Fp>,
|
||||
{
|
||||
assign_free_advice(
|
||||
layouter,
|
||||
column,
|
||||
split_flag.map(|split_flag| {
|
||||
if split_flag {
|
||||
pallas::Base::one()
|
||||
} else {
|
||||
pallas::Base::zero()
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// `DeriveNullifier` from [Section 4.16: Note Commitments and Nullifiers].
|
||||
///
|
||||
/// [Section 4.16: Note Commitments and Nullifiers]: https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers
|
||||
|
@ -138,6 +163,7 @@ pub(in crate::circuit) fn derive_nullifier<
|
|||
EccChip: EccInstructions<
|
||||
pallas::Affine,
|
||||
FixedPoints = OrchardFixedBases,
|
||||
Point = EccPoint,
|
||||
Var = AssignedCell<pallas::Base, pallas::Base>,
|
||||
>,
|
||||
>(
|
||||
|
@ -145,10 +171,12 @@ pub(in crate::circuit) fn derive_nullifier<
|
|||
poseidon_chip: PoseidonChip,
|
||||
add_chip: AddChip,
|
||||
ecc_chip: EccChip,
|
||||
mux_chip: MuxChip,
|
||||
rho: AssignedCell<pallas::Base, pallas::Base>,
|
||||
psi: &AssignedCell<pallas::Base, pallas::Base>,
|
||||
cm: &Point<pallas::Affine, EccChip>,
|
||||
nk: AssignedCell<pallas::Base, pallas::Base>,
|
||||
split_flag: AssignedCell<pallas::Base, pallas::Base>,
|
||||
) -> Result<X<pallas::Affine, EccChip>, plonk::Error> {
|
||||
// hash = poseidon_hash(nk, rho)
|
||||
let hash = {
|
||||
|
@ -173,17 +201,37 @@ pub(in crate::circuit) fn derive_nullifier<
|
|||
// `product` = [poseidon_hash(nk, rho) + psi] NullifierK.
|
||||
//
|
||||
let product = {
|
||||
let nullifier_k = FixedPointBaseField::from_inner(ecc_chip, NullifierK);
|
||||
let nullifier_k = FixedPointBaseField::from_inner(ecc_chip.clone(), 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())
|
||||
// Add cm to multiplied fixed base
|
||||
// nf = cm + [poseidon_output + psi] NullifierK
|
||||
let nf = cm.add(layouter.namespace(|| "nf"), &product)?;
|
||||
|
||||
// Add NullifierL to nf
|
||||
// split_note_nf = NullifierL + nf
|
||||
let nullifier_l = Point::new_from_constant(
|
||||
ecc_chip.clone(),
|
||||
layouter.namespace(|| "witness NullifierL constant"),
|
||||
pallas::Point::hash_to_curve("z.cash:Orchard")(b"L").to_affine(),
|
||||
)?;
|
||||
let split_note_nf = nullifier_l.add(layouter.namespace(|| "split_note_nf"), &nf)?;
|
||||
|
||||
// Select the desired nullifier according to split_flag
|
||||
Ok(Point::from_inner(
|
||||
ecc_chip,
|
||||
mux_chip.mux(
|
||||
layouter.namespace(|| "mux on nf"),
|
||||
&split_flag,
|
||||
nf.inner(),
|
||||
split_note_nf.inner(),
|
||||
)?,
|
||||
)
|
||||
.extract_p())
|
||||
}
|
||||
|
||||
pub(in crate::circuit) use crate::circuit::commit_ivk::gadgets::commit_ivk;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
42
src/note.rs
42
src/note.rs
|
@ -4,7 +4,7 @@ use core::fmt;
|
|||
use group::GroupEncoding;
|
||||
use pasta_curves::pallas;
|
||||
use rand::RngCore;
|
||||
use subtle::CtOption;
|
||||
use subtle::{Choice, ConditionallySelectable, CtOption};
|
||||
|
||||
use crate::{
|
||||
keys::{EphemeralSecretKey, FullViewingKey, Scope, SpendingKey},
|
||||
|
@ -86,6 +86,17 @@ impl RandomSeed {
|
|||
}
|
||||
}
|
||||
|
||||
impl ConditionallySelectable for RandomSeed {
|
||||
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
|
||||
let result: Vec<u8> =
|
||||
a.0.iter()
|
||||
.zip(b.0.iter())
|
||||
.map(|(a_i, b_i)| u8::conditional_select(a_i, b_i, choice))
|
||||
.collect();
|
||||
RandomSeed(<[u8; 32]>::try_from(result).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// A discrete amount of funds received by an address.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Note {
|
||||
|
@ -104,6 +115,10 @@ pub struct Note {
|
|||
rho: Nullifier,
|
||||
/// The seed randomness for various note components.
|
||||
rseed: RandomSeed,
|
||||
/// The seed randomness for split notes.
|
||||
///
|
||||
/// If it is not a split note, this field is `None`.
|
||||
rseed_split_note: CtOption<RandomSeed>,
|
||||
}
|
||||
|
||||
impl PartialEq for Note {
|
||||
|
@ -144,6 +159,7 @@ impl Note {
|
|||
asset,
|
||||
rho,
|
||||
rseed,
|
||||
rseed_split_note: CtOption::new(rseed, 0u8.into()),
|
||||
};
|
||||
CtOption::new(note, note.commitment_inner().is_some())
|
||||
}
|
||||
|
@ -219,6 +235,11 @@ impl Note {
|
|||
&self.rseed
|
||||
}
|
||||
|
||||
/// Returns the rseed_split_note value of this note.
|
||||
pub fn rseed_split_note(&self) -> CtOption<RandomSeed> {
|
||||
self.rseed_split_note
|
||||
}
|
||||
|
||||
/// Derives the ephemeral secret key for this note.
|
||||
pub(crate) fn esk(&self) -> EphemeralSecretKey {
|
||||
EphemeralSecretKey(self.rseed.esk(&self.rho))
|
||||
|
@ -264,13 +285,25 @@ impl Note {
|
|||
|
||||
/// Derives the nullifier for this note.
|
||||
pub fn nullifier(&self, fvk: &FullViewingKey) -> Nullifier {
|
||||
let selected_rseed = self.rseed_split_note.unwrap_or(self.rseed);
|
||||
|
||||
Nullifier::derive(
|
||||
fvk.nk(),
|
||||
self.rho.0,
|
||||
self.rseed.psi(&self.rho),
|
||||
selected_rseed.psi(&self.rho),
|
||||
self.commitment(),
|
||||
self.rseed_split_note.is_some(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a split note which has the same values than the input note except for
|
||||
/// `rseed_split_note` which is equal to a random seed.
|
||||
pub fn create_split_note(self, rng: &mut impl RngCore) -> Self {
|
||||
Note {
|
||||
rseed_split_note: CtOption::new(RandomSeed::random(rng, &self.rho), 1u8.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An encrypted note.
|
||||
|
@ -308,6 +341,8 @@ pub mod testing {
|
|||
address::testing::arb_address, note::nullifier::testing::arb_nullifier, value::NoteValue,
|
||||
};
|
||||
|
||||
use subtle::CtOption;
|
||||
|
||||
use super::{Note, RandomSeed};
|
||||
|
||||
prop_compose! {
|
||||
|
@ -331,6 +366,7 @@ pub mod testing {
|
|||
asset,
|
||||
rho,
|
||||
rseed,
|
||||
rseed_split_note: CtOption::new(rseed, 0u8.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -349,6 +385,7 @@ pub mod testing {
|
|||
asset: AssetBase::native(),
|
||||
rho,
|
||||
rseed,
|
||||
rseed_split_note: CtOption::new(rseed, 0u8.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -367,6 +404,7 @@ pub mod testing {
|
|||
asset,
|
||||
rho,
|
||||
rseed,
|
||||
rseed_split_note: CtOption::new(rseed, 0u8.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use halo2_proofs::arithmetic::CurveExt;
|
|||
use memuse::DynamicUsage;
|
||||
use pasta_curves::pallas;
|
||||
use rand::RngCore;
|
||||
use subtle::CtOption;
|
||||
use subtle::{Choice, ConditionallySelectable, CtOption};
|
||||
|
||||
use super::NoteCommitment;
|
||||
use crate::{
|
||||
|
@ -55,10 +55,18 @@ impl Nullifier {
|
|||
rho: pallas::Base,
|
||||
psi: pallas::Base,
|
||||
cm: NoteCommitment,
|
||||
is_split_note: Choice,
|
||||
) -> Self {
|
||||
let k = pallas::Point::hash_to_curve("z.cash:Orchard")(b"K");
|
||||
let l = pallas::Point::hash_to_curve("z.cash:Orchard")(b"L");
|
||||
|
||||
Nullifier(extract_p(&(k * mod_r_p(nk.prf_nf(rho) + psi) + cm.0)))
|
||||
let nullifier = k * mod_r_p(nk.prf_nf(rho) + psi) + cm.0;
|
||||
let split_note_nullifier = nullifier + l;
|
||||
|
||||
let selected_nullifier =
|
||||
pallas::Point::conditional_select(&nullifier, &split_note_nullifier, is_split_note);
|
||||
|
||||
Nullifier(extract_p(&(selected_nullifier)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue