Skip to content

Commit 9405b00

Browse files
OttoAllmendingerllm-git
andcommitted
feat(wasm-utxo): add PSBT fromNetworkFormat
Deprecate fromHalfSignedLegacyTransaction in favor of fromNetworkFormat, which accepts unsigned, half-signed, and fully-signed transactions. Signature-count enforcement moves to callers. The new API parses fixed-script inputs (multisig or replay protection) and applies signatures using either positional slot indices (if any OP_0 placeholders exist) or ECDSA verification against wallet pubkeys (if all slots are compact). This enables hydration of finalized transactions that were previously rejected by the legacy parser. For Zcash, use ZcashBitGoPsbt.fromNetworkFormat with block height or explicit consensus branch ID. All legacy variants delegate to the new implementation internally for backward compatibility. Issue: BTC-0 Co-authored-by: llm-git <llm-git@ttll.de>
1 parent a8fc88d commit 9405b00

8 files changed

Lines changed: 994 additions & 595 deletions

File tree

packages/wasm-utxo/js/fixedScriptWallet/BitGoPsbt.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,19 +193,22 @@ export class BitGoPsbt extends PsbtBase<WasmBitGoPsbt> implements IPsbtWithAddre
193193
/**
194194
* Convert a half-signed legacy transaction to a psbt-lite.
195195
*
196+
* @deprecated Use `fromNetworkFormat()` instead. Signature-count enforcement
197+
* (exactly 1 sig per wallet input) is moving to the caller.
198+
*
196199
* Extracts partial signatures from scriptSig/witness and creates a PSBT
197200
* with proper wallet metadata (bip32Derivation, scripts, witnessUtxo).
198201
* Only supports p2sh, p2shP2wsh, and p2wsh inputs (not taproot).
199202
*
200203
* Supports both Bitcoin-like coins (BTC, LTC, DOGE) and Dash (DASH).
201-
* Zcash is NOT supported; use ZcashBitGoPsbt.fromHalfSignedLegacyTransaction instead.
204+
* Zcash is NOT supported; use ZcashBitGoPsbt.fromNetworkFormat instead.
202205
*
203206
* @param txBytesOrTx - Transaction bytes or decoded transaction instance (Bitcoin-like or Dash)
204207
* @param network - Network name
205208
* @param walletKeys - The wallet's root keys
206209
* @param unspents - Chain, index, and value for each input
207210
* @param _options - Reserved for future use and signature compatibility with subclasses
208-
* @throws Error if transaction is Zcash (use ZcashBitGoPsbt.fromHalfSignedLegacyTransaction instead)
211+
* @throws Error if transaction is Zcash (use ZcashBitGoPsbt.fromNetworkFormat instead)
209212
*/
210213
static fromHalfSignedLegacyTransaction(
211214
txBytesOrTx: Uint8Array | Transaction | DashTransaction,
@@ -243,6 +246,47 @@ export class BitGoPsbt extends PsbtBase<WasmBitGoPsbt> implements IPsbtWithAddre
243246
return new BitGoPsbt(wasm);
244247
}
245248

