用eccrypto加解密,用crypto签名。互相转换压缩和非压缩公钥

This commit is contained in:
陆柯 2020-02-20 13:36:44 +08:00
parent c494dd51de
commit 09d831af00
6 changed files with 726 additions and 79 deletions

BIN
dump.rdb Normal file

Binary file not shown.

273
eccrypt.browser.js Normal file
View File

@ -0,0 +1,273 @@
"use strict";
var EC = require("elliptic").ec;
var ec = new EC("secp256k1");
var browserCrypto = global.crypto || global.msCrypto || {};
var subtle = browserCrypto.subtle || browserCrypto.webkitSubtle;
var nodeCrypto = require('crypto');
function ab2hex(buffer) { // buffer is an ArrayBuffer
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
}
function hex2ab(hex){
return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16)
})) // 注意,arraybuffer没有 toString('hex')功能
}
const EC_GROUP_ORDER = hex2ab('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
const ZERO32 = new Uint8Array(32);
function assert(condition, message) {
if (!condition) {
throw new Error(message || "Assertion failed");
}
}
function isScalar (x) {
return x instanceof Uint8Array && x.length === 32;
}
function isValidPrivateKey(privateKey) {
if (!isScalar(privateKey))
{
return false;
}
return true // 浏览器里不支持 Buffer即使转成 Uint8Array 也没有 compare 方法,因此为了兼容浏览器,直接返回 true.
return privateKey.compare(ZERO32) > 0 && // > 0
privateKey.compare(EC_GROUP_ORDER) < 0; // < G
}
// Compare two buffers in constant time to prevent timing attacks.
function equalConstTime(b1, b2) {
if (b1.length !== b2.length) {
return false;
}
var res = 0;
for (var i = 0; i < b1.length; i++) {
res |= b1[i] ^ b2[i]; // jshint ignore:line
}
return res === 0;
}
/* This must check if we're in the browser or
not, since the functions are different and does
not convert using browserify */
function randomBytes(size) {
var arr = new Uint8Array(size);
if (typeof browserCrypto.getRandomValues === 'undefined') {
return new Uint8Array(nodeCrypto.randomBytes(size));
} else {
browserCrypto.getRandomValues(arr);
}
return new Uint8Array(arr);
}
function sha512(msg) {
return new Promise(function(resolve) {
var hash = nodeCrypto.createHash('sha512');
var result = hash.update(msg).digest();
resolve(new Uint8Array(result));
});
}
function getAes(op) {
return function(iv, key, data) {
return new Promise(function(resolve) {
if (subtle) {
var importAlgorithm = {name: "AES-CBC"};
var keyp = subtle.importKey("raw", key, importAlgorithm, false, [op]);
return keyp.then(function(cryptoKey) {
var encAlgorithm = {name: "AES-CBC", iv: iv};
return subtle[op](encAlgorithm, cryptoKey, data);
}).then(function(result) {
resolve(new Uint8Array(result));
});
} else {
if (op === 'encrypt') {
var cipher = nodeCrypto.createCipheriv('aes-256-cbc', key, iv);
cipher.update(data);
resolve(cipher.final());
}
else if (op === 'decrypt') {
var decipher = nodeCrypto.createDecipheriv('aes-256-cbc', key, iv);
decipher.update(data);
resolve(decipher.final());
}
}
});
};
}
var aesCbcEncrypt = getAes("encrypt");
var aesCbcDecrypt = getAes("decrypt");
function hmacSha256Sign(key, msg) {
return new Promise(function(resolve) {
var hmac = nodeCrypto.createHmac('sha256', new Uint8Array(key));
hmac.update(msg);
var result = hmac.digest();
resolve(result);
});
}
function hmacSha256Verify(key, msg, sig) {
return new Promise(function(resolve) {
var hmac = nodeCrypto.createHmac('sha256', new Uint8Array(key));
hmac.update(msg);
var expectedSig = hmac.digest();
resolve(equalConstTime(expectedSig, sig));
});
}
/**
* Generate a new valid private key. Will use the window.crypto or window.msCrypto as source
* depending on your browser.
* @return {Buffer} A 32-byte private key.
* @function
*/
exports.generatePrivate = function () {
var privateKey = randomBytes(32);
while (!isValidPrivateKey(privateKey)) {
privateKey = randomBytes(32);
}
return privateKey;
};
var getPublic = exports.getPublic = function(privateKey) {
// This function has sync API so we throw an error immediately.
assert(privateKey.length === 32, "Bad private key");
assert(isValidPrivateKey(privateKey), "Bad private key");
// XXX(Kagami): `elliptic.utils.encode` returns array for every
// encoding except `hex`.
return new Uint8Array(ec.keyFromPrivate(privateKey).getPublic("arr"));
};
/**
* Get compressed version of public key.
*/
var getPublicCompressed = exports.getPublicCompressed = function(privateKey) { // jshint ignore:line
assert(privateKey.length === 32, "Bad private key");
assert(isValidPrivateKey(privateKey), "Bad private key");
// See https://github.com/wanderer/secp256k1-node/issues/46
let compressed = true;
return new Uint8Array(ec.keyFromPrivate(privateKey).getPublic(compressed, "arr"));
};
// NOTE(Kagami): We don't use promise shim in Browser implementation
// because it's supported natively in new browsers (see
// <http://caniuse.com/#feat=promises>) and we can use only new browsers
// because of the WebCryptoAPI (see
// <http://caniuse.com/#feat=cryptography>).
exports.sign = function(privateKey, msg) {
return new Promise(function(resolve) {
assert(privateKey.length === 32, "Bad private key");
assert(isValidPrivateKey(privateKey), "Bad private key");
assert(msg.length > 0, "Message should not be empty");
assert(msg.length <= 32, "Message is too long");
resolve(new Uint8Array(ec.sign(msg, privateKey, {canonical: true}).toDER()));
});
};
exports.verify = function(publicKey, msg, sig) {
return new Promise(function(resolve, reject) {
assert(publicKey.length === 65 || publicKey.length === 33, "Bad public key");
if (publicKey.length === 65)
{
assert(publicKey[0] === 4, "Bad public key");
}
if (publicKey.length === 33)
{
assert(publicKey[0] === 2 || publicKey[0] === 3, "Bad public key");
}
assert(msg.length > 0, "Message should not be empty");
assert(msg.length <= 32, "Message is too long");
if (ec.verify(msg, sig, publicKey)) {
resolve(null);
} else {
reject(new Error("Bad signature"));
}
});
};
var derive = exports.derive = function(privateKeyA, publicKeyB) {
return new Promise(function(resolve) {
assert(privateKeyA instanceof Uint8Array, "Bad private key");
assert(publicKeyB instanceof Uint8Array, "Bad public key");
assert(privateKeyA.length === 32, "Bad private key");
assert(isValidPrivateKey(privateKeyA), "Bad private key");
assert(publicKeyB.length === 65 || publicKeyB.length === 33, "Bad public key");
if (publicKeyB.length === 65)
{
assert(publicKeyB[0] === 4, "Bad public key");
}
if (publicKeyB.length === 33)
{
assert(publicKeyB[0] === 2 || publicKeyB[0] === 3, "Bad public key");
}
var keyA = ec.keyFromPrivate(privateKeyA);
var keyB = ec.keyFromPublic(publicKeyB);
var Px = keyA.derive(keyB.getPublic()); // BN instance
resolve(new Uint8Array(Px.toArray()));
});
};
exports.encrypt = function(publicKeyTo, msg, opts) {
opts = opts || {};
// Tmp variables to save context from flat promises;
var iv, ephemPublicKey, ciphertext, macKey;
return new Promise(function(resolve) {
var ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);
// There is a very unlikely possibility that it is not a valid key
while(!isValidPrivateKey(ephemPrivateKey))
{
ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);
}
ephemPublicKey = getPublic(ephemPrivateKey);
resolve(derive(ephemPrivateKey, publicKeyTo));
}).then(function(Px) {
return sha512(Px);
}).then(function(hash) {
iv = opts.iv || randomBytes(16);
var encryptionKey = hash.slice(0, 32);
macKey = hash.slice(32);
return aesCbcEncrypt(iv, encryptionKey, msg);
}).then(function(data) {
ciphertext = data;
var dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]);
return hmacSha256Sign(macKey, dataToMac);
}).then(function(mac) {
return {
iv: iv,
ephemPublicKey: ephemPublicKey,
ciphertext: ciphertext,
mac: mac,
};
});
};
exports.decrypt = function(privateKey, opts) {
// Tmp variable to save context from flat promises;
var encryptionKey;
return derive(privateKey, opts.ephemPublicKey).then(function(Px) {
return sha512(Px);
}).then(function(hash) {
encryptionKey = hash.slice(0, 32);
var macKey = hash.slice(32);
var dataToMac = Buffer.concat([
opts.iv,
opts.ephemPublicKey,
opts.ciphertext
]);
return hmacSha256Verify(macKey, dataToMac, opts.mac);
}).then(function(macGood) {
assert(macGood, "Bad MAC");
return aesCbcDecrypt(opts.iv, encryptionKey, opts.ciphertext);
}).then(function(msg) {
return new Uint8Array(msg);
});
};

