Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pczt: Define the structure and semantics of the PCZT format #1577

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 60 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ bitvec = "1"
blake2s_simd = "1"
bls12_381 = "0.8"
jubjub = "0.10"
redjubjub = "0.7"
sapling = { package = "sapling-crypto", version = "0.3", default-features = false }

# - Orchard
Expand All @@ -66,10 +67,13 @@ orchard = { version = "0.10", default-features = false }
pasta_curves = "0.5"

# - Transparent
bip32 = { version = "0.5", default-features = false, features = ["secp256k1-ffi"] }
bip32 = { version = "0.5", default-features = false }
ripemd = "0.1"
secp256k1 = "0.27"

# Boilerplate
getset = "0.1"

# CSPRNG
rand = "0.8"
rand_core = "0.6"
Expand Down Expand Up @@ -162,3 +166,7 @@ codegen-units = 1

[workspace.lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("zfuture"))'] }

[patch.crates-io]
orchard = { git = "https://github.com/zcash/orchard.git", rev = "cb404f817480f267d2e138a268a55bac6474794a" }
sapling-crypto = { git = "https://github.com/zcash/sapling-crypto.git", rev = "7696219bf3740f6d979b2b7362e6c11154004010" }
58 changes: 58 additions & 0 deletions pczt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,61 @@ license.workspace = true
categories.workspace = true

[dependencies]
zcash_note_encryption = { workspace = true, optional = true }
zcash_primitives = { workspace = true, optional = true }
zcash_protocol = { workspace = true, optional = true }

blake2b_simd = { workspace = true, optional = true }
rand_core = { workspace = true, optional = true }

# Payment protocols
# - Transparent
secp256k1 = { workspace = true, optional = true }

# - 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 }

# - Orchard
nonempty = { workspace = true, optional = true }
orchard = { workspace = true, optional = true }
pasta_curves = { workspace = true, optional = true }

[dev-dependencies]
incrementalmerkletree.workspace = true
secp256k1 = { workspace = true, features = ["rand"] }
shardtree.workspace = true
zcash_primitives = { workspace = true, features = ["test-dependencies", "transparent-inputs"] }
zcash_proofs = { workspace = true, features = ["bundled-prover"] }
zip32.workspace = true

[features]
orchard = [
"dep:nonempty",
"dep:orchard",
"dep:pasta_curves",
"dep:zcash_protocol",
]
sapling = [
"dep:bls12_381",
"dep:ff",
"dep:jubjub",
"dep:redjubjub",
"dep:sapling",
"dep:zcash_note_encryption",
"dep:zcash_protocol",
]
transparent = ["dep:secp256k1", "dep:zcash_primitives", "dep:zcash_protocol"]
zcp-builder = ["dep:zcash_primitives"]
io-finalizer = ["orchard", "sapling"]
prover = ["dep:rand_core", "sapling?/temporary-zcashd"]
Copy link
Contributor

