Skip to content
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
7 changes: 6 additions & 1 deletion modules/abstract-utxo/src/abstractUtxoCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,12 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
// descriptor Psbt, skipping the fixedScriptWallet.BitGoPsbt intermediate.
return explainTx(decodeDescriptorPsbt(params), { ...params, wallet }, this.wasmName, this.addressCodec);
}
return explainTx(this.decodeTransactionFromPrebuild(params), { ...params, wallet }, this.wasmName, this.addressCodec);
return explainTx(
this.decodeTransactionFromPrebuild(params),
{ ...params, wallet },
this.wasmName,
this.addressCodec
);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function toExpectedOutputs(
});
if (txParams.allowExternalChangeAddress && txParams.changeAddress) {
expectedOutputs.push({
script: addressCodec.decodeChangeScript(txParams.changeAddress),
script: Buffer.from(addressCodec.decodeChangeAddress(txParams.changeAddress)),
// When an external change address is explicitly specified, count all outputs going towards that
// address in the expected outputs (regardless of the output amount)
value: 'max',
Expand Down Expand Up @@ -254,10 +254,9 @@ export async function parseTransaction<TNumber extends bigint | number>(
function toComparableOutputsWithExternal(outputs: Output[]): ComparableOutputWithAddress<bigint | 'max'>[] {
return outputs.map((output) => ({
// Change/custom-change outputs are always transparent wallet addresses.
script:
output.external === false
? addressCodec.decodeChangeScript(output.address)
: addressCodec.fromExtendedAddressFormatToScript(output.address),
script: output.external
? Buffer.from(addressCodec.decodeExternalAddress(output.address))
: Buffer.from(addressCodec.decodeChangeAddress(output.address)),
value: output.amount === 'max' ? 'max' : (BigInt(output.amount) as bigint | 'max'),
external: output.external,
address: output.address,
Expand Down Expand Up @@ -297,7 +296,7 @@ export async function parseTransaction<TNumber extends bigint | number>(

function toOutputs(outputs: ExpectedOutputWithAddress[] | ComparableOutputWithAddress<bigint | 'max'>[]): Output[] {
return outputs.map((output) => ({
address: addressCodec.outputScriptToAddress(output.script, output.address),
address: addressCodec.toExtendedAddressFormat(output.script, output.address),
amount: output.value.toString(),
external: output.external,
}));
Expand Down
32 changes: 13 additions & 19 deletions modules/abstract-utxo/src/transaction/recipient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,10 @@ export class AddressCodec {
return this.decode(address);
}

/** Resolve a transparent change address directly to a Buffer script. */
decodeChangeScript(address: string): Buffer {
return Buffer.from(this.decodeChangeAddress(address));
}

/**
* Convert an output's scriptPubKey back to the address form the output should report. The
* base implementation encodes the script. Coins whose output scripts cannot always be
* re-encoded (e.g. Zcash shielded recipients, whose raw Orchard receiver has no scriptPubKey
* encoding) override this and may fall back to the output's original address.
*/
outputScriptToAddress(script: Buffer, address?: string): string {
return this.toExtendedAddressFormat(script);
/** Resolve an external address, including extended script recipients, to its script. */
decodeExternalAddress(address: string): Uint8Array {
const result = AddressCodec.fromExtendedAddressFormat(address);
return 'script' in result ? Buffer.from(result.script, 'hex') : this.decode(result.address);
}

encode(script: Uint8Array): string {
Expand All @@ -84,13 +75,10 @@ export class AddressCodec {
}

fromExtendedAddressFormatToScript(extendedAddress: string): Buffer {
const result = AddressCodec.fromExtendedAddressFormat(extendedAddress);
if ('script' in result) {
return Buffer.from(result.script, 'hex');
}
return Buffer.from(this.decode(result.address));
return Buffer.from(this.decodeExternalAddress(extendedAddress));
}

/** Check whether a parsed output address resolves to its raw script. */
isMatchingScript(output: AddressCodecOutput): boolean {
if (output.address === undefined || output.address === null) {
return true;
Expand All @@ -116,7 +104,13 @@ export class AddressCodec {
throw new Error('invalid input');
}

toExtendedAddressFormat(script: Buffer): string {
toExtendedAddressFormat(script: Buffer, address?: string): string {
if (address !== undefined && !this.isMatchingScript({ address, script })) {
throw new Error(`address ${address} does not match output script`);
}
if (address !== undefined) {
return address;
}
return script[0] === OP_RETURN ? `${ScriptRecipientPrefix}${script.toString('hex')}` : this.encode(script);
}
}
Expand Down
15 changes: 12 additions & 3 deletions modules/abstract-utxo/test/unit/bip322.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,10 @@ describe('BIP322', function () {
it('should successfully run with a user nonce', function () {
const psbt = createUnsignedPsbt();
assertCommon(
explainPsbtWasm(psbt, walletKeys, { addressCodec: new AddressCodec('btc'), replayProtection: { publicKeys: [] } }),
explainPsbtWasm(psbt, walletKeys, {
addressCodec: new AddressCodec('btc'),
replayProtection: { publicKeys: [] },
}),
0
);
});
Expand All @@ -450,7 +453,10 @@ describe('BIP322', function () {
const psbt = createUnsignedPsbt();
psbt.sign(BIP32.fromBase58(xprivs[0]));
assertCommon(
explainPsbtWasm(psbt, walletKeys, { addressCodec: new AddressCodec('btc'), replayProtection: { publicKeys: [] } }),
explainPsbtWasm(psbt, walletKeys, {
addressCodec: new AddressCodec('btc'),
replayProtection: { publicKeys: [] },
}),
1
);
});
Expand All @@ -460,7 +466,10 @@ describe('BIP322', function () {
psbt.sign(BIP32.fromBase58(xprivs[0]));
psbt.sign(BIP32.fromBase58(xprivs[2]));
assertCommon(
explainPsbtWasm(psbt, walletKeys, { addressCodec: new AddressCodec('btc'), replayProtection: { publicKeys: [] } }),
explainPsbtWasm(psbt, walletKeys, {
addressCodec: new AddressCodec('btc'),
replayProtection: { publicKeys: [] },
}),
2
);
});
Expand Down