Des recherches sur les mnémoniques multilingues nous ont fait découvrir que Substrate ne respectait pas complètement la norme BIP39.
Au lieu de convertir la phrase de type string du mnémonique par une commande pbkdf2
, ils génèrent un mini-secret à partir de l’entropie. Puis le trousseau depuis le mini-secret.
Générer l’entropie depuis le mnémonique
Les mots du mnémonique sont stockés dans des listes de 2048 mots (wordlists) où les mots sont numérotés de 0 à 2047. L’entropie est la liste des indices correspondants aux 12 mots du mnémonique.
Dans Polkadot.js, le code est le suivant :
export function mnemonicToEntropy (mnemonic: string): Uint8Array {
const words = normalize(mnemonic).split(' ');
if (words.length % 3 !== 0) {
throw new Error(INVALID_MNEMONIC);
}
// convert word indices to 11 bit binary strings
const bits = words
.map((word): string => {
const index = DEFAULT_WORDLIST.indexOf(word);
if (index === -1) {
throw new Error(INVALID_MNEMONIC);
}
return index.toString(2).padStart(11, '0');
})
.join('');
// split the binary string into ENT/CS
const dividerIndex = Math.floor(bits.length / 33) * 32;
const entropyBits = bits.slice(0, dividerIndex);
const checksumBits = bits.slice(dividerIndex);
// calculate the checksum and compare
const matched = entropyBits.match(/(.{1,8})/g);
const entropyBytes = matched && matched.map(binaryToByte);
if (!entropyBytes || (entropyBytes.length % 4 !== 0) || (entropyBytes.length < 16) || (entropyBytes.length > 32)) {
throw new Error(INVALID_ENTROPY);
}
const entropy = u8aToU8a(entropyBytes);
if (deriveChecksumBits(entropy) !== checksumBits) {
throw new Error(INVALID_CHECKSUM);
}
return entropy;
}
Générer le mini-secret depuis l’entropie
L’entropie est ensuite convertie en mini-secret.
Dans Polkadot.js, le code est le suivant :
export function mnemonicToMiniSecret (mnemonic: string, password = '', onlyJs?: boolean): Uint8Array {
if (!mnemonicValidate(mnemonic)) {
throw new Error('Invalid bip39 mnemonic specified');
}
if (!onlyJs && isReady()) {
return bip39ToMiniSecret(mnemonic, password);
}
const entropy = mnemonicToEntropy(mnemonic);
const salt = stringToU8a(`mnemonic${password}`);
// return the first 32 bytes as the seed
return pbkdf2Encode(entropy, salt).password.slice(0, 32);
}
Générer un trousseau depuis le mini-secret
Pour cela, Polkadot.js fait appel à un binaire wasm :
import { sr25519KeypairFromSeed } from '@polkadot/wasm-crypto';
Ce binaire wasm est issu du code Rust suivant :
Je mets ce message en mode wiki si certains veulent ajouter des informations.