@daira daira Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the sapling/temporary-zcashd feature dependency strictly necessary? Consider filing an issue (if there isn't one already) against sapling to make the necessary APIs stable.

signer = ["dep:blake2b_simd", "dep:rand_core", "orchard", "sapling", "transparent"]
spend-finalizer = ["transparent"]
tx-extractor = ["dep:rand_core", "orchard", "sapling", "transparent"]

[[test]]
name = "end_to_end"
required-features = ["io-finalizer", "prover", "signer", "tx-extractor"]
73 changes: 73 additions & 0 deletions pczt/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use std::collections::BTreeMap;

use crate::roles::combiner::merge_map;

/// Global fields that are relevant to the transaction as a whole.
#[derive(Clone, Debug)]
pub(crate) struct Global {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keystone's proposal included a network field (indicating mainnet vs testnet vs ?) at the global level, possibly because in my 2019 PCZT draft I suggested it. However, I note that PSBTs (BIP 174) do not include such a field (AFAICT). Is such a field necessary, and/or useful?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why it would be necessary or useful here, but it looks like BIP 174 does include an implicit indication of the network in the serialized extended public keys.

Copy link

@soralit soralit Oct 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We meet a similar problem on Unisat's Fractal Bitcoin recently. PSBT doesn't have a network field, so we have to set it on the global map field which means it becomes a custom protocol between Unisat and us.
This can be useful if the chain have multiple sub/child chains like Bitcoin vs Litecoin or Dogecoin. In theory Litecoin can also use PSBT, but without network field, we cannot know some accurate info like coin precision.
Anyway, that's depends on Zcash's needs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is a good point. And as @arya2 notes above and @pacu notes below, we will already be semi-explicitly encoding the network into the PCZT at some later stage. So I think it makes sense for the Creator to encode the network up-front, and then every later instance of data that includes network information can be checked against it for correctness.

Next question then is what format this network field should have. A SLIP 44 coin_type (as used in BIP 32 and ZIP 32 derivation paths) would make sense for mainnet applications, but SLIP 44 only defines a single testnet code for all coins. Would that be sufficient for Zcash (and potentially other) testnet uses? (I think so, because if you're running against testnet then you already likely know what network you're dealing with, and the important part is that we have domain separation between mainnet and non-mainnet, as well as between Zcash and non-Zcash mainnets.)

//
// Transaction effecting data.
//
// These are required fields that are part of the final transaction, and are filled in
// by the Creator when initializing the PCZT.
//
pub(crate) tx_version: u32,
pub(crate) version_group_id: u32,
Comment on lines +14 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this pair of fields be optional independently of the others?

Copy link

@arya2 arya2 Oct 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to infer the version group id from the tx version instead of having it as an explicit field?

Should this pair of fields be optional independently of the others?

Would it make sense to adding a builder struct where the tx_version field is optional instead of making it optional here?

Update: I see there's already a builder struct, so it seems unnecessary to make the field optional here as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to infer the version group id from the tx version instead of having it as an explicit field?

That's more fragile across consensus forks, but I can see an argument that the tuple (tx_version, consensus_branch_id) might be sufficient to fully constrain the value of version_group_id such that every role can reliably deduce it. The main argument against this would be that it would force PCZT roles to be aware of more details about transaction formats, where they might otherwise just be able to focus on the PCZT.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO the arguments for encoding the version group ID explicitly in the transaction format apply equally to PCZTs.

/// The consensus branch ID for the chain in which this transaction will be mined.
///
/// Non-optional because this commits to the set of consensus rules that will apply to
/// the transaction; differences therein can affect every role.
pub(crate) consensus_branch_id: u32,
arya2 marked this conversation as resolved.
Show resolved Hide resolved
/// TODO: In PSBT this is `fallback_lock_time`; decide whether this should have the
/// same semantics.
pub(crate) lock_time: u32,
pub(crate) expiry_height: u32,
Comment on lines +23 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be independently optional?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be nice to type these as LockTime and BlockHeight.

Should these be independently optional?

If this needs to be able to produce v1 or v2 transactions, then at least expiry_height should probably be optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arya2 I'm intentionally not adding types to the core structure (at least at this stage), to keep its dependencies as minimal / non-existent as possible (because there are many roles that do not need to care about every type in the PCZT, e.g. an Orchard signer doesn't need to be able to parse Sapling-specific types). The Transaction Extractor role necessarily performs all conversions and checks, so that acts as a backstop.

Once we've figured out and agreed on the structure, then it may make sense to add certain newtypes that pczt can rely on (depending on who the downstream consumers of the crate will be).

Copy link
Contributor

@daira daira Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arya2 I don't think it does need to be able to produce v1 or v2 transactions, or v3 for that matter. I only know of use cases for using PCZTs to produce new transactions, which would necessarily have to be at least v4 (required since Sapling).


pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
}

impl Global {
pub(crate) fn merge(mut self, other: Self) -> Option<Self> {
let Self {
tx_version,
version_group_id,
consensus_branch_id,
lock_time,
expiry_height,
proprietary,
} = other;

if self.tx_version != tx_version
|| self.version_group_id != version_group_id
|| self.consensus_branch_id != consensus_branch_id
|| self.lock_time != lock_time
|| self.expiry_height != expiry_height
{
return None;
}

if !merge_map(&mut self.proprietary, proprietary) {
return None;
}

Some(self)
}
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Zip32Derivation {
/// The [ZIP 32 seed fingerprint](https://zips.z.cash/zip-0032#seed-fingerprints).
pub(crate) seed_fingerprint: [u8; 32],

/// The sequence of indices corresponding to the shielded HD path.
///
/// Indices can be hardened or non-hardened (i.e. the hardened flag bit may be set).
/// When used with a Sapling or Orchard spend, the derivation path will generally be
/// entirely hardened; when used with a transparent spend, the derivation path will
/// generally include a non-hardened section matching either the [BIP 44] path, or the
/// path at which ephemeral addresses are derived for [ZIP 320] transactions.
///
/// [BIP 44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
/// [ZIP 320]: https://zips.z.cash/zip-0320
pub(crate) derivation_path: Vec<u32>,
}
Loading