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 && (
+
+
+
link.send('tray:clipboardData', this.state.exportSecret)}
+ >
+ Copy
+
+
this.setState({ exportSecret: null })}>
+ Hide
- ) : null}
- {canReconnect &&
}
-
{
- link.send('dash:removeSigner', id)
- link.send('tray:action', 'backDash')
- }}
- >
- Remove Signer
-
+ )}
)
}
diff --git a/app/dash/Signer/style/index.styl b/app/dash/Signer/style/index.styl
index 9b523132b..90495f16c 100644
--- a/app/dash/Signer/style/index.styl
+++ b/app/dash/Signer/style/index.styl
@@ -124,7 +124,7 @@
background var(--good)
.signerStatusText
- padding 0px 32px 16px 32px
+ padding 0px 0px 16px 0px
margin 0px 6px 6px 6px
border-radius 20px
font-size 12px
@@ -296,6 +296,66 @@
.signerControlOptionSuccess
color var(--good)
+.signerSecretExport
+ width calc(100% - 12px)
+ height fit-content
+ border-radius 27px
+ margin 6px
+ position absolute
+ bottom 0px
+ overflow hidden
+ background var(--ghostZ)
+ transform translate3d(0, 0, 0)
+ box-shadow 0px 2px 8px var(--ghostY)
+ color var(--outerspace)
+
+ textarea
+ width 100%
+ height 77px
+ padding 10px 20px
+ background var(--ghostA)
+ border none
+ border-radius 8px
+ outline none
+ color var(--outerspace)
+ text-align center
+ font-size 15px
+ line-height 20px
+ font-family 'FiraCode'
+ display flex
+ justify-content center
+ align-items center
+ overflow-x hidden
+ overflow-y scroll
+ resize none
+
+ textarea::selection
+ background var(--moon)
+
+ .signerSecretOption
+ height 30px
+ background var(--ghostA)
+ color var(--outerspace)
+ font-size 12px
+ box-sizing border-box
+ margin-top 4px
+ padding-top 1px
+ border-radius 8px
+ display flex
+ justify-content center
+ align-items center
+ font-weight 500
+ cursor pointer
+ width 100%
+ text-transform uppercase
+ cursor pointer
+
+ *
+ pointer-events none
+
+ .signerSecretOption:hover
+ background var(--ghostB)
+
.signerLatticePair
height 200px
@@ -467,6 +527,19 @@
svg
margin 2px 4px 0px 4px
+ .signerAccountExport
+ position absolute
+ top 50%
+ transform translateY(-50%)
+ right 30px
+ padding 5px
+ border-radius 8px
+ cursor pointer
+ pointer-events auto
+
+ .signerAccountExport:hover
+ background var(--ghostD)
+
.signerAccountCheck
position absolute
top 15px
diff --git a/main/rpc/index.js b/main/rpc/index.js
index c71f51147..36fb62d28 100644
--- a/main/rpc/index.js
+++ b/main/rpc/index.js
@@ -223,6 +223,9 @@ const rpc = {
addKeystore(id, keystore, keystorePassword, password, cb) {
signers.addKeystore(id, keystore, keystorePassword, password, cb)
},
+ getSecret(id, index, cb) {
+ signers.getSecret(id, index, cb)
+ },
unlockSigner(id, password, cb) {
signers.unlock(id, password, cb)
},
diff --git a/main/signers/hot/HotSigner/index.js b/main/signers/hot/HotSigner/index.js
index e453e4501..071d87230 100644
--- a/main/signers/hot/HotSigner/index.js
+++ b/main/signers/hot/HotSigner/index.js
@@ -56,6 +56,10 @@ class HotSigner extends Signer {
log.info('Signer erased from disk')
}
+ getSecret(index, cb) {
+ this._callWorker({ method: 'getSecret', params: { index } }, cb)
+ }
+
lock(cb) {
this._callWorker({ method: 'lock' }, () => {
this.status = 'locked'
@@ -92,7 +96,11 @@ class HotSigner extends Signer {
// Update id
this.id = derivedId
// Write to disk
- this.save({ encryptedKeys: this.encryptedKeys, encryptedSeed: this.encryptedSeed })
+ this.save({
+ encryptedKeys: this.encryptedKeys,
+ encryptedPhrase: this.encryptedPhrase,
+ encryptedSeed: this.encryptedSeed
+ })
} else if (this.id !== derivedId) {
// On changed ID
// Erase from disk
@@ -102,7 +110,11 @@ class HotSigner extends Signer {
// Update id
this.id = derivedId
// Write to disk
- this.save({ encryptedKeys: this.encryptedKeys, encryptedSeed: this.encryptedSeed })
+ this.save({
+ encryptedKeys: this.encryptedKeys,
+ encryptedPhrase: this.encryptedPhrase,
+ encryptedSeed: this.encryptedSeed
+ })
}
store.updateSigner(this.summary())
diff --git a/main/signers/hot/RingSigner/worker.js b/main/signers/hot/RingSigner/worker.js
index 31762a6a2..65469ac12 100644
--- a/main/signers/hot/RingSigner/worker.js
+++ b/main/signers/hot/RingSigner/worker.js
@@ -7,6 +7,10 @@ class RingSignerWorker extends HotSignerWorker {
process.on('message', (message) => this.handleMessage(message))
}
+ getSecret({ index }, pseudoCallback) {
+ pseudoCallback(null, this.keys[index].toString('hex'))
+ }
+
unlock({ encryptedKeys, password }, pseudoCallback) {
try {
this.keys = this._decrypt(encryptedKeys, password)
diff --git a/main/signers/hot/SeedSigner/index.js b/main/signers/hot/SeedSigner/index.js
index b110ddc42..42ec7da60 100644
--- a/main/signers/hot/SeedSigner/index.js
+++ b/main/signers/hot/SeedSigner/index.js
@@ -9,6 +9,7 @@ const WORKER_PATH = path.resolve(__dirname, 'worker.js')
class SeedSigner extends HotSigner {
constructor(signer) {
super(signer, WORKER_PATH)
+ this.encryptedPhrase = signer && signer.encryptedPhrase
this.encryptedSeed = signer && signer.encryptedSeed
this.type = 'seed'
this.model = 'phrase'
@@ -42,18 +43,29 @@ class SeedSigner extends HotSigner {
async addPhrase(phrase, password, cb) {
// Validate phrase
if (!bip39.validateMnemonic(phrase)) return cb(new Error('Invalid mnemonic phrase'))
- // Get seed
- const seed = await bip39.mnemonicToSeed(phrase)
- // Add seed to signer
- this.addSeed(seed.toString('hex'), password, cb)
+ // Add phrase to signer
+ this._callWorker(
+ { method: 'encryptPhrase', params: { phrase, password } },
+ async (err, encryptedPhrase) => {
+ if (err) return cb(err)
+
+ // Update signer
+ this.encryptedPhrase = encryptedPhrase
+
+ // Get seed
+ const seed = await bip39.mnemonicToSeed(phrase)
+ // Add seed to signer
+ this.addSeed(seed.toString('hex'), password, cb)
+ }
+ )
}
save() {
- super.save({ encryptedSeed: this.encryptedSeed })
+ super.save({ encryptedPhrase: this.encryptedPhrase, encryptedSeed: this.encryptedSeed })
}
unlock(password, cb) {
- super.unlock(password, { encryptedSeed: this.encryptedSeed }, cb)
+ super.unlock(password, { encryptedPhrase: this.encryptedPhrase, encryptedSeed: this.encryptedSeed }, cb)
}
}
diff --git a/main/signers/hot/SeedSigner/worker.js b/main/signers/hot/SeedSigner/worker.js
index 4245411bf..c1645a649 100644
--- a/main/signers/hot/SeedSigner/worker.js
+++ b/main/signers/hot/SeedSigner/worker.js
@@ -4,12 +4,16 @@ const HotSignerWorker = require('../HotSigner/worker')
class SeedSignerWorker extends HotSignerWorker {
constructor() {
super()
+ this.phrase = null
this.seed = null
process.on('message', (message) => this.handleMessage(message))
}
- unlock({ encryptedSeed, password }, pseudoCallback) {
+ unlock({ encryptedPhrase, encryptedSeed, password }, pseudoCallback) {
try {
+ this.phrase = encryptedPhrase
+ ? this._decrypt(encryptedPhrase, password)
+ : 'The seed phrase for this signer is unknown'
this.seed = this._decrypt(encryptedSeed, password)
pseudoCallback(null)
} catch (e) {
@@ -17,11 +21,20 @@ class SeedSignerWorker extends HotSignerWorker {
}
}
+ getSecret({ index }, pseudoCallback) {
+ pseudoCallback(null, index === null ? this.phrase : this._derivePrivateKey(index).toString('hex'))
+ }
+
lock(_, pseudoCallback) {
+ this.phrase = null
this.seed = null
pseudoCallback(null)
}
+ encryptPhrase({ phrase, password }, pseudoCallback) {
+ pseudoCallback(null, this._encrypt(phrase, password))
+ }
+
encryptSeed({ seed, password }, pseudoCallback) {
pseudoCallback(null, this._encrypt(seed.toString('hex'), password))
}
diff --git a/main/signers/hot/index.js b/main/signers/hot/index.js
index 71642cae3..ad59f5938 100644
--- a/main/signers/hot/index.js
+++ b/main/signers/hot/index.js
@@ -107,12 +107,12 @@ module.exports = {
// Add stored signers
for (const id of Object.keys(storedSigners)) {
await wait(100)
- const { addresses, encryptedKeys, encryptedSeed, type, network } = storedSigners[id]
+ const { addresses, encryptedKeys, encryptedPhrase, encryptedSeed, type, network } = storedSigners[id]
if (addresses && addresses.length) {
const id = crypt.stringToKey(addresses.join()).toString('hex')
if (!signers.exists(id)) {
if (type === 'seed') {
- signers.add(new SeedSigner({ network, addresses, encryptedSeed }))
+ signers.add(new SeedSigner({ network, addresses, encryptedPhrase, encryptedSeed }))
} else if (type === 'ring') {
signers.add(new RingSigner({ network, addresses, encryptedKeys }))
}
diff --git a/main/signers/index.ts b/main/signers/index.ts
index ff12c9be3..5ec9d81b5 100644
--- a/main/signers/index.ts
+++ b/main/signers/index.ts
@@ -212,6 +212,17 @@ class Signers extends EventEmitter {
;(signer as RingSigner).addKeystore(keystore, keystorePassword, password, cb)
}
+ getSecret(id: string, index: number, cb: Callback
) {
+ const signer = this.get(id)
+
+ // @ts-ignore
+ if (signer && signer.getSecret) {
+ ;(signer as HotSigner).getSecret(index, cb)
+ } else {
+ log.error('Signer has no secret stored locally, no getSecret method')
+ }
+ }
+
lock(id: string, cb: Callback) {
const signer = this.get(id)
diff --git a/resources/svg/index.js b/resources/svg/index.js
index dd51f1080..156bfc649 100644
--- a/resources/svg/index.js
+++ b/resources/svg/index.js
@@ -765,6 +765,29 @@ export default {