2021-01-20 12:30:35 -08:00
|
|
|
//! Structs related to bundles of Orchard actions.
|
|
|
|
|
2021-06-22 08:06:49 -07:00
|
|
|
use std::mem;
|
|
|
|
|
2021-02-24 12:07:49 -08:00
|
|
|
use nonempty::NonEmpty;
|
|
|
|
|
2021-01-20 12:30:35 -08:00
|
|
|
use crate::{
|
2021-06-21 05:38:43 -07:00
|
|
|
circuit::{Instance, Proof, VerifyingKey},
|
2021-04-21 08:57:48 -07:00
|
|
|
note::{ExtractedNoteCommitment, Nullifier, TransmittedNoteCiphertext},
|
2021-01-20 12:35:54 -08:00
|
|
|
primitives::redpallas::{self, Binding, SpendAuth},
|
2021-01-20 12:30:35 -08:00
|
|
|
tree::Anchor,
|
2021-06-13 12:55:39 -07:00
|
|
|
value::{ValueCommitTrapdoor, ValueCommitment, ValueSum},
|
2021-01-20 12:30:35 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
/// An action applied to the global ledger.
|
|
|
|
///
|
|
|
|
/// Externally, this both creates a note (adding a commitment to the global ledger),
|
|
|
|
/// and consumes some note created prior to this action (adding a nullifier to the
|
|
|
|
/// global ledger).
|
|
|
|
///
|
|
|
|
/// Internally, this may both consume a note and create a note, or it may do only one of
|
|
|
|
/// the two. TODO: Determine which is more efficient (circuit size vs bundle size).
|
2021-05-13 14:35:44 -07:00
|
|
|
#[derive(Debug, Clone)]
|
2021-04-21 08:57:48 -07:00
|
|
|
pub struct Action<A> {
|
2021-01-20 12:30:35 -08:00
|
|
|
/// The nullifier of the note being spent.
|
2021-04-21 20:33:37 -07:00
|
|
|
nf: Nullifier,
|
2021-01-20 12:30:35 -08:00
|
|
|
/// The randomized verification key for the note being spent.
|
2021-01-20 12:35:54 -08:00
|
|
|
rk: redpallas::VerificationKey<SpendAuth>,
|
2021-01-20 12:30:35 -08:00
|
|
|
/// A commitment to the new note being created.
|
2021-04-21 20:33:37 -07:00
|
|
|
cmx: ExtractedNoteCommitment,
|
2021-05-05 10:10:52 -07:00
|
|
|
/// The transmitted note ciphertext.
|
2021-04-21 08:57:48 -07:00
|
|
|
encrypted_note: TransmittedNoteCiphertext,
|
2021-01-20 12:30:35 -08:00
|
|
|
/// A commitment to the net value created or consumed by this action.
|
|
|
|
cv_net: ValueCommitment,
|
2021-03-03 09:35:25 -08:00
|
|
|
/// The authorization for this action.
|
2021-04-21 08:57:48 -07:00
|
|
|
authorization: A,
|
2021-03-03 09:35:25 -08:00
|
|
|
}
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
impl<T> Action<T> {
|
2021-04-21 20:33:37 -07:00
|
|
|
/// Constructs an `Action` from its constituent parts.
|
|
|
|
pub fn from_parts(
|
|
|
|
nf: Nullifier,
|
|
|
|
rk: redpallas::VerificationKey<SpendAuth>,
|
|
|
|
cmx: ExtractedNoteCommitment,
|
2021-04-21 08:57:48 -07:00
|
|
|
encrypted_note: TransmittedNoteCiphertext,
|
2021-04-21 20:33:37 -07:00
|
|
|
cv_net: ValueCommitment,
|
|
|
|
authorization: T,
|
|
|
|
) -> Self {
|
|
|
|
Action {
|
|
|
|
nf,
|
|
|
|
rk,
|
|
|
|
cmx,
|
|
|
|
encrypted_note,
|
|
|
|
cv_net,
|
|
|
|
authorization,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-21 21:24:01 -07:00
|
|
|
/// Returns the nullifier of the note being spent.
|
|
|
|
pub fn nullifier(&self) -> &Nullifier {
|
|
|
|
&self.nf
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the randomized verification key for the note being spent.
|
|
|
|
pub fn rk(&self) -> &redpallas::VerificationKey<SpendAuth> {
|
|
|
|
&self.rk
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the commitment to the new note being created.
|
|
|
|
pub fn cmx(&self) -> &ExtractedNoteCommitment {
|
|
|
|
&self.cmx
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the encrypted note ciphertext.
|
2021-04-21 08:57:48 -07:00
|
|
|
pub fn encrypted_note(&self) -> &TransmittedNoteCiphertext {
|
2021-04-21 21:24:01 -07:00
|
|
|
&self.encrypted_note
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the commitment to the net value created or consumed by this action.
|
|
|
|
pub fn cv_net(&self) -> &ValueCommitment {
|
|
|
|
&self.cv_net
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the authorization for this action.
|
|
|
|
pub fn authorization(&self) -> &T {
|
|
|
|
&self.authorization
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Transitions this action from one authorization state to another.
|
|
|
|
pub fn map<U>(self, step: impl FnOnce(T) -> U) -> Action<U> {
|
|
|
|
Action {
|
2021-04-21 20:33:37 -07:00
|
|
|
nf: self.nf,
|
2021-04-14 21:14:34 -07:00
|
|
|
rk: self.rk,
|
2021-04-21 20:33:37 -07:00
|
|
|
cmx: self.cmx,
|
2021-04-14 21:14:34 -07:00
|
|
|
encrypted_note: self.encrypted_note,
|
|
|
|
cv_net: self.cv_net,
|
|
|
|
authorization: step(self.authorization),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Transitions this action from one authorization state to another.
|
|
|
|
pub fn try_map<U, E>(self, step: impl FnOnce(T) -> Result<U, E>) -> Result<Action<U>, E> {
|
|
|
|
Ok(Action {
|
2021-04-21 20:33:37 -07:00
|
|
|
nf: self.nf,
|
2021-04-14 21:14:34 -07:00
|
|
|
rk: self.rk,
|
2021-04-21 20:33:37 -07:00
|
|
|
cmx: self.cmx,
|
2021-04-14 21:14:34 -07:00
|
|
|
encrypted_note: self.encrypted_note,
|
|
|
|
cv_net: self.cv_net,
|
|
|
|
authorization: step(self.authorization)?,
|
|
|
|
})
|
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
|
|
|
|
pub(crate) fn to_instance(&self, flags: Flags, anchor: Anchor) -> Instance {
|
|
|
|
Instance {
|
|
|
|
anchor,
|
|
|
|
cv_net: self.cv_net.clone(),
|
2021-05-28 04:06:50 -07:00
|
|
|
nf_old: self.nf,
|
2021-04-14 21:14:34 -07:00
|
|
|
rk: self.rk.clone(),
|
2021-06-08 00:27:08 -07:00
|
|
|
cmx: self.cmx,
|
2021-04-14 21:14:34 -07:00
|
|
|
enable_spend: flags.spends_enabled,
|
|
|
|
enable_output: flags.outputs_enabled,
|
|
|
|
}
|
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
2021-03-03 09:35:25 -08:00
|
|
|
/// Orchard-specific flags.
|
2021-04-14 21:14:34 -07:00
|
|
|
#[derive(Clone, Copy, Debug)]
|
2021-03-03 09:35:25 -08:00
|
|
|
pub struct Flags {
|
2021-04-21 21:35:47 -07:00
|
|
|
/// Flag denoting whether Orchard spends are enabled in the transaction.
|
|
|
|
///
|
2021-04-26 14:10:32 -07:00
|
|
|
/// If `false`, spent notes within [`Action`]s in the transaction's [`Bundle`] are
|
|
|
|
/// guaranteed to be dummy notes. If `true`, the spent notes may be either real or
|
2021-04-21 21:35:47 -07:00
|
|
|
/// dummy notes.
|
2021-03-03 09:35:25 -08:00
|
|
|
spends_enabled: bool,
|
2021-04-21 21:35:47 -07:00
|
|
|
/// Flag denoting whether Orchard outputs are enabled in the transaction.
|
|
|
|
///
|
2021-04-26 14:10:32 -07:00
|
|
|
/// If `false`, created notes within [`Action`]s in the transaction's [`Bundle`] are
|
|
|
|
/// guaranteed to be dummy notes. If `true`, the created notes may be either real or
|
2021-04-21 21:35:47 -07:00
|
|
|
/// dummy notes.
|
2021-03-03 09:35:25 -08:00
|
|
|
outputs_enabled: bool,
|
|
|
|
}
|
|
|
|
|
2021-04-23 12:04:45 -07:00
|
|
|
impl Flags {
|
|
|
|
/// Construct a set of flags from its constituent parts
|
|
|
|
pub fn from_parts(spends_enabled: bool, outputs_enabled: bool) -> Self {
|
2021-04-26 17:28:42 -07:00
|
|
|
Flags {
|
|
|
|
spends_enabled,
|
|
|
|
outputs_enabled,
|
|
|
|
}
|
2021-04-23 12:04:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Flag denoting whether Orchard spends are enabled in the transaction.
|
|
|
|
///
|
2021-04-26 14:10:32 -07:00
|
|
|
/// If `false`, spent notes within [`Action`]s in the transaction's [`Bundle`] are
|
|
|
|
/// guaranteed to be dummy notes. If `true`, the spent notes may be either real or
|
2021-04-23 12:04:45 -07:00
|
|
|
/// dummy notes.
|
|
|
|
pub fn spends_enabled(&self) -> bool {
|
|
|
|
self.spends_enabled
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Flag denoting whether Orchard outputs are enabled in the transaction.
|
|
|
|
///
|
2021-04-26 14:10:32 -07:00
|
|
|
/// If `false`, created notes within [`Action`]s in the transaction's [`Bundle`] are
|
|
|
|
/// guaranteed to be dummy notes. If `true`, the created notes may be either real or
|
2021-04-23 12:04:45 -07:00
|
|
|
/// dummy notes.
|
|
|
|
pub fn outputs_enabled(&self) -> bool {
|
|
|
|
self.outputs_enabled
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-03 09:35:25 -08:00
|
|
|
/// Defines the authorization type of an Orchard bundle.
|
|
|
|
pub trait Authorization {
|
|
|
|
/// The authorization type of an Orchard action.
|
|
|
|
type SpendAuth;
|
2021-01-20 12:30:35 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// A bundle of actions to be applied to the ledger.
|
2021-05-13 14:35:44 -07:00
|
|
|
#[derive(Debug, Clone)]
|
2021-04-23 11:47:22 -07:00
|
|
|
pub struct Bundle<T: Authorization, V> {
|
2021-04-21 21:35:47 -07:00
|
|
|
/// The list of actions that make up this bundle.
|
2021-03-03 09:35:25 -08:00
|
|
|
actions: NonEmpty<Action<T::SpendAuth>>,
|
2021-04-21 21:35:47 -07:00
|
|
|
/// Orchard-specific transaction-level flags for this bundle.
|
2021-04-21 20:33:37 -07:00
|
|
|
flags: Flags,
|
2021-04-22 06:32:20 -07:00
|
|
|
/// The net value moved out of the Orchard shielded pool.
|
2021-04-21 21:35:47 -07:00
|
|
|
///
|
2021-04-22 06:32:20 -07:00
|
|
|
/// This is the sum of Orchard spends minus the sum of Orchard outputs.
|
2021-04-23 11:47:22 -07:00
|
|
|
value_balance: V,
|
2021-04-21 21:35:47 -07:00
|
|
|
/// The root of the Orchard commitment tree that this bundle commits to.
|
2021-03-03 09:35:25 -08:00
|
|
|
anchor: Anchor,
|
2021-04-21 21:35:47 -07:00
|
|
|
/// The authorization for this bundle.
|
2021-03-03 09:35:25 -08:00
|
|
|
authorization: T,
|
2021-01-20 12:30:35 -08:00
|
|
|
}
|
|
|
|
|
2021-04-23 11:47:22 -07:00
|
|
|
impl<T: Authorization, V> Bundle<T, V> {
|
2021-04-21 20:33:37 -07:00
|
|
|
/// Constructs a `Bundle` from its constituent parts.
|
|
|
|
pub fn from_parts(
|
|
|
|
actions: NonEmpty<Action<T::SpendAuth>>,
|
|
|
|
flags: Flags,
|
2021-04-23 11:47:22 -07:00
|
|
|
value_balance: V,
|
2021-04-21 20:33:37 -07:00
|
|
|
anchor: Anchor,
|
|
|
|
authorization: T,
|
|
|
|
) -> Self {
|
|
|
|
Bundle {
|
|
|
|
actions,
|
|
|
|
flags,
|
|
|
|
value_balance,
|
|
|
|
anchor,
|
|
|
|
authorization,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-21 21:24:01 -07:00
|
|
|
/// Returns the list of actions that make up this bundle.
|
|
|
|
pub fn actions(&self) -> &NonEmpty<Action<T::SpendAuth>> {
|
|
|
|
&self.actions
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the Orchard-specific transaction-level flags for this bundle.
|
|
|
|
pub fn flags(&self) -> &Flags {
|
|
|
|
&self.flags
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the net value moved into or out of the Orchard shielded pool.
|
|
|
|
///
|
|
|
|
/// This is the sum of Orchard spends minus the sum Orchard outputs.
|
2021-04-23 11:47:22 -07:00
|
|
|
pub fn value_balance(&self) -> &V {
|
2021-04-21 21:24:01 -07:00
|
|
|
&self.value_balance
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the root of the Orchard commitment tree that this bundle commits to.
|
|
|
|
pub fn anchor(&self) -> &Anchor {
|
|
|
|
&self.anchor
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the authorization for this bundle.
|
|
|
|
///
|
|
|
|
/// In the case of a `Bundle<Authorized>`, this is the proof and binding signature.
|
|
|
|
pub fn authorization(&self) -> &T {
|
|
|
|
&self.authorization
|
|
|
|
}
|
|
|
|
|
2021-01-20 12:30:35 -08:00
|
|
|
/// Computes a commitment to the effects of this bundle, suitable for inclusion within
|
|
|
|
/// a transaction ID.
|
|
|
|
pub fn commitment(&self) -> BundleCommitment {
|
|
|
|
todo!()
|
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2021-04-30 08:43:51 -07:00
|
|
|
/// 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>>(
|
|
|
|
self,
|
|
|
|
f: F,
|
|
|
|
) -> Result<Bundle<T, V0>, E> {
|
|
|
|
Ok(Bundle {
|
|
|
|
actions: self.actions,
|
|
|
|
flags: self.flags,
|
|
|
|
value_balance: f(self.value_balance)?,
|
|
|
|
anchor: self.anchor,
|
|
|
|
authorization: self.authorization,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Transitions this bundle from one authorization state to another.
|
2021-04-26 17:22:04 -07:00
|
|
|
pub fn authorize<R, U: Authorization>(
|
2021-04-14 21:14:34 -07:00
|
|
|
self,
|
2021-04-26 17:22:04 -07:00
|
|
|
context: &mut R,
|
|
|
|
mut spend_auth: impl FnMut(&mut R, &T, T::SpendAuth) -> U::SpendAuth,
|
|
|
|
step: impl FnOnce(&mut R, T) -> U,
|
2021-04-23 11:47:22 -07:00
|
|
|
) -> Bundle<U, V> {
|
2021-04-14 21:14:34 -07:00
|
|
|
let authorization = self.authorization;
|
|
|
|
Bundle {
|
|
|
|
actions: self
|
|
|
|
.actions
|
2021-04-26 17:22:04 -07:00
|
|
|
.map(|a| a.map(|a_auth| spend_auth(context, &authorization, a_auth))),
|
2021-04-21 20:33:37 -07:00
|
|
|
flags: self.flags,
|
2021-04-14 21:14:34 -07:00
|
|
|
value_balance: self.value_balance,
|
|
|
|
anchor: self.anchor,
|
2021-04-26 17:22:04 -07:00
|
|
|
authorization: step(context, authorization),
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Transitions this bundle from one authorization state to another.
|
2021-04-26 17:22:04 -07:00
|
|
|
pub fn try_authorize<R, U: Authorization, E>(
|
2021-04-14 21:14:34 -07:00
|
|
|
self,
|
2021-04-26 17:22:04 -07:00
|
|
|
context: &mut R,
|
|
|
|
mut spend_auth: impl FnMut(&mut R, &T, T::SpendAuth) -> Result<U::SpendAuth, E>,
|
|
|
|
step: impl FnOnce(&mut R, T) -> Result<U, E>,
|
2021-04-23 11:47:22 -07:00
|
|
|
) -> Result<Bundle<U, V>, E> {
|
2021-04-14 21:14:34 -07:00
|
|
|
let authorization = self.authorization;
|
|
|
|
let new_actions = self
|
|
|
|
.actions
|
|
|
|
.into_iter()
|
2021-04-26 17:22:04 -07:00
|
|
|
.map(|a| a.try_map(|a_auth| spend_auth(context, &authorization, a_auth)))
|
2021-04-14 21:14:34 -07:00
|
|
|
.collect::<Result<Vec<_>, E>>()?;
|
|
|
|
|
|
|
|
Ok(Bundle {
|
|
|
|
actions: NonEmpty::from_vec(new_actions).unwrap(),
|
2021-04-21 20:33:37 -07:00
|
|
|
flags: self.flags,
|
2021-04-14 21:14:34 -07:00
|
|
|
value_balance: self.value_balance,
|
|
|
|
anchor: self.anchor,
|
2021-04-26 17:22:04 -07:00
|
|
|
authorization: step(context, authorization)?,
|
2021-04-14 21:14:34 -07:00
|
|
|
})
|
|
|
|
}
|
2021-06-21 05:38:43 -07:00
|
|
|
|
|
|
|
pub(crate) fn to_instances(&self) -> Vec<Instance> {
|
|
|
|
self.actions
|
|
|
|
.iter()
|
|
|
|
.map(|a| a.to_instance(self.flags, self.anchor))
|
|
|
|
.collect()
|
|
|
|
}
|
2021-01-20 12:30:35 -08:00
|
|
|
}
|
|
|
|
|
2021-06-13 12:55:39 -07:00
|
|
|
impl<T: Authorization, V: Copy + Into<ValueSum>> Bundle<T, V> {
|
|
|
|
/// Returns the transaction binding validating key for this bundle.
|
|
|
|
///
|
|
|
|
/// This can be used to validate the [`Authorized::binding_signature`] returned from
|
|
|
|
/// [`Bundle::authorization`].
|
|
|
|
pub fn binding_validating_key(&self) -> redpallas::VerificationKey<Binding> {
|
|
|
|
(self
|
|
|
|
.actions
|
|
|
|
.iter()
|
|
|
|
.map(|a| a.cv_net())
|
|
|
|
.sum::<ValueCommitment>()
|
|
|
|
- ValueCommitment::derive(self.value_balance.into(), ValueCommitTrapdoor::zero()))
|
|
|
|
.into_bvk()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-03 09:35:25 -08:00
|
|
|
/// Authorizing data for a bundle of actions, ready to be committed to the ledger.
|
2021-05-13 14:35:44 -07:00
|
|
|
#[derive(Debug, Clone)]
|
2021-04-27 12:56:36 -07:00
|
|
|
pub struct Authorized {
|
|
|
|
proof: Proof,
|
|
|
|
binding_signature: redpallas::Signature<Binding>,
|
2021-01-20 12:30:35 -08:00
|
|
|
}
|
|
|
|
|
2021-04-27 12:56:36 -07:00
|
|
|
impl Authorization for Authorized {
|
|
|
|
type SpendAuth = redpallas::Signature<SpendAuth>;
|
2021-04-21 08:57:48 -07:00
|
|
|
}
|
|
|
|
|
2021-04-21 20:33:37 -07:00
|
|
|
impl Authorized {
|
|
|
|
/// Constructs the authorizing data for a bundle of actions from its constituent parts.
|
|
|
|
pub fn from_parts(proof: Proof, binding_signature: redpallas::Signature<Binding>) -> Self {
|
|
|
|
Authorized {
|
|
|
|
proof,
|
|
|
|
binding_signature,
|
|
|
|
}
|
|
|
|
}
|
2021-04-27 12:56:36 -07:00
|
|
|
|
|
|
|
/// Return the proof component of the authorizing data.
|
|
|
|
pub fn proof(&self) -> &Proof {
|
|
|
|
&self.proof
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return the binding signature.
|
|
|
|
pub fn binding_signature(&self) -> &redpallas::Signature<Binding> {
|
|
|
|
&self.binding_signature
|
|
|
|
}
|
2021-04-21 20:33:37 -07:00
|
|
|
}
|
|
|
|
|
2021-04-23 11:47:22 -07:00
|
|
|
impl<V> Bundle<Authorized, V> {
|
2021-06-22 08:06:49 -07:00
|
|
|
/// Returns the amount of heap-allocated memory used by this bundle.
|
|
|
|
pub fn dynamic_usage(&self) -> usize {
|
|
|
|
// NonEmpty<T> stores its head element separately from its tail Vec<T>.
|
|
|
|
// TODO: This underestimates the dynamic usage; switch to NonEmpty::capacity once
|
|
|
|
// https://github.com/cloudhead/nonempty/issues/33 is closed.
|
|
|
|
(self.actions.len() - 1) * mem::size_of::<Action<redpallas::Signature<SpendAuth>>>()
|
|
|
|
+ self.authorization.proof.dynamic_usage()
|
|
|
|
}
|
|
|
|
|
2021-01-20 12:30:35 -08:00
|
|
|
/// Computes a commitment to the authorizing data within for this bundle.
|
|
|
|
///
|
2021-03-03 09:35:25 -08:00
|
|
|
/// This together with `Bundle::commitment` bind the entire bundle.
|
2021-01-20 12:30:35 -08:00
|
|
|
pub fn authorizing_commitment(&self) -> BundleAuthorizingCommitment {
|
|
|
|
todo!()
|
|
|
|
}
|
2021-06-21 05:38:43 -07:00
|
|
|
|
|
|
|
/// Verifies the proof for this bundle.
|
|
|
|
pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2::plonk::Error> {
|
|
|
|
self.authorization()
|
|
|
|
.proof()
|
|
|
|
.verify(vk, &self.to_instances())
|
|
|
|
}
|
2021-01-20 12:30:35 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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 BundleCommitment;
|
|
|
|
|
|
|
|
/// A commitment to the authorizing data within a bundle of actions.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct BundleAuthorizingCommitment;
|
2021-04-27 06:49:49 -07:00
|
|
|
|
|
|
|
/// Generators for property testing.
|
|
|
|
#[cfg(any(test, feature = "test-dependencies"))]
|
|
|
|
pub mod testing {
|
|
|
|
use nonempty::NonEmpty;
|
2021-06-12 13:35:37 -07:00
|
|
|
use pasta_curves::{arithmetic::FieldExt, pallas};
|
2021-05-08 12:51:55 -07:00
|
|
|
use rand::{rngs::StdRng, SeedableRng};
|
2021-05-05 10:10:52 -07:00
|
|
|
use reddsa::orchard::SpendAuth;
|
2021-04-27 06:49:49 -07:00
|
|
|
|
|
|
|
use proptest::collection::vec;
|
|
|
|
use proptest::prelude::*;
|
|
|
|
|
|
|
|
use crate::{
|
2021-04-29 15:14:16 -07:00
|
|
|
circuit::Proof,
|
2021-04-27 06:49:49 -07:00
|
|
|
note::{
|
|
|
|
commitment::ExtractedNoteCommitment, nullifier::testing::arb_nullifier,
|
|
|
|
testing::arb_note, TransmittedNoteCiphertext,
|
|
|
|
},
|
2021-04-29 15:14:16 -07:00
|
|
|
primitives::redpallas::{
|
|
|
|
self,
|
|
|
|
testing::{
|
|
|
|
arb_binding_signing_key, arb_spendauth_signing_key, arb_spendauth_verification_key,
|
|
|
|
},
|
|
|
|
},
|
2021-04-27 06:49:49 -07:00
|
|
|
value::{
|
2021-05-05 16:23:21 -07:00
|
|
|
testing::arb_note_value_bounded, NoteValue, ValueCommitTrapdoor, ValueCommitment,
|
2021-05-05 16:19:42 -07:00
|
|
|
ValueSum, MAX_NOTE_VALUE,
|
2021-04-27 06:49:49 -07:00
|
|
|
},
|
|
|
|
Anchor,
|
|
|
|
};
|
|
|
|
|
2021-04-29 15:14:16 -07:00
|
|
|
use super::{Action, Authorization, Authorized, Bundle, Flags};
|
2021-04-27 06:49:49 -07:00
|
|
|
|
|
|
|
/// Marker for an unauthorized bundle with no proofs or signatures.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Unauthorized;
|
|
|
|
|
|
|
|
impl Authorization for Unauthorized {
|
|
|
|
type SpendAuth = ();
|
|
|
|
}
|
|
|
|
|
|
|
|
prop_compose! {
|
|
|
|
/// Generate an action without authorization data.
|
2021-05-05 10:10:52 -07:00
|
|
|
pub fn arb_unauthorized_action(spend_value: NoteValue, output_value: NoteValue)(
|
2021-04-27 06:49:49 -07:00
|
|
|
nf in arb_nullifier(),
|
|
|
|
rk in arb_spendauth_verification_key(),
|
2021-05-05 10:10:52 -07:00
|
|
|
note in arb_note(output_value),
|
2021-04-27 06:49:49 -07:00
|
|
|
) -> Action<()> {
|
|
|
|
let cmx = ExtractedNoteCommitment::from(note.commitment());
|
|
|
|
let cv_net = ValueCommitment::derive(
|
2021-05-05 10:10:52 -07:00
|
|
|
(spend_value - output_value).unwrap(),
|
2021-04-27 06:49:49 -07:00
|
|
|
ValueCommitTrapdoor::zero()
|
|
|
|
);
|
|
|
|
// FIXME: make a real one from the note.
|
|
|
|
let encrypted_note = TransmittedNoteCiphertext {
|
|
|
|
epk_bytes: [0u8; 32],
|
|
|
|
enc_ciphertext: [0u8; 580],
|
|
|
|
out_ciphertext: [0u8; 80]
|
|
|
|
};
|
|
|
|
Action {
|
|
|
|
nf,
|
|
|
|
rk,
|
|
|
|
cmx,
|
|
|
|
encrypted_note,
|
|
|
|
cv_net,
|
|
|
|
authorization: ()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-05 10:10:52 -07:00
|
|
|
/// Generate an unauthorized action having spend and output values less than MAX_NOTE_VALUE / n_actions.
|
|
|
|
pub fn arb_unauthorized_action_n(
|
|
|
|
n_actions: usize,
|
2021-05-05 10:21:45 -07:00
|
|
|
flags: Flags,
|
2021-05-05 10:10:52 -07:00
|
|
|
) -> impl Strategy<Value = (ValueSum, Action<()>)> {
|
2021-05-05 16:36:05 -07:00
|
|
|
let spend_value_gen = if flags.spends_enabled {
|
|
|
|
Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64))
|
|
|
|
} else {
|
|
|
|
Strategy::boxed(Just(NoteValue::zero()))
|
|
|
|
};
|
|
|
|
|
2021-05-05 16:44:10 -07:00
|
|
|
spend_value_gen.prop_flat_map(move |spend_value| {
|
|
|
|
let output_value_gen = if flags.outputs_enabled {
|
|
|
|
Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64))
|
|
|
|
} else {
|
|
|
|
Strategy::boxed(Just(NoteValue::zero()))
|
|
|
|
};
|
|
|
|
|
|
|
|
output_value_gen.prop_flat_map(move |output_value| {
|
|
|
|
arb_unauthorized_action(spend_value, output_value)
|
|
|
|
.prop_map(move |a| ((spend_value - output_value).unwrap(), a))
|
|
|
|
})
|
|
|
|
})
|
2021-05-05 10:10:52 -07:00
|
|
|
}
|
|
|
|
|
2021-04-29 15:14:16 -07:00
|
|
|
prop_compose! {
|
|
|
|
/// Generate an action with invalid (random) authorization data.
|
2021-05-05 10:10:52 -07:00
|
|
|
pub fn arb_action(spend_value: NoteValue, output_value: NoteValue)(
|
2021-04-29 15:14:16 -07:00
|
|
|
nf in arb_nullifier(),
|
|
|
|
sk in arb_spendauth_signing_key(),
|
2021-05-05 10:10:52 -07:00
|
|
|
note in arb_note(output_value),
|
2021-04-29 15:14:16 -07:00
|
|
|
rng_seed in prop::array::uniform32(prop::num::u8::ANY),
|
|
|
|
fake_sighash in prop::array::uniform32(prop::num::u8::ANY),
|
2021-05-05 16:44:10 -07:00
|
|
|
) -> Action<redpallas::Signature<SpendAuth>> {
|
2021-04-29 15:14:16 -07:00
|
|
|
let cmx = ExtractedNoteCommitment::from(note.commitment());
|
|
|
|
let cv_net = ValueCommitment::derive(
|
2021-05-05 10:10:52 -07:00
|
|
|
(spend_value - output_value).unwrap(),
|
2021-04-29 15:14:16 -07:00
|
|
|
ValueCommitTrapdoor::zero()
|
|
|
|
);
|
|
|
|
|
|
|
|
// FIXME: make a real one from the note.
|
|
|
|
let encrypted_note = TransmittedNoteCiphertext {
|
|
|
|
epk_bytes: [0u8; 32],
|
|
|
|
enc_ciphertext: [0u8; 580],
|
|
|
|
out_ciphertext: [0u8; 80]
|
|
|
|
};
|
|
|
|
|
|
|
|
let rng = StdRng::from_seed(rng_seed);
|
|
|
|
|
|
|
|
Action {
|
|
|
|
nf,
|
|
|
|
rk: redpallas::VerificationKey::from(&sk),
|
|
|
|
cmx,
|
|
|
|
encrypted_note,
|
|
|
|
cv_net,
|
|
|
|
authorization: sk.sign(rng, &fake_sighash),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-05 10:10:52 -07:00
|
|
|
/// Generate an authorized action having spend and output values less than MAX_NOTE_VALUE / n_actions.
|
|
|
|
pub fn arb_action_n(
|
|
|
|
n_actions: usize,
|
2021-05-05 10:21:45 -07:00
|
|
|
flags: Flags,
|
2021-05-05 16:44:10 -07:00
|
|
|
) -> impl Strategy<Value = (ValueSum, Action<redpallas::Signature<SpendAuth>>)> {
|
2021-05-05 16:36:05 -07:00
|
|
|
let spend_value_gen = if flags.spends_enabled {
|
|
|
|
Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64))
|
|
|
|
} else {
|
|
|
|
Strategy::boxed(Just(NoteValue::zero()))
|
|
|
|
};
|
|
|
|
|
2021-05-05 16:44:10 -07:00
|
|
|
spend_value_gen.prop_flat_map(move |spend_value| {
|
|
|
|
let output_value_gen = if flags.outputs_enabled {
|
|
|
|
Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64))
|
|
|
|
} else {
|
|
|
|
Strategy::boxed(Just(NoteValue::zero()))
|
|
|
|
};
|
|
|
|
|
|
|
|
output_value_gen.prop_flat_map(move |output_value| {
|
|
|
|
arb_action(spend_value, output_value)
|
|
|
|
.prop_map(move |a| ((spend_value - output_value).unwrap(), a))
|
|
|
|
})
|
|
|
|
})
|
2021-05-05 10:10:52 -07:00
|
|
|
}
|
|
|
|
|
2021-04-27 06:49:49 -07:00
|
|
|
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 {
|
|
|
|
Flags::from_parts(spends_enabled, outputs_enabled)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-12 13:35:37 -07:00
|
|
|
prop_compose! {
|
|
|
|
fn arb_base()(bytes in prop::array::uniform32(0u8..)) -> pallas::Base {
|
|
|
|
// Instead of rejecting out-of-range bytes, let's reduce them.
|
|
|
|
let mut buf = [0; 64];
|
|
|
|
buf[..32].copy_from_slice(&bytes);
|
|
|
|
pallas::Base::from_bytes_wide(&buf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 06:49:49 -07:00
|
|
|
prop_compose! {
|
|
|
|
/// Generate an arbitrary unauthorized bundle. This bundle does not
|
|
|
|
/// necessarily respect consensus rules; for that use
|
|
|
|
/// [`crate::builder::testing::arb_bundle`]
|
2021-05-05 10:10:52 -07:00
|
|
|
pub fn arb_unauthorized_bundle()
|
2021-05-05 10:46:24 -07:00
|
|
|
(
|
2021-05-05 10:21:45 -07:00
|
|
|
n_actions in 1usize..100,
|
2021-04-27 06:49:49 -07:00
|
|
|
flags in arb_flags(),
|
2021-05-05 10:21:45 -07:00
|
|
|
)
|
|
|
|
(
|
|
|
|
acts in vec(arb_unauthorized_action_n(n_actions, flags), n_actions),
|
2021-06-12 13:35:37 -07:00
|
|
|
anchor in arb_base().prop_map(Anchor::from),
|
2021-05-05 10:21:45 -07:00
|
|
|
flags in Just(flags)
|
2021-04-27 06:49:49 -07:00
|
|
|
) -> Bundle<Unauthorized, ValueSum> {
|
2021-05-05 10:10:52 -07:00
|
|
|
let (balances, actions): (Vec<ValueSum>, Vec<Action<_>>) = acts.into_iter().unzip();
|
2021-04-27 06:49:49 -07:00
|
|
|
|
|
|
|
Bundle::from_parts(
|
|
|
|
NonEmpty::from_vec(actions).unwrap(),
|
|
|
|
flags,
|
2021-05-05 10:10:52 -07:00
|
|
|
balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
|
2021-04-27 06:49:49 -07:00
|
|
|
anchor,
|
|
|
|
Unauthorized
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2021-04-29 15:14:16 -07:00
|
|
|
|
|
|
|
prop_compose! {
|
|
|
|
/// Generate an arbitrary bundle with fake authorization data. This bundle does not
|
|
|
|
/// necessarily respect consensus rules; for that use
|
|
|
|
/// [`crate::builder::testing::arb_bundle`]
|
2021-05-05 10:10:52 -07:00
|
|
|
pub fn arb_bundle()
|
2021-05-05 10:46:24 -07:00
|
|
|
(
|
2021-05-05 10:21:45 -07:00
|
|
|
n_actions in 1usize..100,
|
2021-04-29 15:14:16 -07:00
|
|
|
flags in arb_flags(),
|
2021-05-05 10:21:45 -07:00
|
|
|
)
|
|
|
|
(
|
|
|
|
acts in vec(arb_action_n(n_actions, flags), n_actions),
|
2021-06-12 13:35:37 -07:00
|
|
|
anchor in arb_base().prop_map(Anchor::from),
|
2021-04-29 15:14:16 -07:00
|
|
|
sk in arb_binding_signing_key(),
|
|
|
|
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),
|
2021-05-05 10:21:45 -07:00
|
|
|
flags in Just(flags)
|
2021-04-29 15:14:16 -07:00
|
|
|
) -> Bundle<Authorized, ValueSum> {
|
2021-05-05 10:10:52 -07:00
|
|
|
let (balances, actions): (Vec<ValueSum>, Vec<Action<_>>) = acts.into_iter().unzip();
|
2021-04-29 15:14:16 -07:00
|
|
|
let rng = StdRng::from_seed(rng_seed);
|
|
|
|
|
|
|
|
Bundle::from_parts(
|
|
|
|
NonEmpty::from_vec(actions).unwrap(),
|
|
|
|
flags,
|
2021-05-05 10:10:52 -07:00
|
|
|
balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
|
2021-04-29 15:14:16 -07:00
|
|
|
anchor,
|
|
|
|
Authorized {
|
2021-05-05 10:46:24 -07:00
|
|
|
proof: Proof::new(fake_proof),
|
2021-04-29 15:14:16 -07:00
|
|
|
binding_signature: sk.sign(rng, &fake_sighash),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2021-04-27 06:49:49 -07:00
|
|
|
}
|