From cbd46d62a7f531b0eb7ced83781f5e0fb22cc24c Mon Sep 17 00:00:00 2001 From: Luk Lu Date: Wed, 26 Feb 2020 09:35:22 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BA=E9=81=BF=E5=85=8D=20nodejs=20?= =?UTF-8?q?=E7=9A=84=20crypto=20=E7=9A=84=20sign=20=E4=BA=A7=E7=94=9F?= =?UTF-8?q?=E7=9A=84=E7=AD=BE=E5=90=8D=E4=B8=8D=E5=9B=BA=E5=AE=9A=EF=BC=8C?= =?UTF-8?q?=E6=8D=A2=E7=94=A8=20eccrypto=20=E7=9A=84=20sign=20=E5=81=9A?= =?UTF-8?q?=E4=B8=BA=E9=BB=98=E8=AE=A4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.js | 83 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 24 deletions(-) diff --git a/index.js b/index.js index 72020b3..212f18e 100644 --- a/index.js +++ b/index.js @@ -66,14 +66,14 @@ module.exports = { return false } , - isSecword(secword){ + isSecword(secword){ // 注意 not all 12 words combinations are valid for both bitcore and bip39. Must be generated automatically. 另外,实际上bitcore和bip39对12, 15, 18, ... 长度的合法助记词都返回 true。 //// for bitcore-mnemonic. 注意,bitcore-mnemonic 对少于12词的会抛出异常,很蠢。 // if (typeof secword==='string' && 12===secword.split(/ +/).length) // return BitcoreMnemonic.isValid(secword) // else // return false - //// for bip39. 注意,bip39对当前defaultWordlist之外其他语言的合法 mnemonic 也返回 false。所以不能直接 bip39.validateMnemonic(secword) + //// for bip39. 注意,bip39对当前defaultWordlist之外其他语言的合法 mnemonic 也返回 false,这一点不如 bitcore-mnemonic. 所以不能直接 bip39.validateMnemonic(secword) if (typeof secword==='string' && 12===secword.split(/ +/).length) return true else @@ -94,7 +94,7 @@ module.exports = { } , isSignature(signature){ - return /^[a-fA-F0-9]{128,144}$/.test(signature) + return /^[a-fA-F0-9]{128,144}$/.test(signature) && (signature.length % 2 === 0) // 128 for nacl, 140/142/144 for crypto and eccrypto in der format. } , async encrypt(data, {keytype, key, input, output, cipher}={}){ @@ -145,14 +145,22 @@ module.exports = { } , async sign(data, seckey, option={}) { // data can be string or buffer or object, results are the same - if (this.isHashable(data) && this.isSeckey(seckey) && seckey.length===64) { // 纯 crypto - let seckeyPEM = await new keyman.Key('oct', this.hex2buf(seckey), {namedCurve:'P-256K'}).export('pem') - let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER - let outputEncoding=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT) - let signer=crypto.createSign(hasher) - signer.update(this.hash(data, option)).end() - let signature = signer.sign(seckeyPEM, 'hex') - return signature // 发现同样的输入,每次调用会生成不同的 signature, 且长度不定(140~144 hex) 但都可以通过 verify。有一次我竟然徒手修改出一个新签名也通过验证。 + if (this.isHashable(data) && this.isSeckey(seckey) && seckey.length===64) { + if (option.tool==='crypto') { // 纯 crypto + let seckeyPEM = await new keyman.Key('oct', this.hex2buf(seckey), {namedCurve:'P-256K'}).export('pem') // 私钥导出的der格式为144字节。 + let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER + let signer=crypto.createSign(hasher) + signer.update(this.hash(data, option)).end() + let signature = signer.sign(seckeyPEM, 'hex') + return signature // 发现同样的输入,每次调用会生成不同的 signature, 且长度不定(140,142,144 hex) 但都可以通过 verify。 + }else if (option.tool==='nacl') { + // 这样不行,无法和verify共享一套公私钥。 + // let naclSeckey = this.buf2hex(nacl.sign.keyPair.fromSeed(seckey).seckey) + // return await this.sign(data, naclSeckey, option) + }else { // default to eccrypto,因为它对同一组data,seckey生成的签名是固定的,观察到hex长度为140或142,是der格式。 + let signature = await eccrypto.sign(Buffer.from(seckey,'hex'), crypto.createHash('sha256').update(data).digest()) + return signature.toString('hex') + } } if (this.isHashable(data) && this.isSeckey(seckey) && seckey.length===128) { // 使用nacl的签名算法。注意,nacl.sign需要的seckey是64字节=128字符。 option.output='buf' // 哈希必须输出为 buffer @@ -164,15 +172,29 @@ module.exports = { } , async verify (data, signature, pubkey, option={}) { // data could be anything, but converts to string or remains be Buffer/TypedArray/DataView - if (this.isHashable(data) && this.isSignature(signature) && this.isPubkey(pubkey) && signature.length>=140){ // 纯 crypto - let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(pubkey), {namedCurve:'P-256K'}).export('pem') - let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER - let verifier = crypto.createVerify(hasher) - verifier.update(this.hash(data, option)).end() // end() 在 nodejs 12 里返回verifier自身,但在浏览器里返回 undefined,因此不能串联运行。 - let verified = verifier.verify(pubkeyPEM, signature, 'hex') - return verified + if (this.isHashable(data) && this.isSignature(signature) && this.isPubkey(pubkey) && signature.length>=140){ + if (option.tool==='crypto') { // 纯 crypto + let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(pubkey), {namedCurve:'P-256K'}).export('pem') // 公钥导出的der格式为88字节。经测试,同一对压缩和非压缩公钥得出的结果一模一样。 + let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER + let verifier = crypto.createVerify(hasher) + verifier.update(this.hash(data, option)).end() // end() 在 nodejs 12 里返回verifier自身,但在浏览器里返回 undefined,因此不能串联运行。 + let verified = verifier.verify(pubkeyPEM, signature, 'hex') // 如果给signature添加1位hex,crypto 的 verify结果也是true! 估计因为一位hex不被转成字节。 + return verified + }else if ('nacl'===option.tool) { + // 这样不行,无法和sign共享一套公私钥 + // let naclPubkey = nacl.sign.keyPair.fromSeed() + }else { // 默认使用 eccrypto + try { + await eccrypto.verify(Buffer.from(pubkey, 'hex'), + crypto.createHash('sha256').update(data).digest(), + Buffer.from(signature, 'hex')) // 如果给signature添加1位hex,eccrypto 的 verify结果也是true! 估计因为一位hex不被转成字节。 + return true + }catch(exception){ + return false + } + } } - if (signature.length===128){ // nacl + if (this.isHashable(data) && this.isSignature(signature) && this.isPubkey(pubkey) && signature.length===128){ // nacl option.output='buf' // 哈希必须输出为 buffer let bufHash=this.hash(data, option) let bufSignature = Buffer.from(signature, 'hex') @@ -180,7 +202,7 @@ module.exports = { let verified = nacl.sign.detached.verify(bufHash, bufSignature, bufPubkey) return verified } - return null + return false } , pass2keypair(pass, option){ // 如果使用其他机制,例如密码、随机数,不使用secword,也可生成keypair @@ -188,7 +210,7 @@ module.exports = { option=option||{} option.hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER var hashBuf = crypto.createHash(option.hasher).update(pass).digest() - var keypair = nacl.sign.keyPair.fromSeed(hashBuf) + var keypair = nacl.sign.keyPair.fromSeed(hashBuf) // nacl的seed要求是32字节 return { hash: hashBuf.toString('hex'), pubkey: Buffer.from(keypair.publicKey).toString('hex'), // 测试过 不能直接keypair.publicKey.toString('hex'),不是buffer类型 @@ -198,6 +220,14 @@ module.exports = { return null } , + entropy2secword(entropy){ // entropy could be hex string or buffer. Byte length could be of 16, 20, 24, 28, ... which outputs mnemonic of length 12, 15, 18, 21, ... + return bip39.entropyToMnemonic(entropy) // results are the same for the same entropy. + } + , + secword2entropy(secword){ // secword could be of length 12, 15, 18, ... which outputs hex of length 32, 40, ... + return bip39.mnemonicToEntropy(secword) // results are the same for the same secword. + } + , secword2keypair(secword, option){ // option.coin 币种; // option.passphase 密码,默认为空; @@ -256,6 +286,7 @@ module.exports = { option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN let kp=this.secword2keypair(secword, option) if (kp) { + kp.secword=secword kp.address=this.seckey2address(kp.seckey, option) return kp } @@ -448,7 +479,7 @@ module.exports = { return bip39.generateMnemonic() } , - randomSeckey(option){ // todo: 使用 crypto.randomBytes(size) + randomSeckey(option){ // 跳过 secword 直接产生随机密钥 option=option||{} option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN if (option.tool==='nacl'){ @@ -458,8 +489,7 @@ module.exports = { } } , - randomKeypair(option){ - option=option||{} + randomKeypair(option={}){ option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN let kp if (option.tool==='nacl'){ @@ -482,6 +512,11 @@ module.exports = { } } , + randomAccount(option={}){ + let secword=this.randomSecword(option.lang) + return this.secword2account(secword, option) + } + , randomString (length=6, alphabet) { // 长度为 length,字母表为 alphabet 的随机字符串 alphabet = alphabet||"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$%^&*@" var text = ''