mirror of https://github.com/zcash/orchard.git
ZSA burn functionality (#35)
Added a method to add assets to burn to the Builder bvk computation now includes the burnt assets Added Tests for bsk/bvk consistency for burning Added E2E tests for assets burning
This commit is contained in:
parent
9405f801cd
commit
d8f3563c1c
|
@ -12,7 +12,7 @@ jobs:
|
|||
# Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor
|
||||
docker:
|
||||
- image: cimg/rust:1.59.0
|
||||
- image: cimg/rust:1.61.0
|
||||
# Add steps to the job
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
|
||||
steps:
|
||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.61
|
||||
toolchain: 1.61.0
|
||||
override: true
|
||||
- name: Run benchmark
|
||||
run: cargo bench -- --output-format bencher | tee output.txt
|
||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.61
|
||||
toolchain: 1.61.0
|
||||
override: true
|
||||
- name: Run tests
|
||||
uses: actions-rs/cargo@v1
|
||||
|
@ -30,7 +30,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.61
|
||||
toolchain: 1.61.0
|
||||
override: true
|
||||
# Build benchmarks to prevent bitrot
|
||||
- name: Build benchmarks
|
||||
|
@ -46,7 +46,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.61
|
||||
toolchain: 1.61.0
|
||||
override: true
|
||||
- name: Setup mdBook
|
||||
uses: peaceiris/actions-mdbook@v1
|
||||
|
@ -90,7 +90,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.61
|
||||
toolchain: 1.61.0
|
||||
override: true
|
||||
- name: cargo fetch
|
||||
uses: actions-rs/cargo@v1
|
||||
|
@ -113,7 +113,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.61
|
||||
toolchain: 1.61.0
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
|
|
|
@ -5,19 +5,19 @@ on: pull_request
|
|||
|
||||
jobs:
|
||||
clippy:
|
||||
name: Clippy (1.61)
|
||||
name: Clippy (1.61.0)
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.61
|
||||
toolchain: 1.61.0
|
||||
components: clippy
|
||||
override: true
|
||||
- name: Run Clippy
|
||||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
name: Clippy (1.61)
|
||||
name: Clippy (1.61.0)
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features --all-targets -- -D warnings
|
||||
|
|
|
@ -4,4 +4,3 @@ Cargo.lock
|
|||
.vscode
|
||||
.idea
|
||||
action-circuit-layout.png
|
||||
proptest-regressions/*.txt
|
||||
|
|
|
@ -9,7 +9,7 @@ authors = [
|
|||
"Kris Nuttycombe <kris@electriccoin.co>",
|
||||
]
|
||||
edition = "2021"
|
||||
rust-version = "1.61"
|
||||
rust-version = "1.61.0"
|
||||
description = "The Orchard shielded transaction protocol"
|
||||
license-file = "LICENSE-BOSL"
|
||||
repository = "https://github.com/zcash/orchard"
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.61
|
||||
1.61.0
|
||||
|
|
|
@ -251,6 +251,7 @@ impl ActionInfo {
|
|||
pub struct Builder {
|
||||
spends: Vec<SpendInfo>,
|
||||
recipients: Vec<RecipientInfo>,
|
||||
burn: HashMap<AssetId, ValueSum>,
|
||||
flags: Flags,
|
||||
anchor: Anchor,
|
||||
}
|
||||
|
@ -261,6 +262,7 @@ impl Builder {
|
|||
Builder {
|
||||
spends: vec![],
|
||||
recipients: vec![],
|
||||
burn: HashMap::new(),
|
||||
flags,
|
||||
anchor,
|
||||
}
|
||||
|
@ -337,6 +339,17 @@ impl Builder {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Add an instruction to burn a given amount of a specific asset.
|
||||
pub fn add_burn(&mut self, asset: AssetId, value: NoteValue) -> Result<(), &'static str> {
|
||||
if asset.is_native().into() {
|
||||
return Err("Burning is only possible for non-native assets");
|
||||
}
|
||||
let cur = *self.burn.get(&asset).unwrap_or(&ValueSum::zero());
|
||||
let sum = (cur + value).ok_or("Orchard ValueSum operation overflowed")?;
|
||||
self.burn.insert(asset, sum);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The net value of the bundle to be built. The value of all spends,
|
||||
/// minus the value of all outputs.
|
||||
///
|
||||
|
@ -366,7 +379,7 @@ impl Builder {
|
|||
///
|
||||
/// The returned bundle will have no proof or signatures; these can be applied with
|
||||
/// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively.
|
||||
pub fn build<V: TryFrom<i64>>(
|
||||
pub fn build<V: TryFrom<i64> + Copy + Into<i64>>(
|
||||
self,
|
||||
mut rng: impl RngCore,
|
||||
) -> Result<Bundle<InProgress<Unproven, Unauthorized>, V>, Error> {
|
||||
|
@ -419,16 +432,14 @@ impl Builder {
|
|||
let anchor = self.anchor;
|
||||
|
||||
// Determine the value balance for this bundle, ensuring it is valid.
|
||||
let value_balance = pre_actions
|
||||
let native_value_balance: V = pre_actions
|
||||
.iter()
|
||||
.filter(|action| action.spend.note.asset().is_native().into())
|
||||
.fold(Some(ValueSum::zero()), |acc, action| {
|
||||
acc? + action.value_sum()
|
||||
})
|
||||
.ok_or(OverflowError)?;
|
||||
|
||||
let result_value_balance: V = i64::try_from(value_balance)
|
||||
.map_err(Error::ValueSum)
|
||||
.and_then(|i| V::try_from(i).map_err(|_| Error::ValueSum(value::OverflowError)))?;
|
||||
.ok_or(OverflowError)?
|
||||
.into()?;
|
||||
|
||||
// Compute the transaction binding signing key.
|
||||
let bsk = pre_actions
|
||||
|
@ -441,26 +452,26 @@ impl Builder {
|
|||
let (actions, circuits): (Vec<_>, Vec<_>) =
|
||||
pre_actions.into_iter().map(|a| a.build(&mut rng)).unzip();
|
||||
|
||||
// Verify that bsk and bvk are consistent.
|
||||
let bvk = (actions.iter().map(|a| a.cv_net()).sum::<ValueCommitment>()
|
||||
- ValueCommitment::derive(
|
||||
value_balance,
|
||||
ValueCommitTrapdoor::zero(),
|
||||
AssetId::native(),
|
||||
))
|
||||
.into_bvk();
|
||||
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
|
||||
|
||||
Ok(Bundle::from_parts(
|
||||
let bundle = Bundle::from_parts(
|
||||
NonEmpty::from_vec(actions).unwrap(),
|
||||
flags,
|
||||
result_value_balance,
|
||||
native_value_balance,
|
||||
self.burn
|
||||
.into_iter()
|
||||
.map(|(asset, value)| Ok((asset, value.into()?)))
|
||||
.collect::<Result<_, Error>>()?,
|
||||
anchor,
|
||||
InProgress {
|
||||
proof: Unproven { circuits },
|
||||
sigs: Unauthorized { bsk },
|
||||
},
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
redpallas::VerificationKey::from(&bundle.authorization().sigs.bsk),
|
||||
bundle.binding_validating_key()
|
||||
);
|
||||
Ok(bundle)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -785,7 +796,7 @@ pub mod testing {
|
|||
|
||||
impl<R: RngCore + CryptoRng> ArbitraryBundleInputs<R> {
|
||||
/// Create a bundle from the set of arbitrary bundle inputs.
|
||||
fn into_bundle<V: TryFrom<i64>>(mut self) -> Bundle<Authorized, V> {
|
||||
fn into_bundle<V: TryFrom<i64> + Copy + Into<i64>>(mut self) -> Bundle<Authorized, V> {
|
||||
let fvk = FullViewingKey::from(&self.sk);
|
||||
let flags = Flags::from_parts(true, true);
|
||||
let mut builder = Builder::new(flags, self.anchor);
|
||||
|
@ -866,14 +877,15 @@ pub mod testing {
|
|||
}
|
||||
|
||||
/// Produce an arbitrary valid Orchard bundle using a random spending key.
|
||||
pub fn arb_bundle<V: TryFrom<i64> + Debug>() -> impl Strategy<Value = Bundle<Authorized, V>> {
|
||||
pub fn arb_bundle<V: TryFrom<i64> + Debug + Copy + Into<i64>>(
|
||||
) -> impl Strategy<Value = Bundle<Authorized, V>> {
|
||||
arb_spending_key()
|
||||
.prop_flat_map(arb_bundle_inputs)
|
||||
.prop_map(|inputs| inputs.into_bundle::<V>())
|
||||
}
|
||||
|
||||
/// Produce an arbitrary valid Orchard bundle using a specified spending key.
|
||||
pub fn arb_bundle_with_key<V: TryFrom<i64> + Debug>(
|
||||
pub fn arb_bundle_with_key<V: TryFrom<i64> + Debug + Copy + Into<i64>>(
|
||||
k: SpendingKey,
|
||||
) -> impl Strategy<Value = Bundle<Authorized, V>> {
|
||||
arb_bundle_inputs(k).prop_map(|inputs| inputs.into_bundle::<V>())
|
||||
|
|
|
@ -140,6 +140,9 @@ pub struct Bundle<T: Authorization, V> {
|
|||
///
|
||||
/// This is the sum of Orchard spends minus the sum of Orchard outputs.
|
||||
value_balance: V,
|
||||
/// Assets intended for burning
|
||||
/// TODO We need to add a consensus check to make sure that it is impossible to burn ZEC.
|
||||
burn: Vec<(AssetId, V)>,
|
||||
/// The root of the Orchard commitment tree that this bundle commits to.
|
||||
anchor: Anchor,
|
||||
/// The authorization for this bundle.
|
||||
|
@ -172,6 +175,7 @@ impl<T: Authorization, V> Bundle<T, V> {
|
|||
actions: NonEmpty<Action<T::SpendAuth>>,
|
||||
flags: Flags,
|
||||
value_balance: V,
|
||||
burn: Vec<(AssetId, V)>,
|
||||
anchor: Anchor,
|
||||
authorization: T,
|
||||
) -> Self {
|
||||
|
@ -179,6 +183,7 @@ impl<T: Authorization, V> Bundle<T, V> {
|
|||
actions,
|
||||
flags,
|
||||
value_balance,
|
||||
burn,
|
||||
anchor,
|
||||
authorization,
|
||||
}
|
||||
|
@ -214,8 +219,8 @@ impl<T: Authorization, V> Bundle<T, V> {
|
|||
}
|
||||
|
||||
/// Construct a new bundle by applying a transformation that might fail
|
||||
/// to the value balance.
|
||||
pub fn try_map_value_balance<V0, E, F: FnOnce(V) -> Result<V0, E>>(
|
||||
/// to the value balance and balances of assets to burn.
|
||||
pub fn try_map_value_balance<V0, E, F: Fn(V) -> Result<V0, E>>(
|
||||
self,
|
||||
f: F,
|
||||
) -> Result<Bundle<T, V0>, E> {
|
||||
|
@ -223,6 +228,11 @@ impl<T: Authorization, V> Bundle<T, V> {
|
|||
actions: self.actions,
|
||||
flags: self.flags,
|
||||
value_balance: f(self.value_balance)?,
|
||||
burn: self
|
||||
.burn
|
||||
.into_iter()
|
||||
.map(|(asset, value)| Ok((asset, f(value)?)))
|
||||
.collect::<Result<Vec<(AssetId, V0)>, E>>()?,
|
||||
anchor: self.anchor,
|
||||
authorization: self.authorization,
|
||||
})
|
||||
|
@ -244,6 +254,7 @@ impl<T: Authorization, V> Bundle<T, V> {
|
|||
value_balance: self.value_balance,
|
||||
anchor: self.anchor,
|
||||
authorization: step(context, authorization),
|
||||
burn: self.burn,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,6 +278,7 @@ impl<T: Authorization, V> Bundle<T, V> {
|
|||
value_balance: self.value_balance,
|
||||
anchor: self.anchor,
|
||||
authorization: step(context, authorization)?,
|
||||
burn: self.burn,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -386,7 +398,19 @@ impl<T: Authorization, V: Copy + Into<i64>> Bundle<T, V> {
|
|||
ValueSum::from_raw(self.value_balance.into()),
|
||||
ValueCommitTrapdoor::zero(),
|
||||
AssetId::native(),
|
||||
))
|
||||
)
|
||||
- self
|
||||
.burn
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(asset, value)| {
|
||||
ValueCommitment::derive(
|
||||
ValueSum::from_raw(value.into()),
|
||||
ValueCommitTrapdoor::zero(),
|
||||
asset,
|
||||
)
|
||||
})
|
||||
.sum::<ValueCommitment>())
|
||||
.into_bvk()
|
||||
}
|
||||
}
|
||||
|
@ -503,6 +527,9 @@ pub mod testing {
|
|||
use super::{Action, Authorization, Authorized, Bundle, Flags};
|
||||
|
||||
pub use crate::action::testing::{arb_action, arb_unauthorized_action};
|
||||
use crate::note::asset_id::testing::zsa_asset_id;
|
||||
use crate::note::AssetId;
|
||||
use crate::value::testing::arb_value_sum;
|
||||
|
||||
/// Marker for an unauthorized bundle with no proofs or signatures.
|
||||
#[derive(Debug)]
|
||||
|
@ -562,6 +589,13 @@ pub mod testing {
|
|||
})
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
/// Create an arbitrary vector of assets to burn.
|
||||
pub fn arb_asset_to_burn()(asset_id in zsa_asset_id(), value in arb_value_sum()) -> (AssetId, ValueSum) {
|
||||
(asset_id, value)
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
/// Create an arbitrary set of flags.
|
||||
pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY) -> Flags {
|
||||
|
@ -589,7 +623,8 @@ pub mod testing {
|
|||
(
|
||||
acts in vec(arb_unauthorized_action_n(n_actions, flags), n_actions),
|
||||
anchor in arb_base().prop_map(Anchor::from),
|
||||
flags in Just(flags)
|
||||
flags in Just(flags),
|
||||
burn in vec(arb_asset_to_burn(), 1usize..10)
|
||||
) -> Bundle<Unauthorized, ValueSum> {
|
||||
let (balances, actions): (Vec<ValueSum>, Vec<Action<_>>) = acts.into_iter().unzip();
|
||||
|
||||
|
@ -597,8 +632,9 @@ pub mod testing {
|
|||
NonEmpty::from_vec(actions).unwrap(),
|
||||
flags,
|
||||
balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
|
||||
burn,
|
||||
anchor,
|
||||
Unauthorized
|
||||
Unauthorized,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -618,7 +654,8 @@ pub mod testing {
|
|||
rng_seed in prop::array::uniform32(prop::num::u8::ANY),
|
||||
fake_proof in vec(prop::num::u8::ANY, 1973),
|
||||
fake_sighash in prop::array::uniform32(prop::num::u8::ANY),
|
||||
flags in Just(flags)
|
||||
flags in Just(flags),
|
||||
burn in vec(arb_asset_to_burn(), 1usize..10)
|
||||
) -> Bundle<Authorized, ValueSum> {
|
||||
let (balances, actions): (Vec<ValueSum>, Vec<Action<_>>) = acts.into_iter().unzip();
|
||||
let rng = StdRng::from_seed(rng_seed);
|
||||
|
@ -627,11 +664,12 @@ pub mod testing {
|
|||
NonEmpty::from_vec(actions).unwrap(),
|
||||
flags,
|
||||
balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
|
||||
burn,
|
||||
anchor,
|
||||
Authorized {
|
||||
proof: Proof::new(fake_proof),
|
||||
binding_signature: sk.sign(rng, &fake_sighash),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
76
src/value.rs
76
src/value.rs
|
@ -53,6 +53,7 @@ use pasta_curves::{
|
|||
use rand::RngCore;
|
||||
use subtle::CtOption;
|
||||
|
||||
use crate::builder::Error;
|
||||
use crate::note::AssetId;
|
||||
use crate::{
|
||||
constants::fixed_bases::{VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_R_BYTES},
|
||||
|
@ -129,6 +130,12 @@ impl From<&NoteValue> for Assigned<pallas::Base> {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<NoteValue> for i128 {
|
||||
fn from(value: NoteValue) -> Self {
|
||||
value.0 as i128
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for NoteValue {
|
||||
type Output = ValueSum;
|
||||
|
||||
|
@ -181,15 +188,21 @@ impl ValueSum {
|
|||
sign,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn into<V: TryFrom<i64>>(self) -> Result<V, Error> {
|
||||
i64::try_from(self)
|
||||
.map_err(Error::ValueSum)
|
||||
.and_then(|i| V::try_from(i).map_err(|_| Error::ValueSum(OverflowError)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for ValueSum {
|
||||
impl<T: Into<i128>> Add<T> for ValueSum {
|
||||
type Output = Option<ValueSum>;
|
||||
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
fn add(self, rhs: T) -> Self::Output {
|
||||
self.0
|
||||
.checked_add(rhs.0)
|
||||
.checked_add(rhs.into())
|
||||
.filter(|v| VALUE_SUM_RANGE.contains(v))
|
||||
.map(ValueSum)
|
||||
}
|
||||
|
@ -227,6 +240,12 @@ impl TryFrom<ValueSum> for i64 {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ValueSum> for i128 {
|
||||
fn from(value: ValueSum) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
/// The blinding factor for a [`ValueCommitment`].
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ValueCommitTrapdoor(pallas::Scalar);
|
||||
|
@ -452,10 +471,11 @@ mod tests {
|
|||
};
|
||||
use crate::primitives::redpallas;
|
||||
|
||||
fn _bsk_consistent_with_bvk(
|
||||
fn check_binding_signature(
|
||||
native_values: &[(ValueSum, ValueCommitTrapdoor, AssetId)],
|
||||
arb_values: &[(ValueSum, ValueCommitTrapdoor, AssetId)],
|
||||
neg_trapdoors: &[ValueCommitTrapdoor],
|
||||
arb_values_to_burn: &[(ValueSum, ValueCommitTrapdoor, AssetId)],
|
||||
) {
|
||||
// for each arb value, create a negative value with a different trapdoor
|
||||
let neg_arb_values: Vec<_> = arb_values
|
||||
|
@ -471,7 +491,13 @@ mod tests {
|
|||
.sum::<Result<ValueSum, OverflowError>>()
|
||||
.expect("we generate values that won't overflow");
|
||||
|
||||
let values = [native_values, arb_values, &neg_arb_values].concat();
|
||||
let values = [
|
||||
native_values,
|
||||
arb_values,
|
||||
&neg_arb_values,
|
||||
arb_values_to_burn,
|
||||
]
|
||||
.concat();
|
||||
|
||||
let bsk = values
|
||||
.iter()
|
||||
|
@ -480,14 +506,20 @@ mod tests {
|
|||
.into_bsk();
|
||||
|
||||
let bvk = (values
|
||||
.iter()
|
||||
.map(|(value, rcv, asset)| ValueCommitment::derive(*value, *rcv, *asset))
|
||||
.into_iter()
|
||||
.map(|(value, rcv, asset)| ValueCommitment::derive(value, rcv, asset))
|
||||
.sum::<ValueCommitment>()
|
||||
- ValueCommitment::derive(
|
||||
native_value_balance,
|
||||
ValueCommitTrapdoor::zero(),
|
||||
AssetId::native(),
|
||||
))
|
||||
)
|
||||
- arb_values_to_burn
|
||||
.iter()
|
||||
.map(|(value, _, asset)| {
|
||||
ValueCommitment::derive(*value, ValueCommitTrapdoor::zero(), *asset)
|
||||
})
|
||||
.sum::<ValueCommitment>())
|
||||
.into_bvk();
|
||||
|
||||
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
|
||||
|
@ -495,34 +527,26 @@ mod tests {
|
|||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn bsk_consistent_with_bvk_native_only(
|
||||
fn bsk_consistent_with_bvk_native_with_zsa_transfer_and_burning(
|
||||
native_values in (1usize..10).prop_flat_map(|n_values|
|
||||
arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64).prop_flat_map(move |bound|
|
||||
prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor(), native_asset_id()), n_values)
|
||||
)
|
||||
),
|
||||
) {
|
||||
// Test with native note type (zec) only
|
||||
_bsk_consistent_with_bvk(&native_values, &[], &[]);
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn bsk_consistent_with_bvk(
|
||||
native_values in (1usize..10).prop_flat_map(|n_values|
|
||||
arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64).prop_flat_map(move |bound|
|
||||
prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor(), native_asset_id()), n_values)
|
||||
)
|
||||
),
|
||||
(arb_values,neg_trapdoors) in (1usize..10).prop_flat_map(|n_values|
|
||||
(asset_values, neg_trapdoors) in (1usize..10).prop_flat_map(|n_values|
|
||||
(arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64).prop_flat_map(move |bound|
|
||||
prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor(), arb_asset_id()), n_values)
|
||||
), prop::collection::vec(arb_trapdoor(), n_values))
|
||||
),
|
||||
burn_values in (1usize..10).prop_flat_map(|n_values|
|
||||
arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64)
|
||||
.prop_flat_map(move |bound| prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor(), arb_asset_id()), n_values))
|
||||
)
|
||||
) {
|
||||
// Test with native note type (zec)
|
||||
_bsk_consistent_with_bvk(&native_values, &arb_values, &neg_trapdoors);
|
||||
check_binding_signature(&native_values, &[], &[], &[]);
|
||||
check_binding_signature(&native_values, &[], &[], &burn_values);
|
||||
check_binding_signature(&native_values, &asset_values, &neg_trapdoors, &[]);
|
||||
check_binding_signature(&native_values, &asset_values, &neg_trapdoors, &burn_values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
135
tests/zsa.rs
135
tests/zsa.rs
|
@ -231,32 +231,31 @@ struct TestOutputInfo {
|
|||
fn build_and_verify_bundle(
|
||||
spends: Vec<&TestSpendInfo>,
|
||||
outputs: Vec<TestOutputInfo>,
|
||||
assets_to_burn: Vec<(AssetId, NoteValue)>,
|
||||
anchor: Anchor,
|
||||
expected_num_actions: usize,
|
||||
keys: &Keychain,
|
||||
) {
|
||||
) -> Result<(), &'static str> {
|
||||
let rng = OsRng;
|
||||
let shielded_bundle: Bundle<_, i64> = {
|
||||
let mut builder = Builder::new(Flags::from_parts(true, true), anchor);
|
||||
|
||||
spends.iter().for_each(|spend| {
|
||||
assert_eq!(
|
||||
builder.add_spend(keys.fvk().clone(), spend.note, spend.merkle_path().clone()),
|
||||
Ok(())
|
||||
);
|
||||
});
|
||||
outputs.iter().for_each(|output| {
|
||||
assert_eq!(
|
||||
builder.add_recipient(None, keys.recipient, output.value, output.asset, None),
|
||||
Ok(())
|
||||
)
|
||||
});
|
||||
spends.iter().try_for_each(|spend| {
|
||||
builder.add_spend(keys.fvk().clone(), spend.note, spend.merkle_path().clone())
|
||||
})?;
|
||||
outputs.iter().try_for_each(|output| {
|
||||
builder.add_recipient(None, keys.recipient, output.value, output.asset, None)
|
||||
})?;
|
||||
assets_to_burn
|
||||
.into_iter()
|
||||
.try_for_each(|(asset, value)| builder.add_burn(asset, value))?;
|
||||
build_and_sign_bundle(builder, rng, keys.pk(), keys.sk())
|
||||
};
|
||||
|
||||
// Verify the shielded bundle, currently without the proof.
|
||||
verify_bundle(&shielded_bundle, &keys.vk, false);
|
||||
assert_eq!(shielded_bundle.actions().len(), expected_num_actions);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Issue several ZSA and native notes and spend them in different combinations, e.g. split and join
|
||||
|
@ -268,20 +267,32 @@ fn zsa_issue_and_transfer() {
|
|||
let asset_descr = "zsa_asset";
|
||||
|
||||
// Prepare ZSA
|
||||
let (zsa_note1, zsa_note2) = issue_zsa_notes(asset_descr, &keys);
|
||||
let (zsa_note_1, zsa_note_2) = issue_zsa_notes(asset_descr, &keys);
|
||||
|
||||
let (merkle_path1, merkle_path2, anchor) =
|
||||
build_merkle_path_with_two_leaves(&zsa_note1, &zsa_note2);
|
||||
build_merkle_path_with_two_leaves(&zsa_note_1, &zsa_note_2);
|
||||
|
||||
let zsa_spend_1 = TestSpendInfo {
|
||||
note: zsa_note1,
|
||||
note: zsa_note_1,
|
||||
merkle_path: merkle_path1,
|
||||
};
|
||||
let zsa_spend_2 = TestSpendInfo {
|
||||
note: zsa_note2,
|
||||
note: zsa_note_2,
|
||||
merkle_path: merkle_path2,
|
||||
};
|
||||
|
||||
let native_note = create_native_note(&keys);
|
||||
let (native_merkle_path_1, native_merkle_path_2, native_anchor) =
|
||||
build_merkle_path_with_two_leaves(&native_note, &zsa_note_1);
|
||||
let native_spend: TestSpendInfo = TestSpendInfo {
|
||||
note: native_note,
|
||||
merkle_path: native_merkle_path_1,
|
||||
};
|
||||
let zsa_spend_with_native: TestSpendInfo = TestSpendInfo {
|
||||
note: zsa_note_1,
|
||||
merkle_path: native_merkle_path_2,
|
||||
};
|
||||
|
||||
// --------------------------- Tests -----------------------------------------
|
||||
|
||||
// 1. Spend single ZSA note
|
||||
|
@ -291,10 +302,12 @@ fn zsa_issue_and_transfer() {
|
|||
value: zsa_spend_1.note.value(),
|
||||
asset: zsa_spend_1.note.asset(),
|
||||
}],
|
||||
vec![],
|
||||
anchor,
|
||||
2,
|
||||
&keys,
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 2. Split single ZSA note into 2 notes
|
||||
let delta = 2; // arbitrary number for value manipulation
|
||||
|
@ -310,10 +323,12 @@ fn zsa_issue_and_transfer() {
|
|||
asset: zsa_spend_1.note.asset(),
|
||||
},
|
||||
],
|
||||
vec![],
|
||||
anchor,
|
||||
2,
|
||||
&keys,
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 3. Join 2 ZSA notes into a single note
|
||||
build_and_verify_bundle(
|
||||
|
@ -324,10 +339,12 @@ fn zsa_issue_and_transfer() {
|
|||
),
|
||||
asset: zsa_spend_1.note.asset(),
|
||||
}],
|
||||
vec![],
|
||||
anchor,
|
||||
2,
|
||||
&keys,
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 4. Take 2 ZSA notes and send them as 2 notes with different denomination
|
||||
build_and_verify_bundle(
|
||||
|
@ -342,10 +359,12 @@ fn zsa_issue_and_transfer() {
|
|||
asset: zsa_spend_2.note.asset(),
|
||||
},
|
||||
],
|
||||
vec![],
|
||||
anchor,
|
||||
2,
|
||||
&keys,
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 5. Spend single ZSA note, mixed with native note (shielding)
|
||||
build_and_verify_bundle(
|
||||
|
@ -360,24 +379,14 @@ fn zsa_issue_and_transfer() {
|
|||
asset: AssetId::native(),
|
||||
},
|
||||
],
|
||||
vec![],
|
||||
anchor,
|
||||
4,
|
||||
&keys,
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 6. Spend single ZSA note, mixed with native note (shielded to shielded)
|
||||
let native_note = create_native_note(&keys);
|
||||
let (native_merkle_path1, native_merkle_path2, native_anchor) =
|
||||
build_merkle_path_with_two_leaves(&native_note, &zsa_note1);
|
||||
let native_spend: TestSpendInfo = TestSpendInfo {
|
||||
note: native_note,
|
||||
merkle_path: native_merkle_path1,
|
||||
};
|
||||
let zsa_spend_with_native: TestSpendInfo = TestSpendInfo {
|
||||
note: zsa_note1,
|
||||
merkle_path: native_merkle_path2,
|
||||
};
|
||||
|
||||
build_and_verify_bundle(
|
||||
vec![&zsa_spend_with_native, &native_spend],
|
||||
vec![
|
||||
|
@ -390,21 +399,23 @@ fn zsa_issue_and_transfer() {
|
|||
asset: AssetId::native(),
|
||||
},
|
||||
],
|
||||
vec![],
|
||||
native_anchor,
|
||||
4,
|
||||
&keys,
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 7. Spend ZSA notes of different asset types
|
||||
let (zsa_note_t7, _) = issue_zsa_notes("zsa_asset2", &keys);
|
||||
let (merkle_path_t7_1, merkle_path_t7_2, anchor_t7) =
|
||||
build_merkle_path_with_two_leaves(&zsa_note_t7, &zsa_note2);
|
||||
build_merkle_path_with_two_leaves(&zsa_note_t7, &zsa_note_2);
|
||||
let zsa_spend_t7_1: TestSpendInfo = TestSpendInfo {
|
||||
note: zsa_note_t7,
|
||||
merkle_path: merkle_path_t7_1,
|
||||
};
|
||||
let zsa_spend_t7_2: TestSpendInfo = TestSpendInfo {
|
||||
note: zsa_note2,
|
||||
note: zsa_note_2,
|
||||
merkle_path: merkle_path_t7_2,
|
||||
};
|
||||
|
||||
|
@ -420,10 +431,12 @@ fn zsa_issue_and_transfer() {
|
|||
asset: zsa_spend_t7_2.note.asset(),
|
||||
},
|
||||
],
|
||||
vec![],
|
||||
anchor_t7,
|
||||
4,
|
||||
&keys,
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 8. Same but wrong denomination
|
||||
let result = std::panic::catch_unwind(|| {
|
||||
|
@ -439,10 +452,54 @@ fn zsa_issue_and_transfer() {
|
|||
asset: zsa_spend_t7_2.note.asset(),
|
||||
},
|
||||
],
|
||||
vec![],
|
||||
anchor_t7,
|
||||
4,
|
||||
&keys,
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
assert!(result.is_err());
|
||||
|
||||
// 9. Burn ZSA assets
|
||||
build_and_verify_bundle(
|
||||
vec![&zsa_spend_1],
|
||||
vec![],
|
||||
vec![(zsa_spend_1.note.asset(), zsa_spend_1.note.value())],
|
||||
anchor,
|
||||
2,
|
||||
&keys,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 10. Burn a partial amount of the ZSA assets
|
||||
let value_to_burn = 3;
|
||||
let value_to_transfer = zsa_spend_1.note.value().inner() - value_to_burn;
|
||||
|
||||
build_and_verify_bundle(
|
||||
vec![&zsa_spend_1],
|
||||
vec![TestOutputInfo {
|
||||
value: NoteValue::from_raw(value_to_transfer),
|
||||
asset: zsa_spend_1.note.asset(),
|
||||
}],
|
||||
vec![(zsa_spend_1.note.asset(), NoteValue::from_raw(value_to_burn))],
|
||||
anchor,
|
||||
2,
|
||||
&keys,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 11. Try to burn native asset - should fail
|
||||
let result = build_and_verify_bundle(
|
||||
vec![&native_spend],
|
||||
vec![],
|
||||
vec![(AssetId::native(), native_spend.note.value())],
|
||||
native_anchor,
|
||||
2,
|
||||
&keys,
|
||||
);
|
||||
match result {
|
||||
Ok(_) => panic!("Test should fail"),
|
||||
Err(error) => assert_eq!(error, "Burning is only possible for non-native assets"),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue