Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,21 @@ export class ZcashIronwoodBitGoPsbt extends ZcashBitGoPsbt {
combineProof(proof: Uint8Array): Uint8Array {
return this.wasm.combine_ironwood_proof(proof);
}

/**
* The raw serialized orchard PCZT (Partially Created Zcash Transaction) bundle stored in this
* PSBT, or `undefined` if none is present.
*
* A PCZT is the shielded-bundle counterpart to a PSBT: it accumulates the orchard action
* (spend + output), the ZIP-244-committed `out_ciphertext`, and — once {@link combineProof} has
* run — the Halo2 `zkproof` and binding signature, bridging this PSBT to the external proof
* service and back.
*
* Present only after {@link addShieldedOutput} has been called; `undefined` beforehand, and
* `undefined` again after {@link combineProof} succeeds, since that call drops the stored PCZT
* to make extraction terminal (see its doc comment).
*/
getPczt(): Uint8Array | undefined {
return this.wasm.ironwood_pczt_bytes();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,12 @@ impl ZcashBitGoPsbt {
crate::zcash::ironwood_pczt::deserialize_pczt(&bytes).map_err(|e| e.to_string())
}

/// The raw serialized orchard PCZT bytes stored in the proprietary map, or `None` if absent
/// (before [`Self::add_ironwood_output`], or after [`Self::combine_ironwood_proof`] drops it).
pub fn raw_ironwood_pczt_bytes(&self) -> Option<Vec<u8>> {
super::propkv::get_ironwood_pczt(&self.psbt)
}

/// Client-managed `ovk`, raw-key form: re-encrypt the Ironwood output's `out_ciphertext` under
/// an `ovk` derived as the ECDH agreement of `bitgo_pubkey` and `user_privkey`. The `ovk` never
/// leaves this call — it is not returned and not persisted anywhere.
Expand Down
16 changes: 16 additions & 0 deletions packages/wasm-utxo/src/wasm/fixed_script_wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,22 @@ impl BitGoPsbt {
Ok(tx)
}

/// The raw serialized orchard PCZT (Partially Created Zcash Transaction) bundle stored in
/// this v6 (Ironwood) PSBT's proprietary key-value map, or `undefined` if none is present.
///
/// A PCZT is the shielded-bundle counterpart to a PSBT: it accumulates the orchard action
/// (spend + output), the ZIP-244-committed `out_ciphertext`, and — once
/// [`Self::combine_ironwood_proof`] has run — the Halo2 `zkproof` and binding signature,
/// bridging this PSBT to the external proof service and back (see
/// [`crate::zcash::ironwood_pczt`] / [`crate::zcash::ironwood_build`]).
///
/// Present only after [`Self::add_ironwood_output`] has been called on a v6 PSBT; `undefined`
/// beforehand, and `undefined` again after [`Self::combine_ironwood_proof`] succeeds, since
/// that call drops the stored PCZT to make extraction terminal (see its doc comment).
pub fn ironwood_pczt_bytes(&self) -> Option<Vec<u8>> {
self.zcash().ok().and_then(|z| z.raw_ironwood_pczt_bytes())
}

/// Convert a half-signed legacy transaction to a psbt-lite.
///
/// # Arguments
Expand Down
28 changes: 28 additions & 0 deletions packages/wasm-utxo/test/fixedScript/zcashIronwoodPsbt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,32 @@ describe("ZcashIronwoodBitGoPsbt v6 (Ironwood)", function () {
});
});

describe("getPczt", function () {
it("is undefined before a shielded output has been added", function () {
const psbt = ZcashIronwoodBitGoPsbt.createEmpty("zcashTest", walletKeys, {
blockHeight: NU6_3_TESTNET_HEIGHT,
});
assert.strictEqual(psbt.getPczt(), undefined);
});

it("is present after addShieldedOutput, and survives a serialize round-trip", function () {
const psbt = buildShieldPsbt();
const pczt = psbt.getPczt();
assert.ok(pczt instanceof Uint8Array);
assert.ok(pczt.length > 0);

const round = ZcashIronwoodBitGoPsbt.fromBytes(psbt.serialize(), "zcashTest");
assert.deepStrictEqual(round.getPczt(), pczt);
});

it("stays present when combineProof fails, so the call is retryable", function () {
const psbt = buildShieldPsbt();
const pczt = psbt.getPczt();
assert.throws(() => psbt.combineProof(new Uint8Array(192)));
assert.deepStrictEqual(psbt.getPczt(), pczt);
});
});

it("the default memo is the ZIP-302 no-memo encoding, not all zeros", function () {
// `addShieldedOutput` defaults `memo` to this. Asserted on the encoding rather than by comparing
// txids across two builds: `construct_shield_pczt` draws a random rseed, so two separately-built
Expand Down Expand Up @@ -421,8 +447,10 @@ describe("ZcashIronwoodBitGoPsbt v6 (Ironwood)", function () {

// A placeholder proof of the real (4992-byte, single-action) size stands in for the external
// prover; the transparent side is what this test actually exercises.
assert.ok(psbt.getPczt() !== undefined, "PCZT present before combine");
const tx = psbt.combineProof(new Uint8Array(4992));
assert.ok(tx.length > 0, "produced a broadcast-ready v6 transaction");
assert.strictEqual(psbt.getPczt(), undefined, "combineProof drops the stored PCZT");
});

it("accepts any WalletKeysArg form, not just a RootWalletKeys instance", function () {
Expand Down
Loading