Add `sapling::keys::DiversifiedTransmissionKey`
This commit is contained in:
parent
06ba399d80
commit
ded14adbb3
|
@ -241,9 +241,6 @@ pub fn decode_extended_full_viewing_key(
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use group::Group;
|
/// use group::Group;
|
||||||
/// use jubjub::SubgroupPoint;
|
|
||||||
/// use rand_core::SeedableRng;
|
|
||||||
/// use rand_xorshift::XorShiftRng;
|
|
||||||
/// use zcash_client_backend::{
|
/// use zcash_client_backend::{
|
||||||
/// encoding::encode_payment_address,
|
/// encoding::encode_payment_address,
|
||||||
/// };
|
/// };
|
||||||
|
@ -252,15 +249,12 @@ pub fn decode_extended_full_viewing_key(
|
||||||
/// sapling::{Diversifier, PaymentAddress},
|
/// sapling::{Diversifier, PaymentAddress},
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// let rng = &mut XorShiftRng::from_seed([
|
/// let pa = PaymentAddress::from_bytes(&[
|
||||||
/// 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
|
/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x8e, 0x11,
|
||||||
/// 0xbc, 0xe5,
|
/// 0x9d, 0x72, 0x99, 0x2b, 0x56, 0x0d, 0x26, 0x50, 0xff, 0xe0, 0xbe, 0x7f, 0x35, 0x42,
|
||||||
/// ]);
|
/// 0xfd, 0x97, 0x00, 0x3c, 0xb7, 0xcc, 0x3a, 0xbf, 0xf8, 0x1a, 0x7f, 0x90, 0x37, 0xf3,
|
||||||
///
|
/// 0xea,
|
||||||
/// let pa = PaymentAddress::from_parts(
|
/// ])
|
||||||
/// Diversifier([0u8; 11]),
|
|
||||||
/// SubgroupPoint::random(rng),
|
|
||||||
/// )
|
|
||||||
/// .unwrap();
|
/// .unwrap();
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
|
@ -291,9 +285,6 @@ pub fn encode_payment_address_p<P: consensus::Parameters>(
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use group::Group;
|
/// use group::Group;
|
||||||
/// use jubjub::SubgroupPoint;
|
|
||||||
/// use rand_core::SeedableRng;
|
|
||||||
/// use rand_xorshift::XorShiftRng;
|
|
||||||
/// use zcash_client_backend::{
|
/// use zcash_client_backend::{
|
||||||
/// encoding::decode_payment_address,
|
/// encoding::decode_payment_address,
|
||||||
/// };
|
/// };
|
||||||
|
@ -302,15 +293,12 @@ pub fn encode_payment_address_p<P: consensus::Parameters>(
|
||||||
/// sapling::{Diversifier, PaymentAddress},
|
/// sapling::{Diversifier, PaymentAddress},
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// let rng = &mut XorShiftRng::from_seed([
|
/// let pa = PaymentAddress::from_bytes(&[
|
||||||
/// 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
|
/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x8e, 0x11,
|
||||||
/// 0xbc, 0xe5,
|
/// 0x9d, 0x72, 0x99, 0x2b, 0x56, 0x0d, 0x26, 0x50, 0xff, 0xe0, 0xbe, 0x7f, 0x35, 0x42,
|
||||||
/// ]);
|
/// 0xfd, 0x97, 0x00, 0x3c, 0xb7, 0xcc, 0x3a, 0xbf, 0xf8, 0x1a, 0x7f, 0x90, 0x37, 0xf3,
|
||||||
///
|
/// 0xea,
|
||||||
/// let pa = PaymentAddress::from_parts(
|
/// ])
|
||||||
/// Diversifier([0u8; 11]),
|
|
||||||
/// SubgroupPoint::random(rng),
|
|
||||||
/// )
|
|
||||||
/// .unwrap();
|
/// .unwrap();
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
|
@ -461,14 +449,7 @@ pub fn decode_transparent_address(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use group::Group;
|
use zcash_primitives::{constants, sapling::PaymentAddress, zip32::ExtendedSpendingKey};
|
||||||
use rand_core::SeedableRng;
|
|
||||||
use rand_xorshift::XorShiftRng;
|
|
||||||
use zcash_primitives::{
|
|
||||||
constants,
|
|
||||||
sapling::{Diversifier, PaymentAddress},
|
|
||||||
zip32::ExtendedSpendingKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
decode_extended_full_viewing_key, decode_extended_spending_key, decode_payment_address,
|
decode_extended_full_viewing_key, decode_extended_spending_key, decode_payment_address,
|
||||||
|
@ -559,14 +540,13 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn payment_address() {
|
fn payment_address() {
|
||||||
let rng = &mut XorShiftRng::from_seed([
|
let addr = PaymentAddress::from_bytes(&[
|
||||||
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x8e, 0x11,
|
||||||
0xbc, 0xe5,
|
0x9d, 0x72, 0x99, 0x2b, 0x56, 0x0d, 0x26, 0x50, 0xff, 0xe0, 0xbe, 0x7f, 0x35, 0x42,
|
||||||
]);
|
0xfd, 0x97, 0x00, 0x3c, 0xb7, 0xcc, 0x3a, 0xbf, 0xf8, 0x1a, 0x7f, 0x90, 0x37, 0xf3,
|
||||||
|
0xea,
|
||||||
let addr =
|
])
|
||||||
PaymentAddress::from_parts(Diversifier([0u8; 11]), jubjub::SubgroupPoint::random(rng))
|
.unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let encoded_main =
|
let encoded_main =
|
||||||
"zs1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75c8v35z";
|
"zs1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75c8v35z";
|
||||||
|
|
|
@ -8,6 +8,7 @@ and this library adheres to Rust's notion of
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
- `zcash_primitives::sapling`:
|
- `zcash_primitives::sapling`:
|
||||||
|
- `keys::DiversifiedTransmissionKey`
|
||||||
- `keys::{EphemeralSecretKey, EphemeralPublicKey, SharedSecret}`
|
- `keys::{EphemeralSecretKey, EphemeralPublicKey, SharedSecret}`
|
||||||
- `note`, a module containing types related to Sapling notes. The existing
|
- `note`, a module containing types related to Sapling notes. The existing
|
||||||
`Note` and `Rseed` types are re-exported here, and new types are added.
|
`Note` and `Rseed` types are re-exported here, and new types are added.
|
||||||
|
@ -27,6 +28,11 @@ and this library adheres to Rust's notion of
|
||||||
- `zcash_primitives::sapling`:
|
- `zcash_primitives::sapling`:
|
||||||
- `PaymentAddress::from_parts` now rejects invalid diversifiers.
|
- `PaymentAddress::from_parts` now rejects invalid diversifiers.
|
||||||
- `PaymentAddress::create_note` is now infallible.
|
- `PaymentAddress::create_note` is now infallible.
|
||||||
|
- `DiversifiedTransmissionKey` is now used instead of `jubjub::SubgroupPoint`
|
||||||
|
in the following places:
|
||||||
|
- `PaymentAddress::from_parts`
|
||||||
|
- `PaymentAddress::pk_d`
|
||||||
|
- `note_encryption::SaplingDomain::DiversifiedTransmissionKey`
|
||||||
- Note commitments now use
|
- Note commitments now use
|
||||||
`zcash_primitives::sapling::note::ExtractedNoteCommitment` instead of
|
`zcash_primitives::sapling::note::ExtractedNoteCommitment` instead of
|
||||||
`bls12_381::Scalar` in the following places:
|
`bls12_381::Scalar` in the following places:
|
||||||
|
@ -65,6 +71,7 @@ and this library adheres to Rust's notion of
|
||||||
- `NoteValue` (use `zcash_primitives::sapling::value::NoteValue` instead).
|
- `NoteValue` (use `zcash_primitives::sapling::value::NoteValue` instead).
|
||||||
- `ValueCommitment` (use `zcash_primitives::sapling::value::ValueCommitment`
|
- `ValueCommitment` (use `zcash_primitives::sapling::value::ValueCommitment`
|
||||||
or `zcash_proofs::circuit::sapling::ValueCommitmentPreimage` instead).
|
or `zcash_proofs::circuit::sapling::ValueCommitmentPreimage` instead).
|
||||||
|
- `note_encryption::sapling_ka_agree`
|
||||||
- `testing::{arb_note_value, arb_positive_note_value}` (use the methods in
|
- `testing::{arb_note_value, arb_positive_note_value}` (use the methods in
|
||||||
`zcash_primitives::sapling::value::testing` instead).
|
`zcash_primitives::sapling::value::testing` instead).
|
||||||
- `zcash_primitives::transaction::components`:
|
- `zcash_primitives::transaction::components`:
|
||||||
|
|
|
@ -14,7 +14,7 @@ use zcash_primitives::{
|
||||||
},
|
},
|
||||||
prover::mock::MockTxProver,
|
prover::mock::MockTxProver,
|
||||||
value::NoteValue,
|
value::NoteValue,
|
||||||
Diversifier, PaymentAddress, SaplingIvk,
|
Diversifier, SaplingIvk,
|
||||||
},
|
},
|
||||||
transaction::components::sapling::{
|
transaction::components::sapling::{
|
||||||
builder::SaplingBuilder, CompactOutputDescription, GrothProofBytes, OutputDescription,
|
builder::SaplingBuilder, CompactOutputDescription, GrothProofBytes, OutputDescription,
|
||||||
|
@ -34,8 +34,7 @@ fn bench_note_decryption(c: &mut Criterion) {
|
||||||
// Construct a Sapling output.
|
// Construct a Sapling output.
|
||||||
let output: OutputDescription<GrothProofBytes> = {
|
let output: OutputDescription<GrothProofBytes> = {
|
||||||
let diversifier = Diversifier([0; 11]);
|
let diversifier = Diversifier([0; 11]);
|
||||||
let pk_d = diversifier.g_d().unwrap() * valid_ivk.0;
|
let pa = valid_ivk.to_payment_address(diversifier).unwrap();
|
||||||
let pa = PaymentAddress::from_parts(diversifier, pk_d).unwrap();
|
|
||||||
|
|
||||||
let mut builder = SaplingBuilder::new(TEST_NETWORK, height);
|
let mut builder = SaplingBuilder::new(TEST_NETWORK, height);
|
||||||
builder
|
builder
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use group::{Group, GroupEncoding};
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
keys::Diversifier,
|
keys::{DiversifiedTransmissionKey, Diversifier},
|
||||||
note::{Note, Rseed},
|
note::{Note, Rseed},
|
||||||
value::NoteValue,
|
value::NoteValue,
|
||||||
};
|
};
|
||||||
|
@ -15,7 +13,7 @@ use super::{
|
||||||
/// and not the identity).
|
/// and not the identity).
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct PaymentAddress {
|
pub struct PaymentAddress {
|
||||||
pk_d: jubjub::SubgroupPoint,
|
pk_d: DiversifiedTransmissionKey,
|
||||||
diversifier: Diversifier,
|
diversifier: Diversifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,30 +32,17 @@ impl PaymentAddress {
|
||||||
/// Note that we cannot verify in this constructor that `pk_d` is derived from
|
/// Note that we cannot verify in this constructor that `pk_d` is derived from
|
||||||
/// `diversifier`, so addresses for which these values have no known relationship
|
/// `diversifier`, so addresses for which these values have no known relationship
|
||||||
/// (and therefore no-one can receive funds at them) can still be constructed.
|
/// (and therefore no-one can receive funds at them) can still be constructed.
|
||||||
pub fn from_parts(diversifier: Diversifier, pk_d: jubjub::SubgroupPoint) -> Option<Self> {
|
pub fn from_parts(diversifier: Diversifier, pk_d: DiversifiedTransmissionKey) -> Option<Self> {
|
||||||
// Check that the diversifier is valid
|
// Check that the diversifier is valid
|
||||||
diversifier.g_d()?;
|
diversifier.g_d()?;
|
||||||
|
|
||||||
if pk_d.is_identity().into() {
|
if pk_d.is_identity() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(PaymentAddress { pk_d, diversifier })
|
Some(PaymentAddress { pk_d, diversifier })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a PaymentAddress from a diversifier and a Jubjub point.
|
|
||||||
///
|
|
||||||
/// Only for test code, as this explicitly bypasses the invariant. [`Self::g_d`] will
|
|
||||||
/// panic on a `PaymentAddress` that has been constructed by passing an invalid
|
|
||||||
/// diversifier to this method.
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) fn from_parts_unchecked(
|
|
||||||
diversifier: Diversifier,
|
|
||||||
pk_d: jubjub::SubgroupPoint,
|
|
||||||
) -> Self {
|
|
||||||
PaymentAddress { pk_d, diversifier }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses a PaymentAddress from bytes.
|
/// Parses a PaymentAddress from bytes.
|
||||||
pub fn from_bytes(bytes: &[u8; 43]) -> Option<Self> {
|
pub fn from_bytes(bytes: &[u8; 43]) -> Option<Self> {
|
||||||
let diversifier = {
|
let diversifier = {
|
||||||
|
@ -66,7 +51,7 @@ impl PaymentAddress {
|
||||||
Diversifier(tmp)
|
Diversifier(tmp)
|
||||||
};
|
};
|
||||||
|
|
||||||
let pk_d = jubjub::SubgroupPoint::from_bytes(bytes[11..43].try_into().unwrap());
|
let pk_d = DiversifiedTransmissionKey::from_bytes(bytes[11..43].try_into().unwrap());
|
||||||
if pk_d.is_some().into() {
|
if pk_d.is_some().into() {
|
||||||
// The remaining invariants are checked here.
|
// The remaining invariants are checked here.
|
||||||
PaymentAddress::from_parts(diversifier, pk_d.unwrap())
|
PaymentAddress::from_parts(diversifier, pk_d.unwrap())
|
||||||
|
@ -89,7 +74,7 @@ impl PaymentAddress {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `pk_d` for this `PaymentAddress`.
|
/// Returns `pk_d` for this `PaymentAddress`.
|
||||||
pub fn pk_d(&self) -> &jubjub::SubgroupPoint {
|
pub fn pk_d(&self) -> &DiversifiedTransmissionKey {
|
||||||
&self.pk_d
|
&self.pk_d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ use super::{
|
||||||
},
|
},
|
||||||
spec::{
|
spec::{
|
||||||
crh_ivk, diversify_hash, ka_sapling_agree, ka_sapling_agree_prepared,
|
crh_ivk, diversify_hash, ka_sapling_agree, ka_sapling_agree_prepared,
|
||||||
ka_sapling_derive_public,
|
ka_sapling_derive_public, ka_sapling_derive_public_subgroup_prepared, PreparedBaseSubgroup,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -24,7 +24,7 @@ use crate::{
|
||||||
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
|
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
|
||||||
use ff::PrimeField;
|
use ff::PrimeField;
|
||||||
use group::{Curve, Group, GroupEncoding};
|
use group::{Curve, Group, GroupEncoding};
|
||||||
use subtle::{ConstantTimeEq, CtOption};
|
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
|
||||||
use zcash_note_encryption::EphemeralKeyBytes;
|
use zcash_note_encryption::EphemeralKeyBytes;
|
||||||
|
|
||||||
/// Errors that can occur in the decoding of Sapling spending keys.
|
/// Errors that can occur in the decoding of Sapling spending keys.
|
||||||
|
@ -241,11 +241,9 @@ pub struct SaplingIvk(pub jubjub::Fr);
|
||||||
|
|
||||||
impl SaplingIvk {
|
impl SaplingIvk {
|
||||||
pub fn to_payment_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
|
pub fn to_payment_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
|
||||||
diversifier.g_d().and_then(|g_d| {
|
let prepared_ivk = PreparedIncomingViewingKey::new(self);
|
||||||
let pk_d = g_d * self.0;
|
DiversifiedTransmissionKey::derive(&prepared_ivk, &diversifier)
|
||||||
|
.and_then(|pk_d| PaymentAddress::from_parts(diversifier, pk_d))
|
||||||
PaymentAddress::from_parts(diversifier, pk_d)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_repr(&self) -> [u8; 32] {
|
pub fn to_repr(&self) -> [u8; 32] {
|
||||||
|
@ -262,6 +260,62 @@ impl Diversifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The diversified transmission key for a given payment address.
|
||||||
|
///
|
||||||
|
/// Defined in [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents].
|
||||||
|
///
|
||||||
|
/// Note that this type is allowed to be the identity in the protocol, but we reject this
|
||||||
|
/// in [`PaymentAddress::from_parts`].
|
||||||
|
///
|
||||||
|
/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub struct DiversifiedTransmissionKey(jubjub::SubgroupPoint);
|
||||||
|
|
||||||
|
impl DiversifiedTransmissionKey {
|
||||||
|
/// Defined in [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents].
|
||||||
|
///
|
||||||
|
/// Returns `None` if `d` is an invalid diversifier.
|
||||||
|
///
|
||||||
|
/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
|
||||||
|
pub(crate) fn derive(ivk: &PreparedIncomingViewingKey, d: &Diversifier) -> Option<Self> {
|
||||||
|
d.g_d()
|
||||||
|
.map(PreparedBaseSubgroup::new)
|
||||||
|
.map(|g_d| ka_sapling_derive_public_subgroup_prepared(ivk.inner(), &g_d))
|
||||||
|
.map(DiversifiedTransmissionKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// $abst_J(bytes)$
|
||||||
|
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
|
||||||
|
jubjub::SubgroupPoint::from_bytes(bytes).map(DiversifiedTransmissionKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// $repr_J(self)$
|
||||||
|
pub(crate) fn to_bytes(self) -> [u8; 32] {
|
||||||
|
self.0.to_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this is the identity.
|
||||||
|
pub(crate) fn is_identity(&self) -> bool {
|
||||||
|
self.0.is_identity().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exposes the inner Jubjub point.
|
||||||
|
///
|
||||||
|
/// This API is exposed for `zcash_proof` usage, and will be removed when this type is
|
||||||
|
/// refactored into the `sapling-crypto` crate.
|
||||||
|
pub fn inner(&self) -> jubjub::SubgroupPoint {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConditionallySelectable for DiversifiedTransmissionKey {
|
||||||
|
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
|
||||||
|
DiversifiedTransmissionKey(jubjub::SubgroupPoint::conditional_select(
|
||||||
|
&a.0, &b.0, choice,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An ephemeral secret key used to encrypt an output note on-chain.
|
/// An ephemeral secret key used to encrypt an output note on-chain.
|
||||||
///
|
///
|
||||||
/// `esk` is "ephemeral" in the sense that each secret key is only used once. In
|
/// `esk` is "ephemeral" in the sense that each secret key is only used once. In
|
||||||
|
@ -290,8 +344,8 @@ impl EphemeralSecretKey {
|
||||||
EphemeralPublicKey(ka_sapling_derive_public(&self.0, &g_d))
|
EphemeralPublicKey(ka_sapling_derive_public(&self.0, &g_d))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn agree(&self, pk_d: &jubjub::ExtendedPoint) -> SharedSecret {
|
pub(crate) fn agree(&self, pk_d: &DiversifiedTransmissionKey) -> SharedSecret {
|
||||||
SharedSecret(ka_sapling_agree(&self.0, pk_d))
|
SharedSecret(ka_sapling_agree(&self.0, &pk_d.0.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
|
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
|
||||||
use byteorder::{LittleEndian, WriteBytesExt};
|
use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
use ff::PrimeField;
|
use ff::PrimeField;
|
||||||
use group::GroupEncoding;
|
|
||||||
use memuse::DynamicUsage;
|
use memuse::DynamicUsage;
|
||||||
use rand_core::RngCore;
|
use rand_core::RngCore;
|
||||||
|
|
||||||
|
@ -20,8 +19,11 @@ use crate::{
|
||||||
consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD},
|
consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD},
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
sapling::{
|
sapling::{
|
||||||
keys::{EphemeralPublicKey, EphemeralSecretKey, OutgoingViewingKey, SharedSecret},
|
keys::{
|
||||||
spec::{PreparedBase, PreparedBaseSubgroup, PreparedScalar},
|
DiversifiedTransmissionKey, EphemeralPublicKey, EphemeralSecretKey, OutgoingViewingKey,
|
||||||
|
SharedSecret,
|
||||||
|
},
|
||||||
|
spec::{PreparedBase, PreparedScalar},
|
||||||
value::ValueCommitment,
|
value::ValueCommitment,
|
||||||
Diversifier, Note, PaymentAddress, Rseed, SaplingIvk,
|
Diversifier, Note, PaymentAddress, Rseed, SaplingIvk,
|
||||||
},
|
},
|
||||||
|
@ -73,13 +75,6 @@ impl PreparedEphemeralPublicKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sapling key agreement for note encryption.
|
|
||||||
///
|
|
||||||
/// Implements section 5.4.4.3 of the Zcash Protocol Specification.
|
|
||||||
pub fn sapling_ka_agree(esk: &jubjub::Fr, pk_d: &jubjub::ExtendedPoint) -> jubjub::SubgroupPoint {
|
|
||||||
EphemeralSecretKey(*esk).agree(pk_d).into_inner()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sapling PRF^ock.
|
/// Sapling PRF^ock.
|
||||||
///
|
///
|
||||||
/// Implemented per section 5.4.2 of the Zcash Protocol Specification.
|
/// Implemented per section 5.4.2 of the Zcash Protocol Specification.
|
||||||
|
@ -111,7 +106,7 @@ fn sapling_parse_note_plaintext_without_memo<F, P: consensus::Parameters>(
|
||||||
get_validated_pk_d: F,
|
get_validated_pk_d: F,
|
||||||
) -> Option<(Note, PaymentAddress)>
|
) -> Option<(Note, PaymentAddress)>
|
||||||
where
|
where
|
||||||
F: FnOnce(&Diversifier) -> Option<jubjub::SubgroupPoint>,
|
F: FnOnce(&Diversifier) -> Option<DiversifiedTransmissionKey>,
|
||||||
{
|
{
|
||||||
assert!(plaintext.len() >= COMPACT_NOTE_SIZE);
|
assert!(plaintext.len() >= COMPACT_NOTE_SIZE);
|
||||||
|
|
||||||
|
@ -176,7 +171,7 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
|
||||||
type SymmetricKey = Blake2bHash;
|
type SymmetricKey = Blake2bHash;
|
||||||
type Note = Note;
|
type Note = Note;
|
||||||
type Recipient = PaymentAddress;
|
type Recipient = PaymentAddress;
|
||||||
type DiversifiedTransmissionKey = jubjub::SubgroupPoint;
|
type DiversifiedTransmissionKey = DiversifiedTransmissionKey;
|
||||||
type IncomingViewingKey = PreparedIncomingViewingKey;
|
type IncomingViewingKey = PreparedIncomingViewingKey;
|
||||||
type OutgoingViewingKey = OutgoingViewingKey;
|
type OutgoingViewingKey = OutgoingViewingKey;
|
||||||
type ValueCommitment = ValueCommitment;
|
type ValueCommitment = ValueCommitment;
|
||||||
|
@ -209,7 +204,7 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
|
||||||
esk: &Self::EphemeralSecretKey,
|
esk: &Self::EphemeralSecretKey,
|
||||||
pk_d: &Self::DiversifiedTransmissionKey,
|
pk_d: &Self::DiversifiedTransmissionKey,
|
||||||
) -> Self::SharedSecret {
|
) -> Self::SharedSecret {
|
||||||
EphemeralSecretKey(*esk).agree(pk_d.into()).into_inner()
|
EphemeralSecretKey(*esk).agree(pk_d).into_inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ka_agree_dec(
|
fn ka_agree_dec(
|
||||||
|
@ -296,7 +291,7 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
|
||||||
plaintext: &[u8],
|
plaintext: &[u8],
|
||||||
) -> Option<(Self::Note, Self::Recipient)> {
|
) -> Option<(Self::Note, Self::Recipient)> {
|
||||||
sapling_parse_note_plaintext_without_memo(self, plaintext, |diversifier| {
|
sapling_parse_note_plaintext_without_memo(self, plaintext, |diversifier| {
|
||||||
Some(&PreparedBaseSubgroup::new(diversifier.g_d()?) * &ivk.0)
|
DiversifiedTransmissionKey::derive(ivk, diversifier)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,7 +321,7 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_pk_d(op: &OutPlaintextBytes) -> Option<Self::DiversifiedTransmissionKey> {
|
fn extract_pk_d(op: &OutPlaintextBytes) -> Option<Self::DiversifiedTransmissionKey> {
|
||||||
jubjub::SubgroupPoint::from_bytes(
|
DiversifiedTransmissionKey::from_bytes(
|
||||||
op.0[0..32].try_into().expect("slice is the correct length"),
|
op.0[0..32].try_into().expect("slice is the correct length"),
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
|
@ -404,15 +399,15 @@ impl<P: consensus::Parameters> BatchDomain for SaplingDomain<P> {
|
||||||
/// note_encryption::sapling_note_encryption,
|
/// note_encryption::sapling_note_encryption,
|
||||||
/// util::generate_random_rseed,
|
/// util::generate_random_rseed,
|
||||||
/// value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
|
/// value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
|
||||||
/// Diversifier, PaymentAddress, Rseed,
|
/// Diversifier, PaymentAddress, Rseed, SaplingIvk,
|
||||||
/// },
|
/// },
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// let mut rng = OsRng;
|
/// let mut rng = OsRng;
|
||||||
///
|
///
|
||||||
|
/// let ivk = SaplingIvk(jubjub::Scalar::random(&mut rng));
|
||||||
/// let diversifier = Diversifier([0; 11]);
|
/// let diversifier = Diversifier([0; 11]);
|
||||||
/// let pk_d = diversifier.g_d().unwrap();
|
/// let to = ivk.to_payment_address(diversifier).unwrap();
|
||||||
/// let to = PaymentAddress::from_parts(diversifier, pk_d).unwrap();
|
|
||||||
/// let ovk = Some(OutgoingViewingKey([0; 32]));
|
/// let ovk = Some(OutgoingViewingKey([0; 32]));
|
||||||
///
|
///
|
||||||
/// let value = NoteValue::from_raw(1000);
|
/// let value = NoteValue::from_raw(1000);
|
||||||
|
@ -549,7 +544,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
use ff::{Field, PrimeField};
|
use ff::{Field, PrimeField};
|
||||||
use group::Group;
|
use group::Group;
|
||||||
use group::{cofactor::CofactorGroup, GroupEncoding};
|
use group::GroupEncoding;
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
use rand_core::{CryptoRng, RngCore};
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
|
||||||
|
@ -573,7 +568,7 @@ mod tests {
|
||||||
keys::OutgoingViewingKey,
|
keys::OutgoingViewingKey,
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
sapling::{
|
sapling::{
|
||||||
keys::{EphemeralPublicKey, EphemeralSecretKey},
|
keys::{DiversifiedTransmissionKey, EphemeralPublicKey, EphemeralSecretKey},
|
||||||
note::ExtractedNoteCommitment,
|
note::ExtractedNoteCommitment,
|
||||||
note_encryption::PreparedIncomingViewingKey,
|
note_encryption::PreparedIncomingViewingKey,
|
||||||
util::generate_random_rseed,
|
util::generate_random_rseed,
|
||||||
|
@ -632,8 +627,7 @@ mod tests {
|
||||||
OutputDescription<sapling::GrothProofBytes>,
|
OutputDescription<sapling::GrothProofBytes>,
|
||||||
) {
|
) {
|
||||||
let diversifier = Diversifier([0; 11]);
|
let diversifier = Diversifier([0; 11]);
|
||||||
let pk_d = diversifier.g_d().unwrap() * ivk.0;
|
let pa = ivk.to_payment_address(diversifier).unwrap();
|
||||||
let pa = PaymentAddress::from_parts_unchecked(diversifier, pk_d);
|
|
||||||
|
|
||||||
// Construct the value commitment for the proof instance
|
// Construct the value commitment for the proof instance
|
||||||
let value = NoteValue::from_raw(100);
|
let value = NoteValue::from_raw(100);
|
||||||
|
@ -669,6 +663,40 @@ mod tests {
|
||||||
(ovk, ock, output)
|
(ovk, ock, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reencrypt_out_ciphertext(
|
||||||
|
ovk: &OutgoingViewingKey,
|
||||||
|
cv: &ValueCommitment,
|
||||||
|
cmu: &ExtractedNoteCommitment,
|
||||||
|
ephemeral_key: &EphemeralKeyBytes,
|
||||||
|
out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
|
||||||
|
modify_plaintext: impl Fn(&mut [u8; OUT_PLAINTEXT_SIZE]),
|
||||||
|
) -> [u8; OUT_CIPHERTEXT_SIZE] {
|
||||||
|
let ock = prf_ock(ovk, cv, &cmu.to_bytes(), ephemeral_key);
|
||||||
|
|
||||||
|
let mut op = [0; OUT_PLAINTEXT_SIZE];
|
||||||
|
op.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]);
|
||||||
|
|
||||||
|
ChaCha20Poly1305::new(ock.as_ref().into())
|
||||||
|
.decrypt_in_place_detached(
|
||||||
|
[0u8; 12][..].into(),
|
||||||
|
&[],
|
||||||
|
&mut op,
|
||||||
|
out_ciphertext[OUT_PLAINTEXT_SIZE..].into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
modify_plaintext(&mut op);
|
||||||
|
|
||||||
|
let tag = ChaCha20Poly1305::new(ock.as_ref().into())
|
||||||
|
.encrypt_in_place_detached([0u8; 12][..].into(), &[], &mut op)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut out_ciphertext = [0u8; OUT_CIPHERTEXT_SIZE];
|
||||||
|
out_ciphertext[..OUT_PLAINTEXT_SIZE].copy_from_slice(&op);
|
||||||
|
out_ciphertext[OUT_PLAINTEXT_SIZE..].copy_from_slice(&tag);
|
||||||
|
out_ciphertext
|
||||||
|
}
|
||||||
|
|
||||||
fn reencrypt_enc_ciphertext(
|
fn reencrypt_enc_ciphertext(
|
||||||
ovk: &OutgoingViewingKey,
|
ovk: &OutgoingViewingKey,
|
||||||
cv: &ValueCommitment,
|
cv: &ValueCommitment,
|
||||||
|
@ -692,11 +720,11 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let pk_d = jubjub::SubgroupPoint::from_bytes(&op[0..32].try_into().unwrap()).unwrap();
|
let pk_d = DiversifiedTransmissionKey::from_bytes(&op[0..32].try_into().unwrap()).unwrap();
|
||||||
|
|
||||||
let esk = jubjub::Fr::from_repr(op[32..OUT_PLAINTEXT_SIZE].try_into().unwrap()).unwrap();
|
let esk = jubjub::Fr::from_repr(op[32..OUT_PLAINTEXT_SIZE].try_into().unwrap()).unwrap();
|
||||||
|
|
||||||
let shared_secret = EphemeralSecretKey(esk).agree(&pk_d.into());
|
let shared_secret = EphemeralSecretKey(esk).agree(&pk_d);
|
||||||
let key = shared_secret.kdf_sapling(ephemeral_key);
|
let key = shared_secret.kdf_sapling(ephemeral_key);
|
||||||
|
|
||||||
let mut plaintext = [0; NOTE_PLAINTEXT_SIZE];
|
let mut plaintext = [0; NOTE_PLAINTEXT_SIZE];
|
||||||
|
@ -1362,9 +1390,16 @@ mod tests {
|
||||||
];
|
];
|
||||||
|
|
||||||
for &height in heights.iter() {
|
for &height in heights.iter() {
|
||||||
let ivk = SaplingIvk(jubjub::Fr::zero());
|
let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng);
|
||||||
let (ovk, ock, output) = random_enc_ciphertext_with(height, &ivk, &mut rng);
|
|
||||||
|
|
||||||
|
*output.out_ciphertext_mut() = reencrypt_out_ciphertext(
|
||||||
|
&ovk,
|
||||||
|
output.cv(),
|
||||||
|
output.cmu(),
|
||||||
|
output.ephemeral_key(),
|
||||||
|
output.out_ciphertext(),
|
||||||
|
|pt| pt[0..32].copy_from_slice(&jubjub::ExtendedPoint::random(rng).to_bytes()),
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,),
|
try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,),
|
||||||
None
|
None
|
||||||
|
@ -1392,9 +1427,9 @@ mod tests {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! read_point {
|
macro_rules! read_pk_d {
|
||||||
($field:expr) => {
|
($field:expr) => {
|
||||||
jubjub::ExtendedPoint::from_bytes(&$field).unwrap()
|
DiversifiedTransmissionKey::from_bytes(&$field).unwrap()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1412,7 +1447,7 @@ mod tests {
|
||||||
//
|
//
|
||||||
|
|
||||||
let ivk = PreparedIncomingViewingKey::new(&SaplingIvk(read_jubjub_scalar!(tv.ivk)));
|
let ivk = PreparedIncomingViewingKey::new(&SaplingIvk(read_jubjub_scalar!(tv.ivk)));
|
||||||
let pk_d = read_point!(tv.default_pk_d).into_subgroup().unwrap();
|
let pk_d = read_pk_d!(tv.default_pk_d);
|
||||||
let rcm = read_jubjub_scalar!(tv.rcm);
|
let rcm = read_jubjub_scalar!(tv.rcm);
|
||||||
let cv = read_cv!(tv.cv);
|
let cv = read_cv!(tv.cv);
|
||||||
let cmu = read_cmu!(tv.cmu);
|
let cmu = read_cmu!(tv.cmu);
|
||||||
|
@ -1423,7 +1458,7 @@ mod tests {
|
||||||
// Test the individual components
|
// Test the individual components
|
||||||
//
|
//
|
||||||
|
|
||||||
let shared_secret = EphemeralSecretKey(esk).agree(&pk_d.into());
|
let shared_secret = EphemeralSecretKey(esk).agree(&pk_d);
|
||||||
assert_eq!(shared_secret.to_bytes(), tv.shared_secret);
|
assert_eq!(shared_secret.to_bytes(), tv.shared_secret);
|
||||||
|
|
||||||
let k_enc = shared_secret.kdf_sapling(&ephemeral_key);
|
let k_enc = shared_secret.kdf_sapling(&ephemeral_key);
|
||||||
|
|
|
@ -98,6 +98,19 @@ pub(crate) fn ka_sapling_derive_public_prepared(
|
||||||
b * sk
|
b * sk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is defined implicitly by [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents]
|
||||||
|
/// which uses $KA^\mathsf{Sapling}.\mathsf{DerivePublic}$ to produce a diversified
|
||||||
|
/// transmission key with type $KA^\mathsf{Sapling}.\mathsf{PublicPrimeSubgroup}$.
|
||||||
|
///
|
||||||
|
/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
|
||||||
|
pub(crate) fn ka_sapling_derive_public_subgroup_prepared(
|
||||||
|
sk: &PreparedScalar,
|
||||||
|
b: &PreparedBaseSubgroup,
|
||||||
|
) -> jubjub::SubgroupPoint {
|
||||||
|
// [sk] b
|
||||||
|
b * sk
|
||||||
|
}
|
||||||
|
|
||||||
/// Defined in [Zcash Protocol Spec § 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement].
|
/// Defined in [Zcash Protocol Spec § 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement].
|
||||||
///
|
///
|
||||||
/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
|
/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
|
||||||
|
|
|
@ -13,7 +13,7 @@ use crate::{
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
merkle_tree::MerklePath,
|
merkle_tree::MerklePath,
|
||||||
sapling::{
|
sapling::{
|
||||||
keys::EphemeralSecretKey,
|
keys::{EphemeralSecretKey, SaplingIvk},
|
||||||
note_encryption::sapling_note_encryption,
|
note_encryption::sapling_note_encryption,
|
||||||
prover::TxProver,
|
prover::TxProver,
|
||||||
redjubjub::{PrivateKey, Signature},
|
redjubjub::{PrivateKey, Signature},
|
||||||
|
@ -454,26 +454,15 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
|
||||||
} else {
|
} else {
|
||||||
// This is a dummy output
|
// This is a dummy output
|
||||||
let dummy_note = {
|
let dummy_note = {
|
||||||
let (diversifier, g_d) = {
|
let payment_address = {
|
||||||
let mut diversifier;
|
let mut diversifier = Diversifier([0; 11]);
|
||||||
let g_d;
|
|
||||||
loop {
|
loop {
|
||||||
let mut d = [0; 11];
|
rng.fill_bytes(&mut diversifier.0);
|
||||||
rng.fill_bytes(&mut d);
|
let dummy_ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
|
||||||
diversifier = Diversifier(d);
|
if let Some(addr) = dummy_ivk.to_payment_address(diversifier) {
|
||||||
if let Some(val) = diversifier.g_d() {
|
break addr;
|
||||||
g_d = val;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(diversifier, g_d)
|
|
||||||
};
|
|
||||||
let payment_address = loop {
|
|
||||||
let dummy_ivk = jubjub::Fr::random(&mut rng);
|
|
||||||
let pk_d = g_d * dummy_ivk;
|
|
||||||
if let Some(addr) = PaymentAddress::from_parts(diversifier, pk_d) {
|
|
||||||
break addr;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let rseed =
|
let rseed =
|
||||||
|
|
|
@ -476,7 +476,7 @@ impl Circuit<bls12_381::Scalar> for Output {
|
||||||
let pk_d = self
|
let pk_d = self
|
||||||
.payment_address
|
.payment_address
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|e| jubjub::ExtendedPoint::from(*e.pk_d()).to_affine());
|
.map(|e| jubjub::ExtendedPoint::from(e.pk_d().inner()).to_affine());
|
||||||
|
|
||||||
// Witness the v-coordinate, encoded as little
|
// Witness the v-coordinate, encoded as little
|
||||||
// endian bits (to match the representation)
|
// endian bits (to match the representation)
|
||||||
|
|
Loading…
Reference in New Issue