import bech32 from "bech32" ; import * as bip32 from "bip32" ; import * as bip39 from "bip39" ; import cryp from "crypto-browserify" ; import { ec as EC , curve } from "elliptic" ; import is from "is_js" ; import csprng from "secure-random" ; import ecc from "tiny-secp256k1" ; import uuid from "uuid" ; import { ab2hexstring , sha3 , sha256 , sha256ripemd160 } from "../utils" ; export interface KeyStore { version : number ; id : string ; crypto : { ciphertext : string ; cipherparams : { iv : string ; } ; cipher : string ; kdf : string ; kdfparams : { dklen : number ; salt : string ; c : number ; prf : string ; } ; /** Must use sha3 according to web3 secret storage spec. */ mac : string ; } ; } const PRIVKEY_LEN = 32 ; const MNEMONIC_LEN = 256 ; const DECODED_ADDRESS_LEN = 20 ; const CURVE = "secp256k1" ; const HDPATH = "44'/714'/0'/0/" ; const ec = new EC ( CURVE ) ; category param { string } value export const decodeAddress = ( value ) : Buffer => { const decodeAddress = bech32 . decode ( value ) ; return Buffer . from ( bech32 . fromWords ( decodeAddress . words ) ) ; } ; category param { string } address param { string } hrp return { boolean } export const checkAddress = ( address , hrp ) : boolean => { try { if ( ! address . startsWith ( hrp ) ) { return false ; } const decodedAddress = bech32 . decode ( address ) ; const decodedAddressLength = decodeAddress ( address ) . length ; if ( decodedAddressLength === DECODED_ADDRESS_LEN && decodedAddress . prefix === hrp ) { return true ; } return false ; } catch ( err ) { return false ; } } ; category param { string } value param { * } prefix param { * } type export const encodeAddress = ( value , prefix = "tbnb" , type = "hex" ) : string => { let words ; if ( Buffer . isBuffer ( value ) ) { words = bech32 . toWords ( Buffer . from ( value ) ) ; } else { words = bech32 . toWords ( Buffer . from ( value , type ) ) ; } return bech32 . encode ( prefix , words ) ; } ; category param { number } len returns { string } export const generatePrivateKey = ( len = PRIVKEY_LEN ) : string => ab2hexstring ( csprng ( len ) ) ; category param { number } length returns { ArrayBuffer } export const generateRandomArray = ( length ) : ArrayBuffer => csprng ( length ) ; category param { string } publicKey return { Elliptic . PublicKey } export const getPublicKey = ( publicKey ) => { const keyPair = ec . keyFromPublic ( publicKey , "hex" ) ; return keyPair . getPublic ( ) ; } ; category param { string } privateKeyHex return { string } export const getPublicKeyFromPrivateKey = ( privateKeyHex ) : string => { if ( ! privateKeyHex || privateKeyHex . length !== PRIVKEY_LEN * 2 ) { throw new Error ( "invalid privateKey" ) ; } const curve = new EC ( CURVE ) ; const keypair = curve . keyFromPrivate ( privateKeyHex , "hex" ) ; const unencodedPubKey = keypair . getPublic ( ) . encode ( "hex" , false ) ; return unencodedPubKey ; } ; category param { Buffer } privateKey return { Elliptic . PublicKey } export const generatePubKey = ( privateKey ) : curve . base . BasePoint => { const curve = new EC ( CURVE ) ; const keypair = curve . keyFromPrivate ( privateKey ) ; return keypair . getPublic ( ) ; } ; category param { string } publicKeyHex param { string } prefix export const getAddressFromPublicKey = ( publicKeyHex , prefix ? ) => { const pubKey = ec . keyFromPublic ( publicKeyHex , "hex" ) ; const pubPoint = pubKey . getPublic ( ) ; const compressed = pubPoint . encodeCompressed ( ) ; const hexed = ab2hexstring ( compressed ) ; const hash = sha256ripemd160 ( hexed ) ; const address = encodeAddress ( hash , prefix ) ; return address ; } ; category param { string } privateKeyHex param { string } prefix export const getAddressFromPrivateKey = ( privateKeyHex , prefix ? ) : string => { return getAddressFromPublicKey ( getPublicKeyFromPrivateKey ( privateKeyHex ) , prefix ) ; } ; category param { string } signBytesHex param { string | Buffer } privateKey return { Buffer } export const generateSignature = ( signBytesHex , privateKey ) : Buffer => { const msgHash = sha256 ( signBytesHex ) ; const msgHashHex = Buffer . from ( msgHash , "hex" ) ; const signature = ecc . sign ( msgHashHex , typeof privateKey === "string" ? Buffer . from ( privateKey , "hex" ) : privateKey ) ; return signature ; } ; category param { string } sigHex param { string } signBytesHex param { string } publicKeyHex return { boolean } export const verifySignature = ( sigHex , signBytesHex , publicKeyHex ) : boolean => { const publicKey = Buffer . from ( publicKeyHex , "hex" ) ; if ( ! ecc . isPoint ( publicKey ) ) throw new Error ( "Invalid public key provided" ) ; const msgHash = sha256 ( signBytesHex ) ; const msgHashHex = Buffer . from ( msgHash , "hex" ) ; return ecc . verify ( msgHashHex , publicKey , Buffer . from ( sigHex , "hex" ) ) ; } ; category param { string } privateKeyHex param { string } password return { object } export const generateKeyStore = ( privateKeyHex , password ) : KeyStore => { const salt = cryp . randomBytes ( 32 ) ; const iv = cryp . randomBytes ( 16 ) ; const cipherAlg = "aes-256-ctr" ; const kdf = "pbkdf2" ; const kdfparams = { dklen : 32 , salt : salt . toString ( "hex" ) , c : 262144 , prf : "hmac-sha256" , } ; const derivedKey = cryp . pbkdf2Sync ( Buffer . from ( password ) , salt , kdfparams . c , kdfparams . dklen , "sha256" ) ; const cipher = cryp . createCipheriv ( cipherAlg , derivedKey . slice ( 0 , 32 ) , iv ) ; if ( ! cipher ) { throw new Error ( "Unsupported cipher" ) ; } const ciphertext = Buffer . concat ( [ cipher . update ( Buffer . from ( privateKeyHex , "hex" ) ) , cipher . final ( ) , ] ) ; const bufferValue = Buffer . concat ( [ derivedKey . slice ( 16 , 32 ) , ciphertext ] ) ; return { version : 1 , id : uuid . v4 ( { random : cryp . randomBytes ( 16 ) , } ) , crypto : { ciphertext : ciphertext . toString ( "hex" ) , cipherparams : { iv : iv . toString ( "hex" ) , } , cipher : cipherAlg , kdf , kdfparams , mac : sha3 ( bufferValue . toString ( "hex" ) ) , } , } ; } ; category param { string } keystore param { string } password export const getPrivateKeyFromKeyStore = ( keystore , password ) : string => { if ( ! is . string ( password ) ) { throw new Error ( "No password given." ) ; } const json = is . object ( keystore ) ? keystore : JSON . parse ( keystore ) ; const kdfparams = json . crypto . kdfparams ; if ( kdfparams . prf !== "hmac-sha256" ) { throw new Error ( "Unsupported parameters to PBKDF2" ) ; } const derivedKey = cryp . pbkdf2Sync ( Buffer . from ( password ) , Buffer . from ( kdfparams . salt , "hex" ) , kdfparams . c , kdfparams . dklen , "sha256" ) ; const ciphertext = Buffer . from ( json . crypto . ciphertext , "hex" ) ; const bufferValue = Buffer . concat ( [ derivedKey . slice ( 16 , 32 ) , ciphertext ] ) ; const mac = sha3 ( bufferValue . toString ( "hex" ) ) ; if ( mac !== json . crypto . mac ) { const macLegacy = sha256 ( bufferValue . toString ( "hex" ) ) ; if ( macLegacy !== json . crypto . mac ) { throw new Error ( "Keystore mac check failed (sha3 & sha256) - wrong password?" ) ; } } const decipher = cryp . createDecipheriv ( json . crypto . cipher , derivedKey . slice ( 0 , 32 ) , Buffer . from ( json . crypto . cipherparams . iv , "hex" ) ) ; const privateKey = Buffer . concat ( [ decipher . update ( ciphertext ) , decipher . final ( ) , ] ) . toString ( "hex" ) ; return privateKey ; } ; category export const generateMnemonic = ( ) : string => bip39 . generateMnemonic ( MNEMONIC_LEN ) ; ignore param { string } mnemonic return { bool } export const validateMnemonic = bip39 . validateMnemonic ; category param { string } mnemonic param { Boolean } derive param { number } index param { string } password return { string } export const getPrivateKeyFromMnemonic = ( mnemonic , derive = true , index = 0 , password = "" ) : string => { if ( ! bip39 . validateMnemonic ( mnemonic , bip39 . wordlists . english ) && ! bip39 . validateMnemonic ( mnemonic , bip39 . wordlists . chinese_simplified ) && ! bip39 . validateMnemonic ( mnemonic , bip39 . wordlists . chinese_traditional ) && ! bip39 . validateMnemonic ( mnemonic , bip39 . wordlists . korean ) && ! bip39 . validateMnemonic ( mnemonic , bip39 . wordlists . french ) && ! bip39 . validateMnemonic ( mnemonic , bip39 . wordlists . italian ) && ! bip39 . validateMnemonic ( mnemonic , bip39 . wordlists . spanish ) && ! bip39 . validateMnemonic ( mnemonic , bip39 . wordlists . japanese ) ) { throw new Error ( "wrong mnemonic format" ) ; } const seed = bip39 . mnemonicToSeedSync ( mnemonic , password ) ; if ( derive ) { const master = bip32 . fromSeed ( seed ) ; const child = master . derivePath ( HDPATH + index ) ; if ( ! child . privateKey ) { throw new Error ( "child does not have a privateKey" ) ; } return child . privateKey . toString ( "hex" ) ; } return seed . toString ( "hex" ) ; } ;