Merge remote-tracking branch 'upstream/master' into data_access_api
This commit is contained in:
commit
db9eb29eba
|
@ -33,6 +33,7 @@ percent-encoding = "2.1.0"
|
|||
protobuf-codegen-pure = "2.15"
|
||||
|
||||
[dev-dependencies]
|
||||
gumdrop = "0.8"
|
||||
rand_core = "0.5.1"
|
||||
rand_xorshift = "0.2"
|
||||
tempfile = "3.1.0"
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
use std::convert::TryInto;
|
||||
|
||||
use gumdrop::Options;
|
||||
use zcash_client_backend::encoding::{decode_extended_full_viewing_key, encode_payment_address};
|
||||
use zcash_primitives::{
|
||||
constants::{mainnet, testnet},
|
||||
zip32::{DiversifierIndex, ExtendedFullViewingKey},
|
||||
};
|
||||
|
||||
fn parse_viewing_key(s: &str) -> Result<(ExtendedFullViewingKey, bool), &'static str> {
|
||||
decode_extended_full_viewing_key(mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, s)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|vk| (vk, true))
|
||||
.or_else(|| {
|
||||
decode_extended_full_viewing_key(testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, s)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|vk| (vk, false))
|
||||
})
|
||||
.ok_or("Invalid Sapling viewing key")
|
||||
}
|
||||
|
||||
fn parse_diversifier_index(s: &str) -> Result<DiversifierIndex, &'static str> {
|
||||
let i: u128 = s.parse().map_err(|_| "Diversifier index is not a number")?;
|
||||
if i >= (1 << 88) {
|
||||
return Err("Diversifier index too large");
|
||||
}
|
||||
Ok(DiversifierIndex(i.to_le_bytes()[..11].try_into().unwrap()))
|
||||
}
|
||||
|
||||
fn encode_diversifier_index(di: &DiversifierIndex) -> u128 {
|
||||
let mut bytes = [0; 16];
|
||||
bytes[..11].copy_from_slice(&di.0);
|
||||
u128::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
struct MyOptions {
|
||||
#[options(help = "Print this help message and exit.")]
|
||||
help: bool,
|
||||
|
||||
#[options(
|
||||
free,
|
||||
help = "The Sapling viewing key to generate diversified addresses from",
|
||||
parse(try_from_str = "parse_viewing_key")
|
||||
)]
|
||||
viewing_key: Option<(ExtendedFullViewingKey, bool)>,
|
||||
|
||||
#[options(
|
||||
free,
|
||||
help = "The index of the diversified address to generate (default 0). Some indices don't have a corresponding address.",
|
||||
parse(try_from_str = "parse_diversifier_index")
|
||||
)]
|
||||
diversifier_index: DiversifierIndex,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let opts = MyOptions::parse_args_default_or_exit();
|
||||
|
||||
let (extfvk, is_mainnet) = if let Some(res) = opts.viewing_key {
|
||||
res
|
||||
} else {
|
||||
eprintln!("Missing Sapling viewing key");
|
||||
return;
|
||||
};
|
||||
|
||||
let (diversifier_index, address) = extfvk.address(opts.diversifier_index).unwrap();
|
||||
println!(
|
||||
"# Diversifier index: {}",
|
||||
encode_diversifier_index(&diversifier_index)
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
encode_payment_address(
|
||||
if is_mainnet {
|
||||
mainnet::HRP_SAPLING_PAYMENT_ADDRESS
|
||||
} else {
|
||||
testnet::HRP_SAPLING_PAYMENT_ADDRESS
|
||||
},
|
||||
&address
|
||||
)
|
||||
);
|
||||
}
|
|
@ -10,7 +10,7 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
blake2b_simd = "0.5"
|
||||
zcash_primitives = { version = "0.4.0", path = "../zcash_primitives" }
|
||||
zcash_primitives = { version = "0.4.0", path = "../zcash_primitives", features = ["zfuture"] }
|
||||
|
||||
[dev-dependencies]
|
||||
ff = "0.8"
|
||||
|
|
|
@ -47,6 +47,7 @@ rand_xorshift = "0.2"
|
|||
[features]
|
||||
transparent-inputs = ["ripemd160", "secp256k1"]
|
||||
test-dependencies = ["proptest"]
|
||||
zfuture = []
|
||||
|
||||
[[bench]]
|
||||
name = "note_decryption"
|
||||
|
|
|
@ -195,6 +195,7 @@ impl Parameters for MainNetwork {
|
|||
NetworkUpgrade::Blossom => Some(BlockHeight(653_600)),
|
||||
NetworkUpgrade::Heartwood => Some(BlockHeight(903_000)),
|
||||
NetworkUpgrade::Canopy => Some(BlockHeight(1_046_400)),
|
||||
#[cfg(feature = "zfuture")]
|
||||
NetworkUpgrade::ZFuture => None,
|
||||
}
|
||||
}
|
||||
|
@ -238,6 +239,7 @@ impl Parameters for TestNetwork {
|
|||
NetworkUpgrade::Blossom => Some(BlockHeight(584_000)),
|
||||
NetworkUpgrade::Heartwood => Some(BlockHeight(903_800)),
|
||||
NetworkUpgrade::Canopy => Some(BlockHeight(1_028_500)),
|
||||
#[cfg(feature = "zfuture")]
|
||||
NetworkUpgrade::ZFuture => None,
|
||||
}
|
||||
}
|
||||
|
@ -355,6 +357,7 @@ pub enum NetworkUpgrade {
|
|||
/// This upgrade is expected never to activate on mainnet;
|
||||
/// it is intended for use in integration testing of functionality
|
||||
/// that is a candidate for integration in a future network upgrade.
|
||||
#[cfg(feature = "zfuture")]
|
||||
ZFuture,
|
||||
}
|
||||
|
||||
|
@ -366,6 +369,7 @@ impl fmt::Display for NetworkUpgrade {
|
|||
NetworkUpgrade::Blossom => write!(f, "Blossom"),
|
||||
NetworkUpgrade::Heartwood => write!(f, "Heartwood"),
|
||||
NetworkUpgrade::Canopy => write!(f, "Canopy"),
|
||||
#[cfg(feature = "zfuture")]
|
||||
NetworkUpgrade::ZFuture => write!(f, "ZFUTURE"),
|
||||
}
|
||||
}
|
||||
|
@ -379,6 +383,7 @@ impl NetworkUpgrade {
|
|||
NetworkUpgrade::Blossom => BranchId::Blossom,
|
||||
NetworkUpgrade::Heartwood => BranchId::Heartwood,
|
||||
NetworkUpgrade::Canopy => BranchId::Canopy,
|
||||
#[cfg(feature = "zfuture")]
|
||||
NetworkUpgrade::ZFuture => BranchId::ZFuture,
|
||||
}
|
||||
}
|
||||
|
@ -427,6 +432,7 @@ pub enum BranchId {
|
|||
Canopy,
|
||||
/// Candidates for future consensus rules; this branch will never
|
||||
/// activate on mainnet.
|
||||
#[cfg(feature = "zfuture")]
|
||||
ZFuture,
|
||||
}
|
||||
|
||||
|
@ -441,6 +447,7 @@ impl TryFrom<u32> for BranchId {
|
|||
0x2bb4_0e60 => Ok(BranchId::Blossom),
|
||||
0xf5b9_230b => Ok(BranchId::Heartwood),
|
||||
0xe9ff_75a6 => Ok(BranchId::Canopy),
|
||||
#[cfg(feature = "zfuture")]
|
||||
0xffff_ffff => Ok(BranchId::ZFuture),
|
||||
_ => Err("Unknown consensus branch ID"),
|
||||
}
|
||||
|
@ -456,6 +463,7 @@ impl From<BranchId> for u32 {
|
|||
BranchId::Blossom => 0x2bb4_0e60,
|
||||
BranchId::Heartwood => 0xf5b9_230b,
|
||||
BranchId::Canopy => 0xe9ff_75a6,
|
||||
#[cfg(feature = "zfuture")]
|
||||
BranchId::ZFuture => 0xffff_ffff,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
pub mod block;
|
||||
pub mod consensus;
|
||||
pub mod constants;
|
||||
pub mod extensions;
|
||||
pub mod group_hash;
|
||||
pub mod keys;
|
||||
pub mod legacy;
|
||||
|
@ -26,5 +25,8 @@ pub mod transaction;
|
|||
pub mod util;
|
||||
pub mod zip32;
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub mod extensions;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_vectors;
|
||||
|
|
|
@ -237,7 +237,7 @@ pub fn prf_ock(
|
|||
/// let encCiphertext = enc.encrypt_note_plaintext();
|
||||
/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu);
|
||||
/// ```
|
||||
pub struct SaplingNoteEncryption<R: RngCore + CryptoRng> {
|
||||
pub struct SaplingNoteEncryption<R: RngCore> {
|
||||
epk: jubjub::SubgroupPoint,
|
||||
esk: jubjub::Fr,
|
||||
note: Note,
|
||||
|
@ -254,13 +254,25 @@ impl<R: RngCore + CryptoRng> SaplingNoteEncryption<R> {
|
|||
/// Setting `ovk` to `None` represents the `ovk = ⊥` case, where the note cannot be
|
||||
/// recovered by the sender.
|
||||
pub fn new(
|
||||
ovk: Option<OutgoingViewingKey>,
|
||||
note: Note,
|
||||
to: PaymentAddress,
|
||||
memo: Memo,
|
||||
rng: R,
|
||||
) -> Self {
|
||||
Self::new_internal(ovk, note, to, memo, rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: RngCore> SaplingNoteEncryption<R> {
|
||||
pub(crate) fn new_internal(
|
||||
ovk: Option<OutgoingViewingKey>,
|
||||
note: Note,
|
||||
to: PaymentAddress,
|
||||
memo: Memo,
|
||||
mut rng: R,
|
||||
) -> Self {
|
||||
let esk = note.generate_or_derive_esk(&mut rng);
|
||||
let esk = note.generate_or_derive_esk_internal(&mut rng);
|
||||
let epk = note.g_d * esk;
|
||||
|
||||
SaplingNoteEncryption {
|
||||
|
|
|
@ -309,6 +309,10 @@ impl Note {
|
|||
}
|
||||
|
||||
pub fn generate_or_derive_esk<R: RngCore + CryptoRng>(&self, rng: &mut R) -> jubjub::Fr {
|
||||
self.generate_or_derive_esk_internal(rng)
|
||||
}
|
||||
|
||||
pub(crate) fn generate_or_derive_esk_internal<R: RngCore>(&self, rng: &mut R) -> jubjub::Fr {
|
||||
match self.derive_esk() {
|
||||
None => {
|
||||
// create random 64 byte buffer
|
||||
|
|
|
@ -59,7 +59,7 @@ pub trait TxProver {
|
|||
) -> Result<Signature, ()>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod mock {
|
||||
use ff::Field;
|
||||
use rand_core::OsRng;
|
||||
|
@ -78,9 +78,8 @@ pub mod mock {
|
|||
|
||||
use super::TxProver;
|
||||
|
||||
pub(crate) struct MockTxProver;
|
||||
pub struct MockTxProver;
|
||||
|
||||
#[cfg(test)]
|
||||
impl TxProver for MockTxProver {
|
||||
type SaplingProvingContext = ();
|
||||
|
||||
|
|
|
@ -114,6 +114,15 @@ pub fn spend_sig<R: RngCore + CryptoRng>(
|
|||
ar: jubjub::Fr,
|
||||
sighash: &[u8; 32],
|
||||
rng: &mut R,
|
||||
) -> Signature {
|
||||
spend_sig_internal(ask, ar, sighash, rng)
|
||||
}
|
||||
|
||||
pub(crate) fn spend_sig_internal<R: RngCore>(
|
||||
ask: PrivateKey,
|
||||
ar: jubjub::Fr,
|
||||
sighash: &[u8; 32],
|
||||
rng: &mut R,
|
||||
) -> Signature {
|
||||
// We compute `rsk`...
|
||||
let rsk = ask.randomize(ar);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
//! Structs for building transactions.
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use std::boxed::Box;
|
||||
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
@ -10,7 +12,6 @@ use rand::{rngs::OsRng, seq::SliceRandom, CryptoRng, RngCore};
|
|||
|
||||
use crate::{
|
||||
consensus::{self, BlockHeight},
|
||||
extensions::transparent::{self as tze, ExtensionTxBuilder, ToPayload},
|
||||
keys::OutgoingViewingKey,
|
||||
legacy::TransparentAddress,
|
||||
merkle_tree::MerklePath,
|
||||
|
@ -18,21 +19,33 @@ use crate::{
|
|||
primitives::{Diversifier, Note, PaymentAddress},
|
||||
prover::TxProver,
|
||||
redjubjub::PrivateKey,
|
||||
sapling::{spend_sig, Node},
|
||||
sapling::{spend_sig_internal, Node},
|
||||
transaction::{
|
||||
components::{
|
||||
amount::Amount, amount::DEFAULT_FEE, OutPoint, OutputDescription, SpendDescription,
|
||||
TxOut, TzeIn, TzeOut,
|
||||
amount::Amount, amount::DEFAULT_FEE, OutputDescription, SpendDescription, TxOut,
|
||||
},
|
||||
signature_hash_data, SignableInput, Transaction, TransactionData, SIGHASH_ALL,
|
||||
},
|
||||
util::generate_random_rseed,
|
||||
util::generate_random_rseed_internal,
|
||||
zip32::ExtendedSpendingKey,
|
||||
};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use crate::{legacy::Script, transaction::components::TxIn};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use crate::{
|
||||
extensions::transparent::{self as tze, ExtensionTxBuilder, ToPayload},
|
||||
transaction::components::{TzeIn, TzeOut},
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "transparent-inputs", feature = "zfuture"))]
|
||||
use crate::transaction::components::OutPoint;
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
use crate::prover::mock::MockTxProver;
|
||||
|
||||
|
||||
const DEFAULT_TX_EXPIRY_DELTA: u32 = 20;
|
||||
|
||||
/// If there are any shielded inputs, always have at least two shielded outputs, padding
|
||||
|
@ -98,6 +111,18 @@ impl SaplingOutput {
|
|||
to: PaymentAddress,
|
||||
value: Amount,
|
||||
memo: Option<Memo>,
|
||||
) -> Result<Self, Error> {
|
||||
Self::new_internal(params, height, rng, ovk, to, value, memo)
|
||||
}
|
||||
|
||||
fn new_internal<R: RngCore, P: consensus::Parameters>(
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
rng: &mut R,
|
||||
ovk: Option<OutgoingViewingKey>,
|
||||
to: PaymentAddress,
|
||||
value: Amount,
|
||||
memo: Option<Memo>,
|
||||
) -> Result<Self, Error> {
|
||||
let g_d = match to.g_d() {
|
||||
Some(g_d) => g_d,
|
||||
|
@ -107,7 +132,7 @@ impl SaplingOutput {
|
|||
return Err(Error::InvalidAmount);
|
||||
}
|
||||
|
||||
let rseed = generate_random_rseed(params, height, rng);
|
||||
let rseed = generate_random_rseed_internal(params, height, rng);
|
||||
|
||||
let note = Note {
|
||||
g_d,
|
||||
|
@ -130,7 +155,16 @@ impl SaplingOutput {
|
|||
ctx: &mut P::SaplingProvingContext,
|
||||
rng: &mut R,
|
||||
) -> OutputDescription {
|
||||
let mut encryptor = SaplingNoteEncryption::new(
|
||||
self.build_internal(prover, ctx, rng)
|
||||
}
|
||||
|
||||
fn build_internal<P: TxProver, R: RngCore>(
|
||||
self,
|
||||
prover: &P,
|
||||
ctx: &mut P::SaplingProvingContext,
|
||||
rng: &mut R,
|
||||
) -> OutputDescription {
|
||||
let mut encryptor = SaplingNoteEncryption::new_internal(
|
||||
self.ovk,
|
||||
self.note.clone(),
|
||||
self.to.clone(),
|
||||
|
@ -265,15 +299,18 @@ impl TransparentInputs {
|
|||
fn apply_signatures(&self, _: &mut TransactionData, _: consensus::BranchId) {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
struct TzeInputInfo<'a, BuildCtx> {
|
||||
prevout: TzeOut,
|
||||
builder: Box<dyn FnOnce(&BuildCtx) -> Result<(u32, Vec<u8>), Error> + 'a>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
struct TzeInputs<'a, BuildCtx> {
|
||||
builders: Vec<TzeInputInfo<'a, BuildCtx>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
impl<'a, BuildCtx> TzeInputs<'a, BuildCtx> {
|
||||
fn default() -> Self {
|
||||
TzeInputs { builders: vec![] }
|
||||
|
@ -329,7 +366,7 @@ impl TransactionMetadata {
|
|||
}
|
||||
|
||||
/// Generates a [`Transaction`] from its inputs and outputs.
|
||||
pub struct Builder<'a, P: consensus::Parameters, R: RngCore + CryptoRng> {
|
||||
pub struct Builder<'a, P: consensus::Parameters, R: RngCore> {
|
||||
params: P,
|
||||
rng: R,
|
||||
height: BlockHeight,
|
||||
|
@ -339,9 +376,10 @@ pub struct Builder<'a, P: consensus::Parameters, R: RngCore + CryptoRng> {
|
|||
spends: Vec<SpendDescriptionInfo>,
|
||||
outputs: Vec<SaplingOutput>,
|
||||
transparent_inputs: TransparentInputs,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_inputs: TzeInputs<'a, TransactionData>,
|
||||
change_address: Option<(OutgoingViewingKey, PaymentAddress)>,
|
||||
phantom: PhantomData<P>,
|
||||
_phantom: &'a PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng> {
|
||||
|
@ -372,6 +410,7 @@ impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng> {
|
|||
/// The transaction will be constructed and serialized according to the
|
||||
/// NetworkUpgrade::ZFuture rules. This is intended only for use in
|
||||
/// integration testing of new features.
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub fn new_zfuture(params: P, height: BlockHeight) -> Self {
|
||||
Builder::new_with_rng_zfuture(params, height, OsRng)
|
||||
}
|
||||
|
@ -405,11 +444,17 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
/// The transaction will be constructed and serialized according to the
|
||||
/// NetworkUpgrade::ZFuture rules. This is intended only for use in
|
||||
/// integration testing of new features.
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub fn new_with_rng_zfuture(params: P, height: BlockHeight, rng: R) -> Builder<'a, P, R> {
|
||||
Self::new_with_mtx(params, height, rng, TransactionData::zfuture())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
||||
/// Common utility function for builder construction.
|
||||
///
|
||||
/// WARNING: THIS MUST REMAIN PRIVATE AS IT ALLOWS CONSTRUCTION
|
||||
/// OF BUILDERS WITH NON-CryptoRng RNGs
|
||||
fn new_with_mtx(
|
||||
params: P,
|
||||
height: BlockHeight,
|
||||
|
@ -428,9 +473,10 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
spends: vec![],
|
||||
outputs: vec![],
|
||||
transparent_inputs: TransparentInputs::default(),
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_inputs: TzeInputs::default(),
|
||||
change_address: None,
|
||||
phantom: PhantomData,
|
||||
_phantom: &PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -479,7 +525,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
value: Amount,
|
||||
memo: Option<Memo>,
|
||||
) -> Result<(), Error> {
|
||||
let output = SaplingOutput::new(
|
||||
let output = SaplingOutput::new_internal(
|
||||
&self.params,
|
||||
self.height,
|
||||
&mut self.rng,
|
||||
|
@ -558,7 +604,10 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
|
||||
// Valid change
|
||||
let change = self.mtx.value_balance - self.fee + self.transparent_inputs.value_sum()
|
||||
- self.mtx.vout.iter().map(|vo| vo.value).sum::<Amount>()
|
||||
- self.mtx.vout.iter().map(|vo| vo.value).sum::<Amount>();
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
let change = change
|
||||
+ self
|
||||
.tze_inputs
|
||||
.builders
|
||||
|
@ -680,7 +729,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
// Record the post-randomized output location
|
||||
tx_metadata.output_indices[pos] = i;
|
||||
|
||||
output.build(prover, &mut ctx, &mut self.rng)
|
||||
output.build_internal(prover, &mut ctx, &mut self.rng)
|
||||
} else {
|
||||
// This is a dummy output
|
||||
let (dummy_to, dummy_note) = {
|
||||
|
@ -707,7 +756,8 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
}
|
||||
};
|
||||
|
||||
let rseed = generate_random_rseed(&self.params, self.height, &mut self.rng);
|
||||
let rseed =
|
||||
generate_random_rseed_internal(&self.params, self.height, &mut self.rng);
|
||||
|
||||
(
|
||||
payment_address,
|
||||
|
@ -720,7 +770,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
)
|
||||
};
|
||||
|
||||
let esk = dummy_note.generate_or_derive_esk(&mut self.rng);
|
||||
let esk = dummy_note.generate_or_derive_esk_internal(&mut self.rng);
|
||||
let epk = dummy_note.g_d * esk;
|
||||
|
||||
let (zkproof, cv) = prover.output_proof(
|
||||
|
@ -765,7 +815,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
|
||||
// Create Sapling spendAuth and binding signatures
|
||||
for (i, (_, spend)) in spends.into_iter().enumerate() {
|
||||
self.mtx.shielded_spends[i].spend_auth_sig = Some(spend_sig(
|
||||
self.mtx.shielded_spends[i].spend_auth_sig = Some(spend_sig_internal(
|
||||
PrivateKey(spend.extsk.expsk.ask),
|
||||
spend.alpha,
|
||||
&sighash,
|
||||
|
@ -785,6 +835,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
};
|
||||
|
||||
// Create TZE input witnesses
|
||||
#[cfg(feature = "zfuture")]
|
||||
for (i, tze_in) in self.tze_inputs.builders.into_iter().enumerate() {
|
||||
// The witness builder function should have cached/closed over whatever data was necessary for the
|
||||
// witness to commit to at the time it was added to the transaction builder; here, it then computes those
|
||||
|
@ -809,6 +860,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> ExtensionTxBuilder<'a>
|
||||
for Builder<'a, P, R>
|
||||
{
|
||||
|
@ -856,6 +908,56 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> ExtensionTxBuilder<'a
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
||||
/// Creates a new `Builder` targeted for inclusion in the block with the given height
|
||||
/// and randomness source, using default values for general transaction fields.
|
||||
///
|
||||
/// # Default values
|
||||
///
|
||||
/// The expiry height will be set to the given height plus the default transaction
|
||||
/// expiry delta (20 blocks).
|
||||
///
|
||||
/// The fee will be set to the default fee (0.0001 ZEC).
|
||||
///
|
||||
/// WARNING: DO NOT USE IN PRODUCTION
|
||||
pub fn test_only_new_with_rng(params: P, height: BlockHeight, rng: R) -> Builder<'a, P, R> {
|
||||
Self::new_with_mtx(params, height, rng, TransactionData::new())
|
||||
}
|
||||
|
||||
/// Creates a new `Builder` targeted for inclusion in the block with the given height,
|
||||
/// and randomness source, using default values for general transaction fields
|
||||
/// and the `ZFUTURE_TX_VERSION` and `ZFUTURE_VERSION_GROUP_ID` version identifiers.
|
||||
///
|
||||
/// # Default values
|
||||
///
|
||||
/// The expiry height will be set to the given height plus the default transaction
|
||||
/// expiry delta (20 blocks).
|
||||
///
|
||||
/// The fee will be set to the default fee (0.0001 ZEC).
|
||||
///
|
||||
/// The transaction will be constructed and serialized according to the
|
||||
/// NetworkUpgrade::ZFuture rules. This is intended only for use in
|
||||
/// integration testing of new features.
|
||||
///
|
||||
/// WARNING: DO NOT USE IN PRODUCTION
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub fn test_only_new_with_rng_zfuture(
|
||||
params: P,
|
||||
height: BlockHeight,
|
||||
rng: R,
|
||||
) -> Builder<'a, P, R> {
|
||||
Self::new_with_mtx(params, height, rng, TransactionData::zfuture())
|
||||
}
|
||||
|
||||
pub fn mock_build(
|
||||
self,
|
||||
consensus_branch_id: consensus::BranchId,
|
||||
) -> Result<(Transaction, TransactionMetadata), Error> {
|
||||
self.build(consensus_branch_id, &MockTxProver)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ff::{Field, PrimeField};
|
||||
|
@ -873,7 +975,10 @@ mod tests {
|
|||
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||
};
|
||||
|
||||
use super::{Builder, Error, TzeInputs};
|
||||
use super::{Builder, Error};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use super::TzeInputs;
|
||||
|
||||
#[test]
|
||||
fn fails_on_negative_output() {
|
||||
|
@ -912,9 +1017,10 @@ mod tests {
|
|||
spends: vec![],
|
||||
outputs: vec![],
|
||||
transparent_inputs: TransparentInputs::default(),
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_inputs: TzeInputs::default(),
|
||||
change_address: None,
|
||||
phantom: PhantomData,
|
||||
_phantom: &PhantomData,
|
||||
};
|
||||
|
||||
// Create a tx with only t output. No binding_sig should be present
|
||||
|
|
|
@ -5,14 +5,22 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
|||
use ff::PrimeField;
|
||||
use group::GroupEncoding;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use crate::extensions::transparent as tze;
|
||||
use crate::legacy::Script;
|
||||
use crate::primitives::Nullifier;
|
||||
use crate::redjubjub::{PublicKey, Signature};
|
||||
use crate::serialize::{CompactSize, Vector};
|
||||
#[cfg(feature = "zfuture")]
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::{
|
||||
primitives::Nullifier,
|
||||
legacy::Script,
|
||||
redjubjub::{PublicKey, Signature},
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use crate::{
|
||||
extensions::transparent as tze,
|
||||
serialize::{CompactSize, Vector},
|
||||
};
|
||||
|
||||
pub mod amount;
|
||||
pub use self::amount::Amount;
|
||||
|
@ -122,19 +130,22 @@ impl TxOut {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn to_io_error(_: std::num::TryFromIntError) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::InvalidData, "value out of range")
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub struct TzeIn {
|
||||
pub prevout: OutPoint,
|
||||
pub witness: tze::Witness,
|
||||
}
|
||||
|
||||
fn to_io_error(_: std::num::TryFromIntError) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::InvalidData, "value out of range")
|
||||
}
|
||||
|
||||
/// Transaction encoding and decoding functions conforming to ZIP-222
|
||||
///
|
||||
/// https://zips.z.cash/zip-0222#encoding-in-transactions
|
||||
#[cfg(feature = "zfuture")]
|
||||
impl TzeIn {
|
||||
/// Convenience constructor
|
||||
pub fn new(prevout: OutPoint, extension_id: u32, mode: u32) -> Self {
|
||||
|
@ -199,11 +210,13 @@ impl TzeIn {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub struct TzeOut {
|
||||
pub value: Amount,
|
||||
pub precondition: tze::Precondition,
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
impl TzeOut {
|
||||
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
|
||||
let value = {
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
//! Structs and methods for handling Zcash transactions.
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fmt;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::{consensus::BlockHeight, redjubjub::Signature, serialize::Vector};
|
||||
use crate::{
|
||||
consensus::BlockHeight,
|
||||
redjubjub::Signature,
|
||||
serialize::Vector,
|
||||
util::sha256d::{HashReader, HashWriter},
|
||||
};
|
||||
|
||||
pub mod builder;
|
||||
pub mod components;
|
||||
|
@ -17,9 +21,10 @@ mod tests;
|
|||
|
||||
pub use self::sighash::{signature_hash, signature_hash_data, SignableInput, SIGHASH_ALL};
|
||||
|
||||
use self::components::{
|
||||
Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut, TzeIn, TzeOut,
|
||||
};
|
||||
use self::components::{Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use self::components::{TzeIn, TzeOut};
|
||||
|
||||
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270;
|
||||
const OVERWINTER_TX_VERSION: u32 = 3;
|
||||
|
@ -32,7 +37,9 @@ const SAPLING_TX_VERSION: u32 = 4;
|
|||
/// using these constants should be inspected, and use of these constants
|
||||
/// should be removed as appropriate in favor of the new consensus
|
||||
/// transaction version and group.
|
||||
#[cfg(feature = "zfuture")]
|
||||
const ZFUTURE_VERSION_GROUP_ID: u32 = 0xFFFFFFFF;
|
||||
#[cfg(feature = "zfuture")]
|
||||
const ZFUTURE_TX_VERSION: u32 = 0x0000FFFF;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
||||
|
@ -74,7 +81,9 @@ pub struct TransactionData {
|
|||
pub version_group_id: u32,
|
||||
pub vin: Vec<TxIn>,
|
||||
pub vout: Vec<TxOut>,
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub tze_inputs: Vec<TzeIn>,
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub tze_outputs: Vec<TzeOut>,
|
||||
pub lock_time: u32,
|
||||
pub expiry_height: BlockHeight,
|
||||
|
@ -96,9 +105,7 @@ impl std::fmt::Debug for TransactionData {
|
|||
version = {:?},
|
||||
version_group_id = {:?},
|
||||
vin = {:?},
|
||||
vout = {:?},
|
||||
tze_inputs = {:?},
|
||||
tze_outputs = {:?},
|
||||
vout = {:?},{}
|
||||
lock_time = {:?},
|
||||
expiry_height = {:?},
|
||||
value_balance = {:?},
|
||||
|
@ -112,8 +119,19 @@ impl std::fmt::Debug for TransactionData {
|
|||
self.version_group_id,
|
||||
self.vin,
|
||||
self.vout,
|
||||
self.tze_inputs,
|
||||
self.tze_outputs,
|
||||
{
|
||||
#[cfg(feature = "zfuture")]
|
||||
{
|
||||
format!(
|
||||
"
|
||||
tze_inputs = {:?},
|
||||
tze_outputs = {:?},",
|
||||
self.tze_inputs, self.tze_outputs
|
||||
)
|
||||
}
|
||||
#[cfg(not(feature = "zfuture"))]
|
||||
""
|
||||
},
|
||||
self.lock_time,
|
||||
self.expiry_height,
|
||||
self.value_balance,
|
||||
|
@ -140,7 +158,9 @@ impl TransactionData {
|
|||
version_group_id: SAPLING_VERSION_GROUP_ID,
|
||||
vin: vec![],
|
||||
vout: vec![],
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_inputs: vec![],
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_outputs: vec![],
|
||||
lock_time: 0,
|
||||
expiry_height: 0u32.into(),
|
||||
|
@ -154,6 +174,7 @@ impl TransactionData {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub fn zfuture() -> Self {
|
||||
TransactionData {
|
||||
overwintered: true,
|
||||
|
@ -194,11 +215,9 @@ impl Transaction {
|
|||
txid: TxId([0; 32]),
|
||||
data,
|
||||
};
|
||||
let mut raw = vec![];
|
||||
tx.write(&mut raw)?;
|
||||
tx.txid
|
||||
.0
|
||||
.copy_from_slice(&Sha256::digest(&Sha256::digest(&raw)));
|
||||
let mut writer = HashWriter::default();
|
||||
tx.write(&mut writer)?;
|
||||
tx.txid.0.copy_from_slice(&writer.into_hash());
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
|
@ -206,7 +225,9 @@ impl Transaction {
|
|||
self.txid
|
||||
}
|
||||
|
||||
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||
pub fn read<R: Read>(reader: R) -> io::Result<Self> {
|
||||
let mut reader = HashReader::new(reader);
|
||||
|
||||
let header = reader.read_u32::<LittleEndian>()?;
|
||||
let overwintered = (header >> 31) == 1;
|
||||
let version = header & 0x7FFFFFFF;
|
||||
|
@ -223,9 +244,13 @@ impl Transaction {
|
|||
let is_sapling_v4 = overwintered
|
||||
&& version_group_id == SAPLING_VERSION_GROUP_ID
|
||||
&& version == SAPLING_TX_VERSION;
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
let has_tze = overwintered
|
||||
&& version_group_id == ZFUTURE_VERSION_GROUP_ID
|
||||
&& version == ZFUTURE_TX_VERSION;
|
||||
#[cfg(not(feature = "zfuture"))]
|
||||
let has_tze = false;
|
||||
|
||||
if overwintered && !(is_overwinter_v3 || is_sapling_v4 || has_tze) {
|
||||
return Err(io::Error::new(
|
||||
|
@ -236,6 +261,7 @@ impl Transaction {
|
|||
|
||||
let vin = Vector::read(&mut reader, TxIn::read)?;
|
||||
let vout = Vector::read(&mut reader, TxOut::read)?;
|
||||
#[cfg(feature = "zfuture")]
|
||||
let (tze_inputs, tze_outputs) = if has_tze {
|
||||
let wi = Vector::read(&mut reader, TzeIn::read)?;
|
||||
let wo = Vector::read(&mut reader, TzeOut::read)?;
|
||||
|
@ -291,23 +317,31 @@ impl Transaction {
|
|||
None
|
||||
};
|
||||
|
||||
Transaction::from_data(TransactionData {
|
||||
overwintered,
|
||||
version,
|
||||
version_group_id,
|
||||
vin,
|
||||
vout,
|
||||
tze_inputs,
|
||||
tze_outputs,
|
||||
lock_time,
|
||||
expiry_height,
|
||||
value_balance,
|
||||
shielded_spends,
|
||||
shielded_outputs,
|
||||
joinsplits,
|
||||
joinsplit_pubkey,
|
||||
joinsplit_sig,
|
||||
binding_sig,
|
||||
let mut txid = [0; 32];
|
||||
txid.copy_from_slice(&reader.into_hash());
|
||||
|
||||
Ok(Transaction {
|
||||
txid: TxId(txid),
|
||||
data: TransactionData {
|
||||
overwintered,
|
||||
version,
|
||||
version_group_id,
|
||||
vin,
|
||||
vout,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_inputs,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_outputs,
|
||||
lock_time,
|
||||
expiry_height,
|
||||
value_balance,
|
||||
shielded_spends,
|
||||
shielded_outputs,
|
||||
joinsplits,
|
||||
joinsplit_pubkey,
|
||||
joinsplit_sig,
|
||||
binding_sig,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -323,9 +357,13 @@ impl Transaction {
|
|||
let is_sapling_v4 = self.overwintered
|
||||
&& self.version_group_id == SAPLING_VERSION_GROUP_ID
|
||||
&& self.version == SAPLING_TX_VERSION;
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
let has_tze = self.overwintered
|
||||
&& self.version_group_id == ZFUTURE_VERSION_GROUP_ID
|
||||
&& self.version == ZFUTURE_TX_VERSION;
|
||||
#[cfg(not(feature = "zfuture"))]
|
||||
let has_tze = false;
|
||||
|
||||
if self.overwintered && !(is_overwinter_v3 || is_sapling_v4 || has_tze) {
|
||||
return Err(io::Error::new(
|
||||
|
@ -336,6 +374,7 @@ impl Transaction {
|
|||
|
||||
Vector::write(&mut writer, &self.vin, |w, e| e.write(w))?;
|
||||
Vector::write(&mut writer, &self.vout, |w, e| e.write(w))?;
|
||||
#[cfg(feature = "zfuture")]
|
||||
if has_tze {
|
||||
Vector::write(&mut writer, &self.tze_inputs, |w, e| e.write(w))?;
|
||||
Vector::write(&mut writer, &self.tze_outputs, |w, e| e.write(w))?;
|
||||
|
@ -412,3 +451,147 @@ impl Transaction {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-dependencies")]
|
||||
pub mod testing {
|
||||
use proptest::collection::vec;
|
||||
use proptest::prelude::*;
|
||||
use proptest::sample::select;
|
||||
|
||||
use crate::{consensus::BranchId, legacy::Script};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use crate::extensions::transparent as tze;
|
||||
|
||||
use super::{
|
||||
components::{amount::MAX_MONEY, Amount, OutPoint, TxIn, TxOut},
|
||||
Transaction, TransactionData, OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID,
|
||||
SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID,
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use super::{
|
||||
components::{TzeIn, TzeOut},
|
||||
ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID,
|
||||
};
|
||||
|
||||
pub const VALID_OPCODES: [u8; 8] = [
|
||||
0x00, // OP_FALSE,
|
||||
0x51, // OP_1,
|
||||
0x52, // OP_2,
|
||||
0x53, // OP_3,
|
||||
0xac, // OP_CHECKSIG,
|
||||
0x63, // OP_IF,
|
||||
0x65, // OP_VERIF,
|
||||
0x6a, // OP_RETURN,
|
||||
];
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_outpoint()(hash in prop::array::uniform32(1u8..), n in 1..(100 as u32)) -> OutPoint {
|
||||
OutPoint::new(hash, n)
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_script()(v in vec(select(&VALID_OPCODES[..]), 1..256)) -> Script {
|
||||
Script(v)
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_txin()(prevout in arb_outpoint(), script_sig in arb_script(), sequence in any::<u32>()) -> TxIn {
|
||||
TxIn { prevout, script_sig, sequence }
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_amount()(value in 0..MAX_MONEY) -> Amount {
|
||||
Amount::from_i64(value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_txout()(value in arb_amount(), script_pubkey in arb_script()) -> TxOut {
|
||||
TxOut { value, script_pubkey }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_compose! {
|
||||
pub fn arb_witness()(extension_id in 0..(100 as u32), mode in (0..100 as u32), payload in vec(any::<u8>(), 32..256)) -> tze::Witness {
|
||||
tze::Witness { extension_id, mode, payload }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_compose! {
|
||||
pub fn arb_tzein()(prevout in arb_outpoint(), witness in arb_witness()) -> TzeIn {
|
||||
TzeIn { prevout, witness }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_compose! {
|
||||
pub fn arb_precondition()(extension_id in 0..(100 as u32), mode in (0..100 as u32), payload in vec(any::<u8>(), 32..256)) -> tze::Precondition {
|
||||
tze::Precondition { extension_id, mode, payload }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_compose! {
|
||||
fn arb_tzeout()(value in arb_amount(), precondition in arb_precondition()) -> TzeOut {
|
||||
TzeOut { value, precondition }
|
||||
}
|
||||
}
|
||||
|
||||
fn tx_versions(branch_id: BranchId) -> impl Strategy<Value = (u32, u32)> {
|
||||
match branch_id {
|
||||
BranchId::Sprout => (1..(2 as u32)).prop_map(|i| (i, 0)).boxed(),
|
||||
BranchId::Overwinter => {
|
||||
Just((OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID)).boxed()
|
||||
}
|
||||
#[cfg(feature = "zfuture")]
|
||||
BranchId::ZFuture => Just((ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID)).boxed(),
|
||||
_otherwise => Just((SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID)).boxed(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_compose! {
|
||||
pub fn arb_txdata(branch_id: BranchId)(
|
||||
(version, version_group_id) in tx_versions(branch_id),
|
||||
vin in vec(arb_txin(), 0..10),
|
||||
vout in vec(arb_txout(), 0..10),
|
||||
tze_inputs in vec(arb_tzein(), 0..10),
|
||||
tze_outputs in vec(arb_tzeout(), 0..10),
|
||||
lock_time in any::<u32>(),
|
||||
expiry_height in any::<u32>(),
|
||||
value_balance in arb_amount(),
|
||||
) -> TransactionData {
|
||||
TransactionData {
|
||||
overwintered: branch_id != BranchId::Sprout,
|
||||
version,
|
||||
version_group_id,
|
||||
vin, vout,
|
||||
tze_inputs: if branch_id == BranchId::ZFuture { tze_inputs } else { vec![] },
|
||||
tze_outputs: if branch_id == BranchId::ZFuture { tze_outputs } else { vec![] },
|
||||
lock_time,
|
||||
expiry_height: expiry_height.into(),
|
||||
value_balance,
|
||||
shielded_spends: vec![], //FIXME
|
||||
shielded_outputs: vec![], //FIXME
|
||||
joinsplits: vec![], //FIXME
|
||||
joinsplit_pubkey: None, //FIXME
|
||||
joinsplit_sig: None, //FIXME
|
||||
binding_sig: None, //FIXME
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
prop_compose! {
|
||||
pub fn arb_tx(branch_id: BranchId)(tx_data in arb_txdata(branch_id)) -> Transaction {
|
||||
Transaction::from_data(tx_data).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#[cfg(feature = "zfuture")]
|
||||
use std::convert::TryInto;
|
||||
|
||||
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
|
||||
|
@ -5,19 +6,24 @@ use byteorder::{LittleEndian, WriteBytesExt};
|
|||
use ff::PrimeField;
|
||||
use group::GroupEncoding;
|
||||
|
||||
use crate::{consensus, legacy::Script};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use crate::{
|
||||
consensus,
|
||||
extensions::transparent::Precondition,
|
||||
legacy::Script,
|
||||
serialize::{CompactSize, Vector},
|
||||
};
|
||||
|
||||
use super::{
|
||||
components::{
|
||||
Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut, TzeIn, TzeOut,
|
||||
},
|
||||
components::{Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut},
|
||||
Transaction, TransactionData, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION,
|
||||
SAPLING_VERSION_GROUP_ID, ZFUTURE_VERSION_GROUP_ID,
|
||||
SAPLING_VERSION_GROUP_ID,
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use super::{
|
||||
components::{TzeIn, TzeOut},
|
||||
ZFUTURE_VERSION_GROUP_ID,
|
||||
};
|
||||
|
||||
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash";
|
||||
|
@ -27,10 +33,15 @@ const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashOutputsHash";
|
|||
const ZCASH_JOINSPLITS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashJSplitsHash";
|
||||
const ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSSpendsHash";
|
||||
const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSOutputHash";
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
const ZCASH_TZE_INPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"Zcash_TzeInsHash";
|
||||
#[cfg(feature = "zfuture")]
|
||||
const ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashTzeOutsHash";
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
const ZCASH_TZE_SIGNED_INPUT_TAG: &[u8; 1] = &[0x00];
|
||||
#[cfg(feature = "zfuture")]
|
||||
const ZCASH_TRANSPARENT_SIGNED_INPUT_TAG: &[u8; 1] = &[0x01];
|
||||
|
||||
pub const SIGHASH_ALL: u32 = 1;
|
||||
|
@ -56,11 +67,14 @@ macro_rules! update_hash {
|
|||
};
|
||||
}
|
||||
|
||||
/// This is a private enum; when `cfg(feature = "zfuture")` is not
|
||||
/// enabled, SigHashVersion::ZFuture is not constructable.
|
||||
#[derive(PartialEq)]
|
||||
enum SigHashVersion {
|
||||
Sprout,
|
||||
Overwinter,
|
||||
Sapling,
|
||||
#[cfg(feature = "zfuture")]
|
||||
ZFuture,
|
||||
}
|
||||
|
||||
|
@ -70,6 +84,7 @@ impl SigHashVersion {
|
|||
match tx.version_group_id {
|
||||
OVERWINTER_VERSION_GROUP_ID => SigHashVersion::Overwinter,
|
||||
SAPLING_VERSION_GROUP_ID => SigHashVersion::Sapling,
|
||||
#[cfg(feature = "zfuture")]
|
||||
ZFUTURE_VERSION_GROUP_ID => SigHashVersion::ZFuture,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
@ -77,6 +92,21 @@ impl SigHashVersion {
|
|||
SigHashVersion::Sprout
|
||||
}
|
||||
}
|
||||
|
||||
fn has_sapling_components(&self) -> bool {
|
||||
match self {
|
||||
SigHashVersion::Sprout | SigHashVersion::Overwinter => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn has_tze_components(&self) -> bool {
|
||||
match self {
|
||||
SigHashVersion::ZFuture => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prevout_hash(vin: &[TxIn]) -> Blake2bHash {
|
||||
|
@ -172,6 +202,7 @@ fn shielded_outputs_hash(shielded_outputs: &[OutputDescription]) -> Blake2bHash
|
|||
.hash(&data)
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn tze_inputs_hash(tze_inputs: &[TzeIn]) -> Blake2bHash {
|
||||
let mut data = vec![];
|
||||
for tzein in tze_inputs {
|
||||
|
@ -183,6 +214,7 @@ fn tze_inputs_hash(tze_inputs: &[TzeIn]) -> Blake2bHash {
|
|||
.hash(&data)
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn tze_outputs_hash(tze_outputs: &[TzeOut]) -> Blake2bHash {
|
||||
let mut data = vec![];
|
||||
for tzeout in tze_outputs {
|
||||
|
@ -201,6 +233,7 @@ pub enum SignableInput<'a> {
|
|||
script_code: &'a Script,
|
||||
value: Amount,
|
||||
},
|
||||
#[cfg(feature = "zfuture")]
|
||||
Tze {
|
||||
index: usize,
|
||||
precondition: &'a Precondition,
|
||||
|
@ -217,6 +250,7 @@ impl<'a> SignableInput<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
pub fn tze(index: usize, precondition: &'a Precondition, value: Amount) -> Self {
|
||||
SignableInput::Tze {
|
||||
index,
|
||||
|
@ -234,7 +268,14 @@ pub fn signature_hash_data<'a>(
|
|||
) -> Vec<u8> {
|
||||
let sigversion = SigHashVersion::from_tx(tx);
|
||||
match sigversion {
|
||||
SigHashVersion::Overwinter | SigHashVersion::Sapling | SigHashVersion::ZFuture => {
|
||||
SigHashVersion::Sprout => unimplemented!(),
|
||||
|
||||
// ZIP-243 is implemented as a patch to ZIP-143; ZFuture is temporarily added
|
||||
// as an additional patch to ZIP-243 but that match will likely be removed
|
||||
// due to the need for transaction malleability fixes for TZE deployment.
|
||||
//
|
||||
// SigHashVersion::Overwinter | SigHashVersion::Sapling | SigHashVersion::ZFuture => {
|
||||
_ => {
|
||||
let mut personal = [0; 16];
|
||||
(&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
|
||||
(&mut personal[12..])
|
||||
|
@ -276,7 +317,8 @@ pub fn signature_hash_data<'a>(
|
|||
} else {
|
||||
h.update(&[0; 32]);
|
||||
};
|
||||
if sigversion == SigHashVersion::ZFuture {
|
||||
#[cfg(feature = "zfuture")]
|
||||
if sigversion.has_tze_components() {
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.tze_inputs.is_empty(),
|
||||
|
@ -293,7 +335,7 @@ pub fn signature_hash_data<'a>(
|
|||
!tx.joinsplits.is_empty(),
|
||||
joinsplits_hash(tx.version, &tx.joinsplits, &tx.joinsplit_pubkey.unwrap())
|
||||
);
|
||||
if sigversion == SigHashVersion::Sapling || sigversion == SigHashVersion::ZFuture {
|
||||
if sigversion.has_sapling_components() {
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.shielded_spends.is_empty(),
|
||||
|
@ -307,7 +349,7 @@ pub fn signature_hash_data<'a>(
|
|||
}
|
||||
update_u32!(h, tx.lock_time, tmp);
|
||||
update_u32!(h, tx.expiry_height.into(), tmp);
|
||||
if sigversion == SigHashVersion::Sapling || sigversion == SigHashVersion::ZFuture {
|
||||
if sigversion.has_sapling_components() {
|
||||
h.update(&tx.value_balance.to_i64_le_bytes());
|
||||
}
|
||||
update_u32!(h, hash_type, tmp);
|
||||
|
@ -318,7 +360,8 @@ pub fn signature_hash_data<'a>(
|
|||
script_code,
|
||||
value,
|
||||
} => {
|
||||
let mut data = if sigversion == SigHashVersion::ZFuture {
|
||||
#[cfg(feature = "zfuture")]
|
||||
let mut data = if sigversion.has_tze_components() {
|
||||
// domain separation here is to avoid collision attacks
|
||||
// between transparent and TZE inputs.
|
||||
ZCASH_TRANSPARENT_SIGNED_INPUT_TAG.to_vec()
|
||||
|
@ -326,6 +369,9 @@ pub fn signature_hash_data<'a>(
|
|||
vec![]
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "zfuture"))]
|
||||
let mut data = vec![];
|
||||
|
||||
tx.vin[index].prevout.write(&mut data).unwrap();
|
||||
script_code.write(&mut data).unwrap();
|
||||
data.extend_from_slice(&value.to_i64_le_bytes());
|
||||
|
@ -335,11 +381,12 @@ pub fn signature_hash_data<'a>(
|
|||
h.update(&data);
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
SignableInput::Tze {
|
||||
index,
|
||||
precondition,
|
||||
value,
|
||||
} if sigversion == SigHashVersion::ZFuture => {
|
||||
} if sigversion.has_tze_components() => {
|
||||
// domain separation here is to avoid collision attacks
|
||||
// between transparent and TZE inputs.
|
||||
let mut data = ZCASH_TZE_SIGNED_INPUT_TAG.to_vec();
|
||||
|
@ -353,6 +400,7 @@ pub fn signature_hash_data<'a>(
|
|||
h.update(&data);
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
SignableInput::Tze { .. } => {
|
||||
panic!("A request has been made to sign a TZE input, but the signature hash version is not ZFuture");
|
||||
}
|
||||
|
@ -362,7 +410,6 @@ pub fn signature_hash_data<'a>(
|
|||
|
||||
h.finalize().as_ref().to_vec()
|
||||
}
|
||||
SigHashVersion::Sprout => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,133 +1,22 @@
|
|||
use ff::Field;
|
||||
use rand_core::OsRng;
|
||||
|
||||
use proptest::collection::vec;
|
||||
#[cfg(all(feature = "test-dependencies", feature = "zfuture"))]
|
||||
use proptest::prelude::*;
|
||||
use proptest::sample::select;
|
||||
|
||||
use crate::{
|
||||
consensus::BranchId, constants::SPENDING_KEY_GENERATOR, extensions::transparent as tze,
|
||||
legacy::Script, redjubjub::PrivateKey,
|
||||
};
|
||||
use crate::{constants::SPENDING_KEY_GENERATOR, redjubjub::PrivateKey};
|
||||
|
||||
#[cfg(all(feature = "test-dependencies", feature = "zfuture"))]
|
||||
use crate::consensus::BranchId;
|
||||
|
||||
use super::{
|
||||
components::amount::MAX_MONEY,
|
||||
components::{Amount, OutPoint, TxIn, TxOut, TzeIn, TzeOut},
|
||||
components::Amount,
|
||||
sighash::{signature_hash, SignableInput},
|
||||
Transaction, TransactionData, OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID,
|
||||
SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID, ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID,
|
||||
Transaction, TransactionData,
|
||||
};
|
||||
|
||||
prop_compose! {
|
||||
fn arb_outpoint()(hash in prop::array::uniform32(1u8..), n in 1..(100 as u32)) -> OutPoint {
|
||||
OutPoint::new(hash, n)
|
||||
}
|
||||
}
|
||||
|
||||
const VALID_OPCODES: [u8; 8] = [
|
||||
0x00, // OP_FALSE,
|
||||
0x51, // OP_1,
|
||||
0x52, // OP_2,
|
||||
0x53, // OP_3,
|
||||
0xac, // OP_CHECKSIG,
|
||||
0x63, // OP_IF,
|
||||
0x65, // OP_VERIF,
|
||||
0x6a, // OP_RETURN,
|
||||
];
|
||||
|
||||
prop_compose! {
|
||||
fn arb_script()(v in vec(select(&VALID_OPCODES[..]), 1..256)) -> Script {
|
||||
Script(v)
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arb_txin()(prevout in arb_outpoint(), script_sig in arb_script(), sequence in any::<u32>()) -> TxIn {
|
||||
TxIn { prevout, script_sig, sequence }
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arb_amount()(value in 0..MAX_MONEY) -> Amount {
|
||||
Amount::from_i64(value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arb_txout()(value in arb_amount(), script_pubkey in arb_script()) -> TxOut {
|
||||
TxOut { value, script_pubkey }
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arb_witness()(extension_id in 0..(100 as u32), mode in (0..100 as u32), payload in vec(any::<u8>(), 32..256)) -> tze::Witness {
|
||||
tze::Witness { extension_id, mode, payload }
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arb_tzein()(prevout in arb_outpoint(), witness in arb_witness()) -> TzeIn {
|
||||
TzeIn { prevout, witness }
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arb_precondition()(extension_id in 0..(100 as u32), mode in (0..100 as u32), payload in vec(any::<u8>(), 32..256)) -> tze::Precondition {
|
||||
tze::Precondition { extension_id, mode, payload }
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arb_tzeout()(value in arb_amount(), precondition in arb_precondition()) -> TzeOut {
|
||||
TzeOut { value, precondition }
|
||||
}
|
||||
}
|
||||
|
||||
fn tx_versions(branch_id: BranchId) -> impl Strategy<Value = (u32, u32)> {
|
||||
match branch_id {
|
||||
BranchId::Sprout => (1..(2 as u32)).prop_map(|i| (i, 0)).boxed(),
|
||||
BranchId::Overwinter => Just((OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID)).boxed(),
|
||||
BranchId::ZFuture => Just((ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID)).boxed(),
|
||||
_otherwise => Just((SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID)).boxed(),
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arb_txdata(branch_id: BranchId)(
|
||||
(version, version_group_id) in tx_versions(branch_id),
|
||||
vin in vec(arb_txin(), 0..10),
|
||||
vout in vec(arb_txout(), 0..10),
|
||||
tze_inputs in vec(arb_tzein(), 0..10),
|
||||
tze_outputs in vec(arb_tzeout(), 0..10),
|
||||
lock_time in any::<u32>(),
|
||||
expiry_height in any::<u32>(),
|
||||
value_balance in arb_amount(),
|
||||
) -> TransactionData {
|
||||
TransactionData {
|
||||
overwintered: branch_id != BranchId::Sprout,
|
||||
version,
|
||||
version_group_id,
|
||||
vin, vout,
|
||||
tze_inputs: if branch_id == BranchId::ZFuture { tze_inputs } else { vec![] },
|
||||
tze_outputs: if branch_id == BranchId::ZFuture { tze_outputs } else { vec![] },
|
||||
lock_time,
|
||||
expiry_height: expiry_height.into(),
|
||||
value_balance,
|
||||
shielded_spends: vec![], //FIXME
|
||||
shielded_outputs: vec![], //FIXME
|
||||
joinsplits: vec![], //FIXME
|
||||
joinsplit_pubkey: None, //FIXME
|
||||
joinsplit_sig: None, //FIXME
|
||||
binding_sig: None, //FIXME
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arb_tx(branch_id: BranchId)(tx_data in arb_txdata(branch_id)) -> Transaction {
|
||||
Transaction::from_data(tx_data).unwrap()
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature = "test-dependencies", feature = "zfuture"))]
|
||||
use super::testing::arb_tx;
|
||||
|
||||
#[test]
|
||||
fn tx_read_write() {
|
||||
|
@ -186,6 +75,7 @@ fn tx_write_rejects_unexpected_binding_sig() {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "zfuture", feature = "test-dependencies"))]
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_tze_roundtrip(tx in arb_tx(BranchId::ZFuture)) {
|
||||
|
@ -207,6 +97,7 @@ proptest! {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn test_tze_tx_parse() {
|
||||
let txn_bytes = vec![
|
||||
0xFF, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x52, 0x52, 0x52, 0x52,
|
||||
|
|
|
@ -8,6 +8,8 @@ use crate::{
|
|||
use ff::Field;
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
pub(crate) mod sha256d;
|
||||
|
||||
pub fn hash_to_scalar(persona: &[u8], a: &[u8], b: &[u8]) -> jubjub::Fr {
|
||||
let mut hasher = Params::new().hash_length(64).personal(persona).to_state();
|
||||
hasher.update(a);
|
||||
|
@ -20,6 +22,14 @@ pub fn generate_random_rseed<P: consensus::Parameters, R: RngCore + CryptoRng>(
|
|||
params: &P,
|
||||
height: BlockHeight,
|
||||
rng: &mut R,
|
||||
) -> Rseed {
|
||||
generate_random_rseed_internal(params, height, rng)
|
||||
}
|
||||
|
||||
pub(crate) fn generate_random_rseed_internal<P: consensus::Parameters, R: RngCore>(
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
rng: &mut R,
|
||||
) -> Rseed {
|
||||
if params.is_nu_active(NetworkUpgrade::Canopy, height) {
|
||||
let mut buffer = [0u8; 32];
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
use sha2::{digest::Output, Digest, Sha256};
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
/// Abstraction over a reader which SHA-256d-hashes the data being read.
|
||||
pub struct HashReader<R: Read> {
|
||||
reader: R,
|
||||
hasher: Sha256,
|
||||
}
|
||||
|
||||
impl<R: Read> HashReader<R> {
|
||||
/// Construct a new `HashReader` given an existing `reader` by value.
|
||||
pub fn new(reader: R) -> Self {
|
||||
HashReader {
|
||||
reader,
|
||||
hasher: Sha256::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroy this reader and return the hash of what was read.
|
||||
pub fn into_hash(self) -> Output<Sha256> {
|
||||
Sha256::digest(&self.hasher.finalize())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Read for HashReader<R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let bytes = self.reader.read(buf)?;
|
||||
|
||||
if bytes > 0 {
|
||||
self.hasher.update(&buf[0..bytes]);
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstraction over a writer which SHA-256d-hashes the data being read.
|
||||
pub struct HashWriter {
|
||||
hasher: Sha256,
|
||||
}
|
||||
|
||||
impl Default for HashWriter {
|
||||
fn default() -> Self {
|
||||
HashWriter {
|
||||
hasher: Sha256::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HashWriter {
|
||||
/// Destroy this writer and return the hash of what was written.
|
||||
pub fn into_hash(self) -> Output<Sha256> {
|
||||
Sha256::digest(&self.hasher.finalize())
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for HashWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.hasher.update(&buf);
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue