Skip to content

Commit edbb6be

Browse files
fix(abstract-utxo): throw on unrecognised chain code
generateAddress previously fell back to chain=0 (P2SH) when given an integer chain code that ChainCode.is() did not recognise. This produced a misleading UnexpectedAddressError mentioning a P2SH address rather than anything about the unknown chain, as seen in T1-3385 where bitgo@18.0.1 did not know chain=40 (p2trMusig2). Now throws InvalidAddressDerivationPropertyError immediately, naming the unrecognised chain code so the caller knows to upgrade their SDK. Refs: T1-3386
1 parent 9ab5592 commit edbb6be

2 files changed

Lines changed: 30 additions & 44 deletions

File tree

modules/abstract-utxo/src/address/fixedScript.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ export function generateAddress(coinName: UtxoCoinName, params: GenerateFixedScr
7878

7979
const { keychains, chain, segwit = false, bech32 = false } = params as GenerateFixedScriptAddressOptions;
8080

81+
if (_.isNumber(chain) && _.isInteger(chain) && !fixedScriptWallet.ChainCode.is(chain)) {
82+
throw new InvalidAddressDerivationPropertyError(`address validation failure: unrecognised chain code (${chain})`);
83+
}
84+
8185
let derivationChain: ChainCode = fixedScriptWallet.ChainCode.value('p2sh', 'external');
8286
if (_.isNumber(chain) && _.isInteger(chain) && fixedScriptWallet.ChainCode.is(chain)) {
8387
derivationChain = chain;

modules/abstract-utxo/test/unit/address.ts

Lines changed: 26 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -126,56 +126,38 @@ describe('assertFixedScriptWalletAddress', function () {
126126
);
127127
});
128128

129-
// Regression: an unknown chain code must never silently fall back to P2SH.
130-
// If the server returns a new chain code that this SDK version does not
131-
// recognise, the validation should fail loudly rather than derive a P2SH
132-
// address and produce a confusing mismatch error.
133-
//
134-
// Historical instance: bitgo@18.0.1 did not know chain=40 (p2trMusig2).
135-
// When the server returned { chain: 40, address: "tb1p..." }, the client
136-
// fell back to chain=0, derived a P2SH address, and threw
137-
// UnexpectedAddressError("expected 2NEU8... but got tb1p...") — hiding the
138-
// real cause entirely.
139-
it('throws UnexpectedAddressError for an unknown chain code, revealing the P2SH fallback', function () {
129+
// Regression guard for T1-3386 / T1-3385: an unknown chain code must throw
130+
// immediately with a clear message rather than silently falling back to P2SH
131+
// and producing a confusing "expected <P2SH> but got <P2TR>" error.
132+
it('throws InvalidAddressDerivationPropertyError for an unknown chain code', function () {
140133
assert.ok(!fixedScriptWallet.ChainCode.is(unknownChainCode), 'test prerequisite: chain must be unknown');
141-
142-
// The server-side address for chain=40 (p2trMusig2) — what a newer server
143-
// would return when this older client does not know about chain=40 yet.
144134
const serverAddress = generateAddress('btc', { keychains, chain: 40 });
145-
const p2shFallbackAddress = generateAddress('btc', { keychains, chain: 0 });
146-
147-
let err: unknown;
148-
try {
149-
assertFixedScriptWalletAddress('btc', {
150-
chain: unknownChainCode,
151-
index: 0,
152-
keychains,
153-
format: 'base58',
154-
address: serverAddress,
155-
});
156-
} catch (e) {
157-
err = e;
158-
}
159-
160-
assert.ok(err instanceof UnexpectedAddressError);
161-
162-
// The error message reveals the fallback: it mentions the P2SH address
163-
// rather than anything about the unrecognised chain code.
164-
assert.ok(
165-
err.message.includes(p2shFallbackAddress),
166-
`expected error to mention the P2SH fallback address (${p2shFallbackAddress}), got: ${err.message}`
167-
);
168-
assert.ok(
169-
err.message.includes(serverAddress),
170-
`expected error to mention the server address (${serverAddress}), got: ${err.message}`
135+
assert.throws(
136+
() =>
137+
assertFixedScriptWalletAddress('btc', {
138+
chain: unknownChainCode,
139+
index: 0,
140+
keychains,
141+
format: 'base58',
142+
address: serverAddress,
143+
}),
144+
(err: unknown) => {
145+
assert.ok(err instanceof InvalidAddressDerivationPropertyError);
146+
assert.ok(
147+
err.message.includes(String(unknownChainCode)),
148+
`expected error to name the unrecognised chain code, got: ${err.message}`
149+
);
150+
return true;
151+
}
171152
);
172153
});
173154

174-
it('generateAddress silently produces a P2SH address for an unknown chain code', function () {
155+
it('generateAddress throws InvalidAddressDerivationPropertyError for an unknown chain code', function () {
175156
assert.ok(!fixedScriptWallet.ChainCode.is(unknownChainCode), 'test prerequisite: chain must be unknown');
176-
const fallback = generateAddress('btc', { keychains, chain: unknownChainCode });
177-
const p2sh = generateAddress('btc', { keychains, chain: 0 });
178-
assert.strictEqual(fallback, p2sh);
157+
assert.throws(
158+
() => generateAddress('btc', { keychains, chain: unknownChainCode }),
159+
InvalidAddressDerivationPropertyError
160+
);
179161
});
180162

181163
it('succeeds for bch cashaddr (chain 0)', function () {

0 commit comments

Comments
 (0)