diff --git a/blue_modules/crypto.js b/blue_modules/crypto.js deleted file mode 100644 index bad356e74..000000000 --- a/blue_modules/crypto.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @fileOverview wraps a secure native random number generator. - * This module mimics the node crypto api and is intended to work in RN environment. - */ - -const { NativeModules } = require('react-native'); -const { RNRandomBytes } = NativeModules; - -/** - * Generate cryptographically secure random bytes using native api. - * @param {number} size The number of bytes of randomness - * @param {function} callback The callback to return the bytes - * @return {Buffer} The random bytes - */ -exports.randomBytes = (size, callback) => { - RNRandomBytes.randomBytes(size, (err, bytes) => { - if (err) { - callback(err); - } else { - callback(null, Buffer.from(bytes, 'base64')); - } - }); -}; diff --git a/blue_modules/slip39/.eslintrc.js b/blue_modules/slip39/.eslintrc.js new file mode 100644 index 000000000..438c5c0e8 --- /dev/null +++ b/blue_modules/slip39/.eslintrc.js @@ -0,0 +1,197 @@ +//module.exports = { "extends": "standard" }; +module.exports = { + "env": { + "browser": true, + "node": true, + "es6": true + }, + "parserOptions": { + "sourceType": "module", + }, + "rules": { + + // + //Possible Errors + // + // The following rules point out areas where you might have made mistakes. + // + "comma-dangle": 2, // disallow or enforce trailing commas + "no-cond-assign": 2, // disallow assignment in conditional expressions + // NOTE: "no-console": 1, // disallow use of console (off by default in the node environment) + "no-constant-condition": 2, // disallow use of constant expressions in conditions + "no-control-regex": 2, // disallow control characters in regular expressions + "no-debugger": 2, // disallow use of debugger + "no-dupe-args": 2, // disallow duplicate arguments in functions + "no-dupe-keys": 2, // disallow duplicate keys when creating object literals + "no-duplicate-case": 2, // disallow a duplicate case label. + "no-empty": 2, // disallow empty statements + "no-empty-character-class": 2, // disallow the use of empty character classes in regular expressions + "no-ex-assign": 2, // disallow assigning to the exception in a catch block + "no-extra-boolean-cast": 2, // disallow double-negation boolean casts in a boolean context + "no-extra-parens": 0, // disallow unnecessary parentheses (off by default) + "no-extra-semi": 2, // disallow unnecessary semicolons + "no-func-assign": 2, // disallow overwriting functions written as function declarations + "no-inner-declarations": 2, // disallow function or variable declarations in nested blocks + "no-invalid-regexp": 2, // disallow invalid regular expression strings in the RegExp constructor + "no-irregular-whitespace": 2, // disallow irregular whitespace outside of strings and comments + "no-negated-in-lhs": 2, // disallow negation of the left operand of an in expression + "no-obj-calls": 2, // disallow the use of object properties of the global object (Math and JSON) as functions + "no-regex-spaces": 2, // disallow multiple spaces in a regular expression literal + "quote-props": 2, // disallow reserved words being used as object literal keys (off by default) + "no-sparse-arrays": 2, // disallow sparse arrays + "no-unreachable": 2, // disallow unreachable statements after a return, throw, continue, or break statement + "use-isnan": 2, // disallow comparisons with the value NaN + "valid-jsdoc": 2, // Ensure JSDoc comments are valid (off by default) + "valid-typeof": 2, // Ensure that the results of typeof are compared against a valid string + + // + // Best Practices + // + // These are rules designed to prevent you from making mistakes. + // They either prescribe a better way of doing something or help you avoid footguns. + // + "block-scoped-var": 0, // treat var statements as if they were block scoped (off by default). 0: deep destructuring is not compatible https://github.com/eslint/eslint/issues/1863 + "complexity": 0, // specify the maximum cyclomatic complexity allowed in a program (off by default) + //"consistent-return": 2, // require return statements to either always or never specify values + "curly": 2, // specify curly brace conventions for all control statements + "default-case": 2, // require default case in switch statements (off by default) + "dot-notation": 2, // encourages use of dot notation whenever possible + "eqeqeq": 2, // require the use of === and !== + //"guard-for-in": 2, // make sure for-in loops have an if statement (off by default) + "no-alert": 2, // disallow the use of alert, confirm, and prompt + "no-caller": 2, // disallow use of arguments.caller or arguments.callee + "no-div-regex": 2, // disallow division operators explicitly at beginning of regular expression (off by default) + "no-else-return": 2, // disallow else after a return in an if (off by default) + "no-labels": 2, // disallow use of labels for anything other then loops and switches + "no-eq-null": 2, // disallow comparisons to null without a type-checking operator (off by default) + "no-eval": 2, // disallow use of eval() + //"no-extend-native": [0, { "exceptions": ["Object", "String"] }], // disallow adding to native types + "no-extra-bind": 2, // disallow unnecessary function binding + "no-fallthrough": 2, // disallow fallthrough of case statements + "no-floating-decimal": 2, // disallow the use of leading or trailing decimal points in numeric literals (off by default) + "no-implied-eval": 2, // disallow use of eval()-like methods + "no-iterator": 2, // disallow usage of __iterator__ property + "no-labels": 2, // disallow use of labeled statements + "no-lone-blocks": 2, // disallow unnecessary nested blocks + "no-loop-func": 2, // disallow creation of functions within loops + "no-multi-spaces": 2, // disallow use of multiple spaces + "no-multi-str": 2, // disallow use of multiline strings + "no-native-reassign": 2, // disallow reassignments of native objects + "no-new": 2, // disallow use of new operator when not part of the assignment or comparison + "no-new-func": 2, // disallow use of new operator for Function object + "no-new-wrappers": 2, // disallows creating new instances of String,Number, and Boolean + "no-octal": 2, // disallow use of octal literals + "no-octal-escape": 2, // disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251"; + "no-param-reassign": 2, // disallow reassignment of function parameters (off by default) + "no-process-env": 2, // disallow use of process.env (off by default) + "no-proto": 2, // disallow usage of __proto__ property + "no-redeclare": 2, // disallow declaring the same variable more then once + "no-return-assign": 2, // disallow use of assignment in return statement + "no-script-url": 2, // disallow use of javascript: urls. + "no-self-compare": 2, // disallow comparisons where both sides are exactly the same (off by default) + "no-sequences": 2, // disallow use of comma operator + "no-throw-literal": 2, // restrict what can be thrown as an exception (off by default) + //"no-unused-expressions": 2, // disallow usage of expressions in statement position + "no-void": 2, // disallow use of void operator (off by default) + "no-warning-comments": [0, {"terms": ["todo", "fixme"], "location": "start"}], // disallow usage of configurable warning terms in comments": 2, // e.g. TODO or FIXME (off by default) + "no-with": 2, // disallow use of the with statement + "radix": 1, // require use of the second argument for parseInt() (off by default) + "vars-on-top": 2, // requires to declare all vars on top of their containing scope (off by default) + "wrap-iife": 2, // require immediate function invocation to be wrapped in parentheses (off by default) + "yoda": 2, // require or disallow Yoda conditions + + // + // Strict Mode + // + // These rules relate to using strict mode. + // + "strict": 0, // controls location of Use Strict Directives. 0: required by `babel-eslint` + + // + // Variables + // + // These rules have to do with variable declarations. + // + "no-catch-shadow": 2, // disallow the catch clause parameter name being the same as a variable in the outer scope (off by default in the node environment) + "no-delete-var": 2, // disallow deletion of variables + "no-label-var": 2, // disallow labels that share a name with a variable + "no-shadow": 2, // disallow declaration of variables already declared in the outer scope + "no-shadow-restricted-names": 2, // disallow shadowing of names such as arguments + // "no-undef": 2, // disallow use of undeclared variables unless mentioned in a /*global */ block + "no-undef-init": 2, // disallow use of undefined when initializing variables + "no-undefined": 2, // disallow use of undefined variable (off by default) + "no-unused-vars": 2, // disallow declaration of variables that are not used in the code + // "no-use-before-define": 2, // disallow use of variables before they are defined + + // + //Stylistic Issues + // + // These rules are purely matters of style and are quite subjective. + // + "indent": [1, 2], // this option sets a specific tab width for your code (off by default) + "brace-style": 1, // enforce one true brace style (off by default) + "camelcase": 1, // require camel case names + "comma-spacing": [1, {"before": false, "after": true}], // enforce spacing before and after comma + "comma-style": [1, "last"], // enforce one true comma style (off by default) + "consistent-this": [1, "_this"], // enforces consistent naming when capturing the current execution context (off by default) + "eol-last": 1, // enforce newline at the end of file, with no multiple empty lines + "func-names": 0, // require function expressions to have a name (off by default) + "func-style": 0, // enforces use of function declarations or expressions (off by default) + "key-spacing": [1, {"beforeColon": false, "afterColon": true}], // enforces spacing between keys and values in object literal properties + //"max-nested-callbacks": [1, 3], // specify the maximum depth callbacks can be nested (off by default) + "new-cap": [1, {newIsCap: true, capIsNew: false}], // require a capital letter for constructors + "new-parens": 1, // disallow the omission of parentheses when invoking a constructor with no arguments + "newline-after-var": 0, // allow/disallow an empty newline after var statement (off by default) + //"no-array-constructor": 1, // disallow use of the Array constructor + "no-inline-comments": 1, // disallow comments inline after code (off by default) + "no-lonely-if": 1, // disallow if as the only statement in an else block (off by default) + "no-mixed-spaces-and-tabs": 1, // disallow mixed spaces and tabs for indentation + "no-multiple-empty-lines": [1, {"max": 2}], // disallow multiple empty lines (off by default) + "no-nested-ternary": 1, // disallow nested ternary expressions (off by default) + "no-new-object": 1, // disallow use of the Object constructor + "no-spaced-func": 1, // disallow space between function identifier and application + "no-ternary": 0, // disallow the use of ternary operators (off by default) + "no-trailing-spaces": 1, // disallow trailing whitespace at the end of lines + "no-underscore-dangle": 1, // disallow dangling underscores in identifiers + "no-extra-parens": 1, // disallow wrapping of non-IIFE statements in parens + "one-var": [1, "never"], // allow just one var statement per function (off by default) + "operator-assignment": [1, "never"], // require assignment operator shorthand where possible or prohibit it entirely (off by default) + "padded-blocks": [1, "never"], // enforce padding within blocks (off by default) + "quote-props": [1, "as-needed"], // require quotes around object literal property names (off by default) + "quotes": [1, "single"], // specify whether double or single quotes should be used + "semi": [1, "always"], // require or disallow use of semicolons instead of ASI + "semi-spacing": [1, {"before": false, "after": true}], // enforce spacing before and after semicolons + "sort-vars": 0, // sort variables within the same declaration block (off by default) + "space-before-blocks": [1, "always"], // require or disallow space before blocks (off by default) + "space-before-function-paren": [1, {"anonymous": "always", "named": "never"}], // require or disallow space before function opening parenthesis (off by default) + "object-curly-spacing": [1, "never"], // require or disallow spaces inside brackets (off by default) + "space-in-parens": [1, "never"], // require or disallow spaces inside parentheses (off by default) + //"space-infix-ops": [1, "always"], // require spaces around operators + "keyword-spacing": 2, // require a space after return, throw, and case + "space-unary-ops": [1, {"words": true, "nonwords": false}], // Require or disallow spaces before/after unary operators (words on by default, nonwords off by default) + "spaced-comment": [1, "always"], // require or disallow a space immediately following the // in a line comment (off by default) + "wrap-regex": 0, // require regex literals to be wrapped in parentheses (off by default) + + // + // ECMAScript 6 + // + // These rules are only relevant to ES6 environments and are off by default. + // + "no-var": 2, // require let or const instead of var (off by default) + "generator-star-spacing": [2, "before"], // enforce the spacing around the * in generator functions (off by default) + + // + // Legacy + // + // The following rules are included for compatibility with JSHint and JSLint. + // While the names of the rules may not match up with the JSHint/JSLint counterpart, + // the functionality is the same. + // + "max-depth": [2, 3], // specify the maximum depth that blocks can be nested (off by default) + "max-len": [2, 100, 2, { "ignoreStrings": true, "ignoreTemplateLiterals": true, "ignoreUrls": true }], // specify the maximum length of a line in your program (off by default) + "max-params": [2, 8], // limits the number of parameters that can be used in the function declaration. (off by default) + "max-statements": 0, // specify the maximum number of statement allowed in a function (off by default) + "no-bitwise": 0, // disallow use of bitwise operators (off by default) + //"no-plusplus": 2, // disallow use of unary operators, ++ and -- (off by default) + } +} diff --git a/blue_modules/slip39/CHANGELOG.md b/blue_modules/slip39/CHANGELOG.md new file mode 100644 index 000000000..f1df46012 --- /dev/null +++ b/blue_modules/slip39/CHANGELOG.md @@ -0,0 +1,25 @@ +v0.1.0 +* Initial release + +v0.1.1 +* Code clean up and addes some unit tests + +v0.1.2 +* Added length to encodeBigInt() + +v0.1.5-dev.1 +* Bumped version, changed versioning format + +v0.1.5 +* Bumped version +* Added nodejs.yml +* Merge pull requests from different contributors + +v0.1.6 +* Fixed ilap/slip39-js#12 +* Some cosmetic fixes + +v0.1.7 +* Merge pull requests from different contributors +* Fixed ilap/slip39-js#14 +* Fixed ilap/slip39-js#18 diff --git a/blue_modules/slip39/LICENSE b/blue_modules/slip39/LICENSE new file mode 100644 index 000000000..4a356fdc1 --- /dev/null +++ b/blue_modules/slip39/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Pal Dorogi "ilap" + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/blue_modules/slip39/README.md b/blue_modules/slip39/README.md new file mode 100644 index 000000000..680f06f9d --- /dev/null +++ b/blue_modules/slip39/README.md @@ -0,0 +1,261 @@ +# Bluewallet + +It's original [slip39](https://github.com/ilap/slip39-js/) but compiled with `babel-plugin-transform-bigint` to replace js `BigInt` with `JSBI`. + +To update: +- sync src folder +- run `npm build` + + +# SLIP39 + +[![npm](https://img.shields.io/npm/v/slip39.svg)](https://www.npmjs.org/package/slip39) + + +The javascript implementation of the [SLIP39](https://github.com/satoshilabs/slips/blob/master/slip-0039.md) for Shamir's Secret-Sharing for Mnemonic Codes. + +The code based on my [Dart implementation of SLIP-0039](https://github.com/ilap/slip39-dart/). + +# DISCLAIMER + +This project is still in early development phase. Use it at your own risk. + +## Description + + This SLIP39 implementation uses a 3 level height (l=3) of a 16 degree (d=16) tree (T), which is represented as an array of the level two nodes (groups, G). + + The degree (d) and the level (l) of the tree are 16 and 3 respectively, + which means that max d^(l-1), i.e. 16^2, leaf nodes (M) can be in a complete tree (or forest). + + The first level (l=1) node of the tree is the the root (R), the level 2 ones are the `SSS` groups (Gs or group nodes) e.g. `[G0, ..., Gd]`. + + The last, the third, level nodes are the only leafs (M, group members) which contains the generated mnemonics. + + Every node has two values: + - the N and + - M i.e. n(N,M). + + Whihc means, that N (`threshold`) number of M children are required to reconstruct the node's secret. + +## Format + +The tree's human friendly array representation only uses the group (l=2) nodes as arrays. +For example. : ``` [[1,1], [1,1], [3,5], [2,6]]``` +The group's first parameter is the `N` (group threshold) while the second is the `M`, the number of members in the group. See, and example in [Using](#Using). + +## Installing + +``` +npm install slip39 + +``` + +## Using +See `example/main.js` + + ``` javascript +const slip39 = require('../src/slip39.js'); +const assert = require('assert'); +// threshold (N) number of group shares required to reconstruct the master secret. +const threshold = 2; +const masterSecret = 'ABCDEFGHIJKLMNOP'.slip39EncodeHex(); +const passphrase = 'TREZOR'; + +/** + * 4 groups shares. + * = two for Alice + * = one for friends and + * = one for family members + * Two of these group shares are required to reconstruct the master secret. + */ +const groups = [ + // Alice group shares. 1 is enough to reconstruct a group share, + // therefore she needs at least two group shares to be reconstructed, + [1, 1], + [1, 1], + // 3 of 5 Friends' shares are required to reconstruct this group share + [3, 5], + // 2 of 6 Family's shares are required to reconstruct this group share + [2, 6] +]; + +const slip = slip39.fromArray({ + masterSecret: masterSecret, + passphrase: passphrase, + threshold: threshold, + groups: groups +}); + +// One of Alice's share +const aliceShare = slip.fromPath('r/0').mnemonics; + +// and any two of family's shares. +const familyShares = slip.fromPath('r/3/1').mnemonics + .concat(slip.fromPath('r/3/3').mnemonics); + +const allShares = aliceShare.concat(familyShares); + +console.log('Shares used for restoring the master secret:'); +allShares.forEach((s) => console.log(s)); + +const recoveredSecret = slip39.recoverSecret(allShares, passphrase); +console.log('Master secret: ' + masterSecret.slip39DecodeHex()); +console.log('Recovered one: ' + recoveredSecret.slip39DecodeHex()); +assert(masterSecret.slip39DecodeHex() === recoveredSecret.slip39DecodeHex()); +``` + +## Testing + +``` bash + $ npm install + $ npm test + + Basic Tests + Test threshold 1 with 5 of 7 shares of a group combinations + ✓ Test combination 0 1 2 3 4. + ✓ Test combination 0 1 2 3 5. + ✓ Test combination 0 1 2 3 6. + ✓ Test combination 0 1 2 4 5. + ✓ Test combination 0 1 2 4 6. + ✓ Test combination 0 1 2 5 6. + ✓ Test combination 0 1 3 4 5. + ✓ Test combination 0 1 3 4 6. + ✓ Test combination 0 1 3 5 6. + ✓ Test combination 0 1 4 5 6. + ✓ Test combination 0 2 3 4 5. + ✓ Test combination 0 2 3 4 6. + ✓ Test combination 0 2 3 5 6. + ✓ Test combination 0 2 4 5 6. + ✓ Test combination 0 3 4 5 6. + ✓ Test combination 1 2 3 4 5. + ✓ Test combination 1 2 3 4 6. + ✓ Test combination 1 2 3 5 6. + ✓ Test combination 1 2 4 5 6. + ✓ Test combination 1 3 4 5 6. + ✓ Test combination 2 3 4 5 6. + Test passhrase + ✓ should return valid mastersecret when user submits valid passphrse + ✓ should NOT return valid mastersecret when user submits invalid passphrse + ✓ should return valid mastersecret when user does not submit passphrse + Test iteration exponent + ✓ should return valid mastersecret when user apply valid iteration exponent (44ms) + ✓ should throw an Error when user submits invalid iteration exponent + + Group Shares Tests + Test all valid combinations of mnemonics + ✓ should return the valid mastersecret when valid mnemonics used for recovery + Original test vectors Tests + ✓ 1. Valid mnemonic without sharing (128 bits) + ✓ 2. Mnemonic with invalid checksum (128 bits) + ✓ 3. Mnemonic with invalid padding (128 bits) + ✓ 4. Basic sharing 2-of-3 (128 bits) + ✓ 5. Basic sharing 2-of-3 (128 bits) + ✓ 6. Mnemonics with different identifiers (128 bits) + ✓ 7. Mnemonics with different iteration exponents (128 bits) + ✓ 8. Mnemonics with mismatching group thresholds (128 bits) + ✓ 9. Mnemonics with mismatching group counts (128 bits) + ✓ 10. Mnemonics with greater group threshold than group counts (128 bits) + ✓ 11. Mnemonics with duplicate member indices (128 bits) + ✓ 12. Mnemonics with mismatching member thresholds (128 bits) + ✓ 13. Mnemonics giving an invalid digest (128 bits) + ✓ 14. Insufficient number of groups (128 bits, case 1) + ✓ 15. Insufficient number of groups (128 bits, case 2) + ✓ 16. Threshold number of groups, but insufficient number of members in one group (128 bits) + ✓ 17. Threshold number of groups and members in each group (128 bits, case 1) + ✓ 18. Threshold number of groups and members in each group (128 bits, case 2) + ✓ 19. Threshold number of groups and members in each group (128 bits, case 3) + ✓ 20. Valid mnemonic without sharing (256 bits) + ✓ 21. Mnemonic with invalid checksum (256 bits) + ✓ 22. Mnemonic with invalid padding (256 bits) + ✓ 23. Basic sharing 2-of-3 (256 bits) + ✓ 24. Basic sharing 2-of-3 (256 bits) + ✓ 25. Mnemonics with different identifiers (256 bits) + ✓ 26. Mnemonics with different iteration exponents (256 bits) + ✓ 27. Mnemonics with mismatching group thresholds (256 bits) + ✓ 28. Mnemonics with mismatching group counts (256 bits) + ✓ 29. Mnemonics with greater group threshold than group counts (256 bits) + ✓ 30. Mnemonics with duplicate member indices (256 bits) + ✓ 31. Mnemonics with mismatching member thresholds (256 bits) + ✓ 32. Mnemonics giving an invalid digest (256 bits) + ✓ 33. Insufficient number of groups (256 bits, case 1) + ✓ 34. Insufficient number of groups (256 bits, case 2) + ✓ 35. Threshold number of groups, but insufficient number of members in one group (256 bits) + ✓ 36. Threshold number of groups and members in each group (256 bits, case 1) + ✓ 37. Threshold number of groups and members in each group (256 bits, case 2) + ✓ 38. Threshold number of groups and members in each group (256 bits, case 3) + ✓ 39. Mnemonic with insufficient length + ✓ 40. Mnemonic with invalid master secret length + Invalid Shares + ✓ Short master secret + ✓ Odd length master secret + ✓ Group threshold exceeds number of groups + ✓ Invalid group threshold. + ✓ Member threshold exceeds number of members + ✓ Invalid member threshold + ✓ Group with multiple members and threshold 1 + + + 74 passing (477ms) + +``` + +## TODOS + +- [x] Add unit tests. +- [x] Test with the reference code's test vectors. +- [ ] Refactor the helpers to different helper classes e.g. `CryptoHelper()`, `ShamirHelper()` etc. +- [ ] Add `JSON` representation, see [JSON representation](#json-representation) below. +- [ ] Refactor to much simpler code. + +### JSON Representation + +``` json + { + "name": "Slip39", + "threshold": 2, + "shares": [ + { + "name": "My Primary", + "threshold": 1, + "shares": [ + "Primary" + ] + }, + { + "name": "My Secondary", + "threshold": 1, + "shares": [ + "Secondary" + ] + }, + { + "name": "Friends", + "threshold": 3, + "shares": [ + "Alice", + "Bob", + "Charlie", + "David", + "Erin" + ] + }, + { + "name": "Family", + "threshold": 2, + "shares": [ + "Adam", + "Brenda", + "Carol", + "Dan", + "Edward", + "Frank" + ] + } + ] +} +``` +# LICENSE + +CopyRight (c) 2019 Pal Dorogi `"iLap"` + +[MIT License](LICENSE) diff --git a/blue_modules/slip39/babel.config.js b/blue_modules/slip39/babel.config.js new file mode 100644 index 000000000..d53544472 --- /dev/null +++ b/blue_modules/slip39/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + "plugins": [ + "babel-plugin-transform-bigint", + ] +} diff --git a/blue_modules/slip39/dist/slip39.js b/blue_modules/slip39/dist/slip39.js new file mode 100644 index 000000000..5bd1aca37 --- /dev/null +++ b/blue_modules/slip39/dist/slip39.js @@ -0,0 +1,236 @@ +var maybeJSBI = { + BigInt: function BigInt(a) { + return JSBI.BigInt(a); + }, + toNumber: function toNumber(a) { + return typeof a === "object" ? JSBI.toNumber(a) : Number(a); + }, + add: function add(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.add(a, b) : a + b; + }, + subtract: function subtract(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.subtract(a, b) : a - b; + }, + multiply: function multiply(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.multiply(a, b) : a * b; + }, + divide: function divide(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.divide(a, b) : a / b; + }, + remainder: function remainder(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.remainder(a, b) : a % b; + }, + exponentiate: function exponentiate(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.exponentiate(a, b) : typeof a === "bigint" && typeof b === "bigint" ? new Function("a**b", "a", "b")(a, b) : Math.pow(a, b); + }, + leftShift: function leftShift(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.leftShift(a, b) : a << b; + }, + signedRightShift: function signedRightShift(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.signedRightShift(a, b) : a >> b; + }, + bitwiseAnd: function bitwiseAnd(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.bitwiseAnd(a, b) : a & b; + }, + bitwiseOr: function bitwiseOr(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.bitwiseOr(a, b) : a | b; + }, + bitwiseXor: function bitwiseXor(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.bitwiseXor(a, b) : a ^ b; + }, + lessThan: function lessThan(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.lessThan(a, b) : a < b; + }, + greaterThan: function greaterThan(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.greaterThan(a, b) : a > b; + }, + lessThanOrEqual: function lessOrEqualThan(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.lessThanOrEqual(a, b) : a <= b; + }, + greaterThanOrEqual: function greaterOrEqualThan(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.greaterThanOrEqual(a, b) : a >= b; + }, + equal: function equal(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.equal(a, b) : a === b; + }, + notEqual: function notEqual(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.notEqual(a, b) : a !== b; + }, + unaryMinus: function unaryMinus(a) { + return typeof a === "object" ? JSBI.unaryMinus(a) : -a; + }, + bitwiseNot: function bitwiseNot(a) { + return typeof a === "object" ? JSBI.bitwiseNot(a) : ~a; + } +}; +const JSBI = require("jsbi/dist/jsbi-cjs.js"); + +/* eslint-disable radix */ +const slipHelper = require('./slip39_helper.js'); + +const MAX_DEPTH = 2; +/** + * Slip39Node + * For root node, description refers to the whole set's title e.g. "Hardware wallet X SSSS shares" + * For children nodes, description refers to the group e.g. "Family group: mom, dad, sister, wife" + */ + +class Slip39Node { + constructor(index = 0, description = '', mnemonic = '', children = []) { + this.index = index; + this.description = description; + this.mnemonic = mnemonic; + this.children = children; + } + + get mnemonics() { + if (this.children.length === 0) { + return [this.mnemonic]; + } + + const result = this.children.reduce((prev, item) => { + return prev.concat(item.mnemonics); + }, []); + return result; + } + +} // +// The javascript implementation of the SLIP-0039: Shamir's Secret-Sharing for Mnemonic Codes +// see: https://github.com/satoshilabs/slips/blob/master/slip-0039.md) +// + + +class Slip39 { + constructor({ + iterationExponent = 0, + identifier, + groupCount, + groupThreshold + } = {}) { + this.iterationExponent = iterationExponent; + this.identifier = identifier; + this.groupCount = groupCount; + this.groupThreshold = groupThreshold; + } + + static fromArray(masterSecret, { + passphrase = '', + threshold = 1, + groups = [[1, 1, 'Default 1-of-1 group share']], + iterationExponent = 0, + title = 'My default slip39 shares' + } = {}) { + if (masterSecret.length * 8 < slipHelper.MIN_ENTROPY_BITS) { + throw Error(`The length of the master secret (${masterSecret.length} bytes) must be at least ${slipHelper.bitsToBytes(slipHelper.MIN_ENTROPY_BITS)} bytes.`); + } + + if (masterSecret.length % 2 !== 0) { + throw Error('The length of the master secret in bytes must be an even number.'); + } + + if (!/^[\x20-\x7E]*$/.test(passphrase)) { + throw Error('The passphrase must contain only printable ASCII characters (code points 32-126).'); + } + + if (maybeJSBI.greaterThan(threshold, groups.length)) { + throw Error(`The requested group threshold (${threshold}) must not exceed the number of groups (${groups.length}).`); + } + + groups.forEach(item => { + if (item[0] === 1 && item[1] > 1) { + throw Error(`Creating multiple member shares with member threshold 1 is not allowed. Use 1-of-1 member sharing instead. ${groups.join()}`); + } + }); + const identifier = slipHelper.generateIdentifier(); + const slip = new Slip39({ + iterationExponent: iterationExponent, + identifier: identifier, + groupCount: groups.length, + groupThreshold: threshold + }); + const encryptedMasterSecret = slipHelper.crypt(masterSecret, passphrase, iterationExponent, slip.identifier); + const root = slip.buildRecursive(new Slip39Node(0, title), groups, encryptedMasterSecret, threshold); + slip.root = root; + return slip; + } + + buildRecursive(currentNode, nodes, secret, threshold, index) { + // It means it's a leaf. + if (nodes.length === 0) { + const mnemonic = slipHelper.encodeMnemonic(this.identifier, this.iterationExponent, index, this.groupThreshold, this.groupCount, currentNode.index, threshold, secret); + currentNode.mnemonic = mnemonic; + return currentNode; + } + + const secretShares = slipHelper.splitSecret(threshold, nodes.length, secret); + let children = []; + let idx = 0; + nodes.forEach(item => { + // n=threshold + const n = item[0]; // m=members + + const m = item[1]; // d=description + + const d = item[2] || ''; // Generate leaf members, means their `m` is `0` + + const members = Array().slip39Generate(m, () => [n, 0, d]); + const node = new Slip39Node(idx, d); + const branch = this.buildRecursive(node, members, secretShares[idx], n, currentNode.index); + children = children.concat(branch); + idx = idx + 1; + }); + currentNode.children = children; + return currentNode; + } + + static recoverSecret(mnemonics, passphrase) { + return slipHelper.combineMnemonics(mnemonics, passphrase); + } + + static validateMnemonic(mnemonic) { + return slipHelper.validateMnemonic(mnemonic); + } + + fromPath(path) { + this.validatePath(path); + const children = this.parseChildren(path); + + if (typeof children === 'undefined' || children.length === 0) { + return this.root; + } + + return children.reduce((prev, childNumber) => { + let childrenLen = prev.children.length; + + if (childNumber >= childrenLen) { + throw new Error(`The path index (${childNumber}) exceeds the children index (${childrenLen - 1}).`); + } + + return prev.children[childNumber]; + }, this.root); + } + + validatePath(path) { + if (!path.match(/(^r)(\/\d{1,2}){0,2}$/)) { + throw new Error('Expected valid path e.g. "r/0/0".'); + } + + const depth = path.split('/'); + const pathLength = depth.length - 1; + + if (pathLength > MAX_DEPTH) { + throw new Error(`Path\'s (${path}) max depth (${MAX_DEPTH}) is exceeded (${pathLength}).`); + } + } + + parseChildren(path) { + const splitted = path.split('/').slice(1); + const result = splitted.map(pathFragment => { + return parseInt(pathFragment); + }); + return result; + } + +} + +exports = module.exports = Slip39; \ No newline at end of file diff --git a/blue_modules/slip39/dist/slip39_helper.js b/blue_modules/slip39/dist/slip39_helper.js new file mode 100644 index 000000000..e8a91a9ac --- /dev/null +++ b/blue_modules/slip39/dist/slip39_helper.js @@ -0,0 +1,686 @@ +var maybeJSBI = { + BigInt: function BigInt(a) { + return JSBI.BigInt(a); + }, + toNumber: function toNumber(a) { + return typeof a === "object" ? JSBI.toNumber(a) : Number(a); + }, + add: function add(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.add(a, b) : a + b; + }, + subtract: function subtract(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.subtract(a, b) : a - b; + }, + multiply: function multiply(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.multiply(a, b) : a * b; + }, + divide: function divide(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.divide(a, b) : a / b; + }, + remainder: function remainder(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.remainder(a, b) : a % b; + }, + exponentiate: function exponentiate(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.exponentiate(a, b) : typeof a === "bigint" && typeof b === "bigint" ? new Function("a**b", "a", "b")(a, b) : Math.pow(a, b); + }, + leftShift: function leftShift(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.leftShift(a, b) : a << b; + }, + signedRightShift: function signedRightShift(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.signedRightShift(a, b) : a >> b; + }, + bitwiseAnd: function bitwiseAnd(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.bitwiseAnd(a, b) : a & b; + }, + bitwiseOr: function bitwiseOr(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.bitwiseOr(a, b) : a | b; + }, + bitwiseXor: function bitwiseXor(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.bitwiseXor(a, b) : a ^ b; + }, + lessThan: function lessThan(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.lessThan(a, b) : a < b; + }, + greaterThan: function greaterThan(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.greaterThan(a, b) : a > b; + }, + lessThanOrEqual: function lessOrEqualThan(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.lessThanOrEqual(a, b) : a <= b; + }, + greaterThanOrEqual: function greaterOrEqualThan(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.greaterThanOrEqual(a, b) : a >= b; + }, + equal: function equal(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.equal(a, b) : a === b; + }, + notEqual: function notEqual(a, b) { + return typeof a === "object" && typeof b === "object" ? JSBI.notEqual(a, b) : a !== b; + }, + unaryMinus: function unaryMinus(a) { + return typeof a === "object" ? JSBI.unaryMinus(a) : -a; + }, + bitwiseNot: function bitwiseNot(a) { + return typeof a === "object" ? JSBI.bitwiseNot(a) : ~a; + } +}; +const JSBI = require("jsbi/dist/jsbi-cjs.js"); + +/* eslint-disable no-array-constructor */ +const pbkdf2 = require('pbkdf2'); + +const createHmac = require('create-hmac'); + +const randombytes = require('randombytes'); // The length of the radix in bits. + + +const RADIX_BITS = 10; // The length of the random identifier in bits. + +const ID_BITS_LENGTH = 15; // The length of the iteration exponent in bits. + +const ITERATION_EXP_BITS_LENGTH = 5; // The length of the random identifier and iteration exponent in words. + +const ITERATION_EXP_WORDS_LENGTH = parseInt((ID_BITS_LENGTH + ITERATION_EXP_BITS_LENGTH + RADIX_BITS - 1) / RADIX_BITS, 10); // The maximum iteration exponent + +const MAX_ITERATION_EXP = Math.pow(2, ITERATION_EXP_BITS_LENGTH); // The maximum number of shares that can be created. + +const MAX_SHARE_COUNT = 16; // The length of the RS1024 checksum in words. + +const CHECKSUM_WORDS_LENGTH = 3; // The length of the digest of the shared secret in bytes. + +const DIGEST_LENGTH = 4; // The customization string used in the RS1024 checksum and in the PBKDF2 salt. + +const SALT_STRING = 'shamir'; // The minimum allowed entropy of the master secret. + +const MIN_ENTROPY_BITS = 128; // The minimum allowed length of the mnemonic in words. + +const METADATA_WORDS_LENGTH = ITERATION_EXP_WORDS_LENGTH + 2 + CHECKSUM_WORDS_LENGTH; // The length of the mnemonic in words without the share value. + +const MNEMONICS_WORDS_LENGTH = parseInt(METADATA_WORDS_LENGTH + (MIN_ENTROPY_BITS + RADIX_BITS - 1) / RADIX_BITS, 10); // The minimum number of iterations to use in PBKDF2. + +const ITERATION_COUNT = 10000; // The number of rounds to use in the Feistel cipher. + +const ROUND_COUNT = 4; // The index of the share containing the digest of the shared secret. + +const DIGEST_INDEX = 254; // The index of the share containing the shared secret. + +const SECRET_INDEX = 255; // +// Helper functions for SLIP39 implementation. +// + +String.prototype.slip39EncodeHex = function () { + let bytes = []; + + for (let i = 0; i < this.length; ++i) { + bytes.push(this.charCodeAt(i)); + } + + return bytes; +}; + +Array.prototype.slip39DecodeHex = function () { + let str = []; + const hex = this.toString().split(','); + + for (let i = 0; i < hex.length; i++) { + str.push(String.fromCharCode(hex[i])); + } + + return str.toString().replace(/,/g, ''); +}; + +Array.prototype.slip39Generate = function (m, v = _ => _) { + let n = m || this.length; + + for (let i = 0; i < n; i++) { + this[i] = v(i); + } + + return this; +}; + +Array.prototype.toHexString = function () { + return Array.prototype.map.call(this, function (byte) { + return ('0' + (byte & 0xFF).toString(16)).slice(-2); + }).join(''); +}; + +Array.prototype.toByteArray = function (hexString) { + for (let i = 0; i < hexString.length; i = i + 2) { + this.push(parseInt(hexString.substr(i, 2), 16)); + } + + return this; +}; + +const BIGINT_WORD_BITS = JSBI.BigInt(8); + +function decodeBigInt(bytes) { + let result = JSBI.BigInt(0); + + for (let i = 0; i < bytes.length; i++) { + let b = JSBI.BigInt(bytes[bytes.length - i - 1]); + result = JSBI.add(result, JSBI.leftShift(b, JSBI.multiply(BIGINT_WORD_BITS, JSBI.BigInt(i)))); + } + + return result; +} + +function encodeBigInt(number, paddedLength = 0) { + let num = number; + const BYTE_MASK = JSBI.BigInt(0xff); + const BIGINT_ZERO = JSBI.BigInt(0); + let result = new Array(0); + + while (maybeJSBI.greaterThan(num, BIGINT_ZERO)) { + let i = parseInt(maybeJSBI.bitwiseAnd(num, BYTE_MASK), 10); + result.unshift(i); + num = maybeJSBI.signedRightShift(num, BIGINT_WORD_BITS); + } // Zero padding to the length + + + for (let i = result.length; maybeJSBI.lessThan(i, paddedLength); _x = i, i = maybeJSBI.add(i, maybeJSBI.BigInt(1)), _x) { + var _x; + + result.unshift(0); + } + + if (paddedLength !== 0 && maybeJSBI.greaterThan(result.length, paddedLength)) { + throw new Error(`Error in encoding BigInt value, expected less than ${paddedLength} length value, got ${result.length}`); + } + + return result; +} + +function bitsToBytes(n) { + const res = (n + 7) / 8; + const b = parseInt(res, RADIX_BITS); + return b; +} + +function bitsToWords(n) { + const res = (n + RADIX_BITS - 1) / RADIX_BITS; + const b = parseInt(res, RADIX_BITS); + return b; +} // +// Returns a randomly generated integer in the range 0, ... , 2**ID_LENGTH_BITS - 1. +// + + +function randomBytes(length = 32) { + let randoms = randombytes(length); + return Array.prototype.slice.call(randoms, 0); +} // +// The round function used internally by the Feistel cipher. +// + + +function roundFunction(round, passphrase, exp, salt, secret) { + const saltedSecret = salt.concat(secret); + const roundedPhrase = [round].concat(passphrase); + const count = (ITERATION_COUNT << exp) / ROUND_COUNT; + const key = pbkdf2.pbkdf2Sync(Buffer.from(roundedPhrase), Buffer.from(saltedSecret), count, secret.length, 'sha256'); + return Array.prototype.slice.call(key, 0); +} + +function crypt(masterSecret, passphrase, iterationExponent, identifier, encrypt = true) { + // Iteration exponent validated here. + if (iterationExponent < 0 || iterationExponent > MAX_ITERATION_EXP) { + throw Error(`Invalid iteration exponent (${iterationExponent}). Expected between 0 and ${MAX_ITERATION_EXP}`); + } + + let IL = masterSecret.slice().slice(0, masterSecret.length / 2); + let IR = masterSecret.slice().slice(masterSecret.length / 2); + const pwd = passphrase.slip39EncodeHex(); + const salt = getSalt(identifier); + let range = Array().slip39Generate(ROUND_COUNT); + range = encrypt ? range : range.reverse(); + range.forEach(round => { + const f = roundFunction(round, pwd, iterationExponent, salt, IR); + const t = xor(IL, f); + IL = IR; + IR = t; + }); + return IR.concat(IL); +} + +function createDigest(randomData, sharedSecret) { + const hmac = createHmac('sha256', Buffer.from(randomData)); + hmac.update(Buffer.from(sharedSecret)); + let result = hmac.digest(); + result = result.slice(0, 4); + return Array.prototype.slice.call(result, 0); +} + +function splitSecret(threshold, shareCount, sharedSecret) { + if (threshold <= 0) { + throw Error(`The requested threshold (${threshold}) must be a positive integer.`); + } + + if (threshold > shareCount) { + throw Error(`The requested threshold (${threshold}) must not exceed the number of shares (${shareCount}).`); + } + + if (shareCount > MAX_SHARE_COUNT) { + throw Error(`The requested number of shares (${shareCount}) must not exceed ${MAX_SHARE_COUNT}.`); + } // If the threshold is 1, then the digest of the shared secret is not used. + + + if (threshold === 1) { + return Array().slip39Generate(shareCount, () => sharedSecret); + } + + const randomShareCount = threshold - 2; + const randomPart = randomBytes(sharedSecret.length - DIGEST_LENGTH); + const digest = createDigest(randomPart, sharedSecret); + let baseShares = new Map(); + let shares = []; + + if (randomShareCount) { + shares = Array().slip39Generate(randomShareCount, () => randomBytes(sharedSecret.length)); + shares.forEach((item, idx) => { + baseShares.set(idx, item); + }); + } + + baseShares.set(DIGEST_INDEX, digest.concat(randomPart)); + baseShares.set(SECRET_INDEX, sharedSecret); + + for (let i = randomShareCount; i < shareCount; i++) { + const rr = interpolate(baseShares, i); + shares.push(rr); + } + + return shares; +} // +// Returns a randomly generated integer in the range 0, ... , 2**ID_BITS_LENGTH - 1. +// + + +function generateIdentifier() { + const byte = bitsToBytes(ID_BITS_LENGTH); + const bits = ID_BITS_LENGTH % 8; + const identifier = randomBytes(byte); + identifier[0] = identifier[0] & (1 << bits) - 1; + return identifier; +} + +function xor(a, b) { + if (maybeJSBI.notEqual(a.length, b.length)) { + throw new Error(`Invalid padding in mnemonic or insufficient length of mnemonics (${a.length} or ${b.length})`); + } + + return Array().slip39Generate(a.length, i => maybeJSBI.bitwiseXor(a[i], b[i])); +} + +function getSalt(identifier) { + const salt = SALT_STRING.slip39EncodeHex(); + return salt.concat(identifier); +} + +function interpolate(shares, x) { + let xCoord = new Set(shares.keys()); + let arr = Array.from(shares.values(), v => v.length); + let sharesValueLengths = new Set(arr); + + if (sharesValueLengths.size !== 1) { + throw new Error('Invalid set of shares. All share values must have the same length.'); + } + + if (xCoord.has(x)) { + shares.forEach((v, k) => { + if (maybeJSBI.equal(k, x)) { + return v; + } + }); + } // Logarithm of the product of (x_i - x) for i = 1, ... , k. + + + let logProd = 0; + shares.forEach((v, k) => { + logProd = logProd + LOG_TABLE[maybeJSBI.bitwiseXor(k, x)]; + }); + let results = Array().slip39Generate(sharesValueLengths.values().next().value, () => 0); + shares.forEach((v, k) => { + // The logarithm of the Lagrange basis polynomial evaluated at x. + let sum = 0; + shares.forEach((vv, kk) => { + sum = sum + LOG_TABLE[maybeJSBI.bitwiseXor(k, kk)]; + }); // FIXME: -18 % 255 = 237. IT shoulud be 237 and not -18 as it's + // implemented in javascript. + + const basis = (logProd - LOG_TABLE[maybeJSBI.bitwiseXor(k, x)] - sum) % 255; + const logBasisEval = basis < 0 ? 255 + basis : basis; + v.forEach((item, idx) => { + const shareVal = item; + const intermediateSum = results[idx]; + const r = shareVal !== 0 ? EXP_TABLE[(LOG_TABLE[shareVal] + logBasisEval) % 255] : 0; + const res = maybeJSBI.bitwiseXor(intermediateSum, r); + results[idx] = res; + }); + }); + return results; +} + +function rs1024Polymod(data) { + const GEN = [0xE0E040, 0x1C1C080, 0x3838100, 0x7070200, 0xE0E0009, 0x1C0C2412, 0x38086C24, 0x3090FC48, 0x21B1F890, 0x3F3F120]; + let chk = 1; + data.forEach(byte => { + const b = chk >> 20; + chk = (chk & 0xFFFFF) << 10 ^ byte; + + for (let i = 0; i < 10; i++) { + let gen = (b >> i & 1) !== 0 ? GEN[i] : 0; + chk = chk ^ gen; + } + }); + return chk; +} + +function rs1024CreateChecksum(data) { + const values = SALT_STRING.slip39EncodeHex().concat(data).concat(Array().slip39Generate(CHECKSUM_WORDS_LENGTH, () => 0)); + const polymod = rs1024Polymod(values) ^ 1; + const result = Array().slip39Generate(CHECKSUM_WORDS_LENGTH, i => polymod >> 10 * i & 1023).reverse(); + return result; +} + +function rs1024VerifyChecksum(data) { + return rs1024Polymod(SALT_STRING.slip39EncodeHex().concat(data)) === 1; +} // +// Converts a list of base 1024 indices in big endian order to an integer value. +// + + +function intFromIndices(indices) { + let value = JSBI.BigInt(0); + const radix = JSBI.BigInt(Math.pow(2, RADIX_BITS)); + indices.forEach(index => { + value = JSBI.add(maybeJSBI.multiply(value, radix), JSBI.BigInt(index)); + }); + return value; +} // +// Converts a Big integer value to indices in big endian order. +// + + +function intToIndices(value, length, bits) { + const mask = JSBI.BigInt((1 << bits) - 1); + const result = Array().slip39Generate(length, i => parseInt(JSBI.bitwiseAnd(JSBI.signedRightShift(value, JSBI.multiply(JSBI.BigInt(i), JSBI.BigInt(bits))), mask), 10)); + return result.reverse(); +} + +function mnemonicFromIndices(indices) { + const result = indices.map(index => { + return WORD_LIST[index]; + }); + return result.toString().split(',').join(' '); +} + +function mnemonicToIndices(mnemonic) { + if (typeof mnemonic !== 'string') { + throw new Error(`Mnemonic expected to be typeof string with white space separated words. Instead found typeof ${typeof mnemonic}.`); + } + + const words = mnemonic.toLowerCase().split(' '); + const result = words.reduce((prev, item) => { + const index = WORD_LIST_MAP[item]; + + if (typeof index === 'undefined') { + throw new Error(`Invalid mnemonic word ${item}.`); + } + + return prev.concat(index); + }, []); + return result; +} + +function recoverSecret(threshold, shares) { + // If the threshold is 1, then the digest of the shared secret is not used. + if (threshold === 1) { + return shares.values().next().value; + } + + const sharedSecret = interpolate(shares, SECRET_INDEX); + const digestShare = interpolate(shares, DIGEST_INDEX); + const digest = digestShare.slice(0, DIGEST_LENGTH); + const randomPart = digestShare.slice(DIGEST_LENGTH); + const recoveredDigest = createDigest(randomPart, sharedSecret); + + if (!listsAreEqual(digest, recoveredDigest)) { + throw new Error('Invalid digest of the shared secret.'); + } + + return sharedSecret; +} // +// Combines mnemonic shares to obtain the master secret which was previously +// split using Shamir's secret sharing scheme. +// + + +function combineMnemonics(mnemonics, passphrase = '') { + if (mnemonics === null || mnemonics.length === 0) { + throw new Error('The list of mnemonics is empty.'); + } + + const decoded = decodeMnemonics(mnemonics); + const identifier = decoded.identifier; + const iterationExponent = decoded.iterationExponent; + const groupThreshold = decoded.groupThreshold; + const groupCount = decoded.groupCount; + const groups = decoded.groups; + + if (maybeJSBI.lessThan(groups.size, groupThreshold)) { + throw new Error(`Insufficient number of mnemonic groups (${groups.size}). The required number of groups is ${groupThreshold}.`); + } + + if (maybeJSBI.notEqual(groups.size, groupThreshold)) { + throw new Error(`Wrong number of mnemonic groups. Expected ${groupThreshold} groups, but ${groups.size} were provided.`); + } + + let allShares = new Map(); + groups.forEach((members, groupIndex) => { + const threshold = members.keys().next().value; + const shares = members.values().next().value; + + if (maybeJSBI.notEqual(shares.size, threshold)) { + const prefix = groupPrefix(identifier, iterationExponent, groupIndex, groupThreshold, groupCount); + throw new Error(`Wrong number of mnemonics. Expected ${threshold} mnemonics starting with "${mnemonicFromIndices(prefix)}", \n but ${shares.size} were provided.`); + } + + const recovered = recoverSecret(threshold, shares); + allShares.set(groupIndex, recovered); + }); + const ems = recoverSecret(groupThreshold, allShares); + const id = intToIndices(JSBI.BigInt(identifier), ITERATION_EXP_WORDS_LENGTH, 8); + const ms = crypt(ems, passphrase, iterationExponent, id, false); + return ms; +} + +function decodeMnemonics(mnemonics) { + if (!(mnemonics instanceof Array)) { + throw new Error('Mnemonics should be an array of strings'); + } + + const identifiers = new Set(); + const iterationExponents = new Set(); + const groupThresholds = new Set(); + const groupCounts = new Set(); + const groups = new Map(); + mnemonics.forEach(mnemonic => { + const decoded = decodeMnemonic(mnemonic); + identifiers.add(decoded.identifier); + iterationExponents.add(decoded.iterationExponent); + const groupIndex = decoded.groupIndex; + groupThresholds.add(decoded.groupThreshold); + groupCounts.add(decoded.groupCount); + const memberIndex = decoded.memberIndex; + const memberThreshold = decoded.memberThreshold; + const share = decoded.share; + const group = !groups.has(groupIndex) ? new Map() : groups.get(groupIndex); + const member = !group.has(memberThreshold) ? new Map() : group.get(memberThreshold); + member.set(memberIndex, share); + group.set(memberThreshold, member); + + if (group.size !== 1) { + throw new Error('Invalid set of mnemonics. All mnemonics in a group must have the same member threshold.'); + } + + groups.set(groupIndex, group); + }); + + if (identifiers.size !== 1 || iterationExponents.size !== 1) { + throw new Error(`Invalid set of mnemonics. All mnemonics must begin with the same ${ITERATION_EXP_WORDS_LENGTH} words.`); + } + + if (groupThresholds.size !== 1) { + throw new Error('Invalid set of mnemonics. All mnemonics must have the same group threshold.'); + } + + if (groupCounts.size !== 1) { + throw new Error('Invalid set of mnemonics. All mnemonics must have the same group count.'); + } + + return { + identifier: identifiers.values().next().value, + iterationExponent: iterationExponents.values().next().value, + groupThreshold: groupThresholds.values().next().value, + groupCount: groupCounts.values().next().value, + groups: groups + }; +} // +// Converts a share mnemonic to share data. +// + + +function decodeMnemonic(mnemonic) { + const data = mnemonicToIndices(mnemonic); + + if (maybeJSBI.lessThan(data.length, MNEMONICS_WORDS_LENGTH)) { + throw new Error(`Invalid mnemonic length. The length of each mnemonic must be at least ${MNEMONICS_WORDS_LENGTH} words.`); + } + + const paddingLen = RADIX_BITS * (data.length - METADATA_WORDS_LENGTH) % 16; + + if (paddingLen > 8) { + throw new Error('Invalid mnemonic length.'); + } + + if (!rs1024VerifyChecksum(data)) { + throw new Error('Invalid mnemonic checksum'); + } + + const idExpInt = parseInt(intFromIndices(data.slice(0, ITERATION_EXP_WORDS_LENGTH)), 10); + const identifier = idExpInt >> ITERATION_EXP_BITS_LENGTH; + const iterationExponent = idExpInt & (1 << ITERATION_EXP_BITS_LENGTH) - 1; + const tmp = intFromIndices(data.slice(ITERATION_EXP_WORDS_LENGTH, ITERATION_EXP_WORDS_LENGTH + 2)); + const indices = intToIndices(tmp, 5, 4); + const groupIndex = indices[0]; + const groupThreshold = indices[1]; + const groupCount = indices[2]; + const memberIndex = indices[3]; + const memberThreshold = indices[4]; + const valueData = data.slice(ITERATION_EXP_WORDS_LENGTH + 2, data.length - CHECKSUM_WORDS_LENGTH); + + if (groupCount < groupThreshold) { + throw new Error(`Invalid mnemonic: ${mnemonic}.\n Group threshold (${groupThreshold}) cannot be greater than group count (${groupCount}).`); + } + + const valueInt = intFromIndices(valueData); + + try { + const valueByteCount = bitsToBytes(RADIX_BITS * valueData.length - paddingLen); + const share = encodeBigInt(valueInt, valueByteCount); + return { + identifier: identifier, + iterationExponent: iterationExponent, + groupIndex: groupIndex, + groupThreshold: groupThreshold + 1, + groupCount: groupCount + 1, + memberIndex: memberIndex, + memberThreshold: memberThreshold + 1, + share: share + }; + } catch (e) { + throw new Error(`Invalid mnemonic padding (${e})`); + } +} + +function validateMnemonic(mnemonic) { + try { + decodeMnemonic(mnemonic); + return true; + } catch (error) { + return false; + } +} + +function groupPrefix(identifier, iterationExponent, groupIndex, groupThreshold, groupCount) { + const idExpInt = JSBI.BigInt((identifier << ITERATION_EXP_BITS_LENGTH) + iterationExponent); + const indc = intToIndices(idExpInt, ITERATION_EXP_WORDS_LENGTH, RADIX_BITS); + const indc2 = (groupIndex << 6) + (groupThreshold - 1 << 2) + (groupCount - 1 >> 2); + indc.push(indc2); + return indc; +} + +function listsAreEqual(a, b) { + if (a === null || b === null || maybeJSBI.notEqual(a.length, b.length)) { + return false; + } + + let i = 0; + return a.every(item => { + return maybeJSBI.equal(b[i++], item); + }); +} // +// Converts share data to a share mnemonic. +// + + +function encodeMnemonic(identifier, iterationExponent, groupIndex, groupThreshold, groupCount, memberIndex, memberThreshold, value) { + // Convert the share value from bytes to wordlist indices. + const valueWordCount = bitsToWords(value.length * 8); + const valueInt = decodeBigInt(value); + let newIdentifier = parseInt(decodeBigInt(identifier), 10); + const gp = groupPrefix(newIdentifier, iterationExponent, groupIndex, groupThreshold, groupCount); + const tp = intToIndices(valueInt, valueWordCount, RADIX_BITS); + const calc = ((groupCount - 1 & 3) << 8) + (memberIndex << 4) + (memberThreshold - 1); + gp.push(calc); + const shareData = gp.concat(tp); + const checksum = rs1024CreateChecksum(shareData); + return mnemonicFromIndices(shareData.concat(checksum)); +} // The precomputed exponent and log tables. +// ``` +// const exp = List.filled(255, 0) +// const log = List.filled(256, 0) +// const poly = 1 +// +// for (let i = 0; i < exp.length; i++) { +// exp[i] = poly +// log[poly] = i +// // Multiply poly by the polynomial x + 1. +// poly = (poly << 1) ^ poly +// // Reduce poly by x^8 + x^4 + x^3 + x + 1. +// if (poly & 0x100 === 0x100) poly ^= 0x11B +// } +// ``` + + +const EXP_TABLE = [1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246]; +const LOG_TABLE = [0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3, 100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193, 125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120, 101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142, 150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56, 102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16, 126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186, 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87, 175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232, 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160, 127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183, 204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157, 151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209, 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171, 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165, 103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7]; // +// SLIP39 wordlist +// + +const WORD_LIST = ['academic', 'acid', 'acne', 'acquire', 'acrobat', 'activity', 'actress', 'adapt', 'adequate', 'adjust', 'admit', 'adorn', 'adult', 'advance', 'advocate', 'afraid', 'again', 'agency', 'agree', 'aide', 'aircraft', 'airline', 'airport', 'ajar', 'alarm', 'album', 'alcohol', 'alien', 'alive', 'alpha', 'already', 'alto', 'aluminum', 'always', 'amazing', 'ambition', 'amount', 'amuse', 'analysis', 'anatomy', 'ancestor', 'ancient', 'angel', 'angry', 'animal', 'answer', 'antenna', 'anxiety', 'apart', 'aquatic', 'arcade', 'arena', 'argue', 'armed', 'artist', 'artwork', 'aspect', 'auction', 'august', 'aunt', 'average', 'aviation', 'avoid', 'award', 'away', 'axis', 'axle', 'beam', 'beard', 'beaver', 'become', 'bedroom', 'behavior', 'being', 'believe', 'belong', 'benefit', 'best', 'beyond', 'bike', 'biology', 'birthday', 'bishop', 'black', 'blanket', 'blessing', 'blimp', 'blind', 'blue', 'body', 'bolt', 'boring', 'born', 'both', 'boundary', 'bracelet', 'branch', 'brave', 'breathe', 'briefing', 'broken', 'brother', 'browser', 'bucket', 'budget', 'building', 'bulb', 'bulge', 'bumpy', 'bundle', 'burden', 'burning', 'busy', 'buyer', 'cage', 'calcium', 'camera', 'campus', 'canyon', 'capacity', 'capital', 'capture', 'carbon', 'cards', 'careful', 'cargo', 'carpet', 'carve', 'category', 'cause', 'ceiling', 'center', 'ceramic', 'champion', 'change', 'charity', 'check', 'chemical', 'chest', 'chew', 'chubby', 'cinema', 'civil', 'class', 'clay', 'cleanup', 'client', 'climate', 'clinic', 'clock', 'clogs', 'closet', 'clothes', 'club', 'cluster', 'coal', 'coastal', 'coding', 'column', 'company', 'corner', 'costume', 'counter', 'course', 'cover', 'cowboy', 'cradle', 'craft', 'crazy', 'credit', 'cricket', 'criminal', 'crisis', 'critical', 'crowd', 'crucial', 'crunch', 'crush', 'crystal', 'cubic', 'cultural', 'curious', 'curly', 'custody', 'cylinder', 'daisy', 'damage', 'dance', 'darkness', 'database', 'daughter', 'deadline', 'deal', 'debris', 'debut', 'decent', 'decision', 'declare', 'decorate', 'decrease', 'deliver', 'demand', 'density', 'deny', 'depart', 'depend', 'depict', 'deploy', 'describe', 'desert', 'desire', 'desktop', 'destroy', 'detailed', 'detect', 'device', 'devote', 'diagnose', 'dictate', 'diet', 'dilemma', 'diminish', 'dining', 'diploma', 'disaster', 'discuss', 'disease', 'dish', 'dismiss', 'display', 'distance', 'dive', 'divorce', 'document', 'domain', 'domestic', 'dominant', 'dough', 'downtown', 'dragon', 'dramatic', 'dream', 'dress', 'drift', 'drink', 'drove', 'drug', 'dryer', 'duckling', 'duke', 'duration', 'dwarf', 'dynamic', 'early', 'earth', 'easel', 'easy', 'echo', 'eclipse', 'ecology', 'edge', 'editor', 'educate', 'either', 'elbow', 'elder', 'election', 'elegant', 'element', 'elephant', 'elevator', 'elite', 'else', 'email', 'emerald', 'emission', 'emperor', 'emphasis', 'employer', 'empty', 'ending', 'endless', 'endorse', 'enemy', 'energy', 'enforce', 'engage', 'enjoy', 'enlarge', 'entrance', 'envelope', 'envy', 'epidemic', 'episode', 'equation', 'equip', 'eraser', 'erode', 'escape', 'estate', 'estimate', 'evaluate', 'evening', 'evidence', 'evil', 'evoke', 'exact', 'example', 'exceed', 'exchange', 'exclude', 'excuse', 'execute', 'exercise', 'exhaust', 'exotic', 'expand', 'expect', 'explain', 'express', 'extend', 'extra', 'eyebrow', 'facility', 'fact', 'failure', 'faint', 'fake', 'false', 'family', 'famous', 'fancy', 'fangs', 'fantasy', 'fatal', 'fatigue', 'favorite', 'fawn', 'fiber', 'fiction', 'filter', 'finance', 'findings', 'finger', 'firefly', 'firm', 'fiscal', 'fishing', 'fitness', 'flame', 'flash', 'flavor', 'flea', 'flexible', 'flip', 'float', 'floral', 'fluff', 'focus', 'forbid', 'force', 'forecast', 'forget', 'formal', 'fortune', 'forward', 'founder', 'fraction', 'fragment', 'frequent', 'freshman', 'friar', 'fridge', 'friendly', 'frost', 'froth', 'frozen', 'fumes', 'funding', 'furl', 'fused', 'galaxy', 'game', 'garbage', 'garden', 'garlic', 'gasoline', 'gather', 'general', 'genius', 'genre', 'genuine', 'geology', 'gesture', 'glad', 'glance', 'glasses', 'glen', 'glimpse', 'goat', 'golden', 'graduate', 'grant', 'grasp', 'gravity', 'gray', 'greatest', 'grief', 'grill', 'grin', 'grocery', 'gross', 'group', 'grownup', 'grumpy', 'guard', 'guest', 'guilt', 'guitar', 'gums', 'hairy', 'hamster', 'hand', 'hanger', 'harvest', 'have', 'havoc', 'hawk', 'hazard', 'headset', 'health', 'hearing', 'heat', 'helpful', 'herald', 'herd', 'hesitate', 'hobo', 'holiday', 'holy', 'home', 'hormone', 'hospital', 'hour', 'huge', 'human', 'humidity', 'hunting', 'husband', 'hush', 'husky', 'hybrid', 'idea', 'identify', 'idle', 'image', 'impact', 'imply', 'improve', 'impulse', 'include', 'income', 'increase', 'index', 'indicate', 'industry', 'infant', 'inform', 'inherit', 'injury', 'inmate', 'insect', 'inside', 'install', 'intend', 'intimate', 'invasion', 'involve', 'iris', 'island', 'isolate', 'item', 'ivory', 'jacket', 'jerky', 'jewelry', 'join', 'judicial', 'juice', 'jump', 'junction', 'junior', 'junk', 'jury', 'justice', 'kernel', 'keyboard', 'kidney', 'kind', 'kitchen', 'knife', 'knit', 'laden', 'ladle', 'ladybug', 'lair', 'lamp', 'language', 'large', 'laser', 'laundry', 'lawsuit', 'leader', 'leaf', 'learn', 'leaves', 'lecture', 'legal', 'legend', 'legs', 'lend', 'length', 'level', 'liberty', 'library', 'license', 'lift', 'likely', 'lilac', 'lily', 'lips', 'liquid', 'listen', 'literary', 'living', 'lizard', 'loan', 'lobe', 'location', 'losing', 'loud', 'loyalty', 'luck', 'lunar', 'lunch', 'lungs', 'luxury', 'lying', 'lyrics', 'machine', 'magazine', 'maiden', 'mailman', 'main', 'makeup', 'making', 'mama', 'manager', 'mandate', 'mansion', 'manual', 'marathon', 'march', 'market', 'marvel', 'mason', 'material', 'math', 'maximum', 'mayor', 'meaning', 'medal', 'medical', 'member', 'memory', 'mental', 'merchant', 'merit', 'method', 'metric', 'midst', 'mild', 'military', 'mineral', 'minister', 'miracle', 'mixed', 'mixture', 'mobile', 'modern', 'modify', 'moisture', 'moment', 'morning', 'mortgage', 'mother', 'mountain', 'mouse', 'move', 'much', 'mule', 'multiple', 'muscle', 'museum', 'music', 'mustang', 'nail', 'national', 'necklace', 'negative', 'nervous', 'network', 'news', 'nuclear', 'numb', 'numerous', 'nylon', 'oasis', 'obesity', 'object', 'observe', 'obtain', 'ocean', 'often', 'olympic', 'omit', 'oral', 'orange', 'orbit', 'order', 'ordinary', 'organize', 'ounce', 'oven', 'overall', 'owner', 'paces', 'pacific', 'package', 'paid', 'painting', 'pajamas', 'pancake', 'pants', 'papa', 'paper', 'parcel', 'parking', 'party', 'patent', 'patrol', 'payment', 'payroll', 'peaceful', 'peanut', 'peasant', 'pecan', 'penalty', 'pencil', 'percent', 'perfect', 'permit', 'petition', 'phantom', 'pharmacy', 'photo', 'phrase', 'physics', 'pickup', 'picture', 'piece', 'pile', 'pink', 'pipeline', 'pistol', 'pitch', 'plains', 'plan', 'plastic', 'platform', 'playoff', 'pleasure', 'plot', 'plunge', 'practice', 'prayer', 'preach', 'predator', 'pregnant', 'premium', 'prepare', 'presence', 'prevent', 'priest', 'primary', 'priority', 'prisoner', 'privacy', 'prize', 'problem', 'process', 'profile', 'program', 'promise', 'prospect', 'provide', 'prune', 'public', 'pulse', 'pumps', 'punish', 'puny', 'pupal', 'purchase', 'purple', 'python', 'quantity', 'quarter', 'quick', 'quiet', 'race', 'racism', 'radar', 'railroad', 'rainbow', 'raisin', 'random', 'ranked', 'rapids', 'raspy', 'reaction', 'realize', 'rebound', 'rebuild', 'recall', 'receiver', 'recover', 'regret', 'regular', 'reject', 'relate', 'remember', 'remind', 'remove', 'render', 'repair', 'repeat', 'replace', 'require', 'rescue', 'research', 'resident', 'response', 'result', 'retailer', 'retreat', 'reunion', 'revenue', 'review', 'reward', 'rhyme', 'rhythm', 'rich', 'rival', 'river', 'robin', 'rocky', 'romantic', 'romp', 'roster', 'round', 'royal', 'ruin', 'ruler', 'rumor', 'sack', 'safari', 'salary', 'salon', 'salt', 'satisfy', 'satoshi', 'saver', 'says', 'scandal', 'scared', 'scatter', 'scene', 'scholar', 'science', 'scout', 'scramble', 'screw', 'script', 'scroll', 'seafood', 'season', 'secret', 'security', 'segment', 'senior', 'shadow', 'shaft', 'shame', 'shaped', 'sharp', 'shelter', 'sheriff', 'short', 'should', 'shrimp', 'sidewalk', 'silent', 'silver', 'similar', 'simple', 'single', 'sister', 'skin', 'skunk', 'slap', 'slavery', 'sled', 'slice', 'slim', 'slow', 'slush', 'smart', 'smear', 'smell', 'smirk', 'smith', 'smoking', 'smug', 'snake', 'snapshot', 'sniff', 'society', 'software', 'soldier', 'solution', 'soul', 'source', 'space', 'spark', 'speak', 'species', 'spelling', 'spend', 'spew', 'spider', 'spill', 'spine', 'spirit', 'spit', 'spray', 'sprinkle', 'square', 'squeeze', 'stadium', 'staff', 'standard', 'starting', 'station', 'stay', 'steady', 'step', 'stick', 'stilt', 'story', 'strategy', 'strike', 'style', 'subject', 'submit', 'sugar', 'suitable', 'sunlight', 'superior', 'surface', 'surprise', 'survive', 'sweater', 'swimming', 'swing', 'switch', 'symbolic', 'sympathy', 'syndrome', 'system', 'tackle', 'tactics', 'tadpole', 'talent', 'task', 'taste', 'taught', 'taxi', 'teacher', 'teammate', 'teaspoon', 'temple', 'tenant', 'tendency', 'tension', 'terminal', 'testify', 'texture', 'thank', 'that', 'theater', 'theory', 'therapy', 'thorn', 'threaten', 'thumb', 'thunder', 'ticket', 'tidy', 'timber', 'timely', 'ting', 'tofu', 'together', 'tolerate', 'total', 'toxic', 'tracks', 'traffic', 'training', 'transfer', 'trash', 'traveler', 'treat', 'trend', 'trial', 'tricycle', 'trip', 'triumph', 'trouble', 'true', 'trust', 'twice', 'twin', 'type', 'typical', 'ugly', 'ultimate', 'umbrella', 'uncover', 'undergo', 'unfair', 'unfold', 'unhappy', 'union', 'universe', 'unkind', 'unknown', 'unusual', 'unwrap', 'upgrade', 'upstairs', 'username', 'usher', 'usual', 'valid', 'valuable', 'vampire', 'vanish', 'various', 'vegan', 'velvet', 'venture', 'verdict', 'verify', 'very', 'veteran', 'vexed', 'victim', 'video', 'view', 'vintage', 'violence', 'viral', 'visitor', 'visual', 'vitamins', 'vocal', 'voice', 'volume', 'voter', 'voting', 'walnut', 'warmth', 'warn', 'watch', 'wavy', 'wealthy', 'weapon', 'webcam', 'welcome', 'welfare', 'western', 'width', 'wildlife', 'window', 'wine', 'wireless', 'wisdom', 'withdraw', 'wits', 'wolf', 'woman', 'work', 'worthy', 'wrap', 'wrist', 'writing', 'wrote', 'year', 'yelp', 'yield', 'yoga', 'zero']; +const WORD_LIST_MAP = WORD_LIST.reduce((obj, val, idx) => { + obj[val] = idx; + return obj; +}, {}); +exports = module.exports = { + MIN_ENTROPY_BITS, + generateIdentifier, + encodeMnemonic, + validateMnemonic, + splitSecret, + combineMnemonics, + crypt, + bitsToBytes +}; \ No newline at end of file diff --git a/blue_modules/slip39/example/main.js b/blue_modules/slip39/example/main.js new file mode 100644 index 000000000..19e1c0233 --- /dev/null +++ b/blue_modules/slip39/example/main.js @@ -0,0 +1,122 @@ +const slip39 = require('../src/slip39.js'); +const assert = require('assert'); +// threshold (N) number of group-shares required to reconstruct the master secret. +const groupThreshold = 2; +const masterSecret = 'ABCDEFGHIJKLMNOP'.slip39EncodeHex(); +const passphrase = 'TREZOR'; + +function recover(groupShares, pass) { + const recoveredSecret = slip39.recoverSecret(groupShares, pass); + console.log('\tMaster secret: ' + masterSecret.slip39DecodeHex()); + console.log('\tRecovered one: ' + recoveredSecret.slip39DecodeHex()); + assert(masterSecret.slip39DecodeHex() === recoveredSecret.slip39DecodeHex()); +} + +function printShares(shares) { + shares.forEach((s, i) => console.log(`\t${i + 1}) ${s}`)); +} + +/** + * 4 groups shares: + * = two for Alice + * = one for friends and + * = one for family members + * Any two (see threshold) of these four group-shares are required to reconstruct the master secret + * i.e. to recover the master secret the goal is to reconstruct any 2-of-4 group-shares. + * To reconstruct each group share, we need at least N of its M member-shares. + * Thus all possible master secret recovery combinations: + * Case 1) [requires 1 person: Alice] Alice alone with her 1-of-1 member-shares reconstructs both the 1st and 2nd group-shares + * Case 2) [requires 4 persons: Alice + any 3 of her 5 friends] Alice with her 1-of-1 member-shares reconstructs 1st (or 2nd) group-share + any 3-of-5 friend member-shares reconstruct the 3rd group-share + * Case 3) [requires 3 persons: Alice + any 2 of her 6 family relatives] Alice with her 1-of-1 member-shares reconstructs 1st (or 2nd) group-share + any 2-of-6 family member-shares reconstruct the 4th group-share + * Case 4) [requires 5 persons: any 3 of her 5 friends + any 2 of her 6 family relatives] any 3-of-5 friend member-shares reconstruct the 3rd group-share + any 2-of-6 family member-shares reconstruct the 4th group-share + */ +const groups = [ + // Alice group-shares. 1 is enough to reconstruct a group-share, + // therefore she needs at least two group-shares to reconstruct the master secret. + [1, 1, 'Alice personal group share 1'], + [1, 1, 'Alice personal group share 2'], + // 3 of 5 Friends' shares are required to reconstruct this group-share + [3, 5, 'Friends group share for Bob, Charlie, Dave, Frank and Grace'], + // 2 of 6 Family's shares are required to reconstruct this group-share + [2, 6, 'Family group share for mom, dad, brother, sister and wife'] +]; + +const slip = slip39.fromArray(masterSecret, { + passphrase: passphrase, + threshold: groupThreshold, + groups: groups, + title: 'Slip39 example for 2-level SSSS' +}); + +let requiredGroupShares; +let aliceBothGroupShares; +let aliceFirstGroupShare; +let aliceSecondGroupShare; +let friendGroupShares; +let familyGroupShares; + + +/* + * Example of Case 1 + */ +// The 1st, and only, member-share (member 0) of the 1st group-share (group 0) + the 1st, and only, member-share (member 0) of the 2nd group-share (group 1) +aliceBothGroupShares = slip.fromPath('r/0/0').mnemonics + .concat(slip.fromPath('r/1/0').mnemonics); + +requiredGroupShares = aliceBothGroupShares; + +console.log(`\n* Shares used by Alice alone for restoring the master secret (total of ${requiredGroupShares.length} member-shares):`); +printShares(requiredGroupShares); +recover(requiredGroupShares, passphrase); + +/* + * Example of Case 2 + */ +// The 1st, and only, member-share (member 0) of the 2nd group-share (group 1) +aliceSecondGroupShare = slip.fromPath('r/1/0').mnemonics; + +// ...plus the 3rd member-share (member 2) + the 4th member-share (member 3) + the 5th member-share (member 4) of the 3rd group-share (group 2) +friendGroupShares = slip.fromPath('r/2/2').mnemonics + .concat(slip.fromPath('r/2/3').mnemonics) + .concat(slip.fromPath('r/2/4').mnemonics); + +requiredGroupShares = aliceSecondGroupShare.concat(friendGroupShares); + +console.log(`\n* Shares used by Alice + 3 friends for restoring the master secret (total of ${requiredGroupShares.length} member-shares):`); +printShares(requiredGroupShares); +recover(requiredGroupShares, passphrase); + + +/* + * Example of Case 3 + */ +// The 1st, and only, member-share (member 0) of the 1st group-share (group 0) +aliceFirstGroupShare = slip.fromPath('r/0/0').mnemonics; + +// ...plus the 2nd member-share (member 1) + the 3rd member-share (member 2) of the 4th group-share (group 3) +familyGroupShares = slip.fromPath('r/3/1').mnemonics + .concat(slip.fromPath('r/3/2').mnemonics); + +requiredGroupShares = aliceFirstGroupShare.concat(familyGroupShares); + +console.log(`\n* Shares used by Alice + 2 family members for restoring the master secret (total of ${requiredGroupShares.length} member-shares):`); +printShares(requiredGroupShares); +recover(requiredGroupShares, passphrase); + +/* + * Example of Case 4 + */ +// The 3rd member-share (member 2) + the 4th member-share (member 3) + the 5th member-share (member 4) of the 3rd group-share (group 2) +friendGroupShares = slip.fromPath('r/2/2').mnemonics + .concat(slip.fromPath('r/2/3').mnemonics) + .concat(slip.fromPath('r/2/4').mnemonics); + +// ...plus the 2nd member-share (member 1) + the 3rd member-share (member 2) of the 4th group-share (group 3) +familyGroupShares = slip.fromPath('r/3/1').mnemonics + .concat(slip.fromPath('r/3/2').mnemonics); + +requiredGroupShares = friendGroupShares.concat(familyGroupShares); + +console.log(`\n* Shares used by 3 friends + 2 family members for restoring the master secret (total of ${requiredGroupShares.length} member-shares):`); +printShares(requiredGroupShares); +recover(requiredGroupShares, passphrase); diff --git a/blue_modules/slip39/jsbeautifyrc b/blue_modules/slip39/jsbeautifyrc new file mode 100644 index 000000000..7a3cb53df --- /dev/null +++ b/blue_modules/slip39/jsbeautifyrc @@ -0,0 +1,16 @@ +{ + "indent_with_tabs": false, + "indent_size": 2, + "max_preserve_newlines": 2, + "preserve_newlines": true, + "keep_array_indentation": true, + "break_chained_methods": true, + "wrap_line_length": 120, + "end_with_newline": true, + "brace_style": "collapse,preserve-inline", + "unformatted": ["a", "abbr", "area", "audio", "b", "bdi", "bdo", "br", "button", "canvas", "cite", "code", "data", + "datalist", "del", "dfn", "em", "embed", "i", "iframe", "img", "input", "ins", "kbd", "keygen", "label", "map", + "mark", "math", "meter", "noscript", "object", "output", "progress", "q", "ruby", "s", "samp", "select", "small", + "span", "strong", "sub", "sup", "template", "textarea", "time", "u", "var", "video", "wbr", "text", "acronym", + "address", "big", "dt", "ins", "small", "strike", "tt", "pre", "h1", "h2", "h3", "h4", "h5", "h6"] +} diff --git a/blue_modules/slip39/jsconfig.json b/blue_modules/slip39/jsconfig.json new file mode 100644 index 000000000..98f25cc1e --- /dev/null +++ b/blue_modules/slip39/jsconfig.json @@ -0,0 +1,9 @@ +{ + "exclude": [ + "node_modules", + "**/node_modules/*" + ], + "include": [ + "src/**/*" + ] +} diff --git a/blue_modules/slip39/package-lock.json b/blue_modules/slip39/package-lock.json new file mode 100644 index 000000000..aab5f8bae --- /dev/null +++ b/blue_modules/slip39/package-lock.json @@ -0,0 +1,2999 @@ +{ + "name": "slip39", + "version": "0.1.7", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/cli": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.13.14.tgz", + "integrity": "sha512-zmEFV8WBRsW+mPQumO1/4b34QNALBVReaiHJOkxhUsdo/AvYM62c+SKSuLi2aZ42t3ocK6OI0uwUXRvrIbREZw==", + "dev": true, + "requires": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents", + "chokidar": "^3.4.0", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.0.0", + "lodash": "^4.17.19", + "make-dir": "^2.1.0", + "slash": "^2.0.0", + "source-map": "^0.5.0" + } + }, + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/compat-data": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.15.tgz", + "integrity": "sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA==", + "dev": true + }, + "@babel/core": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.15.tgz", + "integrity": "sha512-6GXmNYeNjS2Uz+uls5jalOemgIhnTMeaXo+yBUA72kC2uX/8VW6XyhVIo2L8/q0goKQA3EVKx0KOQpVKSeWadQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-compilation-targets": "^7.13.13", + "@babel/helper-module-transforms": "^7.13.14", + "@babel/helpers": "^7.13.10", + "@babel/parser": "^7.13.15", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.15", + "@babel/types": "^7.13.14", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz", + "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==", + "dev": true, + "requires": { + "@babel/types": "^7.13.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.13.13", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz", + "integrity": "sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.12", + "@babel/helper-validator-option": "^7.12.17", + "browserslist": "^4.14.5", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", + "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "dev": true, + "requires": { + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-module-imports": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", + "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "dev": true, + "requires": { + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-module-transforms": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz", + "integrity": "sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.13.12", + "@babel/helper-replace-supers": "^7.13.12", + "@babel/helper-simple-access": "^7.13.12", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/helper-validator-identifier": "^7.12.11", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.13", + "@babel/types": "^7.13.14" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", + "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", + "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "dev": true + }, + "@babel/helper-replace-supers": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", + "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.13.12", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-simple-access": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", + "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", + "dev": true, + "requires": { + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", + "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.10.tgz", + "integrity": "sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==", + "dev": true, + "requires": { + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0" + } + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", + "dev": true + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/traverse": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz", + "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.13.15", + "@babel/types": "^7.13.14", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz", + "integrity": "sha512-+nb9vWloHNNMFHjGofEam3wopE3m1yuambrrd/fnPc+lFOMB9ROTqQlche9ByFWNkdNqfSgR/kkQtQ8DzEWt2w==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "optional": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "optional": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true, + "optional": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "optional": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true, + "optional": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "optional": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true, + "optional": true + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true, + "optional": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "optional": true + }, + "babel-plugin-transform-bigint": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-bigint/-/babel-plugin-transform-bigint-1.0.9.tgz", + "integrity": "sha512-Lf4X2LssZpl3XwvU9xr7HLMtZozGBzrh/0P1qEFktbiz24RI2qG6QF8VhGoWPynbYHKKGHYCjYwprrTS5Z/kkw==", + "dev": true, + "requires": { + "@babel/plugin-syntax-bigint": "^7.2.0", + "big-integer": "^1.6.41" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "optional": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "optional": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "optional": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "big-integer": { + "version": "1.6.48", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==", + "dev": true + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "optional": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserslist": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz", + "integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001208", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.712", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "optional": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001208", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz", + "integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "dependencies": { + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "optional": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "optional": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "optional": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "optional": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "optional": true + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "optional": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "optional": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "optional": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "optional": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "optional": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true, + "optional": true + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true, + "optional": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "optional": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "optional": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.713", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.713.tgz", + "integrity": "sha512-HWgkyX4xTHmxcWWlvv7a87RHSINEcpKYZmDMxkUlHcY+CJcfx7xEfBHuXVsO1rzyYs1WQJ7EgDp2CoErakBIow==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "es-abstract": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", + "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.2", + "is-string": "^1.0.5", + "object-inspect": "^1.9.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.0" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "optional": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "optional": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "optional": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "optional": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "optional": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "optional": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "optional": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "optional": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "optional": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true, + "optional": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true, + "optional": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "optional": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "optional": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "optional": true + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "optional": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-bigint": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", + "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", + "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", + "dev": true, + "requires": { + "call-bind": "^1.0.0" + } + }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "optional": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "optional": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "optional": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "optional": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "optional": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "optional": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.1" + } + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "optional": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbi": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.4.tgz", + "integrity": "sha512-52QRRFSsi9impURE8ZUbzAMCLjPm4THO7H2fcuIvaaeFTbSysvkodbQQXIVsNgq/ypDbq6dJiuGKL0vZ/i9hUg==" + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "optional": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true, + "optional": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "optional": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "optional": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "optional": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", + "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.4", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node-releases": { + "version": "1.1.71", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", + "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "optional": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "optional": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "optional": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "optional": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "optional": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", + "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "optional": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true, + "optional": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true, + "optional": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "picomatch": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", + "dev": true, + "optional": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "optional": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "optional": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "optional": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true, + "optional": true + }, + "repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true, + "optional": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "optional": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true, + "optional": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "optional": true + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "optional": true, + "requires": { + "ret": "~0.1.10" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "optional": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "optional": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "optional": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "optional": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "optional": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "optional": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "optional": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "dev": true, + "optional": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "optional": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "optional": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "optional": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "optional": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "optional": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "optional": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "optional": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "optional": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "optional": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "optional": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true, + "optional": true + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "optional": true + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true, + "optional": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + } + } +} diff --git a/blue_modules/slip39/package.json b/blue_modules/slip39/package.json new file mode 100644 index 000000000..cffe252a3 --- /dev/null +++ b/blue_modules/slip39/package.json @@ -0,0 +1,36 @@ +{ + "name": "slip39", + "version": "0.1.7", + "description": "The javascript implementation of the SLIP39 for Shamir's Secret-Sharing for Mnemonic Codes.", + "main": "dist/slip39.js", + "scripts": { + "build": "npx babel src --out-dir dist && ./sed.sh", + "test": "mocha" + }, + "author": "Pal Dorogi \"iLap\" ", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/ilap/slip39-js.git" + }, + "keywords": [ + "SLIP39", + "crypto", + "Shamir", + "Shamir's Secret Sharing", + "Shamir's secret-sharing scheme", + "SSS" + ], + "devDependencies": { + "@babel/cli": "^7.13.14", + "@babel/core": "^7.13.15", + "babel-plugin-transform-bigint": "^1.0.9", + "mocha": "^6.2.0" + }, + "dependencies": { + "create-hmac": "^1.1.3", + "jsbi": "^3.1.4", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } +} diff --git a/blue_modules/slip39/scripts/test_publish.sh b/blue_modules/slip39/scripts/test_publish.sh new file mode 100755 index 000000000..4e1f96cac --- /dev/null +++ b/blue_modules/slip39/scripts/test_publish.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +PROJ_DIR=`dirname $0`/.. + +cd "${PROJ_DIR}" + +sudo npm install . -g +sudo npm link + +mkdir ../pubtest +cd ../pubtest +cat > package.json << EOF +{ + "name": "test-slip39", + "version": "0.1.0", + "private": true +} +EOF + +npm install ../slip39-js + +cd - +sudo npm uninstall . -g +sudo npm unlink +rm -rf ../pubtest diff --git a/blue_modules/slip39/sed.sh b/blue_modules/slip39/sed.sh new file mode 100755 index 000000000..c73523bdc --- /dev/null +++ b/blue_modules/slip39/sed.sh @@ -0,0 +1,2 @@ +#!/bin/sh +sed -i '' 's/import JSBI from \"jsbi\"/const JSBI = require(\"jsbi\/dist\/jsbi-cjs.js\")/' dist/*.js diff --git a/blue_modules/slip39/src/slip39.js b/blue_modules/slip39/src/slip39.js new file mode 100644 index 000000000..af284d265 --- /dev/null +++ b/blue_modules/slip39/src/slip39.js @@ -0,0 +1,190 @@ +/* eslint-disable radix */ +const slipHelper = require('./slip39_helper.js'); + +const MAX_DEPTH = 2; + +/** + * Slip39Node + * For root node, description refers to the whole set's title e.g. "Hardware wallet X SSSS shares" + * For children nodes, description refers to the group e.g. "Family group: mom, dad, sister, wife" + */ +class Slip39Node { + constructor(index = 0, description = '', mnemonic = '', children = []) { + this.index = index; + this.description = description; + this.mnemonic = mnemonic; + this.children = children; + } + + get mnemonics() { + if (this.children.length === 0) { + return [this.mnemonic]; + } + const result = this.children.reduce((prev, item) => { + return prev.concat(item.mnemonics); + }, []); + return result; + } +} + +// +// The javascript implementation of the SLIP-0039: Shamir's Secret-Sharing for Mnemonic Codes +// see: https://github.com/satoshilabs/slips/blob/master/slip-0039.md) +// +class Slip39 { + constructor({ + iterationExponent = 0, + identifier, + groupCount, + groupThreshold + } = {}) { + this.iterationExponent = iterationExponent; + this.identifier = identifier; + this.groupCount = groupCount; + this.groupThreshold = groupThreshold; + } + + static fromArray(masterSecret, { + passphrase = '', + threshold = 1, + groups = [ + [1, 1, 'Default 1-of-1 group share'] + ], + iterationExponent = 0, + title = 'My default slip39 shares' + } = {}) { + if (masterSecret.length * 8 < slipHelper.MIN_ENTROPY_BITS) { + throw Error(`The length of the master secret (${masterSecret.length} bytes) must be at least ${slipHelper.bitsToBytes(slipHelper.MIN_ENTROPY_BITS)} bytes.`); + } + + if (masterSecret.length % 2 !== 0) { + throw Error('The length of the master secret in bytes must be an even number.'); + } + + if (!/^[\x20-\x7E]*$/.test(passphrase)) { + throw Error('The passphrase must contain only printable ASCII characters (code points 32-126).'); + } + + if (threshold > groups.length) { + throw Error(`The requested group threshold (${threshold}) must not exceed the number of groups (${groups.length}).`); + } + + groups.forEach((item) => { + if (item[0] === 1 && item[1] > 1) { + throw Error(`Creating multiple member shares with member threshold 1 is not allowed. Use 1-of-1 member sharing instead. ${groups.join()}`); + } + }); + + const identifier = slipHelper.generateIdentifier(); + + const slip = new Slip39({ + iterationExponent: iterationExponent, + identifier: identifier, + groupCount: groups.length, + groupThreshold: threshold + }); + + const encryptedMasterSecret = slipHelper.crypt( + masterSecret, passphrase, iterationExponent, slip.identifier); + + const root = slip.buildRecursive( + new Slip39Node(0, title), + groups, + encryptedMasterSecret, + threshold + ); + + slip.root = root; + return slip; + } + + buildRecursive(currentNode, nodes, secret, threshold, index) { + // It means it's a leaf. + if (nodes.length === 0) { + const mnemonic = slipHelper.encodeMnemonic(this.identifier, this.iterationExponent, index, + this.groupThreshold, this.groupCount, currentNode.index, threshold, secret); + + currentNode.mnemonic = mnemonic; + return currentNode; + } + + const secretShares = slipHelper.splitSecret(threshold, nodes.length, secret); + let children = []; + let idx = 0; + + nodes.forEach((item) => { + // n=threshold + const n = item[0]; + // m=members + const m = item[1]; + // d=description + const d = item[2] || ''; + + // Generate leaf members, means their `m` is `0` + const members = Array().slip39Generate(m, () => [n, 0, d]); + + const node = new Slip39Node(idx, d); + const branch = this.buildRecursive( + node, + members, + secretShares[idx], + n, + currentNode.index); + + children = children.concat(branch); + idx = idx + 1; + }); + currentNode.children = children; + return currentNode; + } + + static recoverSecret(mnemonics, passphrase) { + return slipHelper.combineMnemonics(mnemonics, passphrase); + } + + static validateMnemonic(mnemonic) { + return slipHelper.validateMnemonic(mnemonic); + } + + fromPath(path) { + this.validatePath(path); + + const children = this.parseChildren(path); + + if (typeof children === 'undefined' || children.length === 0) { + return this.root; + } + + return children.reduce((prev, childNumber) => { + let childrenLen = prev.children.length; + if (childNumber >= childrenLen) { + throw new Error(`The path index (${childNumber}) exceeds the children index (${childrenLen - 1}).`); + } + + return prev.children[childNumber]; + }, this.root); + } + + validatePath(path) { + if (!path.match(/(^r)(\/\d{1,2}){0,2}$/)) { + throw new Error('Expected valid path e.g. "r/0/0".'); + } + + const depth = path.split('/'); + const pathLength = depth.length - 1; + if (pathLength > MAX_DEPTH) { + throw new Error(`Path\'s (${path}) max depth (${MAX_DEPTH}) is exceeded (${pathLength}).`); + } + } + + parseChildren(path) { + const splitted = path.split('/').slice(1); + + const result = splitted.map((pathFragment) => { + return parseInt(pathFragment); + }); + return result; + } +} + +exports = module.exports = Slip39; diff --git a/blue_modules/slip39/src/slip39_helper.js b/blue_modules/slip39/src/slip39_helper.js new file mode 100644 index 000000000..ac3b2f286 --- /dev/null +++ b/blue_modules/slip39/src/slip39_helper.js @@ -0,0 +1,2238 @@ +/* eslint-disable no-array-constructor */ +const pbkdf2 = require('pbkdf2') +const createHmac = require('create-hmac') +const randombytes = require('randombytes'); + +// The length of the radix in bits. +const RADIX_BITS = 10; + +// The length of the random identifier in bits. +const ID_BITS_LENGTH = 15; + +// The length of the iteration exponent in bits. +const ITERATION_EXP_BITS_LENGTH = 5; + +// The length of the random identifier and iteration exponent in words. +const ITERATION_EXP_WORDS_LENGTH = + parseInt((ID_BITS_LENGTH + ITERATION_EXP_BITS_LENGTH + RADIX_BITS - 1) / RADIX_BITS, 10); + +// The maximum iteration exponent +const MAX_ITERATION_EXP = Math.pow(2, ITERATION_EXP_BITS_LENGTH); + +// The maximum number of shares that can be created. +const MAX_SHARE_COUNT = 16; + +// The length of the RS1024 checksum in words. +const CHECKSUM_WORDS_LENGTH = 3; + +// The length of the digest of the shared secret in bytes. +const DIGEST_LENGTH = 4; + +// The customization string used in the RS1024 checksum and in the PBKDF2 salt. +const SALT_STRING = 'shamir'; + +// The minimum allowed entropy of the master secret. +const MIN_ENTROPY_BITS = 128; + +// The minimum allowed length of the mnemonic in words. +const METADATA_WORDS_LENGTH = + ITERATION_EXP_WORDS_LENGTH + 2 + CHECKSUM_WORDS_LENGTH; + +// The length of the mnemonic in words without the share value. +const MNEMONICS_WORDS_LENGTH = parseInt( + METADATA_WORDS_LENGTH + (MIN_ENTROPY_BITS + RADIX_BITS - 1) / RADIX_BITS, 10); + +// The minimum number of iterations to use in PBKDF2. +const ITERATION_COUNT = 10000; + +// The number of rounds to use in the Feistel cipher. +const ROUND_COUNT = 4; + +// The index of the share containing the digest of the shared secret. +const DIGEST_INDEX = 254; + +// The index of the share containing the shared secret. +const SECRET_INDEX = 255; + +// +// Helper functions for SLIP39 implementation. +// +String.prototype.slip39EncodeHex = function () { + let bytes = []; + for (let i = 0; i < this.length; ++i) { + bytes.push(this.charCodeAt(i)); + } + return bytes; +}; + +Array.prototype.slip39DecodeHex = function () { + let str = []; + const hex = this.toString().split(','); + for (let i = 0; i < hex.length; i++) { + str.push(String.fromCharCode(hex[i])); + } + return str.toString().replace(/,/g, ''); +}; + +Array.prototype.slip39Generate = function (m, v = _ => _) { + let n = m || this.length; + for (let i = 0; i < n; i++) { + this[i] = v(i); + } + return this; +}; + +Array.prototype.toHexString = function () { + return Array.prototype.map.call(this, function (byte) { + return ('0' + (byte & 0xFF).toString(16)).slice(-2); + }).join(''); +}; + +Array.prototype.toByteArray = function (hexString) { + for (let i = 0; i < hexString.length; i = i + 2) { + this.push(parseInt(hexString.substr(i, 2), 16)); + } + return this; +}; + +const BIGINT_WORD_BITS = BigInt(8); + +function decodeBigInt(bytes) { + let result = BigInt(0); + for (let i = 0; i < bytes.length; i++) { + let b = BigInt(bytes[bytes.length - i - 1]); + result = result + (b << BIGINT_WORD_BITS * BigInt(i)); + } + return result; +} + +function encodeBigInt(number, paddedLength = 0) { + let num = number; + const BYTE_MASK = BigInt(0xff); + const BIGINT_ZERO = BigInt(0); + let result = new Array(0); + + while (num > BIGINT_ZERO) { + let i = parseInt(num & BYTE_MASK, 10); + result.unshift(i); + num = num >> BIGINT_WORD_BITS; + } + + // Zero padding to the length + for (let i = result.length; i < paddedLength; i++) { + result.unshift(0); + } + + if (paddedLength !== 0 && result.length > paddedLength) { + throw new Error(`Error in encoding BigInt value, expected less than ${paddedLength} length value, got ${result.length}`); + } + + return result; +} + +function bitsToBytes(n) { + const res = (n + 7) / 8; + const b = parseInt(res, RADIX_BITS); + return b; +} + +function bitsToWords(n) { + const res = (n + RADIX_BITS - 1) / RADIX_BITS; + const b = parseInt(res, RADIX_BITS); + return b; +} + +// +// Returns a randomly generated integer in the range 0, ... , 2**ID_LENGTH_BITS - 1. +// +function randomBytes(length = 32) { + let randoms = randombytes(length); + return Array.prototype.slice.call(randoms, 0); +} + +// +// The round function used internally by the Feistel cipher. +// +function roundFunction(round, passphrase, exp, salt, secret) { + const saltedSecret = salt.concat(secret); + const roundedPhrase = [round].concat(passphrase); + const count = (ITERATION_COUNT << exp) / ROUND_COUNT; + + const key = pbkdf2.pbkdf2Sync(Buffer.from(roundedPhrase), Buffer.from(saltedSecret), count, secret.length, 'sha256'); + return Array.prototype.slice.call(key, 0); +} + +function crypt(masterSecret, passphrase, iterationExponent, + identifier, + encrypt = true) { + // Iteration exponent validated here. + if (iterationExponent < 0 || iterationExponent > MAX_ITERATION_EXP) { + throw Error(`Invalid iteration exponent (${iterationExponent}). Expected between 0 and ${MAX_ITERATION_EXP}`); + } + + let IL = masterSecret.slice().slice(0, masterSecret.length / 2); + let IR = masterSecret.slice().slice(masterSecret.length / 2); + + const pwd = passphrase.slip39EncodeHex(); + + const salt = getSalt(identifier); + + let range = Array().slip39Generate(ROUND_COUNT); + range = encrypt ? range : range.reverse(); + + range.forEach((round) => { + const f = roundFunction(round, pwd, iterationExponent, salt, IR); + const t = xor(IL, f); + IL = IR; + IR = t; + }); + return IR.concat(IL); +} + +function createDigest(randomData, sharedSecret) { + const hmac = createHmac('sha256', Buffer.from(randomData)); + + hmac.update(Buffer.from(sharedSecret)); + + let result = hmac.digest(); + result = result.slice(0, 4); + return Array.prototype.slice.call(result, 0); +} + +function splitSecret(threshold, shareCount, sharedSecret) { + if (threshold <= 0) { + throw Error(`The requested threshold (${threshold}) must be a positive integer.`); + } + + if (threshold > shareCount) { + throw Error(`The requested threshold (${threshold}) must not exceed the number of shares (${shareCount}).`); + } + + if (shareCount > MAX_SHARE_COUNT) { + throw Error(`The requested number of shares (${shareCount}) must not exceed ${MAX_SHARE_COUNT}.`); + } + // If the threshold is 1, then the digest of the shared secret is not used. + if (threshold === 1) { + return Array().slip39Generate(shareCount, () => sharedSecret); + } + + const randomShareCount = threshold - 2; + + const randomPart = randomBytes(sharedSecret.length - DIGEST_LENGTH); + const digest = createDigest(randomPart, sharedSecret); + + let baseShares = new Map(); + let shares = []; + if (randomShareCount) { + shares = Array().slip39Generate( + randomShareCount, () => randomBytes(sharedSecret.length)); + shares.forEach((item, idx) => { + baseShares.set(idx, item); + }); + } + baseShares.set(DIGEST_INDEX, digest.concat(randomPart)); + baseShares.set(SECRET_INDEX, sharedSecret); + + for (let i = randomShareCount; i < shareCount; i++) { + const rr = interpolate(baseShares, i); + shares.push(rr); + } + + return shares; +} + +// +// Returns a randomly generated integer in the range 0, ... , 2**ID_BITS_LENGTH - 1. +// +function generateIdentifier() { + const byte = bitsToBytes(ID_BITS_LENGTH); + const bits = ID_BITS_LENGTH % 8; + const identifier = randomBytes(byte); + + identifier[0] = identifier[0] & (1 << bits) - 1; + + return identifier; +} + +function xor(a, b) { + if (a.length !== b.length) { + throw new Error(`Invalid padding in mnemonic or insufficient length of mnemonics (${a.length} or ${b.length})`); + } + return Array().slip39Generate(a.length, (i) => a[i] ^ b[i]); +} + +function getSalt(identifier) { + const salt = SALT_STRING.slip39EncodeHex(); + return salt.concat(identifier); +} + +function interpolate(shares, x) { + let xCoord = new Set(shares.keys()); + let arr = Array.from(shares.values(), (v) => v.length); + let sharesValueLengths = new Set(arr); + + if (sharesValueLengths.size !== 1) { + throw new Error('Invalid set of shares. All share values must have the same length.'); + } + + if (xCoord.has(x)) { + shares.forEach((v, k) => { + if (k === x) { + return v; + } + }); + } + + // Logarithm of the product of (x_i - x) for i = 1, ... , k. + let logProd = 0; + + shares.forEach((v, k) => { + logProd = logProd + LOG_TABLE[k ^ x]; + }); + + let results = Array().slip39Generate(sharesValueLengths.values().next().value, () => 0); + + shares.forEach((v, k) => { + // The logarithm of the Lagrange basis polynomial evaluated at x. + let sum = 0; + shares.forEach((vv, kk) => { + sum = sum + LOG_TABLE[k ^ kk]; + }); + + // FIXME: -18 % 255 = 237. IT shoulud be 237 and not -18 as it's + // implemented in javascript. + const basis = (logProd - LOG_TABLE[k ^ x] - sum) % 255; + + const logBasisEval = basis < 0 ? 255 + basis : basis; + + v.forEach((item, idx) => { + const shareVal = item; + const intermediateSum = results[idx]; + const r = shareVal !== 0 ? + EXP_TABLE[(LOG_TABLE[shareVal] + logBasisEval) % 255] : 0; + + const res = intermediateSum ^ r; + results[idx] = res; + }); + }); + return results; +} + +function rs1024Polymod(data) { + const GEN = [ + 0xE0E040, + 0x1C1C080, + 0x3838100, + 0x7070200, + 0xE0E0009, + 0x1C0C2412, + 0x38086C24, + 0x3090FC48, + 0x21B1F890, + 0x3F3F120 + ]; + let chk = 1; + + data.forEach((byte) => { + const b = chk >> 20; + chk = (chk & 0xFFFFF) << 10 ^ byte; + + for (let i = 0; i < 10; i++) { + let gen = (b >> i & 1) !== 0 ? GEN[i] : 0; + chk = chk ^ gen; + } + }); + + return chk; +} + +function rs1024CreateChecksum(data) { + const values = SALT_STRING.slip39EncodeHex() + .concat(data) + .concat(Array().slip39Generate(CHECKSUM_WORDS_LENGTH, () => 0)); + const polymod = rs1024Polymod(values) ^ 1; + const result = + Array().slip39Generate(CHECKSUM_WORDS_LENGTH, (i) => polymod >> 10 * i & 1023).reverse(); + + return result; +} + +function rs1024VerifyChecksum(data) { + return rs1024Polymod(SALT_STRING.slip39EncodeHex().concat(data)) === 1; +} + +// +// Converts a list of base 1024 indices in big endian order to an integer value. +// +function intFromIndices(indices) { + let value = BigInt(0); + const radix = BigInt(Math.pow(2, RADIX_BITS)); + indices.forEach((index) => { + value = value * radix + BigInt(index); + }); + + return value; +} + +// +// Converts a Big integer value to indices in big endian order. +// +function intToIndices(value, length, bits) { + const mask = BigInt((1 << bits) - 1); + const result = + Array().slip39Generate(length, (i) => parseInt(value >> BigInt(i) * BigInt(bits) & mask, 10)); + return result.reverse(); +} + +function mnemonicFromIndices(indices) { + const result = indices.map((index) => { + return WORD_LIST[index]; + }); + return result.toString().split(',').join(' '); +} + +function mnemonicToIndices(mnemonic) { + if (typeof mnemonic !== 'string') { + throw new Error(`Mnemonic expected to be typeof string with white space separated words. Instead found typeof ${typeof mnemonic}.`); + } + + const words = mnemonic.toLowerCase().split(' '); + const result = words.reduce((prev, item) => { + const index = WORD_LIST_MAP[item]; + if (typeof index === 'undefined') { + throw new Error(`Invalid mnemonic word ${item}.`); + } + return prev.concat(index); + }, []); + return result; +} + +function recoverSecret(threshold, shares) { + // If the threshold is 1, then the digest of the shared secret is not used. + if (threshold === 1) { + return shares.values().next().value; + } + + const sharedSecret = interpolate(shares, SECRET_INDEX); + const digestShare = interpolate(shares, DIGEST_INDEX); + const digest = digestShare.slice(0, DIGEST_LENGTH); + const randomPart = digestShare.slice(DIGEST_LENGTH); + + const recoveredDigest = createDigest( + randomPart, sharedSecret); + if (!listsAreEqual(digest, recoveredDigest)) { + throw new Error('Invalid digest of the shared secret.'); + } + return sharedSecret; +} + +// +// Combines mnemonic shares to obtain the master secret which was previously +// split using Shamir's secret sharing scheme. +// +function combineMnemonics(mnemonics, passphrase = '') { + if (mnemonics === null || mnemonics.length === 0) { + throw new Error('The list of mnemonics is empty.'); + } + + const decoded = decodeMnemonics(mnemonics); + const identifier = decoded.identifier; + const iterationExponent = decoded.iterationExponent; + const groupThreshold = decoded.groupThreshold; + const groupCount = decoded.groupCount; + const groups = decoded.groups; + + if (groups.size < groupThreshold) { + throw new Error(`Insufficient number of mnemonic groups (${groups.size}). The required number of groups is ${groupThreshold}.`); + } + + if (groups.size !== groupThreshold) { + throw new Error(`Wrong number of mnemonic groups. Expected ${groupThreshold} groups, but ${groups.size} were provided.`); + } + + let allShares = new Map(); + groups.forEach((members, groupIndex) => { + const threshold = members.keys().next().value; + const shares = members.values().next().value; + if (shares.size !== threshold) { + const prefix = groupPrefix( + identifier, + iterationExponent, + groupIndex, + groupThreshold, + groupCount + ); + throw new Error(`Wrong number of mnemonics. Expected ${threshold} mnemonics starting with "${mnemonicFromIndices(prefix)}", \n but ${shares.size} were provided.`); + } + + const recovered = recoverSecret(threshold, shares); + allShares.set(groupIndex, recovered); + }); + + const ems = recoverSecret(groupThreshold, allShares); + const id = intToIndices(BigInt(identifier), ITERATION_EXP_WORDS_LENGTH, 8); + const ms = crypt(ems, passphrase, iterationExponent, id, false); + + return ms; +} + +function decodeMnemonics(mnemonics) { + if (!(mnemonics instanceof Array)) { + throw new Error('Mnemonics should be an array of strings'); + } + const identifiers = new Set(); + const iterationExponents = new Set(); + const groupThresholds = new Set(); + const groupCounts = new Set(); + const groups = new Map(); + + mnemonics.forEach((mnemonic) => { + const decoded = decodeMnemonic(mnemonic); + + identifiers.add(decoded.identifier); + iterationExponents.add(decoded.iterationExponent); + const groupIndex = decoded.groupIndex; + groupThresholds.add(decoded.groupThreshold); + groupCounts.add(decoded.groupCount); + const memberIndex = decoded.memberIndex; + const memberThreshold = decoded.memberThreshold; + const share = decoded.share; + + const group = !groups.has(groupIndex) ? new Map() : groups.get(groupIndex); + const member = !group.has(memberThreshold) ? new Map() : group.get(memberThreshold); + member.set(memberIndex, share); + group.set(memberThreshold, member); + if (group.size !== 1) { + throw new Error('Invalid set of mnemonics. All mnemonics in a group must have the same member threshold.'); + } + groups.set(groupIndex, group); + }); + + if (identifiers.size !== 1 || iterationExponents.size !== 1) { + throw new Error(`Invalid set of mnemonics. All mnemonics must begin with the same ${ITERATION_EXP_WORDS_LENGTH} words.`); + } + + if (groupThresholds.size !== 1) { + throw new Error('Invalid set of mnemonics. All mnemonics must have the same group threshold.'); + } + + if (groupCounts.size !== 1) { + throw new Error('Invalid set of mnemonics. All mnemonics must have the same group count.'); + } + + return { + identifier: identifiers.values().next().value, + iterationExponent: iterationExponents.values().next().value, + groupThreshold: groupThresholds.values().next().value, + groupCount: groupCounts.values().next().value, + groups: groups + }; +} + +// +// Converts a share mnemonic to share data. +// +function decodeMnemonic(mnemonic) { + const data = mnemonicToIndices(mnemonic); + + if (data.length < MNEMONICS_WORDS_LENGTH) { + throw new Error(`Invalid mnemonic length. The length of each mnemonic must be at least ${MNEMONICS_WORDS_LENGTH} words.`); + } + + const paddingLen = RADIX_BITS * (data.length - METADATA_WORDS_LENGTH) % 16; + if (paddingLen > 8) { + throw new Error('Invalid mnemonic length.'); + } + + if (!rs1024VerifyChecksum(data)) { + throw new Error('Invalid mnemonic checksum'); + } + + const idExpInt = + parseInt(intFromIndices(data.slice(0, ITERATION_EXP_WORDS_LENGTH)), 10); + const identifier = idExpInt >> ITERATION_EXP_BITS_LENGTH; + const iterationExponent = idExpInt & (1 << ITERATION_EXP_BITS_LENGTH) - 1; + const tmp = intFromIndices( + data.slice(ITERATION_EXP_WORDS_LENGTH, ITERATION_EXP_WORDS_LENGTH + 2)); + + const indices = intToIndices(tmp, 5, 4); + + const groupIndex = indices[0]; + const groupThreshold = indices[1]; + const groupCount = indices[2]; + const memberIndex = indices[3]; + const memberThreshold = indices[4]; + + const valueData = data.slice( + ITERATION_EXP_WORDS_LENGTH + 2, data.length - CHECKSUM_WORDS_LENGTH); + + if (groupCount < groupThreshold) { + throw new Error(`Invalid mnemonic: ${mnemonic}.\n Group threshold (${groupThreshold}) cannot be greater than group count (${groupCount}).`); + } + + const valueInt = intFromIndices(valueData); + + try { + const valueByteCount = bitsToBytes(RADIX_BITS * valueData.length - paddingLen); + const share = encodeBigInt(valueInt, valueByteCount); + + return { + identifier: identifier, + iterationExponent: iterationExponent, + groupIndex: groupIndex, + groupThreshold: groupThreshold + 1, + groupCount: groupCount + 1, + memberIndex: memberIndex, + memberThreshold: memberThreshold + 1, + share: share + }; + } catch (e) { + throw new Error(`Invalid mnemonic padding (${e})`); + } +} + +function validateMnemonic(mnemonic) { + try { + decodeMnemonic(mnemonic); + return true; + } catch (error) { + return false; + } +} + +function groupPrefix( + identifier, iterationExponent, groupIndex, groupThreshold, groupCount) { + const idExpInt = BigInt( + (identifier << ITERATION_EXP_BITS_LENGTH) + iterationExponent); + + const indc = intToIndices(idExpInt, ITERATION_EXP_WORDS_LENGTH, RADIX_BITS); + + const indc2 = + (groupIndex << 6) + (groupThreshold - 1 << 2) + (groupCount - 1 >> 2); + + indc.push(indc2); + return indc; +} + +function listsAreEqual(a, b) { + if (a === null || b === null || a.length !== b.length) { + return false; + } + + let i = 0; + return a.every((item) => { + return b[i++] === item; + }); +} + +// +// Converts share data to a share mnemonic. +// +function encodeMnemonic( + identifier, + iterationExponent, + groupIndex, + groupThreshold, + groupCount, + memberIndex, + memberThreshold, + value +) { + // Convert the share value from bytes to wordlist indices. + const valueWordCount = bitsToWords(value.length * 8); + + const valueInt = decodeBigInt(value); + let newIdentifier = parseInt(decodeBigInt(identifier), 10); + + const gp = groupPrefix( + newIdentifier, iterationExponent, groupIndex, groupThreshold, groupCount); + const tp = intToIndices(valueInt, valueWordCount, RADIX_BITS); + + const calc = ((groupCount - 1 & 3) << 8) + + (memberIndex << 4) + + (memberThreshold - 1); + + gp.push(calc); + const shareData = gp.concat(tp); + + const checksum = rs1024CreateChecksum(shareData); + + return mnemonicFromIndices(shareData.concat(checksum)); +} + +// The precomputed exponent and log tables. +// ``` +// const exp = List.filled(255, 0) +// const log = List.filled(256, 0) +// const poly = 1 +// +// for (let i = 0; i < exp.length; i++) { +// exp[i] = poly +// log[poly] = i +// // Multiply poly by the polynomial x + 1. +// poly = (poly << 1) ^ poly +// // Reduce poly by x^8 + x^4 + x^3 + x + 1. +// if (poly & 0x100 === 0x100) poly ^= 0x11B +// } +// ``` +const EXP_TABLE = [ + 1, + 3, + 5, + 15, + 17, + 51, + 85, + 255, + 26, + 46, + 114, + 150, + 161, + 248, + 19, + 53, + 95, + 225, + 56, + 72, + 216, + 115, + 149, + 164, + 247, + 2, + 6, + 10, + 30, + 34, + 102, + 170, + 229, + 52, + 92, + 228, + 55, + 89, + 235, + 38, + 106, + 190, + 217, + 112, + 144, + 171, + 230, + 49, + 83, + 245, + 4, + 12, + 20, + 60, + 68, + 204, + 79, + 209, + 104, + 184, + 211, + 110, + 178, + 205, + 76, + 212, + 103, + 169, + 224, + 59, + 77, + 215, + 98, + 166, + 241, + 8, + 24, + 40, + 120, + 136, + 131, + 158, + 185, + 208, + 107, + 189, + 220, + 127, + 129, + 152, + 179, + 206, + 73, + 219, + 118, + 154, + 181, + 196, + 87, + 249, + 16, + 48, + 80, + 240, + 11, + 29, + 39, + 105, + 187, + 214, + 97, + 163, + 254, + 25, + 43, + 125, + 135, + 146, + 173, + 236, + 47, + 113, + 147, + 174, + 233, + 32, + 96, + 160, + 251, + 22, + 58, + 78, + 210, + 109, + 183, + 194, + 93, + 231, + 50, + 86, + 250, + 21, + 63, + 65, + 195, + 94, + 226, + 61, + 71, + 201, + 64, + 192, + 91, + 237, + 44, + 116, + 156, + 191, + 218, + 117, + 159, + 186, + 213, + 100, + 172, + 239, + 42, + 126, + 130, + 157, + 188, + 223, + 122, + 142, + 137, + 128, + 155, + 182, + 193, + 88, + 232, + 35, + 101, + 175, + 234, + 37, + 111, + 177, + 200, + 67, + 197, + 84, + 252, + 31, + 33, + 99, + 165, + 244, + 7, + 9, + 27, + 45, + 119, + 153, + 176, + 203, + 70, + 202, + 69, + 207, + 74, + 222, + 121, + 139, + 134, + 145, + 168, + 227, + 62, + 66, + 198, + 81, + 243, + 14, + 18, + 54, + 90, + 238, + 41, + 123, + 141, + 140, + 143, + 138, + 133, + 148, + 167, + 242, + 13, + 23, + 57, + 75, + 221, + 124, + 132, + 151, + 162, + 253, + 28, + 36, + 108, + 180, + 199, + 82, + 246 +]; +const LOG_TABLE = [ + 0, + 0, + 25, + 1, + 50, + 2, + 26, + 198, + 75, + 199, + 27, + 104, + 51, + 238, + 223, + 3, + 100, + 4, + 224, + 14, + 52, + 141, + 129, + 239, + 76, + 113, + 8, + 200, + 248, + 105, + 28, + 193, + 125, + 194, + 29, + 181, + 249, + 185, + 39, + 106, + 77, + 228, + 166, + 114, + 154, + 201, + 9, + 120, + 101, + 47, + 138, + 5, + 33, + 15, + 225, + 36, + 18, + 240, + 130, + 69, + 53, + 147, + 218, + 142, + 150, + 143, + 219, + 189, + 54, + 208, + 206, + 148, + 19, + 92, + 210, + 241, + 64, + 70, + 131, + 56, + 102, + 221, + 253, + 48, + 191, + 6, + 139, + 98, + 179, + 37, + 226, + 152, + 34, + 136, + 145, + 16, + 126, + 110, + 72, + 195, + 163, + 182, + 30, + 66, + 58, + 107, + 40, + 84, + 250, + 133, + 61, + 186, + 43, + 121, + 10, + 21, + 155, + 159, + 94, + 202, + 78, + 212, + 172, + 229, + 243, + 115, + 167, + 87, + 175, + 88, + 168, + 80, + 244, + 234, + 214, + 116, + 79, + 174, + 233, + 213, + 231, + 230, + 173, + 232, + 44, + 215, + 117, + 122, + 235, + 22, + 11, + 245, + 89, + 203, + 95, + 176, + 156, + 169, + 81, + 160, + 127, + 12, + 246, + 111, + 23, + 196, + 73, + 236, + 216, + 67, + 31, + 45, + 164, + 118, + 123, + 183, + 204, + 187, + 62, + 90, + 251, + 96, + 177, + 134, + 59, + 82, + 161, + 108, + 170, + 85, + 41, + 157, + 151, + 178, + 135, + 144, + 97, + 190, + 220, + 252, + 188, + 149, + 207, + 205, + 55, + 63, + 91, + 209, + 83, + 57, + 132, + 60, + 65, + 162, + 109, + 71, + 20, + 42, + 158, + 93, + 86, + 242, + 211, + 171, + 68, + 17, + 146, + 217, + 35, + 32, + 46, + 137, + 180, + 124, + 184, + 38, + 119, + 153, + 227, + 165, + 103, + 74, + 237, + 222, + 197, + 49, + 254, + 24, + 13, + 99, + 140, + 128, + 192, + 247, + 112, + 7 +]; + +// +// SLIP39 wordlist +// +const WORD_LIST = [ + 'academic', + 'acid', + 'acne', + 'acquire', + 'acrobat', + 'activity', + 'actress', + 'adapt', + 'adequate', + 'adjust', + 'admit', + 'adorn', + 'adult', + 'advance', + 'advocate', + 'afraid', + 'again', + 'agency', + 'agree', + 'aide', + 'aircraft', + 'airline', + 'airport', + 'ajar', + 'alarm', + 'album', + 'alcohol', + 'alien', + 'alive', + 'alpha', + 'already', + 'alto', + 'aluminum', + 'always', + 'amazing', + 'ambition', + 'amount', + 'amuse', + 'analysis', + 'anatomy', + 'ancestor', + 'ancient', + 'angel', + 'angry', + 'animal', + 'answer', + 'antenna', + 'anxiety', + 'apart', + 'aquatic', + 'arcade', + 'arena', + 'argue', + 'armed', + 'artist', + 'artwork', + 'aspect', + 'auction', + 'august', + 'aunt', + 'average', + 'aviation', + 'avoid', + 'award', + 'away', + 'axis', + 'axle', + 'beam', + 'beard', + 'beaver', + 'become', + 'bedroom', + 'behavior', + 'being', + 'believe', + 'belong', + 'benefit', + 'best', + 'beyond', + 'bike', + 'biology', + 'birthday', + 'bishop', + 'black', + 'blanket', + 'blessing', + 'blimp', + 'blind', + 'blue', + 'body', + 'bolt', + 'boring', + 'born', + 'both', + 'boundary', + 'bracelet', + 'branch', + 'brave', + 'breathe', + 'briefing', + 'broken', + 'brother', + 'browser', + 'bucket', + 'budget', + 'building', + 'bulb', + 'bulge', + 'bumpy', + 'bundle', + 'burden', + 'burning', + 'busy', + 'buyer', + 'cage', + 'calcium', + 'camera', + 'campus', + 'canyon', + 'capacity', + 'capital', + 'capture', + 'carbon', + 'cards', + 'careful', + 'cargo', + 'carpet', + 'carve', + 'category', + 'cause', + 'ceiling', + 'center', + 'ceramic', + 'champion', + 'change', + 'charity', + 'check', + 'chemical', + 'chest', + 'chew', + 'chubby', + 'cinema', + 'civil', + 'class', + 'clay', + 'cleanup', + 'client', + 'climate', + 'clinic', + 'clock', + 'clogs', + 'closet', + 'clothes', + 'club', + 'cluster', + 'coal', + 'coastal', + 'coding', + 'column', + 'company', + 'corner', + 'costume', + 'counter', + 'course', + 'cover', + 'cowboy', + 'cradle', + 'craft', + 'crazy', + 'credit', + 'cricket', + 'criminal', + 'crisis', + 'critical', + 'crowd', + 'crucial', + 'crunch', + 'crush', + 'crystal', + 'cubic', + 'cultural', + 'curious', + 'curly', + 'custody', + 'cylinder', + 'daisy', + 'damage', + 'dance', + 'darkness', + 'database', + 'daughter', + 'deadline', + 'deal', + 'debris', + 'debut', + 'decent', + 'decision', + 'declare', + 'decorate', + 'decrease', + 'deliver', + 'demand', + 'density', + 'deny', + 'depart', + 'depend', + 'depict', + 'deploy', + 'describe', + 'desert', + 'desire', + 'desktop', + 'destroy', + 'detailed', + 'detect', + 'device', + 'devote', + 'diagnose', + 'dictate', + 'diet', + 'dilemma', + 'diminish', + 'dining', + 'diploma', + 'disaster', + 'discuss', + 'disease', + 'dish', + 'dismiss', + 'display', + 'distance', + 'dive', + 'divorce', + 'document', + 'domain', + 'domestic', + 'dominant', + 'dough', + 'downtown', + 'dragon', + 'dramatic', + 'dream', + 'dress', + 'drift', + 'drink', + 'drove', + 'drug', + 'dryer', + 'duckling', + 'duke', + 'duration', + 'dwarf', + 'dynamic', + 'early', + 'earth', + 'easel', + 'easy', + 'echo', + 'eclipse', + 'ecology', + 'edge', + 'editor', + 'educate', + 'either', + 'elbow', + 'elder', + 'election', + 'elegant', + 'element', + 'elephant', + 'elevator', + 'elite', + 'else', + 'email', + 'emerald', + 'emission', + 'emperor', + 'emphasis', + 'employer', + 'empty', + 'ending', + 'endless', + 'endorse', + 'enemy', + 'energy', + 'enforce', + 'engage', + 'enjoy', + 'enlarge', + 'entrance', + 'envelope', + 'envy', + 'epidemic', + 'episode', + 'equation', + 'equip', + 'eraser', + 'erode', + 'escape', + 'estate', + 'estimate', + 'evaluate', + 'evening', + 'evidence', + 'evil', + 'evoke', + 'exact', + 'example', + 'exceed', + 'exchange', + 'exclude', + 'excuse', + 'execute', + 'exercise', + 'exhaust', + 'exotic', + 'expand', + 'expect', + 'explain', + 'express', + 'extend', + 'extra', + 'eyebrow', + 'facility', + 'fact', + 'failure', + 'faint', + 'fake', + 'false', + 'family', + 'famous', + 'fancy', + 'fangs', + 'fantasy', + 'fatal', + 'fatigue', + 'favorite', + 'fawn', + 'fiber', + 'fiction', + 'filter', + 'finance', + 'findings', + 'finger', + 'firefly', + 'firm', + 'fiscal', + 'fishing', + 'fitness', + 'flame', + 'flash', + 'flavor', + 'flea', + 'flexible', + 'flip', + 'float', + 'floral', + 'fluff', + 'focus', + 'forbid', + 'force', + 'forecast', + 'forget', + 'formal', + 'fortune', + 'forward', + 'founder', + 'fraction', + 'fragment', + 'frequent', + 'freshman', + 'friar', + 'fridge', + 'friendly', + 'frost', + 'froth', + 'frozen', + 'fumes', + 'funding', + 'furl', + 'fused', + 'galaxy', + 'game', + 'garbage', + 'garden', + 'garlic', + 'gasoline', + 'gather', + 'general', + 'genius', + 'genre', + 'genuine', + 'geology', + 'gesture', + 'glad', + 'glance', + 'glasses', + 'glen', + 'glimpse', + 'goat', + 'golden', + 'graduate', + 'grant', + 'grasp', + 'gravity', + 'gray', + 'greatest', + 'grief', + 'grill', + 'grin', + 'grocery', + 'gross', + 'group', + 'grownup', + 'grumpy', + 'guard', + 'guest', + 'guilt', + 'guitar', + 'gums', + 'hairy', + 'hamster', + 'hand', + 'hanger', + 'harvest', + 'have', + 'havoc', + 'hawk', + 'hazard', + 'headset', + 'health', + 'hearing', + 'heat', + 'helpful', + 'herald', + 'herd', + 'hesitate', + 'hobo', + 'holiday', + 'holy', + 'home', + 'hormone', + 'hospital', + 'hour', + 'huge', + 'human', + 'humidity', + 'hunting', + 'husband', + 'hush', + 'husky', + 'hybrid', + 'idea', + 'identify', + 'idle', + 'image', + 'impact', + 'imply', + 'improve', + 'impulse', + 'include', + 'income', + 'increase', + 'index', + 'indicate', + 'industry', + 'infant', + 'inform', + 'inherit', + 'injury', + 'inmate', + 'insect', + 'inside', + 'install', + 'intend', + 'intimate', + 'invasion', + 'involve', + 'iris', + 'island', + 'isolate', + 'item', + 'ivory', + 'jacket', + 'jerky', + 'jewelry', + 'join', + 'judicial', + 'juice', + 'jump', + 'junction', + 'junior', + 'junk', + 'jury', + 'justice', + 'kernel', + 'keyboard', + 'kidney', + 'kind', + 'kitchen', + 'knife', + 'knit', + 'laden', + 'ladle', + 'ladybug', + 'lair', + 'lamp', + 'language', + 'large', + 'laser', + 'laundry', + 'lawsuit', + 'leader', + 'leaf', + 'learn', + 'leaves', + 'lecture', + 'legal', + 'legend', + 'legs', + 'lend', + 'length', + 'level', + 'liberty', + 'library', + 'license', + 'lift', + 'likely', + 'lilac', + 'lily', + 'lips', + 'liquid', + 'listen', + 'literary', + 'living', + 'lizard', + 'loan', + 'lobe', + 'location', + 'losing', + 'loud', + 'loyalty', + 'luck', + 'lunar', + 'lunch', + 'lungs', + 'luxury', + 'lying', + 'lyrics', + 'machine', + 'magazine', + 'maiden', + 'mailman', + 'main', + 'makeup', + 'making', + 'mama', + 'manager', + 'mandate', + 'mansion', + 'manual', + 'marathon', + 'march', + 'market', + 'marvel', + 'mason', + 'material', + 'math', + 'maximum', + 'mayor', + 'meaning', + 'medal', + 'medical', + 'member', + 'memory', + 'mental', + 'merchant', + 'merit', + 'method', + 'metric', + 'midst', + 'mild', + 'military', + 'mineral', + 'minister', + 'miracle', + 'mixed', + 'mixture', + 'mobile', + 'modern', + 'modify', + 'moisture', + 'moment', + 'morning', + 'mortgage', + 'mother', + 'mountain', + 'mouse', + 'move', + 'much', + 'mule', + 'multiple', + 'muscle', + 'museum', + 'music', + 'mustang', + 'nail', + 'national', + 'necklace', + 'negative', + 'nervous', + 'network', + 'news', + 'nuclear', + 'numb', + 'numerous', + 'nylon', + 'oasis', + 'obesity', + 'object', + 'observe', + 'obtain', + 'ocean', + 'often', + 'olympic', + 'omit', + 'oral', + 'orange', + 'orbit', + 'order', + 'ordinary', + 'organize', + 'ounce', + 'oven', + 'overall', + 'owner', + 'paces', + 'pacific', + 'package', + 'paid', + 'painting', + 'pajamas', + 'pancake', + 'pants', + 'papa', + 'paper', + 'parcel', + 'parking', + 'party', + 'patent', + 'patrol', + 'payment', + 'payroll', + 'peaceful', + 'peanut', + 'peasant', + 'pecan', + 'penalty', + 'pencil', + 'percent', + 'perfect', + 'permit', + 'petition', + 'phantom', + 'pharmacy', + 'photo', + 'phrase', + 'physics', + 'pickup', + 'picture', + 'piece', + 'pile', + 'pink', + 'pipeline', + 'pistol', + 'pitch', + 'plains', + 'plan', + 'plastic', + 'platform', + 'playoff', + 'pleasure', + 'plot', + 'plunge', + 'practice', + 'prayer', + 'preach', + 'predator', + 'pregnant', + 'premium', + 'prepare', + 'presence', + 'prevent', + 'priest', + 'primary', + 'priority', + 'prisoner', + 'privacy', + 'prize', + 'problem', + 'process', + 'profile', + 'program', + 'promise', + 'prospect', + 'provide', + 'prune', + 'public', + 'pulse', + 'pumps', + 'punish', + 'puny', + 'pupal', + 'purchase', + 'purple', + 'python', + 'quantity', + 'quarter', + 'quick', + 'quiet', + 'race', + 'racism', + 'radar', + 'railroad', + 'rainbow', + 'raisin', + 'random', + 'ranked', + 'rapids', + 'raspy', + 'reaction', + 'realize', + 'rebound', + 'rebuild', + 'recall', + 'receiver', + 'recover', + 'regret', + 'regular', + 'reject', + 'relate', + 'remember', + 'remind', + 'remove', + 'render', + 'repair', + 'repeat', + 'replace', + 'require', + 'rescue', + 'research', + 'resident', + 'response', + 'result', + 'retailer', + 'retreat', + 'reunion', + 'revenue', + 'review', + 'reward', + 'rhyme', + 'rhythm', + 'rich', + 'rival', + 'river', + 'robin', + 'rocky', + 'romantic', + 'romp', + 'roster', + 'round', + 'royal', + 'ruin', + 'ruler', + 'rumor', + 'sack', + 'safari', + 'salary', + 'salon', + 'salt', + 'satisfy', + 'satoshi', + 'saver', + 'says', + 'scandal', + 'scared', + 'scatter', + 'scene', + 'scholar', + 'science', + 'scout', + 'scramble', + 'screw', + 'script', + 'scroll', + 'seafood', + 'season', + 'secret', + 'security', + 'segment', + 'senior', + 'shadow', + 'shaft', + 'shame', + 'shaped', + 'sharp', + 'shelter', + 'sheriff', + 'short', + 'should', + 'shrimp', + 'sidewalk', + 'silent', + 'silver', + 'similar', + 'simple', + 'single', + 'sister', + 'skin', + 'skunk', + 'slap', + 'slavery', + 'sled', + 'slice', + 'slim', + 'slow', + 'slush', + 'smart', + 'smear', + 'smell', + 'smirk', + 'smith', + 'smoking', + 'smug', + 'snake', + 'snapshot', + 'sniff', + 'society', + 'software', + 'soldier', + 'solution', + 'soul', + 'source', + 'space', + 'spark', + 'speak', + 'species', + 'spelling', + 'spend', + 'spew', + 'spider', + 'spill', + 'spine', + 'spirit', + 'spit', + 'spray', + 'sprinkle', + 'square', + 'squeeze', + 'stadium', + 'staff', + 'standard', + 'starting', + 'station', + 'stay', + 'steady', + 'step', + 'stick', + 'stilt', + 'story', + 'strategy', + 'strike', + 'style', + 'subject', + 'submit', + 'sugar', + 'suitable', + 'sunlight', + 'superior', + 'surface', + 'surprise', + 'survive', + 'sweater', + 'swimming', + 'swing', + 'switch', + 'symbolic', + 'sympathy', + 'syndrome', + 'system', + 'tackle', + 'tactics', + 'tadpole', + 'talent', + 'task', + 'taste', + 'taught', + 'taxi', + 'teacher', + 'teammate', + 'teaspoon', + 'temple', + 'tenant', + 'tendency', + 'tension', + 'terminal', + 'testify', + 'texture', + 'thank', + 'that', + 'theater', + 'theory', + 'therapy', + 'thorn', + 'threaten', + 'thumb', + 'thunder', + 'ticket', + 'tidy', + 'timber', + 'timely', + 'ting', + 'tofu', + 'together', + 'tolerate', + 'total', + 'toxic', + 'tracks', + 'traffic', + 'training', + 'transfer', + 'trash', + 'traveler', + 'treat', + 'trend', + 'trial', + 'tricycle', + 'trip', + 'triumph', + 'trouble', + 'true', + 'trust', + 'twice', + 'twin', + 'type', + 'typical', + 'ugly', + 'ultimate', + 'umbrella', + 'uncover', + 'undergo', + 'unfair', + 'unfold', + 'unhappy', + 'union', + 'universe', + 'unkind', + 'unknown', + 'unusual', + 'unwrap', + 'upgrade', + 'upstairs', + 'username', + 'usher', + 'usual', + 'valid', + 'valuable', + 'vampire', + 'vanish', + 'various', + 'vegan', + 'velvet', + 'venture', + 'verdict', + 'verify', + 'very', + 'veteran', + 'vexed', + 'victim', + 'video', + 'view', + 'vintage', + 'violence', + 'viral', + 'visitor', + 'visual', + 'vitamins', + 'vocal', + 'voice', + 'volume', + 'voter', + 'voting', + 'walnut', + 'warmth', + 'warn', + 'watch', + 'wavy', + 'wealthy', + 'weapon', + 'webcam', + 'welcome', + 'welfare', + 'western', + 'width', + 'wildlife', + 'window', + 'wine', + 'wireless', + 'wisdom', + 'withdraw', + 'wits', + 'wolf', + 'woman', + 'work', + 'worthy', + 'wrap', + 'wrist', + 'writing', + 'wrote', + 'year', + 'yelp', + 'yield', + 'yoga', + 'zero' +]; + +const WORD_LIST_MAP = WORD_LIST.reduce((obj, val, idx) => { + obj[val] = idx; + return obj; +}, {}); + +exports = module.exports = { + MIN_ENTROPY_BITS, + generateIdentifier, + encodeMnemonic, + validateMnemonic, + splitSecret, + combineMnemonics, + crypt, + bitsToBytes +}; diff --git a/blue_modules/slip39/test/test.js b/blue_modules/slip39/test/test.js new file mode 100644 index 000000000..341a3c2ce --- /dev/null +++ b/blue_modules/slip39/test/test.js @@ -0,0 +1,353 @@ +const assert = require('assert'); +const slip39 = require('../dist/slip39'); + +const MASTERSECRET = 'ABCDEFGHIJKLMNOP'; +const MS = MASTERSECRET.slip39EncodeHex(); +const PASSPHRASE = 'TREZOR'; +const ONE_GROUP = [ + [5, 7] +]; + +const slip15 = slip39.fromArray(MS, { + passphrase: PASSPHRASE, + threshold: 1, + groups: ONE_GROUP +}); + +const slip15NoPW = slip39.fromArray(MS, { + threshold: 1, + groups: ONE_GROUP +}); + +// +// Shuffle +// +function shuffle(array) { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } +} + +// +// Combination C(n, k) of the grooups +// +function getCombinations(array, k) { + let result = []; + let combinations = []; + + function helper(level, start) { + for (let i = start; i < array.length - k + level + 1; i++) { + combinations[level] = array[i]; + + if (level < k - 1) { + helper(level + 1, i + 1); + } else { + result.push(combinations.slice(0)); + } + } + } + + helper(0, 0); + return result; +} + +describe('Basic Tests', () => { + describe('Test threshold 1 with 5 of 7 shares of a group combinations', () => { + let mnemonics = slip15.fromPath('r/0').mnemonics; + + let combinations = getCombinations([0, 1, 2, 3, 4, 5, 6], 5); + combinations.forEach((item) => { + shuffle(item); + let description = `Test shuffled combination ${item.join(' ')}.`; + it(description, () => { + let shares = item.map((idx) => mnemonics[idx]); + assert(MS.slip39DecodeHex() === slip39.recoverSecret(shares, PASSPHRASE) + .slip39DecodeHex()); + }); + }); + }); + + describe('Test passhrase', () => { + let mnemonics = slip15.fromPath('r/0').mnemonics; + let nopwMnemonics = slip15NoPW.fromPath('r/0').mnemonics; + + it('should return valid mastersecret when user submits valid passphrase', () => { + assert(MS.slip39DecodeHex() === slip39.recoverSecret(mnemonics.slice(0, 5), PASSPHRASE) + .slip39DecodeHex()); + }); + it('should NOT return valid mastersecret when user submits invalid passphrse', () => { + assert(MS.slip39DecodeHex() !== slip39.recoverSecret(mnemonics.slice(0, 5)) + .slip39DecodeHex()); + }); + it('should return valid mastersecret when user does not submit passphrase', () => { + assert(MS.slip39DecodeHex() === slip39.recoverSecret(nopwMnemonics.slice(0, 5)) + .slip39DecodeHex()); + }); + }); + + describe('Test iteration exponent', () => { + const slip1 = slip39.fromArray(MS, { + iterationExponent: 1 + }); + + const slip2 = slip39.fromArray(MS, { + iterationExponent: 2 + }); + + it('should return valid mastersecret when user apply valid iteration exponent', () => { + assert(MS.slip39DecodeHex() === slip39.recoverSecret(slip1.fromPath('r/0').mnemonics) + .slip39DecodeHex()); + + assert(MS.slip39DecodeHex() === slip39.recoverSecret(slip2.fromPath('r/0').mnemonics) + .slip39DecodeHex()); + }); + /** + * assert.throws(() => x.y.z); + * assert.throws(() => x.y.z, ReferenceError); + * assert.throws(() => x.y.z, ReferenceError, /is not defined/); + * assert.throws(() => x.y.z, /is not defined/); + * assert.doesNotThrow(() => 42); + * assert.throws(() => x.y.z, Error); + * assert.throws(() => model.get.z, /Property does not exist in model schema./) + * Ref: https://stackoverflow.com/questions/21587122/mocha-chai-expect-to-throw-not-catching-thrown-errors + */ + it('should throw an Error when user submits invalid iteration exponent', () => { + assert.throws(() => slip39.fromArray(MS, { + iterationExponent: -1 + }), Error); + assert.throws(() => slip39.fromArray(MS, { + iterationExponent: 33 + }), Error); + }); + }); +}); + +// FIXME: finish it. +describe('Group Sharing Tests', () => { + describe('Test all valid combinations of mnemonics', () => { + const groups = [ + [3, 5, 'Group 0'], + [3, 3, 'Group 1'], + [2, 5, 'Group 2'], + [1, 1, 'Group 3'] + ]; + const slip = slip39.fromArray(MS, { + threshold: 2, + groups: groups, + title: 'Trezor one SSSS' + }); + + const group2Mnemonics = slip.fromPath('r/2').mnemonics; + const group3Mnemonic = slip.fromPath('r/3').mnemonics[0]; + + it('Should include overall split title', () => { + assert.equal(slip.fromPath('r').description, 'Trezor one SSSS'); + }); + it('Should include group descriptions', () => { + assert.equal(slip.fromPath('r/0').description, 'Group 0'); + assert.equal(slip.fromPath('r/1').description, 'Group 1'); + assert.equal(slip.fromPath('r/2').description, 'Group 2'); + assert.equal(slip.fromPath('r/3').description, 'Group 3'); + }); + it('Should return the valid master secret when it tested with minimal sets of mnemonics.', () => { + const mnemonics = group2Mnemonics.filter((_, index) => { + return index === 0 || index === 2; + }).concat(group3Mnemonic); + + assert(MS.slip39DecodeHex() === slip39.recoverSecret(mnemonics).slip39DecodeHex()); + }); + it('TODO: Should NOT return the valid master secret when one complete group and one incomplete group out of two groups required', () => { + assert(true); + }); + it('TODO: Should return the valid master secret when one group of two required but only one applied.', () => { + assert(true); + }); + }); +}); + +describe('Original test vectors Tests', () => { + let fs = require('fs'); + let path = require('path'); + let filePath = path.join(__dirname, 'vectors.json'); + + let content = fs.readFileSync(filePath, 'utf-8'); + + const tests = JSON.parse(content); + tests.forEach((item) => { + let description = item[0]; + let mnemonics = item[1]; + let masterSecret = Buffer.from(item[2], 'hex'); + + it(description, () => { + if (masterSecret.length !== 0) { + let ms = slip39.recoverSecret(mnemonics, PASSPHRASE); + assert(masterSecret.every((v, i) => v === ms[i])); + } else { + assert.throws(() => slip39.recoverSecret(mnemonics, PASSPHRASE), Error); + } + }); + }); +}); + +describe('Invalid Shares', () => { + const tests = [ + ['Short master secret', 1, [ + [2, 3] + ], MS.slice(0, 14)], + ['Odd length master secret', 1, [ + [2, 3] + ], MS.concat([55])], + ['Group threshold exceeds number of groups', 3, [ + [3, 5], + [2, 5] + ], MS], + ['Invalid group threshold.', 0, [ + [3, 5], + [2, 5] + ], MS], + ['Member threshold exceeds number of members', 2, [ + [3, 2], + [2, 5] + ], MS], + ['Invalid member threshold', 2, [ + [0, 2], + [2, 5] + ], MS], + ['Group with multiple members and threshold 1', 2, [ + [3, 5], + [1, 3], + [2, 5] + ], MS] + ]; + + tests.forEach((item) => { + let description = item[0]; + let threshold = item[1]; + + let groups = item[2]; + let secret = item[3]; + + it(description, () => { + assert.throws(() => + slip39.fromArray(secret, { + threshold: threshold, + groups: groups + }), Error); + }); + }); +}); + +describe('Mnemonic Validation', () => { + describe('Valid Mnemonics', () => { + let mnemonics = slip15.fromPath('r/0').mnemonics; + + mnemonics.forEach((mnemonic, index) => { + it(`Mnemonic at index ${index} should be valid`, () => { + const isValid = slip39.validateMnemonic(mnemonic); + + assert(isValid); + }); + }); + }); + + const vectors = [ + [ + '2. Mnemonic with invalid checksum (128 bits)', + [ + 'duckling enlarge academic academic agency result length solution fridge kidney coal piece deal husband erode duke ajar critical decision kidney' + ] + ], + [ + '21. Mnemonic with invalid checksum (256 bits)', + [ + 'theory painting academic academic armed sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips brave detect lunar' + ] + ], + [ + '3. Mnemonic with invalid padding (128 bits)', + [ + 'duckling enlarge academic academic email result length solution fridge kidney coal piece deal husband erode duke ajar music cargo fitness' + ] + ], + [ + '22. Mnemonic with invalid padding (256 bits)', + [ + 'theory painting academic academic campus sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips facility obtain sister' + ] + ], + [ + '10. Mnemonics with greater group threshold than group counts (128 bits)', + [ + 'music husband acrobat acid artist finance center either graduate swimming object bike medical clothes station aspect spider maiden bulb welcome', + 'music husband acrobat agency advance hunting bike corner density careful material civil evil tactics remind hawk discuss hobo voice rainbow', + 'music husband beard academic black tricycle clock mayor estimate level photo episode exclude ecology papa source amazing salt verify divorce' + ] + ], + [ + '29. Mnemonics with greater group threshold than group counts (256 bits)', + [ + 'smirk pink acrobat acid auction wireless impulse spine sprinkle fortune clogs elbow guest hush loyalty crush dictate tracks airport talent', + 'smirk pink acrobat agency dwarf emperor ajar organize legs slice harvest plastic dynamic style mobile float bulb health coding credit', + 'smirk pink beard academic alto strategy carve shame language rapids ruin smart location spray training acquire eraser endorse submit peaceful' + ] + ], + [ + '39. Mnemonic with insufficient length', + [ + 'junk necklace academic academic acne isolate join hesitate lunar roster dough calcium chemical ladybug amount mobile glasses verify cylinder' + ] + ], + [ + '40. Mnemonic with invalid master secret length', + [ + 'fraction necklace academic academic award teammate mouse regular testify coding building member verdict purchase blind camera duration email prepare spirit quarter' + ] + ] + ]; + + vectors.forEach((item) => { + const description = item[0]; + const mnemonics = item[1]; + + describe(description, () => { + mnemonics.forEach((mnemonic, index) => { + it(`Mnemonic at index ${index} should be invalid`, () => { + const isValid = slip39.validateMnemonic(mnemonic); + + assert(isValid === false); + }); + }); + }); + }); +}); + +function itTestArray(t, g, gs) { + it( + `recover master secret for ${t} shares (threshold=${t}) of ${g} '[1, 1,]' groups",`, + () => { + let slip = slip39.fromArray(MS, { + groups: gs.slice(0, g), + passphrase: PASSPHRASE, + threshold: t + }); + + let mnemonics = slip.fromPath('r').mnemonics.slice(0, t); + + let recoveredSecret = + slip39.recoverSecret(mnemonics, PASSPHRASE); + + assert(MASTERSECRET === String.fromCharCode(...recoveredSecret)); + }); +} + +describe('Groups test (T=1, N=1 e.g. [1,1]) - ', () => { + let totalGroups = 16; + let groups = Array.from(Array(totalGroups), () => [1, 1]); + + for (group = 1; group <= totalGroups; group++) { + for (threshold = 1; threshold <= group; threshold++) { + itTestArray(threshold, group, groups); + } + } +}); diff --git a/blue_modules/slip39/test/vectors.json b/blue_modules/slip39/test/vectors.json new file mode 100644 index 000000000..ca83a10e9 --- /dev/null +++ b/blue_modules/slip39/test/vectors.json @@ -0,0 +1,322 @@ +[ + [ + "1. Valid mnemonic without sharing (128 bits)", + [ + "duckling enlarge academic academic agency result length solution fridge kidney coal piece deal husband erode duke ajar critical decision keyboard" + ], + "bb54aac4b89dc868ba37d9cc21b2cece" + ], + [ + "2. Mnemonic with invalid checksum (128 bits)", + [ + "duckling enlarge academic academic agency result length solution fridge kidney coal piece deal husband erode duke ajar critical decision kidney" + ], + "" + ], + [ + "3. Mnemonic with invalid padding (128 bits)", + [ + "duckling enlarge academic academic email result length solution fridge kidney coal piece deal husband erode duke ajar music cargo fitness" + ], + "" + ], + [ + "4. Basic sharing 2-of-3 (128 bits)", + [ + "shadow pistol academic always adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip making coding armed", + "shadow pistol academic acid actress prayer class unknown daughter sweater depict flip twice unkind craft early superior advocate guest smoking" + ], + "b43ceb7e57a0ea8766221624d01b0864" + ], + [ + "5. Basic sharing 2-of-3 (128 bits)", + [ + "shadow pistol academic always adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip making coding armed" + ], + "" + ], + [ + "6. Mnemonics with different identifiers (128 bits)", + [ + "adequate smoking academic acid debut wine petition glen cluster slow rhyme slow simple epidemic rumor junk tracks treat olympic tolerate", + "adequate stay academic agency agency formal party ting frequent learn upstairs remember smear leaf damage anatomy ladle market hush corner" + ], + "" + ], + [ + "7. Mnemonics with different iteration exponents (128 bits)", + [ + "peasant leaves academic acid desert exact olympic math alive axle trial tackle drug deny decent smear dominant desert bucket remind", + "peasant leader academic agency cultural blessing percent network envelope medal junk primary human pumps jacket fragment payroll ticket evoke voice" + ], + "" + ], + [ + "8. Mnemonics with mismatching group thresholds (128 bits)", + [ + "liberty category beard echo animal fawn temple briefing math username various wolf aviation fancy visual holy thunder yelp helpful payment", + "liberty category beard email beyond should fancy romp founder easel pink holy hairy romp loyalty material victim owner toxic custody", + "liberty category academic easy being hazard crush diminish oral lizard reaction cluster force dilemma deploy force club veteran expect photo" + ], + "" + ], + [ + "9. Mnemonics with mismatching group counts (128 bits)", + [ + "average senior academic leaf broken teacher expect surface hour capture obesity desire negative dynamic dominant pistol mineral mailman iris aide", + "average senior academic agency curious pants blimp spew clothes slice script dress wrap firm shaft regular slavery negative theater roster" + ], + "" + ], + [ + "10. Mnemonics with greater group threshold than group counts (128 bits)", + [ + "music husband acrobat acid artist finance center either graduate swimming object bike medical clothes station aspect spider maiden bulb welcome", + "music husband acrobat agency advance hunting bike corner density careful material civil evil tactics remind hawk discuss hobo voice rainbow", + "music husband beard academic black tricycle clock mayor estimate level photo episode exclude ecology papa source amazing salt verify divorce" + ], + "" + ], + [ + "11. Mnemonics with duplicate member indices (128 bits)", + [ + "device stay academic always dive coal antenna adult black exceed stadium herald advance soldier busy dryer daughter evaluate minister laser", + "device stay academic always dwarf afraid robin gravity crunch adjust soul branch walnut coastal dream costume scholar mortgage mountain pumps" + ], + "" + ], + [ + "12. Mnemonics with mismatching member thresholds (128 bits)", + [ + "hour painting academic academic device formal evoke guitar random modern justice filter withdraw trouble identify mailman insect general cover oven", + "hour painting academic agency artist again daisy capital beaver fiber much enjoy suitable symbolic identify photo editor romp float echo" + ], + "" + ], + [ + "13. Mnemonics giving an invalid digest (128 bits)", + [ + "guilt walnut academic acid deliver remove equip listen vampire tactics nylon rhythm failure husband fatigue alive blind enemy teaspoon rebound", + "guilt walnut academic agency brave hamster hobo declare herd taste alpha slim criminal mild arcade formal romp branch pink ambition" + ], + "" + ], + [ + "14. Insufficient number of groups (128 bits, case 1)", + [ + "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice" + ], + "" + ], + [ + "15. Insufficient number of groups (128 bits, case 2)", + [ + "eraser senior decision scared cargo theory device idea deliver modify curly include pancake both news skin realize vitamins away join", + "eraser senior decision roster beard treat identify grumpy salt index fake aviation theater cubic bike cause research dragon emphasis counter" + ], + "" + ], + [ + "16. Threshold number of groups, but insufficient number of members in one group (128 bits)", + [ + "eraser senior decision shadow artist work morning estate greatest pipeline plan ting petition forget hormone flexible general goat admit surface", + "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice" + ], + "" + ], + [ + "17. Threshold number of groups and members in each group (128 bits, case 1)", + [ + "eraser senior decision roster beard treat identify grumpy salt index fake aviation theater cubic bike cause research dragon emphasis counter", + "eraser senior ceramic snake clay various huge numb argue hesitate auction category timber browser greatest hanger petition script leaf pickup", + "eraser senior ceramic shaft dynamic become junior wrist silver peasant force math alto coal amazing segment yelp velvet image paces", + "eraser senior ceramic round column hawk trust auction smug shame alive greatest sheriff living perfect corner chest sled fumes adequate", + "eraser senior decision smug corner ruin rescue cubic angel tackle skin skunk program roster trash rumor slush angel flea amazing" + ], + "7c3397a292a5941682d7a4ae2d898d11" + ], + [ + "18. Threshold number of groups and members in each group (128 bits, case 2)", + [ + "eraser senior decision smug corner ruin rescue cubic angel tackle skin skunk program roster trash rumor slush angel flea amazing", + "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice", + "eraser senior decision scared cargo theory device idea deliver modify curly include pancake both news skin realize vitamins away join" + ], + "7c3397a292a5941682d7a4ae2d898d11" + ], + [ + "19. Threshold number of groups and members in each group (128 bits, case 3)", + [ + "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice", + "eraser senior acrobat romp bishop medical gesture pumps secret alive ultimate quarter priest subject class dictate spew material endless market" + ], + "7c3397a292a5941682d7a4ae2d898d11" + ], + [ + "20. Valid mnemonic without sharing (256 bits)", + [ + "theory painting academic academic armed sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips brave detect luck" + ], + "989baf9dcaad5b10ca33dfd8cc75e42477025dce88ae83e75a230086a0e00e92" + ], + [ + "21. Mnemonic with invalid checksum (256 bits)", + [ + "theory painting academic academic armed sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips brave detect lunar" + ], + "" + ], + [ + "22. Mnemonic with invalid padding (256 bits)", + [ + "theory painting academic academic campus sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips facility obtain sister" + ], + "" + ], + [ + "23. Basic sharing 2-of-3 (256 bits)", + [ + "humidity disease academic always aluminum jewelry energy woman receiver strategy amuse duckling lying evidence network walnut tactics forget hairy rebound impulse brother survive clothes stadium mailman rival ocean reward venture always armed unwrap", + "humidity disease academic agency actress jacket gross physics cylinder solution fake mortgage benefit public busy prepare sharp friar change work slow purchase ruler again tricycle involve viral wireless mixture anatomy desert cargo upgrade" + ], + "c938b319067687e990e05e0da0ecce1278f75ff58d9853f19dcaeed5de104aae" + ], + [ + "24. Basic sharing 2-of-3 (256 bits)", + [ + "humidity disease academic always aluminum jewelry energy woman receiver strategy amuse duckling lying evidence network walnut tactics forget hairy rebound impulse brother survive clothes stadium mailman rival ocean reward venture always armed unwrap" + ], + "" + ], + [ + "25. Mnemonics with different identifiers (256 bits)", + [ + "smear husband academic acid deadline scene venture distance dive overall parking bracelet elevator justice echo burning oven chest duke nylon", + "smear isolate academic agency alpha mandate decorate burden recover guard exercise fatal force syndrome fumes thank guest drift dramatic mule" + ], + "" + ], + [ + "26. Mnemonics with different iteration exponents (256 bits)", + [ + "finger trash academic acid average priority dish revenue academic hospital spirit western ocean fact calcium syndrome greatest plan losing dictate", + "finger traffic academic agency building lilac deny paces subject threaten diploma eclipse window unknown health slim piece dragon focus smirk" + ], + "" + ], + [ + "27. Mnemonics with mismatching group thresholds (256 bits)", + [ + "flavor pink beard echo depart forbid retreat become frost helpful juice unwrap reunion credit math burning spine black capital lair", + "flavor pink beard email diet teaspoon freshman identify document rebound cricket prune headset loyalty smell emission skin often square rebound", + "flavor pink academic easy credit cage raisin crazy closet lobe mobile become drink human tactics valuable hand capture sympathy finger" + ], + "" + ], + [ + "28. Mnemonics with mismatching group counts (256 bits)", + [ + "column flea academic leaf debut extra surface slow timber husky lawsuit game behavior husky swimming already paper episode tricycle scroll", + "column flea academic agency blessing garbage party software stadium verify silent umbrella therapy decorate chemical erode dramatic eclipse replace apart" + ], + "" + ], + [ + "29. Mnemonics with greater group threshold than group counts (256 bits)", + [ + "smirk pink acrobat acid auction wireless impulse spine sprinkle fortune clogs elbow guest hush loyalty crush dictate tracks airport talent", + "smirk pink acrobat agency dwarf emperor ajar organize legs slice harvest plastic dynamic style mobile float bulb health coding credit", + "smirk pink beard academic alto strategy carve shame language rapids ruin smart location spray training acquire eraser endorse submit peaceful" + ], + "" + ], + [ + "30. Mnemonics with duplicate member indices (256 bits)", + [ + "fishing recover academic always device craft trend snapshot gums skin downtown watch device sniff hour clock public maximum garlic born", + "fishing recover academic always aircraft view software cradle fangs amazing package plastic evaluate intend penalty epidemic anatomy quarter cage apart" + ], + "" + ], + [ + "31. Mnemonics with mismatching member thresholds (256 bits)", + [ + "evoke garden academic academic answer wolf scandal modern warmth station devote emerald market physics surface formal amazing aquatic gesture medical", + "evoke garden academic agency deal revenue knit reunion decrease magazine flexible company goat repair alarm military facility clogs aide mandate" + ], + "" + ], + [ + "32. Mnemonics giving an invalid digest (256 bits)", + [ + "river deal academic acid average forbid pistol peanut custody bike class aunt hairy merit valid flexible learn ajar very easel", + "river deal academic agency camera amuse lungs numb isolate display smear piece traffic worthy year patrol crush fact fancy emission" + ], + "" + ], + [ + "33. Insufficient number of groups (256 bits, case 1)", + [ + "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium" + ], + "" + ], + [ + "34. Insufficient number of groups (256 bits, case 2)", + [ + "wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen", + "wildlife deal decision smug ancestor genuine move huge cubic strategy smell game costume extend swimming false desire fake traffic vegan senior twice timber submit leader payroll fraction apart exact forward pulse tidy install" + ], + "" + ], + [ + "35. Threshold number of groups, but insufficient number of members in one group (256 bits)", + [ + "wildlife deal decision shadow analysis adjust bulb skunk muscle mandate obesity total guitar coal gravity carve slim jacket ruin rebuild ancestor numerous hour mortgage require herd maiden public ceiling pecan pickup shadow club", + "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium" + ], + "" + ], + [ + "36. Threshold number of groups and members in each group (256 bits, case 1)", + [ + "wildlife deal ceramic round aluminum pitch goat racism employer miracle percent math decision episode dramatic editor lily prospect program scene rebuild display sympathy have single mustang junction relate often chemical society wits estate", + "wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen", + "wildlife deal ceramic scatter argue equip vampire together ruin reject literary rival distance aquatic agency teammate rebound false argue miracle stay again blessing peaceful unknown cover beard acid island language debris industry idle", + "wildlife deal ceramic snake agree voter main lecture axis kitchen physics arcade velvet spine idea scroll promise platform firm sharp patrol divorce ancestor fantasy forbid goat ajar believe swimming cowboy symbolic plastic spelling", + "wildlife deal decision shadow analysis adjust bulb skunk muscle mandate obesity total guitar coal gravity carve slim jacket ruin rebuild ancestor numerous hour mortgage require herd maiden public ceiling pecan pickup shadow club" + ], + "5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b" + ], + [ + "37. Threshold number of groups and members in each group (256 bits, case 2)", + [ + "wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen", + "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium", + "wildlife deal decision smug ancestor genuine move huge cubic strategy smell game costume extend swimming false desire fake traffic vegan senior twice timber submit leader payroll fraction apart exact forward pulse tidy install" + ], + "5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b" + ], + [ + "38. Threshold number of groups and members in each group (256 bits, case 3)", + [ + "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium", + "wildlife deal acrobat romp anxiety axis starting require metric flexible geology game drove editor edge screw helpful have huge holy making pitch unknown carve holiday numb glasses survive already tenant adapt goat fangs" + ], + "5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b" + ], + [ + "39. Mnemonic with insufficient length", + [ + "junk necklace academic academic acne isolate join hesitate lunar roster dough calcium chemical ladybug amount mobile glasses verify cylinder" + ], + "" + ], + [ + "40. Mnemonic with invalid master secret length", + [ + "fraction necklace academic academic award teammate mouse regular testify coding building member verdict purchase blind camera duration email prepare spirit quarter" + ], + "" + ] +] diff --git a/class/app-storage.js b/class/app-storage.js index 7d4f52820..1c2f482d5 100644 --- a/class/app-storage.js +++ b/class/app-storage.js @@ -16,6 +16,9 @@ import { HDSegwitElectrumSeedP2WPKHWallet, HDAezeedWallet, MultisigHDWallet, + SLIP39SegwitP2SHWallet, + SLIP39LegacyP2PKHWallet, + SLIP39SegwitBech32Wallet, } from './'; const encryption = require('../blue_modules/encryption'); const Realm = require('realm'); @@ -273,6 +276,15 @@ export class AppStorage { case HDAezeedWallet.type: unserializedWallet = HDAezeedWallet.fromJson(key); break; + case SLIP39SegwitP2SHWallet.type: + unserializedWallet = SLIP39SegwitP2SHWallet.fromJson(key); + break; + case SLIP39LegacyP2PKHWallet.type: + unserializedWallet = SLIP39LegacyP2PKHWallet.fromJson(key); + break; + case SLIP39SegwitBech32Wallet.type: + unserializedWallet = SLIP39SegwitBech32Wallet.fromJson(key); + break; case LightningCustodianWallet.type: { /** @type {LightningCustodianWallet} */ unserializedWallet = LightningCustodianWallet.fromJson(key); diff --git a/class/index.js b/class/index.js index 2d475b443..25d5abf1b 100644 --- a/class/index.js +++ b/class/index.js @@ -15,5 +15,6 @@ export * from './wallets/hd-legacy-electrum-seed-p2pkh-wallet'; export * from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet'; export * from './wallets/hd-aezeed-wallet'; export * from './wallets/multisig-hd-wallet'; +export * from './wallets/slip39-wallets'; export * from './hd-segwit-bech32-transaction'; export * from './multisig-cosigner'; diff --git a/class/wallet-gradient.js b/class/wallet-gradient.js index 7a665ceba..9481cbc61 100644 --- a/class/wallet-gradient.js +++ b/class/wallet-gradient.js @@ -10,7 +10,8 @@ import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet'; import { HDLegacyElectrumSeedP2PKHWallet } from './wallets/hd-legacy-electrum-seed-p2pkh-wallet'; import { HDSegwitElectrumSeedP2WPKHWallet } from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet'; import { MultisigHDWallet } from './wallets/multisig-hd-wallet'; -import { HDAezeedWallet } from "./wallets/hd-aezeed-wallet"; +import { HDAezeedWallet } from './wallets/hd-aezeed-wallet'; +import { SLIP39LegacyP2PKHWallet, SLIP39SegwitP2SHWallet, SLIP39SegwitBech32Wallet } from './wallets/slip39-wallets'; import { useTheme } from '@react-navigation/native'; export default class WalletGradient { @@ -43,16 +44,19 @@ export default class WalletGradient { break; case HDLegacyP2PKHWallet.type: case HDLegacyElectrumSeedP2PKHWallet.type: + case SLIP39LegacyP2PKHWallet.type: gradient = WalletGradient.hdLegacyP2PKHWallet; break; case HDLegacyBreadwalletWallet.type: gradient = WalletGradient.hdLegacyBreadWallet; break; case HDSegwitP2SHWallet.type: + case SLIP39SegwitP2SHWallet.type: gradient = WalletGradient.hdSegwitP2SHWallet; break; case HDSegwitBech32Wallet.type: case HDSegwitElectrumSeedP2WPKHWallet.type: + case SLIP39SegwitBech32Wallet.type: gradient = WalletGradient.hdSegwitBech32Wallet; break; case LightningCustodianWallet.type: @@ -81,7 +85,7 @@ export default class WalletGradient { let props; switch (type) { case MultisigHDWallet.type: - /* Example + /* Example props = { start: { x: 0, y: 0 } }; https://github.com/react-native-linear-gradient/react-native-linear-gradient */ @@ -103,16 +107,19 @@ export default class WalletGradient { break; case HDLegacyP2PKHWallet.type: case HDLegacyElectrumSeedP2PKHWallet.type: + case SLIP39LegacyP2PKHWallet.type: gradient = WalletGradient.hdLegacyP2PKHWallet; break; case HDLegacyBreadwalletWallet.type: gradient = WalletGradient.hdLegacyBreadWallet; break; case HDSegwitP2SHWallet.type: + case SLIP39SegwitP2SHWallet.type: gradient = WalletGradient.hdSegwitP2SHWallet; break; case HDSegwitBech32Wallet.type: case HDSegwitElectrumSeedP2WPKHWallet.type: + case SLIP39SegwitBech32Wallet.type: gradient = WalletGradient.hdSegwitBech32Wallet; break; case SegwitBech32Wallet.type: diff --git a/class/wallet-import.js b/class/wallet-import.js index 3ad93a518..7c9d81b1b 100644 --- a/class/wallet-import.js +++ b/class/wallet-import.js @@ -14,6 +14,9 @@ import { HDSegwitElectrumSeedP2WPKHWallet, HDAezeedWallet, MultisigHDWallet, + SLIP39LegacyP2PKHWallet, + SLIP39SegwitP2SHWallet, + SLIP39SegwitBech32Wallet, } from '.'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import loc from '../loc'; @@ -110,6 +113,8 @@ function WalletImport() { // 7. check if its private key (segwit address P2SH) TODO // 7. check if its private key (legacy address) TODO + importText = importText.trim(); + if (importText.startsWith('6P')) { let password = false; do { @@ -156,7 +161,6 @@ function WalletImport() { } // trying other wallet types - const hd4 = new HDSegwitBech32Wallet(); hd4.setSecret(importText); if (hd4.validateMnemonic()) { @@ -275,6 +279,31 @@ function WalletImport() { return WalletImport._saveWallet(watchOnly, additionalProperties); } + // if it is multi-line string, then it is probably SLIP39 wallet + // each line - one share + if (importText.includes('\n')) { + const s1 = new SLIP39SegwitP2SHWallet(); + s1.setSecret(importText); + + if (s1.validateMnemonic()) { + if (await s1.wasEverUsed()) { + return WalletImport._saveWallet(s1); + } + + const s2 = new SLIP39LegacyP2PKHWallet(); + s2.setSecret(importText); + if (await s2.wasEverUsed()) { + return WalletImport._saveWallet(s2); + } + + const s3 = new SLIP39SegwitBech32Wallet(); + s3.setSecret(importText); + if (await s3.wasEverUsed()) { + return WalletImport._saveWallet(s3); + } + } + } + // nope? // TODO: try a raw private key diff --git a/class/wallets/abstract-hd-electrum-wallet.js b/class/wallets/abstract-hd-electrum-wallet.js index aead29417..1807a3ca5 100644 --- a/class/wallets/abstract-hd-electrum-wallet.js +++ b/class/wallets/abstract-hd-electrum-wallet.js @@ -96,8 +96,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { */ _getWIFByIndex(internal, index) { if (!this.secret) return false; - const mnemonic = this.secret; - const seed = bip39.mnemonicToSeed(mnemonic); + const seed = this._getSeed(); const root = HDNode.fromSeed(seed); const path = `m/84'/0'/0'/${internal ? 1 : 0}/${index}`; const child = root.derivePath(path); @@ -188,8 +187,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { return this._xpub; // cache hit } // first, getting xpub - const mnemonic = this.secret; - const seed = bip39.mnemonicToSeed(mnemonic); + const seed = this._getSeed(); const root = HDNode.fromSeed(seed); const path = "m/84'/0'/0'"; @@ -1082,8 +1080,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { * @returns {{ tx: Transaction }} */ cosignPsbt(psbt) { - const mnemonic = this.secret; - const seed = bip39.mnemonicToSeed(mnemonic); + const seed = this._getSeed(); const hdRoot = HDNode.fromSeed(seed); for (let cc = 0; cc < psbt.inputCount; cc++) { @@ -1114,11 +1111,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } /** - * @param mnemonic {string} Mnemonic seed phrase + * @param seed {Buffer} Buffer object with seed * @returns {string} Hex string of fingerprint derived from mnemonics. Always has lenght of 8 chars and correct leading zeroes */ - static seedToFingerprint(mnemonic) { - const seed = bip39.mnemonicToSeed(mnemonic); + static seedToFingerprint(seed) { const root = bitcoin.bip32.fromSeed(seed); let hex = root.fingerprint.toString('hex'); while (hex.length < 8) hex = '0' + hex; // leading zeroes @@ -1129,6 +1125,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { * @returns {string} Hex string of fingerprint derived from wallet mnemonics. Always has lenght of 8 chars and correct leading zeroes */ getMasterFingerprintHex() { - return AbstractHDElectrumWallet.seedToFingerprint(this.secret); + const seed = this._getSeed(); + return AbstractHDElectrumWallet.seedToFingerprint(seed); } } diff --git a/class/wallets/abstract-hd-wallet.js b/class/wallets/abstract-hd-wallet.js index 29ce35ee8..085231e42 100644 --- a/class/wallets/abstract-hd-wallet.js +++ b/class/wallets/abstract-hd-wallet.js @@ -47,6 +47,14 @@ export class AbstractHDWallet extends LegacyWallet { throw new Error('Not implemented'); } + /** + * @return {Buffer} wallet seed + */ + _getSeed() { + const mnemonic = this.secret; + return bip39.mnemonicToSeed(mnemonic); + } + setSecret(newSecret) { this.secret = newSecret.trim().toLowerCase(); this.secret = this.secret.replace(/[^a-zA-Z0-9]/g, ' ').replace(/\s+/g, ' '); diff --git a/class/wallets/abstract-wallet.js b/class/wallets/abstract-wallet.js index 2e84887b9..a74eda4ef 100644 --- a/class/wallets/abstract-wallet.js +++ b/class/wallets/abstract-wallet.js @@ -125,6 +125,10 @@ export class AbstractWallet { return false; } + allowXpub() { + return false; + } + weOwnAddress(address) { throw Error('not implemented'); } diff --git a/class/wallets/hd-aezeed-wallet.js b/class/wallets/hd-aezeed-wallet.js index 190a495f6..423b7b431 100644 --- a/class/wallets/hd-aezeed-wallet.js +++ b/class/wallets/hd-aezeed-wallet.js @@ -176,4 +176,8 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet { allowSignVerifyMessage() { return true; } + + allowXpub() { + return true; + } } diff --git a/class/wallets/hd-legacy-breadwallet-wallet.js b/class/wallets/hd-legacy-breadwallet-wallet.js index 37e376128..27d3afa87 100644 --- a/class/wallets/hd-legacy-breadwallet-wallet.js +++ b/class/wallets/hd-legacy-breadwallet-wallet.js @@ -1,4 +1,3 @@ -import bip39 from 'bip39'; import * as bip32 from 'bip32'; import * as bitcoinjs from 'bitcoinjs-lib'; import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; @@ -27,8 +26,7 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { if (this._xpub) { return this._xpub; // cache hit } - const mnemonic = this.secret; - const seed = bip39.mnemonicToSeed(mnemonic); + const seed = this._getSeed(); const root = bip32.fromSeed(seed); const path = "m/0'"; @@ -109,8 +107,7 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { */ _getWIFByIndex(internal, index) { if (!this.secret) return false; - const mnemonic = this.secret; - const seed = bip39.mnemonicToSeed(mnemonic); + const seed = this._getSeed(); const root = bitcoinjs.bip32.fromSeed(seed); const path = `m/0'/${internal ? 1 : 0}/${index}`; const child = root.derivePath(path); diff --git a/class/wallets/hd-legacy-p2pkh-wallet.js b/class/wallets/hd-legacy-p2pkh-wallet.js index a91749b0f..a0fcb5b22 100644 --- a/class/wallets/hd-legacy-p2pkh-wallet.js +++ b/class/wallets/hd-legacy-p2pkh-wallet.js @@ -1,4 +1,3 @@ -import bip39 from 'bip39'; import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; const bitcoin = require('bitcoinjs-lib'); const HDNode = require('bip32'); @@ -30,12 +29,15 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet { return true; } + allowXpub() { + return true; + } + getXpub() { if (this._xpub) { return this._xpub; // cache hit } - const mnemonic = this.secret; - const seed = bip39.mnemonicToSeed(mnemonic); + const seed = this._getSeed(); const root = bitcoin.bip32.fromSeed(seed); const path = "m/44'/0'/0'"; @@ -62,8 +64,7 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet { */ _getWIFByIndex(internal, index) { if (!this.secret) return false; - const mnemonic = this.secret; - const seed = bip39.mnemonicToSeed(mnemonic); + const seed = this._getSeed(); const root = HDNode.fromSeed(seed); const path = `m/44'/0'/0'/${internal ? 1 : 0}/${index}`; diff --git a/class/wallets/hd-segwit-bech32-wallet.js b/class/wallets/hd-segwit-bech32-wallet.js index b8feaf10a..ee8444299 100644 --- a/class/wallets/hd-segwit-bech32-wallet.js +++ b/class/wallets/hd-segwit-bech32-wallet.js @@ -38,4 +38,8 @@ export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet { allowMasterFingerprint() { return true; } + + allowXpub() { + return true; + } } diff --git a/class/wallets/hd-segwit-p2sh-wallet.js b/class/wallets/hd-segwit-p2sh-wallet.js index b4ed167d0..eb4096948 100644 --- a/class/wallets/hd-segwit-p2sh-wallet.js +++ b/class/wallets/hd-segwit-p2sh-wallet.js @@ -1,4 +1,3 @@ -import bip39 from 'bip39'; import b58 from 'bs58check'; import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; const bitcoin = require('bitcoinjs-lib'); @@ -35,6 +34,10 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet { return true; } + allowXpub() { + return true; + } + /** * Get internal/external WIF by wallet index * @param {Boolean} internal @@ -44,8 +47,7 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet { */ _getWIFByIndex(internal, index) { if (!this.secret) return false; - const mnemonic = this.secret; - const seed = bip39.mnemonicToSeed(mnemonic); + const seed = this._getSeed(); const root = bitcoin.bip32.fromSeed(seed); const path = `m/49'/0'/0'/${internal ? 1 : 0}/${index}`; const child = root.derivePath(path); @@ -92,8 +94,7 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet { return this._xpub; // cache hit } // first, getting xpub - const mnemonic = this.secret; - const seed = bip39.mnemonicToSeed(mnemonic); + const seed = this._getSeed(); const root = HDNode.fromSeed(seed); const path = "m/49'/0'/0'"; diff --git a/class/wallets/multisig-hd-wallet.js b/class/wallets/multisig-hd-wallet.js index a75f2ffca..dd813f7ae 100644 --- a/class/wallets/multisig-hd-wallet.js +++ b/class/wallets/multisig-hd-wallet.js @@ -185,7 +185,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { } else { // mnemonics. lets derive fingerprint (if it wasnt provided) if (!bip39.validateMnemonic(key)) throw new Error('Not a valid mnemonic phrase'); - fingerprint = fingerprint || MultisigHDWallet.seedToFingerprint(key); + fingerprint = fingerprint || MultisigHDWallet.mnemonicToFingerprint(key); } if (fingerprint && this._cosignersFingerprints.indexOf(fingerprint.toUpperCase()) !== -1 && fingerprint !== '00000000') { @@ -432,7 +432,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { const xpub = this.convertXpubToMultisignatureXpub( MultisigHDWallet.seedToXpub(this._cosigners[index], this._cosignersCustomPaths[index] || this._derivationPath), ); - const fingerprint = MultisigHDWallet.seedToFingerprint(this._cosigners[index]); + const fingerprint = MultisigHDWallet.mnemonicToFingerprint(this._cosigners[index]); ret += fingerprint + ': ' + xpub + '\n'; } else { ret += 'seed: ' + this._cosigners[index] + '\n'; @@ -1037,7 +1037,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { if (index === -1) return; if (!MultisigHDWallet.isXpubValid(newCosigner)) { // its not an xpub, so lets derive fingerprint ourselves - newFp = MultisigHDWallet.seedToFingerprint(newCosigner); + newFp = MultisigHDWallet.mnemonicToFingerprint(newCosigner); if (oldFp !== newFp) { throw new Error('Fingerprint of new seed doesnt match'); } @@ -1103,4 +1103,9 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { static isXpubForMultisig(xpub) { return ['xpub', 'Ypub', 'Zpub'].includes(xpub.substring(0, 4)); } + + static mnemonicToFingerprint(mnemonic) { + const seed = bip39.mnemonicToSeed(mnemonic); + return MultisigHDWallet.seedToFingerprint(seed); + } } diff --git a/class/wallets/slip39-wallets.js b/class/wallets/slip39-wallets.js new file mode 100644 index 000000000..ed7a891c0 --- /dev/null +++ b/class/wallets/slip39-wallets.js @@ -0,0 +1,75 @@ +import slip39 from 'slip39'; +import createHash from 'create-hash'; + +import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; +import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet'; +import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; + +// collection of SLIP39 functions +const SLIP39Mixin = { + _getSeed() { + const master = slip39.recoverSecret(this.secret); + return Buffer.from(master); + }, + + validateMnemonic() { + if (!this.secret.every(m => slip39.validateMnemonic(m))) return false; + + try { + slip39.recoverSecret(this.secret); + } catch (e) { + return false; + } + return true; + }, + + setSecret(newSecret) { + this.secret = newSecret + .trim() + .split('\n') + .filter(s => s) + .map(s => + s + .trim() + .toLowerCase() + .replace(/[^a-zA-Z0-9]/g, ' ') + .replace(/\s+/g, ' '), + ); + return this; + }, + + getID() { + const string2hash = this.secret.sort().join(','); + return createHash('sha256').update(string2hash).digest().toString('hex'); + }, +}; + +export class SLIP39LegacyP2PKHWallet extends HDLegacyP2PKHWallet { + static type = 'SLIP39legacyP2PKH'; + static typeReadable = 'SLIP39 Legacy (P2PKH)'; + + _getSeed = SLIP39Mixin._getSeed; + validateMnemonic = SLIP39Mixin.validateMnemonic; + setSecret = SLIP39Mixin.setSecret; + getID = SLIP39Mixin.getID; +} + +export class SLIP39SegwitP2SHWallet extends HDSegwitP2SHWallet { + static type = 'SLIP39segwitP2SH'; + static typeReadable = 'SLIP39 SegWit (P2SH)'; + + _getSeed = SLIP39Mixin._getSeed; + validateMnemonic = SLIP39Mixin.validateMnemonic; + setSecret = SLIP39Mixin.setSecret; + getID = SLIP39Mixin.getID; +} + +export class SLIP39SegwitBech32Wallet extends HDSegwitBech32Wallet { + static type = 'SLIP39segwitBech32'; + static typeReadable = 'SLIP39 SegWit (Bech32)'; + + _getSeed = SLIP39Mixin._getSeed; + validateMnemonic = SLIP39Mixin.validateMnemonic; + setSecret = SLIP39Mixin.setSecret; + getID = SLIP39Mixin.getID; +} diff --git a/package-lock.json b/package-lock.json index 5223f17ef..e4ae81432 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6707,6 +6707,17 @@ "safer-buffer": "~2.1.0" } }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "assert": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", @@ -7747,6 +7758,66 @@ "safe-buffer": "^5.0.1" } }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + }, + "dependencies": { + "bn.js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" + } + } + }, + "browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "bn.js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" + } + } + }, "browserslist": { "version": "4.14.6", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.6.tgz", @@ -8503,6 +8574,15 @@ "parse-json": "^4.0.0" } }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, "create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -8804,6 +8884,15 @@ "react-clone-referenced-element": "*" } }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", @@ -9016,6 +9105,16 @@ "integrity": "sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==", "dev": true }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, "dijkstrajs": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.1.tgz", @@ -15162,6 +15261,11 @@ "esprima": "^4.0.0" } }, + "jsbi": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.4.tgz", + "integrity": "sha512-52QRRFSsi9impURE8ZUbzAMCLjPm4THO7H2fcuIvaaeFTbSysvkodbQQXIVsNgq/ypDbq6dJiuGKL0vZ/i9hUg==" + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -16716,6 +16820,15 @@ "to-regex": "^3.0.2" } }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -17468,6 +17581,18 @@ } } }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", @@ -17849,6 +17974,19 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -18032,6 +18170,15 @@ "safe-buffer": "^5.1.0" } }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -18499,6 +18646,33 @@ "prop-types": "^15.6.2" } }, + "react-native-crypto": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-native-crypto/-/react-native-crypto-2.2.0.tgz", + "integrity": "sha512-eZu9Y8pa8BN9FU2pIex7MLRAi+Cd1Y6bsxfiufKh7sfraAACJvjQTeW7/zcQAT93WMfM+D0OVk+bubvkrbrUkw==", + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.4", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "3.0.8", + "public-encrypt": "^4.0.0", + "randomfill": "^1.0.3" + }, + "dependencies": { + "pbkdf2": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.8.tgz", + "integrity": "sha1-L4q/FuvsyCJ3lF10irodeHYfYeI=", + "requires": { + "create-hmac": "^1.1.2" + } + } + } + }, "react-native-default-preference": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/react-native-default-preference/-/react-native-default-preference-1.4.3.tgz", @@ -19887,6 +20061,15 @@ "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" }, + "slip39": { + "version": "file:blue_modules/slip39", + "requires": { + "create-hmac": "^1.1.3", + "jsbi": "^3.1.4", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", diff --git a/package.json b/package.json index ac81dda22..f1f301fde 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "android": "react-native run-android", "android:clean": "cd android; ./gradlew clean ; cd .. ; npm run android", "ios": "react-native run-ios", - "postinstall": "rn-nodeify --install buffer,events,process,stream,util,inherits,fs,path,assert --hack; npm run releasenotes2json; npm run podinstall; npx jetify", + "postinstall": "rn-nodeify --install buffer,events,process,stream,util,inherits,fs,path,assert,crypto --hack; npm run releasenotes2json; npm run podinstall; npx jetify", "test": "npm run lint && npm run unit && npm run jest", "jest": "jest -b -w 1 tests/integration/*", "maccatalystpatches": "./scripts/maccatalystpatches/applypatchesformaccatalyst.sh", @@ -123,10 +123,11 @@ "process": "0.11.10", "prop-types": "15.7.2", "react": "16.13.1", - "react-native": "0.63.4", "react-localization": "1.0.16", + "react-native": "0.63.4", "react-native-blue-crypto": "git+https://github.com/Overtorment/react-native-blue-crypto.git", "react-native-camera": "3.43.0", + "react-native-crypto": "2.2.0", "react-native-default-preference": "1.4.3", "react-native-device-info": "8.0.6", "react-native-document-picker": "git+https://github.com/BlueWallet/react-native-document-picker.git#3684d4fcc2bc0b47c32be39024e4796004c3e428", @@ -175,13 +176,14 @@ "rn-nodeify": "10.2.0", "scryptsy": "file:blue_modules/scryptsy", "secure-random": "1.1.2", + "slip39": "file:blue_modules/slip39", "stream-browserify": "2.0.2", "url": "0.11.0", "util": "0.12.3", "wif": "2.0.6" }, "react-native": { - "crypto": "bluewallet/blue_modules/crypto", + "crypto": "react-native-crypto", "path": "path-browserify", "fs": "react-native-level-fs", "_stream_transform": "readable-stream/transform", @@ -222,6 +224,7 @@ "runner-config": "tests/e2e/config.json" }, "browser": { + "crypto": "react-native-crypto", "path": "path-browserify", "fs": "react-native-level-fs", "_stream_transform": "readable-stream/transform", diff --git a/screen/selftest.js b/screen/selftest.js index 96f455f8b..251b4999e 100644 --- a/screen/selftest.js +++ b/screen/selftest.js @@ -5,7 +5,14 @@ import { ScrollView, View, StyleSheet } from 'react-native'; import loc from '../loc'; import { BlueSpacing20, SafeBlueArea, BlueCard, BlueText, BlueLoading } from '../BlueComponents'; import navigationStyle from '../components/navigationStyle'; -import { SegwitP2SHWallet, LegacyWallet, HDSegwitP2SHWallet, HDSegwitBech32Wallet, HDAezeedWallet } from '../class'; +import { + SegwitP2SHWallet, + LegacyWallet, + HDSegwitP2SHWallet, + HDSegwitBech32Wallet, + HDAezeedWallet, + SLIP39LegacyP2PKHWallet, +} from '../class'; const bitcoin = require('bitcoinjs-lib'); const BlueCrypto = require('react-native-blue-crypto'); const encryption = require('../blue_modules/encryption'); @@ -202,6 +209,16 @@ export default class Selftest extends Component { throw new Error('react-native-blue-crypto is not ok'); } + // slip39 test + if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') { + const w = new SLIP39LegacyP2PKHWallet(); + w.setSecret( + 'shadow pistol academic always adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip making coding armed\n' + + 'shadow pistol academic acid actress prayer class unknown daughter sweater depict flip twice unkind craft early superior advocate guest smoking', + ); + assertStrictEqual(w._getExternalAddressByIndex(0), '18pvMjy7AJbCDtv4TLYbGPbR7SzGzjqUpj'); + } + // } catch (Err) { errorMessage += Err; diff --git a/screen/wallets/details.js b/screen/wallets/details.js index 612f8722a..70648a3bc 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -19,9 +19,6 @@ import { import { BlueCard, BlueLoading, BlueSpacing10, BlueSpacing20, BlueText, SafeBlueArea, SecondButton } from '../../BlueComponents'; import navigationStyle from '../../components/navigationStyle'; import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet'; -import { HDLegacyBreadwalletWallet } from '../../class/wallets/hd-legacy-breadwallet-wallet'; -import { HDLegacyP2PKHWallet } from '../../class/wallets/hd-legacy-p2pkh-wallet'; -import { HDSegwitP2SHWallet } from '../../class/wallets/hd-segwit-p2sh-wallet'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import Biometric from '../../class/biometrics'; import { @@ -549,11 +546,7 @@ const WalletDetails = () => { )} - {(wallet.type === HDLegacyBreadwalletWallet.type || - wallet.type === HDLegacyP2PKHWallet.type || - wallet.type === HDSegwitBech32Wallet.type || - wallet.type === HDAezeedWallet.type || - wallet.type === HDSegwitP2SHWallet.type) && ( + {wallet.allowXpub() && ( <> diff --git a/screen/wallets/export.js b/screen/wallets/export.js index 42fe39816..ad04c5707 100644 --- a/screen/wallets/export.js +++ b/screen/wallets/export.js @@ -1,4 +1,4 @@ -import React, { useState, useCallback, useContext, useRef } from 'react'; +import React, { useState, useCallback, useContext } from 'react'; import { useWindowDimensions, InteractionManager, ScrollView, ActivityIndicator, StatusBar, View, StyleSheet } from 'react-native'; import QRCode from 'react-native-qrcode-svg'; import { useTheme, useNavigation, useFocusEffect, useRoute } from '@react-navigation/native'; @@ -37,11 +37,11 @@ const styles = StyleSheet.create({ const WalletExport = () => { const { wallets, saveToDisk } = useContext(BlueStorageContext); const { walletID } = useRoute().params; - const wallet = useRef(wallets.find(w => w.getID() === walletID)); const [isLoading, setIsLoading] = useState(true); const { goBack } = useNavigation(); const { colors } = useTheme(); const { width, height } = useWindowDimensions(); + const wallet = wallets.find(w => w.getID() === walletID); const stylesHook = { ...styles, loading: { @@ -68,8 +68,8 @@ const WalletExport = () => { return goBack(); } } - if (!wallet.current.getUserHasSavedExport()) { - wallet.current.setUserHasSavedExport(true); + if (!wallet.getUserHasSavedExport()) { + wallet.setUserHasSavedExport(true); saveToDisk(); } setIsLoading(false); @@ -80,52 +80,61 @@ const WalletExport = () => { Privacy.disableBlur(); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [goBack, walletID]), + }, [goBack, wallet]), ); - return isLoading && wallet ? ( - - - - ) : ( + if (isLoading || !wallet) + return ( + + + + ); + + // for SLIP39 we need to show all shares + let secrets = wallet.getSecret(); + if (typeof secrets === 'string') { + secrets = [secrets]; + } + + return ( - {wallet.current.typeReadable} + {wallet.typeReadable} - {(() => { - if ([LegacyWallet.type, SegwitBech32Wallet.type, SegwitP2SHWallet.type].includes(wallet.current.type)) { - return ( - - {wallet.current.getAddress()} - - ); - } - })()} - - - width ? width - 40 : width / 2} - logoSize={70} - color="#000000" - logoBackgroundColor={colors.brandingColor} - backgroundColor="#FFFFFF" - ecl="H" - /> - - {wallet.type !== WatchOnlyWallet.type && {loc.wallets.warning_do_not_disclose}} - - {wallet.current.type === LightningCustodianWallet.type || wallet.current.type === WatchOnlyWallet.type ? ( - - ) : ( - - {wallet.current.getSecret()} - + {[LegacyWallet.type, SegwitBech32Wallet.type, SegwitP2SHWallet.type].includes(wallet.type) && ( + + {wallet.getAddress()} + )} + + {secrets.map(s => ( + + + width ? width - 40 : width / 2} + logoSize={70} + color="#000000" + logoBackgroundColor={colors.brandingColor} + backgroundColor="#FFFFFF" + ecl="H" + /> + + {wallet.type !== WatchOnlyWallet.type && {loc.wallets.warning_do_not_disclose}} + + {wallet.type === LightningCustodianWallet.type || wallet.type === WatchOnlyWallet.type ? ( + + ) : ( + + {wallet.getSecret()} + + )} + + ))} ); diff --git a/shim.js b/shim.js index 5ee63e697..e7959b548 100644 --- a/shim.js +++ b/shim.js @@ -29,3 +29,7 @@ process.env.NODE_ENV = isDev ? 'development' : 'production'; if (typeof localStorage !== 'undefined') { localStorage.debug = isDev ? '*' : ''; } + +// If using the crypto shim, uncomment the following line to ensure +// crypto is loaded first, so it can populate global.crypto +require('crypto'); diff --git a/tests/integration/import.test.js b/tests/integration/import.test.js index 3feec499c..b3e9283b4 100644 --- a/tests/integration/import.test.js +++ b/tests/integration/import.test.js @@ -10,6 +10,7 @@ import { HDSegwitP2SHWallet, WatchOnlyWallet, HDAezeedWallet, + SLIP39SegwitP2SHWallet, } from '../../class'; import WalletImport from '../../class/wallet-import'; import React from 'react'; @@ -175,4 +176,16 @@ describe('import procedure', function () { ); assert.strictEqual(lastImportedWallet.type, WatchOnlyWallet.type); }); + + it('can import slip39 wallet', async () => { + // 2-of-3 slip39 wallet + // crystal lungs academic acid corner infant satisfy spider alcohol laser golden equation fiscal epidemic infant scholar space findings tadpole belong + // crystal lungs academic agency class payment actress avoid rebound ordinary exchange petition tendency mild mobile spine robin fancy shelter increase + // crystal lungs academic always earth satoshi elbow satoshi that pants formal leaf rival texture romantic filter expand regular soul desert + await WalletImport.processImportText( + 'crystal lungs academic acid corner infant satisfy spider alcohol laser golden equation fiscal epidemic infant scholar space findings tadpole belong\n' + + 'crystal lungs academic agency class payment actress avoid rebound ordinary exchange petition tendency mild mobile spine robin fancy shelter increase', + ); + assert.strictEqual(lastImportedWallet.type, SLIP39SegwitP2SHWallet.type); + }); }); diff --git a/tests/unit/slip39-wallets.test.js b/tests/unit/slip39-wallets.test.js new file mode 100644 index 000000000..4c2546bdc --- /dev/null +++ b/tests/unit/slip39-wallets.test.js @@ -0,0 +1,78 @@ +import assert from 'assert'; + +import { SLIP39LegacyP2PKHWallet, SLIP39SegwitP2SHWallet, SLIP39SegwitBech32Wallet } from '../../class'; + +global.crypto = require('crypto'); + +describe('SLIP39 wallets tests', () => { + it('can validateMnemonic', async () => { + const w = new SLIP39LegacyP2PKHWallet(); + // not enought shares + w.setSecret( + 'shadow pistol academic always adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip making coding armed', + ); + assert.strictEqual(w.validateMnemonic(), false); + + // wrong words + w.setSecret('qweasd ewqasd'); + assert.strictEqual(w.validateMnemonic(), false); + }); + + it('can generate ID', () => { + const w = new SLIP39LegacyP2PKHWallet(); + // not enought shares + w.setSecret( + 'shadow pistol academic always adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip making coding armed', + ); + + assert.ok(w.getID()); + }); + + it('SLIP39LegacyP2PKHWallet can generate addresses', async () => { + const w = new SLIP39LegacyP2PKHWallet(); + // 4. Basic sharing 2-of-3 (128 bits) + w.setSecret( + 'shadow pistol academic always adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip making coding armed\n' + + 'shadow pistol academic acid actress prayer class unknown daughter sweater depict flip twice unkind craft early superior advocate guest smoking', + ); + + assert.ok(w.validateMnemonic()); + assert.strictEqual(w._getExternalAddressByIndex(0), '18pvMjy7AJbCDtv4TLYbGPbR7SzGzjqUpj'); + assert.strictEqual(w._getExternalAddressByIndex(1), '1LDm2yFgHesgVjENC4cEpvUnW5HdYp51gX'); + assert.strictEqual(w._getInternalAddressByIndex(0), '1EeW2xsK52vqBpsFLYa1vL6hyxmWCsK3Nx'); + assert.strictEqual(w._getInternalAddressByIndex(1), '1EM8ADickQ9WppVgSGGgjL8PGWhbbTqNpW'); + }); + + it('SLIP39SegwitP2SHWallet can generate addresses', async () => { + const w = new SLIP39SegwitP2SHWallet(); + // 23. Basic sharing 2-of-3 (256 bits) + w.setSecret( + 'humidity disease academic always aluminum jewelry energy woman receiver strategy amuse duckling lying evidence network walnut tactics forget hairy rebound impulse brother survive clothes stadium mailman rival ocean reward venture always armed unwrap\n' + + 'humidity disease academic agency actress jacket gross physics cylinder solution fake mortgage benefit public busy prepare sharp friar change work slow purchase ruler again tricycle involve viral wireless mixture anatomy desert cargo upgrade', + ); + + assert.ok(w.validateMnemonic()); + assert.strictEqual(w._getExternalAddressByIndex(0), '3G3HrQ7DrNJLm8gMrLHTeeD5DdzgeoScRJ'); + assert.strictEqual(w._getExternalAddressByIndex(1), '3HgGV4fhXz8GZYVMRT1tj9WoUdMQMDrGvw'); + assert.strictEqual(w._getInternalAddressByIndex(0), '3BdrAbZCo9BCmzP1nmpEVyGXMB9JF4MJ1L'); + assert.strictEqual(w._getInternalAddressByIndex(1), '3GFSjHbSRGZZavmY7YTm9UJfDbmJdpCeMA'); + }); + + it('SLIP39SegwitBech32Wallet can generate addresses', async () => { + const w = new SLIP39SegwitBech32Wallet(); + // 36. Threshold number of groups and members in each group (256 bits, case 1) + w.setSecret( + 'wildlife deal ceramic round aluminum pitch goat racism employer miracle percent math decision episode dramatic editor lily prospect program scene rebuild display sympathy have single mustang junction relate often chemical society wits estate\n' + + 'wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen\n' + + 'wildlife deal ceramic scatter argue equip vampire together ruin reject literary rival distance aquatic agency teammate rebound false argue miracle stay again blessing peaceful unknown cover beard acid island language debris industry idle\n' + + 'wildlife deal ceramic snake agree voter main lecture axis kitchen physics arcade velvet spine idea scroll promise platform firm sharp patrol divorce ancestor fantasy forbid goat ajar believe swimming cowboy symbolic plastic spelling\n' + + 'wildlife deal decision shadow analysis adjust bulb skunk muscle mandate obesity total guitar coal gravity carve slim jacket ruin rebuild ancestor numerous hour mortgage require herd maiden public ceiling pecan pickup shadow club\n', + ); + + assert.ok(w.validateMnemonic()); + assert.strictEqual(w._getExternalAddressByIndex(0), 'bc1qkchjws74hkuhamxk0qa280xc68643nu32pde20'); + assert.strictEqual(w._getExternalAddressByIndex(1), 'bc1qrslzpjwl7ksdxvealdq0qulgspey62vr3acwnp'); + assert.strictEqual(w._getInternalAddressByIndex(0), 'bc1qgx35amln8aryyr0lw6j2729l3gemzjftp5xrne'); + assert.strictEqual(w._getInternalAddressByIndex(1), 'bc1q48v0hcuz2jjsls628wj8jtn7rqp8wsyz2gxdxm'); + }); +});