├── .gitignore
├── README.md
├── dist
└── .gitkeep
├── index.html
├── package-lock.json
├── package.json
├── src
├── art-state.js
├── art.js
├── base64.js
├── blank.js
├── dh.js
├── eckem.js
├── flat-state.js
├── iota.js
├── main.js
├── renderer.js
├── state-tester.js
├── tree-math.js
├── tree.js
├── treekem-state.js
├── treekem.js
└── util.js
└── web
└── jquery-3.3.1.min.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore files installed by npm
2 | node_modules
3 |
4 | # Ignore dist files, but keep the directory
5 | dist/*
6 | !dist/.gitkeep
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TreeKEM for Group Key Management
2 |
3 | This repo contains an implementation of a group key management
4 | scheme based on key encryption (KEM) rather than DH. In both the DH
5 | and KEM cases, the participants are arranged in a tree. The DH case
6 | corresponds to [ART](https://eprint.iacr.org/2017/666). Since we're
7 | using KEM and a tree here, we call the approach TreeKEM.
8 |
9 | In the `src` folder, there are implementations of three different
10 | group key agreement protocols: ART, TreeKEM, and a "flat" protocol
11 | where everyone just stores / sends `O(N)` keys. After building, you
12 | can use `index.html` to exercise these protocols and see
13 | visualizations of how they work.
14 |
15 | ## Quickstart
16 |
17 | ```
18 | > npm install
19 | > npm run build
20 | > open index.html
21 | # Use buttons to perform tree operations
22 | ```
23 |
--------------------------------------------------------------------------------
/dist/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bifurcation/treekem/a799f95815f542d058c271fc066a2b51a4c55971/dist/.gitkeep
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | TreeKEM
6 |
7 |
8 |
372 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 | UserAdd
438 | GroupAdd
439 |
440 |
441 | Filled boxes are nodes with private keys.
442 | Empty boxes are nodes with only public keys.
443 | Grey boxes are nodes where there is no data.
444 |
445 |
446 |
447 |
448 |
449 |
450 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TKEM",
3 | "version": "0.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "JSONStream": {
8 | "version": "1.3.3",
9 | "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.3.tgz",
10 | "integrity": "sha512-3Sp6WZZ/lXl+nTDoGpGWHEpTnnC6X5fnkolYZR6nwIfzbxxvA8utPWe1gCt7i0m9uVGsSz2IS8K8mJ7HmlduMg==",
11 | "dev": true,
12 | "requires": {
13 | "jsonparse": "^1.2.0",
14 | "through": ">=2.2.7 <3"
15 | }
16 | },
17 | "acorn": {
18 | "version": "5.7.1",
19 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz",
20 | "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==",
21 | "dev": true
22 | },
23 | "acorn-dynamic-import": {
24 | "version": "3.0.0",
25 | "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz",
26 | "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==",
27 | "dev": true,
28 | "requires": {
29 | "acorn": "^5.0.0"
30 | }
31 | },
32 | "acorn-node": {
33 | "version": "1.5.2",
34 | "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.5.2.tgz",
35 | "integrity": "sha512-krFKvw/d1F17AN3XZbybIUzEY4YEPNiGo05AfP3dBlfVKrMHETKpgjpuZkSF8qDNt9UkQcqj7am8yJLseklCMg==",
36 | "dev": true,
37 | "requires": {
38 | "acorn": "^5.7.1",
39 | "acorn-dynamic-import": "^3.0.0",
40 | "xtend": "^4.0.1"
41 | }
42 | },
43 | "array-filter": {
44 | "version": "0.0.1",
45 | "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz",
46 | "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=",
47 | "dev": true
48 | },
49 | "array-map": {
50 | "version": "0.0.0",
51 | "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz",
52 | "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=",
53 | "dev": true
54 | },
55 | "array-reduce": {
56 | "version": "0.0.0",
57 | "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz",
58 | "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=",
59 | "dev": true
60 | },
61 | "asn1.js": {
62 | "version": "4.10.1",
63 | "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
64 | "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
65 | "dev": true,
66 | "requires": {
67 | "bn.js": "^4.0.0",
68 | "inherits": "^2.0.1",
69 | "minimalistic-assert": "^1.0.0"
70 | }
71 | },
72 | "assert": {
73 | "version": "1.4.1",
74 | "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz",
75 | "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=",
76 | "dev": true,
77 | "requires": {
78 | "util": "0.10.3"
79 | },
80 | "dependencies": {
81 | "inherits": {
82 | "version": "2.0.1",
83 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
84 | "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
85 | "dev": true
86 | },
87 | "util": {
88 | "version": "0.10.3",
89 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
90 | "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
91 | "dev": true,
92 | "requires": {
93 | "inherits": "2.0.1"
94 | }
95 | }
96 | }
97 | },
98 | "balanced-match": {
99 | "version": "1.0.0",
100 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
101 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
102 | "dev": true
103 | },
104 | "base64-js": {
105 | "version": "1.3.0",
106 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
107 | "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==",
108 | "dev": true
109 | },
110 | "bn.js": {
111 | "version": "4.11.8",
112 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
113 | "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
114 | },
115 | "brace-expansion": {
116 | "version": "1.1.11",
117 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
118 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
119 | "dev": true,
120 | "requires": {
121 | "balanced-match": "^1.0.0",
122 | "concat-map": "0.0.1"
123 | }
124 | },
125 | "brorand": {
126 | "version": "1.1.0",
127 | "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
128 | "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
129 | },
130 | "browser-pack": {
131 | "version": "6.1.0",
132 | "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz",
133 | "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==",
134 | "dev": true,
135 | "requires": {
136 | "JSONStream": "^1.0.3",
137 | "combine-source-map": "~0.8.0",
138 | "defined": "^1.0.0",
139 | "safe-buffer": "^5.1.1",
140 | "through2": "^2.0.0",
141 | "umd": "^3.0.0"
142 | }
143 | },
144 | "browser-resolve": {
145 | "version": "1.11.3",
146 | "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz",
147 | "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==",
148 | "dev": true,
149 | "requires": {
150 | "resolve": "1.1.7"
151 | },
152 | "dependencies": {
153 | "resolve": {
154 | "version": "1.1.7",
155 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
156 | "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
157 | "dev": true
158 | }
159 | }
160 | },
161 | "browserify": {
162 | "version": "16.2.2",
163 | "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.2.2.tgz",
164 | "integrity": "sha512-fMES05wq1Oukts6ksGUU2TMVHHp06LyQt0SIwbXIHm7waSrQmNBZePsU0iM/4f94zbvb/wHma+D1YrdzWYnF/A==",
165 | "dev": true,
166 | "requires": {
167 | "JSONStream": "^1.0.3",
168 | "assert": "^1.4.0",
169 | "browser-pack": "^6.0.1",
170 | "browser-resolve": "^1.11.0",
171 | "browserify-zlib": "~0.2.0",
172 | "buffer": "^5.0.2",
173 | "cached-path-relative": "^1.0.0",
174 | "concat-stream": "^1.6.0",
175 | "console-browserify": "^1.1.0",
176 | "constants-browserify": "~1.0.0",
177 | "crypto-browserify": "^3.0.0",
178 | "defined": "^1.0.0",
179 | "deps-sort": "^2.0.0",
180 | "domain-browser": "^1.2.0",
181 | "duplexer2": "~0.1.2",
182 | "events": "^2.0.0",
183 | "glob": "^7.1.0",
184 | "has": "^1.0.0",
185 | "htmlescape": "^1.1.0",
186 | "https-browserify": "^1.0.0",
187 | "inherits": "~2.0.1",
188 | "insert-module-globals": "^7.0.0",
189 | "labeled-stream-splicer": "^2.0.0",
190 | "mkdirp": "^0.5.0",
191 | "module-deps": "^6.0.0",
192 | "os-browserify": "~0.3.0",
193 | "parents": "^1.0.1",
194 | "path-browserify": "~0.0.0",
195 | "process": "~0.11.0",
196 | "punycode": "^1.3.2",
197 | "querystring-es3": "~0.2.0",
198 | "read-only-stream": "^2.0.0",
199 | "readable-stream": "^2.0.2",
200 | "resolve": "^1.1.4",
201 | "shasum": "^1.0.0",
202 | "shell-quote": "^1.6.1",
203 | "stream-browserify": "^2.0.0",
204 | "stream-http": "^2.0.0",
205 | "string_decoder": "^1.1.1",
206 | "subarg": "^1.0.0",
207 | "syntax-error": "^1.1.1",
208 | "through2": "^2.0.0",
209 | "timers-browserify": "^1.0.1",
210 | "tty-browserify": "0.0.1",
211 | "url": "~0.11.0",
212 | "util": "~0.10.1",
213 | "vm-browserify": "^1.0.0",
214 | "xtend": "^4.0.0"
215 | }
216 | },
217 | "browserify-aes": {
218 | "version": "1.2.0",
219 | "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
220 | "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
221 | "dev": true,
222 | "requires": {
223 | "buffer-xor": "^1.0.3",
224 | "cipher-base": "^1.0.0",
225 | "create-hash": "^1.1.0",
226 | "evp_bytestokey": "^1.0.3",
227 | "inherits": "^2.0.1",
228 | "safe-buffer": "^5.0.1"
229 | }
230 | },
231 | "browserify-cipher": {
232 | "version": "1.0.1",
233 | "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
234 | "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
235 | "dev": true,
236 | "requires": {
237 | "browserify-aes": "^1.0.4",
238 | "browserify-des": "^1.0.0",
239 | "evp_bytestokey": "^1.0.0"
240 | }
241 | },
242 | "browserify-des": {
243 | "version": "1.0.2",
244 | "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
245 | "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
246 | "dev": true,
247 | "requires": {
248 | "cipher-base": "^1.0.1",
249 | "des.js": "^1.0.0",
250 | "inherits": "^2.0.1",
251 | "safe-buffer": "^5.1.2"
252 | }
253 | },
254 | "browserify-rsa": {
255 | "version": "4.0.1",
256 | "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
257 | "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
258 | "dev": true,
259 | "requires": {
260 | "bn.js": "^4.1.0",
261 | "randombytes": "^2.0.1"
262 | }
263 | },
264 | "browserify-sign": {
265 | "version": "4.0.4",
266 | "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
267 | "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
268 | "dev": true,
269 | "requires": {
270 | "bn.js": "^4.1.1",
271 | "browserify-rsa": "^4.0.0",
272 | "create-hash": "^1.1.0",
273 | "create-hmac": "^1.1.2",
274 | "elliptic": "^6.0.0",
275 | "inherits": "^2.0.1",
276 | "parse-asn1": "^5.0.0"
277 | }
278 | },
279 | "browserify-zlib": {
280 | "version": "0.2.0",
281 | "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
282 | "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
283 | "dev": true,
284 | "requires": {
285 | "pako": "~1.0.5"
286 | }
287 | },
288 | "buffer": {
289 | "version": "5.1.0",
290 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.1.0.tgz",
291 | "integrity": "sha512-YkIRgwsZwJWTnyQrsBTWefizHh+8GYj3kbL1BTiAQ/9pwpino0G7B2gp5tx/FUBqUlvtxV85KNR3mwfAtv15Yw==",
292 | "dev": true,
293 | "requires": {
294 | "base64-js": "^1.0.2",
295 | "ieee754": "^1.1.4"
296 | }
297 | },
298 | "buffer-from": {
299 | "version": "1.1.0",
300 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz",
301 | "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==",
302 | "dev": true
303 | },
304 | "buffer-xor": {
305 | "version": "1.0.3",
306 | "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
307 | "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
308 | "dev": true
309 | },
310 | "builtin-status-codes": {
311 | "version": "3.0.0",
312 | "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
313 | "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
314 | "dev": true
315 | },
316 | "cached-path-relative": {
317 | "version": "1.0.1",
318 | "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz",
319 | "integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=",
320 | "dev": true
321 | },
322 | "cipher-base": {
323 | "version": "1.0.4",
324 | "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
325 | "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
326 | "dev": true,
327 | "requires": {
328 | "inherits": "^2.0.1",
329 | "safe-buffer": "^5.0.1"
330 | }
331 | },
332 | "combine-source-map": {
333 | "version": "0.8.0",
334 | "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz",
335 | "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=",
336 | "dev": true,
337 | "requires": {
338 | "convert-source-map": "~1.1.0",
339 | "inline-source-map": "~0.6.0",
340 | "lodash.memoize": "~3.0.3",
341 | "source-map": "~0.5.3"
342 | }
343 | },
344 | "concat-map": {
345 | "version": "0.0.1",
346 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
347 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
348 | "dev": true
349 | },
350 | "concat-stream": {
351 | "version": "1.6.2",
352 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
353 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
354 | "dev": true,
355 | "requires": {
356 | "buffer-from": "^1.0.0",
357 | "inherits": "^2.0.3",
358 | "readable-stream": "^2.2.2",
359 | "typedarray": "^0.0.6"
360 | }
361 | },
362 | "console-browserify": {
363 | "version": "1.1.0",
364 | "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
365 | "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
366 | "dev": true,
367 | "requires": {
368 | "date-now": "^0.1.4"
369 | }
370 | },
371 | "constants-browserify": {
372 | "version": "1.0.0",
373 | "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
374 | "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
375 | "dev": true
376 | },
377 | "convert-source-map": {
378 | "version": "1.1.3",
379 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
380 | "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=",
381 | "dev": true
382 | },
383 | "core-util-is": {
384 | "version": "1.0.2",
385 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
386 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
387 | "dev": true
388 | },
389 | "create-ecdh": {
390 | "version": "4.0.3",
391 | "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
392 | "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
393 | "dev": true,
394 | "requires": {
395 | "bn.js": "^4.1.0",
396 | "elliptic": "^6.0.0"
397 | }
398 | },
399 | "create-hash": {
400 | "version": "1.2.0",
401 | "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
402 | "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
403 | "dev": true,
404 | "requires": {
405 | "cipher-base": "^1.0.1",
406 | "inherits": "^2.0.1",
407 | "md5.js": "^1.3.4",
408 | "ripemd160": "^2.0.1",
409 | "sha.js": "^2.4.0"
410 | }
411 | },
412 | "create-hmac": {
413 | "version": "1.1.7",
414 | "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
415 | "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
416 | "dev": true,
417 | "requires": {
418 | "cipher-base": "^1.0.3",
419 | "create-hash": "^1.1.0",
420 | "inherits": "^2.0.1",
421 | "ripemd160": "^2.0.0",
422 | "safe-buffer": "^5.0.1",
423 | "sha.js": "^2.4.8"
424 | }
425 | },
426 | "crypto-browserify": {
427 | "version": "3.12.0",
428 | "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
429 | "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
430 | "dev": true,
431 | "requires": {
432 | "browserify-cipher": "^1.0.0",
433 | "browserify-sign": "^4.0.0",
434 | "create-ecdh": "^4.0.0",
435 | "create-hash": "^1.1.0",
436 | "create-hmac": "^1.1.0",
437 | "diffie-hellman": "^5.0.0",
438 | "inherits": "^2.0.1",
439 | "pbkdf2": "^3.0.3",
440 | "public-encrypt": "^4.0.0",
441 | "randombytes": "^2.0.0",
442 | "randomfill": "^1.0.3"
443 | }
444 | },
445 | "date-now": {
446 | "version": "0.1.4",
447 | "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
448 | "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
449 | "dev": true
450 | },
451 | "defined": {
452 | "version": "1.0.0",
453 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
454 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
455 | "dev": true
456 | },
457 | "deps-sort": {
458 | "version": "2.0.0",
459 | "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz",
460 | "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=",
461 | "dev": true,
462 | "requires": {
463 | "JSONStream": "^1.0.3",
464 | "shasum": "^1.0.0",
465 | "subarg": "^1.0.0",
466 | "through2": "^2.0.0"
467 | }
468 | },
469 | "des.js": {
470 | "version": "1.0.0",
471 | "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
472 | "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
473 | "dev": true,
474 | "requires": {
475 | "inherits": "^2.0.1",
476 | "minimalistic-assert": "^1.0.0"
477 | }
478 | },
479 | "detective": {
480 | "version": "5.1.0",
481 | "resolved": "https://registry.npmjs.org/detective/-/detective-5.1.0.tgz",
482 | "integrity": "sha512-TFHMqfOvxlgrfVzTEkNBSh9SvSNX/HfF4OFI2QFGCyPm02EsyILqnUeb5P6q7JZ3SFNTBL5t2sePRgrN4epUWQ==",
483 | "dev": true,
484 | "requires": {
485 | "acorn-node": "^1.3.0",
486 | "defined": "^1.0.0",
487 | "minimist": "^1.1.1"
488 | }
489 | },
490 | "diffie-hellman": {
491 | "version": "5.0.3",
492 | "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
493 | "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
494 | "dev": true,
495 | "requires": {
496 | "bn.js": "^4.1.0",
497 | "miller-rabin": "^4.0.0",
498 | "randombytes": "^2.0.0"
499 | }
500 | },
501 | "domain-browser": {
502 | "version": "1.2.0",
503 | "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
504 | "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
505 | "dev": true
506 | },
507 | "duplexer2": {
508 | "version": "0.1.4",
509 | "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
510 | "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
511 | "dev": true,
512 | "requires": {
513 | "readable-stream": "^2.0.2"
514 | }
515 | },
516 | "elliptic": {
517 | "version": "6.4.0",
518 | "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz",
519 | "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=",
520 | "requires": {
521 | "bn.js": "^4.4.0",
522 | "brorand": "^1.0.1",
523 | "hash.js": "^1.0.0",
524 | "hmac-drbg": "^1.0.0",
525 | "inherits": "^2.0.1",
526 | "minimalistic-assert": "^1.0.0",
527 | "minimalistic-crypto-utils": "^1.0.0"
528 | }
529 | },
530 | "events": {
531 | "version": "2.1.0",
532 | "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz",
533 | "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==",
534 | "dev": true
535 | },
536 | "evp_bytestokey": {
537 | "version": "1.0.3",
538 | "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
539 | "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
540 | "dev": true,
541 | "requires": {
542 | "md5.js": "^1.3.4",
543 | "safe-buffer": "^5.1.1"
544 | }
545 | },
546 | "fs.realpath": {
547 | "version": "1.0.0",
548 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
549 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
550 | "dev": true
551 | },
552 | "function-bind": {
553 | "version": "1.1.1",
554 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
555 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
556 | "dev": true
557 | },
558 | "get-assigned-identifiers": {
559 | "version": "1.2.0",
560 | "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz",
561 | "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==",
562 | "dev": true
563 | },
564 | "glob": {
565 | "version": "7.1.2",
566 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
567 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
568 | "dev": true,
569 | "requires": {
570 | "fs.realpath": "^1.0.0",
571 | "inflight": "^1.0.4",
572 | "inherits": "2",
573 | "minimatch": "^3.0.4",
574 | "once": "^1.3.0",
575 | "path-is-absolute": "^1.0.0"
576 | }
577 | },
578 | "has": {
579 | "version": "1.0.3",
580 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
581 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
582 | "dev": true,
583 | "requires": {
584 | "function-bind": "^1.1.1"
585 | }
586 | },
587 | "hash-base": {
588 | "version": "3.0.4",
589 | "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
590 | "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
591 | "dev": true,
592 | "requires": {
593 | "inherits": "^2.0.1",
594 | "safe-buffer": "^5.0.1"
595 | }
596 | },
597 | "hash.js": {
598 | "version": "1.1.3",
599 | "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz",
600 | "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==",
601 | "requires": {
602 | "inherits": "^2.0.3",
603 | "minimalistic-assert": "^1.0.0"
604 | }
605 | },
606 | "hmac-drbg": {
607 | "version": "1.0.1",
608 | "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
609 | "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
610 | "requires": {
611 | "hash.js": "^1.0.3",
612 | "minimalistic-assert": "^1.0.0",
613 | "minimalistic-crypto-utils": "^1.0.1"
614 | }
615 | },
616 | "htmlescape": {
617 | "version": "1.1.1",
618 | "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz",
619 | "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=",
620 | "dev": true
621 | },
622 | "https-browserify": {
623 | "version": "1.0.0",
624 | "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
625 | "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
626 | "dev": true
627 | },
628 | "ieee754": {
629 | "version": "1.1.12",
630 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz",
631 | "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==",
632 | "dev": true
633 | },
634 | "inflight": {
635 | "version": "1.0.6",
636 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
637 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
638 | "dev": true,
639 | "requires": {
640 | "once": "^1.3.0",
641 | "wrappy": "1"
642 | }
643 | },
644 | "inherits": {
645 | "version": "2.0.3",
646 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
647 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
648 | },
649 | "inline-source-map": {
650 | "version": "0.6.2",
651 | "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz",
652 | "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=",
653 | "dev": true,
654 | "requires": {
655 | "source-map": "~0.5.3"
656 | }
657 | },
658 | "insert-module-globals": {
659 | "version": "7.2.0",
660 | "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.0.tgz",
661 | "integrity": "sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw==",
662 | "dev": true,
663 | "requires": {
664 | "JSONStream": "^1.0.3",
665 | "acorn-node": "^1.5.2",
666 | "combine-source-map": "^0.8.0",
667 | "concat-stream": "^1.6.1",
668 | "is-buffer": "^1.1.0",
669 | "path-is-absolute": "^1.0.1",
670 | "process": "~0.11.0",
671 | "through2": "^2.0.0",
672 | "undeclared-identifiers": "^1.1.2",
673 | "xtend": "^4.0.0"
674 | }
675 | },
676 | "is-buffer": {
677 | "version": "1.1.6",
678 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
679 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
680 | "dev": true
681 | },
682 | "isarray": {
683 | "version": "1.0.0",
684 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
685 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
686 | "dev": true
687 | },
688 | "json-stable-stringify": {
689 | "version": "0.0.1",
690 | "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz",
691 | "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=",
692 | "dev": true,
693 | "requires": {
694 | "jsonify": "~0.0.0"
695 | }
696 | },
697 | "jsonify": {
698 | "version": "0.0.0",
699 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
700 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
701 | "dev": true
702 | },
703 | "jsonparse": {
704 | "version": "1.3.1",
705 | "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
706 | "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
707 | "dev": true
708 | },
709 | "labeled-stream-splicer": {
710 | "version": "2.0.1",
711 | "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.1.tgz",
712 | "integrity": "sha512-MC94mHZRvJ3LfykJlTUipBqenZz1pacOZEMhhQ8dMGcDHs0SBE5GbsavUXV7YtP3icBW17W0Zy1I0lfASmo9Pg==",
713 | "dev": true,
714 | "requires": {
715 | "inherits": "^2.0.1",
716 | "isarray": "^2.0.4",
717 | "stream-splicer": "^2.0.0"
718 | },
719 | "dependencies": {
720 | "isarray": {
721 | "version": "2.0.4",
722 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.4.tgz",
723 | "integrity": "sha512-GMxXOiUirWg1xTKRipM0Ek07rX+ubx4nNVElTJdNLYmNO/2YrDkgJGw9CljXn+r4EWiDQg/8lsRdHyg2PJuUaA==",
724 | "dev": true
725 | }
726 | }
727 | },
728 | "lodash.memoize": {
729 | "version": "3.0.4",
730 | "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz",
731 | "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=",
732 | "dev": true
733 | },
734 | "md5.js": {
735 | "version": "1.3.4",
736 | "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
737 | "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=",
738 | "dev": true,
739 | "requires": {
740 | "hash-base": "^3.0.0",
741 | "inherits": "^2.0.1"
742 | }
743 | },
744 | "miller-rabin": {
745 | "version": "4.0.1",
746 | "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
747 | "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
748 | "dev": true,
749 | "requires": {
750 | "bn.js": "^4.0.0",
751 | "brorand": "^1.0.1"
752 | }
753 | },
754 | "minimalistic-assert": {
755 | "version": "1.0.0",
756 | "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
757 | "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M="
758 | },
759 | "minimalistic-crypto-utils": {
760 | "version": "1.0.1",
761 | "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
762 | "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
763 | },
764 | "minimatch": {
765 | "version": "3.0.4",
766 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
767 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
768 | "dev": true,
769 | "requires": {
770 | "brace-expansion": "^1.1.7"
771 | }
772 | },
773 | "minimist": {
774 | "version": "1.2.0",
775 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
776 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
777 | "dev": true
778 | },
779 | "mkdirp": {
780 | "version": "0.5.1",
781 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
782 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
783 | "dev": true,
784 | "requires": {
785 | "minimist": "0.0.8"
786 | },
787 | "dependencies": {
788 | "minimist": {
789 | "version": "0.0.8",
790 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
791 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
792 | "dev": true
793 | }
794 | }
795 | },
796 | "module-deps": {
797 | "version": "6.1.0",
798 | "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.1.0.tgz",
799 | "integrity": "sha512-NPs5N511VD1rrVJihSso/LiBShRbJALYBKzDW91uZYy7BpjnO4bGnZL3HjZ9yKcFdZUWwaYjDz9zxbuP7vKMuQ==",
800 | "dev": true,
801 | "requires": {
802 | "JSONStream": "^1.0.3",
803 | "browser-resolve": "^1.7.0",
804 | "cached-path-relative": "^1.0.0",
805 | "concat-stream": "~1.6.0",
806 | "defined": "^1.0.0",
807 | "detective": "^5.0.2",
808 | "duplexer2": "^0.1.2",
809 | "inherits": "^2.0.1",
810 | "parents": "^1.0.0",
811 | "readable-stream": "^2.0.2",
812 | "resolve": "^1.4.0",
813 | "stream-combiner2": "^1.1.1",
814 | "subarg": "^1.0.0",
815 | "through2": "^2.0.0",
816 | "xtend": "^4.0.0"
817 | }
818 | },
819 | "once": {
820 | "version": "1.4.0",
821 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
822 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
823 | "dev": true,
824 | "requires": {
825 | "wrappy": "1"
826 | }
827 | },
828 | "os-browserify": {
829 | "version": "0.3.0",
830 | "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
831 | "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
832 | "dev": true
833 | },
834 | "pako": {
835 | "version": "1.0.6",
836 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz",
837 | "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==",
838 | "dev": true
839 | },
840 | "parents": {
841 | "version": "1.0.1",
842 | "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz",
843 | "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=",
844 | "dev": true,
845 | "requires": {
846 | "path-platform": "~0.11.15"
847 | }
848 | },
849 | "parse-asn1": {
850 | "version": "5.1.1",
851 | "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
852 | "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==",
853 | "dev": true,
854 | "requires": {
855 | "asn1.js": "^4.0.0",
856 | "browserify-aes": "^1.0.0",
857 | "create-hash": "^1.1.0",
858 | "evp_bytestokey": "^1.0.0",
859 | "pbkdf2": "^3.0.3"
860 | }
861 | },
862 | "path-browserify": {
863 | "version": "0.0.1",
864 | "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
865 | "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
866 | "dev": true
867 | },
868 | "path-is-absolute": {
869 | "version": "1.0.1",
870 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
871 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
872 | "dev": true
873 | },
874 | "path-parse": {
875 | "version": "1.0.5",
876 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
877 | "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
878 | "dev": true
879 | },
880 | "path-platform": {
881 | "version": "0.11.15",
882 | "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz",
883 | "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=",
884 | "dev": true
885 | },
886 | "pbkdf2": {
887 | "version": "3.0.16",
888 | "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz",
889 | "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==",
890 | "dev": true,
891 | "requires": {
892 | "create-hash": "^1.1.2",
893 | "create-hmac": "^1.1.4",
894 | "ripemd160": "^2.0.1",
895 | "safe-buffer": "^5.0.1",
896 | "sha.js": "^2.4.8"
897 | }
898 | },
899 | "process": {
900 | "version": "0.11.10",
901 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
902 | "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
903 | "dev": true
904 | },
905 | "process-nextick-args": {
906 | "version": "2.0.0",
907 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
908 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
909 | "dev": true
910 | },
911 | "public-encrypt": {
912 | "version": "4.0.2",
913 | "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz",
914 | "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==",
915 | "dev": true,
916 | "requires": {
917 | "bn.js": "^4.1.0",
918 | "browserify-rsa": "^4.0.0",
919 | "create-hash": "^1.1.0",
920 | "parse-asn1": "^5.0.0",
921 | "randombytes": "^2.0.1"
922 | }
923 | },
924 | "punycode": {
925 | "version": "1.4.1",
926 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
927 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
928 | "dev": true
929 | },
930 | "querystring": {
931 | "version": "0.2.0",
932 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
933 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
934 | "dev": true
935 | },
936 | "querystring-es3": {
937 | "version": "0.2.1",
938 | "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
939 | "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
940 | "dev": true
941 | },
942 | "randombytes": {
943 | "version": "2.0.6",
944 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz",
945 | "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==",
946 | "dev": true,
947 | "requires": {
948 | "safe-buffer": "^5.1.0"
949 | }
950 | },
951 | "randomfill": {
952 | "version": "1.0.4",
953 | "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
954 | "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
955 | "dev": true,
956 | "requires": {
957 | "randombytes": "^2.0.5",
958 | "safe-buffer": "^5.1.0"
959 | }
960 | },
961 | "read-only-stream": {
962 | "version": "2.0.0",
963 | "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz",
964 | "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=",
965 | "dev": true,
966 | "requires": {
967 | "readable-stream": "^2.0.2"
968 | }
969 | },
970 | "readable-stream": {
971 | "version": "2.3.6",
972 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
973 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
974 | "dev": true,
975 | "requires": {
976 | "core-util-is": "~1.0.0",
977 | "inherits": "~2.0.3",
978 | "isarray": "~1.0.0",
979 | "process-nextick-args": "~2.0.0",
980 | "safe-buffer": "~5.1.1",
981 | "string_decoder": "~1.1.1",
982 | "util-deprecate": "~1.0.1"
983 | }
984 | },
985 | "resolve": {
986 | "version": "1.8.1",
987 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
988 | "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==",
989 | "dev": true,
990 | "requires": {
991 | "path-parse": "^1.0.5"
992 | }
993 | },
994 | "ripemd160": {
995 | "version": "2.0.2",
996 | "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
997 | "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
998 | "dev": true,
999 | "requires": {
1000 | "hash-base": "^3.0.0",
1001 | "inherits": "^2.0.1"
1002 | }
1003 | },
1004 | "safe-buffer": {
1005 | "version": "5.1.2",
1006 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1007 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
1008 | "dev": true
1009 | },
1010 | "sha.js": {
1011 | "version": "2.4.11",
1012 | "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
1013 | "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
1014 | "dev": true,
1015 | "requires": {
1016 | "inherits": "^2.0.1",
1017 | "safe-buffer": "^5.0.1"
1018 | }
1019 | },
1020 | "shasum": {
1021 | "version": "1.0.2",
1022 | "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz",
1023 | "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=",
1024 | "dev": true,
1025 | "requires": {
1026 | "json-stable-stringify": "~0.0.0",
1027 | "sha.js": "~2.4.4"
1028 | }
1029 | },
1030 | "shell-quote": {
1031 | "version": "1.6.1",
1032 | "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz",
1033 | "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=",
1034 | "dev": true,
1035 | "requires": {
1036 | "array-filter": "~0.0.0",
1037 | "array-map": "~0.0.0",
1038 | "array-reduce": "~0.0.0",
1039 | "jsonify": "~0.0.0"
1040 | }
1041 | },
1042 | "simple-concat": {
1043 | "version": "1.0.0",
1044 | "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
1045 | "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=",
1046 | "dev": true
1047 | },
1048 | "source-map": {
1049 | "version": "0.5.7",
1050 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
1051 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
1052 | "dev": true
1053 | },
1054 | "stream-browserify": {
1055 | "version": "2.0.1",
1056 | "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
1057 | "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
1058 | "dev": true,
1059 | "requires": {
1060 | "inherits": "~2.0.1",
1061 | "readable-stream": "^2.0.2"
1062 | }
1063 | },
1064 | "stream-combiner2": {
1065 | "version": "1.1.1",
1066 | "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz",
1067 | "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=",
1068 | "dev": true,
1069 | "requires": {
1070 | "duplexer2": "~0.1.0",
1071 | "readable-stream": "^2.0.2"
1072 | }
1073 | },
1074 | "stream-http": {
1075 | "version": "2.8.3",
1076 | "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
1077 | "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
1078 | "dev": true,
1079 | "requires": {
1080 | "builtin-status-codes": "^3.0.0",
1081 | "inherits": "^2.0.1",
1082 | "readable-stream": "^2.3.6",
1083 | "to-arraybuffer": "^1.0.0",
1084 | "xtend": "^4.0.0"
1085 | }
1086 | },
1087 | "stream-splicer": {
1088 | "version": "2.0.0",
1089 | "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz",
1090 | "integrity": "sha1-G2O+Q4oTPktnHMGTUZdgAXWRDYM=",
1091 | "dev": true,
1092 | "requires": {
1093 | "inherits": "^2.0.1",
1094 | "readable-stream": "^2.0.2"
1095 | }
1096 | },
1097 | "string_decoder": {
1098 | "version": "1.1.1",
1099 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
1100 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
1101 | "dev": true,
1102 | "requires": {
1103 | "safe-buffer": "~5.1.0"
1104 | }
1105 | },
1106 | "subarg": {
1107 | "version": "1.0.0",
1108 | "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
1109 | "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=",
1110 | "dev": true,
1111 | "requires": {
1112 | "minimist": "^1.1.0"
1113 | }
1114 | },
1115 | "svg.js": {
1116 | "version": "2.6.4",
1117 | "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.6.4.tgz",
1118 | "integrity": "sha512-P+LLmhgekUtpCbyLZgf0wg2uft7IplifCi5STNEtVa6dMCY5MubLPvClViIsU9YQMZ7f+9Vm97ahxcg68PF1RA=="
1119 | },
1120 | "syntax-error": {
1121 | "version": "1.4.0",
1122 | "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz",
1123 | "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==",
1124 | "dev": true,
1125 | "requires": {
1126 | "acorn-node": "^1.2.0"
1127 | }
1128 | },
1129 | "through": {
1130 | "version": "2.3.8",
1131 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
1132 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
1133 | "dev": true
1134 | },
1135 | "through2": {
1136 | "version": "2.0.3",
1137 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
1138 | "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
1139 | "dev": true,
1140 | "requires": {
1141 | "readable-stream": "^2.1.5",
1142 | "xtend": "~4.0.1"
1143 | }
1144 | },
1145 | "timers-browserify": {
1146 | "version": "1.4.2",
1147 | "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz",
1148 | "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=",
1149 | "dev": true,
1150 | "requires": {
1151 | "process": "~0.11.0"
1152 | }
1153 | },
1154 | "to-arraybuffer": {
1155 | "version": "1.0.1",
1156 | "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
1157 | "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
1158 | "dev": true
1159 | },
1160 | "tty-browserify": {
1161 | "version": "0.0.1",
1162 | "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz",
1163 | "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==",
1164 | "dev": true
1165 | },
1166 | "typedarray": {
1167 | "version": "0.0.6",
1168 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
1169 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
1170 | "dev": true
1171 | },
1172 | "umd": {
1173 | "version": "3.0.3",
1174 | "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz",
1175 | "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==",
1176 | "dev": true
1177 | },
1178 | "undeclared-identifiers": {
1179 | "version": "1.1.2",
1180 | "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.2.tgz",
1181 | "integrity": "sha512-13EaeocO4edF/3JKime9rD7oB6QI8llAGhgn5fKOPyfkJbRb6NFv9pYV6dFEmpa4uRjKeBqLZP8GpuzqHlKDMQ==",
1182 | "dev": true,
1183 | "requires": {
1184 | "acorn-node": "^1.3.0",
1185 | "get-assigned-identifiers": "^1.2.0",
1186 | "simple-concat": "^1.0.0",
1187 | "xtend": "^4.0.1"
1188 | }
1189 | },
1190 | "url": {
1191 | "version": "0.11.0",
1192 | "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
1193 | "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
1194 | "dev": true,
1195 | "requires": {
1196 | "punycode": "1.3.2",
1197 | "querystring": "0.2.0"
1198 | },
1199 | "dependencies": {
1200 | "punycode": {
1201 | "version": "1.3.2",
1202 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
1203 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
1204 | "dev": true
1205 | }
1206 | }
1207 | },
1208 | "util": {
1209 | "version": "0.10.4",
1210 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
1211 | "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
1212 | "dev": true,
1213 | "requires": {
1214 | "inherits": "2.0.3"
1215 | }
1216 | },
1217 | "util-deprecate": {
1218 | "version": "1.0.2",
1219 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1220 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
1221 | "dev": true
1222 | },
1223 | "vm-browserify": {
1224 | "version": "1.1.0",
1225 | "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz",
1226 | "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==",
1227 | "dev": true
1228 | },
1229 | "wrappy": {
1230 | "version": "1.0.2",
1231 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1232 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
1233 | "dev": true
1234 | },
1235 | "xtend": {
1236 | "version": "4.0.1",
1237 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
1238 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
1239 | "dev": true
1240 | }
1241 | }
1242 | }
1243 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TKEM",
3 | "description": "Tree-based KEM",
4 | "version": "0.0.0",
5 | "dependencies": {
6 | "bn.js": "^4.11.8",
7 | "browserify": "^16.2.0",
8 | "elliptic": "^6.4.0",
9 | "svg.js": "^2.6.4"
10 | },
11 | "licenses": [
12 | {
13 | "type": "MIT",
14 | "url": "http://www.opensource.org/licenses/mit-license.php"
15 | }
16 | ],
17 | "scripts": {
18 | "watch": "watchify src/*.js -o dist/index.js -v",
19 | "build": "browserify src/main.js -o dist/index.js"
20 | },
21 | "devDependencies": {
22 | "browserify": "^16.2.2"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/art-state.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ART = require('./art').class;
4 | const iota = require('./iota');
5 |
6 | class ARTState {
7 | constructor() {
8 | this.art = null;
9 | }
10 |
11 | get index() {
12 | return this.art.index;
13 | }
14 |
15 | get size() {
16 | return this.art.size;
17 | }
18 |
19 | get nodes() {
20 | return this.art.nodes;
21 | }
22 |
23 | static fromJSON(obj) {
24 | let out = new ARTState();
25 | out.art = ART.fromJSON(obj.art);
26 | return out;
27 | }
28 |
29 | static async oneMemberGroup(leaf) {
30 | let state = new ARTState();
31 | state.art = await ART.oneMemberGroup(leaf);
32 | return state;
33 | }
34 |
35 | static async fromGroupAdd(initLeaf, groupAdd) {
36 | let initKP = await iota(initLeaf);
37 | let leaf = await DH.secret(initKP.privateKey, groupAdd.forJoiner.ephemeral);
38 |
39 | let state = new ARTState();
40 | state.art = await ART.fromFrontier(groupAdd.forJoiner.size, groupAdd.forJoiner.frontier, leaf);
41 | return state;
42 | }
43 |
44 | static async fromUserAdd(leaf, /* IGNORED */ userAdd, groupInitKey) {
45 | let state = new ARTState();
46 | state.art = await ART.fromFrontier(groupInitKey.size, groupInitKey.frontier, leaf);
47 | return state;
48 | }
49 |
50 | static async join(leaf, groupInitKey) {
51 | let art = await ART.fromFrontier(groupInitKey.size, groupInitKey.frontier, leaf);
52 | return {
53 | path: art.dirpath(art.size - 1),
54 | }
55 | }
56 |
57 | async add(userInitPub) {
58 | let kp = await DH.newKeyPair();
59 | let leaf = await DH.secret(kp.privateKey, userInitPub);
60 |
61 | let gik = this.groupInitKey;
62 | let ua = await ARTState.join(leaf, gik);
63 |
64 | return {
65 | forGroup: ua,
66 | forJoiner: {
67 | size: gik.size,
68 | frontier: gik.frontier,
69 | ephemeral: kp.publicKey,
70 | },
71 | };
72 | }
73 |
74 | async update(leaf) {
75 | let path = await this.art.updatePath(leaf);
76 | return { path: path };
77 | }
78 |
79 | async remove(index) {
80 | /* TODO */
81 | return {};
82 | }
83 |
84 | get groupInitKey() {
85 | return {
86 | size: this.art.size,
87 | frontier: this.art.frontier(),
88 | };
89 | }
90 |
91 | async handleUserAdd(ua) {
92 | this.art.size += 1;
93 | await this.art.merge(ua.path);
94 | }
95 |
96 | async handleGroupAdd(ga) {
97 | this.art.size += 1;
98 | await this.art.merge(ga.forGroup.path);
99 | }
100 |
101 | async handleSelfUpdate(/* IGNORED */ update, leaf) {
102 | await this.art.setOwnLeaf(leaf);
103 | }
104 |
105 | async handleUpdate(update) {
106 | await this.art.merge(update.path);
107 | }
108 |
109 | async handleRemove(remove) {
110 | /* TODO */
111 | }
112 | }
113 |
114 | module.exports = ARTState;
115 |
--------------------------------------------------------------------------------
/src/art.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const DH = require('./dh');
4 | const tm = require('./tree-math');
5 | const util = require('./util');
6 |
7 | class ART {
8 | /*
9 | * ART objects should not be constructed directly. Instead, use
10 | * the `ART.fromX` factory methods. This only exists to give
11 | * certain variables public exposure for debugging, and as a base
12 | * for the factory methods. It would be private in C++.
13 | */
14 | constructor() {
15 | this.size = 0;
16 | this.index = 0;
17 | this.nodes = [];
18 | }
19 |
20 | static fromJSON(obj) {
21 | let out = new ART();
22 | out.size = obj.size;
23 | out.index = obj.index;
24 | out.nodes = obj.nodes;
25 | return out;
26 | }
27 |
28 | /*
29 | * Construct an ART representing a group with a single member,
30 | * with the given leaf secret.
31 | */
32 | static async oneMemberGroup(leaf) {
33 | let art = new ART();
34 | art.size = 1;
35 | art.index = 0;
36 | await art.setOwnLeaf(leaf);
37 | return art;
38 | }
39 |
40 | /*
41 | * Construct a tree that extends a tree with the given size and
42 | * frontier by adding a member with the given leaf secret.
43 | */
44 | static async fromFrontier(size, frontier, leaf) {
45 | let art = new ART();
46 | art.size = size + 1;
47 | art.index = size;
48 | await art.merge(frontier);
49 | await art.setOwnLeaf(leaf)
50 | return art;
51 | }
52 |
53 | /*
54 | * Updates the leaf node corresponding to the holder of this tree
55 | */
56 | async setOwnLeaf(leaf) {
57 | let node = await util.newNode(leaf);
58 | let nodes = {};
59 | nodes[2 * this.index] = node;
60 | await this.merge(nodes);
61 | }
62 |
63 | /*
64 | * Updates nodes in the tree. This proceeds in two stages:
65 | * 1. Copy the nodes into the tree
66 | * 2. Update any nodes above the changed nodes
67 | *
68 | * Arguments:
69 | * nodes - Dictionary of nodes to udpate: { Int: Node }
70 | *
71 | * Returns: None
72 | */
73 | async merge(nodes) {
74 | let toUpdate = {};
75 | for (let n in nodes) {
76 | this.nodes[n] = nodes[n];
77 |
78 | const p = tm.parent(n, this.size);
79 | toUpdate[p] = true;
80 | }
81 |
82 | while (Object.keys(toUpdate).length > 0) {
83 | let nextToUpdate = {};
84 |
85 | for (let p in toUpdate) {
86 | let l = tm.left(p);
87 | let r = tm.right(p, this.size);
88 | if ((l == p) || (r == p)) {
89 | continue;
90 | }
91 |
92 | if (!this.nodes[l] || !this.nodes[r]) {
93 | continue;
94 | }
95 |
96 | let secret;
97 | if (this.nodes[l].private) {
98 | secret = await DH.secret(this.nodes[l].private, this.nodes[r].public);
99 | } else if (this.nodes[r].private) {
100 | secret = await DH.secret(this.nodes[r].private, this.nodes[l].public);
101 | } else {
102 | continue;
103 | }
104 |
105 | this.nodes[p] = await util.newNode(secret);
106 |
107 | // #ifdef COLORIZE
108 | this.nodes[p].color = util.colorAvg(this.nodes[l].color, this.nodes[r].color);
109 | // #endif /* def COLORIZE */
110 |
111 | const pp = tm.parent(p, this.size);
112 | if (pp != p) {
113 | nextToUpdate[pp] = true;
114 | }
115 | }
116 |
117 | toUpdate = nextToUpdate;
118 | }
119 | }
120 |
121 | /*
122 | * Returns the nodes along the dirpath from this tree's leaf to
123 | * the root.
124 | */
125 | dirpath() {
126 | return util.nodePath(this.nodes, tm.dirpath(2 * this.index, this.size));
127 | }
128 |
129 | /*
130 | * Returns the nodes on the frontier of the tree { Int: Node }
131 | */
132 | frontier() {
133 | return util.nodePath(this.nodes, tm.frontier(this.size));
134 | }
135 |
136 | /*
137 | * Returns the path to the root from our leaf, assuming we replace
138 | * our leaf with the provided leaf.
139 | */
140 | async updatePath(leaf) {
141 | let art = new ART();
142 | art.size = this.size;
143 | art.index = this.index;
144 | art.merge(this.nodes);
145 | await art.setOwnLeaf(leaf);
146 | return art.dirpath();
147 | }
148 | }
149 |
150 | module.exports = {
151 | class: ART,
152 | }
153 |
--------------------------------------------------------------------------------
/src/base64.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | stringify: function(a) {
5 | if (a instanceof ArrayBuffer) {
6 | a = new Uint8Array(a);
7 | }
8 |
9 | return btoa(String.fromCharCode.apply(0, a))
10 | .replace(/=/g, "")
11 | .replace(/\+/g, "-")
12 | .replace(/\//g, "_");
13 | },
14 |
15 | parse: function(s) {
16 | s = s.replace(/-/g, "+")
17 | .replace(/_/g, "/")
18 | .replace(/\s/g, '');
19 | let u = new Uint8Array(Array.prototype.map.call(atob(s), c => c.charCodeAt(0)));
20 | return u.buffer;
21 | },
22 |
23 | random: function(n) {
24 | const b = crypto.getRandomValues(new Uint8Array(n));
25 | return this.stringify(b);
26 | },
27 | };
28 |
--------------------------------------------------------------------------------
/src/blank.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const tm = require('./tree-math.js');
4 |
5 | function createNode(val, i) {
6 | let priv = val + 'x'.repeat(i);
7 | return {
8 | priv: priv,
9 | pub: priv.toUpperCase(),
10 | };
11 | }
12 |
13 | function formatNode(node) {
14 | return `(${node.priv}, ${node.pub})`;
15 | }
16 |
17 | // Have to extend Array with a non-enumerable property so that
18 | // for-in / for-of loops work properly
19 | Object.defineProperty(Array.prototype, 'remove', {
20 | enumerable: false,
21 | value: function(val) {
22 | if (this.includes(val)) {
23 | this.splice(this.indexOf(val), 1);
24 | }
25 | },
26 | });
27 |
28 | Object.defineProperty(Array.prototype, 'addUnique', {
29 | enumerable: false,
30 | value: function(val) {
31 | if (!this.includes(val)) {
32 | this.push(val);
33 | }
34 | },
35 | });
36 |
37 | class Tree {
38 | constructor(size) {
39 | this.size = size || 0;
40 |
41 | this.nodes = [];
42 | while (this.nodes.length < tm.nodeWidth(this.size)) {
43 | this.nodes.push(null);
44 | }
45 |
46 | this.known = [];
47 | while (this.known.length < this.size) {
48 | this.known.push([]);
49 | }
50 | }
51 |
52 | hasPriv(i, priv) {
53 | return this.known[i].filter(x => priv.startsWith(x)).length > 0;
54 | }
55 |
56 | dump() {
57 | console.log("=====");
58 | console.log("Size:", this.size);
59 | console.log("Nodes:");
60 | for (let i=0; i this.nodes[n]);
108 |
109 | dirpath.map((n, i) => {
110 | this.nodes[n] = createNode(val, i);
111 | if (i == 0) {
112 | return
113 | }
114 |
115 | let L = tm.left(n), R = tm.right(n, this.size);
116 | let child = (dirpath.includes(L))? R : L;
117 | let newPriv = this.nodes[n].priv;
118 | let oldPriv = (old[i])? old[i].priv : null;
119 | this.send(child, newPriv, oldPriv);
120 | });
121 |
122 | let oldPriv = (old[0])? old[0].priv : null;
123 | if (!oldPriv) {
124 | this.known[k].push(val);
125 | } else {
126 | this.sendPriv(oldPriv, val, oldPriv);
127 | }
128 | }
129 |
130 | unset(k, val) {
131 | let dirpath = tm.dirpath(2*k, this.size);
132 | for (let n of dirpath) {
133 | delete this.nodes[n];
134 | }
135 |
136 | if (val) {
137 | let r = tm.root(this.size);
138 | let oldPriv = this.nodes[r].priv;
139 | this.nodes[r] = createNode(val, 0);
140 | this.send(r, val, oldPriv);
141 | }
142 | }
143 |
144 | move(src, dst, val) {
145 | this.unset(src);
146 | this.set(dst, val);
147 | }
148 |
149 | // A node's private key is held by a leaf iff :
150 | // * the leaf is in the shadow of the node, and
151 | // * the leaf is not blank
152 | verify() {
153 | for (let n in this.nodes) {
154 | if (!this.nodes[n]) {
155 | continue;
156 | }
157 |
158 | n = parseInt(n);
159 | let priv = this.nodes[n].priv;
160 | let shadow = tm.shadow(n, this.size)
161 | .filter(x => !(x & 1))
162 | .filter(x => !!this.nodes[x])
163 | .map(x => x/2);
164 | let hasPriv = [...Array(this.size).keys()].filter(i => this.hasPriv(i, priv));
165 |
166 | let shadowSet = {};
167 | for (let x of shadow) {
168 | shadowSet[x] = true;
169 | }
170 | for (let x of hasPriv) {
171 | if (!shadowSet[x]) {
172 | console.log(priv, ':', shadow, '!=', hasPriv);
173 | return false;
174 | }
175 | }
176 | }
177 |
178 | return true;
179 | }
180 | }
181 |
182 | function fill(tree) {
183 | for (let i = 0; i < tree.size; i += 1) {
184 | let val = String.fromCharCode('a'.charCodeAt(0) + i);
185 | tree.set(i, val);
186 | }
187 | return tree;
188 | }
189 |
190 | function testSet() {
191 | let tree = new Tree(7);
192 | fill(tree);
193 | console.log('[set]', tree.verify());
194 | }
195 |
196 | function testUnset() {
197 | let tree = new Tree(7);
198 | fill(tree);
199 |
200 | tree.unset(3, 'r');
201 | tree.unset(2, 's');
202 | tree.unset(1, 't');
203 |
204 | console.log('[unset]', tree.verify());
205 | }
206 |
207 | function testReset() {
208 | let tree = new Tree(7);
209 | fill(tree);
210 |
211 | tree.unset(2, 'h');
212 | tree.set(0, 'i');
213 | tree.set(6, 'j');
214 |
215 | console.log('[reset]', tree.verify());
216 | }
217 |
218 | function testMove() {
219 | let tree = new Tree(7);
220 | fill(tree);
221 |
222 | tree.unset(3, 'r');
223 | tree.unset(2, 's');
224 | tree.unset(1, 't');
225 |
226 | tree.move(4, 1, 'h');
227 | tree.move(5, 2, 'i');
228 | tree.move(6, 3, 'j');
229 |
230 | console.log('[move]', tree.verify());
231 | }
232 |
233 | function testChaos() {
234 | let rounds = 200;
235 | let branches = 3;
236 | let size = 63;
237 |
238 | let tree = new Tree(size);
239 | let all = [...Array(size).keys()];
240 | let unset = [...Array(size).keys()];
241 | let set = [];
242 | let rand = (arr) => arr[Math.floor(Math.random() * arr.length)];
243 | let move = (val, src, dst) => {
244 | src.remove(val);
245 | dst.addUnique(val);
246 | };
247 |
248 | let base = 'a'.charCodeAt(0);
249 | let val = 0;
250 | let next = () => String.fromCharCode(base + val++);
251 |
252 | let doSet = () => {
253 | if (unset.length == 0) {
254 | return true;
255 | }
256 |
257 | let i = rand(all);
258 | tree.set(i, next());
259 | move(i, unset, set);
260 | return false;
261 | };
262 |
263 | let doUnset = () => {
264 | if (set.length == 0) {
265 | return true;
266 | }
267 |
268 | let i = rand(set);
269 | tree.unset(i, next());
270 | move(i, set, unset);
271 | return false;
272 | };
273 |
274 | let doMove = () => {
275 | if (set.length == 0 || unset.length == 0) {
276 | return true;
277 | }
278 |
279 | let src = rand(set);
280 | let dst = rand(unset);
281 | tree.move(src, dst, next());
282 | move(src, set, unset);
283 | move(dst, unset, set);
284 | return false;
285 | };
286 |
287 | let start = Date.now();
288 |
289 | // Initialize the tree about half-full
290 | while (set.length / tree.size < 0.5) {
291 | doSet();
292 | }
293 |
294 | for (let i = 0; i < rounds; ++i) {
295 | let roll = Math.floor(Math.random() * branches);
296 |
297 | let reroll = true;
298 | switch (roll) {
299 | case 0: reroll = doSet(); break;
300 | case 1: reroll = doUnset(); break;
301 | case 2: reroll = doMove(); break;
302 | }
303 |
304 | if (!tree.verify()) {
305 | console.log('[chaos] fail');
306 | return;
307 | }
308 |
309 | if (reroll) {
310 | i--;
311 | }
312 | }
313 |
314 | let finish = Date.now();
315 | let elapsed = (finish - start) / 1000;
316 | console.log(`[chaos] pass in ${elapsed} sec`);
317 | }
318 |
319 | module.exports = {
320 | Tree: Tree,
321 |
322 | // node -e "require('./blank').test()"
323 | test: function test() {
324 | testSet();
325 | testUnset();
326 | testReset();
327 | testMove();
328 | testChaos();
329 | },
330 | };
331 |
332 |
333 |
334 |
335 |
--------------------------------------------------------------------------------
/src/dh.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const base64 = require('./base64');
4 | const cs = window.crypto.subtle;
5 |
6 | const ECDH_KEY_USAGES = ["deriveBits", "deriveKey"];
7 | const ECDH_GEN = { name: "ECDH", namedCurve: "P-256" };
8 | const SECRET_BITS = 256;
9 |
10 | async function _import(jwk) {
11 | // XXX(rlb@ipv.sx): Firefox appears not to set this properly on export
12 | jwk.key_ops = ECDH_KEY_USAGES;
13 | return await cs.importKey("jwk", jwk, ECDH_GEN, true, ["deriveBits"]);
14 | }
15 |
16 | async function _export(pub) {
17 | return cs.exportKey("jwk", pub)
18 | }
19 |
20 | /*
21 | * Arguments: None
22 | *
23 | * Returns: Promise resolving to:
24 | * {
25 | * privateKey: CryptoKey,
26 | * publicKey: CryptoKey,
27 | * }
28 | */
29 | async function newKeyPair() {
30 | const kp = await cs.generateKey(ECDH_GEN, true, ECDH_KEY_USAGES);
31 | return {
32 | privateKey: await _export(kp.privateKey),
33 | publicKey: await _export(kp.publicKey),
34 | };
35 | }
36 |
37 | /*
38 | * Arguments:
39 | * priv: CryptoKey representing a DH private key
40 | * pub: CryptoKey representing a DH public key
41 | *
42 | * Returns: Promise resolving to ArrayBuffer
43 | */
44 | async function secret(privJWK, pubJWK) {
45 | const priv = await _import(privJWK);
46 | const pub = await _import(pubJWK);
47 | const alg = {
48 | name: "ECDH",
49 | namedCurve: "P-256",
50 | public: pub,
51 | };
52 |
53 | const ss = await cs.deriveBits(alg, priv, SECRET_BITS);
54 | return base64.stringify(ss);
55 | }
56 |
57 | /*
58 | * Arguments:
59 | * pub: CryptoKey representing a DH public key
60 | *
61 | * Returns: Promise resolving to a string with the hex SHA-256 hash
62 | * of the SPKI representation of the public key.
63 | */
64 | async function fingerprint(pubJWK) {
65 | const pub = await _import(pubJWK);
66 | const spki = await cs.exportKey("spki", pub);
67 | const digest = await cs.digest("SHA-256", spki);
68 | return base64.stringify(digest);
69 | }
70 |
71 | /*
72 | * Self-test: DH exchange
73 | */
74 | async function test() {
75 | const base64 = require("./base64");
76 |
77 | try {
78 | console.log("newKP...");
79 | const kpA = await newKeyPair();
80 | const kpB = await newKeyPair();
81 |
82 | console.log("secret...");
83 | const ssAB = await secret(kpA.privateKey, kpB.publicKey);
84 | const ssBA = await secret(kpB.privateKey, kpA.publicKey);
85 |
86 | console.log("[DH]", (ssAB == ssBA)? "PASS" : "FAIL");
87 | } catch (err) {
88 | console.log("[DH] FAIL:", err);
89 | }
90 | }
91 |
92 | module.exports = {
93 | newKeyPair: newKeyPair,
94 | secret: secret,
95 | fingerprint: fingerprint,
96 | test: test,
97 | }
98 |
--------------------------------------------------------------------------------
/src/eckem.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const cs = window.crypto.subtle;
4 | const DH = require('./dh');
5 | const base64 = require('./base64');
6 |
7 | function AES_GCM_ENC(iv) {
8 | return {
9 | name: "AES-GCM",
10 | iv: iv || crypto.getRandomValues(new Uint8Array(12)),
11 | };
12 | }
13 |
14 | /*
15 | * Arguments:
16 | * plaintext: The value to be encrypted, as a BufferSource
17 | * pub: Public key for the receiver
18 | *
19 | * Returns: Promise resolving to an ECKEMCiphertext object:
20 | * {
21 | * pub: CryptoKey
22 | * iv: BufferSource
23 | * ct: BufferSource
24 | * }
25 | */
26 | async function encrypt(pt64, pubA) {
27 | const pt = base64.parse(pt64);
28 | const kpE = await DH.newKeyPair();
29 | const ekData64 = await DH.secret(kpE.privateKey, pubA);
30 | const ekData = base64.parse(ekData64);
31 | const ek = await cs.importKey("raw", ekData, "AES-GCM", false, ['encrypt']);
32 |
33 | const iv = window.crypto.getRandomValues(new Uint8Array(12))
34 | const alg = { name: "AES-GCM", iv: iv };
35 | const ct = await cs.encrypt(alg, ek, pt);
36 |
37 | return {
38 | pub: kpE.publicKey,
39 | iv: base64.stringify(iv),
40 | ct: base64.stringify(ct),
41 | };
42 | }
43 |
44 | /*
45 | * Arguments:
46 | * ciphertext: The value to be decrypted, as an object
47 | * priv: Private key for the receiver
48 | *
49 | * Returns: Promise
50 | */
51 | async function decrypt(ciphertext, priv) {
52 | const iv = base64.parse(ciphertext.iv);
53 | const ct = base64.parse(ciphertext.ct);
54 | const alg = { name: "AES-GCM", iv: iv };
55 |
56 | const ekData64 = await DH.secret(priv, ciphertext.pub);
57 | const ekData = base64.parse(ekData64);
58 | const ek = await cs.importKey("raw", ekData, "AES-GCM", false, ['decrypt']);
59 | const pt = await cs.decrypt(alg, ek, ct);
60 | return base64.stringify(pt);
61 | }
62 |
63 | /*
64 | * Self-test: Encrypt/decrypt round trip
65 | */
66 | async function test() {
67 | const original = new Uint8Array([0,1,2,3]);
68 | const kp = await DH.newKeyPair();
69 |
70 | try {
71 | const encrypted = await encrypt(original, kp.publicKey);
72 | const decrypted = await decrypt(encrypted, kp.privateKey);
73 | const equal = (Array.from(decrypted).filter((x,i) => (original[i] != x)).length == 0)
74 | console.log("[ECKEM]", equal? "PASS" : "FAIL");
75 | } catch (err) {
76 | console.log("[ECKEM] FAIL:", err);
77 | }
78 | }
79 |
80 | module.exports = {
81 | encrypt: encrypt,
82 | decrypt: decrypt,
83 | test: test,
84 | }
85 |
--------------------------------------------------------------------------------
/src/flat-state.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const DH = require('./dh');
4 | const iota = require('./iota');
5 | const util = require('./util');
6 | const cs = window.crypto.subtle;
7 |
8 | class FlatState {
9 | constructor() {
10 | this.index = 0;
11 | this.size = 1;
12 | this.nodes = [];
13 | }
14 |
15 | async _setOwnNode(leaf) {
16 | this.nodes[2 * this.index] = await util.newNode(leaf)
17 | }
18 |
19 | static async oneMemberGroup(leaf) {
20 | let state = new FlatState();
21 | await state._setOwnNode(leaf);
22 | return state;
23 | }
24 |
25 | static async fromGroupAdd(initLeaf, groupAdd) {
26 | let state = new FlatState();
27 | state.size = groupAdd.forJoiner.size + 1;
28 | state.index = groupAdd.forJoiner.size;
29 | state.nodes = groupAdd.forJoiner.nodes;
30 | await state._setOwnNode(initLeaf);
31 | return state;
32 | }
33 |
34 | static async fromUserAdd(leaf, /* IGNORED */ userAdd, groupInitKey) {
35 | let state = new FlatState();
36 | state.size = groupInitKey.size + 1;
37 | state.index = groupInitKey.size;
38 | state.nodes = groupInitKey.nodes;
39 | await state._setOwnNode(leaf);
40 | return state;
41 | }
42 |
43 | static async join(leaf, groupInitKey) {
44 | let kp = await iota(leaf);
45 | return kp.publicKey;
46 | }
47 |
48 | async add(userInitPub) {
49 | return {
50 | forJoiner: this.groupInitKey,
51 | forGroup: userInitPub,
52 | };
53 | }
54 |
55 | async update(leaf) {
56 | let kp = await iota(leaf);
57 | return {
58 | from: this.index,
59 | public: kp.publicKey,
60 | };
61 | }
62 |
63 | async remove(index) {
64 | /* TODO */
65 | return {};
66 | }
67 |
68 | get groupInitKey() {
69 | let publicNodes = {};
70 | for (let n in this.nodes) {
71 | publicNodes[n] = util.publicNode(this.nodes[n]);
72 | }
73 |
74 | return {
75 | size: this.size,
76 | nodes: publicNodes,
77 | };
78 | }
79 |
80 | async handleUserAdd(ua) {
81 | this.nodes[2 * this.size] = { public: ua };
82 | this.size += 1;
83 | }
84 |
85 | async handleGroupAdd(ga) {
86 | this.nodes[2 * this.size] = { public: ga.forGroup };
87 | this.size += 1;
88 | }
89 |
90 | async handleSelfUpdate(/* IGNORED */ update, leaf) {
91 | await this._setOwnNode(leaf);
92 | }
93 |
94 | async handleUpdate(update) {
95 | this.nodes[2 * update.from].public = update.public;
96 | }
97 |
98 | async handleRemove(remove) {
99 | /* TODO */
100 | }
101 | }
102 |
103 | module.exports = FlatState;
104 |
--------------------------------------------------------------------------------
/src/iota.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const cs = window.crypto.subtle;
4 | const EC = require('elliptic').ec;
5 | const BN = require('bn.js');
6 | const base64 = require('./base64');
7 |
8 | const p256 = new EC('p256');
9 |
10 | const ENDIAN = 'be';
11 | const INTLEN = 32;
12 |
13 | function bn2b64(n) {
14 | const bytes = n.toArray(ENDIAN, INTLEN);
15 | let base64 = (new Buffer(bytes)).toString('base64');
16 | return base64.replace(/=/g, '')
17 | .replace(/\+/g, '-')
18 | .replace(/\//g, '_');
19 | }
20 |
21 | async function iota(secret64) {
22 | // Digest the input
23 | const secret = base64.parse(secret64);
24 | const digest = await cs.digest("SHA-256", secret);
25 |
26 | // Convert it to an integer and compute the resulting key pair
27 | const arr = Array.from(new Uint8Array(digest));
28 | const hex = arr.map(x => ('0' + x.toString(16)).slice(-2)).join("");
29 | const bnD = new BN(hex, 16);
30 | const keyPair = p256.keyFromPrivate(bnD);
31 | keyPair.getPublic();
32 |
33 | // Build JWKs
34 | const d = bn2b64(bnD);
35 | const x = bn2b64(keyPair.pub.x.fromRed());
36 | const y = bn2b64(keyPair.pub.y.fromRed());
37 | const privJWK = {kty: "EC", crv: "P-256", x: x, y: y, d: d};
38 | const pubJWK = {kty: "EC", crv: "P-256", x: x, y: y};
39 |
40 | const alg = {
41 | name: "ECDH",
42 | namedCurve: "P-256",
43 | };
44 | return {
45 | privateKey: privJWK,
46 | publicKey: pubJWK,
47 | }
48 | }
49 |
50 | module.exports = iota;
51 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | window.base64 = require('./base64');
2 | window.DH = require('./dh');
3 | window.ECKEM = require('./eckem');
4 | window.iota = require('./iota');
5 | window.tm = require('./tree-math');
6 |
7 | window.ART = require('./art');
8 | window.TKEM = require('./treekem');
9 |
10 | window.Tree = require('./tree');
11 | window.FlatState = require('./flat-state');
12 | window.TreeKEMState = require('./treekem-state');
13 | window.ARTState = require('./art-state');
14 | window.Renderer = require('./renderer');
15 | window.StateTester = require('./state-tester');
16 |
--------------------------------------------------------------------------------
/src/renderer.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const tm = require('./tree-math');
4 | const util = require('./util');
5 | const SVG = require('svg.js');
6 |
7 | const PAD = 1;
8 | const RECTRAD = 5;
9 | const RECTSPACE = 25;
10 | const STROKEWIDTH = 2;
11 | const RECTSIZE = 2 * RECTRAD;
12 |
13 | const DEFAULTSTROKE = "#eee";
14 | const DEFAULTFILL = "#fff";
15 |
16 | function center(n, height) {
17 | const h = tm.level(n);
18 | return {
19 | x: n * RECTSPACE + RECTRAD + PAD,
20 | y: (height - h) * RECTSPACE + RECTRAD + PAD,
21 | };
22 | }
23 |
24 | function resize(svg, width, height) {
25 | let w = (width-1) * RECTSPACE + RECTSIZE + 2 * PAD;
26 | let h = height * RECTSPACE + RECTSIZE + 2 * PAD;
27 | svg.size(w, h);
28 | }
29 |
30 | function hsl(vals) {
31 | let [h, s, l] = vals;
32 | return `hsl(${h}, ${s}%, ${l}%)`;
33 | }
34 |
35 | class Renderer {
36 | constructor(id) {
37 | this.svg = SVG(id);
38 | resize(this.svg, 0, 0);
39 | this.lineGroup = this.svg.group();
40 | this.rectGroup = this.svg.group();
41 | this.lines = [];
42 | this.rects = [];
43 | }
44 |
45 | async render(size, nodes) {
46 | const root = tm.root(size);
47 | const height = tm.level(root);
48 | const width = tm.nodeWidth(size);
49 |
50 | let index = [...Array(width).keys()];
51 | let nc = index.map(k => center(k, height));
52 | let np = index.map(k => tm.parent(k, size))
53 | let pc = index.map(k => nc[np[k]]);
54 |
55 | // Add rectangles if needed
56 | resize(this.svg, width, height);
57 | while (this.rects.length < width) {
58 | let k = this.rects.length;
59 |
60 | let line = this.lineGroup.line(nc[k].x, nc[k].y, pc[k].x, pc[k].y)
61 | .stroke({ width: STROKEWIDTH });
62 |
63 | let rect = this.rectGroup.rect(RECTSIZE, RECTSIZE)
64 | .cx(nc[k].x).cy(nc[k].y)
65 | .stroke({ width: STROKEWIDTH });
66 |
67 | this.lines.push(line);
68 | this.rects.push(rect);
69 | }
70 |
71 | // Move everything to the right position
72 | nc.map((c, k) => {
73 | this.rects[k].cx(nc[k].x).cy(nc[k].y);
74 | this.lines[k].plot(nc[k].x, nc[k].y, pc[k].x, pc[k].y);
75 | });
76 |
77 | // Apply colors
78 | let stroke = await Promise.all(index.map(async k => {
79 | return (!nodes[k])? DEFAULTSTROKE
80 | : (nodes[k].color)? hsl(nodes[k].color)
81 | : hsl(await util.keyColor(nodes[k].public));
82 | }));
83 |
84 | let fill = index.map(k => {
85 | let rectStroke = (!nodes[np[k]])? DEFAULTSTROKE : stroke[k];
86 | return (nodes[k] && nodes[k].private)? stroke[k] : DEFAULTFILL;
87 | });
88 |
89 | this.lines.map((line, k) => { line.stroke((!nodes[np[k]])? DEFAULTSTROKE : stroke[k]); });
90 | this.rects.map((rect, k) => { rect.fill(fill[k]).stroke(stroke[k]); });
91 | }
92 | }
93 |
94 | module.exports = Renderer;
95 |
--------------------------------------------------------------------------------
/src/state-tester.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const iota = require('./iota');
4 | const DH = require('./dh');
5 | const base64 = require('./base64');
6 |
7 | async function nodeEqual(lhs, rhs) {
8 | let lfp = await DH.fingerprint(lhs.public);
9 | let rfp = await DH.fingerprint(rhs.public);
10 | return (lfp == rfp);
11 | }
12 |
13 | /*
14 | * Compares to group structs for equality. Each one must have
15 | * `size` and `nodes` attributes. Structs are equal if they have
16 | * the same size and agree on overlapping nodes.
17 | */
18 | async function groupEqual(lhs, rhs) {
19 | let answer = (lhs.size == rhs.size);
20 |
21 | for (let n in lhs.nodes) {
22 | let lhn = lhs.nodes[n];
23 | let rhn = rhs.nodes[n];
24 | if (!lhn || !rhn) {
25 | continue;
26 | }
27 |
28 | let eq = await nodeEqual(lhn, rhn);
29 | answer = answer && eq;
30 | }
31 |
32 | return answer;
33 | }
34 |
35 | /*
36 | * Dumps a human-readable representation of a group struct.
37 | */
38 | async function groupDump(label, group) {
39 | console.log("=====", label, "=====");
40 | console.log("size:", group.size);
41 | console.log("index:", group.index);
42 | console.log("nodes:");
43 | for (let n in group.nodes) {
44 | if (!group.nodes[n]) {
45 | continue;
46 | }
47 |
48 | console.log(" ", n, ":", await DH.fingerprint(group.nodes[n].public));
49 | }
50 | }
51 |
52 |
53 | async function testUserAdd(State) {
54 | const testGroupSize = 5;
55 |
56 | let creator = await State.oneMemberGroup(base64.random(32));
57 | let members = [creator];
58 |
59 | for (let i = 1; i < testGroupSize; ++i) {
60 | let leaf = base64.random(32);
61 | let gik = members[members.length - 1].groupInitKey;
62 | let uaIn = await State.join(leaf, gik);
63 |
64 | let uaEnc = JSON.stringify(uaIn);
65 | let ua = JSON.parse(uaEnc);
66 |
67 | let joiner = await State.fromUserAdd(leaf, ua, gik);
68 |
69 | for (let m of members) {
70 | await m.handleUserAdd(ua);
71 |
72 | let eq = await groupEqual(joiner, m);
73 | if (!eq) {
74 | await groupDump(joiner.index, joiner);
75 | await groupDump(m.index, m);
76 | throw 'state-user-add';
77 | }
78 | }
79 |
80 | members.push(joiner);
81 | }
82 |
83 | console.log("[state-user-add] PASS");
84 | }
85 |
86 | async function testGroupAdd(State) {
87 | const testGroupSize = 5;
88 |
89 | let creator = await State.oneMemberGroup(base64.random(32));
90 | let members = [creator];
91 |
92 | for (let i = 1; i < testGroupSize; ++i) {
93 | let initLeaf = base64.random(32);
94 | let initKP = await iota(initLeaf)
95 | let gaIn = await members[members.length - 1].add(initKP.publicKey)
96 |
97 | let gaEnc = JSON.stringify(gaIn);
98 | let ga = JSON.parse(gaEnc);
99 |
100 | let joiner = await State.fromGroupAdd(initLeaf, ga);
101 |
102 | for (let m of members) {
103 | await m.handleGroupAdd(ga);
104 |
105 | let eq = await groupEqual(joiner, m);
106 | if (!eq) {
107 | await groupDump(joiner.index, joiner);
108 | await groupDump(m.index, m);
109 | throw 'state-group-add';
110 | }
111 | }
112 |
113 | members.push(joiner);
114 | }
115 |
116 | console.log("[state-group-add] PASS");
117 | }
118 |
119 | async function testUpdate(State, transcode) {
120 | let label = (transcode)? 'state-json' : 'state-update';
121 |
122 | // Create a group via GroupAdds
123 | const testGroupSize = 5;
124 | let creator = await State.oneMemberGroup(base64.random(32));
125 | let members = [creator];
126 | for (let i = 1; i < testGroupSize; ++i) {
127 | let initLeaf = base64.random(32);
128 | let initKP = await iota(initLeaf);
129 | let ga = await members[members.length - 1].add(initKP.publicKey)
130 |
131 | for (let m of members) {
132 | await m.handleGroupAdd(ga);
133 | }
134 |
135 | let joiner = await State.fromGroupAdd(initLeaf, ga);
136 | members.push(joiner);
137 | }
138 |
139 | if (transcode) {
140 | let encoded = members.map(m => JSON.stringify(m));
141 | let decoded = encoded.map(e => JSON.parse(e));
142 | let revived = decoded.map(d => State.fromJSON(d));
143 |
144 | let eqp = members.map(async (m, i) => groupEqual(m, revived[i]));
145 | let eqr = await Promise.all(eqp);
146 | let eq = eqr.reduce((x, y) => x && y);
147 | if (!eq) {
148 | throw label;
149 | }
150 | }
151 |
152 | // Have each member update and verify that others are consistent
153 | for (let m1 of members) {
154 | let leaf = base64.random(32);
155 | let updateIn = await m1.update(leaf);
156 |
157 | let updateEnc = JSON.stringify(updateIn);
158 | let update = JSON.parse(updateEnc);
159 |
160 | await m1.handleSelfUpdate(update, leaf);
161 |
162 | for (let m2 of members) {
163 | if (m2.index == m1.index) {
164 | continue
165 | }
166 |
167 | await m2.handleUpdate(update);
168 |
169 | let eq = await groupEqual(m1, m2);
170 | if (!eq) {
171 | await groupDump(m1.index, m1);
172 | await groupDump(m2.index, m2);
173 | throw label;
174 | }
175 | }
176 | }
177 |
178 | console.log(`[${label}] PASS`);
179 | }
180 |
181 | async function testRemove(State) {
182 | // Create a group via GroupAdds
183 | const testGroupSize = 5;
184 | let creator = await State.oneMemberGroup(base64.random(32));
185 | let members = [creator];
186 | for (let i = 1; i < testGroupSize; ++i) {
187 | let initLeaf = base64.random(32);
188 | let initKP = await iota(initLeaf);
189 | let ga = await members[members.length - 1].add(initKP.publicKey)
190 |
191 | for (let m of members) {
192 | await m.handleGroupAdd(ga);
193 | }
194 |
195 | let joiner = await State.fromGroupAdd(initLeaf, ga);
196 | members.push(joiner);
197 | }
198 |
199 | // Have the first member remove two members
200 | let remover = members[0];
201 | let removed = [2, 3];
202 | for (let index of removed) {
203 | let leaf = base64.random(32);
204 | let removeIn = await remover.remove(leaf, index);
205 | let removeEnc = JSON.stringify(removeIn);
206 | let remove = JSON.parse(removeEnc);
207 |
208 | members = members.filter(x => x.index != index);
209 |
210 | for (let m of members) {
211 | await m.handleRemove(remove);
212 | }
213 |
214 | for (let m of members) {
215 | let eq = await groupEqual(m, remover);
216 | if (!eq) {
217 | await groupDump(remover.index, remover);
218 | await groupDump(m.index, m);
219 | throw 'state-remove';
220 | }
221 | }
222 | }
223 |
224 | // Have each remaining member update and verify that others are consistent
225 | for (let m1 of members) {
226 | let leaf = base64.random(32);
227 | let updateIn = await m1.update(leaf);
228 | let updateEnc = JSON.stringify(updateIn);
229 | let update = JSON.parse(updateEnc);
230 |
231 | await m1.handleSelfUpdate(update, leaf);
232 |
233 | for (let m2 of members) {
234 | if (m2.index == m1.index) {
235 | continue
236 | }
237 |
238 | await m2.handleUpdate(update);
239 |
240 | let eq = await groupEqual(m1, m2);
241 | if (!eq) {
242 | await groupDump(m1.index, m1);
243 | await groupDump(m2.index, m2);
244 | throw 'state-remove';
245 | }
246 | }
247 | }
248 |
249 | console.log('[state-remove] PASS');
250 | }
251 |
252 | module.exports = {
253 | test: async function(State) {
254 | await testUserAdd(State);
255 | await testGroupAdd(State);
256 | await testUpdate(State, false);
257 | await testUpdate(State, true);
258 | await testRemove(State);
259 | },
260 | };
261 |
--------------------------------------------------------------------------------
/src/tree-math.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function log2(x) {
4 | if (x == 0) {
5 | return 0;
6 | }
7 |
8 | return Math.floor(Math.log2(x));
9 | }
10 |
11 | function level(x) {
12 | if ((x & 0x01) == 0) {
13 | return 0;
14 | }
15 |
16 | let k = 0;
17 | while (((x >> k) & 0x01) == 1) {
18 | k += 1;
19 | }
20 | return k;
21 | }
22 |
23 | function nodeWidth(n) {
24 | return 2 * (n - 1) + 1;
25 | }
26 |
27 | function assert(test) {
28 | if (!test) {
29 | console.trace();
30 | throw "assertion failure";
31 | }
32 | }
33 |
34 | function assertInRange(x, n) {
35 | if (x > nodeWidth(n)) {
36 | throw `node index out of range (${x} > ${n})`;
37 | }
38 | }
39 |
40 | function root(n) {
41 | let w = nodeWidth(n);
42 | return (1 << log2(w)) - 1;
43 | }
44 |
45 | function left(x) {
46 | if (level(x) == 0) {
47 | return x;
48 | }
49 |
50 | return x ^ (0x01 << (level(x) - 1));
51 | }
52 |
53 | function right(x, n) {
54 | assertInRange(x, n);
55 |
56 | if (level(x) == 0) {
57 | return x;
58 | }
59 |
60 | let r = x ^ (0x03 << (level(x) - 1));
61 | while (r >= nodeWidth(n)) {
62 | r = left(r);
63 | }
64 | return r;
65 | }
66 |
67 | function parentStep(x) {
68 | const k = level(x);
69 | return (x | (1 << k)) & ~(1 << (k + 1));
70 | }
71 |
72 | function parent(x, n) {
73 | assertInRange(x, n);
74 |
75 | if (x == root(n)) {
76 | return x;
77 | }
78 |
79 | let p = parentStep(x);
80 | while (p >= nodeWidth(n)) {
81 | p = parentStep(p);
82 | }
83 | return p;
84 | }
85 |
86 | function sibling(x, n) {
87 | assertInRange(x, n);
88 |
89 | const p = parent(x, n);
90 | if (x < p) {
91 | return right(p, n);
92 | } else if (x > p) {
93 | return left(p);
94 | }
95 |
96 | // root's sibling is itself
97 | return p;
98 | }
99 |
100 | // Ordered from leaf to root
101 | // Includes leaf, but not root
102 | function dirpath(x, n) {
103 | assertInRange(x, n);
104 |
105 | if (x == root(n)) {
106 | return [];
107 | }
108 |
109 | let d = [x];
110 | let p = parent(x, n);
111 | let r = root(n);
112 | while (p != r) {
113 | d.push(p);
114 | p = parent(p, n);
115 | }
116 | return d;
117 | }
118 |
119 | // Ordered from leaf to root
120 | function copath(x, n) {
121 | return dirpath(x, n).map(x => sibling(x, n));
122 | }
123 |
124 | // Ordered from left to right
125 | function frontier(n) {
126 | assert(n > 0);
127 |
128 | let last = 2*(n-1);
129 | let f = copath(last, n).reverse();
130 |
131 | if (f[f.length - 1] != last) {
132 | f.push(last);
133 | }
134 |
135 | while (f.length > 1) {
136 | let r = f[f.length - 1];
137 | let p = parent(r, n);
138 | if (p != parentStep(r)) {
139 | break;
140 | }
141 |
142 | // Replace the last two nodes with their parent
143 | f = f.slice(0, -2).concat(p);
144 | }
145 |
146 | return f;
147 | }
148 |
149 | function shadow(x, n) {
150 | let h = level(x);
151 | let L = x;
152 | let R = x;
153 | while (h > 0) {
154 | L = left(L);
155 | R = right(R, n);
156 | h -= 1;
157 | }
158 |
159 | return [...Array(R - L + 1).keys()].map(x => x + L);
160 | }
161 |
162 | function leaves(n) {
163 | return [...Array(n).keys()].map(x => 2*x);
164 | }
165 |
166 | /////
167 |
168 | // Precomputed answers for the tree on eleven elements:
169 | //
170 | // X
171 | // X
172 | // X X X
173 | // X X X X X
174 | // X X X X X X X X X X X
175 | // 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14
176 |
177 | function arrayEqual(a, b) {
178 | return (a.length == b.length && a.filter((x, i) => (b[i] != x)).length == 0);
179 | }
180 |
181 | function testRoot() {
182 | const n = 0x0b;
183 | const index = [...Array(n).keys()];
184 | const aRoot = [ 0x00, 0x01, 0x03, 0x03, 0x07, 0x07,
185 | 0x07, 0x07, 0x0f, 0x0f, 0x0f ];
186 |
187 | let q = [...Array(n).keys()].map(x => root(x+1));
188 | if (!arrayEqual(q, aRoot)) {
189 | console.log("root", q, aRoot);
190 | throw "root";
191 | }
192 |
193 | console.log("[tree-root] PASS");
194 | }
195 |
196 | function testRelations() {
197 | const n = 0x0b;
198 |
199 | const index = [...Array(nodeWidth(n)).keys()];
200 |
201 | const aLeft = [ 0x00, 0x00, 0x02, 0x01, 0x04, 0x04, 0x06,
202 | 0x03, 0x08, 0x08, 0x0a, 0x09, 0x0c, 0x0c,
203 | 0x0e, 0x07, 0x10, 0x10, 0x12, 0x11, 0x14 ];
204 |
205 | const aRight = [ 0x00, 0x02, 0x02, 0x05, 0x04, 0x06, 0x06,
206 | 0x0b, 0x08, 0x0a, 0x0a, 0x0d, 0x0c, 0x0e,
207 | 0x0e, 0x13, 0x10, 0x12, 0x12, 0x14, 0x14 ];
208 |
209 | const aParent = [ 0x01, 0x03, 0x01, 0x07, 0x05, 0x03, 0x05,
210 | 0x0f, 0x09, 0x0b, 0x09, 0x07, 0x0d, 0x0b,
211 | 0x0d, 0x0f, 0x11, 0x13, 0x11, 0x0f, 0x13 ];
212 |
213 | const aSibling = [ 0x02, 0x05, 0x00, 0x0b, 0x06, 0x01, 0x04,
214 | 0x13, 0x0a, 0x0d, 0x08, 0x03, 0x0e, 0x09,
215 | 0x0c, 0x0f, 0x12, 0x14, 0x10, 0x07, 0x11 ];
216 |
217 | const cases = [
218 | { l: "left", f: left, a: aLeft },
219 | { l: "right", f: x => right(x, n), a: aRight },
220 | { l: "parent", f: x => parent(x, n), a: aParent },
221 | { l: "sibling", f: x => sibling(x, n), a: aSibling },
222 | ];
223 |
224 | for (let c of cases) {
225 | let q = index.map(c.f);
226 | if (!arrayEqual(q, c.a)) {
227 | console.log(c.l, q, c.a);
228 | throw c.l;
229 | }
230 | }
231 | console.log("[tree-relations] PASS");
232 | }
233 |
234 | function testFrontier() {
235 | const n = 0x0b;
236 |
237 | const aFrontier = [
238 | [0x00],
239 | [0x01],
240 | [0x01, 0x04],
241 | [0x03],
242 | [0x03, 0x08],
243 | [0x03, 0x09],
244 | [0x03, 0x09, 0x0c],
245 | [0x07],
246 | [0x07, 0x10],
247 | [0x07, 0x11],
248 | [0x07, 0x11, 0x14],
249 | ];
250 |
251 | for (let x = 1; x <= n; x += 1) {
252 | let f = frontier(x);
253 | if (!arrayEqual(f, aFrontier[x-1])) {
254 | console.log('frontier', f, aFrontier[x-1]);
255 | throw 'frontier';
256 | }
257 | }
258 |
259 | console.log("[tree-frontier] PASS");
260 | }
261 |
262 | function testPaths() {
263 | const n = 0x0b;
264 |
265 | const aDirpath = [
266 | [0, 1, 3, 7],
267 | [1, 3, 7],
268 | [2, 1, 3, 7],
269 | [3, 7],
270 | [4, 5, 3, 7],
271 | [5, 3, 7],
272 | [6, 5, 3, 7],
273 | [7],
274 | [8, 9, 11, 7],
275 | [9, 11, 7],
276 | [10, 9, 11, 7],
277 | [11, 7],
278 | [12, 13, 11, 7],
279 | [13, 11, 7],
280 | [14, 13, 11, 7],
281 | [],
282 | [16, 17, 19],
283 | [17, 19],
284 | [18, 17, 19],
285 | [19],
286 | [20, 19]
287 | ];
288 |
289 | const aCopath = [
290 | [2, 5, 11, 19],
291 | [5, 11, 19],
292 | [0, 5, 11, 19],
293 | [11, 19],
294 | [6, 1, 11, 19],
295 | [1, 11, 19],
296 | [4, 1, 11, 19],
297 | [19],
298 | [10, 13, 3, 19],
299 | [13, 3, 19],
300 | [8, 13, 3, 19],
301 | [3, 19],
302 | [14, 9, 3, 19],
303 | [9, 3, 19],
304 | [12, 9, 3, 19],
305 | [],
306 | [18, 20, 7],
307 | [20, 7],
308 | [16, 20, 7],
309 | [7],
310 | [17, 7]
311 | ];
312 |
313 | const aShadow = [
314 | [0],
315 | [0, 1, 2],
316 | [2],
317 | [0, 1, 2, 3, 4, 5, 6],
318 | [4],
319 | [4, 5, 6],
320 | [6],
321 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
322 | [8],
323 | [8, 9, 10],
324 | [10],
325 | [8, 9, 10, 11, 12, 13, 14],
326 | [12],
327 | [12, 13, 14],
328 | [14],
329 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
330 | [16],
331 | [16, 17, 18],
332 | [18],
333 | [16, 17, 18, 19, 20],
334 | [20],
335 | ];
336 |
337 | for (let x = 0; x < nodeWidth(n); x += 1) {
338 | let d = dirpath(x, n);
339 | if (!arrayEqual(d, aDirpath[x])) {
340 | console.log('dirpath', d, aDirpath[x]);
341 | throw 'dirpath';
342 | }
343 |
344 | let c = copath(x, n);
345 | if (!arrayEqual(c, aCopath[x])) {
346 | console.log('copath', c, aCopath[x]);
347 | throw 'copath';
348 | }
349 |
350 | let s = shadow(x, n);
351 | if (!arrayEqual(s, aShadow[x])) {
352 | console.log('shadow', s, aShadow[x]);
353 | throw 'shadow';
354 | }
355 | }
356 |
357 | console.log("[tree-paths] PASS");
358 | }
359 |
360 | function test() {
361 | testRoot();
362 | testRelations();
363 | testFrontier();
364 | testPaths();
365 | }
366 |
367 | // XXX(rlb@ipv.sx): This list can probably be pared down further.
368 | // Not everything needs to be exposed.
369 | module.exports = {
370 | // Basic tree properties
371 | level: level,
372 | nodeWidth: nodeWidth,
373 | root: root,
374 |
375 | // Node relations
376 | left: left,
377 | right: right,
378 | parent: parent,
379 | sibling: sibling,
380 |
381 | // Paths
382 | frontier: frontier,
383 | dirpath: dirpath,
384 | copath: copath,
385 | shadow: shadow,
386 | leaves: leaves,
387 |
388 | test: test,
389 | }
390 |
--------------------------------------------------------------------------------
/src/tree.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const tm = require('./tree-math');
4 |
5 | /*
6 | * This class represents a tree of public keys, e.g., one obtained
7 | * by scraping keys out of handshake messages.
8 | */
9 | class Tree {
10 | constructor(size) {
11 | this.size = size;
12 | this.nodes = {};
13 | }
14 |
15 | merge(nodes) {
16 | Object.assign(this.nodes, nodes);
17 | }
18 |
19 | remove(index) {
20 | tm.dirpath(2 * index, this.size).map(n => {
21 | delete this.nodes[n];
22 | });
23 | }
24 |
25 | gatherSubtree(node) {
26 | let out = {};
27 |
28 | if (this.nodes[node]) {
29 | out[node] = this.nodes[node];
30 | return out;
31 | }
32 |
33 | let left = tm.left(node);
34 | if (left != node) {
35 | Object.assign(out, this.gatherSubtree(left));
36 | }
37 |
38 | let right = tm.right(node, this.size);
39 | if (right != node) {
40 | Object.assign(out, this.gatherSubtree(right));
41 | }
42 |
43 | return out;
44 | }
45 |
46 | copath(index) {
47 | return tm.copath(2 * index, this.size)
48 | .map(n => this.gatherSubtree(n))
49 | .reduce((a, b) => Object.assign(a, b), {});
50 | }
51 | };
52 |
53 | module.exports = Tree;
54 |
--------------------------------------------------------------------------------
/src/treekem-state.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const TreeKEM = require('./treekem').class;
4 |
5 | class TreeKEMState {
6 | constructor() {
7 | this.tkem = new TreeKEM();
8 | }
9 |
10 | get index() {
11 | return this.tkem.index;
12 | }
13 |
14 | get size() {
15 | return this.tkem.size;
16 | }
17 |
18 | trim(size) {
19 | this.tkem.trim(size);
20 | }
21 |
22 | get nodes() {
23 | return this.tkem.nodes;
24 | }
25 |
26 | get copath() {
27 | return this.tkem.copath(this.tkem.index);
28 | }
29 |
30 | async equal(other) {
31 | return this.tkem.equal(other.tkem);
32 | }
33 |
34 | async dump() {
35 | return this.tkem.dump();
36 | }
37 |
38 | static fromJSON(obj) {
39 | let out = new TreeKEMState();
40 | out.tkem = TreeKEM.fromJSON(obj.tkem);
41 | return out;
42 | }
43 |
44 | static async oneMemberGroup(leaf) {
45 | let state = new TreeKEMState();
46 | state.tkem = await TreeKEM.oneMemberGroup(leaf);
47 | return state;
48 | }
49 |
50 | static async fromGroupAdd(initLeaf, groupAdd) {
51 | let kp = await iota(initLeaf);
52 | let leaf = await ECKEM.decrypt(groupAdd.forJoiner.encryptedLeaf, kp.privateKey);
53 | let state = new TreeKEMState();
54 | state.tkem = await TreeKEM.fromFrontier(groupAdd.forJoiner.size, groupAdd.forJoiner.frontier, leaf);
55 | return state;
56 | }
57 |
58 | static async fromUserAdd(leaf, /* IGNORED */ userAdd, groupInitKey) {
59 | let state = new TreeKEMState();
60 | state.tkem = await TreeKEM.fromFrontier(groupInitKey.size, groupInitKey.frontier, leaf);
61 | return state;
62 | }
63 |
64 | static async join(leaf, groupInitKey) {
65 | let tkem = await TreeKEM.fromFrontier(groupInitKey.size, groupInitKey.frontier, leaf);
66 | let ct = await tkem.encrypt(leaf, tkem.index)
67 | return {
68 | ciphertexts: ct.ciphertexts,
69 | nodes: ct.nodes,
70 | };
71 | }
72 |
73 | async add(userInitPub) {
74 | let leaf = base64.random(32);
75 | let encryptedLeaf = await ECKEM.encrypt(leaf, userInitPub);
76 |
77 | let gik = this.groupInitKey;
78 | let ua = await TreeKEMState.join(leaf, gik);
79 |
80 | return {
81 | forGroup: ua,
82 | forJoiner: {
83 | size: gik.size,
84 | frontier: gik.frontier,
85 | encryptedLeaf: encryptedLeaf,
86 | },
87 | };
88 | }
89 |
90 | async update(leaf) {
91 | let ct = await this.tkem.encrypt(leaf, this.tkem.index);
92 | return {
93 | from: this.tkem.index,
94 | ciphertexts: ct.ciphertexts,
95 | nodes: ct.nodes,
96 | }
97 | }
98 |
99 | async remove(leaf, index, copath) {
100 | this.tkem.merge(copath, true);
101 | let ct = await this.tkem.encrypt(leaf, index);
102 | return {
103 | index: index,
104 | ciphertexts: ct.ciphertexts,
105 | copath: this.tkem.copath(index),
106 | };
107 | }
108 |
109 | async move(leaf, index, copath) {
110 | this.tkem.merge(copath, true);
111 | let ct = await this.tkem.encrypt(leaf, index);
112 | return {
113 | from: this.index,
114 | to: index,
115 | ciphertexts: ct.ciphertexts,
116 | nodes: ct.nodes,
117 | copath: this.tkem.copath(this.tkem.index),
118 | };
119 | }
120 |
121 | get groupInitKey() {
122 | return {
123 | size: this.tkem.size,
124 | frontier: this.tkem.frontier(),
125 | };
126 | }
127 |
128 | async handleUserAdd(ua) {
129 | let pt = await this.tkem.decrypt(this.tkem.size, ua.ciphertexts);
130 | this.tkem.merge(ua.nodes);
131 | this.tkem.merge(pt.nodes);
132 | this.tkem.size += 1;
133 | }
134 |
135 | async handleGroupAdd(ga) {
136 | let pt = await this.tkem.decrypt(this.tkem.size, ga.forGroup.ciphertexts);
137 | this.tkem.merge(ga.forGroup.nodes);
138 | this.tkem.merge(pt.nodes);
139 | this.tkem.size += 1;
140 | }
141 |
142 | async handleSelfUpdate(/* IGNORED */ update, leaf) {
143 | let privateNodes = await TreeKEM.hashUp(2*this.tkem.index, this.tkem.size, leaf);
144 | this.tkem.merge(privateNodes);
145 | }
146 |
147 | async handleUpdate(update) {
148 | let pt = await this.tkem.decrypt(update.from, update.ciphertexts);
149 | this.tkem.merge(update.nodes);
150 | this.tkem.merge(pt.nodes);
151 | }
152 |
153 | async handleRemove(remove) {
154 | let pt = await this.tkem.decrypt(remove.index, remove.ciphertexts);
155 | this.tkem.remove(remove.index);
156 | this.tkem.merge(pt.root);
157 | this.tkem.merge(remove.copath, true);
158 | }
159 |
160 | async handleSelfMove(move, leaf) {
161 | let privateNodes = await TreeKEM.hashUp(2 * move.to, this.tkem.size, leaf);
162 | this.tkem.remove(move.from);
163 | this.tkem.merge(privateNodes);
164 | this.tkem.merge(move.copath, true);
165 | this.tkem.index = move.to;
166 | }
167 |
168 | async handleMove(move) {
169 | let pt = await this.tkem.decrypt(move.to, move.ciphertexts);
170 | this.tkem.remove(move.from);
171 | this.tkem.merge(move.nodes);
172 | this.tkem.merge(move.copath, true);
173 | this.tkem.merge(pt.nodes);
174 | }
175 | }
176 |
177 | module.exports = TreeKEMState;
178 |
--------------------------------------------------------------------------------
/src/treekem.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ECKEM = require('./eckem');
4 | const iota = require('./iota');
5 | const tm = require('./tree-math');
6 | const util = require('./util');
7 | const dh = require('./dh');
8 | const cs = window.crypto.subtle;
9 |
10 | // #ifdef COLORIZE
11 | const FADESTART = 30;
12 | const FADESTOP = 70;
13 | // #endif /* def COLORIZE */
14 |
15 | function hex(ab) {
16 | const arr = Array.from(new Uint8Array(ab));
17 | return arr.map(x => ('0' + x.toString(16)).slice(-2)).join('');
18 | }
19 |
20 | function xor(a, b) {
21 | const ua = new Uint8Array(a);
22 | const ub = new Uint8Array(b);
23 | return (new Uint8Array(ua.map((x, i) => x ^ ub[i]))).buffer;
24 | }
25 |
26 | async function hash(x64) {
27 | const x = base64.parse(x64);
28 | const d = await cs.digest("SHA-256", x);
29 | return base64.stringify(d);
30 | }
31 |
32 | class TreeKEM {
33 | /*
34 | * TreeKEM objects should not be constructed directly. Instead, use
35 | * the `TreeKEM.fromX` factory methods. This only exists to give
36 | * certain variables public exposure for debugging, and as a base
37 | * for the factory methods. It would be private in C++.
38 | */
39 | constructor() {
40 | this.size = 0;
41 | this.index = 0;
42 | this.nodes = [];
43 | }
44 |
45 | static fromJSON(obj) {
46 | let out = new TreeKEM();
47 | out.size = obj.size;
48 | out.index = obj.index;
49 | out.nodes = obj.nodes;
50 | return out;
51 | }
52 |
53 | /*
54 | * Construct a TreeKEM representing a group with a single member,
55 | * with the given leaf secret.
56 | */
57 | static async oneMemberGroup(leaf) {
58 | let tkem = new TreeKEM();
59 | tkem.size = 1;
60 | tkem.index = 0;
61 | tkem.merge(await TreeKEM.hashUp(0, 1, leaf));
62 | return tkem;
63 | }
64 |
65 | /*
66 | * Construct a tree that extends a tree with the given size and
67 | * frontier by adding a member with the given leaf secret.
68 | */
69 | static async fromFrontier(size, frontier, leaf) {
70 | let tkem = new TreeKEM();
71 | tkem.size = size + 1;
72 | tkem.index = size;
73 | tkem.merge(frontier);
74 |
75 | let nodes = await TreeKEM.hashUp(2 * tkem.index, tkem.size, leaf);
76 | tkem.merge(nodes);
77 | return tkem;
78 | }
79 |
80 | /*
81 | * Map a function over the populated subtree heads beneath an
82 | * intermediate node. Results are collated in an object whose
83 | * keys are the indices of the relevant tree nodes.
84 | *
85 | * Inputs:
86 | * * node - Head of the subtree
87 | * * func - func(nodeID) -> T
88 | *
89 | * Returns:
90 | * * {Node: T}
91 | */
92 | mapSubtree(node, func) {
93 | let out = {};
94 |
95 | if (this.nodes[node]) {
96 | out[node] = func(node);
97 | return out;
98 | }
99 |
100 | let left = tm.left(node);
101 | if (left != node) {
102 | Object.assign(out, this.mapSubtree(left, func));
103 | }
104 |
105 | let right = tm.right(node, this.size);
106 | if (right != node) {
107 | Object.assign(out, this.mapSubtree(right, func));
108 | }
109 |
110 | return out;
111 | }
112 |
113 | /*
114 | * Encrypt a value so that it can be decrypted by all nodes in the
115 | * subtree with the indicated head, even if some leaves are
116 | * excluded.
117 | */
118 | async encryptToSubtree(head, value) {
119 | let encryptions = this.mapSubtree(head, async node => {
120 | return await ECKEM.encrypt(value, this.nodes[node].public);
121 | });
122 |
123 | for (let n in encryptions) {
124 | encryptions[n] = await encryptions[n];
125 | }
126 | return encryptions;
127 | }
128 |
129 | /*
130 | * Gather the heads of the populated subtrees below the specified
131 | * subtree head
132 | */
133 | gatherSubtree(head) {
134 | return this.mapSubtree(head, node => util.publicNode(this.nodes[node]));
135 | }
136 |
137 | /*
138 | * Encrypt a fresh root value in a way that all participants in
139 | * the group can decrypt, except for an excluded node.
140 | *
141 | * Arguments:
142 | * * leaf - BufferSource with leaf secret
143 | * * except - index of the node to exclude
144 | *
145 | * Returns: Promise resolving to a TreeKEMCiphertext object:
146 | * {
147 | * // Index of the sender in the tree
148 | * index: Int
149 | *
150 | * // Public nodes along the direct path
151 | * nodes: { Int: Node }
152 | *
153 | * // Private nodes along the direct path
154 | * privateNodes: { Int: Node }
155 | *
156 | * // Ciphertexts along the copath
157 | * ciphertexts: [ ECKEMCiphertext ]
158 | * }
159 | */
160 | async encrypt(leaf, except) {
161 | let dirpath = tm.dirpath(2 * except, this.size);
162 | let copath = tm.copath(2 * except, this.size);
163 |
164 | // Generate hashes up the tree
165 | let privateNodes = await TreeKEM.hashUp(2 * except, this.size, leaf);
166 | let nodes = {};
167 | for (let n in privateNodes) {
168 | nodes[n] = util.publicNode(privateNodes[n]);
169 | }
170 |
171 | // KEM each hash to the corresponding copath node
172 | let ciphertexts = await Promise.all(copath.map(async (c, i) => {
173 | let p = tm.parent(c, this.size);
174 | let s = privateNodes[p].secret;
175 | return this.encryptToSubtree(c, s);
176 | }));
177 |
178 | return {
179 | nodes: nodes,
180 | ciphertexts: ciphertexts,
181 | };
182 | }
183 |
184 | /*
185 | * Decrypts and returns fresh root value.
186 | *
187 | * Arguments:
188 | * * index - Index of sending node in the tree
189 | * * ciphertexts - List of ECKEMCiphertexts along copath
190 | *
191 | * Returns: Promise resolving to an object:
192 | * {
193 | * // The root hash for the tree
194 | * root: ArrayBuffer
195 | *
196 | * // Public nodes resulting from hashes on the direct path
197 | * nodes: { Int: Node }
198 | * }
199 | */
200 | async decrypt(index, ciphertexts) {
201 | // These are the nodes that the sender encrypted to
202 | let senderSize = (index == this.size)? this.size + 1 : this.size;
203 | let copath = tm.copath(2 * index, senderSize);
204 |
205 | // These are the nodes that we should have private keys for
206 | let dirpath = tm.dirpath(2 * this.index, this.size);
207 | dirpath.push(tm.root(this.size));
208 |
209 | // Decrypt at the point where the dirpath and copath overlap
210 | let overlap = dirpath.filter(x => copath.includes(x))[0];
211 | let coIndex = copath.indexOf(overlap);
212 | let dirIndex = dirpath.indexOf(overlap);
213 | let encryptions = ciphertexts[coIndex];
214 |
215 | // Extract an encrypted value that we can decrypt, and decrypt it
216 | let decNode = Object.keys(encryptions)
217 | .map(x => parseInt(x))
218 | .filter(x => dirpath.includes(x))[0];
219 |
220 | let h = await ECKEM.decrypt(encryptions[decNode], this.nodes[decNode].private);
221 |
222 | // Hash up to the root (plus one if we're growing the tree)
223 | let rootNode = tm.root(senderSize);
224 | let newDirpath = tm.dirpath(2 * this.index, senderSize);
225 | newDirpath.push(rootNode);
226 | let nodes = await TreeKEM.hashUp(newDirpath[dirIndex+1], senderSize, h);
227 |
228 | let root = {}
229 | root[rootNode] = nodes[rootNode];
230 |
231 | return {
232 | root: root,
233 | nodes: nodes,
234 | }
235 | }
236 |
237 | /*
238 | * Removes unnecessary nodes from the tree when the size of the
239 | * group shrinks.
240 | */
241 | trim(size) {
242 | if (size > this.size) {
243 | throw "Cannot trim upwards";
244 | }
245 |
246 | let width = tm.nodeWidth(size);
247 | this.nodes = this.nodes.slice(0, width);
248 | this.size = size;
249 | }
250 |
251 | /*
252 | * Remove a node from the tree, including its direct path
253 | *
254 | * Arguments:
255 | * index - Index of the node to remove
256 | *
257 | * Returns: None
258 | */
259 | remove(index) {
260 | for (let n of tm.dirpath(2 * index, this.size)) {
261 | delete this.nodes[n];
262 | }
263 | }
264 |
265 | /*
266 | * Updates nodes in the tree.
267 | *
268 | * Arguments:
269 | * nodes - Dictionary of nodes to update: { Int: Node }
270 | * preserve - Whether existing nodes should be left alone
271 | *
272 | * Returns: None
273 | */
274 | merge(nodes, preserve) {
275 | for (let n in nodes) {
276 | if (this.nodes[n] && preserve) {
277 | continue;
278 | }
279 |
280 | this.nodes[n] = nodes[n];
281 | }
282 | }
283 |
284 | /*
285 | * Returns the nodes on the frontier of the tree { Int: Node },
286 | * including subtree heads if the tree is incomplete.
287 | */
288 | frontier() {
289 | return tm.frontier(this.size)
290 | .map(n => this.gatherSubtree(n))
291 | .reduce((a, b) => Object.assign(a, b), {});
292 | }
293 |
294 | /*
295 | * Returns the nodes on the copath for this node { Int: Node },
296 | * including subtree heads if the tree is incomplete.
297 | */
298 | copath(index) {
299 | return tm.copath(2 * index, this.size)
300 | .map(n => this.gatherSubtree(n))
301 | .reduce((a, b) => Object.assign(a, b), {});
302 | }
303 |
304 | /*
305 | * Two instances are equal if they agree on the nodes where they
306 | * overlap.
307 | */
308 | async equal(other) {
309 | if (this.size != other.size) {
310 | return false;
311 | }
312 |
313 | for (let i in this.nodes) {
314 | if (!other.nodes[i]) {
315 | continue;
316 | }
317 |
318 | let fp1 = await dh.fingerprint(this.nodes[i].public);
319 | let fp2 = await dh.fingerprint(other.nodes[i].public);
320 | if (fp1 !== fp2) {
321 | return false;
322 | }
323 | }
324 |
325 | return true;
326 | }
327 |
328 | static async hashUp(index, size, h) {
329 | // Compute hashes up the tree
330 | let nodes = {};
331 | let n = index;
332 | let root = tm.root(size);
333 | let path = [n];
334 | while (true) {
335 | let kp = await iota(h);
336 | nodes[n] = {
337 | secret: h,
338 | public: kp.publicKey,
339 | private: kp.privateKey,
340 | };
341 |
342 | if (n == root) {
343 | break;
344 | }
345 |
346 | n = tm.parent(n, size);
347 | path.push(n);
348 | h = await hash(h);
349 | }
350 |
351 | // #ifdef COLORIZE
352 | let height = tm.level(root) || 1;
353 | let secret = base64.parse(nodes[root].secret);
354 | let hue = Array.from(new Uint8Array(secret)).reduce((x, y) => x ^ y);
355 | let dl = Math.round((FADESTOP - FADESTART) / height);
356 | for (let i = 0; i < path.length; ++i) {
357 | let l = FADESTART + i * dl;
358 | nodes[path[path.length - i - 1]].color = [hue, 100, l];
359 | }
360 | // #endif /* def COLORIZE */
361 |
362 | return nodes;
363 | }
364 |
365 | async dump(label) {
366 | console.log("===== treekem dump (", label,") =====");
367 | console.log("size:", this.size);
368 | console.log("nodes:", this.nodes);
369 | for (let i in this.nodes) {
370 | console.log(" ", i, ":", await dh.fingerprint(this.nodes[i].public));
371 | }
372 | }
373 | }
374 |
375 | function arrayBufferEqual(a, b) {
376 | let ua = Array.from(new Uint8Array(a));
377 | let ub = Array.from(new Uint8Array(b));
378 | return ua.filter((x, i) => (ub[i] != x)).length == 0;
379 | }
380 |
381 | async function testMembers(size) {
382 | let nodeWidth = tm.nodeWidth(size);
383 | let keyPairs = await Promise.all([...Array(nodeWidth).keys()].map(i => iota(new Uint8Array([i]))));
384 |
385 | let nodes = {}
386 | keyPairs.map((kp, i) => {
387 | nodes[i] = {
388 | private: kp.privateKey,
389 | public: kp.publicKey
390 | };
391 | });
392 |
393 | // Provision members
394 | let members = [];
395 | const root = tm.root(size);
396 | for (let i = 0; i < size; ++i) {
397 | members[i] = new TreeKEM();
398 | members[i].size = size;
399 | members[i].index = i;
400 |
401 | // Public keys along its copath
402 | for (const n of tm.copath(2*i, size)) {
403 | members[i].nodes[n] = {
404 | public: nodes[n].public,
405 | };
406 | }
407 |
408 | // Private keys along its direct path
409 | let dirpath = tm.dirpath(2*i, size);
410 | dirpath.push(root);
411 | for (const n of dirpath) {
412 | members[i].nodes[n] = {
413 | private: nodes[n].private,
414 | public: nodes[n].public,
415 | };
416 | }
417 | }
418 |
419 | return members;
420 | }
421 |
422 | async function testEncryptDecrypt() {
423 | const testGroupSize = 5;
424 | const seed = new Uint8Array([0,1,2,3]);
425 |
426 | // Create a group with the specified size
427 | let members = await testMembers(testGroupSize);
428 |
429 | // Have each member send and be received by all members
430 | for (const m of members) {
431 | let ct = await m.encrypt(seed);
432 | let privateNodes = await TreeKEM.hashUp(2 * m.index, m.size, seed);
433 |
434 | m.merge(ct.nodes)
435 | m.merge(privateNodes);
436 |
437 | for (let m2 of members) {
438 | if (m2.index == m.index) {
439 | continue;
440 | }
441 |
442 | let pt = await m2.decrypt(m.index, ct.ciphertexts);
443 | if (!arrayBufferEqual(ct.root, pt.root)) {
444 | console.log("error:", m.index, "->", m2.index);
445 | console.log("send:", hex(ct.root));
446 | console.log("recv:", hex(pt.root));
447 | throw 'tkem-root';
448 | }
449 |
450 | // Merge public values, then private
451 | m2.merge(ct.nodes);
452 | m2.merge(pt.nodes);
453 |
454 | let eq = await m.equal(m2);
455 | if (!eq) {
456 | console.log("error:", m.index, "->", m2.index);
457 | throw 'tkem-eq';
458 | }
459 | }
460 | }
461 |
462 | console.log("[tkem-encrypt-decrypt] PASS");
463 | }
464 |
465 | async function testSimultaneousUpdate() {
466 | const testGroupSize = 5;
467 | let members = await testMembers(testGroupSize);
468 |
469 | // Have each member emit an update, then have everyone compute and
470 | // apply a merged update
471 | let seeds = members.map(m => new Uint8Array([m.index]));
472 | let cts = await Promise.all(members.map(m => {
473 | return m.encrypt(seeds[m.index]);
474 | }));
475 |
476 | let secrets = await Promise.all(members.map(async m => {
477 | let privateNodes = await TreeKEM.hashUp(2 * m.index, m.size, seeds[m.index]);
478 |
479 | const pts = (await Promise.all(cts.map((ct, i) => {
480 | return (i == m.index)? null : m.decrypt(i, ct.ciphertexts);
481 | })));
482 |
483 | // The secret for the merge will be the XOR of all the
484 | // individual root secrets
485 | const roots = pts.map((pt, i) => {
486 | return (i == m.index)? privateNodes[tm.root(m.size)] : pt.root;
487 | });
488 | let secret = roots.reduce(xor);
489 |
490 | // The key pair changes are applied in order of arrival
491 | for (let i = 0; i < pts.length; ++i) {
492 | if (i == m.index) {
493 | m.merge(privateNodes);
494 | continue;
495 | }
496 |
497 | m.merge(cts[i].nodes);
498 | m.merge(pts[i].nodes);
499 | }
500 | return secret;
501 | }));
502 |
503 | // Check that all of the derived secrets are the same
504 | secrets.reduce((a, b) => {
505 | if (!arrayBufferEqual(a, b)) {
506 | console.log("error:", hex(a), hex(b));
507 | throw 'tkem-simultaneous-secret';
508 | }
509 |
510 | return a;
511 | });
512 |
513 | // Check that all members arrived in the same state
514 | for (const m1 of members) {
515 | for (const m2 of members) {
516 | let eq = await m1.equal(m2);
517 | if (!eq) {
518 | console.log("error:", m1.index, "!=", m2.index);
519 | await m1.dump();
520 | await m2.dump();
521 | throw 'tkem-simultaneous-tree';
522 | }
523 | }
524 | }
525 |
526 | console.log("[tkem-simultaneous] PASS");
527 | }
528 |
529 | async function test() {
530 | await testEncryptDecrypt();
531 | await testSimultaneousUpdate();
532 | }
533 |
534 | module.exports = {
535 | class: TreeKEM,
536 | test: test,
537 | };
538 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const iota = require('./iota');
4 | const cs = window.crypto.subtle;
5 |
6 | // #ifdef COLORIZE
7 | async function keyColor(k) {
8 | let jwk = JSON.stringify(k);
9 | let jsonData = Array.prototype.map.call(jwk, c => c.charCodeAt(0));
10 | let hue = Array.from(new Uint8Array(jsonData)).reduce((x, y) => x ^ y);
11 | return [hue, 100, 50];
12 | }
13 |
14 | /*
15 | function modAverage(x, y, m) {
16 | if (y < x) {
17 | [x, y] = [y, x];
18 | }
19 |
20 | let aSide = (y - x)/2;
21 | let bSide = ((x + m) - y)/2;
22 | if (aSide < bSide) {
23 | return (x + aSide) % m;
24 | } else {
25 | return (y + bSide) % m;
26 | }
27 | }
28 |
29 | function colorAvg(c1, c2) {
30 | return [
31 | modAverage(c1[0], c2[0], 256),
32 | (c1[1] + c2[1]) / 2,
33 | (c1[2] + c2[2]) / 2,
34 | ];
35 | }
36 | */
37 |
38 | // RGB/HSL conversion adapted from StackOverflow
39 | // https://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
40 | function hue2rgb(p, q, t){
41 | if(t < 0) t += 1;
42 | if(t > 1) t -= 1;
43 | if(t < 1/6) return p + (q - p) * 6 * t;
44 | if(t < 1/2) return q;
45 | if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
46 | return p;
47 | }
48 |
49 | function hslToRgb(h, s, l){
50 | var r, g, b;
51 |
52 | if (s == 0) {
53 | r = g = b = l; // achromatic
54 | } else {
55 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
56 | var p = 2 * l - q;
57 | r = hue2rgb(p, q, h + 1/3);
58 | g = hue2rgb(p, q, h);
59 | b = hue2rgb(p, q, h - 1/3);
60 | }
61 |
62 | return [r, g, b];
63 | }
64 |
65 | function rgbToHsl(r, g, b){
66 | let max = Math.max(r, g, b);
67 | let min = Math.min(r, g, b);
68 | let h, s, l = (max + min) / 2;
69 |
70 | if (max == min) {
71 | h = s = 0; // achromatic
72 | }else{
73 | var d = max - min;
74 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
75 | switch(max){
76 | case r: h = (g - b) / d + (g < b ? 6 : 0); break;
77 | case g: h = (b - r) / d + 2; break;
78 | case b: h = (r - g) / d + 4; break;
79 | }
80 | h /= 6;
81 | }
82 |
83 | return [h, s, l];
84 | }
85 |
86 | function colorAvg(c1, c2) {
87 | let [h1, s1, l1] = [c1[0] / 255, c1[1] / 100, c1[2] / 100];
88 | let [h2, s2, l2] = [c2[0] / 255, c2[1] / 100, c2[2] / 100];
89 |
90 | let rgb1 = hslToRgb(h1, s1, l1);
91 | let rgb2 = hslToRgb(h2, s2, l2);
92 | let [ra, ga, ba] = rgb1.map((x, i) => (x + rgb2[i]) / 2);
93 |
94 | let [ha, sa, la] = rgbToHsl(ra, ga, ba);
95 | return [255 * ha, 100 * sa, 100 * la];
96 | }
97 | // #endif /* def COLORIZE */
98 |
99 | async function newNode(secret) {
100 | let kp = await iota(secret);
101 | let color = await keyColor(kp.publicKey);
102 | return {
103 | secret: secret,
104 | private: kp.privateKey,
105 | public: kp.publicKey,
106 | color: color,
107 | };
108 | }
109 |
110 | function publicNode(node) {
111 | return {
112 | public: node.public,
113 | color: node.color,
114 | }
115 | }
116 |
117 | function nodePath(nodes, path) {
118 | let out = {};
119 | for (let n of path) {
120 | out[n] = publicNode(nodes[n]);
121 | }
122 | return out;
123 | }
124 |
125 | module.exports = {
126 | keyColor: keyColor,
127 | colorAvg: colorAvg,
128 | newNode: newNode,
129 | publicNode: publicNode,
130 | nodePath: nodePath,
131 | }
132 |
133 |
--------------------------------------------------------------------------------
/web/jquery-3.3.1.min.js:
--------------------------------------------------------------------------------
1 | /*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */
2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML=" ",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML=" ";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML=" ","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML=" ",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""," "],thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/