Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
# Add steps to the job
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
steps:
Expand Down
58 changes: 35 additions & 23 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ impl ActionInfo {
pub struct Builder {
spends: Vec<SpendInfo>,
recipients: Vec<RecipientInfo>,
burn: HashMap<AssetId, ValueSum>,
flags: Flags,
anchor: Anchor,
}
Expand All @@ -261,6 +262,7 @@ impl Builder {
Builder {
spends: vec![],
recipients: vec![],
burn: HashMap::new(),
flags,
anchor,
}
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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
Expand All @@ -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<Vec<(AssetId, V)>, Error>>()?,
anchor,
InProgress {
proof: Unproven { circuits },
sigs: Unauthorized { bsk },
},
))
);

assert_eq!(
redpallas::VerificationKey::from(&bundle.authorization().sigs.bsk),
bundle.binding_validating_key()
);
Ok(bundle)
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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>())
Expand Down
52 changes: 45 additions & 7 deletions src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -172,13 +175,15 @@ 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 {
Bundle {
actions,
flags,
value_balance,
burn,
anchor,
authorization,
}
Expand Down Expand Up @@ -214,15 +219,20 @@ 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> {
Ok(Bundle {
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,
})
Expand All @@ -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,
}
}

Expand All @@ -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,
})
}

Expand Down Expand Up @@ -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()
}
}
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -589,16 +623,18 @@ 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();

Bundle::from_parts(
NonEmpty::from_vec(actions).unwrap(),
flags,
balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
burn,
anchor,
Unauthorized
Unauthorized,
)
}
}
Expand All @@ -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);
Expand All @@ -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),
}
},
)
}
}
Expand Down
Loading