Skip to content

Commit

Permalink
Fix issuance key derivation (#74)
Browse files Browse the repository at this point in the history
Updated constants for master (extended) issuance key according to ZIP
227. Previously, we used the same personalization for the master
extended spending key and the master extended issuance key, as well as
the same purpose constant for the spending master key and the issuance
master key.

Now, the following updates have been made:
- Personalization for the master extended issuance key: ZIP32ZSAIssue_V1
- Purpose constant for the issuance master key: 227"
  • Loading branch information
ConstanceBeguier committed Jun 20, 2023
1 parent 5b003f8 commit aa1d895
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 78 deletions.
15 changes: 8 additions & 7 deletions src/issuance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,8 @@ mod tests {
};
use crate::issuance::{verify_issue_bundle, IssueAction, Signed};
use crate::keys::{
FullViewingKey, IssuanceAuthorizingKey, IssuanceValidatingKey, Scope, SpendingKey,
FullViewingKey, IssuanceAuthorizingKey, IssuanceKey, IssuanceValidatingKey, Scope,
SpendingKey,
};
use crate::note::{AssetBase, Nullifier};
use crate::value::{NoteValue, ValueSum};
Expand All @@ -605,8 +606,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));
Expand Down Expand Up @@ -876,7 +877,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])
Expand Down Expand Up @@ -1108,7 +1109,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();

Expand Down Expand Up @@ -1203,8 +1204,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
Expand Down
166 changes: 124 additions & 42 deletions src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
///
Expand Down Expand Up @@ -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())
}
}

Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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
Expand All @@ -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.
///
Expand All @@ -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`.
Expand All @@ -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))
}
}

Expand Down Expand Up @@ -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].
Expand Down Expand Up @@ -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::*;
Expand All @@ -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()(
Expand Down Expand Up @@ -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))
}
}

Expand Down Expand Up @@ -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();
Expand Down
18 changes: 9 additions & 9 deletions src/note/asset_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -92,8 +92,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)
Expand Down Expand Up @@ -126,13 +126,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 {
Expand All @@ -155,21 +155,21 @@ 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)
}
}

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)
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/primitives/redpallas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand Down Expand Up @@ -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] {
Expand Down
Loading

0 comments on commit aa1d895

Please sign in to comment.