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
60 changes: 34 additions & 26 deletions packages/wasm-utxo/js/fixedScriptWallet/BitGoPsbt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,15 @@ export type AddWalletOutputOptions = {
};

export class BitGoPsbt {
protected constructor(protected wasm: WasmBitGoPsbt) {}
protected constructor(protected _wasm: WasmBitGoPsbt) {}

/**
* Get the underlying WASM instance
* @internal - for use by other wasm-utxo modules
*/
get wasm(): WasmBitGoPsbt {
return this._wasm;
}

/**
* Create an empty PSBT for the given network with wallet keys
Expand Down Expand Up @@ -135,13 +143,13 @@ export class BitGoPsbt {
options?: CreateEmptyOptions,
): BitGoPsbt {
const keys = RootWalletKeys.from(walletKeys);
const wasm = WasmBitGoPsbt.create_empty(
const wasmPsbt = WasmBitGoPsbt.create_empty(
network,
keys.wasm,
options?.version,
options?.lockTime,
);
return new BitGoPsbt(wasm);
return new BitGoPsbt(wasmPsbt);
}

/**
Expand Down Expand Up @@ -175,7 +183,7 @@ export class BitGoPsbt {
* ```
*/
addInput(options: AddInputOptions, script: Uint8Array): number {
return this.wasm.add_input(
return this._wasm.add_input(
options.txid,
options.vout,
options.value,
Expand All @@ -200,7 +208,7 @@ export class BitGoPsbt {
* ```
*/
addOutput(options: AddOutputOptions): number {
return this.wasm.add_output(options.script, options.value);
return this._wasm.add_output(options.script, options.value);
}

/**
Expand Down Expand Up @@ -248,7 +256,7 @@ export class BitGoPsbt {
walletOptions: AddWalletInputOptions,
): number {
const keys = RootWalletKeys.from(walletKeys);
return this.wasm.add_wallet_input(
return this._wasm.add_wallet_input(
inputOptions.txid,
inputOptions.vout,
inputOptions.value,
Expand Down Expand Up @@ -294,7 +302,7 @@ export class BitGoPsbt {
*/
addWalletOutput(walletKeys: WalletKeysArg, options: AddWalletOutputOptions): number {
const keys = RootWalletKeys.from(walletKeys);
return this.wasm.add_wallet_output(options.chain, options.index, options.value, keys.wasm);
return this._wasm.add_wallet_output(options.chain, options.index, options.value, keys.wasm);
}

/**
Expand All @@ -318,7 +326,7 @@ export class BitGoPsbt {
*/
addReplayProtectionInput(inputOptions: AddInputOptions, key: ECPairArg): number {
const ecpair = ECPair.from(key);
return this.wasm.add_replay_protection_input(
return this._wasm.add_replay_protection_input(
ecpair.wasm,
inputOptions.txid,
inputOptions.vout,
Expand All @@ -332,23 +340,23 @@ export class BitGoPsbt {
* @returns The unsigned transaction ID
*/
unsignedTxid(): string {
return this.wasm.unsigned_txid();
return this._wasm.unsigned_txid();
}

/**
* Get the transaction version
* @returns The transaction version number
*/
get version(): number {
return this.wasm.version();
return this._wasm.version();
}

/**
* Get the transaction lock time
* @returns The transaction lock time
*/
get lockTime(): number {
return this.wasm.lock_time();
return this._wasm.lock_time();
}

/**
Expand All @@ -364,9 +372,9 @@ export class BitGoPsbt {
payGoPubkeys?: ECPairArg[],
): ParsedTransaction {
const keys = RootWalletKeys.from(walletKeys);
const rp = ReplayProtection.from(replayProtection, this.wasm.network());
const rp = ReplayProtection.from(replayProtection, this._wasm.network());
const pubkeys = payGoPubkeys?.map((arg) => ECPair.from(arg).wasm);
return this.wasm.parse_transaction_with_wallet_keys(
return this._wasm.parse_transaction_with_wallet_keys(
keys.wasm,
rp.wasm,
pubkeys,
Expand All @@ -391,7 +399,7 @@ export class BitGoPsbt {
): ParsedOutput[] {
const keys = RootWalletKeys.from(walletKeys);
const pubkeys = payGoPubkeys?.map((arg) => ECPair.from(arg).wasm);
return this.wasm.parse_outputs_with_wallet_keys(keys.wasm, pubkeys) as ParsedOutput[];
return this._wasm.parse_outputs_with_wallet_keys(keys.wasm, pubkeys) as ParsedOutput[];
}

/**
Expand All @@ -406,7 +414,7 @@ export class BitGoPsbt {
* @throws Error if output index is out of bounds or entropy is not 64 bytes
*/
addPayGoAttestation(outputIndex: number, entropy: Uint8Array, signature: Uint8Array): void {
this.wasm.add_paygo_attestation(outputIndex, entropy, signature);
this._wasm.add_paygo_attestation(outputIndex, entropy, signature);
}

/**
Expand Down Expand Up @@ -448,12 +456,12 @@ export class BitGoPsbt {
// Try to parse as BIP32Arg first (string or BIP32 instance)
if (typeof key === "string" || ("derive" in key && typeof key.derive === "function")) {
const wasmKey = BIP32.from(key as BIP32Arg).wasm;
return this.wasm.verify_signature_with_xpub(inputIndex, wasmKey);
return this._wasm.verify_signature_with_xpub(inputIndex, wasmKey);
}

// Otherwise it's an ECPairArg (Uint8Array, ECPair, or WasmECPair)
const wasmECPair = ECPair.from(key as ECPairArg).wasm;
return this.wasm.verify_signature_with_pub(inputIndex, wasmECPair);
return this._wasm.verify_signature_with_pub(inputIndex, wasmECPair);
}

/**
Expand Down Expand Up @@ -508,11 +516,11 @@ export class BitGoPsbt {
) {
// It's a BIP32Arg
const wasmKey = BIP32.from(key as BIP32Arg);
this.wasm.sign_with_xpriv(inputIndex, wasmKey.wasm);
this._wasm.sign_with_xpriv(inputIndex, wasmKey.wasm);
} else {
// It's an ECPairArg
const wasmKey = ECPair.from(key as ECPairArg);
this.wasm.sign_with_privkey(inputIndex, wasmKey.wasm);
this._wasm.sign_with_privkey(inputIndex, wasmKey.wasm);
}
}

Expand Down Expand Up @@ -540,8 +548,8 @@ export class BitGoPsbt {
inputIndex: number,
replayProtection: ReplayProtectionArg,
): boolean {
const rp = ReplayProtection.from(replayProtection, this.wasm.network());
return this.wasm.verify_replay_protection_signature(inputIndex, rp.wasm);
const rp = ReplayProtection.from(replayProtection, this._wasm.network());
return this._wasm.verify_replay_protection_signature(inputIndex, rp.wasm);
}

/**
Expand All @@ -550,7 +558,7 @@ export class BitGoPsbt {
* @returns The serialized PSBT as a byte array
*/
serialize(): Uint8Array {
return this.wasm.serialize();
return this._wasm.serialize();
}

/**
Expand Down Expand Up @@ -593,7 +601,7 @@ export class BitGoPsbt {
*/
generateMusig2Nonces(key: BIP32Arg, sessionId?: Uint8Array): void {
const wasmKey = BIP32.from(key);
this.wasm.generate_musig2_nonces(wasmKey.wasm, sessionId);
this._wasm.generate_musig2_nonces(wasmKey.wasm, sessionId);
}

/**
Expand All @@ -616,7 +624,7 @@ export class BitGoPsbt {
* ```
*/
combineMusig2Nonces(sourcePsbt: BitGoPsbt): void {
this.wasm.combine_musig2_nonces(sourcePsbt.wasm);
this._wasm.combine_musig2_nonces(sourcePsbt.wasm);
}

/**
Expand All @@ -625,7 +633,7 @@ export class BitGoPsbt {
* @throws Error if any input failed to finalize
*/
finalizeAllInputs(): void {
this.wasm.finalize_all_inputs();
this._wasm.finalize_all_inputs();
}

/**
Expand All @@ -635,6 +643,6 @@ export class BitGoPsbt {
* @throws Error if the PSBT is not fully finalized or extraction fails
*/
extractTransaction(): Uint8Array {
return this.wasm.extract_transaction();
return this._wasm.extract_transaction();
}
}
99 changes: 99 additions & 0 deletions packages/wasm-utxo/js/fixedScriptWallet/Dimensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { WasmDimensions } from "../wasm/wasm_utxo.js";
import type { BitGoPsbt, InputScriptType, SignPath } from "./BitGoPsbt.js";
import type { CoinName } from "../coinName.js";
import { toOutputScriptWithCoin } from "../address.js";

type FromInputParams = { chain: number; signPath?: SignPath } | { scriptType: InputScriptType };

/**
* Dimensions class for estimating transaction virtual size.
*
* Tracks weight internally with min/max bounds to handle ECDSA signature variance.
* Schnorr signatures have no variance (always 64 bytes).
*
* This is a thin wrapper over the WASM implementation.
*/
export class Dimensions {
private constructor(private _wasm: WasmDimensions) {}

/**
* Create empty dimensions (zero weight)
*/
static empty(): Dimensions {
return new Dimensions(WasmDimensions.empty());
}

/**
* Create dimensions from a BitGoPsbt
*
* Parses PSBT inputs and outputs to compute weight bounds without
* requiring wallet keys. Input types are detected from BIP32 derivation
* paths stored in the PSBT.
*/
static fromPsbt(psbt: BitGoPsbt): Dimensions {
return new Dimensions(WasmDimensions.from_psbt(psbt.wasm));
}

/**
* Create dimensions for a single input
*
* @param params - Either `{ chain, signPath? }` or `{ scriptType }`
*/
static fromInput(params: FromInputParams): Dimensions {
if ("scriptType" in params) {
return new Dimensions(WasmDimensions.from_input_script_type(params.scriptType));
}
return new Dimensions(
WasmDimensions.from_input(params.chain, params.signPath?.signer, params.signPath?.cosigner),
);
}

/**
* Create dimensions for a single output from script bytes
*/
static fromOutput(script: Uint8Array): Dimensions;
/**
* Create dimensions for a single output from an address
*/
static fromOutput(address: string, network: CoinName): Dimensions;
static fromOutput(scriptOrAddress: Uint8Array | string, network?: CoinName): Dimensions {
if (typeof scriptOrAddress === "string") {
if (network === undefined) {
throw new Error("network is required when passing an address string");
}
const script = toOutputScriptWithCoin(scriptOrAddress, network);
return new Dimensions(WasmDimensions.from_output_script(script));
}
return new Dimensions(WasmDimensions.from_output_script(scriptOrAddress));
}

/**
* Combine with another Dimensions instance
*/
plus(other: Dimensions): Dimensions {
return new Dimensions(this._wasm.plus(other._wasm));
}

/**
* Whether any inputs are segwit (affects overhead calculation)
*/
get hasSegwit(): boolean {
return this._wasm.has_segwit();
}

/**
* Get total weight (min or max)
* @param size - "min" or "max", defaults to "max"
*/
getWeight(size: "min" | "max" = "max"): number {
return this._wasm.get_weight(size);
}

/**
* Get virtual size (min or max)
* @param size - "min" or "max", defaults to "max"
*/
getVSize(size: "min" | "max" = "max"): number {
return this._wasm.get_vsize(size);
}
}
1 change: 1 addition & 0 deletions packages/wasm-utxo/js/fixedScriptWallet/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { RootWalletKeys, type WalletKeysArg, type IWalletKeys } from "./RootWalletKeys.js";
export { ReplayProtection, type ReplayProtectionArg } from "./ReplayProtection.js";
export { outputScript, address } from "./address.js";
export { Dimensions } from "./Dimensions.js";

// Bitcoin-like PSBT (for all non-Zcash networks)
export {
Expand Down
1 change: 1 addition & 0 deletions packages/wasm-utxo/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export * as ecpair from "./ecpair.js";
// Only the most commonly used classes and types are exported at the top level for convenience
export { ECPair } from "./ecpair.js";
export { BIP32 } from "./bip32.js";
export { Dimensions } from "./fixedScriptWallet/Dimensions.js";

export type { CoinName } from "./coinName.js";
export type { Triple } from "./triple.js";
Expand Down
11 changes: 11 additions & 0 deletions packages/wasm-utxo/js/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ export class Transaction {
return this._wasm.to_bytes();
}

/**
* Get the virtual size of the transaction
*
* Virtual size accounts for the segwit discount on witness data.
*
* @returns The virtual size in virtual bytes (vbytes)
*/
getVSize(): number {
return this._wasm.get_vsize();
}

/**
* @internal
*/
Expand Down
Loading