added add_recipent to `IssueBundle`

This commit is contained in:
Paul 2022-08-31 20:14:12 +03:00
parent ca0935e7f1
commit f014f85065
4 changed files with 272 additions and 33 deletions

View File

@ -2,18 +2,40 @@
use memuse::DynamicUsage; use memuse::DynamicUsage;
use nonempty::NonEmpty; 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::{ use crate::{
primitives::redpallas::{self, SpendAuth}, primitives::redpallas::{self, SpendAuth},
Note, Address, Note,
}; };
impl<T> IssueAction<T> { impl IssueAction<Unauthorized> {
/// 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<T: IssueAuth> IssueAction<T> {
/// Constructs an `IssueAction` from its constituent parts. /// Constructs an `IssueAction` from its constituent parts.
pub fn from_parts( pub fn from_parts(
ik: redpallas::VerificationKey<SpendAuth>, ik: IssuerValidatingKey,
asset_desc: Vec<u8>, asset_desc: String,
notes: NonEmpty<Note>, notes: NonEmpty<Note>,
finalize: bool, finalize: bool,
authorization: T, authorization: T,
@ -28,12 +50,12 @@ impl<T> IssueAction<T> {
} }
/// Returns the issuer verification key for the note being created. /// Returns the issuer verification key for the note being created.
pub fn ik(&self) -> &redpallas::VerificationKey<SpendAuth> { pub fn ik(&self) -> &IssuerValidatingKey {
&self.ik &self.ik
} }
/// Returns the asset description for the note being created. /// Returns the asset description for the note being created.
pub fn asset_desc(&self) -> &[u8] { pub fn asset_desc(&self) -> &str {
&self.asset_desc &self.asset_desc
} }
@ -105,15 +127,21 @@ pub(crate) mod testing {
value::NoteValue, value::NoteValue,
}; };
use super::IssueAction; use super::{IssueAction, Signed};
use crate::keys::{testing::arb_spending_key, IssuerAuthorizingKey, IssuerValidatingKey};
prop_compose! { prop_compose! {
/// Generate an issue action with a single note and without authorization data. /// Generate an issue action with a single note and without authorization data.
pub fn arb_unauthorized_issue_action(output_value: NoteValue)( pub fn arb_unauthorized_issue_action(output_value: NoteValue)(
ik in arb_spendauth_verification_key(), sk in arb_spending_key(),
asset_desc in prop::collection::vec(any::<u8>(), 0..=255), vec in prop::collection::vec(any::<u8>(), 0..=255),
note in arb_note(output_value), note in arb_note(output_value),
) -> IssueAction<()> { ) -> IssueAction<()> {
let isk: IssuerAuthorizingKey = (&sk).into();
let ik: IssuerValidatingKey = (&isk).into();
let asset_desc = String::from_utf8(vec).unwrap();
IssueAction { IssueAction {
ik, ik,
asset_desc, asset_desc,
@ -127,21 +155,25 @@ pub(crate) mod testing {
prop_compose! { prop_compose! {
/// Generate an issue action with invalid (random) authorization data. /// Generate an issue action with invalid (random) authorization data.
pub fn arb_issue_action(output_value: NoteValue)( pub fn arb_issue_action(output_value: NoteValue)(
sk in arb_spendauth_signing_key(), sk in arb_spending_key(),
asset_desc in prop::collection::vec(any::<u8>(), 0..=255), vec in prop::collection::vec(any::<u8>(), 0..=255),
note in arb_note(output_value), note in arb_note(output_value),
rng_seed in prop::array::uniform32(prop::num::u8::ANY), rng_seed in prop::array::uniform32(prop::num::u8::ANY),
fake_sighash in prop::array::uniform32(prop::num::u8::ANY), fake_sighash in prop::array::uniform32(prop::num::u8::ANY),
) -> IssueAction<redpallas::Signature<SpendAuth>> { ) -> IssueAction<Signed> {
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 { IssueAction {
ik: redpallas::VerificationKey::from(&sk), ik,
asset_desc, asset_desc: String::from_utf8(vec).unwrap(),
notes:NonEmpty::new(note), //todo: replace note type notes: NonEmpty::new(note), //todo: replace note type
finalize: false, 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)] #[derive(Debug, Clone)]
pub struct IssueAction<A> { pub struct IssueAction<A> {
/// The issuer key for the note being created. /// The issuer key for the note being created.
ik: redpallas::VerificationKey<SpendAuth>, //ik: redpallas::VerificationKey<SpendAuth>,
ik: IssuerValidatingKey,
/// Asset description for verification. /// Asset description for verification.
asset_desc: Vec<u8>, asset_desc: String,
/// The newly issued notes. /// The newly issued notes.
notes: NonEmpty<Note>, notes: NonEmpty<Note>,
/// Finalize will prevent further issuance of the same asset. /// Finalize will prevent further issuance of the same asset.
@ -165,12 +198,208 @@ pub struct IssueAction<A> {
} }
/// A bundle of actions to be applied to the ledger. /// A bundle of actions to be applied to the ledger.
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct IssueBundle<T: Authorization> { pub struct IssueBundle<T: IssueAuth> {
/// The list of issue actions that make up this bundle. /// The list of issue actions that make up this bundle.
actions: NonEmpty<IssueAction<T::SpendAuth>>, actions: Vec<IssueAction<T>>,
/// The authorization for this bundle. }
authorization: T,
/// 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<SpendAuth>,
}
impl IssueAuth for Unauthorized {}
impl IssueAuth for Prepared {}
impl IssueAuth for Signed {}
impl IssueBundle<Unauthorized> {
/// Constructs a new `IssueBundle`.
pub fn new() -> IssueBundle<Unauthorized> {
IssueBundle {
actions: Vec::new(),
}
}
}
impl<T: IssueAuth> IssueBundle<T> {
// /// Constructs a new `IssueBundle`.
// pub fn new() -> IssueBundle<Unauthorized> {
// IssueBundle {
// actions: Vec::new(),
// }
// }
/// Return the actions for a given `IssueBundle`
pub fn actions(&self) -> &Vec<IssueAction<T>> {
&self.actions
}
}
impl IssueBundle<Unauthorized> {
/// 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<SpendAuth>,
ik: IssuerValidatingKey,
asset_desc: String,
recipient: Address,
value: NoteValue,
// memo: Option<[u8; 512]>,
mut rng: impl RngCore,
) -> Result<NoteType, Error> {
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, &note);
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 { // mod tests {

View File

@ -12,7 +12,7 @@ use group::{
Curve, GroupEncoding, Curve, GroupEncoding,
}; };
use pasta_curves::pallas; use pasta_curves::pallas;
use rand::RngCore; use rand::{CryptoRng, RngCore};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
use zcash_note_encryption::EphemeralKeyBytes; use zcash_note_encryption::EphemeralKeyBytes;
@ -208,6 +208,13 @@ impl IssuerAuthorizingKey {
fn derive_inner(sk: &SpendingKey) -> pallas::Scalar { fn derive_inner(sk: &SpendingKey) -> pallas::Scalar {
to_scalar(PrfExpand::ZsaIsk.expand(&sk.0)) to_scalar(PrfExpand::ZsaIsk.expand(&sk.0))
} }
/// RXXXX
///
/// XXXXX
pub fn sign(&self, rng: &mut (impl RngCore + CryptoRng), msg: &[u8]) -> redpallas::Signature<SpendAuth> {
self.0.sign(rng, msg)
}
} }
impl From<&SpendingKey> for IssuerAuthorizingKey { impl From<&SpendingKey> for IssuerAuthorizingKey {

View File

@ -160,6 +160,7 @@ impl Note {
/// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes]. /// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes].
/// ///
/// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes /// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
/// TODO zsa: remove note_type
pub(crate) fn dummy( pub(crate) fn dummy(
rng: &mut impl RngCore, rng: &mut impl RngCore,
rho: Option<Nullifier>, rho: Option<Nullifier>,

View File

@ -38,12 +38,12 @@ impl NoteType {
/// ///
/// [notetypes]: https://zips.z.cash/protocol/nu5.pdf#notetypes /// [notetypes]: https://zips.z.cash/protocol/nu5.pdf#notetypes
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn derive(ik: &IssuerValidatingKey, assetDesc: Vec<u8>) -> Self { pub fn derive(ik: &IssuerValidatingKey, asset_desc: &str) -> Self {
assert!(assetDesc.len() < MAX_ASSET_DESCRIPTION_SIZE); assert!(!asset_desc.is_empty() && asset_desc.len() <= MAX_ASSET_DESCRIPTION_SIZE);
let mut s = vec![]; let mut s = vec![];
s.extend(ik.to_bytes()); s.extend(ik.to_bytes());
s.extend(assetDesc); s.extend(asset_desc.as_bytes());
NoteType(assetID_hasher(s)) NoteType(assetID_hasher(s))
} }
@ -92,15 +92,17 @@ pub mod testing {
pub fn arb_note_type()( pub fn arb_note_type()(
is_native in prop::bool::ANY, is_native in prop::bool::ANY,
sk in arb_spending_key(), sk in arb_spending_key(),
bytes32a 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), // bytes32b in prop::array::uniform32(prop::num::u8::ANY),
vec in prop::collection::vec(any::<u8>(), 0..=255),
) -> NoteType { ) -> NoteType {
if is_native { if is_native {
NoteType::native() NoteType::native()
} else { } else {
let bytes64 = [bytes32a, bytes32b].concat(); //let bytes64 = [bytes32a, bytes32b].concat();
let asset_desc = String::from_utf8(vec).unwrap();
let isk = IssuerAuthorizingKey::from(&sk); let isk = IssuerAuthorizingKey::from(&sk);
NoteType::derive(&IssuerValidatingKey::from(&isk), bytes64) NoteType::derive(&IssuerValidatingKey::from(&isk), asset_desc.as_str())
} }
} }
} }