Added deterministic signing and added a public key class. Note that getPub should now be replaced with getPub().export('bytes')
This commit is contained in:
parent
02a8db111b
commit
30e8b572d6
7 changed files with 130 additions and 120 deletions
6
bitcoinjs-min.js
vendored
6
bitcoinjs-min.js
vendored
File diff suppressed because one or more lines are too long
26
src/bip32.js
26
src/bip32.js
|
@ -1,7 +1,8 @@
|
||||||
var Script = require('./script'),
|
var Script = require('./script'),
|
||||||
util = require('./util'),
|
util = require('./util'),
|
||||||
conv = require('./convert'),
|
conv = require('./convert'),
|
||||||
key = require('./eckey'),
|
ECKey = require('./eckey').ECKey,
|
||||||
|
ECPubKey = require('./eckey').ECPubKey,
|
||||||
base58 = require('./base58'),
|
base58 = require('./base58'),
|
||||||
Crypto = require('./crypto-js/crypto'),
|
Crypto = require('./crypto-js/crypto'),
|
||||||
ECPointFp = require('./jsbn/ec').ECPointFp,
|
ECPointFp = require('./jsbn/ec').ECPointFp,
|
||||||
|
@ -48,7 +49,8 @@ BIP32key.prototype.deserialize = function(str) {
|
||||||
fingerprint: bytes.slice(5,9),
|
fingerprint: bytes.slice(5,9),
|
||||||
i: util.bytesToNum(bytes.slice(9,13).reverse()),
|
i: util.bytesToNum(bytes.slice(9,13).reverse()),
|
||||||
chaincode: bytes.slice(13,45),
|
chaincode: bytes.slice(13,45),
|
||||||
key: new key(type == 'priv' ? bytes.slice(46,78).concat([1]) : bytes.slice(45,78))
|
key: type == 'priv' ? new ECKey(bytes.slice(46,78).concat([1]),true)
|
||||||
|
: new ECPubKey(bytes.slice(45,78))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +61,7 @@ BIP32key.prototype.serialize = function() {
|
||||||
util.numToBytes(this.i,4).reverse(),
|
util.numToBytes(this.i,4).reverse(),
|
||||||
this.chaincode,
|
this.chaincode,
|
||||||
this.type == 'priv' ? [0].concat(this.key.export('bytes').slice(0,32))
|
this.type == 'priv' ? [0].concat(this.key.export('bytes').slice(0,32))
|
||||||
: this.key)
|
: this.key.export('bytes'))
|
||||||
var checksum = Crypto.SHA256(Crypto.SHA256(bytes,{asBytes: true}), {asBytes: true})
|
var checksum = Crypto.SHA256(Crypto.SHA256(bytes,{asBytes: true}), {asBytes: true})
|
||||||
.slice(0,4)
|
.slice(0,4)
|
||||||
return base58.encode(bytes.concat(checksum))
|
return base58.encode(bytes.concat(checksum))
|
||||||
|
@ -69,9 +71,9 @@ BIP32key.prototype.ckd = function(i) {
|
||||||
var priv, pub, newkey, fingerprint, blob, I;
|
var priv, pub, newkey, fingerprint, blob, I;
|
||||||
if (this.type == 'priv') {
|
if (this.type == 'priv') {
|
||||||
priv = this.key.export('bytes')
|
priv = this.key.export('bytes')
|
||||||
pub = this.key.getPub()
|
pub = this.key.getPub().export('bytes')
|
||||||
}
|
}
|
||||||
else pub = this.key
|
else pub = this.key.export('bytes')
|
||||||
|
|
||||||
if (i >= 2147483648) {
|
if (i >= 2147483648) {
|
||||||
if (this.priv) throw new Error("Can't do private derivation on public key!")
|
if (this.priv) throw new Error("Can't do private derivation on public key!")
|
||||||
|
@ -82,16 +84,12 @@ BIP32key.prototype.ckd = function(i) {
|
||||||
I = Crypto.HMAC(Crypto.SHA512,blob,this.chaincode,{ asBytes: true })
|
I = Crypto.HMAC(Crypto.SHA512,blob,this.chaincode,{ asBytes: true })
|
||||||
|
|
||||||
if (this.type == 'priv') {
|
if (this.type == 'priv') {
|
||||||
Ikey = Bitcoin.BigInteger.fromByteArrayUnsigned(I.slice(0,32))
|
newkey = this.key.add(ECKey(I.slice(0,32).concat([1])))
|
||||||
newkey = new key(this.key.priv.add(Ikey))
|
fingerprint = util.sha256ripe160(this.key.getPub().export('bytes')).slice(0,4)
|
||||||
newkey.compressed = true
|
|
||||||
fingerprint = util.sha256ripe160(this.key.getPub()).slice(0,4)
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
newkey = ECPointFp.decodeFrom(ecparams.getCurve(),this.key)
|
newkey = this.key.add(ECKey(I.slice(0,32).concat([1])).getPub());
|
||||||
.add(new key(I.slice(0,32).concat([1])).getPubPoint())
|
fingerprint = util.sha256ripe160(this.key.export('bytes')).slice(0,4)
|
||||||
.getEncoded(true);
|
|
||||||
fingerprint = util.sha256ripe160(this.key).slice(0,4)
|
|
||||||
}
|
}
|
||||||
return new BIP32key({
|
return new BIP32key({
|
||||||
vbytes: this.vbytes,
|
vbytes: this.vbytes,
|
||||||
|
@ -130,7 +128,7 @@ BIP32key.prototype.fromMasterKey = function(seed) {
|
||||||
fingerprint: [0,0,0,0],
|
fingerprint: [0,0,0,0],
|
||||||
i: 0,
|
i: 0,
|
||||||
chaincode: I.slice(32),
|
chaincode: I.slice(32),
|
||||||
key: new key(I.slice(0,32).concat([1]))
|
key: new ECKey(I.slice(0,32).concat([1]),true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
24
src/ecdsa.js
24
src/ecdsa.js
|
@ -2,6 +2,7 @@ var sec = require('./jsbn/sec');
|
||||||
var util = require('./util');
|
var util = require('./util');
|
||||||
var SecureRandom = require('./jsbn/rng');
|
var SecureRandom = require('./jsbn/rng');
|
||||||
var BigInteger = require('./jsbn/jsbn');
|
var BigInteger = require('./jsbn/jsbn');
|
||||||
|
var conv = require('./convert')
|
||||||
|
|
||||||
var ECPointFp = require('./jsbn/ec').ECPointFp;
|
var ECPointFp = require('./jsbn/ec').ECPointFp;
|
||||||
|
|
||||||
|
@ -36,6 +37,19 @@ function implShamirsTrick(P, k, Q, l)
|
||||||
return R;
|
return R;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function deterministicGenerateK(hash,key) {
|
||||||
|
var v = [];
|
||||||
|
var k = [];
|
||||||
|
for (var i = 0;i < 32;i++) v.push(1);
|
||||||
|
for (var i = 0;i < 32;i++) k.push(0);
|
||||||
|
k = Bitcoin.Crypto.HMAC(Bitcoin.Crypto.SHA256,v.concat([0]).concat(key).concat(hash),k,{ asBytes: true })
|
||||||
|
v = Bitcoin.Crypto.HMAC(Bitcoin.Crypto.SHA256,v,k,{ asBytes: true })
|
||||||
|
k = Bitcoin.Crypto.HMAC(Bitcoin.Crypto.SHA256,v.concat([1]).concat(key).concat(hash),k,{ asBytes: true })
|
||||||
|
v = Bitcoin.Crypto.HMAC(Bitcoin.Crypto.SHA256,v,k,{ asBytes: true })
|
||||||
|
v = Bitcoin.Crypto.HMAC(Bitcoin.Crypto.SHA256,v,k,{ asBytes: true })
|
||||||
|
return Bitcoin.BigInteger.fromByteArrayUnsigned(v);
|
||||||
|
}
|
||||||
|
|
||||||
var ECDSA = {
|
var ECDSA = {
|
||||||
getBigRandom: function (limit) {
|
getBigRandom: function (limit) {
|
||||||
return new BigInteger(limit.bitLength(), rng)
|
return new BigInteger(limit.bitLength(), rng)
|
||||||
|
@ -48,12 +62,10 @@ var ECDSA = {
|
||||||
var n = ecparams.getN();
|
var n = ecparams.getN();
|
||||||
var e = BigInteger.fromByteArrayUnsigned(hash);
|
var e = BigInteger.fromByteArrayUnsigned(hash);
|
||||||
|
|
||||||
do {
|
var k = deterministicGenerateK(hash,priv.toByteArrayUnsigned())
|
||||||
var k = ECDSA.getBigRandom(n);
|
|
||||||
var G = ecparams.getG();
|
var G = ecparams.getG();
|
||||||
var Q = G.multiply(k);
|
var Q = G.multiply(k);
|
||||||
var r = Q.getX().toBigInteger().mod(n);
|
var r = Q.getX().toBigInteger().mod(n);
|
||||||
} while (r.compareTo(BigInteger.ZERO) <= 0);
|
|
||||||
|
|
||||||
var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n);
|
var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n);
|
||||||
|
|
||||||
|
@ -258,10 +270,8 @@ var ECDSA = {
|
||||||
|
|
||||||
// TODO (shtylman) this is stupid because this file and eckey
|
// TODO (shtylman) this is stupid because this file and eckey
|
||||||
// have circular dependencies
|
// have circular dependencies
|
||||||
var ECKey = require('./eckey');
|
var ECPubKey = require('./eckey').ECPubKey;
|
||||||
var pubKey = ECKey();
|
return ECPubKey(Q);
|
||||||
pubKey.pub = Q;
|
|
||||||
return pubKey;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
174
src/eckey.js
174
src/eckey.js
|
@ -11,70 +11,46 @@ var ECPointFp = require('./jsbn/ec').ECPointFp;
|
||||||
var ecparams = sec("secp256k1");
|
var ecparams = sec("secp256k1");
|
||||||
|
|
||||||
// input can be nothing, array of bytes, hex string, or base58 string
|
// input can be nothing, array of bytes, hex string, or base58 string
|
||||||
var ECKey = function (input) {
|
var ECKey = function (input,compressed) {
|
||||||
if (!(this instanceof ECKey)) {
|
if (!(this instanceof ECKey)) { return new ECKey(input); }
|
||||||
return new ECKey(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.compressed = !!ECKey.compressByDefault;
|
|
||||||
|
|
||||||
if (!input) {
|
if (!input) {
|
||||||
// Generate new key
|
// Generate new key
|
||||||
var n = ecparams.getN();
|
var n = ecparams.getN();
|
||||||
this.priv = ecdsa.getBigRandom(n);
|
this.priv = ecdsa.getBigRandom(n);
|
||||||
|
this.compressed = compressed || false;
|
||||||
}
|
}
|
||||||
else this.import(input)
|
else this.import(input,compressed)
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
ECKey.prototype.import = function (input,compressed) {
|
||||||
* Whether public keys should be returned compressed by default.
|
function has(li,v) { return li.indexOf(v) >= 0 }
|
||||||
*/
|
function fromBin(x) { return BigInteger.fromByteArrayUnsigned(x) }
|
||||||
ECKey.compressByDefault = false;
|
this.priv =
|
||||||
|
input instanceof ECKey ? input.priv
|
||||||
|
: input instanceof BigInteger ? input.mod(ecparams.getN())
|
||||||
|
: util.isArray(input) ? fromBin(input.slice(0,32))
|
||||||
|
: typeof input != "string" ? null
|
||||||
|
: input.length == 51 && input[0] == '5' ? fromBin(base58.checkDecode(input))
|
||||||
|
: input.length == 52 && has('LK',input[0]) ? fromBin(base58.checkDecode(input))
|
||||||
|
: has([64,65],input.length) ? fromBin(conv.hexToBytes(input.slice(0,64)))
|
||||||
|
: null
|
||||||
|
|
||||||
/**
|
this.compressed =
|
||||||
* Set whether the public key should be returned compressed or not.
|
arguments.length > 1 ? compressed
|
||||||
*/
|
: input instanceof ECKey ? input.compressed
|
||||||
ECKey.prototype.setCompressed = function (v) {
|
: input instanceof BigInteger ? false
|
||||||
this.compressed = !!v;
|
: util.isArray(input) ? false
|
||||||
|
: typeof input != "string" ? null
|
||||||
|
: input.length == 51 && input[0] == '5' ? false
|
||||||
|
: input.length == 52 && has('LK',input[0]) ? true
|
||||||
|
: input.length == 64 ? false
|
||||||
|
: input.length == 65 ? true
|
||||||
|
: null
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Return public key in DER encoding.
|
|
||||||
*/
|
|
||||||
ECKey.prototype.getPub = function() {
|
ECKey.prototype.getPub = function() {
|
||||||
return this.getPubPoint().getEncoded(this.compressed);
|
return ECPubKey(ecparams.getG().multiply(this.priv),this.compressed)
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return public point as ECPoint object.
|
|
||||||
*/
|
|
||||||
ECKey.prototype.getPubPoint = function () {
|
|
||||||
if (!this.pub) this.pub = ecparams.getG().multiply(this.priv);
|
|
||||||
return this.pub;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the pubKeyHash for this key.
|
|
||||||
*
|
|
||||||
* This is calculated as RIPE160(SHA256([encoded pubkey])) and returned as
|
|
||||||
* a byte array.
|
|
||||||
*/
|
|
||||||
ECKey.prototype.getPubKeyHash = function () {
|
|
||||||
if (this.pubKeyHash) return this.pubKeyHash;
|
|
||||||
return this.pubKeyHash = util.sha256ripe160(this.getPub());
|
|
||||||
};
|
|
||||||
|
|
||||||
ECKey.prototype.getBitcoinAddress = function (version) {
|
|
||||||
var hash = this.getPubKeyHash();
|
|
||||||
var addr = new Address(hash,version);
|
|
||||||
return addr;
|
|
||||||
};
|
|
||||||
|
|
||||||
ECKey.prototype.setPub = function (pub) {
|
|
||||||
this.pub = ECPointFp.decodeFrom(ecparams.getCurve(), pub);
|
|
||||||
this.compressed = (pub[0] < 4)
|
|
||||||
return this
|
|
||||||
};
|
|
||||||
|
|
||||||
ECKey.prototype.export = function (format) {
|
ECKey.prototype.export = function (format) {
|
||||||
var bytes = this.priv.toByteArrayUnsigned();
|
var bytes = this.priv.toByteArrayUnsigned();
|
||||||
|
@ -86,12 +62,66 @@ ECKey.prototype.export = function (format) {
|
||||||
: format === "hex" ? conv.bytesToHex(bytes)
|
: format === "hex" ? conv.bytesToHex(bytes)
|
||||||
: bytes
|
: bytes
|
||||||
};
|
};
|
||||||
ECKey.prototype.getExportedPrivateKey = ECKey.prototype.export;
|
|
||||||
|
|
||||||
ECKey.prototype.toString = function (format) {
|
ECKey.prototype.toString = function (format) {
|
||||||
return ''+this.export(format)
|
return ''+this.export(format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ECKey.prototype.getBitcoinAddress = function(v) {
|
||||||
|
return this.getPub().getBitcoinAddress(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
ECKey.prototype.add = function(key) {
|
||||||
|
return ECKey(this.priv.add(ECKey(key).priv),this.compressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
ECKey.prototype.multiply = function(key) {
|
||||||
|
return ECKey(this.priv.multiply(ECKey(key).priv),this.compressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ECPubKey = function(input,compressed) {
|
||||||
|
|
||||||
|
if (!(this instanceof ECPubKey)) { return new ECPubKey(input,compressed); }
|
||||||
|
|
||||||
|
var decode = function(x) { return ECPointFp.decodeFrom(ecparams.getCurve(), x) }
|
||||||
|
this.pub =
|
||||||
|
input instanceof ECPointFp ? input
|
||||||
|
: input instanceof ECKey ? ecparams.getG().multiply(input.priv)
|
||||||
|
: input instanceof ECPubKey ? input.pub
|
||||||
|
: typeof input == "string" ? decode(conv.hexToBytes(input))
|
||||||
|
: util.isArray(input) ? decode(input)
|
||||||
|
: ecparams.getG().multiply(ecdsa.getBigRandom(ecparams.getN()))
|
||||||
|
|
||||||
|
this.compressed =
|
||||||
|
arguments.length > 1 ? compressed
|
||||||
|
: input instanceof ECPointFp ? input.compressed
|
||||||
|
: input instanceof ECPubKey ? input.compressed
|
||||||
|
: (this.pub[0] < 4)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ECPubKey.prototype.add = function(key) {
|
||||||
|
return ECPubKey(this.pub.add(ECPubKey(key).pub),this.compressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
ECPubKey.prototype.multiply = function(key) {
|
||||||
|
return ECPubKey(this.pub.multiply(ECKey(key).priv),this.compressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
ECPubKey.prototype.export = function(formt) {
|
||||||
|
var o = this.pub.getEncoded(this.compressed)
|
||||||
|
return formt == 'hex' ? conv.bytesToHex(o) : o;
|
||||||
|
}
|
||||||
|
|
||||||
|
ECPubKey.prototype.toString = function (format) {
|
||||||
|
return ''+this.export(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
ECPubKey.prototype.getBitcoinAddress = function(v) {
|
||||||
|
return new Address(util.sha256ripe160(this.export()),version);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ECKey.prototype.sign = function (hash) {
|
ECKey.prototype.sign = function (hash) {
|
||||||
return ecdsa.sign(hash, this.priv);
|
return ecdsa.sign(hash, this.priv);
|
||||||
};
|
};
|
||||||
|
@ -103,38 +133,6 @@ ECKey.prototype.verify = function (hash, sig) {
|
||||||
/**
|
/**
|
||||||
* Parse an exported private key contained in a string.
|
* Parse an exported private key contained in a string.
|
||||||
*/
|
*/
|
||||||
ECKey.prototype.import = function (input) {
|
|
||||||
if (input instanceof ECKey) {
|
|
||||||
this.priv = input.priv;
|
|
||||||
this.compressed = input.compressed;
|
|
||||||
}
|
|
||||||
else if (input instanceof BigInteger) {
|
|
||||||
// Input is a private key value
|
|
||||||
this.priv = input;
|
|
||||||
this.compressed = ECKey.compressByDefault;
|
|
||||||
}
|
|
||||||
else if (util.isArray(input)) {
|
|
||||||
// Prepend zero byte to prevent interpretation as negative integer
|
|
||||||
this.priv = BigInteger.fromByteArrayUnsigned(input.slice(0,32));
|
|
||||||
this.compressed = (input.length == 33);
|
|
||||||
}
|
|
||||||
else if ("string" == typeof input) {
|
|
||||||
if (input.length == 51 && input[0] == '5') {
|
|
||||||
// Base58 encoded private key
|
|
||||||
this.priv = BigInteger.fromByteArrayUnsigned(base58.checkDecode(input));
|
|
||||||
this.compressed = false;
|
|
||||||
}
|
|
||||||
else if (input.length == 52 && (input[0] === 'K' || input[0] === 'L')) {
|
|
||||||
// Base58 encoded private key
|
|
||||||
this.priv = BigInteger.fromByteArrayUnsigned(base58.checkDecode(input));
|
|
||||||
this.compressed = true;
|
|
||||||
}
|
|
||||||
else if (input.length >= 64) {
|
|
||||||
// hex string?
|
|
||||||
this.priv = BigInteger.fromByteArrayUnsigned(conv.hexToBytes(input.slice(0,64)));
|
|
||||||
this.compressed = (input.length == 66)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = ECKey;
|
|
||||||
|
module.exports = { ECKey: ECKey, ECPubKey: ECPubKey };
|
||||||
|
|
|
@ -22,9 +22,13 @@ var endian = function (n) {
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var Key = require('./eckey');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Address: require('./address'),
|
Address: require('./address'),
|
||||||
Key: require('./eckey'),
|
Key: Key.ECKey,
|
||||||
|
ECKey: Key.ECKey,
|
||||||
|
ECPubKey: Key.ECPubKey,
|
||||||
Message: require('./message'),
|
Message: require('./message'),
|
||||||
BigInteger: require('./jsbn/jsbn'),
|
BigInteger: require('./jsbn/jsbn'),
|
||||||
Crypto: require('./crypto-js/crypto'),
|
Crypto: require('./crypto-js/crypto'),
|
||||||
|
|
|
@ -4,7 +4,7 @@ var util = require('./util');
|
||||||
var conv = require('./convert');
|
var conv = require('./convert');
|
||||||
var Crypto = require('./crypto-js/crypto');
|
var Crypto = require('./crypto-js/crypto');
|
||||||
var Wallet = require('./wallet');
|
var Wallet = require('./wallet');
|
||||||
var ECKey = require('./eckey');
|
var ECKey = require('./eckey').ECKey;
|
||||||
var ECDSA = require('./ecdsa');
|
var ECDSA = require('./ecdsa');
|
||||||
var Address = require('./address');
|
var Address = require('./address');
|
||||||
|
|
||||||
|
@ -467,7 +467,7 @@ Transaction.deserialize = function(buffer) {
|
||||||
Transaction.prototype.sign = function(index, key, type) {
|
Transaction.prototype.sign = function(index, key, type) {
|
||||||
type = type || SIGHASH_ALL;
|
type = type || SIGHASH_ALL;
|
||||||
key = new ECKey(key);
|
key = new ECKey(key);
|
||||||
var pub = key.getPub(),
|
var pub = key.getPub().export('bytes'),
|
||||||
hash160 = util.sha256ripe160(pub),
|
hash160 = util.sha256ripe160(pub),
|
||||||
script = Script.createOutputScript(new Address(hash160)),
|
script = Script.createOutputScript(new Address(hash160)),
|
||||||
hash = this.hashTransactionForSignature( script, index, type),
|
hash = this.hashTransactionForSignature( script, index, type),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
var Script = require('./script');
|
var Script = require('./script');
|
||||||
var ECKey = require('./eckey');
|
var ECKey = require('./eckey').ECKey;
|
||||||
var conv = require('./convert');
|
var conv = require('./convert');
|
||||||
var util = require('./util');
|
var util = require('./util');
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue