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:
Constance Beguier 2023-06-23 11:40:27 +02:00 committed by GitHub
parent daf6269e89
commit 477f949bd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 693 additions and 820 deletions

View File

@ -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,
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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.

View File

@ -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()),
}
}
}

View File

@ -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)))
}
}