mirror of https://github.com/zcash/orchard.git
1470 lines
45 KiB
Rust
1470 lines
45 KiB
Rust
//! Structs related to issuance bundles and the associated logic.
|
|
use blake2b_simd::Hash as Blake2bHash;
|
|
use group::Group;
|
|
use k256::schnorr;
|
|
use nonempty::NonEmpty;
|
|
use rand::RngCore;
|
|
use std::collections::HashSet;
|
|
use std::fmt;
|
|
|
|
use crate::bundle::commitments::{hash_issue_bundle_auth_data, hash_issue_bundle_txid_data};
|
|
use crate::issuance::Error::{
|
|
AssetBaseCannotBeIdentityPoint, IssueActionNotFound, IssueActionPreviouslyFinalizedAssetBase,
|
|
IssueActionWithoutNoteNotFinalized, IssueBundleIkMismatchAssetBase,
|
|
IssueBundleInvalidSignature, ValueSumOverflow, WrongAssetDescSize,
|
|
};
|
|
use crate::keys::{IssuanceAuthorizingKey, IssuanceValidatingKey};
|
|
use crate::note::asset_base::is_asset_desc_of_valid_size;
|
|
use crate::note::{AssetBase, Nullifier};
|
|
|
|
use crate::value::{NoteValue, ValueSum};
|
|
use crate::{Address, Note};
|
|
|
|
use crate::supply_info::{AssetSupply, SupplyInfo};
|
|
|
|
/// A bundle of actions to be applied to the ledger.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct IssueBundle<T: IssueAuth> {
|
|
/// The issuer key for the note being created.
|
|
ik: IssuanceValidatingKey,
|
|
/// The list of issue actions that make up this bundle.
|
|
actions: NonEmpty<IssueAction>,
|
|
/// The authorization for this action.
|
|
authorization: T,
|
|
}
|
|
|
|
/// An issue action applied to the global ledger.
|
|
///
|
|
/// Externally, this creates new zsa notes (adding a commitment to the global ledger).
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct IssueAction {
|
|
/// Asset description for verification.
|
|
asset_desc: String,
|
|
/// The newly issued notes.
|
|
notes: Vec<Note>,
|
|
/// `finalize` will prevent further issuance of the same asset type.
|
|
finalize: bool,
|
|
}
|
|
|
|
/// The parameters required to add a Note into an IssueAction.
|
|
#[derive(Debug)]
|
|
pub struct IssueInfo {
|
|
/// The recipient of the funds.
|
|
pub recipient: Address,
|
|
/// The value of this note.
|
|
pub value: NoteValue,
|
|
}
|
|
|
|
impl IssueAction {
|
|
/// Constructs a new `IssueAction`.
|
|
pub fn new_with_flags(asset_desc: String, notes: Vec<Note>, flags: u8) -> Option<Self> {
|
|
let finalize = match flags {
|
|
0b0000_0000 => false,
|
|
0b0000_0001 => true,
|
|
_ => return None,
|
|
};
|
|
Some(IssueAction {
|
|
asset_desc,
|
|
notes,
|
|
finalize,
|
|
})
|
|
}
|
|
|
|
/// Constructs an `IssueAction` from its constituent parts.
|
|
pub fn from_parts(asset_desc: String, notes: Vec<Note>, finalize: bool) -> Self {
|
|
IssueAction {
|
|
asset_desc,
|
|
notes,
|
|
finalize,
|
|
}
|
|
}
|
|
|
|
/// Returns the asset description for the note being created.
|
|
pub fn asset_desc(&self) -> &str {
|
|
&self.asset_desc
|
|
}
|
|
|
|
/// Returns the issued notes.
|
|
pub fn notes(&self) -> &Vec<Note> {
|
|
&self.notes
|
|
}
|
|
|
|
/// Returns whether the asset type was finalized in this action.
|
|
pub fn is_finalized(&self) -> bool {
|
|
self.finalize
|
|
}
|
|
|
|
/// Verifies and computes the new asset supply for an `IssueAction`.
|
|
///
|
|
/// This function calculates the total value (supply) of the asset by summing the values
|
|
/// of all its notes and ensures that all note types are equal. It returns the asset and
|
|
/// its supply as a tuple (`AssetBase`, `AssetSupply`) or an error if the asset was not
|
|
/// properly derived or an overflow occurred during the supply amount calculation.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `ik` - A reference to the `IssuanceValidatingKey` used for deriving the asset.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A `Result` containing a tuple with an `AssetBase` and an `AssetSupply`, or an `Error`.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// This function may return an error in any of the following cases:
|
|
///
|
|
/// * `ValueSumOverflow`: If the total amount value of all notes in the `IssueAction` overflows.
|
|
///
|
|
/// * `IssueBundleIkMismatchAssetBase`: If the provided `ik` is not used to derive the
|
|
/// `AssetBase` for **all** internal notes.
|
|
///
|
|
/// * `IssueActionWithoutNoteNotFinalized`:If the `IssueAction` contains no note and is not finalized.
|
|
fn verify_supply(&self, ik: &IssuanceValidatingKey) -> Result<(AssetBase, AssetSupply), Error> {
|
|
if self.notes.is_empty() && !self.is_finalized() {
|
|
return Err(IssueActionWithoutNoteNotFinalized);
|
|
}
|
|
|
|
let issue_asset = AssetBase::derive(ik, &self.asset_desc);
|
|
|
|
// Calculate the value of the asset as a sum of values of all its notes
|
|
// and ensure all note types are equal the asset derived from asset_desc and ik.
|
|
let value_sum = self
|
|
.notes
|
|
.iter()
|
|
.try_fold(ValueSum::zero(), |value_sum, ¬e| {
|
|
//The asset base should not be the identity point of the Pallas curve.
|
|
if bool::from(note.asset().cv_base().is_identity()) {
|
|
return Err(AssetBaseCannotBeIdentityPoint);
|
|
}
|
|
|
|
// All assets should be derived correctly
|
|
note.asset()
|
|
.eq(&issue_asset)
|
|
.then_some(())
|
|
.ok_or(IssueBundleIkMismatchAssetBase)?;
|
|
|
|
// The total amount should not overflow
|
|
(value_sum + note.value()).ok_or(ValueSumOverflow)
|
|
})?;
|
|
|
|
Ok((
|
|
issue_asset,
|
|
AssetSupply::new(value_sum, self.is_finalized()),
|
|
))
|
|
}
|
|
|
|
/// Serialize `finalize` flag to a byte
|
|
#[allow(clippy::bool_to_int_with_if)]
|
|
pub fn flags(&self) -> u8 {
|
|
if self.finalize {
|
|
0b0000_0001
|
|
} else {
|
|
0b0000_0000
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Defines the authorization type of an Issue bundle.
|
|
pub trait IssueAuth: fmt::Debug + Clone {}
|
|
|
|
/// Marker for an unauthorized bundle with no proofs or signatures.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Unauthorized;
|
|
|
|
/// Marker for an unauthorized bundle with injected sighash.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Prepared {
|
|
sighash: [u8; 32],
|
|
}
|
|
|
|
/// Marker for an authorized bundle.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct Signed {
|
|
signature: schnorr::Signature,
|
|
}
|
|
|
|
impl Signed {
|
|
/// Returns the signature for this authorization.
|
|
pub fn signature(&self) -> &schnorr::Signature {
|
|
&self.signature
|
|
}
|
|
}
|
|
|
|
impl IssueAuth for Unauthorized {}
|
|
impl IssueAuth for Prepared {}
|
|
impl IssueAuth for Signed {}
|
|
|
|
impl<T: IssueAuth> IssueBundle<T> {
|
|
/// Returns the issuer verification key for the bundle.
|
|
pub fn ik(&self) -> &IssuanceValidatingKey {
|
|
&self.ik
|
|
}
|
|
/// Return the actions for a given `IssueBundle`.
|
|
pub fn actions(&self) -> &NonEmpty<IssueAction> {
|
|
&self.actions
|
|
}
|
|
/// Return the notes from all actions for a given `IssueBundle`.
|
|
pub fn get_all_notes(&self) -> Vec<Note> {
|
|
self.actions
|
|
.iter()
|
|
.flat_map(|action| action.notes.clone().into_iter())
|
|
.collect()
|
|
}
|
|
|
|
/// Returns the authorization for this action.
|
|
pub fn authorization(&self) -> &T {
|
|
&self.authorization
|
|
}
|
|
|
|
/// Find an action by `ik` and `asset_desc` for a given `IssueBundle`.
|
|
pub fn get_action(&self, asset_desc: String) -> Option<&IssueAction> {
|
|
self.actions.iter().find(|a| a.asset_desc.eq(&asset_desc))
|
|
}
|
|
|
|
/// Find an action by `asset` for a given `IssueBundle`.
|
|
pub fn get_action_by_type(&self, asset: AssetBase) -> Option<&IssueAction> {
|
|
let action = self
|
|
.actions
|
|
.iter()
|
|
.find(|a| AssetBase::derive(&self.ik, &a.asset_desc).eq(&asset));
|
|
action
|
|
}
|
|
|
|
/// Computes a commitment to the effects of this bundle, suitable for inclusion within
|
|
/// a transaction ID.
|
|
pub fn commitment(&self) -> IssueBundleCommitment {
|
|
IssueBundleCommitment(hash_issue_bundle_txid_data(self))
|
|
}
|
|
|
|
/// Constructs an `IssueBundle` from its constituent parts.
|
|
pub fn from_parts(
|
|
ik: IssuanceValidatingKey,
|
|
actions: NonEmpty<IssueAction>,
|
|
authorization: T,
|
|
) -> Self {
|
|
IssueBundle {
|
|
ik,
|
|
actions,
|
|
authorization,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl IssueBundle<Unauthorized> {
|
|
/// Constructs a new `IssueBundle`.
|
|
///
|
|
/// If issue_info is None, the new `IssueBundle` will contain one `IssueAction` without notes
|
|
/// and with `finalize` set to true.
|
|
/// Otherwise, the new `IssueBundle` will contain one `IssueAction with one note created from
|
|
/// issue_info values and with `finalize` set to false. In this created note, rho will be
|
|
/// randomly sampled, similar to dummy note generation.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// This function may return an error in any of the following cases:
|
|
///
|
|
/// * `WrongAssetDescSize`: If `asset_desc` is empty or longer than 512 bytes.
|
|
pub fn new(
|
|
ik: IssuanceValidatingKey,
|
|
asset_desc: String,
|
|
issue_info: Option<IssueInfo>,
|
|
mut rng: impl RngCore,
|
|
) -> Result<(IssueBundle<Unauthorized>, AssetBase), Error> {
|
|
if !is_asset_desc_of_valid_size(&asset_desc) {
|
|
return Err(WrongAssetDescSize);
|
|
}
|
|
|
|
let asset = AssetBase::derive(&ik, &asset_desc);
|
|
|
|
let action = match issue_info {
|
|
None => IssueAction {
|
|
asset_desc,
|
|
notes: vec![],
|
|
finalize: true,
|
|
},
|
|
Some(issue_info) => {
|
|
let note = Note::new(
|
|
issue_info.recipient,
|
|
issue_info.value,
|
|
asset,
|
|
Nullifier::dummy(&mut rng),
|
|
&mut rng,
|
|
);
|
|
|
|
IssueAction {
|
|
asset_desc,
|
|
notes: vec![note],
|
|
finalize: false,
|
|
}
|
|
}
|
|
};
|
|
|
|
Ok((
|
|
IssueBundle {
|
|
ik,
|
|
actions: NonEmpty::new(action),
|
|
authorization: Unauthorized,
|
|
},
|
|
asset,
|
|
))
|
|
}
|
|
|
|
/// Add a new note to the `IssueBundle`.
|
|
///
|
|
/// Rho will be randomly sampled, similar to dummy note generation.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// This function may return an error in any of the following cases:
|
|
///
|
|
/// * `WrongAssetDescSize`: If `asset_desc` is empty or longer than 512 bytes.
|
|
pub fn add_recipient(
|
|
&mut self,
|
|
asset_desc: String,
|
|
recipient: Address,
|
|
value: NoteValue,
|
|
mut rng: impl RngCore,
|
|
) -> Result<AssetBase, Error> {
|
|
if !is_asset_desc_of_valid_size(&asset_desc) {
|
|
return Err(WrongAssetDescSize);
|
|
}
|
|
|
|
let asset = AssetBase::derive(&self.ik, &asset_desc);
|
|
|
|
let note = Note::new(
|
|
recipient,
|
|
value,
|
|
asset,
|
|
Nullifier::dummy(&mut rng),
|
|
&mut rng,
|
|
);
|
|
|
|
let action = self
|
|
.actions
|
|
.iter_mut()
|
|
.find(|issue_action| issue_action.asset_desc.eq(&asset_desc));
|
|
|
|
match action {
|
|
Some(action) => {
|
|
// Append to an existing IssueAction.
|
|
action.notes.push(note);
|
|
}
|
|
None => {
|
|
// Insert a new IssueAction.
|
|
self.actions.push(IssueAction {
|
|
asset_desc,
|
|
notes: vec![note],
|
|
finalize: false,
|
|
});
|
|
}
|
|
};
|
|
|
|
Ok(asset)
|
|
}
|
|
|
|
/// Finalizes a given `IssueAction`
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics if `asset_desc` is empty or longer than 512 bytes.
|
|
pub fn finalize_action(&mut self, asset_desc: String) -> Result<(), Error> {
|
|
if !is_asset_desc_of_valid_size(&asset_desc) {
|
|
return Err(WrongAssetDescSize);
|
|
}
|
|
|
|
match self
|
|
.actions
|
|
.iter_mut()
|
|
.find(|issue_action| issue_action.asset_desc.eq(&asset_desc))
|
|
{
|
|
Some(issue_action) => {
|
|
issue_action.finalize = true;
|
|
}
|
|
None => {
|
|
return Err(IssueActionNotFound);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Loads the sighash into the bundle, as preparation for signing.
|
|
pub fn prepare(self, sighash: [u8; 32]) -> IssueBundle<Prepared> {
|
|
IssueBundle {
|
|
ik: self.ik,
|
|
actions: self.actions,
|
|
authorization: Prepared { sighash },
|
|
}
|
|
}
|
|
}
|
|
|
|
impl IssueBundle<Prepared> {
|
|
/// Sign the `IssueBundle`.
|
|
/// The call makes sure that the provided `isk` matches the `ik` and the derived `asset` for each note in the bundle.
|
|
pub fn sign(self, isk: &IssuanceAuthorizingKey) -> Result<IssueBundle<Signed>, Error> {
|
|
let expected_ik: IssuanceValidatingKey = (isk).into();
|
|
|
|
// Make sure the `expected_ik` matches the `asset` for all notes.
|
|
self.actions.iter().try_for_each(|action| {
|
|
action.verify_supply(&expected_ik)?;
|
|
Ok(())
|
|
})?;
|
|
|
|
// Make sure the signature can be generated.
|
|
let signature = isk
|
|
.try_sign(&self.authorization.sighash)
|
|
.map_err(|_| IssueBundleInvalidSignature)?;
|
|
|
|
Ok(IssueBundle {
|
|
ik: self.ik,
|
|
actions: self.actions,
|
|
authorization: Signed { signature },
|
|
})
|
|
}
|
|
}
|
|
|
|
/// A commitment to a bundle of actions.
|
|
///
|
|
/// This commitment is non-malleable, in the sense that a bundle's commitment will only
|
|
/// change if the effects of the bundle are altered.
|
|
#[derive(Debug)]
|
|
pub struct IssueBundleCommitment(pub Blake2bHash);
|
|
|
|
impl From<IssueBundleCommitment> for [u8; 32] {
|
|
/// Serializes issue bundle commitment as byte array
|
|
fn from(commitment: IssueBundleCommitment) -> Self {
|
|
// The commitment uses BLAKE2b-256.
|
|
commitment.0.as_bytes().try_into().unwrap()
|
|
}
|
|
}
|
|
|
|
/// A commitment to the authorizing data within a bundle of actions.
|
|
#[derive(Debug)]
|
|
pub struct IssueBundleAuthorizingCommitment(pub Blake2bHash);
|
|
|
|
impl IssueBundle<Signed> {
|
|
/// Computes a commitment to the authorizing data within for this bundle.
|
|
///
|
|
/// This together with `IssueBundle::commitment` bind the entire bundle.
|
|
pub fn authorizing_commitment(&self) -> IssueBundleAuthorizingCommitment {
|
|
IssueBundleAuthorizingCommitment(hash_issue_bundle_auth_data(self))
|
|
}
|
|
}
|
|
|
|
/// Validation for Orchard IssueBundles
|
|
///
|
|
/// A set of previously finalized asset types must be provided in `finalized` argument.
|
|
///
|
|
/// The following checks are performed:
|
|
/// * For the `IssueBundle`:
|
|
/// * the Signature on top of the provided `sighash` verifies correctly.
|
|
/// * For each `IssueAction`:
|
|
/// * Asset description size is collect.
|
|
/// * `AssetBase` for the `IssueAction` has not been previously finalized.
|
|
/// * For each `Note` inside an `IssueAction`:
|
|
/// * All notes have the same, correct `AssetBase`.
|
|
///
|
|
// # Returns
|
|
///
|
|
/// A Result containing a SupplyInfo struct, which stores supply information in a HashMap.
|
|
/// The HashMap uses AssetBase as the key, and an AssetSupply struct as the value. The
|
|
/// AssetSupply contains a ValueSum (representing the total value of all notes for the asset)
|
|
/// and a bool indicating whether the asset is finalized.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// * `IssueBundleInvalidSignature`: This error occurs if the signature verification
|
|
/// for the provided `sighash` fails.
|
|
/// * `WrongAssetDescSize`: This error is raised if the asset description size for any
|
|
/// asset in the bundle is incorrect.
|
|
/// * `IssueActionPreviouslyFinalizedAssetBase`: This error occurs if the asset has already been
|
|
/// finalized (inserted into the `finalized` collection).
|
|
/// * `ValueSumOverflow`: This error occurs if an overflow happens during the calculation of
|
|
/// the value sum for the notes in the asset.
|
|
/// * `IssueBundleIkMismatchAssetBase`: This error is raised if the `AssetBase` derived from
|
|
/// the `ik` (Issuance Validating Key) and the `asset_desc` (Asset Description) does not match
|
|
/// the expected `AssetBase`.
|
|
pub fn verify_issue_bundle(
|
|
bundle: &IssueBundle<Signed>,
|
|
sighash: [u8; 32],
|
|
finalized: &HashSet<AssetBase>, // The finalization set.
|
|
) -> Result<SupplyInfo, Error> {
|
|
bundle
|
|
.ik
|
|
.verify(&sighash, &bundle.authorization.signature)
|
|
.map_err(|_| IssueBundleInvalidSignature)?;
|
|
|
|
let supply_info =
|
|
bundle
|
|
.actions()
|
|
.iter()
|
|
.try_fold(SupplyInfo::new(), |mut supply_info, action| {
|
|
if !is_asset_desc_of_valid_size(action.asset_desc()) {
|
|
return Err(WrongAssetDescSize);
|
|
}
|
|
|
|
let (asset, supply) = action.verify_supply(bundle.ik())?;
|
|
|
|
// Fail if the asset was previously finalized.
|
|
if finalized.contains(&asset) {
|
|
return Err(IssueActionPreviouslyFinalizedAssetBase(asset));
|
|
}
|
|
|
|
supply_info.add_supply(asset, supply)?;
|
|
|
|
Ok(supply_info)
|
|
})?;
|
|
|
|
Ok(supply_info)
|
|
}
|
|
|
|
/// Errors produced during the issuance process
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub enum Error {
|
|
/// The requested IssueAction not exists in the bundle.
|
|
IssueActionNotFound,
|
|
/// The provided `isk` and the derived `ik` does not match at least one note type.
|
|
IssueBundleIkMismatchAssetBase,
|
|
/// `asset_desc` should be between 1 and 512 bytes.
|
|
WrongAssetDescSize,
|
|
/// The `IssueAction` is not finalized but contains no notes.
|
|
IssueActionWithoutNoteNotFinalized,
|
|
/// The `AssetBase` is the Pallas identity point, which is invalid.
|
|
AssetBaseCannotBeIdentityPoint,
|
|
|
|
/// Verification errors:
|
|
/// Invalid signature.
|
|
IssueBundleInvalidSignature,
|
|
/// The provided `AssetBase` has been previously finalized.
|
|
IssueActionPreviouslyFinalizedAssetBase(AssetBase),
|
|
|
|
/// Overflow error occurred while calculating the value of the asset
|
|
ValueSumOverflow,
|
|
}
|
|
|
|
impl fmt::Display for Error {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
IssueActionNotFound => {
|
|
write!(f, "the requested IssueAction not exists in the bundle.")
|
|
}
|
|
IssueBundleIkMismatchAssetBase => {
|
|
write!(
|
|
f,
|
|
"the provided `isk` and the derived `ik` do not match at least one note type"
|
|
)
|
|
}
|
|
WrongAssetDescSize => {
|
|
write!(f, "`asset_desc` should be between 1 and 512 bytes")
|
|
}
|
|
IssueActionWithoutNoteNotFinalized => {
|
|
write!(
|
|
f,
|
|
"this `IssueAction` contains no notes but is not finalized"
|
|
)
|
|
}
|
|
AssetBaseCannotBeIdentityPoint => {
|
|
write!(
|
|
f,
|
|
"the AssetBase is the identity point of the Pallas curve, which is invalid."
|
|
)
|
|
}
|
|
IssueBundleInvalidSignature => {
|
|
write!(f, "invalid signature")
|
|
}
|
|
IssueActionPreviouslyFinalizedAssetBase(_) => {
|
|
write!(f, "the provided `AssetBase` has been previously finalized")
|
|
}
|
|
ValueSumOverflow => {
|
|
write!(
|
|
f,
|
|
"overflow error occurred while calculating the value of the asset"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{AssetSupply, IssueBundle, IssueInfo};
|
|
use crate::issuance::Error::{
|
|
AssetBaseCannotBeIdentityPoint, IssueActionNotFound,
|
|
IssueActionPreviouslyFinalizedAssetBase, IssueBundleIkMismatchAssetBase,
|
|
IssueBundleInvalidSignature, WrongAssetDescSize,
|
|
};
|
|
use crate::issuance::{verify_issue_bundle, IssueAction, Signed, Unauthorized};
|
|
use crate::keys::{
|
|
FullViewingKey, IssuanceAuthorizingKey, IssuanceValidatingKey, Scope, SpendingKey,
|
|
};
|
|
use crate::note::{AssetBase, Nullifier};
|
|
use crate::value::{NoteValue, ValueSum};
|
|
use crate::{Address, Note};
|
|
use group::{Group, GroupEncoding};
|
|
use nonempty::NonEmpty;
|
|
use pasta_curves::pallas::{Point, Scalar};
|
|
use rand::rngs::OsRng;
|
|
use rand::RngCore;
|
|
use std::collections::HashSet;
|
|
|
|
fn setup_params() -> (
|
|
OsRng,
|
|
IssuanceAuthorizingKey,
|
|
IssuanceValidatingKey,
|
|
Address,
|
|
[u8; 32],
|
|
) {
|
|
let mut rng = OsRng;
|
|
|
|
let isk = IssuanceAuthorizingKey::random();
|
|
let ik: IssuanceValidatingKey = (&isk).into();
|
|
|
|
let fvk = FullViewingKey::from(&SpendingKey::random(&mut rng));
|
|
let recipient = fvk.address_at(0u32, Scope::External);
|
|
|
|
let mut sighash = [0u8; 32];
|
|
rng.fill_bytes(&mut sighash);
|
|
|
|
(rng, isk, ik, recipient, sighash)
|
|
}
|
|
|
|
fn setup_verify_supply_test_params(
|
|
note1_value: u64,
|
|
note2_value: u64,
|
|
note1_asset_desc: &str,
|
|
note2_asset_desc: Option<&str>, // if None, both notes use the same asset
|
|
finalize: bool,
|
|
) -> (IssuanceValidatingKey, AssetBase, IssueAction) {
|
|
let (mut rng, _, ik, recipient, _) = setup_params();
|
|
|
|
let asset = AssetBase::derive(&ik, note1_asset_desc);
|
|
let note2_asset = note2_asset_desc.map_or(asset, |desc| AssetBase::derive(&ik, desc));
|
|
|
|
let note1 = Note::new(
|
|
recipient,
|
|
NoteValue::from_raw(note1_value),
|
|
asset,
|
|
Nullifier::dummy(&mut rng),
|
|
&mut rng,
|
|
);
|
|
|
|
let note2 = Note::new(
|
|
recipient,
|
|
NoteValue::from_raw(note2_value),
|
|
note2_asset,
|
|
Nullifier::dummy(&mut rng),
|
|
&mut rng,
|
|
);
|
|
|
|
(
|
|
ik,
|
|
asset,
|
|
IssueAction::from_parts(note1_asset_desc.into(), vec![note1, note2], finalize),
|
|
)
|
|
}
|
|
|
|
// This function computes the identity point on the Pallas curve and returns an Asset Base with that value.
|
|
fn identity_point() -> AssetBase {
|
|
let identity_point = (Point::generator() * -Scalar::one()) + Point::generator();
|
|
AssetBase::from_bytes(&identity_point.to_bytes()).unwrap()
|
|
}
|
|
|
|
fn identity_point_test_params(
|
|
note1_value: u64,
|
|
note2_value: u64,
|
|
) -> (IssuanceAuthorizingKey, IssueBundle<Unauthorized>, [u8; 32]) {
|
|
let (mut rng, isk, ik, recipient, sighash) = setup_params();
|
|
|
|
let note1 = Note::new(
|
|
recipient,
|
|
NoteValue::from_raw(note1_value),
|
|
identity_point(),
|
|
Nullifier::dummy(&mut rng),
|
|
&mut rng,
|
|
);
|
|
|
|
let note2 = Note::new(
|
|
recipient,
|
|
NoteValue::from_raw(note2_value),
|
|
identity_point(),
|
|
Nullifier::dummy(&mut rng),
|
|
&mut rng,
|
|
);
|
|
|
|
let action =
|
|
IssueAction::from_parts("arbitrary asset_desc".into(), vec![note1, note2], false);
|
|
|
|
let bundle = IssueBundle::from_parts(ik, NonEmpty::new(action), Unauthorized);
|
|
|
|
(isk, bundle, sighash)
|
|
}
|
|
|
|
#[test]
|
|
fn verify_supply_valid() {
|
|
let (ik, test_asset, action) =
|
|
setup_verify_supply_test_params(10, 20, "Asset 1", None, false);
|
|
|
|
let result = action.verify_supply(&ik);
|
|
|
|
assert!(result.is_ok());
|
|
|
|
let (asset, supply) = result.unwrap();
|
|
|
|
assert_eq!(asset, test_asset);
|
|
assert_eq!(supply.amount, ValueSum::from_raw(30));
|
|
assert!(!supply.is_finalized);
|
|
}
|
|
|
|
#[test]
|
|
fn verify_supply_invalid_for_asset_base_as_identity() {
|
|
let (_, bundle, _) = identity_point_test_params(10, 20);
|
|
|
|
assert_eq!(
|
|
bundle.actions.head.verify_supply(&bundle.ik),
|
|
Err(AssetBaseCannotBeIdentityPoint)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn verify_supply_finalized() {
|
|
let (ik, test_asset, action) =
|
|
setup_verify_supply_test_params(10, 20, "Asset 1", None, true);
|
|
|
|
let result = action.verify_supply(&ik);
|
|
|
|
assert!(result.is_ok());
|
|
|
|
let (asset, supply) = result.unwrap();
|
|
|
|
assert_eq!(asset, test_asset);
|
|
assert_eq!(supply.amount, ValueSum::from_raw(30));
|
|
assert!(supply.is_finalized);
|
|
}
|
|
|
|
#[test]
|
|
fn verify_supply_incorrect_asset_base() {
|
|
let (ik, _, action) =
|
|
setup_verify_supply_test_params(10, 20, "Asset 1", Some("Asset 2"), false);
|
|
|
|
assert_eq!(
|
|
action.verify_supply(&ik),
|
|
Err(IssueBundleIkMismatchAssetBase)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn verify_supply_ik_mismatch_asset_base() {
|
|
let (_, _, action) = setup_verify_supply_test_params(10, 20, "Asset 1", None, false);
|
|
let (_, _, ik, _, _) = setup_params();
|
|
|
|
assert_eq!(
|
|
action.verify_supply(&ik),
|
|
Err(IssueBundleIkMismatchAssetBase)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_basic() {
|
|
let (rng, _, ik, recipient, _) = setup_params();
|
|
|
|
let str = String::from("Halo");
|
|
let str2 = String::from("Halo2");
|
|
|
|
assert_eq!(
|
|
IssueBundle::new(
|
|
ik.clone(),
|
|
String::from_utf8(vec![b'X'; 513]).unwrap(),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::unsplittable()
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap_err(),
|
|
WrongAssetDescSize
|
|
);
|
|
|
|
assert_eq!(
|
|
IssueBundle::new(
|
|
ik.clone(),
|
|
"".to_string(),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::unsplittable()
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap_err(),
|
|
WrongAssetDescSize
|
|
);
|
|
|
|
let (mut bundle, asset) = IssueBundle::new(
|
|
ik,
|
|
str.clone(),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(5),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
let another_asset = bundle
|
|
.add_recipient(str, recipient, NoteValue::from_raw(10), rng)
|
|
.unwrap();
|
|
assert_eq!(asset, another_asset);
|
|
|
|
let third_asset = bundle
|
|
.add_recipient(str2.clone(), recipient, NoteValue::from_raw(15), rng)
|
|
.unwrap();
|
|
assert_ne!(asset, third_asset);
|
|
|
|
let actions = bundle.actions();
|
|
assert_eq!(actions.len(), 2);
|
|
|
|
let action = bundle.get_action_by_type(asset).unwrap();
|
|
assert_eq!(action.notes.len(), 2);
|
|
assert_eq!(action.notes.first().unwrap().value().inner(), 5);
|
|
assert_eq!(action.notes.first().unwrap().asset(), asset);
|
|
assert_eq!(action.notes.first().unwrap().recipient(), recipient);
|
|
|
|
assert_eq!(action.notes.get(1).unwrap().value().inner(), 10);
|
|
assert_eq!(action.notes.get(1).unwrap().asset(), asset);
|
|
assert_eq!(action.notes.get(1).unwrap().recipient(), recipient);
|
|
|
|
let action2 = bundle.get_action(str2).unwrap();
|
|
assert_eq!(action2.notes.len(), 1);
|
|
assert_eq!(action2.notes().first().unwrap().value().inner(), 15);
|
|
assert_eq!(action2.notes().first().unwrap().asset(), third_asset);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_finalize_asset() {
|
|
let (rng, _, ik, recipient, _) = setup_params();
|
|
|
|
let (mut bundle, _) = IssueBundle::new(
|
|
ik,
|
|
String::from("NFT"),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(u64::MIN),
|
|
}),
|
|
rng,
|
|
)
|
|
.expect("Should properly add recipient");
|
|
|
|
bundle
|
|
.finalize_action(String::from("NFT"))
|
|
.expect("Should finalize properly");
|
|
|
|
assert_eq!(
|
|
bundle
|
|
.finalize_action(String::from("Another NFT"))
|
|
.unwrap_err(),
|
|
IssueActionNotFound
|
|
);
|
|
|
|
assert_eq!(
|
|
bundle
|
|
.finalize_action(String::from_utf8(vec![b'X'; 513]).unwrap())
|
|
.unwrap_err(),
|
|
WrongAssetDescSize
|
|
);
|
|
|
|
assert_eq!(
|
|
bundle.finalize_action("".to_string()).unwrap_err(),
|
|
WrongAssetDescSize
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_prepare() {
|
|
let (rng, _, ik, recipient, sighash) = setup_params();
|
|
|
|
let (bundle, _) = IssueBundle::new(
|
|
ik,
|
|
String::from("Frost"),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(5),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
let prepared = bundle.prepare(sighash);
|
|
assert_eq!(prepared.authorization().sighash, sighash);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_sign() {
|
|
let (rng, isk, ik, recipient, sighash) = setup_params();
|
|
|
|
let (bundle, _) = IssueBundle::new(
|
|
ik.clone(),
|
|
String::from("Sign"),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(5),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
let signed = bundle.prepare(sighash).sign(&isk).unwrap();
|
|
|
|
ik.verify(&sighash, &signed.authorization.signature)
|
|
.expect("signature should be valid");
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_invalid_isk_for_signature() {
|
|
let (rng, _, ik, recipient, _) = setup_params();
|
|
|
|
let (bundle, _) = IssueBundle::new(
|
|
ik,
|
|
String::from("IssueBundle"),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(5),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
let wrong_isk: IssuanceAuthorizingKey = IssuanceAuthorizingKey::random();
|
|
|
|
let err = bundle
|
|
.prepare([0; 32])
|
|
.sign(&wrong_isk)
|
|
.expect_err("should not be able to sign");
|
|
|
|
assert_eq!(err, IssueBundleIkMismatchAssetBase);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_incorrect_asset_for_signature() {
|
|
let (mut rng, isk, ik, recipient, _) = setup_params();
|
|
|
|
// Create a bundle with "normal" note
|
|
let (mut bundle, _) = IssueBundle::new(
|
|
ik,
|
|
String::from("IssueBundle"),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(5),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
// Add "bad" note
|
|
let note = Note::new(
|
|
recipient,
|
|
NoteValue::from_raw(5),
|
|
AssetBase::derive(bundle.ik(), "zsa_asset"),
|
|
Nullifier::dummy(&mut rng),
|
|
&mut rng,
|
|
);
|
|
bundle.actions.first_mut().notes.push(note);
|
|
|
|
let err = bundle
|
|
.prepare([0; 32])
|
|
.sign(&isk)
|
|
.expect_err("should not be able to sign");
|
|
|
|
assert_eq!(err, IssueBundleIkMismatchAssetBase);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_verify() {
|
|
let (rng, isk, ik, recipient, sighash) = setup_params();
|
|
|
|
let (bundle, _) = IssueBundle::new(
|
|
ik,
|
|
String::from("Verify"),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(5),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
let signed = bundle.prepare(sighash).sign(&isk).unwrap();
|
|
let prev_finalized = &mut HashSet::new();
|
|
|
|
let supply_info = verify_issue_bundle(&signed, sighash, prev_finalized).unwrap();
|
|
|
|
supply_info.update_finalization_set(prev_finalized);
|
|
|
|
assert!(prev_finalized.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_verify_with_finalize() {
|
|
let (rng, isk, ik, recipient, sighash) = setup_params();
|
|
|
|
let (mut bundle, _) = IssueBundle::new(
|
|
ik.clone(),
|
|
String::from("Verify with finalize"),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(7),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
bundle
|
|
.finalize_action(String::from("Verify with finalize"))
|
|
.unwrap();
|
|
|
|
let signed = bundle.prepare(sighash).sign(&isk).unwrap();
|
|
let prev_finalized = &mut HashSet::new();
|
|
|
|
let supply_info = verify_issue_bundle(&signed, sighash, prev_finalized).unwrap();
|
|
|
|
supply_info.update_finalization_set(prev_finalized);
|
|
|
|
assert_eq!(prev_finalized.len(), 1);
|
|
assert!(prev_finalized.contains(&AssetBase::derive(&ik, "Verify with finalize")));
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_verify_with_supply_info() {
|
|
let (rng, isk, ik, recipient, sighash) = setup_params();
|
|
|
|
let asset1_desc = "Verify with supply info 1";
|
|
let asset2_desc = "Verify with supply info 2";
|
|
let asset3_desc = "Verify with supply info 3";
|
|
|
|
let asset1_base = AssetBase::derive(&ik, &String::from(asset1_desc));
|
|
let asset2_base = AssetBase::derive(&ik, &String::from(asset2_desc));
|
|
let asset3_base = AssetBase::derive(&ik, &String::from(asset3_desc));
|
|
|
|
let (mut bundle, _) = IssueBundle::new(
|
|
ik,
|
|
String::from(asset1_desc),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(7),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
bundle
|
|
.add_recipient(
|
|
String::from(asset1_desc),
|
|
recipient,
|
|
NoteValue::from_raw(8),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
bundle.finalize_action(String::from(asset1_desc)).unwrap();
|
|
|
|
bundle
|
|
.add_recipient(
|
|
String::from(asset2_desc),
|
|
recipient,
|
|
NoteValue::from_raw(10),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
bundle.finalize_action(String::from(asset2_desc)).unwrap();
|
|
|
|
bundle
|
|
.add_recipient(
|
|
String::from(asset3_desc),
|
|
recipient,
|
|
NoteValue::from_raw(5),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
let signed = bundle.prepare(sighash).sign(&isk).unwrap();
|
|
let prev_finalized = &mut HashSet::new();
|
|
|
|
let supply_info = verify_issue_bundle(&signed, sighash, prev_finalized).unwrap();
|
|
|
|
supply_info.update_finalization_set(prev_finalized);
|
|
|
|
assert_eq!(prev_finalized.len(), 2);
|
|
|
|
assert!(prev_finalized.contains(&asset1_base));
|
|
assert!(prev_finalized.contains(&asset2_base));
|
|
assert!(!prev_finalized.contains(&asset3_base));
|
|
|
|
assert_eq!(supply_info.assets.len(), 3);
|
|
|
|
assert_eq!(
|
|
supply_info.assets.get(&asset1_base),
|
|
Some(&AssetSupply::new(ValueSum::from_raw(15), true))
|
|
);
|
|
assert_eq!(
|
|
supply_info.assets.get(&asset2_base),
|
|
Some(&AssetSupply::new(ValueSum::from_raw(10), true))
|
|
);
|
|
assert_eq!(
|
|
supply_info.assets.get(&asset3_base),
|
|
Some(&AssetSupply::new(ValueSum::from_raw(5), false))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_verify_fail_previously_finalized() {
|
|
let (rng, isk, ik, recipient, sighash) = setup_params();
|
|
|
|
let (bundle, _) = IssueBundle::new(
|
|
ik.clone(),
|
|
String::from("already final"),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(5),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
let signed = bundle.prepare(sighash).sign(&isk).unwrap();
|
|
let prev_finalized = &mut HashSet::new();
|
|
|
|
let final_type = AssetBase::derive(&ik, &String::from("already final"));
|
|
|
|
prev_finalized.insert(final_type);
|
|
|
|
assert_eq!(
|
|
verify_issue_bundle(&signed, sighash, prev_finalized).unwrap_err(),
|
|
IssueActionPreviouslyFinalizedAssetBase(final_type)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_verify_fail_bad_signature() {
|
|
// we want to inject "bad" signatures for test purposes.
|
|
impl IssueBundle<Signed> {
|
|
pub fn set_authorization(&mut self, authorization: Signed) {
|
|
self.authorization = authorization;
|
|
}
|
|
}
|
|
|
|
let (rng, isk, ik, recipient, sighash) = setup_params();
|
|
|
|
let (bundle, _) = IssueBundle::new(
|
|
ik,
|
|
String::from("bad sig"),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(5),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
let wrong_isk: IssuanceAuthorizingKey = IssuanceAuthorizingKey::random();
|
|
|
|
let mut signed = bundle.prepare(sighash).sign(&isk).unwrap();
|
|
|
|
signed.set_authorization(Signed {
|
|
signature: wrong_isk.try_sign(&sighash).unwrap(),
|
|
});
|
|
|
|
let prev_finalized = &HashSet::new();
|
|
|
|
assert_eq!(
|
|
verify_issue_bundle(&signed, sighash, prev_finalized).unwrap_err(),
|
|
IssueBundleInvalidSignature
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_verify_fail_wrong_sighash() {
|
|
let (rng, isk, ik, recipient, random_sighash) = setup_params();
|
|
let (bundle, _) = IssueBundle::new(
|
|
ik,
|
|
String::from("Asset description"),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(5),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
let sighash: [u8; 32] = bundle.commitment().into();
|
|
let signed = bundle.prepare(sighash).sign(&isk).unwrap();
|
|
let prev_finalized = &HashSet::new();
|
|
|
|
assert_eq!(
|
|
verify_issue_bundle(&signed, random_sighash, prev_finalized).unwrap_err(),
|
|
IssueBundleInvalidSignature
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_verify_fail_incorrect_asset_description() {
|
|
let (mut rng, isk, ik, recipient, sighash) = setup_params();
|
|
|
|
let (bundle, _) = IssueBundle::new(
|
|
ik,
|
|
String::from("Asset description"),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(5),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
let mut signed = bundle.prepare(sighash).sign(&isk).unwrap();
|
|
|
|
// Add "bad" note
|
|
let note = Note::new(
|
|
recipient,
|
|
NoteValue::from_raw(5),
|
|
AssetBase::derive(signed.ik(), "zsa_asset"),
|
|
Nullifier::dummy(&mut rng),
|
|
&mut rng,
|
|
);
|
|
|
|
signed.actions.first_mut().notes.push(note);
|
|
|
|
let prev_finalized = &HashSet::new();
|
|
|
|
assert_eq!(
|
|
verify_issue_bundle(&signed, sighash, prev_finalized).unwrap_err(),
|
|
IssueBundleIkMismatchAssetBase
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_verify_fail_incorrect_ik() {
|
|
let asset_description = "Asset";
|
|
|
|
let (mut rng, isk, ik, recipient, sighash) = setup_params();
|
|
|
|
let (bundle, _) = IssueBundle::new(
|
|
ik,
|
|
String::from(asset_description),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(5),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
let mut signed = bundle.prepare(sighash).sign(&isk).unwrap();
|
|
|
|
let incorrect_isk = IssuanceAuthorizingKey::random();
|
|
let incorrect_ik: IssuanceValidatingKey = (&incorrect_isk).into();
|
|
|
|
// Add "bad" note
|
|
let note = Note::new(
|
|
recipient,
|
|
NoteValue::from_raw(55),
|
|
AssetBase::derive(&incorrect_ik, asset_description),
|
|
Nullifier::dummy(&mut rng),
|
|
&mut rng,
|
|
);
|
|
|
|
signed.actions.first_mut().notes = vec![note];
|
|
|
|
let prev_finalized = &HashSet::new();
|
|
|
|
assert_eq!(
|
|
verify_issue_bundle(&signed, sighash, prev_finalized).unwrap_err(),
|
|
IssueBundleIkMismatchAssetBase
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_verify_fail_wrong_asset_descr_size() {
|
|
// we want to inject "bad" description for test purposes.
|
|
impl IssueAction {
|
|
pub fn modify_descr(&mut self, new_descr: String) {
|
|
self.asset_desc = new_descr;
|
|
}
|
|
}
|
|
|
|
let (rng, isk, ik, recipient, sighash) = setup_params();
|
|
|
|
let (bundle, _) = IssueBundle::new(
|
|
ik,
|
|
String::from("Asset description"),
|
|
Some(IssueInfo {
|
|
recipient,
|
|
value: NoteValue::from_raw(5),
|
|
}),
|
|
rng,
|
|
)
|
|
.unwrap();
|
|
|
|
let mut signed = bundle.prepare(sighash).sign(&isk).unwrap();
|
|
let prev_finalized = HashSet::new();
|
|
|
|
// 1. Try too long description
|
|
signed
|
|
.actions
|
|
.first_mut()
|
|
.modify_descr(String::from_utf8(vec![b'X'; 513]).unwrap());
|
|
|
|
assert_eq!(
|
|
verify_issue_bundle(&signed, sighash, &prev_finalized).unwrap_err(),
|
|
WrongAssetDescSize
|
|
);
|
|
|
|
// 2. Try empty description
|
|
signed.actions.first_mut().modify_descr("".to_string());
|
|
|
|
assert_eq!(
|
|
verify_issue_bundle(&signed, sighash, &prev_finalized).unwrap_err(),
|
|
WrongAssetDescSize
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_cannot_be_signed_with_asset_base_identity_point() {
|
|
let (isk, bundle, sighash) = identity_point_test_params(10, 20);
|
|
|
|
assert_eq!(
|
|
bundle.prepare(sighash).sign(&isk).unwrap_err(),
|
|
AssetBaseCannotBeIdentityPoint
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn issue_bundle_verify_fail_asset_base_identity_point() {
|
|
let (isk, bundle, sighash) = identity_point_test_params(10, 20);
|
|
|
|
let signed = IssueBundle {
|
|
ik: bundle.ik,
|
|
actions: bundle.actions,
|
|
authorization: Signed {
|
|
signature: isk.try_sign(&sighash).unwrap(),
|
|
},
|
|
};
|
|
|
|
assert_eq!(
|
|
verify_issue_bundle(&signed, sighash, &HashSet::new()).unwrap_err(),
|
|
AssetBaseCannotBeIdentityPoint
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn finalize_flag_serialization() {
|
|
let mut rng = OsRng;
|
|
let (_, _, note) = Note::dummy(&mut rng, None, AssetBase::native());
|
|
|
|
let action =
|
|
IssueAction::new_with_flags(String::from("Asset description"), vec![note], 0u8)
|
|
.unwrap();
|
|
assert_eq!(action.flags(), 0b0000_0000);
|
|
|
|
let action =
|
|
IssueAction::new_with_flags(String::from("Asset description"), vec![note], 1u8)
|
|
.unwrap();
|
|
assert_eq!(action.flags(), 0b0000_0001);
|
|
|
|
let action =
|
|
IssueAction::new_with_flags(String::from("Asset description"), vec![note], 2u8);
|
|
assert!(action.is_none());
|
|
}
|
|
}
|
|
|
|
/// Generators for property testing.
|
|
#[cfg(any(test, feature = "test-dependencies"))]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
|
|
pub mod testing {
|
|
use crate::issuance::{IssueAction, IssueBundle, Prepared, Signed, Unauthorized};
|
|
use crate::keys::testing::arb_issuance_validating_key;
|
|
use crate::note::asset_base::testing::zsa_asset_base;
|
|
use crate::note::testing::arb_zsa_note;
|
|
use k256::schnorr;
|
|
use nonempty::NonEmpty;
|
|
use proptest::collection::vec;
|
|
use proptest::prelude::*;
|
|
use proptest::prop_compose;
|
|
|
|
prop_compose! {
|
|
/// Generate a uniformly distributed signature
|
|
pub(crate) fn arb_signature()(
|
|
sig_bytes in vec(prop::num::u8::ANY, 64)
|
|
) -> schnorr::Signature {
|
|
schnorr::Signature::try_from(sig_bytes.as_slice()).unwrap()
|
|
}
|
|
}
|
|
|
|
prop_compose! {
|
|
/// Generate an issue action
|
|
pub fn arb_issue_action(asset_desc: String)
|
|
(
|
|
asset in zsa_asset_base(asset_desc.clone()),
|
|
)
|
|
(
|
|
note in arb_zsa_note(asset),
|
|
)-> IssueAction {
|
|
IssueAction{
|
|
asset_desc: asset_desc.clone(),
|
|
notes: vec![note],
|
|
finalize: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
prop_compose! {
|
|
/// Generate an arbitrary issue bundle with fake authorization data.
|
|
pub fn arb_unathorized_issue_bundle(n_actions: usize)
|
|
(
|
|
actions in vec(arb_issue_action("asset_desc".to_string()), n_actions),
|
|
ik in arb_issuance_validating_key()
|
|
) -> IssueBundle<Unauthorized> {
|
|
let actions = NonEmpty::from_vec(actions).unwrap();
|
|
IssueBundle {
|
|
ik,
|
|
actions,
|
|
authorization: Unauthorized
|
|
}
|
|
}
|
|
}
|
|
|
|
prop_compose! {
|
|
/// Generate an arbitrary issue bundle with fake authorization data. This bundle does not
|
|
/// necessarily respect consensus rules
|
|
pub fn arb_prepared_issue_bundle(n_actions: usize)
|
|
(
|
|
actions in vec(arb_issue_action("asset_desc".to_string()), n_actions),
|
|
ik in arb_issuance_validating_key(),
|
|
fake_sighash in prop::array::uniform32(prop::num::u8::ANY)
|
|
) -> IssueBundle<Prepared> {
|
|
let actions = NonEmpty::from_vec(actions).unwrap();
|
|
IssueBundle {
|
|
ik,
|
|
actions,
|
|
authorization: Prepared { sighash: fake_sighash }
|
|
}
|
|
}
|
|
}
|
|
|
|
prop_compose! {
|
|
/// Generate an arbitrary issue bundle with fake authorization data. This bundle does not
|
|
/// necessarily respect consensus rules
|
|
pub fn arb_signed_issue_bundle(n_actions: usize)
|
|
(
|
|
actions in vec(arb_issue_action("asset_desc".to_string()), n_actions),
|
|
ik in arb_issuance_validating_key(),
|
|
fake_sig in arb_signature(),
|
|
) -> IssueBundle<Signed> {
|
|
let actions = NonEmpty::from_vec(actions).unwrap();
|
|
IssueBundle {
|
|
ik,
|
|
actions,
|
|
authorization: Signed { signature: fake_sig },
|
|
}
|
|
}
|
|
}
|
|
}
|