From 24ae067ad9dd7e5b00e9228e14b68276dd1b3f27 Mon Sep 17 00:00:00 2001 From: Jack Mallers Date: Mon, 12 Mar 2018 23:14:48 -0500 Subject: [PATCH 01/10] feature(unlockWallet): ability to unlock wallet with helpful error messages --- app/components/Onboarding/InitWallet.js | 35 ++++ app/components/Onboarding/InitWallet.scss | 60 +++++++ app/components/Onboarding/Login.js | 43 +++++ app/components/Onboarding/Login.scss | 124 ++++++++++++++ .../Onboarding/NewWalletPassword.js | 58 +++++++ .../Onboarding/NewWalletPassword.scss | 24 +++ app/components/Onboarding/NewWalletSeed.js | 20 +++ app/components/Onboarding/NewWalletSeed.scss | 11 ++ app/components/Onboarding/Onboarding.js | 59 ++++++- app/components/Onboarding/Signup.js | 19 +++ app/components/Onboarding/Signup.scss | 0 app/containers/Root.js | 52 +++++- app/icons/eye.svg | 1 + app/lnd/config/rpc.proto | 153 ++++++++++++++---- app/lnd/index.js | 18 ++- app/lnd/init/index.js | 13 ++ app/lnd/lib/grpcInit.js | 24 +++ app/lnd/lib/lightning.js | 2 + app/lnd/lib/rpc.proto | 148 +++++++++++++---- app/lnd/lib/walletUnlocker.js | 16 ++ app/lnd/methods/walletController.js | 46 ++++++ app/lnd/walletUnlockerMethods/index.js | 30 ++++ app/main.dev.js | 36 ++++- app/reducers/ipc.js | 18 ++- app/reducers/onboarding.js | 124 +++++++++++++- app/rpc.proto | 148 +++++++++++++---- 26 files changed, 1150 insertions(+), 132 deletions(-) create mode 100644 app/components/Onboarding/InitWallet.js create mode 100644 app/components/Onboarding/InitWallet.scss create mode 100644 app/components/Onboarding/Login.js create mode 100644 app/components/Onboarding/Login.scss create mode 100644 app/components/Onboarding/NewWalletPassword.js create mode 100644 app/components/Onboarding/NewWalletPassword.scss create mode 100644 app/components/Onboarding/NewWalletSeed.js create mode 100644 app/components/Onboarding/NewWalletSeed.scss create mode 100644 app/components/Onboarding/Signup.js create mode 100644 app/components/Onboarding/Signup.scss create mode 100644 app/icons/eye.svg create mode 100644 app/lnd/init/index.js create mode 100644 app/lnd/lib/grpcInit.js create mode 100644 app/lnd/lib/walletUnlocker.js create mode 100644 app/lnd/walletUnlockerMethods/index.js diff --git a/app/components/Onboarding/InitWallet.js b/app/components/Onboarding/InitWallet.js new file mode 100644 index 00000000..5069d0f6 --- /dev/null +++ b/app/components/Onboarding/InitWallet.js @@ -0,0 +1,35 @@ +import React from 'react' +import PropTypes from 'prop-types' +import Login from './Login' +import Signup from './Signup' +import styles from './InitWallet.scss' + +const InitWallet = ({ + password, + passwordIsValid, + hasSeed, + updatePassword, + createWallet, + unlockWallet, + unlockingWallet, + unlockWalletError +}) => ( +
+ { + hasSeed ? + + : + + } +
+) + +InitWallet.propTypes = {} + +export default InitWallet diff --git a/app/components/Onboarding/InitWallet.scss b/app/components/Onboarding/InitWallet.scss new file mode 100644 index 00000000..8eeb04e4 --- /dev/null +++ b/app/components/Onboarding/InitWallet.scss @@ -0,0 +1,60 @@ +@import '../../variables.scss'; + +.container { + position: relative; +} + +.password { + background: transparent; + outline: none; + border: 0; + color: $gold; + -webkit-text-fill-color: $white; + font-size: 22px; +} + +.password::-webkit-input-placeholder { + text-shadow: none; + -webkit-text-fill-color: initial; +} + +.buttons { + margin-top: 15%; + text-align: center; + + div { + color: $white; + + &:nth-child(1) { + text-align: center; + margin-bottom: 40px; + + span { + padding: 15px 35px; + background: $darkspaceblue; + font-size: 14px; + opacity: 0.5; + transition: all 0.25s; + + &.active { + opacity: 1.0; + cursor: pointer; + + &:hover { + background: lighten($darkspaceblue, 10%); + } + } + } + } + + &:nth-child(2), &:nth-child(3) { + font-size: 12px; + cursor: pointer; + margin: 10px 0; + + &:hover { + text-decoration: underline; + } + } + } +} \ No newline at end of file diff --git a/app/components/Onboarding/Login.js b/app/components/Onboarding/Login.js new file mode 100644 index 00000000..6f7386b5 --- /dev/null +++ b/app/components/Onboarding/Login.js @@ -0,0 +1,43 @@ +import React from 'react' +import PropTypes from 'prop-types' +import styles from './Login.scss' + +const Login = ({ + password, + updatePassword, + unlockingWallet, + unlockWallet, + unlockWalletError +}) => ( +
+ input && input.focus()} + value={password} + onChange={event => updatePassword(event.target.value)} + /> +

+ {unlockWalletError.message} +

+ +
+
+ unlockWallet(password)}> + { + unlockingWallet ? + + : + 'Log In' + } + +
+
Recover existing wallet
+
+
+) + +Login.propTypes = {} + +export default Login diff --git a/app/components/Onboarding/Login.scss b/app/components/Onboarding/Login.scss new file mode 100644 index 00000000..ea052c7f --- /dev/null +++ b/app/components/Onboarding/Login.scss @@ -0,0 +1,124 @@ +@import '../../variables.scss'; + +.container { + position: relative; +} + +.password { + background: transparent; + outline: none; + border: 0; + color: $gold; + -webkit-text-fill-color: $white; + font-size: 22px; + border-bottom: 1px solid transparent; + transition: all 0.25s; + + &.inputError { + border-bottom: 1px solid $red; + } +} + +.password::-webkit-input-placeholder { + text-shadow: none; + -webkit-text-fill-color: initial; +} + +.error { + margin-top: 20px; + color: $red; + visibility: hidden; + font-size: 12px; + transition: all 0.25s; + + &.active { + visibility: visible; + } +} + +.buttons { + margin-top: 15%; + text-align: center; + + div { + color: $white; + + &:nth-child(1) { + text-align: center; + margin-bottom: 40px; + + span { + padding: 15px 35px; + background: $darkspaceblue; + font-size: 14px; + opacity: 0.5; + transition: all 0.25s; + + &.button { + position: relative; + } + + &.active { + opacity: 1.0; + cursor: pointer; + + &:hover { + background: lighten($darkspaceblue, 10%); + } + } + } + } + + &:nth-child(2), &:nth-child(3) { + font-size: 12px; + cursor: pointer; + margin: 10px 0; + + &:hover { + text-decoration: underline; + } + } + } +} + +.spinner { + height: 20px; + width: 20px; + border: 1px solid rgba(235, 184, 100, 0.1); + border-left-color: rgba(235, 184, 100, 0.4); + -webkit-border-radius: 999px; + -moz-border-radius: 999px; + border-radius: 999px; + -webkit-animation: animation-rotate 1000ms linear infinite; + -moz-animation: animation-rotate 1000ms linear infinite; + -o-animation: animation-rotate 1000ms linear infinite; + animation: animation-rotate 1000ms linear infinite; + display: inline-block; + position: absolute; + top: calc(50% - 10px); + left: calc(50% - 10px); +} + +@-webkit-keyframes animation-rotate { + 100% { + -webkit-transform: rotate(360deg); + } +} + +@-moz-keyframes animation-rotate { + 100% { + -moz-transform: rotate(360deg); + } +} + +@-o-keyframes animation-rotate { + 100% { + -o-transform: rotate(360deg); + } +} + +@keyframes animation-rotate { + 100% { + transform: rotate(360deg); + } +} diff --git a/app/components/Onboarding/NewWalletPassword.js b/app/components/Onboarding/NewWalletPassword.js new file mode 100644 index 00000000..1ab89d19 --- /dev/null +++ b/app/components/Onboarding/NewWalletPassword.js @@ -0,0 +1,58 @@ +import React from 'react' +import PropTypes from 'prop-types' +import Isvg from 'react-inlinesvg' +import eye from 'icons/eye.svg' +import styles from './NewWalletPassword.scss' + +class NewWalletPassword extends React.Component { + constructor(props) { + super(props) + + this.state = { + inputType: 'password', + confirmPassword: '' + } + } + + render() { + const { createWalletPassword, updateCreateWalletPassword } = this.props + const { inputType, confirmPassword } = this.state + + const toggleInputType = () => { + const newInputType = inputType === 'password' ? 'text' : 'password' + + this.setState({ inputType: newInputType }) + } + + return ( +
+
+ updateCreateWalletPassword(event.target.value)} + /> +
+ +
+ this.setState({ confirmPassword: event.target.value })} + /> +
+
+ ) + } +} + +NewWalletPassword.propTypes = { + createWalletPassword: PropTypes.string.isRequired, + updateCreateWalletPassword: PropTypes.func.isRequired +} + +export default NewWalletPassword diff --git a/app/components/Onboarding/NewWalletPassword.scss b/app/components/Onboarding/NewWalletPassword.scss new file mode 100644 index 00000000..1fd5fd6f --- /dev/null +++ b/app/components/Onboarding/NewWalletPassword.scss @@ -0,0 +1,24 @@ +@import '../../variables.scss'; + +.input:nth-child(2) { + margin-top: 40px; +} + +.password { + background: transparent; + outline: none; + border: 0; + color: $gold; + -webkit-text-fill-color: $white; + font-size: 22px; + transition: all 0.25s; + + &.error { + border-bottom: 1px solid $red; + } +} + +.password::-webkit-input-placeholder { + text-shadow: none; + -webkit-text-fill-color: initial; +} diff --git a/app/components/Onboarding/NewWalletSeed.js b/app/components/Onboarding/NewWalletSeed.js new file mode 100644 index 00000000..82dc5b8a --- /dev/null +++ b/app/components/Onboarding/NewWalletSeed.js @@ -0,0 +1,20 @@ +import React from 'react' +import PropTypes from 'prop-types' +import styles from './NewWalletSeed.scss' + +const NewWalletSeed = ({ seed }) => ( +
+ { + seed.length > 0 ? + seed.join(', ') + : + 'loading' + } +
+) + +NewWalletSeed.propTypes = { + seed: PropTypes.array.isRequired +} + +export default NewWalletSeed diff --git a/app/components/Onboarding/NewWalletSeed.scss b/app/components/Onboarding/NewWalletSeed.scss new file mode 100644 index 00000000..ed6e62f1 --- /dev/null +++ b/app/components/Onboarding/NewWalletSeed.scss @@ -0,0 +1,11 @@ +@import '../../variables.scss'; + +.container { + background: darken(#242833, 10%); + padding: 20px 40px; + font-size: 14px; + line-height: 50px; + color: $white; + font-family: 'Roboto'; + letter-spacing: 1.5px; +} \ No newline at end of file diff --git a/app/components/Onboarding/Onboarding.js b/app/components/Onboarding/Onboarding.js index a508ab64..20f08dde 100644 --- a/app/components/Onboarding/Onboarding.js +++ b/app/components/Onboarding/Onboarding.js @@ -6,25 +6,35 @@ import LoadingBolt from 'components/LoadingBolt' import FormContainer from './FormContainer' import Alias from './Alias' import Autopilot from './Autopilot' +import InitWallet from './InitWallet' +import NewWalletSeed from './NewWalletSeed' +import NewWalletPassword from './NewWalletPassword' import styles from './Onboarding.scss' const Onboarding = ({ onboarding: { step, alias, - autopilot + autopilot, + startingLnd, + createWalletPassword, + seed }, changeStep, - submit, + startLnd, + submitNewWallet, aliasProps, - autopilotProps + initWalletProps, + autopilotProps, + newWalletSeedProps, + newWalletPasswordProps }) => { const renderStep = () => { switch (step) { case 1: return ( changeStep(2)} @@ -35,19 +45,54 @@ const Onboarding = ({ case 2: return ( changeStep(1)} - next={() => submit(alias, autopilot)} + next={() => startLnd(alias, autopilot)} > ) + case 3: + return ( + changeStep(2)} + next={null} + > + + + ) + case 4: + return ( + changeStep(3)} + next={() => changeStep(5)} + > + + + ) + case 5: + return ( + changeStep(4)} + next={() => submitNewWallet(createWalletPassword, seed)} + > + + + ) default: return } } + if (startingLnd) { return } + return (
{renderStep()} @@ -60,7 +105,7 @@ Onboarding.propTypes = { aliasProps: PropTypes.object.isRequired, autopilotProps: PropTypes.object.isRequired, changeStep: PropTypes.func.isRequired, - submit: PropTypes.func.isRequired + startLnd: PropTypes.func.isRequired } export default Onboarding diff --git a/app/components/Onboarding/Signup.js b/app/components/Onboarding/Signup.js new file mode 100644 index 00000000..8ef1f5dd --- /dev/null +++ b/app/components/Onboarding/Signup.js @@ -0,0 +1,19 @@ +import React from 'react' +import PropTypes from 'prop-types' +import styles from './Signup.scss' + +const Signup = ({ + password, + passwordIsValid, + hasSeed, + updatePassword, + createWallet +}) => ( +
+ signup yo +
+) + +Signup.propTypes = {} + +export default Signup diff --git a/app/components/Onboarding/Signup.scss b/app/components/Onboarding/Signup.scss new file mode 100644 index 00000000..e69de29b diff --git a/app/containers/Root.js b/app/containers/Root.js index b517a3da..b6d2aad3 100644 --- a/app/containers/Root.js +++ b/app/containers/Root.js @@ -7,15 +7,31 @@ import PropTypes from 'prop-types' import LoadingBolt from '../components/LoadingBolt' import Onboarding from '../components/Onboarding' import Syncing from '../components/Onboarding/Syncing' -import { updateAlias, setAutopilot, changeStep, submit } from '../reducers/onboarding' +import { + updateAlias, + updatePassword, + setAutopilot, + changeStep, + startLnd, + createWallet, + updateCreateWalletPassword, + submitNewWallet, + onboardingSelectors, + unlockWallet +} from '../reducers/onboarding' import { fetchBlockHeight, lndSelectors } from '../reducers/lnd' import Routes from '../routes' const mapDispatchToProps = { updateAlias, + updatePassword, + updateCreateWalletPassword, setAutopilot, changeStep, - submit, + startLnd, + createWallet, + submitNewWallet, + unlockWallet, fetchBlockHeight } @@ -24,7 +40,8 @@ const mapStateToProps = state => ({ lnd: state.lnd, onboarding: state.onboarding, - syncPercentage: lndSelectors.syncPercentage(state) + syncPercentage: lndSelectors.syncPercentage(state), + passwordIsValid: onboardingSelectors.passwordIsValid(state) }) const mergeProps = (stateProps, dispatchProps, ownProps) => { @@ -43,12 +60,37 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { setAutopilot: dispatchProps.setAutopilot } + const initWalletProps = { + password: stateProps.onboarding.password, + passwordIsValid: stateProps.passwordIsValid, + hasSeed: stateProps.onboarding.hasSeed, + unlockingWallet: stateProps.onboarding.unlockingWallet, + unlockWalletError: stateProps.onboarding.unlockWalletError, + + updatePassword: dispatchProps.updatePassword, + createWallet: dispatchProps.createWallet, + unlockWallet: dispatchProps.unlockWallet + } + + const newWalletSeedProps = { + seed: stateProps.onboarding.seed + } + + const newWalletPasswordProps = { + createWalletPassword: stateProps.onboarding.createWalletPassword, + updateCreateWalletPassword: dispatchProps.updateCreateWalletPassword + } + const onboardingProps = { onboarding: stateProps.onboarding, changeStep: dispatchProps.changeStep, - submit: dispatchProps.submit, + startLnd: dispatchProps.startLnd, + submitNewWallet: dispatchProps.submitNewWallet, aliasProps, - autopilotProps + autopilotProps, + initWalletProps, + newWalletSeedProps, + newWalletPasswordProps } return { diff --git a/app/icons/eye.svg b/app/icons/eye.svg new file mode 100644 index 00000000..9cde2437 --- /dev/null +++ b/app/icons/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/lnd/config/rpc.proto b/app/lnd/config/rpc.proto index 62d7c7f8..541a2d94 100644 --- a/app/lnd/config/rpc.proto +++ b/app/lnd/config/rpc.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -import "google/api/annotations.proto"; +// import "google/api/annotations.proto"; package lnrpc; /** @@ -28,13 +28,39 @@ package lnrpc; // The WalletUnlocker service is used to set up a wallet password for // lnd at first startup, and unlock a previously set up wallet. service WalletUnlocker { - /** lncli: `create` - CreateWallet is used at lnd startup to set the encryption password for - the wallet database. + /** + GenSeed is the first method that should be used to instantiate a new lnd + instance. This method allows a caller to generate a new aezeed cipher seed + given an optional passphrase. If provided, the passphrase will be necessary + to decrypt the cipherseed to expose the internal wallet seed. + + Once the cipherseed is obtained and verified by the user, the InitWallet + method should be used to commit the newly generated seed, and create the + wallet. */ - rpc CreateWallet(CreateWalletRequest) returns (CreateWalletResponse) { + rpc GenSeed(GenSeedRequest) returns (GenSeedResponse) { option (google.api.http) = { - post: "/v1/createwallet" + get: "/v1/genseed" + }; + } + + /** lncli: `init` + InitWallet is used when lnd is starting up for the first time to fully + initialize the daemon and its internal wallet. At the very least a wallet + password must be provided. This will be used to encrypt sensitive material + on disk. + + In the case of a recovery scenario, the user can also specify their aezeed + mnemonic and passphrase. If set, then the daemon will use this prior state + to initialize its internal wallet. + + Alternatively, this can be used along with the GenSeed RPC to obtain a + seed, then present it to the user. Once it has been verified by the user, + the seed can be fed into this RPC in order to commit the new wallet. + */ + rpc InitWallet(InitWalletRequest) returns (InitWalletResponse) { + option (google.api.http) = { + post: "/v1/initwallet" body: "*" }; } @@ -51,20 +77,74 @@ service WalletUnlocker { } } -message CreateWalletRequest { - bytes password = 1; +message GenSeedRequest { + /** + aezeed_passphrase is an optional user provided passphrase that will be used + to encrypt the generated aezeed cipher seed. + */ + bytes aezeed_passphrase = 1; + + /** + seed_entropy is an optional 16-bytes generated via CSPRNG. If not + specified, then a fresh set of randomness will be used to create the seed. + */ + bytes seed_entropy = 2; } -message CreateWalletResponse {} +message GenSeedResponse { + /** + cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + cipher seed obtained by the user. This field is optional, as if not + provided, then the daemon will generate a new cipher seed for the user. + Otherwise, then the daemon will attempt to recover the wallet state linked + to this cipher seed. + */ + repeated string cipher_seed_mnemonic = 1; + /** + enciphered_seed are the raw aezeed cipher seed bytes. This is the raw + cipher text before run through our mnemonic encoding scheme. + */ + bytes enciphered_seed = 2; +} + +message InitWalletRequest { + /** + wallet_password is the passphrase that should be used to encrypt the + wallet. This MUST be at least 8 chars in length. After creation, this + password is required to unlock the daemon. + */ + bytes wallet_password = 1; + + /** + cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + cipher seed obtained by the user. This may have been generated by the + GenSeed method, or be an existing seed. + */ + repeated string cipher_seed_mnemonic = 2; + + /** + aezeed_passphrase is an optional user provided passphrase that will be used + to encrypt the generated aezeed cipher seed. + */ + bytes aezeed_passphrase = 3; +} +message InitWalletResponse { +} message UnlockWalletRequest { - bytes password = 1; + /** + wallet_password should be the current valid passphrase for the daemon. This + will be required to decrypt on-disk material that the daemon requires to + function properly. + */ + bytes wallet_password = 1; } message UnlockWalletResponse {} service Lightning { /** lncli: `walletbalance` - WalletBalance returns total unspent outputs(confirmed and unconfirmed), all confirmed unspent outputs and all unconfirmed unspent outputs under control + WalletBalance returns total unspent outputs(confirmed and unconfirmed), all + confirmed unspent outputs and all unconfirmed unspent outputs under control by the wallet. This method can be modified by having the request specify only witness outputs should be factored into the final output sum. */ @@ -251,7 +331,7 @@ service Lightning { */ rpc CloseChannel (CloseChannelRequest) returns (stream CloseStatusUpdate) { option (google.api.http) = { - delete: "/v1/channels/{channel_point.funding_txid}/{channel_point.output_index}" + delete: "/v1/channels/{channel_point.funding_txid_str}/{channel_point.output_index}" }; } @@ -294,18 +374,18 @@ service Lightning { */ rpc ListInvoices (ListInvoiceRequest) returns (ListInvoiceResponse) { option (google.api.http) = { - get: "/v1/invoices/{pending_only}" + get: "/v1/invoices" }; } /** lncli: `lookupinvoice` - LookupInvoice attemps to look up an invoice according to its payment hash. + LookupInvoice attempts to look up an invoice according to its payment hash. The passed payment hash *must* be exactly 32 bytes, if not, an error is returned. */ rpc LookupInvoice (PaymentHash) returns (Invoice) { option (google.api.http) = { - get: "/v1/invoices/{r_hash_str}" + get: "/v1/invoice/{r_hash_str}" }; } @@ -389,7 +469,7 @@ service Lightning { route to a target destination capable of carrying a specific amount of satoshis. The retuned route contains the full details required to craft and send an HTLC, also including the necessary information that should be - present within the Sphinx packet encapsualted within the HTLC. + present within the Sphinx packet encapsulated within the HTLC. */ rpc QueryRoutes(QueryRoutesRequest) returns (QueryRoutesResponse) { option (google.api.http) = { @@ -447,7 +527,7 @@ service Lightning { */ rpc UpdateChannelPolicy(PolicyUpdateRequest) returns (PolicyUpdateResponse) { option (google.api.http) = { - post: "/v1/fees" + post: "/v1/chanpolicy" body: "*" }; } @@ -518,16 +598,16 @@ message SendResponse { } message ChannelPoint { - // TODO(roasbeef): make str vs bytes into a oneof + oneof funding_txid { + /// Txid of the funding transaction + bytes funding_txid_bytes = 1 [json_name = "funding_txid_bytes"]; - /// Txid of the funding transaction - bytes funding_txid = 1 [ json_name = "funding_txid" ]; - - /// Hex-encoded string representing the funding transaction - string funding_txid_str = 2 [ json_name = "funding_txid_str" ]; + /// Hex-encoded string representing the funding transaction + string funding_txid_str = 2 [json_name = "funding_txid_str"]; + } /// The index of the output of the funding transaction - uint32 output_index = 3 [ json_name = "output_index" ]; + uint32 output_index = 3 [json_name = "output_index"]; } message LightningAddress { @@ -610,7 +690,7 @@ message VerifyMessageRequest { /// The message over which the signature is to be verified bytes msg = 1 [ json_name = "msg" ]; - /// The signature to be verifed over the given message + /// The signature to be verified over the given message string signature = 2 [ json_name = "signature" ]; } message VerifyMessageResponse { @@ -630,8 +710,6 @@ message ConnectPeerRequest { bool perm = 2; } message ConnectPeerResponse { - /// The id of the newly connected peer - int32 peer_id = 1 [json_name = "peer_id"]; } message DisconnectPeerRequest { @@ -738,9 +816,6 @@ message Peer { /// The identity pubkey of the peer string pub_key = 1 [json_name = "pub_key"]; - /// The peer's id from the local point of view - int32 peer_id = 2 [json_name = "peer_id"]; - /// Network address of the peer; eg `127.0.0.1:10011` string address = 3 [json_name = "address"]; @@ -806,6 +881,9 @@ message GetInfoResponse { /// The URIs of the current node. repeated string uris = 12 [json_name = "uris"]; + + /// Timestamp of the block best known to the wallet + int64 best_header_timestamp = 13 [ json_name = "best_header_timestamp" ]; } message ConfirmationUpdate { @@ -840,8 +918,9 @@ message CloseChannelRequest { int32 target_conf = 3; /// A manual fee rate set in sat/byte that should be used when crafting the closure transaction. - int64 sat_per_byte = 5; + int64 sat_per_byte = 4; } + message CloseStatusUpdate { oneof update { PendingUpdate close_pending = 1 [json_name = "close_pending"]; @@ -857,13 +936,10 @@ message PendingUpdate { message OpenChannelRequest { - /// The peer_id of the node to open a channel with - int32 target_peer_id = 1 [json_name = "target_peer_id"]; - /// The pubkey of the node to open a channel with bytes node_pubkey = 2 [json_name = "node_pubkey"]; - /// The hex encorded pubkey of the node to open a channel with + /// The hex encoded pubkey of the node to open a channel with string node_pubkey_string = 3 [json_name = "node_pubkey_string"]; /// The number of satoshis the wallet should commit to the channel @@ -1031,6 +1107,9 @@ message QueryRoutesRequest { /// The amount to send expressed in satoshis int64 amt = 2; + + /// The max number of routes to return. + int32 num_routes = 3; } message QueryRoutesResponse { repeated Route routes = 1 [ json_name = "routes"]; @@ -1337,6 +1416,7 @@ message InvoiceSubscription { message Payment { /// The payment hash string payment_hash = 1 [json_name = "payment_hash"]; + /// The value of the payment in satoshis int64 value = 2 [json_name = "value"]; @@ -1348,6 +1428,9 @@ message Payment { /// The fee paid for this payment in satoshis int64 fee = 5 [json_name = "fee"]; + + /// The payment preimage + string payment_preimage = 6 [json_name = "payment_preimage"]; } message ListPaymentsRequest { diff --git a/app/lnd/index.js b/app/lnd/index.js index 40796b74..4db8a714 100644 --- a/app/lnd/index.js +++ b/app/lnd/index.js @@ -2,10 +2,12 @@ import grpc from 'grpc' import fs from 'fs' import config from './config' import lightning from './lib/lightning' +import walletUnlocker from './lib/walletUnlocker' import subscribe from './subscribe' import methods from './methods' +import walletUnlockerMethods from './walletUnlockerMethods' -export default (callback) => { +const initLnd = (callback) => { const macaroonFile = fs.readFileSync(config.macaroon) const meta = new grpc.Metadata() meta.add('macaroon', macaroonFile.toString('hex')) @@ -14,5 +16,19 @@ export default (callback) => { const lndSubscribe = mainWindow => subscribe(mainWindow, lnd, meta) const lndMethods = (event, msg, data) => methods(lnd, meta, event, msg, data) + callback(lndSubscribe, lndMethods) } + +const initWalletUnlocker = (callback) => { + const walletUnlockerObj = walletUnlocker(config.lightningRpc, config.lightningHost) + const walletUnlockerMethodsCallback = (event, msg, data) => walletUnlockerMethods(walletUnlockerObj, event, msg, data) + + callback(walletUnlockerMethodsCallback) +} + + +export default { + initLnd, + initWalletUnlocker +} \ No newline at end of file diff --git a/app/lnd/init/index.js b/app/lnd/init/index.js new file mode 100644 index 00000000..74b0a81b --- /dev/null +++ b/app/lnd/init/index.js @@ -0,0 +1,13 @@ +/* eslint no-console: 0 */ // --> OFF +import * as walletController from '../methods/walletController' + +export default function (walletUnlocker, meta, event, msg, data) { + console.log('msg yo wtf: ', msg) + switch (msg) { + case 'genSeed': + walletController.genSeed(walletUnlocker, meta) + .then(data => { console.log('data: ', data) }) + .catch(error => { console.log('error: ', error) }) + default: + } +} diff --git a/app/lnd/lib/grpcInit.js b/app/lnd/lib/grpcInit.js new file mode 100644 index 00000000..ac0ec319 --- /dev/null +++ b/app/lnd/lib/grpcInit.js @@ -0,0 +1,24 @@ +import fs from 'fs' +import path from 'path' +import grpc from 'grpc' +import config from '../config' + +const grpcInit = (rpcpath, host) => { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' + process.env.GRPC_SSL_CIPHER_SUITES = 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384' + + const lndCert = fs.readFileSync(config.cert) + const credentials = grpc.credentials.createSsl(lndCert) + const rpc = grpc.load(path.join(__dirname, 'rpc.proto')) + + + const lightning = new rpc.lnrpc.Lightning(host, credentials) + const walletUnlocker = new rpc.lnrpc.WalletUnlocker(host, credentials) + + return { + lightning, + walletUnlocker + } +} + +export default grpcInit diff --git a/app/lnd/lib/lightning.js b/app/lnd/lib/lightning.js index b6c55b2c..b8b7aed9 100644 --- a/app/lnd/lib/lightning.js +++ b/app/lnd/lib/lightning.js @@ -4,6 +4,8 @@ import grpc from 'grpc' import config from '../config' const lightning = (rpcpath, host) => { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' + process.env.GRPC_SSL_CIPHER_SUITES = 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384' const lndCert = fs.readFileSync(config.cert) const credentials = grpc.credentials.createSsl(lndCert) const rpc = grpc.load(path.join(__dirname, 'rpc.proto')) diff --git a/app/lnd/lib/rpc.proto b/app/lnd/lib/rpc.proto index 0d178563..541a2d94 100644 --- a/app/lnd/lib/rpc.proto +++ b/app/lnd/lib/rpc.proto @@ -28,13 +28,39 @@ package lnrpc; // The WalletUnlocker service is used to set up a wallet password for // lnd at first startup, and unlock a previously set up wallet. service WalletUnlocker { - /** lncli: `create` - CreateWallet is used at lnd startup to set the encryption password for - the wallet database. + /** + GenSeed is the first method that should be used to instantiate a new lnd + instance. This method allows a caller to generate a new aezeed cipher seed + given an optional passphrase. If provided, the passphrase will be necessary + to decrypt the cipherseed to expose the internal wallet seed. + + Once the cipherseed is obtained and verified by the user, the InitWallet + method should be used to commit the newly generated seed, and create the + wallet. */ - rpc CreateWallet(CreateWalletRequest) returns (CreateWalletResponse) { + rpc GenSeed(GenSeedRequest) returns (GenSeedResponse) { option (google.api.http) = { - post: "/v1/createwallet" + get: "/v1/genseed" + }; + } + + /** lncli: `init` + InitWallet is used when lnd is starting up for the first time to fully + initialize the daemon and its internal wallet. At the very least a wallet + password must be provided. This will be used to encrypt sensitive material + on disk. + + In the case of a recovery scenario, the user can also specify their aezeed + mnemonic and passphrase. If set, then the daemon will use this prior state + to initialize its internal wallet. + + Alternatively, this can be used along with the GenSeed RPC to obtain a + seed, then present it to the user. Once it has been verified by the user, + the seed can be fed into this RPC in order to commit the new wallet. + */ + rpc InitWallet(InitWalletRequest) returns (InitWalletResponse) { + option (google.api.http) = { + post: "/v1/initwallet" body: "*" }; } @@ -51,20 +77,74 @@ service WalletUnlocker { } } -message CreateWalletRequest { - bytes password = 1; +message GenSeedRequest { + /** + aezeed_passphrase is an optional user provided passphrase that will be used + to encrypt the generated aezeed cipher seed. + */ + bytes aezeed_passphrase = 1; + + /** + seed_entropy is an optional 16-bytes generated via CSPRNG. If not + specified, then a fresh set of randomness will be used to create the seed. + */ + bytes seed_entropy = 2; +} +message GenSeedResponse { + /** + cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + cipher seed obtained by the user. This field is optional, as if not + provided, then the daemon will generate a new cipher seed for the user. + Otherwise, then the daemon will attempt to recover the wallet state linked + to this cipher seed. + */ + repeated string cipher_seed_mnemonic = 1; + + /** + enciphered_seed are the raw aezeed cipher seed bytes. This is the raw + cipher text before run through our mnemonic encoding scheme. + */ + bytes enciphered_seed = 2; } -message CreateWalletResponse {} +message InitWalletRequest { + /** + wallet_password is the passphrase that should be used to encrypt the + wallet. This MUST be at least 8 chars in length. After creation, this + password is required to unlock the daemon. + */ + bytes wallet_password = 1; + + /** + cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + cipher seed obtained by the user. This may have been generated by the + GenSeed method, or be an existing seed. + */ + repeated string cipher_seed_mnemonic = 2; + + /** + aezeed_passphrase is an optional user provided passphrase that will be used + to encrypt the generated aezeed cipher seed. + */ + bytes aezeed_passphrase = 3; +} +message InitWalletResponse { +} message UnlockWalletRequest { - bytes password = 1; + /** + wallet_password should be the current valid passphrase for the daemon. This + will be required to decrypt on-disk material that the daemon requires to + function properly. + */ + bytes wallet_password = 1; } message UnlockWalletResponse {} service Lightning { /** lncli: `walletbalance` - WalletBalance returns total unspent outputs(confirmed and unconfirmed), all confirmed unspent outputs and all unconfirmed unspent outputs under control + WalletBalance returns total unspent outputs(confirmed and unconfirmed), all + confirmed unspent outputs and all unconfirmed unspent outputs under control by the wallet. This method can be modified by having the request specify only witness outputs should be factored into the final output sum. */ @@ -251,7 +331,7 @@ service Lightning { */ rpc CloseChannel (CloseChannelRequest) returns (stream CloseStatusUpdate) { option (google.api.http) = { - delete: "/v1/channels/{channel_point.funding_txid}/{channel_point.output_index}" + delete: "/v1/channels/{channel_point.funding_txid_str}/{channel_point.output_index}" }; } @@ -294,18 +374,18 @@ service Lightning { */ rpc ListInvoices (ListInvoiceRequest) returns (ListInvoiceResponse) { option (google.api.http) = { - get: "/v1/invoices/{pending_only}" + get: "/v1/invoices" }; } /** lncli: `lookupinvoice` - LookupInvoice attemps to look up an invoice according to its payment hash. + LookupInvoice attempts to look up an invoice according to its payment hash. The passed payment hash *must* be exactly 32 bytes, if not, an error is returned. */ rpc LookupInvoice (PaymentHash) returns (Invoice) { option (google.api.http) = { - get: "/v1/invoices/{r_hash_str}" + get: "/v1/invoice/{r_hash_str}" }; } @@ -389,7 +469,7 @@ service Lightning { route to a target destination capable of carrying a specific amount of satoshis. The retuned route contains the full details required to craft and send an HTLC, also including the necessary information that should be - present within the Sphinx packet encapsualted within the HTLC. + present within the Sphinx packet encapsulated within the HTLC. */ rpc QueryRoutes(QueryRoutesRequest) returns (QueryRoutesResponse) { option (google.api.http) = { @@ -447,7 +527,7 @@ service Lightning { */ rpc UpdateChannelPolicy(PolicyUpdateRequest) returns (PolicyUpdateResponse) { option (google.api.http) = { - post: "/v1/fees" + post: "/v1/chanpolicy" body: "*" }; } @@ -518,16 +598,16 @@ message SendResponse { } message ChannelPoint { - // TODO(roasbeef): make str vs bytes into a oneof - - /// Txid of the funding transaction - bytes funding_txid = 1 [ json_name = "funding_txid" ]; + oneof funding_txid { + /// Txid of the funding transaction + bytes funding_txid_bytes = 1 [json_name = "funding_txid_bytes"]; - /// Hex-encoded string representing the funding transaction - string funding_txid_str = 2 [ json_name = "funding_txid_str" ]; + /// Hex-encoded string representing the funding transaction + string funding_txid_str = 2 [json_name = "funding_txid_str"]; + } /// The index of the output of the funding transaction - uint32 output_index = 3 [ json_name = "output_index" ]; + uint32 output_index = 3 [json_name = "output_index"]; } message LightningAddress { @@ -610,7 +690,7 @@ message VerifyMessageRequest { /// The message over which the signature is to be verified bytes msg = 1 [ json_name = "msg" ]; - /// The signature to be verifed over the given message + /// The signature to be verified over the given message string signature = 2 [ json_name = "signature" ]; } message VerifyMessageResponse { @@ -630,8 +710,6 @@ message ConnectPeerRequest { bool perm = 2; } message ConnectPeerResponse { - /// The id of the newly connected peer - int32 peer_id = 1 [json_name = "peer_id"]; } message DisconnectPeerRequest { @@ -738,9 +816,6 @@ message Peer { /// The identity pubkey of the peer string pub_key = 1 [json_name = "pub_key"]; - /// The peer's id from the local point of view - int32 peer_id = 2 [json_name = "peer_id"]; - /// Network address of the peer; eg `127.0.0.1:10011` string address = 3 [json_name = "address"]; @@ -806,6 +881,9 @@ message GetInfoResponse { /// The URIs of the current node. repeated string uris = 12 [json_name = "uris"]; + + /// Timestamp of the block best known to the wallet + int64 best_header_timestamp = 13 [ json_name = "best_header_timestamp" ]; } message ConfirmationUpdate { @@ -840,8 +918,9 @@ message CloseChannelRequest { int32 target_conf = 3; /// A manual fee rate set in sat/byte that should be used when crafting the closure transaction. - int64 sat_per_byte = 5; + int64 sat_per_byte = 4; } + message CloseStatusUpdate { oneof update { PendingUpdate close_pending = 1 [json_name = "close_pending"]; @@ -857,13 +936,10 @@ message PendingUpdate { message OpenChannelRequest { - /// The peer_id of the node to open a channel with - int32 target_peer_id = 1 [json_name = "target_peer_id"]; - /// The pubkey of the node to open a channel with bytes node_pubkey = 2 [json_name = "node_pubkey"]; - /// The hex encorded pubkey of the node to open a channel with + /// The hex encoded pubkey of the node to open a channel with string node_pubkey_string = 3 [json_name = "node_pubkey_string"]; /// The number of satoshis the wallet should commit to the channel @@ -1031,6 +1107,9 @@ message QueryRoutesRequest { /// The amount to send expressed in satoshis int64 amt = 2; + + /// The max number of routes to return. + int32 num_routes = 3; } message QueryRoutesResponse { repeated Route routes = 1 [ json_name = "routes"]; @@ -1337,6 +1416,7 @@ message InvoiceSubscription { message Payment { /// The payment hash string payment_hash = 1 [json_name = "payment_hash"]; + /// The value of the payment in satoshis int64 value = 2 [json_name = "value"]; diff --git a/app/lnd/lib/walletUnlocker.js b/app/lnd/lib/walletUnlocker.js new file mode 100644 index 00000000..334ebadd --- /dev/null +++ b/app/lnd/lib/walletUnlocker.js @@ -0,0 +1,16 @@ +import fs from 'fs' +import path from 'path' +import grpc from 'grpc' +import config from '../config' + +const walletUnlocker = (rpcpath, host) => { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' + process.env.GRPC_SSL_CIPHER_SUITES = 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384' + const lndCert = fs.readFileSync(config.cert) + const credentials = grpc.credentials.createSsl(lndCert) + const rpc = grpc.load(path.join(__dirname, 'rpc.proto')) + + return new rpc.lnrpc.WalletUnlocker(host, credentials) +} + +export default walletUnlocker diff --git a/app/lnd/methods/walletController.js b/app/lnd/methods/walletController.js index d50a674a..1dd84a74 100644 --- a/app/lnd/methods/walletController.js +++ b/app/lnd/methods/walletController.js @@ -1,3 +1,6 @@ +import bitcore from 'bitcore-lib' +const BufferUtil = bitcore.util.buffer + /** * Returns the sum of all confirmed unspent outputs under control by the wallet * @param {[type]} lnd [description] @@ -92,3 +95,46 @@ export function setAlias(lnd, meta, { new_alias }) { }) }) } + +/** + * Generates a seed for the wallet + */ +export function genSeed(walletUnlocker) { + console.log('walletUnlocker: ', walletUnlocker) + return new Promise((resolve, reject) => { + walletUnlocker.genSeed({}, (err, data) => { + if (err) { reject(err) } + + resolve(data) + }) + }) +} + +/** + * Unlocks a wallet with a password + * @param {[type]} password [description] + */ +export function unlockWallet(walletUnlocker, { wallet_password }) { + return new Promise((resolve, reject) => { + walletUnlocker.unlockWallet({ wallet_password }, (err, data) => { + if (err) { reject(err) } + + resolve(data) + }) + }) +} + +/** + * Unlocks a wallet with a password + * @param {[type]} password [description] + * @param {[type]} cipher_seed_mnemonic [description] + */ +export function initWallet(walletUnlocker, { wallet_password, cipher_seed_mnemonic }) { + return new Promise((resolve, reject) => { + walletUnlocker.initWallet({ wallet_password, cipher_seed_mnemonic }, (err, data) => { + if (err) { reject(err) } + + resolve(data) + }) + }) +} diff --git a/app/lnd/walletUnlockerMethods/index.js b/app/lnd/walletUnlockerMethods/index.js new file mode 100644 index 00000000..07a782a1 --- /dev/null +++ b/app/lnd/walletUnlockerMethods/index.js @@ -0,0 +1,30 @@ +/* eslint no-console: 0 */ // --> OFF + +import * as walletController from '../methods/walletController' + +export default function (walletUnlocker, event, msg, data) { + switch (msg) { + case 'genSeed': + walletController.genSeed(walletUnlocker) + .then(data => { + console.log('data yo: ', data) + event.sender.send('receiveSeed', data) + }) + .catch(error => { + console.log('genSeed error: ', error) + event.sender.send('receiveSeedError', error) + }) + break + case 'unlockWallet': + walletController.unlockWallet(walletUnlocker, data) + .then(data => event.sender.send('walletUnlocked')) + .catch(error => event.sender.send('unlockWalletError')) + break + case 'initWallet': + walletController.initWallet(walletUnlocker, data) + .then(data => event.sender.send('successfullyCreatedWallet')) + .catch(error => console.log('initWallet error: ', error)) + break + default: + } +} diff --git a/app/main.dev.js b/app/main.dev.js index e2f4f46f..f3e17dfc 100644 --- a/app/main.dev.js +++ b/app/main.dev.js @@ -17,7 +17,7 @@ import { spawn } from 'child_process' import { lookup } from 'ps-node' import os from 'os' import MenuBuilder from './menu' -import lnd from './lnd' +import { initLnd, initWalletUnlocker } from './lnd' const plat = os.platform() const homedir = os.homedir() @@ -111,7 +111,7 @@ const sendGrpcConnected = () => { // Create and subscribe the grpc object const startGrpc = () => { - lnd((lndSubscribe, lndMethods) => { + initLnd((lndSubscribe, lndMethods) => { // Subscribe to bi-directional streams lndSubscribe(mainWindow) @@ -124,6 +124,18 @@ const startGrpc = () => { }) } +// Create and subscribe the grpc object +const startWalletUnlocker = () => { + initWalletUnlocker((walletUnlockerMethods) => { + // Listen for all gRPC restful methods + ipcMain.on('walletUnlocker', (event, { msg, data }) => { + walletUnlockerMethods(event, msg, data) + }) + }) + + mainWindow.webContents.send('walletUnlockerStarted') +} + // Send the front end event letting them know LND is synced to the blockchain const sendLndSynced = () => { const sendLndSyncedInterval = setInterval(() => { @@ -157,7 +169,6 @@ const startLnd = (alias, autopilot) => { '--neutrino.addpeer=159.65.48.139:18333', '--neutrino.connect=127.0.0.1:18333', '--debuglevel=debug', - '--noencryptwallet', `${autopilot ? '--autopilot.active' : ''}`, `${alias ? `--alias=${alias}` : ''}` ] @@ -179,12 +190,23 @@ const startLnd = (alias, autopilot) => { if (fs.existsSync(certPath)) { clearInterval(certInterval) - console.log('CERT EXISTS, STARTING GRPC') - startGrpc() + console.log('CERT EXISTS, STARTING WALLET UNLOCKER') + startWalletUnlocker() + + if (mainWindow) { + mainWindow.webContents.send('walletUnlockerStarted') + } + } }, 1000) } + if (line.includes('The wallet has been unlocked')) { + console.log('WALLET OPENED, STARTING LIGHTNING GRPC CONNECTION') + sendLndSyncing() + startGrpc() + } + // Pass current clock height progress to front end for loading state UX if (mainWindow && (line.includes('Caught up to height') || line.includes('Catching up block hashes to height'))) { // const blockHeight = line.slice(line.indexOf('Caught up to height') + 'Caught up to height'.length).trim() @@ -282,10 +304,8 @@ app.on('ready', async () => { } // Start LND - // startLnd() // once the onboarding has finished we wanna let the application we have started syncing and start LND - ipcMain.on('onboardingFinished', (event, { alias, autopilot }) => { - sendLndSyncing() + ipcMain.on('startLnd', (event, { alias, autopilot }) => { startLnd(alias, autopilot) }) } else { diff --git a/app/reducers/ipc.js b/app/reducers/ipc.js index c8569d78..d5d624b7 100644 --- a/app/reducers/ipc.js +++ b/app/reducers/ipc.js @@ -35,7 +35,15 @@ import { import { receiveDescribeNetwork, receiveQueryRoutes, receiveInvoiceAndQueryRoutes } from './network' -import { startOnboarding } from './onboarding' +import { + startOnboarding, + walletUnlockerStarted, + receiveSeed, + receiveSeedError, + successfullyCreatedWallet, + walletUnlocked, + unlockWalletError +} from './onboarding' // Import all receiving IPC event handlers and pass them into createIpc const ipc = createIpc({ @@ -95,7 +103,13 @@ const ipc = createIpc({ receiveQueryRoutes, receiveInvoiceAndQueryRoutes, - startOnboarding + startOnboarding, + walletUnlockerStarted, + receiveSeed, + receiveSeedError, + successfullyCreatedWallet, + walletUnlocked, + unlockWalletError }) export default ipc diff --git a/app/reducers/onboarding.js b/app/reducers/onboarding.js index 905f7b93..e967a21b 100644 --- a/app/reducers/onboarding.js +++ b/app/reducers/onboarding.js @@ -1,17 +1,32 @@ +import { createSelector } from 'reselect' import { ipcRenderer } from 'electron' // ------------------------------------ // Constants // ------------------------------------ export const UPDATE_ALIAS = 'UPDATE_ALIAS' +export const UPDATE_PASSWORD = 'UPDATE_PASSWORD' +export const UPDATE_CREATE_WALLET_PASSWORD = 'UPDATE_CREATE_WALLET_PASSWORD' export const CHANGE_STEP = 'CHANGE_STEP' export const SET_AUTOPILOT = 'SET_AUTOPILOT' +export const FETCH_SEED = 'FETCH_SEED' +export const SET_SEED = 'SET_SEED' +export const SET_HAS_SEED = 'SET_HAS_SEED' + export const ONBOARDING_STARTED = 'ONBOARDING_STARTED' export const ONBOARDING_FINISHED = 'ONBOARDING_FINISHED' +export const STARTING_LND = 'STARTING_LND' +export const LND_STARTED = 'LND_STARTED' + +export const CREATING_NEW_WALLET = 'CREATING_NEW_WALLET' + +export const UNLOCKING_WALLET = 'UNLOCKING_WALLET' +export const WALLET_UNLOCKED = 'WALLET_UNLOCKED' +export const SET_UNLOCK_WALLET_ERROR = 'SET_UNLOCK_WALLET_ERROR' // ------------------------------------ // Actions // ------------------------------------ @@ -22,6 +37,20 @@ export function updateAlias(alias) { } } +export function updatePassword(password) { + return { + type: UPDATE_PASSWORD, + password + } +} + +export function updateCreateWalletPassword(createWalletPassword) { + return { + type: UPDATE_CREATE_WALLET_PASSWORD, + createWalletPassword + } +} + export function setAutopilot(autopilot) { return { type: SET_AUTOPILOT, @@ -36,31 +65,98 @@ export function changeStep(step) { } } -export function submit(alias, autopilot) { - // alert the app we're done onboarding and it's cool to start LND - // send the alias they set along with whether they want autopilot on or not - ipcRenderer.send('onboardingFinished', { alias, autopilot }) +export function startLnd(alias, autopilot) { + // once the user submits the data needed to start LND we will alert the app that it should start LND + ipcRenderer.send('startLnd', { alias, autopilot }) return { - type: ONBOARDING_FINISHED + type: STARTING_LND } } +export function submitNewWallet(wallet_password, cipher_seed_mnemonic) { + // once the user submits the data needed to start LND we will alert the app that it should start LND + ipcRenderer.send('walletUnlocker', { msg: 'initWallet', data: { wallet_password, cipher_seed_mnemonic } }) + dispatch({ type: CREATING_NEW_WALLET }) +} + export const startOnboarding = () => (dispatch) => { dispatch({ type: ONBOARDING_STARTED }) } +// Listener from after the LND walletUnlocker has started +export const walletUnlockerStarted = () => (dispatch) => { + dispatch({ type: LND_STARTED }) + dispatch({ type: CHANGE_STEP, step: 3 }) + ipcRenderer.send('walletUnlocker', { msg: 'genSeed' }) +} + +export const createWallet = () => (dispatch) => { + ipcRenderer.send('walletUnlocker', { msg: 'genSeed' }) + dispatch({ type: CHANGE_STEP, step: 4 }) +} + +export const successfullyCreatedWallet = (event) => (dispatch) => dispatch({ type: ONBOARDING_FINISHED }) + +// Listener for when LND creates and sends us a generated seed +export const receiveSeed = (event, { cipher_seed_mnemonic }) => (dispatch) => dispatch({ type: SET_SEED, seed: cipher_seed_mnemonic }) + +// Listener for when LND throws an error on seed creation +export const receiveSeedError = (event, error) => (dispatch) => dispatch({ type: SET_HAS_SEED, hasSeed: true }) + +// Unlock an existing wallet with a wallet password +export const unlockWallet = (wallet_password) => (dispatch) => { + ipcRenderer.send('walletUnlocker', { msg: 'unlockWallet', data: { wallet_password } }) + dispatch({ type: UNLOCKING_WALLET }) +} + +export const walletUnlocked = () => (dispatch) => { + dispatch({ type: WALLET_UNLOCKED }) + dispatch({ type: ONBOARDING_FINISHED }) +} + +export const unlockWalletError = () => (dispatch) => { + dispatch({ type: SET_UNLOCK_WALLET_ERROR }) +} + // ------------------------------------ // Action Handlers // ------------------------------------ const ACTION_HANDLERS = { [UPDATE_ALIAS]: (state, { alias }) => ({ ...state, alias }), + [UPDATE_PASSWORD]: (state, { password }) => ({ ...state, password }), + [UPDATE_CREATE_WALLET_PASSWORD]: (state, { createWalletPassword }) => ({ ...state, createWalletPassword }), + [SET_AUTOPILOT]: (state, { autopilot }) => ({ ...state, autopilot }), + + [SET_HAS_SEED]: (state, { hasSeed }) => ({ ...state, hasSeed }), + [SET_SEED]: (state, { seed }) => ({ ...state, seed, fetchingSeed: false }), + [CHANGE_STEP]: (state, { step }) => ({ ...state, step }), + [ONBOARDING_STARTED]: state => ({ ...state, onboarded: false }), - [ONBOARDING_FINISHED]: state => ({ ...state, onboarded: true }) + [ONBOARDING_FINISHED]: state => ({ ...state, onboarded: true }), + + [STARTING_LND]: state => ({ ...state, startingLnd: true }), + [LND_STARTED]: state => ({ ...state, startingLnd: false }), + + [CREATING_NEW_WALLET]: state => ({ ...state, creatingNewWallet: true }), + + [UNLOCKING_WALLET]: state => ({ ...state, unlockingWallet: true }), + [WALLET_UNLOCKED]: state => ({ ...state, unlockingWallet: false, unlockWalletError: { isError: false, message: '' } }), + [SET_UNLOCK_WALLET_ERROR]: state => ({ ...state, unlockingWallet: false, unlockWalletError: { isError: true, message: 'Incorrect password' } }) } +const onboardingSelectors = {} +const passwordSelector = state => state.onboarding.password + +onboardingSelectors.passwordIsValid = createSelector( + passwordSelector, + password => password.length >= 8 +) + +export { onboardingSelectors } + // ------------------------------------ // Reducer // ------------------------------------ @@ -68,6 +164,22 @@ const initialState = { onboarded: true, step: 1, alias: '', + password: '', + startingLnd: false, + + fetchingSeed: false, + hasSeed: false, + seed: [], + + createWalletPassword: '', + creatingNewWallet: false, + + unlockingWallet: false, + unlockWalletError: { + isError: false, + message: '' + }, + autopilot: null } diff --git a/app/rpc.proto b/app/rpc.proto index 0d178563..541a2d94 100644 --- a/app/rpc.proto +++ b/app/rpc.proto @@ -28,13 +28,39 @@ package lnrpc; // The WalletUnlocker service is used to set up a wallet password for // lnd at first startup, and unlock a previously set up wallet. service WalletUnlocker { - /** lncli: `create` - CreateWallet is used at lnd startup to set the encryption password for - the wallet database. + /** + GenSeed is the first method that should be used to instantiate a new lnd + instance. This method allows a caller to generate a new aezeed cipher seed + given an optional passphrase. If provided, the passphrase will be necessary + to decrypt the cipherseed to expose the internal wallet seed. + + Once the cipherseed is obtained and verified by the user, the InitWallet + method should be used to commit the newly generated seed, and create the + wallet. */ - rpc CreateWallet(CreateWalletRequest) returns (CreateWalletResponse) { + rpc GenSeed(GenSeedRequest) returns (GenSeedResponse) { option (google.api.http) = { - post: "/v1/createwallet" + get: "/v1/genseed" + }; + } + + /** lncli: `init` + InitWallet is used when lnd is starting up for the first time to fully + initialize the daemon and its internal wallet. At the very least a wallet + password must be provided. This will be used to encrypt sensitive material + on disk. + + In the case of a recovery scenario, the user can also specify their aezeed + mnemonic and passphrase. If set, then the daemon will use this prior state + to initialize its internal wallet. + + Alternatively, this can be used along with the GenSeed RPC to obtain a + seed, then present it to the user. Once it has been verified by the user, + the seed can be fed into this RPC in order to commit the new wallet. + */ + rpc InitWallet(InitWalletRequest) returns (InitWalletResponse) { + option (google.api.http) = { + post: "/v1/initwallet" body: "*" }; } @@ -51,20 +77,74 @@ service WalletUnlocker { } } -message CreateWalletRequest { - bytes password = 1; +message GenSeedRequest { + /** + aezeed_passphrase is an optional user provided passphrase that will be used + to encrypt the generated aezeed cipher seed. + */ + bytes aezeed_passphrase = 1; + + /** + seed_entropy is an optional 16-bytes generated via CSPRNG. If not + specified, then a fresh set of randomness will be used to create the seed. + */ + bytes seed_entropy = 2; +} +message GenSeedResponse { + /** + cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + cipher seed obtained by the user. This field is optional, as if not + provided, then the daemon will generate a new cipher seed for the user. + Otherwise, then the daemon will attempt to recover the wallet state linked + to this cipher seed. + */ + repeated string cipher_seed_mnemonic = 1; + + /** + enciphered_seed are the raw aezeed cipher seed bytes. This is the raw + cipher text before run through our mnemonic encoding scheme. + */ + bytes enciphered_seed = 2; } -message CreateWalletResponse {} +message InitWalletRequest { + /** + wallet_password is the passphrase that should be used to encrypt the + wallet. This MUST be at least 8 chars in length. After creation, this + password is required to unlock the daemon. + */ + bytes wallet_password = 1; + + /** + cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + cipher seed obtained by the user. This may have been generated by the + GenSeed method, or be an existing seed. + */ + repeated string cipher_seed_mnemonic = 2; + + /** + aezeed_passphrase is an optional user provided passphrase that will be used + to encrypt the generated aezeed cipher seed. + */ + bytes aezeed_passphrase = 3; +} +message InitWalletResponse { +} message UnlockWalletRequest { - bytes password = 1; + /** + wallet_password should be the current valid passphrase for the daemon. This + will be required to decrypt on-disk material that the daemon requires to + function properly. + */ + bytes wallet_password = 1; } message UnlockWalletResponse {} service Lightning { /** lncli: `walletbalance` - WalletBalance returns total unspent outputs(confirmed and unconfirmed), all confirmed unspent outputs and all unconfirmed unspent outputs under control + WalletBalance returns total unspent outputs(confirmed and unconfirmed), all + confirmed unspent outputs and all unconfirmed unspent outputs under control by the wallet. This method can be modified by having the request specify only witness outputs should be factored into the final output sum. */ @@ -251,7 +331,7 @@ service Lightning { */ rpc CloseChannel (CloseChannelRequest) returns (stream CloseStatusUpdate) { option (google.api.http) = { - delete: "/v1/channels/{channel_point.funding_txid}/{channel_point.output_index}" + delete: "/v1/channels/{channel_point.funding_txid_str}/{channel_point.output_index}" }; } @@ -294,18 +374,18 @@ service Lightning { */ rpc ListInvoices (ListInvoiceRequest) returns (ListInvoiceResponse) { option (google.api.http) = { - get: "/v1/invoices/{pending_only}" + get: "/v1/invoices" }; } /** lncli: `lookupinvoice` - LookupInvoice attemps to look up an invoice according to its payment hash. + LookupInvoice attempts to look up an invoice according to its payment hash. The passed payment hash *must* be exactly 32 bytes, if not, an error is returned. */ rpc LookupInvoice (PaymentHash) returns (Invoice) { option (google.api.http) = { - get: "/v1/invoices/{r_hash_str}" + get: "/v1/invoice/{r_hash_str}" }; } @@ -389,7 +469,7 @@ service Lightning { route to a target destination capable of carrying a specific amount of satoshis. The retuned route contains the full details required to craft and send an HTLC, also including the necessary information that should be - present within the Sphinx packet encapsualted within the HTLC. + present within the Sphinx packet encapsulated within the HTLC. */ rpc QueryRoutes(QueryRoutesRequest) returns (QueryRoutesResponse) { option (google.api.http) = { @@ -447,7 +527,7 @@ service Lightning { */ rpc UpdateChannelPolicy(PolicyUpdateRequest) returns (PolicyUpdateResponse) { option (google.api.http) = { - post: "/v1/fees" + post: "/v1/chanpolicy" body: "*" }; } @@ -518,16 +598,16 @@ message SendResponse { } message ChannelPoint { - // TODO(roasbeef): make str vs bytes into a oneof - - /// Txid of the funding transaction - bytes funding_txid = 1 [ json_name = "funding_txid" ]; + oneof funding_txid { + /// Txid of the funding transaction + bytes funding_txid_bytes = 1 [json_name = "funding_txid_bytes"]; - /// Hex-encoded string representing the funding transaction - string funding_txid_str = 2 [ json_name = "funding_txid_str" ]; + /// Hex-encoded string representing the funding transaction + string funding_txid_str = 2 [json_name = "funding_txid_str"]; + } /// The index of the output of the funding transaction - uint32 output_index = 3 [ json_name = "output_index" ]; + uint32 output_index = 3 [json_name = "output_index"]; } message LightningAddress { @@ -610,7 +690,7 @@ message VerifyMessageRequest { /// The message over which the signature is to be verified bytes msg = 1 [ json_name = "msg" ]; - /// The signature to be verifed over the given message + /// The signature to be verified over the given message string signature = 2 [ json_name = "signature" ]; } message VerifyMessageResponse { @@ -630,8 +710,6 @@ message ConnectPeerRequest { bool perm = 2; } message ConnectPeerResponse { - /// The id of the newly connected peer - int32 peer_id = 1 [json_name = "peer_id"]; } message DisconnectPeerRequest { @@ -738,9 +816,6 @@ message Peer { /// The identity pubkey of the peer string pub_key = 1 [json_name = "pub_key"]; - /// The peer's id from the local point of view - int32 peer_id = 2 [json_name = "peer_id"]; - /// Network address of the peer; eg `127.0.0.1:10011` string address = 3 [json_name = "address"]; @@ -806,6 +881,9 @@ message GetInfoResponse { /// The URIs of the current node. repeated string uris = 12 [json_name = "uris"]; + + /// Timestamp of the block best known to the wallet + int64 best_header_timestamp = 13 [ json_name = "best_header_timestamp" ]; } message ConfirmationUpdate { @@ -840,8 +918,9 @@ message CloseChannelRequest { int32 target_conf = 3; /// A manual fee rate set in sat/byte that should be used when crafting the closure transaction. - int64 sat_per_byte = 5; + int64 sat_per_byte = 4; } + message CloseStatusUpdate { oneof update { PendingUpdate close_pending = 1 [json_name = "close_pending"]; @@ -857,13 +936,10 @@ message PendingUpdate { message OpenChannelRequest { - /// The peer_id of the node to open a channel with - int32 target_peer_id = 1 [json_name = "target_peer_id"]; - /// The pubkey of the node to open a channel with bytes node_pubkey = 2 [json_name = "node_pubkey"]; - /// The hex encorded pubkey of the node to open a channel with + /// The hex encoded pubkey of the node to open a channel with string node_pubkey_string = 3 [json_name = "node_pubkey_string"]; /// The number of satoshis the wallet should commit to the channel @@ -1031,6 +1107,9 @@ message QueryRoutesRequest { /// The amount to send expressed in satoshis int64 amt = 2; + + /// The max number of routes to return. + int32 num_routes = 3; } message QueryRoutesResponse { repeated Route routes = 1 [ json_name = "routes"]; @@ -1337,6 +1416,7 @@ message InvoiceSubscription { message Payment { /// The payment hash string payment_hash = 1 [json_name = "payment_hash"]; + /// The value of the payment in satoshis int64 value = 2 [json_name = "value"]; From 96f7a3e20bbefa50a27fc335fb0a43ffffe25aa9 Mon Sep 17 00:00:00 2001 From: Jack Mallers Date: Tue, 20 Mar 2018 20:08:11 -0500 Subject: [PATCH 02/10] feature(seed): style copy/enter seed pages --- app/components/Onboarding/InitWallet.js | 21 +--- .../Onboarding/NewAezeedPassword.js | 49 ++++++++++ .../Onboarding/NewAezeedPassword.scss | 24 +++++ app/components/Onboarding/NewWalletSeed.js | 22 +++-- app/components/Onboarding/NewWalletSeed.scss | 44 ++++++++- app/components/Onboarding/Onboarding.js | 66 ++++++++++--- app/components/Onboarding/ReEnterSeed.js | 41 ++++++++ app/components/Onboarding/ReEnterSeed.scss | 61 ++++++++++++ app/components/Onboarding/Signup.js | 36 +++++-- app/components/Onboarding/Signup.scss | 52 ++++++++++ app/containers/Root.js | 56 +++++++++-- app/lnd/methods/channelController.js | 9 +- app/lnd/methods/walletController.js | 11 ++- app/reducers/onboarding.js | 98 +++++++++++++++++-- 14 files changed, 515 insertions(+), 75 deletions(-) create mode 100644 app/components/Onboarding/NewAezeedPassword.js create mode 100644 app/components/Onboarding/NewAezeedPassword.scss create mode 100644 app/components/Onboarding/ReEnterSeed.js create mode 100644 app/components/Onboarding/ReEnterSeed.scss diff --git a/app/components/Onboarding/InitWallet.js b/app/components/Onboarding/InitWallet.js index 5069d0f6..ed3b80ce 100644 --- a/app/components/Onboarding/InitWallet.js +++ b/app/components/Onboarding/InitWallet.js @@ -4,28 +4,13 @@ import Login from './Login' import Signup from './Signup' import styles from './InitWallet.scss' -const InitWallet = ({ - password, - passwordIsValid, - hasSeed, - updatePassword, - createWallet, - unlockWallet, - unlockingWallet, - unlockWalletError -}) => ( +const InitWallet = ({ hasSeed, loginProps, signupProps }) => (
{ hasSeed ? - + : - + }
) diff --git a/app/components/Onboarding/NewAezeedPassword.js b/app/components/Onboarding/NewAezeedPassword.js new file mode 100644 index 00000000..7c796173 --- /dev/null +++ b/app/components/Onboarding/NewAezeedPassword.js @@ -0,0 +1,49 @@ +import React from 'react' +import PropTypes from 'prop-types' +import Isvg from 'react-inlinesvg' +import eye from 'icons/eye.svg' +import styles from './NewAezeedPassword.scss' + +class NewAezeedPassword extends React.Component { + constructor(props) { + super(props) + this.state = { confirmPassword: '' } + } + + render() { + const { aezeedPassword, updateAezeedPassword } = this.props + const { confirmPassword } = this.state + + return ( +
+
+ updateAezeedPassword(event.target.value)} + /> +
+ +
+ this.setState({ confirmPassword: event.target.value })} + /> +
+
+ ) + } +} + +NewAezeedPassword.propTypes = { + aezeedPassword: PropTypes.string.isRequired, + updateAezeedPassword: PropTypes.func.isRequired +} + +export default NewAezeedPassword + diff --git a/app/components/Onboarding/NewAezeedPassword.scss b/app/components/Onboarding/NewAezeedPassword.scss new file mode 100644 index 00000000..1fd5fd6f --- /dev/null +++ b/app/components/Onboarding/NewAezeedPassword.scss @@ -0,0 +1,24 @@ +@import '../../variables.scss'; + +.input:nth-child(2) { + margin-top: 40px; +} + +.password { + background: transparent; + outline: none; + border: 0; + color: $gold; + -webkit-text-fill-color: $white; + font-size: 22px; + transition: all 0.25s; + + &.error { + border-bottom: 1px solid $red; + } +} + +.password::-webkit-input-placeholder { + text-shadow: none; + -webkit-text-fill-color: initial; +} diff --git a/app/components/Onboarding/NewWalletSeed.js b/app/components/Onboarding/NewWalletSeed.js index 82dc5b8a..b04207f7 100644 --- a/app/components/Onboarding/NewWalletSeed.js +++ b/app/components/Onboarding/NewWalletSeed.js @@ -4,12 +4,22 @@ import styles from './NewWalletSeed.scss' const NewWalletSeed = ({ seed }) => (
- { - seed.length > 0 ? - seed.join(', ') - : - 'loading' - } +
    + { + seed.map((word, index) => { + return ( +
  • +
    + +
    +
    + {word} +
    +
  • + ) + }) + } +
) diff --git a/app/components/Onboarding/NewWalletSeed.scss b/app/components/Onboarding/NewWalletSeed.scss index ed6e62f1..cc5b95b8 100644 --- a/app/components/Onboarding/NewWalletSeed.scss +++ b/app/components/Onboarding/NewWalletSeed.scss @@ -1,11 +1,49 @@ @import '../../variables.scss'; .container { - background: darken(#242833, 10%); - padding: 20px 40px; font-size: 14px; - line-height: 50px; color: $white; font-family: 'Roboto'; letter-spacing: 1.5px; + + li { + display: inline-block; + margin: 5px 0; + width: 25%; + font-family: 'Courier'; + + section { + display: inline-block; + vertical-align: middle; + color: $white; + + &:nth-child(1) { + width: 15%; + text-align: center; + opacity: 0.5; + } + + &:nth-child(2) { + width: calc(85% - 10px); + margin: 0 5px; + } + } + } + + .word { + margin: 0 3px; + background-color: #1c1e26; + outline: 0; + border: none; + padding: 10px; + color: $white; + + &.valid { + color: $green; + } + + &.invalid { + color: $red; + } + } } \ No newline at end of file diff --git a/app/components/Onboarding/Onboarding.js b/app/components/Onboarding/Onboarding.js index 20f08dde..1bb9e54b 100644 --- a/app/components/Onboarding/Onboarding.js +++ b/app/components/Onboarding/Onboarding.js @@ -7,8 +7,12 @@ import FormContainer from './FormContainer' import Alias from './Alias' import Autopilot from './Autopilot' import InitWallet from './InitWallet' +import Login from './Login' +import Signup from './Signup' import NewWalletSeed from './NewWalletSeed' +import ReEnterSeed from './ReEnterSeed' import NewWalletPassword from './NewWalletPassword' +import NewAezeedPassword from './NewAezeedPassword' import styles from './Onboarding.scss' const Onboarding = ({ @@ -18,7 +22,8 @@ const Onboarding = ({ autopilot, startingLnd, createWalletPassword, - seed + seed, + aezeedPassword }, changeStep, startLnd, @@ -27,7 +32,9 @@ const Onboarding = ({ initWalletProps, autopilotProps, newWalletSeedProps, - newWalletPasswordProps + newWalletPasswordProps, + newAezeedPasswordProps, + reEnterSeedProps }) => { const renderStep = () => { switch (step) { @@ -56,34 +63,67 @@ const Onboarding = ({ case 3: return ( changeStep(2)} + back={null} next={null} > - + ) case 4: return ( changeStep(3)} + title='Welcome!' + description='Looks like you are new here. Set a password to encrypt your wallet. This password will be needed to unlock Zap in the future' // eslint-disable-line + back={null} next={() => changeStep(5)} > - + ) case 5: return ( changeStep(4)} - next={() => submitNewWallet(createWalletPassword, seed)} + next={() => initWalletProps.signupProps.signupForm.create ? changeStep(6) : console.log('import')} > - + + + ) + case 6: + return ( + changeStep(5)} + next={() => changeStep(7)} + > + + + ) + case 7: + return ( + changeStep(6)} + next={() => changeStep(8)} + > + + + ) + case 8: + return ( + changeStep(6)} + next={() => submitNewWallet(createWalletPassword, seed, aezeedPassword)} + > + ) default: diff --git a/app/components/Onboarding/ReEnterSeed.js b/app/components/Onboarding/ReEnterSeed.js new file mode 100644 index 00000000..aab83657 --- /dev/null +++ b/app/components/Onboarding/ReEnterSeed.js @@ -0,0 +1,41 @@ +import React from 'react' +import PropTypes from 'prop-types' +import styles from './ReEnterSeed.scss' + +const ReEnterSeed = ({ seed, seedInput, updateSeedInput, reEnterSeedChecker, renderEnterSeedHtml }) => { + return ( +
+
    + { + seed.map((word, index) => { + return ( +
  • +
    + +
    +
    + updateSeedInput({ word: event.target.value, index })} + className={`${styles.word} ${seedInput[index] && word === seedInput[index].word ? styles.valid : styles.invalid}`} + /> +
    +
  • + ) + }) + } +
+
+ ) +} + +ReEnterSeed.propTypes = { + seedInput: PropTypes.array.isRequired, + updateSeedInput: PropTypes.func.isRequired, + reEnterSeedChecker: PropTypes.array.isRequired +} + +export default ReEnterSeed diff --git a/app/components/Onboarding/ReEnterSeed.scss b/app/components/Onboarding/ReEnterSeed.scss new file mode 100644 index 00000000..4c794ac5 --- /dev/null +++ b/app/components/Onboarding/ReEnterSeed.scss @@ -0,0 +1,61 @@ +@import '../../variables.scss'; + +.seedContainer { + position: relative; + display: inline-block; + font-size: 12px; + + li { + display: inline-block; + margin: 5px 0; + width: 25%; + + section { + display: inline-block; + vertical-align: middle; + color: $white; + + &:nth-child(1) { + width: 15%; + text-align: center; + opacity: 0.5; + } + + &:nth-child(2) { + width: calc(85% - 10px); + margin: 0 5px; + } + } + } +} + + + +.word { + margin: 0 3px; + background-color: #1c1e26; + outline: 0; + border: none; + padding: 5px 10px; + color: $white; + font-family: courier; + font-family: 'Courier'; + + &.valid { + color: $green; + } + + &.invalid { + color: $red; + } +} + +.word::-webkit-input-placeholder { + text-shadow: none; + -webkit-text-fill-color: initial; +} + +.contentEditable { + width: 100px; + background: red; +} diff --git a/app/components/Onboarding/Signup.js b/app/components/Onboarding/Signup.js index 8ef1f5dd..3e28e6fd 100644 --- a/app/components/Onboarding/Signup.js +++ b/app/components/Onboarding/Signup.js @@ -1,16 +1,36 @@ import React from 'react' import PropTypes from 'prop-types' +import { FaCircle, FaCircleThin } from 'react-icons/lib/fa' import styles from './Signup.scss' -const Signup = ({ - password, - passwordIsValid, - hasSeed, - updatePassword, - createWallet -}) => ( +const Signup = ({ signupForm, setSignupCreate, setSignupImport }) => (
- signup yo +
+
+ { + signupForm.create ? + + : + + } + + Create new wallet + +
+
+
+
+ { + signupForm.import ? + + : + + } + + Import existing wallet + +
+
) diff --git a/app/components/Onboarding/Signup.scss b/app/components/Onboarding/Signup.scss index e69de29b..18d543b8 100644 --- a/app/components/Onboarding/Signup.scss +++ b/app/components/Onboarding/Signup.scss @@ -0,0 +1,52 @@ +@import '../../variables.scss'; + +.container { + color: $white; + + section { + margin: 20px 0; + + &.enable { + &.active { + div { + color: $green; + border-color: $green; + } + } + + div:hover { + color: $green; + border-color: $green; + } + } + + &.disable { + &.active { + div { + color: $red; + border-color: $red; + } + } + + div:hover { + color: $red; + border-color: $red; + } + } + + div { + width: 200px; + display: inline-block; + padding: 20px; + border: 1px solid $white; + border-radius: 5px; + font-weight: 200; + cursor: pointer; + transition: all 0.25s; + } + + .label { + margin-left: 15px; + } + } +} \ No newline at end of file diff --git a/app/containers/Root.js b/app/containers/Root.js index b6d2aad3..cf6530a6 100644 --- a/app/containers/Root.js +++ b/app/containers/Root.js @@ -15,9 +15,13 @@ import { startLnd, createWallet, updateCreateWalletPassword, + updateAezeedPassword, submitNewWallet, onboardingSelectors, - unlockWallet + unlockWallet, + setSignupCreate, + setSignupImport, + updateSeedInput } from '../reducers/onboarding' import { fetchBlockHeight, lndSelectors } from '../reducers/lnd' import Routes from '../routes' @@ -26,12 +30,16 @@ const mapDispatchToProps = { updateAlias, updatePassword, updateCreateWalletPassword, + updateAezeedPassword, setAutopilot, changeStep, startLnd, createWallet, submitNewWallet, unlockWallet, + setSignupCreate, + setSignupImport, + updateSeedInput, fetchBlockHeight } @@ -41,7 +49,9 @@ const mapStateToProps = state => ({ onboarding: state.onboarding, syncPercentage: lndSelectors.syncPercentage(state), - passwordIsValid: onboardingSelectors.passwordIsValid(state) + passwordIsValid: onboardingSelectors.passwordIsValid(state), + reEnterSeedChecker: onboardingSelectors.reEnterSeedChecker(state), + renderEnterSeedHtml: onboardingSelectors.renderEnterSeedHtml(state) }) const mergeProps = (stateProps, dispatchProps, ownProps) => { @@ -61,15 +71,26 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { } const initWalletProps = { - password: stateProps.onboarding.password, - passwordIsValid: stateProps.passwordIsValid, hasSeed: stateProps.onboarding.hasSeed, - unlockingWallet: stateProps.onboarding.unlockingWallet, - unlockWalletError: stateProps.onboarding.unlockWalletError, - updatePassword: dispatchProps.updatePassword, - createWallet: dispatchProps.createWallet, - unlockWallet: dispatchProps.unlockWallet + loginProps: { + password: stateProps.onboarding.password, + passwordIsValid: stateProps.passwordIsValid, + hasSeed: stateProps.onboarding.hasSeed, + unlockingWallet: stateProps.onboarding.unlockingWallet, + unlockWalletError: stateProps.onboarding.unlockWalletError, + + updatePassword: dispatchProps.updatePassword, + createWallet: dispatchProps.createWallet, + unlockWallet: dispatchProps.unlockWallet + }, + + signupProps: { + signupForm: stateProps.onboarding.signupForm, + + setSignupCreate: dispatchProps.setSignupCreate, + setSignupImport: dispatchProps.setSignupImport + } } const newWalletSeedProps = { @@ -81,6 +102,19 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { updateCreateWalletPassword: dispatchProps.updateCreateWalletPassword } + const newAezeedPasswordProps = { + aezeedPassword: stateProps.onboarding.aezeedPassword, + updateAezeedPassword: dispatchProps.updateAezeedPassword + } + + const reEnterSeedProps = { + seed: stateProps.onboarding.seed, + seedInput: stateProps.onboarding.seedInput, + reEnterSeedChecker: stateProps.reEnterSeedChecker, + renderEnterSeedHtml: stateProps.renderEnterSeedHtml, + updateSeedInput: dispatchProps.updateSeedInput + } + const onboardingProps = { onboarding: stateProps.onboarding, changeStep: dispatchProps.changeStep, @@ -90,7 +124,9 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { autopilotProps, initWalletProps, newWalletSeedProps, - newWalletPasswordProps + newWalletPasswordProps, + newAezeedPasswordProps, + reEnterSeedProps } return { diff --git a/app/lnd/methods/channelController.js b/app/lnd/methods/channelController.js index 5d841e97..9cb2c48f 100644 --- a/app/lnd/methods/channelController.js +++ b/app/lnd/methods/channelController.js @@ -1,10 +1,7 @@ -import bitcore from 'bitcore-lib' import find from 'lodash/find' import { listPeers, connectPeer } from './peersController' import pushopenchannel from '../push/openchannel' -const BufferUtil = bitcore.util.buffer - function ensurePeerConnected(lnd, meta, pubkey, host) { return listPeers(lnd, meta) .then(({ peers }) => { @@ -29,7 +26,7 @@ export function connectAndOpen(lnd, meta, event, payload) { return ensurePeerConnected(lnd, meta, pubkey, host) .then(() => { const call = lnd.openChannel({ - node_pubkey: BufferUtil.hexToBuffer(pubkey), + node_pubkey: Buffer.from(pubkey, 'hex'), local_funding_amount: Number(localamt) }, meta) @@ -54,7 +51,7 @@ export function connectAndOpen(lnd, meta, event, payload) { export function openChannel(lnd, meta, event, payload) { const { pubkey, localamt, pushamt } = payload const res = { - node_pubkey: BufferUtil.hexToBuffer(pubkey), + node_pubkey: Buffer.from(pubkey, 'hex'), local_funding_amount: Number(localamt), push_sat: Number(pushamt) } @@ -110,7 +107,7 @@ export function closeChannel(lnd, meta, event, payload) { const tx = payload.channel_point.funding_txid.match(/.{2}/g).reverse().join('') const res = { channel_point: { - funding_txid: BufferUtil.hexToBuffer(tx), + funding_txid: Buffer.from(tx, 'hex'), output_index: Number(payload.channel_point.output_index) }, force diff --git a/app/lnd/methods/walletController.js b/app/lnd/methods/walletController.js index 1dd84a74..934f0fe9 100644 --- a/app/lnd/methods/walletController.js +++ b/app/lnd/methods/walletController.js @@ -1,6 +1,3 @@ -import bitcore from 'bitcore-lib' -const BufferUtil = bitcore.util.buffer - /** * Returns the sum of all confirmed unspent outputs under control by the wallet * @param {[type]} lnd [description] @@ -129,9 +126,13 @@ export function unlockWallet(walletUnlocker, { wallet_password }) { * @param {[type]} password [description] * @param {[type]} cipher_seed_mnemonic [description] */ -export function initWallet(walletUnlocker, { wallet_password, cipher_seed_mnemonic }) { +export function initWallet(walletUnlocker, { wallet_password, cipher_seed_mnemonic, aezeed_passphrase }) { return new Promise((resolve, reject) => { - walletUnlocker.initWallet({ wallet_password, cipher_seed_mnemonic }, (err, data) => { + walletUnlocker.initWallet({ + wallet_password, + cipher_seed_mnemonic, + aezeed_passphrase: Buffer.from(aezeed_passphrase, 'hex') + }, (err, data) => { if (err) { reject(err) } resolve(data) diff --git a/app/reducers/onboarding.js b/app/reducers/onboarding.js index e967a21b..c628bf90 100644 --- a/app/reducers/onboarding.js +++ b/app/reducers/onboarding.js @@ -7,6 +7,8 @@ import { ipcRenderer } from 'electron' export const UPDATE_ALIAS = 'UPDATE_ALIAS' export const UPDATE_PASSWORD = 'UPDATE_PASSWORD' export const UPDATE_CREATE_WALLET_PASSWORD = 'UPDATE_CREATE_WALLET_PASSWORD' +export const UPDATE_AEZEED_PASSWORD = 'UPDATE_AEZEED_PASSWORD' +export const UPDATE_SEED_INPUT = 'UPDATE_SEED_INPUT' export const CHANGE_STEP = 'CHANGE_STEP' @@ -27,6 +29,9 @@ export const CREATING_NEW_WALLET = 'CREATING_NEW_WALLET' export const UNLOCKING_WALLET = 'UNLOCKING_WALLET' export const WALLET_UNLOCKED = 'WALLET_UNLOCKED' export const SET_UNLOCK_WALLET_ERROR = 'SET_UNLOCK_WALLET_ERROR' + +export const SET_SIGNUP_CREATE = 'SET_SIGNUP_CREATE' +export const SET_SIGNUP_IMPORT = 'SET_SIGNUP_IMPORT' // ------------------------------------ // Actions // ------------------------------------ @@ -51,6 +56,20 @@ export function updateCreateWalletPassword(createWalletPassword) { } } +export function updateAezeedPassword(aezeedPassword) { + return { + type: UPDATE_AEZEED_PASSWORD, + aezeedPassword + } +} + +export function updateSeedInput(inputSeedObj) { + return { + type: UPDATE_SEED_INPUT, + inputSeedObj + } +} + export function setAutopilot(autopilot) { return { type: SET_AUTOPILOT, @@ -58,6 +77,18 @@ export function setAutopilot(autopilot) { } } +export function setSignupCreate() { + return { + type: SET_SIGNUP_CREATE + } +} + +export function setSignupImport() { + return { + type: SET_SIGNUP_IMPORT + } +} + export function changeStep(step) { return { type: CHANGE_STEP, @@ -74,9 +105,9 @@ export function startLnd(alias, autopilot) { } } -export function submitNewWallet(wallet_password, cipher_seed_mnemonic) { +export const submitNewWallet = (wallet_password, cipher_seed_mnemonic, aezeed_passphrase) => (dispatch) => { // once the user submits the data needed to start LND we will alert the app that it should start LND - ipcRenderer.send('walletUnlocker', { msg: 'initWallet', data: { wallet_password, cipher_seed_mnemonic } }) + ipcRenderer.send('walletUnlocker', { msg: 'initWallet', data: { wallet_password, cipher_seed_mnemonic, aezeed_passphrase } }) dispatch({ type: CREATING_NEW_WALLET }) } @@ -87,7 +118,6 @@ export const startOnboarding = () => (dispatch) => { // Listener from after the LND walletUnlocker has started export const walletUnlockerStarted = () => (dispatch) => { dispatch({ type: LND_STARTED }) - dispatch({ type: CHANGE_STEP, step: 3 }) ipcRenderer.send('walletUnlocker', { msg: 'genSeed' }) } @@ -99,10 +129,18 @@ export const createWallet = () => (dispatch) => { export const successfullyCreatedWallet = (event) => (dispatch) => dispatch({ type: ONBOARDING_FINISHED }) // Listener for when LND creates and sends us a generated seed -export const receiveSeed = (event, { cipher_seed_mnemonic }) => (dispatch) => dispatch({ type: SET_SEED, seed: cipher_seed_mnemonic }) +export const receiveSeed = (event, { cipher_seed_mnemonic }) => (dispatch) => { + dispatch({ type: SET_SEED, seed: cipher_seed_mnemonic }) + // there was no seed and we just generated a new one, send user to the login component + dispatch({ type: CHANGE_STEP, step: 4 }) +} // Listener for when LND throws an error on seed creation -export const receiveSeedError = (event, error) => (dispatch) => dispatch({ type: SET_HAS_SEED, hasSeed: true }) +export const receiveSeedError = (event, error) => (dispatch) => { + dispatch({ type: SET_HAS_SEED, hasSeed: true }) + // there is already a seed, send user to the login component + dispatch({ type: CHANGE_STEP, step: 3 }) +} // Unlock an existing wallet with a wallet password export const unlockWallet = (wallet_password) => (dispatch) => { @@ -126,6 +164,13 @@ const ACTION_HANDLERS = { [UPDATE_ALIAS]: (state, { alias }) => ({ ...state, alias }), [UPDATE_PASSWORD]: (state, { password }) => ({ ...state, password }), [UPDATE_CREATE_WALLET_PASSWORD]: (state, { createWalletPassword }) => ({ ...state, createWalletPassword }), + [UPDATE_AEZEED_PASSWORD]: (state, { aezeedPassword }) => ({ ...state, aezeedPassword }), + [UPDATE_SEED_INPUT]: (state, { inputSeedObj }) => { + return { + ...state, + seedInput: Object.assign([], state.seedInput, { [inputSeedObj['index']]: inputSeedObj }) + } + }, [SET_AUTOPILOT]: (state, { autopilot }) => ({ ...state, autopilot }), @@ -144,17 +189,46 @@ const ACTION_HANDLERS = { [UNLOCKING_WALLET]: state => ({ ...state, unlockingWallet: true }), [WALLET_UNLOCKED]: state => ({ ...state, unlockingWallet: false, unlockWalletError: { isError: false, message: '' } }), - [SET_UNLOCK_WALLET_ERROR]: state => ({ ...state, unlockingWallet: false, unlockWalletError: { isError: true, message: 'Incorrect password' } }) + [SET_UNLOCK_WALLET_ERROR]: state => ({ ...state, unlockingWallet: false, unlockWalletError: { isError: true, message: 'Incorrect password' } }), + + [SET_SIGNUP_CREATE]: state => ({ ...state, signupForm: { create: true, import: false } }), + [SET_SIGNUP_IMPORT]: state => ({ ...state, signupForm: { create: false, import: true } }) } const onboardingSelectors = {} const passwordSelector = state => state.onboarding.password +const seedSelector = state => state.onboarding.seed +const seedInputSelector = state => state.onboarding.seedInput onboardingSelectors.passwordIsValid = createSelector( passwordSelector, password => password.length >= 8 ) +onboardingSelectors.reEnterSeedChecker = createSelector( + seedSelector, + seedInputSelector, + (seed, seedInput) => { + // console.log('seedInput: ', seedInput) + + // const seedInputArr = seedInput.split(' ').filter(n => true && n.length) + + // console.log('seedInputArr: ', seedInputArr) + + // return seedInputArr.map((word, index) => { return { valid: word === seed[index], word } }) + } +) + +onboardingSelectors.renderEnterSeedHtml = createSelector( + onboardingSelectors.reEnterSeedChecker, + (reEnterSeedChecker) => { + // console.log('reEnterSeedChecker: ', reEnterSeedChecker) + // if (!reEnterSeedChecker.length) { return 'gang' } + + // return reEnterSeedChecker.map( ({ valid, word }) => (`${word}`) ).join('') + } +) + export { onboardingSelectors } // ------------------------------------ @@ -171,15 +245,27 @@ const initialState = { hasSeed: false, seed: [], + // wallet password. password used to encrypt the wallet and is required to unlock the daemon after set createWalletPassword: '', creatingNewWallet: false, + // seed password. this is optional and used to encrypt the seed + aezeedPassword: '', + unlockingWallet: false, unlockWalletError: { isError: false, message: '' }, + // array of inputs for when the user re-enters their seed + seedInput: [], + // step where the user decides whether they want a newly created seed or to import an existing one + signupForm: { + create: false, + import: false + }, + autopilot: null } From f2c544cab8f0be03d0b2a90a18dd3043d64ab216 Mon Sep 17 00:00:00 2001 From: Jack Mallers Date: Tue, 20 Mar 2018 20:48:32 -0500 Subject: [PATCH 03/10] feature(reEnterSeedChecker): return true or false based on whether the user has re-entered the seed correctly --- app/components/Onboarding/Onboarding.js | 7 ++++++- app/components/Onboarding/ReEnterSeed.js | 6 +++--- app/containers/Root.js | 4 +--- app/reducers/onboarding.js | 22 +++------------------- 4 files changed, 13 insertions(+), 26 deletions(-) diff --git a/app/components/Onboarding/Onboarding.js b/app/components/Onboarding/Onboarding.js index 1bb9e54b..b6f8bef3 100644 --- a/app/components/Onboarding/Onboarding.js +++ b/app/components/Onboarding/Onboarding.js @@ -110,7 +110,12 @@ const Onboarding = ({ title='Re-enter your seed' description='Yeah I know, might be annoying, but just to be safe!' // eslint-disable-line back={() => changeStep(6)} - next={() => changeStep(8)} + next={() => { + // don't allow them to move on if they havent re-entered the seed correctly + if (!reEnterSeedProps.reEnterSeedChecker) { return } + + changeStep(8) + }} > diff --git a/app/components/Onboarding/ReEnterSeed.js b/app/components/Onboarding/ReEnterSeed.js index aab83657..2e8270b1 100644 --- a/app/components/Onboarding/ReEnterSeed.js +++ b/app/components/Onboarding/ReEnterSeed.js @@ -2,7 +2,7 @@ import React from 'react' import PropTypes from 'prop-types' import styles from './ReEnterSeed.scss' -const ReEnterSeed = ({ seed, seedInput, updateSeedInput, reEnterSeedChecker, renderEnterSeedHtml }) => { +const ReEnterSeed = ({ seed, seedInput, updateSeedInput }) => { return (
    @@ -33,9 +33,9 @@ const ReEnterSeed = ({ seed, seedInput, updateSeedInput, reEnterSeedChecker, ren } ReEnterSeed.propTypes = { + seed: PropTypes.array.isRequired, seedInput: PropTypes.array.isRequired, - updateSeedInput: PropTypes.func.isRequired, - reEnterSeedChecker: PropTypes.array.isRequired + updateSeedInput: PropTypes.func.isRequired } export default ReEnterSeed diff --git a/app/containers/Root.js b/app/containers/Root.js index cf6530a6..b5bffa74 100644 --- a/app/containers/Root.js +++ b/app/containers/Root.js @@ -50,8 +50,7 @@ const mapStateToProps = state => ({ syncPercentage: lndSelectors.syncPercentage(state), passwordIsValid: onboardingSelectors.passwordIsValid(state), - reEnterSeedChecker: onboardingSelectors.reEnterSeedChecker(state), - renderEnterSeedHtml: onboardingSelectors.renderEnterSeedHtml(state) + reEnterSeedChecker: onboardingSelectors.reEnterSeedChecker(state) }) const mergeProps = (stateProps, dispatchProps, ownProps) => { @@ -111,7 +110,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { seed: stateProps.onboarding.seed, seedInput: stateProps.onboarding.seedInput, reEnterSeedChecker: stateProps.reEnterSeedChecker, - renderEnterSeedHtml: stateProps.renderEnterSeedHtml, updateSeedInput: dispatchProps.updateSeedInput } diff --git a/app/reducers/onboarding.js b/app/reducers/onboarding.js index c628bf90..e35fd16d 100644 --- a/app/reducers/onboarding.js +++ b/app/reducers/onboarding.js @@ -208,25 +208,7 @@ onboardingSelectors.passwordIsValid = createSelector( onboardingSelectors.reEnterSeedChecker = createSelector( seedSelector, seedInputSelector, - (seed, seedInput) => { - // console.log('seedInput: ', seedInput) - - // const seedInputArr = seedInput.split(' ').filter(n => true && n.length) - - // console.log('seedInputArr: ', seedInputArr) - - // return seedInputArr.map((word, index) => { return { valid: word === seed[index], word } }) - } -) - -onboardingSelectors.renderEnterSeedHtml = createSelector( - onboardingSelectors.reEnterSeedChecker, - (reEnterSeedChecker) => { - // console.log('reEnterSeedChecker: ', reEnterSeedChecker) - // if (!reEnterSeedChecker.length) { return 'gang' } - - // return reEnterSeedChecker.map( ({ valid, word }) => (`${word}`) ).join('') - } + (seed, seedInput) => seed.length === seedInput.length && seed.every((word, i) => word === seedInput[i].word) ) export { onboardingSelectors } @@ -259,6 +241,8 @@ const initialState = { }, // array of inputs for when the user re-enters their seed + // object has a word attr and a index attr: + // { word: 'foo', index: 0 } seedInput: [], // step where the user decides whether they want a newly created seed or to import an existing one signupForm: { From 4a6a360128116c997a79382afe4b3b64de63c95d Mon Sep 17 00:00:00 2001 From: Jack Mallers Date: Tue, 20 Mar 2018 21:49:46 -0500 Subject: [PATCH 04/10] feature(createWalletConfirmation): add walletPasswordConfirmation to reducer --- app/components/Onboarding/Autopilot.scss | 16 ++++++++-------- app/components/Onboarding/Onboarding.js | 6 +++++- app/components/Onboarding/Signup.scss | 18 +++++++++--------- app/reducers/onboarding.js | 14 ++++++++++++-- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/app/components/Onboarding/Autopilot.scss b/app/components/Onboarding/Autopilot.scss index b9ff9ac2..df5000b2 100644 --- a/app/components/Onboarding/Autopilot.scss +++ b/app/components/Onboarding/Autopilot.scss @@ -9,28 +9,28 @@ &.enable { &.active { div { - color: $green; - border-color: $green; + color: $gold; + border-color: $gold; } } div:hover { - color: $green; - border-color: $green; + color: $gold; + border-color: $gold; } } &.disable { &.active { div { - color: $red; - border-color: $red; + color: $gold; + border-color: $gold; } } div:hover { - color: $red; - border-color: $red; + color: $gold; + border-color: $gold; } } diff --git a/app/components/Onboarding/Onboarding.js b/app/components/Onboarding/Onboarding.js index b6f8bef3..631a721b 100644 --- a/app/components/Onboarding/Onboarding.js +++ b/app/components/Onboarding/Onboarding.js @@ -23,7 +23,8 @@ const Onboarding = ({ startingLnd, createWalletPassword, seed, - aezeedPassword + aezeedPassword, + fetchingSeed }, changeStep, startLnd, @@ -137,6 +138,9 @@ const Onboarding = ({ } if (startingLnd) { return } + if (fetchingSeed) { + console.log('got em!') + return } return (
    diff --git a/app/components/Onboarding/Signup.scss b/app/components/Onboarding/Signup.scss index 18d543b8..27e96f79 100644 --- a/app/components/Onboarding/Signup.scss +++ b/app/components/Onboarding/Signup.scss @@ -9,33 +9,33 @@ &.enable { &.active { div { - color: $green; - border-color: $green; + color: $gold; + border-color: $gold; } } div:hover { - color: $green; - border-color: $green; + color: $gold; + border-color: $gold; } } &.disable { &.active { div { - color: $red; - border-color: $red; + color: $gold; + border-color: $gold; } } div:hover { - color: $red; - border-color: $red; + color: $gold; + border-color: $gold; } } div { - width: 200px; + width: 185px; display: inline-block; padding: 20px; border: 1px solid $white; diff --git a/app/reducers/onboarding.js b/app/reducers/onboarding.js index e35fd16d..73f64f16 100644 --- a/app/reducers/onboarding.js +++ b/app/reducers/onboarding.js @@ -7,6 +7,7 @@ import { ipcRenderer } from 'electron' export const UPDATE_ALIAS = 'UPDATE_ALIAS' export const UPDATE_PASSWORD = 'UPDATE_PASSWORD' export const UPDATE_CREATE_WALLET_PASSWORD = 'UPDATE_CREATE_WALLET_PASSWORD' +export const UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION = 'UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION' export const UPDATE_AEZEED_PASSWORD = 'UPDATE_AEZEED_PASSWORD' export const UPDATE_SEED_INPUT = 'UPDATE_SEED_INPUT' @@ -56,6 +57,13 @@ export function updateCreateWalletPassword(createWalletPassword) { } } +export function updateCreateWalletPasswordConfirmation(updateCreateWalletPasswordConfirmation) { + return { + type: UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION, + updateCreateWalletPasswordConfirmation + } +} + export function updateAezeedPassword(aezeedPassword) { return { type: UPDATE_AEZEED_PASSWORD, @@ -130,9 +138,9 @@ export const successfullyCreatedWallet = (event) => (dispatch) => dispatch({ typ // Listener for when LND creates and sends us a generated seed export const receiveSeed = (event, { cipher_seed_mnemonic }) => (dispatch) => { - dispatch({ type: SET_SEED, seed: cipher_seed_mnemonic }) - // there was no seed and we just generated a new one, send user to the login component dispatch({ type: CHANGE_STEP, step: 4 }) + // there was no seed and we just generated a new one, send user to the login component + dispatch({ type: SET_SEED, seed: cipher_seed_mnemonic }) } // Listener for when LND throws an error on seed creation @@ -164,6 +172,7 @@ const ACTION_HANDLERS = { [UPDATE_ALIAS]: (state, { alias }) => ({ ...state, alias }), [UPDATE_PASSWORD]: (state, { password }) => ({ ...state, password }), [UPDATE_CREATE_WALLET_PASSWORD]: (state, { createWalletPassword }) => ({ ...state, createWalletPassword }), + [UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION]: (state, { createWalletConfirmation }) => ({ ...state, createWalletConfirmation }), [UPDATE_AEZEED_PASSWORD]: (state, { aezeedPassword }) => ({ ...state, aezeedPassword }), [UPDATE_SEED_INPUT]: (state, { inputSeedObj }) => { return { @@ -229,6 +238,7 @@ const initialState = { // wallet password. password used to encrypt the wallet and is required to unlock the daemon after set createWalletPassword: '', + createWalletConfirmation: '', creatingNewWallet: false, // seed password. this is optional and used to encrypt the seed From 9369baf308b448435051583a43d24acc03c93597 Mon Sep 17 00:00:00 2001 From: Jack Mallers Date: Wed, 21 Mar 2018 13:11:13 -0500 Subject: [PATCH 05/10] fix(createWalletPassword): refactor component to be stateless. use selector to show error on non matching passwords and not allow user to continue until passwords are matching --- .../Onboarding/NewWalletPassword.js | 45 ++++++++----------- .../Onboarding/NewWalletPassword.scss | 11 +++++ app/components/Onboarding/Onboarding.js | 7 ++- app/containers/Root.js | 8 +++- app/reducers/onboarding.js | 18 ++++++-- 5 files changed, 56 insertions(+), 33 deletions(-) diff --git a/app/components/Onboarding/NewWalletPassword.js b/app/components/Onboarding/NewWalletPassword.js index 1ab89d19..b530b2e9 100644 --- a/app/components/Onboarding/NewWalletPassword.js +++ b/app/components/Onboarding/NewWalletPassword.js @@ -4,31 +4,19 @@ import Isvg from 'react-inlinesvg' import eye from 'icons/eye.svg' import styles from './NewWalletPassword.scss' -class NewWalletPassword extends React.Component { - constructor(props) { - super(props) - - this.state = { - inputType: 'password', - confirmPassword: '' - } - } - - render() { - const { createWalletPassword, updateCreateWalletPassword } = this.props - const { inputType, confirmPassword } = this.state - - const toggleInputType = () => { - const newInputType = inputType === 'password' ? 'text' : 'password' - - this.setState({ inputType: newInputType }) - } - +const NewWalletPassword = ({ + createWalletPassword, + createWalletPasswordConfirmation, + showCreateWalletPasswordConfirmationError, + + updateCreateWalletPassword, + updateCreateWalletPasswordConfirmation +}) => { return (
    this.setState({ confirmPassword: event.target.value })} + className={`${styles.password} ${showCreateWalletPasswordConfirmationError && styles.error}`} + value={createWalletPasswordConfirmation} + onChange={event => updateCreateWalletPasswordConfirmation(event.target.value)} /> +

    Passwords do not match

    ) - } } NewWalletPassword.propTypes = { createWalletPassword: PropTypes.string.isRequired, - updateCreateWalletPassword: PropTypes.func.isRequired + createWalletPasswordConfirmation: PropTypes.string.isRequired, + showCreateWalletPasswordConfirmationError: PropTypes.bool.isRequired, + updateCreateWalletPassword: PropTypes.func.isRequired, + updateCreateWalletPasswordConfirmation: PropTypes.func.isRequired } export default NewWalletPassword diff --git a/app/components/Onboarding/NewWalletPassword.scss b/app/components/Onboarding/NewWalletPassword.scss index 1fd5fd6f..66aa30c5 100644 --- a/app/components/Onboarding/NewWalletPassword.scss +++ b/app/components/Onboarding/NewWalletPassword.scss @@ -22,3 +22,14 @@ text-shadow: none; -webkit-text-fill-color: initial; } + +.errorMessage { + color: $red; + margin-top: 10px; + font-size: 10px; + visibility: hidden; + + &.visible { + visibility: visible; + } +} diff --git a/app/components/Onboarding/Onboarding.js b/app/components/Onboarding/Onboarding.js index 631a721b..273d2781 100644 --- a/app/components/Onboarding/Onboarding.js +++ b/app/components/Onboarding/Onboarding.js @@ -78,7 +78,12 @@ const Onboarding = ({ title='Welcome!' description='Looks like you are new here. Set a password to encrypt your wallet. This password will be needed to unlock Zap in the future' // eslint-disable-line back={null} - next={() => changeStep(5)} + next={() => { + // dont allow the user to move on if the confirmation password doesnt match the original password + if (newWalletPasswordProps.showCreateWalletPasswordConfirmationError) { return } + + changeStep(5) + }} > diff --git a/app/containers/Root.js b/app/containers/Root.js index b5bffa74..d0d9f40d 100644 --- a/app/containers/Root.js +++ b/app/containers/Root.js @@ -15,6 +15,7 @@ import { startLnd, createWallet, updateCreateWalletPassword, + updateCreateWalletPasswordConfirmation, updateAezeedPassword, submitNewWallet, onboardingSelectors, @@ -30,6 +31,7 @@ const mapDispatchToProps = { updateAlias, updatePassword, updateCreateWalletPassword, + updateCreateWalletPasswordConfirmation, updateAezeedPassword, setAutopilot, changeStep, @@ -50,6 +52,7 @@ const mapStateToProps = state => ({ syncPercentage: lndSelectors.syncPercentage(state), passwordIsValid: onboardingSelectors.passwordIsValid(state), + showCreateWalletPasswordConfirmationError: onboardingSelectors.showCreateWalletPasswordConfirmationError(state), reEnterSeedChecker: onboardingSelectors.reEnterSeedChecker(state) }) @@ -98,7 +101,10 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { const newWalletPasswordProps = { createWalletPassword: stateProps.onboarding.createWalletPassword, - updateCreateWalletPassword: dispatchProps.updateCreateWalletPassword + createWalletPasswordConfirmation: stateProps.onboarding.createWalletPasswordConfirmation, + showCreateWalletPasswordConfirmationError: stateProps.showCreateWalletPasswordConfirmationError, + updateCreateWalletPassword: dispatchProps.updateCreateWalletPassword, + updateCreateWalletPasswordConfirmation: dispatchProps.updateCreateWalletPasswordConfirmation } const newAezeedPasswordProps = { diff --git a/app/reducers/onboarding.js b/app/reducers/onboarding.js index 73f64f16..36921f67 100644 --- a/app/reducers/onboarding.js +++ b/app/reducers/onboarding.js @@ -57,10 +57,10 @@ export function updateCreateWalletPassword(createWalletPassword) { } } -export function updateCreateWalletPasswordConfirmation(updateCreateWalletPasswordConfirmation) { +export function updateCreateWalletPasswordConfirmation(createWalletPasswordConfirmation) { return { type: UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION, - updateCreateWalletPasswordConfirmation + createWalletPasswordConfirmation } } @@ -172,7 +172,7 @@ const ACTION_HANDLERS = { [UPDATE_ALIAS]: (state, { alias }) => ({ ...state, alias }), [UPDATE_PASSWORD]: (state, { password }) => ({ ...state, password }), [UPDATE_CREATE_WALLET_PASSWORD]: (state, { createWalletPassword }) => ({ ...state, createWalletPassword }), - [UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION]: (state, { createWalletConfirmation }) => ({ ...state, createWalletConfirmation }), + [UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION]: (state, { createWalletPasswordConfirmation }) => ({ ...state, createWalletPasswordConfirmation }), [UPDATE_AEZEED_PASSWORD]: (state, { aezeedPassword }) => ({ ...state, aezeedPassword }), [UPDATE_SEED_INPUT]: (state, { inputSeedObj }) => { return { @@ -206,6 +206,10 @@ const ACTION_HANDLERS = { const onboardingSelectors = {} const passwordSelector = state => state.onboarding.password + +const createWalletPasswordSelector = state => state.onboarding.createWalletPassword +const createWalletPasswordConfirmationSelector = state => state.onboarding.createWalletPasswordConfirmation + const seedSelector = state => state.onboarding.seed const seedInputSelector = state => state.onboarding.seedInput @@ -214,6 +218,12 @@ onboardingSelectors.passwordIsValid = createSelector( password => password.length >= 8 ) +onboardingSelectors.showCreateWalletPasswordConfirmationError = createSelector( + createWalletPasswordSelector, + createWalletPasswordConfirmationSelector, + (pass1, pass2) => pass1 !== pass2 && pass2.length > 0 +) + onboardingSelectors.reEnterSeedChecker = createSelector( seedSelector, seedInputSelector, @@ -238,7 +248,7 @@ const initialState = { // wallet password. password used to encrypt the wallet and is required to unlock the daemon after set createWalletPassword: '', - createWalletConfirmation: '', + createWalletPasswordConfirmation: '', creatingNewWallet: false, // seed password. this is optional and used to encrypt the seed From c1f9ec58bc8b82082121a3cf27108a9f62bddd44 Mon Sep 17 00:00:00 2001 From: Jack Mallers Date: Thu, 22 Mar 2018 12:20:02 -0500 Subject: [PATCH 06/10] fix(grpcInit): remove old file --- app/lnd/lib/grpcInit.js | 24 ------------------------ app/lnd/lib/lightning.js | 2 -- app/lnd/lib/walletUnlocker.js | 17 +++++++++++++++-- 3 files changed, 15 insertions(+), 28 deletions(-) delete mode 100644 app/lnd/lib/grpcInit.js diff --git a/app/lnd/lib/grpcInit.js b/app/lnd/lib/grpcInit.js deleted file mode 100644 index ac0ec319..00000000 --- a/app/lnd/lib/grpcInit.js +++ /dev/null @@ -1,24 +0,0 @@ -import fs from 'fs' -import path from 'path' -import grpc from 'grpc' -import config from '../config' - -const grpcInit = (rpcpath, host) => { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' - process.env.GRPC_SSL_CIPHER_SUITES = 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384' - - const lndCert = fs.readFileSync(config.cert) - const credentials = grpc.credentials.createSsl(lndCert) - const rpc = grpc.load(path.join(__dirname, 'rpc.proto')) - - - const lightning = new rpc.lnrpc.Lightning(host, credentials) - const walletUnlocker = new rpc.lnrpc.WalletUnlocker(host, credentials) - - return { - lightning, - walletUnlocker - } -} - -export default grpcInit diff --git a/app/lnd/lib/lightning.js b/app/lnd/lib/lightning.js index a6d9f7b4..224d7352 100644 --- a/app/lnd/lib/lightning.js +++ b/app/lnd/lib/lightning.js @@ -19,8 +19,6 @@ process.env.GRPC_SSL_CIPHER_SUITES = process.env.GRPC_SSL_CIPHER_SUITES || [ ].join(':') const lightning = (rpcpath, host) => { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' - process.env.GRPC_SSL_CIPHER_SUITES = 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384' const lndCert = fs.readFileSync(config.cert) const credentials = grpc.credentials.createSsl(lndCert) const rpc = grpc.load(path.join(__dirname, 'rpc.proto')) diff --git a/app/lnd/lib/walletUnlocker.js b/app/lnd/lib/walletUnlocker.js index 334ebadd..55ddef4f 100644 --- a/app/lnd/lib/walletUnlocker.js +++ b/app/lnd/lib/walletUnlocker.js @@ -3,9 +3,22 @@ import path from 'path' import grpc from 'grpc' import config from '../config' +// Default is ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384 +// https://github.com/grpc/grpc/blob/master/doc/environment_variables.md +// +// Current LND cipher suites here: +// https://github.com/lightningnetwork/lnd/blob/master/lnd.go#L80 +// +// We order the suites by priority, based on the recommendations provided by SSL Labs here: +// https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites +process.env.GRPC_SSL_CIPHER_SUITES = process.env.GRPC_SSL_CIPHER_SUITES || [ + 'ECDHE-ECDSA-AES128-GCM-SHA256', + 'ECDHE-ECDSA-AES256-GCM-SHA384', + 'ECDHE-ECDSA-AES128-CBC-SHA256', + 'ECDHE-ECDSA-CHACHA20-POLY1305' +].join(':') + const walletUnlocker = (rpcpath, host) => { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' - process.env.GRPC_SSL_CIPHER_SUITES = 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384' const lndCert = fs.readFileSync(config.cert) const credentials = grpc.credentials.createSsl(lndCert) const rpc = grpc.load(path.join(__dirname, 'rpc.proto')) From 2bb28e0bbd61cd96ca1f4d983c26e22638d9632c Mon Sep 17 00:00:00 2001 From: Jack Mallers Date: Thu, 22 Mar 2018 13:03:59 -0500 Subject: [PATCH 07/10] fix(lint): fix linting errors --- app/components/Onboarding/InitWallet.js | 6 +- app/components/Onboarding/Login.js | 8 ++- .../Onboarding/NewAezeedPassword.js | 2 - .../Onboarding/NewWalletPassword.js | 50 ++++++++--------- app/components/Onboarding/NewWalletSeed.js | 28 +++++----- app/components/Onboarding/Onboarding.js | 17 +++--- app/components/Onboarding/ReEnterSeed.js | 55 +++++++++---------- app/components/Onboarding/Signup.js | 6 +- app/containers/Root.js | 4 +- app/lnd/index.js | 2 +- app/lnd/init/index.js | 13 ----- app/lnd/walletUnlockerMethods/index.js | 18 ++---- app/main.dev.js | 7 +-- app/reducers/onboarding.js | 36 ++++++------ 14 files changed, 118 insertions(+), 134 deletions(-) delete mode 100644 app/lnd/init/index.js diff --git a/app/components/Onboarding/InitWallet.js b/app/components/Onboarding/InitWallet.js index ed3b80ce..244b6717 100644 --- a/app/components/Onboarding/InitWallet.js +++ b/app/components/Onboarding/InitWallet.js @@ -15,6 +15,10 @@ const InitWallet = ({ hasSeed, loginProps, signupProps }) => (
    ) -InitWallet.propTypes = {} +InitWallet.propTypes = { + hasSeed: PropTypes.bool.isRequired, + loginProps: PropTypes.object.isRequired, + signupProps: PropTypes.object.isRequired +} export default InitWallet diff --git a/app/components/Onboarding/Login.js b/app/components/Onboarding/Login.js index 6f7386b5..5121d59a 100644 --- a/app/components/Onboarding/Login.js +++ b/app/components/Onboarding/Login.js @@ -38,6 +38,12 @@ const Login = ({
) -Login.propTypes = {} +Login.propTypes = { + password: PropTypes.string.isRequired, + updatePassword: PropTypes.func.isRequired, + unlockingWallet: PropTypes.bool.isRequired, + unlockWallet: PropTypes.func.isRequired, + unlockWalletError: PropTypes.object.isRequired +} export default Login diff --git a/app/components/Onboarding/NewAezeedPassword.js b/app/components/Onboarding/NewAezeedPassword.js index 7c796173..10390738 100644 --- a/app/components/Onboarding/NewAezeedPassword.js +++ b/app/components/Onboarding/NewAezeedPassword.js @@ -1,7 +1,5 @@ import React from 'react' import PropTypes from 'prop-types' -import Isvg from 'react-inlinesvg' -import eye from 'icons/eye.svg' import styles from './NewAezeedPassword.scss' class NewAezeedPassword extends React.Component { diff --git a/app/components/Onboarding/NewWalletPassword.js b/app/components/Onboarding/NewWalletPassword.js index b530b2e9..38bb69b7 100644 --- a/app/components/Onboarding/NewWalletPassword.js +++ b/app/components/Onboarding/NewWalletPassword.js @@ -1,7 +1,5 @@ import React from 'react' import PropTypes from 'prop-types' -import Isvg from 'react-inlinesvg' -import eye from 'icons/eye.svg' import styles from './NewWalletPassword.scss' const NewWalletPassword = ({ @@ -11,32 +9,30 @@ const NewWalletPassword = ({ updateCreateWalletPassword, updateCreateWalletPasswordConfirmation -}) => { - return ( -
-
- updateCreateWalletPassword(event.target.value)} - /> -
+}) => ( +
+
+ updateCreateWalletPassword(event.target.value)} + /> +
-
- updateCreateWalletPasswordConfirmation(event.target.value)} - /> -

Passwords do not match

-
-
- ) -} +
+ updateCreateWalletPasswordConfirmation(event.target.value)} + /> +

Passwords do not match

+
+
+) NewWalletPassword.propTypes = { createWalletPassword: PropTypes.string.isRequired, diff --git a/app/components/Onboarding/NewWalletSeed.js b/app/components/Onboarding/NewWalletSeed.js index b04207f7..a2cdc55b 100644 --- a/app/components/Onboarding/NewWalletSeed.js +++ b/app/components/Onboarding/NewWalletSeed.js @@ -5,21 +5,19 @@ import styles from './NewWalletSeed.scss' const NewWalletSeed = ({ seed }) => (
    - { - seed.map((word, index) => { - return ( -
  • -
    - -
    -
    - {word} -
    -
  • - ) - }) - } -
+ { + seed.map((word, index) => ( +
  • +
    + +
    +
    + {word} +
    +
  • + )) + } +
    ) diff --git a/app/components/Onboarding/Onboarding.js b/app/components/Onboarding/Onboarding.js index 273d2781..5f37a8a3 100644 --- a/app/components/Onboarding/Onboarding.js +++ b/app/components/Onboarding/Onboarding.js @@ -6,7 +6,6 @@ import LoadingBolt from 'components/LoadingBolt' import FormContainer from './FormContainer' import Alias from './Alias' import Autopilot from './Autopilot' -import InitWallet from './InitWallet' import Login from './Login' import Signup from './Signup' import NewWalletSeed from './NewWalletSeed' @@ -81,7 +80,7 @@ const Onboarding = ({ next={() => { // dont allow the user to move on if the confirmation password doesnt match the original password if (newWalletPasswordProps.showCreateWalletPasswordConfirmationError) { return } - + changeStep(5) }} > @@ -94,7 +93,7 @@ const Onboarding = ({ title={'Alright, let\'s get set up'} description='Would you like to create a new wallet or import an existing one?' // eslint-disable-line back={() => changeStep(4)} - next={() => initWalletProps.signupProps.signupForm.create ? changeStep(6) : console.log('import')} + next={() => (initWalletProps.signupProps.signupForm.create ? changeStep(6) : console.log('import'))} > @@ -143,9 +142,7 @@ const Onboarding = ({ } if (startingLnd) { return } - if (fetchingSeed) { - console.log('got em!') - return } + if (fetchingSeed) { return } return (
    @@ -158,8 +155,14 @@ Onboarding.propTypes = { onboarding: PropTypes.object.isRequired, aliasProps: PropTypes.object.isRequired, autopilotProps: PropTypes.object.isRequired, + initWalletProps: PropTypes.object.isRequired, + newWalletSeedProps: PropTypes.object.isRequired, + newWalletPasswordProps: PropTypes.object.isRequired, + newAezeedPasswordProps: PropTypes.object.isRequired, + reEnterSeedProps: PropTypes.object.isRequired, changeStep: PropTypes.func.isRequired, - startLnd: PropTypes.func.isRequired + startLnd: PropTypes.func.isRequired, + submitNewWallet: PropTypes.func.isRequired } export default Onboarding diff --git a/app/components/Onboarding/ReEnterSeed.js b/app/components/Onboarding/ReEnterSeed.js index 2e8270b1..5bb38b27 100644 --- a/app/components/Onboarding/ReEnterSeed.js +++ b/app/components/Onboarding/ReEnterSeed.js @@ -2,35 +2,32 @@ import React from 'react' import PropTypes from 'prop-types' import styles from './ReEnterSeed.scss' -const ReEnterSeed = ({ seed, seedInput, updateSeedInput }) => { - return ( -
    -
      - { - seed.map((word, index) => { - return ( -
    • -
      - -
      -
      - updateSeedInput({ word: event.target.value, index })} - className={`${styles.word} ${seedInput[index] && word === seedInput[index].word ? styles.valid : styles.invalid}`} - /> -
      -
    • - ) - }) - } -
    -
    - ) -} +const ReEnterSeed = ({ seed, seedInput, updateSeedInput }) => ( +
    +
      + { + seed.map((word, index) => ( +
    • +
      + +
      +
      + updateSeedInput({ word: event.target.value, index })} + className={`${styles.word} ${seedInput[index] && word === seedInput[index].word ? styles.valid : styles.invalid}`} + /> +
      +
    • + )) + } +
    +
    +) + ReEnterSeed.propTypes = { seed: PropTypes.array.isRequired, diff --git a/app/components/Onboarding/Signup.js b/app/components/Onboarding/Signup.js index 3e28e6fd..251d1719 100644 --- a/app/components/Onboarding/Signup.js +++ b/app/components/Onboarding/Signup.js @@ -34,6 +34,10 @@ const Signup = ({ signupForm, setSignupCreate, setSignupImport }) => (
    ) -Signup.propTypes = {} +Signup.propTypes = { + signupForm: PropTypes.object.isRequired, + setSignupCreate: PropTypes.func.isRequired, + setSignupImport: PropTypes.func.isRequired +} export default Signup diff --git a/app/containers/Root.js b/app/containers/Root.js index d0d9f40d..6f1bc2b4 100644 --- a/app/containers/Root.js +++ b/app/containers/Root.js @@ -74,14 +74,14 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { const initWalletProps = { hasSeed: stateProps.onboarding.hasSeed, - + loginProps: { password: stateProps.onboarding.password, passwordIsValid: stateProps.passwordIsValid, hasSeed: stateProps.onboarding.hasSeed, unlockingWallet: stateProps.onboarding.unlockingWallet, unlockWalletError: stateProps.onboarding.unlockWalletError, - + updatePassword: dispatchProps.updatePassword, createWallet: dispatchProps.createWallet, unlockWallet: dispatchProps.unlockWallet diff --git a/app/lnd/index.js b/app/lnd/index.js index 4db8a714..aa5733e4 100644 --- a/app/lnd/index.js +++ b/app/lnd/index.js @@ -31,4 +31,4 @@ const initWalletUnlocker = (callback) => { export default { initLnd, initWalletUnlocker -} \ No newline at end of file +} diff --git a/app/lnd/init/index.js b/app/lnd/init/index.js deleted file mode 100644 index 74b0a81b..00000000 --- a/app/lnd/init/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint no-console: 0 */ // --> OFF -import * as walletController from '../methods/walletController' - -export default function (walletUnlocker, meta, event, msg, data) { - console.log('msg yo wtf: ', msg) - switch (msg) { - case 'genSeed': - walletController.genSeed(walletUnlocker, meta) - .then(data => { console.log('data: ', data) }) - .catch(error => { console.log('error: ', error) }) - default: - } -} diff --git a/app/lnd/walletUnlockerMethods/index.js b/app/lnd/walletUnlockerMethods/index.js index 07a782a1..19adeb81 100644 --- a/app/lnd/walletUnlockerMethods/index.js +++ b/app/lnd/walletUnlockerMethods/index.js @@ -6,24 +6,18 @@ export default function (walletUnlocker, event, msg, data) { switch (msg) { case 'genSeed': walletController.genSeed(walletUnlocker) - .then(data => { - console.log('data yo: ', data) - event.sender.send('receiveSeed', data) - }) - .catch(error => { - console.log('genSeed error: ', error) - event.sender.send('receiveSeedError', error) - }) + .then(genSeedData => event.sender.send('receiveSeed', genSeedData)) + .catch(error => event.sender.send('receiveSeedError', error)) break case 'unlockWallet': walletController.unlockWallet(walletUnlocker, data) - .then(data => event.sender.send('walletUnlocked')) - .catch(error => event.sender.send('unlockWalletError')) + .then(() => event.sender.send('walletUnlocked')) + .catch(() => event.sender.send('unlockWalletError')) break case 'initWallet': walletController.initWallet(walletUnlocker, data) - .then(data => event.sender.send('successfullyCreatedWallet')) - .catch(error => console.log('initWallet error: ', error)) + .then(() => event.sender.send('successfullyCreatedWallet')) + .catch(error => console.log('initWallet error: ', error)) break default: } diff --git a/app/main.dev.js b/app/main.dev.js index 06d3ebc7..e3b54bba 100644 --- a/app/main.dev.js +++ b/app/main.dev.js @@ -17,7 +17,7 @@ import { spawn } from 'child_process' import { lookup } from 'ps-node' import os from 'os' import MenuBuilder from './menu' -import { initLnd, initWalletUnlocker } from './lnd' +import lnd from './lnd' const plat = os.platform() const homedir = os.homedir() @@ -111,7 +111,7 @@ const sendGrpcConnected = () => { // Create and subscribe the grpc object const startGrpc = () => { - initLnd((lndSubscribe, lndMethods) => { + lnd.initLnd((lndSubscribe, lndMethods) => { // Subscribe to bi-directional streams lndSubscribe(mainWindow) @@ -126,7 +126,7 @@ const startGrpc = () => { // Create and subscribe the grpc object const startWalletUnlocker = () => { - initWalletUnlocker((walletUnlockerMethods) => { + lnd.initWalletUnlocker((walletUnlockerMethods) => { // Listen for all gRPC restful methods ipcMain.on('walletUnlocker', (event, { msg, data }) => { walletUnlockerMethods(event, msg, data) @@ -196,7 +196,6 @@ const startLnd = (alias, autopilot) => { if (mainWindow) { mainWindow.webContents.send('walletUnlockerStarted') } - } }, 1000) } diff --git a/app/reducers/onboarding.js b/app/reducers/onboarding.js index 36921f67..675d7369 100644 --- a/app/reducers/onboarding.js +++ b/app/reducers/onboarding.js @@ -105,7 +105,7 @@ export function changeStep(step) { } export function startLnd(alias, autopilot) { - // once the user submits the data needed to start LND we will alert the app that it should start LND + // once the user submits the data needed to start LND we will alert the app that it should start LND ipcRenderer.send('startLnd', { alias, autopilot }) return { @@ -114,7 +114,7 @@ export function startLnd(alias, autopilot) { } export const submitNewWallet = (wallet_password, cipher_seed_mnemonic, aezeed_passphrase) => (dispatch) => { - // once the user submits the data needed to start LND we will alert the app that it should start LND + // once the user submits the data needed to start LND we will alert the app that it should start LND ipcRenderer.send('walletUnlocker', { msg: 'initWallet', data: { wallet_password, cipher_seed_mnemonic, aezeed_passphrase } }) dispatch({ type: CREATING_NEW_WALLET }) } @@ -134,7 +134,7 @@ export const createWallet = () => (dispatch) => { dispatch({ type: CHANGE_STEP, step: 4 }) } -export const successfullyCreatedWallet = (event) => (dispatch) => dispatch({ type: ONBOARDING_FINISHED }) +export const successfullyCreatedWallet = () => dispatch => dispatch({ type: ONBOARDING_FINISHED }) // Listener for when LND creates and sends us a generated seed export const receiveSeed = (event, { cipher_seed_mnemonic }) => (dispatch) => { @@ -144,14 +144,14 @@ export const receiveSeed = (event, { cipher_seed_mnemonic }) => (dispatch) => { } // Listener for when LND throws an error on seed creation -export const receiveSeedError = (event, error) => (dispatch) => { +export const receiveSeedError = () => (dispatch) => { dispatch({ type: SET_HAS_SEED, hasSeed: true }) // there is already a seed, send user to the login component dispatch({ type: CHANGE_STEP, step: 3 }) } // Unlock an existing wallet with a wallet password -export const unlockWallet = (wallet_password) => (dispatch) => { +export const unlockWallet = wallet_password => (dispatch) => { ipcRenderer.send('walletUnlocker', { msg: 'unlockWallet', data: { wallet_password } }) dispatch({ type: UNLOCKING_WALLET }) } @@ -174,23 +174,21 @@ const ACTION_HANDLERS = { [UPDATE_CREATE_WALLET_PASSWORD]: (state, { createWalletPassword }) => ({ ...state, createWalletPassword }), [UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION]: (state, { createWalletPasswordConfirmation }) => ({ ...state, createWalletPasswordConfirmation }), [UPDATE_AEZEED_PASSWORD]: (state, { aezeedPassword }) => ({ ...state, aezeedPassword }), - [UPDATE_SEED_INPUT]: (state, { inputSeedObj }) => { - return { - ...state, - seedInput: Object.assign([], state.seedInput, { [inputSeedObj['index']]: inputSeedObj }) - } - }, - + [UPDATE_SEED_INPUT]: (state, { inputSeedObj }) => ({ + ...state, + seedInput: Object.assign([], state.seedInput, { [inputSeedObj.index]: inputSeedObj }) + }), + [SET_AUTOPILOT]: (state, { autopilot }) => ({ ...state, autopilot }), - + [SET_HAS_SEED]: (state, { hasSeed }) => ({ ...state, hasSeed }), [SET_SEED]: (state, { seed }) => ({ ...state, seed, fetchingSeed: false }), - + [CHANGE_STEP]: (state, { step }) => ({ ...state, step }), - + [ONBOARDING_STARTED]: state => ({ ...state, onboarded: false }), [ONBOARDING_FINISHED]: state => ({ ...state, onboarded: true }), - + [STARTING_LND]: state => ({ ...state, startingLnd: true }), [LND_STARTED]: state => ({ ...state, startingLnd: false }), @@ -199,7 +197,7 @@ const ACTION_HANDLERS = { [UNLOCKING_WALLET]: state => ({ ...state, unlockingWallet: true }), [WALLET_UNLOCKED]: state => ({ ...state, unlockingWallet: false, unlockWalletError: { isError: false, message: '' } }), [SET_UNLOCK_WALLET_ERROR]: state => ({ ...state, unlockingWallet: false, unlockWalletError: { isError: true, message: 'Incorrect password' } }), - + [SET_SIGNUP_CREATE]: state => ({ ...state, signupForm: { create: true, import: false } }), [SET_SIGNUP_IMPORT]: state => ({ ...state, signupForm: { create: false, import: true } }) } @@ -241,11 +239,11 @@ const initialState = { alias: '', password: '', startingLnd: false, - + fetchingSeed: false, hasSeed: false, seed: [], - + // wallet password. password used to encrypt the wallet and is required to unlock the daemon after set createWalletPassword: '', createWalletPasswordConfirmation: '', From 00a93b3aec375e81afba3e998b2cb0bfb0429629 Mon Sep 17 00:00:00 2001 From: Jack Mallers Date: Thu, 22 Mar 2018 13:25:58 -0500 Subject: [PATCH 08/10] fix(): small console logs and errors --- app/components/Wallet/ReceiveModal.js | 2 +- app/lnd/methods/walletController.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/components/Wallet/ReceiveModal.js b/app/components/Wallet/ReceiveModal.js index 89196d60..e1cc7700 100644 --- a/app/components/Wallet/ReceiveModal.js +++ b/app/components/Wallet/ReceiveModal.js @@ -103,7 +103,7 @@ class ReceiveModal extends React.Component { ReceiveModal.propTypes = { isOpen: PropTypes.bool.isRequired, - pubkey: PropTypes.string.isRequired, + pubkey: PropTypes.string, address: PropTypes.string.isRequired, closeReceiveModal: PropTypes.func.isRequired } diff --git a/app/lnd/methods/walletController.js b/app/lnd/methods/walletController.js index 934f0fe9..e6b93f41 100644 --- a/app/lnd/methods/walletController.js +++ b/app/lnd/methods/walletController.js @@ -97,7 +97,6 @@ export function setAlias(lnd, meta, { new_alias }) { * Generates a seed for the wallet */ export function genSeed(walletUnlocker) { - console.log('walletUnlocker: ', walletUnlocker) return new Promise((resolve, reject) => { walletUnlocker.genSeed({}, (err, data) => { if (err) { reject(err) } From 9893fb4214b20b1b591658b9402a4b70d553a5d3 Mon Sep 17 00:00:00 2001 From: Jack Mallers Date: Thu, 22 Mar 2018 16:19:57 -0500 Subject: [PATCH 09/10] fix(aezeed): make aezeedPassword component stateless --- .../Onboarding/NewAezeedPassword.js | 68 +++++++++---------- .../Onboarding/NewAezeedPassword.scss | 11 +++ app/containers/Root.js | 8 ++- app/reducers/onboarding.js | 19 ++++++ 4 files changed, 70 insertions(+), 36 deletions(-) diff --git a/app/components/Onboarding/NewAezeedPassword.js b/app/components/Onboarding/NewAezeedPassword.js index 10390738..bf4412f9 100644 --- a/app/components/Onboarding/NewAezeedPassword.js +++ b/app/components/Onboarding/NewAezeedPassword.js @@ -2,45 +2,43 @@ import React from 'react' import PropTypes from 'prop-types' import styles from './NewAezeedPassword.scss' -class NewAezeedPassword extends React.Component { - constructor(props) { - super(props) - this.state = { confirmPassword: '' } - } +const NewAezeedPassword = ({ + aezeedPassword, + aezeedPasswordConfirmation, + showAezeedPasswordConfirmationError, + updateAezeedPassword, + updateAezeedPasswordConfirmation +}) => ( +
    +
    + updateAezeedPassword(event.target.value)} + /> +
    - render() { - const { aezeedPassword, updateAezeedPassword } = this.props - const { confirmPassword } = this.state - - return ( -
    -
    - updateAezeedPassword(event.target.value)} - /> -
    - -
    - this.setState({ confirmPassword: event.target.value })} - /> -
    -
    - ) - } -} +
    + updateAezeedPasswordConfirmation(event.target.value)} + /> +

    Passwords do not match

    +
    +
    +) NewAezeedPassword.propTypes = { aezeedPassword: PropTypes.string.isRequired, - updateAezeedPassword: PropTypes.func.isRequired + aezeedPasswordConfirmation: PropTypes.string.isRequired, + showAezeedPasswordConfirmationError: PropTypes.bool.isRequired, + updateAezeedPassword: PropTypes.func.isRequired, + updateAezeedPasswordConfirmation: PropTypes.func.isRequired } export default NewAezeedPassword diff --git a/app/components/Onboarding/NewAezeedPassword.scss b/app/components/Onboarding/NewAezeedPassword.scss index 1fd5fd6f..66aa30c5 100644 --- a/app/components/Onboarding/NewAezeedPassword.scss +++ b/app/components/Onboarding/NewAezeedPassword.scss @@ -22,3 +22,14 @@ text-shadow: none; -webkit-text-fill-color: initial; } + +.errorMessage { + color: $red; + margin-top: 10px; + font-size: 10px; + visibility: hidden; + + &.visible { + visibility: visible; + } +} diff --git a/app/containers/Root.js b/app/containers/Root.js index 6f1bc2b4..dc5de787 100644 --- a/app/containers/Root.js +++ b/app/containers/Root.js @@ -17,6 +17,7 @@ import { updateCreateWalletPassword, updateCreateWalletPasswordConfirmation, updateAezeedPassword, + updateAezeedPasswordConfirmation, submitNewWallet, onboardingSelectors, unlockWallet, @@ -33,6 +34,7 @@ const mapDispatchToProps = { updateCreateWalletPassword, updateCreateWalletPasswordConfirmation, updateAezeedPassword, + updateAezeedPasswordConfirmation, setAutopilot, changeStep, startLnd, @@ -53,6 +55,7 @@ const mapStateToProps = state => ({ syncPercentage: lndSelectors.syncPercentage(state), passwordIsValid: onboardingSelectors.passwordIsValid(state), showCreateWalletPasswordConfirmationError: onboardingSelectors.showCreateWalletPasswordConfirmationError(state), + showAezeedPasswordConfirmationError: onboardingSelectors.showAezeedPasswordConfirmationError(state), reEnterSeedChecker: onboardingSelectors.reEnterSeedChecker(state) }) @@ -109,7 +112,10 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { const newAezeedPasswordProps = { aezeedPassword: stateProps.onboarding.aezeedPassword, - updateAezeedPassword: dispatchProps.updateAezeedPassword + aezeedPasswordConfirmation: stateProps.onboarding.updateAezeedPasswordConfirmation, + showAezeedPasswordConfirmationError: stateProps.showAezeedPasswordConfirmationError, + updateAezeedPassword: dispatchProps.updateAezeedPassword, + updateAezeedPasswordConfirmation: dispatchProps.updateAezeedPasswordConfirmation } const reEnterSeedProps = { diff --git a/app/reducers/onboarding.js b/app/reducers/onboarding.js index 675d7369..f3a7a442 100644 --- a/app/reducers/onboarding.js +++ b/app/reducers/onboarding.js @@ -9,6 +9,7 @@ export const UPDATE_PASSWORD = 'UPDATE_PASSWORD' export const UPDATE_CREATE_WALLET_PASSWORD = 'UPDATE_CREATE_WALLET_PASSWORD' export const UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION = 'UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION' export const UPDATE_AEZEED_PASSWORD = 'UPDATE_AEZEED_PASSWORD' +export const UPDATE_AEZEED_PASSWORD_CONFIRMATION = 'UPDATE_AEZEED_PASSWORD_CONFIRMATION' export const UPDATE_SEED_INPUT = 'UPDATE_SEED_INPUT' export const CHANGE_STEP = 'CHANGE_STEP' @@ -71,6 +72,13 @@ export function updateAezeedPassword(aezeedPassword) { } } +export function updateAezeedPasswordConfirmation(aezeedPasswordConfirmation) { + return { + type: UPDATE_AEZEED_PASSWORD_CONFIRMATION, + aezeedPasswordConfirmation + } +} + export function updateSeedInput(inputSeedObj) { return { type: UPDATE_SEED_INPUT, @@ -174,6 +182,7 @@ const ACTION_HANDLERS = { [UPDATE_CREATE_WALLET_PASSWORD]: (state, { createWalletPassword }) => ({ ...state, createWalletPassword }), [UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION]: (state, { createWalletPasswordConfirmation }) => ({ ...state, createWalletPasswordConfirmation }), [UPDATE_AEZEED_PASSWORD]: (state, { aezeedPassword }) => ({ ...state, aezeedPassword }), + [UPDATE_AEZEED_PASSWORD_CONFIRMATION]: (state, { aezeedPasswordConfirmation }) => ({ ...state, aezeedPasswordConfirmation }), [UPDATE_SEED_INPUT]: (state, { inputSeedObj }) => ({ ...state, seedInput: Object.assign([], state.seedInput, { [inputSeedObj.index]: inputSeedObj }) @@ -208,6 +217,9 @@ const passwordSelector = state => state.onboarding.password const createWalletPasswordSelector = state => state.onboarding.createWalletPassword const createWalletPasswordConfirmationSelector = state => state.onboarding.createWalletPasswordConfirmation +const aezeedPasswordSelector = state => state.onboarding.aezeedPassword +const aezeedPasswordConfirmationSelector = state => state.onboarding.aezeedPasswordConfirmation + const seedSelector = state => state.onboarding.seed const seedInputSelector = state => state.onboarding.seedInput @@ -222,6 +234,12 @@ onboardingSelectors.showCreateWalletPasswordConfirmationError = createSelector( (pass1, pass2) => pass1 !== pass2 && pass2.length > 0 ) +onboardingSelectors.showAezeedPasswordConfirmationError = createSelector( + aezeedPasswordSelector, + aezeedPasswordConfirmationSelector, + (pass1, pass2) => pass1 !== pass2 && pass2.length > 0 +) + onboardingSelectors.reEnterSeedChecker = createSelector( seedSelector, seedInputSelector, @@ -251,6 +269,7 @@ const initialState = { // seed password. this is optional and used to encrypt the seed aezeedPassword: '', + aezeedPasswordConfirmation: '', unlockingWallet: false, unlockWalletError: { From ef03dccf47fc4f4be25d1874f59d208e11471416 Mon Sep 17 00:00:00 2001 From: Jack Mallers Date: Thu, 22 Mar 2018 16:21:51 -0500 Subject: [PATCH 10/10] fix(submit wallet): dont allow user to move on if the aezeedPass doesnt match the confirmation pass --- app/components/Onboarding/Onboarding.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/components/Onboarding/Onboarding.js b/app/components/Onboarding/Onboarding.js index 5f37a8a3..789c0ce8 100644 --- a/app/components/Onboarding/Onboarding.js +++ b/app/components/Onboarding/Onboarding.js @@ -131,7 +131,12 @@ const Onboarding = ({ title='Encrypt your seed' description='Totally optional, but we encourage it. Set a password that will be used to encrypt your wallet seed' // eslint-disable-line back={() => changeStep(6)} - next={() => submitNewWallet(createWalletPassword, seed, aezeedPassword)} + next={() => { + // dont allow the user to move on if the confirmation password doesnt match the original password + if (newAezeedPasswordProps.showAezeedPasswordConfirmationError) { return } + + submitNewWallet(createWalletPassword, seed, aezeedPassword) + }} >