ipfs storage for images and other nontext items. for use with etica - runs on etica network and currencys
https://collect.etica-stats.org
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1063 lines
45 KiB
1063 lines
45 KiB
"use strict"; |
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
exports.mapToCurveSimpleSWU = exports.SWUFpSqrtRatio = exports.weierstrass = exports.weierstrassPoints = exports.DER = void 0; |
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ |
|
// Short Weierstrass curve. The formula is: y² = x³ + ax + b |
|
const mod = require("./modular.js"); |
|
const ut = require("./utils.js"); |
|
const utils_js_1 = require("./utils.js"); |
|
const curve_js_1 = require("./curve.js"); |
|
function validatePointOpts(curve) { |
|
const opts = (0, curve_js_1.validateBasic)(curve); |
|
ut.validateObject(opts, { |
|
a: 'field', |
|
b: 'field', |
|
}, { |
|
allowedPrivateKeyLengths: 'array', |
|
wrapPrivateKey: 'boolean', |
|
isTorsionFree: 'function', |
|
clearCofactor: 'function', |
|
allowInfinityPoint: 'boolean', |
|
fromBytes: 'function', |
|
toBytes: 'function', |
|
}); |
|
const { endo, Fp, a } = opts; |
|
if (endo) { |
|
if (!Fp.eql(a, Fp.ZERO)) { |
|
throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0'); |
|
} |
|
if (typeof endo !== 'object' || |
|
typeof endo.beta !== 'bigint' || |
|
typeof endo.splitScalar !== 'function') { |
|
throw new Error('Expected endomorphism with beta: bigint and splitScalar: function'); |
|
} |
|
} |
|
return Object.freeze({ ...opts }); |
|
} |
|
// ASN.1 DER encoding utilities |
|
const { bytesToNumberBE: b2n, hexToBytes: h2b } = ut; |
|
exports.DER = { |
|
// asn.1 DER encoding utils |
|
Err: class DERErr extends Error { |
|
constructor(m = '') { |
|
super(m); |
|
} |
|
}, |
|
_parseInt(data) { |
|
const { Err: E } = exports.DER; |
|
if (data.length < 2 || data[0] !== 0x02) |
|
throw new E('Invalid signature integer tag'); |
|
const len = data[1]; |
|
const res = data.subarray(2, len + 2); |
|
if (!len || res.length !== len) |
|
throw new E('Invalid signature integer: wrong length'); |
|
// https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag, |
|
// since we always use positive integers here. It must always be empty: |
|
// - add zero byte if exists |
|
// - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding) |
|
if (res[0] & 0b10000000) |
|
throw new E('Invalid signature integer: negative'); |
|
if (res[0] === 0x00 && !(res[1] & 0b10000000)) |
|
throw new E('Invalid signature integer: unnecessary leading zero'); |
|
return { d: b2n(res), l: data.subarray(len + 2) }; // d is data, l is left |
|
}, |
|
toSig(hex) { |
|
// parse DER signature |
|
const { Err: E } = exports.DER; |
|
const data = typeof hex === 'string' ? h2b(hex) : hex; |
|
if (!(data instanceof Uint8Array)) |
|
throw new Error('ui8a expected'); |
|
let l = data.length; |
|
if (l < 2 || data[0] != 0x30) |
|
throw new E('Invalid signature tag'); |
|
if (data[1] !== l - 2) |
|
throw new E('Invalid signature: incorrect length'); |
|
const { d: r, l: sBytes } = exports.DER._parseInt(data.subarray(2)); |
|
const { d: s, l: rBytesLeft } = exports.DER._parseInt(sBytes); |
|
if (rBytesLeft.length) |
|
throw new E('Invalid signature: left bytes after parsing'); |
|
return { r, s }; |
|
}, |
|
hexFromSig(sig) { |
|
// Add leading zero if first byte has negative bit enabled. More details in '_parseInt' |
|
const slice = (s) => (Number.parseInt(s[0], 16) & 0b1000 ? '00' + s : s); |
|
const h = (num) => { |
|
const hex = num.toString(16); |
|
return hex.length & 1 ? `0${hex}` : hex; |
|
}; |
|
const s = slice(h(sig.s)); |
|
const r = slice(h(sig.r)); |
|
const shl = s.length / 2; |
|
const rhl = r.length / 2; |
|
const sl = h(shl); |
|
const rl = h(rhl); |
|
return `30${h(rhl + shl + 4)}02${rl}${r}02${sl}${s}`; |
|
}, |
|
}; |
|
// Be friendly to bad ECMAScript parsers by not using bigint literals |
|
// prettier-ignore |
|
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4); |
|
function weierstrassPoints(opts) { |
|
const CURVE = validatePointOpts(opts); |
|
const { Fp } = CURVE; // All curves has same field / group length as for now, but they can differ |
|
const toBytes = CURVE.toBytes || |
|
((_c, point, _isCompressed) => { |
|
const a = point.toAffine(); |
|
return ut.concatBytes(Uint8Array.from([0x04]), Fp.toBytes(a.x), Fp.toBytes(a.y)); |
|
}); |
|
const fromBytes = CURVE.fromBytes || |
|
((bytes) => { |
|
// const head = bytes[0]; |
|
const tail = bytes.subarray(1); |
|
// if (head !== 0x04) throw new Error('Only non-compressed encoding is supported'); |
|
const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES)); |
|
const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES)); |
|
return { x, y }; |
|
}); |
|
/** |
|
* y² = x³ + ax + b: Short weierstrass curve formula |
|
* @returns y² |
|
*/ |
|
function weierstrassEquation(x) { |
|
const { a, b } = CURVE; |
|
const x2 = Fp.sqr(x); // x * x |
|
const x3 = Fp.mul(x2, x); // x2 * x |
|
return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b |
|
} |
|
// Validate whether the passed curve params are valid. |
|
// We check if curve equation works for generator point. |
|
// `assertValidity()` won't work: `isTorsionFree()` is not available at this point in bls12-381. |
|
// ProjectivePoint class has not been initialized yet. |
|
if (!Fp.eql(Fp.sqr(CURVE.Gy), weierstrassEquation(CURVE.Gx))) |
|
throw new Error('bad generator point: equation left != right'); |
|
// Valid group elements reside in range 1..n-1 |
|
function isWithinCurveOrder(num) { |
|
return typeof num === 'bigint' && _0n < num && num < CURVE.n; |
|
} |
|
function assertGE(num) { |
|
if (!isWithinCurveOrder(num)) |
|
throw new Error('Expected valid bigint: 0 < bigint < curve.n'); |
|
} |
|
// Validates if priv key is valid and converts it to bigint. |
|
// Supports options allowedPrivateKeyLengths and wrapPrivateKey. |
|
function normPrivateKeyToScalar(key) { |
|
const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE; |
|
if (lengths && typeof key !== 'bigint') { |
|
if (key instanceof Uint8Array) |
|
key = ut.bytesToHex(key); |
|
// Normalize to hex string, pad. E.g. P521 would norm 130-132 char hex to 132-char bytes |
|
if (typeof key !== 'string' || !lengths.includes(key.length)) |
|
throw new Error('Invalid key'); |
|
key = key.padStart(nByteLength * 2, '0'); |
|
} |
|
let num; |
|
try { |
|
num = |
|
typeof key === 'bigint' |
|
? key |
|
: ut.bytesToNumberBE((0, utils_js_1.ensureBytes)('private key', key, nByteLength)); |
|
} |
|
catch (error) { |
|
throw new Error(`private key must be ${nByteLength} bytes, hex or bigint, not ${typeof key}`); |
|
} |
|
if (wrapPrivateKey) |
|
num = mod.mod(num, n); // disabled by default, enabled for BLS |
|
assertGE(num); // num in range [1..N-1] |
|
return num; |
|
} |
|
const pointPrecomputes = new Map(); |
|
function assertPrjPoint(other) { |
|
if (!(other instanceof Point)) |
|
throw new Error('ProjectivePoint expected'); |
|
} |
|
/** |
|
* Projective Point works in 3d / projective (homogeneous) coordinates: (x, y, z) ∋ (x=x/z, y=y/z) |
|
* Default Point works in 2d / affine coordinates: (x, y) |
|
* We're doing calculations in projective, because its operations don't require costly inversion. |
|
*/ |
|
class Point { |
|
constructor(px, py, pz) { |
|
this.px = px; |
|
this.py = py; |
|
this.pz = pz; |
|
if (px == null || !Fp.isValid(px)) |
|
throw new Error('x required'); |
|
if (py == null || !Fp.isValid(py)) |
|
throw new Error('y required'); |
|
if (pz == null || !Fp.isValid(pz)) |
|
throw new Error('z required'); |
|
} |
|
// Does not validate if the point is on-curve. |
|
// Use fromHex instead, or call assertValidity() later. |
|
static fromAffine(p) { |
|
const { x, y } = p || {}; |
|
if (!p || !Fp.isValid(x) || !Fp.isValid(y)) |
|
throw new Error('invalid affine point'); |
|
if (p instanceof Point) |
|
throw new Error('projective point not allowed'); |
|
const is0 = (i) => Fp.eql(i, Fp.ZERO); |
|
// fromAffine(x:0, y:0) would produce (x:0, y:0, z:1), but we need (x:0, y:1, z:0) |
|
if (is0(x) && is0(y)) |
|
return Point.ZERO; |
|
return new Point(x, y, Fp.ONE); |
|
} |
|
get x() { |
|
return this.toAffine().x; |
|
} |
|
get y() { |
|
return this.toAffine().y; |
|
} |
|
/** |
|
* Takes a bunch of Projective Points but executes only one |
|
* inversion on all of them. Inversion is very slow operation, |
|
* so this improves performance massively. |
|
* Optimization: converts a list of projective points to a list of identical points with Z=1. |
|
*/ |
|
static normalizeZ(points) { |
|
const toInv = Fp.invertBatch(points.map((p) => p.pz)); |
|
return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine); |
|
} |
|
/** |
|
* Converts hash string or Uint8Array to Point. |
|
* @param hex short/long ECDSA hex |
|
*/ |
|
static fromHex(hex) { |
|
const P = Point.fromAffine(fromBytes((0, utils_js_1.ensureBytes)('pointHex', hex))); |
|
P.assertValidity(); |
|
return P; |
|
} |
|
// Multiplies generator point by privateKey. |
|
static fromPrivateKey(privateKey) { |
|
return Point.BASE.multiply(normPrivateKeyToScalar(privateKey)); |
|
} |
|
// "Private method", don't use it directly |
|
_setWindowSize(windowSize) { |
|
this._WINDOW_SIZE = windowSize; |
|
pointPrecomputes.delete(this); |
|
} |
|
// A point on curve is valid if it conforms to equation. |
|
assertValidity() { |
|
if (this.is0()) { |
|
// (0, 1, 0) aka ZERO is invalid in most contexts. |
|
// In BLS, ZERO can be serialized, so we allow it. |
|
// (0, 0, 0) is wrong representation of ZERO and is always invalid. |
|
if (CURVE.allowInfinityPoint && !Fp.is0(this.py)) |
|
return; |
|
throw new Error('bad point: ZERO'); |
|
} |
|
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex` |
|
const { x, y } = this.toAffine(); |
|
// Check if x, y are valid field elements |
|
if (!Fp.isValid(x) || !Fp.isValid(y)) |
|
throw new Error('bad point: x or y not FE'); |
|
const left = Fp.sqr(y); // y² |
|
const right = weierstrassEquation(x); // x³ + ax + b |
|
if (!Fp.eql(left, right)) |
|
throw new Error('bad point: equation left != right'); |
|
if (!this.isTorsionFree()) |
|
throw new Error('bad point: not in prime-order subgroup'); |
|
} |
|
hasEvenY() { |
|
const { y } = this.toAffine(); |
|
if (Fp.isOdd) |
|
return !Fp.isOdd(y); |
|
throw new Error("Field doesn't support isOdd"); |
|
} |
|
/** |
|
* Compare one point to another. |
|
*/ |
|
equals(other) { |
|
assertPrjPoint(other); |
|
const { px: X1, py: Y1, pz: Z1 } = this; |
|
const { px: X2, py: Y2, pz: Z2 } = other; |
|
const U1 = Fp.eql(Fp.mul(X1, Z2), Fp.mul(X2, Z1)); |
|
const U2 = Fp.eql(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1)); |
|
return U1 && U2; |
|
} |
|
/** |
|
* Flips point to one corresponding to (x, -y) in Affine coordinates. |
|
*/ |
|
negate() { |
|
return new Point(this.px, Fp.neg(this.py), this.pz); |
|
} |
|
// Renes-Costello-Batina exception-free doubling formula. |
|
// There is 30% faster Jacobian formula, but it is not complete. |
|
// https://eprint.iacr.org/2015/1060, algorithm 3 |
|
// Cost: 8M + 3S + 3*a + 2*b3 + 15add. |
|
double() { |
|
const { a, b } = CURVE; |
|
const b3 = Fp.mul(b, _3n); |
|
const { px: X1, py: Y1, pz: Z1 } = this; |
|
let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore |
|
let t0 = Fp.mul(X1, X1); // step 1 |
|
let t1 = Fp.mul(Y1, Y1); |
|
let t2 = Fp.mul(Z1, Z1); |
|
let t3 = Fp.mul(X1, Y1); |
|
t3 = Fp.add(t3, t3); // step 5 |
|
Z3 = Fp.mul(X1, Z1); |
|
Z3 = Fp.add(Z3, Z3); |
|
X3 = Fp.mul(a, Z3); |
|
Y3 = Fp.mul(b3, t2); |
|
Y3 = Fp.add(X3, Y3); // step 10 |
|
X3 = Fp.sub(t1, Y3); |
|
Y3 = Fp.add(t1, Y3); |
|
Y3 = Fp.mul(X3, Y3); |
|
X3 = Fp.mul(t3, X3); |
|
Z3 = Fp.mul(b3, Z3); // step 15 |
|
t2 = Fp.mul(a, t2); |
|
t3 = Fp.sub(t0, t2); |
|
t3 = Fp.mul(a, t3); |
|
t3 = Fp.add(t3, Z3); |
|
Z3 = Fp.add(t0, t0); // step 20 |
|
t0 = Fp.add(Z3, t0); |
|
t0 = Fp.add(t0, t2); |
|
t0 = Fp.mul(t0, t3); |
|
Y3 = Fp.add(Y3, t0); |
|
t2 = Fp.mul(Y1, Z1); // step 25 |
|
t2 = Fp.add(t2, t2); |
|
t0 = Fp.mul(t2, t3); |
|
X3 = Fp.sub(X3, t0); |
|
Z3 = Fp.mul(t2, t1); |
|
Z3 = Fp.add(Z3, Z3); // step 30 |
|
Z3 = Fp.add(Z3, Z3); |
|
return new Point(X3, Y3, Z3); |
|
} |
|
// Renes-Costello-Batina exception-free addition formula. |
|
// There is 30% faster Jacobian formula, but it is not complete. |
|
// https://eprint.iacr.org/2015/1060, algorithm 1 |
|
// Cost: 12M + 0S + 3*a + 3*b3 + 23add. |
|
add(other) { |
|
assertPrjPoint(other); |
|
const { px: X1, py: Y1, pz: Z1 } = this; |
|
const { px: X2, py: Y2, pz: Z2 } = other; |
|
let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore |
|
const a = CURVE.a; |
|
const b3 = Fp.mul(CURVE.b, _3n); |
|
let t0 = Fp.mul(X1, X2); // step 1 |
|
let t1 = Fp.mul(Y1, Y2); |
|
let t2 = Fp.mul(Z1, Z2); |
|
let t3 = Fp.add(X1, Y1); |
|
let t4 = Fp.add(X2, Y2); // step 5 |
|
t3 = Fp.mul(t3, t4); |
|
t4 = Fp.add(t0, t1); |
|
t3 = Fp.sub(t3, t4); |
|
t4 = Fp.add(X1, Z1); |
|
let t5 = Fp.add(X2, Z2); // step 10 |
|
t4 = Fp.mul(t4, t5); |
|
t5 = Fp.add(t0, t2); |
|
t4 = Fp.sub(t4, t5); |
|
t5 = Fp.add(Y1, Z1); |
|
X3 = Fp.add(Y2, Z2); // step 15 |
|
t5 = Fp.mul(t5, X3); |
|
X3 = Fp.add(t1, t2); |
|
t5 = Fp.sub(t5, X3); |
|
Z3 = Fp.mul(a, t4); |
|
X3 = Fp.mul(b3, t2); // step 20 |
|
Z3 = Fp.add(X3, Z3); |
|
X3 = Fp.sub(t1, Z3); |
|
Z3 = Fp.add(t1, Z3); |
|
Y3 = Fp.mul(X3, Z3); |
|
t1 = Fp.add(t0, t0); // step 25 |
|
t1 = Fp.add(t1, t0); |
|
t2 = Fp.mul(a, t2); |
|
t4 = Fp.mul(b3, t4); |
|
t1 = Fp.add(t1, t2); |
|
t2 = Fp.sub(t0, t2); // step 30 |
|
t2 = Fp.mul(a, t2); |
|
t4 = Fp.add(t4, t2); |
|
t0 = Fp.mul(t1, t4); |
|
Y3 = Fp.add(Y3, t0); |
|
t0 = Fp.mul(t5, t4); // step 35 |
|
X3 = Fp.mul(t3, X3); |
|
X3 = Fp.sub(X3, t0); |
|
t0 = Fp.mul(t3, t1); |
|
Z3 = Fp.mul(t5, Z3); |
|
Z3 = Fp.add(Z3, t0); // step 40 |
|
return new Point(X3, Y3, Z3); |
|
} |
|
subtract(other) { |
|
return this.add(other.negate()); |
|
} |
|
is0() { |
|
return this.equals(Point.ZERO); |
|
} |
|
wNAF(n) { |
|
return wnaf.wNAFCached(this, pointPrecomputes, n, (comp) => { |
|
const toInv = Fp.invertBatch(comp.map((p) => p.pz)); |
|
return comp.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine); |
|
}); |
|
} |
|
/** |
|
* Non-constant-time multiplication. Uses double-and-add algorithm. |
|
* It's faster, but should only be used when you don't care about |
|
* an exposed private key e.g. sig verification, which works over *public* keys. |
|
*/ |
|
multiplyUnsafe(n) { |
|
const I = Point.ZERO; |
|
if (n === _0n) |
|
return I; |
|
assertGE(n); // Will throw on 0 |
|
if (n === _1n) |
|
return this; |
|
const { endo } = CURVE; |
|
if (!endo) |
|
return wnaf.unsafeLadder(this, n); |
|
// Apply endomorphism |
|
let { k1neg, k1, k2neg, k2 } = endo.splitScalar(n); |
|
let k1p = I; |
|
let k2p = I; |
|
let d = this; |
|
while (k1 > _0n || k2 > _0n) { |
|
if (k1 & _1n) |
|
k1p = k1p.add(d); |
|
if (k2 & _1n) |
|
k2p = k2p.add(d); |
|
d = d.double(); |
|
k1 >>= _1n; |
|
k2 >>= _1n; |
|
} |
|
if (k1neg) |
|
k1p = k1p.negate(); |
|
if (k2neg) |
|
k2p = k2p.negate(); |
|
k2p = new Point(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz); |
|
return k1p.add(k2p); |
|
} |
|
/** |
|
* Constant time multiplication. |
|
* Uses wNAF method. Windowed method may be 10% faster, |
|
* but takes 2x longer to generate and consumes 2x memory. |
|
* Uses precomputes when available. |
|
* Uses endomorphism for Koblitz curves. |
|
* @param scalar by which the point would be multiplied |
|
* @returns New point |
|
*/ |
|
multiply(scalar) { |
|
assertGE(scalar); |
|
let n = scalar; |
|
let point, fake; // Fake point is used to const-time mult |
|
const { endo } = CURVE; |
|
if (endo) { |
|
const { k1neg, k1, k2neg, k2 } = endo.splitScalar(n); |
|
let { p: k1p, f: f1p } = this.wNAF(k1); |
|
let { p: k2p, f: f2p } = this.wNAF(k2); |
|
k1p = wnaf.constTimeNegate(k1neg, k1p); |
|
k2p = wnaf.constTimeNegate(k2neg, k2p); |
|
k2p = new Point(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz); |
|
point = k1p.add(k2p); |
|
fake = f1p.add(f2p); |
|
} |
|
else { |
|
const { p, f } = this.wNAF(n); |
|
point = p; |
|
fake = f; |
|
} |
|
// Normalize `z` for both points, but return only real one |
|
return Point.normalizeZ([point, fake])[0]; |
|
} |
|
/** |
|
* Efficiently calculate `aP + bQ`. Unsafe, can expose private key, if used incorrectly. |
|
* Not using Strauss-Shamir trick: precomputation tables are faster. |
|
* The trick could be useful if both P and Q are not G (not in our case). |
|
* @returns non-zero affine point |
|
*/ |
|
multiplyAndAddUnsafe(Q, a, b) { |
|
const G = Point.BASE; // No Strauss-Shamir trick: we have 10% faster G precomputes |
|
const mul = (P, a // Select faster multiply() method |
|
) => (a === _0n || a === _1n || !P.equals(G) ? P.multiplyUnsafe(a) : P.multiply(a)); |
|
const sum = mul(this, a).add(mul(Q, b)); |
|
return sum.is0() ? undefined : sum; |
|
} |
|
// Converts Projective point to affine (x, y) coordinates. |
|
// Can accept precomputed Z^-1 - for example, from invertBatch. |
|
// (x, y, z) ∋ (x=x/z, y=y/z) |
|
toAffine(iz) { |
|
const { px: x, py: y, pz: z } = this; |
|
const is0 = this.is0(); |
|
// If invZ was 0, we return zero point. However we still want to execute |
|
// all operations, so we replace invZ with a random number, 1. |
|
if (iz == null) |
|
iz = is0 ? Fp.ONE : Fp.inv(z); |
|
const ax = Fp.mul(x, iz); |
|
const ay = Fp.mul(y, iz); |
|
const zz = Fp.mul(z, iz); |
|
if (is0) |
|
return { x: Fp.ZERO, y: Fp.ZERO }; |
|
if (!Fp.eql(zz, Fp.ONE)) |
|
throw new Error('invZ was invalid'); |
|
return { x: ax, y: ay }; |
|
} |
|
isTorsionFree() { |
|
const { h: cofactor, isTorsionFree } = CURVE; |
|
if (cofactor === _1n) |
|
return true; // No subgroups, always torsion-free |
|
if (isTorsionFree) |
|
return isTorsionFree(Point, this); |
|
throw new Error('isTorsionFree() has not been declared for the elliptic curve'); |
|
} |
|
clearCofactor() { |
|
const { h: cofactor, clearCofactor } = CURVE; |
|
if (cofactor === _1n) |
|
return this; // Fast-path |
|
if (clearCofactor) |
|
return clearCofactor(Point, this); |
|
return this.multiplyUnsafe(CURVE.h); |
|
} |
|
toRawBytes(isCompressed = true) { |
|
this.assertValidity(); |
|
return toBytes(Point, this, isCompressed); |
|
} |
|
toHex(isCompressed = true) { |
|
return ut.bytesToHex(this.toRawBytes(isCompressed)); |
|
} |
|
} |
|
Point.BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE); |
|
Point.ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO); |
|
const _bits = CURVE.nBitLength; |
|
const wnaf = (0, curve_js_1.wNAF)(Point, CURVE.endo ? Math.ceil(_bits / 2) : _bits); |
|
// Validate if generator point is on curve |
|
return { |
|
CURVE, |
|
ProjectivePoint: Point, |
|
normPrivateKeyToScalar, |
|
weierstrassEquation, |
|
isWithinCurveOrder, |
|
}; |
|
} |
|
exports.weierstrassPoints = weierstrassPoints; |
|
function validateOpts(curve) { |
|
const opts = (0, curve_js_1.validateBasic)(curve); |
|
ut.validateObject(opts, { |
|
hash: 'hash', |
|
hmac: 'function', |
|
randomBytes: 'function', |
|
}, { |
|
bits2int: 'function', |
|
bits2int_modN: 'function', |
|
lowS: 'boolean', |
|
}); |
|
return Object.freeze({ lowS: true, ...opts }); |
|
} |
|
function weierstrass(curveDef) { |
|
const CURVE = validateOpts(curveDef); |
|
const { Fp, n: CURVE_ORDER } = CURVE; |
|
const compressedLen = Fp.BYTES + 1; // e.g. 33 for 32 |
|
const uncompressedLen = 2 * Fp.BYTES + 1; // e.g. 65 for 32 |
|
function isValidFieldElement(num) { |
|
return _0n < num && num < Fp.ORDER; // 0 is banned since it's not invertible FE |
|
} |
|
function modN(a) { |
|
return mod.mod(a, CURVE_ORDER); |
|
} |
|
function invN(a) { |
|
return mod.invert(a, CURVE_ORDER); |
|
} |
|
const { ProjectivePoint: Point, normPrivateKeyToScalar, weierstrassEquation, isWithinCurveOrder, } = weierstrassPoints({ |
|
...CURVE, |
|
toBytes(_c, point, isCompressed) { |
|
const a = point.toAffine(); |
|
const x = Fp.toBytes(a.x); |
|
const cat = ut.concatBytes; |
|
if (isCompressed) { |
|
return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x); |
|
} |
|
else { |
|
return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y)); |
|
} |
|
}, |
|
fromBytes(bytes) { |
|
const len = bytes.length; |
|
const head = bytes[0]; |
|
const tail = bytes.subarray(1); |
|
// this.assertValidity() is done inside of fromHex |
|
if (len === compressedLen && (head === 0x02 || head === 0x03)) { |
|
const x = ut.bytesToNumberBE(tail); |
|
if (!isValidFieldElement(x)) |
|
throw new Error('Point is not on curve'); |
|
const y2 = weierstrassEquation(x); // y² = x³ + ax + b |
|
let y = Fp.sqrt(y2); // y = y² ^ (p+1)/4 |
|
const isYOdd = (y & _1n) === _1n; |
|
// ECDSA |
|
const isHeadOdd = (head & 1) === 1; |
|
if (isHeadOdd !== isYOdd) |
|
y = Fp.neg(y); |
|
return { x, y }; |
|
} |
|
else if (len === uncompressedLen && head === 0x04) { |
|
const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES)); |
|
const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES)); |
|
return { x, y }; |
|
} |
|
else { |
|
throw new Error(`Point of length ${len} was invalid. Expected ${compressedLen} compressed bytes or ${uncompressedLen} uncompressed bytes`); |
|
} |
|
}, |
|
}); |
|
const numToNByteStr = (num) => ut.bytesToHex(ut.numberToBytesBE(num, CURVE.nByteLength)); |
|
function isBiggerThanHalfOrder(number) { |
|
const HALF = CURVE_ORDER >> _1n; |
|
return number > HALF; |
|
} |
|
function normalizeS(s) { |
|
return isBiggerThanHalfOrder(s) ? modN(-s) : s; |
|
} |
|
// slice bytes num |
|
const slcNum = (b, from, to) => ut.bytesToNumberBE(b.slice(from, to)); |
|
/** |
|
* ECDSA signature with its (r, s) properties. Supports DER & compact representations. |
|
*/ |
|
class Signature { |
|
constructor(r, s, recovery) { |
|
this.r = r; |
|
this.s = s; |
|
this.recovery = recovery; |
|
this.assertValidity(); |
|
} |
|
// pair (bytes of r, bytes of s) |
|
static fromCompact(hex) { |
|
const l = CURVE.nByteLength; |
|
hex = (0, utils_js_1.ensureBytes)('compactSignature', hex, l * 2); |
|
return new Signature(slcNum(hex, 0, l), slcNum(hex, l, 2 * l)); |
|
} |
|
// DER encoded ECDSA signature |
|
// https://bitcoin.stackexchange.com/questions/57644/what-are-the-parts-of-a-bitcoin-transaction-input-script |
|
static fromDER(hex) { |
|
const { r, s } = exports.DER.toSig((0, utils_js_1.ensureBytes)('DER', hex)); |
|
return new Signature(r, s); |
|
} |
|
assertValidity() { |
|
// can use assertGE here |
|
if (!isWithinCurveOrder(this.r)) |
|
throw new Error('r must be 0 < r < CURVE.n'); |
|
if (!isWithinCurveOrder(this.s)) |
|
throw new Error('s must be 0 < s < CURVE.n'); |
|
} |
|
addRecoveryBit(recovery) { |
|
return new Signature(this.r, this.s, recovery); |
|
} |
|
recoverPublicKey(msgHash) { |
|
const { r, s, recovery: rec } = this; |
|
const h = bits2int_modN((0, utils_js_1.ensureBytes)('msgHash', msgHash)); // Truncate hash |
|
if (rec == null || ![0, 1, 2, 3].includes(rec)) |
|
throw new Error('recovery id invalid'); |
|
const radj = rec === 2 || rec === 3 ? r + CURVE.n : r; |
|
if (radj >= Fp.ORDER) |
|
throw new Error('recovery id 2 or 3 invalid'); |
|
const prefix = (rec & 1) === 0 ? '02' : '03'; |
|
const R = Point.fromHex(prefix + numToNByteStr(radj)); |
|
const ir = invN(radj); // r^-1 |
|
const u1 = modN(-h * ir); // -hr^-1 |
|
const u2 = modN(s * ir); // sr^-1 |
|
const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1) |
|
if (!Q) |
|
throw new Error('point at infinify'); // unsafe is fine: no priv data leaked |
|
Q.assertValidity(); |
|
return Q; |
|
} |
|
// Signatures should be low-s, to prevent malleability. |
|
hasHighS() { |
|
return isBiggerThanHalfOrder(this.s); |
|
} |
|
normalizeS() { |
|
return this.hasHighS() ? new Signature(this.r, modN(-this.s), this.recovery) : this; |
|
} |
|
// DER-encoded |
|
toDERRawBytes() { |
|
return ut.hexToBytes(this.toDERHex()); |
|
} |
|
toDERHex() { |
|
return exports.DER.hexFromSig({ r: this.r, s: this.s }); |
|
} |
|
// padded bytes of r, then padded bytes of s |
|
toCompactRawBytes() { |
|
return ut.hexToBytes(this.toCompactHex()); |
|
} |
|
toCompactHex() { |
|
return numToNByteStr(this.r) + numToNByteStr(this.s); |
|
} |
|
} |
|
const utils = { |
|
isValidPrivateKey(privateKey) { |
|
try { |
|
normPrivateKeyToScalar(privateKey); |
|
return true; |
|
} |
|
catch (error) { |
|
return false; |
|
} |
|
}, |
|
normPrivateKeyToScalar: normPrivateKeyToScalar, |
|
/** |
|
* Produces cryptographically secure private key from random of size |
|
* (groupLen + ceil(groupLen / 2)) with modulo bias being negligible. |
|
*/ |
|
randomPrivateKey: () => { |
|
const length = mod.getMinHashLength(CURVE.n); |
|
return mod.mapHashToField(CURVE.randomBytes(length), CURVE.n); |
|
}, |
|
/** |
|
* Creates precompute table for an arbitrary EC point. Makes point "cached". |
|
* Allows to massively speed-up `point.multiply(scalar)`. |
|
* @returns cached point |
|
* @example |
|
* const fast = utils.precompute(8, ProjectivePoint.fromHex(someonesPubKey)); |
|
* fast.multiply(privKey); // much faster ECDH now |
|
*/ |
|
precompute(windowSize = 8, point = Point.BASE) { |
|
point._setWindowSize(windowSize); |
|
point.multiply(BigInt(3)); // 3 is arbitrary, just need any number here |
|
return point; |
|
}, |
|
}; |
|
/** |
|
* Computes public key for a private key. Checks for validity of the private key. |
|
* @param privateKey private key |
|
* @param isCompressed whether to return compact (default), or full key |
|
* @returns Public key, full when isCompressed=false; short when isCompressed=true |
|
*/ |
|
function getPublicKey(privateKey, isCompressed = true) { |
|
return Point.fromPrivateKey(privateKey).toRawBytes(isCompressed); |
|
} |
|
/** |
|
* Quick and dirty check for item being public key. Does not validate hex, or being on-curve. |
|
*/ |
|
function isProbPub(item) { |
|
const arr = item instanceof Uint8Array; |
|
const str = typeof item === 'string'; |
|
const len = (arr || str) && item.length; |
|
if (arr) |
|
return len === compressedLen || len === uncompressedLen; |
|
if (str) |
|
return len === 2 * compressedLen || len === 2 * uncompressedLen; |
|
if (item instanceof Point) |
|
return true; |
|
return false; |
|
} |
|
/** |
|
* ECDH (Elliptic Curve Diffie Hellman). |
|
* Computes shared public key from private key and public key. |
|
* Checks: 1) private key validity 2) shared key is on-curve. |
|
* Does NOT hash the result. |
|
* @param privateA private key |
|
* @param publicB different public key |
|
* @param isCompressed whether to return compact (default), or full key |
|
* @returns shared public key |
|
*/ |
|
function getSharedSecret(privateA, publicB, isCompressed = true) { |
|
if (isProbPub(privateA)) |
|
throw new Error('first arg must be private key'); |
|
if (!isProbPub(publicB)) |
|
throw new Error('second arg must be public key'); |
|
const b = Point.fromHex(publicB); // check for being on-curve |
|
return b.multiply(normPrivateKeyToScalar(privateA)).toRawBytes(isCompressed); |
|
} |
|
// RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets. |
|
// FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which matches bits2int. |
|
// bits2int can produce res>N, we can do mod(res, N) since the bitLen is the same. |
|
// int2octets can't be used; pads small msgs with 0: unacceptatble for trunc as per RFC vectors |
|
const bits2int = CURVE.bits2int || |
|
function (bytes) { |
|
// For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m) |
|
// for some cases, since bytes.length * 8 is not actual bitLength. |
|
const num = ut.bytesToNumberBE(bytes); // check for == u8 done here |
|
const delta = bytes.length * 8 - CURVE.nBitLength; // truncate to nBitLength leftmost bits |
|
return delta > 0 ? num >> BigInt(delta) : num; |
|
}; |
|
const bits2int_modN = CURVE.bits2int_modN || |
|
function (bytes) { |
|
return modN(bits2int(bytes)); // can't use bytesToNumberBE here |
|
}; |
|
// NOTE: pads output with zero as per spec |
|
const ORDER_MASK = ut.bitMask(CURVE.nBitLength); |
|
/** |
|
* Converts to bytes. Checks if num in `[0..ORDER_MASK-1]` e.g.: `[0..2^256-1]`. |
|
*/ |
|
function int2octets(num) { |
|
if (typeof num !== 'bigint') |
|
throw new Error('bigint expected'); |
|
if (!(_0n <= num && num < ORDER_MASK)) |
|
throw new Error(`bigint expected < 2^${CURVE.nBitLength}`); |
|
// works with order, can have different size than numToField! |
|
return ut.numberToBytesBE(num, CURVE.nByteLength); |
|
} |
|
// Steps A, D of RFC6979 3.2 |
|
// Creates RFC6979 seed; converts msg/privKey to numbers. |
|
// Used only in sign, not in verify. |
|
// NOTE: we cannot assume here that msgHash has same amount of bytes as curve order, this will be wrong at least for P521. |
|
// Also it can be bigger for P224 + SHA256 |
|
function prepSig(msgHash, privateKey, opts = defaultSigOpts) { |
|
if (['recovered', 'canonical'].some((k) => k in opts)) |
|
throw new Error('sign() legacy options not supported'); |
|
const { hash, randomBytes } = CURVE; |
|
let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default |
|
if (lowS == null) |
|
lowS = true; // RFC6979 3.2: we skip step A, because we already provide hash |
|
msgHash = (0, utils_js_1.ensureBytes)('msgHash', msgHash); |
|
if (prehash) |
|
msgHash = (0, utils_js_1.ensureBytes)('prehashed msgHash', hash(msgHash)); |
|
// We can't later call bits2octets, since nested bits2int is broken for curves |
|
// with nBitLength % 8 !== 0. Because of that, we unwrap it here as int2octets call. |
|
// const bits2octets = (bits) => int2octets(bits2int_modN(bits)) |
|
const h1int = bits2int_modN(msgHash); |
|
const d = normPrivateKeyToScalar(privateKey); // validate private key, convert to bigint |
|
const seedArgs = [int2octets(d), int2octets(h1int)]; |
|
// extraEntropy. RFC6979 3.6: additional k' (optional). |
|
if (ent != null) { |
|
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k') |
|
const e = ent === true ? randomBytes(Fp.BYTES) : ent; // generate random bytes OR pass as-is |
|
seedArgs.push((0, utils_js_1.ensureBytes)('extraEntropy', e)); // check for being bytes |
|
} |
|
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2 |
|
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash! |
|
// Converts signature params into point w r/s, checks result for validity. |
|
function k2sig(kBytes) { |
|
// RFC 6979 Section 3.2, step 3: k = bits2int(T) |
|
const k = bits2int(kBytes); // Cannot use fields methods, since it is group element |
|
if (!isWithinCurveOrder(k)) |
|
return; // Important: all mod() calls here must be done over N |
|
const ik = invN(k); // k^-1 mod n |
|
const q = Point.BASE.multiply(k).toAffine(); // q = Gk |
|
const r = modN(q.x); // r = q.x mod n |
|
if (r === _0n) |
|
return; |
|
// Can use scalar blinding b^-1(bm + bdr) where b ∈ [1,q−1] according to |
|
// https://tches.iacr.org/index.php/TCHES/article/view/7337/6509. We've decided against it: |
|
// a) dependency on CSPRNG b) 15% slowdown c) doesn't really help since bigints are not CT |
|
const s = modN(ik * modN(m + r * d)); // Not using blinding here |
|
if (s === _0n) |
|
return; |
|
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n) |
|
let normS = s; |
|
if (lowS && isBiggerThanHalfOrder(s)) { |
|
normS = normalizeS(s); // if lowS was passed, ensure s is always |
|
recovery ^= 1; // // in the bottom half of N |
|
} |
|
return new Signature(r, normS, recovery); // use normS, not s |
|
} |
|
return { seed, k2sig }; |
|
} |
|
const defaultSigOpts = { lowS: CURVE.lowS, prehash: false }; |
|
const defaultVerOpts = { lowS: CURVE.lowS, prehash: false }; |
|
/** |
|
* Signs message hash with a private key. |
|
* ``` |
|
* sign(m, d, k) where |
|
* (x, y) = G × k |
|
* r = x mod n |
|
* s = (m + dr)/k mod n |
|
* ``` |
|
* @param msgHash NOT message. msg needs to be hashed to `msgHash`, or use `prehash`. |
|
* @param privKey private key |
|
* @param opts lowS for non-malleable sigs. extraEntropy for mixing randomness into k. prehash will hash first arg. |
|
* @returns signature with recovery param |
|
*/ |
|
function sign(msgHash, privKey, opts = defaultSigOpts) { |
|
const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2. |
|
const C = CURVE; |
|
const drbg = ut.createHmacDrbg(C.hash.outputLen, C.nByteLength, C.hmac); |
|
return drbg(seed, k2sig); // Steps B, C, D, E, F, G |
|
} |
|
// Enable precomputes. Slows down first publicKey computation by 20ms. |
|
Point.BASE._setWindowSize(8); |
|
// utils.precompute(8, ProjectivePoint.BASE) |
|
/** |
|
* Verifies a signature against message hash and public key. |
|
* Rejects lowS signatures by default: to override, |
|
* specify option `{lowS: false}`. Implements section 4.1.4 from https://www.secg.org/sec1-v2.pdf: |
|
* |
|
* ``` |
|
* verify(r, s, h, P) where |
|
* U1 = hs^-1 mod n |
|
* U2 = rs^-1 mod n |
|
* R = U1⋅G - U2⋅P |
|
* mod(R.x, n) == r |
|
* ``` |
|
*/ |
|
function verify(signature, msgHash, publicKey, opts = defaultVerOpts) { |
|
const sg = signature; |
|
msgHash = (0, utils_js_1.ensureBytes)('msgHash', msgHash); |
|
publicKey = (0, utils_js_1.ensureBytes)('publicKey', publicKey); |
|
if ('strict' in opts) |
|
throw new Error('options.strict was renamed to lowS'); |
|
const { lowS, prehash } = opts; |
|
let _sig = undefined; |
|
let P; |
|
try { |
|
if (typeof sg === 'string' || sg instanceof Uint8Array) { |
|
// Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length). |
|
// Since DER can also be 2*nByteLength bytes, we check for it first. |
|
try { |
|
_sig = Signature.fromDER(sg); |
|
} |
|
catch (derError) { |
|
if (!(derError instanceof exports.DER.Err)) |
|
throw derError; |
|
_sig = Signature.fromCompact(sg); |
|
} |
|
} |
|
else if (typeof sg === 'object' && typeof sg.r === 'bigint' && typeof sg.s === 'bigint') { |
|
const { r, s } = sg; |
|
_sig = new Signature(r, s); |
|
} |
|
else { |
|
throw new Error('PARSE'); |
|
} |
|
P = Point.fromHex(publicKey); |
|
} |
|
catch (error) { |
|
if (error.message === 'PARSE') |
|
throw new Error(`signature must be Signature instance, Uint8Array or hex string`); |
|
return false; |
|
} |
|
if (lowS && _sig.hasHighS()) |
|
return false; |
|
if (prehash) |
|
msgHash = CURVE.hash(msgHash); |
|
const { r, s } = _sig; |
|
const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element |
|
const is = invN(s); // s^-1 |
|
const u1 = modN(h * is); // u1 = hs^-1 mod n |
|
const u2 = modN(r * is); // u2 = rs^-1 mod n |
|
const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine(); // R = u1⋅G + u2⋅P |
|
if (!R) |
|
return false; |
|
const v = modN(R.x); |
|
return v === r; |
|
} |
|
return { |
|
CURVE, |
|
getPublicKey, |
|
getSharedSecret, |
|
sign, |
|
verify, |
|
ProjectivePoint: Point, |
|
Signature, |
|
utils, |
|
}; |
|
} |
|
exports.weierstrass = weierstrass; |
|
/** |
|
* Implementation of the Shallue and van de Woestijne method for any weierstrass curve. |
|
* TODO: check if there is a way to merge this with uvRatio in Edwards; move to modular. |
|
* b = True and y = sqrt(u / v) if (u / v) is square in F, and |
|
* b = False and y = sqrt(Z * (u / v)) otherwise. |
|
* @param Fp |
|
* @param Z |
|
* @returns |
|
*/ |
|
function SWUFpSqrtRatio(Fp, Z) { |
|
// Generic implementation |
|
const q = Fp.ORDER; |
|
let l = _0n; |
|
for (let o = q - _1n; o % _2n === _0n; o /= _2n) |
|
l += _1n; |
|
const c1 = l; // 1. c1, the largest integer such that 2^c1 divides q - 1. |
|
// We need 2n ** c1 and 2n ** (c1-1). We can't use **; but we can use <<. |
|
// 2n ** c1 == 2n << (c1-1) |
|
const _2n_pow_c1_1 = _2n << (c1 - _1n - _1n); |
|
const _2n_pow_c1 = _2n_pow_c1_1 * _2n; |
|
const c2 = (q - _1n) / _2n_pow_c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic |
|
const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic |
|
const c4 = _2n_pow_c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic |
|
const c5 = _2n_pow_c1_1; // 5. c5 = 2^(c1 - 1) # Integer arithmetic |
|
const c6 = Fp.pow(Z, c2); // 6. c6 = Z^c2 |
|
const c7 = Fp.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2) |
|
let sqrtRatio = (u, v) => { |
|
let tv1 = c6; // 1. tv1 = c6 |
|
let tv2 = Fp.pow(v, c4); // 2. tv2 = v^c4 |
|
let tv3 = Fp.sqr(tv2); // 3. tv3 = tv2^2 |
|
tv3 = Fp.mul(tv3, v); // 4. tv3 = tv3 * v |
|
let tv5 = Fp.mul(u, tv3); // 5. tv5 = u * tv3 |
|
tv5 = Fp.pow(tv5, c3); // 6. tv5 = tv5^c3 |
|
tv5 = Fp.mul(tv5, tv2); // 7. tv5 = tv5 * tv2 |
|
tv2 = Fp.mul(tv5, v); // 8. tv2 = tv5 * v |
|
tv3 = Fp.mul(tv5, u); // 9. tv3 = tv5 * u |
|
let tv4 = Fp.mul(tv3, tv2); // 10. tv4 = tv3 * tv2 |
|
tv5 = Fp.pow(tv4, c5); // 11. tv5 = tv4^c5 |
|
let isQR = Fp.eql(tv5, Fp.ONE); // 12. isQR = tv5 == 1 |
|
tv2 = Fp.mul(tv3, c7); // 13. tv2 = tv3 * c7 |
|
tv5 = Fp.mul(tv4, tv1); // 14. tv5 = tv4 * tv1 |
|
tv3 = Fp.cmov(tv2, tv3, isQR); // 15. tv3 = CMOV(tv2, tv3, isQR) |
|
tv4 = Fp.cmov(tv5, tv4, isQR); // 16. tv4 = CMOV(tv5, tv4, isQR) |
|
// 17. for i in (c1, c1 - 1, ..., 2): |
|
for (let i = c1; i > _1n; i--) { |
|
let tv5 = i - _2n; // 18. tv5 = i - 2 |
|
tv5 = _2n << (tv5 - _1n); // 19. tv5 = 2^tv5 |
|
let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5 |
|
const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1 |
|
tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1 |
|
tv1 = Fp.mul(tv1, tv1); // 23. tv1 = tv1 * tv1 |
|
tvv5 = Fp.mul(tv4, tv1); // 24. tv5 = tv4 * tv1 |
|
tv3 = Fp.cmov(tv2, tv3, e1); // 25. tv3 = CMOV(tv2, tv3, e1) |
|
tv4 = Fp.cmov(tvv5, tv4, e1); // 26. tv4 = CMOV(tv5, tv4, e1) |
|
} |
|
return { isValid: isQR, value: tv3 }; |
|
}; |
|
if (Fp.ORDER % _4n === _3n) { |
|
// sqrt_ratio_3mod4(u, v) |
|
const c1 = (Fp.ORDER - _3n) / _4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic |
|
const c2 = Fp.sqrt(Fp.neg(Z)); // 2. c2 = sqrt(-Z) |
|
sqrtRatio = (u, v) => { |
|
let tv1 = Fp.sqr(v); // 1. tv1 = v^2 |
|
const tv2 = Fp.mul(u, v); // 2. tv2 = u * v |
|
tv1 = Fp.mul(tv1, tv2); // 3. tv1 = tv1 * tv2 |
|
let y1 = Fp.pow(tv1, c1); // 4. y1 = tv1^c1 |
|
y1 = Fp.mul(y1, tv2); // 5. y1 = y1 * tv2 |
|
const y2 = Fp.mul(y1, c2); // 6. y2 = y1 * c2 |
|
const tv3 = Fp.mul(Fp.sqr(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v |
|
const isQR = Fp.eql(tv3, u); // 9. isQR = tv3 == u |
|
let y = Fp.cmov(y2, y1, isQR); // 10. y = CMOV(y2, y1, isQR) |
|
return { isValid: isQR, value: y }; // 11. return (isQR, y) isQR ? y : y*c2 |
|
}; |
|
} |
|
// No curves uses that |
|
// if (Fp.ORDER % _8n === _5n) // sqrt_ratio_5mod8 |
|
return sqrtRatio; |
|
} |
|
exports.SWUFpSqrtRatio = SWUFpSqrtRatio; |
|
/** |
|
* Simplified Shallue-van de Woestijne-Ulas Method |
|
* https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2 |
|
*/ |
|
function mapToCurveSimpleSWU(Fp, opts) { |
|
mod.validateField(Fp); |
|
if (!Fp.isValid(opts.A) || !Fp.isValid(opts.B) || !Fp.isValid(opts.Z)) |
|
throw new Error('mapToCurveSimpleSWU: invalid opts'); |
|
const sqrtRatio = SWUFpSqrtRatio(Fp, opts.Z); |
|
if (!Fp.isOdd) |
|
throw new Error('Fp.isOdd is not implemented!'); |
|
// Input: u, an element of F. |
|
// Output: (x, y), a point on E. |
|
return (u) => { |
|
// prettier-ignore |
|
let tv1, tv2, tv3, tv4, tv5, tv6, x, y; |
|
tv1 = Fp.sqr(u); // 1. tv1 = u^2 |
|
tv1 = Fp.mul(tv1, opts.Z); // 2. tv1 = Z * tv1 |
|
tv2 = Fp.sqr(tv1); // 3. tv2 = tv1^2 |
|
tv2 = Fp.add(tv2, tv1); // 4. tv2 = tv2 + tv1 |
|
tv3 = Fp.add(tv2, Fp.ONE); // 5. tv3 = tv2 + 1 |
|
tv3 = Fp.mul(tv3, opts.B); // 6. tv3 = B * tv3 |
|
tv4 = Fp.cmov(opts.Z, Fp.neg(tv2), !Fp.eql(tv2, Fp.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) |
|
tv4 = Fp.mul(tv4, opts.A); // 8. tv4 = A * tv4 |
|
tv2 = Fp.sqr(tv3); // 9. tv2 = tv3^2 |
|
tv6 = Fp.sqr(tv4); // 10. tv6 = tv4^2 |
|
tv5 = Fp.mul(tv6, opts.A); // 11. tv5 = A * tv6 |
|
tv2 = Fp.add(tv2, tv5); // 12. tv2 = tv2 + tv5 |
|
tv2 = Fp.mul(tv2, tv3); // 13. tv2 = tv2 * tv3 |
|
tv6 = Fp.mul(tv6, tv4); // 14. tv6 = tv6 * tv4 |
|
tv5 = Fp.mul(tv6, opts.B); // 15. tv5 = B * tv6 |
|
tv2 = Fp.add(tv2, tv5); // 16. tv2 = tv2 + tv5 |
|
x = Fp.mul(tv1, tv3); // 17. x = tv1 * tv3 |
|
const { isValid, value } = sqrtRatio(tv2, tv6); // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) |
|
y = Fp.mul(tv1, u); // 19. y = tv1 * u -> Z * u^3 * y1 |
|
y = Fp.mul(y, value); // 20. y = y * y1 |
|
x = Fp.cmov(x, tv3, isValid); // 21. x = CMOV(x, tv3, is_gx1_square) |
|
y = Fp.cmov(y, value, isValid); // 22. y = CMOV(y, y1, is_gx1_square) |
|
const e1 = Fp.isOdd(u) === Fp.isOdd(y); // 23. e1 = sgn0(u) == sgn0(y) |
|
y = Fp.cmov(Fp.neg(y), y, e1); // 24. y = CMOV(-y, y, e1) |
|
x = Fp.div(x, tv4); // 25. x = x / tv4 |
|
return { x, y }; |
|
}; |
|
} |
|
exports.mapToCurveSimpleSWU = mapToCurveSimpleSWU; |
|
//# sourceMappingURL=weierstrass.js.map
|