Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
str4d committed Nov 26, 2024
1 parent e638dbe commit d807c7e
Show file tree
Hide file tree
Showing 10 changed files with 416 additions and 0 deletions.
5 changes: 5 additions & 0 deletions pczt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pasta_curves = { workspace = true, optional = true }

[features]
orchard = [
"dep:ff",
"dep:nonempty",
"dep:orchard",
"dep:pasta_curves",
Expand All @@ -51,3 +52,7 @@ io-finalizer = ["orchard", "sapling"]
prover = ["dep:rand_core", "sapling?/temporary-zcashd"]
signer = ["dep:rand_core", "orchard", "sapling", "transparent"]
tx-extractor = ["dep:rand_core", "orchard", "sapling", "transparent"]

[[test]]
name = "end_to_end"
required-features = ["io-finalizer", "prover", "signer", "tx-extractor"]
49 changes: 49 additions & 0 deletions pczt/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,54 @@
//! The Partially Created Zcash Transaction (PCZT) format.
//!
//! General flow for creating a shielded transaction:
//! - Create "unsigned transaction"
//! - In practice means deciding on the global parts of the transaction
//! - Collect each output
//! - Proofs can be created at this time
//! - Decide on an anchor
//! - All spends should use the same anchor for indistinguishability
//! - In a future transaction version, all spends will be required to do so
//! - Collect each spend
//! - Proofs can and should be created at this time
//! - Create proofs for each spend and output
//! - Data necessary for proofs can be stripped out of the format
//! - Collect proofs
//! - Distribute collected data to signers
//! - Signers must verify the transaction before signing, and reject if not satisfied.
//! - This is the no-turning-back point regarding spend authorization!
//! - Collect signatures
//! - Create binding signature
//! - The party that performs this does not need to be trusted, because each signer
//! has verified the transaction and signed it, so the bindingSig can only be
//! computed over the same data if a valid transaction is to be created.
//! - Extract final transaction
//!
//! Goal is to split up the parts of creating a transaction across distinct entities.
//! The entity roles roughly match BIP 174: Partially Signed Bitcoin Transaction Format.
//! - Creator (single entity)
//! - Creates the base PCZT with no information about spends or outputs.
//! - Constructor (anyone can contribute)
//! - Adds spends and outputs to the PCZT.
//! - Before any input or output may be added, the constructor must check the
//! PSBT_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.
//! - IO Finalizer (anyone can execute)
//! - Sets the appropriate bits in PSBT_GLOBAL_TX_MODIFIABLE to 0. (TODO fix up)
//! - Inspects the inputs and outputs throughout the PCZT and picks a transaction
//! version that is compatible with all of them (or returns an error).
//! - Updates the various bsk values using the rcv information from spends and outputs.
//! - This can happen after each spend or output is added if they are added serially.
//! If spends and outputs are created in parallel, the IO Finalizer must act after
//! the Combiner.
//! - Updater (anyone can contribute)
//! - Adds information necessary for subsequent entities to proceed, such as key paths
//! for signing spends.
//! - Redactor (anyone can execute)
//! - Removes information that is unnecessary for subsequent entities to proceed.
//! - This can be useful e.g. when creating a transaction that has inputs from multiple
//! independent Signers; each can receive a PCZT with just the information they need
//! to sign, but (e.g.) not the `alpha` values for other Signers.
//! - 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
Expand Down Expand Up @@ -43,6 +83,9 @@
//! the PCZT, with spendAuthSigs and bindingSig empty, and then enforcing equality.
//! - This is equivalent to BIP 147's equality definition (the partial transactions
//! must be identical).
//! - Spend Finalizer (anyone can execute)
//! - Currently unnecessary, but when shielded multisig is implemented, this would be the
//! entity that combines the separate signatures into a multisignature.
//! - Transaction Extractor (anyone can execute)
//! - Creates bindingSig and extracts the final transaction.

Expand Down Expand Up @@ -75,6 +118,12 @@ pub struct Pczt {
orchard: orchard::Bundle,
}

/// The defined versions of PCZT.
#[derive(Clone, PartialEq, Eq)]
enum Version {
V0,
}

trait IgnoreMissing {
type Value;
type Error;
Expand Down
6 changes: 6 additions & 0 deletions pczt/src/orchard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ pub(crate) struct Bundle {
/// This is initialized by the Creator, and updated by the Constructor as spends or
/// outputs are added to the PCZT. It enables per-spend and per-output values to be
/// redacted from the PCZT after they are no longer necessary.
//
// TODO: This is technically too small to contain the full range of intermediate value
// balances. Either:
// - Change to u128 and rely on the circuit to constrain each action balance.
// - Change to i64 and have it be calculated by the IO Finalizer (which would then
// require all values).
pub(crate) value_balance: u64,

/// The Orchard anchor for this transaction.
Expand Down
2 changes: 2 additions & 0 deletions pczt/src/roles.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub mod creator;

pub mod constructor;

#[cfg(feature = "io-finalizer")]
pub mod io_finalizer;

Expand Down
26 changes: 26 additions & 0 deletions pczt/src/roles/constructor/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use crate::Pczt;

#[cfg(feature = "orchard")]
mod orchard;

// #[cfg(feature = "sapling")]
// mod sapling;

#[cfg(feature = "transparent")]
mod transparent;

pub struct Constructor {
pczt: Pczt,
}

impl Constructor {
/// Instantiates the Constructor role with the given PCZT.
pub fn new(pczt: Pczt) -> Self {
Self { pczt }
}

/// Finishes the Constructor role, returning the updated PCZT.
pub fn finish(self) -> Pczt {
self.pczt
}
}
87 changes: 87 additions & 0 deletions pczt/src/roles/constructor/orchard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use std::collections::BTreeMap;

use ff::{Field, PrimeField};
use orchard::{
keys::{EphemeralPublicKey, FullViewingKey, SpendValidatingKey},
note::ExtractedNoteCommitment,
note_encryption::OrchardDomain,
value::{ValueCommitTrapdoor, ValueCommitment},
};
use pasta_curves::pallas;
use rand_core::OsRng;
use zcash_note_encryption::Domain;

impl super::Constructor {
/// Adds an Orchard action to the PCZT.
pub fn add_orchard_action(
&mut self,
spend_fvk: &FullViewingKey,
spend_note: &orchard::Note,
output_note: &orchard::Note,
epk: &EphemeralPublicKey,
enc_ciphertext: &[u8; 580],
out_ciphertext: &[u8; 80],
rcv: &ValueCommitTrapdoor,
) -> Result<(), OrchardError> {
let action_balance = spend_note.value() - output_note.value();

self.pczt.orchard.value_balance = self
.pczt
.orchard
.value_balance
.checked_add_signed(
// TODO: This can throw an error for a technically legitimate action
// balance.
i64::try_from(action_balance).map_err(|_| OrchardError::BalanceViolation)?,
)
.ok_or(OrchardError::BalanceViolation)?;

let cv = ValueCommitment::derive(action_balance, rcv.clone());

let alpha = pallas::Scalar::random(OsRng);
let ak = SpendValidatingKey::from(spend_fvk.clone());
let rk = ak.randomize(&alpha);

self.pczt.orchard.actions.push(crate::orchard::Action {
cv: cv.to_bytes(),
spend: crate::orchard::Spend {
nullifier: spend_note.nullifier(spend_fvk).to_bytes(),
rk: rk.into(),
spend_auth_sig: None,
recipient: Some(spend_note.recipient().to_raw_address_bytes()),
value: Some(spend_note.value().inner()),
rho: Some(spend_note.rho().to_bytes()),
rseed: Some(*spend_note.rseed().as_bytes()),
// TODO: Documented as being set by the Updater, but the Constructor needs
// it to derive rk.
fvk: Some(spend_fvk.to_bytes()),
witness: None,
alpha: Some(alpha.to_repr()),
zip32_derivation: None,
proprietary: BTreeMap::new(),
},
output: crate::orchard::Output {
cmx: ExtractedNoteCommitment::from(output_note.commitment()).to_bytes(),
ephemeral_key: OrchardDomain::epk_bytes(&epk).0,
enc_ciphertext: enc_ciphertext.to_vec(),
out_ciphertext: out_ciphertext.to_vec(),
recipient: Some(output_note.recipient().to_raw_address_bytes()),
value: Some(output_note.value().inner()),
rseed: Some(*output_note.rseed().as_bytes()),
shared_secret: None,
ock: None,
zip32_derivation: None,
proprietary: BTreeMap::new(),
},
rcv: Some(rcv.to_bytes()),
});

Ok(())
}
}

/// Errors that can occur while adding Orchard actions to a PCZT.
#[derive(Debug)]
pub enum OrchardError {
BalanceViolation,
}
96 changes: 96 additions & 0 deletions pczt/src/roles/constructor/sapling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use std::collections::BTreeMap;

use ff::Field;
use rand_core::OsRng;
use sapling::{
keys::{EphemeralPublicKey, FullViewingKey},
note_encryption::SaplingDomain,
value::{ValueCommitTrapdoor, ValueCommitment},
Note,
};
use zcash_note_encryption::Domain;

impl super::Constructor {
/// Adds a Sapling note to be spent in this transaction.
pub fn add_sapling_spend(
&mut self,
fvk: &FullViewingKey,
note: &Note,
position: u64,
rcv: &ValueCommitTrapdoor,
) -> Result<(), SaplingError> {
self.pczt.sapling.value_balance = self
.pczt
.sapling
.value_balance
.checked_add(note.value().inner())
.ok_or(SaplingError::BalanceViolation)?;

let cv = ValueCommitment::derive(note.value(), rcv.clone());

let alpha = jubjub::Scalar::random(OsRng);
let rk = fvk.vk.ak.randomize(&alpha);

self.pczt.sapling.spends.push(crate::sapling::Spend {
cv: cv.to_bytes(),
nullifier: note.nf(&fvk.vk.nk, position).0,
rk: rk.into(),
zkproof: None,
spend_auth_sig: None,
recipient: Some(note.recipient().to_bytes()),
value: Some(note.value().inner()),
rseed: Some(note.rseed()),
rcv: Some(rcv.inner().to_bytes()),
proof_generation_key: None,
witness: None,
alpha: Some(alpha.to_bytes()),
zip32_derivation: None,
proprietary: BTreeMap::new(),
});
Ok(())
}

/// Adds a Sapling address to send funds to.
pub fn add_sapling_output(
&mut self,
note: &Note,
epk: &EphemeralPublicKey,
enc_ciphertext: &[u8; 580],
out_ciphertext: &[u8; 80],
rcv: &ValueCommitTrapdoor,
) -> Result<(), SaplingError> {
self.pczt.sapling.value_balance = self
.pczt
.sapling
.value_balance
.checked_sub(note.value().inner())
.ok_or(SaplingError::BalanceViolation)?;

let cv = ValueCommitment::derive(note.value(), rcv.clone());

self.pczt.sapling.outputs.push(crate::sapling::Output {
cv: cv.to_bytes(),
cmu: note.cmu().to_bytes(),
ephemeral_key: SaplingDomain::epk_bytes(epk).0,
enc_ciphertext: enc_ciphertext.to_vec(),
out_ciphertext: out_ciphertext.to_vec(),
zkproof: None,
recipient: Some(note.recipient().to_bytes()),
value: Some(note.value().inner()),
rseed: Some(note.rseed()),
rcv: Some(rcv.inner().to_bytes()),
shared_secret: None,
ock: None,
zip32_derivation: None,
proprietary: BTreeMap::new(),
});

Ok(())
}
}

/// Errors that can occur while adding Sapling spends or outputs to a PCZT.
#[derive(Debug)]
pub enum SaplingError {
BalanceViolation,
}
55 changes: 55 additions & 0 deletions pczt/src/roles/constructor/transparent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use std::collections::BTreeMap;

use zcash_primitives::{
legacy::TransparentAddress,
transaction::components::{transparent, TxOut},
};
use zcash_protocol::value::Zatoshis;

impl super::Constructor {
/// Adds a transparent coin to be spent in this transaction.
pub fn add_transparent_input(
&mut self,
utxo: transparent::OutPoint,
coin: TxOut,
sequence: u32,
required_locktime: u32,
sighash_type: u32,
) {
self.pczt
.transparent
.inputs
.push(crate::transparent::Input {
prevout_txid: *utxo.hash(),
prevout_index: utxo.n(),
sequence,
required_locktime,
script_sig: None,
value: coin.value.into_u64(),
script_pubkey: coin.script_pubkey.0,
redeem_script: None,
partial_signatures: BTreeMap::new(),
sighash_type,
bip32_derivation: BTreeMap::new(),
ripemd160_preimages: BTreeMap::new(),
sha256_preimages: BTreeMap::new(),
hash160_preimages: BTreeMap::new(),
hash256_preimages: BTreeMap::new(),
proprietary: BTreeMap::new(),
});
}

/// Adds a transparent address to send funds to.
pub fn add_transparent_output(&mut self, to: &TransparentAddress, value: Zatoshis) {
self.pczt
.transparent
.outputs
.push(crate::transparent::Output {
value: value.into_u64(),
script_pubkey: to.script().0,
redeem_script: None,
bip32_derivation: BTreeMap::new(),
proprietary: BTreeMap::new(),
});
}
}
6 changes: 6 additions & 0 deletions pczt/src/sapling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ pub(crate) struct Bundle {
/// This is initialized by the Creator, and updated by the Constructor as spends or
/// outputs are added to the PCZT. It enables per-spend and per-output values to be
/// redacted from the PCZT after they are no longer necessary.
//
// TODO: This is technically too small to contain the full range of intermediate value
// balances. Either:
// - Change to u128 and rely on the circuit to constrain each action balance.
// - Change to i64 and have it be calculated by the IO Finalizer (which would then
// require all values).
pub(crate) value_balance: u64,

/// The Sapling anchor for this transaction.
Expand Down
Loading

0 comments on commit d807c7e

Please sign in to comment.