Merge pull request #39 from str4d/zcash-transaction-primitives

Transaction primitives
This commit is contained in:
str4d 2018-11-30 23:38:38 +00:00 committed by GitHub
commit 8be50c9531
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 6533 additions and 6 deletions

8
Cargo.lock generated
View File

@ -704,6 +704,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "zcash_primitives"
version = "0.0.0"
dependencies = [
"blake2-rfc 0.2.18 (git+https://github.com/gtank/blake2-rfc?rev=7a5b5fc99ae483a0043db7547fb79a6fa44b88a9)",
"byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pairing 0.14.2",
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"sapling-crypto 0.0.1",
]
[[package]]
name = "zcash_proofs"

View File

@ -6,3 +6,12 @@ authors = [
]
[dependencies]
byteorder = "1"
lazy_static = "1"
pairing = { path = "../pairing" }
rand = "0.4"
sapling-crypto = { path = "../sapling-crypto" }
[dependencies.blake2-rfc]
git = "https://github.com/gtank/blake2-rfc"
rev = "7a5b5fc99ae483a0043db7547fb79a6fa44b88a9"

View File

@ -1,7 +1,17 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[macro_use]
extern crate lazy_static;
extern crate blake2_rfc;
extern crate byteorder;
extern crate pairing;
extern crate rand;
extern crate sapling_crypto;
use sapling_crypto::jubjub::JubjubBls12;
mod serialize;
pub mod transaction;
lazy_static! {
static ref JUBJUB: JubjubBls12 = { JubjubBls12::new() };
}

View File

