@bsv/sdk
Version:
BSV Blockchain Software Development Kit
358 lines • 13 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const BigNumber_js_1 = __importDefault(require("./BigNumber.js"));
const PublicKey_js_1 = __importDefault(require("./PublicKey.js"));
const ECDSA_js_1 = require("./ECDSA.js");
const Hash_js_1 = require("./Hash.js");
const utils_js_1 = require("./utils.js");
const Point_js_1 = __importDefault(require("./Point.js"));
const Curve_js_1 = __importDefault(require("./Curve.js"));
/**
* Represents a digital signature.
*
* A digital signature is a mathematical scheme for verifying the authenticity of
* digital messages or documents. In many scenarios, it is equivalent to a handwritten signature or stamped seal.
* The signature pair (R, S) corresponds to the raw ECDSA ([Elliptic Curve Digital Signature Algorithm](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm)) signature.
* Signatures are often serialized into a format known as '[DER encoding](https://en.wikipedia.org/wiki/X.690#DER_encoding)' for transmission.
*
* @class Signature
*/
class Signature {
/**
* Takes an array of numbers or a string and returns a new Signature instance.
* This method will throw an error if the DER encoding is invalid.
* If a string is provided, it is assumed to represent a hexadecimal sequence.
*
* @static
* @method fromDER
* @param data - The sequence to decode from DER encoding.
* @param enc - The encoding of the data string.
* @returns The decoded data in the form of Signature instance.
*
* @example
* const signature = Signature.fromDER('30440220018c1f5502f8...', 'hex');
*/
static fromDER(data, enc) {
const getLength = (buf, p) => {
const initial = buf[p.place++];
if ((initial & 0x80) === 0) {
return initial;
}
else {
throw new Error('Invalid DER entity length');
}
};
class Position {
constructor() {
this.place = 0;
}
}
data = (0, utils_js_1.toArray)(data, enc);
const p = new Position();
if (data[p.place++] !== 0x30) {
throw new Error('Signature DER must start with 0x30');
}
const len = getLength(data, p);
if (len + p.place !== data.length) {
throw new Error('Signature DER invalid');
}
if (data[p.place++] !== 0x02) {
throw new Error('Signature DER invalid');
}
const rlen = getLength(data, p);
let r = data.slice(p.place, rlen + p.place);
p.place += rlen;
if (data[p.place++] !== 0x02) {
throw new Error('Signature DER invalid');
}
const slen = getLength(data, p);
if (data.length !== slen + p.place) {
throw new Error('Invalid R-length in signature DER');
}
let s = data.slice(p.place, slen + p.place);
if (r[0] === 0) {
if ((r[1] & 0x80) !== 0) {
r = r.slice(1);
}
else {
throw new Error('Invalid R-value in signature DER');
}
}
if (s[0] === 0) {
if ((s[1] & 0x80) !== 0) {
s = s.slice(1);
}
else {
throw new Error('Invalid S-value in signature DER');
}
}
return new Signature(new BigNumber_js_1.default(r), new BigNumber_js_1.default(s));
}
/**
* Takes an array of numbers or a string and returns a new Signature instance.
* This method will throw an error if the Compact encoding is invalid.
* If a string is provided, it is assumed to represent a hexadecimal sequence.
* compactByte value 27-30 means uncompressed public key.
* 31-34 means compressed public key.
* The range represents the recovery param which can be 0,1,2,3.
* We could support recovery functions in future if there's demand.
*
* @static
* @method fromCompact
* @param data - The sequence to decode from Compact encoding.
* @param enc - The encoding of the data string.
* @returns The decoded data in the form of Signature instance.
*
* @example
* const signature = Signature.fromCompact('1b18c1f5502f8...', 'hex');
*/
static fromCompact(data, enc) {
data = (0, utils_js_1.toArray)(data, enc);
if (data.length !== 65) {
throw new Error('Invalid Compact Signature');
}
const compactByte = data[0];
if (compactByte < 27 || compactByte >= 35) {
throw new Error('Invalid Compact Byte');
}
return new Signature(new BigNumber_js_1.default(data.slice(1, 33)), new BigNumber_js_1.default(data.slice(33, 65)));
}
/**
* Creates an instance of the Signature class.
*
* @constructor
* @param r - The R component of the signature.
* @param s - The S component of the signature.
*
* @example
* const r = new BigNumber('208755674028...');
* const s = new BigNumber('564745627577...');
* const signature = new Signature(r, s);
*/
constructor(r, s) {
this.r = r;
this.s = s;
}
/**
* Verifies a digital signature.
*
* This method will return true if the signature, key, and message hash match.
* If the data or key do not match the signature, the function returns false.
*
* @method verify
* @param msg - The message to verify.
* @param key - The public key used to sign the original message.
* @param enc - The encoding of the msg string.
* @returns A boolean representing whether the signature is valid.
*
* @example
* const msg = 'The quick brown fox jumps over the lazy dog';
* const publicKey = PublicKey.fromString('04188ca1050...');
* const isVerified = signature.verify(msg, publicKey);
*/
verify(msg, key, enc) {
const msgHash = new BigNumber_js_1.default((0, Hash_js_1.sha256)(msg, enc), 16);
return (0, ECDSA_js_1.verify)(msgHash, this, key);
}
/**
* Converts an instance of Signature into DER encoding.
* An alias for the toDER method.
*
* If the encoding parameter is set to 'hex', the function will return a hex string.
* If 'base64', it will return a base64 string.
* Otherwise, it will return an array of numbers.
*
* @method toDER
* @param enc - The encoding to use for the output.
* @returns The current instance in DER encoding.
*
* @example
* const der = signature.toString('base64');
*/
toString(enc) {
return this.toDER(enc);
}
/**
* Converts an instance of Signature into DER encoding.
*
* If the encoding parameter is set to 'hex', the function will return a hex string.
* If 'base64', it will return a base64 string.
* Otherwise, it will return an array of numbers.
*
* @method toDER
* @param enc - The encoding to use for the output.
* @returns The current instance in DER encoding.
*
* @example
* const der = signature.toDER('hex');
*/
toDER(enc) {
const constructLength = (arr, len) => {
if (len < 0x80) {
arr.push(len);
}
else {
throw new Error('len must be < 0x80');
}
};
const rmPadding = (buf) => {
let i = 0;
const len = buf.length - 1;
while (buf[i] === 0 && (buf[i + 1] & 0x80) === 0 && i < len) {
i++;
}
if (i === 0) {
return buf;
}
return buf.slice(i);
};
let r = this.r.toArray();
let s = this.s.toArray();
// Pad values
if ((r[0] & 0x80) !== 0) {
r = [0].concat(r);
}
// Pad values
if ((s[0] & 0x80) !== 0) {
s = [0].concat(s);
}
r = rmPadding(r);
s = rmPadding(s);
while (s[0] === 0 && (s[1] & 0x80) === 0) {
s = s.slice(1);
}
let arr = [0x02];
constructLength(arr, r.length);
arr = arr.concat(r);
arr.push(0x02);
constructLength(arr, s.length);
const backHalf = arr.concat(s);
let res = [0x30];
constructLength(res, backHalf.length);
res = res.concat(backHalf);
if (enc === 'hex') {
return (0, utils_js_1.toHex)(res);
}
else if (enc === 'base64') {
return (0, utils_js_1.toBase64)(res);
}
else {
return res;
}
}
/**
* Converts an instance of Signature into Compact encoding.
*
* If the encoding parameter is set to 'hex', the function will return a hex string.
* If 'base64', it will return a base64 string.
* Otherwise, it will return an array of numbers.
*
* @method toCompact
* @param enc - The encoding to use for the output.
* @returns The current instance in DER encoding.
*
* @example
* const compact = signature.toCompact(3, true, 'base64');
*/
toCompact(recovery, compressed, enc) {
if (recovery < 0 || recovery > 3)
throw new Error('Invalid recovery param');
if (typeof compressed !== 'boolean') {
throw new Error('Invalid compressed param');
}
let compactByte = 27 + recovery;
if (compressed) {
compactByte += 4;
}
let arr = [compactByte];
arr = arr.concat(this.r.toArray('be', 32));
arr = arr.concat(this.s.toArray('be', 32));
if (enc === 'hex') {
return (0, utils_js_1.toHex)(arr);
}
else if (enc === 'base64') {
return (0, utils_js_1.toBase64)(arr);
}
else {
return arr;
}
}
/**
* Recovers the public key from a signature.
* This method will return the public key if it finds a valid public key.
* If it does not find a valid public key, it will throw an error.
* The recovery factor is a number between 0 and 3.
* @method RecoverPublicKey
* @param recovery - The recovery factor.
* @param e - The message hash.
* @returns The public key associated with the signature.
*
* @example
* const publicKey = signature.RecoverPublicKey(0, msgHash);
*/
RecoverPublicKey(recovery, e) {
const r = this.r;
const s = this.s;
// A set LSB signifies that the y-coordinate is odd
const isYOdd = (recovery & 1) !== 0;
// The more significant bit specifies whether we should use the
// first or second candidate key.
const isSecondKey = recovery >> 1;
const curve = new Curve_js_1.default();
const n = curve.n;
const G = curve.g;
// 1.1 LEt x = r + jn
const x = isSecondKey !== 0 ? r.add(n) : r;
const R = Point_js_1.default.fromX(x, isYOdd);
// 1.4 Check that nR is at infinity
const nR = R.mul(n);
if (!nR.isInfinity()) {
throw new Error('nR is not at infinity');
}
// Compute -e from e
const eNeg = e.neg().umod(n);
// 1.6.1 Compute Q = r^-1 (sR - eG)
// Q = r^-1 (sR + -eG)
const rInv = r.invm(n);
// const Q = R.multiplyTwo(s, G, eNeg).mul(rInv)
const srInv = rInv.mul(s).umod(n);
const eInvrInv = rInv.mul(eNeg).umod(n);
const Q = G.mul(eInvrInv).add(R.mul(srInv));
const pubKey = new PublicKey_js_1.default(Q);
pubKey.validate();
return pubKey;
}
/**
* Calculates the recovery factor which will work for a particular public key and message hash.
* This method will return the recovery factor if it finds a valid recovery factor.
* If it does not find a valid recovery factor, it will throw an error.
* The recovery factor is a number between 0 and 3.
*
* @method CalculateRecoveryFactor
* @param msgHash - The message hash.
* @returns the recovery factor: number
* /
* @example
* const recovery = signature.CalculateRecoveryFactor(publicKey, msgHash);
*/
CalculateRecoveryFactor(pubkey, msgHash) {
for (let recovery = 0; recovery < 4; recovery++) {
let Qprime;
try {
Qprime = this.RecoverPublicKey(recovery, msgHash);
}
catch {
continue;
}
if (pubkey.eq(Qprime)) {
return recovery;
}
}
throw new Error('Unable to find valid recovery factor');
}
}
exports.default = Signature;
//# sourceMappingURL=Signature.js.map