librustzcash/zcash_primitives/src/transaction/components/orchard.rs

289 lines
9.0 KiB
Rust

/// Functions for parsing & serialization of Orchard transaction components.
use std::convert::TryFrom;
use std::io::{self, Read, Write};
use byteorder::{ReadBytesExt, WriteBytesExt};
use nonempty::NonEmpty;
use orchard::{
bundle::{Authorization, Authorized, Flags},
note::{ExtractedNoteCommitment, Nullifier, TransmittedNoteCiphertext},
primitives::redpallas::{self, SigType, Signature, SpendAuth, VerificationKey},
value::ValueCommitment,
Action, Anchor,
};
use zcash_encoding::{Array, CompactSize, Vector};
use super::Amount;
use crate::transaction::Transaction;
pub mod builder;
pub const FLAG_SPENDS_ENABLED: u8 = 0b0000_0001;
pub const FLAG_OUTPUTS_ENABLED: u8 = 0b0000_0010;
pub const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED);
/// Marker for a bundle with no proofs or signatures.
pub type Unauthorized = orchard::builder::InProgress<orchard::builder::Unproven, orchard::builder::Unauthorized>;
pub trait MapAuth<A: Authorization, B: Authorization> {
fn map_spend_auth(&self, s: A::SpendAuth) -> B::SpendAuth;
fn map_authorization(&self, a: A) -> B;
}
/// Reads an [`orchard::Bundle`] from a v5 transaction format.
pub fn read_v5_bundle<R: Read>(
mut reader: R,
) -> io::Result<Option<orchard::Bundle<Authorized, Amount>>> {
#[allow(clippy::redundant_closure)]
let actions_without_auth = Vector::read(&mut reader, |r| read_action_without_auth(r))?;
if actions_without_auth.is_empty() {
Ok(None)
} else {
let flags = read_flags(&mut reader)?;
let value_balance = Transaction::read_amount(&mut reader)?;
let anchor = read_anchor(&mut reader)?;
let proof_bytes = Vector::read(&mut reader, |r| r.read_u8())?;
let actions = NonEmpty::from_vec(
actions_without_auth
.into_iter()
.map(|act| act.try_map(|_| read_signature::<_, redpallas::SpendAuth>(&mut reader)))
.collect::<Result<Vec<_>, _>>()?,
)
.expect("A nonzero number of actions was read from the transaction data.");
let binding_signature = read_signature::<_, redpallas::Binding>(&mut reader)?;
let authorization = orchard::bundle::Authorized::from_parts(
orchard::Proof::new(proof_bytes),
binding_signature,
);
Ok(Some(orchard::Bundle::from_parts(
actions,
flags,
value_balance,
anchor,
authorization,
)))
}
}
pub fn read_value_commitment<R: Read>(mut reader: R) -> io::Result<ValueCommitment> {
let mut bytes = [0u8; 32];
reader.read_exact(&mut bytes)?;
let cv = ValueCommitment::from_bytes(&bytes);
if cv.is_none().into() {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid Pallas point for value commitment".to_owned(),
))
} else {
Ok(cv.unwrap())
}
}
pub fn read_nullifier<R: Read>(mut reader: R) -> io::Result<Nullifier> {
let mut bytes = [0u8; 32];
reader.read_exact(&mut bytes)?;
let nullifier_ctopt = Nullifier::from_bytes(&bytes);
if nullifier_ctopt.is_none().into() {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid Pallas point for nullifier".to_owned(),
))
} else {
Ok(nullifier_ctopt.unwrap())
}
}
pub fn read_verification_key<R: Read>(mut reader: R) -> io::Result<VerificationKey<SpendAuth>> {
let mut bytes = [0u8; 32];
reader.read_exact(&mut bytes)?;
VerificationKey::try_from(bytes).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"invalid verification key".to_owned(),
)
})
}
pub fn read_cmx<R: Read>(mut reader: R) -> io::Result<ExtractedNoteCommitment> {
let mut bytes = [0u8; 32];
reader.read_exact(&mut bytes)?;
let cmx = ExtractedNoteCommitment::from_bytes(&bytes);
Option::from(cmx).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"invalid Pallas base for field cmx".to_owned(),
)
})
}
pub fn read_note_ciphertext<R: Read>(mut reader: R) -> io::Result<TransmittedNoteCiphertext> {
let mut tnc = TransmittedNoteCiphertext {
epk_bytes: [0u8; 32],
enc_ciphertext: [0u8; 580],
out_ciphertext: [0u8; 80],
};
reader.read_exact(&mut tnc.epk_bytes)?;
reader.read_exact(&mut tnc.enc_ciphertext)?;
reader.read_exact(&mut tnc.out_ciphertext)?;
Ok(tnc)
}
pub fn read_action_without_auth<R: Read>(mut reader: R) -> io::Result<Action<()>> {
let cv_net = read_value_commitment(&mut reader)?;
let nf_old = read_nullifier(&mut reader)?;
let rk = read_verification_key(&mut reader)?;
let cmx = read_cmx(&mut reader)?;
let encrypted_note = read_note_ciphertext(&mut reader)?;
Ok(Action::from_parts(
nf_old,
rk,
cmx,
encrypted_note,
cv_net,
(),
))
}
pub fn read_flags<R: Read>(mut reader: R) -> io::Result<Flags> {
let mut byte = [0u8; 1];
reader.read_exact(&mut byte)?;
Flags::from_byte(byte[0]).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"invalid Orchard flags".to_owned(),
)
})
}
pub fn read_anchor<R: Read>(mut reader: R) -> io::Result<Anchor> {
let mut bytes = [0u8; 32];
reader.read_exact(&mut bytes)?;
Option::from(Anchor::from_bytes(bytes)).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"invalid Orchard anchor".to_owned(),
)
})
}
pub fn read_signature<R: Read, T: SigType>(mut reader: R) -> io::Result<Signature<T>> {
let mut bytes = [0u8; 64];
reader.read_exact(&mut bytes)?;
Ok(Signature::from(bytes))
}
/// Writes an [`orchard::Bundle`] in the v5 transaction format.
pub fn write_v5_bundle<W: Write>(
bundle: Option<&orchard::Bundle<Authorized, Amount>>,
mut writer: W,
) -> io::Result<()> {
if let Some(bundle) = &bundle {
Vector::write_nonempty(&mut writer, bundle.actions(), |w, a| {
write_action_without_auth(w, a)
})?;
writer.write_all(&[bundle.flags().to_byte()])?;
writer.write_all(&bundle.value_balance().to_i64_le_bytes())?;
writer.write_all(&bundle.anchor().to_bytes())?;
Vector::write(
&mut writer,
bundle.authorization().proof().as_ref(),
|w, b| w.write_u8(*b),
)?;
Array::write(
&mut writer,
bundle.actions().iter().map(|a| a.authorization()),
|w, auth| w.write_all(&<[u8; 64]>::from(*auth)),
)?;
writer.write_all(&<[u8; 64]>::from(
bundle.authorization().binding_signature(),
))?;
} else {
CompactSize::write(&mut writer, 0)?;
}
Ok(())
}
pub fn write_value_commitment<W: Write>(mut writer: W, cv: &ValueCommitment) -> io::Result<()> {
writer.write_all(&cv.to_bytes())
}
pub fn write_nullifier<W: Write>(mut writer: W, nf: &Nullifier) -> io::Result<()> {
writer.write_all(&nf.to_bytes())
}
pub fn write_verification_key<W: Write>(
mut writer: W,
rk: &redpallas::VerificationKey<SpendAuth>,
) -> io::Result<()> {
writer.write_all(&<[u8; 32]>::from(rk))
}
pub fn write_cmx<W: Write>(mut writer: W, cmx: &ExtractedNoteCommitment) -> io::Result<()> {
writer.write_all(&cmx.to_bytes())
}
pub fn write_note_ciphertext<W: Write>(
mut writer: W,
nc: &TransmittedNoteCiphertext,
) -> io::Result<()> {
writer.write_all(&nc.epk_bytes)?;
writer.write_all(&nc.enc_ciphertext)?;
writer.write_all(&nc.out_ciphertext)
}
pub fn write_action_without_auth<W: Write>(
mut writer: W,
act: &Action<<Authorized as Authorization>::SpendAuth>,
) -> io::Result<()> {
write_value_commitment(&mut writer, act.cv_net())?;
write_nullifier(&mut writer, act.nullifier())?;
write_verification_key(&mut writer, act.rk())?;
write_cmx(&mut writer, act.cmx())?;
write_note_ciphertext(&mut writer, act.encrypted_note())?;
Ok(())
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::prelude::*;
use orchard::bundle::{
testing::{self as t_orch},
Authorized, Bundle,
};
use crate::transaction::{
components::amount::{testing::arb_amount, Amount},
TxVersion,
};
prop_compose! {
pub fn arb_bundle(n_actions: usize)(
orchard_value_balance in arb_amount(),
bundle in t_orch::arb_bundle(n_actions)
) -> Bundle<Authorized, Amount> {
// overwrite the value balance, as we can't guarantee that the
// value doesn't exceed the MAX_MONEY bounds.
bundle.try_map_value_balance::<_, (), _>(|_| Ok(orchard_value_balance)).unwrap()
}
}
pub fn arb_bundle_for_version(
v: TxVersion,
) -> impl Strategy<Value = Option<Bundle<Authorized, Amount>>> {
if v.has_orchard() {
Strategy::boxed((1usize..100).prop_flat_map(|n| prop::option::of(arb_bundle(n))))
} else {
Strategy::boxed(Just(None))
}
}
}