-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'taproot' of github.com:kajoseph/bitcore
- Loading branch information
Showing
38 changed files
with
180,565 additions
and
444 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
const crypto = require('crypto'); | ||
const $ = require('../util/preconditions'); | ||
const JS = require('../util/js'); | ||
const BN = require('./bn'); | ||
const Point = require('./point'); | ||
const TaggedHash = require('./taggedhash'); | ||
|
||
const Schnorr = function Schnorr() { | ||
if (!(this instanceof Schnorr)) { | ||
return new Schnorr(); | ||
} | ||
return this; | ||
}; | ||
|
||
Schnorr.prototype.set = function() {}; | ||
|
||
/** | ||
* Create a schnorr signature | ||
* @param {PrivateKey|Buffer|BN} privateKey | ||
* @param {String|Buffer} message Hex string or buffer | ||
* @param {String|Buffer} aux Hex string or buffer | ||
* @returns {Buffer} | ||
* @link https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#Default_Signing | ||
*/ | ||
Schnorr.sign = function(privateKey, message, aux) { | ||
privateKey = Buffer.isBuffer(privateKey) ? privateKey : privateKey.toBuffer(); | ||
if (privateKey.length !== 32) { | ||
throw new Error('Private key should be 32 bytes for schnorr signatures'); | ||
} | ||
|
||
if (typeof message === 'string') { | ||
$.checkArgument(JS.isHexaString(message), 'Schnorr message string is not hex'); | ||
message = Buffer.from(message, 'hex') | ||
} | ||
$.checkArgument($.isType(message, 'Buffer'), 'Schnorr message must be a hex string or buffer'); | ||
|
||
if (!aux) { | ||
aux = crypto.randomBytes(32); | ||
} | ||
if (typeof aux === 'string') { | ||
$.checkArgument(JS.isHexaString(aux), 'Schnorr aux string is not hex'); | ||
aux = Buffer.from(aux, 'hex') | ||
} | ||
$.checkArgument($.isType(aux, 'Buffer'), 'Schnorr aux must be a hex string or buffer'); | ||
|
||
const G = Point.getG(); | ||
const n = Point.getN(); | ||
|
||
const dPrime = new BN(privateKey); | ||
if (dPrime.eqn(0) || dPrime.gte(n)) { | ||
throw new Error('Invalid private key for schnorr signing'); | ||
} | ||
const P = G.mul(dPrime); | ||
const Pbuf = Buffer.from(P.encodeCompressed().slice(1)); // slice(1) removes the encoding prefix byte | ||
const d = P.y.isEven() ? dPrime : n.sub(dPrime); | ||
const t = d.xor(new BN(new TaggedHash('BIP0340/aux', aux).finalize())); | ||
const rand = new TaggedHash('BIP0340/nonce', Buffer.concat([t.toBuffer(), Pbuf, message])).finalize(); | ||
const kPrime = new BN(rand).mod(n); | ||
if (kPrime.eqn(0)) { | ||
throw new Error('Error creating schnorr signature'); | ||
} | ||
const R = G.mul(kPrime); | ||
const Rbuf = Buffer.from(R.encodeCompressed().slice(1)); // slice(1) removes the encoding prefix byte | ||
const k = R.y.isEven() ? kPrime : n.sub(kPrime); | ||
const e = new BN(new TaggedHash('BIP0340/challenge', Buffer.concat([Rbuf, Pbuf, message])).finalize()).mod(n); | ||
const sig = Buffer.concat([Rbuf, k.add(e.mul(d)).mod(n).toBuffer()]); | ||
|
||
if (!Schnorr.verify(Pbuf, message, sig)) { | ||
throw new Error('Error creating schnorr signature. Verification failed'); | ||
} | ||
return sig; | ||
}; | ||
|
||
|
||
/** | ||
* Verify a schnorr signature | ||
* @param {PublicKey|Buffer} publicKey | ||
* @param {String|Buffer} message Hex string or buffer | ||
* @param {String|Signature|Buffer} signature Hex string, Signature instance, or buffer | ||
* @returns {Boolean} | ||
* @link https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#Verification | ||
*/ | ||
Schnorr.verify = function(publicKey, message, signature) { | ||
if ($.isType(publicKey, 'PublicKey')) { | ||
publicKey = publicKey.point.x.toBuffer(); | ||
} | ||
if (publicKey.length !== 32) { | ||
throw new Error('Public key should be 32 bytes for schnorr signatures'); | ||
} | ||
|
||
if (typeof message === 'string') { | ||
$.checkArgument(JS.isHexaString(message), 'Schnorr message string is not hex'); | ||
message = Buffer.from(message, 'hex'); | ||
} | ||
if (message.length !== 32) { | ||
throw new Error('Message should be a 32 byte buffer'); | ||
} | ||
|
||
if (typeof signature === 'string') { | ||
$.checkArgument(JS.isHexaString(signature), 'Schnorr signature string is not hex'); | ||
signature = Buffer.from(signature, 'hex'); | ||
} | ||
if (typeof signature.toBuffer === 'function') { | ||
signature = signature.toBuffer(); | ||
if (signature.length === 65) { | ||
signature = signature.slice(0, 64); // remove the sighashType byte | ||
} | ||
} | ||
if (signature.length !== 64) { | ||
throw new Error('Signature should be a 64 byte buffer'); | ||
} | ||
|
||
try { | ||
const p = Point.getP(); | ||
const n = Point.getN(); | ||
|
||
const P = Point.fromX(false, publicKey).liftX(); | ||
const r = new BN(signature.slice(0, 32)); | ||
const s = new BN(signature.slice(32, 64)); | ||
if (r.gte(p) || s.gte(n)) { | ||
return false; | ||
} | ||
const e = getE(r, P, message); | ||
const G = Point.getG(); | ||
const R = G.mul(s).add(P.mul(e).neg()); | ||
if (R.inf || !R.y.isEven() || !R.x.eq(r)) { | ||
return false; | ||
} | ||
return true; | ||
} catch (e) { | ||
return false; | ||
} | ||
}; | ||
|
||
/* Utility function used in Verify() */ | ||
const getE = function(r, P, message) { | ||
const n = Point.getN(); | ||
const hash = new TaggedHash('BIP0340/challenge', Buffer.concat([r.toBuffer({ size: 32 }), P.x.toBuffer({ size: 32 }), message])).finalize(); | ||
return new BN(hash).mod(n); | ||
}; | ||
|
||
module.exports = Schnorr; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
const Hash = require('./hash'); | ||
const BufferWriter = require('../encoding/bufferwriter'); | ||
const inherits = require('inherits'); | ||
|
||
/** | ||
* Creates a tag hash to ensure uniqueness of a message between purposes. | ||
* For example, if there's a potential for a collision of messages between | ||
* multiple purposes, a tag can be added to guard against such collisions. | ||
* @link https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#Design (see 'Tagged Hashes') | ||
* @param {String} tag The tag to prevent message collisions. Should uniquely reflect the purpose of the message. | ||
* @param {Buffer|String} message (optional) | ||
* @param {String} messageEncoding (default: 'hex') If `message` is a string, provide the encoding | ||
* @returns {TaggedHash} Instance of a BufferWriter with the written tag and `finalize` method | ||
*/ | ||
function TaggedHash(tag, message, messageEncoding = 'hex') { | ||
if (!(this instanceof TaggedHash)) { | ||
return new TaggedHash(tag, message, messageEncoding); | ||
} | ||
BufferWriter.apply(this); | ||
tag = Buffer.from(tag); | ||
|
||
const taghash = Hash.sha256(tag); | ||
this.write(taghash); | ||
this.write(taghash); | ||
if (message) { | ||
message = Buffer.isBuffer(message) ? message : Buffer.from(message, messageEncoding); | ||
this.write(message); | ||
} | ||
return this; | ||
}; | ||
|
||
inherits(TaggedHash, BufferWriter); | ||
|
||
/** | ||
* Returns a 32-byte SHA256 hash of the double tagged hashes concat'd with the message | ||
* as defined by BIP-340: SHA256(SHA256(tag), SHA256(tag), message) | ||
* @returns {Buffer} | ||
*/ | ||
TaggedHash.prototype.finalize = function() { | ||
return Buffer.from(Hash.sha256(this.toBuffer())); | ||
}; | ||
|
||
/** | ||
* Commonly used tags | ||
*/ | ||
Object.defineProperties(TaggedHash, { | ||
TAPSIGHASH: { get: () => new TaggedHash('TapSighash') }, | ||
TAPLEAF: { get: () => new TaggedHash('TapLeaf') }, | ||
TAPBRANCH: { get: () => new TaggedHash('TapBranch') } | ||
}); | ||
|
||
module.exports = TaggedHash; |
Oops, something went wrong.