mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-03 12:06:21 +01:00
ADD: SLIP39 wallets and react-native-crypto
This commit is contained in:
parent
8f7426076e
commit
e70ac56329
41 changed files with 8272 additions and 107 deletions
|
@ -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'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
197
blue_modules/slip39/.eslintrc.js
Normal file
197
blue_modules/slip39/.eslintrc.js
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
25
blue_modules/slip39/CHANGELOG.md
Normal file
25
blue_modules/slip39/CHANGELOG.md
Normal file
|
@ -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
|
21
blue_modules/slip39/LICENSE
Normal file
21
blue_modules/slip39/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Pal Dorogi "ilap"<pal.dorogi@gmail.com>
|
||||||
|
|
||||||
|
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.
|
261
blue_modules/slip39/README.md
Normal file
261
blue_modules/slip39/README.md
Normal file
|
@ -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
|
||||||
|
|
||||||
|
[](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"` <pal.dorogi@gmail.com>
|
||||||
|
|
||||||
|
[MIT License](LICENSE)
|
5
blue_modules/slip39/babel.config.js
Normal file
5
blue_modules/slip39/babel.config.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
"plugins": [
|
||||||
|
"babel-plugin-transform-bigint",
|
||||||
|
]
|
||||||
|
}
|
236
blue_modules/slip39/dist/slip39.js
vendored
Normal file
236
blue_modules/slip39/dist/slip39.js
vendored
Normal file
|
@ -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;
|
686
blue_modules/slip39/dist/slip39_helper.js
vendored
Normal file
686
blue_modules/slip39/dist/slip39_helper.js
vendored
Normal file
File diff suppressed because one or more lines are too long
122
blue_modules/slip39/example/main.js
Normal file
122
blue_modules/slip39/example/main.js
Normal file
|
@ -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);
|
16
blue_modules/slip39/jsbeautifyrc
Normal file
16
blue_modules/slip39/jsbeautifyrc
Normal file
|
@ -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"]
|
||||||
|
}
|
9
blue_modules/slip39/jsconfig.json
Normal file
9
blue_modules/slip39/jsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"**/node_modules/*"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
]
|
||||||
|
}
|
2999
blue_modules/slip39/package-lock.json
generated
Normal file
2999
blue_modules/slip39/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
36
blue_modules/slip39/package.json
Normal file
36
blue_modules/slip39/package.json
Normal file
|
@ -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\" <pal.dorogi@gmail.com>",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
25
blue_modules/slip39/scripts/test_publish.sh
Executable file
25
blue_modules/slip39/scripts/test_publish.sh
Executable file
|
@ -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
|
2
blue_modules/slip39/sed.sh
Executable file
2
blue_modules/slip39/sed.sh
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
sed -i '' 's/import JSBI from \"jsbi\"/const JSBI = require(\"jsbi\/dist\/jsbi-cjs.js\")/' dist/*.js
|
190
blue_modules/slip39/src/slip39.js
Normal file
190
blue_modules/slip39/src/slip39.js
Normal file
|
@ -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;
|
2238
blue_modules/slip39/src/slip39_helper.js
Normal file
2238
blue_modules/slip39/src/slip39_helper.js
Normal file
File diff suppressed because it is too large
Load diff
353
blue_modules/slip39/test/test.js
Normal file
353
blue_modules/slip39/test/test.js
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
322
blue_modules/slip39/test/vectors.json
Normal file
322
blue_modules/slip39/test/vectors.json
Normal file
|
@ -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"
|
||||||
|
],
|
||||||
|
""
|
||||||
|
]
|
||||||
|
]
|
|
@ -16,6 +16,9 @@ import {
|
||||||
HDSegwitElectrumSeedP2WPKHWallet,
|
HDSegwitElectrumSeedP2WPKHWallet,
|
||||||
HDAezeedWallet,
|
HDAezeedWallet,
|
||||||
MultisigHDWallet,
|
MultisigHDWallet,
|
||||||
|
SLIP39SegwitP2SHWallet,
|
||||||
|
SLIP39LegacyP2PKHWallet,
|
||||||
|
SLIP39SegwitBech32Wallet,
|
||||||
} from './';
|
} from './';
|
||||||
const encryption = require('../blue_modules/encryption');
|
const encryption = require('../blue_modules/encryption');
|
||||||
const Realm = require('realm');
|
const Realm = require('realm');
|
||||||
|
@ -273,6 +276,15 @@ export class AppStorage {
|
||||||
case HDAezeedWallet.type:
|
case HDAezeedWallet.type:
|
||||||
unserializedWallet = HDAezeedWallet.fromJson(key);
|
unserializedWallet = HDAezeedWallet.fromJson(key);
|
||||||
break;
|
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: {
|
case LightningCustodianWallet.type: {
|
||||||
/** @type {LightningCustodianWallet} */
|
/** @type {LightningCustodianWallet} */
|
||||||
unserializedWallet = LightningCustodianWallet.fromJson(key);
|
unserializedWallet = LightningCustodianWallet.fromJson(key);
|
||||||
|
|
|
@ -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-segwit-electrum-seed-p2wpkh-wallet';
|
||||||
export * from './wallets/hd-aezeed-wallet';
|
export * from './wallets/hd-aezeed-wallet';
|
||||||
export * from './wallets/multisig-hd-wallet';
|
export * from './wallets/multisig-hd-wallet';
|
||||||
|
export * from './wallets/slip39-wallets';
|
||||||
export * from './hd-segwit-bech32-transaction';
|
export * from './hd-segwit-bech32-transaction';
|
||||||
export * from './multisig-cosigner';
|
export * from './multisig-cosigner';
|
||||||
|
|
|
@ -10,7 +10,8 @@ import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet';
|
||||||
import { HDLegacyElectrumSeedP2PKHWallet } from './wallets/hd-legacy-electrum-seed-p2pkh-wallet';
|
import { HDLegacyElectrumSeedP2PKHWallet } from './wallets/hd-legacy-electrum-seed-p2pkh-wallet';
|
||||||
import { HDSegwitElectrumSeedP2WPKHWallet } from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet';
|
import { HDSegwitElectrumSeedP2WPKHWallet } from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet';
|
||||||
import { MultisigHDWallet } from './wallets/multisig-hd-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';
|
import { useTheme } from '@react-navigation/native';
|
||||||
|
|
||||||
export default class WalletGradient {
|
export default class WalletGradient {
|
||||||
|
@ -43,16 +44,19 @@ export default class WalletGradient {
|
||||||
break;
|
break;
|
||||||
case HDLegacyP2PKHWallet.type:
|
case HDLegacyP2PKHWallet.type:
|
||||||
case HDLegacyElectrumSeedP2PKHWallet.type:
|
case HDLegacyElectrumSeedP2PKHWallet.type:
|
||||||
|
case SLIP39LegacyP2PKHWallet.type:
|
||||||
gradient = WalletGradient.hdLegacyP2PKHWallet;
|
gradient = WalletGradient.hdLegacyP2PKHWallet;
|
||||||
break;
|
break;
|
||||||
case HDLegacyBreadwalletWallet.type:
|
case HDLegacyBreadwalletWallet.type:
|
||||||
gradient = WalletGradient.hdLegacyBreadWallet;
|
gradient = WalletGradient.hdLegacyBreadWallet;
|
||||||
break;
|
break;
|
||||||
case HDSegwitP2SHWallet.type:
|
case HDSegwitP2SHWallet.type:
|
||||||
|
case SLIP39SegwitP2SHWallet.type:
|
||||||
gradient = WalletGradient.hdSegwitP2SHWallet;
|
gradient = WalletGradient.hdSegwitP2SHWallet;
|
||||||
break;
|
break;
|
||||||
case HDSegwitBech32Wallet.type:
|
case HDSegwitBech32Wallet.type:
|
||||||
case HDSegwitElectrumSeedP2WPKHWallet.type:
|
case HDSegwitElectrumSeedP2WPKHWallet.type:
|
||||||
|
case SLIP39SegwitBech32Wallet.type:
|
||||||
gradient = WalletGradient.hdSegwitBech32Wallet;
|
gradient = WalletGradient.hdSegwitBech32Wallet;
|
||||||
break;
|
break;
|
||||||
case LightningCustodianWallet.type:
|
case LightningCustodianWallet.type:
|
||||||
|
@ -81,7 +85,7 @@ export default class WalletGradient {
|
||||||
let props;
|
let props;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MultisigHDWallet.type:
|
case MultisigHDWallet.type:
|
||||||
/* Example
|
/* Example
|
||||||
props = { start: { x: 0, y: 0 } };
|
props = { start: { x: 0, y: 0 } };
|
||||||
https://github.com/react-native-linear-gradient/react-native-linear-gradient
|
https://github.com/react-native-linear-gradient/react-native-linear-gradient
|
||||||
*/
|
*/
|
||||||
|
@ -103,16 +107,19 @@ export default class WalletGradient {
|
||||||
break;
|
break;
|
||||||
case HDLegacyP2PKHWallet.type:
|
case HDLegacyP2PKHWallet.type:
|
||||||
case HDLegacyElectrumSeedP2PKHWallet.type:
|
case HDLegacyElectrumSeedP2PKHWallet.type:
|
||||||
|
case SLIP39LegacyP2PKHWallet.type:
|
||||||
gradient = WalletGradient.hdLegacyP2PKHWallet;
|
gradient = WalletGradient.hdLegacyP2PKHWallet;
|
||||||
break;
|
break;
|
||||||
case HDLegacyBreadwalletWallet.type:
|
case HDLegacyBreadwalletWallet.type:
|
||||||
gradient = WalletGradient.hdLegacyBreadWallet;
|
gradient = WalletGradient.hdLegacyBreadWallet;
|
||||||
break;
|
break;
|
||||||
case HDSegwitP2SHWallet.type:
|
case HDSegwitP2SHWallet.type:
|
||||||
|
case SLIP39SegwitP2SHWallet.type:
|
||||||
gradient = WalletGradient.hdSegwitP2SHWallet;
|
gradient = WalletGradient.hdSegwitP2SHWallet;
|
||||||
break;
|
break;
|
||||||
case HDSegwitBech32Wallet.type:
|
case HDSegwitBech32Wallet.type:
|
||||||
case HDSegwitElectrumSeedP2WPKHWallet.type:
|
case HDSegwitElectrumSeedP2WPKHWallet.type:
|
||||||
|
case SLIP39SegwitBech32Wallet.type:
|
||||||
gradient = WalletGradient.hdSegwitBech32Wallet;
|
gradient = WalletGradient.hdSegwitBech32Wallet;
|
||||||
break;
|
break;
|
||||||
case SegwitBech32Wallet.type:
|
case SegwitBech32Wallet.type:
|
||||||
|
|
|
@ -14,6 +14,9 @@ import {
|
||||||
HDSegwitElectrumSeedP2WPKHWallet,
|
HDSegwitElectrumSeedP2WPKHWallet,
|
||||||
HDAezeedWallet,
|
HDAezeedWallet,
|
||||||
MultisigHDWallet,
|
MultisigHDWallet,
|
||||||
|
SLIP39LegacyP2PKHWallet,
|
||||||
|
SLIP39SegwitP2SHWallet,
|
||||||
|
SLIP39SegwitBech32Wallet,
|
||||||
} from '.';
|
} from '.';
|
||||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||||
import loc from '../loc';
|
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 (segwit address P2SH) TODO
|
||||||
// 7. check if its private key (legacy address) TODO
|
// 7. check if its private key (legacy address) TODO
|
||||||
|
|
||||||
|
importText = importText.trim();
|
||||||
|
|
||||||
if (importText.startsWith('6P')) {
|
if (importText.startsWith('6P')) {
|
||||||
let password = false;
|
let password = false;
|
||||||
do {
|
do {
|
||||||
|
@ -156,7 +161,6 @@ function WalletImport() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// trying other wallet types
|
// trying other wallet types
|
||||||
|
|
||||||
const hd4 = new HDSegwitBech32Wallet();
|
const hd4 = new HDSegwitBech32Wallet();
|
||||||
hd4.setSecret(importText);
|
hd4.setSecret(importText);
|
||||||
if (hd4.validateMnemonic()) {
|
if (hd4.validateMnemonic()) {
|
||||||
|
@ -275,6 +279,31 @@ function WalletImport() {
|
||||||
return WalletImport._saveWallet(watchOnly, additionalProperties);
|
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?
|
// nope?
|
||||||
|
|
||||||
// TODO: try a raw private key
|
// TODO: try a raw private key
|
||||||
|
|
|
@ -96,8 +96,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||||
*/
|
*/
|
||||||
_getWIFByIndex(internal, index) {
|
_getWIFByIndex(internal, index) {
|
||||||
if (!this.secret) return false;
|
if (!this.secret) return false;
|
||||||
const mnemonic = this.secret;
|
const seed = this._getSeed();
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
|
||||||
const root = HDNode.fromSeed(seed);
|
const root = HDNode.fromSeed(seed);
|
||||||
const path = `m/84'/0'/0'/${internal ? 1 : 0}/${index}`;
|
const path = `m/84'/0'/0'/${internal ? 1 : 0}/${index}`;
|
||||||
const child = root.derivePath(path);
|
const child = root.derivePath(path);
|
||||||
|
@ -188,8 +187,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||||
return this._xpub; // cache hit
|
return this._xpub; // cache hit
|
||||||
}
|
}
|
||||||
// first, getting xpub
|
// first, getting xpub
|
||||||
const mnemonic = this.secret;
|
const seed = this._getSeed();
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
|
||||||
const root = HDNode.fromSeed(seed);
|
const root = HDNode.fromSeed(seed);
|
||||||
|
|
||||||
const path = "m/84'/0'/0'";
|
const path = "m/84'/0'/0'";
|
||||||
|
@ -1082,8 +1080,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||||
* @returns {{ tx: Transaction }}
|
* @returns {{ tx: Transaction }}
|
||||||
*/
|
*/
|
||||||
cosignPsbt(psbt) {
|
cosignPsbt(psbt) {
|
||||||
const mnemonic = this.secret;
|
const seed = this._getSeed();
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
|
||||||
const hdRoot = HDNode.fromSeed(seed);
|
const hdRoot = HDNode.fromSeed(seed);
|
||||||
|
|
||||||
for (let cc = 0; cc < psbt.inputCount; cc++) {
|
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
|
* @returns {string} Hex string of fingerprint derived from mnemonics. Always has lenght of 8 chars and correct leading zeroes
|
||||||
*/
|
*/
|
||||||
static seedToFingerprint(mnemonic) {
|
static seedToFingerprint(seed) {
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
|
||||||
const root = bitcoin.bip32.fromSeed(seed);
|
const root = bitcoin.bip32.fromSeed(seed);
|
||||||
let hex = root.fingerprint.toString('hex');
|
let hex = root.fingerprint.toString('hex');
|
||||||
while (hex.length < 8) hex = '0' + hex; // leading zeroes
|
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
|
* @returns {string} Hex string of fingerprint derived from wallet mnemonics. Always has lenght of 8 chars and correct leading zeroes
|
||||||
*/
|
*/
|
||||||
getMasterFingerprintHex() {
|
getMasterFingerprintHex() {
|
||||||
return AbstractHDElectrumWallet.seedToFingerprint(this.secret);
|
const seed = this._getSeed();
|
||||||
|
return AbstractHDElectrumWallet.seedToFingerprint(seed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,14 @@ export class AbstractHDWallet extends LegacyWallet {
|
||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Buffer} wallet seed
|
||||||
|
*/
|
||||||
|
_getSeed() {
|
||||||
|
const mnemonic = this.secret;
|
||||||
|
return bip39.mnemonicToSeed(mnemonic);
|
||||||
|
}
|
||||||
|
|
||||||
setSecret(newSecret) {
|
setSecret(newSecret) {
|
||||||
this.secret = newSecret.trim().toLowerCase();
|
this.secret = newSecret.trim().toLowerCase();
|
||||||
this.secret = this.secret.replace(/[^a-zA-Z0-9]/g, ' ').replace(/\s+/g, ' ');
|
this.secret = this.secret.replace(/[^a-zA-Z0-9]/g, ' ').replace(/\s+/g, ' ');
|
||||||
|
|
|
@ -125,6 +125,10 @@ export class AbstractWallet {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allowXpub() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
weOwnAddress(address) {
|
weOwnAddress(address) {
|
||||||
throw Error('not implemented');
|
throw Error('not implemented');
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,4 +176,8 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet {
|
||||||
allowSignVerifyMessage() {
|
allowSignVerifyMessage() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allowXpub() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import bip39 from 'bip39';
|
|
||||||
import * as bip32 from 'bip32';
|
import * as bip32 from 'bip32';
|
||||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||||
import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet';
|
import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet';
|
||||||
|
@ -27,8 +26,7 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet {
|
||||||
if (this._xpub) {
|
if (this._xpub) {
|
||||||
return this._xpub; // cache hit
|
return this._xpub; // cache hit
|
||||||
}
|
}
|
||||||
const mnemonic = this.secret;
|
const seed = this._getSeed();
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
|
||||||
const root = bip32.fromSeed(seed);
|
const root = bip32.fromSeed(seed);
|
||||||
|
|
||||||
const path = "m/0'";
|
const path = "m/0'";
|
||||||
|
@ -109,8 +107,7 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet {
|
||||||
*/
|
*/
|
||||||
_getWIFByIndex(internal, index) {
|
_getWIFByIndex(internal, index) {
|
||||||
if (!this.secret) return false;
|
if (!this.secret) return false;
|
||||||
const mnemonic = this.secret;
|
const seed = this._getSeed();
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
|
||||||
const root = bitcoinjs.bip32.fromSeed(seed);
|
const root = bitcoinjs.bip32.fromSeed(seed);
|
||||||
const path = `m/0'/${internal ? 1 : 0}/${index}`;
|
const path = `m/0'/${internal ? 1 : 0}/${index}`;
|
||||||
const child = root.derivePath(path);
|
const child = root.derivePath(path);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import bip39 from 'bip39';
|
|
||||||
import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet';
|
import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet';
|
||||||
const bitcoin = require('bitcoinjs-lib');
|
const bitcoin = require('bitcoinjs-lib');
|
||||||
const HDNode = require('bip32');
|
const HDNode = require('bip32');
|
||||||
|
@ -30,12 +29,15 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allowXpub() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
getXpub() {
|
getXpub() {
|
||||||
if (this._xpub) {
|
if (this._xpub) {
|
||||||
return this._xpub; // cache hit
|
return this._xpub; // cache hit
|
||||||
}
|
}
|
||||||
const mnemonic = this.secret;
|
const seed = this._getSeed();
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
|
||||||
const root = bitcoin.bip32.fromSeed(seed);
|
const root = bitcoin.bip32.fromSeed(seed);
|
||||||
|
|
||||||
const path = "m/44'/0'/0'";
|
const path = "m/44'/0'/0'";
|
||||||
|
@ -62,8 +64,7 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet {
|
||||||
*/
|
*/
|
||||||
_getWIFByIndex(internal, index) {
|
_getWIFByIndex(internal, index) {
|
||||||
if (!this.secret) return false;
|
if (!this.secret) return false;
|
||||||
const mnemonic = this.secret;
|
const seed = this._getSeed();
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
|
||||||
|
|
||||||
const root = HDNode.fromSeed(seed);
|
const root = HDNode.fromSeed(seed);
|
||||||
const path = `m/44'/0'/0'/${internal ? 1 : 0}/${index}`;
|
const path = `m/44'/0'/0'/${internal ? 1 : 0}/${index}`;
|
||||||
|
|
|
@ -38,4 +38,8 @@ export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet {
|
||||||
allowMasterFingerprint() {
|
allowMasterFingerprint() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allowXpub() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import bip39 from 'bip39';
|
|
||||||
import b58 from 'bs58check';
|
import b58 from 'bs58check';
|
||||||
import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet';
|
import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet';
|
||||||
const bitcoin = require('bitcoinjs-lib');
|
const bitcoin = require('bitcoinjs-lib');
|
||||||
|
@ -35,6 +34,10 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allowXpub() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get internal/external WIF by wallet index
|
* Get internal/external WIF by wallet index
|
||||||
* @param {Boolean} internal
|
* @param {Boolean} internal
|
||||||
|
@ -44,8 +47,7 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
|
||||||
*/
|
*/
|
||||||
_getWIFByIndex(internal, index) {
|
_getWIFByIndex(internal, index) {
|
||||||
if (!this.secret) return false;
|
if (!this.secret) return false;
|
||||||
const mnemonic = this.secret;
|
const seed = this._getSeed();
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
|
||||||
const root = bitcoin.bip32.fromSeed(seed);
|
const root = bitcoin.bip32.fromSeed(seed);
|
||||||
const path = `m/49'/0'/0'/${internal ? 1 : 0}/${index}`;
|
const path = `m/49'/0'/0'/${internal ? 1 : 0}/${index}`;
|
||||||
const child = root.derivePath(path);
|
const child = root.derivePath(path);
|
||||||
|
@ -92,8 +94,7 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
|
||||||
return this._xpub; // cache hit
|
return this._xpub; // cache hit
|
||||||
}
|
}
|
||||||
// first, getting xpub
|
// first, getting xpub
|
||||||
const mnemonic = this.secret;
|
const seed = this._getSeed();
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
|
||||||
const root = HDNode.fromSeed(seed);
|
const root = HDNode.fromSeed(seed);
|
||||||
|
|
||||||
const path = "m/49'/0'/0'";
|
const path = "m/49'/0'/0'";
|
||||||
|
|
|
@ -185,7 +185,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
} else {
|
} else {
|
||||||
// mnemonics. lets derive fingerprint (if it wasnt provided)
|
// mnemonics. lets derive fingerprint (if it wasnt provided)
|
||||||
if (!bip39.validateMnemonic(key)) throw new Error('Not a valid mnemonic phrase');
|
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') {
|
if (fingerprint && this._cosignersFingerprints.indexOf(fingerprint.toUpperCase()) !== -1 && fingerprint !== '00000000') {
|
||||||
|
@ -432,7 +432,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
const xpub = this.convertXpubToMultisignatureXpub(
|
const xpub = this.convertXpubToMultisignatureXpub(
|
||||||
MultisigHDWallet.seedToXpub(this._cosigners[index], this._cosignersCustomPaths[index] || this._derivationPath),
|
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';
|
ret += fingerprint + ': ' + xpub + '\n';
|
||||||
} else {
|
} else {
|
||||||
ret += 'seed: ' + this._cosigners[index] + '\n';
|
ret += 'seed: ' + this._cosigners[index] + '\n';
|
||||||
|
@ -1037,7 +1037,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
if (index === -1) return;
|
if (index === -1) return;
|
||||||
if (!MultisigHDWallet.isXpubValid(newCosigner)) {
|
if (!MultisigHDWallet.isXpubValid(newCosigner)) {
|
||||||
// its not an xpub, so lets derive fingerprint ourselves
|
// its not an xpub, so lets derive fingerprint ourselves
|
||||||
newFp = MultisigHDWallet.seedToFingerprint(newCosigner);
|
newFp = MultisigHDWallet.mnemonicToFingerprint(newCosigner);
|
||||||
if (oldFp !== newFp) {
|
if (oldFp !== newFp) {
|
||||||
throw new Error('Fingerprint of new seed doesnt match');
|
throw new Error('Fingerprint of new seed doesnt match');
|
||||||
}
|
}
|
||||||
|
@ -1103,4 +1103,9 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
static isXpubForMultisig(xpub) {
|
static isXpubForMultisig(xpub) {
|
||||||
return ['xpub', 'Ypub', 'Zpub'].includes(xpub.substring(0, 4));
|
return ['xpub', 'Ypub', 'Zpub'].includes(xpub.substring(0, 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static mnemonicToFingerprint(mnemonic) {
|
||||||
|
const seed = bip39.mnemonicToSeed(mnemonic);
|
||||||
|
return MultisigHDWallet.seedToFingerprint(seed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
75
class/wallets/slip39-wallets.js
Normal file
75
class/wallets/slip39-wallets.js
Normal file
|
@ -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;
|
||||||
|
}
|
183
package-lock.json
generated
183
package-lock.json
generated
|
@ -6707,6 +6707,17 @@
|
||||||
"safer-buffer": "~2.1.0"
|
"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": {
|
"assert": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
|
||||||
|
@ -7747,6 +7758,66 @@
|
||||||
"safe-buffer": "^5.0.1"
|
"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": {
|
"browserslist": {
|
||||||
"version": "4.14.6",
|
"version": "4.14.6",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.6.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.6.tgz",
|
||||||
|
@ -8503,6 +8574,15 @@
|
||||||
"parse-json": "^4.0.0"
|
"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": {
|
"create-hash": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||||
|
@ -8804,6 +8884,15 @@
|
||||||
"react-clone-referenced-element": "*"
|
"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": {
|
"destroy": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||||
|
@ -9016,6 +9105,16 @@
|
||||||
"integrity": "sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==",
|
"integrity": "sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==",
|
||||||
"dev": true
|
"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": {
|
"dijkstrajs": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.1.tgz",
|
||||||
|
@ -15162,6 +15261,11 @@
|
||||||
"esprima": "^4.0.0"
|
"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": {
|
"jsbn": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||||
|
@ -16716,6 +16820,15 @@
|
||||||
"to-regex": "^3.0.2"
|
"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": {
|
"mime": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
"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": {
|
"parse-glob": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
|
||||||
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
|
"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": {
|
"pump": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||||
|
@ -18032,6 +18170,15 @@
|
||||||
"safe-buffer": "^5.1.0"
|
"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": {
|
"range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
@ -18499,6 +18646,33 @@
|
||||||
"prop-types": "^15.6.2"
|
"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": {
|
"react-native-default-preference": {
|
||||||
"version": "1.4.3",
|
"version": "1.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-default-preference/-/react-native-default-preference-1.4.3.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
|
||||||
"integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc="
|
"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": {
|
"snapdragon": {
|
||||||
"version": "0.8.2",
|
"version": "0.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
"android:clean": "cd android; ./gradlew clean ; cd .. ; npm run android",
|
"android:clean": "cd android; ./gradlew clean ; cd .. ; npm run android",
|
||||||
"ios": "react-native run-ios",
|
"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",
|
"test": "npm run lint && npm run unit && npm run jest",
|
||||||
"jest": "jest -b -w 1 tests/integration/*",
|
"jest": "jest -b -w 1 tests/integration/*",
|
||||||
"maccatalystpatches": "./scripts/maccatalystpatches/applypatchesformaccatalyst.sh",
|
"maccatalystpatches": "./scripts/maccatalystpatches/applypatchesformaccatalyst.sh",
|
||||||
|
@ -123,10 +123,11 @@
|
||||||
"process": "0.11.10",
|
"process": "0.11.10",
|
||||||
"prop-types": "15.7.2",
|
"prop-types": "15.7.2",
|
||||||
"react": "16.13.1",
|
"react": "16.13.1",
|
||||||
"react-native": "0.63.4",
|
|
||||||
"react-localization": "1.0.16",
|
"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-blue-crypto": "git+https://github.com/Overtorment/react-native-blue-crypto.git",
|
||||||
"react-native-camera": "3.43.0",
|
"react-native-camera": "3.43.0",
|
||||||
|
"react-native-crypto": "2.2.0",
|
||||||
"react-native-default-preference": "1.4.3",
|
"react-native-default-preference": "1.4.3",
|
||||||
"react-native-device-info": "8.0.6",
|
"react-native-device-info": "8.0.6",
|
||||||
"react-native-document-picker": "git+https://github.com/BlueWallet/react-native-document-picker.git#3684d4fcc2bc0b47c32be39024e4796004c3e428",
|
"react-native-document-picker": "git+https://github.com/BlueWallet/react-native-document-picker.git#3684d4fcc2bc0b47c32be39024e4796004c3e428",
|
||||||
|
@ -175,13 +176,14 @@
|
||||||
"rn-nodeify": "10.2.0",
|
"rn-nodeify": "10.2.0",
|
||||||
"scryptsy": "file:blue_modules/scryptsy",
|
"scryptsy": "file:blue_modules/scryptsy",
|
||||||
"secure-random": "1.1.2",
|
"secure-random": "1.1.2",
|
||||||
|
"slip39": "file:blue_modules/slip39",
|
||||||
"stream-browserify": "2.0.2",
|
"stream-browserify": "2.0.2",
|
||||||
"url": "0.11.0",
|
"url": "0.11.0",
|
||||||
"util": "0.12.3",
|
"util": "0.12.3",
|
||||||
"wif": "2.0.6"
|
"wif": "2.0.6"
|
||||||
},
|
},
|
||||||
"react-native": {
|
"react-native": {
|
||||||
"crypto": "bluewallet/blue_modules/crypto",
|
"crypto": "react-native-crypto",
|
||||||
"path": "path-browserify",
|
"path": "path-browserify",
|
||||||
"fs": "react-native-level-fs",
|
"fs": "react-native-level-fs",
|
||||||
"_stream_transform": "readable-stream/transform",
|
"_stream_transform": "readable-stream/transform",
|
||||||
|
@ -222,6 +224,7 @@
|
||||||
"runner-config": "tests/e2e/config.json"
|
"runner-config": "tests/e2e/config.json"
|
||||||
},
|
},
|
||||||
"browser": {
|
"browser": {
|
||||||
|
"crypto": "react-native-crypto",
|
||||||
"path": "path-browserify",
|
"path": "path-browserify",
|
||||||
"fs": "react-native-level-fs",
|
"fs": "react-native-level-fs",
|
||||||
"_stream_transform": "readable-stream/transform",
|
"_stream_transform": "readable-stream/transform",
|
||||||
|
|
|
@ -5,7 +5,14 @@ import { ScrollView, View, StyleSheet } from 'react-native';
|
||||||
import loc from '../loc';
|
import loc from '../loc';
|
||||||
import { BlueSpacing20, SafeBlueArea, BlueCard, BlueText, BlueLoading } from '../BlueComponents';
|
import { BlueSpacing20, SafeBlueArea, BlueCard, BlueText, BlueLoading } from '../BlueComponents';
|
||||||
import navigationStyle from '../components/navigationStyle';
|
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 bitcoin = require('bitcoinjs-lib');
|
||||||
const BlueCrypto = require('react-native-blue-crypto');
|
const BlueCrypto = require('react-native-blue-crypto');
|
||||||
const encryption = require('../blue_modules/encryption');
|
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');
|
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) {
|
} catch (Err) {
|
||||||
errorMessage += Err;
|
errorMessage += Err;
|
||||||
|
|
|
@ -19,9 +19,6 @@ import {
|
||||||
import { BlueCard, BlueLoading, BlueSpacing10, BlueSpacing20, BlueText, SafeBlueArea, SecondButton } from '../../BlueComponents';
|
import { BlueCard, BlueLoading, BlueSpacing10, BlueSpacing20, BlueText, SafeBlueArea, SecondButton } from '../../BlueComponents';
|
||||||
import navigationStyle from '../../components/navigationStyle';
|
import navigationStyle from '../../components/navigationStyle';
|
||||||
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
|
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 ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||||
import Biometric from '../../class/biometrics';
|
import Biometric from '../../class/biometrics';
|
||||||
import {
|
import {
|
||||||
|
@ -549,11 +546,7 @@ const WalletDetails = () => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(wallet.type === HDLegacyBreadwalletWallet.type ||
|
{wallet.allowXpub() && (
|
||||||
wallet.type === HDLegacyP2PKHWallet.type ||
|
|
||||||
wallet.type === HDSegwitBech32Wallet.type ||
|
|
||||||
wallet.type === HDAezeedWallet.type ||
|
|
||||||
wallet.type === HDSegwitP2SHWallet.type) && (
|
|
||||||
<>
|
<>
|
||||||
<BlueSpacing20 />
|
<BlueSpacing20 />
|
||||||
<SecondButton onPress={navigateToXPub} testID="XPub" title={loc.wallets.details_show_xpub} />
|
<SecondButton onPress={navigateToXPub} testID="XPub" title={loc.wallets.details_show_xpub} />
|
||||||
|
|
|
@ -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 { useWindowDimensions, InteractionManager, ScrollView, ActivityIndicator, StatusBar, View, StyleSheet } from 'react-native';
|
||||||
import QRCode from 'react-native-qrcode-svg';
|
import QRCode from 'react-native-qrcode-svg';
|
||||||
import { useTheme, useNavigation, useFocusEffect, useRoute } from '@react-navigation/native';
|
import { useTheme, useNavigation, useFocusEffect, useRoute } from '@react-navigation/native';
|
||||||
|
@ -37,11 +37,11 @@ const styles = StyleSheet.create({
|
||||||
const WalletExport = () => {
|
const WalletExport = () => {
|
||||||
const { wallets, saveToDisk } = useContext(BlueStorageContext);
|
const { wallets, saveToDisk } = useContext(BlueStorageContext);
|
||||||
const { walletID } = useRoute().params;
|
const { walletID } = useRoute().params;
|
||||||
const wallet = useRef(wallets.find(w => w.getID() === walletID));
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const { goBack } = useNavigation();
|
const { goBack } = useNavigation();
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
const { width, height } = useWindowDimensions();
|
const { width, height } = useWindowDimensions();
|
||||||
|
const wallet = wallets.find(w => w.getID() === walletID);
|
||||||
const stylesHook = {
|
const stylesHook = {
|
||||||
...styles,
|
...styles,
|
||||||
loading: {
|
loading: {
|
||||||
|
@ -68,8 +68,8 @@ const WalletExport = () => {
|
||||||
return goBack();
|
return goBack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!wallet.current.getUserHasSavedExport()) {
|
if (!wallet.getUserHasSavedExport()) {
|
||||||
wallet.current.setUserHasSavedExport(true);
|
wallet.setUserHasSavedExport(true);
|
||||||
saveToDisk();
|
saveToDisk();
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
@ -80,52 +80,61 @@ const WalletExport = () => {
|
||||||
Privacy.disableBlur();
|
Privacy.disableBlur();
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [goBack, walletID]),
|
}, [goBack, wallet]),
|
||||||
);
|
);
|
||||||
|
|
||||||
return isLoading && wallet ? (
|
if (isLoading || !wallet)
|
||||||
<View style={stylesHook.loading}>
|
return (
|
||||||
<ActivityIndicator />
|
<View style={stylesHook.loading}>
|
||||||
</View>
|
<ActivityIndicator />
|
||||||
) : (
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
// for SLIP39 we need to show all shares
|
||||||
|
let secrets = wallet.getSecret();
|
||||||
|
if (typeof secrets === 'string') {
|
||||||
|
secrets = [secrets];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<SafeBlueArea style={stylesHook.root}>
|
<SafeBlueArea style={stylesHook.root}>
|
||||||
<StatusBar barStyle="light-content" />
|
<StatusBar barStyle="light-content" />
|
||||||
<ScrollView contentContainerStyle={styles.scrollViewContent} testID="WalletExportScroll">
|
<ScrollView contentContainerStyle={styles.scrollViewContent} testID="WalletExportScroll">
|
||||||
<View>
|
<View>
|
||||||
<BlueText style={stylesHook.type}>{wallet.current.typeReadable}</BlueText>
|
<BlueText style={stylesHook.type}>{wallet.typeReadable}</BlueText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{(() => {
|
{[LegacyWallet.type, SegwitBech32Wallet.type, SegwitP2SHWallet.type].includes(wallet.type) && (
|
||||||
if ([LegacyWallet.type, SegwitBech32Wallet.type, SegwitP2SHWallet.type].includes(wallet.current.type)) {
|
<BlueCard>
|
||||||
return (
|
<BlueText>{wallet.getAddress()}</BlueText>
|
||||||
<BlueCard>
|
</BlueCard>
|
||||||
<BlueText>{wallet.current.getAddress()}</BlueText>
|
|
||||||
</BlueCard>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
<BlueSpacing20 />
|
|
||||||
<View style={styles.activeQrcode}>
|
|
||||||
<QRCode
|
|
||||||
value={wallet.current.getSecret()}
|
|
||||||
logo={require('../../img/qr-code.png')}
|
|
||||||
size={height > width ? width - 40 : width / 2}
|
|
||||||
logoSize={70}
|
|
||||||
color="#000000"
|
|
||||||
logoBackgroundColor={colors.brandingColor}
|
|
||||||
backgroundColor="#FFFFFF"
|
|
||||||
ecl="H"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
{wallet.type !== WatchOnlyWallet.type && <BlueText style={stylesHook.warning}>{loc.wallets.warning_do_not_disclose}</BlueText>}
|
|
||||||
<BlueSpacing20 />
|
|
||||||
{wallet.current.type === LightningCustodianWallet.type || wallet.current.type === WatchOnlyWallet.type ? (
|
|
||||||
<BlueCopyTextToClipboard text={wallet.current.getSecret()} />
|
|
||||||
) : (
|
|
||||||
<BlueText style={stylesHook.secret} testID="Secret">
|
|
||||||
{wallet.current.getSecret()}
|
|
||||||
</BlueText>
|
|
||||||
)}
|
)}
|
||||||
|
<BlueSpacing20 />
|
||||||
|
{secrets.map(s => (
|
||||||
|
<React.Fragment key={s}>
|
||||||
|
<View style={styles.activeQrcode}>
|
||||||
|
<QRCode
|
||||||
|
value={wallet.getSecret()}
|
||||||
|
logo={require('../../img/qr-code.png')}
|
||||||
|
size={height > width ? width - 40 : width / 2}
|
||||||
|
logoSize={70}
|
||||||
|
color="#000000"
|
||||||
|
logoBackgroundColor={colors.brandingColor}
|
||||||
|
backgroundColor="#FFFFFF"
|
||||||
|
ecl="H"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
{wallet.type !== WatchOnlyWallet.type && <BlueText style={stylesHook.warning}>{loc.wallets.warning_do_not_disclose}</BlueText>}
|
||||||
|
<BlueSpacing20 />
|
||||||
|
{wallet.type === LightningCustodianWallet.type || wallet.type === WatchOnlyWallet.type ? (
|
||||||
|
<BlueCopyTextToClipboard text={wallet.getSecret()} />
|
||||||
|
) : (
|
||||||
|
<BlueText style={stylesHook.secret} testID="Secret">
|
||||||
|
{wallet.getSecret()}
|
||||||
|
</BlueText>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeBlueArea>
|
</SafeBlueArea>
|
||||||
);
|
);
|
||||||
|
|
4
shim.js
4
shim.js
|
@ -29,3 +29,7 @@ process.env.NODE_ENV = isDev ? 'development' : 'production';
|
||||||
if (typeof localStorage !== 'undefined') {
|
if (typeof localStorage !== 'undefined') {
|
||||||
localStorage.debug = isDev ? '*' : '';
|
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');
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
HDSegwitP2SHWallet,
|
HDSegwitP2SHWallet,
|
||||||
WatchOnlyWallet,
|
WatchOnlyWallet,
|
||||||
HDAezeedWallet,
|
HDAezeedWallet,
|
||||||
|
SLIP39SegwitP2SHWallet,
|
||||||
} from '../../class';
|
} from '../../class';
|
||||||
import WalletImport from '../../class/wallet-import';
|
import WalletImport from '../../class/wallet-import';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
@ -175,4 +176,16 @@ describe('import procedure', function () {
|
||||||
);
|
);
|
||||||
assert.strictEqual(lastImportedWallet.type, WatchOnlyWallet.type);
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
78
tests/unit/slip39-wallets.test.js
Normal file
78
tests/unit/slip39-wallets.test.js
Normal file
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Reference in a new issue