turtb 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/bin/turtb.js +259 -0
- package/lib/display/h.js +204 -0
- package/lib/display/helpers.js +78 -0
- package/lib/display/render.js +177 -0
- package/lib/display/shapes.js +91 -0
- package/lib/turtle/Signer.js +120 -0
- package/lib/turtle/Signer.test.js +38 -0
- package/lib/turtle/TurtleBranch.js +147 -0
- package/lib/turtle/TurtleBranch.test.js +89 -0
- package/lib/turtle/TurtleDictionary.js +157 -0
- package/lib/turtle/TurtleDictionary.test.js +331 -0
- package/lib/turtle/U8aTurtle.js +203 -0
- package/lib/turtle/U8aTurtle.test.js +60 -0
- package/lib/turtle/Workspace.js +62 -0
- package/lib/turtle/Workspace.test.js +63 -0
- package/lib/turtle/codecs/CodecType.js +36 -0
- package/lib/turtle/codecs/CodecTypeVersion.js +37 -0
- package/lib/turtle/codecs/Commit.js +10 -0
- package/lib/turtle/codecs/CompositeCodec.js +86 -0
- package/lib/turtle/codecs/TreeNode.js +38 -0
- package/lib/turtle/codecs/codec.js +441 -0
- package/lib/turtle/connections/AbstractUpdater.js +176 -0
- package/lib/turtle/connections/TurtleBranchMultiplexer.js +102 -0
- package/lib/turtle/connections/TurtleBranchMultiplexer.test.js +26 -0
- package/lib/turtle/connections/TurtleBranchUpdater.js +47 -0
- package/lib/turtle/connections/TurtleDB.js +165 -0
- package/lib/turtle/connections/TurtleDB.test.js +45 -0
- package/lib/turtle/connections/TurtleTalker.js +34 -0
- package/lib/turtle/connections/TurtleTalker.test.js +101 -0
- package/lib/turtle/utils.js +192 -0
- package/lib/turtle/utils.test.js +158 -0
- package/lib/utils/Assert.js +115 -0
- package/lib/utils/NestedSet.js +68 -0
- package/lib/utils/NestedSet.test.js +30 -0
- package/lib/utils/OWN_KEYS.js +1 -0
- package/lib/utils/Recaller.js +175 -0
- package/lib/utils/Recaller.test.js +75 -0
- package/lib/utils/TestRunner.js +200 -0
- package/lib/utils/TestRunner.test.js +144 -0
- package/lib/utils/TestRunnerConstants.js +13 -0
- package/lib/utils/combineUint8ArrayLikes.js +18 -0
- package/lib/utils/combineUint8Arrays.js +23 -0
- package/lib/utils/components.js +88 -0
- package/lib/utils/crypto.js +17 -0
- package/lib/utils/deepEqual.js +16 -0
- package/lib/utils/deepEqual.test.js +27 -0
- package/lib/utils/fileTransformer.js +16 -0
- package/lib/utils/handleRedirect.js +93 -0
- package/lib/utils/logger.js +24 -0
- package/lib/utils/nextTick.js +47 -0
- package/lib/utils/noble-secp256k1.js +602 -0
- package/lib/utils/proxyWithRecaller.js +51 -0
- package/lib/utils/toCombinedVersion.js +14 -0
- package/lib/utils/toSubVersions.js +14 -0
- package/lib/utils/toVersionCount.js +5 -0
- package/lib/utils/webSocketMuxFactory.js +123 -0
- package/lib/utils/zabacaba.js +25 -0
- package/package.json +24 -0
- package/src/ArchiveUpdater.js +99 -0
- package/src/S3Updater.js +99 -0
- package/src/archiveSync.js +28 -0
- package/src/fileSync.js +155 -0
- package/src/getExistenceLength.js +19 -0
- package/src/manageCert.js +36 -0
- package/src/originSync.js +75 -0
- package/src/outletSync.js +50 -0
- package/src/proxyFolder.js +195 -0
- package/src/s3Sync.js +32 -0
- package/src/webSync.js +101 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
/*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
|
|
2
|
+
/**
|
|
3
|
+
* 4KB JS implementation of secp256k1 signatures & ECDH. Compliant with RFC6979.
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
const B256 = 2n ** 256n;
|
|
7
|
+
const P = B256 - 0x1000003d1n; // curve's field prime
|
|
8
|
+
const N = B256 - 0x14551231950b75fc4402da1732fc9bebfn; // curve (group) order
|
|
9
|
+
const Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n; // base point x
|
|
10
|
+
const Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n; // base point y
|
|
11
|
+
/**
|
|
12
|
+
* secp256k1 curve parameters. Equation is x³ + ax + b.
|
|
13
|
+
* Gx and Gy are generator coordinates. p is field order, n is group order.
|
|
14
|
+
*/
|
|
15
|
+
const CURVE = {
|
|
16
|
+
p: P, n: N, a: 0n, b: 7n, Gx, Gy
|
|
17
|
+
}; // exported variables incl. a, b
|
|
18
|
+
const fLen = 32; // field / group byte length
|
|
19
|
+
const curve = (x) => M(M(x * x) * x + CURVE.b); // x³ + ax + b weierstrass formula; a=0
|
|
20
|
+
const err = (m = '') => { throw new Error(m); }; // error helper, messes-up stack trace
|
|
21
|
+
const isB = (n) => typeof n === 'bigint'; // is big integer
|
|
22
|
+
const isS = (s) => typeof s === 'string'; // is string
|
|
23
|
+
const fe = (n) => isB(n) && 0n < n && n < P; // is field element (invertible)
|
|
24
|
+
const ge = (n) => isB(n) && 0n < n && n < N; // is group element
|
|
25
|
+
const isu8 = (a) => (a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array'));
|
|
26
|
+
const au8 = (a, l) => // assert is Uint8Array (of specific length)
|
|
27
|
+
!isu8(a) || (typeof l === 'number' && l > 0 && a.length !== l) ?
|
|
28
|
+
err('Uint8Array expected') : a;
|
|
29
|
+
const u8n = (data) => new Uint8Array(data); // creates Uint8Array
|
|
30
|
+
const toU8 = (a, len) => au8(isS(a) ? h2b(a) : u8n(au8(a)), len); // norm(hex/u8a) to u8a
|
|
31
|
+
const M = (a, b = P) => {
|
|
32
|
+
const r = a % b;
|
|
33
|
+
return r >= 0n ? r : b + r;
|
|
34
|
+
};
|
|
35
|
+
const aPoint = (p) => (p instanceof Point ? p : err('Point expected')); // is 3d point
|
|
36
|
+
/** Point in 3d xyz projective coordinates. 3d takes less inversions than 2d. */
|
|
37
|
+
class Point {
|
|
38
|
+
constructor(px, py, pz) {
|
|
39
|
+
this.px = px;
|
|
40
|
+
this.py = py;
|
|
41
|
+
this.pz = pz;
|
|
42
|
+
Object.freeze(this);
|
|
43
|
+
}
|
|
44
|
+
/** Create 3d xyz point from 2d xy. (0, 0) => (0, 1, 0), not (0, 0, 1) */
|
|
45
|
+
static fromAffine(p) {
|
|
46
|
+
return ((p.x === 0n) && (p.y === 0n)) ? I : new Point(p.x, p.y, 1n);
|
|
47
|
+
}
|
|
48
|
+
/** Convert Uint8Array or hex string to Point. */
|
|
49
|
+
static fromHex(hex) {
|
|
50
|
+
hex = toU8(hex); // convert hex string to Uint8Array
|
|
51
|
+
let p = undefined;
|
|
52
|
+
const head = hex[0], tail = hex.subarray(1); // first byte is prefix, rest is data
|
|
53
|
+
const x = slc(tail, 0, fLen), len = hex.length; // next 32 bytes are x coordinate
|
|
54
|
+
if (len === 33 && [0x02, 0x03].includes(head)) { // compressed points: 33b, start
|
|
55
|
+
if (!fe(x))
|
|
56
|
+
err('Point hex invalid: x not FE'); // with byte 0x02 or 0x03. Check if 0<x<P
|
|
57
|
+
let y = sqrt(curve(x)); // x³ + ax + b is right side of equation
|
|
58
|
+
const isYOdd = (y & 1n) === 1n; // y² is equivalent left-side. Calculate y²:
|
|
59
|
+
const headOdd = (head & 1) === 1; // y = √y²; there are two solutions: y, -y
|
|
60
|
+
if (headOdd !== isYOdd)
|
|
61
|
+
y = M(-y); // determine proper solution
|
|
62
|
+
p = new Point(x, y, 1n); // create point
|
|
63
|
+
} // Uncompressed points: 65b, start with 0x04
|
|
64
|
+
if (len === 65 && head === 0x04)
|
|
65
|
+
p = new Point(x, slc(tail, fLen, 2 * fLen), 1n);
|
|
66
|
+
return p ? p.ok() : err('Point invalid: not on curve'); // Verify the result
|
|
67
|
+
}
|
|
68
|
+
/** Create point from a private key. */
|
|
69
|
+
static fromPrivateKey(k) { return G.mul(toPriv(k)); }
|
|
70
|
+
get x() { return this.aff().x; } // .x, .y will call expensive toAffine:
|
|
71
|
+
get y() { return this.aff().y; } // should be used with care.
|
|
72
|
+
/** Equality check: compare points P&Q. */
|
|
73
|
+
equals(other) {
|
|
74
|
+
const { px: X1, py: Y1, pz: Z1 } = this;
|
|
75
|
+
const { px: X2, py: Y2, pz: Z2 } = aPoint(other); // isPoint() checks class equality
|
|
76
|
+
const X1Z2 = M(X1 * Z2), X2Z1 = M(X2 * Z1);
|
|
77
|
+
const Y1Z2 = M(Y1 * Z2), Y2Z1 = M(Y2 * Z1);
|
|
78
|
+
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
|
79
|
+
}
|
|
80
|
+
/** Flip point over y coordinate. */
|
|
81
|
+
negate() { return new Point(this.px, M(-this.py), this.pz); }
|
|
82
|
+
/** Point doubling: P+P, complete formula. */
|
|
83
|
+
double() { return this.add(this); }
|
|
84
|
+
/**
|
|
85
|
+
* Point addition: P+Q, complete, exception-free formula
|
|
86
|
+
* (Renes-Costello-Batina, algo 1 of [2015/1060](https://eprint.iacr.org/2015/1060)).
|
|
87
|
+
* Cost: 12M + 0S + 3*a + 3*b3 + 23add.
|
|
88
|
+
*/
|
|
89
|
+
add(other) {
|
|
90
|
+
const { px: X1, py: Y1, pz: Z1 } = this;
|
|
91
|
+
const { px: X2, py: Y2, pz: Z2 } = aPoint(other);
|
|
92
|
+
const { a, b } = CURVE;
|
|
93
|
+
let X3 = 0n, Y3 = 0n, Z3 = 0n;
|
|
94
|
+
const b3 = M(b * 3n);
|
|
95
|
+
let t0 = M(X1 * X2), t1 = M(Y1 * Y2), t2 = M(Z1 * Z2), t3 = M(X1 + Y1); // step 1
|
|
96
|
+
let t4 = M(X2 + Y2); // step 5
|
|
97
|
+
t3 = M(t3 * t4);
|
|
98
|
+
t4 = M(t0 + t1);
|
|
99
|
+
t3 = M(t3 - t4);
|
|
100
|
+
t4 = M(X1 + Z1);
|
|
101
|
+
let t5 = M(X2 + Z2); // step 10
|
|
102
|
+
t4 = M(t4 * t5);
|
|
103
|
+
t5 = M(t0 + t2);
|
|
104
|
+
t4 = M(t4 - t5);
|
|
105
|
+
t5 = M(Y1 + Z1);
|
|
106
|
+
X3 = M(Y2 + Z2); // step 15
|
|
107
|
+
t5 = M(t5 * X3);
|
|
108
|
+
X3 = M(t1 + t2);
|
|
109
|
+
t5 = M(t5 - X3);
|
|
110
|
+
Z3 = M(a * t4);
|
|
111
|
+
X3 = M(b3 * t2); // step 20
|
|
112
|
+
Z3 = M(X3 + Z3);
|
|
113
|
+
X3 = M(t1 - Z3);
|
|
114
|
+
Z3 = M(t1 + Z3);
|
|
115
|
+
Y3 = M(X3 * Z3);
|
|
116
|
+
t1 = M(t0 + t0); // step 25
|
|
117
|
+
t1 = M(t1 + t0);
|
|
118
|
+
t2 = M(a * t2);
|
|
119
|
+
t4 = M(b3 * t4);
|
|
120
|
+
t1 = M(t1 + t2);
|
|
121
|
+
t2 = M(t0 - t2); // step 30
|
|
122
|
+
t2 = M(a * t2);
|
|
123
|
+
t4 = M(t4 + t2);
|
|
124
|
+
t0 = M(t1 * t4);
|
|
125
|
+
Y3 = M(Y3 + t0);
|
|
126
|
+
t0 = M(t5 * t4); // step 35
|
|
127
|
+
X3 = M(t3 * X3);
|
|
128
|
+
X3 = M(X3 - t0);
|
|
129
|
+
t0 = M(t3 * t1);
|
|
130
|
+
Z3 = M(t5 * Z3);
|
|
131
|
+
Z3 = M(Z3 + t0); // step 40
|
|
132
|
+
return new Point(X3, Y3, Z3);
|
|
133
|
+
}
|
|
134
|
+
mul(n, safe = true) {
|
|
135
|
+
if (!safe && n === 0n)
|
|
136
|
+
return I; // in unsafe mode, allow zero
|
|
137
|
+
if (!ge(n))
|
|
138
|
+
err('scalar invalid'); // must be 0 < n < CURVE.n
|
|
139
|
+
if (this.equals(G))
|
|
140
|
+
return wNAF(n).p; // use precomputes for base point
|
|
141
|
+
let p = I, f = G; // init result point & fake point
|
|
142
|
+
for (let d = this; n > 0n; d = d.double(), n >>= 1n) { // double-and-add ladder
|
|
143
|
+
if (n & 1n)
|
|
144
|
+
p = p.add(d); // if bit is present, add to point
|
|
145
|
+
else if (safe)
|
|
146
|
+
f = f.add(d); // if not, add to fake for timing safety
|
|
147
|
+
}
|
|
148
|
+
return p;
|
|
149
|
+
}
|
|
150
|
+
mulAddQUns(R, u1, u2) {
|
|
151
|
+
return this.mul(u1, false).add(R.mul(u2, false)).ok(); // Unsafe: do NOT use for stuff related
|
|
152
|
+
} // to private keys. Doesn't use Shamir trick
|
|
153
|
+
/** Convert point to 2d xy affine point. (x, y, z) ∋ (x=x/z, y=y/z) */
|
|
154
|
+
toAffine() {
|
|
155
|
+
const { px: x, py: y, pz: z } = this;
|
|
156
|
+
if (this.equals(I))
|
|
157
|
+
return { x: 0n, y: 0n }; // fast-path for zero point
|
|
158
|
+
if (z === 1n)
|
|
159
|
+
return { x, y }; // if z is 1, pass affine coordinates as-is
|
|
160
|
+
const iz = inv(z, P); // z^-1: invert z
|
|
161
|
+
if (M(z * iz) !== 1n)
|
|
162
|
+
err('inverse invalid'); // (z * z^-1) must be 1, otherwise bad math
|
|
163
|
+
return { x: M(x * iz), y: M(y * iz) }; // x = x*z^-1; y = y*z^-1
|
|
164
|
+
}
|
|
165
|
+
/** Checks if the point is valid and on-curve. */
|
|
166
|
+
assertValidity() {
|
|
167
|
+
const { x, y } = this.aff(); // convert to 2d xy affine point.
|
|
168
|
+
if (!fe(x) || !fe(y))
|
|
169
|
+
err('Point invalid: x or y'); // x and y must be in range 0 < n < P
|
|
170
|
+
return M(y * y) === curve(x) ? // y² = x³ + ax + b, must be equal
|
|
171
|
+
this : err('Point invalid: not on curve');
|
|
172
|
+
}
|
|
173
|
+
multiply(n) { return this.mul(n); } // Aliases to compress code
|
|
174
|
+
aff() { return this.toAffine(); }
|
|
175
|
+
ok() { return this.assertValidity(); }
|
|
176
|
+
toHex(isCompressed = true) {
|
|
177
|
+
const { x, y } = this.aff(); // convert to 2d xy affine point
|
|
178
|
+
const head = isCompressed ? ((y & 1n) === 0n ? '02' : '03') : '04'; // 0x02, 0x03, 0x04 prefix
|
|
179
|
+
return head + n2h(x) + (isCompressed ? '' : n2h(y)); // prefix||x and ||y
|
|
180
|
+
}
|
|
181
|
+
toRawBytes(isCompressed = true) {
|
|
182
|
+
return h2b(this.toHex(isCompressed)); // re-use toHex(), convert hex to bytes
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** Generator / base point */
|
|
186
|
+
Point.BASE = new Point(Gx, Gy, 1n);
|
|
187
|
+
/** Identity / zero point */
|
|
188
|
+
Point.ZERO = new Point(0n, 1n, 0n);
|
|
189
|
+
const { BASE: G, ZERO: I } = Point; // Generator, identity points
|
|
190
|
+
const padh = (n, pad) => n.toString(16).padStart(pad, '0');
|
|
191
|
+
const b2h = (b) => Array.from(au8(b)).map(e => padh(e, 2)).join(''); // bytes to hex
|
|
192
|
+
const C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 }; // ASCII characters
|
|
193
|
+
const _ch = (ch) => {
|
|
194
|
+
if (ch >= C._0 && ch <= C._9)
|
|
195
|
+
return ch - C._0; // '2' => 50-48
|
|
196
|
+
if (ch >= C.A && ch <= C.F)
|
|
197
|
+
return ch - (C.A - 10); // 'B' => 66-(65-10)
|
|
198
|
+
if (ch >= C.a && ch <= C.f)
|
|
199
|
+
return ch - (C.a - 10); // 'b' => 98-(97-10)
|
|
200
|
+
return;
|
|
201
|
+
};
|
|
202
|
+
const h2b = (hex) => {
|
|
203
|
+
const e = 'hex invalid';
|
|
204
|
+
if (!isS(hex))
|
|
205
|
+
return err(e);
|
|
206
|
+
const hl = hex.length, al = hl / 2;
|
|
207
|
+
if (hl % 2)
|
|
208
|
+
return err(e);
|
|
209
|
+
const array = u8n(al);
|
|
210
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) { // treat each char as ASCII
|
|
211
|
+
const n1 = _ch(hex.charCodeAt(hi)); // parse first char, multiply it by 16
|
|
212
|
+
const n2 = _ch(hex.charCodeAt(hi + 1)); // parse second char
|
|
213
|
+
if (n1 === undefined || n2 === undefined)
|
|
214
|
+
return err(e);
|
|
215
|
+
array[ai] = n1 * 16 + n2; // example: 'A9' => 10*16 + 9
|
|
216
|
+
}
|
|
217
|
+
return array;
|
|
218
|
+
};
|
|
219
|
+
const b2n = (b) => BigInt('0x' + (b2h(b) || '0')); // bytes to number
|
|
220
|
+
const slc = (b, from, to) => b2n(b.slice(from, to)); // slice bytes num
|
|
221
|
+
const n2b = (num) => {
|
|
222
|
+
return isB(num) && num >= 0n && num < B256 ? h2b(padh(num, 2 * fLen)) : err('bigint expected');
|
|
223
|
+
};
|
|
224
|
+
const n2h = (num) => b2h(n2b(num)); // number to 32b hex
|
|
225
|
+
const concatB = (...arrs) => {
|
|
226
|
+
const r = u8n(arrs.reduce((sum, a) => sum + au8(a).length, 0)); // create u8a of summed length
|
|
227
|
+
let pad = 0; // walk through each array,
|
|
228
|
+
arrs.forEach(a => { r.set(a, pad); pad += a.length; }); // ensure they have proper type
|
|
229
|
+
return r;
|
|
230
|
+
};
|
|
231
|
+
const inv = (num, md) => {
|
|
232
|
+
if (num === 0n || md <= 0n)
|
|
233
|
+
err('no inverse n=' + num + ' mod=' + md); // no neg exponent for now
|
|
234
|
+
let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
|
|
235
|
+
while (a !== 0n) { // uses euclidean gcd algorithm
|
|
236
|
+
const q = b / a, r = b % a; // not constant-time
|
|
237
|
+
const m = x - u * q, n = y - v * q;
|
|
238
|
+
b = a, a = r, x = u, y = v, u = m, v = n;
|
|
239
|
+
}
|
|
240
|
+
return b === 1n ? M(x, md) : err('no inverse'); // b is gcd at this point
|
|
241
|
+
};
|
|
242
|
+
const sqrt = (n) => {
|
|
243
|
+
let r = 1n; // So, a special, fast case. Paper: "Square Roots from 1;24,51,10 to Dan Shanks".
|
|
244
|
+
for (let num = n, e = (P + 1n) / 4n; e > 0n; e >>= 1n) { // powMod: modular exponentiation.
|
|
245
|
+
if (e & 1n)
|
|
246
|
+
r = (r * num) % P; // Uses exponentiation by squaring.
|
|
247
|
+
num = (num * num) % P; // Not constant-time.
|
|
248
|
+
}
|
|
249
|
+
return M(r * r) === n ? r : err('sqrt invalid'); // check if result is valid
|
|
250
|
+
};
|
|
251
|
+
const toPriv = (p) => {
|
|
252
|
+
if (!isB(p))
|
|
253
|
+
p = b2n(toU8(p, fLen)); // convert to bigint when bytes
|
|
254
|
+
return ge(p) ? p : err('private key invalid 3'); // check if bigint is in range
|
|
255
|
+
};
|
|
256
|
+
const high = (n) => n > (N >> 1n); // if a number is bigger than CURVE.n/2
|
|
257
|
+
/** Creates 33/65-byte public key from 32-byte private key. */
|
|
258
|
+
const getPublicKey = (privKey, isCompressed = true) => {
|
|
259
|
+
return Point.fromPrivateKey(privKey).toRawBytes(isCompressed);
|
|
260
|
+
};
|
|
261
|
+
/** ECDSA Signature class. Supports only compact 64-byte representation, not DER. */
|
|
262
|
+
class Signature {
|
|
263
|
+
constructor(r, s, recovery) {
|
|
264
|
+
this.r = r;
|
|
265
|
+
this.s = s;
|
|
266
|
+
this.recovery = recovery;
|
|
267
|
+
this.assertValidity(); // recovery bit is optional when
|
|
268
|
+
} // constructed outside.
|
|
269
|
+
/** Create signature from 64b compact (r || s) representation. */
|
|
270
|
+
static fromCompact(hex) {
|
|
271
|
+
hex = toU8(hex, 64); // compact repr is (32b r)||(32b s)
|
|
272
|
+
return new Signature(slc(hex, 0, fLen), slc(hex, fLen, 2 * fLen));
|
|
273
|
+
}
|
|
274
|
+
assertValidity() { return ge(this.r) && ge(this.s) ? this : err(); } // 0 < r or s < CURVE.n
|
|
275
|
+
/** Create new signature, with added recovery bit. */
|
|
276
|
+
addRecoveryBit(rec) {
|
|
277
|
+
return new Signature(this.r, this.s, rec);
|
|
278
|
+
}
|
|
279
|
+
hasHighS() { return high(this.s); }
|
|
280
|
+
normalizeS() {
|
|
281
|
+
return high(this.s) ? new Signature(this.r, M(-this.s, N), this.recovery) : this;
|
|
282
|
+
}
|
|
283
|
+
/** ECDSA public key recovery. Requires msg hash and recovery id. */
|
|
284
|
+
recoverPublicKey(msgh) {
|
|
285
|
+
const { r, s, recovery: rec } = this; // secg.org/sec1-v2.pdf 4.1.6
|
|
286
|
+
if (![0, 1, 2, 3].includes(rec))
|
|
287
|
+
err('recovery id invalid'); // check recovery id
|
|
288
|
+
const h = bits2int_modN(toU8(msgh, fLen)); // Truncate hash
|
|
289
|
+
const radj = rec === 2 || rec === 3 ? r + N : r; // If rec was 2 or 3, q.x is bigger than n
|
|
290
|
+
if (radj >= P)
|
|
291
|
+
err('q.x invalid'); // ensure q.x is still a field element
|
|
292
|
+
const head = (rec & 1) === 0 ? '02' : '03'; // head is 0x02 or 0x03
|
|
293
|
+
const R = Point.fromHex(head + n2h(radj)); // concat head + hex repr of r
|
|
294
|
+
const ir = inv(radj, N); // r^-1
|
|
295
|
+
const u1 = M(-h * ir, N); // -hr^-1
|
|
296
|
+
const u2 = M(s * ir, N); // sr^-1
|
|
297
|
+
return G.mulAddQUns(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1)
|
|
298
|
+
}
|
|
299
|
+
/** Uint8Array 64b compact (r || s) representation. */
|
|
300
|
+
toCompactRawBytes() { return h2b(this.toCompactHex()); }
|
|
301
|
+
/** Hex string 64b compact (r || s) representation. */
|
|
302
|
+
toCompactHex() { return n2h(this.r) + n2h(this.s); }
|
|
303
|
+
}
|
|
304
|
+
const bits2int = (bytes) => {
|
|
305
|
+
const delta = bytes.length * 8 - 256; // RFC suggests optional truncating via bits2octets
|
|
306
|
+
if (delta > 1024)
|
|
307
|
+
err('msg invalid'); // our CUSTOM check, "just-in-case"
|
|
308
|
+
const num = b2n(bytes); // FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which
|
|
309
|
+
return delta > 0 ? num >> BigInt(delta) : num; // matches bits2int. bits2int can produce res>N.
|
|
310
|
+
};
|
|
311
|
+
const bits2int_modN = (bytes) => {
|
|
312
|
+
return M(bits2int(bytes), N); // with 0: BAD for trunc as per RFC vectors
|
|
313
|
+
};
|
|
314
|
+
const i2o = (num) => n2b(num); // int to octets
|
|
315
|
+
const cr = () => // We support: 1) browsers 2) node.js 19+ 3) deno, other envs with crypto
|
|
316
|
+
typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;
|
|
317
|
+
let _hmacSync; // Can be redefined by use in utils; built-ins don't provide it
|
|
318
|
+
const optS = { lowS: true }; // opts for sign()
|
|
319
|
+
const optV = { lowS: true }; // standard opts for verify()
|
|
320
|
+
const prepSig = (msgh, priv, opts = optS) => {
|
|
321
|
+
if (['der', 'recovered', 'canonical'].some(k => k in opts))
|
|
322
|
+
err('option not supported'); // legacy opts
|
|
323
|
+
let { lowS } = opts; // generates low-s sigs by default
|
|
324
|
+
if (lowS == null)
|
|
325
|
+
lowS = true; // RFC6979 3.2: we skip step A
|
|
326
|
+
const h1i = bits2int_modN(toU8(msgh)); // msg bigint
|
|
327
|
+
const h1o = i2o(h1i); // msg octets
|
|
328
|
+
const d = toPriv(priv); // validate private key, convert to bigint
|
|
329
|
+
const seed = [i2o(d), h1o]; // Step D of RFC6979 3.2
|
|
330
|
+
let ent = opts.extraEntropy; // RFC6979 3.6: additional k' (optional)
|
|
331
|
+
if (ent) // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
|
|
332
|
+
seed.push(ent === true ? etc.randomBytes(fLen) : toU8(ent)); // true == fetch from CSPRNG
|
|
333
|
+
const m = h1i; // convert msg to bigint
|
|
334
|
+
const k2sig = (kBytes) => {
|
|
335
|
+
const k = bits2int(kBytes); // RFC6979 method.
|
|
336
|
+
if (!ge(k))
|
|
337
|
+
return; // Check 0 < k < CURVE.n
|
|
338
|
+
const ik = inv(k, N); // k^-1 mod n, NOT mod P
|
|
339
|
+
const q = G.mul(k).aff(); // q = Gk
|
|
340
|
+
const r = M(q.x, N); // r = q.x mod n
|
|
341
|
+
if (r === 0n)
|
|
342
|
+
return; // r=0 invalid
|
|
343
|
+
const s = M(ik * M(m + M(d * r, N), N), N); // s = k^-1(m + rd) mod n
|
|
344
|
+
if (s === 0n)
|
|
345
|
+
return; // s=0 invalid
|
|
346
|
+
let normS = s; // normalized S
|
|
347
|
+
let rec = (q.x === r ? 0 : 2) | Number(q.y & 1n); // recovery bit
|
|
348
|
+
if (lowS && high(s)) { // if lowS was passed, ensure s is always
|
|
349
|
+
normS = M(-s, N); // in the bottom half of CURVE.n
|
|
350
|
+
rec ^= 1;
|
|
351
|
+
}
|
|
352
|
+
return new Signature(r, normS, rec); // use normS, not s
|
|
353
|
+
};
|
|
354
|
+
return { seed: concatB(...seed), k2sig };
|
|
355
|
+
};
|
|
356
|
+
function hmacDrbg(asynchronous) {
|
|
357
|
+
let v = u8n(fLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
|
|
358
|
+
let k = u8n(fLen); // Steps B, C of RFC6979 3.2: set hashLen, in our case always same
|
|
359
|
+
let i = 0; // Iterations counter, will throw when over 1000
|
|
360
|
+
const reset = () => { v.fill(1); k.fill(0); i = 0; };
|
|
361
|
+
const _e = 'drbg: tried 1000 values';
|
|
362
|
+
if (asynchronous) { // asynchronous=true
|
|
363
|
+
const h = (...b) => etc.hmacSha256Async(k, v, ...b); // hmac(k)(v, ...values)
|
|
364
|
+
const reseed = async (seed = u8n()) => {
|
|
365
|
+
k = await h(u8n([0x00]), seed); // k = hmac(K || V || 0x00 || seed)
|
|
366
|
+
v = await h(); // v = hmac(K || V)
|
|
367
|
+
if (seed.length === 0)
|
|
368
|
+
return;
|
|
369
|
+
k = await h(u8n([0x01]), seed); // k = hmac(K || V || 0x01 || seed)
|
|
370
|
+
v = await h(); // v = hmac(K || V)
|
|
371
|
+
};
|
|
372
|
+
const gen = async () => {
|
|
373
|
+
if (i++ >= 1000)
|
|
374
|
+
err(_e);
|
|
375
|
+
v = await h(); // v = hmac(K || V)
|
|
376
|
+
return v;
|
|
377
|
+
};
|
|
378
|
+
return async (seed, pred) => {
|
|
379
|
+
reset(); // the returned fn, don't, it's: 1. slower (JIT). 2. unsafe (async race conditions)
|
|
380
|
+
await reseed(seed); // Steps D-G
|
|
381
|
+
let res = undefined; // Step H: grind until k is in [1..n-1]
|
|
382
|
+
while (!(res = pred(await gen())))
|
|
383
|
+
await reseed(); // test predicate until it returns ok
|
|
384
|
+
reset();
|
|
385
|
+
return res;
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
const h = (...b) => {
|
|
390
|
+
const f = _hmacSync;
|
|
391
|
+
if (!f)
|
|
392
|
+
err('etc.hmacSha256Sync not set');
|
|
393
|
+
return f(k, v, ...b); // hmac(k)(v, ...values)
|
|
394
|
+
};
|
|
395
|
+
const reseed = (seed = u8n()) => {
|
|
396
|
+
k = h(u8n([0x00]), seed); // k = hmac(k || v || 0x00 || seed)
|
|
397
|
+
v = h(); // v = hmac(k || v)
|
|
398
|
+
if (seed.length === 0)
|
|
399
|
+
return;
|
|
400
|
+
k = h(u8n([0x01]), seed); // k = hmac(k || v || 0x01 || seed)
|
|
401
|
+
v = h(); // v = hmac(k || v)
|
|
402
|
+
};
|
|
403
|
+
const gen = () => {
|
|
404
|
+
if (i++ >= 1000)
|
|
405
|
+
err(_e);
|
|
406
|
+
v = h(); // v = hmac(k || v)
|
|
407
|
+
return v;
|
|
408
|
+
};
|
|
409
|
+
return (seed, pred) => {
|
|
410
|
+
reset();
|
|
411
|
+
reseed(seed); // Steps D-G
|
|
412
|
+
let res = undefined; // Step H: grind until k is in [1..n-1]
|
|
413
|
+
while (!(res = pred(gen())))
|
|
414
|
+
reseed(); // test predicate until it returns ok
|
|
415
|
+
reset();
|
|
416
|
+
return res;
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
;
|
|
421
|
+
/** ECDSA signature generation. via secg.org/sec1-v2.pdf 4.1.2 + RFC6979 deterministic k. */
|
|
422
|
+
/**
|
|
423
|
+
* Sign a msg hash using secp256k1. Async.
|
|
424
|
+
* It is advised to use `extraEntropy: true` (from RFC6979 3.6) to prevent fault attacks.
|
|
425
|
+
* Worst case: if randomness source for extraEntropy is bad, it would be as secure as if
|
|
426
|
+
* the option has not been used.
|
|
427
|
+
* @param msgh - message HASH, not message itself e.g. sha256(message)
|
|
428
|
+
* @param priv - private key
|
|
429
|
+
* @param opts - `lowS: true` to prevent malleability (s >= CURVE.n/2), `extraEntropy: boolean | Hex` to improve sig security.
|
|
430
|
+
*/
|
|
431
|
+
const signAsync = async (msgh, priv, opts = optS) => {
|
|
432
|
+
const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg
|
|
433
|
+
return hmacDrbg(true)(seed, k2sig); // Re-run drbg until k2sig returns ok
|
|
434
|
+
};
|
|
435
|
+
/**
|
|
436
|
+
* Sign a msg hash using secp256k1.
|
|
437
|
+
* It is advised to use `extraEntropy: true` (from RFC6979 3.6) to prevent fault attacks.
|
|
438
|
+
* Worst case: if randomness source for extraEntropy is bad, it would be as secure as if
|
|
439
|
+
* the option has not been used.
|
|
440
|
+
* @param msgh - message HASH, not message itself e.g. sha256(message)
|
|
441
|
+
* @param priv - private key
|
|
442
|
+
* @param opts - `lowS: true` to prevent malleability (s >= CURVE.n/2), `extraEntropy: boolean | Hex` to improve sig security.
|
|
443
|
+
* @example
|
|
444
|
+
* const sig = sign(sha256('hello'), privKey, { extraEntropy: true }).toCompactRawBytes();
|
|
445
|
+
*/
|
|
446
|
+
const sign = (msgh, priv, opts = optS) => {
|
|
447
|
+
const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg
|
|
448
|
+
return hmacDrbg(false)(seed, k2sig); // Re-run drbg until k2sig returns ok
|
|
449
|
+
};
|
|
450
|
+
/**
|
|
451
|
+
* Verify a signature using secp256k1.
|
|
452
|
+
* @param sig - signature, 64-byte or Signature instance
|
|
453
|
+
* @param msgh - message HASH, not message itself e.g. sha256(message)
|
|
454
|
+
* @param pub - public key
|
|
455
|
+
* @param opts - { lowS: true } is default, prohibits s >= CURVE.n/2 to prevent malleability
|
|
456
|
+
*/
|
|
457
|
+
const verify = (sig, msgh, pub, opts = optV) => {
|
|
458
|
+
let { lowS } = opts; // ECDSA signature verification
|
|
459
|
+
if (lowS == null)
|
|
460
|
+
lowS = true; // Default lowS=true
|
|
461
|
+
if ('strict' in opts)
|
|
462
|
+
err('option not supported'); // legacy param
|
|
463
|
+
let sig_, h, P; // secg.org/sec1-v2.pdf 4.1.4
|
|
464
|
+
const rs = sig && typeof sig === 'object' && 'r' in sig; // Previous ver supported DER sigs. We
|
|
465
|
+
if (!rs && (toU8(sig).length !== 2 * fLen)) // throw error when DER is suspected now.
|
|
466
|
+
err('signature must be 64 bytes');
|
|
467
|
+
try {
|
|
468
|
+
sig_ = rs ? new Signature(sig.r, sig.s).assertValidity() : Signature.fromCompact(sig);
|
|
469
|
+
h = bits2int_modN(toU8(msgh)); // Truncate hash
|
|
470
|
+
P = pub instanceof Point ? pub.ok() : Point.fromHex(pub); // Validate public key
|
|
471
|
+
}
|
|
472
|
+
catch (e) {
|
|
473
|
+
return false;
|
|
474
|
+
} // Check sig for validity in both cases
|
|
475
|
+
if (!sig_)
|
|
476
|
+
return false;
|
|
477
|
+
const { r, s } = sig_;
|
|
478
|
+
if (lowS && high(s))
|
|
479
|
+
return false; // lowS bans sig.s >= CURVE.n/2
|
|
480
|
+
let R;
|
|
481
|
+
try {
|
|
482
|
+
const is = inv(s, N); // s^-1
|
|
483
|
+
const u1 = M(h * is, N); // u1 = hs^-1 mod n
|
|
484
|
+
const u2 = M(r * is, N); // u2 = rs^-1 mod n
|
|
485
|
+
R = G.mulAddQUns(P, u1, u2).aff(); // R = u1⋅G + u2⋅P
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
if (!R)
|
|
491
|
+
return false; // stop if R is identity / zero point
|
|
492
|
+
const v = M(R.x, N); // R.x must be in N's field, not P's
|
|
493
|
+
return v === r; // mod(R.x, n) == r
|
|
494
|
+
};
|
|
495
|
+
/**
|
|
496
|
+
* Elliptic Curve Diffie-Hellman (ECDH) on secp256k1.
|
|
497
|
+
* Result is **NOT hashed**. Use hash on it if you need.
|
|
498
|
+
* @param privA private key A
|
|
499
|
+
* @param pubB public key B
|
|
500
|
+
* @param isCompressed 33-byte or 65-byte output
|
|
501
|
+
* @returns public key C
|
|
502
|
+
*/
|
|
503
|
+
const getSharedSecret = (privA, pubB, isCompressed = true) => {
|
|
504
|
+
return Point.fromHex(pubB).mul(toPriv(privA)).toRawBytes(isCompressed); // ECDH
|
|
505
|
+
};
|
|
506
|
+
const hashToPrivateKey = (hash) => {
|
|
507
|
+
hash = toU8(hash); // produces private keys with modulo bias
|
|
508
|
+
if (hash.length < fLen + 8 || hash.length > 1024)
|
|
509
|
+
err('expected 40-1024b'); // being neglible.
|
|
510
|
+
const num = M(b2n(hash), N - 1n); // takes n+8 bytes
|
|
511
|
+
return n2b(num + 1n); // returns (hash mod n-1)+1
|
|
512
|
+
};
|
|
513
|
+
/** Math, hex, byte helpers. Not in `utils` because utils share API with noble-curves. */
|
|
514
|
+
const etc = {
|
|
515
|
+
hexToBytes: h2b,
|
|
516
|
+
bytesToHex: b2h,
|
|
517
|
+
concatBytes: concatB,
|
|
518
|
+
bytesToNumberBE: b2n,
|
|
519
|
+
numberToBytesBE: n2b,
|
|
520
|
+
mod: M,
|
|
521
|
+
invert: inv, // math utilities
|
|
522
|
+
hmacSha256Async: async (key, ...msgs) => {
|
|
523
|
+
const c = cr(); // async HMAC-SHA256, no sync built-in!
|
|
524
|
+
const s = c && c.subtle; // For React Native support, see README.
|
|
525
|
+
if (!s)
|
|
526
|
+
return err('etc.hmacSha256Async or crypto.subtle must be defined'); // Uses webcrypto built-in cryptography.
|
|
527
|
+
const k = await s.importKey('raw', key, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']);
|
|
528
|
+
return u8n(await s.sign('HMAC', k, concatB(...msgs)));
|
|
529
|
+
},
|
|
530
|
+
hmacSha256Sync: _hmacSync, // For TypeScript. Actual logic is below
|
|
531
|
+
hashToPrivateKey: hashToPrivateKey,
|
|
532
|
+
randomBytes: (len = 32) => {
|
|
533
|
+
const crypto = cr(); // Must be shimmed in node.js <= 18 to prevent error. See README.
|
|
534
|
+
if (!crypto || !crypto.getRandomValues)
|
|
535
|
+
err('crypto.getRandomValues must be defined');
|
|
536
|
+
return crypto.getRandomValues(u8n(len));
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
/** Curve-specific utilities for private keys. */
|
|
540
|
+
const utils = {
|
|
541
|
+
normPrivateKeyToScalar: toPriv,
|
|
542
|
+
isValidPrivateKey: (key) => { try {
|
|
543
|
+
return !!toPriv(key);
|
|
544
|
+
}
|
|
545
|
+
catch (e) {
|
|
546
|
+
return false;
|
|
547
|
+
} },
|
|
548
|
+
randomPrivateKey: () => hashToPrivateKey(etc.randomBytes(fLen + 16)), // FIPS 186 B.4.1.
|
|
549
|
+
precompute: (w = 8, p = G) => { p.multiply(3n); w; return p; }, // no-op
|
|
550
|
+
};
|
|
551
|
+
Object.defineProperties(etc, { hmacSha256Sync: {
|
|
552
|
+
configurable: false, get() { return _hmacSync; }, set(f) { if (!_hmacSync)
|
|
553
|
+
_hmacSync = f; },
|
|
554
|
+
} });
|
|
555
|
+
const W = 8; // Precomputes-related code. W = window size
|
|
556
|
+
const precompute = () => {
|
|
557
|
+
const points = []; // 10x sign(), 2x verify(). To achieve this,
|
|
558
|
+
const windows = 256 / W + 1; // app needs to spend 40ms+ to calculate
|
|
559
|
+
let p = G, b = p; // a lot of points related to base point G.
|
|
560
|
+
for (let w = 0; w < windows; w++) { // Points are stored in array and used
|
|
561
|
+
b = p; // any time Gx multiplication is done.
|
|
562
|
+
points.push(b); // They consume 16-32 MiB of RAM.
|
|
563
|
+
for (let i = 1; i < 2 ** (W - 1); i++) {
|
|
564
|
+
b = b.add(p);
|
|
565
|
+
points.push(b);
|
|
566
|
+
}
|
|
567
|
+
p = b.double(); // Precomputes don't speed-up getSharedKey,
|
|
568
|
+
} // which multiplies user point by scalar,
|
|
569
|
+
return points; // when precomputes are using base point
|
|
570
|
+
};
|
|
571
|
+
let Gpows = undefined; // precomputes for base point G
|
|
572
|
+
const wNAF = (n) => {
|
|
573
|
+
// Compared to other point mult methods,
|
|
574
|
+
const comp = Gpows || (Gpows = precompute()); // stores 2x less points using subtraction
|
|
575
|
+
const neg = (cnd, p) => { let n = p.negate(); return cnd ? n : p; }; // negate
|
|
576
|
+
let p = I, f = G; // f must be G, or could become I in the end
|
|
577
|
+
const windows = 1 + 256 / W; // W=8 17 windows
|
|
578
|
+
const wsize = 2 ** (W - 1); // W=8 128 window size
|
|
579
|
+
const mask = BigInt(2 ** W - 1); // W=8 will create mask 0b11111111
|
|
580
|
+
const maxNum = 2 ** W; // W=8 256
|
|
581
|
+
const shiftBy = BigInt(W); // W=8 8
|
|
582
|
+
for (let w = 0; w < windows; w++) {
|
|
583
|
+
const off = w * wsize;
|
|
584
|
+
let wbits = Number(n & mask); // extract W bits.
|
|
585
|
+
n >>= shiftBy; // shift number by W bits.
|
|
586
|
+
if (wbits > wsize) {
|
|
587
|
+
wbits -= maxNum;
|
|
588
|
+
n += 1n;
|
|
589
|
+
} // split if bits > max: +224 => 256-32
|
|
590
|
+
const off1 = off, off2 = off + Math.abs(wbits) - 1; // offsets, evaluate both
|
|
591
|
+
const cnd1 = w % 2 !== 0, cnd2 = wbits < 0; // conditions, evaluate both
|
|
592
|
+
if (wbits === 0) {
|
|
593
|
+
f = f.add(neg(cnd1, comp[off1])); // bits are 0: add garbage to fake point
|
|
594
|
+
}
|
|
595
|
+
else { // ^ can't add off2, off2 = I
|
|
596
|
+
p = p.add(neg(cnd2, comp[off2])); // bits are 1: add to result point
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return { p, f }; // return both real and fake points for JIT
|
|
600
|
+
}; // !! you can disable precomputes by commenting-out call of the wNAF() inside Point#mul()
|
|
601
|
+
export { getPublicKey, sign, signAsync, verify, CURVE, // Remove the export to easily use in REPL
|
|
602
|
+
getSharedSecret, etc, utils, Point as ProjectivePoint, Signature }; // envs like browser console
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { OWN_KEYS } from './OWN_KEYS.js'
|
|
2
|
+
import { Recaller } from './Recaller.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {Object} target
|
|
6
|
+
* @param {Recaller} [recaller=new Recaller('<unnamed proxyWithRecaller>')]
|
|
7
|
+
* @param {string} [name=recaller.name]
|
|
8
|
+
*/
|
|
9
|
+
export function proxyWithRecaller (
|
|
10
|
+
target,
|
|
11
|
+
recaller = new Recaller('<unnamed proxyWithRecaller>'),
|
|
12
|
+
name = recaller.name
|
|
13
|
+
) {
|
|
14
|
+
if (!name && recaller) name = recaller.name
|
|
15
|
+
if (!target || typeof target !== 'object') throw new Error('proxyWithRecaller can only proxy objects')
|
|
16
|
+
return new Proxy(target, {
|
|
17
|
+
has: (target, propertyKey) => {
|
|
18
|
+
recaller.reportKeyAccess(target, propertyKey, 'get', name)
|
|
19
|
+
return Reflect.has(target, propertyKey)
|
|
20
|
+
},
|
|
21
|
+
get: (target, propertyKey) => {
|
|
22
|
+
recaller.reportKeyAccess(target, propertyKey, 'get', name)
|
|
23
|
+
if (propertyKey === 'length' && Array.isArray(target)) return target.length
|
|
24
|
+
return Reflect.get(target, propertyKey)
|
|
25
|
+
},
|
|
26
|
+
set: (target, propertyKey, value) => {
|
|
27
|
+
const length = target.length
|
|
28
|
+
if (value !== target[propertyKey]) {
|
|
29
|
+
recaller.reportKeyMutation(target, propertyKey, 'set', name)
|
|
30
|
+
}
|
|
31
|
+
if (!(propertyKey in target)) {
|
|
32
|
+
recaller.reportKeyMutation(target, OWN_KEYS, 'set', name)
|
|
33
|
+
}
|
|
34
|
+
const result = Reflect.set(target, propertyKey, value)
|
|
35
|
+
if (Array.isArray(target) && length !== target.length) {
|
|
36
|
+
recaller.reportKeyMutation(target, 'length', 'set', name)
|
|
37
|
+
}
|
|
38
|
+
return result
|
|
39
|
+
},
|
|
40
|
+
deleteProperty: (target, propertyKey) => {
|
|
41
|
+
if (propertyKey in target) {
|
|
42
|
+
recaller.reportKeyMutation(target, OWN_KEYS, 'delete', name)
|
|
43
|
+
}
|
|
44
|
+
return Reflect.deleteProperty(target, propertyKey)
|
|
45
|
+
},
|
|
46
|
+
ownKeys: target => {
|
|
47
|
+
recaller.reportKeyAccess(target, OWN_KEYS, 'ownKeys', name)
|
|
48
|
+
return Reflect.ownKeys(target)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {Array.<number>} versionArrays
|
|
3
|
+
* @param {Array.<number>} versionArrayCounts
|
|
4
|
+
* @return number
|
|
5
|
+
*/
|
|
6
|
+
export const toCombinedVersion = (versionArrays, versionArrayCounts) => {
|
|
7
|
+
if (versionArrays.length !== versionArrayCounts.length) throw new Error('versionArrays/versionArrayCounts mismatch')
|
|
8
|
+
let combinedVersion = 0
|
|
9
|
+
for (let i = 0; i < versionArrays.length; ++i) {
|
|
10
|
+
combinedVersion *= versionArrayCounts[i]
|
|
11
|
+
combinedVersion += versionArrays[i]
|
|
12
|
+
}
|
|
13
|
+
return combinedVersion
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {number} combinedVersion
|
|
3
|
+
* @param {Array.<number>} versionArrayCounts
|
|
4
|
+
* @return {Array.<number}
|
|
5
|
+
*/
|
|
6
|
+
export const toSubVersions = (combinedVersion, versionArrayCounts) => {
|
|
7
|
+
const versionArrays = new Array(versionArrayCounts.length)
|
|
8
|
+
for (let i = versionArrays.length - 1; i >= 0; --i) {
|
|
9
|
+
const versionArrayCount = versionArrayCounts[i]
|
|
10
|
+
versionArrays[i] = combinedVersion % versionArrayCount
|
|
11
|
+
combinedVersion = Math.floor(combinedVersion / versionArrayCount)
|
|
12
|
+
}
|
|
13
|
+
return versionArrays
|
|
14
|
+
}
|