Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] update note encryption #58

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 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 @@
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 @@
}
}

#[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 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 @@
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 @@
.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 @@
}
}

/// 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 @@
// 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 @@
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 @@
// 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 @@
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
Loading