mirror of https://github.com/zcash/orchard.git
Merge branch 'zsa1' into upgrade_librustzcash_for_orchard_v05_new
This commit is contained in:
commit
d4ff716bb8
|
@ -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.
130
src/issuance.rs
130
src/issuance.rs
|
@ -1,5 +1,6 @@
|
|||
//! Structs related to issuance bundles and the associated logic.
|
||||
use blake2b_simd::Hash as Blake2bHash;
|
||||
use group::Group;
|
||||
use nonempty::NonEmpty;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::collections::HashSet;
|
||||
|
@ -7,7 +8,7 @@ use std::fmt;
|
|||
|
||||
use crate::bundle::commitments::{hash_issue_bundle_auth_data, hash_issue_bundle_txid_data};
|
||||
use crate::issuance::Error::{
|
||||
IssueActionNotFound, IssueActionPreviouslyFinalizedAssetBase,
|
||||
AssetBaseCannotBeIdentityPoint, IssueActionNotFound, IssueActionPreviouslyFinalizedAssetBase,
|
||||
IssueActionWithoutNoteNotFinalized, IssueBundleIkMismatchAssetBase,
|
||||
IssueBundleInvalidSignature, ValueSumOverflow, WrongAssetDescSize,
|
||||
};
|
||||
|
@ -134,6 +135,11 @@ impl IssueAction {
|
|||
.notes
|
||||
.iter()
|
||||
.try_fold(ValueSum::zero(), |value_sum, ¬e| {
|
||||
//The asset base should not be the identity point of the Pallas curve.
|
||||
if bool::from(note.asset().cv_base().is_identity()) {
|
||||
return Err(AssetBaseCannotBeIdentityPoint);
|
||||
}
|
||||
|
||||
// All assets should be derived correctly
|
||||
note.asset()
|
||||
.eq(&issue_asset)
|
||||
|
@ -527,6 +533,8 @@ pub enum Error {
|
|||
WrongAssetDescSize,
|
||||
/// The `IssueAction` is not finalized but contains no notes.
|
||||
IssueActionWithoutNoteNotFinalized,
|
||||
/// The `AssetBase` is the Pallas identity point, which is invalid.
|
||||
AssetBaseCannotBeIdentityPoint,
|
||||
|
||||
/// Verification errors:
|
||||
/// Invalid signature.
|
||||
|
@ -561,6 +569,12 @@ impl fmt::Display for Error {
|
|||
"this `IssueAction` contains no notes but is not finalized"
|
||||
)
|
||||
}
|
||||
AssetBaseCannotBeIdentityPoint => {
|
||||
write!(
|
||||
f,
|
||||
"the AssetBase is the identity point of the Pallas curve, which is invalid."
|
||||
)
|
||||
}
|
||||
IssueBundleInvalidSignature(_) => {
|
||||
write!(f, "invalid signature")
|
||||
}
|
||||
|
@ -581,16 +595,21 @@ impl fmt::Display for Error {
|
|||
mod tests {
|
||||
use super::{AssetSupply, IssueBundle, IssueInfo};
|
||||
use crate::issuance::Error::{
|
||||
IssueActionNotFound, IssueActionPreviouslyFinalizedAssetBase,
|
||||
IssueBundleIkMismatchAssetBase, IssueBundleInvalidSignature, WrongAssetDescSize,
|
||||
AssetBaseCannotBeIdentityPoint, IssueActionNotFound,
|
||||
IssueActionPreviouslyFinalizedAssetBase, IssueBundleIkMismatchAssetBase,
|
||||
IssueBundleInvalidSignature, WrongAssetDescSize,
|
||||
};
|
||||
use crate::issuance::{verify_issue_bundle, IssueAction, Signed};
|
||||
use crate::issuance::{verify_issue_bundle, IssueAction, Signed, Unauthorized};
|
||||
use crate::keys::{
|
||||
FullViewingKey, IssuanceAuthorizingKey, IssuanceValidatingKey, Scope, SpendingKey,
|
||||
FullViewingKey, IssuanceAuthorizingKey, IssuanceKey, IssuanceValidatingKey, Scope,
|
||||
SpendingKey,
|
||||
};
|
||||
use crate::note::{AssetBase, Nullifier};
|
||||
use crate::value::{NoteValue, ValueSum};
|
||||
use crate::{Address, Note};
|
||||
use group::{Group, GroupEncoding};
|
||||
use nonempty::NonEmpty;
|
||||
use pasta_curves::pallas::{Point, Scalar};
|
||||
use rand::rngs::OsRng;
|
||||
use rand::RngCore;
|
||||
use reddsa::Error::InvalidSignature;
|
||||
|
@ -605,8 +624,8 @@ mod tests {
|
|||
) {
|
||||
let mut rng = OsRng;
|
||||
|
||||
let sk = SpendingKey::random(&mut rng);
|
||||
let isk: IssuanceAuthorizingKey = (&sk).into();
|
||||
let sk_iss = IssuanceKey::random(&mut rng);
|
||||
let isk: IssuanceAuthorizingKey = (&sk_iss).into();
|
||||
let ik: IssuanceValidatingKey = (&isk).into();
|
||||
|
||||
let fvk = FullViewingKey::from(&SpendingKey::random(&mut rng));
|
||||
|
@ -653,8 +672,49 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
// This function computes the identity point on the Pallas curve and returns an Asset Base with that value.
|
||||
fn identity_point() -> AssetBase {
|
||||
let identity_point = (Point::generator() * -Scalar::one()) + Point::generator();
|
||||
AssetBase::from_bytes(&identity_point.to_bytes()).unwrap()
|
||||
}
|
||||
|
||||
fn identity_point_test_params(
|
||||
note1_value: u64,
|
||||
note2_value: u64,
|
||||
) -> (
|
||||
OsRng,
|
||||
IssuanceAuthorizingKey,
|
||||
IssueBundle<Unauthorized>,
|
||||
[u8; 32],
|
||||
) {
|
||||
let (mut rng, isk, ik, recipient, sighash) = setup_params();
|
||||
|
||||
let note1 = Note::new(
|
||||
recipient,
|
||||
NoteValue::from_raw(note1_value),
|
||||
identity_point(),
|
||||
Nullifier::dummy(&mut rng),
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
let note2 = Note::new(
|
||||
recipient,
|
||||
NoteValue::from_raw(note2_value),
|
||||
identity_point(),
|
||||
Nullifier::dummy(&mut rng),
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
let action =
|
||||
IssueAction::from_parts("arbitrary asset_desc".into(), vec![note1, note2], false);
|
||||
|
||||
let bundle = IssueBundle::from_parts(ik, NonEmpty::new(action), Unauthorized);
|
||||
|
||||
(rng, isk, bundle, sighash)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_supply_valid() {
|
||||
fn verify_supply_valid() {
|
||||
let (ik, test_asset, action) =
|
||||
setup_verify_supply_test_params(10, 20, "Asset 1", None, false);
|
||||
|
||||
|
@ -670,7 +730,17 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_supply_finalized() {
|
||||
fn verify_supply_invalid_for_asset_base_as_identity() {
|
||||
let (_, _, bundle, _) = identity_point_test_params(10, 20);
|
||||
|
||||
assert_eq!(
|
||||
bundle.actions.head.verify_supply(&bundle.ik),
|
||||
Err(AssetBaseCannotBeIdentityPoint)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_supply_finalized() {
|
||||
let (ik, test_asset, action) =
|
||||
setup_verify_supply_test_params(10, 20, "Asset 1", None, true);
|
||||
|
||||
|
@ -686,7 +756,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_supply_incorrect_asset_base() {
|
||||
fn verify_supply_incorrect_asset_base() {
|
||||
let (ik, _, action) =
|
||||
setup_verify_supply_test_params(10, 20, "Asset 1", Some("Asset 2"), false);
|
||||
|
||||
|
@ -697,7 +767,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_supply_ik_mismatch_asset_base() {
|
||||
fn verify_supply_ik_mismatch_asset_base() {
|
||||
let (_, _, action) = setup_verify_supply_test_params(10, 20, "Asset 1", None, false);
|
||||
let (_, _, ik, _, _) = setup_params();
|
||||
|
||||
|
@ -876,7 +946,7 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let wrong_isk: IssuanceAuthorizingKey = (&SpendingKey::random(&mut OsRng)).into();
|
||||
let wrong_isk: IssuanceAuthorizingKey = (&IssuanceKey::random(&mut OsRng)).into();
|
||||
|
||||
let err = bundle
|
||||
.prepare([0; 32])
|
||||
|
@ -1108,7 +1178,7 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let wrong_isk: IssuanceAuthorizingKey = (&SpendingKey::random(&mut rng)).into();
|
||||
let wrong_isk: IssuanceAuthorizingKey = (&IssuanceKey::random(&mut rng)).into();
|
||||
|
||||
let mut signed = bundle.prepare(sighash).sign(rng, &isk).unwrap();
|
||||
|
||||
|
@ -1203,8 +1273,8 @@ mod tests {
|
|||
|
||||
let mut signed = bundle.prepare(sighash).sign(rng, &isk).unwrap();
|
||||
|
||||
let incorrect_sk = SpendingKey::random(&mut rng);
|
||||
let incorrect_isk: IssuanceAuthorizingKey = (&incorrect_sk).into();
|
||||
let incorrect_sk_iss = IssuanceKey::random(&mut rng);
|
||||
let incorrect_isk: IssuanceAuthorizingKey = (&incorrect_sk_iss).into();
|
||||
let incorrect_ik: IssuanceValidatingKey = (&incorrect_isk).into();
|
||||
|
||||
// Add "bad" note
|
||||
|
@ -1272,7 +1342,35 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_finalize_flag_serialization() {
|
||||
fn issue_bundle_cannot_be_signed_with_asset_base_identity_point() {
|
||||
let (rng, isk, bundle, sighash) = identity_point_test_params(10, 20);
|
||||
|
||||
assert_eq!(
|
||||
bundle.prepare(sighash).sign(rng, &isk).unwrap_err(),
|
||||
AssetBaseCannotBeIdentityPoint
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_bundle_verify_fail_asset_base_identity_point() {
|
||||
let (mut rng, isk, bundle, sighash) = identity_point_test_params(10, 20);
|
||||
|
||||
let signed = IssueBundle {
|
||||
ik: bundle.ik,
|
||||
actions: bundle.actions,
|
||||
authorization: Signed {
|
||||
signature: isk.sign(&mut rng, &sighash),
|
||||
},
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
verify_issue_bundle(&signed, sighash, &HashSet::new()).unwrap_err(),
|
||||
AssetBaseCannotBeIdentityPoint
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalize_flag_serialization() {
|
||||
let mut rng = OsRng;
|
||||
let (_, _, note) = Note::dummy(&mut rng, None, AssetBase::native());
|
||||
|
||||
|
|
166
src/keys.rs
166
src/keys.rs
|
@ -11,7 +11,7 @@ use group::{
|
|||
prime::PrimeCurveAffine,
|
||||
Curve, GroupEncoding,
|
||||
};
|
||||
use pasta_curves::pallas;
|
||||
use pasta_curves::{pallas, pallas::Scalar};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
|
||||
use zcash_note_encryption::EphemeralKeyBytes;
|
||||
|
@ -24,11 +24,15 @@ use crate::{
|
|||
to_scalar, NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar,
|
||||
PreparedNonIdentityBase, PreparedNonZeroScalar, PrfExpand,
|
||||
},
|
||||
zip32::{self, ChildIndex, ExtendedSpendingKey},
|
||||
zip32::{
|
||||
self, ChildIndex, ExtendedSpendingKey, ZIP32_ORCHARD_PERSONALIZATION,
|
||||
ZIP32_ORCHARD_PERSONALIZATION_FOR_ISSUANCE,
|
||||
},
|
||||
};
|
||||
|
||||
const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF";
|
||||
const ZIP32_PURPOSE: u32 = 32;
|
||||
const ZIP32_PURPOSE_FOR_ISSUANCE: u32 = 227;
|
||||
|
||||
/// A spending key, from which all key material is derived.
|
||||
///
|
||||
|
@ -99,7 +103,8 @@ impl SpendingKey {
|
|||
ChildIndex::try_from(coin_type)?,
|
||||
ChildIndex::try_from(account)?,
|
||||
];
|
||||
ExtendedSpendingKey::from_path(seed, path).map(|esk| esk.sk())
|
||||
ExtendedSpendingKey::from_path(seed, path, ZIP32_ORCHARD_PERSONALIZATION)
|
||||
.map(|esk| esk.sk())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,13 +137,17 @@ impl From<&SpendingKey> for SpendAuthorizingKey {
|
|||
// SpendingKey cannot be constructed such that this assertion would fail.
|
||||
assert!(!bool::from(ask.is_zero()));
|
||||
// TODO: Add TryFrom<S::Scalar> for SpendAuthorizingKey.
|
||||
let ret = SpendAuthorizingKey(ask.to_repr().try_into().unwrap());
|
||||
// If the last bit of repr_P(ak) is 1, negate ask.
|
||||
if (<[u8; 32]>::from(SpendValidatingKey::from(&ret).0)[31] >> 7) == 1 {
|
||||
SpendAuthorizingKey((-ask).to_repr().try_into().unwrap())
|
||||
} else {
|
||||
ret
|
||||
}
|
||||
SpendAuthorizingKey(conditionally_negate(ask))
|
||||
}
|
||||
}
|
||||
|
||||
// If the last bit of repr_P(ak) is 1, negate ask.
|
||||
fn conditionally_negate<T: redpallas::SigType>(scalar: Scalar) -> redpallas::SigningKey<T> {
|
||||
let ret = redpallas::SigningKey::<T>(scalar.to_repr().try_into().unwrap());
|
||||
if (<[u8; 32]>::from(redpallas::VerificationKey::<T>::from(&ret).0)[31] >> 7) == 1 {
|
||||
redpallas::SigningKey::<T>((-scalar).to_repr().try_into().unwrap())
|
||||
} else {
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,7 +187,7 @@ impl SpendValidatingKey {
|
|||
self.0.randomize(randomizer)
|
||||
}
|
||||
|
||||
/// Converts this issuance validating key to its serialized form,
|
||||
/// Converts this spend key to its serialized form,
|
||||
/// I2LEOSP_256(ak).
|
||||
pub(crate) fn to_bytes(&self) -> [u8; 32] {
|
||||
// This is correct because the wrapped point must have ỹ = 0, and
|
||||
|
@ -194,9 +203,87 @@ impl SpendValidatingKey {
|
|||
}
|
||||
}
|
||||
|
||||
/// A function to check structural validity of the validating keys for authorizing transfers and
|
||||
/// issuing assets
|
||||
/// Structural validity checks for ak_P or ik_P:
|
||||
/// - The point must not be the identity (which for Pallas is canonically encoded as all-zeroes).
|
||||
/// - The compressed y-coordinate bit must be 0.
|
||||
fn check_structural_validity(
|
||||
verification_key_bytes: [u8; 32],
|
||||
) -> Option<VerificationKey<SpendAuth>> {
|
||||
if verification_key_bytes != [0; 32] && verification_key_bytes[31] & 0x80 == 0 {
|
||||
<redpallas::VerificationKey<SpendAuth>>::try_from(verification_key_bytes).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// We currently use `SpendAuth` as the `IssuanceAuth`.
|
||||
type IssuanceAuth = SpendAuth;
|
||||
|
||||
/// An issuance key, from which all key material is derived.
|
||||
///
|
||||
/// $\mathsf{sk}$ as defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
||||
///
|
||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct IssuanceKey([u8; 32]);
|
||||
|
||||
impl From<SpendingKey> for IssuanceKey {
|
||||
fn from(sk: SpendingKey) -> Self {
|
||||
IssuanceKey(*sk.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstantTimeEq for IssuanceKey {
|
||||
fn ct_eq(&self, other: &Self) -> Choice {
|
||||
self.to_bytes().ct_eq(other.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl IssuanceKey {
|
||||
/// Generates a random issuance key.
|
||||
///
|
||||
/// This is only used when generating a random AssetBase.
|
||||
/// Real issuance keys should be derived according to [ZIP 32].
|
||||
///
|
||||
/// [ZIP 32]: https://zips.z.cash/zip-0032
|
||||
pub(crate) fn random(rng: &mut impl RngCore) -> Self {
|
||||
SpendingKey::random(rng).into()
|
||||
}
|
||||
|
||||
/// Constructs an Orchard issuance key from uniformly-random bytes.
|
||||
///
|
||||
/// Returns `None` if the bytes do not correspond to a valid Orchard issuance key.
|
||||
pub fn from_bytes(sk_iss: [u8; 32]) -> CtOption<Self> {
|
||||
let sk_iss = IssuanceKey(sk_iss);
|
||||
// If isk = 0 (A scalar value), discard this key.
|
||||
let isk = IssuanceAuthorizingKey::derive_inner(&sk_iss);
|
||||
CtOption::new(sk_iss, !isk.is_zero())
|
||||
}
|
||||
|
||||
/// Returns the raw bytes of the issuance key.
|
||||
pub fn to_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Derives the Orchard issuance key for the given seed, coin type, and account.
|
||||
pub fn from_zip32_seed(
|
||||
seed: &[u8],
|
||||
coin_type: u32,
|
||||
account: u32,
|
||||
) -> Result<Self, zip32::Error> {
|
||||
// Call zip32 logic
|
||||
let path = &[
|
||||
ChildIndex::try_from(ZIP32_PURPOSE_FOR_ISSUANCE)?,
|
||||
ChildIndex::try_from(coin_type)?,
|
||||
ChildIndex::try_from(account)?,
|
||||
];
|
||||
ExtendedSpendingKey::from_path(seed, path, ZIP32_ORCHARD_PERSONALIZATION_FOR_ISSUANCE)
|
||||
.map(|esk| esk.sk().into())
|
||||
}
|
||||
}
|
||||
|
||||
/// An issuance authorizing key, used to create issuance authorization signatures.
|
||||
/// This type enforces that the corresponding public point (ik^ℙ) has ỹ = 0.
|
||||
///
|
||||
|
@ -208,9 +295,9 @@ type IssuanceAuth = SpendAuth;
|
|||
pub struct IssuanceAuthorizingKey(redpallas::SigningKey<IssuanceAuth>);
|
||||
|
||||
impl IssuanceAuthorizingKey {
|
||||
/// Derives isk from sk. Internal use only, does not enforce all constraints.
|
||||
fn derive_inner(sk: &SpendingKey) -> pallas::Scalar {
|
||||
to_scalar(PrfExpand::ZsaIsk.expand(&sk.0))
|
||||
/// Derives isk from sk_iss. Internal use only, does not enforce all constraints.
|
||||
fn derive_inner(sk_iss: &IssuanceKey) -> pallas::Scalar {
|
||||
to_scalar(PrfExpand::ZsaIsk.expand(&sk_iss.0))
|
||||
}
|
||||
|
||||
/// Sign the provided message using the `IssuanceAuthorizingKey`.
|
||||
|
@ -223,18 +310,12 @@ impl IssuanceAuthorizingKey {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&SpendingKey> for IssuanceAuthorizingKey {
|
||||
fn from(sk: &SpendingKey) -> Self {
|
||||
let isk = Self::derive_inner(sk);
|
||||
impl From<&IssuanceKey> for IssuanceAuthorizingKey {
|
||||
fn from(sk_iss: &IssuanceKey) -> Self {
|
||||
let isk = IssuanceAuthorizingKey::derive_inner(sk_iss);
|
||||
// IssuanceAuthorizingKey cannot be constructed such that this assertion would fail.
|
||||
assert!(!bool::from(isk.is_zero()));
|
||||
let ret = IssuanceAuthorizingKey(isk.to_repr().try_into().unwrap());
|
||||
// If the last bit of repr_P(ik) is 1, negate isk.
|
||||
if (<[u8; 32]>::from(IssuanceValidatingKey::from(&ret).0)[31] >> 7) == 1 {
|
||||
IssuanceAuthorizingKey((-isk).to_repr().try_into().unwrap())
|
||||
} else {
|
||||
ret
|
||||
}
|
||||
IssuanceAuthorizingKey(conditionally_negate(isk))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -297,21 +378,6 @@ impl IssuanceValidatingKey {
|
|||
}
|
||||
}
|
||||
|
||||
/// A function to check structural validity of the validating keys for authorizing transfers and
|
||||
/// issuing assets
|
||||
/// Structural validity checks for ak_P or ik_P:
|
||||
/// - The point must not be the identity (which for Pallas is canonically encoded as all-zeroes).
|
||||
/// - The compressed y-coordinate bit must be 0.
|
||||
fn check_structural_validity(
|
||||
verification_key_bytes: [u8; 32],
|
||||
) -> Option<VerificationKey<SpendAuth>> {
|
||||
if verification_key_bytes != [0; 32] && verification_key_bytes[31] & 0x80 == 0 {
|
||||
<redpallas::VerificationKey<SpendAuth>>::try_from(verification_key_bytes).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A key used to derive [`Nullifier`]s from [`Note`]s.
|
||||
///
|
||||
/// $\mathsf{nk}$ as defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
||||
|
@ -1050,7 +1116,7 @@ impl SharedSecret {
|
|||
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
|
||||
pub mod testing {
|
||||
use super::{
|
||||
DiversifierIndex, DiversifierKey, EphemeralSecretKey, IssuanceAuthorizingKey,
|
||||
DiversifierIndex, DiversifierKey, EphemeralSecretKey, IssuanceAuthorizingKey, IssuanceKey,
|
||||
IssuanceValidatingKey, SpendingKey,
|
||||
};
|
||||
use proptest::prelude::*;
|
||||
|
@ -1070,6 +1136,20 @@ pub mod testing {
|
|||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
/// Generate a uniformly distributed Orchard issuance key.
|
||||
pub fn arb_issuance_key()(
|
||||
key in prop::array::uniform32(prop::num::u8::ANY)
|
||||
.prop_map(IssuanceKey::from_bytes)
|
||||
.prop_filter(
|
||||
"Values must correspond to valid Orchard issuance keys.",
|
||||
|opt| bool::from(opt.is_some())
|
||||
)
|
||||
) -> IssuanceKey {
|
||||
key.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
/// Generate a uniformly distributed Orchard ephemeral secret key.
|
||||
pub fn arb_esk()(
|
||||
|
@ -1106,7 +1186,7 @@ pub mod testing {
|
|||
/// Generate a uniformly distributed RedDSA issuance authorizing key.
|
||||
pub fn arb_issuance_authorizing_key()(rng_seed in prop::array::uniform32(prop::num::u8::ANY)) -> IssuanceAuthorizingKey {
|
||||
let mut rng = StdRng::from_seed(rng_seed);
|
||||
IssuanceAuthorizingKey::from(&SpendingKey::random(&mut rng))
|
||||
IssuanceAuthorizingKey::from(&IssuanceKey::random(&mut rng))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1187,7 +1267,9 @@ mod tests {
|
|||
let ask: SpendAuthorizingKey = (&sk).into();
|
||||
assert_eq!(<[u8; 32]>::from(&ask.0), tv.ask);
|
||||
|
||||
let isk: IssuanceAuthorizingKey = (&sk).into();
|
||||
let sk_iss = IssuanceKey::from_bytes(tv.sk).unwrap();
|
||||
|
||||
let isk: IssuanceAuthorizingKey = (&sk_iss).into();
|
||||
assert_eq!(<[u8; 32]>::from(&isk.0), tv.isk);
|
||||
|
||||
let ak: SpendValidatingKey = (&ask).into();
|
||||
|
|
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use blake2b_simd::{Hash as Blake2bHash, Params};
|
||||
use group::GroupEncoding;
|
||||
use group::{Group, GroupEncoding};
|
||||
use halo2_proofs::arithmetic::CurveExt;
|
||||
use pasta_curves::pallas;
|
||||
use rand::RngCore;
|
||||
|
@ -10,7 +10,7 @@ use subtle::{Choice, ConstantTimeEq, CtOption};
|
|||
use crate::constants::fixed_bases::{
|
||||
NATIVE_ASSET_BASE_V_BYTES, VALUE_COMMITMENT_PERSONALIZATION, ZSA_ASSET_BASE_PERSONALIZATION,
|
||||
};
|
||||
use crate::keys::{IssuanceAuthorizingKey, IssuanceValidatingKey, SpendingKey};
|
||||
use crate::keys::{IssuanceAuthorizingKey, IssuanceKey, IssuanceValidatingKey};
|
||||
|
||||
/// Note type identifier.
|
||||
#[derive(Clone, Copy, Debug, Eq)]
|
||||
|
@ -54,10 +54,13 @@ impl AssetBase {
|
|||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `asset_desc` is empty or greater than `MAX_ASSET_DESCRIPTION_SIZE`.
|
||||
/// Panics if `asset_desc` is empty or greater than `MAX_ASSET_DESCRIPTION_SIZE` or if the derived Asset Base is the identity point.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn derive(ik: &IssuanceValidatingKey, asset_desc: &str) -> Self {
|
||||
assert!(is_asset_desc_of_valid_size(asset_desc));
|
||||
assert!(
|
||||
is_asset_desc_of_valid_size(asset_desc),
|
||||
"The asset_desc string is not of valid size"
|
||||
);
|
||||
|
||||
// EncodeAssetId(ik, asset_desc) = version_byte || ik || asset_desc
|
||||
let version_byte = [0x00];
|
||||
|
@ -65,10 +68,17 @@ impl AssetBase {
|
|||
|
||||
let asset_digest = asset_digest(encode_asset_id);
|
||||
|
||||
let asset_base =
|
||||
pallas::Point::hash_to_curve(ZSA_ASSET_BASE_PERSONALIZATION)(asset_digest.as_bytes());
|
||||
|
||||
// this will happen with negligible probability.
|
||||
assert!(
|
||||
bool::from(!asset_base.is_identity()),
|
||||
"The Asset Base is the identity point, which is invalid."
|
||||
);
|
||||
|
||||
// AssetBase = ZSAValueBase(AssetDigest)
|
||||
AssetBase(
|
||||
pallas::Point::hash_to_curve(ZSA_ASSET_BASE_PERSONALIZATION)(asset_digest.as_bytes()),
|
||||
)
|
||||
AssetBase(asset_base)
|
||||
}
|
||||
|
||||
/// Note type for the "native" currency (zec), maintains backward compatibility with Orchard untyped notes.
|
||||
|
@ -92,8 +102,8 @@ impl AssetBase {
|
|||
///
|
||||
/// This is only used in tests.
|
||||
pub(crate) fn random(rng: &mut impl RngCore) -> Self {
|
||||
let sk = SpendingKey::random(rng);
|
||||
let isk = IssuanceAuthorizingKey::from(&sk);
|
||||
let sk_iss = IssuanceKey::random(rng);
|
||||
let isk = IssuanceAuthorizingKey::from(&sk_iss);
|
||||
let ik = IssuanceValidatingKey::from(&isk);
|
||||
let asset_descr = "zsa_asset";
|
||||
AssetBase::derive(&ik, asset_descr)
|
||||
|
@ -126,13 +136,13 @@ pub mod testing {
|
|||
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::keys::{testing::arb_spending_key, IssuanceAuthorizingKey, IssuanceValidatingKey};
|
||||
use crate::keys::{testing::arb_issuance_key, IssuanceAuthorizingKey, IssuanceValidatingKey};
|
||||
|
||||
prop_compose! {
|
||||
/// Generate a uniformly distributed note type
|
||||
pub fn arb_asset_id()(
|
||||
is_native in prop::bool::ANY,
|
||||
sk in arb_spending_key(),
|
||||
sk in arb_issuance_key(),
|
||||
str in "[A-Za-z]{255}",
|
||||
) -> AssetBase {
|
||||
if is_native {
|
||||
|
@ -155,10 +165,10 @@ pub mod testing {
|
|||
prop_compose! {
|
||||
/// Generate an asset ID
|
||||
pub fn arb_zsa_asset_id()(
|
||||
sk in arb_spending_key(),
|
||||
sk_iss in arb_issuance_key(),
|
||||
str in "[A-Za-z]{255}"
|
||||
) -> AssetBase {
|
||||
let isk = IssuanceAuthorizingKey::from(&sk);
|
||||
let isk = IssuanceAuthorizingKey::from(&sk_iss);
|
||||
AssetBase::derive(&IssuanceValidatingKey::from(&isk), &str)
|
||||
}
|
||||
}
|
||||
|
@ -166,10 +176,10 @@ pub mod testing {
|
|||
prop_compose! {
|
||||
/// Generate an asset ID using a specific description
|
||||
pub fn zsa_asset_id(asset_desc: String)(
|
||||
sk in arb_spending_key(),
|
||||
sk_iss in arb_issuance_key(),
|
||||
) -> AssetBase {
|
||||
assert!(super::is_asset_desc_of_valid_size(&asset_desc));
|
||||
let isk = IssuanceAuthorizingKey::from(&sk);
|
||||
let isk = IssuanceAuthorizingKey::from(&sk_iss);
|
||||
AssetBase::derive(&IssuanceValidatingKey::from(&isk), &asset_desc)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ impl SigType for Binding {}
|
|||
|
||||
/// A RedPallas signing key.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SigningKey<T: SigType>(reddsa::SigningKey<T>);
|
||||
pub struct SigningKey<T: SigType>(pub(crate) reddsa::SigningKey<T>);
|
||||
|
||||
impl<T: SigType> From<SigningKey<T>> for [u8; 32] {
|
||||
fn from(sk: SigningKey<T>) -> [u8; 32] {
|
||||
|
@ -63,7 +63,7 @@ impl<T: SigType> SigningKey<T> {
|
|||
|
||||
/// A RedPallas verification key.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VerificationKey<T: SigType>(reddsa::VerificationKey<T>);
|
||||
pub struct VerificationKey<T: SigType>(pub(crate) reddsa::VerificationKey<T>);
|
||||
|
||||
impl<T: SigType> From<VerificationKey<T>> for [u8; 32] {
|
||||
fn from(vk: VerificationKey<T>) -> [u8; 32] {
|
||||
|
|
|
@ -80,10 +80,10 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
fn create_test_asset(asset_desc: &str) -> AssetBase {
|
||||
use crate::keys::{IssuanceAuthorizingKey, IssuanceValidatingKey, SpendingKey};
|
||||
use crate::keys::{IssuanceAuthorizingKey, IssuanceKey, IssuanceValidatingKey};
|
||||
|
||||
let sk = SpendingKey::from_bytes([0u8; 32]).unwrap();
|
||||
let isk: IssuanceAuthorizingKey = (&sk).into();
|
||||
let sk_iss = IssuanceKey::from_bytes([0u8; 32]).unwrap();
|
||||
let isk: IssuanceAuthorizingKey = (&sk_iss).into();
|
||||
|
||||
AssetBase::derive(&IssuanceValidatingKey::from(&isk), asset_desc)
|
||||
}
|
||||
|
|
41
src/zip32.rs
41
src/zip32.rs
|
@ -10,8 +10,11 @@ use crate::{
|
|||
spec::PrfExpand,
|
||||
};
|
||||
|
||||
const ZIP32_ORCHARD_PERSONALIZATION: &[u8; 16] = b"ZcashIP32Orchard";
|
||||
const ZIP32_ORCHARD_FVFP_PERSONALIZATION: &[u8; 16] = b"ZcashOrchardFVFP";
|
||||
/// Personalization for the master extended spending key
|
||||
pub const ZIP32_ORCHARD_PERSONALIZATION: &[u8; 16] = b"ZcashIP32Orchard";
|
||||
/// Personalization for the master extended issuance key
|
||||
pub const ZIP32_ORCHARD_PERSONALIZATION_FOR_ISSUANCE: &[u8; 16] = b"ZIP32ZSAIssue_V1";
|
||||
|
||||
/// Errors produced in derivation of extended spending keys
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
@ -117,8 +120,12 @@ impl ExtendedSpendingKey {
|
|||
/// # Panics
|
||||
///
|
||||
/// Panics if seed results in invalid spending key.
|
||||
pub fn from_path(seed: &[u8], path: &[ChildIndex]) -> Result<Self, Error> {
|
||||
let mut xsk = Self::master(seed)?;
|
||||
pub fn from_path(
|
||||
seed: &[u8],
|
||||
path: &[ChildIndex],
|
||||
personalization: &[u8; 16],
|
||||
) -> Result<Self, Error> {
|
||||
let mut xsk = Self::master(seed, personalization)?;
|
||||
for i in path {
|
||||
xsk = xsk.derive_child(*i)?;
|
||||
}
|
||||
|
@ -134,13 +141,13 @@ impl ExtendedSpendingKey {
|
|||
/// # Panics
|
||||
///
|
||||
/// Panics if the seed is shorter than 32 bytes or longer than 252 bytes.
|
||||
fn master(seed: &[u8]) -> Result<Self, Error> {
|
||||
fn master(seed: &[u8], personalization: &[u8; 16]) -> Result<Self, Error> {
|
||||
assert!(seed.len() >= 32 && seed.len() <= 252);
|
||||
// I := BLAKE2b-512("ZcashIP32Orchard", seed)
|
||||
let I: [u8; 64] = {
|
||||
let mut I = Blake2bParams::new()
|
||||
.hash_length(64)
|
||||
.personal(ZIP32_ORCHARD_PERSONALIZATION)
|
||||
.personal(personalization)
|
||||
.to_state();
|
||||
I.update(seed);
|
||||
I.finalize().as_bytes().try_into().unwrap()
|
||||
|
@ -213,7 +220,7 @@ mod tests {
|
|||
#[test]
|
||||
fn derive_child() {
|
||||
let seed = [0; 32];
|
||||
let xsk_m = ExtendedSpendingKey::master(&seed).unwrap();
|
||||
let xsk_m = ExtendedSpendingKey::master(&seed, ZIP32_ORCHARD_PERSONALIZATION).unwrap();
|
||||
|
||||
let i_5 = 5;
|
||||
let xsk_5 = xsk_m.derive_child(i_5.try_into().unwrap());
|
||||
|
@ -224,20 +231,28 @@ mod tests {
|
|||
#[test]
|
||||
fn path() {
|
||||
let seed = [0; 32];
|
||||
let xsk_m = ExtendedSpendingKey::master(&seed).unwrap();
|
||||
let xsk_m = ExtendedSpendingKey::master(&seed, ZIP32_ORCHARD_PERSONALIZATION).unwrap();
|
||||
|
||||
let xsk_5h = xsk_m.derive_child(5.try_into().unwrap()).unwrap();
|
||||
assert!(bool::from(
|
||||
ExtendedSpendingKey::from_path(&seed, &[5.try_into().unwrap()])
|
||||
.unwrap()
|
||||
.ct_eq(&xsk_5h)
|
||||
ExtendedSpendingKey::from_path(
|
||||
&seed,
|
||||
&[5.try_into().unwrap()],
|
||||
ZIP32_ORCHARD_PERSONALIZATION
|
||||
)
|
||||
.unwrap()
|
||||
.ct_eq(&xsk_5h)
|
||||
));
|
||||
|
||||
let xsk_5h_7 = xsk_5h.derive_child(7.try_into().unwrap()).unwrap();
|
||||
assert!(bool::from(
|
||||
ExtendedSpendingKey::from_path(&seed, &[5.try_into().unwrap(), 7.try_into().unwrap()])
|
||||
.unwrap()
|
||||
.ct_eq(&xsk_5h_7)
|
||||
ExtendedSpendingKey::from_path(
|
||||
&seed,
|
||||
&[5.try_into().unwrap(), 7.try_into().unwrap()],
|
||||
ZIP32_ORCHARD_PERSONALIZATION
|
||||
)
|
||||
.unwrap()
|
||||
.ct_eq(&xsk_5h_7)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,10 @@ use orchard::{
|
|||
builder::Builder,
|
||||
bundle::Flags,
|
||||
circuit::{ProvingKey, VerifyingKey},
|
||||
keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey},
|
||||
keys::{
|
||||
FullViewingKey, IssuanceKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey,
|
||||
SpendingKey,
|
||||
},
|
||||
value::NoteValue,
|
||||
Address, Anchor, Bundle, Note,
|
||||
};
|
||||
|
@ -58,7 +61,8 @@ fn prepare_keys() -> Keychain {
|
|||
let fvk = FullViewingKey::from(&sk);
|
||||
let recipient = fvk.address_at(0u32, Scope::External);
|
||||
|
||||
let isk = IssuanceAuthorizingKey::from(&sk);
|
||||
let sk_iss = IssuanceKey::from_bytes([0; 32]).unwrap();
|
||||
let isk = IssuanceAuthorizingKey::from(&sk_iss);
|
||||
let ik = IssuanceValidatingKey::from(&isk);
|
||||
Keychain {
|
||||
pk,
|
||||
|
|
Loading…
Reference in New Issue