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

Implement PCZT support #440

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft

Implement PCZT support #440

wants to merge 1 commit into from

Conversation

str4d
Copy link
Contributor

@str4d str4d commented Nov 27, 2024

No description provided.

@str4d str4d marked this pull request as draft November 27, 2024 10:03
///
/// - This is chosen by the Constructor.
/// - This is required by the IO Finalizer, and is cleared by it once used.
/// - Signers MUST reject PCZTs that contain `dummy_sk` values.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • I both named this dummy_sk and added a "MUST reject" here because I do not want PCZTs to get serialized with non-dummy spending keys.
  • I stored the dummy note's sk instead of the smaller-scoped ask, because in Orchard we impose validity requirements at parse time of sk (specifically to ensure that a valid ivk is produced), and we already had APIs for handling that parsing here. If desired this can be scoped down to dummy_ask, but given that (again) this field is only for dummy notes, I think sk should be fine here.

pub fn parse(
actions: Vec<Action>,
flags: u8,
value_sum: (u64, bool),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I decided on this serialization format for ValueSum (rather than e.g. i128) because it has no edge cases; all values are valid.

Copy link
Contributor

@nuttycom nuttycom left a comment

Choose a reason for hiding this comment

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

utACK e7a2945 with minor questions / naming nits. Overall this looks excellent.

src/builder.rs Show resolved Hide resolved
/// 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.
pub(crate) value_sum: ValueSum,
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't the fact that a mut getter is exposed for actions make it so that this can get out of sync with the actions that have been added? Would it be safer to have explicit add_action and redact_action methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The mut getter here is actually for the Fun Third Option: mutating existing actions so that signatures can be applied. In the PCZT format PR I implemented all the roles as consuming / transforming, but the orchard APIs are what get used under the hood to do the transformations, and for the scale of PCZTs a mapping-based API with the necessary granularity would be ridiculously verbose and onerous to work with.

If we don't want to expose the dual capabilities of "modify existing Actions" and "add / remove Actions" here, then I can replace this with a method that returns &mut [Action] instead (hiding the Vec nature and only exposing the modification capability). That would be sufficient for now as the "add Actions" capability is currently provided by Builder (I have no plans to enable piece-wise PCZT building in this first pass).

Copy link
Contributor

@nuttycom nuttycom Nov 27, 2024

Choose a reason for hiding this comment

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

Hiding the Vec nature (for mutation) sounds like the right approach to me. Right now it presents a footgun, which we can eliminate.

Comment on lines +147 to +148
/// TODO: This could be merged with `rseed` into a tuple. `recipient` and `value` are
/// separate because they might need to be independently redacted. (For which role?)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// TODO: This could be merged with `rseed` into a tuple. `recipient` and `value` are
/// separate because they might need to be independently redacted. (For which role?)
// TODO: This could be merged with `rseed` into a tuple. `recipient` and `value` are
// separate because they might need to be independently redacted. (For which role?)

let (anchor, merkle_path) = {
let cmx: ExtractedNoteCommitment = note.commitment().into();
let leaf = MerkleHashOrchard::from_cmx(&cmx);
let mut tree = BridgeTree::<MerkleHashOrchard, u32, 32>::new(100);
Copy link
Contributor

Choose a reason for hiding this comment

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

TODO: since BridgeTree is no longer being maintained, we should move away from using it.


/// Authorizing data for a bundle of actions that is just missing a binding signature.
#[derive(Debug)]
pub struct Unbound {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this name isn't particularly intuitive to me, but I don't have a better suggestion; maybe BindingSigInputs or something?

Copy link
Contributor Author

@str4d str4d Nov 27, 2024

Choose a reason for hiding this comment

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

The name appears primarily in type signatures, i.e. Bundle<Unbound, ZatBalance>; I chose it to make sense there (same as Authorized / Unauthorized / EffectsOnly).

/// Verifies the given sighash with every `spend_auth_sig`, and then binds the bundle.
///
/// Returns `None` if the given sighash does not validate against every `spend_auth_sig`.
pub fn bind<R: RngCore + CryptoRng>(
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe apply_binding_sig or bind_to_sighash?


// Add signatures to dummy spends.
for action in self.actions.iter_mut() {
if let Some(sk) = action.spend.dummy_sk.take() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Point the take() out.

/// 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.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • Remove shared_secret reference.
  • Describe how the Signer can use rseed and recipient to decrypt and verify enc_ciphertext (in the case where ock is not present, or OvkPolicy::None was used to make the data unrecoverable from chain by the sender).


// Run the Creator and Constructor roles.
let mut builder = Builder::new(BundleType::DEFAULT, anchor);
builder.add_spend(fvk, note, merkle_path).unwrap();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Modify this test to send an output to a different recipient (with change), and then verify that the PCZT contains sufficient information to decrypt and check enc_ciphertext.

sighash: [u8; 32],
rng: R,
) -> Option<crate::Bundle<Authorized, V>> {
let bound = self.map_authorization(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Move this to after the check so we don't bother making the signature if the binding would be wrong.

MissingBindingSignatureSigningKey,
/// The Transaction Extractor role requires `zkproof` to be set.
MissingProof,
/// The Transaction Extractor role requires all `zkproof` fields to be set.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
/// The Transaction Extractor role requires all `zkproof` fields to be set.
/// The Transaction Extractor role requires all `spend_auth_sig` fields to be set.

Ok(redpallas::Signature::from(
action
.spend
.spend_auth_sig
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy-pasta: this is already a redpallas::Signature.

.map(|r| {
Address::from_raw_address_bytes(r)
.into_option()
.ok_or(ParseError::InvalidSpendRecipient)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rename this error to InvalidRecipient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants