mirror of
https://github.com/alexgo-io/stacks.js.git
synced 2026-04-29 09:45:50 +08:00
153 lines
6.7 KiB
TypeScript
153 lines
6.7 KiB
TypeScript
// https://github.com/paulmillr/scure-bip39
|
|
// Secure, audited & minimal implementation of BIP39 mnemonic phrases.
|
|
import {
|
|
entropyToMnemonic,
|
|
mnemonicToEntropy,
|
|
mnemonicToSeed,
|
|
validateMnemonic,
|
|
} from '@scure/bip39';
|
|
// Word lists not imported by default as that would increase bundle sizes too much as in case of bitcoinjs/bip39
|
|
// Use default english world list similiar to bitcoinjs/bip39
|
|
// Backward compatible with bitcoinjs/bip39 dependency
|
|
// Very small in size as compared to bitcoinjs/bip39 wordlist
|
|
// Reference: https://github.com/paulmillr/scure-bip39
|
|
import { wordlist } from '@scure/bip39/wordlists/english';
|
|
import { bytesToHex } from '@stacks/common';
|
|
import { TransactionVersion } from '@stacks/transactions';
|
|
import {
|
|
generateSecretKey,
|
|
generateWallet,
|
|
getAppPrivateKey,
|
|
getGaiaAddress,
|
|
getStxAddress,
|
|
} from '../src';
|
|
|
|
describe(generateSecretKey, () => {
|
|
test('generates a 24 word phrase by default', () => {
|
|
const key = generateSecretKey();
|
|
const words = key.split(' ');
|
|
expect(words.length).toEqual(24);
|
|
});
|
|
|
|
test('generates a 12 word seed if 128 bits entropy', () => {
|
|
const key = generateSecretKey(128);
|
|
expect(key.split(' ').length).toEqual(12);
|
|
});
|
|
|
|
test('generates a valid mnemonic', () => {
|
|
expect(validateMnemonic(generateSecretKey(), wordlist)).toBeTruthy();
|
|
expect(validateMnemonic(generateSecretKey(128), wordlist)).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe(generateWallet, () => {
|
|
test('backwards compatibility test', async () => {
|
|
const secretKey =
|
|
'sound idle panel often situate develop unit text design antenna ' +
|
|
'vendor screen opinion balcony share trigger accuse scatter visa uniform brass ' +
|
|
'update opinion media';
|
|
|
|
const wallet = await generateWallet({ secretKey, password: 'password' });
|
|
|
|
expect(wallet.salt).toEqual('c15619adafe7e75a195a1a2b5788ca42e585a3fd181ae2ff009c6089de54ed9e');
|
|
expect(wallet.rootKey).toEqual(
|
|
'xprv9s21ZrQH143K2KAnQL9secDrgY84y7bFrxFdtBjASeGwYyCRgDRjuJAbmnUCjRsGX8z7A7ML2Kj91Uv7aWe8n5suV5bUa6mvcysgCx9TGFc'
|
|
);
|
|
expect(wallet.configPrivateKey).toEqual(
|
|
'67e113e8ccf43fc8a724710620cf369f23c34c396c649615c31e1fd9aaf23d72'
|
|
);
|
|
|
|
expect(wallet.accounts.length).toEqual(1);
|
|
const [account] = wallet.accounts;
|
|
const appsKey =
|
|
'xprvA1y4zBndD83n6PWgVH6ivkTpNQ2WU1UGPg9hWa2q8sCANa7YrYMZFHWMhrbpsarx' +
|
|
'XMuQRa4jtaT2YXugwsKrjFgn765tUHu9XjyiDFEjB7f';
|
|
expect(account.appsKey).toEqual(appsKey);
|
|
expect(account.stxPrivateKey).toEqual(
|
|
'8721c6a5237f5e8d361161a7855aa56885a3e19e2ea6ee268fb14eabc5e2ed9001'
|
|
);
|
|
expect(account.dataPrivateKey).toEqual(
|
|
'a29c3e73dba79ab0f84cb792bafd65ec71f243ebe67a7ebd842ef5cdce3b21eb'
|
|
);
|
|
|
|
expect(getStxAddress({ account, transactionVersion: TransactionVersion.Testnet })).toEqual(
|
|
'ST384CVPNDTYA0E92TKJZQTYXQHNZSWGCAH0ER64E'
|
|
);
|
|
expect(getStxAddress({ account, transactionVersion: TransactionVersion.Mainnet })).toEqual(
|
|
'SP384CVPNDTYA0E92TKJZQTYXQHNZSWGCAG7SAPVB'
|
|
);
|
|
|
|
expect(getGaiaAddress(account)).toEqual('1JeTQ5cQjsD57YGcsVFhwT7iuQUXJR6BSk');
|
|
|
|
expect(getAppPrivateKey({ account, appDomain: 'https://banter.pub' })).toEqual(
|
|
'6f8b6a170f8b2ee57df5ead49b0f4c8acde05f9e1c4c6ef8223d6a42fabfa314'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Compatibility verification @scure/bip39 vs bitcoinjs/bip39', () => {
|
|
test('Verify compatibility @scure/bip39 <=> bitcoinjs/bip39', () => {
|
|
// Consider an entropy
|
|
const entropy = '00000000000000000000000000000000';
|
|
// Consider same entropy in array format
|
|
const entropyUint8Array = new Uint8Array(entropy.split('').map(Number));
|
|
|
|
// Use vectors to verify result with bitcoinjs/bip39 instead of importing bitcoinjs/bip39
|
|
const bitcoinjsBip39 = {
|
|
// Consider it equivalent to bitcoinjs/bip39 (offloaded now)
|
|
// Using this map of required functions from bitcoinjs/bip39 and mocking the output for considered entropy
|
|
entropyToMnemonicBip39: (_: string) =>
|
|
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
|
|
validateMnemonicBip39: (_: string) => true,
|
|
mnemonicToEntropyBip39: (_: string) => '00000000000000000000000000000000',
|
|
};
|
|
|
|
// entropyToMnemonicBip39 imported from bitcoinjs/bip39
|
|
const bip39Mnemonic = bitcoinjsBip39.entropyToMnemonicBip39(entropy);
|
|
// entropyToMnemonic imported from @scure/bip39
|
|
const mnemonic = entropyToMnemonic(entropyUint8Array, wordlist);
|
|
|
|
//Phase 1: Cross verify mnemonic validity: @scure/bip39 <=> bitcoinjs/bip39
|
|
|
|
// validateMnemonic imported from @scure/bip39
|
|
expect(validateMnemonic(bip39Mnemonic, wordlist)).toEqual(true);
|
|
// validateMnemonicBip39 imported from bitcoinjs/bip39
|
|
expect(bitcoinjsBip39.validateMnemonicBip39(mnemonic)).toEqual(true);
|
|
|
|
// validateMnemonic imported from @scure/bip39
|
|
expect(validateMnemonic(mnemonic, wordlist)).toEqual(true);
|
|
// validateMnemonicBip39 imported from bitcoinjs/bip39
|
|
expect(bitcoinjsBip39.validateMnemonicBip39(bip39Mnemonic)).toEqual(true);
|
|
|
|
//Phase 2: Get back entropy from mnemonic and verify @scure/bip39 <=> bitcoinjs/bip39
|
|
|
|
// mnemonicToEntropy imported from @scure/bip39
|
|
expect(mnemonicToEntropy(mnemonic, wordlist)).toEqual(entropyUint8Array);
|
|
// mnemonicToEntropyBip39 imported from bitcoinjs/bip39
|
|
expect(bitcoinjsBip39.mnemonicToEntropyBip39(bip39Mnemonic)).toEqual(entropy);
|
|
// mnemonicToEntropy imported from @scure/bip39
|
|
expect(bytesToHex(mnemonicToEntropy(bip39Mnemonic, wordlist))).toEqual(entropy);
|
|
// mnemonicToEntropyBip39 imported from bitcoinjs/bip39
|
|
const entropyString = bitcoinjsBip39.mnemonicToEntropyBip39(mnemonic);
|
|
// Convert entropy to bytes
|
|
const entropyInBytes = new Uint8Array(entropyString.split('').map(Number));
|
|
// entropy should match with entropyUint8Array
|
|
expect(entropyInBytes).toEqual(entropyUint8Array);
|
|
});
|
|
|
|
test('Seed verification @scure/bip39 <=> bitcoinjs/bip39', async () => {
|
|
// Consider an entropy as actually generated by calling generateMnemonic(wordlist)
|
|
const mnemonic =
|
|
'limb basket cactus metal come display chicken brief execute version attract journey';
|
|
const seed = await mnemonicToSeed(mnemonic);
|
|
// Use vectors to verify result with bitcoinjs/bip39 instead of importing bitcoinjs/bip39
|
|
const bitcoinjsBip39 = {
|
|
// Consider it equivalent to bitcoinjs/bip39 (offloaded now)
|
|
// Using this map of required functions from bitcoinjs/bip39 and mocking the output for considered entropy
|
|
mnemonicToSeedBip39: (_: string) =>
|
|
'8f157914f06a56abf3a188c9a96faa74100e34d30aff7a6bafe8af33d5c398ef703759e30654f536a2241dc88a5fd3d963b743153b450c91dcfc0ab9f3d90256',
|
|
};
|
|
expect(bytesToHex(seed)).toEqual(bitcoinjsBip39.mnemonicToSeedBip39(mnemonic));
|
|
});
|
|
});
|