UNPKG

js-ecutils

Version:

JavaScript Library for Elliptic Curve Cryptography: key exchanges (Diffie-Hellman, Massey-Omura), ECDSA signatures, and Koblitz encoding. Suitable for crypto education and secure systems.

646 lines (599 loc) 92.9 kB
"use strict"; var _globals = require("@jest/globals"); var _point = require("./core/point"); var _curve = require("./core/curve"); var _registry = require("./curves/registry"); var _affine = require("./core/arithmetic/affine"); var _jacobian = require("./core/arithmetic/jacobian"); function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } // --------------------------------------------------------------------------- // Elliptic curve operations // // Tests for the Point-operator API on the short Weierstrass curve // y² = x³ + ax + b (mod p) // // Covers: // • Point addition (chord formula): // λ = (y₂ - y₁) · (x₂ - x₁)⁻¹ (mod p) // x₃ = λ² - x₁ - x₂ (mod p) // y₃ = λ(x₁ - x₃) - y₁ (mod p) // // • Point doubling (tangent formula): // λ = (3x₁² + a) · (2y₁)⁻¹ (mod p) // x₃ = λ² - 2x₁ (mod p) // y₃ = λ(x₁ - x₃) - y₁ (mod p) // // • Scalar multiplication k·P via double-and-add in O(log k) // • Negation: -P = (x, -y mod p) // • Identity element (point at infinity): P + O = P // • Inverse: P + (-P) = O // --------------------------------------------------------------------------- (0, _globals.describe)('Elliptic curve operations', function () { // secp192k1: y² = x³ + 3 over a 192-bit prime field var curve = (0, _registry.getCurve)('secp192k1'); var affineCurve = new _curve.CurveParams({ p: curve.p, a: curve.a, b: curve.b, n: curve.n, h: curve.h, coord: _curve.CoordinateSystem.AFFINE }); var point1 = new _point.Point(0xf091cf6331b1747684f5d2549cd1d4b3a8bed93b94f93cb6n, 0xfd7af42e1e7565a02e6268661c5e42e603da2d98a18f2ed5n, curve); var point2 = new _point.Point(0x6e43b7dcae2fd5e0bf2a1ba7615ca3b9065487c9a67b4583n, 0xc48dcea47ae08e84d5fedc3d09e4c19606a290f7a19a6a58n, curve); (0, _globals.test)('affine arithmetic: addition, doubling, and scalar multiplication', function () { var p1 = new _point.Point(point1.x, point1.y, affineCurve); var p2 = new _point.Point(point2.x, point2.y, affineCurve); // P₁ + P₂ (P₁ ≠ P₂) — chord formula p1.add(p2); // P₁ + P₁ — tangent formula (doubling) var expectedDouble = p1.add(p1); var calculatedDouble = p1.add(p1); (0, _globals.expect)(calculatedDouble.x).toBe(expectedDouble.x); (0, _globals.expect)(calculatedDouble.y).toBe(expectedDouble.y); // 3·P₁ = 2·P₁ + P₁ var expectedProduct = p1.add(p1); expectedProduct = new _point.Point(expectedProduct.x, expectedProduct.y, affineCurve); expectedProduct = expectedProduct.add(p1); var calculatedProduct = p1.mul(3n); (0, _globals.expect)(calculatedProduct.x).toBe(expectedProduct.x); (0, _globals.expect)(calculatedProduct.y).toBe(expectedProduct.y); }); // P₁ + P₂ on secp192k1 — verified against known test vectors (0, _globals.test)('point addition produces expected result in both coordinate systems', function () { var expectedSumX = 0x3cd61e370d02ca0687c0b5f7ebf6d0373f4dd0ccccb7cc2dn; var expectedSumY = 0x2c4befd9b02f301eb4014504f0533aa7eb19e9ea56441f78n; // Affine: λ = (y₂-y₁)·(x₂-x₁)⁻¹, x₃ = λ²-x₁-x₂, y₃ = λ(x₁-x₃)-y₁ var p1a = new _point.Point(point1.x, point1.y, affineCurve); var p2a = new _point.Point(point2.x, point2.y, affineCurve); var sumAffine = p1a.add(p2a); (0, _globals.expect)(sumAffine.x).toBe(expectedSumX); (0, _globals.expect)(sumAffine.y).toBe(expectedSumY); // Jacobian: same result via (X/Z², Y/Z³) representation var sumJacobian = point1.add(point2); (0, _globals.expect)(sumJacobian.x).toBe(expectedSumX); (0, _globals.expect)(sumJacobian.y).toBe(expectedSumY); }); // 2·P₁ — point doubling verified against known test vectors (0, _globals.test)('point doubling produces expected result in both coordinate systems', function () { var expectedDoubleX = 0xea525dd5a1353762a14e9e78b9063316d1f2d5e792f87862n; var expectedDoubleY = 0xa936d583530982690c445427cdf2c5b0bb1c88749247b02en; // Affine: λ = (3x₁²+a)·(2y₁)⁻¹ var p1a = new _point.Point(point1.x, point1.y, affineCurve); var dblAffine = p1a.add(p1a); (0, _globals.expect)(dblAffine.x).toBe(expectedDoubleX); (0, _globals.expect)(dblAffine.y).toBe(expectedDoubleY); // Jacobian: S = 4XY², M = 3X²+aZ⁴, X' = M²-2S var dblJacobian = point1.add(point1); (0, _globals.expect)(dblJacobian.x).toBe(expectedDoubleX); (0, _globals.expect)(dblJacobian.y).toBe(expectedDoubleY); }); // Point(x, y) must satisfy y² ≡ x³ + ax + b (mod p) (0, _globals.test)('constructor rejects points not on the curve', function () { (0, _globals.expect)(function () { return new _point.Point(200n, 119n, curve); }).toThrow(); }); // k·P via double-and-add: 2·P₁ should equal the known doubled value (0, _globals.test)('scalar multiplication with known vectors', function () { var expectedX = 0xea525dd5a1353762a14e9e78b9063316d1f2d5e792f87862n; // Affine var p1a = new _point.Point(point1.x, point1.y, affineCurve); (0, _globals.expect)(p1a.mul(2n).x).toBe(expectedX); // Large scalar var productLarge = p1a.mul(0xea525dd5a1353762a14e9e78b9063316d1f2d5e792f87862n); (0, _globals.expect)(productLarge.x).toBe(5095008632516147798595855149669871701227161828659032863660n); // Jacobian (0, _globals.expect)(point1.mul(2n).x).toBe(expectedX); }); // y² ≡ x³ + ax + b (mod p) verification (0, _globals.test)('isOnCurve() returns true for valid points, false for identity', function () { (0, _globals.expect)(point1.isOnCurve()).toBe(true); (0, _globals.expect)(new _point.Point().isOnCurve()).toBe(false); }); // P + O = P (identity element of the group) (0, _globals.test)('adding the identity element returns the original point', function () { var identity = new _point.Point(null, null, curve, true); (0, _globals.expect)(point1.add(identity).x).toBe(point1.x); (0, _globals.expect)(identity.add(point1).x).toBe(point1.x); }); // 0·P = O and n·P = O (group order) (0, _globals.test)('multiplying by 0 or n yields the identity', function () { (0, _globals.expect)(point1.mul(0n).isIdentity).toBe(true); (0, _globals.expect)(point1.mul(curve.n).isIdentity).toBe(true); }); // P + (-P) = O (additive inverse in affine coordinates) (0, _globals.test)('P + (-P) = O in affine coordinates', function () { var p1a = new _point.Point(point1.x, point1.y, affineCurve); (0, _globals.expect)(p1a.add(p1a.neg()).isIdentity).toBe(true); }); // P + (-P) = O (additive inverse in Jacobian coordinates) (0, _globals.test)('P + (-P) = O in Jacobian coordinates', function () { (0, _globals.expect)(point1.add(point1.neg()).isIdentity).toBe(true); }); // Doubling a point with y = 0: tangent is vertical → result is O (0, _globals.test)('doubling a point with y = 0 yields identity', function () { var crv = new _curve.CurveParams({ p: 13n, a: 1n, b: 0n, n: 4n, h: 1n }); var P = new _point.Point(0n, 0n, crv); (0, _globals.expect)(P.add(P).isIdentity).toBe(true); }); // k·O = O (scalar multiplication of identity) (0, _globals.test)('multiplying identity by any scalar remains identity (Jacobian)', function () { var crv = new _curve.CurveParams({ p: 13n, a: 1n, b: 0n, n: 4n, h: 1n }); var inf = new _point.Point(null, null, crv, true); (0, _globals.expect)(inf.mul(3n).isIdentity).toBe(true); }); (0, _globals.test)('multiplying identity by any scalar remains identity (affine)', function () { var crv = new _curve.CurveParams({ p: 13n, a: 1n, b: 0n, n: 4n, h: 1n, coord: _curve.CoordinateSystem.AFFINE }); var inf = new _point.Point(null, null, crv, true); (0, _globals.expect)(inf.mul(3n).isIdentity).toBe(true); }); // Affine: P + (-P) = O via affine_add detecting x₁ = x₂, y₁ ≠ y₂ (0, _globals.test)('affine addition of inverse points returns identity', function () { var p1 = new _point.Point(point1.x, point1.y, affineCurve); (0, _globals.expect)(p1.add(p1.neg()).isIdentity).toBe(true); }); // Affine: doubling (0, 0) where 2y = 0 → no inverse → O (0, _globals.test)('affine doubling with y = 0 returns identity', function () { var crv = new _curve.CurveParams({ p: 13n, a: 1n, b: 0n, n: 4n, h: 1n, coord: _curve.CoordinateSystem.AFFINE }); (0, _globals.expect)(new _point.Point(0n, 0n, crv).add(new _point.Point(0n, 0n, crv)).isIdentity).toBe(true); }); // Jacobian: jac_add detects U₁ = U₂, S₁ ≠ S₂ → identity (0, _globals.test)('Jacobian addition of inverse points returns identity', function () { var crv = new _curve.CurveParams({ p: 13n, a: 1n, b: 0n, n: 4n, h: 1n }); var pJac = (0, _jacobian.toJacobian)(new _point.Point(10n, 3n, crv)); var pInvJac = (0, _jacobian.toJacobian)(new _point.Point(10n, crv.p - 3n, crv)); var result = (0, _jacobian.jacAdd)(pJac, pInvJac, crv); (0, _globals.expect)(result.x).toBe(null); (0, _globals.expect)(result.y).toBe(null); }); // Jacobian: doubling with Y = 0 → identity (0, _globals.test)('Jacobian doubling with y = 0 returns identity', function () { var crv = new _curve.CurveParams({ p: 13n, a: 1n, b: 0n, n: 4n, h: 1n }); var result = (0, _jacobian.jacDouble)((0, _jacobian.toJacobian)(new _point.Point(0n, 0n, crv)), crv); (0, _globals.expect)(result.x).toBe(null); (0, _globals.expect)(result.y).toBe(null); }); // O + O = O (0, _globals.test)('doubling identity returns identity', function () { var identity = new _point.Point(null, null, curve, true); (0, _globals.expect)(identity.add(identity).isIdentity).toBe(true); }); // jac_add(P, O) = P (0, _globals.test)('Jacobian add with identity returns original point', function () { var crv = new _curve.CurveParams({ p: 13n, a: 1n, b: 0n, n: 4n, h: 1n }); var pJac = (0, _jacobian.toJacobian)(new _point.Point(10n, 3n, crv)); var result = (0, _jacobian.jacAdd)(pJac, new _jacobian.JacobianPoint(), crv); (0, _globals.expect)(result.x).toBe(pJac.x); (0, _globals.expect)(result.y).toBe(pJac.y); (0, _globals.expect)(result.z).toBe(pJac.z); }); // jac_double(O) = O (0, _globals.test)('Jacobian doubling identity returns identity', function () { var crv = new _curve.CurveParams({ p: 13n, a: 1n, b: 0n, n: 4n, h: 1n }); var result = (0, _jacobian.jacDouble)(new _jacobian.JacobianPoint(), crv); (0, _globals.expect)(result.x).toBe(null); (0, _globals.expect)(result.y).toBe(null); }); // toJacobian(O) = JacobianPoint(null, null, 1) (0, _globals.test)('converting affine identity to Jacobian gives Jacobian identity', function () { var result = (0, _jacobian.toJacobian)(new _point.Point()); (0, _globals.expect)(result.x).toBe(null); (0, _globals.expect)(result.y).toBe(null); }); // -P = (x, -y mod p) (0, _globals.test)('negation computes (x, p - y)', function () { var negPoint = point1.neg(); (0, _globals.expect)(negPoint.x).toBe(point1.x); (0, _globals.expect)(negPoint.y).toBe((-point1.y % curve.p + curve.p) % curve.p); }); // P₁ - P₂ = P₁ + (-P₂) (0, _globals.test)('subtraction equals addition of negation', function () { var resultSub = point1.sub(point2); var resultAddNeg = point1.add(point2.neg()); (0, _globals.expect)(resultSub.x).toBe(resultAddNeg.x); (0, _globals.expect)(resultSub.y).toBe(resultAddNeg.y); }); // affine_double(null, null) = (null, null) (0, _globals.test)('affine_double of identity returns identity', function () { (0, _globals.expect)((0, _affine.affineDouble)(null, null, affineCurve)).toEqual([null, null]); }); // affine_add(P, O) = P (0, _globals.test)('affine_add with identity as second operand returns first point', function () { (0, _globals.expect)((0, _affine.affineAdd)(point1.x, point1.y, null, null, affineCurve)).toEqual([point1.x, point1.y]); }); // Arithmetic requires curve parameters (0, _globals.test)('arithmetic without curve parameters throws', function () { var p = new _point.Point(1n, 2n); (0, _globals.expect)(function () { return p.add(p); }).toThrow(); }); // _coerce assigns curve params to a point that lacks them (0, _globals.test)('_coerce borrows curve from the other operand', function () { var pWithoutCurve = new _point.Point(point2.x, point2.y); var coerced = point1._coerce(pWithoutCurve); (0, _globals.expect)(coerced.curve).toBe(curve); (0, _globals.expect)(point1.add(pWithoutCurve).isIdentity).toBe(false); }); // -O = O (0, _globals.test)('negation of identity returns identity', function () { var identity = new _point.Point(null, null, curve, true); (0, _globals.expect)(identity.neg().isIdentity).toBe(true); }); // toString representation (0, _globals.test)('toString of identity displays Point(∞)', function () { (0, _globals.expect)(new _point.Point().toString()).toBe('Point(∞)'); }); (0, _globals.test)('toString of non-identity displays coordinates', function () { (0, _globals.expect)(point1.toString()).toContain('Point(x='); (0, _globals.expect)(point1.toString()).toContain(point1.x.toString()); }); // --- SEC 1 compression / decompression --- (0, _globals.test)('compressSec1 produces correct prefix and length', function () { var G = (0, _registry.getGenerator)('secp256k1'); var compressed = G.compressSec1(); // 256-bit curve → 32-byte x + 1-byte prefix = 33 bytes (0, _globals.expect)(compressed.length).toBe(33); (0, _globals.expect)([0x02, 0x03]).toContain(compressed[0]); }); (0, _globals.test)('compressSec1 throws for identity', function () { var identity = new _point.Point(null, null, curve, true); (0, _globals.expect)(function () { return identity.compressSec1(); }).toThrow('identity'); }); (0, _globals.test)('toUncompressedSec1 produces correct prefix and length', function () { var G = (0, _registry.getGenerator)('secp256k1'); var uncompressed = G.toUncompressedSec1(); // 256-bit curve → 32-byte x + 32-byte y + 1-byte prefix = 65 bytes (0, _globals.expect)(uncompressed.length).toBe(65); (0, _globals.expect)(uncompressed[0]).toBe(0x04); }); (0, _globals.test)('toUncompressedSec1 throws for identity', function () { var identity = new _point.Point(null, null, curve, true); (0, _globals.expect)(function () { return identity.toUncompressedSec1(); }).toThrow('identity'); }); (0, _globals.test)('fromSec1 compressed roundtrip', function () { var G = (0, _registry.getGenerator)('secp256k1'); var sec1Curve = (0, _registry.getCurve)('secp256k1'); var compressed = G.compressSec1(); var recovered = _point.Point.fromSec1(compressed, sec1Curve); (0, _globals.expect)(recovered.x).toBe(G.x); (0, _globals.expect)(recovered.y).toBe(G.y); }); (0, _globals.test)('fromSec1 uncompressed roundtrip', function () { var G = (0, _registry.getGenerator)('secp256k1'); var sec1Curve = (0, _registry.getCurve)('secp256k1'); var uncompressed = G.toUncompressedSec1(); var recovered = _point.Point.fromSec1(uncompressed, sec1Curve); (0, _globals.expect)(recovered.x).toBe(G.x); (0, _globals.expect)(recovered.y).toBe(G.y); }); (0, _globals.test)('fromSec1 throws for data too short', function () { var sec1Curve = (0, _registry.getCurve)('secp256k1'); (0, _globals.expect)(function () { return _point.Point.fromSec1(new Uint8Array([0x02]), sec1Curve); }).toThrow(); }); (0, _globals.test)('fromSec1 throws for unknown prefix', function () { var sec1Curve = (0, _registry.getCurve)('secp256k1'); var bad = new Uint8Array(33); bad[0] = 0x05; (0, _globals.expect)(function () { return _point.Point.fromSec1(bad, sec1Curve); }).toThrow('Unknown SEC 1 prefix'); }); (0, _globals.test)('fromSec1 throws for wrong compressed length', function () { var sec1Curve = (0, _registry.getCurve)('secp256k1'); var bad = new Uint8Array(20); bad[0] = 0x02; (0, _globals.expect)(function () { return _point.Point.fromSec1(bad, sec1Curve); }).toThrow(); }); (0, _globals.test)('fromSec1 throws for wrong uncompressed length', function () { var sec1Curve = (0, _registry.getCurve)('secp256k1'); var bad = new Uint8Array(20); bad[0] = 0x04; (0, _globals.expect)(function () { return _point.Point.fromSec1(bad, sec1Curve); }).toThrow(); }); (0, _globals.test)('compress throws for identity', function () { var identity = new _point.Point(null, null, curve, true); (0, _globals.expect)(function () { return identity.compress(); }).toThrow('identity'); }); (0, _globals.test)('decompress with parity flip selects correct root', function () { var toyCurve = new _curve.CurveParams({ p: 23n, a: 1n, b: 1n, n: 28n, h: 1n }); var P = new _point.Point(0n, 1n, toyCurve); var _P$compress = P.compress(), _P$compress2 = _slicedToArray(_P$compress, 2), x = _P$compress2[0], parity = _P$compress2[1]; // Decompress with opposite parity → y = p - original_y var flipped = _point.Point.decompress(x, parity === 0n ? 1n : 0n, toyCurve); (0, _globals.expect)(flipped.y).toBe(toyCurve.p - P.y); }); (0, _globals.test)('decompress throws for invalid x', function () { // Use a curve where x=999 has no valid point var toyCurve = new _curve.CurveParams({ p: 23n, a: 1n, b: 1n, n: 28n, h: 1n }); // x=2: rhs = 8 + 2 + 1 = 11. 11 is not a QR mod 23. (0, _globals.expect)(function () { return _point.Point.decompress(2n, 0n, toyCurve); }).toThrow('does not correspond'); }); (0, _globals.test)('SEC 1 roundtrip on secp521r1 (odd-byte field size)', function () { var G = (0, _registry.getGenerator)('secp521r1'); var c = (0, _registry.getCurve)('secp521r1'); var comp = G.compressSec1(); var uncomp = G.toUncompressedSec1(); (0, _globals.expect)(_point.Point.fromSec1(comp, c).x).toBe(G.x); (0, _globals.expect)(_point.Point.fromSec1(uncomp, c).x).toBe(G.x); }); }); // --------------------------------------------------------------------------- // Group law properties on E: y² = x³ + x + 1 over F₂₃ (n = 28) // // An elliptic curve over a finite field forms an abelian group: // 1. Identity: P + O = P // 2. Inverse: P + (-P) = O // 3. Associativity: (P + Q) + R = P + (Q + R) // 4. Commutativity: P + Q = Q + P // 5. Distributivity: (a + b)·P = a·P + b·P // --------------------------------------------------------------------------- (0, _globals.describe)('Group law properties (toy curve E/F₂₃)', function () { var curve = new _curve.CurveParams({ p: 23n, a: 1n, b: 1n, n: 28n, h: 1n }); var P = new _point.Point(0n, 1n, curve); var Q = new _point.Point(6n, 19n, curve); var inf = new _point.Point(null, null, curve, true); (0, _globals.test)('P + O = P (right identity)', function () { var result = P.add(inf); (0, _globals.expect)(result.x).toBe(P.x); (0, _globals.expect)(result.y).toBe(P.y); }); (0, _globals.test)('O + P = P (left identity)', function () { var result = inf.add(P); (0, _globals.expect)(result.x).toBe(P.x); (0, _globals.expect)(result.y).toBe(P.y); }); (0, _globals.test)('P + (-P) = O (inverse)', function () { (0, _globals.expect)(P.add(P.neg()).isIdentity).toBe(true); }); (0, _globals.test)('n·P = O (group order)', function () { (0, _globals.expect)(P.mul(curve.n).isIdentity).toBe(true); }); (0, _globals.test)('0·P = O', function () { (0, _globals.expect)(P.mul(0n).isIdentity).toBe(true); }); (0, _globals.test)('1·P = P', function () { var result = P.mul(1n); (0, _globals.expect)(result.x).toBe(P.x); (0, _globals.expect)(result.y).toBe(P.y); }); (0, _globals.test)('(P + Q) + R = P + (Q + R) (associativity)', function () { var R = P.mul(3n); var lhs = P.add(Q).add(R); var rhs = P.add(Q.add(R)); (0, _globals.expect)(lhs.x).toBe(rhs.x); (0, _globals.expect)(lhs.y).toBe(rhs.y); }); (0, _globals.test)('P + Q = Q + P (commutativity)', function () { var pq = P.add(Q); var qp = Q.add(P); (0, _globals.expect)(pq.x).toBe(qp.x); (0, _globals.expect)(pq.y).toBe(qp.y); }); (0, _globals.test)('(a + b)·P = a·P + b·P (distributivity)', function () { var a = 7n, b = 13n; var lhs = P.mul(a + b); var rhs = P.mul(a).add(P.mul(b)); (0, _globals.expect)(lhs.x).toBe(rhs.x); (0, _globals.expect)(lhs.y).toBe(rhs.y); }); (0, _globals.test)('P + P = 2·P (doubling via addition equals scalar multiplication)', function () { var pp = P.add(P); var twoP = P.mul(2n); (0, _globals.expect)(pp.x).toBe(twoP.x); (0, _globals.expect)(pp.y).toBe(twoP.y); }); (0, _globals.test)('k·P computed via mul is consistent', function () { var r1 = P.mul(5n); var r2 = P.mul(5n); (0, _globals.expect)(r1.x).toBe(r2.x); (0, _globals.expect)(r1.y).toBe(r2.y); }); }); // --------------------------------------------------------------------------- // Jacobian ↔ Affine consistency // // Jacobian coordinates (X, Y, Z) map to affine (x, y) via: // x = X / Z², y = Y / Z³ // // Both systems must produce identical results for all scalar multiples. // --------------------------------------------------------------------------- (0, _globals.describe)('Jacobian ↔ Affine consistency (E/F₂₃)', function () { var curveJac = new _curve.CurveParams({ p: 23n, a: 1n, b: 1n, n: 28n, h: 1n, coord: _curve.CoordinateSystem.JACOBIAN }); var curveAff = new _curve.CurveParams({ p: 23n, a: 1n, b: 1n, n: 28n, h: 1n, coord: _curve.CoordinateSystem.AFFINE }); var Pjac = new _point.Point(0n, 1n, curveJac); var Paff = new _point.Point(0n, 1n, curveAff); (0, _globals.test)('k·P matches for k = 1, 2, ..., n-1', function () { for (var k = 1n; k < curveJac.n; k++) { var rj = Pjac.mul(k); var ra = Paff.mul(k); (0, _globals.expect)(rj.x).toBe(ra.x); (0, _globals.expect)(rj.y).toBe(ra.y); } }); (0, _globals.test)('n·P = O in both coordinate systems', function () { (0, _globals.expect)(Pjac.mul(curveJac.n).isIdentity).toBe(true); (0, _globals.expect)(Paff.mul(curveAff.n).isIdentity).toBe(true); }); (0, _globals.test)('P + Q matches in both coordinate systems', function () { var Qjac = new _point.Point(6n, 19n, curveJac); var Qaff = new _point.Point(6n, 19n, curveAff); var rj = Pjac.add(Qjac); var ra = Paff.add(Qaff); (0, _globals.expect)(rj.x).toBe(ra.x); (0, _globals.expect)(rj.y).toBe(ra.y); }); }); // --------------------------------------------------------------------------- // Group law properties on secp256k1 (production curve) // // secp256k1: y² = x³ + 7 over a 256-bit prime field // Used by Bitcoin and many other cryptocurrencies. // Order n ≈ 2²⁵⁶ — ensures ECDLP security. // --------------------------------------------------------------------------- (0, _globals.describe)('Group law properties (secp256k1)', function () { var curve = (0, _registry.getCurve)('secp256k1'); var G = (0, _registry.getGenerator)('secp256k1'); (0, _globals.test)('generator G lies on the curve', function () { (0, _globals.expect)(G.isOnCurve()).toBe(true); }); (0, _globals.test)('n·G = O (generator order)', function () { (0, _globals.expect)(G.mul(curve.n).isIdentity).toBe(true); }); (0, _globals.test)('(P + Q) + R = P + (Q + R) (associativity)', function () { var P = G.mul(7n); var Q = G.mul(13n); var R = G.mul(42n); var lhs = P.add(Q).add(R); var rhs = P.add(Q.add(R)); (0, _globals.expect)(lhs.x).toBe(rhs.x); (0, _globals.expect)(lhs.y).toBe(rhs.y); }); (0, _globals.test)('P + Q = Q + P (commutativity)', function () { var P = G.mul(7n); var Q = G.mul(13n); var pq = P.add(Q); var qp = Q.add(P); (0, _globals.expect)(pq.x).toBe(qp.x); (0, _globals.expect)(pq.y).toBe(qp.y); }); (0, _globals.test)('(a + b)·G = a·G + b·G (distributivity)', function () { var a = 123n, b = 456n; var lhs = G.mul(a + b); var rhs = G.mul(a).add(G.mul(b)); (0, _globals.expect)(lhs.x).toBe(rhs.x); (0, _globals.expect)(lhs.y).toBe(rhs.y); }); (0, _globals.test)('P + (-P) = O (inverse)', function () { var P = G.mul(42n); (0, _globals.expect)(P.add(P.neg()).isIdentity).toBe(true); }); }); //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfZ2xvYmFscyIsInJlcXVpcmUiLCJfcG9pbnQiLCJfY3VydmUiLCJfcmVnaXN0cnkiLCJfYWZmaW5lIiwiX2phY29iaWFuIiwiX3NsaWNlZFRvQXJyYXkiLCJyIiwiZSIsIl9hcnJheVdpdGhIb2xlcyIsIl9pdGVyYWJsZVRvQXJyYXlMaW1pdCIsIl91bnN1cHBvcnRlZEl0ZXJhYmxlVG9BcnJheSIsIl9ub25JdGVyYWJsZVJlc3QiLCJUeXBlRXJyb3IiLCJhIiwiX2FycmF5TGlrZVRvQXJyYXkiLCJ0IiwidG9TdHJpbmciLCJjYWxsIiwic2xpY2UiLCJjb25zdHJ1Y3RvciIsIm5hbWUiLCJBcnJheSIsImZyb20iLCJ0ZXN0IiwibGVuZ3RoIiwibiIsImwiLCJTeW1ib2wiLCJpdGVyYXRvciIsImkiLCJ1IiwiZiIsIm8iLCJuZXh0IiwiT2JqZWN0IiwiZG9uZSIsInB1c2giLCJ2YWx1ZSIsImlzQXJyYXkiLCJkZXNjcmliZSIsImN1cnZlIiwiZ2V0Q3VydmUiLCJhZmZpbmVDdXJ2ZSIsIkN1cnZlUGFyYW1zIiwicCIsImIiLCJoIiwiY29vcmQiLCJDb29yZGluYXRlU3lzdGVtIiwiQUZGSU5FIiwicG9pbnQxIiwiUG9pbnQiLCJwb2ludDIiLCJwMSIsIngiLCJ5IiwicDIiLCJhZGQiLCJleHBlY3RlZERvdWJsZSIsImNhbGN1bGF0ZWREb3VibGUiLCJleHBlY3QiLCJ0b0JlIiwiZXhwZWN0ZWRQcm9kdWN0IiwiY2FsY3VsYXRlZFByb2R1Y3QiLCJtdWwiLCJleHBlY3RlZFN1bVgiLCJleHBlY3RlZFN1bVkiLCJwMWEiLCJwMmEiLCJzdW1BZmZpbmUiLCJzdW1KYWNvYmlhbiIsImV4cGVjdGVkRG91YmxlWCIsImV4cGVjdGVkRG91YmxlWSIsImRibEFmZmluZSIsImRibEphY29iaWFuIiwidG9UaHJvdyIsImV4cGVjdGVkWCIsInByb2R1Y3RMYXJnZSIsImlzT25DdXJ2ZSIsImlkZW50aXR5IiwiaXNJZGVudGl0eSIsIm5lZyIsImNydiIsIlAiLCJpbmYiLCJwSmFjIiwidG9KYWNvYmlhbiIsInBJbnZKYWMiLCJyZXN1bHQiLCJqYWNBZGQiLCJqYWNEb3VibGUiLCJKYWNvYmlhblBvaW50IiwieiIsIm5lZ1BvaW50IiwicmVzdWx0U3ViIiwic3ViIiwicmVzdWx0QWRkTmVnIiwiYWZmaW5lRG91YmxlIiwidG9FcXVhbCIsImFmZmluZUFkZCIsInBXaXRob3V0Q3VydmUiLCJjb2VyY2VkIiwiX2NvZXJjZSIsInRvQ29udGFpbiIsIkciLCJnZXRHZW5lcmF0b3IiLCJjb21wcmVzc2VkIiwiY29tcHJlc3NTZWMxIiwidW5jb21wcmVzc2VkIiwidG9VbmNvbXByZXNzZWRTZWMxIiwic2VjMUN1cnZlIiwicmVjb3ZlcmVkIiwiZnJvbVNlYzEiLCJVaW50OEFycmF5IiwiYmFkIiwiY29tcHJlc3MiLCJ0b3lDdXJ2ZSIsIl9QJGNvbXByZXNzIiwiX1AkY29tcHJlc3MyIiwicGFyaXR5IiwiZmxpcHBlZCIsImRlY29tcHJlc3MiLCJjIiwiY29tcCIsInVuY29tcCIsIlEiLCJSIiwibGhzIiwicmhzIiwicHEiLCJxcCIsInBwIiwidHdvUCIsInIxIiwicjIiLCJjdXJ2ZUphYyIsIkpBQ09CSUFOIiwiY3VydmVBZmYiLCJQamFjIiwiUGFmZiIsImsiLCJyaiIsInJhIiwiUWphYyIsIlFhZmYiXSwic291cmNlcyI6WyIuLi8uLi9zcmMvY29yZS50ZXN0LmpzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHRlc3QsIGV4cGVjdCwgZGVzY3JpYmUgfSBmcm9tICdAamVzdC9nbG9iYWxzJ1xuaW1wb3J0IHsgUG9pbnQgfSBmcm9tICcuL2NvcmUvcG9pbnQnXG5pbXBvcnQgeyBDdXJ2ZVBhcmFtcywgQ29vcmRpbmF0ZVN5c3RlbSB9IGZyb20gJy4vY29yZS9jdXJ2ZSdcbmltcG9ydCB7IGdldEN1cnZlLCBnZXRHZW5lcmF0b3IgfSBmcm9tICcuL2N1cnZlcy9yZWdpc3RyeSdcbmltcG9ydCB7IGFmZmluZURvdWJsZSwgYWZmaW5lQWRkIH0gZnJvbSAnLi9jb3JlL2FyaXRobWV0aWMvYWZmaW5lJ1xuaW1wb3J0IHtcbiAgSmFjb2JpYW5Qb2ludCxcbiAgdG9KYWNvYmlhbixcbiAgamFjQWRkLFxuICBqYWNEb3VibGUsXG59IGZyb20gJy4vY29yZS9hcml0aG1ldGljL2phY29iaWFuJ1xuXG4vLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbi8vIEVsbGlwdGljIGN1cnZlIG9wZXJhdGlvbnNcbi8vXG4vLyBUZXN0cyBmb3IgdGhlIFBvaW50LW9wZXJhdG9yIEFQSSBvbiB0aGUgc2hvcnQgV2VpZXJzdHJhc3MgY3VydmVcbi8vICAgecKyID0geMKzICsgYXggKyBiICAobW9kIHApXG4vL1xuLy8gQ292ZXJzOlxuLy8gICDigKIgUG9pbnQgYWRkaXRpb24gKGNob3JkIGZvcm11bGEpOlxuLy8gICAgICAgzrsgID0gKHnigoIgLSB54oKBKSDCtyAoeOKCgiAtIHjigoEp4oG7wrkgIChtb2QgcClcbi8vICAgICAgIHjigoMgPSDOu8KyIC0geOKCgSAtIHjigoIgICAgICAgICAgICAgIChtb2QgcClcbi8vICAgICAgIHnigoMgPSDOuyh44oKBIC0geOKCgykgLSB54oKBICAgICAgICAgICAobW9kIHApXG4vL1xuLy8gICDigKIgUG9pbnQgZG91YmxpbmcgKHRhbmdlbnQgZm9ybXVsYSk6XG4vLyAgICAgICDOuyAgPSAoM3jigoHCsiArIGEpIMK3ICgyeeKCgSnigbvCuSAgICAgIChtb2QgcClcbi8vICAgICAgIHjigoMgPSDOu8KyIC0gMnjigoEgICAgICAgICAgICAgICAgICAobW9kIHApXG4vLyAgICAgICB54oKDID0gzrsoeOKCgSAtIHjigoMpIC0geeKCgSAgICAgICAgICAgKG1vZCBwKVxuLy9cbi8vICAg4oCiIFNjYWxhciBtdWx0aXBsaWNhdGlvbiBrwrdQIHZpYSBkb3VibGUtYW5kLWFkZCBpbiBPKGxvZyBrKVxuLy8gICDigKIgTmVnYXRpb246IC1QID0gKHgsIC15IG1vZCBwKVxuLy8gICDigKIgSWRlbnRpdHkgZWxlbWVudCAocG9pbnQgYXQgaW5maW5pdHkpOiBQICsgTyA9IFBcbi8vICAg4oCiIEludmVyc2U6IFAgKyAoLVApID0gT1xuLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbmRlc2NyaWJlKCdFbGxpcHRpYyBjdXJ2ZSBvcGVyYXRpb25zJywgKCkgPT4ge1xuICAvLyBzZWNwMTkyazE6IHnCsiA9IHjCsyArIDMgb3ZlciBhIDE5Mi1iaXQgcHJpbWUgZmllbGRcbiAgY29uc3QgY3VydmUgPSBnZXRDdXJ2ZSgnc2VjcDE5MmsxJylcbiAgY29uc3QgYWZmaW5lQ3VydmUgPSBuZXcgQ3VydmVQYXJhbXMoe1xuICAgIHA6IGN1cnZlLnAsXG4gICAgYTogY3VydmUuYSxcbiAgICBiOiBjdXJ2ZS5iLFxuICAgIG46IGN1cnZlLm4sXG4gICAgaDogY3VydmUuaCxcbiAgICBjb29yZDogQ29vcmRpbmF0ZVN5c3RlbS5BRkZJTkUsXG4gIH0pXG5cbiAgY29uc3QgcG9pbnQxID0gbmV3IFBvaW50KFxuICAgIDB4ZjA5MWNmNjMzMWIxNzQ3Njg0ZjVkMjU0OWNkMWQ0YjNhOGJlZDkzYjk0ZjkzY2I2bixcbiAgICAweGZkN2FmNDJlMWU3NTY1YTAyZTYyNjg2NjFjNWU0MmU2MDNkYTJkOThhMThmMmVkNW4sXG4gICAgY3VydmUsXG4gIClcbiAgY29uc3QgcG9pbnQyID0gbmV3IFBvaW50KFxuICAgIDB4NmU0M2I3ZGNhZTJmZDVlMGJmMmExYmE3NjE1Y2EzYjkwNjU0ODdjOWE2N2I0NTgzbixcbiAgICAweGM0OGRjZWE0N2FlMDhlODRkNWZlZGMzZDA5ZTRjMTk2MDZhMjkwZjdhMTlhNmE1OG4sXG4gICAgY3VydmUsXG4gIClcblxuICB0ZXN0KCdhZmZpbmUgYXJpdGhtZXRpYzogYWRkaXRpb24sIGRvdWJsaW5nLCBhbmQgc2NhbGFyIG11bHRpcGxpY2F0aW9uJywgKCkgPT4ge1xuICAgIGNvbnN0IHAxID0gbmV3IFBvaW50KHBvaW50MS54LCBwb2ludDEueSwgYWZmaW5lQ3VydmUpXG4gICAgY29uc3QgcDIgPSBuZXcgUG9pbnQocG9pbnQyLngsIHBvaW50Mi55LCBhZmZpbmVDdXJ2ZSlcblxuICAgIC8vIFDigoEgKyBQ4oKCIChQ4oKBIOKJoCBQ4oKCKSDigJQgY2hvcmQgZm9ybXVsYVxuICAgIHAxLmFkZChwMilcblxuICAgIC8vIFDigoEgKyBQ4oKBIOKAlCB0YW5nZW50IGZvcm11bGEgKGRvdWJsaW5nKVxuICAgIGNvbnN0IGV4cGVjdGVkRG91YmxlID0gcDEuYWRkKHAxKVxuICAgIGNvbnN0IGNhbGN1bGF0ZWREb3VibGUgPSBwMS5hZGQocDEpXG4gICAgZXhwZWN0KGNhbGN1bGF0ZWREb3VibGUueCkudG9CZShleHBlY3RlZERvdWJsZS54KVxuICAgIGV4cGVjdChjYWxjdWxhdGVkRG91YmxlLnkpLnRvQmUoZXhwZWN0ZWREb3VibGUueSlcblxuICAgIC8vIDPCt1DigoEgPSAywrdQ4oKBICsgUOKCgVxuICAgIGxldCBleHBlY3RlZFByb2R1Y3QgPSBwMS5hZGQocDEpXG4gICAgZXhwZWN0ZWRQcm9kdWN0ID0gbmV3IFBvaW50KFxuICAgICAgZXhwZWN0ZWRQcm9kdWN0LngsXG4gICAgICBleHBlY3RlZFByb2R1Y3QueSxcbiAgICAgIGFmZmluZUN1cnZlLFxuICAgIClcbiAgICBleHBlY3RlZFByb2R1Y3QgPSBleHBlY3RlZFByb2R1Y3QuYWRkKHAxKVxuICAgIGNvbnN0IGNhbGN1bGF0ZWRQcm9kdWN0ID0gcDEubXVsKDNuKVxuICAgIGV4cGVjdChjYWxjdWxhdGVkUHJvZHVjdC54KS50b0JlKGV4cGVjdGVkUHJvZHVjdC54KVxuICAgIGV4cGVjdChjYWxjdWxhdGVkUHJvZHVjdC55KS50b0JlKGV4cGVjdGVkUHJvZHVjdC55KVxuICB9KVxuXG4gIC8vIFDigoEgKyBQ4oKCIG9uIHNlY3AxOTJrMSDigJQgdmVyaWZpZWQgYWdhaW5zdCBrbm93biB0ZXN0IHZlY3RvcnNcbiAgdGVzdCgncG9pbnQgYWRkaXRpb24gcHJvZHVjZXMgZXhwZWN0ZWQgcmVzdWx0IGluIGJvdGggY29vcmRpbmF0ZSBzeXN0ZW1zJywgKCkgPT4ge1xuICAgIGNvbnN0IGV4cGVjdGVkU3VtWCA9IDB4M2NkNjFlMzcwZDAyY2EwNjg3YzBiNWY3ZWJmNmQwMzczZjRkZDBjY2NjYjdjYzJkblxuICAgIGNvbnN0IGV4cGVjdGVkU3VtWSA9IDB4MmM0YmVmZDliMDJmMzAxZWI0MDE0NTA0ZjA1MzNhYTdlYjE5ZTllYTU2NDQxZjc4blxuXG4gICAgLy8gQWZmaW5lOiDOuyA9ICh54oKCLXnigoEpwrcoeOKCgi144oKBKeKBu8K5LCB44oKDID0gzrvCsi144oKBLXjigoIsIHnigoMgPSDOuyh44oKBLXjigoMpLXnigoFcbiAgICBjb25zdCBwMWEgPSBuZXcgUG9pbnQocG9pbnQxLngsIHBvaW50MS55LCBhZmZpbmVDdXJ2ZSlcbiAgICBjb25zdCBwMmEgPSBuZXcgUG9pbnQocG9pbnQyLngsIHBvaW50Mi55LCBhZmZpbmVDdXJ2ZSlcbiAgICBjb25zdCBzdW1BZmZpbmUgPSBwMWEuYWRkKHAyYSlcbiAgICBleHBlY3Qoc3VtQWZmaW5lLngpLnRvQmUoZXhwZWN0ZWRTdW1YKVxuICAgIGV4cGVjdChzdW1BZmZpbmUueSkudG9CZShleHBlY3RlZFN1bVkpXG5cbiAgICAvLyBKYWNvYmlhbjogc2FtZSByZXN1bHQgdmlhIChYL1rCsiwgWS9awrMpIHJlcHJlc2VudGF0aW9uXG4gICAgY29uc3Qgc3VtSmFjb2JpYW4gPSBwb2ludDEuYWRkKHBvaW50MilcbiAgICBleHBlY3Qoc3VtSmFjb2JpYW4ueCkudG9CZShleHBlY3RlZFN1bVgpXG4gICAgZXhwZWN0KHN1bUphY29iaWFuLnkpLnRvQmUoZXhwZWN0ZWRTdW1ZKVxuICB9KVxuXG4gIC8vIDLCt1DigoEg4oCUIHBvaW50IGRvdWJsaW5nIHZlcmlmaWVkIGFnYWluc3Qga25vd24gdGVzdCB2ZWN0b3JzXG4gIHRlc3QoJ3BvaW50IGRvdWJsaW5nIHByb2R1Y2VzIGV4cGVjdGVkIHJlc3VsdCBpbiBib3RoIGNvb3JkaW5hdGUgc3lzdGVtcycsICgpID0+IHtcbiAgICBjb25zdCBleHBlY3RlZERvdWJsZVggPSAweGVhNTI1ZGQ1YTEzNTM3NjJhMTRlOWU3OGI5MDYzMzE2ZDFmMmQ1ZTc5MmY4Nzg2Mm5cbiAgICBjb25zdCBleHBlY3RlZERvdWJsZVkgPSAweGE5MzZkNTgzNTMwOTgyNjkwYzQ0NTQyN2NkZjJjNWIwYmIxYzg4NzQ5MjQ3YjAyZW5cblxuICAgIC8vIEFmZmluZTogzrsgPSAoM3jigoHCsithKcK3KDJ54oKBKeKBu8K5XG4gICAgY29uc3QgcDFhID0gbmV3IFBvaW50KHBvaW50MS54LCBwb2ludDEueSwgYWZmaW5lQ3VydmUpXG4gICAgY29uc3QgZGJsQWZmaW5lID0gcDFhLmFkZChwMWEpXG4gICAgZXhwZWN0KGRibEFmZmluZS54KS50b0JlKGV4cGVjdGVkRG91YmxlWClcbiAgICBleHBlY3QoZGJsQWZmaW5lLnkpLnRvQmUoZXhwZWN0ZWREb3VibGVZKVxuXG4gICAgLy8gSmFjb2JpYW46IFMgPSA0WFnCsiwgTSA9IDNYwrIrYVrigbQsIFgnID0gTcKyLTJTXG4gICAgY29uc3QgZGJsSmFjb2JpYW4gPSBwb2ludDEuYWRkKHBvaW50MSlcbiAgICBleHBlY3QoZGJsSmFjb2JpYW4ueCkudG9CZShleHBlY3RlZERvdWJsZVgpXG4gICAgZXhwZWN0KGRibEphY29iaWFuLnkpLnRvQmUoZXhwZWN0ZWREb3VibGVZKVxuICB9KVxuXG4gIC8vIFBvaW50KHgsIHkpIG11c3Qgc2F0aXNmeSB5wrIg4omhIHjCsyArIGF4ICsgYiAobW9kIHApXG4gIHRlc3QoJ2NvbnN0cnVjdG9yIHJlamVjdHMgcG9pbnRzIG5vdCBvbiB0aGUgY3VydmUnLCAoKSA9PiB7XG4gICAgZXhwZWN0KCgpID0+IG5ldyBQb2ludCgyMDBuLCAxMTluLCBjdXJ2ZSkpLnRvVGhyb3coKVxuICB9KVxuXG4gIC8vIGvCt1AgdmlhIGRvdWJsZS1hbmQtYWRkOiAywrdQ4oKBIHNob3VsZCBlcXVhbCB0aGUga25vd24gZG91YmxlZCB2YWx1ZVxuICB0ZXN0KCdzY2FsYXIgbXVsdGlwbGljYXRpb24gd2l0aCBrbm93biB2ZWN0b3JzJywgKCkgPT4ge1xuICAgIGNvbnN0IGV4cGVjdGVkWCA9IDB4ZWE1MjVkZDVhMTM1Mzc2MmExNGU5ZTc4YjkwNjMzMTZkMWYyZDVlNzkyZjg3ODYyblxuXG4gICAgLy8gQWZmaW5lXG4gICAgY29uc3QgcDFhID0gbmV3IFBvaW50KHBvaW50MS54LCBwb2ludDEueSwgYWZmaW5lQ3VydmUpXG4gICAgZXhwZWN0KHAxYS5tdWwoMm4pLngpLnRvQmUoZXhwZWN0ZWRYKVxuXG4gICAgLy8gTGFyZ2Ugc2NhbGFyXG4gICAgY29uc3QgcHJvZHVjdExhcmdlID1cbiAgICAgIHAxYS5tdWwoMHhlYTUyNWRkNWExMzUzNzYyYTE0ZTllNzhiOTA2MzMxNmQxZjJkNWU3OTJmODc4NjJuKVxuICAgIGV4cGVjdChwcm9kdWN0TGFyZ2UueCkudG9CZShcbiAgICAgIDUwOTUwMDg2MzI1MTYxNDc3OTg1OTU4NTUxNDk2Njk4NzE3MDEyMjcxNjE4Mjg2NTkwMzI4NjM2NjBuLFxuICAgIClcblxuICAgIC8vIEphY29iaWFuXG4gICAgZXhwZWN0KHBvaW50MS5tdWwoMm4pLngpLnRvQmUoZXhwZWN0ZWRYKVxuICB9KVxuXG4gIC8vIHnCsiDiiaEgeMKzICsgYXggKyBiIChtb2QgcCkgdmVyaWZpY2F0aW9uXG4gIHRlc3QoJ2lzT25DdXJ2ZSgpIHJldHVybnMgdHJ1ZSBmb3IgdmFsaWQgcG9pbnRzLCBmYWxzZSBmb3IgaWRlbnRpdHknLCAoKSA9PiB7XG4gICAgZXhwZWN0KHBvaW50MS5pc09uQ3VydmUoKSkudG9CZSh0cnVlKVxuICAgIGV4cGVjdChuZXcgUG9pbnQoKS5pc09uQ3VydmUoKSkudG9CZShmYWxzZSlcbiAgfSlcblxuICAvLyBQICsgTyA9IFAgIChpZGVudGl0eSBlbGVtZW50IG9mIHRoZSBncm91cClcbiAgdGVzdCgnYWRkaW5nIHRoZSBpZGVudGl0eSBlbGVtZW50IHJldHVybnMgdGhlIG9yaWdpbmFsIHBvaW50JywgKCkgPT4ge1xuICAgIGNvbnN0IGlkZW50aXR5ID0gbmV3IFBvaW50KG51bGwsIG51bGwsIGN1cnZlLCB0cnVlKVxuICAgIGV4cGVjdChwb2ludDEuYWRkKGlkZW50aXR5KS54KS50b0JlKHBvaW50MS54KVxuICAgIGV4cGVjdChpZGVudGl0eS5hZGQocG9pbnQxKS54KS50b0JlKHBvaW50MS54KVxuICB9KVxuXG4gIC8vIDDCt1AgPSBPICBhbmQgIG7Ct1AgPSBPICAoZ3JvdXAgb3JkZXIpXG4gIHRlc3QoJ211bHRpcGx5aW5nIGJ5IDAgb3IgbiB5aWVsZHMgdGhlIGlkZW50aXR5JywgKCkgPT4ge1xuICAgIGV4cGVjdChwb2ludDEubXVsKDBuKS5pc0lkZW50aXR5KS50b0JlKHRydWUpXG4gICAgZXhwZWN0KHBvaW50MS5tdWwoY3VydmUubikuaXNJZGVudGl0eSkudG9CZSh0cnVlKVxuICB9KVxuXG4gIC8vIFAgKyAoLVApID0gTyAgKGFkZGl0aXZlIGludmVyc2UgaW4gYWZmaW5lIGNvb3JkaW5hdGVzKVxuICB0ZXN0KCdQICsgKC1QKSA9IE8gaW4gYWZmaW5lIGNvb3JkaW5hdGVzJywgKCkgPT4ge1xuICAgIGNvbnN0IHAxYSA9IG5ldyBQb2ludChwb2ludDEueCwgcG9pbnQxLnksIGFmZmluZUN1cnZlKVxuICAgIGV4cGVjdChwMWEuYWRkKHAxYS5uZWcoKSkuaXNJZGVudGl0eSkudG9CZSh0cnVlKVxuICB9KVxuXG4gIC8vIFAgKyAoLVApID0gTyAgKGFkZGl0aXZlIGludmVyc2UgaW4gSmFjb2JpYW4gY29vcmRpbmF0ZXMpXG4gIHRlc3QoJ1AgKyAoLVApID0gTyBpbiBKYWNvYmlhbiBjb29yZGluYXRlcycsICgpID0+IHtcbiAgICBleHBlY3QocG9pbnQxLmFkZChwb2ludDEubmVnKCkpLmlzSWRlbnRpdHkpLnRvQmUodHJ1ZSlcbiAgfSlcblxuICAvLyBEb3VibGluZyBhIHBvaW50IHdpdGggeSA9IDA6IHRhbmdlbnQgaXMgdmVydGljYWwg4oaSIHJlc3VsdCBpcyBPXG4gIHRlc3QoJ2RvdWJsaW5nIGEgcG9pbnQgd2l0aCB5ID0gMCB5aWVsZHMgaWRlbnRpdHknLCAoKSA9PiB7XG4gICAgY29uc3QgY3J2ID0gbmV3IEN1cnZlUGFyYW1zKHsgcDogMTNuLCBhOiAxbiwgYjogMG4sIG46IDRuLCBoOiAxbiB9KVxuICAgIGNvbnN0IFAgPSBuZXcgUG9pbnQoMG4sIDBuLCBjcnYpXG4gICAgZXhwZWN0KFAuYWRkKFApLmlzSWRlbnRpdHkpLnRvQmUodHJ1ZSlcbiAgfSlcblxuICAvLyBrwrdPID0gTyAgKHNjYWxhciBtdWx0aXBsaWNhdGlvbiBvZiBpZGVudGl0eSlcbiAgdGVzdCgnbXVsdGlwbHlpbmcgaWRlbnRpdHkgYnkgYW55IHNjYWxhciByZW1haW5zIGlkZW50aXR5IChKYWNvYmlhbiknLCAoKSA9PiB7XG4gICAgY29uc3QgY3J2ID0gbmV3IEN1cnZlUGFyYW1zKHsgcDogMTNuLCBhOiAxbiwgYjogMG4sIG46IDRuLCBoOiAxbiB9KVxuICAgIGNvbnN0IGluZiA9IG5ldyBQb2ludChudWxsLCBudWxsLCBjcnYsIHRydWUpXG4gICAgZXhwZWN0KGluZi5tdWwoM24pLmlzSWRlbnRpdHkpLnRvQmUodHJ1ZSlcbiAgfSlcblxuICB0ZXN0KCdtdWx0aXBseWluZyBpZGVudGl0eSBieSBhbnkgc2NhbGFyIHJlbWFpbnMgaWRlbnRpdHkgKGFmZmluZSknLCAoKSA9PiB7XG4gICAgY29uc3QgY3J2ID0gbmV3IEN1cnZlUGFyYW1zKHtcbiAgICAgIHA6IDEzbixcbiAgICAgIGE6IDFuLFxuICAgICAgYjogMG4sXG4gICAgICBuOiA0bixcbiAgICAgIGg6IDFuLFxuICAgICAgY29vcmQ6IENvb3JkaW5hdGVTeXN0ZW0uQUZGSU5FLFxuICAgIH0pXG4gICAgY29uc3QgaW5mID0gbmV3IFBvaW50KG51bGwsIG51bGwsIGNydiwgdHJ1ZSlcbiAgICBleHBlY3QoaW5mLm11bCgzbikuaXNJZGVudGl0eSkudG9CZSh0cnVlKVxuICB9KVxuXG4gIC8vIEFmZmluZTogUCArICgtUCkgPSBPIHZpYSBhZmZpbmVfYWRkIGRldGVjdGluZyB44oKBID0geOKCgiwgeeKCgSDiiaAgeeKCglxuICB0ZXN0KCdhZmZpbmUgYWRkaXRpb24gb2YgaW52ZXJzZSBwb2ludHMgcmV0dXJucyBpZGVudGl0eScsICgpID0+IHtcbiAgICBjb25zdCBwMSA9IG5ldyBQb2ludChwb2ludDEueCwgcG9pbnQxLnksIGFmZmluZUN1cnZlKVxuICAgIGV4cGVjdChwMS5hZGQocDEubmVnKCkpLmlzSWRlbnRpdHkpLnRvQmUodHJ1ZSlcbiAgfSlcblxuICAvLyBBZmZpbmU6IGRvdWJsaW5nICgwLCAwKSB3aGVyZSAyeSA9IDAg4oaSIG5vIGludmVyc2Ug4oaSIE9cbiAgdGVzdCgnYWZmaW5lIGRvdWJsaW5nIHdpdGggeSA9IDAgcmV0dXJucyBpZGVudGl0eScsICgpID0+IHtcbiAgICBjb25zdCBjcnYgPSBuZXcgQ3VydmVQYXJhbXMoe1xuICAgICAgcDogMTNuLFxuICAgICAgYTogMW4sXG4gICAgICBiOiAwbixcbiAgICAgIG46IDRuLFxuICAgICAgaDogMW4sXG4gICAgICBjb29yZDogQ29vcmRpbmF0ZVN5c3RlbS5BRkZJTkUsXG4gICAgfSlcbiAgICBleHBlY3QobmV3IFBvaW50KDBuLCAwbiwgY3J2KS5hZGQobmV3IFBvaW50KDBuLCAwbiwgY3J2KSkuaXNJZGVudGl0eSkudG9CZShcbiAgICAgIHRydWUsXG4gICAgKVxuICB9KVxuXG4gIC8vIEphY29iaWFuOiBqYWNfYWRkIGRldGVjdHMgVeKCgSA9IFXigoIsIFPigoEg4omgIFPigoIg4oaSIGlkZW50aXR5XG4gIHRlc3QoJ0phY29iaWFuIGFkZGl0aW9uIG9mIGludmVyc2UgcG9pbnRzIHJldHVybnMgaWRlbnRpdHknLCAoKSA9PiB7XG4gICAgY29uc3QgY3J2ID0gbmV3IEN1cnZlUGFyYW1zKHsgcDogMTNuLCBhOiAxbiwgYjogMG4sIG46IDRuLCBoOiAxbiB9KVxuICAgIGNvbnN0IHBKYWMgPSB0b0phY29iaWFuKG5ldyBQb2ludCgxMG4sIDNuLCBjcnYpKVxuICAgIGNvbnN0IHBJbnZKYWMgPSB0b0phY29iaWFuKG5ldyBQb2ludCgxMG4sIGNydi5wIC0gM24sIGNydikpXG4gICAgY29uc3QgcmVzdWx0ID0gamFjQWRkKHBKYWMsIHBJbnZKYWMsIGNydilcbiAgICBleHBlY3QocmVzdWx0LngpLnRvQmUobnVsbClcbiAgICBleHBlY3QocmVzdWx0LnkpLnRvQmUobnVsbClcbiAgfSlcblxuICAvLyBKYWNvYmlhbjogZG91Ymxpbmcgd2l0aCBZID0gMCDihpIgaWRlbnRpdHlcbiAgdGVzdCgnSmFjb2JpYW4gZG91Ymxpbmcgd2l0aCB5ID0gMCByZXR1cm5zIGlkZW50aXR5JywgKCkgPT4ge1xuICAgIGNvbnN0IGNydiA9IG5ldyBDdXJ2ZVBhcmFtcyh7IHA6IDEzbiwgYTogMW4sIGI6IDBuLCBuOiA0biwgaDogMW4gfSlcbiAgICBjb25zdCByZXN1bHQgPSBqYWNEb3VibGUodG9KYWNvYmlhbihuZXcgUG9pbnQoMG4sIDBuLCBjcnYpKSwgY3J2KVxuICAgIGV4cGVjdChyZXN1bHQueCkudG9CZShudWxsKVxuICAgIGV4cGVjdChyZXN1bHQueSkudG9CZShudWxsKVxuICB9KVxuXG4gIC8vIE8gKyBPID0gT1xuICB0ZXN0KCdkb3VibGluZyBpZGVudGl0eSByZXR1cm5zIGlkZW50aXR5JywgKCkgPT4ge1xuICAgIGNvbnN0IGlkZW50aXR5ID0gbmV3IFBvaW50KG51bGwsIG51bGwsIGN1cnZlLCB0cnVlKVxuICAgIGV4cGVjdChpZGVudGl0eS5hZGQoaWRlbnRpdHkpLmlzSWRlbnRpdHkpLnRvQmUodHJ1ZSlcbiAgfSlcblxuICAvLyBqYWNfYWRkKFAsIE8pID0gUFxuICB0ZXN0KCdKYWNvYmlhbiBhZGQgd2l0aCBpZGVudGl0eSByZXR1cm5zIG9yaWdpbmFsIHBvaW50JywgKCkgPT4ge1xuICAgIGNvbnN0IGNydiA9IG5ldyBDdXJ2ZVBhcmFtcyh7IHA6IDEzbiwgYTogMW4sIGI6IDBuLCBuOiA0biwgaDogMW4gfSlcbiAgICBjb25zdCBwSmFjID0gdG9KYWNvYmlhbihuZXcgUG9pbnQoMTBuLCAzbiwgY3J2KSlcbiAgICBjb25zdCByZXN1bHQgPSBqYWNBZGQocEphYywgbmV3IEphY29iaWFuUG9pbnQoKSwgY3J2KVxuICAgIGV4cGVjdChyZXN1bHQueCkudG9CZShwSmFjLngpXG4gICAgZXhwZWN0KHJlc3VsdC55KS50b0JlKHBKYWMueSlcbiAgICBleHBlY3QocmVzdWx0LnopLnRvQmUocEphYy56KVxuICB9KVxuXG4gIC8vIGphY19kb3VibGUoTykgPSBPXG4gIHRlc3QoJ0phY29iaWFuIGRvdWJsaW5nIGlkZW50aXR5IHJldHVybnMgaWRlbnRpdHknLCAoKSA9PiB7XG4gICAgY29uc3QgY3J2ID0gbmV3IEN1cnZlUGFyYW1zKHsgcDogMTNuLCBhOiAxbiwgYjogMG4sIG46IDRuLCBoOiAxbiB9KVxuICAgIGNvbnN0IHJlc3VsdCA9IGphY0RvdWJsZShuZXcgSmFjb2JpYW5Qb2ludCgpLCBjcnYpXG4gICAgZXhwZWN0KHJlc3VsdC54KS50b0JlKG51bGwpXG4gICAgZXhwZWN0KHJlc3VsdC55KS50b0JlKG51bGwpXG4gIH0pXG5cbiAgLy8gdG9KYWNvYmlhbihPKSA9IEphY29iaWFuUG9pbnQobnVsbCwgbnVsbCwgMSlcbiAgdGVzdCgnY29udmVydGluZyBhZmZpbmUgaWRlbnRpdHkgdG8gSmFjb2JpYW4gZ2l2ZXMgSmFjb2JpYW4gaWRlbnRpdHknLCAoKSA9PiB7XG4gICAgY29uc3QgcmVzdWx0ID0gdG9KYWNvYmlhbihuZXcgUG9pbnQoKSlcbiAgICBleHBlY3QocmVzdWx0LngpLnRvQmUobnVsbClcbiAgICBleHBlY3QocmVzdWx0LnkpLnRvQmUobnVsbClcbiAgfSlcblxuICAvLyAtUCA9ICh4LCAteSBtb2QgcClcbiAgdGVzdCgnbmVnYXRpb24gY29tcHV0ZXMgKHgsIHAgLSB5KScsICgpID0+IHtcbiAgICBjb25zdCBuZWdQb2ludCA9IHBvaW50MS5uZWcoKVxuICAgIGV4cGVjdChuZWdQb2ludC54KS50b0JlKHBvaW50MS54KVxuICAgIGV4cGVjdChuZWdQb2ludC55KS50b0JlKCgoLXBvaW50MS55ICUgY3VydmUucCkgKyBjdXJ2ZS5wKSAlIGN1cnZlLnApXG4gIH0pXG5cbiAgLy8gUOKCgSAtIFDigoIgPSBQ4oKBICsgKC1Q4oKCKVxuICB0ZXN0KCdzdWJ0cmFjdGlvbiBlcXVhbHMgYWRkaXRpb24gb2YgbmVnYXRpb24nLCAoKSA9PiB7XG4gICAgY29uc3QgcmVzdWx0U3ViID0gcG9pbnQxLnN1Yihwb2ludDIpXG4gICAgY29uc3QgcmVzdWx0QWRkTmVnID0gcG9pbnQxLmFkZChwb2ludDIubmVnKCkpXG4gICAgZXhwZWN0KHJlc3VsdFN1Yi54KS50b0JlKHJlc3VsdEFkZE5lZy54KVxuICAgIGV4cGVjdChyZXN1bHRTdWIueSkudG9CZShyZXN1bHRBZGROZWcueSlcbiAgfSlcblxuICAvLyBhZmZpbmVfZG91YmxlKG51bGwsIG51bGwpID0gKG51bGwsIG51bGwpXG4gIHRlc3QoJ2FmZmluZV9kb3VibGUgb2YgaWRlbnRpdHkgcmV0dXJucyBpZGVudGl0eScsICgpID0+IHtcbiAgICBleHBlY3QoYWZmaW5lRG91YmxlKG51bGwsIG51bGwsIGFmZmluZUN1cnZlKSkudG9FcXVhbChbbnVsbCwgbnVsbF0pXG4gIH0pXG5cbiAgLy8gYWZmaW5lX2FkZChQLCBPKSA9IFBcbiAgdGVzdCgnYWZmaW5lX2FkZCB3aXRoIGlkZW50aXR5IGFzIHNlY29uZCBvcGVyYW5kIHJldHVybnMgZmlyc3QgcG9pbnQnLCAoKSA9PiB7XG4gICAgZXhwZWN0KGFmZmluZUFkZChwb2ludDEueCwgcG9pbnQxLnksIG51bGwsIG51bGwsIGFmZmluZUN1cnZlKSkudG9FcXVhbChbXG4gICAgICBwb2ludDEueCxcbiAgICAgIHBvaW50MS55LFxuICAgIF0pXG4gIH0pXG5cbiAgLy8gQXJpdGhtZXRpYyByZXF1aXJlcyBjdXJ2ZSBwYXJhbWV0ZXJzXG4gIHRlc3QoJ2FyaXRobWV0aWMgd2l0aG91dCBjdXJ2ZSBwYXJhbWV0ZXJzIHRocm93cycsICgpID0+IHtcbiAgICBjb25zdCBwID0gbmV3IFBvaW50KDFuLCAybilcbiAgICBleHBlY3QoKCkgPT4gcC5hZGQocCkpLnRvVGhyb3coKVxuICB9KVxuXG4gIC8vIF9jb2VyY2UgYXNzaWducyBjdXJ2ZSBwYXJhbXMgdG8gYSBwb2ludCB0aGF0IGxhY2tzIHRoZW1cbiAgdGVzdCgnX2NvZXJjZSBib3Jyb3dzIGN1cnZlIGZyb20gdGhlIG90aGVyIG9wZXJhbmQnLCAoKSA9PiB7XG4gICAgY29uc3QgcFdpdGhvdXRDdXJ2ZSA9IG5ldyBQb2ludChwb2ludDIueCwgcG9pbnQyLnkpXG4gICAgY29uc3QgY29lcmNlZCA9IHBvaW50MS5fY29lcmNlKHBXaXRob3V0Q3VydmUpXG4gICAgZXhwZWN0KGNvZXJjZWQuY3VydmUpLnRvQmUoY3VydmUpXG4gICAgZXhwZWN0KHBvaW50MS5hZGQocFdpdGhvdXRDdXJ2ZSkuaXNJZGVudGl0eSkudG9CZShmYWxzZSlcbiAgfSlcblxuICAvLyAtTyA9IE9cbiAgdGVzdCgnbmVnYXRpb24gb2YgaWRlbnRpdHkgcmV0dXJucyBpZGVudGl0eScsICgpID0+IHtcbiAgICBjb25zdCBpZGVudGl0eSA9IG5ldyBQb2ludChudWxsLCBudWxsLCBjdXJ2ZSwgdHJ1ZSlcbiAgICBleHBlY3QoaWRlbnRpdHkubmVnKCkuaXNJZGVudGl0eSkudG9CZSh0cnVlKVxuICB9KVxuXG4gIC8vIHRvU3RyaW5nIHJlcHJlc2VudGF0aW9uXG4gIHRlc3QoJ3RvU3RyaW5nIG9mIGlkZW50aXR5IGRpc3BsYXlzIFBvaW50KOKIniknLCAoKSA9PiB7XG4gICAgZXhwZWN0KG5ldyBQb2ludCgpLnRvU3RyaW5nKCkpLnRvQmUoJ1BvaW50KOKIniknKVxuICB9KVxuXG4gIHRlc3QoJ3RvU3RyaW5nIG9mIG5vbi1pZGVudGl0eSBkaXNwbGF5cyBjb29yZGluYXRlcycsICgpID0+IHtcbiAgICBleHBlY3QocG9pbnQxLnRvU3RyaW5nKCkpLnRvQ29udGFpbignUG9pbnQoeD0nKVxuICAgIGV4cGVjdChwb2ludDEudG9TdHJpbmcoKSkudG9Db250YWluKHBvaW50MS54LnRvU3RyaW5nKCkpXG4gIH0pXG5cbiAgLy8gLS0tIFNFQyAxIGNvbXByZXNzaW9uIC8gZGVjb21wcmVzc2lvbiAtLS1cblxuICB0ZXN0KCdjb21wcmVzc1NlYzEgcHJvZHVjZXMgY29ycmVjdCBwcmVmaXggYW5kIGxlbmd0aCcsICgpID0+IHtcbiAgICBjb25zdCBHID0gZ2V0R2VuZXJhdG9yKCdzZWNwMjU2azEnKVxuICAgIGNvbnN0IGNvbXByZXNzZWQgPSBHLmNvbXByZXNzU2VjMSgpXG4gICAgLy8gMjU2LWJpdCBjdXJ2ZSDihpIgMzItYnl0ZSB4ICsgMS1ieXRlIHByZWZpeCA9IDMzIGJ5dGVzXG4gICAgZXhwZWN0KGNvbXByZXNzZWQubGVuZ3RoKS50b0JlKDMzKVxuICAgIGV4cGVjdChbMHgwMiwgMHgwM10pLnRvQ29udGFpbihjb21wcmVzc2VkWzBdKVxuICB9KVxuXG4gIHRlc3QoJ2NvbXByZXNzU2VjMSB0aHJvd3MgZm9yIGlkZW50aXR5JywgKCkgPT4ge1xuICAgIGNvbnN0IGlkZW50aXR5ID0gbmV3IFBvaW50KG51bGwsIG51bGwsIGN1cnZlLCB0cnVlKVxuICAgIGV4cGVjdCgoKSA9PiBpZGVudGl0eS5jb21wcmVzc1NlYzEoKSkudG9UaHJvdygnaWRlbnRpdHknKVxuICB9KVxuXG4gIHRlc3QoJ3RvVW5jb21wcmVzc2VkU2VjMSBwcm9kdWNlcyBjb3JyZWN0IHByZWZpeCBhbmQgbGVuZ3RoJywgKCkgPT4ge1xuICAgIGNvbnN0IEcgPSBnZXRHZW5lcmF0b3IoJ3NlY3AyNTZrMScpXG4gICAgY29uc3QgdW5jb21wcmVzc2VkID0gRy50b1VuY29tcHJlc3NlZFNlYzEoKVxuICAgIC8vIDI1Ni1iaXQgY3VydmUg4oaSIDMyLWJ5dGUgeCArIDMyLWJ5dGUgeSArIDEtYnl0ZSBwcmVmaXggPSA2NSBieXRlc1xuICAgIGV4cGVjdCh1bmNvbXByZXNzZWQubGVuZ3RoKS50b0JlKDY1KVxuICAgIGV4cGVjdCh1bmNvbXByZXNzZWRbMF0pLnRvQmUoMHgwNClcbiAgfSlcblxuICB0ZXN0KCd0b1VuY29tcHJlc3NlZFNlYzEgdGhyb3dzIGZvciBpZGVudGl0eScsICgpID0+IHtcbiAgICBjb25zdCBpZGVudGl0eSA9IG5ldyBQb2ludChudWxsLCBudWxsLCBjdXJ2ZSwgdHJ1ZSlcbiAgICBleHBlY3QoKCkgPT4gaWRlbnRpdHkudG9VbmNvbXByZXNzZWRTZWMxKCkpLnRvVGhyb3coJ2lkZW50aXR5JylcbiAgfSlcblxuICB0ZXN0KCdmcm9tU2VjMSBjb21wcmVzc2VkIHJvdW5kdHJpcCcsICgpID0+IHtcbiAgICBjb25zdCBHID0gZ2V0R2VuZXJhdG9yKCdzZWNwMjU2azEnKVxuICAgIGNvbnN0IHNlYzFDdXJ2ZSA9IGdldEN1cnZlKCdzZWNwMjU2azEnKVxuICAgIGNvbnN0IGNvbXByZXNzZWQgPSBHLmNvbXByZXNzU2VjMSgpXG4gICAgY29uc3QgcmVjb3ZlcmVkID0gUG9pbnQuZnJvbVNlYzEoY29tcHJlc3NlZCwgc2VjMUN1cnZlKVxuICAgIGV4cGVjdChyZWNvdmVyZWQueCkudG9CZShHLngpXG4gICAgZXhwZWN0KHJlY292ZXJlZC55KS50b0JlKEcueSlcbiAgfSlcblxuICB0ZXN0KCdmcm9tU2VjMSB1bmNvbXByZXNzZWQgcm91bmR0cmlwJywgKCkgPT4ge1xuICAgIGNvbnN0IEcgPSBnZXRHZW5lcmF0b3IoJ3NlY3AyNTZrMScpXG4gICAgY29uc3Qgc2VjMUN1cnZlID0gZ2V0Q3VydmUoJ3NlY3AyNTZrMScpXG4gICAgY29uc3QgdW5jb21wcmVzc2VkID0gRy50b1VuY29tcHJlc3NlZFNlYzEoKVxuICAgIGNvbnN0IHJlY292ZXJlZCA9IFBvaW50LmZyb21TZWMxKHVuY29tcHJlc3NlZCwgc2VjMUN1cnZlKVxuICAgIGV4cGVjdChyZWNvdmVyZWQueCkudG9CZShHLngpXG4gICAgZXhwZWN0KHJlY292ZXJlZC55KS50b0JlKEcueSlcbiAgfSlcblxuICB0ZXN0KCdmcm9tU2VjMSB0aHJvd3MgZm9yIGRhdGEgdG9vIHNob3J0JywgKCkgPT4ge1xuICAgIGNvbnN0IHNlYzFDdXJ2ZSA9IGdldEN1cnZlKCdzZWNwMjU2azEnKVxuICAgIGV4cGVjdCgoKSA9PiBQb2ludC5mcm9tU2VjMShuZXcgVWludDhBcnJheShbMHgwMl0pLCBzZWMxQ3VydmUpKS50b1Rocm93KClcbiAgfSlcblxuICB0ZXN0KCdmcm9tU2VjMSB0aHJvd3MgZm9yIHVua25vd24gcHJlZml4JywgKCkgPT4ge1xuICAgIGNvbnN0IHNlYzFDdXJ2ZSA9IGdldEN1cnZlKCdzZWNwMjU2azEnKVxuICAgIGNvbnN0IGJhZCA9IG5ldyBVaW50OEFycmF5KDMzKVxuICAgIGJhZFswXSA9IDB4MDVcbiAgICBleHBlY3QoKCkgPT4gUG9pbnQuZnJvbVNlYzEoYmFkLCBzZWMxQ3VydmUpKS50b1Rocm93KCdVbmtub3duIFNFQyAxIHByZWZpeCcpXG4gIH0pXG5cbiAgdGVzdCgnZnJvbVNlYzEgdGhyb3dzIGZvciB3cm9uZyBjb21wcmVzc2VkIGxlbmd0aCcsICgpID0+IHtcbiAgICBjb25zdCBzZWMxQ3VydmUgPSBnZXRDdXJ2ZSgnc2VjcDI1NmsxJylcbiAgICBjb25zdCBiYWQgPSBuZXcgVWludDhBcnJheSgyMClcbiAgICBiYWRbMF0gPSAweDAyXG4gICAgZXhwZWN0KCgpID0+IFBvaW50LmZyb21TZWMxKGJhZCwgc2VjMUN1cnZlKSkudG9UaHJvdygpXG4gIH0pXG5cbiAgdGVzdCgnZnJvbVNlYzEgdGhyb3dzIGZvciB3cm9uZyB1bmNvbXByZXNzZWQgbGVuZ3RoJywgKCkgPT4ge1xuICAgIGNvbnN0IHNlYzFDdXJ2ZSA9IGdldEN1cnZlKCdzZWNwMjU2azEnKVxuICAgIGNvbnN0IGJhZCA9IG5ldyBVaW50OEFycmF5KDIwKVxuICAgIGJhZFswXSA9IDB4MDRcbiAgICBleHBlY3QoKCkgPT4gUG9pbnQuZnJvbVNlYzEoYmFkLCBzZWMxQ3VydmUpKS50b1Rocm93KClcbiAgfSlcblxuICB0ZXN0KCdjb21wcmVzcyB0aHJvd3MgZm9yIGlkZW50aXR5JywgKCkgPT4ge1xuICAgIGNvbnN0IGlkZW50aXR5ID0gbmV3IFBvaW50KG51bGwsIG51bGwsIGN1cnZlLCB0cnVlKVxuICAgIGV4cGVjdCgoKSA9PiBpZGVudGl0eS5jb21wcmVzcygpKS50b1Rocm93KCdpZGVudGl0eScpXG4gIH0pXG5cbiAgdGVzdCgnZGVjb21wcmVzcyB3aXRoIHBhcml0eSBmbGlwIHNlbGVjdHMgY29ycmVjdCByb290JywgKCkgPT4ge1xuICAgIGNvbnN0IHRveUN1cnZlID0gbmV3IEN1cnZlUGFyYW1zKHsgcDogMjNuLCBhOiAxbiwgYjogMW4sIG46IDI4biwgaDogMW4gfSlcbiAgICBjb25zdCBQID0gbmV3IFBvaW50KDBuLCAxbiwgdG95Q3VydmUpXG4gICAgY29uc3QgW3gsIHBhcml0eV0gPSBQLmNvbXByZXNzKClcbiAgICAvLyBEZWNvbXByZXNzIHdpdGggb3Bwb3NpdGUgcGFyaXR5IOKGkiB5ID0gcCAtIG9yaWdpbmFsX3lcbiAgICBjb25zdCBmbGlwcGVkID0gUG9pbnQuZGVjb21wcmVzcyh4LCBwYXJpdHkgPT09IDBuID8gMW4gOiAwbiwgdG95Q3VydmUpXG4gICAgZXhwZWN0KGZsaXBwZWQueSkudG9CZSh0b3lDdXJ2ZS5wIC0gUC55KVxuICB9KVxuXG4gIHRlc3QoJ2RlY29tcHJlc3MgdGhyb3dzIGZvciBpbnZhbGlkIHgnLCAoKSA9PiB7XG4gICAgLy8gVXNlIGEgY3VydmUgd2hlcmUgeD05OTkgaGFzIG5vIHZhbGlkIHBvaW50XG4gICAgY29uc3QgdG95Q3VydmUgPSBuZXcgQ3VydmVQYXJhbXMoeyBwOiAyM24sIGE6IDFuLCBiOiAxbiwgbjogMjhuLCBoOiAxbiB9KVxuICAgIC8vIHg9MjogcmhzID0gOCArIDIgKyAxID0gMTEuIDExIGlzIG5vdCBhIFFSIG1vZCAyMy5cbiAgICBleHBlY3QoKCkgPT4gUG9pbnQuZGVjb21wcmVzcygybiwgMG4sIHRveUN1cnZlKSkudG9UaHJvdyhcbiAgICAgICdkb2VzIG5vdCBjb3JyZXNwb25kJyxcbiAgICApXG4gIH0pXG5cbiAgdGVzdCgnU0VDIDEgcm91bmR0cmlwIG9uIHNlY3A1MjFyMSAob2RkLWJ5dGUgZmllbGQgc2l6ZSknLCAoKSA9PiB7XG4gICAgY29uc3QgRyA9IGdldEdlbmVyYXRvcignc2VjcDUyMXIxJylcbiAgICBjb25zdCBjID0gZ2V0Q3VydmUoJ3NlY3A1MjFyMScpXG4gICAgY29uc3QgY29tcCA9IEcuY29tcHJlc3NTZWMxKClcbiAgICBjb25zdCB1bmNvbXAgPSBHLnRvVW5jb21wcmVzc2VkU2VjMSgpXG4gICAgZXhwZWN0KFBvaW50LmZyb21TZWMxKGNvbXAsIGMpLngpLnRvQmUoRy54KVxuICAgIGV4cGVjdChQb2ludC5mcm9tU2VjMSh1bmNvbXAsIGMpLngpLnRvQmUoRy54KVxuICB9KVxufSlcblxuLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4vLyBHcm91cCBsYXcgcHJvcGVydGllcyBvbiBFOiB5wrIgPSB4wrMgKyB4ICsgMSBvdmVyIEbigoLigoMgIChuID0gMjgpXG4vL1xuLy8gQW4gZWxsaXB0aWMgY3VydmUgb3ZlciBhIGZpbml0ZSBmaWVsZCBmb3JtcyBhbiBhYmVsaWFuIGdyb3VwOlxuLy8gICAxLiBJZGVudGl0eTogICAgICAgUCArIE8gPSBQXG4vLyAgIDIuIEludmVyc2U6ICAgICAg