diff --git a/lib/service/InitService.ts b/lib/service/InitService.ts index b65c717b0..82c67dd84 100644 --- a/lib/service/InitService.ts +++ b/lib/service/InitService.ts @@ -3,7 +3,7 @@ import { promises as fs } from 'fs'; import NodeKey from '../nodekey/NodeKey'; import swapErrors from '../swaps/errors'; import SwapClientManager from '../swaps/SwapClientManager'; -import { decipher } from '../utils/seedutil'; +import { decipher, generate } from '../utils/seedutil'; import errors from './errors'; interface InitService { @@ -32,7 +32,7 @@ class InitService extends EventEmitter { await this.prepareCall(); try { - const seedMnemonic = await this.swapClientManager.genSeed(); + const seedMnemonic = await generate(); // we use the deciphered seed (without the salt and extra fields that make up the enciphered seed) // to generate an xud nodekey from the same seed used for wallets diff --git a/lib/swaps/SwapClientManager.ts b/lib/swaps/SwapClientManager.ts index 1b69e0600..67c34e3fb 100644 --- a/lib/swaps/SwapClientManager.ts +++ b/lib/swaps/SwapClientManager.ts @@ -1,5 +1,5 @@ -import { promises as fs } from 'fs'; import { EventEmitter } from 'events'; +import { promises as fs } from 'fs'; import Config from '../Config'; import { SwapClientType } from '../constants/enums'; import { Models } from '../db/DB'; @@ -151,28 +151,6 @@ class SwapClientManager extends EventEmitter { } } - /** - * Generates a cryptographically random 24 word seed mnemonic from an lnd client. - */ - public genSeed = async () => { - const lndClients = this.getLndClientsMap().values(); - // loop through swap clients until we find an lnd client awaiting unlock - for (const lndClient of lndClients) { - if (lndClient.isWaitingUnlock()) { - try { - const seed = await lndClient.genSeed(); - return seed; - } catch (err) { - lndClient.logger.error('could not generate seed', err); - } - } - } - - // TODO: use seedutil tool to generate a seed instead of throwing error - // when we can't generate one with lnd - throw errors.SWAP_CLIENT_WALLET_NOT_CREATED('could not generate aezeed'); - } - /** * Initializes wallets with seed and password. */ diff --git a/lib/utils/seedutil.ts b/lib/utils/seedutil.ts index c4b0c82cf..694ada604 100644 --- a/lib/utils/seedutil.ts +++ b/lib/utils/seedutil.ts @@ -1,3 +1,4 @@ +import assert from 'assert'; import { exec as childProcessExec } from 'child_process'; import { promisify } from 'util'; @@ -49,4 +50,16 @@ async function decipher(mnemonic: string[]) { return Buffer.from(decipheredSeed, 'hex'); } -export { keystore, encipher, decipher }; +async function generate() { + const { stdout, stderr } = await exec('./seedutil/seedutil generate'); + + if (stderr) { + throw new Error(stderr); + } + + const mnemonic = stdout.trim().split(' '); + assert.equal(mnemonic.length, 24, 'seedutil did not generate mnemonic of exactly 24 words'); + return mnemonic; +} + +export { keystore, encipher, decipher, generate }; diff --git a/seedutil/SeedUtil.spec.ts b/seedutil/SeedUtil.spec.ts index 0892a023c..cc30a4584 100644 --- a/seedutil/SeedUtil.spec.ts +++ b/seedutil/SeedUtil.spec.ts @@ -132,6 +132,17 @@ describe('SeedUtil decipher', () => { }); }); +describe('SeedUtil generate', () => { + test('it prints a 24 word mnemonic which can be deciphered', async () => { + const cmd = './seedutil/seedutil generate'; + const mnemonic = await executeCommand(cmd); + expect(mnemonic.split(' ')).toHaveLength(24); + + const cmd2 = `./seedutil/seedutil decipher ${mnemonic}`; + await executeCommand(cmd2); + }); +}); + describe('SeedUtil keystore', () => { beforeEach(async () => { await deleteDir(DEFAULT_KEYSTORE_PATH); diff --git a/seedutil/main.go b/seedutil/main.go index b631d2d9d..2dbedb21d 100644 --- a/seedutil/main.go +++ b/seedutil/main.go @@ -8,6 +8,8 @@ import ( "fmt" "os" "path/filepath" + "strings" + "time" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/crypto" @@ -26,7 +28,7 @@ var ( defaultKeyStorePath = filepath.Join(filepath.Dir(os.Args[0])) ) -func parseMnemonic(words []string) aezeed.Mnemonic{ +func parseMnemonic(words []string) aezeed.Mnemonic { if len(words) < aezeed.NummnemonicWords { fmt.Fprintf(os.Stderr, "\nerror: expecting %v-word mnemonic seed separated by spaces\n", aezeed.NummnemonicWords) os.Exit(1) @@ -54,7 +56,8 @@ func mnemonicToCipherSeed(mnemonic aezeed.Mnemonic, aezeedPassphrase *string) *a func main() { keystoreCommand := flag.NewFlagSet("keystore", flag.ExitOnError) encipherCommand := flag.NewFlagSet("encipher", flag.ExitOnError) - mnemonicCommand := flag.NewFlagSet("mnemonic", flag.ExitOnError) + decipherCommand := flag.NewFlagSet("decipher", flag.ExitOnError) + generateCommand := flag.NewFlagSet("generate", flag.ExitOnError) if len(os.Args) < 2 { fmt.Println("subcommand is required") @@ -117,9 +120,9 @@ func main() { encipheredSeed, _ := cipherSeed.Encipher([]byte(*aezeedPassphrase)) fmt.Println(hex.EncodeToString(encipheredSeed[:])) case "decipher": - aezeedPassphrase := mnemonicCommand.String("aezeedpass", defaultAezeedPassphrase, "aezeed passphrase") - mnemonicCommand.Parse(os.Args[2:]) - args = mnemonicCommand.Args() + aezeedPassphrase := decipherCommand.String("aezeedpass", defaultAezeedPassphrase, "aezeed passphrase") + decipherCommand.Parse(os.Args[2:]) + args = decipherCommand.Args() mnemonic := parseMnemonic(args) decipheredSeed, err := mnemonic.Decipher([]byte(*aezeedPassphrase)) @@ -129,6 +132,23 @@ func main() { } fmt.Println(hex.EncodeToString(decipheredSeed[:])) + case "generate": + aezeedPassphrase := generateCommand.String("aezeedpass", defaultAezeedPassphrase, "aezeed passphrase") + generateCommand.Parse(os.Args[2:]) + + cipherSeed, err := aezeed.New(0, nil, time.Now()) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + mnemonic, err := cipherSeed.ToMnemonic([]byte(*aezeedPassphrase)) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + fmt.Println(strings.Join([]string(mnemonic[:]), " ")) default: flag.PrintDefaults() os.Exit(1)