Add v5 parsing and serialization for Sapling components.
This commit is contained in:
parent
1a5aad723b
commit
e828dbf5d0
|
@ -206,7 +206,7 @@ impl SpendDescription<Authorized> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
pub fn write_v4<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_all(&self.cv.to_bytes())?;
|
||||
writer.write_all(self.anchor.to_repr().as_ref())?;
|
||||
writer.write_all(&self.nullifier.0)?;
|
||||
|
@ -214,6 +214,45 @@ impl SpendDescription<Authorized> {
|
|||
writer.write_all(&self.zkproof)?;
|
||||
self.spend_auth_sig.write(&mut writer)
|
||||
}
|
||||
|
||||
pub fn write_v5_without_witness_data<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_all(&self.cv.to_bytes())?;
|
||||
writer.write_all(&self.nullifier.0)?;
|
||||
self.rk.write(&mut writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SpendDescriptionV5 {
|
||||
pub cv: jubjub::ExtendedPoint,
|
||||
pub nullifier: Nullifier,
|
||||
pub rk: PublicKey,
|
||||
}
|
||||
|
||||
impl SpendDescriptionV5 {
|
||||
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
|
||||
let cv = read_point(&mut reader, "cv")?;
|
||||
let nullifier = SpendDescription::read_nullifier(&mut reader)?;
|
||||
let rk = SpendDescription::read_rk(&mut reader)?;
|
||||
|
||||
Ok(SpendDescriptionV5 { cv, nullifier, rk })
|
||||
}
|
||||
|
||||
pub fn into_spend_description(
|
||||
self,
|
||||
anchor: bls12_381::Scalar,
|
||||
zkproof: GrothProofBytes,
|
||||
spend_auth_sig: Signature,
|
||||
) -> SpendDescription<Authorized> {
|
||||
SpendDescription {
|
||||
cv: self.cv,
|
||||
anchor,
|
||||
nullifier: self.nullifier,
|
||||
rk: self.rk,
|
||||
zkproof,
|
||||
spend_auth_sig,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -253,56 +292,27 @@ impl<A: Authorization> std::fmt::Debug for OutputDescription<A> {
|
|||
}
|
||||
|
||||
impl OutputDescription<Authorized> {
|
||||
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
|
||||
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 = {
|
||||
let mut bytes = [0u8; 32];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
let cv = jubjub::ExtendedPoint::from_bytes(&bytes);
|
||||
if cv.is_none().into() {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid cv"));
|
||||
}
|
||||
cv.unwrap()
|
||||
};
|
||||
let cv = read_point(&mut reader, "cv")?;
|
||||
|
||||
// Consensus rule (§7.4): Canonical encoding is enforced here
|
||||
let cmu = {
|
||||
let mut f = [0u8; 32];
|
||||
reader.read_exact(&mut f)?;
|
||||
bls12_381::Scalar::from_repr(f)
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "cmu not in field"))?
|
||||
};
|
||||
let cmu = read_base(&mut reader, "cmu")?;
|
||||
|
||||
// Consensus rules (§4.5):
|
||||
// - Canonical encoding is enforced here.
|
||||
// - "Not small order" is enforced in SaplingVerificationContext::check_output()
|
||||
let ephemeral_key = {
|
||||
let mut bytes = [0u8; 32];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
let ephemeral_key = jubjub::ExtendedPoint::from_bytes(&bytes);
|
||||
if ephemeral_key.is_none().into() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid ephemeral_key",
|
||||
));
|
||||
}
|
||||
ephemeral_key.unwrap()
|
||||
};
|
||||
let ephemeral_key = read_point(&mut reader, "ephemeral key")?;
|
||||
|
||||
let mut enc_ciphertext = [0u8; 580];
|
||||
let mut out_ciphertext = [0u8; 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 = [0u8; GROTH_PROOF_SIZE];
|
||||
reader.read_exact(&mut zkproof)?;
|
||||
let zkproof = read_zkproof(&mut reader)?;
|
||||
|
||||
Ok(OutputDescription {
|
||||
cv,
|
||||
|
@ -316,7 +326,7 @@ impl OutputDescription<Authorized> {
|
|||
}
|
||||
|
||||
impl<A: Authorization<Proof = GrothProofBytes>> OutputDescription<A> {
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
pub fn write_v4<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_all(&self.cv.to_bytes())?;
|
||||
writer.write_all(self.cmu.to_repr().as_ref())?;
|
||||
writer.write_all(&self.ephemeral_key.to_bytes())?;
|
||||
|
@ -324,6 +334,58 @@ impl<A: Authorization<Proof = GrothProofBytes>> OutputDescription<A> {
|
|||
writer.write_all(&self.out_ciphertext)?;
|
||||
writer.write_all(&self.zkproof)
|
||||
}
|
||||
|
||||
pub fn write_v5_without_proof<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_all(&self.cv.to_bytes())?;
|
||||
writer.write_all(self.cmu.to_repr().as_ref())?;
|
||||
writer.write_all(&self.ephemeral_key.to_bytes())?;
|
||||
writer.write_all(&self.enc_ciphertext)?;
|
||||
writer.write_all(&self.out_ciphertext)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OutputDescriptionV5 {
|
||||
pub cv: jubjub::ExtendedPoint,
|
||||
pub cmu: bls12_381::Scalar,
|
||||
pub ephemeral_key: jubjub::ExtendedPoint,
|
||||
pub enc_ciphertext: [u8; 580],
|
||||
pub out_ciphertext: [u8; 80],
|
||||
}
|
||||
|
||||
impl OutputDescriptionV5 {
|
||||
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
|
||||
let cv = read_point(&mut reader, "cv")?;
|
||||
let cmu = read_base(&mut reader, "cmu")?;
|
||||
let ephemeral_key = read_point(&mut reader, "ephemeral key")?;
|
||||
|
||||
let mut enc_ciphertext = [0u8; 580];
|
||||
let mut out_ciphertext = [0u8; 80];
|
||||
reader.read_exact(&mut enc_ciphertext)?;
|
||||
reader.read_exact(&mut out_ciphertext)?;
|
||||
|
||||
Ok(OutputDescriptionV5 {
|
||||
cv,
|
||||
cmu,
|
||||
ephemeral_key,
|
||||
enc_ciphertext,
|
||||
out_ciphertext,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_output_description(
|
||||
self,
|
||||
zkproof: GrothProofBytes,
|
||||
) -> OutputDescription<Authorized> {
|
||||
OutputDescription {
|
||||
cv: self.cv,
|
||||
cmu: self.cmu,
|
||||
ephemeral_key: self.ephemeral_key,
|
||||
enc_ciphertext: self.enc_ciphertext,
|
||||
out_ciphertext: self.out_ciphertext,
|
||||
zkproof,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputDescription<Unauthorized> {
|
||||
|
@ -368,3 +430,115 @@ impl<P: consensus::Parameters> ShieldedOutput<SaplingDomain<P>> for CompactOutpu
|
|||
&self.enc_ciphertext
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use ff::Field;
|
||||
use group::GroupEncoding;
|
||||
use proptest::collection::vec;
|
||||
use proptest::prelude::*;
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::{
|
||||
constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR},
|
||||
sapling::{
|
||||
redjubjub::{PrivateKey, PublicKey},
|
||||
Nullifier,
|
||||
},
|
||||
transaction::components::{amount::testing::arb_amount, GROTH_PROOF_SIZE},
|
||||
};
|
||||
|
||||
use super::{Authorized, Bundle, OutputDescription, SpendDescription};
|
||||
|
||||
prop_compose! {
|
||||
/// produce a spend description with invalid data (useful only for serialization
|
||||
/// roundtrip testing).
|
||||
fn arb_spend_description()(
|
||||
cv in prop::array::uniform32(any::<u8>())
|
||||
.prop_map(|v| jubjub::ExtendedPoint::from_bytes(&v))
|
||||
.prop_filter("Must generate valid extended points.", |v| v.is_some().unwrap_u8() == 1)
|
||||
.prop_map(|v| v.unwrap()),
|
||||
anchor in vec(any::<u8>(), 64)
|
||||
.prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap())
|
||||
.prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)),
|
||||
nullifier in prop::array::uniform32(any::<u8>())
|
||||
.prop_map(|v| Nullifier::from_slice(&v).unwrap()),
|
||||
zkproof in vec(any::<u8>(), GROTH_PROOF_SIZE)
|
||||
.prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()),
|
||||
rng_seed in prop::array::uniform32(prop::num::u8::ANY),
|
||||
fake_sighash_bytes in prop::array::uniform32(prop::num::u8::ANY),
|
||||
) -> SpendDescription<Authorized> {
|
||||
let mut rng = StdRng::from_seed(rng_seed);
|
||||
let sk1 = PrivateKey(jubjub::Fr::random(&mut rng));
|
||||
let rk = PublicKey::from_private(&sk1, SPENDING_KEY_GENERATOR);
|
||||
SpendDescription {
|
||||
cv,
|
||||
anchor,
|
||||
nullifier,
|
||||
rk,
|
||||
zkproof,
|
||||
spend_auth_sig: sk1.sign(&fake_sighash_bytes, &mut rng, SPENDING_KEY_GENERATOR),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
/// produce an output description with invalid data (useful only for serialization
|
||||
/// roundtrip testing).
|
||||
pub fn arb_output_description()(
|
||||
cv in prop::array::uniform32(any::<u8>())
|
||||
.prop_map(|v| jubjub::ExtendedPoint::from_bytes(&v))
|
||||
.prop_filter("Must generate valid extended points.", |v| v.is_some().unwrap_u8() == 1)
|
||||
.prop_map(|v| v.unwrap()),
|
||||
cmu in vec(any::<u8>(), 64)
|
||||
.prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap())
|
||||
.prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)),
|
||||
enc_ciphertext in vec(any::<u8>(), 580)
|
||||
.prop_map(|v| <[u8;580]>::try_from(v.as_slice()).unwrap()),
|
||||
ephemeral_key in prop::array::uniform32(any::<u8>())
|
||||
.prop_map(|v| jubjub::ExtendedPoint::from_bytes(&v))
|
||||
.prop_filter("Must generate valid extended points.", |v| v.is_some().unwrap_u8() == 1)
|
||||
.prop_map(|v| v.unwrap()),
|
||||
out_ciphertext in vec(any::<u8>(), 80)
|
||||
.prop_map(|v| <[u8;80]>::try_from(v.as_slice()).unwrap()),
|
||||
zkproof in vec(any::<u8>(), GROTH_PROOF_SIZE)
|
||||
.prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()),
|
||||
) -> OutputDescription<Authorized> {
|
||||
OutputDescription {
|
||||
cv,
|
||||
cmu,
|
||||
ephemeral_key,
|
||||
enc_ciphertext,
|
||||
out_ciphertext,
|
||||
zkproof,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
pub fn arb_bundle()(
|
||||
shielded_spends in vec(arb_spend_description(), 0..30),
|
||||
shielded_outputs in vec(arb_output_description(), 0..30),
|
||||
value_balance in arb_amount(),
|
||||
rng_seed in prop::array::uniform32(prop::num::u8::ANY),
|
||||
fake_bvk_bytes in prop::array::uniform32(prop::num::u8::ANY),
|
||||
) -> Option<Bundle<Authorized>> {
|
||||
let mut rng = StdRng::from_seed(rng_seed);
|
||||
let bsk = PrivateKey(jubjub::Fr::random(&mut rng));
|
||||
|
||||
if shielded_spends.is_empty() && shielded_outputs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
Bundle {
|
||||
shielded_spends,
|
||||
shielded_outputs,
|
||||
value_balance,
|
||||
authorization: Authorized { binding_sig: bsk.sign(&fake_bvk_bytes, &mut rng, VALUE_COMMITMENT_RANDOMNESS_GENERATOR) },
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -608,14 +608,14 @@ impl Transaction {
|
|||
self.sapling_bundle
|
||||
.as_ref()
|
||||
.map_or(&[], |b| &b.shielded_spends),
|
||||
|w, e| e.write(w),
|
||||
|w, e| e.write_v4(w),
|
||||
)?;
|
||||
Vector::write(
|
||||
&mut writer,
|
||||
self.sapling_bundle
|
||||
.as_ref()
|
||||
.map_or(&[], |b| &b.shielded_outputs),
|
||||
|w, e| e.write(w),
|
||||
|w, e| e.write_v4(w),
|
||||
)?;
|
||||
} else if self.sapling_bundle.is_some() {
|
||||
return Err(io::Error::new(
|
||||
|
|
|
@ -129,7 +129,7 @@ fn shielded_outputs_hash<A: sapling::Authorization<Proof = GrothProofBytes>>(
|
|||
) -> Blake2bHash {
|
||||
let mut data = Vec::with_capacity(shielded_outputs.len() * 948);
|
||||
for s_out in shielded_outputs {
|
||||
s_out.write(&mut data).unwrap();
|
||||
s_out.write_v4(&mut data).unwrap();
|
||||
}
|
||||
Blake2bParams::new()
|
||||
.hash_length(32)
|
||||
|
|
Loading…
Reference in New Issue