diff --git a/src/keys.rs b/src/keys.rs index cec83b0b..9c07e6de 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -8,6 +8,7 @@ use fpe::ff1::{BinaryNumeralString, FF1}; use group::GroupEncoding; use halo2::arithmetic::FieldExt; use pasta_curves::pallas; +use rand::RngCore; use subtle::CtOption; use crate::{ @@ -28,6 +29,23 @@ use crate::{ pub struct SpendingKey([u8; 32]); impl SpendingKey { + /// Generates a random spending key. + /// + /// This is only used when generating dummy notes. Real spending 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 { + loop { + let mut bytes = [0; 32]; + rng.fill_bytes(&mut bytes); + let sk = SpendingKey::from_bytes(bytes); + if sk.is_some().into() { + break sk.unwrap(); + } + } + } + /// Constructs an Orchard spending key from uniformly-random bytes. /// /// Returns `None` if the bytes do not correspond to a valid Orchard spending key. diff --git a/src/note.rs b/src/note.rs index 110a50ec..60931e5a 100644 --- a/src/note.rs +++ b/src/note.rs @@ -1,8 +1,9 @@ use group::GroupEncoding; use pasta_curves::pallas; +use rand::RngCore; use crate::{ - keys::FullViewingKey, + keys::{FullViewingKey, SpendingKey}, spec::{prf_expand, to_base, to_scalar}, value::NoteValue, Address, @@ -19,6 +20,12 @@ pub use self::nullifier::Nullifier; struct RandomSeed([u8; 32]); impl RandomSeed { + pub(crate) fn random(rng: &mut impl RngCore) -> Self { + let mut bytes = [0; 32]; + rng.fill_bytes(&mut bytes); + RandomSeed(bytes) + } + /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend]. /// /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend @@ -53,6 +60,25 @@ pub struct Note { } impl Note { + /// 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 + pub(crate) fn dummy(rng: &mut impl RngCore, rho: Option) -> (FullViewingKey, Self) { + let fvk: FullViewingKey = (&SpendingKey::random(rng)).into(); + let recipient = fvk.default_address(); + + let note = Note { + recipient, + value: NoteValue::zero(), + rho: rho.unwrap_or_else(|| Nullifier::dummy(rng)), + rseed: RandomSeed::random(rng), + }; + + (fvk, note) + } + /// Derives the commitment to this note. /// /// Defined in [Zcash Protocol Spec § 3.2: Notes][notes]. diff --git a/src/note/nullifier.rs b/src/note/nullifier.rs index 93e8f661..a2102492 100644 --- a/src/note/nullifier.rs +++ b/src/note/nullifier.rs @@ -1,5 +1,7 @@ +use group::Group; use halo2::arithmetic::CurveExt; use pasta_curves::pallas; +use rand::RngCore; use super::NoteCommitment; use crate::{ @@ -12,6 +14,22 @@ use crate::{ pub struct Nullifier(pub(super) pallas::Base); impl Nullifier { + /// Generates a dummy nullifier for use as $\rho$ in dummy spent notes. + /// + /// Nullifiers are required by consensus to be unique. For dummy output notes, we get + /// this restriction as intended: the note's $\rho$ value is set to the nullifier of + /// the accompanying spent note within the action, which is constrained by consensus + /// to be unique. In the case of dummy spent notes, we get this restriction by + /// following the chain backwards: the nullifier of the dummy spent note will be + /// constrained by consensus to be unique, and the nullifier's uniqueness is derived + /// from the uniqueness of $\rho$. + /// + /// Instead of explicitly sampling for a unique nullifier, we rely here on the size of + /// the base field to make the chance of sapling a colliding nullifier negligible. + pub(crate) fn dummy(rng: &mut impl RngCore) -> Self { + Nullifier(extract_p(&pallas::Point::random(rng))) + } + /// $DeriveNullifier$. /// /// Defined in [Zcash Protocol Spec § 4.16: Note Commitments and Nullifiers][commitmentsandnullifiers]. diff --git a/src/value.rs b/src/value.rs index 2079b18d..75fe837c 100644 --- a/src/value.rs +++ b/src/value.rs @@ -47,6 +47,11 @@ impl std::error::Error for OverflowError {} pub struct NoteValue(u64); impl NoteValue { + pub(crate) fn zero() -> Self { + // Default for u64 is zero. + Default::default() + } + pub(crate) fn to_le_bits(self) -> BitArray { BitArray::::new(self.0.to_le_bytes()) }