300
index.js
View File

@ -1,17 +1,22 @@
const BigNumber=require('bignumber.js') // https://github.com/MikeMcl/bignumber.js 几个库的比较: node-bignum: 使用到openssl在windows上需要下载二进制包有时下载失败。bigi: 不错。 bignumber.js不错。
// const BigNumber=require('bignumber.js') // 处理整数 https://github.com/MikeMcl/bignumber.js
const BigInt = require("big-integer") // 处理整数 https://github.com/peterolson/BigInteger.js
const crypto=require('crypto')
const nacl = require('tweetnacl')
const bs58check = require('bs58check')
const uuid = require('uuid')
const keccak = require('keccak')
const eccrypto = require('eccrypto') // 用于加解密。不知道怎么用 crypto 本身的加解密。
const keyman = require('js-crypto-key-utils') // 转换原始密钥和 PER/DER 格式。
const Secword = require('bitcore-mnemonic') // https://bitcore.io/api/mnemonic/ https://github.com/bitpay/bitcore-mnemonic
// const bip39 = require('bip39') // https://github.com/bitcoinjs/bip39 // 有更多语言,但不方便选择语言,也不能使用 pass
// const HDKey = require('hdkey') // https://github.com/cryptocoinjs/hdkey // 或者用 bitcore-mnemonic 或者 ethers 里的相同功能
// const bitcorelib = require('bitcore-lib')
// const secp256k1 = require('secp256k1')
// 全部以hex为默认输入输出格式方便人的阅读以及方便函数之间统一接口
const my={}
my.HASHER='sha256' // 默认的哈希算法。could be md5, sha1, sha256, sha512, ripemd160。 可用 Crypto.getHashes/Ciphers/Curves() 查看支持的种类。
my.HASHER='sha256' // 默认的哈希算法。could be md5, sha1, sha256, sha512, ripemd160 and much more。 可用 Crypto.getHashes/Ciphers/Curves() 查看支持的种类。
my.HASHER_LIST=crypto.getHashes()
my.CIPHER='aes-256-cfb' // 默认的加解密算法
my.CIPHER_LIST=crypto.getCiphers()
@ -61,78 +66,117 @@ module.exports = {
return false
}
,
encrypt(data, pwd, option){
if (this.isHashable(data) && typeof(pwd)==='string') {
option=option||{}
let inputEncoding=my.INPUT_LIST.indexOf(option.input)>=0?option.input:my.INPUT // 'utf8' by default, 'ascii', 'latin1' for string or ignored for Buffer/TypedArray/DataView
let outputEncoding=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT) // 'latin1', 'base64', 'hex' by default or 'buf' to Buffer explicitly
let cipher=crypto.createCipher(
my.CIPHER_LIST.indexOf(option.cipher)>=0?option.cipher:my.CIPHER,
this.hash(pwd))
if (typeof(data)!=='string' && !(data instanceof Buffer) && !(data instanceof DataView))
data=JSON.stringify(data)
let encrypted = cipher.update(data, inputEncoding, outputEncoding)
encrypted += cipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string
return encrypted
}
return null
}
,
decrypt(data, pwd, option){ // data 应当是 encrypt 输出的数据类型
if (data && (typeof(data)==='string' || data instanceof Buffer) && typeof(pwd)==='string') {
option=option||{}
let inputEncoding=my.OUTPUT_LIST.indexOf(option.input)>=0?option.input:my.OUTPUT // input (=output of encrypt) could be 'latin1', 'base64', 'hex' by default for string or ignored for Buffer
let outputEncoding=(option.output==='buf')?undefined:(my.INPUT_LIST.indexOf(option.output)>=0?option.output:my.INPUT) // output (=input of encrypt) could be 'latin1', 'ascii', 'utf8' by default or 'buf' to Buffer explicitly
let decipher=crypto.createDecipher(
my.CIPHER_LIST.indexOf(option.cipher)>=0?option.cipher:my.CIPHER,
this.hash(pwd))
let decrypted = decipher.update(data, inputEncoding, outputEncoding)
decrypted += decipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string
if (option.format==='json') { // 如果用户输入错误密码deciper也能返回结果。为了判断是否正确结果对应当是 json 格式的原文做解析来验证。
try{
JSON.parse(decrypted)
}catch(exception){
return null
}
async encrypt(data, {keytype, key, input, output, cipher}={}){
if (keytype==='pwd') {
if (this.isHashable(data) && typeof(key)==='string') {
let inputEncoding=my.INPUT_LIST.indexOf(input)>=0?input:my.INPUT // 'utf8' by default, 'ascii', 'latin1' for string or ignored for Buffer/TypedArray/DataView
let outputEncoding=(output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(output)>=0?output:my.OUTPUT) // 'latin1', 'base64', 'hex' by default or 'buf' to Buffer explicitly
let cipher=crypto.createCipher(
my.CIPHER_LIST.indexOf(cipher)>=0?cipher:my.CIPHER,
this.hash(key))
if (typeof(data)!=='string' && !(data instanceof Buffer) && !(data instanceof DataView))
data=JSON.stringify(data)
let encrypted = cipher.update(data, inputEncoding, outputEncoding)
encrypted += cipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string
return encrypted
}
return decrypted
}else if (keytype==='pubkey') { // data 应当是 utf8 的字符串。// 但在浏览器里不能使用 Failed to execute 'encrypt' on 'SubtleCrypto': The provided value is not of type '(ArrayBuffer or ArrayBufferView)'
let cipherobject = await eccrypto.encrypt(this.hex2buf(key), data)
return cipherobject
}
return null
}
,
sign(data, seckey, option) { // data can be string or buffer or object, results are the same
async decrypt(data, {keytype, key, input, output, cipher, format}={}){ // data 应当是 encrypt 输出的数据类型
if (keytype==='pwd') {
if (data && (typeof(data)==='string' || data instanceof Buffer) && typeof(key)==='string') {
let inputEncoding=my.OUTPUT_LIST.indexOf(input)>=0?input:my.OUTPUT // input (=output of encrypt) could be 'latin1', 'base64', 'hex' by default for string or ignored for Buffer
let outputEncoding=(output==='buf')?undefined:(my.INPUT_LIST.indexOf(output)>=0?output:my.INPUT) // output (=input of encrypt) could be 'latin1', 'ascii', 'utf8' by default or 'buf' to Buffer explicitly
let decipher=crypto.createDecipher(
my.CIPHER_LIST.indexOf(cipher)>=0?cipher:my.CIPHER,
this.hash(key))
let decrypted = decipher.update(data, inputEncoding, outputEncoding)
decrypted += decipher.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string
if (format==='json') { // 如果用户输入错误密码deciper也能返回结果。为了判断是否正确结果对应当是 json 格式的原文做解析来验证。
try{
JSON.parse(decrypted)
}catch(exception){
return null
}
}
return decrypted
}else if (keytype==='seckey'){ // cipherobject 需要是 eccrypto 自身encrypt方法返回的对象
let plaindata = await eccrypto.decrypt(Buffer.from(key, 'hex'), data) // eccrypto 需要调用 Buffer.compare 方法,不能在这里直接用 hex2buf
return plaindata.toString('utf8')
}
}
return null
}
,
/* eccrypto
*/
async encryptPubkey(plaindata, pubkey, option){ // plaindata 应当是 utf8 的字符串
let cipherobject = await eccrypto.encrypt(this.hex2buf(pubkey), plaindata)
return cipherobject
}
,
async decryptSeckey(cipherobject, seckey, option){ // cipherobject 需要是 eccrypto 自身encrypt方法返回的对象
let plaindata
if (Buffer) { // nodejs
plaindata = await eccrypto.decrypt(Buffer.from(seckey, 'hex'), cipherobject) // eccrypto 需要调用 Buffer.compare 方法
}else { // browser
plaindata = await eccrypto.decrypt(this.hex2buf(seckey), cipherobject)
}
return plaindata.toString('utf8')
}
,
async sign(data, seckey, option) { // data can be string or buffer or object, results are the same
if (this.isHashable(data) && this.isSeckey(seckey)) {
option=option||{}
// 使用nacl的签名算法。注意nacl.sign需要的seckey是64字节=512位而比特币/以太坊的seckey是32字节。因此本方法只能用于 TIC 币的 keypair。
option.output='buf' // 哈希必须输出为 buffer
var hashBuf = this.hash(data, option)
var signature = nacl.sign.detached(hashBuf, Buffer.from(seckey, 'hex'))
return Buffer.from(signature).toString('hex') // 返回128个hex字符64字节
// 方案1: 使用nacl的签名算法。注意nacl.sign需要的seckey是64字节=512位而比特币/以太坊的seckey是32字节。因此本方法只能用于 TIC 币的 keypair。
// option.output='buf' // 哈希必须输出为 buffer
// var hashBuf = this.hash(data, option)
// var signature = nacl.sign.detached(hashBuf, Buffer.from(seckey, 'hex'))
// return Buffer.from(signature).toString('hex') // 返回128个hex字符64字节
// 方案2尚未彻底实现。
// let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER
// let inputEncoding=my.INPUT_LIST.indexOf(option.input)>=0?option.input:my.INPUT // 'utf8', 'ascii' or 'latin1' for string data, default to utf8 if not specified; ignored for Buffer, TypedArray, or DataView.
// let outputEncoding=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT)
// let signer=crypto.createSign(hasher)
// return signer.update(data, inputEncoding).sign(seckey, outputEncoding) // todo: crypto的sign要求的seckey必须是PEM格式因此这样写是不能用的。
// 方案2: 纯 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 inputEncoding=my.INPUT_LIST.indexOf(option.input)>=0?option.input:my.INPUT // 'utf8', 'ascii' or 'latin1' for string data, default to utf8 if not specified; ignored for Buffer, TypedArray, or DataView.
let outputEncoding=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT)
let signer=crypto.createSign(hasher)
signer.update(data, inputEncoding).end()
let signature = signer.sign(seckeyPEM, outputEncoding)
return signature // 发现同样的输入,每次调用会生成不同的 signature, 但都可以通过 verify。有一次我竟然徒手修改出一个新签名也通过验证。
}
return null
}
,
isSignature(signature){
return /^[a-fA-F0-9]{128}$/.test(signature)
return /^[a-fA-F0-9]{128,144}$/.test(signature)
}
,
verify (data, signature, pubkey, option) { // data could be anything, but converts to string or remains be Buffer/TypedArray/DataView
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)){
option=option||{}
option.output='buf' // 哈希必须输出为 buffer
var bufHash=this.hash(data, option)
var bufSignature = Buffer.from(signature, 'hex')
var bufPubkey = Buffer.from(pubkey, 'hex')
var res = nacl.sign.detached.verify(bufHash, bufSignature, bufPubkey)
return res
// 方案1: nacl
// option=option||{}
// option.output='buf' // 哈希必须输出为 buffer
// var bufHash=this.hash(data, option)
// var bufSignature = Buffer.from(signature, 'hex')
// var bufPubkey = Buffer.from(pubkey, 'hex')
// var res = nacl.sign.detached.verify(bufHash, bufSignature, bufPubkey)
// return res
// 方案2: 纯 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 inputEncoding=my.INPUT_LIST.indexOf(option.input)>=0?option.input:my.INPUT // 'utf8', 'ascii' or 'latin1' for string data, default to utf8 if not specified; ignored for Buffer, TypedArray, or DataView.
let outputEncoding=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT)
let verifier = crypto.createVerify(hasher)
verifier.update(data, inputEncoding).end() // end() 在 nodejs 12 里返回verifier自身但在浏览器里返回 undefined因此不能串联运行。
let verified = verifier.verify(pubkeyPEM, signature, 'hex')
return verified
}
return null
}
@ -230,16 +274,16 @@ module.exports = {
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
if (this.isSeckey(seckey) && seckey.length===64){ // 只能用于32字节的私钥BTC, ETH)。也就是不能用于 TIC 的私钥。
let curve = my.CURVE_LIST.indexOf(option.curve)>=0?option.curve:my.CURVE // 默认为 secp256k1
let compress = ['compressed', 'uncompressed'].indexOf(option.compress)>=0?option.compress:'compressed' // 默认为压缩格式的公钥
return new crypto.ECDH(curve).setPrivateKey(seckey,'hex').getPublicKey('hex',compress).toString('hex') // ecdh.getPublicKey(不加参数) 默认为 'uncompressed'
// if (option.compressed==='false'){
// return ecc.getPublic(this.hex2arrbuf(seckey)).toString('hex')
return new crypto.createECDH(curve).setPrivateKey(seckey,'hex').getPublicKey('hex', option.compress===false?'uncompressed':'compressed') // ecdh.getPublicKey(不加参数) 默认为 'uncompressed'
// 从 nodejs 10.0 开始,还有 crypto.ECDH.convertKey 方法,更直接。但可惜,浏览器里不存在 crypto.ECDH。
// 或者 return this.buf2hex(require('secp256k1').publicKeyCreate(Buffer.from(seckey, 'hex'), option.compress!==false)) // 可用于浏览器。secp256k1缺省或true时输出压缩公钥false时输出非压缩公钥。
// 或者 bitcorelib.PublicKey.fromPrivateKey(new bitcorelib.PrivateKey(seckey)).toString('hex') // 可用于浏览器
// 或者 const ecc = require('eccrypto')
// if (option.compress===false){
// return ecc.getPublic(this.hex2buf(seckey)).toString('hex')
// }else{
// return ecc.getPublicCompressed(this.hex2arrbuf(seckey)).toString('hex')
// return ecc.getPublicCompressed(this.hex2buf(seckey)).toString('hex')
// }
// 从 nodejs 10.0 开始,还有 crypto.ECDH.convertKey 方法,更直接。
// 或者 require('secp256k1').publicKeyCreate(Buffer.from(seckey, 'hex'),compress).toString('hex')
// 或者 require('bitcore-lib').PublicKey.fromPrivateKey(new Btc.PrivateKey(seckey)).toString('hex')
// 注意Buffer.from(nacl.box.keyPair.fromSecretKey(Buffer.from(seckey,'hex')).publicKey).toString('hex') 得到的公钥与上面的不同
}else if (this.isSeckey(seckey) && seckey.length===128 && option.coin==='TIC'){ // 用于64字节=128 hex的 TIC 私钥
let keypair=nacl.sign.keyPair.fromSecretKey(Buffer.from(seckey,'hex'))
@ -254,10 +298,10 @@ module.exports = {
if (this.isSeckey(seckey)){
let pubkey
if (option.coin==='ETH'){
pubkey = this.seckey2pubkey(seckey, {compress:'uncompressed'})
pubkey = this.seckey2pubkey(seckey, {compress:false})
return this.pubkey2address(pubkey, option)
}else {
pubkey = this.seckey2pubkey(seckey, {compress:'compressed'})
pubkey = this.seckey2pubkey(seckey, {compress:true})
return this.pubkey2address(pubkey, option)
}
}
@ -285,11 +329,70 @@ module.exports = {
return /^[m|t|d|T][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{33}$/.test(address) // && address.length>25 && bs58check.decode(address.slice(1)) && ['A'].indexOf(address[0]>=0)) {
}
,
pubkey2address (pubkey, option) { // pubkey 应当是string类型
option=option||{}
pubkey2position (pubkey, {coin}={}){
coin = my.COIN_LIST.indexOf(coin)>=0?coin:my.COIN
if(this.isPubkey(pubkey)){
if (coin==='ETH'){
// 注意必须要用非压缩的64字节的公钥的buffer并去掉开头的 04。
if (pubkey.length===66) {
pubkey = this.decompressPubkey(pubkey)
}
return keccak('keccak256').update(Buffer.from(pubkey.slice(2),'hex')).digest('hex').slice(-40)
}else {
let h256 = crypto.createHash('sha256').update(Buffer.from(pubkey, 'hex')).digest()
let h160 = crypto.createHash('ripemd160').update(h256).digest('hex')
return h160
}
}
return null
}
,
position2address(position, {coin, netType}={}){
if (!position) return null
coin = my.COIN_LIST.indexOf(coin)>=0?coin:my.COIN
let address
if (coin==='ETH'){ // 对以太坊,按照 EIP55把纯位置转换为大小写敏感能自我验证的hex地址
position = position.toLowerCase().replace('0x', '')
let hash = keccak('keccak256').update(position).digest('hex')
address = '0x'
for (var i = 0; i < position.length; i++) {
if (parseInt(hash[i], 16) >= 8) {
address += position[i].toUpperCase()
} else {
address += position[i]
}
}
return address
}else if (coin === 'BTC'){ // 对比特币把纯位置转换为大小写敏感能自我验证的bs58check地址
let prefix
switch (netType) {
case 'mainnet': prefix='00'; break; // pubkey hash => 1
case 'mainnetSh': prefix='05'; break; // script hash => 3
case 'testnet': prefix='6f'; break; // testnet pubkey hash => m or n
case 'testnetSh': prefix='c4'; break // testnet script hash => 2
case 'namecoin': prefix='34'; break; // Namecoin pubkey hash => M or N
case 'compact': prefix='15'; break; // compact pubkey (proposed) => 4
default: prefix='00'
}
address=bs58check.encode(Buffer.from(prefix+position, 'hex')) // wallet import format
return address
}else {
let prefix
switch (netType){
case 'mainnet': prefix='42'; break; // '42'=>T, '6E'=>m
case 'testnet': prefix='7F'; break; // '7F'=>t
case 'devnet': prefix='5A'; break; // '5A'=>d
default: prefix='42' // 默认暂且为 42为了兼容已经运行的链。
}
address=bs58check.encode(Buffer.from(prefix+position, 'hex')) // wallet import format
return address
}
return null
}
,
pubkey2address (pubkey, option={}) { // pubkey 应当是string类型
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
if (this.isPubkey(pubkey)) {
option = option||{}
let h256 = crypto.createHash('sha256').update(Buffer.from(pubkey, 'hex')).digest()
let h160 = crypto.createHash('ripemd160').update(h256).digest('hex')
let prefix
@ -308,7 +411,7 @@ module.exports = {
default: prefix='00'
}
}else if (option.coin==='ETH'){
// 注意必须要用非压缩的64字节的公钥的buffer
// 注意必须要用非压缩的64字节的公钥的buffer,并去掉开头的 04
return '0x' + keccak('keccak256').update(Buffer.from(pubkey.slice(2),'hex')).digest('hex').slice(-40)
// 或 const { keccak256 } = require('ethereumjs-util'); keccak256(Buffer.from(pubkey.slice(2),'hex)).toString('hex').slice(40)
// 或 const { Keccak } = require('sha3'); new Keccak('').update(Bufer.from(pubkey.slice(2),'hex')).digest('hex').slice(-40)
@ -334,13 +437,13 @@ module.exports = {
return new Secword(Secword.Words[language]).phrase
}
,
randomSeckey(option){
randomSeckey(option){ // todo: 使用 crypto.randomBytes(size)
option=option||{}
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
if (option.coin==='TIC'){
return Buffer.from(nacl.sign.keyPair().secretKey).toString('hex') // 64字节
return crypto.randomBytes(64).toString('hex') // Buffer.from(nacl.sign.keyPair().secretKey).toString('hex') // 64字节
}else{
return Buffer.from(nacl.box.keyPair().secretKey).toString('hex') // 32字节
return crypto.randomBytes(32).toString('hex') // Buffer.from(nacl.box.keyPair().secretKey).toString('hex') // 32字节
}
}
,
@ -436,7 +539,7 @@ module.exports = {
distanceSig(hash, sig){ // hash为64hex字符sig为128hex字符。返回用hex表达的距离。
if (this.isSignature(sig) && this.isHash(hash)){
var hashSig=this.hash(sig) // 把签名也转成32字节的哈希同样长度方便比较
return new BigNumber(hash,16).minus(new BigNumber(hashSig,16)).abs().toString(16)
return new BigInt(hash,16).sub(new BigInt(hashSig,16)).abs().toString(16)
}
return null
}
@ -520,14 +623,55 @@ module.exports = {
return verifier.update(string2Verify).verify(pubkey, sign, 'base64')
}
,
arrbuf2hex(buffer) { // buffer is an ArrayBuffer
buf2hex(buffer) { // buffer is an ArrayBuffer
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
}
,
hex2arrbuf(hex){
hex2buf(hex){
return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16)
})) // 注意,arraybuffer没有 toString('hex')功能
})) // 注意,arraybuffer没有 toString('hex')功能, Buffer才有。
}
,
hex2base58check(hex){
return bs58check.encode(Buffer.from(hex, 'hex'))
}
,
base58check2hex(box){
try{
return bs58check.decode(box).toString('hex').toUpperCase()
}catch(exception){
return null
}
}
,
hex2eip55(){
}
,
// test: https://iancoleman.io/bitcoin-key-compression/
// compress: https://hacpai.com/article/1550844562914
compressPubkey(uncompressed){
let [all, x, y]=uncompressed.toLowerCase().match(/^04(.{64})(.{64})$/)
if (/[1,3,5,7,9,b,d,f]$/.test(y)){
return '03'+x // y为奇数=>前缀03
}else{
return '02'+x // y为偶数=>前缀02
}
}
,
// uncompress: https://stackoverflow.com/questions/17171542/algorithm-for-elliptic-curve-point-compression/53478265#53478265
// https://en.bitcoin.it/wiki/Secp256k1
decompressPubkey(compressed){
// Consts for secp256k1 curve. Adjust accordingly
const prime = new BigInt('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16) // 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1
const pIdent = new BigInt('3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c', 16) // prime.add(1).divide(4);
var signY = new Number(compressed[1]) - 2
var x = new BigInt(compressed.substring(2), 16)
var y = x.modPow(3, prime).add(7).mod(prime).modPow( pIdent, prime ) // y mod p = +-(x^3 + 7)^((p+1)/4) mod p
if( y.mod(2).toJSNumber() !== signY ) { // If the parity doesn't match it's the *other* root
y = prime.subtract( y ) // y = prime - y
}
return '04' + x.toString(16).padStart(64, '0') + y.toString(16).padStart(64, '0')
}
}

View File

@ -3,9 +3,11 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"bignumber.js": "^9.0.0",
"big-integer": "^1.6.48",
"bitcore-mnemonic": "^8.16.0",
"bs58check": "^2.1.1",
"eccrypto": "^1.1.3",
"js-crypto-key-utils": "^0.7.3",
"keccak": "^2.1.0",
"tweetnacl": "^1.0.3",
"uuid": "^3.3.2"

38
test-decompress.js Normal file
View File

@ -0,0 +1,38 @@
const bigInt = require("big-integer");
// Consts for P256 curve. Adjust accordingly
const two = new bigInt(2),
// 115792089210356248762697446949407573530086143415290314195533631308867097853951
prime = two.pow(256).subtract( two.pow(224) ).add( two.pow(192) ).add( two.pow(96) ).subtract(1),
b = new bigInt( '41058363725152142129326129780047268409114441015993725554835256314039467401291' ),
// Pre-computed value, or literal
// 28948022302589062190674361737351893382521535853822578548883407827216774463488
pIdent = prime.add(1).divide(4);
function pad_with_zeroes(number, length) {
var retval = '' + number;
while (retval.length < length) {
retval = '0' + retval;
}
return retval;
}
/**
* Point decompress NIST curve
* @param {string} Compressed representation in hex string
* @return {string} Uncompressed representation in hex string
*/
function ECPointDecompress( comp ) {
var signY = new Number(comp[1]) - 2;
var x = new bigInt(comp.substring(2), 16);
// y^2 = x^3 - 3x + b
var y = x.pow(3).subtract( x.multiply(3) ).add( b ).modPow( pIdent, prime );
// If the parity doesn't match it's the *other* root
if( y.mod(2).toJSNumber() !== signY ) {
// y = prime - y
y = prime.subtract( y );
}
return '04' + pad_with_zeroes(x.toString(16), 64) + pad_with_zeroes(y.toString(16), 64);
}
console.log(ECPointDecompress('035d77c1e3eac37f685aeea2ae872c4e7e4d159756e57601db3bcccbc549f360b2'))

190
test.js Normal file
View File

@ -0,0 +1,190 @@
const bigInt = require("big-integer");
// Consts for secp256k1 curve. Adjust accordingly
// https://en.bitcoin.it/wiki/Secp256k1
const prime = new bigInt('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16), // 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1
pIdent = new bigInt('3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c', 16) // prime.add(1).divide(4);
console.log('pIdent=',pIdent.toString(), ' = ', pIdent.toString(16))
/**
* Point decompress secp256k1 curve
* @param {string} Compressed representation in hex string
* @return {string} Uncompressed representation in hex string
*/
function ECPointDecompress( comp ) {
var signY = new Number(comp[1]) - 2;
var x = new bigInt(comp.substring(2), 16);
// y mod p = +-(x^3 + 7)^((p+1)/4) mod p
console.log('ECP x=', x.toString(), ' = ', x.toString(16))
var y = x.modPow(3, prime).add(7).mod(prime).modPow( pIdent, prime );
// If the parity doesn't match it's the *other* root
console.log('ECP y=', y.toString(), ' = ', y.toString(16))
if( y.mod(2).toJSNumber() !== signY ) {
// y = prime - y
y = prime.subtract( y );
}
console.log('ECP y=', y.toString(), ' = ', y.toString(16))
return '04' + x.toString(16).padStart(64, '0') + y.toString(16).padStart(64, '0');
}
let pubkey1= ECPointDecompress('035d77c1e3eac37f685aeea2ae872c4e7e4d159756e57601db3bcccbc549f360b2')
console.log(pubkey1) // "045d77c1e3eac37f685aeea2ae872c4e7e4d159756e57601db3bcccbc549f360b24a323dd24b19c55f0a060ccd4bce314323bd7e804f3dfa8a77f14e3ab1cc4749"
correct = "045d77c1e3eac37f685aeea2ae872c4e7e4d159756e57601db3bcccbc549f360b2356d086fb7a78f3ce3359a4caee6dd4fcf0c19a961b1c36b5b442d031d219d75"
BigNumber = require('bignumber.js')
function uncompressPubkey(comp){
// Consts for P256 curve. Adjust accordingly
const prime = new BigNumber('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16).integerValue(),
pIdent = prime.plus(1).idiv(4).integerValue()
console.log('pIdent=', pIdent.toString(), ' = ', pIdent.toString(16))
var signY = new Number(comp[1]) - 2;
var x = new BigNumber(comp.substring(2), 16).integerValue();
console.log('x=',x.toString(), ' = ', x.toString(16))
// y^2 = x^3 - 3x + b
var y = x.pow(3).mod(prime).plus(7).mod(prime).pow(pIdent).mod(prime).integerValue();
console.log('y=',y.toString(), ' = ', y.toString(16))
// If the parity doesn't match it's the *other* root
if( y.mod(2).integerValue().toNumber() !== signY ) {
// y = prime - y
y = prime.minus( y ).integerValue();
}
console.log('yy=', y.toString(), ' = ', y.toString(16))
return '04' + x.toString(16).padStart(64, '0') + y.toString(16).padStart(64, '0');
}
let pubkey2=uncompressPubkey('035d77c1e3eac37f685aeea2ae872c4e7e4d159756e57601db3bcccbc549f360b2')
console.log(pubkey2)
///////////////////////////////////////
const tic = require('./index')
const crypto = require('crypto')
const keyutil = require('js-crypto-key-utils') // https://github.com/junkurihara/jscu/tree/master/packages/js-crypto-key-utils
// https://github.com/arvati/crypto-keys
// nodejs cipher/decipher 用同一个密码在stream上操作。
let w = '驳 惊 而 煤 靠 客 示 待 诉 屈 屏 未' // tic.randomSecword('chinese')
console.log('secword = ', w)
let acc = tic.secword2account(w, {coin:'ETH'})
console.log('account = ', acc)
let add = tic.secword2address(w, {coin:'ETH'})
console.log('address = ', add)
/////////////////////// keyutil
let seckeyObject = new keyutil.Key('oct', Buffer.from(acc.seckey, 'hex'), {namedCurve:'P-256K'}) // {P-256 : secp256r1, P-384 : secp384r1, P-521 : secp521r1, P-256K : secp256k1}
let seckeyObject2 = new keyutil.Key('oct', tic.hex2buf(acc.seckey, 'hex'), {namedCurve:'P-256K'})
let seckeyPEM
seckeyObject.export('pem').then(data=>seckeyPEM=data)
let seckeyDER
seckeyObject2.export('der').then(data=>seckeyDER=data)
var signerKU = crypto.createSign('sha256');
signerKU.write('毛主席万岁');
signerKU.end();
var signatureKU = signerKU.sign(seckeyPEM); // specify format in [pem,der] and type in [pkcs1, pkcs8, sec1]
console.log('signature = ', signatureKU.toString('hex'))
console.log('length = ', signatureKU.toString('hex').length)
var signerKUDER = crypto.createSign('sha256')
signerKUDER.write('毛主席万岁');
signerKUDER.end();
var signatureKUDER = signerKUDER.sign({key:seckeyDER, format:'der', type:'pkcs8'}); // specify format in [pem,der] and type in [pkcs1, pkcs8, sec1]
console.log('signature DER = ', signatureKUDER.toString('hex'))
console.log('length DER = ', signatureKUDER.toString('hex').length)
let pubkeyObject = new keyutil.Key('oct', Buffer.from(acc.pubkey, 'hex'), {namedCurve:'P-256K'})
let pubkeyPEM
pubkeyObject.export('der').then(data=>pubkeyPEM=data)
var verifyKU = crypto.createVerify('sha256');
verifyKU.write('毛主席万岁');
verifyKU.end();
var verified = verifyKU.verify(pubkeyPEM, signatureKU); // specify format in [pem,der] and type in [pkcs1,spki]
console.log('verified = ', verified) // 可以验证通过但是用的privatekey没有成功使用publickey。
crypto.createCipheriv('aes-256-cfb', Buffer.from(acc.seckey,'hex'), Buffer.alloc(16))
////////////////////// crypto + PEM
toPEM=function (kp){
let pubkey = crypto.createECDH('secp256k1').setPrivateKey(kp.seckey, 'hex').getPublicKey('hex','compressed')
console.log('ECDH created publickey = ', pubkey)
let mykey = '308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420' + kp.seckey + 'a144034200' + pubkey
console.log(mykey)
let privKey = '-----BEGIN PRIVATE KEY-----\n' + Buffer.from(mykey, 'hex').toString('base64') + '\n-----END PRIVATE KEY-----'
// pubKey2 = crypto.createPublicKey(privKey); //也可恢复出公钥。测试不成功。
return privKey
}
let privKeyPEM = toPEM(acc)
const signerPEM = crypto.createSign('sha256')
signerPEM.write('毛主席万岁')
signerPEM.end()
let signaturePEM = signerPEM.sign(privKeyPEM, 'hex') // 失败,无论对压缩或非压缩公钥
console.log('signaturePEM = ', signaturePEM)
let pemKP = toPEM(acc)
console.log('pemKP = ', pemKP)
//////////////////// crypto, DER
// https://stackoverflow.com/questions/58350484/why-nodejs-crypto-sign-function-only-accept-privatekey-pem-format
// https://www.shangyang.me/2017/05/24/encrypt-rsa-keyformat/
var buf1 = Buffer.from('308141020100301306072a8648ce3d020106082a8648ce3d030107042730250201010420', 'hex'); // specific byte-sequence for curve prime256v1
var buf2 = Buffer.from(acc.seckey, 'hex'); // raw private key (32 bytes)
var privateKeyPkcs8Der = Buffer.concat([buf1, buf2], buf1.length + buf2.length);
var sign = crypto.createSign('sha256');
sign.write('毛主席万岁');
sign.end();
var signature = sign.sign({ key: privateKeyPkcs8Der, format: 'der', type: 'pkcs8' }); // specify format in [pem,der] and type in [pkcs1, pkcs8, sec1]
console.log('signature = ', signature.toString('hex'))
console.log('length = ', signature.toString('hex').length)
var buf3 = Buffer.from('3059301306072a8648ce3d020106082a8648ce3d030107034200', 'hex'); // specific byte-sequence for curve prime256v1
var buf4 = Buffer.from(acc.pubkey, 'hex'); // raw public key (uncompressed, 65 bytes, startting with 04)
// 这个key无法sign。reason: 'too long'
//var publicKeyX509Der = Buffer.concat([buf3, buf4], buf3.length + buf4.length);
//var publicKey = crypto.createPublicKey({key:publicKeyX509Der, format:'der', type:'spki'})
var publicKey = crypto.createPublicKey({ key: privateKeyPkcs8Der, type: 'pkcs8', format: 'der' });
var publicKeyX509Der = publicKey.export({type: 'spki', format: 'der'})
var verify = crypto.createVerify('sha256');
verify.write('毛主席万岁');
verify.end();
var verified = verify.verify({ key: publicKeyX509Der, format: 'der', type: 'spki' }, signature); // specify format in [pem,der] and type in [pkcs1,spki]
console.log('verified = ', verified) // 可以验证通过但是用的privatekey没有成功使用publickey。
/////////////////////// elliptic
var EC = require('elliptic').ec;
// Create and initialize EC context
// (better do it once and reuse it)
var ec = new EC('secp256k1');
// Generate keys
//var key = ec.genKeyPair();
var key = ec.keyFromPrivate(acc.seckey) // 注意,不需要 'hex' 参数
// Sign the message's hash (input must be an array, or a hex-string)
var msgHash = tic.hash('毛主席万岁')
var msgHashBad = tic.hash('毛主席万岁 ')
var signature2 = key.sign(msgHash);
// Export DER encoded signature in Array
var derSign = signature2.toDER(); // 无法直接导出成 hex。可以
console.log('signature by elliptic = ', Buffer.from(derSign).toString('hex'))
// 或者重新创建使用 pubkey也能成功
// ec.keyFromPublic(acc.pubkey, 'hex').verify(msgHash, signature2)
console.log(key.verify(msgHash, signature2))
console.log(key.verify(msgHashBad, signature2))
//////////////////
/*
createCipher/Decipher: 使用 pwd, 对称加解密已放弃
createCipheriv/Deciperiv: 使用 key, 对称加解密
private/publicEncrypt/Decrypt: 非对称加解密
crypto.privateEncrypt(crypto.generateKeyPairSync('rsa', {modulusLength:2048}).privateKey, Buffer.from('锦瑟无端五十弦'))
以上是唯一测出来可用的privatekey不能用 'ec', {namedCurve:'secp256k1'}, 也不能用crypto.createPrivateKey(pem格式的字符串)
*/