2021-04-14 21:14:34 -07:00
|
|
|
//! Logic for building Orchard components of transactions.
|
|
|
|
|
2021-04-23 11:47:22 -07:00
|
|
|
use std::convert::TryFrom;
|
2021-04-14 21:14:34 -07:00
|
|
|
use std::iter;
|
|
|
|
|
|
|
|
use ff::Field;
|
|
|
|
use nonempty::NonEmpty;
|
|
|
|
use pasta_curves::pallas;
|
2021-05-08 12:51:55 -07:00
|
|
|
use rand::{CryptoRng, RngCore};
|
2021-04-14 21:14:34 -07:00
|
|
|
|
|
|
|
use crate::{
|
2021-05-05 10:46:24 -07:00
|
|
|
address::Address,
|
2021-04-14 21:14:34 -07:00
|
|
|
bundle::{Action, Authorization, Authorized, Bundle, Flags},
|
|
|
|
circuit::{Circuit, Proof, ProvingKey},
|
|
|
|
keys::{
|
|
|
|
FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey, SpendingKey,
|
|
|
|
},
|
2021-05-05 10:46:24 -07:00
|
|
|
note::{Note, TransmittedNoteCiphertext},
|
2021-04-14 21:14:34 -07:00
|
|
|
primitives::redpallas::{self, Binding, SpendAuth},
|
|
|
|
tree::{Anchor, MerklePath},
|
2021-04-27 12:56:36 -07:00
|
|
|
value::{self, NoteValue, OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum},
|
2021-04-14 21:14:34 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
const MIN_ACTIONS: usize = 2;
|
|
|
|
|
2021-04-27 12:56:36 -07:00
|
|
|
/// An error type for the kinds of errors that can occur during bundle construction.
|
2021-04-14 21:14:34 -07:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Error {
|
2021-04-27 12:56:36 -07:00
|
|
|
/// A bundle could not be built because required signatures were missing.
|
2021-04-14 21:14:34 -07:00
|
|
|
MissingSignatures,
|
2021-04-27 12:56:36 -07:00
|
|
|
/// An error occurred in the process of producing a proof for a bundle.
|
2021-04-14 21:14:34 -07:00
|
|
|
Proof(halo2::plonk::Error),
|
2021-04-27 12:56:36 -07:00
|
|
|
/// An overflow error occurred while attempting to construct the value
|
|
|
|
/// for a bundle.
|
2021-04-14 21:14:34 -07:00
|
|
|
ValueSum(value::OverflowError),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<halo2::plonk::Error> for Error {
|
|
|
|
fn from(e: halo2::plonk::Error) -> Self {
|
|
|
|
Error::Proof(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<value::OverflowError> for Error {
|
|
|
|
fn from(e: value::OverflowError) -> Self {
|
|
|
|
Error::ValueSum(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Information about a specific note to be spent in an [`Action`].
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct SpendInfo {
|
|
|
|
dummy_sk: Option<SpendingKey>,
|
|
|
|
fvk: FullViewingKey,
|
|
|
|
note: Note,
|
|
|
|
merkle_path: MerklePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SpendInfo {
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes].
|
|
|
|
///
|
|
|
|
/// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
|
|
|
|
fn dummy(rng: &mut impl RngCore) -> Self {
|
|
|
|
let (sk, fvk, note) = Note::dummy(rng, None);
|
|
|
|
let merkle_path = MerklePath::dummy(rng);
|
|
|
|
|
|
|
|
SpendInfo {
|
|
|
|
dummy_sk: Some(sk),
|
|
|
|
fvk,
|
|
|
|
note,
|
|
|
|
merkle_path,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Information about a specific recipient to receive funds in an [`Action`].
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct RecipientInfo {
|
|
|
|
ovk: Option<OutgoingViewingKey>,
|
|
|
|
recipient: Address,
|
|
|
|
value: NoteValue,
|
|
|
|
memo: Option<()>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RecipientInfo {
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes].
|
|
|
|
///
|
|
|
|
/// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
|
|
|
|
fn dummy(rng: &mut impl RngCore) -> Self {
|
|
|
|
let fvk: FullViewingKey = (&SpendingKey::random(rng)).into();
|
|
|
|
let recipient = fvk.default_address();
|
|
|
|
|
|
|
|
RecipientInfo {
|
|
|
|
ovk: None,
|
|
|
|
recipient,
|
2021-04-27 14:06:33 -07:00
|
|
|
value: NoteValue::zero(),
|
2021-04-14 21:14:34 -07:00
|
|
|
memo: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Information about a specific [`Action`] we plan to build.
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct ActionInfo {
|
|
|
|
spend: SpendInfo,
|
|
|
|
output: RecipientInfo,
|
|
|
|
rcv: ValueCommitTrapdoor,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ActionInfo {
|
|
|
|
fn new(spend: SpendInfo, output: RecipientInfo, rng: impl RngCore) -> Self {
|
|
|
|
ActionInfo {
|
|
|
|
spend,
|
|
|
|
output,
|
|
|
|
rcv: ValueCommitTrapdoor::random(rng),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the value sum for this action.
|
2021-04-23 11:47:22 -07:00
|
|
|
fn value_sum(&self) -> Option<ValueSum> {
|
2021-04-14 21:14:34 -07:00
|
|
|
self.spend.note.value() - self.output.value
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Builds the action.
|
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
|
|
|
|
///
|
|
|
|
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
2021-04-27 12:56:36 -07:00
|
|
|
fn build(self, mut rng: impl RngCore) -> (Action<SigningMetadata>, Circuit) {
|
2021-04-14 21:14:34 -07:00
|
|
|
let v_net = self.value_sum().expect("already checked this");
|
|
|
|
let cv_net = ValueCommitment::derive(v_net, self.rcv);
|
|
|
|
|
|
|
|
let nf_old = self.spend.note.nullifier(&self.spend.fvk);
|
|
|
|
let ak: SpendValidatingKey = self.spend.fvk.into();
|
|
|
|
let alpha = pallas::Scalar::random(&mut rng);
|
|
|
|
let rk = ak.randomize(&alpha);
|
|
|
|
|
2021-05-28 04:06:50 -07:00
|
|
|
let note = Note::new(self.output.recipient, self.output.value, nf_old, rng);
|
2021-04-14 21:14:34 -07:00
|
|
|
let cm_new = note.commitment();
|
|
|
|
|
|
|
|
// TODO: Note encryption
|
2021-04-21 08:57:48 -07:00
|
|
|
let encrypted_note = TransmittedNoteCiphertext {
|
|
|
|
epk_bytes: [0u8; 32],
|
|
|
|
enc_ciphertext: [0u8; 580],
|
|
|
|
out_ciphertext: [0u8; 80],
|
|
|
|
};
|
2021-04-14 21:14:34 -07:00
|
|
|
|
|
|
|
(
|
|
|
|
Action::from_parts(
|
|
|
|
nf_old,
|
|
|
|
rk,
|
|
|
|
cm_new.into(),
|
|
|
|
encrypted_note,
|
|
|
|
cv_net,
|
2021-04-28 15:38:08 -07:00
|
|
|
SigningMetadata {
|
|
|
|
dummy_ask: self.spend.dummy_sk.as_ref().map(SpendAuthorizingKey::from),
|
2021-04-14 21:14:34 -07:00
|
|
|
ak,
|
2021-04-28 15:38:08 -07:00
|
|
|
},
|
2021-04-14 21:14:34 -07:00
|
|
|
),
|
|
|
|
Circuit {},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A builder that constructs a [`Bundle`] from a set of notes to be spent, and recipients
|
|
|
|
/// to receive funds.
|
2021-04-27 12:56:36 -07:00
|
|
|
#[derive(Debug)]
|
2021-04-14 21:14:34 -07:00
|
|
|
pub struct Builder {
|
|
|
|
spends: Vec<SpendInfo>,
|
|
|
|
recipients: Vec<RecipientInfo>,
|
|
|
|
flags: Flags,
|
|
|
|
anchor: Anchor,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Builder {
|
2021-05-05 10:10:52 -07:00
|
|
|
/// Constructs a new empty builder for an Orchard bundle.
|
2021-04-14 21:14:34 -07:00
|
|
|
pub fn new(flags: Flags, anchor: Anchor) -> Self {
|
|
|
|
Builder {
|
|
|
|
spends: vec![],
|
|
|
|
recipients: vec![],
|
|
|
|
flags,
|
|
|
|
anchor,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds a note to be spent in this transaction.
|
|
|
|
///
|
|
|
|
/// Returns an error if the given Merkle path does not have the required anchor for
|
|
|
|
/// the given note.
|
|
|
|
pub fn add_spend(
|
|
|
|
&mut self,
|
|
|
|
fvk: FullViewingKey,
|
|
|
|
note: Note,
|
|
|
|
merkle_path: MerklePath,
|
|
|
|
) -> Result<(), &'static str> {
|
|
|
|
if !self.flags.spends_enabled() {
|
|
|
|
return Err("Spends are not enabled for this builder");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Consistency check: all anchors must be equal.
|
2021-06-07 22:18:16 -07:00
|
|
|
let cm = note.commitment();
|
|
|
|
let path_root: Anchor = merkle_path.root(cm.into());
|
|
|
|
if path_root != self.anchor {
|
|
|
|
return Err("All anchors must be equal.");
|
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
|
|
|
|
self.spends.push(SpendInfo {
|
|
|
|
dummy_sk: None,
|
|
|
|
fvk,
|
|
|
|
note,
|
|
|
|
merkle_path,
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds an address which will receive funds in this transaction.
|
|
|
|
pub fn add_recipient(
|
|
|
|
&mut self,
|
|
|
|
ovk: Option<OutgoingViewingKey>,
|
|
|
|
recipient: Address,
|
|
|
|
value: NoteValue,
|
|
|
|
memo: Option<()>,
|
|
|
|
) -> Result<(), &'static str> {
|
|
|
|
if !self.flags.outputs_enabled() {
|
|
|
|
return Err("Outputs are not enabled for this builder");
|
|
|
|
}
|
|
|
|
|
|
|
|
self.recipients.push(RecipientInfo {
|
|
|
|
ovk,
|
|
|
|
recipient,
|
|
|
|
value,
|
|
|
|
memo,
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Builds a bundle containing the given spent notes and recipients.
|
|
|
|
///
|
|
|
|
/// This API assumes that none of the notes being spent are controlled by (threshold)
|
|
|
|
/// multisignatures, and immediately constructs the bundle proof.
|
2021-04-23 11:47:22 -07:00
|
|
|
fn build<V: TryFrom<i64>>(
|
2021-04-14 21:14:34 -07:00
|
|
|
mut self,
|
|
|
|
mut rng: impl RngCore,
|
|
|
|
pk: &ProvingKey,
|
2021-04-23 11:47:22 -07:00
|
|
|
) -> Result<Bundle<Unauthorized, V>, Error> {
|
2021-04-14 21:14:34 -07:00
|
|
|
// Pair up the spends and recipients, extending with dummy values as necessary.
|
|
|
|
//
|
|
|
|
// TODO: Do we want to shuffle the order like we do for Sapling? And if we do, do
|
|
|
|
// we need the extra logic for mapping the user-provided input order to the
|
|
|
|
// shuffled order?
|
|
|
|
let pre_actions: Vec<_> = {
|
|
|
|
let num_spends = self.spends.len();
|
|
|
|
let num_recipients = self.recipients.len();
|
|
|
|
let num_actions = [num_spends, num_recipients, MIN_ACTIONS]
|
|
|
|
.iter()
|
|
|
|
.max()
|
|
|
|
.cloned()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
self.spends.extend(
|
|
|
|
iter::repeat_with(|| SpendInfo::dummy(&mut rng)).take(num_actions - num_spends),
|
|
|
|
);
|
|
|
|
self.recipients.extend(
|
|
|
|
iter::repeat_with(|| RecipientInfo::dummy(&mut rng))
|
|
|
|
.take(num_actions - num_recipients),
|
|
|
|
);
|
|
|
|
|
|
|
|
self.spends
|
|
|
|
.into_iter()
|
|
|
|
.zip(self.recipients.into_iter())
|
|
|
|
.map(|(spend, recipient)| ActionInfo::new(spend, recipient, &mut rng))
|
|
|
|
.collect()
|
|
|
|
};
|
|
|
|
|
|
|
|
// Move some things out of self that we will need.
|
|
|
|
let flags = self.flags;
|
|
|
|
let anchor = self.anchor;
|
|
|
|
|
|
|
|
// Determine the value balance for this bundle, ensuring it is valid.
|
2021-04-23 11:47:22 -07:00
|
|
|
let value_balance = pre_actions
|
2021-04-14 21:14:34 -07:00
|
|
|
.iter()
|
2021-04-23 11:47:22 -07:00
|
|
|
.fold(Some(ValueSum::zero()), |acc, action| {
|
2021-04-14 21:14:34 -07:00
|
|
|
acc? + action.value_sum()?
|
2021-04-27 12:56:36 -07:00
|
|
|
})
|
|
|
|
.ok_or(OverflowError)?;
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2021-05-05 10:10:52 -07:00
|
|
|
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)))?;
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
// Compute the transaction binding signing key.
|
|
|
|
let bsk = pre_actions
|
|
|
|
.iter()
|
|
|
|
.map(|a| &a.rcv)
|
|
|
|
.sum::<ValueCommitTrapdoor>()
|
|
|
|
.into_bsk();
|
|
|
|
|
|
|
|
// Create the actions.
|
2021-04-27 12:56:36 -07:00
|
|
|
let (actions, circuits): (Vec<_>, Vec<_>) =
|
|
|
|
pre_actions.into_iter().map(|a| a.build(&mut rng)).unzip();
|
2021-04-14 21:14:34 -07:00
|
|
|
|
|
|
|
// Verify that bsk and bvk are consistent.
|
|
|
|
let bvk = (actions.iter().map(|a| a.cv_net()).sum::<ValueCommitment>()
|
|
|
|
- ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero()))
|
|
|
|
.into_bvk();
|
|
|
|
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
|
|
|
|
|
|
|
|
// Create the proof.
|
|
|
|
let instances: Vec<_> = actions
|
|
|
|
.iter()
|
|
|
|
.map(|a| a.to_instance(flags, anchor.clone()))
|
|
|
|
.collect();
|
|
|
|
let proof = Proof::create(pk, &circuits, &instances)?;
|
|
|
|
|
|
|
|
Ok(Bundle::from_parts(
|
|
|
|
NonEmpty::from_vec(actions).unwrap(),
|
|
|
|
flags,
|
2021-05-05 10:10:52 -07:00
|
|
|
result_value_balance,
|
2021-04-14 21:14:34 -07:00
|
|
|
anchor,
|
|
|
|
Unauthorized { proof, bsk },
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Marker for an unauthorized bundle, with a proof but no signatures.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Unauthorized {
|
|
|
|
proof: Proof,
|
|
|
|
bsk: redpallas::SigningKey<Binding>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Authorization for Unauthorized {
|
2021-04-28 15:38:08 -07:00
|
|
|
type SpendAuth = SigningMetadata;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Container for metadata needed to sign an [`Action`].
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct SigningMetadata {
|
|
|
|
/// If this action is spending a dummy note, this field holds that note's spend
|
|
|
|
/// authorizing key.
|
|
|
|
///
|
|
|
|
/// These keys are used automatically in [`Bundle<Unauthorized>::prepare`] or
|
|
|
|
/// [`Bundle<Unauthorized>::apply_signatures`] to sign dummy spends.
|
|
|
|
dummy_ask: Option<SpendAuthorizingKey>,
|
|
|
|
/// The spend validating key for this action. Used to match spend authorizing keys to
|
|
|
|
/// actions they can create signatures for.
|
|
|
|
ak: SpendValidatingKey,
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Marker for a partially-authorized bundle, in the process of being signed.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct PartiallyAuthorized {
|
|
|
|
proof: Proof,
|
|
|
|
binding_signature: redpallas::Signature<Binding>,
|
|
|
|
sighash: [u8; 32],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Authorization for PartiallyAuthorized {
|
|
|
|
type SpendAuth = (Option<redpallas::Signature<SpendAuth>>, SpendValidatingKey);
|
|
|
|
}
|
|
|
|
|
2021-04-23 11:47:22 -07:00
|
|
|
impl<V> Bundle<Unauthorized, V> {
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Loads the sighash into this bundle, preparing it for signing.
|
|
|
|
///
|
|
|
|
/// This API ensures that all signatures are created over the same sighash.
|
2021-05-08 12:51:55 -07:00
|
|
|
pub fn prepare<R: RngCore + CryptoRng>(
|
2021-04-14 21:14:34 -07:00
|
|
|
self,
|
|
|
|
mut rng: R,
|
|
|
|
sighash: [u8; 32],
|
2021-04-23 11:47:22 -07:00
|
|
|
) -> Bundle<PartiallyAuthorized, V> {
|
2021-04-14 21:14:34 -07:00
|
|
|
self.authorize(
|
|
|
|
&mut rng,
|
2021-04-28 15:38:08 -07:00
|
|
|
|rng, _, SigningMetadata { dummy_ask, ak }| {
|
2021-04-14 21:14:34 -07:00
|
|
|
(
|
2021-04-28 15:57:53 -07:00
|
|
|
// We can create signatures for dummy spends immediately.
|
|
|
|
dummy_ask.map(|ask| ask.sign(rng, &sighash)),
|
2021-04-14 21:14:34 -07:00
|
|
|
ak,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|rng, unauth| PartiallyAuthorized {
|
|
|
|
proof: unauth.proof,
|
|
|
|
binding_signature: unauth.bsk.sign(rng, &sighash),
|
|
|
|
sighash,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Applies signatures to this bundle, in order to authorize it.
|
2021-05-08 12:51:55 -07:00
|
|
|
pub fn apply_signatures<R: RngCore + CryptoRng>(
|
2021-04-14 21:14:34 -07:00
|
|
|
self,
|
|
|
|
mut rng: R,
|
|
|
|
sighash: [u8; 32],
|
|
|
|
signing_keys: &[SpendAuthorizingKey],
|
2021-04-23 11:47:22 -07:00
|
|
|
) -> Result<Bundle<Authorized, V>, Error> {
|
2021-04-14 21:14:34 -07:00
|
|
|
signing_keys
|
|
|
|
.iter()
|
|
|
|
.fold(self.prepare(&mut rng, sighash), |partial, ask| {
|
|
|
|
partial.sign(&mut rng, ask)
|
|
|
|
})
|
|
|
|
.finalize()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-23 11:47:22 -07:00
|
|
|
impl<V> Bundle<PartiallyAuthorized, V> {
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Signs this bundle with the given [`SpendAuthorizingKey`].
|
|
|
|
///
|
|
|
|
/// This will apply signatures for all notes controlled by this spending key.
|
2021-05-08 12:51:55 -07:00
|
|
|
pub fn sign<R: RngCore + CryptoRng>(self, mut rng: R, ask: &SpendAuthorizingKey) -> Self {
|
2021-04-14 21:14:34 -07:00
|
|
|
let expected_ak = ask.into();
|
|
|
|
self.authorize(
|
|
|
|
&mut rng,
|
|
|
|
|rng, partial, (sig, ak)| {
|
|
|
|
(
|
|
|
|
sig.or_else(|| {
|
|
|
|
if ak == expected_ak {
|
|
|
|
Some(ask.sign(rng, &partial.sighash))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
ak,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|_, partial| partial,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Finalizes this bundle, enabling it to be included in a transaction.
|
|
|
|
///
|
|
|
|
/// Returns an error if any signatures are missing.
|
2021-04-23 11:47:22 -07:00
|
|
|
pub fn finalize(self) -> Result<Bundle<Authorized, V>, Error> {
|
2021-04-14 21:14:34 -07:00
|
|
|
self.try_authorize(
|
|
|
|
&mut (),
|
|
|
|
|_, _, (sig, _)| match sig {
|
|
|
|
Some(sig) => Ok(sig),
|
|
|
|
None => Err(Error::MissingSignatures),
|
|
|
|
},
|
|
|
|
|_, partial| {
|
|
|
|
Ok(Authorized::from_parts(
|
|
|
|
partial.proof,
|
|
|
|
partial.binding_signature,
|
|
|
|
))
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 06:49:49 -07:00
|
|
|
/// Generators for property testing.
|
|
|
|
#[cfg(any(test, feature = "test-dependencies"))]
|
|
|
|
pub mod testing {
|
2021-04-29 08:55:11 -07:00
|
|
|
use rand::{rngs::StdRng, CryptoRng, SeedableRng};
|
2021-04-27 12:56:36 -07:00
|
|
|
use std::convert::TryFrom;
|
|
|
|
use std::fmt::Debug;
|
2021-04-27 06:49:49 -07:00
|
|
|
|
|
|
|
use proptest::collection::vec;
|
|
|
|
use proptest::prelude::*;
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
address::testing::arb_address,
|
|
|
|
bundle::{Authorized, Bundle, Flags},
|
|
|
|
circuit::ProvingKey,
|
2021-04-27 12:56:36 -07:00
|
|
|
keys::{
|
|
|
|
testing::arb_spending_key, FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey,
|
|
|
|
SpendingKey,
|
|
|
|
},
|
2021-06-08 00:27:08 -07:00
|
|
|
tree::{testing::arb_tree, Anchor, MerklePath},
|
2021-05-05 16:19:42 -07:00
|
|
|
value::{testing::arb_positive_note_value, NoteValue, MAX_NOTE_VALUE},
|
2021-04-27 12:56:36 -07:00
|
|
|
Address, Note,
|
2021-04-27 06:49:49 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
use super::Builder;
|
|
|
|
|
2021-04-27 12:56:36 -07:00
|
|
|
/// An intermediate type used for construction of arbitrary
|
|
|
|
/// bundle values. This type is required because of a limitation
|
|
|
|
/// of the proptest prop_compose! macro which does not correctly
|
|
|
|
/// handle polymorphic generator functions. Instead of generating
|
|
|
|
/// a bundle directly, we generate the bundle inputs, and then
|
|
|
|
/// are able to use the `build` function to construct the bundle
|
|
|
|
/// from these inputs, but using a `ValueBalance` implementation that
|
|
|
|
/// is defined by the end user.
|
|
|
|
#[derive(Debug)]
|
2021-05-08 12:51:55 -07:00
|
|
|
struct ArbitraryBundleInputs<R> {
|
2021-04-29 08:55:11 -07:00
|
|
|
rng: R,
|
2021-04-27 12:56:36 -07:00
|
|
|
sk: SpendingKey,
|
|
|
|
anchor: Anchor,
|
2021-06-07 22:18:16 -07:00
|
|
|
notes: Vec<(Note, MerklePath)>,
|
2021-04-27 12:56:36 -07:00
|
|
|
recipient_amounts: Vec<(Address, NoteValue)>,
|
|
|
|
}
|
|
|
|
|
2021-05-08 12:51:55 -07:00
|
|
|
impl<R: RngCore + CryptoRng> ArbitraryBundleInputs<R> {
|
2021-04-27 12:56:36 -07:00
|
|
|
/// Create a bundle from the set of arbitrary bundle inputs.
|
2021-04-29 08:55:11 -07:00
|
|
|
fn into_bundle<V: TryFrom<i64>>(mut self) -> Bundle<Authorized, V> {
|
2021-04-27 12:56:36 -07:00
|
|
|
let fvk = FullViewingKey::from(&self.sk);
|
2021-04-27 06:49:49 -07:00
|
|
|
let ovk = OutgoingViewingKey::from(&fvk);
|
|
|
|
let flags = Flags::from_parts(true, true);
|
2021-04-27 12:56:36 -07:00
|
|
|
let mut builder = Builder::new(flags, self.anchor);
|
2021-04-27 06:49:49 -07:00
|
|
|
|
2021-06-07 22:18:16 -07:00
|
|
|
for (note, path) in self.notes.into_iter() {
|
|
|
|
builder.add_spend(fvk.clone(), note, path).unwrap();
|
2021-04-27 06:49:49 -07:00
|
|
|
}
|
|
|
|
|
2021-04-27 12:56:36 -07:00
|
|
|
for (addr, value) in self.recipient_amounts.into_iter() {
|
|
|
|
builder
|
|
|
|
.add_recipient(Some(ovk.clone()), addr, value, None)
|
|
|
|
.unwrap();
|
2021-04-27 06:49:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
let pk = ProvingKey::build();
|
|
|
|
builder
|
2021-04-29 08:55:11 -07:00
|
|
|
.build(&mut self.rng, &pk)
|
2021-04-27 06:49:49 -07:00
|
|
|
.unwrap()
|
2021-05-08 12:51:55 -07:00
|
|
|
.prepare(&mut self.rng, [0; 32])
|
|
|
|
.sign(&mut self.rng, &SpendAuthorizingKey::from(&self.sk))
|
2021-04-27 06:49:49 -07:00
|
|
|
.finalize()
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
}
|
2021-04-27 12:56:36 -07:00
|
|
|
|
|
|
|
prop_compose! {
|
|
|
|
/// Produce a random valid Orchard bundle.
|
2021-05-05 10:10:52 -07:00
|
|
|
fn arb_bundle_inputs(sk: SpendingKey)
|
|
|
|
(
|
|
|
|
n_notes in 1..30,
|
|
|
|
n_recipients in 1..30,
|
|
|
|
)
|
|
|
|
(
|
2021-06-08 00:27:08 -07:00
|
|
|
(notes_and_auth_paths, anchor) in arb_tree(n_notes),
|
2021-04-27 12:56:36 -07:00
|
|
|
recipient_amounts in vec(
|
2021-05-05 10:10:52 -07:00
|
|
|
arb_address().prop_flat_map(move |a| {
|
|
|
|
arb_positive_note_value(MAX_NOTE_VALUE / n_recipients as u64)
|
2021-05-28 04:06:50 -07:00
|
|
|
.prop_map(move |v| (a, v))
|
2021-05-05 10:10:52 -07:00
|
|
|
}),
|
|
|
|
n_recipients as usize
|
2021-04-27 12:56:36 -07:00
|
|
|
),
|
2021-04-29 08:55:11 -07:00
|
|
|
rng_seed in prop::array::uniform32(prop::num::u8::ANY)
|
2021-05-08 12:51:55 -07:00
|
|
|
) -> ArbitraryBundleInputs<StdRng> {
|
2021-04-27 12:56:36 -07:00
|
|
|
ArbitraryBundleInputs {
|
2021-04-29 08:55:11 -07:00
|
|
|
rng: StdRng::from_seed(rng_seed),
|
2021-04-27 12:56:36 -07:00
|
|
|
sk: sk.clone(),
|
|
|
|
anchor,
|
2021-06-08 00:27:08 -07:00
|
|
|
notes: notes_and_auth_paths,
|
2021-04-27 12:56:36 -07:00
|
|
|
recipient_amounts
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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>> {
|
|
|
|
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>(
|
|
|
|
k: SpendingKey,
|
|
|
|
) -> impl Strategy<Value = Bundle<Authorized, V>> {
|
|
|
|
arb_bundle_inputs(k).prop_map(|inputs| inputs.into_bundle::<V>())
|
|
|
|
}
|
2021-04-27 06:49:49 -07:00
|
|
|
}
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use rand::rngs::OsRng;
|
|
|
|
|
|
|
|
use super::Builder;
|
|
|
|
use crate::{
|
2021-04-27 06:49:49 -07:00
|
|
|
bundle::{Authorized, Bundle, Flags},
|
2021-04-14 21:14:34 -07:00
|
|
|
circuit::ProvingKey,
|
|
|
|
keys::{FullViewingKey, SpendingKey},
|
|
|
|
tree::Anchor,
|
2021-04-27 06:49:49 -07:00
|
|
|
value::NoteValue,
|
2021-04-14 21:14:34 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shielding_bundle() {
|
|
|
|
let pk = ProvingKey::build();
|
|
|
|
let mut rng = OsRng;
|
|
|
|
|
|
|
|
let sk = SpendingKey::random(&mut rng);
|
|
|
|
let fvk = FullViewingKey::from(&sk);
|
|
|
|
let recipient = fvk.default_address();
|
|
|
|
|
2021-04-27 06:49:49 -07:00
|
|
|
let mut builder = Builder::new(Flags::from_parts(true, true), Anchor([0; 32]));
|
2021-04-14 21:14:34 -07:00
|
|
|
builder
|
|
|
|
.add_recipient(None, recipient, NoteValue::from_raw(5000), None)
|
|
|
|
.unwrap();
|
2021-04-27 06:49:49 -07:00
|
|
|
let bundle: Bundle<Authorized, i64> = dbg!(builder
|
2021-04-14 21:14:34 -07:00
|
|
|
.build(&mut rng, &pk)
|
|
|
|
.unwrap()
|
2021-05-08 12:51:55 -07:00
|
|
|
.prepare(&mut rng, [0; 32]))
|
2021-04-14 21:14:34 -07:00
|
|
|
.finalize()
|
|
|
|
.unwrap();
|
2021-04-27 06:49:49 -07:00
|
|
|
assert_eq!(bundle.value_balance(), &(-5000))
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
}
|