mirror of
https://github.com/Qortal/pirate-librustzcash.git
synced 2025-01-30 23:42:13 +00:00
Merge pull request #92 from str4d/transaction-builder
Transaction builder
This commit is contained in:
commit
52ea437e11
34
Cargo.lock
generated
34
Cargo.lock
generated
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -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" }
|
||||
|
168
zcash_primitives/src/legacy.rs
Normal file
168
zcash_primitives/src/legacy.rs
Normal 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,
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
166
zcash_primitives/src/prover.rs
Normal file
166
zcash_primitives/src/prover.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
)
|
||||
|
702
zcash_primitives/src/transaction/builder.rs
Normal file
702
zcash_primitives/src/transaction/builder.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
232
zcash_primitives/src/transaction/components/amount.rs
Normal file
232
zcash_primitives/src/transaction/components/amount.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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))?;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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"]
|
||||
|
@ -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
193
zcash_proofs/src/prover.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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,
|
||||
};
|
||||
|
@ -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, ()> {
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user