249+
/**
250+
* Convert a network-format transaction to a PSBT.
251+
*
252+
* Accepts both the half-signed legacy format (5-slot scriptSig/witness with OP_0
253+
* placeholders) and the fully-signed network format (compact 4-item form). The
254+
* resulting PSBT will contain all partial signatures present in the transaction.
255+
*
256+
* Use this when you don't know ahead of time whether the transaction is half-signed
257+
* or fully-signed. For Zcash, use ZcashBitGoPsbt.fromNetworkFormat() instead.
258+
*
259+
* @param txBytesOrTx - Transaction bytes or decoded Transaction/DashTransaction
260+
* @param network - Network name
261+
* @param walletKeys - The wallet's root keys
262+
* @param unspents - Chain, index, and value for each input
263+
*/
264+
static fromNetworkFormat(
265+
txBytesOrTx: Uint8Array | Transaction | DashTransaction,
266+
network: NetworkName,
267+
walletKeys: WalletKeysArg,
268+
unspents: HydrationUnspent[],
269+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
270+
_options?: unknown,
271+
): BitGoPsbt {
272+
const keys = RootWalletKeys.from(walletKeys);
273+
274+
const tx =
275+
txBytesOrTx instanceof Uint8Array
276+
? Transaction.fromBytes(txBytesOrTx, toCoinName(network))
277+
: txBytesOrTx;
278+
279+
if (tx instanceof ZcashTransaction) {
280+
throw new Error("Use ZcashBitGoPsbt.fromNetworkFormat() for Zcash transactions");
281+
}
282+
283+
const wasm: WasmBitGoPsbt =
284+
tx instanceof DashTransaction
285+
? WasmBitGoPsbt.from_network_format_dash(tx.wasm, network, keys.wasm, unspents)
286+
: WasmBitGoPsbt.from_network_format(tx.wasm, network, keys.wasm, unspents);
287+
return new BitGoPsbt(wasm);
288+
}
289+
246290
/**
247291
* Add an input to the PSBT
248292
*

packages/wasm-utxo/js/fixedScriptWallet/ZcashBitGoPsbt.ts

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,14 @@ export class ZcashBitGoPsbt extends BitGoPsbt {
145145
}
146146

147147
/**
148-
* Reconstruct a Zcash PSBT from a half-signed legacy transaction
148+
* Reconstruct a Zcash PSBT from a network-format transaction (unsigned, half-signed, or fully-signed).
149149
*
150-
* This is the inverse of `getHalfSignedLegacyFormat()` for Zcash. It decodes the Zcash wire
151-
* format (which includes version_group_id, expiry_height, and sapling fields), extracts
152-
* partial signatures, and reconstructs a proper Zcash PSBT with consensus metadata.
150+
* This is the Zcash equivalent of `BitGoPsbt.fromNetworkFormat()`. It decodes the Zcash wire
151+
* format (which includes version_group_id, expiry_height, and sapling fields), extracts any
152+
* partial signatures present, and reconstructs a proper Zcash PSBT with consensus metadata.
153+
*
154+
* Use this as the modern replacement for `fromHalfSignedLegacyTransaction`. Signature-count
155+
* discovery (unsigned / half-signed / fully-signed) is left to the caller.
153156
*
154157
* Supports two modes for determining consensus_branch_id:
155158
* - **Recommended**: Pass `blockHeight` to auto-determine consensus_branch_id via network upgrade activation heights
@@ -161,28 +164,53 @@ export class ZcashBitGoPsbt extends BitGoPsbt {
161164
* @param unspents - Chain, index, and value for each input
162165
* @param options - Either `{ blockHeight: number }` or `{ consensusBranchId: number }`
163166
* @returns A ZcashBitGoPsbt instance
167+
*/
168+
static fromNetworkFormat(
169+
txBytesOrTx: Uint8Array | ITransaction,
170+
network: ZcashNetworkName,
171+
walletKeys: WalletKeysArg,
172+
unspents: HydrationUnspent[],
173+
options: { blockHeight: number } | { consensusBranchId: number },
174+
): ZcashBitGoPsbt {
175+
const keys = RootWalletKeys.from(walletKeys);
176+
const tx =
177+
txBytesOrTx instanceof Uint8Array
178+
? ZcashTransaction.fromBytes(txBytesOrTx)
179+
: (txBytesOrTx as ZcashTransaction);
180+
181+
if ("blockHeight" in options) {
182+
const wasm = WasmBitGoPsbt.from_network_format_zcash_with_block_height(
183+
tx.wasm,
184+
network,
185+
keys.wasm,
186+
unspents,
187+
options.blockHeight,
188+
);
189+
return new ZcashBitGoPsbt(wasm);
190+
} else {
191+
const wasm = WasmBitGoPsbt.from_network_format_zcash_with_branch_id(
192+
tx.wasm,
193+
network,
194+
keys.wasm,
195+
unspents,
196+
options.consensusBranchId,
197+
);
198+
return new ZcashBitGoPsbt(wasm);
199+
}
200+
}
201+
202+
/**
203+
* Reconstruct a Zcash PSBT from a half-signed legacy transaction.
164204
*
165-
* @example
166-
* ```typescript
167-
* // Round-trip with block height (recommended)
168-
* const legacyBytes = psbt.getHalfSignedLegacyFormat();
169-
* const reconstructed = ZcashBitGoPsbt.fromHalfSignedLegacyTransaction(
170-
* legacyBytes,
171-
* "zec",
172-
* walletKeys,
173-
* unspents,
174-
* { blockHeight: 1687105 } // NU5 activation height
175-
* );
205+
* @deprecated Use `fromNetworkFormat()` instead. Signature-count enforcement
206+
* (exactly 1 sig per wallet input) is moving to the caller.
176207
*
177-
* // Or with explicit consensus branch ID
178-
* const reconstructed = ZcashBitGoPsbt.fromHalfSignedLegacyTransaction(
179-
* legacyBytes,
180-
* "zec",
181-
* walletKeys,
182-
* unspents,
183-
* { consensusBranchId: 0xC2D6D0B4 } // NU5 branch ID
184-
* );
185-
* ```
208+
* @param txBytesOrTx - Either serialized Zcash transaction bytes or a decoded ZcashTransaction instance
209+
* @param network - Zcash network name ("zcash", "zcashTest", "zec", "tzec")
210+
* @param walletKeys - The wallet's root keys
211+
* @param unspents - Chain, index, and value for each input
212+
* @param options - Either `{ blockHeight: number }` or `{ consensusBranchId: number }`
213+
* @returns A ZcashBitGoPsbt instance
186214
*/
187215
static fromHalfSignedLegacyTransaction(
188216
txBytesOrTx: Uint8Array | ITransaction,
@@ -198,7 +226,7 @@ export class ZcashBitGoPsbt extends BitGoPsbt {
198226
: (txBytesOrTx as ZcashTransaction);
199227

200228
if ("blockHeight" in options) {
201-
const wasm = WasmBitGoPsbt.from_half_signed_legacy_transaction_zcash_with_block_height(
229+
const wasm = WasmBitGoPsbt.from_network_format_zcash_with_block_height(
202230
tx.wasm,
203231
network,
204232
keys.wasm,
@@ -207,7 +235,7 @@ export class ZcashBitGoPsbt extends BitGoPsbt {
207235
);
208236
return new ZcashBitGoPsbt(wasm);
209237
} else {
210-
const wasm = WasmBitGoPsbt.from_half_signed_legacy_transaction_zcash_with_branch_id(
238+
const wasm = WasmBitGoPsbt.from_network_format_zcash_with_branch_id(
211239
tx.wasm,
212240
network,
213241
keys.wasm,

0 commit comments

Comments
 (0)