Merge remote-tracking branch 'upstream/master' into data_access_api

This commit is contained in:
Kris Nuttycombe 2021-01-12 21:09:40 -07:00
commit db9eb29eba
17 changed files with 638 additions and 201 deletions

View File

@ -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"

View File

@ -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
)
);
}

View File

@ -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"

View File

@ -47,6 +47,7 @@ rand_xorshift = "0.2"
[features]
transparent-inputs = ["ripemd160", "secp256k1"]
test-dependencies = ["proptest"]
zfuture = []
[[bench]]
name = "note_decryption"

View File

@ -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,
}
}

View File

@ -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;

View File

@ -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 {

View File

@ -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

View File

@ -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 = ();

View File

@ -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);

View File

@ -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

View File

@ -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 = {

View File

@ -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()
}
}
}

View File

@ -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!(),
}
}

View File

@ -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,

View File

@ -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];

View File

@ -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(())
}
}