diff --git a/config.json b/config.json index 1b6c892..3dc802b 100644 --- a/config.json +++ b/config.json @@ -65,7 +65,7 @@ "signature": "tendermint/SignatureSecp256k1", "pubKey": "tendermint/PubKeySecp256k1" }, - "bip39Path": "44'/118'/0'/0/0", + "bip39Path": "m/44'/118'/0'/0/0", "keystore": { "kdf": "pbkdf2", "cipherAlg": "aes-128-ctr", @@ -130,7 +130,7 @@ "signature": "tendermint/SignatureSecp256k1", "pubKey": "tendermint/PubKeySecp256k1" }, - "bip39Path": "44'/118'/0'/0/0" + "bip39Path": "m/44'/118'/0'/0/0" }, "language": { "cn": "chinese_simplified", diff --git a/src/builder.js b/src/builder.js index d1d0f28..b0afc91 100644 --- a/src/builder.js +++ b/src/builder.js @@ -55,12 +55,16 @@ class Builder { case Config.chain.iris: { return require('./chains/iris/builder')(); } + break; case Config.chain.ethermint: { - return require('./chains/ethermint/ethermint_builder')(); + throw new Error("not implement"); + // return require('./chains/ethermint/ethermint_builder')(); } + break; case Config.chain.cosmos: { return require('./chains/cosmos/builder')(); } + break; default: { throw new Error("not correct chain"); } diff --git a/src/chains/cosmos/crypto.js b/src/chains/cosmos/crypto.js index 856d3b1..5398e6f 100644 --- a/src/chains/cosmos/crypto.js +++ b/src/chains/cosmos/crypto.js @@ -14,21 +14,32 @@ class CosmosCrypto extends Crypto { * @param language * @returns {*} */ - create(language) { - let keyPair = CosmosKeypair.create(switchToWordList(language)); + create(language, mnemonicLength = 24) { + let keyPair = CosmosKeypair.create(switchToWordList(language), mnemonicLength); if (keyPair) { return encode({ address: keyPair.address, phrase: keyPair.secret, privateKey: keyPair.privateKey, - publicKey: keyPair.publicKey + publicKey: keyPair.publicKey, }); } return keyPair; } - recover(secret, language) { - let keyPair = CosmosKeypair.recover(secret,switchToWordList(language)); + /** + * + * @param language + * @param mnemonicLength 12/15/18/21/24 + * @returns mnemonics + */ + generateMnemonic(language, mnemonicLength = 24) { + return CosmosKeypair.generateMnemonic(switchToWordList(language), mnemonicLength); + } + + recover(secret, language, path) { + path = path || Config.cosmos.bip39Path; + let keyPair = CosmosKeypair.recover(secret,switchToWordList(language), path); if (keyPair) { return encode({ address: keyPair.address, @@ -60,11 +71,24 @@ class CosmosCrypto extends Crypto { } getAddress(publicKey) { + if (Codec.Bech32.isBech32(Config.cosmos.bech32.accPub, publicKey)) { + publicKey = Codec.Bech32.fromBech32(publicKey); + } let pubKey = Codec.Hex.hexToBytes(publicKey); let address = CosmosKeypair.getAddress(pubKey); address = Codec.Bech32.toBech32(Config.cosmos.bech32.accAddr, address); return address; } + + encodePublicKey(publicKey){ + let pubkey = publicKey; + if (Codec.Bech32.isBech32(Config.cosmos.bech32.accPub, pubkey)) { + pubkey = Codec.Bech32.toBech32(Config.cosmos.bech32.accPub, Codec.Bech32.fromBech32(pubkey)); + }else if (Codec.Hex.isHex(pubkey)){ + pubkey = Codec.Bech32.toBech32(Config.cosmos.bech32.accPub, pubkey); + } + return pubkey; + } } function encode(acc){ diff --git a/src/chains/cosmos/keypair.js b/src/chains/cosmos/keypair.js index 2bdcb74..6b38542 100644 --- a/src/chains/cosmos/keypair.js +++ b/src/chains/cosmos/keypair.js @@ -13,10 +13,10 @@ const Amino = require('../base'); class CosmosKeypair { - static getPrivateKeyFromSecret(mnemonicS) { + static getPrivateKeyFromSecret(mnemonicS, path) { let seed = Bip39.mnemonicToSeed(mnemonicS); let master = Hd.ComputeMastersFromSeed(seed); - let derivedPriv = Hd.DerivePrivateKeyForPath(master.secret,master.chainCode,Config.cosmos.bip39Path); + let derivedPriv = Hd.DerivePrivateKeyForPath(master.secret,master.chainCode, path); return derivedPriv; } @@ -43,18 +43,11 @@ class CosmosKeypair { return addr.digest('hex').toUpperCase(); } - static create(language) { - //生成24位助记词 - let entropySize = 24 * 11 - 8; - let entropy = Random(entropySize / 8); - let mnemonicS = Bip39.entropyToMnemonic(entropy,language); - while (Util.hasRepeatElement(mnemonicS," ")){ - entropy = Random(entropySize / 8); - mnemonicS = Bip39.entropyToMnemonic(entropy,language); - } + static create(language, mnemonicLength){ + let mnemonicS = this.generateMnemonic(language, mnemonicLength); //生成私钥 - let secretKey = this.getPrivateKeyFromSecret(mnemonicS); + let secretKey = this.getPrivateKeyFromSecret(mnemonicS, Config.cosmos.bip39Path); //构造公钥 let pubKey = Secp256k1.publicKeyCreate(secretKey); pubKey = Amino.MarshalBinary(Config.cosmos.amino.pubKey,pubKey); @@ -63,18 +56,43 @@ class CosmosKeypair { "secret": mnemonicS, "address": this.getAddress(pubKey), "privateKey": Codec.Hex.bytesToHex(secretKey), - "publicKey": Codec.Hex.bytesToHex(pubKey) + "publicKey": Codec.Hex.bytesToHex(pubKey), }; } - static recover(mnemonic,language){ + static generateMnemonic(language, mnemonicLength) { + //默认生成24位助记词 + let entropySize = 24 * 11 - 8; + switch(mnemonicLength){ + case 12: + entropySize = 12 * 11 - 4; + break; + case 15: + entropySize = 15 * 11 - 5; + break; + case 18: + entropySize = 18 * 11 - 6; + break; + case 21: + entropySize = 21 * 11 - 7; + break; + } + let entropy = Random(entropySize / 8); + let mnemonicS = Bip39.entropyToMnemonic(entropy,language); + while (Util.hasRepeatElement(mnemonicS," ")){ + entropy = Random(entropySize / 8); + mnemonicS = Bip39.entropyToMnemonic(entropy,language); + } + return mnemonicS; + } + + static recover(mnemonic,language, path){ this.checkSeed(mnemonic,language); //生成私钥 - let secretKey = this.getPrivateKeyFromSecret(mnemonic); + let secretKey = this.getPrivateKeyFromSecret(mnemonic, path); //构造公钥 let pubKey = Secp256k1.publicKeyCreate(secretKey); pubKey = Amino.MarshalBinary(Config.cosmos.amino.pubKey,pubKey); - return { "secret": mnemonic, "address": this.getAddress(pubKey), @@ -85,8 +103,8 @@ class CosmosKeypair { static checkSeed(mnemonic,language){ const seed = mnemonic.split(" "); - if(seed.length != 12 && seed.length != 24){ - throw new Error("seed length must be equal 12 or 24"); + if(seed.length != 12 && seed.length != 15 && seed.length != 18 && seed.length != 21 && seed.length != 24){ + throw new Error("seed length must be equal 12、15、18、21、24"); } if (!Bip39.validateMnemonic(mnemonic,language)){ throw new Error("seed is invalid"); @@ -124,9 +142,20 @@ class Hd { } static DerivePrivateKeyForPath(privKeyBytes, chainCode, path) { + if (path === 'm' || path === 'M' || path === "m'" || path === "M'") { + return privKeyBytes; + } + let data = privKeyBytes; let parts = path.split("/"); - parts.forEach(function (part) { + parts.forEach(function (part, index) { + if (index === 0) { + if(!(/^[mM]{1}/.test(part))){ + throw new Error('Path must start with "m" or "M"'); + } + return; + } + let harden = part.slice(part.length-1,part.length) === "'"; if (harden) { part = part.slice(0,part.length -1); @@ -153,7 +182,9 @@ class Hd { static DerivePrivateKey(privKeyBytes, chainCode, index, harden) { let data; - let indexBuffer = Buffer.from([index]); + // let indexBuffer = Buffer.from([index]); + let indexBuffer = Buffer.allocUnsafe(4); + indexBuffer.writeUInt32BE(index, 0); if(harden){ let c = new BN(index).or(new BN(0x80000000)); indexBuffer = c.toBuffer(); @@ -163,9 +194,9 @@ class Hd { data = Buffer.concat([data,privKeyBuffer]); }else{ const pubKey =Secp256k1.publicKeyCreate(privKeyBytes); - if (index ==0){ - indexBuffer = Buffer.from([0,0,0,0]); - } + // if (index ==0){ + // indexBuffer = Buffer.from([0,0,0,0]); + // } data = pubKey } data = Buffer.concat([data,indexBuffer]); diff --git a/src/chains/iris/crypto.js b/src/chains/iris/crypto.js index 69396e9..9c1e27a 100644 --- a/src/chains/iris/crypto.js +++ b/src/chains/iris/crypto.js @@ -16,21 +16,32 @@ class IrisCrypto extends Crypto { * @param language * @returns {*} */ - create(language) { - let keyPair = IrisKeypair.create(switchToWordList(language)); + create(language, mnemonicLength = 24) { + let keyPair = IrisKeypair.create(switchToWordList(language), mnemonicLength); if (keyPair) { return encode({ address: keyPair.address, phrase: keyPair.secret, privateKey: keyPair.privateKey, - publicKey: keyPair.publicKey + publicKey: keyPair.publicKey, }); } return keyPair; } - recover(secret, language) { - let keyPair = IrisKeypair.recover(secret,switchToWordList(language)); + /** + * + * @param language + * @param mnemonicLength 12/15/18/21/24 + * @returns mnemonics + */ + generateMnemonic(language, mnemonicLength = 24) { + return IrisKeypair.generateMnemonic(switchToWordList(language), mnemonicLength); + } + + recover(secret, language, path) { + path = path || Config.iris.bip39Path; + let keyPair = IrisKeypair.recover(secret,switchToWordList(language), path); if (keyPair) { return encode({ address: keyPair.address, @@ -62,12 +73,25 @@ class IrisCrypto extends Crypto { } getAddress(publicKey) { + if (Codec.Bech32.isBech32(Config.iris.bech32.accPub, publicKey)) { + publicKey = Codec.Bech32.fromBech32(publicKey); + } let pubKey = Codec.Hex.hexToBytes(publicKey); let address = IrisKeypair.getAddress(pubKey); address = Codec.Bech32.toBech32(Config.iris.bech32.accAddr, address); return address; } + encodePublicKey(publicKey){ + let pubkey = publicKey; + if (Codec.Bech32.isBech32(Config.iris.bech32.accPub, pubkey)) { + pubkey = Codec.Bech32.toBech32(Config.iris.bech32.accPub, Codec.Bech32.fromBech32(pubkey)); + }else if (Codec.Hex.isHex(pubkey)){ + pubkey = Codec.Bech32.toBech32(Config.iris.bech32.accPub, pubkey); + } + return pubkey; + } + // @see:https://github.com/binance-chain/javascript-sdk/blob/master/src/crypto/index.js exportKeystore(privateKeyHex,password,opts = {}){ if (Utils.isEmpty(password) || password.length < 8){ diff --git a/src/chains/iris/keypair.js b/src/chains/iris/keypair.js index d276486..62a238c 100644 --- a/src/chains/iris/keypair.js +++ b/src/chains/iris/keypair.js @@ -13,10 +13,10 @@ const Amino = require('../base'); class IrisKeypair { - static getPrivateKeyFromSecret(mnemonicS) { + static getPrivateKeyFromSecret(mnemonicS, path) { let seed = Bip39.mnemonicToSeed(mnemonicS); let master = Hd.ComputeMastersFromSeed(seed); - let derivedPriv = Hd.DerivePrivateKeyForPath(master.secret,master.chainCode,Config.iris.bip39Path); + let derivedPriv = Hd.DerivePrivateKeyForPath(master.secret,master.chainCode, path); return derivedPriv; } @@ -43,18 +43,10 @@ class IrisKeypair { return addr.digest('hex').toUpperCase(); } - static create(language) { - //生成24位助记词 - let entropySize = 24 * 11 - 8; - let entropy = Random(entropySize / 8); - let mnemonicS = Bip39.entropyToMnemonic(entropy,language); - while (Util.hasRepeatElement(mnemonicS," ")){ - entropy = Random(entropySize / 8); - mnemonicS = Bip39.entropyToMnemonic(entropy,language); - } - + static create(language, mnemonicLength) { + let mnemonicS = this.generateMnemonic(language, mnemonicLength); //生成私钥 - let secretKey = this.getPrivateKeyFromSecret(mnemonicS); + let secretKey = this.getPrivateKeyFromSecret(mnemonicS, Config.iris.bip39Path); //构造公钥 let pubKey = Secp256k1.publicKeyCreate(secretKey); @@ -64,18 +56,43 @@ class IrisKeypair { "secret": mnemonicS, "address": this.getAddress(pubKey), "privateKey": Codec.Hex.bytesToHex(secretKey), - "publicKey": Codec.Hex.bytesToHex(pubKey) + "publicKey": Codec.Hex.bytesToHex(pubKey), }; } - static recover(mnemonic,language){ + static generateMnemonic(language, mnemonicLength) { + //默认生成24位助记词 + let entropySize = 24 * 11 - 8; + switch(mnemonicLength){ + case 12: + entropySize = 12 * 11 - 4; + break; + case 15: + entropySize = 15 * 11 - 5; + break; + case 18: + entropySize = 18 * 11 - 6; + break; + case 21: + entropySize = 21 * 11 - 7; + break; + } + let entropy = Random(entropySize / 8); + let mnemonicS = Bip39.entropyToMnemonic(entropy,language); + while (Util.hasRepeatElement(mnemonicS," ")){ + entropy = Random(entropySize / 8); + mnemonicS = Bip39.entropyToMnemonic(entropy,language); + } + return mnemonicS; + } + + static recover(mnemonic,language, path){ this.checkSeed(mnemonic,language); //生成私钥 - let secretKey = this.getPrivateKeyFromSecret(mnemonic); + let secretKey = this.getPrivateKeyFromSecret(mnemonic, path); //构造公钥 let pubKey = Secp256k1.publicKeyCreate(secretKey); pubKey = Amino.MarshalBinary(Config.iris.amino.pubKey,pubKey); - return { "secret": mnemonic, "address": this.getAddress(pubKey), @@ -86,8 +103,8 @@ class IrisKeypair { static checkSeed(mnemonic,language){ const seed = mnemonic.split(" "); - if(seed.length != 12 && seed.length != 24){ - throw new Error("seed length must be equal 12 or 24"); + if(seed.length != 12 && seed.length != 15 && seed.length != 18 && seed.length != 21 && seed.length != 24){ + throw new Error("seed length must be equal 12、15、18、21、24"); } if (!Bip39.validateMnemonic(mnemonic,language)){ throw new Error("seed is invalid"); @@ -130,9 +147,20 @@ class Hd { } static DerivePrivateKeyForPath(privKeyBytes, chainCode, path) { + if (path === 'm' || path === 'M' || path === "m'" || path === "M'") { + return privKeyBytes; + } + let data = privKeyBytes; let parts = path.split("/"); - parts.forEach(function (part) { + parts.forEach(function (part, index) { + if (index === 0) { + if(!(/^[mM]{1}/.test(part))){ + throw new Error('Path must start with "m" or "M"'); + } + return; + } + let harden = part.slice(part.length-1,part.length) === "'"; if (harden) { part = part.slice(0,part.length -1); @@ -159,7 +187,9 @@ class Hd { static DerivePrivateKey(privKeyBytes, chainCode, index, harden) { let data; - let indexBuffer = Buffer.from([index]); + // let indexBuffer = Buffer.from([index]); + let indexBuffer = Buffer.allocUnsafe(4); + indexBuffer.writeUInt32BE(index, 0); if(harden){ let c = new BN(index).or(new BN(0x80000000)); indexBuffer = c.toBuffer(); @@ -168,10 +198,10 @@ class Hd { data = Buffer.from([0]); data = Buffer.concat([data,privKeyBuffer]); }else{ - const pubKey =Secp256k1.publicKeyCreate(privKeyBytes); - if (index ==0){ - indexBuffer = Buffer.from([0,0,0,0]); - } + const pubKey = Secp256k1.publicKeyCreate(privKeyBytes); + // if (index ==0){ + // indexBuffer = Buffer.from([0,0,0,0]); + // } data = pubKey } data = Buffer.concat([data,indexBuffer]); diff --git a/src/crypto.js b/src/crypto.js index c34bc6b..663de09 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -14,14 +14,26 @@ class Crypto { throw new Error("not implement"); } + /** + * 生成指定数量的助记词 + * + * @param language + * @param mnemonicLength 12/15/18/21/24 + * @returns mnemonics + */ + generateMnemonic(language, mnemonicLength) { + throw new Error("not implement"); + } + /** * 通过助记词恢复账户 * * @param (seedphrase:string} 助记词 * @param (language:string} 字符集 (constants.Language.CH_S | constants.Language.JP |constants.Language.SP |constants.Language.EN |) + * @param (path:string} 推导path * @returns {{address, phrase, privateKey, publicKey}} */ - recover(seedphrase, language) { + recover(seedphrase, language, path) { throw new Error("not implement"); } @@ -65,6 +77,15 @@ class Crypto { throw new Error("not implement"); } + /** + * encode PublicKey + * + * @param (publicKey:string} + */ + encodePublicKey(publicKey){ + throw new Error("not implement"); + } + /** * export keystore data from private and password * @param privateKeyHex user's private key @@ -94,12 +115,16 @@ class Crypto { case Config.chain.iris: { return require('./chains/iris/crypto')(); } + break; case Config.chain.ethermint: { - return require('./chains/ethermint/ethermint_crypto')(); + throw new Error("not implement"); + // return require('./chains/ethermint/ethermint_crypto')(); } + break; case Config.chain.cosmos: { return require('./chains/cosmos/crypto')(); } + break; default: { throw new Error("not correct chain"); } diff --git a/test/test_account.js b/test/test_account.js index 837ee2c..904db3e 100644 --- a/test/test_account.js +++ b/test/test_account.js @@ -26,6 +26,15 @@ describe('account', function () { // fs.close() }); + it('test generate Mnemonic', function () { + let crypto = Irisnet.getCrypto(chainName, 'testnet'); + [12, 15, 18, 21, 24].forEach((item)=>{ + let mnemonics = crypto.generateMnemonic(Irisnet.config.language.en, item); + console.log('==>mnemonics length:',mnemonics.split(' ').length); + console.log('mnemonics:',mnemonics); + }); + }); + it('test import', function () { let crypto = Irisnet.getCrypto(chainName, 'testnet'); let srcAccount = crypto.create(Irisnet.config.language.en); @@ -86,6 +95,15 @@ describe('account', function () { // fs.close() }); + it('test generate Mnemonic', function () { + let crypto = Irisnet.getCrypto(chainName); + [12, 15, 18, 21, 24].forEach((item)=>{ + let mnemonics = crypto.generateMnemonic(Irisnet.config.language.en, item); + console.log('==>mnemonics length:',mnemonics.split(' ').length); + console.log('mnemonics:',mnemonics); + }); + }); + it('test import', function () { let crypto = Irisnet.getCrypto(chainName); let srcAccount = crypto.create(Irisnet.config.language.en);