mirror of https://github.com/zcash/orchard.git
added add_recipent to `IssueBundle`
This commit is contained in:
parent
ca0935e7f1
commit
f014f85065
279
src/issuance.rs
279
src/issuance.rs
|
@ -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, ¬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 {
|
// mod tests {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue