From e255f0528a50911d6fd27ec4e2f05ca43a317795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=A9o=20GRIVAULT?= Date: Sat, 16 Sep 2023 16:19:38 +0200 Subject: [PATCH] add option to export seed phrase or private key for hot signers --- app/dash/Signer/SignerStatus/index.js | 12 +- app/dash/Signer/index.js | 257 ++++++++++++++++---------- app/dash/Signer/style/index.styl | 75 +++++++- main/rpc/index.js | 3 + main/signers/hot/HotSigner/index.js | 16 +- main/signers/hot/RingSigner/worker.js | 4 + main/signers/hot/SeedSigner/index.js | 24 ++- main/signers/hot/SeedSigner/worker.js | 15 +- main/signers/hot/index.js | 4 +- main/signers/index.ts | 11 ++ resources/svg/index.js | 23 +++ 11 files changed, 333 insertions(+), 111 deletions(-) diff --git a/app/dash/Signer/SignerStatus/index.js b/app/dash/Signer/SignerStatus/index.js index 9e46cb415..d6c83da65 100644 --- a/app/dash/Signer/SignerStatus/index.js +++ b/app/dash/Signer/SignerStatus/index.js @@ -35,6 +35,10 @@ class SignerStatus extends React.Component { unlockSubmit() { link.rpc('unlockSigner', this.props.signer.id, this.state.unlockInput, (err) => { if (err) this.shake() + else { + if (this.props.exportCb) this.props.exportCb() + this.setState({ unlockInput: '' }) + } }) } @@ -84,7 +88,7 @@ class SignerStatus extends React.Component { const signer = this.props.signer || {} - return !isHardwareSigner(signer) && signer.id && signer.status === 'locked' ? ( + return !isHardwareSigner(signer) && signer.id && (signer.status === 'locked' || this.props.exportCb) ? (
@@ -103,9 +107,11 @@ class SignerStatus extends React.Component { } }} /> -
{'Enter password to unlock'}
+
+ {'Enter password to ' + (this.props.exportCb ? 'export' : 'unlock')} +
- {'Unlock'} + {this.props.exportCb ? 'Export' : 'Unlock'}
diff --git a/app/dash/Signer/index.js b/app/dash/Signer/index.js index 8d19d2cb9..1a6dec7a8 100644 --- a/app/dash/Signer/index.js +++ b/app/dash/Signer/index.js @@ -23,7 +23,10 @@ class Signer extends React.Component { addressLimit: 5, latticePairCode: '', tPin: '', - tPhrase: '' + tPhrase: '', + exportIndex: null, + exportCb: null, + exportSecret: null } } @@ -202,7 +205,15 @@ class Signer extends React.Component { statusText() { const status = this.getStatus() - if (status === 'ok') { + if (this.state.exportCb) { + const signer = this.store('main.signers', this.props.id) + return ( +
+ {'exporting ' + + (this.state.exportIndex === null ? 'seed phrase' : signer.addresses[this.state.exportIndex])} +
+ ) + } else if (status === 'ok') { return
{'ready to sign'}
} else if (status === 'locked') { const hwSigner = isHardwareSigner(this.props.type) @@ -350,7 +361,13 @@ class Signer extends React.Component { renderSignerStatus() { const signer = this.store('main.signers', this.props.id) - return + return + } + + showSecret() { + link.rpc('getSecret', this.props.id, this.state.exportIndex, (err, secret) => { + if (!err) this.setState({ exportSecret: secret, exportCb: null }) + }) } renderExpanded() { @@ -375,108 +392,156 @@ class Signer extends React.Component { const zIndex = 1000 - index return ( -
- {
} - {this.statusText()} - {type === 'lattice' && status === 'pair' ? ( -
-
Please input your Lattice's pairing code
-
- this.setState({ latticePairCode: (e.target.value || '').toUpperCase() })} - onKeyPress={(e) => { - if (e.key === 'Enter') this.pairToLattice() - }} - /> -
-
this.pairToLattice()} className='signerLatticePairSubmit'> - Pair +
+
+ {
} + {this.statusText()} + {type === 'lattice' && status === 'pair' ? ( +
+
Please input your Lattice's pairing code
+
+ this.setState({ latticePairCode: (e.target.value || '').toUpperCase() })} + onKeyPress={(e) => { + if (e.key === 'Enter') this.pairToLattice() + }} + /> +
+
this.pairToLattice()} className='signerLatticePairSubmit'> + Pair +
-
- ) : status === 'ok' || isLocked ? ( - <> - {this.renderSignerStatus()} -
{'available accounts'}
-
- {signer.addresses.slice(startIndex, startIndex + addressLimit).map((address, index) => { - const added = this.store('main.accounts', address.toLowerCase()) - const checkSummedAddress = getAddress(address) - return ( -
{ - if (this.store('main.accounts', address.toLowerCase())) { - link.rpc('removeAccount', address, {}, () => {}) - } else { - const type = getSignerDisplayType(signer) - link.rpc( - 'createAccount', - address, - `${capitalize(type)} Account`, - { type: signer.type }, - (e) => { - if (e) console.error(e) - } - ) - } - }} - > -
{index + 1 + startIndex}
-
- {checkSummedAddress.substr(0, 11)} {svg.octicon('kebab-horizontal', { height: 20 })}{' '} - {checkSummedAddress.substr(address.length - 10)} + ) : status === 'ok' || isLocked ? ( + <> + {this.renderSignerStatus()} +
{'available accounts'}
+
+ {signer.addresses.slice(startIndex, startIndex + addressLimit).map((address, index) => { + const added = this.store('main.accounts', address.toLowerCase()) + const checkSummedAddress = getAddress(address) + return ( +
{ + if (this.store('main.accounts', address.toLowerCase())) { + link.rpc('removeAccount', address, {}, () => {}) + } else { + const type = getSignerDisplayType(signer) + link.rpc( + 'createAccount', + address, + `${capitalize(type)} Account`, + { type: signer.type }, + (e) => { + if (e) console.error(e) + } + ) + } + }} + > +
{index + 1 + startIndex}
+
+ {checkSummedAddress.substr(0, 11)} {svg.octicon('kebab-horizontal', { height: 20 })}{' '} + {checkSummedAddress.substr(address.length - 10)} +
+ {status === 'ok' && ( +
{ + e.stopPropagation() + if (this.state.exportCb && this.state.exportIndex === index + startIndex) { + this.setState({ exportCb: null }) + } else { + this.setState({ + exportIndex: index + startIndex, + exportCb: this.showSecret.bind(this) + }) + } + }} + > + {svg.export(15)} +
+ )} +
-
-
- ) - })} -
-
-
this.nextPage(true)}> - {svg.triangleLeft(20)} + ) + })} +
+
+
this.nextPage(true)}> + {svg.triangleLeft(20)} +
+
+ {page + 1 + ' / ' + Math.ceil(signer.addresses.length / addressLimit)} +
+
this.nextPage()}> + {svg.triangleLeft(20)} +
-
- {page + 1 + ' / ' + Math.ceil(signer.addresses.length / addressLimit)} + + ) : type === 'trezor' && (status === 'need pin' || status === 'enter passphrase') ? ( +
+ {this.renderTrezorPin(this.props.type === 'trezor' && status === 'need pin')} + {this.renderTrezorPhrase(this.props.type === 'trezor' && status === 'enter passphrase')} +
+ ) : loading ? ( +
+
+
+ ) : ( + <> + )} +
+ {permissionId ? ( +
+
{'PERMISSION ID:'}
+
{permissionId}
-
this.nextPage()}> - {svg.triangleLeft(20)} + ) : null} + {canReconnect && } + {this.props.type === 'seed' && status === 'ok' && ( +
{ + if (this.state.exportCb && this.state.exportIndex === null) { + this.setState({ exportCb: null }) + } else { + this.setState({ exportIndex: null, exportCb: this.showSecret.bind(this) }) + } + }} + > + Export Seed Phrase
+ )} +
{ + link.send('dash:removeSigner', id) + link.send('tray:action', 'backDash') + }} + > + Remove Signer
- - ) : type === 'trezor' && (status === 'need pin' || status === 'enter passphrase') ? ( -
- {this.renderTrezorPin(this.props.type === 'trezor' && status === 'need pin')} - {this.renderTrezorPhrase(this.props.type === 'trezor' && status === 'enter passphrase')} -
- ) : loading ? ( -
-
- ) : ( - <> - )} -
- {permissionId ? ( -
-
{'PERMISSION ID:'}
-
{permissionId}
+
+ {this.state.exportSecret && ( +
+