simple-js-ecdsa
Advanced tools
Comparing version 1.2.0 to 1.3.0
{ | ||
"name": "simple-js-ecdsa", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"description": "easy and light weight ecdsa implementation", | ||
@@ -5,0 +5,0 @@ "main": "./src/index.js", |
@@ -5,25 +5,25 @@ # simple-js-ecdsa | ||
creating a new wallet | ||
creating a new identity | ||
``` | ||
let wallet = Wallet.new() | ||
let identity = Identity.new() | ||
``` | ||
opening an existing wallet using a private key | ||
opening an existing identity using a private key | ||
``` | ||
Wallet.fromKey(<private number>) | ||
Identity.fromKey(<private number>) | ||
``` | ||
opening a wallet using a wif | ||
opening a identity using a wif | ||
``` | ||
Wallet.fromWif(<private wif>) | ||
Identity.fromWif(<private wif>) | ||
``` | ||
retrievable items in a wallet | ||
retrievable items in a identity | ||
``` | ||
wallet.key | ||
wallet.sec1Compressed | ||
wallet.sec1Uncompressed | ||
wallet.wif | ||
wallet.address | ||
wallet.compressAddress | ||
identity.key | ||
identity.sec1Compressed | ||
identity.sec1Uncompressed | ||
identity.wif | ||
identity.address | ||
identity.compressAddress | ||
``` | ||
@@ -33,3 +33,3 @@ | ||
``` | ||
const signature = wallet.sign(message) | ||
const signature = identity.sign(message) | ||
``` | ||
@@ -39,3 +39,3 @@ | ||
``` | ||
wallet.verify(message, signature) | ||
identity.verify(message, signature) | ||
``` | ||
@@ -45,3 +45,3 @@ | ||
``` | ||
const signature = wallet.bip66Sign(message) | ||
const signature = identity.bip66Sign(message) | ||
``` | ||
@@ -51,3 +51,3 @@ | ||
``` | ||
wallet.verifyBip66(message, signature) | ||
identity.verifyBip66(message, signature) | ||
``` | ||
@@ -54,0 +54,0 @@ |
170
src/index.js
@@ -1,7 +0,165 @@ | ||
const ECDSA = require('./ecdsa.js') | ||
const Wallet = require('./wallet.js') | ||
const bigInt = require('big-integer') | ||
const base58 = require('bs58') | ||
const crypto = require('crypto') | ||
const secp256k1 = require('./secp256k1.js') | ||
module.exports = { | ||
ECDSA: ECDSA, | ||
Wallet: Wallet, | ||
} | ||
let hex = { '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, a: 10, b: 11, c: 12, d: 13, e: 14, f: 15 } | ||
const sha256 = require('./sha256.js') | ||
const ripemd160 = data => crypto.createHash('ripemd160').update(data, 'hex').digest('hex').toString() | ||
const hexStringToBinaryString = s => { | ||
s = s.toLowerCase() | ||
const len = s.length; | ||
var data = []; | ||
for (let i = 0; i < len; i += 2) { | ||
data[i / 2] = String.fromCharCode((hex[s[i]] << 4) + (hex[s[i + 1]])); | ||
} | ||
return data.join(''); | ||
} | ||
const binSha256 = data => sha256(hexStringToBinaryString(data)) | ||
const binRipemd160 = data => ripemd160(data) | ||
class Identity { | ||
static new(curve = secp256k1) { | ||
return Identity.fromKey(curve.modSet.random(), curve) | ||
} | ||
static fromKey(key, curve = secp256k1) { | ||
const wallet = new Identity() | ||
wallet.curve = curve | ||
wallet.key = key | ||
return wallet | ||
} | ||
static fromWif(wif, curve = secp256k1) { | ||
const wallet = new Identity() | ||
wallet.curve = curve | ||
const hexWif = base58.decode(wif).toString('hex') | ||
wallet.key = hexWif.substring(2, hexWif.length - 8) | ||
if (wallet.wif !== wif) { | ||
throw 'invalid wif' | ||
} | ||
return wallet | ||
} | ||
sign(message, k = this.curve.modSet.random()) { | ||
k = bigInt(k, 16) | ||
const e = bigInt(sha256(message), 16) | ||
const da = bigInt(this.key, 16) // private key | ||
const r = this.curve.multiply(this.curve.g, k) | ||
const s1 = da.multiply(r.x).add(e) | ||
const s = s1.multiply(k.modInv(this.curve.n)).mod(this.curve.n) | ||
return { | ||
r: bigInt(r.x).toString(16), | ||
s: s.toString(16) | ||
} | ||
} | ||
bip66Sign(message, k = this.curve.modSet.random()) { | ||
let signature = this.sign(message, k) | ||
const r = Buffer.from(signature.r, 'hex') | ||
const s = Buffer.from(signature.s, 'hex') | ||
const rl = r.length | ||
const sl = s.length | ||
signature = Buffer.allocUnsafe(6 + rl + sl) | ||
signature[0] = 0x30 | ||
signature[1] = signature.length - 2 | ||
signature[2] = 0x02 | ||
signature[3] = rl | ||
r.copy(signature, 4) | ||
signature[4+rl] = 0x02 | ||
signature[5+rl] = sl | ||
s.copy(signature, rl + 6) | ||
if (this.verifyBip66(message, signature)) { | ||
return signature.toString('hex') | ||
} | ||
console.warn('⚠️ k value was unusable, making new k. this will be fixed in a later version') | ||
return this.bip66Sign(message) | ||
} | ||
verify(message, signature) { | ||
const e = bigInt(sha256(message), 16) | ||
const r = bigInt(signature.r, 16) | ||
const s = bigInt(signature.s, 16) | ||
const w = bigInt(s).modInv(this.curve.n) | ||
const u1 = bigInt(e).multiply(w).mod(this.curve.n) | ||
const u2 = r.multiply(w).mod(this.curve.n) | ||
const p = this.curve.add(this.curve.multiply(this.curve.g, u1), this.curve.multiply(this.publicPoint, u2)) | ||
return p.x == r | ||
} | ||
verifyBip66(message, signature) { | ||
signature = Buffer.from(signature, 'hex') | ||
const lr = signature[3] | ||
return this.verify(message, { | ||
r: signature.slice(4, 4 + lr).toString('hex'), | ||
s: signature.slice(6 + lr).toString('hex') | ||
}) | ||
} | ||
static fromAddress(address, curve = secp256k1) { | ||
const wallet = new Identity() | ||
wallet.curve = curve | ||
wallet._address = address | ||
return wallet | ||
} | ||
get wif() { | ||
if (this._wif) { | ||
return this._wif | ||
} | ||
const formatted = `80${this.key}` | ||
const checksum = binSha256(binSha256(formatted)).substr(0, 8) | ||
return this._wif = base58.encode(Buffer.from(`${formatted}${checksum}`, 'hex')) | ||
} | ||
get publicPoint() { | ||
if (this._publicPoint) { | ||
return this._publicPoint | ||
} | ||
return this._publicPoint = this.curve.multiply(this.curve.g, bigInt(this.key, 16)) | ||
} | ||
get sec1Compressed() { | ||
if (this._sec1Compressed) { | ||
return this._sec1Compressed | ||
} | ||
let xStr = bigInt(this.publicPoint.x).toString(16) | ||
while (xStr.length < 64) { | ||
xStr = '0' + xStr | ||
} | ||
return this._sec1Compressed = `${bigInt(this.publicPoint.y).isOdd() ? '03' : '02'}${xStr}` | ||
} | ||
get sec1Uncompressed() { | ||
if (this._sec1Uncompressed) { | ||
return this._sec1Uncompressed | ||
} | ||
let yStr = bigInt(this.publicPoint.y).toString(16) | ||
while (yStr.length < 64) { | ||
yStr = '0' + yStr | ||
} | ||
let xStr = bigInt(this.publicPoint.x).toString(16) | ||
while (xStr.length < 64) { | ||
xStr = '0' + xStr | ||
} | ||
return this._sec1Uncompressed = `04${xStr}${yStr}` | ||
} | ||
get address() { | ||
if (this._address) { | ||
return this._address | ||
} | ||
const formatted = `00${binRipemd160(binSha256(this.sec1Uncompressed))}` | ||
const checksum = binSha256(binSha256(formatted)).substr(0, 8) | ||
return this._address = base58.encode(Buffer.from(`${formatted}${checksum}`, 'hex')) | ||
} | ||
get compressAddress() { | ||
if (this._compressAddress) { | ||
return this._compressAddress | ||
} | ||
const formatted = `00${binRipemd160(binSha256(this.sec1Compressed))}` | ||
const checksum = binSha256(binSha256(formatted)).substr(0, 8) | ||
return this._compressAddress = base58.encode(Buffer.from(`${formatted}${checksum}`, 'hex')) | ||
} | ||
} | ||
module.exports = Identity |
try { | ||
const bigInt = require('big-integer') | ||
const ecdsa = require('../src/index.js') | ||
const Wallet = ecdsa.Wallet | ||
const Identity = require('../src/index.js') | ||
;(() => { | ||
@@ -37,83 +36,90 @@ const privateToPublic = { | ||
for (privateKey in privateToPublic) { | ||
const wallet = Wallet.fromKey(privateKey) | ||
if (privateToPublic[privateKey].pub !== wallet.sec1Compressed) { | ||
throw `private key failed to make public ${privateKey} ${privateToPublic[privateKey].pub} ${wallet.address}` | ||
const identity = Identity.fromKey(privateKey) | ||
if (privateToPublic[privateKey].pub !== identity.sec1Compressed) { | ||
throw `private key failed to make public ${privateKey} ${privateToPublic[privateKey].pub} ${identity.address}` | ||
} | ||
} | ||
console.log('✅ retrieving identity from private key passed') | ||
})() | ||
const privateToPublic = { | ||
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf':'1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm', | ||
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreJQmdp3Y':'1KFLggHezq3kiAtpaKMeQPcnoWsXuZHRyn', | ||
'5J1F7GHadZG3sCCKHCwg8Jvys9xUbFsjLnGec4H125Ny1V9nR6V':'16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM', | ||
'5HwoXVkHoRM8sL2KmNRS217n1g8mPPBomrY7yehCuXC1115WWsh':'1MsHWS1BnwMc3tLE8G35UXsS58fKipzB7a' | ||
} | ||
for (const private of Object.keys(privateToPublic)) { | ||
let wallet = Wallet.fromWif(private) | ||
// console.log('key', wallet.key) | ||
// console.log('sec1 (compressed)',wallet.sec1Compressed) | ||
// console.log('sec1 (uncompressed)',wallet.sec1Uncompressed) | ||
// console.log('wif',wallet.wif) | ||
// console.log('address',wallet.address) | ||
// console.log('compressAddress',wallet.compressAddress) | ||
if (wallet.address !== privateToPublic[private]) { | ||
throw 'invalid public key generation '+privateToPublic[private] | ||
;(()=>{ | ||
const privateToPublic = { | ||
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf':'1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm', | ||
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreJQmdp3Y':'1KFLggHezq3kiAtpaKMeQPcnoWsXuZHRyn', | ||
'5J1F7GHadZG3sCCKHCwg8Jvys9xUbFsjLnGec4H125Ny1V9nR6V':'16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM', | ||
'5HwoXVkHoRM8sL2KmNRS217n1g8mPPBomrY7yehCuXC1115WWsh':'1MsHWS1BnwMc3tLE8G35UXsS58fKipzB7a' | ||
} | ||
} | ||
for (const private of Object.keys(privateToPublic)) { | ||
let identity = Identity.fromWif(private) | ||
// console.log('key', identity.key) | ||
// console.log('sec1 (compressed)',identity.sec1Compressed) | ||
// console.log('sec1 (uncompressed)',identity.sec1Uncompressed) | ||
// console.log('wif',identity.wif) | ||
// console.log('address',identity.address) | ||
// console.log('compressAddress',identity.compressAddress) | ||
if (identity.address !== privateToPublic[private]) { | ||
throw 'invalid public key generation '+privateToPublic[private] | ||
} | ||
} | ||
console.log('✅ retrieving identity from wif passed') | ||
})() | ||
;(() => { | ||
let wallet = Wallet.fromKey('82ef796afbce6e67bcb6bc44d922e5d2e664ebe118c0ed5b6ce3b481a638ec90') | ||
const signature = wallet.sign('test', '2900c9abe4a9d00b2a4aa6663d8f4989c8cac35f4fe9b2c5b66e07a3903e1c3') | ||
const identity = Identity.fromKey('82ef796afbce6e67bcb6bc44d922e5d2e664ebe118c0ed5b6ce3b481a638ec90') | ||
const signature = identity.sign('test', '2900c9abe4a9d00b2a4aa6663d8f4989c8cac35f4fe9b2c5b66e07a3903e1c3') | ||
const bip66Sig = identity.bip66Sign('test') | ||
if (signature.r.toString(16) !== '85a44b824bda975b15ac77a3256c5d6f21c19b0412eb19333844fc2dbd25dbba') { | ||
throw 'invalid signature r value' | ||
} | ||
// console.log('bip66',bip66.encode(new Buffer(signature.r.toString(16), 'hex'), new Buffer(signature.s.toString(16), 'hex'))) | ||
// if (signature.s.toString(16) !== '79a1e1e6c94d3cc0388d8659cf7e7fbc6d6e03a1a19446258d19b82071b95c7d') { | ||
// throw 'invalid signature s value' | ||
// } | ||
if (wallet.verify('test', signature) !== true) { | ||
if (identity.verify('test', signature) !== true) { | ||
throw 'signing or verification failure' | ||
} | ||
if (wallet.verifyBip66('test', wallet.bip66Sign('test')) !== true) { | ||
if (identity.verifyBip66('test', bip66Sig) !== true) { | ||
throw 'bip 66 signing or verification failure' | ||
} | ||
if (wallet.verifyBip66(`${Math.random()}`, wallet.bip66Sign('test')) !== false) { | ||
throw 'falsifiable bip 66 wallet verification' | ||
if (identity.verifyBip66(`${Math.random()}`, bip66Sig) !== false) { | ||
throw 'falsifiable bip 66 identity verification' | ||
} | ||
if (wallet.verify(`${Math.random()}`, signature) !== false) { | ||
if (identity.verify(`${Math.random()}`, signature) !== false) { | ||
throw 'falsifiable verification' | ||
} | ||
if (Wallet.new().verify('test', signature) !== false) { | ||
throw 'falsifiable wallet verification' | ||
if (Identity.new().verify('test', signature) !== false) { | ||
throw 'falsifiable identity verification' | ||
} | ||
})() | ||
let i = 0 | ||
while (i < 3) { | ||
const message = `${Math.random()}` | ||
let wallet = Wallet.new() | ||
const signature = wallet.sign(message) | ||
if (wallet.verify(message, signature) !== true) { | ||
throw 'signing or verification failure' | ||
;(()=>{ | ||
let i = 0 | ||
while (i < 2) { | ||
const message = `${Math.random()}` | ||
const identity = Identity.new() | ||
const signature = identity.sign(message) | ||
const bip66Sig = identity.bip66Sign(message) | ||
if (identity.verify(message, signature) !== true) { | ||
throw 'signing or verification failure' | ||
} | ||
if (identity.verifyBip66(message, bip66Sig) !== true) { | ||
throw 'bip 66 signing or verification failure' | ||
} | ||
if (identity.verifyBip66(`${Math.random()}`, bip66Sig) !== false) { | ||
throw 'falsifiable bip 66 identity verification' | ||
} | ||
if (identity.verify(`${Math.random()}`, signature) !== false) { | ||
throw 'falsifiable verification' | ||
} | ||
if (Identity.new().verify(message, signature) !== false) { | ||
throw 'falsifiable identity verification' | ||
} | ||
i++ | ||
} | ||
if (wallet.verifyBip66(message, wallet.bip66Sign(message)) !== true) { | ||
throw 'bip 66 signing or verification failure' | ||
} | ||
if (wallet.verifyBip66(`${Math.random()}`, wallet.bip66Sign(message)) !== false) { | ||
throw 'falsifiable bip 66 wallet verification' | ||
} | ||
if (wallet.verify(`${Math.random()}`, signature) !== false) { | ||
throw 'falsifiable verification' | ||
} | ||
if (Wallet.new().verify(message, signature) !== false) { | ||
throw 'falsifiable wallet verification' | ||
} | ||
i++ | ||
} | ||
console.log('✅ wallet tests passed') | ||
console.log('✅ signing and verification passed') | ||
})() | ||
} | ||
catch (e) { | ||
console.log('⚠️ failed to test wallets', e) | ||
console.log('⚠️ failed to test identitys', e) | ||
throw e | ||
} |
33315
8
881