Skip to content

Commit

Permalink
Merge branch 'zsa1' into asset_base_non_identity
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulLaux committed Jun 21, 2023
2 parents 048052e + aa1d895 commit 395cb00
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 @@ -601,7 +601,8 @@ mod tests {
};
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};
Expand All @@ -623,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));
Expand Down Expand Up @@ -945,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])
Expand Down Expand Up @@ -1177,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();

Expand Down Expand Up @@ -1272,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
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 @@ -102,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)
Expand Down Expand Up @@ -136,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 {
Expand All @@ -165,21 +165,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 395cb00

Please sign in to comment.