@bsv/sdk
Version:
BSV Blockchain Software Development Kit
188 lines • 9.23 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.verify = exports.sign = void 0;
const BigNumber_js_1 = __importDefault(require("./BigNumber.js"));
const Signature_js_1 = __importDefault(require("./Signature.js"));
const Curve_js_1 = __importDefault(require("./Curve.js"));
const Point_js_1 = require("./Point.js");
const DRBG_js_1 = __importDefault(require("./DRBG.js"));
/**
* Truncates a BigNumber message to the length of the curve order n, in the context of the Elliptic Curve Digital Signature Algorithm (ECDSA).
* This method is used as part of ECDSA signing and verification.
*
* The method calculates `delta`, which is a difference obtained by subtracting the bit length of the curve order `n` from the byte length of the message in bits.
* If `delta` is greater than zero, logical shifts msg to the right by `delta`, retaining the sign.
*
* Another condition is tested, but only if `truncOnly` is false. This condition compares the value of msg to curve order `n`.
* If msg is greater or equal to `n`, it is decreased by `n` and returned.
*
* @method truncateToN
* @param msg - The BigNumber message to be truncated.
* @param truncOnly - An optional boolean parameter that if set to true, the method will only perform truncation of the BigNumber without doing the additional subtraction from the curve order.
* @returns Returns the truncated BigNumber value, potentially subtracted by the curve order n.
*
* @example
* let msg = new BigNumber('1234567890abcdef', 16);
* let truncatedMsg = truncateToN(msg);
*/
function truncateToN(msg, truncOnly, curve = new Curve_js_1.default()) {
const delta = msg.byteLength() * 8 - curve.n.bitLength();
if (delta > 0) {
msg.iushrn(delta);
}
if (truncOnly === null && msg.cmp(curve.n) >= 0) {
return msg.sub(curve.n);
}
else {
return msg;
}
}
const curve = new Curve_js_1.default();
const bytes = curve.n.byteLength();
const ns1 = curve.n.subn(1);
const halfN = Point_js_1.N_BIGINT >> 1n;
/**
* Generates a digital signature for a given message.
*
* @function sign
* @param msg - The BigNumber message for which the signature has to be computed.
* @param key - Private key in BigNumber.
* @param forceLowS - Optional boolean flag if True forces "s" to be the lower of two possible values.
* @param customK - Optional specification for k value, which can be a function or BigNumber.
* @returns Returns the elliptic curve digital signature of the message.
*
* @example
* const msg = new BigNumber('2664878')
* const key = new BigNumber('123456')
* const signature = sign(msg, key)
*/
const sign = (msg, key, forceLowS = false, customK) => {
// —— prepare inputs ────────────────────────────────────────────────────────
msg = truncateToN(msg);
const msgBig = BigInt('0x' + msg.toString(16));
const keyBig = BigInt('0x' + key.toString(16));
// DRBG seeding identical to previous implementation
const bkey = key.toArray('be', bytes);
const nonce = msg.toArray('be', bytes);
const drbg = new DRBG_js_1.default(bkey, nonce);
for (let iter = 0;; iter++) {
// —— k generation & basic validity checks ───────────────────────────────
let kBN = typeof customK === 'function'
? customK(iter)
: BigNumber_js_1.default.isBN(customK)
? customK
: new BigNumber_js_1.default(drbg.generate(bytes), 16);
if (kBN == null)
throw new Error('k is undefined');
kBN = truncateToN(kBN, true);
if (kBN.cmpn(1) <= 0 || kBN.cmp(ns1) >= 0) {
if (BigNumber_js_1.default.isBN(customK)) {
throw new Error('Invalid fixed custom K value (must be >1 and <N‑1)');
}
continue;
}
const kBig = BigInt('0x' + kBN.toString(16));
// —— R = k·G (Jacobian, window‑NAF) ──────────────────────────────────────
const R = (0, Point_js_1.scalarMultiplyWNAF)(kBig, { x: Point_js_1.GX_BIGINT, y: Point_js_1.GY_BIGINT });
if (R.Z === 0n) { // point at infinity – should never happen for valid k
if (BigNumber_js_1.default.isBN(customK)) {
throw new Error('Invalid fixed custom K value (k·G at infinity)');
}
continue;
}
// affine X coordinate of R
const zInv = (0, Point_js_1.biModInv)(R.Z);
const zInv2 = (0, Point_js_1.biModMul)(zInv, zInv);
const xAff = (0, Point_js_1.biModMul)(R.X, zInv2);
const rBig = (0, Point_js_1.modN)(xAff);
if (rBig === 0n) {
if (BigNumber_js_1.default.isBN(customK)) {
throw new Error('Invalid fixed custom K value (r == 0)');
}
continue;
}
// —— s = k⁻¹ · (msg + r·key) mod n ─────────────────────────────────────
const kInv = (0, Point_js_1.modInvN)(kBig);
const rTimesKey = (0, Point_js_1.modMulN)(rBig, keyBig);
const sum = (0, Point_js_1.modN)(msgBig + rTimesKey);
let sBig = (0, Point_js_1.modMulN)(kInv, sum);
if (sBig === 0n) {
if (BigNumber_js_1.default.isBN(customK)) {
throw new Error('Invalid fixed custom K value (s == 0)');
}
continue;
}
// low‑S mitigation (BIP‑62/BIP‑340 style)
if (forceLowS && sBig > halfN) {
sBig = Point_js_1.N_BIGINT - sBig;
}
// —— convert back to BigNumber & return ─────────────────────────────────
const r = new BigNumber_js_1.default(rBig.toString(16), 16);
const s = new BigNumber_js_1.default(sBig.toString(16), 16);
return new Signature_js_1.default(r, s);
}
};
exports.sign = sign;
/**
* Verifies a digital signature of a given message.
*
* Message and key used during the signature generation process, and the previously computed signature
* are used to validate the authenticity of the digital signature.
*
* @function verify
* @param msg - The BigNumber message for which the signature has to be verified.
* @param sig - Signature object consisting of parameters 'r' and 's'.
* @param key - Public key in Point.
* @returns Returns true if the signature is valid and false otherwise.
*
* @example
* const msg = new BigNumber('2664878', 16)
* const key = new Point(new BigNumber(10), new BigNumber(20)
* const signature = sign(msg, new BigNumber('123456'))
* const isVerified = verify(msg, sig, key)
*/
const verify = (msg, sig, key) => {
// Convert inputs to BigInt
const hash = BigInt('0x' + msg.toString(16));
if ((key.x == null) || (key.y == null)) {
throw new Error('Invalid public key: missing coordinates.');
}
const publicKey = {
x: BigInt('0x' + key.x.toString(16)),
y: BigInt('0x' + key.y.toString(16))
};
const signature = {
r: BigInt('0x' + sig.r.toString(16)),
s: BigInt('0x' + sig.s.toString(16))
};
const { r, s } = signature;
const z = hash;
// Check r and s are in [1, n - 1]
if (r <= Point_js_1.BI_ZERO || r >= Point_js_1.N_BIGINT || s <= Point_js_1.BI_ZERO || s >= Point_js_1.N_BIGINT) {
return false;
}
// ── compute u₁ = z·s⁻¹ mod n and u₂ = r·s⁻¹ mod n ───────────────────────
const w = (0, Point_js_1.modInvN)(s); // s⁻¹ mod n
if (w === 0n)
return false; // should never happen
const u1 = (0, Point_js_1.modMulN)(z, w);
const u2 = (0, Point_js_1.modMulN)(r, w);
// ── R = u₁·G + u₂·Q (Jacobian, window‑NAF) ──────────────────────────────
const RG = (0, Point_js_1.scalarMultiplyWNAF)(u1, { x: Point_js_1.GX_BIGINT, y: Point_js_1.GY_BIGINT });
const RQ = (0, Point_js_1.scalarMultiplyWNAF)(u2, publicKey);
const R = (0, Point_js_1.jpAdd)(RG, RQ);
if (R.Z === 0n)
return false; // point at infinity
// ── affine x‑coordinate of R (mod p) ─────────────────────────────────────
const zInv = (0, Point_js_1.biModInv)(R.Z); // (Z⁻¹ mod p)
const zInv2 = (0, Point_js_1.biModMul)(zInv, zInv); // Z⁻²
const xAff = (0, Point_js_1.biModMul)(R.X, zInv2); // X / Z² mod p
// ── v = xAff mod n and final check ───────────────────────────────────────
const v = (0, Point_js_1.modN)(xAff);
return v === r;
};
exports.verify = verify;
//# sourceMappingURL=ECDSA.js.map