Browse Source

Added deterministic signing and added a public key class. Note that getPub should now be replaced with getPub().export('bytes')

hk-custom-address
Vitalik Buterin 11 years ago
parent
commit
30e8b572d6
  1. 6
      bitcoinjs-min.js
  2. 26
      src/bip32.js
  3. 30
      src/ecdsa.js
  4. 178
      src/eckey.js
  5. 6
      src/index.js
  6. 4
      src/transaction.js
  7. 2
      src/wallet.js

6
bitcoinjs-min.js

File diff suppressed because one or more lines are too long

26
src/bip32.js

@ -1,7 +1,8 @@
var Script = require('./script'),
util = require('./util'),
conv = require('./convert'),
key = require('./eckey'),
ECKey = require('./eckey').ECKey,
ECPubKey = require('./eckey').ECPubKey,
base58 = require('./base58'),
Crypto = require('./crypto-js/crypto'),
ECPointFp = require('./jsbn/ec').ECPointFp,
@ -48,7 +49,8 @@ BIP32key.prototype.deserialize = function(str) {
fingerprint: bytes.slice(5,9),
i: util.bytesToNum(bytes.slice(9,13).reverse()),
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(),
this.chaincode,
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})
.slice(0,4)
return base58.encode(bytes.concat(checksum))
@ -69,9 +71,9 @@ BIP32key.prototype.ckd = function(i) {
var priv, pub, newkey, fingerprint, blob, I;
if (this.type == 'priv') {
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 (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 })
if (this.type == 'priv') {
Ikey = Bitcoin.BigInteger.fromByteArrayUnsigned(I.slice(0,32))
newkey = new key(this.key.priv.add(Ikey))
newkey.compressed = true
fingerprint = util.sha256ripe160(this.key.getPub()).slice(0,4)
newkey = this.key.add(ECKey(I.slice(0,32).concat([1])))
fingerprint = util.sha256ripe160(this.key.getPub().export('bytes')).slice(0,4)
}
else {
newkey = ECPointFp.decodeFrom(ecparams.getCurve(),this.key)
.add(new key(I.slice(0,32).concat([1])).getPubPoint())
.getEncoded(true);
fingerprint = util.sha256ripe160(this.key).slice(0,4)
newkey = this.key.add(ECKey(I.slice(0,32).concat([1])).getPub());
fingerprint = util.sha256ripe160(this.key.export('bytes')).slice(0,4)
}
return new BIP32key({
vbytes: this.vbytes,
@ -130,7 +128,7 @@ BIP32key.prototype.fromMasterKey = function(seed) {
fingerprint: [0,0,0,0],
i: 0,
chaincode: I.slice(32),
key: new key(I.slice(0,32).concat([1]))
key: new ECKey(I.slice(0,32).concat([1]),true)
})
}

30
src/ecdsa.js

@ -2,6 +2,7 @@ var sec = require('./jsbn/sec');
var util = require('./util');
var SecureRandom = require('./jsbn/rng');
var BigInteger = require('./jsbn/jsbn');
var conv = require('./convert')
var ECPointFp = require('./jsbn/ec').ECPointFp;
@ -36,6 +37,19 @@ function implShamirsTrick(P, k, Q, l)
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 = {
getBigRandom: function (limit) {
return new BigInteger(limit.bitLength(), rng)
@ -48,12 +62,10 @@ var ECDSA = {
var n = ecparams.getN();
var e = BigInteger.fromByteArrayUnsigned(hash);
do {
var k = ECDSA.getBigRandom(n);
var G = ecparams.getG();
var Q = G.multiply(k);
var r = Q.getX().toBigInteger().mod(n);
} while (r.compareTo(BigInteger.ZERO) <= 0);
var k = deterministicGenerateK(hash,priv.toByteArrayUnsigned())
var G = ecparams.getG();
var Q = G.multiply(k);
var r = Q.getX().toBigInteger().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
// have circular dependencies
var ECKey = require('./eckey');
var pubKey = ECKey();
pubKey.pub = Q;
return pubKey;
var ECPubKey = require('./eckey').ECPubKey;
return ECPubKey(Q);
},
/**

178
src/eckey.js

@ -11,70 +11,46 @@ var ECPointFp = require('./jsbn/ec').ECPointFp;
var ecparams = sec("secp256k1");
// input can be nothing, array of bytes, hex string, or base58 string
var ECKey = function (input) {
if (!(this instanceof ECKey)) {
return new ECKey(input);
}
this.compressed = !!ECKey.compressByDefault;
var ECKey = function (input,compressed) {
if (!(this instanceof ECKey)) { return new ECKey(input); }
if (!input) {
// Generate new key
var n = ecparams.getN();
this.priv = ecdsa.getBigRandom(n);
this.compressed = compressed || false;
}
else this.import(input)
};
/**
* Whether public keys should be returned compressed by default.
*/
ECKey.compressByDefault = false;
/**
* Set whether the public key should be returned compressed or not.
*/
ECKey.prototype.setCompressed = function (v) {
this.compressed = !!v;
else this.import(input,compressed)
};
/**
* Return public key in DER encoding.
*/
ECKey.prototype.getPub = function () {
return this.getPubPoint().getEncoded(this.compressed);
ECKey.prototype.import = function (input,compressed) {
function has(li,v) { return li.indexOf(v) >= 0 }
function fromBin(x) { return BigInteger.fromByteArrayUnsigned(x) }
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 =
arguments.length > 1 ? compressed
: input instanceof ECKey ? input.compressed
: input instanceof BigInteger ? false
: 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 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.getPub = function() {
return ECPubKey(ecparams.getG().multiply(this.priv),this.compressed)
}
ECKey.prototype.export = function (format) {
var bytes = this.priv.toByteArrayUnsigned();
@ -86,12 +62,66 @@ ECKey.prototype.export = function (format) {
: format === "hex" ? conv.bytesToHex(bytes)
: bytes
};
ECKey.prototype.getExportedPrivateKey = ECKey.prototype.export;
ECKey.prototype.toString = function (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) {
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.
*/
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 };

6
src/index.js

@ -22,9 +22,13 @@ var endian = function (n) {
return n;
}
var Key = require('./eckey');
module.exports = {
Address: require('./address'),
Key: require('./eckey'),
Key: Key.ECKey,
ECKey: Key.ECKey,
ECPubKey: Key.ECPubKey,
Message: require('./message'),
BigInteger: require('./jsbn/jsbn'),
Crypto: require('./crypto-js/crypto'),

4
src/transaction.js

@ -4,7 +4,7 @@ var util = require('./util');
var conv = require('./convert');
var Crypto = require('./crypto-js/crypto');
var Wallet = require('./wallet');
var ECKey = require('./eckey');
var ECKey = require('./eckey').ECKey;
var ECDSA = require('./ecdsa');
var Address = require('./address');
@ -467,7 +467,7 @@ Transaction.deserialize = function(buffer) {
Transaction.prototype.sign = function(index, key, type) {
type = type || SIGHASH_ALL;
key = new ECKey(key);
var pub = key.getPub(),
var pub = key.getPub().export('bytes'),
hash160 = util.sha256ripe160(pub),
script = Script.createOutputScript(new Address(hash160)),
hash = this.hashTransactionForSignature( script, index, type),

2
src/wallet.js

@ -1,5 +1,5 @@
var Script = require('./script');
var ECKey = require('./eckey');
var ECKey = require('./eckey').ECKey;
var conv = require('./convert');
var util = require('./util');

Loading…
Cancel
Save