diff --git a/package.json b/package.json index 4e30f28..f754cd4 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,10 @@ "main": "lib/index.js", "scripts": { "prepublish": "npm run build_lib && npm run docs", - "build_lib": "node_modules/babel-cli/bin/babel.js src --out-dir lib", "docs": "npm run docs_common", "docs_common": "npm run doc -- readme src/api_common.js --section \"Common API\" --shallow", "doc": "node_modules/documentation/bin/documentation.js", + "build_lib": "node_modules/babel-cli/bin/babel.js src --out-dir lib", "build": "mkdir -p dist && browserify -o dist/eosjs-ecc.js -s eosjs_ecc src/index.js && sha256sum dist/eosjs-ecc.js" }, "repository": { diff --git a/src/aes.js b/src/aes.js index 69888b0..31ed62e 100644 --- a/src/aes.js +++ b/src/aes.js @@ -78,7 +78,7 @@ function crypt(private_key, public_key, nonce, message, checksum) { // D E B U G // console.log('crypt', { - // priv_to_pub: private_key.toPublicKey().toString(), + // priv_to_pub: private_key.toPublic().toString(), // pub: public_key.toString(), // nonce: nonce.toString(), // message: message.length, diff --git a/src/ecdsa.js b/src/ecdsa.js index e09b134..84453ef 100644 --- a/src/ecdsa.js +++ b/src/ecdsa.js @@ -91,7 +91,7 @@ function sign(curve, hash, d, nonce) { s = n.subtract(s) } - return new ECSignature(r, s) + return ECSignature(r, s) } function verifyRaw(curve, e, signature, Q) { diff --git a/src/ecsignature.js b/src/ecsignature.js index 48b73d5..53ec280 100644 --- a/src/ecsignature.js +++ b/src/ecsignature.js @@ -7,8 +7,47 @@ function ECSignature(r, s) { enforceType(BigInteger, r) enforceType(BigInteger, s) - this.r = r - this.s = s + function toCompact(i, compressed) { + if (compressed) i += 4 + i += 27 + + var buffer = new Buffer(65) + buffer.writeUInt8(i, 0) + + r.toBuffer(32).copy(buffer, 1) + s.toBuffer(32).copy(buffer, 33) + + return buffer + } + + function toDER() { + var rBa = r.toDERInteger() + var sBa = s.toDERInteger() + + var sequence = [] + + // INTEGER + sequence.push(0x02, rBa.length) + sequence = sequence.concat(rBa) + + // INTEGER + sequence.push(0x02, sBa.length) + sequence = sequence.concat(sBa) + + // SEQUENCE + sequence.unshift(0x30, sequence.length) + + return new Buffer(sequence) + } + + function toScriptSignature(hashType) { + var hashTypeBuffer = new Buffer(1) + hashTypeBuffer.writeUInt8(hashType, 0) + + return Buffer.concat([toDER(), hashTypeBuffer]) + } + + return {r, s, toCompact, toDER, toScriptSignature} } // Import operations @@ -29,7 +68,7 @@ ECSignature.parseCompact = function(buffer) { return { compressed: compressed, i: i, - signature: new ECSignature(r, s) + signature: ECSignature(r, s) } } @@ -66,7 +105,7 @@ ECSignature.fromDER = function(buffer) { assert(r.signum() >= 0, 'R value is negative') assert(s.signum() >= 0, 'S value is negative') - return new ECSignature(r, s) + return ECSignature(r, s) } // FIXME: 0x00, 0x04, 0x80 are SIGHASH_* boundary constants, importing Transaction causes a circular dependency @@ -82,45 +121,4 @@ ECSignature.parseScriptSignature = function(buffer) { } } -// Export operations -ECSignature.prototype.toCompact = function(i, compressed) { - if (compressed) i += 4 - i += 27 - - var buffer = new Buffer(65) - buffer.writeUInt8(i, 0) - - this.r.toBuffer(32).copy(buffer, 1) - this.s.toBuffer(32).copy(buffer, 33) - - return buffer -} - -ECSignature.prototype.toDER = function() { - var rBa = this.r.toDERInteger() - var sBa = this.s.toDERInteger() - - var sequence = [] - - // INTEGER - sequence.push(0x02, rBa.length) - sequence = sequence.concat(rBa) - - // INTEGER - sequence.push(0x02, sBa.length) - sequence = sequence.concat(sBa) - - // SEQUENCE - sequence.unshift(0x30, sequence.length) - - return new Buffer(sequence) -} - -ECSignature.prototype.toScriptSignature = function(hashType) { - var hashTypeBuffer = new Buffer(1) - hashTypeBuffer.writeUInt8(hashType, 0) - - return Buffer.concat([this.toDER(), hashTypeBuffer]) -} - module.exports = ECSignature diff --git a/src/key_private.js b/src/key_private.js index f190f4d..30193a3 100644 --- a/src/key_private.js +++ b/src/key_private.js @@ -11,73 +11,18 @@ const keyUtils = require('./key_utils'); const G = secp256k1.G const n = secp256k1.n -class PrivateKey { - - /** - @private see static functions - @param {BigInteger} - */ - constructor(d) { this.d = d; } - - static fromBuffer(buf) { - if (!Buffer.isBuffer(buf)) { - throw new Error("Expecting parameter to be a Buffer type"); - } - if (32 !== buf.length) { - console.log(`WARN: Expecting 32 bytes, instead got ${buf.length}, stack trace:`, new Error().stack); - } - if (buf.length === 0) { - throw new Error("Empty buffer"); - } - return new PrivateKey(BigInteger.fromBuffer(buf)); - } - - /** @arg {string} seed - any length string. This is private, the same seed produces the same private key every time. */ - static fromSeed(seed) { // generate_private_key - if (!(typeof seed === 'string')) { - throw new Error('seed must be of type string'); - } - return PrivateKey.fromBuffer(hash.sha256(seed)); - } - - static isWif(text) { - try { - this.fromWif(text) - return true - } catch(e) { - return false - } - } - - /** - @throws {AssertError|Error} parsing key - @return {string} Wallet Import Format (still a secret, Not encrypted) - */ - static fromWif(_private_wif) { - var private_wif = new Buffer(base58.decode(_private_wif)); - var version = private_wif.readUInt8(0); - assert.equal(0x80, version, `Expected version ${0x80}, instead got ${version}`); - // checksum includes the version - var private_key = private_wif.slice(0, -4); - var checksum = private_wif.slice(-4); - var new_checksum = hash.sha256(private_key); - new_checksum = hash.sha256(new_checksum); - new_checksum = new_checksum.slice(0, 4); - if (checksum.toString() !== new_checksum.toString()) - throw new Error('Invalid WIF key (checksum miss-match), ' + - `${checksum.toString('hex')} != ${new_checksum.toString('hex')}` - ) - - private_key = private_key.slice(1); - return PrivateKey.fromBuffer(private_key); - } +module.exports = PrivateKey; - static randomKey(cpuEntropyBits) { - return PrivateKey.fromBuffer(keyUtils.random32ByteBuffer({cpuEntropyBits})); +/** + @param {BigInteger} d +*/ +function PrivateKey(d) { + if(!BigInteger.isBigInteger(d)) { + throw new TypeError('BigInteger private key point required') } - toWif() { - var private_key = this.toBuffer(); + function toWif() { + var private_key = toBuffer(); // checksum includes the version private_key = Buffer.concat([new Buffer([0x80]), private_key]); var checksum = hash.sha256(private_key); @@ -87,30 +32,27 @@ class PrivateKey { return base58.encode(private_wif); } - /** Alias for {@link toWif} */ - toString() { - return this.toWif() - } + let public_key; /** @return {Point} */ - toPublicKeyPoint() { - var Q; - return Q = secp256k1.G.multiply(this.d); - } - - toPublic() { - if (this.public_key) { return this.public_key; } - return this.public_key = PublicKey.fromPoint(this.toPublicKeyPoint()); + function toPublic() { + if (public_key) { + // Hundreds of keys can be S L O W in the browser + // cache + return public_key + } + const Q = secp256k1.G.multiply(d); + return public_key = PublicKey.fromPoint(Q); } - toBuffer() { - return this.d.toBuffer(32); + function toBuffer() { + return d.toBuffer(32); } /** ECIES */ - getSharedSecret(public_key) { + function getSharedSecret(public_key) { public_key = toPublic(public_key) let KB = public_key.toUncompressed().toBuffer() let KBP = Point.fromAffine( @@ -118,7 +60,7 @@ class PrivateKey { BigInteger.fromBuffer( KB.slice( 1,33 )), // x BigInteger.fromBuffer( KB.slice( 33,65 )) // y ) - let r = this.toBuffer() + let r = toBuffer() let P = KBP.multiply(BigInteger.fromBuffer(r)) let S = P.affineX.toBuffer({size: 32}) // SHA512 used in ECIES @@ -128,51 +70,104 @@ class PrivateKey { // /** ECIES (does not always match the Point.fromAffine version above) */ // getSharedSecret(public_key){ // public_key = toPublic(public_key) - // var P = public_key.Q.multiply( this.d ); + // var P = public_key.Q.multiply( d ); // var S = P.affineX.toBuffer({size: 32}); // // ECIES, adds an extra sha512 // return hash.sha512(S); // } /** @throws {Error} - overflow of the key could not be derived */ - child( offset ) { - offset = Buffer.concat([ this.toPublicKey().toBuffer(), offset ]) + function child( offset ) { + offset = Buffer.concat([ toPublic().toBuffer(), offset ]) offset = hash.sha256( offset ) let c = BigInteger.fromBuffer(offset) if (c.compareTo(n) >= 0) throw new Error("Child offset went out of bounds, try again") - let derived = this.d.add(c)//.mod(n) + let derived = d.add(c)//.mod(n) if( derived.signum() === 0 ) throw new Error("Child offset derived to an invalid key, try again") - return new PrivateKey( derived ) + return PrivateKey( derived ) } - // toByteBuffer() { - // var b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); - // this.appendByteBuffer(b); - // return b.copy(0, b.offset); - // } + function toHex() { + return toBuffer().toString('hex'); + } + + return { + d, + toWif, + toPublic, + toBuffer, + toString: toWif, + getSharedSecret, + child + } +} + +PrivateKey.fromHex = function(hex) { + return PrivateKey.fromBuffer(new Buffer(hex, 'hex')); +} - static fromHex(hex) { - return PrivateKey.fromBuffer(new Buffer(hex, 'hex')); +PrivateKey.fromBuffer = function(buf) { + if (!Buffer.isBuffer(buf)) { + throw new Error("Expecting parameter to be a Buffer type"); + } + if (32 !== buf.length) { + console.log(`WARN: Expecting 32 bytes, instead got ${buf.length}, stack trace:`, new Error().stack); + } + if (buf.length === 0) { + throw new Error("Empty buffer"); } + return PrivateKey(BigInteger.fromBuffer(buf)); +} - toHex() { - return this.toBuffer().toString('hex'); +/** @arg {string} seed - any length string. This is private, the same seed produces the same private key every time. */ +PrivateKey.fromSeed = function(seed) { // generate_private_key + if (!(typeof seed === 'string')) { + throw new Error('seed must be of type string'); } + return PrivateKey.fromBuffer(hash.sha256(seed)); +} - toPublicKey() { - return this.toPublic() +PrivateKey.isWif = function(text) { + try { + PrivateKey.fromWif(text) + return true + } catch(e) { + return false } +} + +/** + @throws {AssertError|Error} parsing key + @return {string} Wallet Import Format (still a secret, Not encrypted) +*/ +PrivateKey.fromWif = function(_private_wif) { + var private_wif = new Buffer(base58.decode(_private_wif)); + var version = private_wif.readUInt8(0); + assert.equal(0x80, version, `Expected version ${0x80}, instead got ${version}`); + // checksum includes the version + var private_key = private_wif.slice(0, -4); + var checksum = private_wif.slice(-4); + var new_checksum = hash.sha256(private_key); + new_checksum = hash.sha256(new_checksum); + new_checksum = new_checksum.slice(0, 4); + if (checksum.toString() !== new_checksum.toString()) + throw new Error('Invalid WIF key (checksum miss-match), ' + + `${checksum.toString('hex')} != ${new_checksum.toString('hex')}` + ) - /* */ + private_key = private_key.slice(1); + return PrivateKey.fromBuffer(private_key); } -module.exports = PrivateKey; +PrivateKey.randomKey = function(cpuEntropyBits) { + return PrivateKey.fromBuffer(keyUtils.random32ByteBuffer({cpuEntropyBits})); +} const toPublic = data => data == null ? data : data.Q ? data : PublicKey.fromStringOrThrow(data) diff --git a/src/key_public.js b/src/key_public.js index b45956b..deebf33 100644 --- a/src/key_public.js +++ b/src/key_public.js @@ -9,124 +9,47 @@ const assert = require('assert'); var G = secp256k1.G var n = secp256k1.n -class PublicKey { +module.exports = PublicKey - /** @param {ecurve.Point} public key */ - constructor(Q) { this.Q = Q; } +/** @param {ecurve.Point} public key */ +function PublicKey(Q) { - static fromBinary(bin) { - return PublicKey.fromBuffer(new Buffer(bin, 'binary')); + if(!Q.compressed) { + throw new TypeError('Invalid public key point') } - static fromBuffer(buffer) { - return new PublicKey(ecurve.Point.decodeFrom(secp256k1, buffer)); - } - - toBuffer(compressed = this.Q.compressed) { - return this.Q.getEncoded(compressed); - } - - static fromPoint(point) { - return new PublicKey(point); - } - - toUncompressed() { - var buf = this.Q.getEncoded(false); - var point = ecurve.Point.decodeFrom(secp256k1, buf); - return PublicKey.fromPoint(point); - } - - /** bts::blockchain::address (unique but not a full public key) */ - toBlockchainAddress() { - var pub_buf = this.toBuffer(); - var pub_sha = hash.sha512(pub_buf); - return hash.ripemd160(pub_sha); - } - - toString(address_prefix = config.address_prefix) { - return this.toPublicKeyString(address_prefix) + function toBuffer(compressed = Q.compressed) { + return Q.getEncoded(compressed); } + let pubdata // cache + /** Full public key - {return} string + @return {string} EOSKey.. */ - toPublicKeyString(address_prefix = config.address_prefix) { - if(this.pubdata) return address_prefix + this.pubdata - const pub_buf = this.toBuffer(); + function toString(address_prefix = config.address_prefix) { + if(pubdata) { + return address_prefix + pubdata + } + const pub_buf = toBuffer(); const checksum = hash.ripemd160(pub_buf); const addy = Buffer.concat([pub_buf, checksum.slice(0, 4)]); - this.pubdata = base58.encode(addy) - return address_prefix + this.pubdata; - } - - /** - @arg {string} public_key - like STMXyz... - @arg {string} address_prefix - like STM - @return PublicKey or `null` (if the public_key string is invalid) - @deprecated fromPublicKeyString (use fromString instead) - */ - static fromString(public_key, address_prefix = config.address_prefix) { - try { - return PublicKey.fromStringOrThrow(public_key, address_prefix) - } catch (e) { - return null; - } + pubdata = base58.encode(addy) + return address_prefix + pubdata; } - /** - @arg {string} public_key - like STMXyz... - @arg {string} address_prefix - like STM - @throws {Error} if public key is invalid - @return PublicKey - */ - static fromStringOrThrow(public_key, address_prefix = config.address_prefix) { - var prefix = public_key.slice(0, address_prefix.length); - assert.equal( - address_prefix, prefix, - `Expecting key to begin with ${address_prefix}, instead got ${prefix}`); - public_key = public_key.slice(address_prefix.length); - - public_key = new Buffer(base58.decode(public_key), 'binary'); - var checksum = public_key.slice(-4); - public_key = public_key.slice(0, -4); - var new_checksum = hash.ripemd160(public_key); - new_checksum = new_checksum.slice(0, 4); - assert.deepEqual(checksum, new_checksum, - 'Checksum did not match, ' + - `${checksum.toString('hex')} != ${new_checksum.toString('hex')}` - ); - return PublicKey.fromBuffer(public_key); - } - - toAddressString(address_prefix = config.address_prefix) { - var pub_buf = this.toBuffer(); - var pub_sha = hash.sha512(pub_buf); - var addy = hash.ripemd160(pub_sha); - var checksum = hash.ripemd160(addy); - addy = Buffer.concat([addy, checksum.slice(0, 4)]); - return address_prefix + base58.encode(addy); - } - - toPtsAddy() { - var pub_buf = this.toBuffer(); - var pub_sha = hash.sha256(pub_buf); - var addy = hash.ripemd160(pub_sha); - addy = Buffer.concat([new Buffer([0x38]), addy]); //version 56(decimal) - - var checksum = hash.sha256(addy); - checksum = hash.sha256(checksum); - - addy = Buffer.concat([addy, checksum.slice(0, 4)]); - return base58.encode(addy); + function toUncompressed() { + var buf = Q.getEncoded(false); + var point = ecurve.Point.decodeFrom(secp256k1, buf); + return PublicKey.fromPoint(point); } - child( offset ) { - + function child( offset ) { assert(Buffer.isBuffer(offset), "Buffer required: offset") assert.equal(offset.length, 32, "offset length") - offset = Buffer.concat([ this.toBuffer(), offset ]) + offset = Buffer.concat([ toBuffer(), offset ]) offset = hash.sha256( offset ) let c = BigInteger.fromBuffer( offset ) @@ -136,7 +59,7 @@ class PublicKey { let cG = G.multiply(c) - let Qprime = this.Q.add(cG) + let Qprime = Q.add(cG) if( secp256k1.isInfinity(Qprime) ) throw new Error("Child offset derived to an invalid key, try again") @@ -146,24 +69,79 @@ class PublicKey { // toByteBuffer() { // var b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); - // this.appendByteBuffer(b); + // appendByteBuffer(b); // return b.copy(0, b.offset); // } - static fromHex(hex) { - return PublicKey.fromBuffer(new Buffer(hex, 'hex')); + function toHex() { + return toBuffer().toString('hex'); } - toHex() { - return this.toBuffer().toString('hex'); + return { + Q, + toString, + toUncompressed, + toBuffer, + child, + toHex } +} - static fromStringHex(hex) { - return PublicKey.fromString(new Buffer(hex, 'hex')); +PublicKey.fromBinary = function(bin) { + return PublicKey.fromBuffer(new Buffer(bin, 'binary')); +} + +PublicKey.fromBuffer = function(buffer) { + return PublicKey(ecurve.Point.decodeFrom(secp256k1, buffer)); +} + +PublicKey.fromPoint = function(point) { + return PublicKey(point); +} + +/** + @arg {string} public_key - like STMXyz... + @arg {string} address_prefix - like STM + @return PublicKey or `null` (if the public_key string is invalid) + @deprecated fromPublicKeyString (use fromString instead) +*/ +PublicKey.fromString = function(public_key, address_prefix = config.address_prefix) { + try { + return PublicKey.fromStringOrThrow(public_key, address_prefix) + } catch (e) { + return null; } +} - /* */ +/** + @arg {string} public_key - like STMXyz... + @arg {string} address_prefix - like STM + @throws {Error} if public key is invalid + @return PublicKey +*/ +PublicKey.fromStringOrThrow = function(public_key, address_prefix = config.address_prefix) { + var prefix = public_key.slice(0, address_prefix.length); + assert.equal( + address_prefix, prefix, + `Expecting key to begin with ${address_prefix}, instead got ${prefix}`); + public_key = public_key.slice(address_prefix.length); + + public_key = new Buffer(base58.decode(public_key), 'binary'); + var checksum = public_key.slice(-4); + public_key = public_key.slice(0, -4); + var new_checksum = hash.ripemd160(public_key); + new_checksum = new_checksum.slice(0, 4); + assert.deepEqual(checksum, new_checksum, + 'Checksum did not match, ' + + `${checksum.toString('hex')} != ${new_checksum.toString('hex')}` + ); + return PublicKey.fromBuffer(public_key); } +PublicKey.fromHex = function(hex) { + return PublicKey.fromBuffer(new Buffer(hex, 'hex')); +} -module.exports = PublicKey; +PublicKey.fromStringHex = function(hex) { + return PublicKey.fromString(new Buffer(hex, 'hex')); +} diff --git a/src/signature.js b/src/signature.js index abc7a2a..293393c 100644 --- a/src/signature.js +++ b/src/signature.js @@ -6,148 +6,144 @@ var BigInteger = require('bigi'); var PublicKey = require('./key_public'); var PrivateKey = require('./key_private'); -class Signature { - - constructor(r1, s1, i1) { - this.r = r1; - this.s = s1; - this.i = i1; - assert.equal(this.r != null, true, 'Missing parameter'); - assert.equal(this.s != null, true, 'Missing parameter'); - assert.equal(this.i != null, true, 'Missing parameter'); - } +module.exports = Signature - static fromBuffer(buf) { - var i, r, s; - assert.equal(buf.length, 65, 'Invalid signature length'); - i = buf.readUInt8(0); - assert.equal(i - 27, i - 27 & 7, 'Invalid signature parameter'); - r = BigInteger.fromBuffer(buf.slice(1, 33)); - s = BigInteger.fromBuffer(buf.slice(33)); - return new Signature(r, s, i); - }; +function Signature(r, s, i) { + assert.equal(r != null, true, 'Missing parameter'); + assert.equal(s != null, true, 'Missing parameter'); + assert.equal(i != null, true, 'Missing parameter'); - toBuffer() { + function toBuffer() { var buf; buf = new Buffer(65); - buf.writeUInt8(this.i, 0); - this.r.toBuffer(32).copy(buf, 1); - this.s.toBuffer(32).copy(buf, 33); + buf.writeUInt8(i, 0); + r.toBuffer(32).copy(buf, 1); + s.toBuffer(32).copy(buf, 33); return buf; }; - recoverPublicKeyFromBuffer(buffer) { - return this.recoverPublicKey(hash.sha256(buffer)); + function recoverPublicKeyFromBuffer(buffer) { + return recoverPublicKey(hash.sha256(buffer)); }; /** @return {PublicKey} */ - recoverPublicKey(sha256_buffer) { + function recoverPublicKey(sha256_buffer) { let Q, e, i; e = BigInteger.fromBuffer(sha256_buffer); - i = this.i; + i = i; i -= 27; i = i & 3; Q = ecdsa.recoverPubKey(curve, e, this, i); return PublicKey.fromPoint(Q); }; - - /** - @param {Buffer} buf - @param {PrivateKey} private_key - @return {Signature} - */ - static signBuffer(buf, private_key) { - var _hash = hash.sha256(buf); - return Signature.signBufferSha256(_hash, private_key) - } - - /** Sign a buffer of exactally 32 bytes in size (sha256(text)) - @param {Buffer} buf - 32 bytes binary - @param {PrivateKey} private_key - @return {Signature} - */ - static signBufferSha256(buf_sha256, private_key) { - if( buf_sha256.length !== 32 || ! Buffer.isBuffer(buf_sha256) ) - throw new Error("buf_sha256: 32 byte buffer requred") - private_key = toPrivateObj(private_key) - assert(private_key, 'private_key required') - - var der, e, ecsignature, i, lenR, lenS, nonce; - i = null; - nonce = 0; - e = BigInteger.fromBuffer(buf_sha256); - while (true) { - ecsignature = ecdsa.sign(curve, buf_sha256, private_key.d, nonce++); - der = ecsignature.toDER(); - lenR = der[3]; - lenS = der[5 + lenR]; - if (lenR === 32 && lenS === 32) { - i = ecdsa.calcPubKeyRecoveryParam(curve, e, ecsignature, private_key.toPublicKey().Q); - i += 4; // compressed - i += 27; // compact // 24 or 27 :( forcing odd-y 2nd key candidate) - break; - } - if (nonce % 10 === 0) { - console.log("WARN: " + nonce + " attempts to find canonical signature"); - } - } - return new Signature(ecsignature.r, ecsignature.s, i); - }; - - static sign(string, private_key) { - return Signature.signBuffer(new Buffer(string), private_key); - }; - - /** @param {Buffer} un-hashed @param {./PublicKey} @return {boolean} */ - verifyBuffer(buf, public_key) { + function verifyBuffer(buf, public_key) { var _hash = hash.sha256(buf); - return this.verifyHash(_hash, public_key); + return verifyHash(_hash, public_key); }; - verifyHash(hash, public_key) { + function verifyHash(hash, public_key) { assert.equal(hash.length, 32, "A SHA 256 should be 32 bytes long, instead got " + hash.length); return ecdsa.verify(curve, hash, { - r: this.r, - s: this.s + r: r, + s: s }, public_key.Q); }; - - // toByteBuffer() { - // var b; - // b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); - // this.appendByteBuffer(b); - // return b.copy(0, b.offset); - // }; - - static fromHex(hex) { - return Signature.fromBuffer(new Buffer(hex, "hex")); - }; - - toHex() { - return this.toBuffer().toString("hex"); + function toHex() { + return toBuffer().toString("hex"); }; - static signHex(hex, private_key) { + function verifyHex(hex, public_key) { var buf; buf = new Buffer(hex, 'hex'); - return Signature.signBuffer(buf, private_key); + return verifyBuffer(buf, public_key); }; - verifyHex(hex, public_key) { - var buf; - buf = new Buffer(hex, 'hex'); - return this.verifyBuffer(buf, public_key); - }; + return { + r, s, i, + toBuffer, + recoverPublicKeyFromBuffer, + recoverPublicKey, + verifyBuffer, + verifyHash, + toHex, + verifyHex + } +} +Signature.fromBuffer = function(buf) { + var i, r, s; + assert.equal(buf.length, 65, 'Invalid signature length'); + i = buf.readUInt8(0); + assert.equal(i - 27, i - 27 & 7, 'Invalid signature parameter'); + r = BigInteger.fromBuffer(buf.slice(1, 33)); + s = BigInteger.fromBuffer(buf.slice(33)); + return Signature(r, s, i); +}; + +/** + @param {Buffer} buf + @param {PrivateKey} private_key + @return {Signature} +*/ +Signature.signBuffer = function(buf, private_key) { + var _hash = hash.sha256(buf); + return Signature.signBufferSha256(_hash, private_key) } + +/** Sign a buffer of exactally 32 bytes in size (sha256(text)) + @param {Buffer} buf - 32 bytes binary + @param {PrivateKey} private_key + @return {Signature} +*/ +Signature.signBufferSha256 = function(buf_sha256, private_key) { + if( buf_sha256.length !== 32 || ! Buffer.isBuffer(buf_sha256) ) + throw new Error("buf_sha256: 32 byte buffer requred") + private_key = toPrivateObj(private_key) + assert(private_key, 'private_key required') + + var der, e, ecsignature, i, lenR, lenS, nonce; + i = null; + nonce = 0; + e = BigInteger.fromBuffer(buf_sha256); + while (true) { + ecsignature = ecdsa.sign(curve, buf_sha256, private_key.d, nonce++); + der = ecsignature.toDER(); + lenR = der[3]; + lenS = der[5 + lenR]; + if (lenR === 32 && lenS === 32) { + i = ecdsa.calcPubKeyRecoveryParam(curve, e, ecsignature, private_key.toPublic().Q); + i += 4; // compressed + i += 27; // compact // 24 or 27 :( forcing odd-y 2nd key candidate) + break; + } + if (nonce % 10 === 0) { + console.log("WARN: " + nonce + " attempts to find canonical signature"); + } + } + return Signature(ecsignature.r, ecsignature.s, i); +}; + +Signature.sign = function(string, private_key) { + return Signature.signBuffer(new Buffer(string), private_key); +}; + +Signature.fromHex = function(hex) { + return Signature.fromBuffer(new Buffer(hex, "hex")); +}; + +Signature.signHex = function(hex, private_key) { + var buf; + buf = new Buffer(hex, 'hex'); + return Signature.signBuffer(buf, private_key); +}; + const toPrivateObj = o => (o ? o.d ? o : PrivateKey.fromWif(o) : o/*null or undefined*/) -module.exports = Signature;