mirror of
https://github.com/Qortal/pirate-librustzcash.git
synced 2025-07-31 12:31:22 +00:00
431 lines
14 KiB
Rust
431 lines
14 KiB
Rust
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
|
use ff::{PrimeField, PrimeFieldRepr};
|
|
use pairing::bls12_381::{Bls12, Fr, FrRepr};
|
|
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());
|
|
}
|
|
}
|