Merge pull request #92 from str4d/transaction-builder

Transaction builder
This commit is contained in:
str4d 2019-08-14 01:11:12 +01:00 committed by GitHub
commit 52ea437e11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1609 additions and 156 deletions

34
Cargo.lock generated
View File

@ -185,6 +185,15 @@ dependencies = [
"generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "directories"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
@ -531,6 +540,25 @@ name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "zcash_client_backend"
version = "0.0.0"
@ -556,6 +584,7 @@ dependencies = [
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pairing 0.14.2",
"rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_os 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sapling-crypto 0.0.1",
@ -569,6 +598,7 @@ dependencies = [
"bellman 0.1.0",
"blake2b_simd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"directories 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ff 0.4.0",
"pairing 0.14.2",
"rand_os 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -600,6 +630,7 @@ dependencies = [
"checksum crypto_api_chachapoly 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9ee35dbace0831b5fe7cb9b43eb029aa14a10f594a115025d4628a2baa63ab"
"checksum digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "00a49051fef47a72c9623101b19bd71924a45cca838826caae3eaa4d00772603"
"checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c"
"checksum directories 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "72d337a64190607d4fcca2cb78982c5dd57f4916e19696b48a575fa746b6cb0f"
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
"checksum fpe 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce3371c82bfbd984f624cab093f55e7336f5a6e589f8518e1258f54f011b89ad"
"checksum futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "1a70b146671de62ec8c8ed572219ca5d594d9b06c0b364d5e67b722fc559b48c"
@ -636,3 +667,6 @@ dependencies = [
"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741"
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -63,6 +63,7 @@ use zcash_primitives::{
merkle_tree::CommitmentTreeWitness,
note_encryption::sapling_ka_agree,
sapling::{merkle_hash, spend_sig},
transaction::components::Amount,
zip32, JUBJUB,
};
use zcash_proofs::{
@ -704,6 +705,11 @@ pub extern "system" fn librustzcash_sapling_final_check(
binding_sig: *const [c_uchar; 64],
sighash_value: *const [c_uchar; 32],
) -> bool {
let value_balance = match Amount::from_i64(value_balance) {
Ok(vb) => vb,
Err(()) => return false,
};
// Deserialize the signature
let binding_sig = match Signature::read(&(unsafe { &*binding_sig })[..]) {
Ok(sig) => sig,
@ -1002,8 +1008,11 @@ pub extern "system" fn librustzcash_sapling_spend_sig(
Err(_) => return false,
};
// Initialize secure RNG
let mut rng = OsRng;
// Do the signing
let sig = spend_sig(ask, ar, unsafe { &*sighash }, &JUBJUB);
let sig = spend_sig(ask, ar, unsafe { &*sighash }, &mut rng, &JUBJUB);
// Write out the signature
sig.write(&mut (unsafe { &mut *result })[..])
@ -1019,6 +1028,11 @@ pub extern "system" fn librustzcash_sapling_binding_sig(
sighash: *const [c_uchar; 32],
result: *mut [c_uchar; 64],
) -> bool {
let value_balance = match Amount::from_i64(value_balance) {
Ok(vb) => vb,
Err(()) => return false,
};
// Sign
let sig = match unsafe { &*ctx }.binding_sig(value_balance, unsafe { &*sighash }, &JUBJUB) {
Ok(s) => s,

View File

@ -15,6 +15,7 @@ fpe = "0.1"
hex = "0.3"
lazy_static = "1"
pairing = { path = "../pairing" }
rand = "0.7"
rand_core = "0.5"
rand_os = "0.2"
sapling-crypto = { path = "../sapling-crypto" }

View File

@ -0,0 +1,168 @@
//! Support for legacy transparent addresses and scripts.
use byteorder::{ReadBytesExt, WriteBytesExt};
use std::io::{self, Read, Write};
use std::ops::Shl;
use crate::serialize::Vector;
/// Minimal subset of script opcodes.
enum OpCode {
// push value
PushData1 = 0x4c,
PushData2 = 0x4d,
PushData4 = 0x4e,
// stack ops
Dup = 0x76,
// bit logic
Equal = 0x87,
EqualVerify = 0x88,
// crypto
Hash160 = 0xa9,
CheckSig = 0xac,
}
/// A serialized script, used inside transparent inputs and outputs of a transaction.
#[derive(Debug, Default)]
pub struct Script(pub Vec<u8>);
impl Script {
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let script = Vector::read(&mut reader, |r| r.read_u8())?;
Ok(Script(script))
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
Vector::write(&mut writer, &self.0, |w, e| w.write_u8(*e))
}
}
impl Shl<OpCode> for Script {
type Output = Self;
fn shl(mut self, rhs: OpCode) -> Self {
self.0.push(rhs as u8);
self
}
}
impl Shl<&[u8]> for Script {
type Output = Self;
fn shl(mut self, data: &[u8]) -> Self {
if data.len() < OpCode::PushData1 as usize {
self.0.push(data.len() as u8);
} else if data.len() <= 0xff {
self.0.push(OpCode::PushData1 as u8);
self.0.push(data.len() as u8);
} else if data.len() <= 0xffff {
self.0.push(OpCode::PushData2 as u8);
self.0.extend(&(data.len() as u16).to_le_bytes());
} else {
self.0.push(OpCode::PushData4 as u8);
self.0.extend(&(data.len() as u32).to_le_bytes());
}
self.0.extend(data);
self
}
}
/// A transparent address corresponding to either a public key or a `Script`.
#[derive(Debug, PartialEq)]
pub enum TransparentAddress {
PublicKey([u8; 20]),
Script([u8; 20]),
}
impl TransparentAddress {
/// Generate the `scriptPubKey` corresponding to this address.
pub fn script(&self) -> Script {
match self {
TransparentAddress::PublicKey(key_id) => {
// P2PKH script
Script::default()
<< OpCode::Dup
<< OpCode::Hash160
<< &key_id[..]
<< OpCode::EqualVerify
<< OpCode::CheckSig
}
TransparentAddress::Script(script_id) => {
// P2SH script
Script::default() << OpCode::Hash160 << &script_id[..] << OpCode::Equal
}
}
}
}
#[cfg(test)]
mod tests {
use super::{OpCode, Script, TransparentAddress};
#[test]
fn script_opcode() {
{
let script = Script::default() << OpCode::PushData1;
assert_eq!(&script.0, &[OpCode::PushData1 as u8]);
}
}
#[test]
fn script_pushdata() {
{
let script = Script::default() << &[1, 2, 3, 4][..];
assert_eq!(&script.0, &[4, 1, 2, 3, 4]);
}
{
let short_data = vec![2; 100];
let script = Script::default() << &short_data[..];
assert_eq!(script.0[0], OpCode::PushData1 as u8);
assert_eq!(script.0[1] as usize, 100);
assert_eq!(&script.0[2..], &short_data[..]);
}
{
let medium_data = vec![7; 1024];
let script = Script::default() << &medium_data[..];
assert_eq!(script.0[0], OpCode::PushData2 as u8);
assert_eq!(&script.0[1..3], &[0x00, 0x04][..]);
assert_eq!(&script.0[3..], &medium_data[..]);
}
{
let long_data = vec![42; 1_000_000];
let script = Script::default() << &long_data[..];
assert_eq!(script.0[0], OpCode::PushData4 as u8);
assert_eq!(&script.0[1..5], &[0x40, 0x42, 0x0f, 0x00][..]);
assert_eq!(&script.0[5..], &long_data[..]);
}
}
#[test]
fn p2pkh() {
let addr = TransparentAddress::PublicKey([4; 20]);
assert_eq!(
&addr.script().0,
&[
0x76, 0xa9, 0x14, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x88, 0xac,
]
)
}
#[test]
fn p2sh() {
let addr = TransparentAddress::Script([7; 20]);
assert_eq!(
&addr.script().0,
&[
0xa9, 0x14, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x87,
]
)
}
}

View File

@ -9,6 +9,7 @@ extern crate ff;
extern crate fpe;
extern crate hex;
extern crate pairing;
extern crate rand;
extern crate rand_core;
extern crate rand_os;
extern crate sapling_crypto;
@ -18,8 +19,10 @@ use sapling_crypto::jubjub::JubjubBls12;
pub mod block;
pub mod keys;
pub mod legacy;
pub mod merkle_tree;
pub mod note_encryption;
pub mod prover;
pub mod sapling;
mod serialize;
pub mod transaction;

View File

@ -420,7 +420,7 @@ impl<Node: Hashable> IncrementalWitness<Node> {
/// A witness to a path from a position in a particular commitment tree to the root of
/// that tree.
#[derive(Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub struct CommitmentTreeWitness<Node: Hashable> {
pub auth_path: Vec<Option<(Node, bool)>>,
pub position: u64,

View File

@ -5,8 +5,7 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf};
use ff::{PrimeField, PrimeFieldRepr};
use pairing::bls12_381::{Bls12, Fr};
use rand_core::RngCore;
use rand_os::OsRng;
use rand_core::{CryptoRng, RngCore};
use sapling_crypto::{
jubjub::{
edwards,
@ -135,9 +134,8 @@ impl Memo {
}
}
fn generate_esk() -> Fs {
pub fn generate_esk<R: RngCore + CryptoRng>(rng: &mut R) -> Fs {
// create random 64 byte buffer
let mut rng = OsRng;
let mut buffer = [0u8; 64];
rng.fill_bytes(&mut buffer);
@ -247,7 +245,7 @@ fn prf_ock(
/// let note = to.create_note(value, rcv, &JUBJUB).unwrap();
/// let cmu = note.cm(&JUBJUB);
///
/// let enc = SaplingNoteEncryption::new(ovk, note, to, Memo::default());
/// let enc = SaplingNoteEncryption::new(ovk, note, to, Memo::default(), &mut rng);
/// let encCiphertext = enc.encrypt_note_plaintext();
/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.cm(&JUBJUB).into(), &cmu);
/// ```
@ -262,13 +260,14 @@ pub struct SaplingNoteEncryption {
impl SaplingNoteEncryption {
/// Creates a new encryption context for the given note.
pub fn new(
pub fn new<R: RngCore + CryptoRng>(
ovk: OutgoingViewingKey,
note: Note<Bls12>,
to: PaymentAddress<Bls12>,
memo: Memo,
rng: &mut R,
) -> SaplingNoteEncryption {
let esk = generate_esk();
let esk = generate_esk(rng);
let epk = note.g_d.mul(esk, &JUBJUB);
SaplingNoteEncryption {
@ -561,7 +560,7 @@ mod tests {
use crypto_api_chachapoly::ChachaPolyIetf;
use ff::{Field, PrimeField, PrimeFieldRepr};
use pairing::bls12_381::{Bls12, Fr, FrRepr};
use rand_core::RngCore;
use rand_core::{CryptoRng, RngCore};
use rand_os::OsRng;
use sapling_crypto::{
jubjub::{
@ -694,7 +693,7 @@ mod tests {
assert_eq!(Memo::default().to_utf8(), None);
}
fn random_enc_ciphertext<R: RngCore>(
fn random_enc_ciphertext<R: RngCore + CryptoRng>(
mut rng: &mut R,
) -> (
OutgoingViewingKey,
@ -724,7 +723,7 @@ mod tests {
let cmu = note.cm(&JUBJUB);
let ovk = OutgoingViewingKey([0; 32]);
let ne = SaplingNoteEncryption::new(ovk, note, pa, Memo([0; 512]));
let ne = SaplingNoteEncryption::new(ovk, note, pa, Memo([0; 512]), rng);
let epk = ne.epk();
let enc_ciphertext = ne.encrypt_note_plaintext();
let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu);
@ -1371,7 +1370,7 @@ mod tests {
// Test encryption
//
let mut ne = SaplingNoteEncryption::new(ovk, note, to, Memo(tv.memo));
let mut ne = SaplingNoteEncryption::new(ovk, note, to, Memo(tv.memo), &mut OsRng);
// Swap in the ephemeral keypair from the test vectors
ne.esk = esk;
ne.epk = epk;

View File

@ -0,0 +1,166 @@
//! Abstractions over the proving system and parameters.
use pairing::bls12_381::{Bls12, Fr};
use sapling_crypto::{
jubjub::{edwards, fs::Fs, Unknown},
primitives::{Diversifier, PaymentAddress, ProofGenerationKey},
redjubjub::{PublicKey, Signature},
};
use crate::{
merkle_tree::CommitmentTreeWitness,
sapling::Node,
transaction::components::{Amount, GROTH_PROOF_SIZE},
};
/// Interface for creating zero-knowledge proofs for shielded transactions.
pub trait TxProver {
/// Type for persisting any necessary context across multiple Sapling proofs.
type SaplingProvingContext;
/// Instantiate a new Sapling proving context.
fn new_sapling_proving_context(&self) -> Self::SaplingProvingContext;
/// Create the value commitment, re-randomized key, and proof for a Sapling
/// [`SpendDescription`], while accumulating its value commitment randomness inside
/// the context for later use.
///
/// [`SpendDescription`]: crate::transaction::components::SpendDescription
fn spend_proof(
&self,
ctx: &mut Self::SaplingProvingContext,
proof_generation_key: ProofGenerationKey<Bls12>,
diversifier: Diversifier,
rcm: Fs,
ar: Fs,
value: u64,
anchor: Fr,
witness: CommitmentTreeWitness<Node>,
) -> Result<
(
[u8; GROTH_PROOF_SIZE],
edwards::Point<Bls12, Unknown>,
PublicKey<Bls12>,
),
(),
>;
/// Create the value commitment and proof for a Sapling [`OutputDescription`],
/// while accumulating its value commitment randomness inside the context for later
/// use.
///
/// [`OutputDescription`]: crate::transaction::components::OutputDescription
fn output_proof(
&self,
ctx: &mut Self::SaplingProvingContext,
esk: Fs,
payment_address: PaymentAddress<Bls12>,
rcm: Fs,
value: u64,
) -> ([u8; GROTH_PROOF_SIZE], edwards::Point<Bls12, Unknown>);
/// Create the `bindingSig` for a Sapling transaction. All calls to
/// [`TxProver::spend_proof`] and [`TxProver::output_proof`] must be completed before
/// calling this function.
fn binding_sig(
&self,
ctx: &mut Self::SaplingProvingContext,
value_balance: Amount,
sighash: &[u8; 32],
) -> Result<Signature, ()>;
}
#[cfg(test)]
pub(crate) mod mock {
use ff::Field;
use pairing::bls12_381::{Bls12, Fr};
use rand_os::OsRng;
use sapling_crypto::{
jubjub::{edwards, fs::Fs, FixedGenerators, Unknown},
primitives::{Diversifier, PaymentAddress, ProofGenerationKey, ValueCommitment},
redjubjub::{PublicKey, Signature},
};
use crate::{
merkle_tree::CommitmentTreeWitness,
sapling::Node,
transaction::components::{Amount, GROTH_PROOF_SIZE},
JUBJUB,
};
use super::TxProver;
pub(crate) struct MockTxProver;
#[cfg(test)]
impl TxProver for MockTxProver {
type SaplingProvingContext = ();
fn new_sapling_proving_context(&self) -> Self::SaplingProvingContext {}
fn spend_proof(
&self,
_ctx: &mut Self::SaplingProvingContext,
proof_generation_key: ProofGenerationKey<Bls12>,
_diversifier: Diversifier,
_rcm: Fs,
ar: Fs,
value: u64,
_anchor: Fr,
_witness: CommitmentTreeWitness<Node>,
) -> Result<
(
[u8; GROTH_PROOF_SIZE],
edwards::Point<Bls12, Unknown>,
PublicKey<Bls12>,
),
(),
> {
let mut rng = OsRng;
let cv = ValueCommitment::<Bls12> {
value,
randomness: Fs::random(&mut rng),
}
.cm(&JUBJUB)
.into();
let rk = PublicKey::<Bls12>(proof_generation_key.ak.clone().into()).randomize(
ar,
FixedGenerators::SpendingKeyGenerator,
&JUBJUB,
);
Ok(([0u8; GROTH_PROOF_SIZE], cv, rk))
}
fn output_proof(
&self,
_ctx: &mut Self::SaplingProvingContext,
_esk: Fs,
_payment_address: PaymentAddress<Bls12>,
_rcm: Fs,
value: u64,
) -> ([u8; GROTH_PROOF_SIZE], edwards::Point<Bls12, Unknown>) {
let mut rng = OsRng;
let cv = ValueCommitment::<Bls12> {
value,
randomness: Fs::random(&mut rng),
}
.cm(&JUBJUB)
.into();
([0u8; GROTH_PROOF_SIZE], cv)
}
fn binding_sig(
&self,
_ctx: &mut Self::SaplingProvingContext,
_value_balance: Amount,
_sighash: &[u8; 32],
) -> Result<Signature, ()> {
Err(())
}
}
}

View File

@ -2,7 +2,7 @@
use ff::{BitIterator, PrimeField, PrimeFieldRepr};
use pairing::bls12_381::{Bls12, Fr, FrRepr};
use rand_os::OsRng;
use rand_core::{CryptoRng, RngCore};
use sapling_crypto::{
jubjub::{fs::Fs, FixedGenerators, JubjubBls12},
pedersen_hash::{pedersen_hash, Personalization},
@ -106,15 +106,13 @@ lazy_static! {
}
/// Create the spendAuthSig for a Sapling SpendDescription.
pub fn spend_sig(
pub fn spend_sig<R: RngCore + CryptoRng>(
ask: PrivateKey<Bls12>,
ar: Fs,
sighash: &[u8; 32],
rng: &mut R,
params: &JubjubBls12,
) -> Signature {
// Initialize secure RNG
let mut rng = OsRng;
// We compute `rsk`...
let rsk = ask.randomize(ar);
@ -130,7 +128,7 @@ pub fn spend_sig(
// Do the signing
rsk.sign(
&data_to_be_signed,
&mut rng,
rng,
FixedGenerators::SpendingKeyGenerator,
params,
)

View File

@ -0,0 +1,702 @@
//! Structs for building transactions.
use ff::Field;
use pairing::bls12_381::{Bls12, Fr};
use rand::{rngs::OsRng, seq::SliceRandom, CryptoRng, RngCore};
use sapling_crypto::{
jubjub::fs::Fs,
primitives::{Diversifier, Note, PaymentAddress},
redjubjub::PrivateKey,
};
use zip32::ExtendedSpendingKey;
use crate::{
keys::OutgoingViewingKey,
legacy::TransparentAddress,
merkle_tree::{CommitmentTreeWitness, IncrementalWitness},
note_encryption::{generate_esk, Memo, SaplingNoteEncryption},
prover::TxProver,
sapling::{spend_sig, Node},
transaction::{
components::{amount::DEFAULT_FEE, Amount, OutputDescription, SpendDescription, TxOut},
signature_hash_data, Transaction, TransactionData, SIGHASH_ALL,
},
JUBJUB,
};
const DEFAULT_TX_EXPIRY_DELTA: u32 = 20;
/// If there are any shielded inputs, always have at least two shielded outputs, padding
/// with dummy outputs if necessary. See https://github.com/zcash/zcash/issues/3615
const MIN_SHIELDED_OUTPUTS: usize = 2;
#[derive(Debug, PartialEq)]
pub enum Error {
AnchorMismatch,
BindingSig,
ChangeIsNegative(Amount),
InvalidAddress,
InvalidAmount,
InvalidWitness,
NoChangeAddress,
SpendProof,
}
struct SpendDescriptionInfo {
extsk: ExtendedSpendingKey,
diversifier: Diversifier,
note: Note<Bls12>,
alpha: Fs,
witness: CommitmentTreeWitness<Node>,
}
pub struct SaplingOutput {
ovk: OutgoingViewingKey,
to: PaymentAddress<Bls12>,
note: Note<Bls12>,
memo: Memo,
}
impl SaplingOutput {
pub fn new<R: RngCore + CryptoRng>(
rng: &mut R,
ovk: OutgoingViewingKey,
to: PaymentAddress<Bls12>,
value: Amount,
memo: Option<Memo>,
) -> Result<Self, Error> {
let g_d = match to.g_d(&JUBJUB) {
Some(g_d) => g_d,
None => return Err(Error::InvalidAddress),
};
if value.is_negative() {
return Err(Error::InvalidAmount);
}
let rcm = Fs::random(rng);
let note = Note {
g_d,
pk_d: to.pk_d.clone(),
value: value.into(),
r: rcm,
};
Ok(SaplingOutput {
ovk,
to,
note,
memo: memo.unwrap_or_default(),
})
}
pub fn build<P: TxProver, R: RngCore + CryptoRng>(
self,
prover: &P,
ctx: &mut P::SaplingProvingContext,
rng: &mut R,
) -> OutputDescription {
let encryptor = SaplingNoteEncryption::new(
self.ovk,
self.note.clone(),
self.to.clone(),
self.memo,
rng,
);
let (zkproof, cv) = prover.output_proof(
ctx,
encryptor.esk().clone(),
self.to,
self.note.r,
self.note.value,
);
let cmu = self.note.cm(&JUBJUB);
let enc_ciphertext = encryptor.encrypt_note_plaintext();
let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu);
let ephemeral_key = encryptor.epk().clone().into();
OutputDescription {
cv,
cmu,
ephemeral_key,
enc_ciphertext,
out_ciphertext,
zkproof,
}
}
}
/// Metadata about a transaction created by a [`Builder`].
#[derive(Debug, PartialEq)]
pub struct TransactionMetadata {
spend_indices: Vec<usize>,
output_indices: Vec<usize>,
}
impl TransactionMetadata {
fn new() -> Self {
TransactionMetadata {
spend_indices: vec![],
output_indices: vec![],
}
}
/// Returns the index within the transaction of the [`SpendDescription`] corresponding
/// to the `n`-th call to [`Builder::add_sapling_spend`].
///
/// Note positions are randomized when building transactions for indistinguishability.
/// This means that the transaction consumer cannot assume that e.g. the first spend
/// they added (via the first call to [`Builder::add_sapling_spend`]) is the first
/// [`SpendDescription`] in the transaction.
pub fn spend_index(&self, n: usize) -> Option<usize> {
self.spend_indices.get(n).map(|i| *i)
}
/// Returns the index within the transaction of the [`OutputDescription`] corresponding
/// to the `n`-th call to [`Builder::add_sapling_output`].
///
/// Note positions are randomized when building transactions for indistinguishability.
/// This means that the transaction consumer cannot assume that e.g. the first output
/// they added (via the first call to [`Builder::add_sapling_output`]) is the first
/// [`OutputDescription`] in the transaction.
pub fn output_index(&self, n: usize) -> Option<usize> {
self.output_indices.get(n).map(|i| *i)
}
}
/// Generates a [`Transaction`] from its inputs and outputs.
pub struct Builder<R: RngCore + CryptoRng> {
rng: R,
mtx: TransactionData,
fee: Amount,
anchor: Option<Fr>,
spends: Vec<SpendDescriptionInfo>,
outputs: Vec<SaplingOutput>,
change_address: Option<(OutgoingViewingKey, PaymentAddress<Bls12>)>,
}
impl Builder<OsRng> {
/// Creates a new `Builder` targeted for inclusion in the block with the given height,
/// using default values for general transaction fields and the default OS random.
///
/// # 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).
pub fn new(height: u32) -> Self {
Builder::new_with_rng(height, OsRng)
}
}
impl<R: RngCore + CryptoRng> Builder<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).
pub fn new_with_rng(height: u32, rng: R) -> Builder<R> {
let mut mtx = TransactionData::new();
mtx.expiry_height = height + DEFAULT_TX_EXPIRY_DELTA;
Builder {
rng,
mtx,
fee: DEFAULT_FEE,
anchor: None,
spends: vec![],
outputs: vec![],
change_address: None,
}
}
/// Adds a Sapling note to be spent in this transaction.
///
/// Returns an error if the given witness does not have the same anchor as previous
/// witnesses, or has no path.
pub fn add_sapling_spend(
&mut self,
extsk: ExtendedSpendingKey,
diversifier: Diversifier,
note: Note<Bls12>,
witness: IncrementalWitness<Node>,
) -> Result<(), Error> {
// Consistency check: all anchors must equal the first one
if let Some(anchor) = self.anchor {
let witness_root: Fr = witness.root().into();
if witness_root != anchor {
return Err(Error::AnchorMismatch);
}
} else {
self.anchor = Some(witness.root().into())
}
let witness = witness.path().ok_or(Error::InvalidWitness)?;
let alpha = Fs::random(&mut self.rng);
self.mtx.value_balance += Amount::from_u64(note.value).map_err(|_| Error::InvalidAmount)?;
self.spends.push(SpendDescriptionInfo {
extsk,
diversifier,
note,
alpha,
witness,
});
Ok(())
}
/// Adds a Sapling address to send funds to.
pub fn add_sapling_output(
&mut self,
ovk: OutgoingViewingKey,
to: PaymentAddress<Bls12>,
value: Amount,
memo: Option<Memo>,
) -> Result<(), Error> {
let output = SaplingOutput::new(&mut self.rng, ovk, to, value, memo)?;
self.mtx.value_balance -= value;
self.outputs.push(output);
Ok(())
}
/// Adds a transparent address to send funds to.
pub fn add_transparent_output(
&mut self,
to: &TransparentAddress,
value: Amount,
) -> Result<(), Error> {
if value.is_negative() {
return Err(Error::InvalidAmount);
}
self.mtx.vout.push(TxOut {
value,
script_pubkey: to.script(),
});
Ok(())
}
/// Sets the Sapling address to which any change will be sent.
///
/// By default, change is sent to the Sapling address corresponding to the first note
/// being spent (i.e. the first call to [`Builder::add_sapling_spend`]).
pub fn send_change_to(&mut self, ovk: OutgoingViewingKey, to: PaymentAddress<Bls12>) {
self.change_address = Some((ovk, to));
}
/// Builds a transaction from the configured spends and outputs.
///
/// Upon success, returns a tuple containing the final transaction, and the
/// [`TransactionMetadata`] generated during the build process.
///
/// `consensus_branch_id` must be valid for the block height that this transaction is
/// targeting. An invalid `consensus_branch_id` will *not* result in an error from
/// this function, and instead will generate a transaction that will be rejected by
/// the network.
pub fn build(
mut self,
consensus_branch_id: u32,
prover: impl TxProver,
) -> Result<(Transaction, TransactionMetadata), Error> {
let mut tx_metadata = TransactionMetadata::new();
//
// Consistency checks
//
// Valid change
let change = self.mtx.value_balance
- self.fee
- self
.mtx
.vout
.iter()
.map(|output| output.value)
.sum::<Amount>();
if change.is_negative() {
return Err(Error::ChangeIsNegative(change));
}
//
// Change output
//
if change.is_positive() {
// Send change to the specified change address. If no change address
// was set, send change to the first Sapling address given as input.
let change_address = if let Some(change_address) = self.change_address.take() {
change_address
} else if !self.spends.is_empty() {
(
self.spends[0].extsk.expsk.ovk,
PaymentAddress {
diversifier: self.spends[0].diversifier,
pk_d: self.spends[0].note.pk_d.clone(),
},
)
} else {
return Err(Error::NoChangeAddress);
};
self.add_sapling_output(change_address.0, change_address.1, change, None)?;
}
//
// Record initial positions of spends and outputs
//
let mut spends: Vec<_> = self.spends.into_iter().enumerate().collect();
let mut outputs: Vec<_> = self
.outputs
.into_iter()
.enumerate()
.map(|(i, o)| Some((i, o)))
.collect();
//
// Sapling spends and outputs
//
let mut ctx = prover.new_sapling_proving_context();
let anchor = self.anchor.expect("anchor was set if spends were added");
// Pad Sapling outputs
let orig_outputs_len = outputs.len();
if !spends.is_empty() {
while outputs.len() < MIN_SHIELDED_OUTPUTS {
outputs.push(None);
}
}
// Randomize order of inputs and outputs
spends.shuffle(&mut self.rng);
outputs.shuffle(&mut self.rng);
tx_metadata.spend_indices.resize(spends.len(), 0);
tx_metadata.output_indices.resize(orig_outputs_len, 0);
// Create Sapling SpendDescriptions
for (i, (pos, spend)) in spends.iter().enumerate() {
let proof_generation_key = spend.extsk.expsk.proof_generation_key(&JUBJUB);
let mut nullifier = [0u8; 32];
nullifier.copy_from_slice(&spend.note.nf(
&proof_generation_key.into_viewing_key(&JUBJUB),
spend.witness.position,
&JUBJUB,
));
let (zkproof, cv, rk) = prover
.spend_proof(
&mut ctx,
proof_generation_key,
spend.diversifier,
spend.note.r,
spend.alpha,
spend.note.value,
anchor,
spend.witness.clone(),
)
.map_err(|()| Error::SpendProof)?;
self.mtx.shielded_spends.push(SpendDescription {
cv,
anchor: anchor,
nullifier,
rk,
zkproof,
spend_auth_sig: None,
});
// Record the post-randomized spend location
tx_metadata.spend_indices[*pos] = i;
}
// Create Sapling OutputDescriptions
for (i, output) in outputs.into_iter().enumerate() {
let output_desc = if let Some((pos, output)) = output {
// Record the post-randomized output location
tx_metadata.output_indices[pos] = i;
output.build(&prover, &mut ctx, &mut self.rng)
} else {
// This is a dummy output
let (dummy_to, dummy_note) = {
let (diversifier, g_d) = {
let mut diversifier;
let g_d;
loop {
let mut d = [0; 11];
self.rng.fill_bytes(&mut d);
diversifier = Diversifier(d);
if let Some(val) = diversifier.g_d::<Bls12>(&JUBJUB) {
g_d = val;
break;
}
}
(diversifier, g_d)
};
let pk_d = {
let dummy_ivk = Fs::random(&mut self.rng);
g_d.mul(dummy_ivk, &JUBJUB)
};
(
PaymentAddress {
diversifier,
pk_d: pk_d.clone(),
},
Note {
g_d,
pk_d,
r: Fs::random(&mut self.rng),
value: 0,
},
)
};
let esk = generate_esk(&mut self.rng);
let epk = dummy_note.g_d.mul(esk, &JUBJUB);
let (zkproof, cv) =
prover.output_proof(&mut ctx, esk, dummy_to, dummy_note.r, dummy_note.value);
let cmu = dummy_note.cm(&JUBJUB);
let mut enc_ciphertext = [0u8; 580];
let mut out_ciphertext = [0u8; 80];
self.rng.fill_bytes(&mut enc_ciphertext[..]);
self.rng.fill_bytes(&mut out_ciphertext[..]);
OutputDescription {
cv,
cmu,
ephemeral_key: epk.into(),
enc_ciphertext,
out_ciphertext,
zkproof,
}
};
self.mtx.shielded_outputs.push(output_desc);
}
//
// Signatures
//
let mut sighash = [0u8; 32];
sighash.copy_from_slice(&signature_hash_data(
&self.mtx,
consensus_branch_id,
SIGHASH_ALL,
None,
));
// 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(
PrivateKey(spend.extsk.expsk.ask),
spend.alpha,
&sighash,
&mut self.rng,
&JUBJUB,
));
}
self.mtx.binding_sig = Some(
prover
.binding_sig(&mut ctx, self.mtx.value_balance, &sighash)
.map_err(|()| Error::BindingSig)?,
);
Ok((
self.mtx.freeze().expect("Transaction should be complete"),
tx_metadata,
))
}
}
#[cfg(test)]
mod tests {
use ff::{Field, PrimeField};
use rand::rngs::OsRng;
use sapling_crypto::jubjub::fs::Fs;
use super::{Builder, Error};
use crate::{
legacy::TransparentAddress,
merkle_tree::{CommitmentTree, IncrementalWitness},
prover::mock::MockTxProver,
sapling::Node,
transaction::components::Amount,
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
JUBJUB,
};
#[test]
fn fails_on_negative_output() {
let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk);
let ovk = extfvk.fvk.ovk;
let to = extfvk.default_address().unwrap().1;
let mut builder = Builder::new(0);
assert_eq!(
builder.add_sapling_output(ovk, to, Amount::from_i64(-1).unwrap(), None),
Err(Error::InvalidAmount)
);
}
#[test]
fn fails_on_negative_transparent_output() {
let mut builder = Builder::new(0);
assert_eq!(
builder.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]),
Amount::from_i64(-1).unwrap(),
),
Err(Error::InvalidAmount)
);
}
#[test]
fn fails_on_negative_change() {
let mut rng = OsRng;
// Just use the master key as the ExtendedSpendingKey for this test
let extsk = ExtendedSpendingKey::master(&[]);
// Fails with no inputs or outputs
// 0.0001 t-ZEC fee
{
let builder = Builder::new(0);
assert_eq!(
builder.build(1, MockTxProver),
Err(Error::ChangeIsNegative(Amount::from_i64(-10000).unwrap()))
);
}
let extfvk = ExtendedFullViewingKey::from(&extsk);
let ovk = extfvk.fvk.ovk;
let to = extfvk.default_address().unwrap().1;
// Fail if there is only a Sapling output
// 0.0005 z-ZEC out, 0.0001 t-ZEC fee
{
let mut builder = Builder::new(0);
builder
.add_sapling_output(
ovk.clone(),
to.clone(),
Amount::from_u64(50000).unwrap(),
None,
)
.unwrap();
assert_eq!(
builder.build(1, MockTxProver),
Err(Error::ChangeIsNegative(Amount::from_i64(-60000).unwrap()))
);
}
// Fail if there is only a transparent output
// 0.0005 t-ZEC out, 0.0001 t-ZEC fee
{
let mut builder = Builder::new(0);
builder
.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]),
Amount::from_u64(50000).unwrap(),
)
.unwrap();
assert_eq!(
builder.build(1, MockTxProver),
Err(Error::ChangeIsNegative(Amount::from_i64(-60000).unwrap()))
);
}
let note1 = to
.create_note(59999, Fs::random(&mut rng), &JUBJUB)
.unwrap();
let cm1 = Node::new(note1.cm(&JUBJUB).into_repr());
let mut tree = CommitmentTree::new();
tree.append(cm1).unwrap();
let mut witness1 = IncrementalWitness::from_tree(&tree);
// Fail if there is insufficient input
// 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.00059999 z-ZEC in
{
let mut builder = Builder::new(0);
builder
.add_sapling_spend(
extsk.clone(),
to.diversifier,
note1.clone(),
witness1.clone(),
)
.unwrap();
builder
.add_sapling_output(
ovk.clone(),
to.clone(),
Amount::from_u64(30000).unwrap(),
None,
)
.unwrap();
builder
.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]),
Amount::from_u64(20000).unwrap(),
)
.unwrap();
assert_eq!(
builder.build(1, MockTxProver),
Err(Error::ChangeIsNegative(Amount::from_i64(-1).unwrap()))
);
}
let note2 = to.create_note(1, Fs::random(&mut rng), &JUBJUB).unwrap();
let cm2 = Node::new(note2.cm(&JUBJUB).into_repr());
tree.append(cm2).unwrap();
witness1.append(cm2).unwrap();
let witness2 = IncrementalWitness::from_tree(&tree);
// Succeeds if there is sufficient input
// 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.0006 z-ZEC in
//
// (Still fails because we are using a MockTxProver which doesn't correctly
// compute bindingSig.)
{
let mut builder = Builder::new(0);
builder
.add_sapling_spend(extsk.clone(), to.diversifier, note1, witness1)
.unwrap();
builder
.add_sapling_spend(extsk, to.diversifier, note2, witness2)
.unwrap();
builder
.add_sapling_output(ovk, to, Amount::from_u64(30000).unwrap(), None)
.unwrap();
builder
.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]),
Amount::from_u64(20000).unwrap(),
)
.unwrap();
assert_eq!(builder.build(1, MockTxProver), Err(Error::BindingSig))
}
}
}

View File

@ -7,71 +7,20 @@ use sapling_crypto::{
};
use std::io::{self, Read, Write};
use serialize::Vector;
use legacy::Script;
use JUBJUB;
pub mod amount;
pub use self::amount::Amount;
// π_A + π_B + π_C
const GROTH_PROOF_SIZE: usize = (48 + 96 + 48);
pub const GROTH_PROOF_SIZE: usize = (48 + 96 + 48);
// π_A + π_A' + π_B + π_B' + π_C + π_C' + π_K + π_H
const PHGR_PROOF_SIZE: usize = (33 + 33 + 65 + 33 + 33 + 33 + 33 + 33);
const ZC_NUM_JS_INPUTS: usize = 2;
const ZC_NUM_JS_OUTPUTS: usize = 2;
const COIN: i64 = 1_0000_0000;
const MAX_MONEY: i64 = 21_000_000 * COIN;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Amount(pub i64);
impl Amount {
// Read an Amount from a signed 64-bit little-endian integer.
pub fn read_i64<R: Read>(mut reader: R, allow_negative: bool) -> io::Result<Self> {
let amount = reader.read_i64::<LittleEndian>()?;
if 0 <= amount && amount <= MAX_MONEY {
Ok(Amount(amount))
} else if allow_negative && -MAX_MONEY <= amount && amount < 0 {
Ok(Amount(amount))
} else {
Err(io::Error::new(
io::ErrorKind::InvalidData,
if allow_negative {
"Amount not in {-MAX_MONEY..MAX_MONEY}"
} else {
"Amount not in {0..MAX_MONEY}"
},
))
}
}
// Read an Amount from an unsigned 64-bit little-endian integer.
pub fn read_u64<R: Read>(mut reader: R) -> io::Result<Self> {
let amount = reader.read_u64::<LittleEndian>()?;
if amount <= MAX_MONEY as u64 {
Ok(Amount(amount as i64))
} else {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"Amount not in {0..MAX_MONEY}",
))
}
}
}
#[derive(Debug)]
pub struct Script(pub Vec<u8>);
impl Script {
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let script = Vector::read(&mut reader, |r| r.read_u8())?;
Ok(Script(script))
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
Vector::write(&mut writer, &self.0, |w, e| w.write_u8(*e))
}
}
#[derive(Debug)]
pub struct OutPoint {
hash: [u8; 32],
@ -121,13 +70,18 @@ impl TxIn {
#[derive(Debug)]
pub struct TxOut {
value: Amount,
script_pubkey: Script,
pub value: Amount,
pub script_pubkey: Script,
}
impl TxOut {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
let value = Amount::read_i64(&mut reader, false)?;
let value = {
let mut tmp = [0; 8];
reader.read_exact(&mut tmp)?;
Amount::from_nonnegative_i64_le_bytes(tmp)
}
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "value out of range"))?;
let script_pubkey = Script::read(&mut reader)?;
Ok(TxOut {
@ -137,7 +91,7 @@ impl TxOut {
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_i64::<LittleEndian>(self.value.0)?;
writer.write_all(&self.value.to_i64_le_bytes())?;
self.script_pubkey.write(&mut writer)
}
}
@ -349,10 +303,20 @@ impl std::fmt::Debug for JSDescription {
impl JSDescription {
pub fn read<R: Read>(mut reader: R, use_groth: bool) -> io::Result<Self> {
// Consensus rule (§4.3): Canonical encoding is enforced here
let vpub_old = Amount::read_u64(&mut reader)?;
let vpub_old = {
let mut tmp = [0; 8];
reader.read_exact(&mut tmp)?;
Amount::from_u64_le_bytes(tmp)
}
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "vpub_old out of range"))?;
// Consensus rule (§4.3): Canonical encoding is enforced here
let vpub_new = Amount::read_u64(&mut reader)?;
let vpub_new = {
let mut tmp = [0; 8];
reader.read_exact(&mut tmp)?;
Amount::from_u64_le_bytes(tmp)
}
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "vpub_new out of range"))?;
// Consensus rule (§4.3): One of vpub_old and vpub_new being zero is
// enforced by CheckTransactionWithoutProofVerification() in zcashd.
@ -425,8 +389,8 @@ impl JSDescription {
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_i64::<LittleEndian>(self.vpub_old.0)?;
writer.write_i64::<LittleEndian>(self.vpub_new.0)?;
writer.write_all(&self.vpub_old.to_i64_le_bytes())?;
writer.write_all(&self.vpub_new.to_i64_le_bytes())?;
writer.write_all(&self.anchor)?;
writer.write_all(&self.nullifiers[0])?;
writer.write_all(&self.nullifiers[1])?;
@ -446,50 +410,3 @@ impl JSDescription {
writer.write_all(&self.ciphertexts[1])
}
}
#[cfg(test)]
mod tests {
use super::{Amount, MAX_MONEY};
#[test]
fn amount_in_range() {
let zero = b"\x00\x00\x00\x00\x00\x00\x00\x00";
assert_eq!(Amount::read_u64(&zero[..]).unwrap(), Amount(0));
assert_eq!(Amount::read_i64(&zero[..], false).unwrap(), Amount(0));
assert_eq!(Amount::read_i64(&zero[..], true).unwrap(), Amount(0));
let neg_one = b"\xff\xff\xff\xff\xff\xff\xff\xff";
assert!(Amount::read_u64(&neg_one[..]).is_err());
assert!(Amount::read_i64(&neg_one[..], false).is_err());
assert_eq!(Amount::read_i64(&neg_one[..], true).unwrap(), Amount(-1));
let max_money = b"\x00\x40\x07\x5a\xf0\x75\x07\x00";
assert_eq!(Amount::read_u64(&max_money[..]).unwrap(), Amount(MAX_MONEY));
assert_eq!(
Amount::read_i64(&max_money[..], false).unwrap(),
Amount(MAX_MONEY)
);
assert_eq!(
Amount::read_i64(&max_money[..], true).unwrap(),
Amount(MAX_MONEY)
);
let max_money_p1 = b"\x01\x40\x07\x5a\xf0\x75\x07\x00";
assert!(Amount::read_u64(&max_money_p1[..]).is_err());
assert!(Amount::read_i64(&max_money_p1[..], false).is_err());
assert!(Amount::read_i64(&max_money_p1[..], true).is_err());
let neg_max_money = b"\x00\xc0\xf8\xa5\x0f\x8a\xf8\xff";
assert!(Amount::read_u64(&neg_max_money[..]).is_err());
assert!(Amount::read_i64(&neg_max_money[..], false).is_err());
assert_eq!(
Amount::read_i64(&neg_max_money[..], true).unwrap(),
Amount(-MAX_MONEY)
);
let neg_max_money_m1 = b"\xff\xbf\xf8\xa5\x0f\x8a\xf8\xff";
assert!(Amount::read_u64(&neg_max_money_m1[..]).is_err());
assert!(Amount::read_i64(&neg_max_money_m1[..], false).is_err());
assert!(Amount::read_i64(&neg_max_money_m1[..], true).is_err());
}
}

View File

@ -0,0 +1,232 @@
use std::iter::Sum;
use std::ops::{Add, AddAssign, Sub, SubAssign};
const COIN: i64 = 1_0000_0000;
const MAX_MONEY: i64 = 21_000_000 * COIN;
pub const DEFAULT_FEE: Amount = Amount(10000);
/// A type-safe representation of some quantity of Zcash.
///
/// An Amount can only be constructed from an integer that is within the valid monetary
/// range of `{-MAX_MONEY..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis).
/// However, this range is not preserved as an invariant internally; it is possible to
/// add two valid Amounts together to obtain an invalid Amount. It is the user's
/// responsibility to handle the result of serializing potentially-invalid Amounts. In
/// particular, a [`Transaction`] containing serialized invalid Amounts will be rejected
/// by the network consensus rules.
///
/// [`Transaction`]: crate::transaction::Transaction
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Amount(i64);
impl Amount {
/// Returns a zero-valued Amount.
pub const fn zero() -> Self {
Amount(0)
}
/// Creates an Amount from an i64.
///
/// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`.
pub fn from_i64(amount: i64) -> Result<Self, ()> {
if -MAX_MONEY <= amount && amount <= MAX_MONEY {
Ok(Amount(amount))
} else {
Err(())
}
}
/// Creates a non-negative Amount from an i64.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
pub fn from_nonnegative_i64(amount: i64) -> Result<Self, ()> {
if 0 <= amount && amount <= MAX_MONEY {
Ok(Amount(amount))
} else {
Err(())
}
}
/// Creates an Amount from a u64.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
pub fn from_u64(amount: u64) -> Result<Self, ()> {
if amount <= MAX_MONEY as u64 {
Ok(Amount(amount as i64))
} else {
Err(())
}
}
/// Reads an Amount from a signed 64-bit little-endian integer.
///
/// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`.
pub fn from_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, ()> {
let amount = i64::from_le_bytes(bytes);
Amount::from_i64(amount)
}
/// Reads a non-negative Amount from a signed 64-bit little-endian integer.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, ()> {
let amount = i64::from_le_bytes(bytes);
Amount::from_nonnegative_i64(amount)
}
/// Reads an Amount from an unsigned 64-bit little-endian integer.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result<Self, ()> {
let amount = u64::from_le_bytes(bytes);
Amount::from_u64(amount)
}
/// Returns the Amount encoded as a signed 64-bit little-endian integer.
pub fn to_i64_le_bytes(self) -> [u8; 8] {
self.0.to_le_bytes()
}
/// Returns `true` if `self` is positive and `false` if the Amount is zero or
/// negative.
pub const fn is_positive(self) -> bool {
self.0.is_positive()
}
/// Returns `true` if `self` is negative and `false` if the Amount is zero or
/// positive.
pub const fn is_negative(self) -> bool {
self.0.is_negative()
}
}
impl From<Amount> for i64 {
fn from(amount: Amount) -> i64 {
amount.0
}
}
impl From<Amount> for u64 {
fn from(amount: Amount) -> u64 {
amount.0 as u64
}
}
impl Add<Amount> for Amount {
type Output = Amount;
fn add(self, rhs: Amount) -> Amount {
Amount::from_i64(self.0 + rhs.0).expect("addition should remain in range")
}
}
impl AddAssign<Amount> for Amount {
fn add_assign(&mut self, rhs: Amount) {
*self = *self + rhs
}
}
impl Sub<Amount> for Amount {
type Output = Amount;
fn sub(self, rhs: Amount) -> Amount {
Amount::from_i64(self.0 - rhs.0).expect("subtraction should remain in range")
}
}
impl SubAssign<Amount> for Amount {
fn sub_assign(&mut self, rhs: Amount) {
*self = *self - rhs
}
}
impl Sum for Amount {
fn sum<I: Iterator<Item = Amount>>(iter: I) -> Amount {
iter.fold(Amount::zero(), Add::add)
}
}
#[cfg(test)]
mod tests {
use super::{Amount, MAX_MONEY};
#[test]
fn amount_in_range() {
let zero = b"\x00\x00\x00\x00\x00\x00\x00\x00";
assert_eq!(Amount::from_u64_le_bytes(zero.clone()).unwrap(), Amount(0));
assert_eq!(
Amount::from_nonnegative_i64_le_bytes(zero.clone()).unwrap(),
Amount(0)
);
assert_eq!(Amount::from_i64_le_bytes(zero.clone()).unwrap(), Amount(0));
let neg_one = b"\xff\xff\xff\xff\xff\xff\xff\xff";
assert!(Amount::from_u64_le_bytes(neg_one.clone()).is_err());
assert!(Amount::from_nonnegative_i64_le_bytes(neg_one.clone()).is_err());
assert_eq!(
Amount::from_i64_le_bytes(neg_one.clone()).unwrap(),
Amount(-1)
);
let max_money = b"\x00\x40\x07\x5a\xf0\x75\x07\x00";
assert_eq!(
Amount::from_u64_le_bytes(max_money.clone()).unwrap(),
Amount(MAX_MONEY)
);
assert_eq!(
Amount::from_nonnegative_i64_le_bytes(max_money.clone()).unwrap(),
Amount(MAX_MONEY)
);
assert_eq!(
Amount::from_i64_le_bytes(max_money.clone()).unwrap(),
Amount(MAX_MONEY)
);
let max_money_p1 = b"\x01\x40\x07\x5a\xf0\x75\x07\x00";
assert!(Amount::from_u64_le_bytes(max_money_p1.clone()).is_err());
assert!(Amount::from_nonnegative_i64_le_bytes(max_money_p1.clone()).is_err());
assert!(Amount::from_i64_le_bytes(max_money_p1.clone()).is_err());
let neg_max_money = b"\x00\xc0\xf8\xa5\x0f\x8a\xf8\xff";
assert!(Amount::from_u64_le_bytes(neg_max_money.clone()).is_err());
assert!(Amount::from_nonnegative_i64_le_bytes(neg_max_money.clone()).is_err());
assert_eq!(
Amount::from_i64_le_bytes(neg_max_money.clone()).unwrap(),
Amount(-MAX_MONEY)
);
let neg_max_money_m1 = b"\xff\xbf\xf8\xa5\x0f\x8a\xf8\xff";
assert!(Amount::from_u64_le_bytes(neg_max_money_m1.clone()).is_err());
assert!(Amount::from_nonnegative_i64_le_bytes(neg_max_money_m1.clone()).is_err());
assert!(Amount::from_i64_le_bytes(neg_max_money_m1.clone()).is_err());
}
#[test]
#[should_panic]
fn add_panics_on_overflow() {
let v = Amount(MAX_MONEY);
let sum = v + Amount(1);
}
#[test]
#[should_panic]
fn add_assign_panics_on_overflow() {
let mut a = Amount(MAX_MONEY);
a += Amount(1);
}
#[test]
#[should_panic]
fn sub_panics_on_underflow() {
let v = Amount(-MAX_MONEY);
let diff = v - Amount(1);
}
#[test]
#[should_panic]
fn sub_assign_panics_on_underflow() {
let mut a = Amount(-MAX_MONEY);
a -= Amount(1);
}
}

View File

@ -8,6 +8,7 @@ use std::ops::Deref;
use serialize::Vector;
pub mod builder;
pub mod components;
mod sighash;
@ -49,6 +50,12 @@ impl Deref for Transaction {
}
}
impl PartialEq for Transaction {
fn eq(&self, other: &Transaction) -> bool {
self.txid == other.txid
}
}
pub struct TransactionData {
pub overwintered: bool,
pub version: u32,
@ -111,7 +118,7 @@ impl TransactionData {
vout: vec![],
lock_time: 0,
expiry_height: 0,
value_balance: Amount(0),
value_balance: Amount::zero(),
shielded_spends: vec![],
shielded_outputs: vec![],
joinsplits: vec![],
@ -184,12 +191,17 @@ impl Transaction {
};
let (value_balance, shielded_spends, shielded_outputs) = if is_sapling_v4 {
let vb = Amount::read_i64(&mut reader, true)?;
let vb = {
let mut tmp = [0; 8];
reader.read_exact(&mut tmp)?;
Amount::from_i64_le_bytes(tmp)
}
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "valueBalance out of range"))?;
let ss = Vector::read(&mut reader, SpendDescription::read)?;
let so = Vector::read(&mut reader, OutputDescription::read)?;
(vb, ss, so)
} else {
(Amount(0), vec![], vec![])
(Amount::zero(), vec![], vec![])
};
let (joinsplits, joinsplit_pubkey, joinsplit_sig) = if version >= 2 {
@ -261,7 +273,7 @@ impl Transaction {
}
if is_sapling_v4 {
writer.write_i64::<LittleEndian>(self.value_balance.0)?;
writer.write_all(&self.value_balance.to_i64_le_bytes())?;
Vector::write(&mut writer, &self.shielded_spends, |w, e| e.write(w))?;
Vector::write(&mut writer, &self.shielded_outputs, |w, e| e.write(w))?;
}

View File

@ -3,10 +3,11 @@ use byteorder::{LittleEndian, WriteBytesExt};
use ff::{PrimeField, PrimeFieldRepr};
use super::{
components::{Amount, Script, TxOut},
components::{Amount, TxOut},
Transaction, TransactionData, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION,
SAPLING_VERSION_GROUP_ID,
};
use legacy::Script;
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &'static [u8; 12] = b"ZcashSigHash";
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &'static [u8; 16] = b"ZcashPrevoutHash";
@ -29,13 +30,6 @@ macro_rules! update_u32 {
};
}
macro_rules! update_i64 {
($h:expr, $value:expr, $tmp:expr) => {
(&mut $tmp[..8]).write_i64::<LittleEndian>($value).unwrap();
$h.update(&$tmp[..8]);
};
}
macro_rules! update_hash {
($h:expr, $cond:expr, $value:expr) => {
if $cond {
@ -213,7 +207,7 @@ pub fn signature_hash_data(
update_u32!(h, tx.lock_time, tmp);
update_u32!(h, tx.expiry_height, tmp);
if sigversion == SigHashVersion::Sapling {
update_i64!(h, tx.value_balance.0, tmp);
h.update(&tx.value_balance.to_i64_le_bytes());
}
update_u32!(h, hash_type, tmp);
@ -221,7 +215,7 @@ pub fn signature_hash_data(
let mut data = vec![];
tx.vin[n].prevout.write(&mut data).unwrap();
script_code.write(&mut data).unwrap();
(&mut data).write_i64::<LittleEndian>(amount.0).unwrap();
data.extend_from_slice(&amount.to_i64_le_bytes());
(&mut data)
.write_u32::<LittleEndian>(tx.vin[n].sequence)
.unwrap();

View File

@ -6,11 +6,8 @@ use sapling_crypto::{
redjubjub::PrivateKey,
};
use super::{
components::{Amount, Script},
sighash::signature_hash,
Transaction, TransactionData,
};
use super::{components::Amount, sighash::signature_hash, Transaction, TransactionData};
use legacy::Script;
use JUBJUB;
#[test]
@ -1905,7 +1902,11 @@ fn zip_0143() {
for tv in test_vectors {
let tx = Transaction::read(&tv.tx[..]).unwrap();
let transparent_input = if let Some(n) = tv.transparent_input {
Some((n as usize, Script(tv.script_code), Amount(tv.amount)))
Some((
n as usize,
Script(tv.script_code),
Amount::from_nonnegative_i64(tv.amount).unwrap(),
))
} else {
None
};
@ -5396,7 +5397,11 @@ fn zip_0243() {
for tv in test_vectors {
let tx = Transaction::read(&tv.tx[..]).unwrap();
let transparent_input = if let Some(n) = tv.transparent_input {
Some((n as usize, Script(tv.script_code), Amount(tv.amount)))
Some((
n as usize,
Script(tv.script_code),
Amount::from_nonnegative_i64(tv.amount).unwrap(),
))
} else {
None
};

View File

@ -9,8 +9,13 @@ authors = [
bellman = { path = "../bellman" }
blake2b_simd = "0.5"
byteorder = "1"
directories = { version = "1", optional = true }
ff = { path = "../ff" }
pairing = { path = "../pairing" }
rand_os = "0.2"
sapling-crypto = { path = "../sapling-crypto" }
zcash_primitives = { path = "../zcash_primitives" }
[features]
default = ["local-prover"]
local-prover = ["directories"]

View File

@ -7,6 +7,9 @@ extern crate rand_os;
extern crate sapling_crypto;
extern crate zcash_primitives;
#[cfg(feature = "local-prover")]
extern crate directories;
use bellman::groth16::{prepare_verifying_key, Parameters, PreparedVerifyingKey, VerifyingKey};
use pairing::bls12_381::Bls12;
use std::fs::File;
@ -16,6 +19,9 @@ use std::path::Path;
mod hashreader;
pub mod sapling;
#[cfg(feature = "local-prover")]
pub mod prover;
pub fn load_parameters(
spend_path: &Path,
spend_hash: &str,

193
zcash_proofs/src/prover.rs Normal file
View File

@ -0,0 +1,193 @@
//! Abstractions over the proving system and parameters for ease of use.
use bellman::groth16::{Parameters, PreparedVerifyingKey};
use directories::BaseDirs;
use pairing::bls12_381::{Bls12, Fr};
use sapling_crypto::{
jubjub::{edwards, fs::Fs, Unknown},
primitives::{Diversifier, PaymentAddress, ProofGenerationKey},
redjubjub::{PublicKey, Signature},
};
use std::path::Path;
use zcash_primitives::{
merkle_tree::CommitmentTreeWitness,
prover::TxProver,
sapling::Node,
transaction::components::{Amount, GROTH_PROOF_SIZE},
JUBJUB,
};
use crate::{load_parameters, sapling::SaplingProvingContext};
const SAPLING_SPEND_HASH: &str = "8270785a1a0d0bc77196f000ee6d221c9c9894f55307bd9357c3f0105d31ca63991ab91324160d8f53e2bbd3c2633a6eb8bdf5205d822e7f3f73edac51b2b70c";
const SAPLING_OUTPUT_HASH: &str = "657e3d38dbb5cb5e7dd2970e8b03d69b4787dd907285b5a7f0790dcc8072f60bf593b32cc2d1c030e00ff5ae64bf84c5c3beb84ddc841d48264b4a171744d028";
/// An implementation of [`TxProver`] using Sapling Spend and Output parameters from
/// locally-accessible paths.
pub struct LocalTxProver {
spend_params: Parameters<Bls12>,
spend_vk: PreparedVerifyingKey<Bls12>,
output_params: Parameters<Bls12>,
}
impl LocalTxProver {
/// Creates a `LocalTxProver` using parameters from the given local paths.
///
/// # Examples
///
/// ```should_panic
/// use std::path::Path;
/// use zcash_proofs::prover::LocalTxProver;
///
/// let tx_prover = LocalTxProver::new(
/// Path::new("/path/to/sapling-spend.params"),
/// Path::new("/path/to/sapling-output.params"),
/// );
/// ```
///
/// # Panics
///
/// This function will panic if the paths do not point to valid parameter files with
/// the expected hashes.
pub fn new(spend_path: &Path, output_path: &Path) -> Self {
let (spend_params, spend_vk, output_params, _, _) = load_parameters(
spend_path,
SAPLING_SPEND_HASH,
output_path,
SAPLING_OUTPUT_HASH,
None,
None,
);
LocalTxProver {
spend_params,
spend_vk,
output_params,
}
}
/// Attempts to create a `LocalTxProver` using parameters from the default local
/// location.
///
/// Returns `None` if any of the parameters cannot be found in the default local
/// location.
///
/// # Examples
///
/// ```
/// use zcash_proofs::prover::LocalTxProver;
///
/// match LocalTxProver::with_default_location() {
/// Some(tx_prover) => (),
/// None => println!("Please run zcash-fetch-params or fetch-params.sh to download the parameters."),
/// }
/// ```
///
/// # Panics
///
/// This function will panic if the parameters in the default local location do not
/// have the expected hashes.
pub fn with_default_location() -> Option<Self> {
let base_dirs = BaseDirs::new()?;
let unix_params_dir = base_dirs.home_dir().join(".zcash-params");
let win_osx_params_dir = base_dirs.data_dir().join("ZcashParams");
let (spend_path, output_path) = if unix_params_dir.exists() {
(
unix_params_dir.join("sapling-spend.params"),
unix_params_dir.join("sapling-output.params"),
)
} else if win_osx_params_dir.exists() {
(
win_osx_params_dir.join("sapling-spend.params"),
win_osx_params_dir.join("sapling-output.params"),
)
} else {
return None;
};
if !(spend_path.exists() && output_path.exists()) {
return None;
}
Some(LocalTxProver::new(&spend_path, &output_path))
}
}
impl TxProver for LocalTxProver {
type SaplingProvingContext = SaplingProvingContext;
fn new_sapling_proving_context(&self) -> Self::SaplingProvingContext {
SaplingProvingContext::new()
}
fn spend_proof(
&self,
ctx: &mut Self::SaplingProvingContext,
proof_generation_key: ProofGenerationKey<Bls12>,
diversifier: Diversifier,
rcm: Fs,
ar: Fs,
value: u64,
anchor: Fr,
witness: CommitmentTreeWitness<Node>,
) -> Result<
(
[u8; GROTH_PROOF_SIZE],
edwards::Point<Bls12, Unknown>,
PublicKey<Bls12>,
),
(),
> {
let (proof, cv, rk) = ctx.spend_proof(
proof_generation_key,
diversifier,
rcm,
ar,
value,
anchor,
witness,
&self.spend_params,
&self.spend_vk,
&JUBJUB,
)?;
let mut zkproof = [0u8; GROTH_PROOF_SIZE];
proof
.write(&mut zkproof[..])
.expect("should be able to serialize a proof");
Ok((zkproof, cv, rk))
}
fn output_proof(
&self,
ctx: &mut Self::SaplingProvingContext,
esk: Fs,
payment_address: PaymentAddress<Bls12>,
rcm: Fs,
value: u64,
) -> ([u8; GROTH_PROOF_SIZE], edwards::Point<Bls12, Unknown>) {
let (proof, cv) = ctx.output_proof(
esk,
payment_address,
rcm,
value,
&self.output_params,
&JUBJUB,
);
let mut zkproof = [0u8; GROTH_PROOF_SIZE];
proof
.write(&mut zkproof[..])
.expect("should be able to serialize a proof");
(zkproof, cv)
}
fn binding_sig(
&self,
ctx: &mut Self::SaplingProvingContext,
value_balance: Amount,
sighash: &[u8; 32],
) -> Result<Signature, ()> {
ctx.binding_sig(value_balance, sighash, &JUBJUB)
}
}

View File

@ -2,6 +2,7 @@ use pairing::bls12_381::Bls12;
use sapling_crypto::jubjub::{
edwards, fs::FsRepr, FixedGenerators, JubjubBls12, JubjubParams, Unknown,
};
use zcash_primitives::transaction::components::Amount;
mod prover;
mod verifier;
@ -11,12 +12,12 @@ pub use self::verifier::SaplingVerificationContext;
// This function computes `value` in the exponent of the value commitment base
fn compute_value_balance(
value: i64,
value: Amount,
params: &JubjubBls12,
) -> Option<edwards::Point<Bls12, Unknown>> {
// Compute the absolute value (failing if -i64::MAX is
// the value)
let abs = match value.checked_abs() {
let abs = match i64::from(value).checked_abs() {
Some(a) => a as u64,
None => return None,
};

View File

@ -13,7 +13,9 @@ use sapling_crypto::{
primitives::{Diversifier, Note, PaymentAddress, ProofGenerationKey, ValueCommitment},
redjubjub::{PrivateKey, PublicKey, Signature},
};
use zcash_primitives::{merkle_tree::CommitmentTreeWitness, sapling::Node};
use zcash_primitives::{
merkle_tree::CommitmentTreeWitness, sapling::Node, transaction::components::Amount,
};
use super::compute_value_balance;
@ -245,7 +247,7 @@ impl SaplingProvingContext {
/// and output_proof() must be completed before calling this function.
pub fn binding_sig(
&self,
value_balance: i64,
value_balance: Amount,
sighash: &[u8; 32],
params: &JubjubBls12,
) -> Result<Signature, ()> {

View File

@ -6,6 +6,7 @@ use sapling_crypto::{
jubjub::{edwards, FixedGenerators, JubjubBls12, Unknown},
redjubjub::{PublicKey, Signature},
};
use zcash_primitives::transaction::components::Amount;
use super::compute_value_balance;
@ -169,7 +170,7 @@ impl SaplingVerificationContext {
/// have been checked before calling this function.
pub fn final_check(
&self,
value_balance: i64,
value_balance: Amount,
sighash_value: &[u8; 32],
binding_sig: Signature,
params: &JubjubBls12,