diff --git a/package.json b/package.json index 58c3b573f..9978834da 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,6 @@ }, "resolutions": { "ganache": "npm:@celo/ganache@7.8.0-unofficial.0", - "bip39": "https://github.com/bitcoinjs/bip39#a7ecbfe2e60d0214ce17163d610cad9f7b23140c", "blind-threshold-bls": "npm:@celo/blind-threshold-bls@1.0.0-beta", "@types/bn.js": "4.11.6", "bignumber.js": "9.0.0" diff --git a/packages/cli/package.json b/packages/cli/package.json index 4ab904cf8..19cb5e5c5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -55,9 +55,9 @@ "@oclif/plugin-not-found": "^3.0.9", "@oclif/plugin-plugins": "^4.1.17", "@oclif/plugin-warn-if-update-available": "^3.0.9", + "@scure/bip32": "^1.3.3", "@types/command-exists": "^1.2.3", "bignumber.js": "9.0.0", - "bip32": "3.1.0", "chalk": "^2.4.2", "command-exists": "^1.2.9", "debug": "^4.1.1", diff --git a/packages/sdk/cryptographic-utils/package.json b/packages/sdk/cryptographic-utils/package.json index 5a98f792b..d4883ca48 100644 --- a/packages/sdk/cryptographic-utils/package.json +++ b/packages/sdk/cryptographic-utils/package.json @@ -29,16 +29,13 @@ "@noble/ciphers": "0.4.1", "@noble/curves": "1.3.0", "@noble/hashes": "1.3.3", + "@scure/bip32": "^1.3.3", + "@scure/bip39": "^1.2.2", "@types/bn.js": "^5.1.0", "@types/node": "^18.7.16", - "@types/randombytes": "^2.0.0", "bigi": "^1.4.2", - "bip32": "^3.1.0", - "bip39": "https://github.com/bitcoinjs/bip39#a7ecbfe2e60d0214ce17163d610cad9f7b23140c", "buffer-reverse": "^1.0.1", - "ethereum-cryptography": "1.2.0", - "randombytes": "^2.0.1", - "tiny-secp256k1": "2.2.1" + "ethereum-cryptography": "1.2.0" }, "devDependencies": { "@celo/typescript": "0.0.1" diff --git a/packages/sdk/cryptographic-utils/src/account.test.ts b/packages/sdk/cryptographic-utils/src/account.test.ts index bf4aefb72..8a71c50c6 100644 --- a/packages/sdk/cryptographic-utils/src/account.test.ts +++ b/packages/sdk/cryptographic-utils/src/account.test.ts @@ -1,5 +1,5 @@ import { MnemonicLanguages } from '@celo/base/lib/account' -import * as bip39 from 'bip39' +import * as bip39 from '@scure/bip39' import { generateKeys, generateMnemonic, @@ -10,6 +10,7 @@ import { suggestMnemonicCorrections, validateMnemonic, } from './account' +import wordlists from './wordlists' describe('AccountUtils', () => { describe('.generateMnemonic()', () => { @@ -32,7 +33,7 @@ describe('AccountUtils', () => { // This validates against all languages expect(validateMnemonic(mnemonic)).toBeTruthy() // This validates using a specific wordlist - expect(bip39.validateMnemonic(mnemonic, bip39.wordlists[languageName])).toBeTruthy() + expect(bip39.validateMnemonic(mnemonic, wordlists[language])).toBeTruthy() }) } }) @@ -69,7 +70,7 @@ describe('AccountUtils', () => { // This validates against all languages expect(validateMnemonic(mnemonic)).toBeTruthy() // This validates using a specific wordlist - expect(bip39.validateMnemonic(mnemonic, bip39.wordlists[languageName])).toBeTruthy() + expect(bip39.validateMnemonic(mnemonic, wordlists[language])).toBeTruthy() }) } }) diff --git a/packages/sdk/cryptographic-utils/src/account.ts b/packages/sdk/cryptographic-utils/src/account.ts index c596f0cd9..a9b470261 100644 --- a/packages/sdk/cryptographic-utils/src/account.ts +++ b/packages/sdk/cryptographic-utils/src/account.ts @@ -1,5 +1,4 @@ import { - Bip39, CELO_DERIVATION_PATH_BASE, MnemonicLanguages, MnemonicStrength, @@ -8,17 +7,18 @@ import { import { normalizeAccents } from '@celo/base/lib/string' import { privateKeyToAddress } from '@celo/utils/lib/address' import { levenshteinDistance } from '@celo/utils/lib/levenshtein' -// TODO replace by @scure/bip32 -import BIP32Factory from 'bip32' -// TODO replace by @scure/bip32 -import * as bip39 from 'bip39' -// TODO replace by @noble/hashes/sha3 { keccak_256 } -import { keccak256 } from 'ethereum-cryptography/keccak' -import { utf8ToBytes } from 'ethereum-cryptography/utils' -import randomBytes from 'randombytes' - -// TODO replace by @noble/curves/secp256k1 -import * as ecc from 'tiny-secp256k1' +import { keccak_256 } from '@noble/hashes/sha3' +import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils' +import { HDKey } from '@scure/bip32' +import { + validateMnemonic as _validateMnemonic, + entropyToMnemonic, + mnemonicToSeed, + mnemonicToSeedSync, +} from '@scure/bip39' +import { randomBytes } from 'crypto' +import wordlists from './wordlists' + // Exports moved to @celo/base, forwarding them // here for backwards compatibility export { @@ -28,12 +28,11 @@ export { MnemonicStrength, RandomNumberGenerator, } from '@celo/base/lib/account' -const bip32 = BIP32Factory(ecc) function defaultGenerateMnemonic( strength?: number, rng?: RandomNumberGenerator, - wordlist?: string[] + wordlist: string[] = wordlists[MnemonicLanguages.english] ): Promise { return new Promise((resolve, reject) => { strength = strength || 128 @@ -43,30 +42,30 @@ function defaultGenerateMnemonic( if (error) { reject(error) } else { - resolve(bip39.entropyToMnemonic(randomBytesBuffer.toString('hex'), wordlist)) + resolve(entropyToMnemonic(randomBytesBuffer, wordlist)) } }) }) } -const bip39Wrapper: Bip39 = { - mnemonicToSeedSync: bip39.mnemonicToSeedSync, - mnemonicToSeed: bip39.mnemonicToSeed, +const bip39Wrapper = { + mnemonicToSeedSync: mnemonicToSeedSync, + mnemonicToSeed: mnemonicToSeed, generateMnemonic: defaultGenerateMnemonic, - validateMnemonic: bip39.validateMnemonic, + validateMnemonic: _validateMnemonic, } export async function generateMnemonic( strength: MnemonicStrength = MnemonicStrength.s256_24words, language?: MnemonicLanguages, - bip39ToUse: Bip39 = bip39Wrapper + bip39ToUse = bip39Wrapper ): Promise { return bip39ToUse.generateMnemonic(strength, undefined, getWordList(language)) } export function validateMnemonic( mnemonic: string, - bip39ToUse: Bip39 = bip39Wrapper, + bip39ToUse = bip39Wrapper, language?: MnemonicLanguages ) { if (language !== undefined) { @@ -166,28 +165,8 @@ export function formatNonAccentedCharacters(mnemonic: string) { } // Unify the bip39.wordlists (otherwise depends on the instance of the bip39) -function getWordList(language?: MnemonicLanguages): string[] { - // Use exhaustive switch to ensure that every language is accounted for. - switch (language ?? MnemonicLanguages.english) { - case MnemonicLanguages.chinese_simplified: - return bip39.wordlists.chinese_simplified - case MnemonicLanguages.chinese_traditional: - return bip39.wordlists.chinese_traditional - case MnemonicLanguages.english: - return bip39.wordlists.english - case MnemonicLanguages.french: - return bip39.wordlists.french - case MnemonicLanguages.italian: - return bip39.wordlists.italian - case MnemonicLanguages.japanese: - return bip39.wordlists.japanese - case MnemonicLanguages.korean: - return bip39.wordlists.korean - case MnemonicLanguages.spanish: - return bip39.wordlists.spanish - case MnemonicLanguages.portuguese: - return bip39.wordlists.portuguese - } +function getWordList(language: MnemonicLanguages = MnemonicLanguages.english): string[] { + return wordlists[language] } export function getAllLanguages(): MnemonicLanguages[] { @@ -410,7 +389,7 @@ export async function generateKeys( password?: string, changeIndex: number = 0, addressIndex: number = 0, - bip39ToUse: Bip39 = bip39Wrapper, + bip39ToUse = bip39Wrapper, derivationPath: string = CELO_DERIVATION_PATH_BASE ): Promise<{ privateKey: string; publicKey: string; address: string }> { const seed: Buffer = await generateSeed(mnemonic, password, bip39ToUse) @@ -425,7 +404,7 @@ export function generateDeterministicInviteCode( changeIndex: number = 0, derivationPath: string = CELO_DERIVATION_PATH_BASE ): { privateKey: string; publicKey: string } { - const seed = keccak256(utf8ToBytes(recipientPhoneHash + recipientPepper)) as Buffer + const seed = keccak_256(utf8ToBytes(recipientPhoneHash + recipientPepper)) as Buffer return generateKeysFromSeed(seed, changeIndex, addressIndex, derivationPath) } @@ -434,10 +413,10 @@ export function generateDeterministicInviteCode( export async function generateSeed( mnemonic: string, password?: string, - bip39ToUse: Bip39 = bip39Wrapper, + bip39ToUse = bip39Wrapper, keyByteLength: number = 64 ): Promise { - let seed: Buffer = await bip39ToUse.mnemonicToSeed(mnemonic, password) + let seed = Buffer.from(await bip39ToUse.mnemonicToSeed(mnemonic, password)) if (keyByteLength > 0 && seed.byteLength > keyByteLength) { const bufAux = Buffer.allocUnsafe(keyByteLength) seed.copy(bufAux, 0, 0, keyByteLength) @@ -452,18 +431,21 @@ export function generateKeysFromSeed( addressIndex: number = 0, derivationPath: string = CELO_DERIVATION_PATH_BASE ): { privateKey: string; publicKey: string; address: string } { - const node = bip32.fromSeed(seed) - const newNode = node.derivePath( + const node = HDKey.fromMasterSeed(seed) + const newNode = node.derive( `${derivationPath ? `${derivationPath}/` : ''}${changeIndex}/${addressIndex}` ) if (!newNode.privateKey) { // As we are generating the node from a seed, the node will always have a private key and this would never happened throw new Error('utils-accounts@generateKeys: invalid node to derivate') } + const privateKey = bytesToHex(newNode.privateKey) + const publicKey = bytesToHex(newNode.publicKey!) + return { - privateKey: newNode.privateKey.toString('hex'), - publicKey: newNode.publicKey.toString('hex'), - address: privateKeyToAddress(newNode.privateKey.toString('hex')), + privateKey, + publicKey, + address: privateKeyToAddress(privateKey), } } diff --git a/packages/sdk/cryptographic-utils/src/bls.ts b/packages/sdk/cryptographic-utils/src/bls.ts index c4f22ca68..4a2ef5e86 100644 --- a/packages/sdk/cryptographic-utils/src/bls.ts +++ b/packages/sdk/cryptographic-utils/src/bls.ts @@ -1,11 +1,11 @@ import { BLS } from '@celo/bls12377js' // this is an implementation of a subset of BLS12-377 import { isValidAddress } from '@celo/utils/lib/address' -import { keccak256 } from 'ethereum-cryptography/keccak' -const BigInteger = require('bigi') +import { keccak_256 } from '@noble/hashes/sha3' +import { bytesToHex } from '@noble/hashes/utils' const reverse = require('buffer-reverse') -const n = BigInteger.fromHex('12ab655e9a2ca55660b44d1e5c37b00159aa76fed00000010a11800000000001', 16) +const n = BigInt('0x12ab655e9a2ca55660b44d1e5c37b00159aa76fed00000010a11800000000001') const MODULUSMASK = 31 export const BLS_PUBLIC_KEY_SIZE = 96 @@ -22,17 +22,18 @@ export const blsPrivateKeyToProcessedPrivateKey = (privateKeyHex: string) => { iBuffer, originalPrivateKeyBytes, ]) - const privateKeyBLSBytes = keccak256(keyBytes) + const privateKeyBLSBytes = keccak_256(keyBytes) // eslint-disable-next-line no-bitwise privateKeyBLSBytes[0] &= MODULUSMASK - const privateKeyNum = BigInteger.fromBuffer(privateKeyBLSBytes) - if (privateKeyNum.compareTo(n) >= 0) { + const _privateKeyHex = `0x${bytesToHex(privateKeyBLSBytes)}` + const privateKeyNum = BigInt(_privateKeyHex) + if (privateKeyNum >= n) { continue } - const privateKeyBytes = reverse(privateKeyNum.toBuffer()) + const privateKeyBytes = reverse(Buffer.from(_privateKeyHex, 'hex')) return privateKeyBytes } diff --git a/packages/sdk/cryptographic-utils/src/wordlists.ts b/packages/sdk/cryptographic-utils/src/wordlists.ts new file mode 100644 index 000000000..9f9a40392 --- /dev/null +++ b/packages/sdk/cryptographic-utils/src/wordlists.ts @@ -0,0 +1,25 @@ +import { MnemonicLanguages } from '@celo/base/lib/account' + +import { wordlist as english } from '@scure/bip39/wordlists/english' +import { wordlist as french } from '@scure/bip39/wordlists/french' +import { wordlist as italian } from '@scure/bip39/wordlists/italian' +import { wordlist as japanese } from '@scure/bip39/wordlists/japanese' +import { wordlist as korean } from '@scure/bip39/wordlists/korean' +import { wordlist as portuguese } from '@scure/bip39/wordlists/portuguese' +import { wordlist as chinese_simplified } from '@scure/bip39/wordlists/simplified-chinese' +import { wordlist as spanish } from '@scure/bip39/wordlists/spanish' +import { wordlist as chinese_traditional } from '@scure/bip39/wordlists/traditional-chinese' + +const wordlists: Record = { + [MnemonicLanguages.english]: english, + [MnemonicLanguages.french]: french, + [MnemonicLanguages.italian]: italian, + [MnemonicLanguages.japanese]: japanese, + [MnemonicLanguages.korean]: korean, + [MnemonicLanguages.portuguese]: portuguese, + [MnemonicLanguages.chinese_simplified]: chinese_simplified, + [MnemonicLanguages.spanish]: spanish, + [MnemonicLanguages.chinese_traditional]: chinese_traditional, +} as const + +export default wordlists diff --git a/yarn.lock b/yarn.lock index 361503939..c6905dacf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1637,6 +1637,7 @@ __metadata: "@oclif/plugin-not-found": "npm:^3.0.9" "@oclif/plugin-plugins": "npm:^4.1.17" "@oclif/plugin-warn-if-update-available": "npm:^3.0.9" + "@scure/bip32": "npm:^1.3.3" "@types/command-exists": "npm:^1.2.3" "@types/debug": "npm:^4.1.4" "@types/fs-extra": "npm:^8.0.0" @@ -1647,7 +1648,6 @@ __metadata: "@types/prompts": "npm:^1.1.1" "@types/web3": "npm:^1.0.18" bignumber.js: "npm:9.0.0" - bip32: "npm:3.1.0" chalk: "npm:^2.4.2" command-exists: "npm:^1.2.9" debug: "npm:^4.1.1" @@ -1758,16 +1758,13 @@ __metadata: "@noble/ciphers": "npm:0.4.1" "@noble/curves": "npm:1.3.0" "@noble/hashes": "npm:1.3.3" + "@scure/bip32": "npm:^1.3.3" + "@scure/bip39": "npm:^1.2.2" "@types/bn.js": "npm:^5.1.0" "@types/node": "npm:^18.7.16" - "@types/randombytes": "npm:^2.0.0" bigi: "npm:^1.4.2" - bip32: "npm:^3.1.0" - bip39: "https://github.com/bitcoinjs/bip39#a7ecbfe2e60d0214ce17163d610cad9f7b23140c" buffer-reverse: "npm:^1.0.1" ethereum-cryptography: "npm:1.2.0" - randombytes: "npm:^2.0.1" - tiny-secp256k1: "npm:2.2.1" languageName: unknown linkType: soft @@ -1984,7 +1981,6 @@ __metadata: "@types/bn.js": "npm:^5.1.0" "@types/node": "npm:^18.7.16" bignumber.js: "npm:^9.0.0" - elliptic: "npm:^6.5.4" ethereum-cryptography: "npm:1.2.0" fp-ts: "npm:2.1.1" io-ts: "npm:2.0.1" @@ -3768,7 +3764,7 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:1.3.0": +"@noble/curves@npm:1.3.0, @noble/curves@npm:~1.3.0": version: 1.3.0 resolution: "@noble/curves@npm:1.3.0" dependencies: @@ -3798,7 +3794,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.3.3, @noble/hashes@npm:^1.2.0": +"@noble/hashes@npm:1.3.3, @noble/hashes@npm:~1.3.2": version: 1.3.3 resolution: "@noble/hashes@npm:1.3.3" checksum: 1025ddde4d24630e95c0818e63d2d54ee131b980fe113312d17ed7468bc18f54486ac86c907685759f8a7e13c2f9b9e83ec7b67d1cc20836f36b5e4a65bb102d @@ -5696,7 +5692,7 @@ __metadata: languageName: node linkType: hard -"@scure/base@npm:~1.1.0": +"@scure/base@npm:~1.1.0, @scure/base@npm:~1.1.4": version: 1.1.5 resolution: "@scure/base@npm:1.1.5" checksum: 543fa9991c6378b6a0d5ab7f1e27b30bb9c1e860d3ac81119b4213cfdf0ad7b61be004e06506e89de7ce0cec9391c17f5c082bb34c3b617a2ee6a04129f52481 @@ -5736,6 +5732,17 @@ __metadata: languageName: node linkType: hard +"@scure/bip32@npm:^1.3.3": + version: 1.3.3 + resolution: "@scure/bip32@npm:1.3.3" + dependencies: + "@noble/curves": "npm:~1.3.0" + "@noble/hashes": "npm:~1.3.2" + "@scure/base": "npm:~1.1.4" + checksum: 4b8b75567866ff7d6b3ba154538add02d2951e9433e8dd7f0014331ac500cda5a88fe3d39b408fcc36e86b633682013f172b967af022c2e4e4ab07336801d688 + languageName: node + linkType: hard + "@scure/bip39@npm:1.1.1": version: 1.1.1 resolution: "@scure/bip39@npm:1.1.1" @@ -5766,6 +5773,16 @@ __metadata: languageName: node linkType: hard +"@scure/bip39@npm:^1.2.2": + version: 1.2.2 + resolution: "@scure/bip39@npm:1.2.2" + dependencies: + "@noble/hashes": "npm:~1.3.2" + "@scure/base": "npm:~1.1.4" + checksum: f71aceda10a7937bf3779fd2b4c4156c95ec9813269470ddca464cb8ab610d2451b173037f4b1e6dac45414e406e7adc7b5814c51279f4474d5d38140bbee542 + languageName: node + linkType: hard + "@sideway/address@npm:^4.1.3": version: 4.1.4 resolution: "@sideway/address@npm:4.1.4" @@ -7513,15 +7530,6 @@ __metadata: languageName: node linkType: hard -"@types/randombytes@npm:^2.0.0": - version: 2.0.0 - resolution: "@types/randombytes@npm:2.0.0" - dependencies: - "@types/node": "npm:*" - checksum: 3835678127f020e1f0812f9dbcf400ef98916bc6fb9aff7d36e701c996ae668163f49a69aa6ce6832ca5dd150e6376e3e586094be4f45cc1e190b0db39320671 - languageName: node - linkType: hard - "@types/range-parser@npm:*": version: 1.2.4 resolution: "@types/range-parser@npm:1.2.4" @@ -8726,29 +8734,6 @@ __metadata: languageName: node linkType: hard -"bip32@npm:3.1.0, bip32@npm:^3.1.0": - version: 3.1.0 - resolution: "bip32@npm:3.1.0" - dependencies: - bs58check: "npm:^2.1.1" - create-hash: "npm:^1.2.0" - create-hmac: "npm:^1.1.7" - ripemd160: "npm:^2.0.2" - typeforce: "npm:^1.11.5" - wif: "npm:^2.0.6" - checksum: 6cdad901d25959e21835fe7b43066fc2498548962aa07ce438860b7420a7a93ceec3a9b34ddee68bbc568a68b9d96029338dbea1eadb548651b06d800a14fc3d - languageName: node - linkType: hard - -"bip39@https://github.com/bitcoinjs/bip39#a7ecbfe2e60d0214ce17163d610cad9f7b23140c": - version: 3.1.0 - resolution: "bip39@https://github.com/bitcoinjs/bip39.git#commit=a7ecbfe2e60d0214ce17163d610cad9f7b23140c" - dependencies: - "@noble/hashes": "npm:^1.2.0" - checksum: 406c0b5bdab0d43df2ff8916c5b57bb7f65cd36a115cbd7620a75b972638f8511840bfc27bfc68946027086fd33ed3050d8cd304e85fd527503b23d857a8486e - languageName: node - linkType: hard - "bl@npm:^1.0.0": version: 1.2.3 resolution: "bl@npm:1.2.3" @@ -8977,7 +8962,7 @@ __metadata: languageName: node linkType: hard -"bs58check@npm:<3.0.0, bs58check@npm:^2.1.1, bs58check@npm:^2.1.2": +"bs58check@npm:^2.1.2": version: 2.1.2 resolution: "bs58check@npm:2.1.2" dependencies: @@ -20390,15 +20375,6 @@ __metadata: languageName: node linkType: hard -"tiny-secp256k1@npm:2.2.1": - version: 2.2.1 - resolution: "tiny-secp256k1@npm:2.2.1" - dependencies: - uint8array-tools: "npm:0.0.7" - checksum: 8eb871e912c18d3282d9a6e4cf7f33e18548d48445b16d8381442529cbbf958bbaaa7912817898eadf896d2b8c631ddf3f212162e2305397d4260019d2e17418 - languageName: node - linkType: hard - "title-case@npm:^2.1.0": version: 2.1.1 resolution: "title-case@npm:2.1.1" @@ -20872,13 +20848,6 @@ __metadata: languageName: node linkType: hard -"typeforce@npm:^1.11.5": - version: 1.18.0 - resolution: "typeforce@npm:1.18.0" - checksum: dbf98c75b1d57e56e33c1e1271d5505e67981f4e6a2e2e6e8e31160b58777fea1726160810b6c606517db16476805b7dce315926ba2d4859b9a56cab05b7a41f - languageName: node - linkType: hard - "typescript@npm:5.3.3": version: 5.3.3 resolution: "typescript@npm:5.3.3" @@ -20908,13 +20877,6 @@ __metadata: languageName: node linkType: hard -"uint8array-tools@npm:0.0.7": - version: 0.0.7 - resolution: "uint8array-tools@npm:0.0.7" - checksum: 6ffc45c7d2136757d63c6e556eb8345f908948618a9de37c805fec1249d989c265187b3fbef6cffc4ce5129083204829025b3c58800a0f24c8548e243d42ba13 - languageName: node - linkType: hard - "ultron@npm:~1.1.0": version: 1.1.1 resolution: "ultron@npm:1.1.1" @@ -22151,15 +22113,6 @@ __metadata: languageName: node linkType: hard -"wif@npm:^2.0.6": - version: 2.0.6 - resolution: "wif@npm:2.0.6" - dependencies: - bs58check: "npm:<3.0.0" - checksum: c8d7581664532d9ab6d163ee5194a9bec71b089a6e50d54d6ec57a9bd714fcf84bc8d9f22f4cfc7c297fc6ad10b973b8e83eca5c41240163fc61f44b5154b7da - languageName: node - linkType: hard - "window-size@npm:^0.2.0": version: 0.2.0 resolution: "window-size@npm:0.2.0"