@ -0,0 +1,156 @@
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::{self, Read, Write};
const MAX_SIZE: usize = 0x02000000;
struct CompactSize;
impl CompactSize {
fn read<R: Read>(mut reader: R) -> io::Result<usize> {
let flag = reader.read_u8()?;
match if flag < 253 {
Ok(flag as usize)
} else if flag == 253 {
match reader.read_u16::<LittleEndian>()? {
n if n < 253 => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"non-canonical CompactSize",
)),
n => Ok(n as usize),
}
} else if flag == 254 {
match reader.read_u32::<LittleEndian>()? {
n if n < 0x10000 => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"non-canonical CompactSize",
)),
n => Ok(n as usize),
}
} else {
match reader.read_u64::<LittleEndian>()? {
n if n < 0x100000000 => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"non-canonical CompactSize",
)),
n => Ok(n as usize),
}
}? {
s if s > MAX_SIZE => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"CompactSize too large",
)),
s => Ok(s),
}
}
fn write<W: Write>(mut writer: W, size: usize) -> io::Result<()> {
match size {
s if s < 253 => writer.write_u8(s as u8),
s if s <= 0xFFFF => {
writer.write_u8(253)?;
writer.write_u16::<LittleEndian>(s as u16)
}
s if s <= 0xFFFFFFFF => {
writer.write_u8(254)?;
writer.write_u32::<LittleEndian>(s as u32)
}
s => {
writer.write_u8(255)?;
writer.write_u64::<LittleEndian>(s as u64)
}
}
}
}
pub struct Vector;
impl Vector {
pub fn read<R: Read, E, F>(mut reader: R, func: F) -> io::Result<Vec<E>>
where
F: Fn(&mut R) -> io::Result<E>,
{
let count = CompactSize::read(&mut reader)?;
(0..count).into_iter().map(|_| func(&mut reader)).collect()
}
pub fn write<W: Write, E, F>(mut writer: W, vec: &[E], func: F) -> io::Result<()>
where
F: Fn(&mut W, &E) -> io::Result<()>,
{
CompactSize::write(&mut writer, vec.len())?;
vec.iter().map(|e| func(&mut writer, e)).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compact_size() {
macro_rules! eval {
($value:expr, $expected:expr) => {
let mut data = vec![];
CompactSize::write(&mut data, $value).unwrap();
assert_eq!(&data[..], &$expected[..]);
match CompactSize::read(&data[..]) {
Ok(n) => assert_eq!(n, $value),
Err(e) => panic!("Unexpected error: {:?}", e),
}
};
}
eval!(0, [0]);
eval!(1, [1]);
eval!(252, [252]);
eval!(253, [253, 253, 0]);
eval!(254, [253, 254, 0]);
eval!(255, [253, 255, 0]);
eval!(256, [253, 0, 1]);
eval!(256, [253, 0, 1]);
eval!(65535, [253, 255, 255]);
eval!(65536, [254, 0, 0, 1, 0]);
eval!(65537, [254, 1, 0, 1, 0]);
eval!(33554432, [254, 0, 0, 0, 2]);
{
let value = 33554433;
let encoded = &[254, 1, 0, 0, 2][..];
let mut data = vec![];
CompactSize::write(&mut data, value).unwrap();
assert_eq!(&data[..], encoded);
assert!(CompactSize::read(encoded).is_err());
}
}
#[test]
fn vector() {
macro_rules! eval {
($value:expr, $expected:expr) => {
let mut data = vec![];
Vector::write(&mut data, &$value, |w, e| w.write_u8(*e)).unwrap();
assert_eq!(&data[..], &$expected[..]);
match Vector::read(&data[..], |r| r.read_u8()) {
Ok(v) => assert_eq!(v, $value),
Err(e) => panic!("Unexpected error: {:?}", e),
}
};
}
eval!(vec![], [0]);
eval!(vec![0], [1, 0]);
eval!(vec![1], [1, 1]);
eval!(vec![5; 8], [8, 5, 5, 5, 5, 5, 5, 5, 5]);
{
// expected = [253, 4, 1, 7, 7, 7, ...]
let mut expected = vec![7; 263];
expected[0] = 253;
expected[1] = 4;
expected[2] = 1;
eval!(vec![7; 260], expected);
}
}
}

View File

@ -0,0 +1,432 @@
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use pairing::{
bls12_381::{Bls12, Fr, FrRepr},
PrimeField, PrimeFieldRepr,
};
use sapling_crypto::{
jubjub::{edwards, Unknown},
redjubjub::{PublicKey, Signature},
};
use std::io::{self, Read, Write};
use serialize::Vector;
use JUBJUB;
// π_A + π_B + π_C
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}",
))
}
}
}
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))
}
}
pub struct OutPoint {
hash: [u8; 32],
n: u32,
}
impl OutPoint {
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let mut hash = [0; 32];
reader.read_exact(&mut hash)?;
let n = reader.read_u32::<LittleEndian>()?;
Ok(OutPoint { hash, n })
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.hash)?;
writer.write_u32::<LittleEndian>(self.n)
}
}
pub struct TxIn {
pub prevout: OutPoint,
script_sig: Script,
pub sequence: u32,
}
impl TxIn {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
let prevout = OutPoint::read(&mut reader)?;
let script_sig = Script::read(&mut reader)?;
let sequence = reader.read_u32::<LittleEndian>()?;
Ok(TxIn {
prevout,
script_sig,
sequence,
})
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
self.prevout.write(&mut writer)?;
self.script_sig.write(&mut writer)?;
writer.write_u32::<LittleEndian>(self.sequence)
}
}
pub struct TxOut {
value: Amount,
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 script_pubkey = Script::read(&mut reader)?;
Ok(TxOut {
value,
script_pubkey,
})
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_i64::<LittleEndian>(self.value.0)?;
self.script_pubkey.write(&mut writer)
}
}
pub struct SpendDescription {
pub cv: edwards::Point<Bls12, Unknown>,
pub anchor: Fr,
pub nullifier: [u8; 32],
pub rk: PublicKey<Bls12>,
pub zkproof: [u8; GROTH_PROOF_SIZE],
pub spend_auth_sig: Signature,
}
impl SpendDescription {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
// Consensus rules (§4.4):
// - Canonical encoding is enforced here.
// - "Not small order" is enforced in SaplingVerificationContext::check_spend()
// (located in zcash_proofs::sapling::verifier).
let cv = edwards::Point::<Bls12, Unknown>::read(&mut reader, &JUBJUB)?;
// Consensus rule (§7.3): Canonical encoding is enforced here
let anchor = {
let mut f = FrRepr::default();
f.read_le(&mut reader)?;
Fr::from_repr(f).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?
};
let mut nullifier = [0; 32];
reader.read_exact(&mut nullifier)?;
// Consensus rules (§4.4):
// - Canonical encoding is enforced here.
// - "Not small order" is enforced in SaplingVerificationContext::check_spend()
let rk = PublicKey::<Bls12>::read(&mut reader, &JUBJUB)?;
// Consensus rules (§4.4):
// - Canonical encoding is enforced by the API of SaplingVerificationContext::check_spend()
// due to the need to parse this into a bellman::groth16::Proof.
// - Proof validity is enforced in SaplingVerificationContext::check_spend()
let mut zkproof = [0; GROTH_PROOF_SIZE];
reader.read_exact(&mut zkproof)?;
// Consensus rules (§4.4):
// - Canonical encoding is enforced here.
// - Signature validity is enforced in SaplingVerificationContext::check_spend()
let spend_auth_sig = Signature::read(&mut reader)?;
Ok(SpendDescription {
cv,
anchor,
nullifier,
rk,
zkproof,
spend_auth_sig,
})
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
self.cv.write(&mut writer)?;
self.anchor.into_repr().write_le(&mut writer)?;
writer.write_all(&self.nullifier)?;
self.rk.write(&mut writer)?;
writer.write_all(&self.zkproof)?;
self.spend_auth_sig.write(&mut writer)
}
}
pub struct OutputDescription {
pub cv: edwards::Point<Bls12, Unknown>,
pub cmu: Fr,
pub ephemeral_key: edwards::Point<Bls12, Unknown>,
pub enc_ciphertext: [u8; 580],
pub out_ciphertext: [u8; 80],
pub zkproof: [u8; GROTH_PROOF_SIZE],
}
impl OutputDescription {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
// Consensus rules (§4.5):
// - Canonical encoding is enforced here.
// - "Not small order" is enforced in SaplingVerificationContext::check_output()
// (located in zcash_proofs::sapling::verifier).
let cv = edwards::Point::<Bls12, Unknown>::read(&mut reader, &JUBJUB)?;
// Consensus rule (§7.4): Canonical encoding is enforced here
let cmu = {
let mut f = FrRepr::default();
f.read_le(&mut reader)?;
Fr::from_repr(f).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?
};
// Consensus rules (§4.5):
// - Canonical encoding is enforced here.
// - "Not small order" is enforced in SaplingVerificationContext::check_output()
let ephemeral_key = edwards::Point::<Bls12, Unknown>::read(&mut reader, &JUBJUB)?;
let mut enc_ciphertext = [0; 580];
let mut out_ciphertext = [0; 80];
reader.read_exact(&mut enc_ciphertext)?;
reader.read_exact(&mut out_ciphertext)?;
// Consensus rules (§4.5):
// - Canonical encoding is enforced by the API of SaplingVerificationContext::check_output()
// due to the need to parse this into a bellman::groth16::Proof.
// - Proof validity is enforced in SaplingVerificationContext::check_output()
let mut zkproof = [0; GROTH_PROOF_SIZE];
reader.read_exact(&mut zkproof)?;
Ok(OutputDescription {
cv,
cmu,
ephemeral_key,
enc_ciphertext,
out_ciphertext,
zkproof,
})
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
self.cv.write(&mut writer)?;
self.cmu.into_repr().write_le(&mut writer)?;
self.ephemeral_key.write(&mut writer)?;
writer.write_all(&self.enc_ciphertext)?;
writer.write_all(&self.out_ciphertext)?;
writer.write_all(&self.zkproof)
}
}
enum SproutProof {
Groth([u8; GROTH_PROOF_SIZE]),
PHGR([u8; PHGR_PROOF_SIZE]),
}
pub struct JSDescription {
vpub_old: Amount,
vpub_new: Amount,
anchor: [u8; 32],
nullifiers: [[u8; 32]; ZC_NUM_JS_INPUTS],
commitments: [[u8; 32]; ZC_NUM_JS_OUTPUTS],
ephemeral_key: [u8; 32],
random_seed: [u8; 32],
macs: [[u8; 32]; ZC_NUM_JS_INPUTS],
proof: SproutProof,
ciphertexts: [[u8; 601]; ZC_NUM_JS_OUTPUTS],
}
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)?;
// Consensus rule (§4.3): Canonical encoding is enforced here
let vpub_new = Amount::read_u64(&mut reader)?;
// Consensus rule (§4.3): One of vpub_old and vpub_new being zero is
// enforced by CheckTransactionWithoutProofVerification() in zcashd.
let mut anchor = [0; 32];
reader.read_exact(&mut anchor)?;
let mut nullifiers = [[0; 32]; ZC_NUM_JS_INPUTS];
nullifiers
.iter_mut()
.map(|nf| reader.read_exact(nf))
.collect::<io::Result<()>>()?;
let mut commitments = [[0; 32]; ZC_NUM_JS_OUTPUTS];
commitments
.iter_mut()
.map(|cm| reader.read_exact(cm))
.collect::<io::Result<()>>()?;
// Consensus rule (§4.3): Canonical encoding is enforced by
// ZCNoteDecryption::decrypt() in zcashd
let mut ephemeral_key = [0; 32];
reader.read_exact(&mut ephemeral_key)?;
let mut random_seed = [0; 32];
reader.read_exact(&mut random_seed)?;
let mut macs = [[0; 32]; ZC_NUM_JS_INPUTS];
macs.iter_mut()
.map(|mac| reader.read_exact(mac))
.collect::<io::Result<()>>()?;
let proof = match use_groth {
true => {
// Consensus rules (§4.3):
// - Canonical encoding is enforced in librustzcash_sprout_verify()
// - Proof validity is enforced in librustzcash_sprout_verify()
let mut proof = [0; GROTH_PROOF_SIZE];
reader.read_exact(&mut proof)?;
SproutProof::Groth(proof)
}
false => {
// Consensus rules (§4.3):
// - Canonical encoding is enforced by PHGRProof in zcashd
// - Proof validity is enforced by JSDescription::Verify() in zcashd
let mut proof = [0; PHGR_PROOF_SIZE];
reader.read_exact(&mut proof)?;
SproutProof::PHGR(proof)
}
};
let mut ciphertexts = [[0; 601]; ZC_NUM_JS_OUTPUTS];
ciphertexts
.iter_mut()
.map(|ct| reader.read_exact(ct))
.collect::<io::Result<()>>()?;
Ok(JSDescription {
vpub_old,
vpub_new,
anchor,
nullifiers,
commitments,
ephemeral_key,
random_seed,
macs,
proof,
ciphertexts,
})
}
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.anchor)?;
writer.write_all(&self.nullifiers[0])?;
writer.write_all(&self.nullifiers[1])?;
writer.write_all(&self.commitments[0])?;
writer.write_all(&self.commitments[1])?;
writer.write_all(&self.ephemeral_key)?;
writer.write_all(&self.random_seed)?;
writer.write_all(&self.macs[0])?;
writer.write_all(&self.macs[1])?;
match &self.proof {
SproutProof::Groth(p) => writer.write_all(p)?,
SproutProof::PHGR(p) => writer.write_all(p)?,
}
writer.write_all(&self.ciphertexts[0])?;
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,257 @@
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use sapling_crypto::redjubjub::Signature;
use std::io::{self, Read, Write};
use std::ops::Deref;
use serialize::Vector;
pub mod components;
mod sighash;
#[cfg(test)]
mod tests;
pub use self::sighash::{signature_hash, signature_hash_data, SIGHASH_ALL};
use self::components::{Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut};
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270;
const OVERWINTER_TX_VERSION: u32 = 3;
const SAPLING_VERSION_GROUP_ID: u32 = 0x892F2085;
const SAPLING_TX_VERSION: u32 = 4;
/// A Zcash transaction.
pub struct Transaction(TransactionData);
impl Deref for Transaction {
type Target = TransactionData;
fn deref(&self) -> &TransactionData {
&self.0
}
}
pub struct TransactionData {
pub overwintered: bool,
pub version: u32,
pub version_group_id: u32,
pub vin: Vec<TxIn>,
pub vout: Vec<TxOut>,
pub lock_time: u32,
pub expiry_height: u32,
pub value_balance: Amount,
pub shielded_spends: Vec<SpendDescription>,
pub shielded_outputs: Vec<OutputDescription>,
pub joinsplits: Vec<JSDescription>,
pub joinsplit_pubkey: Option<[u8; 32]>,
pub joinsplit_sig: Option<[u8; 64]>,
pub binding_sig: Option<Signature>,
}
impl TransactionData {
pub fn new() -> Self {
TransactionData {
overwintered: true,
version: SAPLING_TX_VERSION,
version_group_id: SAPLING_VERSION_GROUP_ID,
vin: vec![],
vout: vec![],
lock_time: 0,
expiry_height: 0,
value_balance: Amount(0),
shielded_spends: vec![],
shielded_outputs: vec![],
joinsplits: vec![],
joinsplit_pubkey: None,
joinsplit_sig: None,
binding_sig: None,
}
}
fn header(&self) -> u32 {
let mut header = self.version;
if self.overwintered {
header |= 1 << 31;
}
header
}
pub fn freeze(self) -> Transaction {
Transaction(self)
}
}
impl Transaction {
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let header = reader.read_u32::<LittleEndian>()?;
let overwintered = (header >> 31) == 1;
let version = header & 0x7FFFFFFF;
let version_group_id = match overwintered {
true => reader.read_u32::<LittleEndian>()?,
false => 0,
};
let is_overwinter_v3 = overwintered
&& version_group_id == OVERWINTER_VERSION_GROUP_ID
&& version == OVERWINTER_TX_VERSION;
let is_sapling_v4 = overwintered
&& version_group_id == SAPLING_VERSION_GROUP_ID
&& version == SAPLING_TX_VERSION;
if overwintered && !(is_overwinter_v3 || is_sapling_v4) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Unknown transaction format",
));
}
let vin = Vector::read(&mut reader, TxIn::read)?;
let vout = Vector::read(&mut reader, TxOut::read)?;
let lock_time = reader.read_u32::<LittleEndian>()?;
let expiry_height = match is_overwinter_v3 || is_sapling_v4 {
true => reader.read_u32::<LittleEndian>()?,
false => 0,
};
let (value_balance, shielded_spends, shielded_outputs) = if is_sapling_v4 {
let vb = Amount::read_i64(&mut reader, true)?;
let ss = Vector::read(&mut reader, SpendDescription::read)?;
let so = Vector::read(&mut reader, OutputDescription::read)?;
(vb, ss, so)
} else {
(Amount(0), vec![], vec![])
};
let (joinsplits, joinsplit_pubkey, joinsplit_sig) = if version >= 2 {
let jss = Vector::read(&mut reader, |r| {
JSDescription::read(r, overwintered && version >= SAPLING_TX_VERSION)
})?;
let (pubkey, sig) = if !jss.is_empty() {
let mut joinsplit_pubkey = [0; 32];
let mut joinsplit_sig = [0; 64];
reader.read_exact(&mut joinsplit_pubkey)?;
reader.read_exact(&mut joinsplit_sig)?;
(Some(joinsplit_pubkey), Some(joinsplit_sig))
} else {
(None, None)
};
(jss, pubkey, sig)
} else {
(vec![], None, None)
};
let binding_sig =
match is_sapling_v4 && !(shielded_spends.is_empty() && shielded_outputs.is_empty()) {
true => Some(Signature::read(&mut reader)?),
false => None,
};
Ok(Transaction(TransactionData {
overwintered,
version,
version_group_id,
vin,
vout,
lock_time,
expiry_height,
value_balance,
shielded_spends,
shielded_outputs,
joinsplits,
joinsplit_pubkey,
joinsplit_sig,
binding_sig,
}))
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_u32::<LittleEndian>(self.header())?;
if self.overwintered {
writer.write_u32::<LittleEndian>(self.version_group_id)?;
}
let is_overwinter_v3 = self.overwintered
&& self.version_group_id == OVERWINTER_VERSION_GROUP_ID
&& self.version == OVERWINTER_TX_VERSION;
let is_sapling_v4 = self.overwintered
&& self.version_group_id == SAPLING_VERSION_GROUP_ID
&& self.version == SAPLING_TX_VERSION;
if self.overwintered && !(is_overwinter_v3 || is_sapling_v4) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Unknown transaction format",
));
}
Vector::write(&mut writer, &self.vin, |w, e| e.write(w))?;
Vector::write(&mut writer, &self.vout, |w, e| e.write(w))?;
writer.write_u32::<LittleEndian>(self.lock_time)?;
if is_overwinter_v3 || is_sapling_v4 {
writer.write_u32::<LittleEndian>(self.expiry_height)?;
}
if is_sapling_v4 {
writer.write_i64::<LittleEndian>(self.value_balance.0)?;
Vector::write(&mut writer, &self.shielded_spends, |w, e| e.write(w))?;
Vector::write(&mut writer, &self.shielded_outputs, |w, e| e.write(w))?;
}
if self.version >= 2 {
Vector::write(&mut writer, &self.joinsplits, |w, e| e.write(w))?;
if !self.joinsplits.is_empty() {
match self.joinsplit_pubkey {
Some(pubkey) => writer.write_all(&pubkey)?,
None => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Missing JoinSplit pubkey",
))
}
}
match self.joinsplit_sig {
Some(sig) => writer.write_all(&sig)?,
None => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Missing JoinSplit signature",
))
}
}
}
}
if self.version < 2 || self.joinsplits.is_empty() {
if self.joinsplit_pubkey.is_some() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"JoinSplit pubkey should not be present",
));
}
if self.joinsplit_sig.is_some() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"JoinSplit signature should not be present",
));
}
}
if is_sapling_v4 && !(self.shielded_spends.is_empty() && self.shielded_outputs.is_empty()) {
match self.binding_sig {
Some(sig) => sig.write(&mut writer)?,
None => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Missing binding signature",
))
}
}
} else if self.binding_sig.is_some() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Binding signature should not be present",
));
}
Ok(())
}
}

