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 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<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.
pub fn from_parts(
ik: redpallas::VerificationKey<SpendAuth>,
asset_desc: Vec<u8>,
ik: IssuerValidatingKey,
asset_desc: String,
notes: NonEmpty<Note>,
finalize: bool,
authorization: T,
@ -28,12 +50,12 @@ impl<T> IssueAction<T> {
}
/// Returns the issuer verification key for the note being created.
pub fn ik(&self) -> &redpallas::VerificationKey<SpendAuth> {
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::<u8>(), 0..=255),
sk in arb_spending_key(),
vec in prop::collection::vec(any::<u8>(), 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::<u8>(), 0..=255),
sk in arb_spending_key(),
vec in prop::collection::vec(any::<u8>(), 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<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 {
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<A> {
/// The issuer key for the note being created.
ik: redpallas::VerificationKey<SpendAuth>,
//ik: redpallas::VerificationKey<SpendAuth>,
ik: IssuerValidatingKey,
/// Asset description for verification.
asset_desc: Vec<u8>,
asset_desc: String,
/// The newly issued notes.
notes: NonEmpty<Note>,
/// 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.
#[derive(Debug, Clone)]
pub struct IssueBundle<T: Authorization> {
#[derive(Debug)]
pub struct IssueBundle<T: IssueAuth> {
/// The list of issue actions that make up this bundle.
actions: NonEmpty<IssueAction<T::SpendAuth>>,
/// The authorization for this bundle.
authorization: T,
actions: Vec<IssueAction<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 {

View File

@ -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<SpendAuth> {
self.0.sign(rng, msg)
}
}
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].
///
/// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
/// TODO zsa: remove note_type
pub(crate) fn dummy(
rng: &mut impl RngCore,
rho: Option<Nullifier>,

View File

@ -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<u8>) -> 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::<u8>(), 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())
}
}
}