2021-05-05 13:05:17 -07:00
|
|
|
//! Data structures used for note construction.
|
2022-04-28 13:20:23 -07:00
|
|
|
use core::fmt;
|
2022-04-05 15:38:10 -07:00
|
|
|
|
2021-03-12 16:04:13 -08:00
|
|
|
use group::GroupEncoding;
|
|
|
|
use pasta_curves::pallas;
|
2021-04-14 21:14:34 -07:00
|
|
|
use rand::RngCore;
|
2021-05-11 03:39:24 -07:00
|
|
|
use subtle::CtOption;
|
2021-03-12 16:04:13 -08:00
|
|
|
|
|
|
|
use crate::{
|
2022-03-30 04:53:46 -07:00
|
|
|
keys::{EphemeralSecretKey, FullViewingKey, Scope, SpendingKey},
|
2021-06-02 15:14:13 -07:00
|
|
|
spec::{to_base, to_scalar, NonZeroPallasScalar, PrfExpand},
|
2021-03-12 16:04:13 -08:00
|
|
|
value::NoteValue,
|
|
|
|
Address,
|
|
|
|
};
|
|
|
|
|
2021-04-27 06:49:49 -07:00
|
|
|
pub(crate) mod commitment;
|
2021-04-19 15:26:58 -07:00
|
|
|
pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment};
|
2021-01-20 12:09:09 -08:00
|
|
|
|
2021-04-27 06:49:49 -07:00
|
|
|
pub(crate) mod nullifier;
|
2021-03-15 18:27:08 -07:00
|
|
|
pub use self::nullifier::Nullifier;
|
|
|
|
|
2021-02-08 07:21:04 -08:00
|
|
|
/// The ZIP 212 seed randomness for a note.
|
2021-06-06 04:13:20 -07:00
|
|
|
#[derive(Copy, Clone, Debug)]
|
2021-04-14 21:14:34 -07:00
|
|
|
pub(crate) struct RandomSeed([u8; 32]);
|
2021-02-08 07:21:04 -08:00
|
|
|
|
2021-06-02 15:14:13 -07:00
|
|
|
impl RandomSeed {
|
|
|
|
pub(crate) fn random(rng: &mut impl RngCore, rho: &Nullifier) -> Self {
|
|
|
|
loop {
|
|
|
|
let mut bytes = [0; 32];
|
|
|
|
rng.fill_bytes(&mut bytes);
|
|
|
|
let rseed = RandomSeed::from_bytes(bytes, rho);
|
|
|
|
if rseed.is_some().into() {
|
|
|
|
break rseed.unwrap();
|
|
|
|
}
|
|
|
|
}
|
2021-05-28 03:57:21 -07:00
|
|
|
}
|
|
|
|
|
2021-06-02 15:14:13 -07:00
|
|
|
pub(crate) fn from_bytes(rseed: [u8; 32], rho: &Nullifier) -> CtOption<Self> {
|
|
|
|
let rseed = RandomSeed(rseed);
|
|
|
|
let esk = rseed.esk_inner(rho);
|
|
|
|
CtOption::new(rseed, esk.is_some())
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
2022-01-20 07:16:45 -08:00
|
|
|
pub(crate) fn as_bytes(&self) -> &[u8; 32] {
|
2021-06-02 15:22:22 -07:00
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
|
2021-03-12 16:04:13 -08:00
|
|
|
/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
|
|
|
|
///
|
|
|
|
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
2021-06-06 04:13:20 -07:00
|
|
|
pub(crate) fn psi(&self, rho: &Nullifier) -> pallas::Base {
|
2021-05-28 05:11:54 -07:00
|
|
|
to_base(PrfExpand::Psi.with_ad(&self.0, &rho.to_bytes()[..]))
|
2021-02-08 07:21:04 -08:00
|
|
|
}
|
|
|
|
|
2021-03-12 16:04:13 -08:00
|
|
|
/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
|
|
|
|
///
|
|
|
|
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
2021-06-02 15:14:13 -07:00
|
|
|
fn esk_inner(&self, rho: &Nullifier) -> CtOption<NonZeroPallasScalar> {
|
|
|
|
NonZeroPallasScalar::from_scalar(to_scalar(
|
|
|
|
PrfExpand::Esk.with_ad(&self.0, &rho.to_bytes()[..]),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
|
|
|
|
///
|
|
|
|
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
|
|
|
fn esk(&self, rho: &Nullifier) -> NonZeroPallasScalar {
|
|
|
|
// We can't construct a RandomSeed for which this unwrap fails.
|
|
|
|
self.esk_inner(rho).unwrap()
|
2021-05-11 03:50:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
|
|
|
|
///
|
|
|
|
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
2021-06-06 04:13:20 -07:00
|
|
|
pub(crate) fn rcm(&self, rho: &Nullifier) -> commitment::NoteCommitTrapdoor {
|
2021-05-28 05:11:54 -07:00
|
|
|
commitment::NoteCommitTrapdoor(to_scalar(
|
|
|
|
PrfExpand::Rcm.with_ad(&self.0, &rho.to_bytes()[..]),
|
|
|
|
))
|
2021-02-08 07:21:04 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-20 12:09:09 -08:00
|
|
|
/// A discrete amount of funds received by an address.
|
2021-06-10 22:02:05 -07:00
|
|
|
#[derive(Debug, Copy, Clone)]
|
2021-01-21 04:16:50 -08:00
|
|
|
pub struct Note {
|
2021-01-20 12:09:09 -08:00
|
|
|
/// The recipient of the funds.
|
2021-01-21 04:16:50 -08:00
|
|
|
recipient: Address,
|
2021-01-20 12:09:09 -08:00
|
|
|
/// The value of this note.
|
2021-01-21 04:16:50 -08:00
|
|
|
value: NoteValue,
|
2021-02-08 07:21:04 -08:00
|
|
|
/// A unique creation ID for this note.
|
|
|
|
///
|
|
|
|
/// This is set to the nullifier of the note that was spent in the [`Action`] that
|
|
|
|
/// created this note.
|
|
|
|
///
|
2022-04-28 14:46:24 -07:00
|
|
|
/// [`Action`]: crate::action::Action
|
2021-02-08 07:21:04 -08:00
|
|
|
rho: Nullifier,
|
|
|
|
/// The seed randomness for various note components.
|
|
|
|
rseed: RandomSeed,
|
2021-01-20 12:09:09 -08:00
|
|
|
}
|
|
|
|
|
2021-06-10 11:19:08 -07:00
|
|
|
impl PartialEq for Note {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
// Notes are canonically defined by their commitments.
|
|
|
|
ExtractedNoteCommitment::from(self.commitment())
|
|
|
|
.eq(&ExtractedNoteCommitment::from(other.commitment()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Eq for Note {}
|
|
|
|
|
2021-01-21 04:16:50 -08:00
|
|
|
impl Note {
|
2021-05-28 03:57:21 -07:00
|
|
|
pub(crate) fn from_parts(
|
|
|
|
recipient: Address,
|
|
|
|
value: NoteValue,
|
|
|
|
rho: Nullifier,
|
|
|
|
rseed: RandomSeed,
|
|
|
|
) -> Self {
|
|
|
|
Note {
|
|
|
|
recipient,
|
|
|
|
value,
|
|
|
|
rho,
|
|
|
|
rseed,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Generates a new note.
|
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
|
|
|
|
///
|
|
|
|
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
|
|
|
pub(crate) fn new(
|
|
|
|
recipient: Address,
|
|
|
|
value: NoteValue,
|
|
|
|
rho: Nullifier,
|
|
|
|
mut rng: impl RngCore,
|
|
|
|
) -> Self {
|
2021-05-11 03:39:24 -07:00
|
|
|
loop {
|
|
|
|
let note = Note {
|
|
|
|
recipient,
|
|
|
|
value,
|
|
|
|
rho,
|
2021-06-02 15:14:13 -07:00
|
|
|
rseed: RandomSeed::random(&mut rng, &rho),
|
2021-05-11 03:39:24 -07:00
|
|
|
};
|
|
|
|
if note.commitment_inner().is_some().into() {
|
|
|
|
break note;
|
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Generates a dummy spent note.
|
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes].
|
|
|
|
///
|
|
|
|
/// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
|
2021-04-26 17:25:03 -07:00
|
|
|
pub(crate) fn dummy(
|
|
|
|
rng: &mut impl RngCore,
|
|
|
|
rho: Option<Nullifier>,
|
|
|
|
) -> (SpendingKey, FullViewingKey, Self) {
|
|
|
|
let sk = SpendingKey::random(rng);
|
|
|
|
let fvk: FullViewingKey = (&sk).into();
|
2022-03-30 04:53:46 -07:00
|
|
|
let recipient = fvk.address_at(0u32, Scope::External);
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2021-05-11 03:39:24 -07:00
|
|
|
let note = Note::new(
|
2021-04-14 21:14:34 -07:00
|
|
|
recipient,
|
2021-05-11 03:39:24 -07:00
|
|
|
NoteValue::zero(),
|
|
|
|
rho.unwrap_or_else(|| Nullifier::dummy(rng)),
|
|
|
|
rng,
|
|
|
|
);
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2021-04-26 17:25:03 -07:00
|
|
|
(sk, fvk, note)
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
2021-06-02 15:22:22 -07:00
|
|
|
/// Returns the recipient of this note.
|
|
|
|
pub fn recipient(&self) -> Address {
|
|
|
|
self.recipient
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:34:51 -07:00
|
|
|
/// Returns the value of this note.
|
|
|
|
pub fn value(&self) -> NoteValue {
|
|
|
|
self.value
|
|
|
|
}
|
|
|
|
|
2021-09-27 19:52:16 -07:00
|
|
|
/// Returns the rseed value of this note.
|
2021-06-02 15:22:22 -07:00
|
|
|
pub(crate) fn rseed(&self) -> &RandomSeed {
|
|
|
|
&self.rseed
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Derives the ephemeral secret key for this note.
|
|
|
|
pub(crate) fn esk(&self) -> EphemeralSecretKey {
|
|
|
|
EphemeralSecretKey(self.rseed.esk(&self.rho))
|
|
|
|
}
|
|
|
|
|
2021-06-06 04:13:20 -07:00
|
|
|
/// Returns rho of this note.
|
|
|
|
pub fn rho(&self) -> Nullifier {
|
|
|
|
self.rho
|
|
|
|
}
|
|
|
|
|
2021-01-20 12:09:09 -08:00
|
|
|
/// Derives the commitment to this note.
|
2021-03-12 16:04:13 -08:00
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 3.2: Notes][notes].
|
|
|
|
///
|
|
|
|
/// [notes]: https://zips.z.cash/protocol/nu5.pdf#notes
|
2021-01-20 12:09:09 -08:00
|
|
|
pub fn commitment(&self) -> NoteCommitment {
|
2021-05-11 03:39:24 -07:00
|
|
|
// `Note` will always have a note commitment by construction.
|
|
|
|
self.commitment_inner().unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Derives the commitment to this note.
|
|
|
|
///
|
|
|
|
/// This is the internal fallible API, used to check at construction time that the
|
|
|
|
/// note has a commitment. Once you have a [`Note`] object, use `note.commitment()`
|
|
|
|
/// instead.
|
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 3.2: Notes][notes].
|
|
|
|
///
|
|
|
|
/// [notes]: https://zips.z.cash/protocol/nu5.pdf#notes
|
|
|
|
fn commitment_inner(&self) -> CtOption<NoteCommitment> {
|
2021-03-12 16:04:13 -08:00
|
|
|
let g_d = self.recipient.g_d();
|
|
|
|
|
|
|
|
NoteCommitment::derive(
|
|
|
|
g_d.to_bytes(),
|
|
|
|
self.recipient.pk_d().to_bytes(),
|
|
|
|
self.value,
|
|
|
|
self.rho.0,
|
2021-05-11 03:50:01 -07:00
|
|
|
self.rseed.psi(&self.rho),
|
|
|
|
self.rseed.rcm(&self.rho),
|
2021-03-12 16:04:13 -08:00
|
|
|
)
|
2021-01-20 12:09:09 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Derives the nullifier for this note.
|
2021-03-15 18:27:08 -07:00
|
|
|
pub fn nullifier(&self, fvk: &FullViewingKey) -> Nullifier {
|
2021-05-11 03:50:01 -07:00
|
|
|
Nullifier::derive(
|
|
|
|
fvk.nk(),
|
|
|
|
self.rho.0,
|
|
|
|
self.rseed.psi(&self.rho),
|
|
|
|
self.commitment(),
|
|
|
|
)
|
2021-01-20 12:09:09 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// An encrypted note.
|
2022-04-05 15:38:10 -07:00
|
|
|
#[derive(Clone)]
|
2021-04-21 08:57:48 -07:00
|
|
|
pub struct TransmittedNoteCiphertext {
|
|
|
|
/// The serialization of the ephemeral public key
|
|
|
|
pub epk_bytes: [u8; 32],
|
|
|
|
/// The encrypted note ciphertext
|
|
|
|
pub enc_ciphertext: [u8; 580],
|
|
|
|
/// An encrypted value that allows the holder of the outgoing cipher
|
|
|
|
/// key for the note to recover the note plaintext.
|
|
|
|
pub out_ciphertext: [u8; 80],
|
|
|
|
}
|
2021-04-27 06:49:49 -07:00
|
|
|
|
2022-04-05 15:38:10 -07:00
|
|
|
impl fmt::Debug for TransmittedNoteCiphertext {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
f.debug_struct("TransmittedNoteCiphertext")
|
|
|
|
.field("epk_bytes", &self.epk_bytes)
|
|
|
|
.field("enc_ciphertext", &hex::encode(self.enc_ciphertext))
|
|
|
|
.field("out_ciphertext", &hex::encode(self.out_ciphertext))
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 06:49:49 -07:00
|
|
|
/// Generators for property testing.
|
|
|
|
#[cfg(any(test, feature = "test-dependencies"))]
|
2021-12-17 14:08:58 -08:00
|
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
|
2021-04-27 06:49:49 -07:00
|
|
|
pub mod testing {
|
|
|
|
use proptest::prelude::*;
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
address::testing::arb_address, note::nullifier::testing::arb_nullifier, value::NoteValue,
|
|
|
|
};
|
|
|
|
|
|
|
|
use super::{Note, RandomSeed};
|
|
|
|
|
|
|
|
prop_compose! {
|
|
|
|
/// Generate an arbitrary random seed
|
|
|
|
pub(crate) fn arb_rseed()(elems in prop::array::uniform32(prop::num::u8::ANY)) -> RandomSeed {
|
|
|
|
RandomSeed(elems)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
prop_compose! {
|
|
|
|
/// Generate an action without authorization data.
|
|
|
|
pub fn arb_note(value: NoteValue)(
|
|
|
|
recipient in arb_address(),
|
|
|
|
rho in arb_nullifier(),
|
|
|
|
rseed in arb_rseed(),
|
|
|
|
) -> Note {
|
|
|
|
Note {
|
|
|
|
recipient,
|
|
|
|
value,
|
|
|
|
rho,
|
|
|
|
rseed,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|