Skip to content

Commit 3d8aa19

Browse files
Merge pull request #8859 from BitGo/otto/derive-key-with-seed
refactor(abstract-utxo): inline deriveKeyWithSeed to drop utxolib BIP32 bridge
2 parents c8dcc42 + b9a874c commit 3d8aa19

4 files changed

Lines changed: 55 additions & 8 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { createHash } from 'crypto';
2+
3+
import type { BIP32 } from '@bitgo/wasm-utxo';
4+
5+
/**
6+
* Derive a child key from `key` using a path determined by `seed`.
7+
*
8+
* Mirrors `BaseCoin.deriveKeyWithSeedBip32` from sdk-core but operates on
9+
* wasm-utxo's BIP32 class (which has the same `derivePath` semantics).
10+
*/
11+
export function deriveKeyWithSeed(key: BIP32, seed: string): { key: BIP32; derivationPath: string } {
12+
const sha = (input: string | Buffer): Buffer => createHash('sha256').update(input).digest();
13+
const derivationPathInput = sha(sha(seed)).toString('hex');
14+
const derivationPath = `m/999999/${parseInt(derivationPathInput.slice(0, 7), 16)}/${parseInt(
15+
derivationPathInput.slice(7, 14),
16+
16
17+
)}`;
18+
return { key: key.derivePath(derivationPath), derivationPath };
19+
}

modules/abstract-utxo/src/impl/btc/inscriptionBuilder.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import assert from 'assert';
22

33
import {
4-
BaseCoin,
54
HalfSignedUtxoTransaction,
65
IInscriptionBuilder,
76
IWallet,
@@ -29,8 +28,8 @@ import {
2928
import { BIP32, fixedScriptWallet } from '@bitgo/wasm-utxo';
3029

3130
import { AbstractUtxoCoin } from '../../abstractUtxoCoin';
31+
import { deriveKeyWithSeed } from '../../deriveKeyWithSeed';
3232
import { fetchKeychains } from '../../keychains';
33-
import { toUtxolibBIP32 } from '../../wasmUtil';
3433

3534
/** Key identifier for signing */
3635
type SignerKey = 'user' | 'backup' | 'bitgo';
@@ -58,8 +57,7 @@ export class InscriptionBuilder implements IInscriptionBuilder {
5857
const user = await this.wallet.baseCoin.keychains().get({ id: this.wallet.keyIds()[KeyIndices.USER] });
5958
assert(user.pub);
6059

61-
const userKey = toUtxolibBIP32(BIP32.fromBase58(user.pub));
62-
const { key: derivedKey } = BaseCoin.deriveKeyWithSeedBip32(userKey, inscriptionData.toString());
60+
const { key: derivedKey } = deriveKeyWithSeed(BIP32.fromBase58(user.pub), inscriptionData.toString());
6361

6462
const result = inscriptions.createInscriptionRevealData(
6563
derivedKey.publicKey,

modules/abstract-utxo/src/offlineVault/OfflineVaultHalfSigned.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { BIP32, bip32, Psbt } from '@bitgo/wasm-utxo';
2-
import { BaseCoin } from '@bitgo/sdk-core';
32

3+
import { deriveKeyWithSeed } from '../deriveKeyWithSeed';
44
import { UtxoCoinName } from '../names';
5-
import { toUtxolibBIP32 } from '../wasmUtil';
65

76
import { OfflineVaultSignable } from './OfflineVaultSignable';
87
import { DescriptorTransaction, getHalfSignedPsbt } from './descriptor';
@@ -21,8 +20,8 @@ export function createHalfSigned(
2120
derivationId: string,
2221
tx: unknown
2322
): OfflineVaultHalfSigned {
24-
const key = typeof prv === 'string' ? BIP32.fromBase58(prv) : prv;
25-
const derivedKey = BaseCoin.deriveKeyWithSeedBip32(toUtxolibBIP32(key), derivationId).key;
23+
const wasmKey = typeof prv === 'string' ? BIP32.fromBase58(prv) : BIP32.fromBase58(prv.toBase58());
24+
const derivedKey = deriveKeyWithSeed(wasmKey, derivationId).key;
2625
if (!OfflineVaultSignable.is(tx)) {
2726
throw new Error('unsupported transaction type');
2827
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as assert from 'assert';
2+
3+
import * as utxolib from '@bitgo/utxo-lib';
4+
import { BIP32 } from '@bitgo/wasm-utxo';
5+
import { BaseCoin } from '@bitgo/sdk-core';
6+
7+
import { deriveKeyWithSeed } from '../../src/deriveKeyWithSeed';
8+
9+
// Deterministic test xprv — derived from a 32-byte all-ones seed.
10+
const XPRV =
11+
'xprv9s21ZrQH143K2QPmabzR6Q9tkNRfFxy1jy9p1PRctypZJWAjtjWBJAxxvQ3454vPpnUoLfGH8YP5KcHFX4Z5Jh7bYnFuBhxztHRy72yXmnC';
12+
13+
const SEEDS = ['', 'hello', 'some long seed string with spaces', '\u{1F600}'];
14+
15+
describe('deriveKeyWithSeed', function () {
16+
for (const seed of SEEDS) {
17+
it(`matches BaseCoin.deriveKeyWithSeedBip32 for seed ${JSON.stringify(seed)}`, function () {
18+
const wasmKey = BIP32.fromBase58(XPRV);
19+
const utxolibKey = utxolib.bip32.fromBase58(XPRV);
20+
21+
const wasmResult = deriveKeyWithSeed(wasmKey, seed);
22+
const sdkCoreResult = BaseCoin.deriveKeyWithSeedBip32(utxolibKey, seed);
23+
24+
assert.strictEqual(wasmResult.derivationPath, sdkCoreResult.derivationPath);
25+
assert.strictEqual(
26+
Buffer.from(wasmResult.key.publicKey).toString('hex'),
27+
Buffer.from(sdkCoreResult.key.publicKey).toString('hex')
28+
);
29+
});
30+
}
31+
});

0 commit comments

Comments
 (0)