Skip to content

Commit

Permalink
Add a Rho type, to distinguish from revealed note nullifiers.
Browse files Browse the repository at this point in the history
  • Loading branch information
nuttycom committed Mar 10, 2024
1 parent 1db9741 commit 562896f
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 49 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to Rust's notion of

## [Unreleased]

### Added
- `orchard::note::Rho`

### Changed
- The following methods have their `Nullifier`-typed argument or return value
now take or return `note::Rho` instead:
- `orchard::note::RandomSeed::from_bytes`
- `orchard::note::Note::from_parts`
- `orchard::note::Note::rho`

## [0.7.1] - 2024-02-29
### Added
- `impl subtle::ConstantTimeEq for orchard::note::Nullifier`
Expand Down
8 changes: 5 additions & 3 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use nonempty::NonEmpty;
use pasta_curves::pallas;
use rand::{prelude::SliceRandom, CryptoRng, RngCore};

use crate::note::Rho;
use crate::{
action::Action,
address::Address,
Expand Down Expand Up @@ -334,12 +335,13 @@ impl ActionInfo {
let v_net = self.value_sum();
let cv_net = ValueCommitment::derive(v_net, self.rcv.clone());

let nf_old = self.spend.note.nullifier(&self.spend.fvk);
let nf_revealed = self.spend.note.nullifier(&self.spend.fvk);
let rho = Rho::from_paired_spend_revealed_nf(nf_revealed);
let ak: SpendValidatingKey = self.spend.fvk.clone().into();
let alpha = pallas::Scalar::random(&mut rng);
let rk = ak.randomize(&alpha);

let note = Note::new(self.output.recipient, self.output.value, nf_old, &mut rng);
let note = Note::new(self.output.recipient, self.output.value, rho, &mut rng);
let cm_new = note.commitment();
let cmx = cm_new.into();

Expand All @@ -353,7 +355,7 @@ impl ActionInfo {

(
Action::from_parts(
nf_old,
nf_revealed,
rk,
cmx,
encrypted_note,
Expand Down
11 changes: 6 additions & 5 deletions src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use crate::{
note::{
commitment::{NoteCommitTrapdoor, NoteCommitment},
nullifier::Nullifier,
ExtractedNoteCommitment, Note,
ExtractedNoteCommitment, Note, Rho,
},
primitives::redpallas::{SpendAuth, VerificationKey},
spec::NonIdentityPallasPoint,
Expand Down Expand Up @@ -105,7 +105,7 @@ pub struct Circuit {
pub(crate) g_d_old: Value<NonIdentityPallasPoint>,
pub(crate) pk_d_old: Value<DiversifiedTransmissionKey>,
pub(crate) v_old: Value<NoteValue>,
pub(crate) rho_old: Value<Nullifier>,
pub(crate) rho_old: Value<Rho>,
pub(crate) psi_old: Value<pallas::Base>,
pub(crate) rcm_old: Value<NoteCommitTrapdoor>,
pub(crate) cm_old: Value<NoteCommitment>,
Expand Down Expand Up @@ -143,7 +143,7 @@ impl Circuit {
alpha: pallas::Scalar,
rcv: ValueCommitTrapdoor,
) -> Option<Circuit> {
(spend.note.nullifier(&spend.fvk) == output_note.rho())
(Rho::from_paired_spend_revealed_nf(spend.note.nullifier(&spend.fvk)) == output_note.rho())
.then(|| Self::from_action_context_unchecked(spend, output_note, alpha, rcv))
}

Expand Down Expand Up @@ -970,7 +970,7 @@ mod tests {
use super::{Circuit, Instance, Proof, ProvingKey, VerifyingKey, K};
use crate::{
keys::SpendValidatingKey,
note::Note,
note::{Note, Rho},
tree::MerklePath,
value::{ValueCommitTrapdoor, ValueCommitment},
};
Expand All @@ -982,11 +982,12 @@ mod tests {
let nk = *fvk.nk();
let rivk = fvk.rivk(fvk.scope_for_address(&spent_note.recipient()).unwrap());
let nf_old = spent_note.nullifier(&fvk);
let rho = Rho::from_paired_spend_revealed_nf(nf_old);
let ak: SpendValidatingKey = fvk.into();
let alpha = pallas::Scalar::random(&mut rng);
let rk = ak.randomize(&alpha);

let (_, _, output_note) = Note::dummy(&mut rng, Some(nf_old));
let (_, _, output_note) = Note::dummy(&mut rng, Some(rho));
let cmx = output_note.commitment().into();

let value = spent_note.value() - output_note.value();
Expand Down
4 changes: 2 additions & 2 deletions src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -961,7 +961,7 @@ mod tests {
*,
};
use crate::{
note::{ExtractedNoteCommitment, Nullifier, RandomSeed},
note::{ExtractedNoteCommitment, RandomSeed, Rho},
value::NoteValue,
Note,
};
Expand Down Expand Up @@ -1041,7 +1041,7 @@ mod tests {
let addr = fvk.address(diversifier, Scope::External);
assert_eq!(&addr.pk_d().to_bytes(), &tv.default_pk_d);

let rho = Nullifier::from_bytes(&tv.note_rho).unwrap();
let rho = Rho::from_bytes(&tv.note_rho).unwrap();
let note = Note::from_parts(
addr,
NoteValue::from_raw(tv.note_v),
Expand Down
70 changes: 52 additions & 18 deletions src/note.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Data structures used for note construction.
use core::fmt;
use memuse::DynamicUsage;

use ff::PrimeField;
use group::GroupEncoding;
use pasta_curves::pallas;
use rand::RngCore;
Expand All @@ -19,12 +21,44 @@ pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment};
pub(crate) mod nullifier;
pub use self::nullifier::Nullifier;

// We know that `pallas::Base` doesn't allocate internally.
memuse::impl_no_dynamic_usage!(Rho);

/// The randomness used to construct a note.
///
/// The [`Rho`] value for a note should always be constructed from the revealed nullifier of the
/// paired spend in the process of creating an [`Action`].
///
/// [`Action`]: crate::action::Action
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Rho(pub(crate) pallas::Base);

impl Rho {
/// Constructs the [`Rho`] value to be used to construct a new note from the revealed nullifier
/// of the note being spent in the [`Action`] under construction.
///
/// [`Action`]: crate::action::Action
pub fn from_paired_spend_revealed_nf(nf: Nullifier) -> Self {
Rho(nf.0)
}

/// Deserialize the rho value from a byte array.
pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
pallas::Base::from_repr(*bytes).map(Rho)
}

/// Serialize the rho value to its canonical byte representation.
pub fn to_bytes(self) -> [u8; 32] {
self.0.to_repr()
}
}

/// The ZIP 212 seed randomness for a note.
#[derive(Copy, Clone, Debug)]
pub struct RandomSeed([u8; 32]);

impl RandomSeed {
pub(crate) fn random(rng: &mut impl RngCore, rho: &Nullifier) -> Self {
pub(crate) fn random(rng: &mut impl RngCore, rho: &Rho) -> Self {
loop {
let mut bytes = [0; 32];
rng.fill_bytes(&mut bytes);
Expand All @@ -35,10 +69,10 @@ impl RandomSeed {
}
}

/// Reads a note's random seed from bytes, given the note's nullifier.
/// Reads a note's random seed from bytes, given the note's rho value.
///
/// Returns `None` if the nullifier is not for the same note as the seed.
pub fn from_bytes(rseed: [u8; 32], rho: &Nullifier) -> CtOption<Self> {
/// Returns `None` if the rho value is not for the same note as the seed.
pub fn from_bytes(rseed: [u8; 32], rho: &Rho) -> CtOption<Self> {
let rseed = RandomSeed(rseed);
let esk = rseed.esk_inner(rho);
CtOption::new(rseed, esk.is_some())
Expand All @@ -52,14 +86,14 @@ impl RandomSeed {
/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
///
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
pub(crate) fn psi(&self, rho: &Nullifier) -> pallas::Base {
pub(crate) fn psi(&self, rho: &Rho) -> pallas::Base {
to_base(PrfExpand::PSI.with(&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_inner(&self, rho: &Nullifier) -> CtOption<NonZeroPallasScalar> {
fn esk_inner(&self, rho: &Rho) -> CtOption<NonZeroPallasScalar> {
NonZeroPallasScalar::from_scalar(to_scalar(
PrfExpand::ORCHARD_ESK.with(&self.0, &rho.to_bytes()),
))
Expand All @@ -68,15 +102,15 @@ impl RandomSeed {
/// 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 {
fn esk(&self, rho: &Rho) -> NonZeroPallasScalar {
// We can't construct a RandomSeed for which this unwrap fails.
self.esk_inner(rho).unwrap()
}

/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
///
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
pub(crate) fn rcm(&self, rho: &Nullifier) -> commitment::NoteCommitTrapdoor {
pub(crate) fn rcm(&self, rho: &Rho) -> commitment::NoteCommitTrapdoor {
commitment::NoteCommitTrapdoor(to_scalar(
PrfExpand::ORCHARD_RCM.with(&self.0, &rho.to_bytes()),
))
Expand All @@ -92,11 +126,11 @@ pub struct Note {
value: NoteValue,
/// 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.
/// This is produced from the nullifier of the note that will be spent in the [`Action`] that
/// creates this note.
///
/// [`Action`]: crate::action::Action
rho: Nullifier,
rho: Rho,
/// The seed randomness for various note components.
rseed: RandomSeed,
}
Expand Down Expand Up @@ -129,7 +163,7 @@ impl Note {
pub fn from_parts(
recipient: Address,
value: NoteValue,
rho: Nullifier,
rho: Rho,
rseed: RandomSeed,
) -> CtOption<Self> {
let note = Note {
Expand All @@ -149,7 +183,7 @@ impl Note {
pub(crate) fn new(
recipient: Address,
value: NoteValue,
rho: Nullifier,
rho: Rho,
mut rng: impl RngCore,
) -> Self {
loop {
Expand All @@ -167,7 +201,7 @@ impl Note {
/// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
pub(crate) fn dummy(
rng: &mut impl RngCore,
rho: Option<Nullifier>,
rho: Option<Rho>,
) -> (SpendingKey, FullViewingKey, Self) {
let sk = SpendingKey::random(rng);
let fvk: FullViewingKey = (&sk).into();
Expand All @@ -176,7 +210,7 @@ impl Note {
let note = Note::new(
recipient,
NoteValue::zero(),
rho.unwrap_or_else(|| Nullifier::dummy(rng)),
rho.unwrap_or_else(|| Rho::from_paired_spend_revealed_nf(Nullifier::dummy(rng))),
rng,
);

Expand Down Expand Up @@ -204,7 +238,7 @@ impl Note {
}

/// Returns rho of this note.
pub fn rho(&self) -> Nullifier {
pub fn rho(&self) -> Rho {
self.rho
}

Expand Down Expand Up @@ -283,7 +317,7 @@ pub mod testing {
address::testing::arb_address, note::nullifier::testing::arb_nullifier, value::NoteValue,
};

use super::{Note, RandomSeed};
use super::{Note, RandomSeed, Rho};

prop_compose! {
/// Generate an arbitrary random seed
Expand All @@ -296,7 +330,7 @@ pub mod testing {
/// Generate an action without authorization data.
pub fn arb_note(value: NoteValue)(
recipient in arb_address(),
rho in arb_nullifier(),
rho in arb_nullifier().prop_map(Rho::from_paired_spend_revealed_nf),
rseed in arb_rseed(),
) -> Note {
Note {
Expand Down
24 changes: 14 additions & 10 deletions src/note_encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
DiversifiedTransmissionKey, Diversifier, EphemeralPublicKey, EphemeralSecretKey,
OutgoingViewingKey, PreparedEphemeralPublicKey, PreparedIncomingViewingKey, SharedSecret,
},
note::{ExtractedNoteCommitment, Nullifier, RandomSeed},
note::{ExtractedNoteCommitment, Nullifier, RandomSeed, Rho},
value::{NoteValue, ValueCommitment},
Address, Note,
};
Expand Down Expand Up @@ -81,7 +81,7 @@ where
/// Orchard-specific note encryption logic.
#[derive(Debug)]
pub struct OrchardDomain {
rho: Nullifier,
rho: Rho,
}

impl memuse::DynamicUsage for OrchardDomain {
Expand All @@ -97,14 +97,17 @@ impl memuse::DynamicUsage for OrchardDomain {
impl OrchardDomain {
/// Constructs a domain that can be used to trial-decrypt this action's output note.
pub fn for_action<T>(act: &Action<T>) -> Self {
OrchardDomain {
rho: *act.nullifier(),
}
Self::for_nullifier(*act.nullifier())
}

/// Constructs a domain from a nullifier.
///
/// The provided nullifier must be the nullifier revealed in the action of the note being
/// encrypted or decrypted.
pub fn for_nullifier(nullifier: Nullifier) -> Self {
OrchardDomain { rho: nullifier }
OrchardDomain {
rho: Rho::from_paired_spend_revealed_nf(nullifier),
}
}
}

Expand Down Expand Up @@ -352,7 +355,7 @@ mod tests {
DiversifiedTransmissionKey, Diversifier, EphemeralSecretKey, IncomingViewingKey,
OutgoingViewingKey, PreparedIncomingViewingKey,
},
note::{ExtractedNoteCommitment, Nullifier, RandomSeed, TransmittedNoteCiphertext},
note::{ExtractedNoteCommitment, Nullifier, RandomSeed, Rho, TransmittedNoteCiphertext},
primitives::redpallas,
value::{NoteValue, ValueCommitment},
Address, Note,
Expand All @@ -377,7 +380,8 @@ mod tests {

// Received Action
let cv_net = ValueCommitment::from_bytes(&tv.cv_net).unwrap();
let rho = Nullifier::from_bytes(&tv.rho).unwrap();
let nf_old = Nullifier::from_bytes(&tv.nf_old).unwrap();
let rho = Rho::from_paired_spend_revealed_nf(nf_old);
let cmx = ExtractedNoteCommitment::from_bytes(&tv.cmx).unwrap();

let esk = EphemeralSecretKey::from_bytes(&tv.esk).unwrap();
Expand Down Expand Up @@ -405,8 +409,8 @@ mod tests {
assert_eq!(ExtractedNoteCommitment::from(note.commitment()), cmx);

let action = Action::from_parts(
// rho is the nullifier in the receiving Action.
rho,
// nf_old is the nullifier revealed by the receiving Action.
nf_old,
// We don't need a valid rk for this test.
redpallas::VerificationKey::dummy(),
cmx,
Expand Down
Loading

0 comments on commit 562896f

Please sign in to comment.