用eccrypto加解密,用crypto签名。互相转换压缩和非压缩公钥
This commit is contained in:
parent
c494dd51de
commit
09d831af00
273
eccrypt.browser.js
Normal file
273
eccrypt.browser.js
Normal 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
300
index.js
@ -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')
|
||||
}
|
||||
}
|
@ -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
38
test-decompress.js
Normal 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
190
test.js
Normal 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格式的字符串)。
|
||||
*/
|
Loading…
Reference in New Issue
Block a user