From bcada9d1199dc35d29736270e827bfc536149807 Mon Sep 17 00:00:00 2001 From: Destry Saul Date: Mon, 29 Jun 2020 13:59:04 -0700 Subject: [PATCH] feat(wallet): allow signing method to be 'unknown', and client empty If a wallet config is uploaded that does not have a signing method included, caravan will set the method to 'unknown' and during signing, the method selector dialog will be shown, but the BIP32 path from the config will be used. If a client isn't in a config, the ClientPicker will be shown. BREAKING CHANGE: none Co-authored-by: Destry Saul --- README.md | 2 +- src/components/ClientPicker/index.jsx | 5 +- .../ScriptExplorer/SignatureImporter.jsx | 4 +- src/components/Wallet/WalletGenerator.jsx | 30 ++++++- src/components/Wallet/index.jsx | 82 +++++++++++++------ 5 files changed, 91 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 8e94b2b7..6af0b034 100644 --- a/README.md +++ b/README.md @@ -133,4 +133,4 @@ point caravan at 'http://localhost:1337/localhost:8332'. ## Contributing -Please see the [`CONTRIBUTING.md`](./CONTRIBUTING.md) and the open [GitHub Issues](https://github.com/caravan/issues). +Please see the [`CONTRIBUTING.md`](./CONTRIBUTING.md) and the open [GitHub Issues](https://github.com/caravan/issues) diff --git a/src/components/ClientPicker/index.jsx b/src/components/ClientPicker/index.jsx index 76934993..676a360f 100644 --- a/src/components/ClientPicker/index.jsx +++ b/src/components/ClientPicker/index.jsx @@ -140,7 +140,7 @@ class ClientPicker extends React.Component { checked={client.type === "private"} /> - {client.type === "public" ? ( + {client.type === "public" && ( {"'Public' uses the "} blockstream.info @@ -148,7 +148,8 @@ class ClientPicker extends React.Component { bitcoind {" node."} - ) : ( + )} + {client.type === "private" && ( this.handleUrlChange(event)} handleUsernameChange={(event) => diff --git a/src/components/ScriptExplorer/SignatureImporter.jsx b/src/components/ScriptExplorer/SignatureImporter.jsx index 78cad9cc..c76b2c85 100644 --- a/src/components/ScriptExplorer/SignatureImporter.jsx +++ b/src/components/ScriptExplorer/SignatureImporter.jsx @@ -42,6 +42,7 @@ import { import "react-table/react-table.css"; const TEXT = "text"; +const UNKNOWN = "unknown"; class SignatureImporter extends React.Component { titleRef = React.createRef(); @@ -106,7 +107,8 @@ class SignatureImporter extends React.Component {
{(extendedPublicKeyImporter === null || typeof extendedPublicKeyImporter === "undefined" || - extendedPublicKeyImporter.method === TEXT) && ( + extendedPublicKeyImporter.method === TEXT || + extendedPublicKeyImporter.method === UNKNOWN) && ( Select Method diff --git a/src/components/Wallet/WalletGenerator.jsx b/src/components/Wallet/WalletGenerator.jsx index ce7ee44c..e900c8b8 100644 --- a/src/components/Wallet/WalletGenerator.jsx +++ b/src/components/Wallet/WalletGenerator.jsx @@ -26,6 +26,7 @@ import { } from "../../blockchain"; // Components +import ClientPicker from "../ClientPicker"; import ConfirmWallet from "./ConfirmWallet"; import WalletControl from "./WalletControl"; import WalletConfigInteractionButtons from "./WalletConfigInteractionButtons"; @@ -53,6 +54,7 @@ class WalletGenerator extends React.Component { super(props); this.state = { connectSuccess: false, + unknownClient: false, }; } @@ -79,7 +81,17 @@ class WalletGenerator extends React.Component { client, common: { nodesLoaded }, setIsWallet, + configuring, } = this.props; + const { unknownClient } = this.state; + if (client.type === "unknown" && prevProps.client.type === "public") { + this.setState({ unknownClient: true }); // eslint-disable-line react/no-did-update-set-state + } else if (configuring && client.type !== "unknown" && unknownClient) { + // re-initializes the state if we're in the configuring stage. + // catches situation where wallet is cleared and new one is added + // eslint-disable-next-line react/no-did-update-set-state + this.setState({ unknownClient: false }); + } // if the password was updated if (prevPassword !== client.password && client.password.length) { // test the connection using the set password @@ -319,7 +331,7 @@ class WalletGenerator extends React.Component { client, generating, } = this.props; - const { connectSuccess } = this.state; + const { connectSuccess, unknownClient } = this.state; if (this.extendedPublicKeyCount() === totalSigners) { if (generating && !configuring) { return ( @@ -343,7 +355,16 @@ class WalletGenerator extends React.Component { onClearFn={(e) => this.toggleImporters(e)} onDownloadFn={downloadWalletDetails} /> - {client.type === "private" && ( + {unknownClient && ( + + + This config does not contain client information. Please choose + a client to connect to before importing your wallet. + + + + )} + {client.type === "private" && !unknownClient && ( @@ -401,7 +422,10 @@ class WalletGenerator extends React.Component { variant="contained" color="primary" onClick={this.generate} - disabled={client.type === "private" && !connectSuccess} + disabled={ + (client.type === "private" && !connectSuccess) || + client.type === "unknown" + } > Confirm diff --git a/src/components/Wallet/index.jsx b/src/components/Wallet/index.jsx index f7ffe196..62b9ef9f 100644 --- a/src/components/Wallet/index.jsx +++ b/src/components/Wallet/index.jsx @@ -77,7 +77,6 @@ class CreateWallet extends React.Component { "name", "addressType", "network", - "client", "quorum", "extendedPublicKeys", ]; @@ -88,14 +87,18 @@ class CreateWallet extends React.Component { ); if (validProperties !== "") return validProperties; - const clientProperties = - config.client.type === "public" ? ["type"] : ["type", "url", "username"]; - const validClient = CreateWallet.validateProperties( - config, - clientProperties, - "client" - ); - if (validClient !== "") return validClient; + if (config.client) { + const clientProperties = + config.client.type === "public" + ? ["type"] + : ["type", "url", "username"]; + const validClient = CreateWallet.validateProperties( + config, + clientProperties, + "client" + ); + if (validClient !== "") return validClient; + } const quorumProperties = ["requiredSigners", "totalSigners"]; const validQuorum = CreateWallet.validateProperties( @@ -112,6 +115,10 @@ class CreateWallet extends React.Component { } static validateExtendedPublicKeys(xpubs, network) { + let tmpNetwork = network; + if (network === "regtest") { + tmpNetwork = "testnet"; + } const xpubFields = { name: (name, index) => typeof name === "string" @@ -119,15 +126,18 @@ class CreateWallet extends React.Component { : `Extended public key ${index} name must be a string`, bip32Path: (bip32Path, index) => { if (xpubs[index - 1].method === "text") return ""; + if (typeof xpubs[index - 1].method === "undefined") return ""; const pathError = validateBIP32Path(bip32Path); if (pathError !== "") return `Extended public key ${index} error: ${pathError}`; return ""; }, - xpub: (xpub) => validateExtendedPublicKey(xpub, network), + xpub: (xpub) => validateExtendedPublicKey(xpub, tmpNetwork), method: (method, index) => // eslint-disable-next-line no-bitwise - ~["trezor", "ledger", "hermit", "xpub", "text"].indexOf(method) // FIXME + ~["trezor", "ledger", "hermit", "xpub", "text", undefined].indexOf( + method + ) ? "" : `Invalid method for extended public key ${index}`, }; @@ -167,7 +177,7 @@ class CreateWallet extends React.Component { const config = JSON.parse(configJson); configError = CreateWallet.validateConfig(config); } catch (parseError) { - configError = "Invlaid JSON"; + configError = "Invalid JSON"; } if (sessionStorage) sessionStorage.setItem(CARAVAN_CONFIG, configJson); @@ -223,18 +233,32 @@ class CreateWallet extends React.Component { setTotalSigners(walletConfiguration.quorum.totalSigners); setRequiredSigners(walletConfiguration.quorum.requiredSigners); setAddressType(walletConfiguration.addressType); - setNetwork(walletConfiguration.network); + if (walletConfiguration.network === "regtest") { + setNetwork("testnet"); + } else { + setNetwork(walletConfiguration.network); + } updateWalletNameAction(0, walletConfiguration.name); - setClientType(walletConfiguration.client.type); - if (walletConfiguration.client.type === "private") { - setClientUrl(walletConfiguration.client.url); - setClientUsername(walletConfiguration.client.username); + // set client to unknown + if (walletConfiguration.client) { + setClientType(walletConfiguration.client.type); + if (walletConfiguration.client.type === "private") { + setClientUrl(walletConfiguration.client.url); + setClientUsername(walletConfiguration.client.username); + } + } else { + setClientType("unknown"); } walletConfiguration.extendedPublicKeys.forEach( (extendedPublicKey, index) => { const number = index + 1; setExtendedPublicKeyImporterName(number, extendedPublicKey.name); - setExtendedPublicKeyImporterMethod(number, extendedPublicKey.method); + if (extendedPublicKey.method) { + setExtendedPublicKeyImporterMethod(number, extendedPublicKey.method); + } else { + setExtendedPublicKeyImporterMethod(number, "unknown"); + } + setExtendedPublicKeyImporterBIP32Path( number, extendedPublicKey.bip32Path @@ -248,6 +272,7 @@ class CreateWallet extends React.Component { ); }; + // add client picker if client === 'unknown' renderWalletImporter = () => { const { configError } = this.state; const { configuring } = this.props; @@ -301,7 +326,6 @@ class CreateWallet extends React.Component { ); - return settings; }; @@ -399,12 +423,20 @@ ${this.extendedPublicKeyImporterBIP32Paths()} extendedPublicKeyImporter.method === "text" ? "Unknown (make sure you have written this down previously!)" : extendedPublicKeyImporter.bip32Path; - return ` { - "name": "${extendedPublicKeyImporter.name}", - "bip32Path": "${bip32Path}", - "xpub": "${extendedPublicKeyImporter.extendedPublicKey}", - "method": "${extendedPublicKeyImporter.method}" - }`; + const importer = + extendedPublicKeyImporter.method === "unknown" + ? ` { + "name": "${extendedPublicKeyImporter.name}", + "bip32Path": "${bip32Path}", + "xpub": "${extendedPublicKeyImporter.extendedPublicKey}" + }` + : ` { + "name": "${extendedPublicKeyImporter.name}", + "bip32Path": "${bip32Path}", + "xpub": "${extendedPublicKeyImporter.extendedPublicKey}", + "method": "${extendedPublicKeyImporter.method}" + }`; + return importer; }; walletDetailsFilename = () => {