2021-01-20 12:30:35 -08:00
|
|
|
//! Structs related to bundles of Orchard actions.
|
|
|
|
|
2022-06-20 19:39:34 -07:00
|
|
|
mod batch;
|
2021-07-01 09:10:24 -07:00
|
|
|
pub mod commitments;
|
|
|
|
|
2022-06-20 19:39:34 -07:00
|
|
|
pub use batch::BatchValidator;
|
|
|
|
|
2022-04-28 13:20:23 -07:00
|
|
|
use core::fmt;
|
2021-06-22 08:06:49 -07:00
|
|
|
|
2021-07-01 09:10:24 -07:00
|
|
|
use blake2b_simd::Hash as Blake2bHash;
|
2021-09-14 12:40:15 -07:00
|
|
|
use memuse::DynamicUsage;
|
2021-02-24 12:07:49 -08:00
|
|
|
use nonempty::NonEmpty;
|
2022-03-14 16:30:37 -07:00
|
|
|
use zcash_note_encryption::{try_note_decryption, try_output_recovery_with_ovk};
|
2021-02-24 12:07:49 -08:00
|
|
|
|
2021-01-20 12:30:35 -08:00
|
|
|
use crate::{
|
2022-04-28 14:46:24 -07:00
|
|
|
action::Action,
|
2021-07-21 13:31:37 -07:00
|
|
|
address::Address,
|
2021-07-01 09:10:24 -07:00
|
|
|
bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data},
|
2021-06-21 05:38:43 -07:00
|
|
|
circuit::{Instance, Proof, VerifyingKey},
|
2022-10-15 14:00:09 -07:00
|
|
|
keys::{IncomingViewingKey, OutgoingViewingKey, PreparedIncomingViewingKey},
|
2022-04-28 14:46:24 -07:00
|
|
|
note::Note,
|
2021-07-21 13:31:37 -07:00
|
|
|
note_encryption::OrchardDomain,
|
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
|
|
|
};
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
impl<T> Action<T> {
|
2021-06-16 06:46:17 -07:00
|
|
|
/// Prepares the public instance for this action, for creating and verifying the
|
|
|
|
/// bundle proof.
|
|
|
|
pub fn to_instance(&self, flags: Flags, anchor: Anchor) -> Instance {
|
2021-04-14 21:14:34 -07:00
|
|
|
Instance {
|
|
|
|
anchor,
|
2022-04-28 14:46:24 -07:00
|
|
|
cv_net: self.cv_net().clone(),
|
|
|
|
nf_old: *self.nullifier(),
|
|
|
|
rk: self.rk().clone(),
|
|
|
|
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.
|
2023-12-15 15:19:24 -08:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
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-07-01 09:10:24 -07:00
|
|
|
const FLAG_SPENDS_ENABLED: u8 = 0b0000_0001;
|
|
|
|
const FLAG_OUTPUTS_ENABLED: u8 = 0b0000_0010;
|
|
|
|
const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED);
|
|
|
|
|
2021-04-23 12:04:45 -07:00
|
|
|
impl Flags {
|
|
|
|
/// Construct a set of flags from its constituent parts
|
2023-12-20 18:00:47 -08:00
|
|
|
pub(crate) const 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
|
|
|
}
|
|
|
|
|
2023-12-15 15:19:24 -08:00
|
|
|
/// The flag set with both spends and outputs enabled.
|
|
|
|
pub const ENABLED: Flags = Flags {
|
|
|
|
spends_enabled: true,
|
|
|
|
outputs_enabled: true,
|
|
|
|
};
|
|
|
|
|
|
|
|
/// The flag set with spends disabled.
|
|
|
|
pub const SPENDS_DISABLED: Flags = Flags {
|
|
|
|
spends_enabled: false,
|
|
|
|
outputs_enabled: true,
|
|
|
|
};
|
|
|
|
|
|
|
|
/// The flag set with outputs disabled.
|
|
|
|
pub const OUTPUTS_DISABLED: Flags = Flags {
|
|
|
|
spends_enabled: true,
|
|
|
|
outputs_enabled: false,
|
|
|
|
};
|
|
|
|
|
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-07-01 09:10:24 -07:00
|
|
|
|
|
|
|
/// Serialize flags to a byte as defined in [Zcash Protocol Spec § 7.1: Transaction
|
|
|
|
/// Encoding And Consensus][txencoding].
|
|
|
|
///
|
2021-07-29 06:16:14 -07:00
|
|
|
/// [txencoding]: https://zips.z.cash/protocol/protocol.pdf#txnencoding
|
2021-07-01 09:10:24 -07:00
|
|
|
pub fn to_byte(&self) -> u8 {
|
|
|
|
let mut value = 0u8;
|
|
|
|
if self.spends_enabled {
|
|
|
|
value |= FLAG_SPENDS_ENABLED;
|
|
|
|
}
|
|
|
|
if self.outputs_enabled {
|
|
|
|
value |= FLAG_OUTPUTS_ENABLED;
|
|
|
|
}
|
|
|
|
value
|
|
|
|
}
|
|
|
|
|
2022-04-28 13:45:05 -07:00
|
|
|
/// Parses flags from a single byte as defined in [Zcash Protocol Spec § 7.1:
|
|
|
|
/// Transaction Encoding And Consensus][txencoding].
|
|
|
|
///
|
|
|
|
/// Returns `None` if unexpected bits are set in the flag byte.
|
2021-07-01 09:10:24 -07:00
|
|
|
///
|
2021-07-29 06:16:14 -07:00
|
|
|
/// [txencoding]: https://zips.z.cash/protocol/protocol.pdf#txnencoding
|
2022-04-28 13:45:05 -07:00
|
|
|
pub fn from_byte(value: u8) -> Option<Self> {
|
2022-07-21 13:43:00 -07:00
|
|
|
// https://p.z.cash/TCR:bad-txns-v5-reserved-bits-nonzero
|
2021-07-01 09:10:24 -07:00
|
|
|
if value & FLAGS_EXPECTED_UNSET == 0 {
|
2023-12-15 15:19:24 -08:00
|
|
|
Some(Self {
|
|
|
|
spends_enabled: value & FLAG_SPENDS_ENABLED != 0,
|
|
|
|
outputs_enabled: value & FLAG_OUTPUTS_ENABLED != 0,
|
|
|
|
})
|
2021-07-01 09:10:24 -07:00
|
|
|
} else {
|
2022-04-28 13:45:05 -07:00
|
|
|
None
|
2021-07-01 09:10:24 -07:00
|
|
|
}
|
|
|
|
}
|
2021-04-23 12:04:45 -07:00
|
|
|
}
|
|
|
|
|
2021-03-03 09:35:25 -08:00
|
|
|
/// Defines the authorization type of an Orchard bundle.
|
2022-02-28 12:01:21 -08:00
|
|
|
pub trait Authorization: fmt::Debug {
|
2021-03-03 09:35:25 -08:00
|
|
|
/// The authorization type of an Orchard action.
|
2022-02-28 12:01:21 -08:00
|
|
|
type SpendAuth: fmt::Debug;
|
2021-01-20 12:30:35 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// A bundle of actions to be applied to the ledger.
|
2022-04-05 15:39:06 -07:00
|
|
|
#[derive(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
|
|
|
}
|
|
|
|
|
2022-04-05 15:39:06 -07:00
|
|
|
impl<T: Authorization, V: fmt::Debug> fmt::Debug for Bundle<T, V> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
/// Helper struct for debug-printing actions without exposing `NonEmpty`.
|
|
|
|
struct Actions<'a, T>(&'a NonEmpty<Action<T>>);
|
|
|
|
impl<'a, T: fmt::Debug> fmt::Debug for Actions<'a, T> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
f.debug_list().entries(self.0.iter()).finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
f.debug_struct("Bundle")
|
|
|
|
.field("actions", &Actions(&self.actions))
|
|
|
|
.field("flags", &self.flags)
|
|
|
|
.field("value_balance", &self.value_balance)
|
|
|
|
.field("anchor", &self.anchor)
|
|
|
|
.field("authorization", &self.authorization)
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-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.
|
2022-04-28 17:16:16 -07:00
|
|
|
pub fn map_authorization<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.
|
2022-04-28 17:16:16 -07:00
|
|
|
pub fn try_map_authorization<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-07-21 13:31:37 -07:00
|
|
|
|
2022-03-14 16:30:37 -07:00
|
|
|
/// Performs trial decryption of each action in the bundle with each of the
|
2022-03-15 06:47:21 -07:00
|
|
|
/// specified incoming viewing keys, and returns a vector of each decrypted
|
|
|
|
/// note plaintext contents along with the index of the action from which it
|
|
|
|
/// was derived.
|
2022-03-14 16:30:37 -07:00
|
|
|
pub fn decrypt_outputs_with_keys(
|
2021-07-21 13:31:37 -07:00
|
|
|
&self,
|
2021-07-30 10:45:01 -07:00
|
|
|
keys: &[IncomingViewingKey],
|
|
|
|
) -> Vec<(usize, IncomingViewingKey, Note, Address, [u8; 512])> {
|
2022-10-18 11:37:12 -07:00
|
|
|
let prepared_keys: Vec<_> = keys
|
|
|
|
.iter()
|
|
|
|
.map(|ivk| (ivk, PreparedIncomingViewingKey::new(ivk)))
|
|
|
|
.collect();
|
2021-07-21 13:31:37 -07:00
|
|
|
self.actions
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.filter_map(|(idx, action)| {
|
|
|
|
let domain = OrchardDomain::for_action(action);
|
2022-10-18 11:37:12 -07:00
|
|
|
prepared_keys.iter().find_map(|(ivk, prepared_ivk)| {
|
|
|
|
try_note_decryption(&domain, prepared_ivk, action)
|
|
|
|
.map(|(n, a, m)| (idx, (*ivk).clone(), n, a, m))
|
2021-07-21 13:31:37 -07:00
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
2021-08-18 18:00:21 -07:00
|
|
|
|
2022-03-14 16:30:37 -07:00
|
|
|
/// Performs trial decryption of the action at `action_idx` in the bundle with the
|
2022-03-15 06:47:21 -07:00
|
|
|
/// specified incoming viewing key, and returns the decrypted note plaintext
|
|
|
|
/// contents if successful.
|
2021-08-18 18:00:21 -07:00
|
|
|
pub fn decrypt_output_with_key(
|
|
|
|
&self,
|
|
|
|
action_idx: usize,
|
|
|
|
key: &IncomingViewingKey,
|
|
|
|
) -> Option<(Note, Address, [u8; 512])> {
|
2022-10-15 14:00:09 -07:00
|
|
|
let prepared_ivk = PreparedIncomingViewingKey::new(key);
|
2021-08-18 18:00:21 -07:00
|
|
|
self.actions.get(action_idx).and_then(move |action| {
|
|
|
|
let domain = OrchardDomain::for_action(action);
|
2022-10-15 14:00:09 -07:00
|
|
|
try_note_decryption(&domain, &prepared_ivk, action)
|
2021-08-18 18:00:21 -07:00
|
|
|
})
|
|
|
|
}
|
2022-03-14 16:30:37 -07:00
|
|
|
|
|
|
|
/// Performs trial decryption of each action in the bundle with each of the
|
2022-03-15 06:47:21 -07:00
|
|
|
/// specified outgoing viewing keys, and returns a vector of each decrypted
|
|
|
|
/// note plaintext contents along with the index of the action from which it
|
|
|
|
/// was derived.
|
2022-03-14 16:30:37 -07:00
|
|
|
pub fn recover_outputs_with_ovks(
|
|
|
|
&self,
|
|
|
|
keys: &[OutgoingViewingKey],
|
|
|
|
) -> Vec<(usize, OutgoingViewingKey, Note, Address, [u8; 512])> {
|
|
|
|
self.actions
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.filter_map(|(idx, action)| {
|
|
|
|
let domain = OrchardDomain::for_action(action);
|
|
|
|
keys.iter().find_map(move |key| {
|
|
|
|
try_output_recovery_with_ovk(
|
|
|
|
&domain,
|
|
|
|
key,
|
|
|
|
action,
|
|
|
|
action.cv_net(),
|
|
|
|
&action.encrypted_note().out_ciphertext,
|
|
|
|
)
|
|
|
|
.map(|(n, a, m)| (idx, key.clone(), n, a, m))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempts to decrypt the action at the specified index with the specified
|
2022-03-15 06:47:21 -07:00
|
|
|
/// outgoing viewing key, and returns the decrypted note plaintext contents
|
|
|
|
/// if successful.
|
2022-03-14 16:30:37 -07:00
|
|
|
pub fn recover_output_with_ovk(
|
|
|
|
&self,
|
|
|
|
action_idx: usize,
|
|
|
|
key: &OutgoingViewingKey,
|
|
|
|
) -> Option<(Note, Address, [u8; 512])> {
|
|
|
|
self.actions.get(action_idx).and_then(move |action| {
|
|
|
|
let domain = OrchardDomain::for_action(action);
|
|
|
|
try_output_recovery_with_ovk(
|
|
|
|
&domain,
|
|
|
|
key,
|
|
|
|
action,
|
|
|
|
action.cv_net(),
|
|
|
|
&action.encrypted_note().out_ciphertext,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
2021-01-20 12:30:35 -08:00
|
|
|
}
|
|
|
|
|
2022-02-15 11:55:54 -08:00
|
|
|
impl<T: Authorization, V: Copy + Into<i64>> Bundle<T, V> {
|
|
|
|
/// Computes a commitment to the effects of this bundle, suitable for inclusion within
|
|
|
|
/// a transaction ID.
|
|
|
|
pub fn commitment(&self) -> BundleCommitment {
|
|
|
|
BundleCommitment(hash_bundle_txid_data(self))
|
|
|
|
}
|
|
|
|
|
2021-06-13 12:55:39 -07:00
|
|
|
/// 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> {
|
2022-07-21 13:43:00 -07:00
|
|
|
// https://p.z.cash/TCR:bad-txns-orchard-binding-signature-invalid?partial
|
2021-06-13 12:55:39 -07:00
|
|
|
(self
|
|
|
|
.actions
|
|
|
|
.iter()
|
|
|
|
.map(|a| a.cv_net())
|
|
|
|
.sum::<ValueCommitment>()
|
2022-02-15 11:55:54 -08:00
|
|
|
- ValueCommitment::derive(
|
|
|
|
ValueSum::from_raw(self.value_balance.into()),
|
|
|
|
ValueCommitTrapdoor::zero(),
|
|
|
|
))
|
2021-06-13 12:55:39 -07:00
|
|
|
.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-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 {
|
2021-07-01 09:10:24 -07:00
|
|
|
BundleAuthorizingCommitment(hash_bundle_auth_data(self))
|
2021-01-20 12:30:35 -08:00
|
|
|
}
|
2021-06-21 05:38:43 -07:00
|
|
|
|
|
|
|
/// Verifies the proof for this bundle.
|
2022-01-28 07:53:03 -08:00
|
|
|
pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> {
|
2021-06-21 05:38:43 -07:00
|
|
|
self.authorization()
|
|
|
|
.proof()
|
|
|
|
.verify(vk, &self.to_instances())
|
|
|
|
}
|
2021-01-20 12:30:35 -08:00
|
|
|
}
|
|
|
|
|
2021-09-03 13:22:22 -07:00
|
|
|
impl<V: DynamicUsage> DynamicUsage for Bundle<Authorized, V> {
|
|
|
|
fn dynamic_usage(&self) -> usize {
|
|
|
|
self.actions.dynamic_usage()
|
|
|
|
+ self.value_balance.dynamic_usage()
|
|
|
|
+ self.authorization.proof.dynamic_usage()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
|
|
|
|
let bounds = (
|
|
|
|
self.actions.dynamic_usage_bounds(),
|
|
|
|
self.value_balance.dynamic_usage_bounds(),
|
|
|
|
self.authorization.proof.dynamic_usage_bounds(),
|
|
|
|
);
|
|
|
|
(
|
|
|
|
bounds.0 .0 + bounds.1 .0 + bounds.2 .0,
|
|
|
|
bounds
|
|
|
|
.0
|
|
|
|
.1
|
|
|
|
.zip(bounds.1 .1)
|
|
|
|
.zip(bounds.2 .1)
|
|
|
|
.map(|((a, b), c)| a + b + c),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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)]
|
2021-07-01 09:10:24 -07:00
|
|
|
pub struct BundleCommitment(pub Blake2bHash);
|
2021-01-20 12:30:35 -08:00
|
|
|
|
2022-02-15 11:55:54 -08:00
|
|
|
impl From<BundleCommitment> for [u8; 32] {
|
|
|
|
fn from(commitment: BundleCommitment) -> Self {
|
|
|
|
// The commitment uses BLAKE2b-256.
|
|
|
|
commitment.0.as_bytes().try_into().unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-20 12:30:35 -08:00
|
|
|
/// A commitment to the authorizing data within a bundle of actions.
|
|
|
|
#[derive(Debug)]
|
2021-07-01 09:10:24 -07:00
|
|
|
pub struct BundleAuthorizingCommitment(pub Blake2bHash);
|
2021-04-27 06:49:49 -07:00
|
|
|
|
|
|
|
/// Generators for property testing.
|
|
|
|
#[cfg(any(test, feature = "test-dependencies"))]
|
2021-12-17 14:08:58 -08:00
|
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
|
2021-04-27 06:49:49 -07:00
|
|
|
pub mod testing {
|
2023-02-28 14:21:37 -08:00
|
|
|
use group::ff::FromUniformBytes;
|
2021-04-27 06:49:49 -07:00
|
|
|
use nonempty::NonEmpty;
|
2023-02-28 14:21:37 -08:00
|
|
|
use pasta_curves::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,
|
2022-04-28 14:46:24 -07:00
|
|
|
primitives::redpallas::{self, testing::arb_binding_signing_key},
|
|
|
|
value::{testing::arb_note_value_bounded, NoteValue, 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
|
|
|
|
2022-04-28 14:46:24 -07:00
|
|
|
pub use crate::action::testing::{arb_action, arb_unauthorized_action};
|
|
|
|
|
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 = ();
|
|
|
|
}
|
|
|
|
|
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)
|
2022-04-29 06:54:42 -07:00
|
|
|
.prop_map(move |a| (spend_value - output_value, a))
|
2021-05-05 16:44:10 -07:00
|
|
|
})
|
|
|
|
})
|
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)
|
2022-04-29 06:54:42 -07:00
|
|
|
.prop_map(move |a| (spend_value - output_value, a))
|
2021-05-05 16:44:10 -07:00
|
|
|
})
|
|
|
|
})
|
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);
|
2023-02-28 14:21:37 -08:00
|
|
|
pallas::Base::from_uniform_bytes(&buf)
|
2021-06-12 13:35:37 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-06-22 09:48:25 -07:00
|
|
|
pub fn arb_unauthorized_bundle(n_actions: usize)
|
2021-05-05 10:46:24 -07:00
|
|
|
(
|
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-06-22 09:48:25 -07:00
|
|
|
pub fn arb_bundle(n_actions: usize)
|
2021-05-05 10:46:24 -07:00
|
|
|
(
|
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
|
|
|
}
|