Revert "Set pbkdf2Encode rounds to default to 210,000 (#1983)" (#2007)

This reverts commit e5e6bdd814.
This commit is contained in:
Valentin Fernandez
2025-10-13 11:20:32 -03:00
committed by GitHub
parent 047840319e
commit 2212f31f9a
15 changed files with 43 additions and 88 deletions

View File

@@ -358,7 +358,7 @@ describe('keypair', (): void => {
});
it('creates with dev phrase with derivation path specified', (): void => {
const pair = keyring.createFromUri(PHRASE, undefined, undefined, undefined, 2048);
const pair = keyring.createFromUri(PHRASE);
expect(
pair.address
@@ -367,7 +367,7 @@ describe('keypair', (): void => {
it('creates with dev phrase with derivation path specified - addFromUri', (): void => {
expect(
keyring.addFromUri(PHRASE, undefined, undefined, undefined, 2048).address
keyring.addFromUri(PHRASE).address
).toEqual(ETH_ADDRESS_ONE);
});
@@ -375,12 +375,12 @@ describe('keypair', (): void => {
const keyringUntyped = new Keyring();
expect(
keyringUntyped.addFromUri(PHRASE, {}, 'ethereum', undefined, 2048).address
keyringUntyped.addFromUri(PHRASE, {}, 'ethereum').address
).toEqual(ETH_ADDRESS_ONE);
});
it('encodes a pair toJSON (and decodes)', (): void => {
const pair = keyring.createFromUri(PHRASE, undefined, undefined, undefined, 2048);
const pair = keyring.createFromUri(PHRASE);
const json = pair.toJson('password');
expect(json.address).toEqual('0x0381351b1b46d2602b0992bb5d5531f9c1696b0812feb2534b6884adc47e2e1d8b'); // this is the public key (different from address for ethereum)
@@ -399,7 +399,7 @@ describe('keypair', (): void => {
});
it('encodes a pair toJSON and back', (): void => {
const pairOriginal = keyring.createFromUri(PHRASE, undefined, undefined, undefined, 2048);
const pairOriginal = keyring.createFromUri(PHRASE);
const json = pairOriginal.toJson('password');
const pair = keyring.addFromJson(
json

View File

@@ -152,15 +152,10 @@ export class Keyring implements KeyringInstance {
* @name addFromUri
* @summary Creates an account via an suri
* @description Extracts the phrase, path and password from a SURI format for specifying secret keys `<secret>/<soft-key>//<hard-key>///<password>` (the `///password` may be omitted, and `/<soft-key>` and `//<hard-key>` maybe repeated and mixed). The secret can be a hex string, mnemonic phrase or a string (to be padded)
* @param _suri
* @param meta
* @param type The supported types of pairs. Either 'ed25519' | 'sr25519' | 'ecdsa' | 'ethereum'.
* @param wordlist - Optional custom wordlist for mnemonic.
* @param rounds - Optional: Number of PBKDF2 iterations to run (default: 210000 (when onlyJS = true) or 2048 (when onlyJS = false).
*/
public addFromUri (suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type, wordlist?: string[], rounds?: number): KeyringPair {
*/
public addFromUri (suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type, wordlist?: string[]): KeyringPair {
return this.addPair(
this.createFromUri(suri, meta, type, wordlist, rounds)
this.createFromUri(suri, meta, type, wordlist)
);
}
@@ -207,14 +202,8 @@ export class Keyring implements KeyringInstance {
* @name createFromUri
* @summary Creates a Keypair from an suri
* @description This creates a pair from the suri, but does not add it to the keyring
*
* @param _suri
* @param meta
* @param type The supported types of pairs. Either 'ed25519' | 'sr25519' | 'ecdsa' | 'ethereum'.
* @param wordlist - Optional custom wordlist for mnemonic.
* @param rounds - Optional: Number of PBKDF2 iterations to run (default: 210000 (when onlyJS = true) or 2048 (when onlyJS = false).
*/
public createFromUri (_suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type, wordlist?: string[], rounds?: number): KeyringPair {
public createFromUri (_suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type, wordlist?: string[]): KeyringPair {
// here we only aut-add the dev phrase if we have a hard-derived path
const suri = _suri.startsWith('//')
? `${DEV_PHRASE}${_suri}`
@@ -230,8 +219,8 @@ export class Keyring implements KeyringInstance {
if ([12, 15, 18, 21, 24].includes(parts.length)) {
seed = type === 'ethereum'
? mnemonicToLegacySeed(phrase, '', false, 64, rounds)
: mnemonicToMiniSecret(phrase, password, wordlist, false, rounds);
? mnemonicToLegacySeed(phrase, '', false, 64)
: mnemonicToMiniSecret(phrase, password, wordlist);
} else {
if (phrase.length > 32) {
throw new Error('specified phrase is not a valid mnemonic and is invalid as a raw seed at > 32 bytes');

View File

@@ -98,7 +98,7 @@ describe('keyring.addFromUri', (): void => {
describe(`${type}`, (): void => {
tests.forEach(({ pk, ss, uri }): void => {
it(`creates ${uri}`, (): void => {
const pair = keyring.addFromUri(uri, {}, type as KeypairType, undefined, 2048);
const pair = keyring.addFromUri(uri, {}, type as KeypairType);
expect(u8aToHex(pair.publicKey)).toEqual(pk);
expect(pair.address).toEqual(ss);

View File

@@ -39,14 +39,14 @@ describe('hdEthereum', (): void => {
]);
it('derives the right key pair from a mnemonic', (): void => {
const key = hdEthereum(mnemonicToLegacySeed(PHRASE, '', false, 64, 2048));
const key = hdEthereum(mnemonicToLegacySeed(PHRASE, '', false, 64));
expect(key.publicKey).toEqual(PUBLIC);
expect(key.secretKey).toEqual(SECRET);
});
it('derives the right key pair from a mnemonic and a derivation path', (): void => {
const key = hdEthereum(mnemonicToLegacySeed(PHRASE, '', false, 64, 2048), derivationPath);
const key = hdEthereum(mnemonicToLegacySeed(PHRASE, '', false, 64), derivationPath);
expect(key.publicKey).toEqual(PUBLICDERIVED);
expect(key.secretKey).toEqual(SECRETDERIVED);

View File

@@ -54,7 +54,7 @@ describe('ledgerDerive', (): void => {
tests.forEach(({ ed25519, index: [account, address], mnemonic }, index): void => {
it(`derives a known ed25519 seed for ${network} (${index})`, (): void => {
expect(u8aToHex(
hdLedger(mnemonic, `m/44'/${slip44}'/${account}'/0'/${address}'`, 2048)
hdLedger(mnemonic, `m/44'/${slip44}'/${account}'/0'/${address}'`)
.secretKey
.slice(0, 32)
)).toEqual(ed25519);

View File

@@ -9,7 +9,7 @@ import { HARDENED, hdValidatePath } from '../validatePath.js';
import { ledgerDerivePrivate } from './derivePrivate.js';
import { ledgerMaster } from './master.js';
export function hdLedger (_mnemonic: string, path: string, rounds?: number): Keypair {
export function hdLedger (_mnemonic: string, path: string): Keypair {
const words = _mnemonic
.split(' ')
.map((s) => s.trim())
@@ -30,7 +30,7 @@ export function hdLedger (_mnemonic: string, path: string, rounds?: number): Key
}
const parts = path.split('/').slice(1);
let seed = ledgerMaster(mnemonic, password, rounds);
let seed = ledgerMaster(mnemonic, password);
for (const p of parts) {
const n = parseInt(p.replace(/'$/, ''), 10);

View File

@@ -13,7 +13,7 @@ const XPRV = '0x402b03cd9c8bed9ba9f9bd6cd9c315ce9fcc59c7c25d37c85a36096617e69d41
describe('ledgerDerive', (): void => {
it('derives a known master xprv', (): void => {
expect(u8aToHex(
ledgerMaster(MNEMONIC, undefined, 2048)
ledgerMaster(MNEMONIC)
)).toEqual(XPRV);
});
});

View File

@@ -8,17 +8,9 @@ import { mnemonicToSeedSync } from '../../mnemonic/bip39.js';
const ED25519_CRYPTO = 'ed25519 seed';
/**
* Gets an xprv from a mnemonic
*
* @param mnemonic - The BIP-39 mnemonic phrase to derive the secret from.
* @param password - Optional: password to secure the seed (default: empty string).
* @param wordlist - Optional custom wordlist for mnemonic.
* @param onlyJs - Optional: If `true`, forces use of the JavaScript implementation instead of WASM.
* @param rounds - Optional: Number of PBKDF2 iterations to run (default: 210000).
*/
export function ledgerMaster (mnemonic: string, password?: string, rounds?: number): Uint8Array {
const seed = mnemonicToSeedSync(mnemonic, password, rounds);
// gets an xprv from a mnemonic
export function ledgerMaster (mnemonic: string, password?: string): Uint8Array {
const seed = mnemonicToSeedSync(mnemonic, password);
const chainCode = hmacShaAsU8a(ED25519_CRYPTO, new Uint8Array([1, ...seed]), 256);
let priv;

View File

@@ -47,16 +47,10 @@ function deriveChecksumBits (entropyBuffer: Uint8Array): string {
).slice(0, (entropyBuffer.length * 8) / 32);
}
/*
* @param mnemonic - The BIP-39 mnemonic phrase to derive the secret from.
* @param password - Optional: password to secure the seed (default: empty string).
* @param rounds - Optional: Number of PBKDF2 iterations to run (default: 210000).
*/
export function mnemonicToSeedSync (mnemonic: string, password?: string, rounds?: number): Uint8Array {
export function mnemonicToSeedSync (mnemonic: string, password?: string): Uint8Array {
return pbkdf2Encode(
stringToU8a(normalize(mnemonic)),
stringToU8a(`mnemonic${normalize(password)}`),
rounds
stringToU8a(`mnemonic${normalize(password)}`)
).password;
}

View File

@@ -21,8 +21,8 @@ describe('mnemonicToLegacySeed', (): void => {
it(`generates Wasm & Js equivalents for password=${password || 'undefined'}`, (): void => {
expect(
u8aEq(
mnemonicToLegacySeed(MNEMONIC, password, true, 32, 2048),
mnemonicToLegacySeed(MNEMONIC, password, false, 32, 2048)
mnemonicToLegacySeed(MNEMONIC, password, true),
mnemonicToLegacySeed(MNEMONIC, password, false)
)
).toEqual(true);
});
@@ -32,19 +32,19 @@ describe('mnemonicToLegacySeed', (): void => {
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
it('generates a valid 64bytes seed', (): void => {
expect(
u8aToHex(mnemonicToLegacySeed(MNEMONIC, undefined, onlyJs, 64, 2048))
u8aToHex(mnemonicToLegacySeed(MNEMONIC, undefined, onlyJs, 64))
).toEqual(SEED_64);
});
it('generates a valid 32bytes seed', (): void => {
expect(
u8aToHex(mnemonicToLegacySeed(MNEMONIC, undefined, onlyJs, 32, 2048))
u8aToHex(mnemonicToLegacySeed(MNEMONIC, undefined, onlyJs))
).toEqual(SEED_32);
});
it('fails with non-mnemonics', (): void => {
expect(
() => mnemonicToLegacySeed('foo bar baz', undefined, onlyJs, 32, 2048)
() => mnemonicToLegacySeed('foo bar baz', undefined, onlyJs)
).toThrow(/mnemonic specified/);
});
});

View File

@@ -23,14 +23,8 @@ import { mnemonicValidate } from './validate.js';
* console.log(`Seed generated from mnemonic: ${mnemonicToLegacySeed(mnemonic)}`); => u8a
* }
* ```
*
* @param mnemonic - The BIP-39 mnemonic phrase to derive the secret from.
* @param password - Optional: password to secure the seed (default: empty string).
* @param onlyJs - Optional: If `true`, forces use of the JavaScript implementation instead of WASM.
* @param byteLength - Optional: Either 32 or 64. Default is 32
* @param rounds - Optional: Number of PBKDF2 iterations to run (default: 210000).
*/
export function mnemonicToLegacySeed (mnemonic: string, password = '', onlyJs?: boolean, byteLength: 32 | 64 = 32, rounds?: number): Uint8Array {
*/
export function mnemonicToLegacySeed (mnemonic: string, password = '', onlyJs?: boolean, byteLength: 32 | 64 = 32): Uint8Array {
if (!mnemonicValidate(mnemonic)) {
throw new Error('Invalid bip39 mnemonic specified');
} else if (![32, 64].includes(byteLength)) {
@@ -40,6 +34,6 @@ export function mnemonicToLegacySeed (mnemonic: string, password = '', onlyJs?:
return byteLength === 32
? !hasBigInt || (!onlyJs && isReady())
? bip39ToSeed(mnemonic, password)
: mnemonicToSeedSync(mnemonic, password, rounds).subarray(0, 32)
: mnemonicToSeedSync(mnemonic, password, rounds);
: mnemonicToSeedSync(mnemonic, password).subarray(0, 32)
: mnemonicToSeedSync(mnemonic, password);
}

View File

@@ -20,8 +20,8 @@ describe('mnemonicToMiniSecret', (): void => {
it(`generates Wasm & Js equivalents for password=${password || 'undefined'}`, (): void => {
expect(
u8aEq(
mnemonicToMiniSecret(MNEMONIC, password, undefined, true, 2048),
mnemonicToMiniSecret(MNEMONIC, password, undefined, false, 2048)
mnemonicToMiniSecret(MNEMONIC, password, undefined, true),
mnemonicToMiniSecret(MNEMONIC, password, undefined, false)
)
).toEqual(true);
});
@@ -31,10 +31,10 @@ describe('mnemonicToMiniSecret', (): void => {
const mnemonic = '엉덩이 능동적 숫자 팩시밀리 비난 서적 파출소 도움 독창적 인생 상류 먼지 답변 음반 수박 사업 노란색 공사 우체국 특급 도대체 금지 굉장히 고무신';
expect(
() => mnemonicToMiniSecret(mnemonic, 'testing', undefined, false, 2048)
() => mnemonicToMiniSecret(mnemonic, 'testing')
).toThrow();
expect(
u8aToHex(mnemonicToMiniSecret(mnemonic, 'testing', koreanWords, false, 2048))
u8aToHex(mnemonicToMiniSecret(mnemonic, 'testing', koreanWords))
).toEqual('0xefa278a62535581767a2f49cb542ed91b65fb911e1b05e7a09c702b257f10c13');
});
@@ -42,20 +42,20 @@ describe('mnemonicToMiniSecret', (): void => {
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
it('generates a valid seed', (): void => {
expect(
u8aToHex(mnemonicToMiniSecret(MNEMONIC, undefined, undefined, onlyJs, 2048))
u8aToHex(mnemonicToMiniSecret(MNEMONIC, undefined, undefined, onlyJs))
).toEqual(SEED);
});
it('fails with non-mnemonics', (): void => {
expect(
() => mnemonicToMiniSecret('foo bar baz', undefined, undefined, onlyJs, 2048)
() => mnemonicToMiniSecret('foo bar baz', undefined, undefined, onlyJs)
).toThrow(/mnemonic specified/);
});
tests.forEach(([mnemonic, , seed], index): void => {
it(`Created correct seed for ${index}`, (): void => {
expect(
u8aToHex(mnemonicToMiniSecret(mnemonic, 'Substrate', undefined, onlyJs, 2048))
u8aToHex(mnemonicToMiniSecret(mnemonic, 'Substrate', undefined, onlyJs))
).toEqual(
// mini returned here, only check first 32-bytes (64 hex + 2 prefix)
seed.substring(0, 66)

View File

@@ -8,14 +8,7 @@ import { pbkdf2Encode } from '../pbkdf2/index.js';
import { mnemonicToEntropy } from './toEntropy.js';
import { mnemonicValidate } from './validate.js';
/**
* @param mnemonic - The BIP-39 mnemonic phrase to derive the secret from.
* @param password - Optional: password to secure the seed (default: empty string).
* @param wordlist - Optional custom wordlist for mnemonic.
* @param onlyJs - Optional: If `true`, forces use of the JavaScript implementation instead of WASM.
* @param rounds - Optional: Number of PBKDF2 iterations to run (default: 210000 (when onlyJS = true) or 2048 (when onlyJS = false).
*/
export function mnemonicToMiniSecret (mnemonic: string, password = '', wordlist?: string[], onlyJs?: boolean, rounds?: number): Uint8Array {
export function mnemonicToMiniSecret (mnemonic: string, password = '', wordlist?: string[], onlyJs?: boolean): Uint8Array {
if (!mnemonicValidate(mnemonic, wordlist, onlyJs)) {
throw new Error('Invalid bip39 mnemonic specified');
} else if (!wordlist && !onlyJs && isReady()) {
@@ -26,5 +19,5 @@ export function mnemonicToMiniSecret (mnemonic: string, password = '', wordlist?
const salt = stringToU8a(`mnemonic${password}`);
// return the first 32 bytes as the seed
return pbkdf2Encode(entropy, salt, rounds).password.slice(0, 32);
return pbkdf2Encode(entropy, salt).password.slice(0, 32);
}

View File

@@ -49,6 +49,6 @@ describe('pbkdf2Encode', (): void => {
}
perfWasm('pbkdf2Encode', 8, (input, onlyJs) =>
pbkdf2Encode(input, input, 2048, onlyJs)
pbkdf2Encode(input, input, undefined, onlyJs)
);
});

View File

@@ -15,14 +15,7 @@ interface Result {
salt: Uint8Array;
}
export function pbkdf2Encode (passphrase?: string | Uint8Array, salt: Uint8Array = randomAsU8a(), rounds = 210000, onlyJs?: boolean): Result {
// When using the JS implementation (pbkdf2Js), large iteration counts can
// cause excessive computation time or memory usage. In that case, we cap
// the number of rounds to 2048 to ensure reliable performance.
if (onlyJs && rounds > 2048) {
rounds = 2048;
}
export function pbkdf2Encode (passphrase?: string | Uint8Array, salt: Uint8Array = randomAsU8a(), rounds = 2048, onlyJs?: boolean): Result {
const u8aPass = u8aToU8a(passphrase);
const u8aSalt = u8aToU8a(salt);