├── .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 |
GitHub
433 |
434 |
435 | 436 | 437 | 438 | 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=/\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w("