Merge branch 'zsa1' into upgrade_librustzcash_for_orchard_v05_new

This commit is contained in:
Dmitry Demin 2023-06-23 13:06:22 +02:00
commit d4ff716bb8
15 changed files with 995 additions and 913 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

@ -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, &note| {
//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());

View File

@ -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();

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

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

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

View File

@ -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] {

View File

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

View File

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

View File

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