Skip to content

Commit 30e8b57

Browse files
committed
Added deterministic signing and added a public key class. Note that getPub should now be replaced with getPub().export('bytes')
1 parent 02a8db1 commit 30e8b57

File tree

7 files changed

+131
-121
lines changed

7 files changed

+131
-121
lines changed

bitcoinjs-min.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bip32.js

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
var Script = require('./script'),
22
util = require('./util'),
33
conv = require('./convert'),
4-
key = require('./eckey'),
4+
ECKey = require('./eckey').ECKey,
5+
ECPubKey = require('./eckey').ECPubKey,
56
base58 = require('./base58'),
67
Crypto = require('./crypto-js/crypto'),
78
ECPointFp = require('./jsbn/ec').ECPointFp,
@@ -48,7 +49,8 @@ BIP32key.prototype.deserialize = function(str) {
4849
fingerprint: bytes.slice(5,9),
4950
i: util.bytesToNum(bytes.slice(9,13).reverse()),
5051
chaincode: bytes.slice(13,45),
51-
key: new key(type == 'priv' ? bytes.slice(46,78).concat([1]) : bytes.slice(45,78))
52+
key: type == 'priv' ? new ECKey(bytes.slice(46,78).concat([1]),true)
53+
: new ECPubKey(bytes.slice(45,78))
5254
})
5355
}
5456

