UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

490 lines 15.8 kB
"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 K256_js_1 = __importDefault(require("./K256.js")); /** * A base reduction engine that provides several arithmetic operations over * big numbers under a modulus context. It's particularly suitable for * calculations required in cryptography algorithms and encoding schemas. * * @class ReductionContext * * @property prime - The prime number utilised in the reduction context, typically an instance of Mersenne class. * @property m - The modulus used for reduction operations. */ class ReductionContext { /** * Constructs a new ReductionContext. * * @constructor * @param m - A BigNumber representing the modulus, or 'k256' to create a context for Koblitz curve. * * @example * new ReductionContext(new BigNumber(11)); * new ReductionContext('k256'); */ constructor(m) { if (m === 'k256') { const prime = new K256_js_1.default(); this.m = prime.p; this.prime = prime; } else { this.assert(m.gtn(1), 'modulus must be greater than 1'); this.m = m; this.prime = null; } } /** * Asserts that given value is truthy. Throws an Error with a provided message * if the value is falsy. * * @private * @param val - The value to be checked. * @param msg - The error message to be thrown if the value is falsy. * * @example * this.assert(1 < 2, '1 is not less than 2'); * this.assert(2 < 1, '2 is less than 1'); // throws an Error with message '2 is less than 1' */ assert(val, msg = 'Assertion failed') { if (!val) throw new Error(msg); } /** * Verifies that a BigNumber is positive and red. Throws an error if these * conditions are not met. * * @param a - The BigNumber to be verified. * * @example * this.verify1(new BigNumber(10).toRed()); * this.verify1(new BigNumber(-10).toRed()); //throws an Error * this.verify1(new BigNumber(10)); //throws an Error */ verify1(a) { this.assert(a.negative === 0, 'red works only with positives'); this.assert(a.red, 'red works only with red numbers'); } /** * Verifies that two BigNumbers are both positive and red. Also checks * that they have the same reduction context. Throws an error if these * conditions are not met. * * @param a - The first BigNumber to be verified. * @param b - The second BigNumber to be verified. * * @example * this.verify2(new BigNumber(10).toRed(this), new BigNumber(20).toRed(this)); * this.verify2(new BigNumber(-10).toRed(this), new BigNumber(20).toRed(this)); //throws an Error * this.verify2(new BigNumber(10).toRed(this), new BigNumber(20)); //throws an Error */ verify2(a, b) { this.assert((a.negative | b.negative) === 0, 'red works only with positives'); this.assert(a.red != null && a.red === b.red, 'red works only with red numbers'); } /** * Performs an in-place reduction of the given BigNumber by the modulus of the reduction context, 'm'. * * @method imod * * @param a - BigNumber to be reduced. * * @returns Returns the reduced result. * * @example * const context = new ReductionContext(new BigNumber(7)); * context.imod(new BigNumber(19)); // Returns 5 */ imod(a) { if (this.prime != null) return this.prime.ireduce(a).forceRed(this); BigNumber_js_1.default.move(a, a.umod(this.m).forceRed(this)); return a; } /** * Negates a BigNumber in the context of the modulus. * * @method neg * * @param a - BigNumber to negate. * * @returns Returns the negation of 'a' in the reduction context. * * @example * const context = new ReductionContext(new BigNumber(7)); * context.neg(new BigNumber(3)); // Returns 4 */ neg(a) { if (a.isZero()) { return a.clone(); } return this.m.sub(a).forceRed(this); } /** * Performs the addition operation on two BigNumbers in the reduction context. * * @method add * * @param a - First BigNumber to add. * @param b - Second BigNumber to add. * * @returns Returns the result of 'a + b' in the reduction context. * * @example * const context = new ReductionContext(new BigNumber(5)); * context.add(new BigNumber(2), new BigNumber(4)); // Returns 1 */ add(a, b) { this.verify2(a, b); const res = a.add(b); if (res.cmp(this.m) >= 0) { res.isub(this.m); } return res.forceRed(this); } /** * Performs an in-place addition operation on two BigNumbers in the reduction context * in order to avoid creating a new BigNumber, it modifies the first one with the result. * * @method iadd * * @param a - First BigNumber to add. * @param b - Second BigNumber to add. * * @returns Returns the modified 'a' after addition with 'b' in the reduction context. * * @example * const context = new ReductionContext(new BigNumber(5)); * const a = new BigNumber(2); * context.iadd(a, new BigNumber(4)); // Modifies 'a' to be 1 */ iadd(a, b) { this.verify2(a, b); const res = a.iadd(b); if (res.cmp(this.m) >= 0) { res.isub(this.m); } return res; } /** * Subtracts one BigNumber from another BigNumber in the reduction context. * * @method sub * * @param a - BigNumber to be subtracted from. * @param b - BigNumber to subtract. * * @returns Returns the result of 'a - b' in the reduction context. * * @example * const context = new ReductionContext(new BigNumber(7)); * context.sub(new BigNumber(3), new BigNumber(2)); // Returns 1 */ sub(a, b) { this.verify2(a, b); const res = a.sub(b); if (res.cmpn(0) < 0) { res.iadd(this.m); } return res.forceRed(this); } /** * Performs in-place subtraction of one BigNumber from another in the reduction context, * it modifies the first BigNumber with the result. * * @method isub * * @param a - BigNumber to be subtracted from. * @param b - BigNumber to subtract. * * @returns Returns the modified 'a' after subtraction of 'b' in the reduction context. * * @example * const context = new ReductionContext(new BigNumber(5)); * const a = new BigNumber(4); * context.isub(a, new BigNumber(2)); // Modifies 'a' to be 2 */ isub(a, b) { this.verify2(a, b); const res = a.isub(b); if (res.cmpn(0) < 0) { res.iadd(this.m); } return res; } /** * Performs bitwise shift left operation on a BigNumber in the reduction context. * * @method shl * * @param a - BigNumber to perform shift on. * @param num - The number of positions to shift. * * @returns Returns the result of shifting 'a' left by 'num' positions in the reduction context. * * @example * const context = new ReductionContext(new BigNumber(32)); * context.shl(new BigNumber(4), 2); // Returns 16 */ shl(a, num) { this.verify1(a); return this.imod(a.ushln(num)); } /** * Performs in-place multiplication of two BigNumbers in the reduction context, * modifying the first BigNumber with the result. * * @method imul * * @param a - First BigNumber to multiply. * @param b - Second BigNumber to multiply. * * @returns Returns the modified 'a' after multiplication with 'b' in the reduction context. * * @example * const context = new ReductionContext(new BigNumber(7)); * const a = new BigNumber(3); * context.imul(a, new BigNumber(2)); // Modifies 'a' to be 6 */ imul(a, b) { this.verify2(a, b); return this.imod(a.imul(b)); } /** * Multiplies two BigNumbers in the reduction context. * * @method mul * * @param a - First BigNumber to multiply. * @param b - Second BigNumber to multiply. * * @returns Returns the result of 'a * b' in the reduction context. * * @example * const context = new ReductionContext(new BigNumber(7)); * context.mul(new BigNumber(3), new BigNumber(2)); // Returns 6 */ mul(a, b) { this.verify2(a, b); return this.imod(a.mul(b)); } /** * Calculates the square of a BigNumber in the reduction context, * modifying the original BigNumber with the result. * * @method isqr * * @param a - BigNumber to be squared. * * @returns Returns the squared 'a' in the reduction context. * * @example * const context = new ReductionContext(new BigNumber(7)); * const a = new BigNumber(3); * context.isqr(a); // Modifies 'a' to be 2 (9 % 7 = 2) */ isqr(a) { return this.imul(a, a.clone()); } /** * Calculates the square of a BigNumber in the reduction context. * * @method sqr * * @param a - BigNumber to be squared. * * @returns Returns the result of 'a^2' in the reduction context. * * @example * const context = new ReductionContext(new BigNumber(7)); * context.sqr(new BigNumber(3)); // Returns 2 (9 % 7 = 2) */ sqr(a) { return this.mul(a, a); } /** * Calculates the square root of a BigNumber in the reduction context. * * @method sqrt * * @param a - The BigNumber to calculate the square root of. * * @returns Returns the square root of 'a' in the reduction context. * * @example * const context = new ReductionContext(new BigNumber(9)); * context.sqrt(new BigNumber(4)); // Returns 2 */ sqrt(a) { if (a.isZero()) return a.clone(); const mod3 = this.m.andln(3); this.assert(mod3 % 2 === 1); // Fast case if (mod3 === 3) { const pow = this.m.add(new BigNumber_js_1.default(1)).iushrn(2); return this.pow(a, pow); } // Tonelli-Shanks algorithm (Totally unoptimized and slow) // // Find Q and S, that Q * 2 ^ S = (P - 1) const q = this.m.subn(1); let s = 0; while (!q.isZero() && q.andln(1) === 0) { s++; q.iushrn(1); } this.assert(!q.isZero()); const one = new BigNumber_js_1.default(1).toRed(this); const nOne = one.redNeg(); // Find quadratic non-residue // NOTE: Max is such because of generalized Riemann hypothesis. const lpow = this.m.subn(1).iushrn(1); const zl = this.m.bitLength(); const z = new BigNumber_js_1.default(2 * zl * zl).toRed(this); while (this.pow(z, lpow).cmp(nOne) !== 0) { z.redIAdd(nOne); } let c = this.pow(z, q); let r = this.pow(a, q.addn(1).iushrn(1)); let t = this.pow(a, q); let m = s; while (t.cmp(one) !== 0) { let tmp = t; let i = 0; for (; tmp.cmp(one) !== 0; i++) { tmp = tmp.redSqr(); } this.assert(i < m); const b = this.pow(c, new BigNumber_js_1.default(1).iushln(m - i - 1)); r = r.redMul(b); c = b.redSqr(); t = t.redMul(c); m = i; } return r; } /** * Calculates the multiplicative inverse of a BigNumber in the reduction context. * * @method invm * * @param a - The BigNumber to find the multiplicative inverse of. * * @returns Returns the multiplicative inverse of 'a' in the reduction context. * * @example * const context = new ReductionContext(new BigNumber(11)); * context.invm(new BigNumber(3)); // Returns 4 (3*4 mod 11 = 1) */ invm(a) { const inv = a._invmp(this.m); if (inv.negative !== 0) { inv.negative = 0; return this.imod(inv).redNeg(); } else { return this.imod(inv); } } /** * Raises a BigNumber to a power in the reduction context. * * @method pow * * @param a - The BigNumber to be raised to a power. * @param num - The power to raise the BigNumber to. * * @returns Returns the result of 'a' raised to the power of 'num' in the reduction context. * * @example * const context = new ReductionContext(new BigNumber(7)); * context.pow(new BigNumber(3), new BigNumber(2)); // Returns 2 (3^2 % 7) */ pow(a, num) { if (num.isZero()) return new BigNumber_js_1.default(1).toRed(this); if (num.cmpn(1) === 0) return a.clone(); const windowSize = 4; const wnd = new Array(1 << windowSize); wnd[0] = new BigNumber_js_1.default(1).toRed(this); wnd[1] = a; let i = 2; for (; i < wnd.length; i++) { wnd[i] = this.mul(wnd[i - 1], a); } let res = wnd[0]; let current = 0; let currentLen = 0; let start = num.bitLength() % 26; if (start === 0) { start = 26; } for (i = num.length - 1; i >= 0; i--) { const word = num.words[i]; for (let j = start - 1; j >= 0; j--) { const bit = (word >> j) & 1; if (res !== wnd[0]) { res = this.sqr(res); } if (bit === 0 && current === 0) { currentLen = 0; continue; } current <<= 1; current |= bit; currentLen++; if (currentLen !== windowSize && (i !== 0 || j !== 0)) continue; res = this.mul(res, wnd[current]); currentLen = 0; current = 0; } start = 26; } return res; } /** * Converts a BigNumber to its equivalent in the reduction context. * * @method convertTo * * @param num - The BigNumber to convert to the reduction context. * * @returns Returns the converted BigNumber compatible with the reduction context. * * @example * const context = new ReductionContext(new BigNumber(7)); * context.convertTo(new BigNumber(8)); // Returns 1 (8 % 7) */ convertTo(num) { const r = num.umod(this.m); return r === num ? r.clone() : r; } /** * Converts a BigNumber from reduction context to its regular form. * * @method convertFrom * * @param num - The BigNumber to convert from the reduction context. * * @returns Returns the converted BigNumber in its regular form. * * @example * const context = new ReductionContext(new BigNumber(7)); * const a = context.convertTo(new BigNumber(8)); // 'a' is now 1 in the reduction context * context.convertFrom(a); // Returns 1 */ convertFrom(num) { const res = num.clone(); res.red = null; return res; } } exports.default = ReductionContext; //# sourceMappingURL=ReductionContext.js.map