View File

@ -0,0 +1,233 @@
use blake2_rfc::blake2b::Blake2b;
use byteorder::{LittleEndian, WriteBytesExt};
use pairing::{PrimeField, PrimeFieldRepr};
use super::{
components::{Amount, Script, TxOut},
Transaction, TransactionData, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION,
SAPLING_VERSION_GROUP_ID,
};
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &'static [u8; 12] = b"ZcashSigHash";
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &'static [u8; 16] = b"ZcashPrevoutHash";
const ZCASH_SEQUENCE_HASH_PERSONALIZATION: &'static [u8; 16] = b"ZcashSequencHash";
const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &'static [u8; 16] = b"ZcashOutputsHash";
const ZCASH_JOINSPLITS_HASH_PERSONALIZATION: &'static [u8; 16] = b"ZcashJSplitsHash";
const ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION: &'static [u8; 16] = b"ZcashSSpendsHash";
const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &'static [u8; 16] = b"ZcashSOutputHash";
pub const SIGHASH_ALL: u32 = 1;
const SIGHASH_NONE: u32 = 2;
const SIGHASH_SINGLE: u32 = 3;
const SIGHASH_MASK: u32 = 0x1f;
const SIGHASH_ANYONECANPAY: u32 = 0x80;
macro_rules! update_u32 {
($h:expr, $value:expr, $tmp:expr) => {
(&mut $tmp[..4]).write_u32::<LittleEndian>($value).unwrap();
$h.update(&$tmp[..4]);
};
}
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 {
$h.update(&$value);
} else {
$h.update(&[0; 32]);
}
};
}
#[derive(PartialEq)]
enum SigHashVersion {
Sprout,
Overwinter,
Sapling,
}
impl SigHashVersion {
fn from_tx(tx: &TransactionData) -> Self {
if tx.overwintered {
match tx.version_group_id {
OVERWINTER_VERSION_GROUP_ID => SigHashVersion::Overwinter,
SAPLING_VERSION_GROUP_ID => SigHashVersion::Sapling,
_ => unimplemented!(),
}
} else {
SigHashVersion::Sprout
}
}
}
fn prevout_hash(tx: &TransactionData) -> Vec<u8> {
let mut data = Vec::with_capacity(tx.vin.len() * 36);
for t_in in &tx.vin {
t_in.prevout.write(&mut data).unwrap();
}
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_PREVOUTS_HASH_PERSONALIZATION);
h.update(&data);
h.finalize().as_ref().to_vec()
}
fn sequence_hash(tx: &TransactionData) -> Vec<u8> {
let mut data = Vec::with_capacity(tx.vin.len() * 4);
for t_in in &tx.vin {
(&mut data)
.write_u32::<LittleEndian>(t_in.sequence)
.unwrap();
}
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_SEQUENCE_HASH_PERSONALIZATION);
h.update(&data);
h.finalize().as_ref().to_vec()
}
fn outputs_hash(tx: &TransactionData) -> Vec<u8> {
let mut data = Vec::with_capacity(tx.vout.len() * (4 + 1));
for t_out in &tx.vout {
t_out.write(&mut data).unwrap();
}
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_OUTPUTS_HASH_PERSONALIZATION);
h.update(&data);
h.finalize().as_ref().to_vec()
}
fn single_output_hash(tx_out: &TxOut) -> Vec<u8> {
let mut data = vec![];
tx_out.write(&mut data).unwrap();
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_OUTPUTS_HASH_PERSONALIZATION);
h.update(&data);
h.finalize().as_ref().to_vec()
}
fn joinsplits_hash(tx: &TransactionData) -> Vec<u8> {
let mut data = Vec::with_capacity(
tx.joinsplits.len() * if tx.version < SAPLING_TX_VERSION {
1802 // JSDescription with PHGR13 proof
} else {
1698 // JSDescription with Groth16 proof
},
);
for js in &tx.joinsplits {
js.write(&mut data).unwrap();
}
data.extend_from_slice(&tx.joinsplit_pubkey.unwrap());
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_JOINSPLITS_HASH_PERSONALIZATION);
h.update(&data);
h.finalize().as_ref().to_vec()
}
fn shielded_spends_hash(tx: &TransactionData) -> Vec<u8> {
let mut data = Vec::with_capacity(tx.shielded_spends.len() * 384);
for s_spend in &tx.shielded_spends {
s_spend.cv.write(&mut data).unwrap();
s_spend.anchor.into_repr().write_le(&mut data).unwrap();
data.extend_from_slice(&s_spend.nullifier);
s_spend.rk.write(&mut data).unwrap();
data.extend_from_slice(&s_spend.zkproof);
}
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION);
h.update(&data);
h.finalize().as_ref().to_vec()
}
fn shielded_outputs_hash(tx: &TransactionData) -> Vec<u8> {
let mut data = Vec::with_capacity(tx.shielded_outputs.len() * 948);
for s_out in &tx.shielded_outputs {
s_out.write(&mut data).unwrap();
}
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION);
h.update(&data);
h.finalize().as_ref().to_vec()
}
pub fn signature_hash_data(
tx: &TransactionData,
consensus_branch_id: u32,
hash_type: u32,
transparent_input: Option<(usize, Script, Amount)>,
) -> Vec<u8> {
let sigversion = SigHashVersion::from_tx(tx);
match sigversion {
SigHashVersion::Overwinter | SigHashVersion::Sapling => {
let hash_outputs = if (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE
{
outputs_hash(tx)
} else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE
&& transparent_input.is_some()
&& transparent_input.as_ref().unwrap().0 < tx.vout.len()
{
single_output_hash(&tx.vout[transparent_input.as_ref().unwrap().0])
} else {
vec![0; 32]
};
let mut personal = [0; 16];
(&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
(&mut personal[12..])
.write_u32::<LittleEndian>(consensus_branch_id)
.unwrap();
let mut h = Blake2b::with_params(32, &[], &[], &personal);
let mut tmp = [0; 8];
update_u32!(h, tx.header(), tmp);
update_u32!(h, tx.version_group_id, tmp);
update_hash!(h, hash_type & SIGHASH_ANYONECANPAY == 0, prevout_hash(tx));
update_hash!(
h,
hash_type & SIGHASH_ANYONECANPAY == 0
&& (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE,
sequence_hash(tx)
);
h.update(&hash_outputs);
update_hash!(h, !tx.joinsplits.is_empty(), joinsplits_hash(tx));
if sigversion == SigHashVersion::Sapling {
update_hash!(h, !tx.shielded_spends.is_empty(), shielded_spends_hash(tx));
update_hash!(
h,
!tx.shielded_outputs.is_empty(),
shielded_outputs_hash(tx)
);
}
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);
}
update_u32!(h, hash_type, tmp);
if let Some((n, script_code, amount)) = transparent_input {
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();
(&mut data)
.write_u32::<LittleEndian>(tx.vin[n].sequence)
.unwrap();
h.update(&data);
}
h.finalize().as_ref().to_vec()
}
SigHashVersion::Sprout => unimplemented!(),
}
}
pub fn signature_hash(
tx: &Transaction,
consensus_branch_id: u32,
hash_type: u32,
transparent_input: Option<(usize, Script, Amount)>,
) -> Vec<u8> {
signature_hash_data(tx, consensus_branch_id, hash_type, transparent_input)
}

File diff suppressed because it is too large Load Diff