UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

209 lines (185 loc) 8.06 kB
import BigNumber from './BigNumber.js' import Signature from './Signature.js' import Curve from './Curve.js' import Point, { scalarMultiplyWNAF, biModInv, BI_ZERO, biModMul, GX_BIGINT, GY_BIGINT, jpAdd, N_BIGINT, modInvN, modMulN, modN } from './Point.js' import DRBG from './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: BigNumber, truncOnly?: boolean, curve = new Curve() ): BigNumber { 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() const bytes = curve.n.byteLength() const ns1 = curve.n.subn(1) const halfN = 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) */ export const sign = ( msg: BigNumber, key: BigNumber, forceLowS: boolean = false, customK?: BigNumber | ((iter: number) => BigNumber) ): Signature => { // —— 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(bkey, nonce) for (let iter = 0; ; iter++) { // —— k generation & basic validity checks ─────────────────────────────── let kBN = typeof customK === 'function' ? customK(iter) : BigNumber.isBN(customK) ? customK : new BigNumber(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.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 = scalarMultiplyWNAF(kBig, { x: GX_BIGINT, y: GY_BIGINT }) if (R.Z === 0n) { // point at infinity – should never happen for valid k if (BigNumber.isBN(customK)) { throw new Error('Invalid fixed custom K value (k·G at infinity)') } continue } // affine X coordinate of R const zInv = biModInv(R.Z) const zInv2 = biModMul(zInv, zInv) const xAff = biModMul(R.X, zInv2) const rBig = modN(xAff) if (rBig === 0n) { if (BigNumber.isBN(customK)) { throw new Error('Invalid fixed custom K value (r == 0)') } continue } // —— s = k⁻¹ · (msg + r·key) mod n ───────────────────────────────────── const kInv = modInvN(kBig) const rTimesKey = modMulN(rBig, keyBig) const sum = modN(msgBig + rTimesKey) let sBig = modMulN(kInv, sum) if (sBig === 0n) { if (BigNumber.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 = N_BIGINT - sBig } // —— convert back to BigNumber & return ───────────────────────────────── const r = new BigNumber(rBig.toString(16), 16) const s = new BigNumber(sBig.toString(16), 16) return new Signature(r, s) } } /** * 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) */ export const verify = (msg: BigNumber, sig: Signature, key: Point): boolean => { // 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 <= BI_ZERO || r >= N_BIGINT || s <= BI_ZERO || s >= N_BIGINT) { return false } // ── compute u₁ = z·s⁻¹ mod n and u₂ = r·s⁻¹ mod n ─────────────────────── const w = modInvN(s) // s⁻¹ mod n if (w === 0n) return false // should never happen const u1 = modMulN(z, w) const u2 = modMulN(r, w) // ── R = u₁·G + u₂·Q (Jacobian, window‑NAF) ────────────────────────────── const RG = scalarMultiplyWNAF(u1, { x: GX_BIGINT, y: GY_BIGINT }) const RQ = scalarMultiplyWNAF(u2, publicKey) const R = jpAdd(RG, RQ) if (R.Z === 0n) return false // point at infinity // ── affine x‑coordinate of R (mod p) ───────────────────────────────────── const zInv = biModInv(R.Z) // (Z⁻¹ mod p) const zInv2 = biModMul(zInv, zInv) // Z⁻² const xAff = biModMul(R.X, zInv2) // X / Z² mod p // ── v = xAff mod n and final check ─────────────────────────────────────── const v = modN(xAff) return v === r }