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

Add a Rho type, to distinguish from revealed nullifiers of spent notes. #421

Merged
merged 1 commit into from
Mar 12, 2024
Merged
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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ and this project adheres to Rust's notion of

## [Unreleased]

### Added
- `orchard::note::Rho`
- `orchard::action::Action::rho`
- `orchard::note_encryption::CompactAction::rho`
- `orchard::note_encryption::OrchardDomain::for_compact_action`

### 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`

### Removed
- `orchard::note_encryption::OrchardDomain::for_nullifier` (use `for_action`
or `for_compact_action` instead).

## [0.7.1] - 2024-02-29
### Added
- `impl subtle::ConstantTimeEq for orchard::note::Nullifier`
Expand Down
7 changes: 6 additions & 1 deletion src/action.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use memuse::DynamicUsage;

use crate::{
note::{ExtractedNoteCommitment, Nullifier, TransmittedNoteCiphertext},
note::{ExtractedNoteCommitment, Nullifier, Rho, TransmittedNoteCiphertext},
primitives::redpallas::{self, SpendAuth},
value::ValueCommitment,
};
Expand Down Expand Up @@ -66,6 +66,11 @@ impl<T> Action<T> {
&self.encrypted_note
}

/// Obtains the [`Rho`] value that was used to construct the new note being created.
pub fn rho(&self) -> Rho {
Rho::from_nf_old(self.nf)
}

/// Returns the commitment to the net value created or consumed by this action.
pub fn cv_net(&self) -> &ValueCommitment {
&self.cv_net
Expand Down
5 changes: 3 additions & 2 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
FullViewingKey, OutgoingViewingKey, Scope, SpendAuthorizingKey, SpendValidatingKey,
SpendingKey,
},
note::{Note, TransmittedNoteCiphertext},
note::{Note, Rho, TransmittedNoteCiphertext},
note_encryption::OrchardNoteEncryption,
primitives::redpallas::{self, Binding, SpendAuth},
tree::{Anchor, MerklePath},
Expand Down Expand Up @@ -335,11 +335,12 @@ impl ActionInfo {
let cv_net = ValueCommitment::derive(v_net, self.rcv.clone());

let nf_old = self.spend.note.nullifier(&self.spend.fvk);
let rho = Rho::from_nf_old(nf_old);
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 Down
13 changes: 7 additions & 6 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_nf_old(spend.note.nullifier(&spend.fvk)) == output_note.rho())
.then(|| Self::from_action_context_unchecked(spend, output_note, alpha, rcv))
}

Expand Down Expand Up @@ -406,7 +406,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
let rho_old = assign_free_advice(
layouter.namespace(|| "witness rho_old"),
config.advices[0],
self.rho_old.map(|rho| rho.0),
self.rho_old.map(|rho| rho.into_inner()),
)?;

// Witness cm_old
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_nf_old(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
76 changes: 58 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,50 @@ pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment};
pub(crate) mod nullifier;
pub use self::nullifier::Nullifier;

/// The randomness used to construct a note.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Rho(pallas::Base);

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

impl Rho {
/// Deserialize the rho value from a byte array.
///
/// This should only be used in cases where the components of a `Note` are being serialized and
/// stored individually. Use [`Action::rho`] or [`CompactAction::rho`] to obtain the [`Rho`]
/// value otherwise.
///
/// [`Action::rho`]: crate::action::Action::rho
/// [`CompactAction::rho`]: crate::note_encryption::CompactAction::rho
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()
}

/// 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(crate) fn from_nf_old(nf: Nullifier) -> Self {
Rho(nf.0)
}

pub(crate) fn into_inner(self) -> pallas::Base {
self.0
}
}

/// 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 +75,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 +92,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 +108,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 +132,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 +169,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 +189,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 +207,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 +216,7 @@ impl Note {
let note = Note::new(
recipient,
NoteValue::zero(),
rho.unwrap_or_else(|| Nullifier::dummy(rng)),
rho.unwrap_or_else(|| Rho::from_nf_old(Nullifier::dummy(rng))),
rng,
);

Expand Down Expand Up @@ -204,7 +244,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 +323,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 +336,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_nf_old),
rseed in arb_rseed(),
) -> Note {
Note {
Expand Down
Loading
Loading