Skip to content

Commit

Permalink
update note encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
joe bebel committed Aug 21, 2023
1 parent d130e70 commit c5d822f
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 160 deletions.
46 changes: 10 additions & 36 deletions masp_note_encryption/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;

use core::convert::TryInto;

use chacha20::{
cipher::{StreamCipher, StreamCipherSeek},
ChaCha20,
};
use chacha20poly1305::{aead::AeadInPlace, ChaCha20Poly1305, KeyInit};
use cipher::KeyIvInit;
use core::convert::TryInto;

//use crate::constants::ASSET_IDENTIFIER_LENGTH;
pub const ASSET_IDENTIFIER_LENGTH: usize = 32;
Expand Down Expand Up @@ -176,20 +175,7 @@ pub trait Domain {
fn kdf(secret: Self::SharedSecret, ephemeral_key: &EphemeralKeyBytes) -> Self::SymmetricKey;

/// Encodes the given `Note` and `Memo` as a note plaintext.
///
/// # Future breaking changes
///
/// The `recipient` argument is present as a secondary way to obtain the diversifier;
/// this is due to a historical quirk of how the Sapling `Note` struct was implemented
/// in the `zcash_primitives` crate. `recipient` will be removed from this method in a
/// future crate release, once [`zcash_primitives` has been refactored].
///
/// [`zcash_primitives` has been refactored]: https://github.com/zcash/librustzcash/issues/454
fn note_plaintext_bytes(
note: &Self::Note,
recipient: &Self::Recipient,
memo: &Self::Memo,
) -> NotePlaintextBytes;
fn note_plaintext_bytes(note: &Self::Note, memo: &Self::Memo) -> NotePlaintextBytes;

/// Derives the [`OutgoingCipherKey`] for an encrypted note, given the note-specific
/// public data and an `OutgoingViewingKey`.
Expand Down Expand Up @@ -247,8 +233,6 @@ pub trait Domain {
/// which may be passed via `self`).
/// - The note plaintext contains valid encodings of its various fields.
/// - Any domain-specific requirements are satisfied.
/// - `ephemeral_key` can be derived from `esk` and the diversifier within the note
/// plaintext.
///
/// `&self` is passed here to enable the implementation to enforce contextual checks,
/// such as rules like [ZIP 212] that become active at a specific block height.
Expand All @@ -257,8 +241,6 @@ pub trait Domain {
fn parse_note_plaintext_without_memo_ovk(
&self,
pk_d: &Self::DiversifiedTransmissionKey,
esk: &Self::EphemeralSecretKey,
ephemeral_key: &EphemeralKeyBytes,
plaintext: &NotePlaintextBytes,
) -> Option<(Self::Note, Self::Recipient)>;

Expand Down Expand Up @@ -350,12 +332,10 @@ pub trait ShieldedOutput<D: Domain, const CIPHERTEXT_SIZE: usize> {
///
/// Implements section 4.19 of the
/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#saplingandorchardinband)

pub struct NoteEncryption<D: Domain> {
epk: D::EphemeralPublicKey,
esk: D::EphemeralSecretKey,
note: D::Note,
to: D::Recipient,
memo: D::Memo,
/// `None` represents the `ovk = ⊥` case.
ovk: Option<D::OutgoingViewingKey>,
Expand All @@ -364,18 +344,12 @@ pub struct NoteEncryption<D: Domain> {
impl<D: Domain> NoteEncryption<D> {
/// Construct a new note encryption context for the specified note,
/// recipient, and memo.
pub fn new(
ovk: Option<D::OutgoingViewingKey>,
note: D::Note,
to: D::Recipient,
memo: D::Memo,
) -> Self {
pub fn new(ovk: Option<D::OutgoingViewingKey>, note: D::Note, memo: D::Memo) -> Self {
let esk = D::derive_esk(&note).expect("ZIP 212 is active.");
NoteEncryption {
epk: D::ka_derive_public(&note, &esk),
esk,
note,
to,
memo,
ovk,
}
Expand All @@ -390,14 +364,12 @@ impl<D: Domain> NoteEncryption<D> {
esk: D::EphemeralSecretKey,
ovk: Option<D::OutgoingViewingKey>,
note: D::Note,
to: D::Recipient,
memo: D::Memo,
) -> Self {
NoteEncryption {
epk: D::ka_derive_public(&note, &esk),
esk,
note,
to,
memo,
ovk,
}
Expand All @@ -418,7 +390,7 @@ impl<D: Domain> NoteEncryption<D> {
let pk_d = D::get_pk_d(&self.note);
let shared_secret = D::ka_agree_enc(&self.esk, &pk_d);
let key = D::kdf(shared_secret, &D::epk_bytes(&self.epk));
let input = D::note_plaintext_bytes(&self.note, &self.to, &self.memo);
let input = D::note_plaintext_bytes(&self.note, &self.memo);

let mut output = [0u8; ENC_CIPHERTEXT_SIZE];
output[..NOTE_PLAINTEXT_SIZE].copy_from_slice(&input.0);
Expand Down Expand Up @@ -545,6 +517,8 @@ fn check_note_validity<D: Domain>(
cmstar_bytes: &D::ExtractedCommitmentBytes,
) -> NoteValidity {
if &D::ExtractedCommitmentBytes::from(&D::cmstar(note)) == cmstar_bytes {
// In the case corresponding to specification section 4.19.3, we check that `esk` is equal
// to `D::derive_esk(note)` prior to calling this method.
if let Some(derived_esk) = D::derive_esk(note) {
if D::epk_bytes(&D::ka_derive_public(note, &derived_esk))
.ct_eq(ephemeral_key)
Expand Down Expand Up @@ -683,12 +657,12 @@ pub fn try_output_recovery_with_ock<D: Domain, Output: ShieldedOutput<D, ENC_CIP
)
.ok()?;

let (note, to) =
domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, &ephemeral_key, &plaintext)?;
let (note, to) = domain.parse_note_plaintext_without_memo_ovk(&pk_d, &plaintext)?;
let memo = domain.extract_memo(&plaintext);

// ZIP 212: Check that the esk provided to this function is consistent with the esk we
// can derive from the note.
// ZIP 212: Check that the esk provided to this function is consistent with the esk we can
// derive from the note. This check corresponds to `ToScalar(PRF^{expand}_{rseed}([4]) = esk`
// in https://zips.z.cash/protocol/protocol.pdf#decryptovk. (`ρ^opt = []` for Sapling.)
if let Some(derived_esk) = D::derive_esk(&note) {
if (!derived_esk.ct_eq(&esk)).into() {
return None;
Expand Down
138 changes: 101 additions & 37 deletions masp_primitives/src/sapling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub mod prover;
pub mod redjubjub;
pub mod util;

use bitvec::{order::Lsb0, view::AsBits};
use bitvec::{array::BitArray, order::Lsb0, view::AsBits};
use blake2s_simd::Params as Blake2sParams;
use borsh::{BorshDeserialize, BorshSerialize};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
Expand Down Expand Up @@ -434,14 +434,8 @@ impl PaymentAddress {
self.diversifier.g_d()
}

pub fn create_note(&self, asset_type: AssetType, value: u64, rseed: Rseed) -> Option<Note> {
self.g_d().map(|g_d| Note {
asset_type,
value,
rseed,
g_d,
pk_d: self.pk_d,
})
pub fn create_note(&self, asset_type: AssetType, value: u64, rseed: Rseed) -> Note {
Note::from_parts(asset_type, *self, NoteValue::from_raw(value), rseed)
}
}

Expand Down Expand Up @@ -531,9 +525,29 @@ impl ConstantTimeEq for Nullifier {
}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
/// The non-negative value of an individual Sapling note.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct NoteValue(u64);

impl NoteValue {
/// Returns the raw underlying value.
pub fn inner(&self) -> u64 {
self.0
}

/// Creates a note value from its raw numeric value.
///
/// This only enforces that the value is an unsigned 64-bit integer. Callers should
/// enforce any additional constraints on the value's valid range themselves.
pub fn from_raw(value: u64) -> Self {
NoteValue(value)
}

pub(crate) fn to_le_bits(self) -> BitArray<[u8; 8], Lsb0> {

Check failure on line 546 in masp_primitives/src/sapling.rs

View workflow job for this annotation

GitHub Actions / Clippy (MSRV)

associated function `to_le_bits` is never used

error: associated function `to_le_bits` is never used --> masp_primitives/src/sapling.rs:546:19 | 546 | pub(crate) fn to_le_bits(self) -> BitArray<[u8; 8], Lsb0> { | ^^^^^^^^^^ | = note: `-D dead-code` implied by `-D warnings`

Check failure on line 546 in masp_primitives/src/sapling.rs

View workflow job for this annotation

GitHub Actions / Clippy (MSRV)

associated function `to_le_bits` is never used

error: associated function `to_le_bits` is never used --> masp_primitives/src/sapling.rs:546:19 | 546 | pub(crate) fn to_le_bits(self) -> BitArray<[u8; 8], Lsb0> { | ^^^^^^^^^^ | = note: `-D dead-code` implied by `-D warnings`
BitArray::<_, Lsb0>::new(self.0.to_le_bytes())
}
}

impl TryFrom<u64> for NoteValue {
type Error = ();

Expand All @@ -552,36 +566,80 @@ impl From<NoteValue> for u64 {
}
}

impl From<NoteValue> for i128 {
fn from(value: NoteValue) -> i128 {
value.0.into()
}
}

/// A discrete amount of funds received by an address.
#[derive(Clone, Debug, Copy)]
pub struct Note {
/// The asset type that the note represents
pub asset_type: AssetType,
/// The recipient of the funds.
recipient: PaymentAddress,
/// The value of the note
pub value: u64,
/// The diversified base of the address, GH(d)
pub g_d: jubjub::SubgroupPoint,
/// The public key of the address, g_d^ivk
pub pk_d: jubjub::SubgroupPoint,
/// rseed
pub value: NoteValue,
/// The seed randomness for various note components.
pub rseed: Rseed,
}

impl PartialEq for Note {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
&& self.asset_type == other.asset_type
&& self.g_d == other.g_d
&& self.pk_d == other.pk_d
&& self.rcm() == other.rcm()
// Notes are canonically defined by their commitments.
self.cmu().eq(&other.cmu())
}
}

impl Eq for Note {}

impl Note {
pub fn uncommitted() -> bls12_381::Scalar {
// The smallest u-coordinate that is not on the curve
// is one.
bls12_381::Scalar::one()
}
/// Creates a note from its component parts.
///
/// # Caveats
///
/// This low-level constructor enforces that the provided arguments produce an
/// internally valid `Note`. However, it allows notes to be constructed in a way that
/// violates required security checks for note decryption, as specified in
/// [Section 4.19] of the Zcash Protocol Specification. Users of this constructor
/// should only call it with note components that have been fully validated by
/// decrypting a received note according to [Section 4.19].
///
/// [Section 4.19]: https://zips.z.cash/protocol/protocol.pdf#saplingandorchardinband
pub fn from_parts(
asset_type: AssetType,
recipient: PaymentAddress,
value: NoteValue,
rseed: Rseed,
) -> Self {
Note {
asset_type,
recipient,
value,
rseed,
}
}

/// Returns the recipient of this note.
pub fn recipient(&self) -> PaymentAddress {
self.recipient
}

/// Returns the value of this note.
pub fn value(&self) -> NoteValue {
self.value
}

/// Returns the rseed value of this note.
pub fn rseed(&self) -> &Rseed {
&self.rseed
}

/// Computes the note commitment, returning the full point.
fn cm_full_point(&self) -> jubjub::SubgroupPoint {
Expand All @@ -592,13 +650,15 @@ impl Note {
note_contents.extend_from_slice(&self.asset_type.asset_generator().to_bytes());

// Writing the value in little endian
note_contents.write_u64::<LittleEndian>(self.value).unwrap();
note_contents
.write_u64::<LittleEndian>(self.value.into())
.unwrap();

// Write g_d
note_contents.extend_from_slice(&self.g_d.to_bytes());
note_contents.extend_from_slice(&self.recipient.g_d().unwrap().to_bytes());

// Write pk_d
note_contents.extend_from_slice(&self.pk_d.to_bytes());
note_contents.extend_from_slice(&self.recipient.pk_d().to_bytes());

assert_eq!(note_contents.len(), 32 + 32 + 32 + 8);

Expand Down Expand Up @@ -644,6 +704,9 @@ impl Note {
.get_u()
}

/// Defined in [Zcash Protocol Spec § 4.7.2: Sending Notes (Sapling)][saplingsend].
///
/// [saplingsend]: https://zips.z.cash/protocol/protocol.pdf#saplingsend
pub fn rcm(&self) -> jubjub::Fr {
match self.rseed {
Rseed::BeforeZip212(rcm) => rcm,
Expand All @@ -653,6 +716,8 @@ impl Note {
}
}

/// Derives `esk` from the internal `Rseed` value, or generates a random value if this
/// note was created with a v1 (i.e. pre-ZIP 212) note plaintext.
pub fn generate_or_derive_esk<R: RngCore + CryptoRng>(&self, rng: &mut R) -> jubjub::Fr {
self.generate_or_derive_esk_internal(rng)
}
Expand Down Expand Up @@ -688,11 +753,11 @@ impl BorshSerialize for Note {
// Write asset type
self.asset_type.serialize(writer)?;
// Write note value
writer.write_u64::<LittleEndian>(self.value)?;
// Write diversified base
writer.write_all(&self.g_d.to_bytes())?;
writer.write_u64::<LittleEndian>(self.value().inner())?;
// Write diversifier
writer.write_all(&self.recipient().diversifier().0)?;
// Write diversified transmission key
writer.write_all(&self.pk_d.to_bytes())?;
writer.write_all(&self.recipient().pk_d().to_bytes())?;
match self.rseed {
Rseed::BeforeZip212(rcm) => {
// Write note plaintext lead byte
Expand All @@ -717,9 +782,10 @@ impl BorshDeserialize for Note {
let asset_type = AssetType::deserialize(buf)?;
// Read note value
let value = buf.read_u64::<LittleEndian>()?;
// Read diversified base
let g_d_bytes = <[u8; 32]>::deserialize(buf)?;
let g_d = Option::from(jubjub::SubgroupPoint::from_bytes(&g_d_bytes))
// Read diversifier
let diversifier = Diversifier(<[u8; 11]>::deserialize(buf)?);
diversifier
.g_d()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "g_d not in field"))?;
// Read diversified transmission key
let pk_d_bytes = <[u8; 32]>::deserialize(buf)?;
Expand All @@ -739,9 +805,8 @@ impl BorshDeserialize for Note {
// Finally construct note object
Ok(Note {
asset_type,
value,
g_d,
pk_d,
value: NoteValue::from_raw(value),
recipient: PaymentAddress::from_parts(diversifier, pk_d).unwrap(),
rseed,
})
}
Expand Down Expand Up @@ -799,13 +864,12 @@ pub mod testing {
prop_compose! {
pub fn arb_note(value: NoteValue)(
asset_type in crate::asset_type::testing::arb_asset_type(),
addr in arb_payment_address(),
recipient in arb_payment_address(),
rseed in prop::array::uniform32(prop::num::u8::ANY).prop_map(Rseed::AfterZip212)
) -> Note {
Note {
value: value.into(),

Check failure on line 871 in masp_primitives/src/sapling.rs

View workflow job for this annotation

GitHub Actions / Clippy (MSRV)

useless conversion to the same type: `sapling::NoteValue`

error: useless conversion to the same type: `sapling::NoteValue` --> masp_primitives/src/sapling.rs:871:24 | 871 | value: value.into(), | ^^^^^^^^^^^^ help: consider removing `.into()`: `value` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion

Check failure on line 871 in masp_primitives/src/sapling.rs

View workflow job for this annotation

GitHub Actions / Clippy (MSRV)

useless conversion to the same type: `sapling::NoteValue`

error: useless conversion to the same type: `sapling::NoteValue` --> masp_primitives/src/sapling.rs:871:24 | 871 | value: value.into(), | ^^^^^^^^^^^^ help: consider removing `.into()`: `value` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
g_d: addr.g_d().unwrap(), // this unwrap is safe because arb_payment_address always generates an address with a valid g_d
pk_d: *addr.pk_d(),
recipient,
rseed,
asset_type
}
Expand Down
Loading

0 comments on commit c5d822f

Please sign in to comment.