pczt: Add fields necessary for creating proofs
This commit is contained in:
parent
8766c51142
commit
1a185afa06
|
@ -2867,6 +2867,7 @@ name = "pczt"
|
|||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bls12_381",
|
||||
"ff",
|
||||
"jubjub",
|
||||
"nonempty",
|
||||
"orchard",
|
||||
|
@ -3684,8 +3685,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "sapling-crypto"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfff8cfce16aeb38da50b8e2ed33c9018f30552beff2210c266662a021b17f38"
|
||||
source = "git+https://github.com/zcash/sapling-crypto.git?rev=a8fb276252894cf5d0e97e289f5509cc89bad095#a8fb276252894cf5d0e97e289f5509cc89bad095"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bellman",
|
||||
|
|
|
@ -182,3 +182,6 @@ debug = true
|
|||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("zfuture"))'] }
|
||||
|
||||
[patch.crates-io]
|
||||
sapling-crypto = { git = "https://github.com/zcash/sapling-crypto.git", rev = "a8fb276252894cf5d0e97e289f5509cc89bad095" }
|
||||
|
|
|
@ -20,6 +20,7 @@ rand_core = { workspace = true, optional = true }
|
|||
# Payment protocols
|
||||
# - Sapling
|
||||
bls12_381 = { workspace = true, optional = true }
|
||||
ff = { workspace = true, optional = true }
|
||||
jubjub = { workspace = true, optional = true }
|
||||
redjubjub = { workspace = true, optional = true }
|
||||
sapling = { workspace = true, optional = true }
|
||||
|
@ -38,6 +39,7 @@ orchard = [
|
|||
]
|
||||
sapling = [
|
||||
"dep:bls12_381",
|
||||
"dep:ff",
|
||||
"dep:jubjub",
|
||||
"dep:redjubjub",
|
||||
"dep:sapling",
|
||||
|
@ -45,5 +47,6 @@ sapling = [
|
|||
"dep:zcash_protocol",
|
||||
]
|
||||
transparent = ["dep:zcash_primitives", "dep:zcash_protocol"]
|
||||
prover = ["dep:rand_core", "sapling?/temporary-zcashd"]
|
||||
signer = ["dep:rand_core", "orchard", "sapling", "transparent"]
|
||||
tx-extractor = ["dep:rand_core", "orchard", "sapling", "transparent"]
|
||||
|
|
|
@ -10,9 +10,21 @@
|
|||
//! `Global.tx_modifiable` field. Inputs may only be added if the Inputs Modifiable
|
||||
//! flag is True. Outputs may only be added if the Outputs Modifiable flag is True.
|
||||
//! - A single entity is likely to be both a Creator and Constructor.
|
||||
//! - Prover (capability holders can contribute)
|
||||
//! - Needs all private information for a single spend or output.
|
||||
//! - In practice, the Updater that adds a given spend or output will either act as
|
||||
//! the Prover themselves, or add the necessary data, offload to the Prover, and
|
||||
//! then receive back the PCZT with private data stripped and proof added.
|
||||
//! - Signer (capability holders can contribute)
|
||||
//! - Needs the spend authorization randomizers to create signatures.
|
||||
//! - Needs sufficient information to verify that the proof is over the correct data,
|
||||
//! without needing to verify the proof itself.
|
||||
//! - A Signer should only need to implement:
|
||||
//! - Pedersen commitments using Jubjub / Pallas arithmetic (for note and value
|
||||
//! commitments)
|
||||
//! - BLAKE2b and BLAKE2s (and the various PRFs / CRHs they are used in)
|
||||
//! - Nullifier check (using Jubjub / Pallas arithmetic)
|
||||
//! - KDF plus note decryption (AEAD_CHACHA20_POLY1305)
|
||||
//! - SignatureHash algorithm
|
||||
//! - Signatures (RedJubjub / RedPallas)
|
||||
//! - A source of randomness.
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
use crate::roles::combiner::merge_optional;
|
||||
use crate::{roles::combiner::merge_optional, IgnoreMissing};
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
use pasta_curves::{group::ff::PrimeField, pallas};
|
||||
use {
|
||||
orchard::{
|
||||
note::{RandomSeed, Rho},
|
||||
value::NoteValue,
|
||||
Address, Note,
|
||||
},
|
||||
pasta_curves::{group::ff::PrimeField, pallas},
|
||||
};
|
||||
|
||||
/// PCZT fields that are specific to producing the transaction's Orchard bundle (if any).
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -58,6 +65,17 @@ pub(crate) struct Action {
|
|||
pub(crate) cv_net: [u8; 32],
|
||||
pub(crate) spend: Spend,
|
||||
pub(crate) output: Output,
|
||||
|
||||
/// The value commitment randomness.
|
||||
///
|
||||
/// - This is set by the Constructor.
|
||||
/// - The IO Finalizer compresses it into the bsk.
|
||||
/// - This is required by the Prover.
|
||||
/// - This may be used by Signers to verify that the value correctly matches `cv`.
|
||||
///
|
||||
/// This opens `cv` for all participants. For Signers who don't need this information,
|
||||
/// or after proofs / signatures have been applied, this can be redacted.
|
||||
pub(crate) rcv: Option<[u8; 32]>,
|
||||
}
|
||||
|
||||
/// Information about a Sapling spend within a transaction.
|
||||
|
@ -77,6 +95,48 @@ pub(crate) struct Spend {
|
|||
/// This is set by the Signer.
|
||||
pub(crate) spend_auth_sig: Option<[u8; 64]>,
|
||||
|
||||
/// The [raw encoding] of the Orchard payment address that received the note being spent.
|
||||
///
|
||||
/// - This is set by the Constructor.
|
||||
/// - This is required by the Prover.
|
||||
///
|
||||
/// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding
|
||||
pub(crate) recipient: Option<[u8; 43]>,
|
||||
|
||||
/// The value of the input being spent.
|
||||
///
|
||||
/// - This is required by the Prover.
|
||||
/// - This may be used by Signers to verify that the value matches `cv`, and to
|
||||
/// confirm the values and change involved in the transaction.
|
||||
///
|
||||
/// This exposes the input value to all participants. For Signers who don't need this
|
||||
/// information, or after signatures have been applied, this can be redacted.
|
||||
pub(crate) value: Option<u64>,
|
||||
|
||||
/// The rho value for the note being spent.
|
||||
///
|
||||
/// - This is set by the Constructor.
|
||||
/// - This is required by the Prover.
|
||||
pub(crate) rho: Option<[u8; 32]>,
|
||||
|
||||
/// The seed randomness for the note being spent.
|
||||
///
|
||||
/// - This is set by the Constructor.
|
||||
/// - This is required by the Prover.
|
||||
pub(crate) rseed: Option<[u8; 32]>,
|
||||
|
||||
/// The full viewing key that received the note being spent.
|
||||
///
|
||||
/// - This is set by the Updater.
|
||||
/// - This is required by the Prover.
|
||||
pub(crate) fvk: Option<[u8; 96]>,
|
||||
|
||||
/// A witness from the note to the bundle's anchor.
|
||||
///
|
||||
/// - This is set by the Updater.
|
||||
/// - This is required by the Prover.
|
||||
pub(crate) witness: Option<(u32, [[u8; 32]; 32])>,
|
||||
|
||||
/// The spend authorization randomizer.
|
||||
///
|
||||
/// - This is chosen by the Constructor.
|
||||
|
@ -115,6 +175,29 @@ pub(crate) struct Output {
|
|||
///
|
||||
/// Encoded as a `Vec<u8>` because its length depends on the transaction version.
|
||||
pub(crate) out_ciphertext: Vec<u8>,
|
||||
|
||||
/// The [raw encoding] of the Orchard payment address that will receive the output.
|
||||
///
|
||||
/// - This is set by the Constructor.
|
||||
/// - This is required by the Prover.
|
||||
///
|
||||
/// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding
|
||||
pub(crate) recipient: Option<[u8; 43]>,
|
||||
|
||||
/// The value of the output.
|
||||
///
|
||||
/// This may be used by Signers to verify that the value matches `cv`, and to confirm
|
||||
/// the values and change involved in the transaction.
|
||||
///
|
||||
/// This exposes the value to all participants. For Signers who don't need this
|
||||
/// information, we can drop the values and compress the rcvs into the bsk global.
|
||||
pub(crate) value: Option<u64>,
|
||||
|
||||
/// The seed randomness for the output.
|
||||
///
|
||||
/// - This is set by the Constructor.
|
||||
/// - This is required by the Prover, instead of disclosing `shared_secret` to them.
|
||||
pub(crate) rseed: Option<[u8; 32]>,
|
||||
}
|
||||
|
||||
impl Bundle {
|
||||
|
@ -180,6 +263,12 @@ impl Bundle {
|
|||
nullifier,
|
||||
rk,
|
||||
spend_auth_sig,
|
||||
recipient,
|
||||
value,
|
||||
rho,
|
||||
rseed,
|
||||
fvk,
|
||||
witness,
|
||||
alpha,
|
||||
dummy_sk,
|
||||
},
|
||||
|
@ -189,7 +278,11 @@ impl Bundle {
|
|||
ephemeral_key,
|
||||
enc_ciphertext,
|
||||
out_ciphertext,
|
||||
recipient: output_recipient,
|
||||
value: output_value,
|
||||
rseed: output_rseed,
|
||||
},
|
||||
rcv,
|
||||
} = rhs;
|
||||
|
||||
if lhs.cv_net != cv_net
|
||||
|
@ -204,8 +297,18 @@ impl Bundle {
|
|||
}
|
||||
|
||||
if !(merge_optional(&mut lhs.spend.spend_auth_sig, spend_auth_sig)
|
||||
&& merge_optional(&mut lhs.spend.recipient, recipient)
|
||||
&& merge_optional(&mut lhs.spend.value, value)
|
||||
&& merge_optional(&mut lhs.spend.rho, rho)
|
||||
&& merge_optional(&mut lhs.spend.rseed, rseed)
|
||||
&& merge_optional(&mut lhs.spend.fvk, fvk)
|
||||
&& merge_optional(&mut lhs.spend.witness, witness)
|
||||
&& merge_optional(&mut lhs.spend.alpha, alpha)
|
||||
&& merge_optional(&mut lhs.spend.dummy_sk, dummy_sk))
|
||||
&& merge_optional(&mut lhs.spend.dummy_sk, dummy_sk)
|
||||
&& merge_optional(&mut lhs.output.recipient, output_recipient)
|
||||
&& merge_optional(&mut lhs.output.value, output_value)
|
||||
&& merge_optional(&mut lhs.output.rseed, output_rseed)
|
||||
&& merge_optional(&mut lhs.rcv, rcv))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
@ -313,6 +416,57 @@ impl Bundle {
|
|||
|
||||
#[cfg(feature = "orchard")]
|
||||
impl Spend {
|
||||
/// Parses a [`Note`] from the explicit fields of this spend.
|
||||
pub(crate) fn note_from_fields(&self) -> Result<Note, Error> {
|
||||
// We want to parse all fields that are present for validity, before raising any
|
||||
// errors about missing fields. The only exception is that we raise an error for a
|
||||
// missing rho before validating rseed (as the latter requires rho).
|
||||
|
||||
let recipient = self
|
||||
.recipient
|
||||
.as_ref()
|
||||
.map(|r| {
|
||||
Address::from_raw_address_bytes(r)
|
||||
.into_option()
|
||||
.ok_or(Error::InvalidSpendRecipient)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let value = self.value.map(NoteValue::from_raw);
|
||||
|
||||
let rho = self
|
||||
.rho
|
||||
.as_ref()
|
||||
.map(|rho| Rho::from_bytes(rho).into_option().ok_or(Error::InvalidRho))
|
||||
.transpose()?
|
||||
.ok_or(Error::MissingRho)?;
|
||||
|
||||
let rseed = self
|
||||
.rseed
|
||||
.map(|rseed| {
|
||||
RandomSeed::from_bytes(rseed, &rho)
|
||||
.into_option()
|
||||
.ok_or(Error::InvalidRandomSeed)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
Note::from_parts(
|
||||
recipient.ok_or(Error::MissingSpendRecipient)?,
|
||||
value.ok_or(Error::MissingValue)?,
|
||||
rho,
|
||||
rseed.ok_or(Error::MissingRandomSeed)?,
|
||||
)
|
||||
.into_option()
|
||||
.ok_or(Error::InvalidSpendNote)
|
||||
}
|
||||
|
||||
pub(crate) fn fvk_from_field(&self) -> Result<orchard::keys::FullViewingKey, Error> {
|
||||
orchard::keys::FullViewingKey::from_bytes(
|
||||
self.fvk.as_ref().ok_or(Error::MissingFullViewingKey)?,
|
||||
)
|
||||
.ok_or(Error::InvalidFullViewingKey)
|
||||
}
|
||||
|
||||
pub(crate) fn alpha_from_field(&self) -> Result<pallas::Scalar, Error> {
|
||||
pallas::Scalar::from_repr(self.alpha.ok_or(Error::MissingSpendAuthRandomizer)?)
|
||||
.into_option()
|
||||
|
@ -326,12 +480,40 @@ pub enum Error {
|
|||
InvalidAnchor,
|
||||
InvalidEncCiphertext,
|
||||
InvalidExtractedNoteCommitment,
|
||||
InvalidFullViewingKey,
|
||||
InvalidNullifier,
|
||||
InvalidOutCiphertext,
|
||||
InvalidRandomizedKey,
|
||||
InvalidRandomSeed,
|
||||
InvalidRho,
|
||||
InvalidSpendAuthRandomizer,
|
||||
InvalidSpendNote,
|
||||
InvalidSpendRecipient,
|
||||
InvalidValueBalance(zcash_protocol::value::BalanceError),
|
||||
InvalidValueCommitment,
|
||||
MissingFullViewingKey,
|
||||
MissingRandomSeed,
|
||||
MissingRho,
|
||||
MissingSpendAuthRandomizer,
|
||||
MissingSpendRecipient,
|
||||
MissingValue,
|
||||
UnexpectedFlagBitsSet,
|
||||
}
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
impl<V> IgnoreMissing for Result<V, Error> {
|
||||
type Value = V;
|
||||
type Error = Error;
|
||||
|
||||
fn ignore_missing(self) -> Result<Option<Self::Value>, Self::Error> {
|
||||
self.map(Some).or_else(|e| match e {
|
||||
Error::MissingFullViewingKey
|
||||
| Error::MissingRandomSeed
|
||||
| Error::MissingRho
|
||||
| Error::MissingSpendAuthRandomizer
|
||||
| Error::MissingSpendRecipient
|
||||
| Error::MissingValue => Ok(None),
|
||||
_ => Err(e),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
pub mod creator;
|
||||
|
||||
#[cfg(feature = "prover")]
|
||||
pub mod prover;
|
||||
|
||||
#[cfg(feature = "signer")]
|
||||
pub mod signer;
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
use crate::Pczt;
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
mod orchard;
|
||||
#[cfg(feature = "orchard")]
|
||||
pub use orchard::OrchardError;
|
||||
|
||||
#[cfg(feature = "sapling")]
|
||||
mod sapling;
|
||||
#[cfg(feature = "sapling")]
|
||||
pub use sapling::SaplingError;
|
||||
|
||||
pub struct Prover {
|
||||
pczt: Pczt,
|
||||
}
|
||||
|
||||
impl Prover {
|
||||
/// Instantiates the Prover role with the given PCZT.
|
||||
pub fn new(pczt: Pczt) -> Self {
|
||||
Self { pczt }
|
||||
}
|
||||
|
||||
/// Finishes the Prover role, returning the updated PCZT.
|
||||
pub fn finish(self) -> Pczt {
|
||||
self.pczt
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
use orchard::{
|
||||
builder::SpendInfo,
|
||||
bundle::Flags,
|
||||
circuit::{Circuit, Instance, ProvingKey},
|
||||
note::{ExtractedNoteCommitment, Nullifier, RandomSeed, Rho},
|
||||
primitives::redpallas,
|
||||
tree::{MerkleHashOrchard, MerklePath},
|
||||
value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
|
||||
Address, Anchor, Note,
|
||||
};
|
||||
use rand_core::OsRng;
|
||||
|
||||
impl super::Prover {
|
||||
pub fn create_orchard_proof(&mut self, pk: &ProvingKey) -> Result<(), OrchardError> {
|
||||
let bundle = &self.pczt.orchard;
|
||||
|
||||
let flags =
|
||||
Flags::from_byte(bundle.flags).ok_or(crate::orchard::Error::UnexpectedFlagBitsSet)?;
|
||||
|
||||
let anchor = Anchor::from_bytes(bundle.anchor)
|
||||
.into_option()
|
||||
.ok_or(crate::orchard::Error::InvalidAnchor)?;
|
||||
|
||||
let circuits = self
|
||||
.pczt
|
||||
.orchard
|
||||
.actions
|
||||
.iter()
|
||||
.map(|action| {
|
||||
let fvk = action.spend.fvk_from_field()?;
|
||||
let note = action.spend.note_from_fields()?;
|
||||
|
||||
let merkle_path = {
|
||||
let (position, auth_path_bytes) =
|
||||
action.spend.witness.ok_or(OrchardError::MissingWitness)?;
|
||||
|
||||
let auth_path = auth_path_bytes
|
||||
.into_iter()
|
||||
.map(|hash| {
|
||||
MerkleHashOrchard::from_bytes(&hash)
|
||||
.into_option()
|
||||
.ok_or(OrchardError::InvalidWitness)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
MerklePath::from_parts(
|
||||
position,
|
||||
auth_path[..].try_into().expect("correct length"),
|
||||
)
|
||||
};
|
||||
|
||||
let spend =
|
||||
SpendInfo::new(fvk, note, merkle_path).ok_or(OrchardError::WrongFvkForNote)?;
|
||||
|
||||
let output_note = {
|
||||
let recipient = Address::from_raw_address_bytes(
|
||||
action
|
||||
.output
|
||||
.recipient
|
||||
.as_ref()
|
||||
.ok_or(OrchardError::MissingOutputRecipient)?,
|
||||
)
|
||||
.into_option()
|
||||
.ok_or(OrchardError::InvalidOutputRecipient)?;
|
||||
|
||||
let value =
|
||||
NoteValue::from_raw(action.output.value.ok_or(OrchardError::MissingValue)?);
|
||||
|
||||
let rho = Rho::from_bytes(&action.spend.nullifier)
|
||||
.into_option()
|
||||
.ok_or(crate::orchard::Error::InvalidNullifier)?;
|
||||
|
||||
let rseed = RandomSeed::from_bytes(
|
||||
action.output.rseed.ok_or(OrchardError::MissingRandomSeed)?,
|
||||
&rho,
|
||||
)
|
||||
.into_option()
|
||||
.ok_or(OrchardError::InvalidRandomSeed)?;
|
||||
|
||||
Note::from_parts(recipient, value, rho, rseed)
|
||||
.into_option()
|
||||
.ok_or(OrchardError::InvalidOutputNote)?
|
||||
};
|
||||
|
||||
let alpha = action.spend.alpha_from_field()?;
|
||||
|
||||
let rcv = ValueCommitTrapdoor::from_bytes(
|
||||
action.rcv.ok_or(OrchardError::MissingValueCommitTrapdoor)?,
|
||||
)
|
||||
.into_option()
|
||||
.ok_or(OrchardError::InvalidValueCommitTrapdoor)?;
|
||||
|
||||
Ok(Circuit::from_action_context(spend, output_note, alpha, rcv)
|
||||
.expect("rho should match nf by construction above")) // TODO: Check if this changes
|
||||
})
|
||||
.collect::<Result<Vec<_>, OrchardError>>()?;
|
||||
|
||||
let instances = self
|
||||
.pczt
|
||||
.orchard
|
||||
.actions
|
||||
.iter()
|
||||
.map(|action| {
|
||||
let cv_net = ValueCommitment::from_bytes(&action.cv_net)
|
||||
.into_option()
|
||||
.ok_or(crate::orchard::Error::InvalidValueCommitment)?;
|
||||
|
||||
let nf_old = Nullifier::from_bytes(&action.spend.nullifier)
|
||||
.into_option()
|
||||
.ok_or(crate::orchard::Error::InvalidNullifier)?;
|
||||
|
||||
let rk = redpallas::VerificationKey::try_from(action.spend.rk)
|
||||
.map_err(|_| crate::orchard::Error::InvalidRandomizedKey)?;
|
||||
|
||||
let cmx = ExtractedNoteCommitment::from_bytes(&action.output.cmx)
|
||||
.into_option()
|
||||
.ok_or(crate::orchard::Error::InvalidExtractedNoteCommitment)?;
|
||||
|
||||
Ok(Instance::from_parts(
|
||||
anchor,
|
||||
cv_net,
|
||||
nf_old,
|
||||
rk,
|
||||
cmx,
|
||||
flags.spends_enabled(),
|
||||
flags.outputs_enabled(),
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<_>, OrchardError>>()?;
|
||||
|
||||
let proof = orchard::Proof::create(pk, &circuits, &instances, OsRng)
|
||||
.map_err(|_| OrchardError::ProofFailed)?;
|
||||
|
||||
self.pczt.orchard.zkproof = Some(proof.as_ref().to_vec());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur while creating Orchard proofs for a PCZT.
|
||||
#[derive(Debug)]
|
||||
pub enum OrchardError {
|
||||
Data(crate::orchard::Error),
|
||||
InvalidOutputNote,
|
||||
InvalidOutputRecipient,
|
||||
InvalidRandomSeed,
|
||||
InvalidSpendAuthRandomizer,
|
||||
InvalidValueCommitTrapdoor,
|
||||
InvalidWitness,
|
||||
MissingOutputRecipient,
|
||||
MissingRandomSeed,
|
||||
MissingSpendAuthRandomizer,
|
||||
MissingValue,
|
||||
MissingValueCommitTrapdoor,
|
||||
MissingWitness,
|
||||
ProofFailed,
|
||||
WrongFvkForNote,
|
||||
}
|
||||
|
||||
impl From<crate::orchard::Error> for OrchardError {
|
||||
fn from(e: crate::orchard::Error) -> Self {
|
||||
Self::Data(e)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
use rand_core::OsRng;
|
||||
use sapling::{
|
||||
prover::{OutputProver, SpendProver},
|
||||
value::{NoteValue, ValueCommitTrapdoor},
|
||||
Note, PaymentAddress,
|
||||
};
|
||||
|
||||
impl super::Prover {
|
||||
pub fn create_sapling_proofs<S, O>(
|
||||
&mut self,
|
||||
spend_prover: &S,
|
||||
output_prover: &O,
|
||||
) -> Result<(), SaplingError>
|
||||
where
|
||||
S: SpendProver,
|
||||
O: OutputProver,
|
||||
{
|
||||
let mut rng = OsRng;
|
||||
|
||||
let anchor = jubjub::Base::from_bytes(&self.pczt.sapling.anchor)
|
||||
.into_option()
|
||||
.ok_or(crate::sapling::Error::InvalidAnchor)?;
|
||||
|
||||
for spend in &mut self.pczt.sapling.spends {
|
||||
let proof_generation_key = spend.proof_generation_key_from_field()?;
|
||||
let note = spend.note_from_fields()?;
|
||||
let alpha = spend.alpha_from_field()?;
|
||||
|
||||
let rcv = ValueCommitTrapdoor::from_bytes(
|
||||
spend.rcv.ok_or(SaplingError::MissingValueCommitTrapdoor)?,
|
||||
)
|
||||
.into_option()
|
||||
.ok_or(SaplingError::InvalidValueCommitTrapdoor)?;
|
||||
|
||||
let merkle_path = spend.witness_from_field()?;
|
||||
|
||||
let circuit = S::prepare_circuit(
|
||||
proof_generation_key,
|
||||
*note.recipient().diversifier(),
|
||||
*note.rseed(),
|
||||
note.value(),
|
||||
alpha,
|
||||
rcv,
|
||||
anchor,
|
||||
merkle_path,
|
||||
)
|
||||
.ok_or(SaplingError::InvalidDiversifier)?;
|
||||
|
||||
let proof = spend_prover.create_proof(circuit, &mut rng);
|
||||
spend.zkproof = Some(S::encode_proof(proof));
|
||||
}
|
||||
|
||||
for output in &mut self.pczt.sapling.outputs {
|
||||
let recipient = PaymentAddress::from_bytes(
|
||||
output
|
||||
.recipient
|
||||
.as_ref()
|
||||
.ok_or(SaplingError::MissingRecipient)?,
|
||||
)
|
||||
.ok_or(SaplingError::InvalidRecipient)?;
|
||||
|
||||
let value = NoteValue::from_raw(output.value.ok_or(SaplingError::MissingValue)?);
|
||||
|
||||
let rseed =
|
||||
sapling::Rseed::AfterZip212(output.rseed.ok_or(SaplingError::MissingRandomSeed)?);
|
||||
|
||||
let note = Note::from_parts(recipient, value, rseed);
|
||||
|
||||
let esk = note.generate_or_derive_esk(&mut rng);
|
||||
let rcm = note.rcm();
|
||||
|
||||
let rcv = ValueCommitTrapdoor::from_bytes(
|
||||
output.rcv.ok_or(SaplingError::MissingValueCommitTrapdoor)?,
|
||||
)
|
||||
.into_option()
|
||||
.ok_or(SaplingError::InvalidValueCommitTrapdoor)?;
|
||||
|
||||
let circuit = O::prepare_circuit(&esk, recipient, rcm, value, rcv);
|
||||
let proof = output_prover.create_proof(circuit, &mut rng);
|
||||
output.zkproof = Some(O::encode_proof(proof));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur while creating Sapling proofs for a PCZT.
|
||||
#[derive(Debug)]
|
||||
pub enum SaplingError {
|
||||
Data(crate::sapling::Error),
|
||||
InvalidDiversifier,
|
||||
InvalidRecipient,
|
||||
InvalidValueCommitTrapdoor,
|
||||
MissingRandomSeed,
|
||||
MissingRecipient,
|
||||
MissingValue,
|
||||
MissingValueCommitTrapdoor,
|
||||
}
|
||||
|
||||
impl From<crate::sapling::Error> for SaplingError {
|
||||
fn from(e: crate::sapling::Error) -> Self {
|
||||
Self::Data(e)
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ use zcash_primitives::{
|
|||
};
|
||||
use zcash_protocol::{consensus::BranchId, value::Zatoshis};
|
||||
|
||||
use crate::Pczt;
|
||||
use crate::{IgnoreMissing, Pczt};
|
||||
|
||||
use super::tx_extractor::determine_lock_time;
|
||||
|
||||
|
@ -42,8 +42,6 @@ impl Signer {
|
|||
index: usize,
|
||||
ask: &sapling::keys::SpendAuthorizingKey,
|
||||
) -> Result<(), Error> {
|
||||
// TODO: Check consistency of the input being signed.
|
||||
|
||||
let spend = self
|
||||
.pczt
|
||||
.sapling
|
||||
|
@ -51,6 +49,34 @@ impl Signer {
|
|||
.get_mut(index)
|
||||
.ok_or(Error::InvalidIndex)?;
|
||||
|
||||
// Check consistency of the input being signed.
|
||||
let note_from_fields = spend
|
||||
.note_from_fields()
|
||||
.ignore_missing()
|
||||
.map_err(Error::Sapling)?;
|
||||
|
||||
if let Some(note) = note_from_fields {
|
||||
let tx_spend = self
|
||||
.tx_data
|
||||
.sapling_bundle()
|
||||
.expect("index checked above")
|
||||
.shielded_spends()
|
||||
.get(index)
|
||||
.expect("index checked above");
|
||||
|
||||
let proof_generation_key = spend
|
||||
.proof_generation_key_from_field()
|
||||
.map_err(Error::Sapling)?;
|
||||
|
||||
let nk = proof_generation_key.to_viewing_key().nk;
|
||||
|
||||
let merkle_path = spend.witness_from_field().map_err(Error::Sapling)?;
|
||||
|
||||
if ¬e.nf(&nk, merkle_path.position().into()) != tx_spend.nullifier() {
|
||||
return Err(Error::InvalidNullifier);
|
||||
}
|
||||
}
|
||||
|
||||
let alpha = spend.alpha_from_field().map_err(Error::Sapling)?;
|
||||
|
||||
let rsk = ask.randomize(&alpha);
|
||||
|
@ -79,8 +105,6 @@ impl Signer {
|
|||
index: usize,
|
||||
ask: &orchard::keys::SpendAuthorizingKey,
|
||||
) -> Result<(), Error> {
|
||||
// TODO: Check consistency of the input being signed.
|
||||
|
||||
let action = self
|
||||
.pczt
|
||||
.orchard
|
||||
|
@ -88,6 +112,29 @@ impl Signer {
|
|||
.get_mut(index)
|
||||
.ok_or(Error::InvalidIndex)?;
|
||||
|
||||
// Check consistency of the input being signed.
|
||||
let note_from_fields = action
|
||||
.spend
|
||||
.note_from_fields()
|
||||
.ignore_missing()
|
||||
.map_err(Error::Orchard)?;
|
||||
|
||||
if let Some(note) = note_from_fields {
|
||||
let tx_action = self
|
||||
.tx_data
|
||||
.orchard_bundle()
|
||||
.expect("index checked above")
|
||||
.actions()
|
||||
.get(index)
|
||||
.expect("index checked above");
|
||||
|
||||
let fvk = action.spend.fvk_from_field().map_err(Error::Orchard)?;
|
||||
|
||||
if ¬e.nullifier(fvk) != tx_action.nullifier() {
|
||||
return Err(Error::InvalidNullifier);
|
||||
}
|
||||
}
|
||||
|
||||
let alpha = action.spend.alpha_from_field().map_err(Error::Orchard)?;
|
||||
|
||||
let rsk = ask.randomize(&alpha);
|
||||
|
@ -233,6 +280,8 @@ pub enum Error {
|
|||
Global(GlobalError),
|
||||
IncompatibleLockTimes,
|
||||
InvalidIndex,
|
||||
InvalidNoteCommitment,
|
||||
InvalidNullifier,
|
||||
Orchard(crate::orchard::Error),
|
||||
Sapling(crate::sapling::Error),
|
||||
Transparent(crate::transparent::Error),
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use crate::roles::combiner::merge_optional;
|
||||
use crate::{roles::combiner::merge_optional, IgnoreMissing};
|
||||
|
||||
#[cfg(feature = "sapling")]
|
||||
use pasta_curves::group::ff::PrimeField;
|
||||
use {
|
||||
ff::PrimeField,
|
||||
sapling::{
|
||||
keys::SpendValidatingKey, value::NoteValue, MerklePath, Node, Note, PaymentAddress, Rseed,
|
||||
},
|
||||
};
|
||||
|
||||
const GROTH_PROOF_SIZE: usize = 48 + 96 + 48;
|
||||
|
||||
|
@ -55,6 +60,65 @@ pub(crate) struct Spend {
|
|||
/// This is set by the Signer.
|
||||
pub(crate) spend_auth_sig: Option<[u8; 64]>,
|
||||
|
||||
/// The [raw encoding] of the Sapling payment address that received the note being spent.
|
||||
///
|
||||
/// - This is set by the Constructor.
|
||||
/// - This is required by the Prover.
|
||||
///
|
||||
/// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding
|
||||
pub(crate) recipient: Option<[u8; 43]>,
|
||||
|
||||
/// The value of the input being spent.
|
||||
///
|
||||
/// This may be used by Signers to verify that the value matches `cv`, and to confirm
|
||||
/// the values and change involved in the transaction.
|
||||
///
|
||||
/// This exposes the input value to all participants. For Signers who don't need this
|
||||
/// information, or after signatures have been applied, this can be redacted.
|
||||
pub(crate) value: Option<u64>,
|
||||
|
||||
/// The note commitment randomness.
|
||||
///
|
||||
/// - This is set by the Constructor. It MUST NOT be set if the note has an `rseed`
|
||||
/// (i.e. was created after [ZIP 212] activation).
|
||||
/// - The Prover requires either this or `rseed`.
|
||||
///
|
||||
/// [ZIP 212]: https://zips.z.cash/zip-0212
|
||||
pub(crate) rcm: Option<[u8; 32]>,
|
||||
|
||||
/// The seed randomness for the note being spent.
|
||||
///
|
||||
/// - This is set by the Constructor. It MUST NOT be set if the note has no `rseed`
|
||||
/// (i.e. was created before [ZIP 212] activation).
|
||||
/// - The Prover requires either this or `rcm`.
|
||||
///
|
||||
/// [ZIP 212]: https://zips.z.cash/zip-0212
|
||||
pub(crate) rseed: Option<[u8; 32]>,
|
||||
|
||||
/// The value commitment randomness.
|
||||
///
|
||||
/// - This is set by the Constructor.
|
||||
/// - The IO Finalizer compresses it into `bsk`.
|
||||
/// - This is required by the Prover.
|
||||
/// - This may be used by Signers to verify that the value correctly matches `cv`.
|
||||
///
|
||||
/// This opens `cv` for all participants. For Signers who don't need this information,
|
||||
/// or after proofs / signatures have been applied, this can be redacted.
|
||||
pub(crate) rcv: Option<[u8; 32]>,
|
||||
|
||||
/// The proof generation key `(ak, nsk)` corresponding to the recipient that received
|
||||
/// the note being spent.
|
||||
///
|
||||
/// - This is set by the Updater.
|
||||
/// - This is required by the Prover.
|
||||
pub(crate) proof_generation_key: Option<([u8; 32], [u8; 32])>,
|
||||
|
||||
/// A witness from the note to the bundle's anchor.
|
||||
///
|
||||
/// - This is set by the Updater.
|
||||
/// - This is required by the Prover.
|
||||
pub(crate) witness: Option<(u32, [[u8; 32]; 32])>,
|
||||
|
||||
/// The spend authorization randomizer.
|
||||
///
|
||||
/// - This is chosen by the Constructor.
|
||||
|
@ -101,6 +165,40 @@ pub(crate) struct Output {
|
|||
///
|
||||
/// This is set by the Prover.
|
||||
pub(crate) zkproof: Option<[u8; GROTH_PROOF_SIZE]>,
|
||||
|
||||
/// The [raw encoding] of the Sapling payment address that will receive the output.
|
||||
///
|
||||
/// - This is set by the Constructor.
|
||||
/// - This is required by the Prover.
|
||||
///
|
||||
/// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding
|
||||
pub(crate) recipient: Option<[u8; 43]>,
|
||||
|
||||
/// The value of the output.
|
||||
///
|
||||
/// This may be used by Signers to verify that the value matches `cv`, and to confirm
|
||||
/// the values and change involved in the transaction.
|
||||
///
|
||||
/// This exposes the output value to all participants. For Signers who don't need this
|
||||
/// information, or after signatures have been applied, this can be redacted.
|
||||
pub(crate) value: Option<u64>,
|
||||
|
||||
/// The seed randomness for the output.
|
||||
///
|
||||
/// - This is set by the Constructor.
|
||||
/// - This is required by the Prover, instead of disclosing `shared_secret` to them.
|
||||
pub(crate) rseed: Option<[u8; 32]>,
|
||||
|
||||
/// The value commitment randomness.
|
||||
///
|
||||
/// - This is set by the Constructor.
|
||||
/// - The IO Finalizer compresses it into `bsk`.
|
||||
/// - This is required by the Prover.
|
||||
/// - This may be used by Signers to verify that the value correctly matches `cv`.
|
||||
///
|
||||
/// This opens `cv` for all participants. For Signers who don't need this information,
|
||||
/// or after proofs / signatures have been applied, this can be redacted.
|
||||
pub(crate) rcv: Option<[u8; 32]>,
|
||||
}
|
||||
|
||||
impl Bundle {
|
||||
|
@ -180,6 +278,13 @@ impl Bundle {
|
|||
rk,
|
||||
zkproof,
|
||||
spend_auth_sig,
|
||||
recipient,
|
||||
value,
|
||||
rcm,
|
||||
rseed,
|
||||
rcv,
|
||||
proof_generation_key,
|
||||
witness,
|
||||
alpha,
|
||||
dummy_ask,
|
||||
} = rhs;
|
||||
|
@ -190,6 +295,13 @@ impl Bundle {
|
|||
|
||||
if !(merge_optional(&mut lhs.zkproof, zkproof)
|
||||
&& merge_optional(&mut lhs.spend_auth_sig, spend_auth_sig)
|
||||
&& merge_optional(&mut lhs.recipient, recipient)
|
||||
&& merge_optional(&mut lhs.value, value)
|
||||
&& merge_optional(&mut lhs.rcm, rcm)
|
||||
&& merge_optional(&mut lhs.rseed, rseed)
|
||||
&& merge_optional(&mut lhs.rcv, rcv)
|
||||
&& merge_optional(&mut lhs.proof_generation_key, proof_generation_key)
|
||||
&& merge_optional(&mut lhs.witness, witness)
|
||||
&& merge_optional(&mut lhs.alpha, alpha)
|
||||
&& merge_optional(&mut lhs.dummy_ask, dummy_ask))
|
||||
{
|
||||
|
@ -206,6 +318,10 @@ impl Bundle {
|
|||
enc_ciphertext,
|
||||
out_ciphertext,
|
||||
zkproof,
|
||||
recipient,
|
||||
value,
|
||||
rseed,
|
||||
rcv,
|
||||
} = rhs;
|
||||
|
||||
if lhs.cv != cv
|
||||
|
@ -217,7 +333,12 @@ impl Bundle {
|
|||
return None;
|
||||
}
|
||||
|
||||
if !merge_optional(&mut lhs.zkproof, zkproof) {
|
||||
if !(merge_optional(&mut lhs.zkproof, zkproof)
|
||||
&& merge_optional(&mut lhs.recipient, recipient)
|
||||
&& merge_optional(&mut lhs.value, value)
|
||||
&& merge_optional(&mut lhs.rseed, rseed)
|
||||
&& merge_optional(&mut lhs.rcv, rcv))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
@ -329,6 +450,59 @@ impl Bundle {
|
|||
|
||||
#[cfg(feature = "sapling")]
|
||||
impl Spend {
|
||||
/// Parses a [`Note`] from the explicit fields of this spend.
|
||||
pub(crate) fn note_from_fields(&self) -> Result<Note, Error> {
|
||||
// We want to parse all fields that are present for validity, before raising any
|
||||
// errors about missing fields.
|
||||
|
||||
let recipient = self
|
||||
.recipient
|
||||
.as_ref()
|
||||
.map(|r| PaymentAddress::from_bytes(r).ok_or(Error::InvalidRecipient))
|
||||
.transpose()?;
|
||||
|
||||
let value = self.value.map(NoteValue::from_raw);
|
||||
let rseed = self.rseed.map(Rseed::AfterZip212);
|
||||
|
||||
Ok(Note::from_parts(
|
||||
recipient.ok_or(Error::MissingRecipient)?,
|
||||
value.ok_or(Error::MissingValue)?,
|
||||
rseed.ok_or(Error::MissingRandomSeed)?,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn proof_generation_key_from_field(
|
||||
&self,
|
||||
) -> Result<sapling::ProofGenerationKey, Error> {
|
||||
let (ak, nsk) = self
|
||||
.proof_generation_key
|
||||
.ok_or(Error::MissingProofGenerationKey)?;
|
||||
|
||||
Ok(sapling::ProofGenerationKey {
|
||||
ak: SpendValidatingKey::temporary_zcash_from_bytes(&ak)
|
||||
.ok_or(Error::InvalidProofGenerationKey)?,
|
||||
nsk: jubjub::Scalar::from_repr(nsk)
|
||||
.into_option()
|
||||
.ok_or(Error::InvalidProofGenerationKey)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn witness_from_field(&self) -> Result<MerklePath, Error> {
|
||||
let (position, auth_path_bytes) = self.witness.ok_or(Error::MissingWitness)?;
|
||||
|
||||
let path_elems = auth_path_bytes
|
||||
.into_iter()
|
||||
.map(|hash| {
|
||||
Node::from_bytes(hash)
|
||||
.into_option()
|
||||
.ok_or(Error::InvalidWitness)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
MerklePath::from_parts(path_elems, u64::from(position).into())
|
||||
.map_err(|()| Error::InvalidWitness)
|
||||
}
|
||||
|
||||
pub(crate) fn alpha_from_field(&self) -> Result<jubjub::Scalar, Error> {
|
||||
jubjub::Scalar::from_repr(self.alpha.ok_or(Error::MissingSpendAuthRandomizer)?)
|
||||
.into_option()
|
||||
|
@ -343,9 +517,35 @@ pub enum Error {
|
|||
InvalidEncCiphertext,
|
||||
InvalidExtractedNoteCommitment,
|
||||
InvalidOutCiphertext,
|
||||
InvalidProofGenerationKey,
|
||||
InvalidRandomizedKey,
|
||||
InvalidRecipient,
|
||||
InvalidSpendAuthRandomizer,
|
||||
InvalidValueBalance(zcash_protocol::value::BalanceError),
|
||||
InvalidValueCommitment,
|
||||
InvalidWitness,
|
||||
MissingProofGenerationKey,
|
||||
MissingRandomSeed,
|
||||
MissingRecipient,
|
||||
MissingSpendAuthRandomizer,
|
||||
MissingValue,
|
||||
MissingWitness,
|
||||
}
|
||||
|
||||
#[cfg(feature = "sapling")]
|
||||
impl<V> IgnoreMissing for Result<V, Error> {
|
||||
type Value = V;
|
||||
type Error = Error;
|
||||
|
||||
fn ignore_missing(self) -> Result<Option<Self::Value>, Self::Error> {
|
||||
self.map(Some).or_else(|e| match e {
|
||||
Error::MissingProofGenerationKey
|
||||
| Error::MissingRandomSeed
|
||||
| Error::MissingRecipient
|
||||
| Error::MissingSpendAuthRandomizer
|
||||
| Error::MissingValue
|
||||
| Error::MissingWitness => Ok(None),
|
||||
_ => Err(e),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use std::path::Path;
|
|||
|
||||
use sapling::{
|
||||
bundle::GrothProofBytes,
|
||||
keys::EphemeralSecretKey,
|
||||
prover::{OutputProver, SpendProver},
|
||||
value::{NoteValue, ValueCommitTrapdoor},
|
||||
Diversifier, MerklePath, PaymentAddress, ProofGenerationKey, Rseed,
|
||||
|
@ -176,7 +177,7 @@ impl OutputProver for LocalTxProver {
|
|||
type Proof = Proof<Bls12>;
|
||||
|
||||
fn prepare_circuit(
|
||||
esk: jubjub::Fr,
|
||||
esk: &EphemeralSecretKey,
|
||||
payment_address: PaymentAddress,
|
||||
rcm: jubjub::Fr,
|
||||
value: NoteValue,
|
||||
|
|
Loading…
Reference in New Issue