@@ -59,7 +61,7 @@ BIP32key.prototype.serialize = function() {
5961
util.numToBytes(this.i,4).reverse(),
6062
this.chaincode,
6163
this.type == 'priv' ? [0].concat(this.key.export('bytes').slice(0,32))
62-
: this.key)
64+
: this.key.export('bytes'))
6365
var checksum = Crypto.SHA256(Crypto.SHA256(bytes,{asBytes: true}), {asBytes: true})
6466
.slice(0,4)
6567
return base58.encode(bytes.concat(checksum))
@@ -69,9 +71,9 @@ BIP32key.prototype.ckd = function(i) {
6971
var priv, pub, newkey, fingerprint, blob, I;
7072
if (this.type == 'priv') {
7173
priv = this.key.export('bytes')
72-
pub = this.key.getPub()
74+
pub = this.key.getPub().export('bytes')
7375
}
74-
else pub = this.key
76+
else pub = this.key.export('bytes')
7577

7678
if (i >= 2147483648) {
7779
if (this.priv) throw new Error("Can't do private derivation on public key!")
@@ -82,16 +84,12 @@ BIP32key.prototype.ckd = function(i) {
8284
I = Crypto.HMAC(Crypto.SHA512,blob,this.chaincode,{ asBytes: true })
8385

8486
if (this.type == 'priv') {
85-
Ikey = Bitcoin.BigInteger.fromByteArrayUnsigned(I.slice(0,32))
86-
newkey = new key(this.key.priv.add(Ikey))
87-
newkey.compressed = true
88-
fingerprint = util.sha256ripe160(this.key.getPub()).slice(0,4)
87+
newkey = this.key.add(ECKey(I.slice(0,32).concat([1])))
88+
fingerprint = util.sha256ripe160(this.key.getPub().export('bytes')).slice(0,4)
8989
}
9090
else {
91-
newkey = ECPointFp.decodeFrom(ecparams.getCurve(),this.key)
92-
.add(new key(I.slice(0,32).concat([1])).getPubPoint())
93-
.getEncoded(true);
94-
fingerprint = util.sha256ripe160(this.key).slice(0,4)
91+
newkey = this.key.add(ECKey(I.slice(0,32).concat([1])).getPub());
92+
fingerprint = util.sha256ripe160(this.key.export('bytes')).slice(0,4)
9593
}
9694
return new BIP32key({
9795
vbytes: this.vbytes,
@@ -130,7 +128,7 @@ BIP32key.prototype.fromMasterKey = function(seed) {
130128
fingerprint: [0,0,0,0],
131129
i: 0,
132130
chaincode: I.slice(32),
133-
key: new key(I.slice(0,32).concat([1]))
131+
key: new ECKey(I.slice(0,32).concat([1]),true)
134132
})
135133
}
136134

src/ecdsa.js

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ var sec = require('./jsbn/sec');
22
var util = require('./util');
33
var SecureRandom = require('./jsbn/rng');
44
var BigInteger = require('./jsbn/jsbn');
5+
var conv = require('./convert')
56

67
var ECPointFp = require('./jsbn/ec').ECPointFp;
78

@@ -36,6 +37,19 @@ function implShamirsTrick(P, k, Q, l)
3637
return R;
3738
};
3839

40+
function deterministicGenerateK(hash,key) {
41+
var v = [];
42+
var k = [];
43+
for (var i = 0;i < 32;i++) v.push(1);
44+
for (var i = 0;i < 32;i++) k.push(0);
45+
k = Bitcoin.Crypto.HMAC(Bitcoin.Crypto.SHA256,v.concat([0]).concat(key).concat(hash),k,{ asBytes: true })
46+
v = Bitcoin.Crypto.HMAC(Bitcoin.Crypto.SHA256,v,k,{ asBytes: true })
47+
k = Bitcoin.Crypto.HMAC(Bitcoin.Crypto.SHA256,v.concat([1]).concat(key).concat(hash),k,{ asBytes: true })
48+
v = Bitcoin.Crypto.HMAC(Bitcoin.Crypto.SHA256,v,k,{ asBytes: true })
49+
v = Bitcoin.Crypto.HMAC(Bitcoin.Crypto.SHA256,v,k,{ asBytes: true })
50+
return Bitcoin.BigInteger.fromByteArrayUnsigned(v);
51+
}
52+
3953
var ECDSA = {
4054
getBigRandom: function (limit) {
4155
return new BigInteger(limit.bitLength(), rng)
@@ -48,12 +62,10 @@ var ECDSA = {
4862
var n = ecparams.getN();
4963
var e = BigInteger.fromByteArrayUnsigned(hash);
5064

51-
do {
52-
var k = ECDSA.getBigRandom(n);
53-
var G = ecparams.getG();
54-
var Q = G.multiply(k);
55-
var r = Q.getX().toBigInteger().mod(n);
56-
} while (r.compareTo(BigInteger.ZERO) <= 0);
65+
var k = deterministicGenerateK(hash,priv.toByteArrayUnsigned())
66+
var G = ecparams.getG();
67+
var Q = G.multiply(k);
68+
var r = Q.getX().toBigInteger().mod(n);
5769

5870
var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n);
5971

@@ -258,10 +270,8 @@ var ECDSA = {
258270

259271
// TODO (shtylman) this is stupid because this file and eckey
260272
// have circular dependencies
261-
var ECKey = require('./eckey');
262-
var pubKey = ECKey();
263-
pubKey.pub = Q;
264-
return pubKey;
273+
var ECPubKey = require('./eckey').ECPubKey;
274+
return ECPubKey(Q);
265275
},
266276

267277
/**

src/eckey.js

Lines changed: 88 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -11,70 +11,46 @@ var ECPointFp = require('./jsbn/ec').ECPointFp;
1111
var ecparams = sec("secp256k1");
1212

1313
// input can be nothing, array of bytes, hex string, or base58 string
14-
var ECKey = function (input) {
15-
if (!(this instanceof ECKey)) {
16-
return new ECKey(input);
17-
}
18-
19-
this.compressed = !!ECKey.compressByDefault;
20-
14+
var ECKey = function (input,compressed) {
15+
if (!(this instanceof ECKey)) { return new ECKey(input); }
2116
if (!input) {
2217
// Generate new key
2318
var n = ecparams.getN();
2419
this.priv = ecdsa.getBigRandom(n);
20+
this.compressed = compressed || false;
2521
}
26-
else this.import(input)
27-
};
28-
29-
/**
30-
* Whether public keys should be returned compressed by default.
31-
*/
32-
ECKey.compressByDefault = false;
33-
34-
/**
35-
* Set whether the public key should be returned compressed or not.
36-
*/
37-
ECKey.prototype.setCompressed = function (v) {
38-
this.compressed = !!v;
22+
else this.import(input,compressed)
3923
};
4024

41-
/**
42-
* Return public key in DER encoding.
43-
*/
44-
ECKey.prototype.getPub = function () {
45-
return this.getPubPoint().getEncoded(this.compressed);
25+
ECKey.prototype.import = function (input,compressed) {
26+
function has(li,v) { return li.indexOf(v) >= 0 }
27+
function fromBin(x) { return BigInteger.fromByteArrayUnsigned(x) }
28+
this.priv =
29+
input instanceof ECKey ? input.priv
30+
: input instanceof BigInteger ? input.mod(ecparams.getN())
31+
: util.isArray(input) ? fromBin(input.slice(0,32))
32+
: typeof input != "string" ? null
33+
: input.length == 51 && input[0] == '5' ? fromBin(base58.checkDecode(input))
34+
: input.length == 52 && has('LK',input[0]) ? fromBin(base58.checkDecode(input))
35+
: has([64,65],input.length) ? fromBin(conv.hexToBytes(input.slice(0,64)))
36+
: null
37+
38+
this.compressed =
39+
arguments.length > 1 ? compressed
40+
: input instanceof ECKey ? input.compressed
41+
: input instanceof BigInteger ? false
42+
: util.isArray(input) ? false
43+
: typeof input != "string" ? null
44+
: input.length == 51 && input[0] == '5' ? false
45+
: input.length == 52 && has('LK',input[0]) ? true
46+
: input.length == 64 ? false
47+
: input.length == 65 ? true
48+
: null
4649
};
4750

48-
/**
49-
* Return public point as ECPoint object.
50-
*/
51-
ECKey.prototype.getPubPoint = function () {
52-
if (!this.pub) this.pub = ecparams.getG().multiply(this.priv);
53-
return this.pub;
54-
};
55-
56-
/**
57-
* Get the pubKeyHash for this key.
58-
*
59-
* This is calculated as RIPE160(SHA256([encoded pubkey])) and returned as
60-
* a byte array.
61-
*/
62-
ECKey.prototype.getPubKeyHash = function () {
63-
if (this.pubKeyHash) return this.pubKeyHash;
64-
return this.pubKeyHash = util.sha256ripe160(this.getPub());
65-
};
66-
67-
ECKey.prototype.getBitcoinAddress = function (version) {
68-
var hash = this.getPubKeyHash();
69-
var addr = new Address(hash,version);
70-
return addr;
71-
};
72-
73-
ECKey.prototype.setPub = function (pub) {
74-
this.pub = ECPointFp.decodeFrom(ecparams.getCurve(), pub);
75-
this.compressed = (pub[0] < 4)
76-
return this
77-
};
51+
ECKey.prototype.getPub = function() {
52+
return ECPubKey(ecparams.getG().multiply(this.priv),this.compressed)
53+
}
7854

7955
ECKey.prototype.export = function (format) {
8056
var bytes = this.priv.toByteArrayUnsigned();
@@ -86,12 +62,66 @@ ECKey.prototype.export = function (format) {
8662
: format === "hex" ? conv.bytesToHex(bytes)
8763
: bytes
8864
};
89-
ECKey.prototype.getExportedPrivateKey = ECKey.prototype.export;
9065

9166
ECKey.prototype.toString = function (format) {
9267
return ''+this.export(format)
9368
}
9469

70+
ECKey.prototype.getBitcoinAddress = function(v) {
71+
return this.getPub().getBitcoinAddress(v)
72+
}
73+
74+
ECKey.prototype.add = function(key) {
75+
return ECKey(this.priv.add(ECKey(key).priv),this.compressed)
76+
}
77+
78+
ECKey.prototype.multiply = function(key) {
79+
return ECKey(this.priv.multiply(ECKey(key).priv),this.compressed)
80+
}
81+
82+
var ECPubKey = function(input,compressed) {
83+
84+
if (!(this instanceof ECPubKey)) { return new ECPubKey(input,compressed); }
85+
86+
var decode = function(x) { return ECPointFp.decodeFrom(ecparams.getCurve(), x) }
87+
this.pub =
88+
input instanceof ECPointFp ? input
89+
: input instanceof ECKey ? ecparams.getG().multiply(input.priv)
90+
: input instanceof ECPubKey ? input.pub
91+
: typeof input == "string" ? decode(conv.hexToBytes(input))
92+
: util.isArray(input) ? decode(input)
93+
: ecparams.getG().multiply(ecdsa.getBigRandom(ecparams.getN()))
94+
95+
this.compressed =
96+
arguments.length > 1 ? compressed
97+
: input instanceof ECPointFp ? input.compressed
98+
: input instanceof ECPubKey ? input.compressed
99+
: (this.pub[0] < 4)
100+
101+
}
102+
103+
ECPubKey.prototype.add = function(key) {
104+
return ECPubKey(this.pub.add(ECPubKey(key).pub),this.compressed)
105+
}
106+
107+
ECPubKey.prototype.multiply = function(key) {
108+
return ECPubKey(this.pub.multiply(ECKey(key).priv),this.compressed)
109+
}
110+
111+
ECPubKey.prototype.export = function(formt) {
112+
var o = this.pub.getEncoded(this.compressed)
113+
return formt == 'hex' ? conv.bytesToHex(o) : o;
114+
}
115+
116+
ECPubKey.prototype.toString = function (format) {
117+
return ''+this.export(format)
118+
}
119+
120+
ECPubKey.prototype.getBitcoinAddress = function(v) {
121+
return new Address(util.sha256ripe160(this.export()),version);
122+
}
123+
124+
95125
ECKey.prototype.sign = function (hash) {
96126
return ecdsa.sign(hash, this.priv);
97127
};
@@ -103,38 +133,6 @@ ECKey.prototype.verify = function (hash, sig) {
103133
/**
104134
* Parse an exported private key contained in a string.
105135
*/
106-
ECKey.prototype.import = function (input) {
107-
if (input instanceof ECKey) {
108-
this.priv = input.priv;
109-
this.compressed = input.compressed;
110-
}
111-
else if (input instanceof BigInteger) {
112-
// Input is a private key value
113-
this.priv = input;
114-
this.compressed = ECKey.compressByDefault;
115-
}
116-
else if (util.isArray(input)) {
117-
// Prepend zero byte to prevent interpretation as negative integer
118-
this.priv = BigInteger.fromByteArrayUnsigned(input.slice(0,32));
119-
this.compressed = (input.length == 33);
120-
}
121-
else if ("string" == typeof input) {
122-
if (input.length == 51 && input[0] == '5') {
123-
// Base58 encoded private key
124-
this.priv = BigInteger.fromByteArrayUnsigned(base58.checkDecode(input));
125-
this.compressed = false;
126-
}
127-
else if (input.length == 52 && (input[0] === 'K' || input[0] === 'L')) {
128-
// Base58 encoded private key
129-
this.priv = BigInteger.fromByteArrayUnsigned(base58.checkDecode(input));
130-
this.compressed = true;
131-
}
132-
else if (input.length >= 64) {
133-
// hex string?
134-
this.priv = BigInteger.fromByteArrayUnsigned(conv.hexToBytes(input.slice(0,64)));
135-
this.compressed = (input.length == 66)
136-
}
137-
}
138-
};
139136

140-
module.exports = ECKey;
137+
138+
module.exports = { ECKey: ECKey, ECPubKey: ECPubKey };

src/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ var endian = function (n) {
2222
return n;
2323
}
2424

25+
var Key = require('./eckey');
26+
2527
module.exports = {
2628
Address: require('./address'),
27-
Key: require('./eckey'),
29+
Key: Key.ECKey,
30+
ECKey: Key.ECKey,
31+
ECPubKey: Key.ECPubKey,
2832
Message: require('./message'),
2933
BigInteger: require('./jsbn/jsbn'),
3034
Crypto: require('./crypto-js/crypto'),

src/transaction.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ var util = require('./util');
44
var conv = require('./convert');
55
var Crypto = require('./crypto-js/crypto');
66
var Wallet = require('./wallet');
7-
var ECKey = require('./eckey');
7+
var ECKey = require('./eckey').ECKey;
88
var ECDSA = require('./ecdsa');
99
var Address = require('./address');
1010

@@ -467,7 +467,7 @@ Transaction.deserialize = function(buffer) {
467467
Transaction.prototype.sign = function(index, key, type) {
468468
type = type || SIGHASH_ALL;
469469
key = new ECKey(key);
470-
var pub = key.getPub(),
470+
var pub = key.getPub().export('bytes'),
471471
hash160 = util.sha256ripe160(pub),
472472
script = Script.createOutputScript(new Address(hash160)),
473473
hash = this.hashTransactionForSignature( script, index, type),

src/wallet.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
var Script = require('./script');
2-
var ECKey = require('./eckey');
2+
var ECKey = require('./eckey').ECKey;
33
var conv = require('./convert');
44
var util = require('./util');
55

0 commit comments

Comments
 (0)