Skip to content
Merged
43 changes: 14 additions & 29 deletions src/eckey.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,22 @@ function ECKey(D, compressed) {
}

// Static constructors
ECKey.fromBuffer = function(buffer, compressed) {
assert(Buffer.isBuffer(buffer), 'First argument must be a Buffer')
assert.strictEqual(buffer.length, 32, 'Invalid buffer length')

var D = BigInteger.fromBuffer(buffer)
return new ECKey(D, compressed)
}

ECKey.fromHex = function(hex, compressed) {
return ECKey.fromBuffer(new Buffer(hex, 'hex'), compressed)
}

ECKey.fromWIF = function(string) {
var decode = base58check.decode(string)

var payload = decode.payload
var compressed = false

if (payload.length === 33) {
assert.strictEqual(payload[32], 0x01, 'Invalid WIF string')

return ECKey.fromBuffer(payload.slice(0, 32), true)
payload = payload.slice(0, -1)
compressed = true
}

return ECKey.fromBuffer(payload, false)
assert.equal(payload.length, 32, 'Invalid WIF payload length')

var D = BigInteger.fromBuffer(payload.slice(0, 32))
return new ECKey(D, compressed)
}

ECKey.makeRandom = function(compressed, rng) {
Expand All @@ -56,29 +49,21 @@ ECKey.makeRandom = function(compressed, rng) {
return new ECKey(D, compressed)
}

// Operations
ECKey.prototype.sign = function(hash) {
return ecdsa.sign(hash, this.D)
}

// Export functions
ECKey.prototype.toBuffer = function() {
return this.D.toBuffer(32)
}

ECKey.prototype.toHex = function() {
return this.toBuffer().toString('hex')
}

ECKey.prototype.toWIF = function(version) {
version = version || networks.bitcoin.wif

var buffer = this.toBuffer()
var buffer = this.D.toBuffer(32)
if (this.pub.compressed) {
buffer = Buffer.concat([buffer, new Buffer([0x01])])
}

return base58check.encode(buffer, version)
}

// Operations
ECKey.prototype.sign = function(hash) {
return ecdsa.sign(hash, this.D)
}

module.exports = ECKey
11 changes: 8 additions & 3 deletions src/hdwallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ function HDWallet(seed, networkString) {
var IR = I.slice(32)

// In case IL is 0 or >= n, the master key is invalid (handled by ECKey.fromBuffer)
this.priv = ECKey.fromBuffer(IL, true)
var pIL = BigInteger.fromBuffer(IL)

this.priv = new ECKey(pIL, true)
this.pub = this.priv.pub

this.chaincode = IR
Expand Down Expand Up @@ -103,7 +105,10 @@ HDWallet.fromBuffer = function(input) {
// 33 bytes: the public key or private key data (0x02 + X or 0x03 + X for
// public keys, 0x00 + k for private keys)
if (type == 'priv') {
hd.priv = ECKey.fromBuffer(input.slice(46, 78), true)
assert.equal(input.readUInt8(45), 0, 'Invalid private key')
var D = BigInteger.fromBuffer(input.slice(46, 78))

hd.priv = new ECKey(D, true)
hd.pub = hd.priv.pub
} else {
hd.pub = ECPubKey.fromBuffer(input.slice(45, 78), true)
Expand Down Expand Up @@ -153,7 +158,7 @@ HDWallet.prototype.toBuffer = function(priv) {

// 0x00 + k for private keys
buffer.writeUInt8(0, 45)
this.priv.toBuffer().copy(buffer, 46)
this.priv.D.toBuffer(32).copy(buffer, 46)
} else {

// X9.62 encoding for public keys
Expand Down
31 changes: 18 additions & 13 deletions src/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,27 @@ var Address = require('./address')
var bufferutils = require('./bufferutils')
var crypto = require('./crypto')
var ecdsa = require('./ecdsa')
var ECPubKey = require('./ecpubkey')
var networks = require('./networks')

// FIXME: incompatible with other networks (Litecoin etc)
var MAGIC_PREFIX = new Buffer('\x18Bitcoin Signed Message:\n')
var Address = require('./address')
var ECPubKey = require('./ecpubkey')

function magicHash(message) {
function magicHash(message, network) {
var magicPrefix = new Buffer(network.magicPrefix)
var messageBuffer = new Buffer(message)
var lengthBuffer = new Buffer(bufferutils.varIntSize(messageBuffer.length))
bufferutils.writeVarInt(lengthBuffer, messageBuffer.length, 0)

var buffer = Buffer.concat([
MAGIC_PREFIX, lengthBuffer, messageBuffer
magicPrefix, lengthBuffer, messageBuffer
])
return crypto.hash256(buffer)
}

// TODO: parameterize compression instead of using ECKey.compressed
function sign(key, message) {
var hash = magicHash(message)
function sign(key, message, network) {
network = network || networks.bitcoin

var hash = magicHash(message, network)
var sig = ecdsa.parseSig(key.sign(hash))
var i = ecdsa.calcPubKeyRecoveryParam(key.pub.Q, sig.r, sig.s, hash)

Expand All @@ -36,17 +38,20 @@ function sign(key, message) {
return Buffer.concat([new Buffer([i]), rB, sB], 65)
}

// FIXME: stricter API?
function verify(address, sig, message) {
// TODO: network could be implied from address
function verify(address, compactSig, message, network) {
if (typeof address === 'string') {
address = Address.fromBase58Check(address)
}

sig = ecdsa.parseSigCompact(sig)
network = network || networks.bitcoin

var pubKey = new ECPubKey(ecdsa.recoverPubKey(sig.r, sig.s, magicHash(message), sig.i))
pubKey.compressed = !!(sig.i & 4)
var hash = magicHash(message, network)
var sig = ecdsa.parseSigCompact(compactSig)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not related to this particular PR, it occurs to me that ecdsa maybe not be the best place to host parseSigCompact because

  1. It is not used else where other than message sig verification. And I don't know how useful it is to expose it as a publicly accessible API.
  2. Sitting side by side with parseSig it is just confusing.

Maybe consider moving it into message.js and make it a internal function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its a valid concern, but I'm not sure where to put it. I settled with the status-quo after trying to figure out where to put ecdsa.serializeSigCompact.
I don't mind, but it should be noted it is a custom format specifically for Bitcoin.

var Q = ecdsa.recoverPubKey(sig.r, sig.s, hash, sig.i)
var compressed = !!(sig.i & 4)

var pubKey = new ECPubKey(Q, compressed)
return pubKey.getAddress(address.version).toString() === address.toString()
}

Expand Down
6 changes: 5 additions & 1 deletion src/networks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731
module.exports = {
bitcoin: {
magicPrefix: '\x18Bitcoin Signed Message:\n',
bip32: {
pub: 0x0488b21e,
priv: 0x0488ade4
Expand All @@ -11,6 +12,7 @@ module.exports = {
wif: 0x80
},
dogecoin: {
magicPrefix: '\x19Dogecoin Signed Message:\n',
bip32: {
pub: 0x02facafd,
priv: 0x02fac398
Expand All @@ -20,6 +22,7 @@ module.exports = {
wif: 0x9e
},
litecoin: {
magicPrefix: '\x19Litecoin Signed Message:\n',
bip32: {
pub: 0x019da462,
priv: 0x019d9cfe
Expand All @@ -29,6 +32,7 @@ module.exports = {
wif: 0xb0
},
testnet: {
magicPrefix: '\x18Bitcoin Signed Message:\n',
bip32: {
pub: 0x043587cf,
priv: 0x04358394
Expand All @@ -37,4 +41,4 @@ module.exports = {
scriptHash: 0xc4,
wif: 0xef
}
}
}
19 changes: 12 additions & 7 deletions test/ecdsa.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
var assert = require('assert')
var crypto = require('../src/crypto')
var ecdsa = require('../src/ecdsa')
var message = require('../src/message')
var networks = require('../src/networks')

var sec = require('../src/sec')
var ecparams = sec("secp256k1")

var BigInteger = require('bigi')
var ECKey = require('../src/eckey')
var ECPubKey = require('../src/ecpubkey')
var Message = require('../src/message')

var fixtures = require('./fixtures/ecdsa.js')

describe('ecdsa', function() {
describe('deterministicGenerateK', function() {
it('matches the test vectors', function() {
fixtures.forEach(function(f) {
var priv = BigInteger.fromHex(f.privateKey)
var priv = BigInteger.fromHex(f.D)
var h1 = crypto.sha256(f.message)

var k = ecdsa.deterministicGenerateK(h1, priv)
Expand All @@ -27,10 +28,12 @@ describe('ecdsa', function() {

describe('recoverPubKey', function() {
it('succesfully recovers a public key', function() {
var addr = 'mgQK8S6CfSXKjPmnujArSmVxafeJfrZsa3'
var signature = new Buffer('H0PG6+PUo96UPTJ/DVj8aBU5it+Nuli4YdsLuTMvfJxoHH9Jb7jYTQXCCOX2jrTChD5S1ic3vCrUQHdmB5/sEQY=', 'base64')

var obj = ecdsa.parseSigCompact(signature)
var pubKey = new ECPubKey(ecdsa.recoverPubKey(obj.r, obj.s, Message.magicHash('1111'), obj.i))
var hash = message.magicHash('1111', networks.bitcoin)

var pubKey = new ECPubKey(ecdsa.recoverPubKey(obj.r, obj.s, hash, obj.i))

assert.equal(pubKey.toHex(), '02e8fcf4d749b35879bc1f3b14b49e67ab7301da3558c5a9b74a54f1e6339c334c')
})
Expand All @@ -39,7 +42,8 @@ describe('ecdsa', function() {
describe('sign', function() {
it('matches the test vectors', function() {
fixtures.forEach(function(f) {
var priv = ECKey.fromHex(f.privateKey)
var D = BigInteger.fromHex(f.D)
var priv = new ECKey(D)
var hash = crypto.sha256(f.message)
var sig = ecdsa.parseSig(priv.sign(hash))

Expand All @@ -49,7 +53,7 @@ describe('ecdsa', function() {
})

it('should sign with low S value', function() {
var priv = ECKey.fromHex('ca48ec9783cf3ad0dfeff1fc254395a2e403cbbc666477b61b45e31d3b8ab458')
var priv = ECKey.makeRandom()
var hash = crypto.sha256('Vires in numeris')
var signature = priv.sign(hash)
var psig = ecdsa.parseSig(signature)
Expand All @@ -62,7 +66,8 @@ describe('ecdsa', function() {
describe('verifyRaw', function() {
it('matches the test vectors', function() {
fixtures.forEach(function(f) {
var priv = ECKey.fromHex(f.privateKey)
var D = BigInteger.fromHex(f.D)
var priv = new ECKey(D)

var r = BigInteger.fromHex(f.signature.slice(0, 64))
var s = BigInteger.fromHex(f.signature.slice(64))
Expand Down
Loading