Skip to content

Commit 5c34219

Browse files
Merge pull request #118 from BitGo/BTC-2980.export-testutils
feat(wasm-utxo): export testutils module, fix testutils browser compat
2 parents 4347d89 + fec213d commit 5c34219

File tree

5 files changed

+68
-2
lines changed

5 files changed

+68
-2
lines changed

packages/wasm-utxo/js/bip32.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@ export class BIP32 implements BIP32Interface {
9797
return new BIP32(wasm);
9898
}
9999

100+
/**
101+
* Create a BIP32 master key from a string by hashing it with SHA256.
102+
* Useful for deterministic test key generation.
103+
* @param seedString - The seed string to hash
104+
* @param network - Optional network string
105+
* @returns A BIP32 instance
106+
*/
107+
static fromSeedSha256(seedString: string, network?: string | null): BIP32 {
108+
const wasm = WasmBIP32.from_seed_sha256(seedString, network);
109+
return new BIP32(wasm);
110+
}
111+
100112
/**
101113
* Get the chain code as a Uint8Array
102114
*/

packages/wasm-utxo/js/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export * as utxolibCompat from "./utxolibCompat.js";
1414
export * as fixedScriptWallet from "./fixedScriptWallet/index.js";
1515
export * as bip32 from "./bip32.js";
1616
export * as ecpair from "./ecpair.js";
17+
export * as testutils from "./testutils/index.js";
1718

1819
// Only the most commonly used classes and types are exported at the top level for convenience
1920
export { ECPair } from "./ecpair.js";

packages/wasm-utxo/js/testutils/keys.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as crypto from "crypto";
21
import { BIP32 } from "../bip32.js";
32
import { RootWalletKeys } from "../fixedScriptWallet/RootWalletKeys.js";
43
import type { Triple } from "../triple.js";
@@ -17,7 +16,7 @@ import type { Triple } from "../triple.js";
1716
* ```
1817
*/
1918
export function getKey(seed: string): BIP32 {
20-
return BIP32.fromSeed(crypto.createHash("sha256").update(seed).digest());
19+
return BIP32.fromSeedSha256(seed);
2120
}
2221

2322
/**

packages/wasm-utxo/src/wasm/bip32.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,18 @@ impl WasmBIP32 {
210210
Ok(WasmBIP32(BIP32Key::Private(xpriv)))
211211
}
212212

213+
/// Create a BIP32 master key from a string by hashing it with SHA256.
214+
/// This is useful for deterministic test key generation.
215+
#[wasm_bindgen]
216+
pub fn from_seed_sha256(
217+
seed_string: &str,
218+
network: Option<String>,
219+
) -> Result<WasmBIP32, WasmUtxoError> {
220+
use crate::bitcoin::hashes::{sha256, Hash};
221+
let hash = sha256::Hash::hash(seed_string.as_bytes());
222+
Self::from_seed(&hash[..], network)
223+
}
224+
213225
/// Get the chain code as a Uint8Array
214226
#[wasm_bindgen(getter)]
215227
pub fn chain_code(&self) -> js_sys::Uint8Array {

packages/wasm-utxo/test/bip32.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as assert from "assert";
2+
import * as crypto from "crypto";
23
import { bip32 as utxolibBip32 } from "@bitgo/utxo-lib";
34
import { BIP32 } from "../js/bip32.js";
45

@@ -138,6 +139,25 @@ describe("WasmBIP32", () => {
138139
assert.strictEqual(key.isNeutered(), false);
139140
assert.ok(key.toBase58().startsWith("tprv"));
140141
});
142+
143+
it("should create from seed string using SHA256", () => {
144+
const seedString = "test";
145+
const key = bip32.BIP32.fromSeedSha256(seedString);
146+
assert.strictEqual(key.depth, 0);
147+
assert.strictEqual(key.isNeutered(), false);
148+
assert.ok(key.privateKey instanceof Uint8Array);
149+
// Should be deterministic
150+
const key2 = bip32.BIP32.fromSeedSha256(seedString);
151+
assert.strictEqual(key.toBase58(), key2.toBase58());
152+
});
153+
154+
it("should create from seed string with network", () => {
155+
const seedString = "test";
156+
const key = bip32.BIP32.fromSeedSha256(seedString, "BitcoinTestnet3");
157+
assert.strictEqual(key.depth, 0);
158+
assert.strictEqual(key.isNeutered(), false);
159+
assert.ok(key.toBase58().startsWith("tprv"));
160+
});
141161
});
142162

143163
describe("WasmBIP32 parity with utxolib", () => {
@@ -336,4 +356,26 @@ describe("WasmBIP32 parity with utxolib", () => {
336356
const wasmParentFp = new DataView(wasmKey.fingerprint.buffer).getUint32(0, false);
337357
assert.strictEqual(wasmChild.parentFingerprint, wasmParentFp);
338358
});
359+
360+
it("should match utxolib when using fromSeedSha256", () => {
361+
// Test various seed strings to ensure parity with manual SHA256 + fromSeed
362+
const seedStrings = ["test", "user", "backup", "bitgo", "default.0", "default.1", "default.2"];
363+
364+
for (const seedString of seedStrings) {
365+
// Manual approach: hash with SHA256, then create from seed
366+
const hash = crypto.createHash("sha256").update(seedString).digest();
367+
const utxolibKey = utxolibBip32.fromSeed(hash);
368+
369+
// WASM approach: fromSeedSha256 does hashing internally
370+
const wasmKey = bip32.BIP32.fromSeedSha256(seedString);
371+
372+
assert.strictEqual(
373+
wasmKey.toBase58(),
374+
utxolibKey.toBase58(),
375+
`Failed for seed string: ${seedString}`,
376+
);
377+
assert.ok(bufferEqual(wasmKey.publicKey, utxolibKey.publicKey));
378+
assert.ok(bufferEqual(wasmKey.chainCode, utxolibKey.chainCode));
379+
}
380+
});
339381
});

0 commit comments

Comments
 (0)