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