UNPKG

@aladas-org/cryptocalc

Version:
1,034 lines (805 loc) 41.3 kB
// ===================================================================================== // ================================= bip39_utils.js ================================== // ===================================================================================== // References: // https://medium.com/@sundar.sat84/bip39-mnemonic-generation-with-detailed-explanation-84abde9da4c1 // https://www.blockplate.com/blogs/blockplate/how-a-seed-phrase-is-created // https://github.com/massmux/HowtoCalculateBip39Mnemonic // Reference waterfall: // * https://mdrza.medium.com/how-to-convert-mnemonic-12-word-to-private-key-address-wallet-bitcoin-and-ethereum-81aa9ca91a57 // https://bitcointalk.org/index.php?topic=5288888.0 // BIP32: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki // // https://support.ledger.com/hc/fr-fr/articles/4415198323089-Comment-un-appareil-Ledger-g%C3%A9n%C3%A8re-une-phrase-de-r%C3%A9cup%C3%A9ration-de-24-mots&language-suggestion?docs=true // ==================================== Bip39Utils class ==================================== // * static EntropyToMnemonics( entropy_hex, options ) // * static EntropyToChecksum( entropy_hex, options ) // * static EntropySourceToEntropy( salted_entropy_src_str, options ) // * static EntropySourceToMnemonics( salted_entropy_src_str, options ) // * static MnemonicsToEntropyInfo( mnemonics ) // * static GetWordIndexes( mnemonics, options ) // * static WordIndexesToSeedPhrase( word_indexes, lang ) // * static GuessMnemonicsLang( mnemonics ) // * static CheckMnemonics( mnemonics, private_key, options ) // * static MnemonicsAs4letter( mnemonics ) // * static MnemonicsAsTwoParts( mnemonics ) // * static GetBIP39Dictionary( lang ) // * static GetArgs( args ) const sha256 = require('js-sha256'); const Bip39Mnemonic = require('bitcore-mnemonic'); const { v4: uuidv4 } = require('uuid'); // https://bitcoin.stackexchange.com/questions/113286/uncaught-typeerror-bip32-fromseed-is-not-a-function //const bip32 = require('bip32'); => ERROR: "bip32.fromSeed in not a function" const ecc = require('tiny-secp256k1') const { BIP32Factory } = require('bip32'); // You must wrap a tiny-secp256k1 compatible implementation const bip32 = BIP32Factory(ecc); const CS_WORDLIST_JSON = require('@scure/bip39/wordlists/czech'); const CS_WORDLIST = CS_WORDLIST_JSON['wordlist']; const PT_WORDLIST_JSON = require('@scure/bip39/wordlists/portuguese'); const PT_WORDLIST = PT_WORDLIST_JSON['wordlist']; const KO_WORDLIST_JSON = require('@scure/bip39/wordlists/korean'); const KO_WORDLIST = KO_WORDLIST_JSON['wordlist']; // Simplified Chinese const SC_WORDLIST_JSON = require('@scure/bip39/wordlists/simplified-chinese'); const SC_WORDLIST = SC_WORDLIST_JSON['wordlist']; // Traditional Chinese const TC_WORDLIST_JSON = require('@scure/bip39/wordlists/traditional-chinese'); const TC_WORDLIST = TC_WORDLIST_JSON['wordlist']; const { GERMAN_MNEMONICS } = require('../../../assets/mnemonics/DE_mnemonics.js'); const { ESPERANTO_MNEMONICS } = require('../../../assets/mnemonics/EO_2048_mnemonics.js'); const { RUSSIAN_MNEMONICS } = require('../../../assets/mnemonics/RU_2048_mnemonics.js'); const { LATIN_MNEMONICS } = require('../../../assets/mnemonics/LA_2048_mnemonics.js'); const { GREEK_MNEMONICS } = require('../../../assets/mnemonics/EL_2048_mnemonics.js'); const { HINDI_MNEMONICS } = require('../../../assets/mnemonics/HI_2048_mnemonics.js'); const { GUJARATI_MNEMONICS } = require('../../../assets/mnemonics/GU_2048_mnemonics.js'); const { BENGALI_MNEMONICS } = require('../../../assets/mnemonics/BN_2048_mnemonics.js'); // NB: Separator for Japanese // https://bitcoin.stackexchange.com/questions/37780/bip39-japanese-mnemonic-vector-unit-test-process // For display: IDEOGRAPHIC SPACEs: '\u3000' // Else: Normal Space: '\u0020' const { JAPANESE_MNEMONICS } = require('../../../assets/mnemonics/JP_2048_mnemonics.js'); const SUPPORTED_LANGS = [ "EN", "DE", "FR", "ES", "IT", "CS", "PT", "JP", "KO", "SC", "TC", "EO", "RU", "LA", "EL", "HI", "GU", "BN" ]; const { _RED_, _CYAN_, _PURPLE_, _YELLOW_, _GREEN_, _RED_HIGH_, _BLUE_HIGH_, _END_ } = require('../util/color/color_console_codes.js'); const { pretty_func_header_log, pretty_log } = require('../util/log/log_utils.js'); const { NULL_COIN, BITCOIN, DOGECOIN, LITECOIN, BINANCE_BSC, ETHEREUM, AVALANCHE, POLYGON, TON, CARDANO, STELLAR, SUI, RIPPLE, TRON, DASH, VECOIN, ETHEREUM_CLASSIC, BITCOIN_CASH, BITCOIN_SV, TERRA_LUNA, RAVENCOIN, HORIZEN, MAINNET, TESTNET, COIN_ABBREVIATIONS, COIN_TYPES } = require('./const_blockchains.js'); const { NULL_HEX, CRYPTO_NET, ADDRESS, UUID, CHECKSUM, CHAINCODE, ENTROPY_HEX, MASTER_PK_HEX, ROOT_PK_HEX, PRIVATE_KEY, PUBLIC_KEY_HEX } = require('./const_wallet.js'); const { BLOCKCHAIN, NULL_BLOCKCHAIN, LANG, MNEMONICS, WORD_COUNT, ACCOUNT, ADDRESS_INDEX } = require('../const_keywords.js'); const { hexToBinary, binaryToHex, hexWithPrefix, hexWithoutPrefix, isHexString, uint8ArrayToHex, hexToUint8Array } = require('./hex_utils.js'); const { EntropySize } = require('./entropy_size.js'); const { getShortenedString } = require('../util/values/string_utils.js'); const { stringIsNumber } = require('../util/values/number_utils.js'); // https://stackoverflow.com/questions/10073699/pad-a-number-with-leading-zeros-in-javascript const pad = (n, width, z) => { z = z || '0'; n = n + ''; return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; }; // pad() const normalizeBip39 = (str) =>{ if (!str) return ''; // Convertir TOUT en forme décomposée, puis supprimer les accents combinants return str.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); }; // normalizeBip39() class Bip39Utils { // NB: Validation test: // entropy = "37297b2455dbaa522dea2cd9a0be34ab488c8f00e0ef7085d73137e2b84c94db" static EntropyToMnemonics( entropy_hex, lang ) { pretty_func_header_log( "Bip39Utils.EntropyToMnemonics" ); // pretty_log( "b39.E2Mnk> entropy_hex", entropy_hex ); if ( entropy_hex == undefined || entropy_hex == "" ) { throw new Error("Bip39Utils.EntropyToMnemonics 'entropy_hex' NOT DEFINED"); } if ( lang == undefined ) lang = "EN"; let word_count = EntropySize.GetWordCount( entropy_hex ); // pretty_log( "b39.E2Mnk> word_count", word_count ); // pretty_log( "b39.E2Mnk> lang", lang ); let checksum_size = EntropySize.GetChecksumBitCount( word_count ); // pretty_log( "b39.E2Mnk> checksum_size", checksum_size); // pretty_log( "b39.E2Mnk> entropy_hex", entropy_hex ); entropy_hex = EntropySize.GetSHA256Substring( entropy_hex, word_count ); pretty_log( "b39.E2Mnk> entropy_hex.substr(" + word_count + ")", entropy_hex ); let entropy_bits = hexToBinary( entropy_hex ); //pretty_log( "b39.E2Mnk> entropy_bits: '" + entropy_bits + "'" ); let checksum_bits = Bip39Utils.EntropyToChecksum( entropy_hex ); // NB: entropy MUST BE HASHED to compute 'Checksum' // pretty_log( Bip39Utils.LabelWithSize("b39.E2Mnk> checksum_bits 2", checksum_size), checksum_bits ); entropy_bits += checksum_bits; let mnemonics = ""; let LANG_WORDLIST = Bip39Utils.GetBIP39Dictionary( lang ); let word_index = 0; let word = ""; let retrieved_word_count = 0; // pretty_log( "b39.E2Mnk> entropy_bits.length", entropy_bits.length ); for ( let i=0; i < entropy_bits.length; i+=11 ) { // pretty_log( "b39.E2Mnk> loop on entropy_bits i: ", i ); let binary_11bits = entropy_bits.substring(i, i+11); word_index = parseInt( binary_11bits, 2 ); // convert binary string in decimal //pretty_log( "b39.E2Mnk> word_index", word_index ); word = LANG_WORDLIST[word_index]; // pretty_log( "b39.E2Mnk> word[" + retrieved_word_count + "]" , word ); let separator = ((i + 11) >= entropy_bits.length) ? "" : " "; mnemonics += word + separator; retrieved_word_count++; } // pretty_log( "b39.E2Mnk> retrieved_word_count", retrieved_word_count ); // pretty_func_header_log( "<END> Bip39Utils.EntropyToMnemonics" ); return mnemonics; } // Bip39Utils.EntropyToMnemonics() static PrivateKeyToMnemonics( private_key ) { pretty_func_header_log( "Bip39Utils.PrivateKeyToMnemonics" ); let lang = "EN"; // TEST https://github.com/massmux/HowtoCalculateBip39Mnemonic?tab=readme-ov-file // entropy_hex = "656d338db7c217ad57b2516cdddd6d06"; // TEST // private_key_hex = "0ed797c1da6515542acda6358045702a0a558be931cb0490ea7044e0c0311645"; // Erreur // Pour private_key_hex = // 0ed797c1da6515542acda6358045702a0a558be931cb0490ea7044e0c0311645 // => attract royal vacant regular eyebrow present private regular // culture acquire foster favorite pipe shine pill defense // afraid mansion orchard mean army blush flip rubber // ^^^^^^ OK // => attract royal vacant regular eyebrow present private regular // culture acquire foster favorite pipe shine pill defense // afraid mansion orchard mean army blush flip peace // ****** KO private_key = hexWithoutPrefix( private_key ); let private_key_byte_count = private_key.length / 2; pretty_log( Bip39Utils.LabelWithSize( "private_key", private_key_byte_count ), private_key ); if ( ! isHexString( private_key ) ) { let error_msg = "**ERROR** Input for 'Private Key' is Not a in Hexadecimal"; return error_msg; } if ( private_key_byte_count != 32 ) { let error_msg = "**ERROR** Input for 'Private Key' provides " + private_key_byte_count + " bytes while 32 are expected"; return error_msg; } let word_index = 0; let word = ""; let mnemonics = ""; // https://github.com/massmux/HowtoCalculateBip39Mnemonic let private_key_bits = hexToBinary( private_key ); // pretty_log( "b39.E2Mnk> private_key_bits: '" + private_key_bits + "'" ); let checksum_size = ( (private_key.length / 2) * 8 ) / 32; let pk_sha256 = ""; if ( isHexString( private_key ) ) { pk_sha256 = sha256( hexToUint8Array( private_key ) ); } else { pk_sha256 = sha256( private_key ); } let pk_sha256_bits = hexToBinary( pk_sha256 ); let checksum_bits = pk_sha256_bits.substring( 0, checksum_size ); // pretty_log( "bip39.pk2Mnk> " + Bip39Utils.LabelWithSize( "checksum_bits", checksum_size ), checksum_bits ); private_key_bits += checksum_bits; let LANG_WORDLIST = Bip39Utils.GetBIP39Dictionary( lang ); for ( let i=0; i < private_key_bits.length; i+=11 ) { let word_index_11bits = private_key_bits.substring( i, i+11 ); word_index = parseInt( word_index_11bits, 2 ); // convert binary string in decimal word = LANG_WORDLIST[word_index]; // pretty_log( "bip39.pk2Mnk> word[" + i + "]", word ); //console.log(" word: " + word + " word_index:" + word_index); let separator = ((i + 11) >= private_key_bits.length) ? "" : " "; mnemonics += word + separator; } return mnemonics; } // Bip39Utils.PrivateKeyToMnemonics() // https://github.com/massmux/HowtoCalculateBip39Mnemonic // NB: doesnt hash 'salted_entropy_src_str' if it is hex and provides expected byte count static EntropySourceToEntropy( salted_entropy_src_str, options ) { pretty_func_header_log( "Bip39Utils.EntropySourceToEntropy" ); options = Bip39Utils.GetArgs( options ); let word_count = options[WORD_COUNT]; //pretty_log( "salted_entropy_src_str", getShortenedString( salted_entropy_src_str )); //console.log(" word_count: " + word_count); let checksum_bit_count = Bip39Utils.GetChecksumBitCount( word_count ); let expected_bit_count = ( word_count * 11 ) - checksum_bit_count; //console.log(" expected_bit_count: " + expected_bit_count); let entropy_hex = ""; // test: https://github.com/massmux/HowtoCalculateBip39Mnemonic?tab=readme-ov-file // salted_entropy_src_str = "656d338db7c217ad57b2516cdddd6d06"; // word_count = 12; // test // !! NB !!: there was error here: // SHA256(Uint8Array( entropy_hex )) should be computed, NOT SHA256( entropy_hex ) if ( isHexString( salted_entropy_src_str ) ) { let entropy_binary = hexToBinary( salted_entropy_src_str ); let entropy_binary_bit_count = entropy_binary.length; if ( entropy_binary_bit_count == expected_bit_count ) { entropy_hex = salted_entropy_src_str; } else { entropy_hex = sha256( hexToUint8Array( salted_entropy_src_str ) ); } } else { // 'salted_entropy_src_str' is not Hex entropy_hex = sha256( salted_entropy_src_str ); } switch ( word_count ) { case 12: entropy_hex = entropy_hex.substring(0, 32); break; // [0..31]: first 32 bytes of SHA256 case 15: entropy_hex = entropy_hex.substring(0, 40); break; // [0..39]: first 40 bytes of SHA256 case 18: entropy_hex = entropy_hex.substring(0, 48); break; // [0..47]: first 48 bytes of SHA256 case 21: entropy_hex = entropy_hex.substring(0, 56); break; // [0..55]: first 56 bytes of SHA256 } // word_count //console.log(" entropy_hex: " + entropy_hex); return entropy_hex; } // Bip39Utils.EntropySourceToEntropy() static EntropySourceToMnemonics( salted_entropy_src_str, options ) { pretty_func_header_log( "Bip39Utils.EntropySourceToMnemonics" ); let entropy_hex = Bip39Utils.EntropySourceToEntropy( salted_entropy_src_str, options ); pretty_log( "b39.Esrc2Mnk> entropy_hex", entropy_hex ); let mnemonics = Bip39Utils.EntropyToMnemonics( entropy_hex, options ); return mnemonics; } // Bip39Utils.EntropySourceToMnemonics() static EntropyToChecksum( entropy_hex ) { // pretty_func_header_log( "Bip39Utils.EntropyToChecksum" ); let word_count = EntropySize.GetWordCount( entropy_hex ); let checksum_size = 8; switch ( word_count ) { case 12: entropy_hex = entropy_hex.substring(0, 32); // [0..31]: first 32 chars of SHA256 checksum_size = 4; break; case 15: entropy_hex = entropy_hex.substring(0, 40); // [0..39]: first 40 chars of SHA256 checksum_size = 5; break; case 18: entropy_hex = entropy_hex.substring(0, 48); // [0..47]: first 48 chars of SHA256 checksum_size = 6; break; case 21: entropy_hex = entropy_hex.substring(0, 56); // [0..55]: first 56 chars of SHA256 checksum_size = 7; break; case 24: //..........................................// [0..64]: All chars of SHA256 checksum_size = 8; break; } // word_count entropy_hex = hexWithoutPrefix( entropy_hex ); let entropy_binary = hexToBinary( entropy_hex ); // !! NB !!: // There was error here: SHA256(Uint8Array(entropy_hex)) should be computed, NOT SHA256(entropy_hex) let entropy_hex_sha256 = ""; if ( isHexString(entropy_hex) ) { entropy_hex_sha256 = sha256( hexToUint8Array( entropy_hex ) ); } else { entropy_hex_sha256 = sha256( entropy_hex ); } let entropy_hex_sha256_bits = hexToBinary( entropy_hex_sha256 ); let checksum_bits = entropy_hex_sha256_bits.substring(0, checksum_size); //console.log( " " + _YELLOW_ // + Bip39Utils.LabelWithSize("checksum_bits", checksum_size) + _END_ // + " " + checksum_bits); return checksum_bits; } // Bip39Utils.EntropyToChecksum() // https://github.com/bitcoinjs/bip38/issues/63 static MnemonicsToEntropyInfo( mnemonics, lang ) { pretty_func_header_log( "Bip39Utils.MnemonicsToEntropyInfo" ); let entropy_info = {}; if ( mnemonics == undefined ) { mnemonics = "rich hard unveil charge stadium affair net ski style stadium helmet void"; } if ( lang == undefined ) lang = "EN"; // pretty_log("Mnk2Einf> mnemonics(" + lang + "): '" + mnemonics + "'"); let LANG_WORDLIST = Bip39Utils.GetBIP39Dictionary( lang ); // console.log(" LANG_WORDLIST: " + LANG_WORDLIST.length); let NORMALIZED_WORDLIST = LANG_WORDLIST.map(normalizeBip39); let words = mnemonics.split(' '); // console.log(" words: " +JSON.stringify(words)); let entropy_binary = ''; let word_index = -1; let current_word = ''; let word_index_binary = ''; // * Case 1: 24 words (24*11 = 264) => 256 bits (SHA256) + last 11 bits = "checksum word index" // * Case 2: 12 words (12*11 = 132) => 128 bits (SHA-1) + last 11 bits = "checksum word index" try { for ( let i=0; i < words.length; i++ ) { current_word = words[i]; // console.log(" current_word(" + i + "): '" + current_word + "'"); // current_word = current_word.normalize('NFC'); current_word = normalizeBip39( current_word ); // console.log(" AFTER current_word(" + i + "): '" + current_word); if ( stringIsNumber( current_word ) ) { word_index = parseInt( current_word ); } else { // console.log(" words[" + i + "]: '" + current_word + "'"); // word_index = LANG_WORDLIST.indexOf( current_word ); word_index = NORMALIZED_WORDLIST.indexOf( current_word ); // console.log(" word_index: " + word_index); } word_index_binary = parseInt(word_index, 10).toString(2).padStart(11, "0"); //console.log(">> " + "ToPrivateKey index_binary(" + i + "): " + index_binary); entropy_binary += word_index_binary; } // for each word in 'mnemonics' } catch (err) { throw new Error("***ERROR*** in Bip39Utils.MnemonicsToEntropyInfo lines: 358..412"); } let word_count = words.length; // pretty_log("Mnk2Einf> word_count", word_count); let checksum_bit_count = Bip39Utils.GetChecksumBitCount( word_count ); // pretty_log("Mnk2Einf> checksum_bit_count", checksum_bit_count); let checksum_bits = entropy_binary.substring( entropy_binary.length - checksum_bit_count ); //console.log(" checksum_bits: " + checksum_bits); entropy_info[CHECKSUM] = checksum_bits; let entropy_bits = entropy_binary.substring( 0, entropy_binary.length - checksum_bit_count ); let entropy_hex = binaryToHex( entropy_bits ); pretty_log("Mnk2Einf> entropy_hex: '" + entropy_hex + "'"); entropy_info[ENTROPY_HEX] = entropy_hex; return entropy_info; } // Bip39Utils.MnemonicsToEntropyInfo() // ++++++++++++++++++++++++++++++ Custom handling of mnemonics cuount 1..24 ++++++++++++++++++++++++++++++ // NOUVELLE METHODE: Supporte les phrases de 1 à 24 mots static EntropyToChecksumCustom( entropy_hex, word_count ) { let checksum_bits_count = Bip39Utils.GetChecksumBitCountCustom( word_count ); // Nettoyer l'hex entropy_hex = hexWithoutPrefix( entropy_hex ); // Si l'hex a une longueur impaire, ajouter un zéro devant if (entropy_hex.length % 2 !== 0) { entropy_hex = "0" + entropy_hex; } // Convertir en bytes let entropy_bytes; try { entropy_bytes = hexToUint8Array( entropy_hex ); } catch(e) { // Si la conversion échoue, créer un buffer à partir des bits pretty_log("EntropyToChecksumCustom> hexToUint8Array failed, using fallback", e.message); // Fallback: utiliser directement la chaîne hex entropy_bytes = new Uint8Array([0]); } // Calculer SHA256 let entropy_hex_sha256 = sha256( entropy_bytes ); let entropy_hex_sha256_bits = hexToBinary( entropy_hex_sha256 ); let checksum_bits = entropy_hex_sha256_bits.substring(0, checksum_bits_count); return checksum_bits; } // EntropyToChecksumCustom() // ++++++++++ NOUVELLE METHODE: Supporte les phrases de 1 à 24 mots static MnemonicsToEntropyInfoCustom( mnemonics, lang ) { pretty_func_header_log( "Bip39Utils.MnemonicsToEntropyInfoCustom" ); let entropy_info = {}; if ( mnemonics == undefined ) { throw new Error("Mnemonics non défini"); } // Auto-détection de la langue si non fournie if ( lang == undefined ) { lang = Bip39Utils.GuessMnemonicsLang( mnemonics ); } pretty_log("Mnk2EinfC> mnemonics(" + lang + "): " + mnemonics); let LANG_WORDLIST = Bip39Utils.GetBIP39Dictionary( lang ); // Normaliser la liste une fois au chargement (à faire au début) let words = mnemonics.trim().split(/\s+/); let word_count = words.length; if ( word_count < 1 || word_count > 24 ) { throw new Error("La phrase doit contenir entre 1 et 24 mots"); } // pretty_log("Mnk2EinfC> word_count", word_count); // Convertir chaque mot en bits let full_bits = ''; let word_index = -1; // Prétraiter le dictionnaire UNE SEULE FOIS const NORMALIZED_WORDLIST = LANG_WORDLIST.map(normalizeBip39); for ( let i = 0; i < words.length; i++ ) { let current_word = words[i]; current_word = normalizeBip39(current_word); // console.log( "Bip39.MnemonicsToEntropyInfoCustom current_word: '" + current_word + "'"); if ( stringIsNumber( current_word ) ) { word_index = parseInt( current_word ); } else { // word_index = LANG_WORDLIST.indexOf( current_word ); word_index = NORMALIZED_WORDLIST.indexOf( current_word ); if ( word_index === -1 ) { throw new Error(`Mot "${words[i]}" non trouvé dans la liste ${lang}`); } } let word_bits = word_index.toString(2).padStart(11, '0'); full_bits += word_bits; } // pretty_log("Mnk2EinfC> full_bits.length", full_bits.length); // pretty_log("Mnk2EinfC> full_bits", full_bits.substring(0, 50) + "..."); // Utiliser la nouvelle méthode de checksum let checksum_bit_count = Bip39Utils.GetChecksumBitCountCustom( word_count ); let entropy_bit_count = full_bits.length - checksum_bit_count; // pretty_log("Mnk2EinfC> checksum_bit_count", checksum_bit_count); // pretty_log("Mnk2EinfC> entropy_bit_count", entropy_bit_count); let entropy_bits = full_bits.substring(0, entropy_bit_count); let received_checksum = full_bits.substring(entropy_bit_count); // pretty_log("Mnk2EinfC> entropy_bits.length", entropy_bits.length); // pretty_log("Mnk2EinfC> received_checksum", received_checksum); // Convertir entropy_bits en hex - CORRECTION POUR BITS NON MULTIPLES DE 4 let entropy_hex = ""; if (entropy_bits.length > 0) { // Convertir directement les bits en hex sans passer par binaryToHex // qui nécessite des multiples de 4 let hex_chars = []; let temp_bits = entropy_bits; // Traiter par groupes de 4 bits for (let i = 0; i < temp_bits.length; i += 4) { let nibble = temp_bits.substring(i, i + 4); // Ajouter des zéros à droite si nécessaire while (nibble.length < 4) { nibble += "0"; } hex_chars.push(parseInt(nibble, 2).toString(16)); } entropy_hex = hex_chars.join(''); // Enlever les zéros de fin inutiles (optionnel) entropy_hex = entropy_hex.replace(/0+$/, ''); if ( entropy_hex === "" ) entropy_hex = "0"; } pretty_log("Mnk2EinfC> entropy_hex", entropy_hex); // Recalculer le checksum attendu let expected_checksum = Bip39Utils.EntropyToChecksumCustom(entropy_hex, word_count); pretty_log("Mnk2EinfC> expected_checksum", expected_checksum); let is_valid = (received_checksum === expected_checksum); pretty_log("Mnk2EinfC> isValid", is_valid); entropy_info[CHECKSUM] = received_checksum; entropy_info[ENTROPY_HEX] = entropy_hex; entropy_info["isValid"] = is_valid; entropy_info["wordCount"] = word_count; entropy_info["isStandard"] = (word_count >= 12 && word_count % 3 === 0); entropy_info["checksumBitCount"] = checksum_bit_count; entropy_info["expectedChecksum"] = expected_checksum; entropy_info["fullBits"] = full_bits; entropy_info["entropyBits"] = entropy_bits; return entropy_info; } // Bip39Utils.MnemonicsToEntropyInfoCustom() // ++++++++++ // Nouvelle méthode pour support 1-24 mots static GetChecksumBitCountCustom( word_count ) { if ( word_count == undefined ) { word_count = 12; } if ( word_count >= 12 ) { // Standard BIP39 pour 12-24 mots switch ( word_count ) { case 12: return 4; case 15: return 5; case 18: return 6; case 21: return 7; case 24: return 8; default: return Math.floor(word_count / 3); } } else { // Pour 1-11 mots: minimum 1 bit de checksum return Math.max(1, Math.floor(word_count / 3)); } } // Bip39Utils.GetChecksumBitCountCustom() // ++++++++++++++++++++++++++++++ Custom handling of mnemonics cuount 1..24 // https://medium.com/@sundar.sat84/bip39-mnemonic-generation-with-detailed-explanation-84abde9da4c1 static GetChecksumBitCount( word_count ) { if ( word_count == undefined ) { word_count = 12; } let checksum_bit_count = 4; // default case is 12 words / 4 bits switch ( word_count ) { case 12: checksum_bit_count = 4; break; case 15: checksum_bit_count = 5; break; case 18: checksum_bit_count = 6; break; case 21: checksum_bit_count = 7; break; case 24: checksum_bit_count = 8; break; default: checksum_bit_count = 4; break; } return checksum_bit_count; } // Bip39Utils.GetChecksumBitCount() static GetWordIndexes( mnemonics, args ) { console.log(">> " + _CYAN_ + "Bip39Utils.GetWordIndexes " + _END_); // console.log("Bip39.GetWordIdx mnemonics: " + mnemonics); args = Bip39Utils.GetArgs( args ); // console.log("Bip39.GetWordIdx args: " + JSON.stringify(args)); let lang = (args[LANG] != undefined) ? args[LANG] : "EN"; // console.log("Bip39.GetWordIdx lang: " + lang); let word_index_base = (args["word_index_base"] != undefined) ? args["word_index_base"] : "Decimal"; // console.log("Bip39.GetWordIdx word_index_base: " + word_index_base); // console.log(" word_index_base: " + word_index_base); // console.log(" Lang: " + lang); let words = mnemonics.split(' '); // console.log("Bip39.GetWordIdx words: " + JSON.stringify(words)); // console.log(" words("+ words.length + "): " + words); let LANG_WORDLIST = Bip39Utils.GetBIP39Dictionary( lang ); let word_indexes = []; const NORMALIZED_WORDLIST = LANG_WORDLIST.map(normalizeBip39); let word_index = -1; let current_word = ''; for ( let i=0; i < words.length; i++ ) { current_word = words[i]; let normalized_word = normalizeBip39(current_word); // console.log(" word("+ i + "): " + current_word); word_index = LANG_WORDLIST.indexOf( current_word ); if ( word_index == -1 ) { word_index = NORMALIZED_WORDLIST.indexOf( normalized_word ); } // console.log(" word_index: " + word_index); if ( word_index_base == "Decimal" ) { word_indexes.push( word_index.toString() ); } else if (word_index_base == "Binary") { let word_index_binary = parseInt(word_index, 10).toString(2).padStart(11, "0"); // console.log(" word_index_binary: " + word_index_binary); word_indexes.push( word_index_binary ); } } //console.log(">> END " + _CYAN_ + "Bip39Utils.GetWordIndexes " + _END_); return word_indexes; } // Bip39Utils.GetWordIndexes() static WordIndexesToMnemonics( word_indexes, lang ) { if ( word_indexes == undefined ) { word_indexes = []; } if ( lang == undefined ) { lang = "EN"; } let LANG_WORDLIST = Bip39Utils.GetBIP39Dictionary( lang ); let word_count = word_indexes.length; let mnemonics = []; if ( ! ( word_count == 12 || word_count == 15 || word_count == 18 || word_count == 21 || word_count == 24 ) ) { return mnemonics; } let entropy_binary = ""; for ( let i=0; i < word_indexes.length; i++ ) { let current_word = word_indexes[i]; let word_index = parseInt( current_word ); let word_index_binary = parseInt(word_index, 10).toString(2).padStart(11, "0"); entropy_binary += word_index_binary; } // for each word in 'words' let checksum_bit_count = Bip39Utils.GetChecksumBitCount( word_count ); // console.log(" checksum_bit_count: " + checksum_bit_count); let checksum_bits = entropy_binary.substring( entropy_binary.length - checksum_bit_count ); // console.log(" checksum_bits: " + checksum_bits); let entropy_bits = entropy_binary.substring( 0, entropy_binary.length - checksum_bit_count ); let entropy_hex = binaryToHex( entropy_bits ); // pretty_log(" entropy_hex", entropy_hex); mnemonics = Bip39Utils.EntropyToMnemonics( entropy_hex, lang ); return mnemonics; } // Bip39Utils.WordIndexesToMnemonics() static GetBIP39Dictionary( lang ) { //console.log(">> " + _CYAN_ + "Bip39Utils.GetBIP39Dictionary " + _END_); if (lang == undefined) { lang = "EN"; } //console.log(" lang: " + lang); let mnemonics_dictionary = Bip39Mnemonic.Words.ENGLISH; switch ( lang ) { case "EN": mnemonics_dictionary = Bip39Mnemonic.Words.ENGLISH; break; // English case "FR": mnemonics_dictionary = Bip39Mnemonic.Words.FRENCH; break; // French case "ES": mnemonics_dictionary = Bip39Mnemonic.Words.SPANISH; break; // Spanish case "IT": mnemonics_dictionary = Bip39Mnemonic.Words.ITALIAN; break; // Italian case "CS": mnemonics_dictionary = CS_WORDLIST; break; // Czech case "PT": mnemonics_dictionary = PT_WORDLIST; break; // Portuguese case "SC": mnemonics_dictionary = SC_WORDLIST; break; // Simplified Chinese case "TC": mnemonics_dictionary = TC_WORDLIST; break; // Traditional Chinese case "JP": mnemonics_dictionary = JAPANESE_MNEMONICS; break; // Japanese case "KO": mnemonics_dictionary = KO_WORDLIST; break; // Korean // ------------------ Added 'Non official in BIP39' languages ------------------ case "DE": mnemonics_dictionary = GERMAN_MNEMONICS; break; // German case "EO": mnemonics_dictionary = ESPERANTO_MNEMONICS; break; // Esperanto case "RU": mnemonics_dictionary = RUSSIAN_MNEMONICS; break; // Russian case "EL": mnemonics_dictionary = GREEK_MNEMONICS; break; // Greek case "LA": mnemonics_dictionary = LATIN_MNEMONICS; break; // Latin case "HI": mnemonics_dictionary = HINDI_MNEMONICS; break; // Hindi case "GU": mnemonics_dictionary = GUJARATI_MNEMONICS; break; // Gujarati case "BN": mnemonics_dictionary = BENGALI_MNEMONICS; break; // Bengali // ------------------ Added 'beyond BIP39 standard' languages default: mnemonics_dictionary = Bip39Mnemonic.Words.ENGLISH; break; } return mnemonics_dictionary; } // Bip39Utils.GetBIP39Dictionary() static GetMnemonicFromWordIndex( word_index, lang ) { console.log(">> " + _CYAN_ + "Bip39Utils.GetMnemonicFromWordIndex " + _END_); if ( word_index < 0 || word_index > 2047 ) return ''; // console.log(">> -------- Bip39.widx2word lang: '" + lang + "'"); if ( lang == undefined ) { lang = "EN"; } // console.log(">> -------- Bip39.widx2word word_index: " + word_index); // console.log(">> -------- Bip39.widx2word lang: " + lang); let mnemonics_dictionary = Bip39Utils.GetBIP39Dictionary( lang ); let mnemonic = mnemonics_dictionary[word_index]; // console.log(">> -------- Bip39.widx2Mnic mnemonic: " + mnemonic); return mnemonic; } // Bip39Utils.GetMnemonicFromWordIndex() static GuessMnemonicsLang( mnemonics ) { pretty_log(">> Bip39Utils.GuessMnemonicsLang", "" ); let lang = "EN"; let words = mnemonics.split(' '); let current_lang = ""; for ( let i=0; i < SUPPORTED_LANGS.length; i++ ) { current_lang = SUPPORTED_LANGS[i]; // console.log(" Check if 'mnemonics' is: " + current_lang); let LANG_WORDLIST = Bip39Utils.GetBIP39Dictionary( current_lang ); // console.log(" LANG_WORDLIST.length: " + LANG_WORDLIST.length); let found_word_count = 0; for ( let j=0; j < words.length; j++ ) { let current_word = words[j]; let word_index = LANG_WORDLIST.indexOf( current_word ); if ( word_index != -1 ) { found_word_count++; // console.log(" current_word[" + j + "]: " + current_word ); // console.log(" found_word_count: " + found_word_count ); } } if ( found_word_count == words.length ) { return current_lang; } } return current_lang; } // Bip39Utils.GuessMnemonicsLang() static CheckMnemonics( mnemonics, args ) { pretty_log(">> Bip39Utils.CheckMnemonics", ""); args = Bip39Utils.GetArgs( args ); let lang = args[LANG]; let word_count = args["word_count"]; let words = mnemonics.split(' '); //console.log(" words.length: " + words.length); if ( words.length != word_count ) { return false; } let LANG_WORDLIST = Bip39Utils.GetBIP39Dictionary( lang ); for ( let i=0; i < words.length; i++ ) { let current_word = words[i]; //console.log(" word[" + i + "] : " + current_word); let word_index = LANG_WORDLIST.indexOf( current_word ); if ( word_index == -1 ) { return false; } } return true; } // Bip39Utils.CheckMnemonics() static MnemonicsAs4letter( mnemonics ) { //console.log(">> " + _CYAN_ + "Bip39Utils.MnemonicsAs4letter" + _END_); let word_4letter_prefixes = ''; let words = mnemonics.split(' '); let word_count = words.length; let capitalize = (in_str) => { return in_str.charAt(0).toUpperCase() + in_str.slice(1); }; // capitalize() for ( let i=0; i < word_count; i++ ) { let word_prefix = words[i].substr(0,4); // [0..3] word_4letter_prefixes += capitalize(word_prefix); } //console.log(">> getNewFriezeTextvalues word_4letter_prefixes: " + word_4letter_prefixes); //console.log(">> getNewFriezeTextvalues word_4letter_prefixes.length: " + word_4letter_prefixes.length); const mnemonics_as_4letter = word_4letter_prefixes; //console.log(">> getNewFriezeTextvalues new_frieze_text: " + new_frieze_text); //console.log(">> getNewFriezeTextvalues length: " + new_frieze_text.length); return mnemonics_as_4letter; } // Bip39Utils.MnemonicsAs4letter() static MnemonicsAsTwoParts( mnemonics ) { //console.log(">> " + _CYAN_ + "Bip39Utils.MnemonicsAsTwoParts" + _END_); if (mnemonics == undefined) { return "Null-MNEMONICS"; } let words = mnemonics.split(' '); let word_count = words.length; let mnemonics_2parts = []; // NB: No need to cut in 2 parts if only 12 words if ( word_count == 12 ) { mnemonics_2parts.push( mnemonics ); mnemonics_2parts.push( "" ); } else { let half_index = Math.floor( words.length / 2 ); // console.log("half_index: " + half_index); mnemonics_2parts.push( words.slice( 0, half_index ).join().replaceAll(',' , ' ') ); mnemonics_2parts.push( words.slice( half_index).join().replaceAll(',' , ' ') ); } return mnemonics_2parts; } // Bip39Utils.MnemonicsAsTwoParts() static LabelWithSize(data, size) { let msg = data + "(" + size + ")"; return msg; } // Bip39Utils.LabelWithSize() static GetArgs( args ) { const getArgValue = ( args, key, default_value ) => { let arg_value = default_value; if ( args[key] != undefined ) { arg_value = args[key]; } return arg_value; }; // getArgValue() // console.log(">> " + _CYAN_ + "Bip39Utils.GetArgs" + _END_); // console.log("Bip39.GetArgs args: " + JSON.stringify(args)); if ( args == undefined ) { args = {}; } args[LANG] = getArgValue( args, LANG, "EN" ); // console.log("Bip39.GetArgs(LANG): LANG constant: " + LANG + " args[LANG]:" + args[LANG]); args[WORD_COUNT] = getArgValue( args, WORD_COUNT, 12 ); args[BLOCKCHAIN] = getArgValue( args, BLOCKCHAIN, BITCOIN ); args[CRYPTO_NET] = getArgValue( args, CRYPTO_NET, MAINNET ); args[UUID] = getArgValue( args, UUID, uuidv4() ); args[ADDRESS_INDEX] = getArgValue( args, ADDRESS_INDEX, 0 ); args[ACCOUNT] = getArgValue( args, ACCOUNT, 0 ); //console.log(" args 2: " + JSON.stringify(args)); return args; } // Bip39Utils.GetArgs() } // Bip39Utils class const test_Bip39Utils = () => { let mnemonics = "cupboard merge release real stock learn allow obey soup gasp pupil sail " + "allow enroll name yard note silver abuse castle public you ship stay"; mnemonics = "cupboard merge release real stock learn allow obey " // 15 + "soup gasp pupil sail allow enroll name"; mnemonics = "cupboard merge release real stock learn allow obey soup " // 18 + "gasp pupil sail allow enroll name yard note silver"; mnemonics = "cupboard merge release real stock learn allow " // 21 + "obey soup gasp pupil sail allow enroll " + "name yard note silver abuse castle public"; let mnemonics_items = Bip39Utils.MnemonicsAsTwoParts( mnemonics ); pretty_log("mnemonics: " , mnemonics); pretty_log("mnemonics_items[0]: " , mnemonics_items[0]); pretty_log("mnemonics_items[0]: " , mnemonics_items[1]); }; // test_EntropyToMnemonics() // test_Bip39Utils(); const test_GuessMnemonicsLang = () => { let mnemonics_PT = "agrupar jato bitola moderno ocupado ossada sedutor quente grisalho julho moto vagaroso"; let lang = ""; lang = Bip39Utils.GuessMnemonicsLang( mnemonics_PT ); pretty_log(" lang: " , lang); let mnemonics_RU = "аршин огурец вскоре пощечина режим рояль укол стать нагрузка округ программа шланг"; lang = Bip39Utils.GuessMnemonicsLang( mnemonics_RU ); pretty_log(" lang: " , lang); }; // test_GuessMnemonicsLang() const test_LessThan12Mnemonics = () => { let mnemonics = "trumpet perfect belt duty stay"; let lang = "EN"; let result = Bip39Utils.MnemonicsToEntropyInfoCustom( mnemonics, lang ); console.log( JSON.stringify(result) ); }; // test_GuessMnemonicsLang() const translate_ConvertLessThan12Mnemonics = async () => { let current_mnemonic = ''; let new_mnemonic = ''; let mnemonics = '' let lang = ''; let options = {}; let word_index = -1; let current_lang = 'EN'; let new_lang = 'FR'; let current_mnemonics = "trumpet perfect belt duty stay".split(' '); let translated_secret_phrase_value = ''; for ( let i=0; i < current_mnemonics.length; i++ ) { current_mnemonic = current_mnemonics[ i ]; mnemonics = current_mnemonic; options = { [LANG]: 'EN' }; let word_indexes = Bip39Utils.GetWordIndexes( mnemonics, options ); console.log("word_indexes: " + JSON.stringify(word_indexes)); // console.log("word_indexes): " + JSON.stringify(word_indexes) ); word_index = word_indexes[0]; new_mnemonic = Bip39Utils.GetMnemonicFromWordIndex( word_index, 'FR' ).normalize('NFC'); console.log(" *** word{ 'No':" + i + ", 'word_idx':" + word_index + "} in '" + current_lang + "': '" + current_mnemonic + "' translated to '" + new_lang + "': " + new_mnemonic); translated_secret_phrase_value += new_mnemonic + ' '; } translated_secret_phrase_value = translated_secret_phrase_value.trim(' '); console.log( "\ntranslated_mnemonics: " + translated_secret_phrase_value); }; // translate_ConvertLessThan12Mnemonics() // test_GuessMnemonicsLang(); // test_LessThan12Mnemonics(); // translate_ConvertLessThan12Mnemonics(); if (typeof exports === 'object') { exports.Bip39Utils = Bip39Utils } // exports of 'bip39_utils.js'