import { Bip39 , CELO_DERIVATION_PATH_BASE , MnemonicLanguages , MnemonicStrength , RandomNumberGenerator , } from '@celo/base/lib/account' ; import { normalizeAccents } from '@celo/base/lib/string' ; import * as bip32 from 'bip32' ; import * as bip39 from 'bip39' ; import { keccak256 } from 'ethereumjs-util' ; import randomBytes from 'randombytes' ; import { privateKeyToAddress } from './address' ; import { levenshteinDistance } from './levenshtein' ; export { Bip39 , CELO_DERIVATION_PATH_BASE , MnemonicLanguages , MnemonicStrength , RandomNumberGenerator , } from '@celo/base/lib/account' ; function defaultGenerateMnemonic ( strength ? , rng ? , wordlist ? ) { return new Promise ( ( resolve , reject ) => { strength = strength || 128 ; rng = rng || randomBytes ; rng ( strength / 8 , ( error , randomBytesBuffer ) => { if ( error ) { reject ( error ) ; } else { resolve ( bip39 . entropyToMnemonic ( randomBytesBuffer . toString ( 'hex' ) , wordlist ) ) ; } } ) ; } ) ; } const bip39Wrapper = { mnemonicToSeedSync : bip39 . mnemonicToSeedSync , mnemonicToSeed : bip39 . mnemonicToSeed , generateMnemonic : defaultGenerateMnemonic , validateMnemonic : bip39 . validateMnemonic } ; export async function generateMnemonic ( strength = MnemonicStrength . s256_24words , language ? , bip39ToUse = bip39Wrapper ) { return bip39ToUse . generateMnemonic ( strength , undefined , getWordList ( language ) ) ; } export function validateMnemonic ( mnemonic , bip39ToUse = bip39Wrapper , language ? ) { if ( language !== undefined ) { return bip39ToUse . validateMnemonic ( mnemonic , getWordList ( language ) ) ; } const languages = getAllLanguages ( ) ; for ( const guessedLanguage of languages ) { if ( bip39ToUse . validateMnemonic ( mnemonic , getWordList ( guessedLanguage ) ) ) { return true ; } } return false ; } remarks export function invalidMnemonicWords ( mnemonic , language ? ) { const words = splitMnemonic ( mnemonic ) ; const detectedLanguage = language ?? detectMnemonicLanguage ( words ) ; if ( detectedLanguage === undefined ) { return undefined ; } const wordSet = new Set ( getWordList ( detectedLanguage ) ) ; return words . filter ( ( word ) => ! wordSet . has ( word ) ) ; } remarks export function normalizeMnemonic ( mnemonic , language ? ) { const words = splitMnemonic ( mnemonic ) ; const lowered = words . map ( ( word ) => word . toLowerCase ( ) ) ; const detectedLanguage = language ?? detectMnemonicLanguage ( lowered ) ; if ( detectedLanguage === undefined ) { return joinMnemonic ( lowered , detectedLanguage ) ; } return joinMnemonic ( formatNonAccentedWords ( lowered , detectedLanguage ) , detectedLanguage ) ; } remarks function formatNonAccentedWords ( words , language ) { if ( isLatinBasedLanguage ( language ) ) { const wordList = getWordList ( language ) ; const normalizedWordMap = new Map ( wordList . map ( ( word ) => [ normalizeAccents ( word ) , word ] ) ) ; return words . map ( ( word ) => normalizedWordMap . get ( normalizeAccents ( word ) ) ?? word ) ; } return words ; } function isLatinBasedLanguage ( language ) { switch ( language ) { case MnemonicLanguages . english : case MnemonicLanguages . french : case MnemonicLanguages . italian : case MnemonicLanguages . spanish : case MnemonicLanguages . portuguese : return true ; case MnemonicLanguages . chinese_simplified : case MnemonicLanguages . chinese_traditional : case MnemonicLanguages . japanese : case MnemonicLanguages . korean : return false ; } } deprecated export function formatNonAccentedCharacters ( mnemonic ) { return normalizeMnemonic ( mnemonic ) ; } function getWordList ( language ? ) { 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 ; } } export function getAllLanguages ( ) { return [ MnemonicLanguages . chinese_simplified , MnemonicLanguages . chinese_traditional , MnemonicLanguages . english , MnemonicLanguages . french , MnemonicLanguages . italian , MnemonicLanguages . japanese , MnemonicLanguages . korean , MnemonicLanguages . spanish , MnemonicLanguages . portuguese , ] ; } export function mnemonicLengthFromStrength ( strength ) { switch ( strength ) { case MnemonicStrength . s128_12words : return 12 ; case MnemonicStrength . s256_24words : return 24 ; } } /**
 * Splits a mnemonic phrase into words, handling extra whitespace anywhere in the phrase.
 */ function splitMnemonic ( mnemonic ) { return [ ... mnemonic . trim ( ) . split ( /\s+/ ) ] ; } /**
 * Joins a list of words into a mnemonic phrase. Inverse of splitMnemonic.
 */ function joinMnemonic ( words , language ) { return words . join ( language === MnemonicLanguages . japanese ? '\u3000' : ' ' ) ; } remarks export function detectMnemonicLanguage ( words , candidates ? ) { const scores = ( candidates ?? getAllLanguages ( ) ) . map ( ( candidate ) => { const wordSet = new Set ( getWordList ( candidate ) ) ; const score = words . reduce ( ( count , word ) => ( wordSet . has ( word ) ? count + 1 : count ) , 0 ) ; return [ candidate , score ] ; } ) ; const [ winners , highscore ] = scores . reduce ( ( [ leaders , leadingScore ] , [ candidate , score ] ) => { if ( score > leadingScore ) { return [ [ candidate ] , score ] ; } else if ( score === leadingScore ) { return [ [ ... leaders , candidate ] , leadingScore ] ; } return [ leaders , leadingScore ] ; } , [ [ ] , 0 ] as [ MnemonicLanguages [ ] , number ] ) ; if ( winners . length !== 1 || highscore < 1 ) { return undefined ; } return winners [ 0 ] ; } remarks privateRemarks export function * suggestMnemonicCorrections ( mnemonic , language ? , strength ? ) { const words = splitMnemonic ( mnemonic ) ; const expectedLength = strength && mnemonicLengthFromStrength ( strength ) ; if ( ( expectedLength && words . length !== expectedLength ) || words . length % 3 !== 0 ) { return ; } const lang = language ?? detectMnemonicLanguage ( words ) ; if ( lang === undefined ) { return ; } for ( const suggestion of suggestUnvalidatedCorrections ( words , lang ) ) { const phrase = joinMnemonic ( suggestion , lang ) ; if ( validateMnemonic ( phrase , undefined , lang ) ) { yield phrase ; } } } type SuggestionsByDistance = Map < number , string [ ] > ; function * suggestUnvalidatedCorrections ( words , language ) { const spotSuggestions = words . map ( ( word ) => wordSuggestions ( word , language ) ) ; function * combineSuggestions ( suggestionsLists , weight ) { if ( suggestionsLists . length < 1 || weight < 0 ) { throw Error ( 'programming error: suggestions map must have at least one entry' ) ; } const suggestions = suggestionsLists [ 0 ] ; if ( suggestionsLists . length === 1 ) { yield * suggestions . get ( weight ) ?. map ( ( suggestion ) : string [ ] => [ suggestion ] ) ?? [ ] ; return ; } const remaining = [ ... suggestionsLists . slice ( 1 ) ] ; for ( const distance of [ ... suggestions . keys ( ) ] . sort ( ) ) { if ( distance > weight ) { break ; } for ( const list of combineSuggestions ( remaining , weight - distance ) ) { for ( const suggestion of suggestions . get ( distance ) ?? [ ] ) { yield [ suggestion , ... list ] ; } } } } for ( let weight = 0 ; weight < 1000 ; weight ++ ) { for ( const suggestedWords of combineSuggestions ( spotSuggestions , weight ) ) { yield suggestedWords ; } } } function wordSuggestions ( typo , language ) { return getWordList ( language ) . map ( ( word ) => ( { distance : levenshteinDistance ( typo , word ) , word } ) ) . reduce ( ( map , { distance , word } ) => { const list = map . get ( distance ) ; if ( list !== undefined ) { list . push ( word ) ; } else { map . set ( distance , [ word ] ) ; } return map ; } , new Map < number , string [ ] > ( ) ) ; } export async function generateKeys ( mnemonic , password ? , changeIndex = 0 , addressIndex = 0 , bip39ToUse = bip39Wrapper , derivationPath = CELO_DERIVATION_PATH_BASE ) { const seed = await generateSeed ( mnemonic , password , bip39ToUse ) ; return generateKeysFromSeed ( seed , changeIndex , addressIndex , derivationPath ) ; } export function generateDeterministicInviteCode ( recipientPhoneHash , recipientPepper , addressIndex = 0 , changeIndex = 0 , derivationPath = CELO_DERIVATION_PATH_BASE ) { const seed = keccak256 ( recipientPhoneHash + recipientPepper ) as Buffer ; return generateKeysFromSeed ( seed , changeIndex , addressIndex , derivationPath ) ; } export async function generateSeed ( mnemonic , password ? , bip39ToUse = bip39Wrapper , keyByteLength = 64 ) { let seed = await bip39ToUse . mnemonicToSeed ( mnemonic , password ) ; if ( keyByteLength > 0 && seed . byteLength > keyByteLength ) { const bufAux = Buffer . allocUnsafe ( keyByteLength ) ; seed . copy ( bufAux , 0 , 0 , keyByteLength ) ; seed = bufAux ; } return seed ; } export function generateKeysFromSeed ( seed , changeIndex = 0 , addressIndex = 0 , derivationPath = CELO_DERIVATION_PATH_BASE ) { const node = bip32 . fromSeed ( seed ) ; const newNode = node . derivePath ( `${ derivationPath ? `${ derivationPath }/` : '' }${ changeIndex }/${ addressIndex }` ) ; if ( ! newNode . privateKey ) { throw new Error ( 'utils-accounts@generateKeys: invalid node to derivate' ) ; } return { privateKey : newNode . privateKey . toString ( 'hex' ) , publicKey : newNode . publicKey . toString ( 'hex' ) , address : privateKeyToAddress ( newNode . privateKey . toString ( 'hex' ) ) } ; } export const AccountUtils = { detectMnemonicLanguage , generateMnemonic , normalizeMnemonic , validateMnemonic , invalidMnemonicWords , suggestMnemonicCorrections , generateKeys , generateSeed , generateKeysFromSeed } ;