diff --git a/src/issuance.rs b/src/issuance.rs index 97b4818b..01df652b 100644 --- a/src/issuance.rs +++ b/src/issuance.rs @@ -2,18 +2,40 @@ use memuse::DynamicUsage; use nonempty::NonEmpty; +use rand::RngCore; +use std::fmt; -use crate::bundle::Authorization; +use crate::keys::IssuerValidatingKey; +use crate::note::note_type::MAX_ASSET_DESCRIPTION_SIZE; +use crate::note::{NoteType, Nullifier}; +use crate::value::NoteValue; use crate::{ primitives::redpallas::{self, SpendAuth}, - Note, + Address, Note, }; -impl IssueAction { +impl IssueAction { + /// Constructs a new `IssueAction`. + pub fn new(ik: IssuerValidatingKey, asset_desc: String, note: &Note) -> Self { + IssueAction { + ik, + asset_desc, + notes: NonEmpty { + head: *note, + tail: vec![], + }, + finalize: false, + authorization: Unauthorized, + } + } + +} + +impl IssueAction { /// Constructs an `IssueAction` from its constituent parts. pub fn from_parts( - ik: redpallas::VerificationKey, - asset_desc: Vec, + ik: IssuerValidatingKey, + asset_desc: String, notes: NonEmpty, finalize: bool, authorization: T, @@ -28,12 +50,12 @@ impl IssueAction { } /// Returns the issuer verification key for the note being created. - pub fn ik(&self) -> &redpallas::VerificationKey { + pub fn ik(&self) -> &IssuerValidatingKey { &self.ik } /// Returns the asset description for the note being created. - pub fn asset_desc(&self) -> &[u8] { + pub fn asset_desc(&self) -> &str { &self.asset_desc } @@ -105,15 +127,21 @@ pub(crate) mod testing { value::NoteValue, }; - use super::IssueAction; + use super::{IssueAction, Signed}; + + use crate::keys::{testing::arb_spending_key, IssuerAuthorizingKey, IssuerValidatingKey}; prop_compose! { /// Generate an issue action with a single note and without authorization data. pub fn arb_unauthorized_issue_action(output_value: NoteValue)( - ik in arb_spendauth_verification_key(), - asset_desc in prop::collection::vec(any::(), 0..=255), + sk in arb_spending_key(), + vec in prop::collection::vec(any::(), 0..=255), note in arb_note(output_value), ) -> IssueAction<()> { + let isk: IssuerAuthorizingKey = (&sk).into(); + let ik: IssuerValidatingKey = (&isk).into(); + let asset_desc = String::from_utf8(vec).unwrap(); + IssueAction { ik, asset_desc, @@ -127,21 +155,25 @@ pub(crate) mod testing { prop_compose! { /// Generate an issue action with invalid (random) authorization data. pub fn arb_issue_action(output_value: NoteValue)( - sk in arb_spendauth_signing_key(), - asset_desc in prop::collection::vec(any::(), 0..=255), + sk in arb_spending_key(), + vec in prop::collection::vec(any::(), 0..=255), note in arb_note(output_value), rng_seed in prop::array::uniform32(prop::num::u8::ANY), fake_sighash in prop::array::uniform32(prop::num::u8::ANY), - ) -> IssueAction> { + ) -> IssueAction { - let rng = StdRng::from_seed(rng_seed); + let mut rng = StdRng::from_seed(rng_seed); + let isk: IssuerAuthorizingKey = (&sk).into(); + let ik: IssuerValidatingKey = (&isk).into(); IssueAction { - ik: redpallas::VerificationKey::from(&sk), - asset_desc, - notes:NonEmpty::new(note), //todo: replace note type + ik, + asset_desc: String::from_utf8(vec).unwrap(), + notes: NonEmpty::new(note), //todo: replace note type finalize: false, - authorization: sk.sign(rng, &fake_sighash), + authorization: Signed { + signature: isk.sign(&mut rng, &fake_sighash), + } } } } @@ -153,9 +185,10 @@ pub(crate) mod testing { #[derive(Debug, Clone)] pub struct IssueAction { /// The issuer key for the note being created. - ik: redpallas::VerificationKey, + //ik: redpallas::VerificationKey, + ik: IssuerValidatingKey, /// Asset description for verification. - asset_desc: Vec, + asset_desc: String, /// The newly issued notes. notes: NonEmpty, /// Finalize will prevent further issuance of the same asset. @@ -165,12 +198,208 @@ pub struct IssueAction { } /// A bundle of actions to be applied to the ledger. -#[derive(Debug, Clone)] -pub struct IssueBundle { +#[derive(Debug)] +pub struct IssueBundle { /// The list of issue actions that make up this bundle. - actions: NonEmpty>, - /// The authorization for this bundle. - authorization: T, + actions: Vec>, +} + +/// Defines the authorization type of an Issue bundle. +pub trait IssueAuth: fmt::Debug {} + +/// Marker for an unauthorized bundle with no proofs or signatures. +#[derive(Debug)] +pub struct Unauthorized; + +/// Marker for an unauthorized bundle with injected sighash. +#[derive(Debug)] +pub struct Prepared { + sighash: [u8; 32], +} + +/// Marker for an authorized bundle. +#[derive(Debug)] +pub struct Signed { + signature: redpallas::Signature, +} + +impl IssueAuth for Unauthorized {} +impl IssueAuth for Prepared {} +impl IssueAuth for Signed {} + +impl IssueBundle { + /// Constructs a new `IssueBundle`. + pub fn new() -> IssueBundle { + IssueBundle { + actions: Vec::new(), + } + } +} + +impl IssueBundle { + // /// Constructs a new `IssueBundle`. + // pub fn new() -> IssueBundle { + // IssueBundle { + // actions: Vec::new(), + // } + // } + + /// Return the actions for a given `IssueBundle` + pub fn actions(&self) -> &Vec> { + &self.actions + } +} + +impl IssueBundle { + /// Add a new note to the `IssueBundle`. + /// + /// Rho will be randomly sampled, similar to dummy note generation. + /// + /// [orchardmasterkey]: https://zips.z.cash/zip-0032#orchard-master-key-generation + /// + /// # Panics + /// + /// Panics if `asset_desc` is empty or longer than 512 bytes. + pub fn add_recipient( + &mut self, + //ik: redpallas::VerificationKey, + ik: IssuerValidatingKey, + asset_desc: String, + recipient: Address, + value: NoteValue, + // memo: Option<[u8; 512]>, + mut rng: impl RngCore, + ) -> Result { + assert!(!asset_desc.is_empty() && asset_desc.len() <= MAX_ASSET_DESCRIPTION_SIZE); + + let note_type = NoteType::derive(&ik, &asset_desc); + + let note = Note::new( + recipient, + value, + note_type, + Nullifier::dummy(&mut rng), + &mut rng, + ); + + match self.actions.iter_mut().find(|issue_action| { + issue_action.ik.eq(&ik) && issue_action.asset_desc.eq(&asset_desc) + }) { + // Append to an existing IssueAction. + Some(issue_action) => { + if issue_action.finalize { + return Err(Error::IssueActionAlreadyFinalized); + }; + issue_action.notes.push(note); + } + // Insert a new IssueAction. + None => { + let action = IssueAction::new(ik.clone(), asset_desc, ¬e); + self.actions.push(action); + } + } + + Ok(note_type) + } + + /// Finalizes a given IssueAction + /// + /// [orchardmasterkey]: https://zips.z.cash/zip-0032#orchard-master-key-generation + /// + /// # Panics + /// + /// Panics if `asset_desc` is empty or longer than 512 bytes. + pub fn finalize_action( + &mut self, + ik: IssuerValidatingKey, + asset_desc: String, + ) -> Result<(), Error> { + assert!(!asset_desc.is_empty() && asset_desc.len() <= MAX_ASSET_DESCRIPTION_SIZE); + + match self + .actions + .iter_mut() + .find(|issue_action| issue_action.ik.eq(&ik) && issue_action.asset_desc.eq(&asset_desc)) + { + Some(issue_action) => { + issue_action.finalize = true; + } + None => { + return Err(Error::IssueActionNotFound); + } + } + + Ok(()) + } +} + +/// Errors produced during the issuance process +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + /// Unable to add note to the IssueAction since it has already been finalized. + IssueActionAlreadyFinalized, + /// The requested IssueAction not exists in the bundle. + IssueActionNotFound, +} + +mod tests { + use crate::issuance::IssueBundle; + use crate::keys::{ + FullViewingKey, IssuerAuthorizingKey, IssuerValidatingKey, Scope, SpendingKey, + }; + use crate::value::NoteValue; + use rand::rngs::OsRng; + + #[test] + fn issue_bundle_basic() { + let mut rng = OsRng; + let sk = SpendingKey::random(&mut rng); + let isk: IssuerAuthorizingKey = (&sk).into(); + let ik: IssuerValidatingKey = (&isk).into(); + + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + + let mut bundle = IssueBundle::new(); + + let str = String::from("asset_desc"); + + let note_type = bundle + .add_recipient( + ik.clone(), + str.clone(), + recipient, + NoteValue::from_raw(5), + rng, + ) + .unwrap(); + let another_note_type = bundle + .add_recipient( + ik.clone(), + str.clone(), + recipient, + NoteValue::from_raw(10), + rng, + ) + .unwrap(); + + assert_eq!(note_type, another_note_type); + + // let mut builder = Builder::new( + // Flags::from_parts(true, true), + // EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), + // ); + // + // builder + // .add_recipient( + // None, + // recipient, + // NoteValue::from_raw(5000), + // NoteType::native(), + // None, + // ) + // .unwrap(); + } } // mod tests { diff --git a/src/keys.rs b/src/keys.rs index 811e86b3..37a7b0ea 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -12,7 +12,7 @@ use group::{ Curve, GroupEncoding, }; use pasta_curves::pallas; -use rand::RngCore; +use rand::{CryptoRng, RngCore}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; use zcash_note_encryption::EphemeralKeyBytes; @@ -208,6 +208,13 @@ impl IssuerAuthorizingKey { fn derive_inner(sk: &SpendingKey) -> pallas::Scalar { to_scalar(PrfExpand::ZsaIsk.expand(&sk.0)) } + + /// RXXXX + /// + /// XXXXX + pub fn sign(&self, rng: &mut (impl RngCore + CryptoRng), msg: &[u8]) -> redpallas::Signature { + self.0.sign(rng, msg) + } } impl From<&SpendingKey> for IssuerAuthorizingKey { diff --git a/src/note.rs b/src/note.rs index 9e56e31d..c78a0a6b 100644 --- a/src/note.rs +++ b/src/note.rs @@ -160,6 +160,7 @@ impl Note { /// Defined in [Zcash Protocol Spec ยง 4.8.3: Dummy Notes (Orchard)][orcharddummynotes]. /// /// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes + /// TODO zsa: remove note_type pub(crate) fn dummy( rng: &mut impl RngCore, rho: Option, diff --git a/src/note/note_type.rs b/src/note/note_type.rs index 8ac04b00..3571b215 100644 --- a/src/note/note_type.rs +++ b/src/note/note_type.rs @@ -38,12 +38,12 @@ impl NoteType { /// /// [notetypes]: https://zips.z.cash/protocol/nu5.pdf#notetypes #[allow(non_snake_case)] - pub fn derive(ik: &IssuerValidatingKey, assetDesc: Vec) -> Self { - assert!(assetDesc.len() < MAX_ASSET_DESCRIPTION_SIZE); + pub fn derive(ik: &IssuerValidatingKey, asset_desc: &str) -> Self { + assert!(!asset_desc.is_empty() && asset_desc.len() <= MAX_ASSET_DESCRIPTION_SIZE); let mut s = vec![]; s.extend(ik.to_bytes()); - s.extend(assetDesc); + s.extend(asset_desc.as_bytes()); NoteType(assetID_hasher(s)) } @@ -92,15 +92,17 @@ pub mod testing { pub fn arb_note_type()( is_native in prop::bool::ANY, sk in arb_spending_key(), - bytes32a in prop::array::uniform32(prop::num::u8::ANY), - bytes32b in prop::array::uniform32(prop::num::u8::ANY), + // bytes32a in prop::array::uniform32(prop::num::u8::ANY), + // bytes32b in prop::array::uniform32(prop::num::u8::ANY), + vec in prop::collection::vec(any::(), 0..=255), ) -> NoteType { if is_native { NoteType::native() } else { - let bytes64 = [bytes32a, bytes32b].concat(); + //let bytes64 = [bytes32a, bytes32b].concat(); + let asset_desc = String::from_utf8(vec).unwrap(); let isk = IssuerAuthorizingKey::from(&sk); - NoteType::derive(&IssuerValidatingKey::from(&isk), bytes64) + NoteType::derive(&IssuerValidatingKey::from(&isk), asset_desc.as_str()) } } }