diff --git a/modules/abstract-utxo/src/address/fixedScript.ts b/modules/abstract-utxo/src/address/fixedScript.ts index 6c77abbf9e..fd3002124a 100644 --- a/modules/abstract-utxo/src/address/fixedScript.ts +++ b/modules/abstract-utxo/src/address/fixedScript.ts @@ -78,6 +78,10 @@ export function generateAddress(coinName: UtxoCoinName, params: GenerateFixedScr const { keychains, chain, segwit = false, bech32 = false } = params as GenerateFixedScriptAddressOptions; + if (_.isNumber(chain) && _.isInteger(chain) && !fixedScriptWallet.ChainCode.is(chain)) { + throw new InvalidAddressDerivationPropertyError(`address validation failure: unrecognised chain code (${chain})`); + } + let derivationChain: ChainCode = fixedScriptWallet.ChainCode.value('p2sh', 'external'); if (_.isNumber(chain) && _.isInteger(chain) && fixedScriptWallet.ChainCode.is(chain)) { derivationChain = chain; diff --git a/modules/abstract-utxo/test/unit/address.ts b/modules/abstract-utxo/test/unit/address.ts index 3e64975ba0..8a66840f64 100644 --- a/modules/abstract-utxo/test/unit/address.ts +++ b/modules/abstract-utxo/test/unit/address.ts @@ -1,6 +1,7 @@ import * as assert from 'assert'; import { InvalidAddressDerivationPropertyError, UnexpectedAddressError } from '@bitgo/sdk-core'; +import { fixedScriptWallet } from '@bitgo/wasm-utxo'; import { assertFixedScriptWalletAddress, generateAddress } from '../../src'; @@ -8,6 +9,10 @@ import { keychainsBase58 } from './util'; const keychains = keychainsBase58.map((k) => ({ pub: k.pub })); +// A chain code that no released SDK version has ever defined — simulates a future +// server-side address type unknown to an older client. +const unknownChainCode = 99; + describe('assertFixedScriptWalletAddress', function () { describe('input validation', function () { it('throws InvalidAddressDerivationPropertyError when both chain and index are undefined', function () { @@ -121,6 +126,40 @@ describe('assertFixedScriptWalletAddress', function () { ); }); + // Regression guard for T1-3386 / T1-3385: an unknown chain code must throw + // immediately with a clear message rather than silently falling back to P2SH + // and producing a confusing "expected but got " error. + it('throws InvalidAddressDerivationPropertyError for an unknown chain code', function () { + assert.ok(!fixedScriptWallet.ChainCode.is(unknownChainCode), 'test prerequisite: chain must be unknown'); + const serverAddress = generateAddress('btc', { keychains, chain: 40 }); + assert.throws( + () => + assertFixedScriptWalletAddress('btc', { + chain: unknownChainCode, + index: 0, + keychains, + format: 'base58', + address: serverAddress, + }), + (err: unknown) => { + assert.ok(err instanceof InvalidAddressDerivationPropertyError); + assert.ok( + err.message.includes(String(unknownChainCode)), + `expected error to name the unrecognised chain code, got: ${err.message}` + ); + return true; + } + ); + }); + + it('generateAddress throws InvalidAddressDerivationPropertyError for an unknown chain code', function () { + assert.ok(!fixedScriptWallet.ChainCode.is(unknownChainCode), 'test prerequisite: chain must be unknown'); + assert.throws( + () => generateAddress('btc', { keychains, chain: unknownChainCode }), + InvalidAddressDerivationPropertyError + ); + }); + it('succeeds for bch cashaddr (chain 0)', function () { const address = generateAddress('bch', { keychains, chain: 0, format: 'cashaddr' }); assert.doesNotThrow(() =>