Make Amount opaque, and use it more
This helps to ensure type-safety of values that are required to satisfy zatoshi range bounds.
This commit is contained in:
parent
ab60b8804a
commit
59ed258c7f
|
@ -63,6 +63,7 @@ use zcash_primitives::{
|
||||||
merkle_tree::CommitmentTreeWitness,
|
merkle_tree::CommitmentTreeWitness,
|
||||||
note_encryption::sapling_ka_agree,
|
note_encryption::sapling_ka_agree,
|
||||||
sapling::{merkle_hash, spend_sig},
|
sapling::{merkle_hash, spend_sig},
|
||||||
|
transaction::components::Amount,
|
||||||
zip32, JUBJUB,
|
zip32, JUBJUB,
|
||||||
};
|
};
|
||||||
use zcash_proofs::{
|
use zcash_proofs::{
|
||||||
|
@ -704,6 +705,11 @@ pub extern "system" fn librustzcash_sapling_final_check(
|
||||||
binding_sig: *const [c_uchar; 64],
|
binding_sig: *const [c_uchar; 64],
|
||||||
sighash_value: *const [c_uchar; 32],
|
sighash_value: *const [c_uchar; 32],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
let value_balance = match Amount::from_i64(value_balance, true) {
|
||||||
|
Ok(vb) => vb,
|
||||||
|
Err(()) => return false,
|
||||||
|
};
|
||||||
|
|
||||||
// Deserialize the signature
|
// Deserialize the signature
|
||||||
let binding_sig = match Signature::read(&(unsafe { &*binding_sig })[..]) {
|
let binding_sig = match Signature::read(&(unsafe { &*binding_sig })[..]) {
|
||||||
Ok(sig) => sig,
|
Ok(sig) => sig,
|
||||||
|
@ -1022,6 +1028,11 @@ pub extern "system" fn librustzcash_sapling_binding_sig(
|
||||||
sighash: *const [c_uchar; 32],
|
sighash: *const [c_uchar; 32],
|
||||||
result: *mut [c_uchar; 64],
|
result: *mut [c_uchar; 64],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
let value_balance = match Amount::from_i64(value_balance, true) {
|
||||||
|
Ok(vb) => vb,
|
||||||
|
Err(()) => return false,
|
||||||
|
};
|
||||||
|
|
||||||
// Sign
|
// Sign
|
||||||
let sig = match unsafe { &*ctx }.binding_sig(value_balance, unsafe { &*sighash }, &JUBJUB) {
|
let sig = match unsafe { &*ctx }.binding_sig(value_balance, unsafe { &*sighash }, &JUBJUB) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
|
|
|
@ -8,7 +8,9 @@ use sapling_crypto::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
merkle_tree::CommitmentTreeWitness, sapling::Node, transaction::components::GROTH_PROOF_SIZE,
|
merkle_tree::CommitmentTreeWitness,
|
||||||
|
sapling::Node,
|
||||||
|
transaction::components::{Amount, GROTH_PROOF_SIZE},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Interface for creating zero-knowledge proofs for shielded transactions.
|
/// Interface for creating zero-knowledge proofs for shielded transactions.
|
||||||
|
@ -63,7 +65,7 @@ pub trait TxProver {
|
||||||
fn binding_sig(
|
fn binding_sig(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Self::SaplingProvingContext,
|
ctx: &mut Self::SaplingProvingContext,
|
||||||
value_balance: i64,
|
value_balance: Amount,
|
||||||
sighash: &[u8; 32],
|
sighash: &[u8; 32],
|
||||||
) -> Result<Signature, ()>;
|
) -> Result<Signature, ()>;
|
||||||
}
|
}
|
||||||
|
@ -80,8 +82,10 @@ pub(crate) mod mock {
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
merkle_tree::CommitmentTreeWitness, sapling::Node,
|
merkle_tree::CommitmentTreeWitness,
|
||||||
transaction::components::GROTH_PROOF_SIZE, JUBJUB,
|
sapling::Node,
|
||||||
|
transaction::components::{Amount, GROTH_PROOF_SIZE},
|
||||||
|
JUBJUB,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::TxProver;
|
use super::TxProver;
|
||||||
|
@ -153,7 +157,7 @@ pub(crate) mod mock {
|
||||||
fn binding_sig(
|
fn binding_sig(
|
||||||
&self,
|
&self,
|
||||||
_ctx: &mut Self::SaplingProvingContext,
|
_ctx: &mut Self::SaplingProvingContext,
|
||||||
_value_balance: i64,
|
_value_balance: Amount,
|
||||||
_sighash: &[u8; 32],
|
_sighash: &[u8; 32],
|
||||||
) -> Result<Signature, ()> {
|
) -> Result<Signature, ()> {
|
||||||
Err(())
|
Err(())
|
||||||
|
|
|
@ -18,13 +18,12 @@ use crate::{
|
||||||
prover::TxProver,
|
prover::TxProver,
|
||||||
sapling::{spend_sig, Node},
|
sapling::{spend_sig, Node},
|
||||||
transaction::{
|
transaction::{
|
||||||
components::{Amount, OutputDescription, SpendDescription, TxOut},
|
components::{amount::DEFAULT_FEE, Amount, OutputDescription, SpendDescription, TxOut},
|
||||||
signature_hash_data, Transaction, TransactionData, SIGHASH_ALL,
|
signature_hash_data, Transaction, TransactionData, SIGHASH_ALL,
|
||||||
},
|
},
|
||||||
JUBJUB,
|
JUBJUB,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_FEE: Amount = Amount(10000);
|
|
||||||
const DEFAULT_TX_EXPIRY_DELTA: u32 = 20;
|
const DEFAULT_TX_EXPIRY_DELTA: u32 = 20;
|
||||||
|
|
||||||
/// If there are any shielded inputs, always have at least two shielded outputs, padding
|
/// If there are any shielded inputs, always have at least two shielded outputs, padding
|
||||||
|
@ -88,7 +87,7 @@ impl SaplingOutput {
|
||||||
let note = Note {
|
let note = Note {
|
||||||
g_d,
|
g_d,
|
||||||
pk_d: to.pk_d.clone(),
|
pk_d: to.pk_d.clone(),
|
||||||
value: value.0 as u64,
|
value: value.into(),
|
||||||
r: rcm,
|
r: rcm,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -252,7 +251,8 @@ impl<R: RngCore + CryptoRng> Builder<R> {
|
||||||
|
|
||||||
let alpha = Fs::random(&mut self.rng);
|
let alpha = Fs::random(&mut self.rng);
|
||||||
|
|
||||||
self.mtx.value_balance += Amount(note.value as i64);
|
self.mtx.value_balance +=
|
||||||
|
Amount::from_u64(note.value).map_err(|_| Error(ErrorKind::InvalidAmount))?;
|
||||||
|
|
||||||
self.spends.push(SpendDescriptionInfo {
|
self.spends.push(SpendDescriptionInfo {
|
||||||
extsk,
|
extsk,
|
||||||
|
@ -528,7 +528,7 @@ impl<R: RngCore + CryptoRng> Builder<R> {
|
||||||
}
|
}
|
||||||
self.mtx.binding_sig = Some(
|
self.mtx.binding_sig = Some(
|
||||||
prover
|
prover
|
||||||
.binding_sig(&mut ctx, self.mtx.value_balance.0, &sighash)
|
.binding_sig(&mut ctx, self.mtx.value_balance, &sighash)
|
||||||
.map_err(|()| Error(ErrorKind::BindingSig))?,
|
.map_err(|()| Error(ErrorKind::BindingSig))?,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -564,7 +564,7 @@ mod tests {
|
||||||
let to = extfvk.default_address().unwrap().1;
|
let to = extfvk.default_address().unwrap().1;
|
||||||
|
|
||||||
let mut builder = Builder::new(0);
|
let mut builder = Builder::new(0);
|
||||||
match builder.add_sapling_output(ovk, to, Amount(-1), None) {
|
match builder.add_sapling_output(ovk, to, Amount::from_i64(-1, true).unwrap(), None) {
|
||||||
Err(e) => assert_eq!(e.kind(), &ErrorKind::InvalidAmount),
|
Err(e) => assert_eq!(e.kind(), &ErrorKind::InvalidAmount),
|
||||||
Ok(_) => panic!("Should have failed"),
|
Ok(_) => panic!("Should have failed"),
|
||||||
}
|
}
|
||||||
|
@ -573,7 +573,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn fails_on_negative_transparent_output() {
|
fn fails_on_negative_transparent_output() {
|
||||||
let mut builder = Builder::new(0);
|
let mut builder = Builder::new(0);
|
||||||
match builder.add_transparent_output(&TransparentAddress::PublicKey([0; 20]), Amount(-1)) {
|
match builder.add_transparent_output(
|
||||||
|
&TransparentAddress::PublicKey([0; 20]),
|
||||||
|
Amount::from_i64(-1, true).unwrap(),
|
||||||
|
) {
|
||||||
Err(e) => assert_eq!(e.kind(), &ErrorKind::InvalidAmount),
|
Err(e) => assert_eq!(e.kind(), &ErrorKind::InvalidAmount),
|
||||||
Ok(_) => panic!("Should have failed"),
|
Ok(_) => panic!("Should have failed"),
|
||||||
}
|
}
|
||||||
|
@ -591,7 +594,10 @@ mod tests {
|
||||||
{
|
{
|
||||||
let builder = Builder::new(0);
|
let builder = Builder::new(0);
|
||||||
match builder.build(1, MockTxProver) {
|
match builder.build(1, MockTxProver) {
|
||||||
Err(e) => assert_eq!(e.kind(), &ErrorKind::ChangeIsNegative(Amount(-10000))),
|
Err(e) => assert_eq!(
|
||||||
|
e.kind(),
|
||||||
|
&ErrorKind::ChangeIsNegative(Amount::from_i64(-10000, true).unwrap())
|
||||||
|
),
|
||||||
Ok(_) => panic!("Should have failed"),
|
Ok(_) => panic!("Should have failed"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -605,10 +611,18 @@ mod tests {
|
||||||
{
|
{
|
||||||
let mut builder = Builder::new(0);
|
let mut builder = Builder::new(0);
|
||||||
builder
|
builder
|
||||||
.add_sapling_output(ovk.clone(), to.clone(), Amount(50000), None)
|
.add_sapling_output(
|
||||||
|
ovk.clone(),
|
||||||
|
to.clone(),
|
||||||
|
Amount::from_u64(50000).unwrap(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
match builder.build(1, MockTxProver) {
|
match builder.build(1, MockTxProver) {
|
||||||
Err(e) => assert_eq!(e.kind(), &ErrorKind::ChangeIsNegative(Amount(-60000))),
|
Err(e) => assert_eq!(
|
||||||
|
e.kind(),
|
||||||
|
&ErrorKind::ChangeIsNegative(Amount::from_i64(-60000, true).unwrap())
|
||||||
|
),
|
||||||
Ok(_) => panic!("Should have failed"),
|
Ok(_) => panic!("Should have failed"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -618,10 +632,16 @@ mod tests {
|
||||||
{
|
{
|
||||||
let mut builder = Builder::new(0);
|
let mut builder = Builder::new(0);
|
||||||
builder
|
builder
|
||||||
.add_transparent_output(&TransparentAddress::PublicKey([0; 20]), Amount(50000))
|
.add_transparent_output(
|
||||||
|
&TransparentAddress::PublicKey([0; 20]),
|
||||||
|
Amount::from_u64(50000).unwrap(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
match builder.build(1, MockTxProver) {
|
match builder.build(1, MockTxProver) {
|
||||||
Err(e) => assert_eq!(e.kind(), &ErrorKind::ChangeIsNegative(Amount(-60000))),
|
Err(e) => assert_eq!(
|
||||||
|
e.kind(),
|
||||||
|
&ErrorKind::ChangeIsNegative(Amount::from_i64(-60000, true).unwrap())
|
||||||
|
),
|
||||||
Ok(_) => panic!("Should have failed"),
|
Ok(_) => panic!("Should have failed"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -647,13 +667,24 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_sapling_output(ovk.clone(), to.clone(), Amount(30000), None)
|
.add_sapling_output(
|
||||||
|
ovk.clone(),
|
||||||
|
to.clone(),
|
||||||
|
Amount::from_u64(30000).unwrap(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_transparent_output(&TransparentAddress::PublicKey([0; 20]), Amount(20000))
|
.add_transparent_output(
|
||||||
|
&TransparentAddress::PublicKey([0; 20]),
|
||||||
|
Amount::from_u64(20000).unwrap(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
match builder.build(1, MockTxProver) {
|
match builder.build(1, MockTxProver) {
|
||||||
Err(e) => assert_eq!(e.kind(), &ErrorKind::ChangeIsNegative(Amount(-1))),
|
Err(e) => assert_eq!(
|
||||||
|
e.kind(),
|
||||||
|
&ErrorKind::ChangeIsNegative(Amount::from_i64(-1, true).unwrap())
|
||||||
|
),
|
||||||
Ok(_) => panic!("Should have failed"),
|
Ok(_) => panic!("Should have failed"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -678,10 +709,13 @@ mod tests {
|
||||||
.add_sapling_spend(extsk, to.diversifier, note2, witness2)
|
.add_sapling_spend(extsk, to.diversifier, note2, witness2)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_sapling_output(ovk, to, Amount(30000), None)
|
.add_sapling_output(ovk, to, Amount::from_u64(30000).unwrap(), None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_transparent_output(&TransparentAddress::PublicKey([0; 20]), Amount(20000))
|
.add_transparent_output(
|
||||||
|
&TransparentAddress::PublicKey([0; 20]),
|
||||||
|
Amount::from_u64(20000).unwrap(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
match builder.build(1, MockTxProver) {
|
match builder.build(1, MockTxProver) {
|
||||||
Err(e) => assert_eq!(e.kind(), &ErrorKind::BindingSig),
|
Err(e) => assert_eq!(e.kind(), &ErrorKind::BindingSig),
|
||||||
|
|
|
@ -10,7 +10,7 @@ use std::io::{self, Read, Write};
|
||||||
use legacy::Script;
|
use legacy::Script;
|
||||||
use JUBJUB;
|
use JUBJUB;
|
||||||
|
|
||||||
mod amount;
|
pub mod amount;
|
||||||
pub use self::amount::Amount;
|
pub use self::amount::Amount;
|
||||||
|
|
||||||
// π_A + π_B + π_C
|
// π_A + π_B + π_C
|
||||||
|
@ -76,7 +76,12 @@ pub struct TxOut {
|
||||||
|
|
||||||
impl TxOut {
|
impl TxOut {
|
||||||
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
|
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_i64_le_bytes(tmp, false)
|
||||||
|
}
|
||||||
|
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "value out of range"))?;
|
||||||
let script_pubkey = Script::read(&mut reader)?;
|
let script_pubkey = Script::read(&mut reader)?;
|
||||||
|
|
||||||
Ok(TxOut {
|
Ok(TxOut {
|
||||||
|
@ -86,7 +91,7 @@ impl TxOut {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
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)
|
self.script_pubkey.write(&mut writer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -298,10 +303,20 @@ impl std::fmt::Debug for JSDescription {
|
||||||
impl JSDescription {
|
impl JSDescription {
|
||||||
pub fn read<R: Read>(mut reader: R, use_groth: bool) -> io::Result<Self> {
|
pub fn read<R: Read>(mut reader: R, use_groth: bool) -> io::Result<Self> {
|
||||||
// Consensus rule (§4.3): Canonical encoding is enforced here
|
// 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
|
// 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
|
// Consensus rule (§4.3): One of vpub_old and vpub_new being zero is
|
||||||
// enforced by CheckTransactionWithoutProofVerification() in zcashd.
|
// enforced by CheckTransactionWithoutProofVerification() in zcashd.
|
||||||
|
@ -374,8 +389,8 @@ impl JSDescription {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||||
writer.write_i64::<LittleEndian>(self.vpub_old.0)?;
|
writer.write_all(&self.vpub_old.to_i64_le_bytes())?;
|
||||||
writer.write_i64::<LittleEndian>(self.vpub_new.0)?;
|
writer.write_all(&self.vpub_new.to_i64_le_bytes())?;
|
||||||
writer.write_all(&self.anchor)?;
|
writer.write_all(&self.anchor)?;
|
||||||
writer.write_all(&self.nullifiers[0])?;
|
writer.write_all(&self.nullifiers[0])?;
|
||||||
writer.write_all(&self.nullifiers[1])?;
|
writer.write_all(&self.nullifiers[1])?;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
|
||||||
use std::io::{self, Read};
|
|
||||||
use std::iter::Sum;
|
use std::iter::Sum;
|
||||||
use std::ops::{Add, AddAssign, Sub, SubAssign};
|
use std::ops::{Add, AddAssign, Sub, SubAssign};
|
||||||
|
|
||||||
const COIN: i64 = 1_0000_0000;
|
const COIN: i64 = 1_0000_0000;
|
||||||
const MAX_MONEY: i64 = 21_000_000 * COIN;
|
const MAX_MONEY: i64 = 21_000_000 * COIN;
|
||||||
|
|
||||||
|
pub const DEFAULT_FEE: Amount = Amount(10000);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct Amount(pub i64);
|
pub struct Amount(i64);
|
||||||
|
|
||||||
impl Amount {
|
impl Amount {
|
||||||
/// Returns a zero-valued Amount.
|
/// Returns a zero-valued Amount.
|
||||||
|
@ -15,38 +15,51 @@ impl Amount {
|
||||||
Amount(0)
|
Amount(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read an Amount from a signed 64-bit little-endian integer.
|
/// Creates an Amount from an i64.
|
||||||
pub fn read_i64<R: Read>(mut reader: R, allow_negative: bool) -> io::Result<Self> {
|
///
|
||||||
let amount = reader.read_i64::<LittleEndian>()?;
|
/// Returns an error if the amount is out of range.
|
||||||
|
pub fn from_i64(amount: i64, allow_negative: bool) -> Result<Self, ()> {
|
||||||
if 0 <= amount && amount <= MAX_MONEY {
|
if 0 <= amount && amount <= MAX_MONEY {
|
||||||
Ok(Amount(amount))
|
Ok(Amount(amount))
|
||||||
} else if allow_negative && -MAX_MONEY <= amount && amount < 0 {
|
} else if allow_negative && -MAX_MONEY <= amount && amount < 0 {
|
||||||
Ok(Amount(amount))
|
Ok(Amount(amount))
|
||||||
} else {
|
} else {
|
||||||
Err(io::Error::new(
|
Err(())
|
||||||
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.
|
/// Creates an Amount from a u64.
|
||||||
pub fn read_u64<R: Read>(mut reader: R) -> io::Result<Self> {
|
///
|
||||||
let amount = reader.read_u64::<LittleEndian>()?;
|
/// Returns an error if the amount is out of range.
|
||||||
|
pub fn from_u64(amount: u64) -> Result<Self, ()> {
|
||||||
if amount <= MAX_MONEY as u64 {
|
if amount <= MAX_MONEY as u64 {
|
||||||
Ok(Amount(amount as i64))
|
Ok(Amount(amount as i64))
|
||||||
} else {
|
} else {
|
||||||
Err(io::Error::new(
|
Err(())
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
"Amount not in {0..MAX_MONEY}",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads an Amount from a signed 64-bit little-endian integer.
|
||||||
|
///
|
||||||
|
/// Returns an error if the amount is out of range.
|
||||||
|
pub fn from_i64_le_bytes(bytes: [u8; 8], allow_negative: bool) -> Result<Self, ()> {
|
||||||
|
let amount = i64::from_le_bytes(bytes);
|
||||||
|
Amount::from_i64(amount, allow_negative)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads an Amount from an unsigned 64-bit little-endian integer.
|
||||||
|
///
|
||||||
|
/// Returns an error if the amount is out of range.
|
||||||
|
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 number is zero or
|
/// Returns `true` if `self` is positive and `false` if the number is zero or
|
||||||
/// negative.
|
/// negative.
|
||||||
pub const fn is_positive(self) -> bool {
|
pub const fn is_positive(self) -> bool {
|
||||||
|
@ -60,6 +73,18 @@ impl Amount {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
impl Add<Amount> for Amount {
|
||||||
type Output = Amount;
|
type Output = Amount;
|
||||||
|
|
||||||
|
@ -101,42 +126,54 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn amount_in_range() {
|
fn amount_in_range() {
|
||||||
let zero = b"\x00\x00\x00\x00\x00\x00\x00\x00";
|
let zero = b"\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||||
assert_eq!(Amount::read_u64(&zero[..]).unwrap(), Amount(0));
|
assert_eq!(Amount::from_u64_le_bytes(zero.clone()).unwrap(), Amount(0));
|
||||||
assert_eq!(Amount::read_i64(&zero[..], false).unwrap(), Amount(0));
|
assert_eq!(
|
||||||
assert_eq!(Amount::read_i64(&zero[..], true).unwrap(), Amount(0));
|
Amount::from_i64_le_bytes(zero.clone(), false).unwrap(),
|
||||||
|
Amount(0)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Amount::from_i64_le_bytes(zero.clone(), true).unwrap(),
|
||||||
|
Amount(0)
|
||||||
|
);
|
||||||
|
|
||||||
let neg_one = b"\xff\xff\xff\xff\xff\xff\xff\xff";
|
let neg_one = b"\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||||
assert!(Amount::read_u64(&neg_one[..]).is_err());
|
assert!(Amount::from_u64_le_bytes(neg_one.clone()).is_err());
|
||||||
assert!(Amount::read_i64(&neg_one[..], false).is_err());
|
assert!(Amount::from_i64_le_bytes(neg_one.clone(), false).is_err());
|
||||||
assert_eq!(Amount::read_i64(&neg_one[..], true).unwrap(), Amount(-1));
|
assert_eq!(
|
||||||
|
Amount::from_i64_le_bytes(neg_one.clone(), true).unwrap(),
|
||||||
|
Amount(-1)
|
||||||
|
);
|
||||||
|
|
||||||
let max_money = b"\x00\x40\x07\x5a\xf0\x75\x07\x00";
|
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!(
|
assert_eq!(
|
||||||
Amount::read_i64(&max_money[..], false).unwrap(),
|
Amount::from_u64_le_bytes(max_money.clone()).unwrap(),
|
||||||
Amount(MAX_MONEY)
|
Amount(MAX_MONEY)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Amount::read_i64(&max_money[..], true).unwrap(),
|
Amount::from_i64_le_bytes(max_money.clone(), false).unwrap(),
|
||||||
|
Amount(MAX_MONEY)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Amount::from_i64_le_bytes(max_money.clone(), true).unwrap(),
|
||||||
Amount(MAX_MONEY)
|
Amount(MAX_MONEY)
|
||||||
);
|
);
|
||||||
|
|
||||||
let max_money_p1 = b"\x01\x40\x07\x5a\xf0\x75\x07\x00";
|
let max_money_p1 = b"\x01\x40\x07\x5a\xf0\x75\x07\x00";
|
||||||
assert!(Amount::read_u64(&max_money_p1[..]).is_err());
|
assert!(Amount::from_u64_le_bytes(max_money_p1.clone()).is_err());
|
||||||
assert!(Amount::read_i64(&max_money_p1[..], false).is_err());
|
assert!(Amount::from_i64_le_bytes(max_money_p1.clone(), false).is_err());
|
||||||
assert!(Amount::read_i64(&max_money_p1[..], true).is_err());
|
assert!(Amount::from_i64_le_bytes(max_money_p1.clone(), true).is_err());
|
||||||
|
|
||||||
let neg_max_money = b"\x00\xc0\xf8\xa5\x0f\x8a\xf8\xff";
|
let neg_max_money = b"\x00\xc0\xf8\xa5\x0f\x8a\xf8\xff";
|
||||||
assert!(Amount::read_u64(&neg_max_money[..]).is_err());
|
assert!(Amount::from_u64_le_bytes(neg_max_money.clone()).is_err());
|
||||||
assert!(Amount::read_i64(&neg_max_money[..], false).is_err());
|
assert!(Amount::from_i64_le_bytes(neg_max_money.clone(), false).is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Amount::read_i64(&neg_max_money[..], true).unwrap(),
|
Amount::from_i64_le_bytes(neg_max_money.clone(), true).unwrap(),
|
||||||
Amount(-MAX_MONEY)
|
Amount(-MAX_MONEY)
|
||||||
);
|
);
|
||||||
|
|
||||||
let neg_max_money_m1 = b"\xff\xbf\xf8\xa5\x0f\x8a\xf8\xff";
|
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::from_u64_le_bytes(neg_max_money_m1.clone()).is_err());
|
||||||
assert!(Amount::read_i64(&neg_max_money_m1[..], false).is_err());
|
assert!(Amount::from_i64_le_bytes(neg_max_money_m1.clone(), false).is_err());
|
||||||
assert!(Amount::read_i64(&neg_max_money_m1[..], true).is_err());
|
assert!(Amount::from_i64_le_bytes(neg_max_money_m1.clone(), true).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,7 +185,12 @@ impl Transaction {
|
||||||
};
|
};
|
||||||
|
|
||||||
let (value_balance, shielded_spends, shielded_outputs) = if is_sapling_v4 {
|
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, true)
|
||||||
|
}
|
||||||
|
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "valueBalance out of range"))?;
|
||||||
let ss = Vector::read(&mut reader, SpendDescription::read)?;
|
let ss = Vector::read(&mut reader, SpendDescription::read)?;
|
||||||
let so = Vector::read(&mut reader, OutputDescription::read)?;
|
let so = Vector::read(&mut reader, OutputDescription::read)?;
|
||||||
(vb, ss, so)
|
(vb, ss, so)
|
||||||
|
@ -262,7 +267,7 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_sapling_v4 {
|
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_spends, |w, e| e.write(w))?;
|
||||||
Vector::write(&mut writer, &self.shielded_outputs, |w, e| e.write(w))?;
|
Vector::write(&mut writer, &self.shielded_outputs, |w, e| e.write(w))?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,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 {
|
macro_rules! update_hash {
|
||||||
($h:expr, $cond:expr, $value:expr) => {
|
($h:expr, $cond:expr, $value:expr) => {
|
||||||
if $cond {
|
if $cond {
|
||||||
|
@ -214,7 +207,7 @@ pub fn signature_hash_data(
|
||||||
update_u32!(h, tx.lock_time, tmp);
|
update_u32!(h, tx.lock_time, tmp);
|
||||||
update_u32!(h, tx.expiry_height, tmp);
|
update_u32!(h, tx.expiry_height, tmp);
|
||||||
if sigversion == SigHashVersion::Sapling {
|
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);
|
update_u32!(h, hash_type, tmp);
|
||||||
|
|
||||||
|
@ -222,7 +215,7 @@ pub fn signature_hash_data(
|
||||||
let mut data = vec![];
|
let mut data = vec![];
|
||||||
tx.vin[n].prevout.write(&mut data).unwrap();
|
tx.vin[n].prevout.write(&mut data).unwrap();
|
||||||
script_code.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)
|
(&mut data)
|
||||||
.write_u32::<LittleEndian>(tx.vin[n].sequence)
|
.write_u32::<LittleEndian>(tx.vin[n].sequence)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -1902,7 +1902,11 @@ fn zip_0143() {
|
||||||
for tv in test_vectors {
|
for tv in test_vectors {
|
||||||
let tx = Transaction::read(&tv.tx[..]).unwrap();
|
let tx = Transaction::read(&tv.tx[..]).unwrap();
|
||||||
let transparent_input = if let Some(n) = tv.transparent_input {
|
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_i64(tv.amount, false).unwrap(),
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -5393,7 +5397,11 @@ fn zip_0243() {
|
||||||
for tv in test_vectors {
|
for tv in test_vectors {
|
||||||
let tx = Transaction::read(&tv.tx[..]).unwrap();
|
let tx = Transaction::read(&tv.tx[..]).unwrap();
|
||||||
let transparent_input = if let Some(n) = tv.transparent_input {
|
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_i64(tv.amount, false).unwrap(),
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,8 +10,11 @@ use sapling_crypto::{
|
||||||
};
|
};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
merkle_tree::CommitmentTreeWitness, prover::TxProver, sapling::Node,
|
merkle_tree::CommitmentTreeWitness,
|
||||||
transaction::components::GROTH_PROOF_SIZE, JUBJUB,
|
prover::TxProver,
|
||||||
|
sapling::Node,
|
||||||
|
transaction::components::{Amount, GROTH_PROOF_SIZE},
|
||||||
|
JUBJUB,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{load_parameters, sapling::SaplingProvingContext};
|
use crate::{load_parameters, sapling::SaplingProvingContext};
|
||||||
|
@ -182,7 +185,7 @@ impl TxProver for LocalTxProver {
|
||||||
fn binding_sig(
|
fn binding_sig(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Self::SaplingProvingContext,
|
ctx: &mut Self::SaplingProvingContext,
|
||||||
value_balance: i64,
|
value_balance: Amount,
|
||||||
sighash: &[u8; 32],
|
sighash: &[u8; 32],
|
||||||
) -> Result<Signature, ()> {
|
) -> Result<Signature, ()> {
|
||||||
ctx.binding_sig(value_balance, sighash, &JUBJUB)
|
ctx.binding_sig(value_balance, sighash, &JUBJUB)
|
||||||
|
|
|
@ -2,6 +2,7 @@ use pairing::bls12_381::Bls12;
|
||||||
use sapling_crypto::jubjub::{
|
use sapling_crypto::jubjub::{
|
||||||
edwards, fs::FsRepr, FixedGenerators, JubjubBls12, JubjubParams, Unknown,
|
edwards, fs::FsRepr, FixedGenerators, JubjubBls12, JubjubParams, Unknown,
|
||||||
};
|
};
|
||||||
|
use zcash_primitives::transaction::components::Amount;
|
||||||
|
|
||||||
mod prover;
|
mod prover;
|
||||||
mod verifier;
|
mod verifier;
|
||||||
|
@ -11,12 +12,12 @@ pub use self::verifier::SaplingVerificationContext;
|
||||||
|
|
||||||
// This function computes `value` in the exponent of the value commitment base
|
// This function computes `value` in the exponent of the value commitment base
|
||||||
fn compute_value_balance(
|
fn compute_value_balance(
|
||||||
value: i64,
|
value: Amount,
|
||||||
params: &JubjubBls12,
|
params: &JubjubBls12,
|
||||||
) -> Option<edwards::Point<Bls12, Unknown>> {
|
) -> Option<edwards::Point<Bls12, Unknown>> {
|
||||||
// Compute the absolute value (failing if -i64::MAX is
|
// Compute the absolute value (failing if -i64::MAX is
|
||||||
// the value)
|
// the value)
|
||||||
let abs = match value.checked_abs() {
|
let abs = match i64::from(value).checked_abs() {
|
||||||
Some(a) => a as u64,
|
Some(a) => a as u64,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,9 @@ use sapling_crypto::{
|
||||||
primitives::{Diversifier, Note, PaymentAddress, ProofGenerationKey, ValueCommitment},
|
primitives::{Diversifier, Note, PaymentAddress, ProofGenerationKey, ValueCommitment},
|
||||||
redjubjub::{PrivateKey, PublicKey, Signature},
|
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;
|
use super::compute_value_balance;
|
||||||
|
|
||||||
|
@ -245,7 +247,7 @@ impl SaplingProvingContext {
|
||||||
/// and output_proof() must be completed before calling this function.
|
/// and output_proof() must be completed before calling this function.
|
||||||
pub fn binding_sig(
|
pub fn binding_sig(
|
||||||
&self,
|
&self,
|
||||||
value_balance: i64,
|
value_balance: Amount,
|
||||||
sighash: &[u8; 32],
|
sighash: &[u8; 32],
|
||||||
params: &JubjubBls12,
|
params: &JubjubBls12,
|
||||||
) -> Result<Signature, ()> {
|
) -> Result<Signature, ()> {
|
||||||
|
|
|
@ -6,6 +6,7 @@ use sapling_crypto::{
|
||||||
jubjub::{edwards, FixedGenerators, JubjubBls12, Unknown},
|
jubjub::{edwards, FixedGenerators, JubjubBls12, Unknown},
|
||||||
redjubjub::{PublicKey, Signature},
|
redjubjub::{PublicKey, Signature},
|
||||||
};
|
};
|
||||||
|
use zcash_primitives::transaction::components::Amount;
|
||||||
|
|
||||||
use super::compute_value_balance;
|
use super::compute_value_balance;
|
||||||
|
|
||||||
|
@ -169,7 +170,7 @@ impl SaplingVerificationContext {
|
||||||
/// have been checked before calling this function.
|
/// have been checked before calling this function.
|
||||||
pub fn final_check(
|
pub fn final_check(
|
||||||
&self,
|
&self,
|
||||||
value_balance: i64,
|
value_balance: Amount,
|
||||||
sighash_value: &[u8; 32],
|
sighash_value: &[u8; 32],
|
||||||
binding_sig: Signature,
|
binding_sig: Signature,
|
||||||
params: &JubjubBls12,
|
params: &JubjubBls12,
|
||||||
|
|
Loading…
Reference in New Issue