From e743198a50037801a78a23fd20dbed7522deec0b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 21 Apr 2021 09:57:48 -0600 Subject: [PATCH] Expose constructors required for ZIP-225 parsing. --- src/builder.rs | 8 +++- src/bundle.rs | 96 ++++++++++++++++++++++++++++++++----- src/circuit.rs | 6 +++ src/lib.rs | 7 ++- src/note.rs | 10 +++- src/note/commitment.rs | 9 +++- src/note/nullifier.rs | 8 +++- src/primitives/redpallas.rs | 6 +++ src/tree.rs | 2 +- 9 files changed, 133 insertions(+), 19 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 88a86868..9e5372b2 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -16,7 +16,7 @@ use crate::{ primitives::redpallas::{self, Binding, SpendAuth}, tree::{Anchor, MerklePath}, value::{self, NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, - Address, EncryptedNote, Note, + Address, TransmittedNoteCiphertext, Note, }; const MIN_ACTIONS: usize = 2; @@ -137,7 +137,11 @@ impl ActionInfo { let cm_new = note.commitment(); // TODO: Note encryption - let encrypted_note = EncryptedNote; + let encrypted_note = TransmittedNoteCiphertext { + epk_bytes: [0u8; 32], + enc_ciphertext: [0u8; 580], + out_ciphertext: [0u8; 80], + }; ( Action::from_parts( diff --git a/src/bundle.rs b/src/bundle.rs index 37254e3b..03eefe65 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -4,7 +4,7 @@ use nonempty::NonEmpty; use crate::{ circuit::{Instance, Proof}, - note::{EncryptedNote, ExtractedNoteCommitment, Nullifier}, + note::{ExtractedNoteCommitment, Nullifier, TransmittedNoteCiphertext}, primitives::redpallas::{self, Binding, SpendAuth}, tree::Anchor, value::{ValueCommitment, ValueSum}, @@ -19,19 +19,19 @@ use crate::{ /// Internally, this may both consume a note and create a note, or it may do only one of /// the two. TODO: Determine which is more efficient (circuit size vs bundle size). #[derive(Debug)] -pub struct Action { +pub struct Action { /// The nullifier of the note being spent. nf: Nullifier, /// The randomized verification key for the note being spent. rk: redpallas::VerificationKey, /// A commitment to the new note being created. cmx: ExtractedNoteCommitment, - /// The encrypted output note. - encrypted_note: EncryptedNote, + /// The transmitted note ciphertext + encrypted_note: TransmittedNoteCiphertext, /// A commitment to the net value created or consumed by this action. cv_net: ValueCommitment, /// The authorization for this action. - authorization: T, + authorization: A, } impl Action { @@ -40,7 +40,7 @@ impl Action { nf: Nullifier, rk: redpallas::VerificationKey, cmx: ExtractedNoteCommitment, - encrypted_note: EncryptedNote, + encrypted_note: TransmittedNoteCiphertext, cv_net: ValueCommitment, authorization: T, ) -> Self { @@ -70,7 +70,7 @@ impl Action { } /// Returns the encrypted note ciphertext. - pub fn encrypted_note(&self) -> &EncryptedNote { + pub fn encrypted_note(&self) -> &TransmittedNoteCiphertext { &self.encrypted_note } @@ -172,6 +172,35 @@ pub trait Authorization { type SpendAuth; } +/// Marker for an unauthorized bundle with no proofs or signatures. +#[derive(Debug)] +pub struct Unauthorized {} + +impl Authorization for Unauthorized { + type SpendAuth = (); +} + +/// Authorizing data for a bundle of actions, ready to be committed to the ledger. +#[derive(Debug)] +pub struct Authorized { + proof: Proof, + binding_signature: redpallas::Signature, +} + +impl Authorized { + /// Construct a new value with authorizing data. + pub fn new(proof: Proof, binding_signature: redpallas::Signature) -> Self { + Authorized { + proof, + binding_signature, + } + } +} + +impl Authorization for Authorized { + type SpendAuth = redpallas::Signature; +} + /// A bundle of actions to be applied to the ledger. #[derive(Debug)] pub struct Bundle { @@ -287,13 +316,56 @@ impl Bundle { /// Authorizing data for a bundle of actions, ready to be committed to the ledger. #[derive(Debug)] -pub struct Authorized { - proof: Proof, - binding_signature: redpallas::Signature, +pub struct BundleAuth { + /// The authorizing data for the actions in a bundle + pub action_authorizations: NonEmpty<::SpendAuth>, + /// The authorizing data that covers the bundle as a whole + pub authorization: Authorized, } -impl Authorization for Authorized { - type SpendAuth = redpallas::Signature; +/// Errors that may be generated in the process of constructing bundle authorizing data. +#[derive(Debug)] +pub enum BundleAuthError { + /// An error produced by the underlying computation of authorizing data for a bundle + Wrapped(E), + /// Authorizing data for the bundle could not be matched to bundle contents. + AuthLengthMismatch(usize, usize), +} + +impl Bundle { + /// Compute the authorizing data for a bundle and apply it to the bundle, returning the + /// authorized result. + pub fn with_auth Result>( + self, + f: F, + ) -> Result, BundleAuthError> { + let auth = f(&self).map_err(BundleAuthError::Wrapped)?; + let actions_len = self.actions.len(); + + if actions_len != auth.action_authorizations.len() { + Err(BundleAuthError::AuthLengthMismatch( + actions_len, + auth.action_authorizations.len(), + )) + } else { + let actions = NonEmpty::from_vec( + self.actions + .into_iter() + .zip(auth.action_authorizations.into_iter()) + .map(|(act, a)| act.map(|_| a)) + .collect(), + ) + .ok_or(BundleAuthError::AuthLengthMismatch(actions_len, 0))?; + + Ok(Bundle { + actions, + flags: self.flags, + value_balance: self.value_balance, + anchor: self.anchor, + authorization: auth.authorization, + }) + } + } } impl Authorized { diff --git a/src/circuit.rs b/src/circuit.rs index 2bf53bc2..4fb447cc 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -156,6 +156,11 @@ impl Proof { Err(plonk::Error::ConstraintSystemFailure) } } + + /// Construct a new Proof value. + pub fn new(bytes: Vec) -> Self { + Proof(bytes) + } } #[cfg(test)] @@ -174,6 +179,7 @@ mod tests { value::{ValueCommitTrapdoor, ValueCommitment}, }; + // TODO: recast as a proptest #[test] fn round_trip() { let mut rng = OsRng; diff --git a/src/lib.rs b/src/lib.rs index 0d56315c..edd54100 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,4 +29,9 @@ mod tree; pub mod value; pub use address::Address; -pub use note::{EncryptedNote, Note, NoteCommitment, Nullifier}; +pub use bundle::{Action, Authorization, Authorized, Bundle, Unauthorized}; +pub use circuit::Proof; +pub use note::{ + ExtractedNoteCommitment, Note, NoteCommitment, Nullifier, TransmittedNoteCiphertext, +}; +pub use tree::Anchor; diff --git a/src/note.rs b/src/note.rs index 0294a079..00aca21b 100644 --- a/src/note.rs +++ b/src/note.rs @@ -135,4 +135,12 @@ impl Note { /// An encrypted note. #[derive(Debug)] -pub struct EncryptedNote; +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], +} diff --git a/src/note/commitment.rs b/src/note/commitment.rs index 00c6d853..3915b49f 100644 --- a/src/note/commitment.rs +++ b/src/note/commitment.rs @@ -2,7 +2,7 @@ use std::iter; use bitvec::{array::BitArray, order::Lsb0}; use ff::PrimeField; -use pasta_curves::pallas; +use pasta_curves::{arithmetic::FieldExt, pallas}; use subtle::CtOption; use crate::{ @@ -59,6 +59,13 @@ impl NoteCommitment { #[derive(Clone, Debug)] pub struct ExtractedNoteCommitment(pub(super) pallas::Base); +impl ExtractedNoteCommitment { + /// Deserialize the extracted note commitment from a byte array. + pub fn from_bytes(bytes: &[u8; 32]) -> CtOption { + pallas::Base::from_bytes(bytes).map(ExtractedNoteCommitment) + } +} + impl From for ExtractedNoteCommitment { fn from(cm: NoteCommitment) -> Self { ExtractedNoteCommitment(extract_p(&cm.0)) diff --git a/src/note/nullifier.rs b/src/note/nullifier.rs index 65f55afd..0356d21c 100644 --- a/src/note/nullifier.rs +++ b/src/note/nullifier.rs @@ -1,7 +1,8 @@ use group::Group; use halo2::arithmetic::CurveExt; -use pasta_curves::pallas; +use pasta_curves::{arithmetic::FieldExt, pallas}; use rand::RngCore; +use subtle::CtOption; use super::NoteCommitment; use crate::{ @@ -30,6 +31,11 @@ impl Nullifier { Nullifier(extract_p(&pallas::Point::random(rng))) } + /// Deserialize the nullifier from a byte array. + pub fn from_bytes(bytes: &[u8; 32]) -> CtOption { + pallas::Base::from_bytes(bytes).map(Nullifier) + } + /// $DeriveNullifier$. /// /// Defined in [Zcash Protocol Spec ยง 4.16: Note Commitments and Nullifiers][commitmentsandnullifiers]. diff --git a/src/primitives/redpallas.rs b/src/primitives/redpallas.rs index 7db402ec..82b04d3d 100644 --- a/src/primitives/redpallas.rs +++ b/src/primitives/redpallas.rs @@ -96,6 +96,12 @@ impl VerificationKey { #[derive(Debug)] pub struct Signature(reddsa::Signature); +impl From<[u8; 64]> for Signature { + fn from(bytes: [u8; 64]) -> Self { + Signature(bytes.into()) + } +} + pub(crate) mod private { use super::{Binding, SpendAuth}; diff --git a/src/tree.rs b/src/tree.rs index 63089e31..6e8ad2b5 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -2,7 +2,7 @@ use rand::RngCore; /// The root of an Orchard commitment tree. #[derive(Clone, Debug)] -pub struct Anchor; +pub struct Anchor(pub [u8; 32]); #[derive(Debug)] pub struct MerklePath;