From 4357adb444b7dc0df8c07eb3cdf4ce4418f7b286 Mon Sep 17 00:00:00 2001 From: Daniel McNally Date: Thu, 21 Nov 2019 05:43:59 -0500 Subject: [PATCH] feat(rpc): RestoreNode call to restore from seed This adds a new `RestoreNode` call that accepts a 24 word seed mnemonic to restore an xud key, lnd wallets, and a raiden keystore from the seed. This call can be extended to also restore lnd channel backups and xud db backups with additional parameters. The seedutil tool is extended to support an `encipher` subcommand that converts a mnemonic to the enciphered seed bytes, which is necessary for deriving an xud key from aezeed. Closes #1017. Closes #1020. --- docs/api.md | 35 ++ lib/Xud.ts | 3 +- lib/cli/commands/create.ts | 36 +- lib/cli/commands/restore.ts | 86 +++++ lib/cli/utils.ts | 30 ++ lib/grpc/GrpcInitService.ts | 27 +- lib/proto/xudrpc.swagger.json | 17 + lib/proto/xudrpc_grpc_pb.d.ts | 17 + lib/proto/xudrpc_grpc_pb.js | 34 ++ lib/proto/xudrpc_pb.d.ts | 54 +++ lib/proto/xudrpc_pb.js | 384 +++++++++++++++++++ lib/service/InitService.ts | 143 ++++--- lib/swaps/SwapClientManager.ts | 6 +- lib/utils/seedutil.ts | 27 +- proto/xudrpc.proto | 17 + seedutil/README.md | 10 +- seedutil/SeedUtil.spec.ts | 47 ++- seedutil/__snapshots__/SeedUtil.spec.ts.snap | 11 + seedutil/go.mod | 12 +- seedutil/go.sum | 103 +---- seedutil/main.go | 96 +++-- 21 files changed, 948 insertions(+), 247 deletions(-) create mode 100644 lib/cli/commands/restore.ts create mode 100644 seedutil/__snapshots__/SeedUtil.spec.ts.snap diff --git a/docs/api.md b/docs/api.md index 83f4e06cb..ecc1cf5f6 100644 --- a/docs/api.md +++ b/docs/api.md @@ -59,6 +59,8 @@ - [RemoveOrderResponse](#xudrpc.RemoveOrderResponse) - [RemovePairRequest](#xudrpc.RemovePairRequest) - [RemovePairResponse](#xudrpc.RemovePairResponse) + - [RestoreNodeRequest](#xudrpc.RestoreNodeRequest) + - [RestoreNodeResponse](#xudrpc.RestoreNodeResponse) - [ShutdownRequest](#xudrpc.ShutdownRequest) - [ShutdownResponse](#xudrpc.ShutdownResponse) - [SubscribeOrdersRequest](#xudrpc.SubscribeOrdersRequest) @@ -947,6 +949,38 @@ + + +### RestoreNodeRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| seed_mnemonic | [string](#string) | repeated | The 24 word mnemonic to recover the xud identity key and underlying wallets | +| password | [string](#string) | | The password in utf-8 with which to encrypt the restored xud node key as well as any restored underlying wallets. | + + + + + + + + +### RestoreNodeResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| restored_lnds | [string](#string) | repeated | The list of lnd clients that were initialized. | +| restored_raiden | [bool](#bool) | | Whether raiden was initialized. | + + + + + + ### ShutdownRequest @@ -1268,6 +1302,7 @@ | ----------- | ------------ | ------------- | ------------| | CreateNode | [CreateNodeRequest](#xudrpc.CreateNodeRequest) | [CreateNodeResponse](#xudrpc.CreateNodeResponse) | Creates an xud identity node key and underlying wallets. The node key and wallets are derived from a single seed and encrypted using a single password provided as a parameter to the call. | | UnlockNode | [UnlockNodeRequest](#xudrpc.UnlockNodeRequest) | [UnlockNodeResponse](#xudrpc.UnlockNodeResponse) | Unlocks and decrypts the xud node key and any underlying wallets. | +| RestoreNode | [RestoreNodeRequest](#xudrpc.RestoreNodeRequest) | [RestoreNodeResponse](#xudrpc.RestoreNodeResponse) | Restores an xud instance and underlying wallets from a seed. | diff --git a/lib/Xud.ts b/lib/Xud.ts index df9c8d83c..b86991ce7 100644 --- a/lib/Xud.ts +++ b/lib/Xud.ts @@ -120,8 +120,7 @@ class Xud extends EventEmitter { const initService = new InitService(this.swapClientManager, nodeKeyPath, nodeKeyExists); this.rpcServer.grpcInitService.setInitService(initService); - this.logger.info("Node key is encrypted, unlock using 'xucli unlock' or set password using" + - " 'xucli create' if this is the first time starting xud"); + this.logger.info("Node key is encrypted, unlock with 'xucli unlock', 'xucli create', or 'xucli restore'"); nodeKey = await new Promise((resolve) => { initService.once('nodekey', resolve); this.on('shutdown', () => { diff --git a/lib/cli/commands/create.ts b/lib/cli/commands/create.ts index a29c892ed..01479023a 100644 --- a/lib/cli/commands/create.ts +++ b/lib/cli/commands/create.ts @@ -1,47 +1,15 @@ -import { accessSync, watch } from 'fs'; -import path from 'path'; import readline from 'readline'; import { Arguments } from 'yargs'; import { CreateNodeRequest, CreateNodeResponse } from '../../proto/xudrpc_pb'; import { callback, loadXudInitClient } from '../command'; -import { getDefaultCertPath } from '../utils'; +import { getDefaultCertPath, waitForCert } from '../utils'; export const command = 'create'; -export const describe = 'use this to create a new xud instance and set a password'; +export const describe = 'create a new xud instance and set a password'; export const builder = {}; -const waitForCert = (certPath: string) => { - return new Promise((resolve, reject) => { - try { - accessSync(certPath); - resolve(); - } catch (err) { - if (err.code === 'ENOENT') { - // wait up to 5 seconds for the tls.cert file to be created in case - // this is the first time xud has been run - const certDir = path.dirname(certPath); - const certFilename = path.basename(certPath); - const fsWatcher = watch(certDir, (event, filename) => { - if (event === 'change' && filename === certFilename) { - clearTimeout(timeout); - fsWatcher.close(); - resolve(); - } - }); - const timeout = setTimeout(() => { - fsWatcher.close(); - reject(`timed out waiting for cert to be created at ${certPath}`); - }, 5000); - } else { - // we handle errors due to file not existing, otherwise reject - reject(err); - } - } - }); -}; - const formatOutput = (response: CreateNodeResponse.AsObject) => { if (response.seedMnemonicList.length === 24) { const WORDS_PER_ROW = 4; diff --git a/lib/cli/commands/restore.ts b/lib/cli/commands/restore.ts new file mode 100644 index 000000000..2811d6899 --- /dev/null +++ b/lib/cli/commands/restore.ts @@ -0,0 +1,86 @@ +import readline from 'readline'; +import { Arguments } from 'yargs'; +import { RestoreNodeRequest, RestoreNodeResponse } from '../../proto/xudrpc_pb'; +import { callback, loadXudInitClient } from '../command'; +import { getDefaultCertPath, waitForCert } from '../utils'; + +export const command = 'restore'; + +export const describe = 'restore an xud instance from seed'; + +export const builder = {}; + +const formatOutput = (response: RestoreNodeResponse.AsObject) => { + let walletRestoredMessage = 'The following wallets were restored: '; + + if (response.restoredLndsList.length) { + walletRestoredMessage += response.restoredLndsList.join(', '); + } + + if (response.restoredRaiden) { + if (!walletRestoredMessage.endsWith(' ')) { + walletRestoredMessage += ', '; + } + + walletRestoredMessage += 'ERC20(ETH)'; + } + + console.log(walletRestoredMessage); +}; + +export const handler = (argv: Arguments) => { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: true, + }); + + console.log(` +You are restoring an xud node key and underlying wallets. All will be secured by +a single password provided below. + `); + rl.question('Enter your 24 word mnemonic separated by spaces: ', (mnemonicStr) => { + rl.close(); + const rlQuiet = readline.createInterface({ + input: process.stdin, + terminal: true, + }); + const mnemonic = mnemonicStr.split(' '); + if (mnemonic.length !== 24) { + console.error('Mnemonic must be exactly 24 words'); + process.exitCode = 1; + return; + } + process.stdout.write('Enter a password: '); + rlQuiet.question('', (password1) => { + process.stdout.write('\nRe-enter password: '); + rlQuiet.question('', async (password2) => { + process.stdout.write('\n\n'); + rlQuiet.close(); + if (password1 === password2) { + const request = new RestoreNodeRequest(); + request.setPassword(password1); + request.setSeedMnemonicList(mnemonic); + + const certPath = argv.tlscertpath ? argv.tlscertpath : getDefaultCertPath(); + try { + await waitForCert(certPath); + } catch (err) { + console.error(err); + process.exitCode = 1; + return; + } + + const client = loadXudInitClient(argv); + // wait up to 3 seconds for rpc server to listen before call in case xud was just started + client.waitForReady(Date.now() + 3000, () => { + client.restoreNode(request, callback(argv, formatOutput)); + }); + } else { + process.exitCode = 1; + console.error('Passwords do not match, please try again'); + } + }); + }); + }); +}; diff --git a/lib/cli/utils.ts b/lib/cli/utils.ts index 6c286a7ee..0535e9aee 100644 --- a/lib/cli/utils.ts +++ b/lib/cli/utils.ts @@ -1,4 +1,5 @@ import colors from 'colors/safe'; +import { accessSync, watch } from 'fs'; import os from 'os'; import path from 'path'; @@ -36,3 +37,32 @@ export const coinsToSats = (coinsQuantity: number) => { export const satsToCoinsStr = (satsQuantity: number) => { return (satsQuantity / SATOSHIS_PER_COIN).toFixed(8).replace(/\.?0+$/, ''); }; + +/** Waits up to 5 seconds for the tls.cert file to be created in case this is the first time xud has been run. */ +export const waitForCert = (certPath: string) => { + return new Promise((resolve, reject) => { + try { + accessSync(certPath); + resolve(); + } catch (err) { + if (err.code === 'ENOENT') { + const certDir = path.dirname(certPath); + const certFilename = path.basename(certPath); + const fsWatcher = watch(certDir, (event, filename) => { + if (event === 'change' && filename === certFilename) { + clearTimeout(timeout); + fsWatcher.close(); + resolve(); + } + }); + const timeout = setTimeout(() => { + fsWatcher.close(); + reject(`timed out waiting for cert to be created at ${certPath}`); + }, 5000); + } else { + // we handle errors due to file not existing, otherwise reject + reject(err); + } + } + }); +}; diff --git a/lib/grpc/GrpcInitService.ts b/lib/grpc/GrpcInitService.ts index a17a4a0c2..9e87d8117 100644 --- a/lib/grpc/GrpcInitService.ts +++ b/lib/grpc/GrpcInitService.ts @@ -1,6 +1,6 @@ /* tslint:disable no-null-keyword */ import grpc, { status } from 'grpc'; -import InitService from 'lib/service/InitService'; +import InitService from '../service/InitService'; import * as xudrpc from '../proto/xudrpc_pb'; import getGrpcError from './getGrpcError'; @@ -44,16 +44,13 @@ class GrpcInitService { if (mnemonic) { response.setSeedMnemonicList(mnemonic); } - if (initializedLndWallets) { - response.setInitializedLndsList(initializedLndWallets); - } + response.setInitializedLndsList(initializedLndWallets); response.setInitializedRaiden(initializedRaiden); callback(null, response); } catch (err) { callback(getGrpcError(err), null); } - this.initService.pendingCall = false; } /** @@ -73,7 +70,25 @@ class GrpcInitService { } catch (err) { callback(getGrpcError(err), null); } - this.initService.pendingCall = false; + } + + /** + * See [[InitService.restoreNode]] + */ + public restoreNode: grpc.handleUnaryCall = async (call, callback) => { + if (!this.isReady(this.initService, callback)) { + return; + } + try { + const { restoredLndWallets, restoredRaiden } = await this.initService.restoreNode(call.request.toObject()); + const response = new xudrpc.RestoreNodeResponse(); + response.setRestoredLndsList(restoredLndWallets); + response.setRestoredRaiden(restoredRaiden); + + callback(null, response); + } catch (err) { + callback(getGrpcError(err), null); + } } } diff --git a/lib/proto/xudrpc.swagger.json b/lib/proto/xudrpc.swagger.json index 81ff40f0c..43db03f95 100644 --- a/lib/proto/xudrpc.swagger.json +++ b/lib/proto/xudrpc.swagger.json @@ -1425,6 +1425,23 @@ "xudrpcRemovePairResponse": { "type": "object" }, + "xudrpcRestoreNodeResponse": { + "type": "object", + "properties": { + "restored_lnds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of lnd clients that were initialized." + }, + "restored_raiden": { + "type": "boolean", + "format": "boolean", + "description": "Whether raiden was initialized." + } + } + }, "xudrpcShutdownRequest": { "type": "object" }, diff --git a/lib/proto/xudrpc_grpc_pb.d.ts b/lib/proto/xudrpc_grpc_pb.d.ts index b139fc94c..5777da905 100644 --- a/lib/proto/xudrpc_grpc_pb.d.ts +++ b/lib/proto/xudrpc_grpc_pb.d.ts @@ -10,6 +10,7 @@ import * as annotations_pb from "./annotations_pb"; interface IXudInitService extends grpc.ServiceDefinition { createNode: IXudInitService_ICreateNode; unlockNode: IXudInitService_IUnlockNode; + restoreNode: IXudInitService_IRestoreNode; } interface IXudInitService_ICreateNode extends grpc.MethodDefinition { @@ -30,12 +31,22 @@ interface IXudInitService_IUnlockNode extends grpc.MethodDefinition; responseDeserialize: grpc.deserialize; } +interface IXudInitService_IRestoreNode extends grpc.MethodDefinition { + path: string; // "/xudrpc.XudInit/RestoreNode" + requestStream: boolean; // false + responseStream: boolean; // false + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} export const XudInitService: IXudInitService; export interface IXudInitServer { createNode: grpc.handleUnaryCall; unlockNode: grpc.handleUnaryCall; + restoreNode: grpc.handleUnaryCall; } export interface IXudInitClient { @@ -45,6 +56,9 @@ export interface IXudInitClient { unlockNode(request: xudrpc_pb.UnlockNodeRequest, callback: (error: grpc.ServiceError | null, response: xudrpc_pb.UnlockNodeResponse) => void): grpc.ClientUnaryCall; unlockNode(request: xudrpc_pb.UnlockNodeRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: xudrpc_pb.UnlockNodeResponse) => void): grpc.ClientUnaryCall; unlockNode(request: xudrpc_pb.UnlockNodeRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: xudrpc_pb.UnlockNodeResponse) => void): grpc.ClientUnaryCall; + restoreNode(request: xudrpc_pb.RestoreNodeRequest, callback: (error: grpc.ServiceError | null, response: xudrpc_pb.RestoreNodeResponse) => void): grpc.ClientUnaryCall; + restoreNode(request: xudrpc_pb.RestoreNodeRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: xudrpc_pb.RestoreNodeResponse) => void): grpc.ClientUnaryCall; + restoreNode(request: xudrpc_pb.RestoreNodeRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: xudrpc_pb.RestoreNodeResponse) => void): grpc.ClientUnaryCall; } export class XudInitClient extends grpc.Client implements IXudInitClient { @@ -55,6 +69,9 @@ export class XudInitClient extends grpc.Client implements IXudInitClient { public unlockNode(request: xudrpc_pb.UnlockNodeRequest, callback: (error: grpc.ServiceError | null, response: xudrpc_pb.UnlockNodeResponse) => void): grpc.ClientUnaryCall; public unlockNode(request: xudrpc_pb.UnlockNodeRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: xudrpc_pb.UnlockNodeResponse) => void): grpc.ClientUnaryCall; public unlockNode(request: xudrpc_pb.UnlockNodeRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: xudrpc_pb.UnlockNodeResponse) => void): grpc.ClientUnaryCall; + public restoreNode(request: xudrpc_pb.RestoreNodeRequest, callback: (error: grpc.ServiceError | null, response: xudrpc_pb.RestoreNodeResponse) => void): grpc.ClientUnaryCall; + public restoreNode(request: xudrpc_pb.RestoreNodeRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: xudrpc_pb.RestoreNodeResponse) => void): grpc.ClientUnaryCall; + public restoreNode(request: xudrpc_pb.RestoreNodeRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: xudrpc_pb.RestoreNodeResponse) => void): grpc.ClientUnaryCall; } interface IXudService extends grpc.ServiceDefinition { diff --git a/lib/proto/xudrpc_grpc_pb.js b/lib/proto/xudrpc_grpc_pb.js index 5684b8b6a..1da8148e0 100644 --- a/lib/proto/xudrpc_grpc_pb.js +++ b/lib/proto/xudrpc_grpc_pb.js @@ -477,6 +477,28 @@ function deserialize_xudrpc_RemovePairResponse(buffer_arg) { return xudrpc_pb.RemovePairResponse.deserializeBinary(new Uint8Array(buffer_arg)); } +function serialize_xudrpc_RestoreNodeRequest(arg) { + if (!(arg instanceof xudrpc_pb.RestoreNodeRequest)) { + throw new Error('Expected argument of type xudrpc.RestoreNodeRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_xudrpc_RestoreNodeRequest(buffer_arg) { + return xudrpc_pb.RestoreNodeRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_xudrpc_RestoreNodeResponse(arg) { + if (!(arg instanceof xudrpc_pb.RestoreNodeResponse)) { + throw new Error('Expected argument of type xudrpc.RestoreNodeResponse'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_xudrpc_RestoreNodeResponse(buffer_arg) { + return xudrpc_pb.RestoreNodeResponse.deserializeBinary(new Uint8Array(buffer_arg)); +} + function serialize_xudrpc_ShutdownRequest(arg) { if (!(arg instanceof xudrpc_pb.ShutdownRequest)) { throw new Error('Expected argument of type xudrpc.ShutdownRequest'); @@ -637,6 +659,18 @@ var XudInitService = exports.XudInitService = { responseSerialize: serialize_xudrpc_UnlockNodeResponse, responseDeserialize: deserialize_xudrpc_UnlockNodeResponse, }, + // Restores an xud instance and underlying wallets from a seed. + restoreNode: { + path: '/xudrpc.XudInit/RestoreNode', + requestStream: false, + responseStream: false, + requestType: xudrpc_pb.RestoreNodeRequest, + responseType: xudrpc_pb.RestoreNodeResponse, + requestSerialize: serialize_xudrpc_RestoreNodeRequest, + requestDeserialize: deserialize_xudrpc_RestoreNodeRequest, + responseSerialize: serialize_xudrpc_RestoreNodeResponse, + responseDeserialize: deserialize_xudrpc_RestoreNodeResponse, + }, }; exports.XudInitClient = grpc.makeGenericClientConstructor(XudInitService); diff --git a/lib/proto/xudrpc_pb.d.ts b/lib/proto/xudrpc_pb.d.ts index febef4175..a873e207a 100644 --- a/lib/proto/xudrpc_pb.d.ts +++ b/lib/proto/xudrpc_pb.d.ts @@ -114,6 +114,60 @@ export namespace UnlockNodeResponse { } } +export class RestoreNodeRequest extends jspb.Message { + clearSeedMnemonicList(): void; + getSeedMnemonicList(): Array; + setSeedMnemonicList(value: Array): void; + addSeedMnemonic(value: string, index?: number): string; + + getPassword(): string; + setPassword(value: string): void; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): RestoreNodeRequest.AsObject; + static toObject(includeInstance: boolean, msg: RestoreNodeRequest): RestoreNodeRequest.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: RestoreNodeRequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): RestoreNodeRequest; + static deserializeBinaryFromReader(message: RestoreNodeRequest, reader: jspb.BinaryReader): RestoreNodeRequest; +} + +export namespace RestoreNodeRequest { + export type AsObject = { + seedMnemonicList: Array, + password: string, + } +} + +export class RestoreNodeResponse extends jspb.Message { + clearRestoredLndsList(): void; + getRestoredLndsList(): Array; + setRestoredLndsList(value: Array): void; + addRestoredLnds(value: string, index?: number): string; + + getRestoredRaiden(): boolean; + setRestoredRaiden(value: boolean): void; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): RestoreNodeResponse.AsObject; + static toObject(includeInstance: boolean, msg: RestoreNodeResponse): RestoreNodeResponse.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: RestoreNodeResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): RestoreNodeResponse; + static deserializeBinaryFromReader(message: RestoreNodeResponse, reader: jspb.BinaryReader): RestoreNodeResponse; +} + +export namespace RestoreNodeResponse { + export type AsObject = { + restoredLndsList: Array, + restoredRaiden: boolean, + } +} + export class Currency extends jspb.Message { getCurrency(): string; setCurrency(value: string): void; diff --git a/lib/proto/xudrpc_pb.js b/lib/proto/xudrpc_pb.js index 815871305..534917ce0 100644 --- a/lib/proto/xudrpc_pb.js +++ b/lib/proto/xudrpc_pb.js @@ -66,6 +66,8 @@ goog.exportSymbol('proto.xudrpc.RemoveOrderRequest', null, global); goog.exportSymbol('proto.xudrpc.RemoveOrderResponse', null, global); goog.exportSymbol('proto.xudrpc.RemovePairRequest', null, global); goog.exportSymbol('proto.xudrpc.RemovePairResponse', null, global); +goog.exportSymbol('proto.xudrpc.RestoreNodeRequest', null, global); +goog.exportSymbol('proto.xudrpc.RestoreNodeResponse', null, global); goog.exportSymbol('proto.xudrpc.ShutdownRequest', null, global); goog.exportSymbol('proto.xudrpc.ShutdownResponse', null, global); goog.exportSymbol('proto.xudrpc.SubscribeOrdersRequest', null, global); @@ -832,6 +834,388 @@ proto.xudrpc.UnlockNodeResponse.prototype.clearLockedLndsList = function() { +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.xudrpc.RestoreNodeRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.xudrpc.RestoreNodeRequest.repeatedFields_, null); +}; +goog.inherits(proto.xudrpc.RestoreNodeRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.xudrpc.RestoreNodeRequest.displayName = 'proto.xudrpc.RestoreNodeRequest'; +} +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.xudrpc.RestoreNodeRequest.repeatedFields_ = [1]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.xudrpc.RestoreNodeRequest.prototype.toObject = function(opt_includeInstance) { + return proto.xudrpc.RestoreNodeRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.xudrpc.RestoreNodeRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.xudrpc.RestoreNodeRequest.toObject = function(includeInstance, msg) { + var f, obj = { + seedMnemonicList: jspb.Message.getRepeatedField(msg, 1), + password: jspb.Message.getFieldWithDefault(msg, 2, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.xudrpc.RestoreNodeRequest} + */ +proto.xudrpc.RestoreNodeRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.xudrpc.RestoreNodeRequest; + return proto.xudrpc.RestoreNodeRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.xudrpc.RestoreNodeRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.xudrpc.RestoreNodeRequest} + */ +proto.xudrpc.RestoreNodeRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.addSeedMnemonic(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setPassword(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.xudrpc.RestoreNodeRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.xudrpc.RestoreNodeRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.xudrpc.RestoreNodeRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.xudrpc.RestoreNodeRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getSeedMnemonicList(); + if (f.length > 0) { + writer.writeRepeatedString( + 1, + f + ); + } + f = message.getPassword(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } +}; + + +/** + * repeated string seed_mnemonic = 1; + * @return {!Array} + */ +proto.xudrpc.RestoreNodeRequest.prototype.getSeedMnemonicList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 1)); +}; + + +/** @param {!Array} value */ +proto.xudrpc.RestoreNodeRequest.prototype.setSeedMnemonicList = function(value) { + jspb.Message.setField(this, 1, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + */ +proto.xudrpc.RestoreNodeRequest.prototype.addSeedMnemonic = function(value, opt_index) { + jspb.Message.addToRepeatedField(this, 1, value, opt_index); +}; + + +proto.xudrpc.RestoreNodeRequest.prototype.clearSeedMnemonicList = function() { + this.setSeedMnemonicList([]); +}; + + +/** + * optional string password = 2; + * @return {string} + */ +proto.xudrpc.RestoreNodeRequest.prototype.getPassword = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** @param {string} value */ +proto.xudrpc.RestoreNodeRequest.prototype.setPassword = function(value) { + jspb.Message.setProto3StringField(this, 2, value); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.xudrpc.RestoreNodeResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.xudrpc.RestoreNodeResponse.repeatedFields_, null); +}; +goog.inherits(proto.xudrpc.RestoreNodeResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.xudrpc.RestoreNodeResponse.displayName = 'proto.xudrpc.RestoreNodeResponse'; +} +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.xudrpc.RestoreNodeResponse.repeatedFields_ = [1]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.xudrpc.RestoreNodeResponse.prototype.toObject = function(opt_includeInstance) { + return proto.xudrpc.RestoreNodeResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.xudrpc.RestoreNodeResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.xudrpc.RestoreNodeResponse.toObject = function(includeInstance, msg) { + var f, obj = { + restoredLndsList: jspb.Message.getRepeatedField(msg, 1), + restoredRaiden: jspb.Message.getFieldWithDefault(msg, 2, false) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.xudrpc.RestoreNodeResponse} + */ +proto.xudrpc.RestoreNodeResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.xudrpc.RestoreNodeResponse; + return proto.xudrpc.RestoreNodeResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.xudrpc.RestoreNodeResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.xudrpc.RestoreNodeResponse} + */ +proto.xudrpc.RestoreNodeResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.addRestoredLnds(value); + break; + case 2: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setRestoredRaiden(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.xudrpc.RestoreNodeResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.xudrpc.RestoreNodeResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.xudrpc.RestoreNodeResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.xudrpc.RestoreNodeResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getRestoredLndsList(); + if (f.length > 0) { + writer.writeRepeatedString( + 1, + f + ); + } + f = message.getRestoredRaiden(); + if (f) { + writer.writeBool( + 2, + f + ); + } +}; + + +/** + * repeated string restored_lnds = 1; + * @return {!Array} + */ +proto.xudrpc.RestoreNodeResponse.prototype.getRestoredLndsList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 1)); +}; + + +/** @param {!Array} value */ +proto.xudrpc.RestoreNodeResponse.prototype.setRestoredLndsList = function(value) { + jspb.Message.setField(this, 1, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + */ +proto.xudrpc.RestoreNodeResponse.prototype.addRestoredLnds = function(value, opt_index) { + jspb.Message.addToRepeatedField(this, 1, value, opt_index); +}; + + +proto.xudrpc.RestoreNodeResponse.prototype.clearRestoredLndsList = function() { + this.setRestoredLndsList([]); +}; + + +/** + * optional bool restored_raiden = 2; + * Note that Boolean fields may be set to 0/1 when serialized from a Java server. + * You should avoid comparisons like {@code val === true/false} in those cases. + * @return {boolean} + */ +proto.xudrpc.RestoreNodeResponse.prototype.getRestoredRaiden = function() { + return /** @type {boolean} */ (jspb.Message.getFieldWithDefault(this, 2, false)); +}; + + +/** @param {boolean} value */ +proto.xudrpc.RestoreNodeResponse.prototype.setRestoredRaiden = function(value) { + jspb.Message.setProto3BooleanField(this, 2, value); +}; + + + /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a diff --git a/lib/service/InitService.ts b/lib/service/InitService.ts index 8eb30e47f..5ddd8e94d 100644 --- a/lib/service/InitService.ts +++ b/lib/service/InitService.ts @@ -4,6 +4,7 @@ import swapErrors from '../swaps/errors'; import SwapClientManager from '../swaps/SwapClientManager'; import errors from './errors'; import assert = require('assert'); +import { encipher } from '../utils/seedutil'; interface InitService { once(event: 'nodekey', listener: (nodeKey: NodeKey) => void): this; @@ -13,7 +14,7 @@ interface InitService { /** A class containing the methods available for a locked, uninitialized instance of xud. */ class InitService extends EventEmitter { /** Whether there is a pending `CreateNode` or `UnlockNode` call. */ - public pendingCall = false; + private pendingCall = false; constructor(private swapClientManager: SwapClientManager, private nodeKeyPath: string, private nodeKeyExists: boolean) { super(); @@ -21,60 +22,107 @@ class InitService extends EventEmitter { public createNode = async (args: { password: string }) => { const { password } = args; - if (password.length < 8) { - // lnd requires 8+ character passwords, so we must as well - throw errors.INVALID_ARGUMENT('password must be at least 8 characters'); + + this.newWalletValidation(password); + await this.prepareCall(); + + try { + const seed = await this.swapClientManager.genSeed(); + + const seedBytes = typeof seed.encipheredSeed === 'string' ? + Buffer.from(seed.encipheredSeed, 'base64') : + Buffer.from(seed.encipheredSeed); + assert.equal(seedBytes.length, 33); + + // the seed is 33 bytes, the first byte of which is the version + // so we use the remaining 32 bytes to generate our private key + // TODO: use seedutil tool to derive a child private key from deciphered seed key? + const privKey = Buffer.from(seedBytes.slice(1)); + const nodeKey = new NodeKey(privKey); + + // use this seed to init any lnd wallets that are uninitialized + const initWalletResult = await this.swapClientManager.initWallets(password, seed.cipherSeedMnemonicList); + const initializedLndWallets = initWalletResult.initializedLndWallets; + const initializedRaiden = initWalletResult.initializedRaiden; + + await nodeKey.toFile(this.nodeKeyPath, password); + this.emit('nodekey', nodeKey); + return { + initializedLndWallets, + initializedRaiden, + mnemonic: seed ? seed.cipherSeedMnemonicList : undefined, + }; + } finally { + this.pendingCall = false; } - if (this.nodeKeyExists) { + } + + public unlockNode = async (args: { password: string }) => { + const { password } = args; + + if (!this.nodeKeyExists) { throw errors.UNIMPLEMENTED; } - if (this.swapClientManager.misconfiguredClientLabels.length > 0) { - throw swapErrors.SWAP_CLIENT_MISCONFIGURED(this.swapClientManager.misconfiguredClientLabels); - } - if (this.pendingCall) { - throw errors.PENDING_CALL_CONFLICT; - } + await this.prepareCall(); - this.pendingCall = true; + try { + const nodeKey = await NodeKey.fromFile(this.nodeKeyPath, password); + this.emit('nodekey', nodeKey); - // wait briefly for all lnd instances to be available - await this.swapClientManager.waitForLnd(); + return this.swapClientManager.unlockWallets(password); + } finally { + this.pendingCall = false; + } + } - const seed = await this.swapClientManager.genSeed(); - let initializedLndWallets: string[] | undefined; - let initializedRaiden = false; - let nodeKey: NodeKey; - - const seedBytes = typeof seed.encipheredSeed === 'string' ? - Buffer.from(seed.encipheredSeed, 'base64') : - Buffer.from(seed.encipheredSeed); - assert.equal(seedBytes.length, 33); - - // the seed is 33 bytes, the first byte of which is the version - // so we use the remaining 32 bytes to generate our private key - // TODO: use seedutil tool to derive a child private key from deciphered seed key? - const privKey = Buffer.from(seedBytes.slice(1)); - nodeKey = new NodeKey(privKey); - - // use this seed to init any lnd wallets that are uninitialized - const initWalletResult = await this.swapClientManager.initWallets(password, seed.cipherSeedMnemonicList); - initializedLndWallets = initWalletResult.initializedLndWallets; - initializedRaiden = initWalletResult.initializedRaiden; - - await nodeKey.toFile(this.nodeKeyPath, password); - this.emit('nodekey', nodeKey); - return { - initializedLndWallets, - initializedRaiden, - mnemonic: seed ? seed.cipherSeedMnemonicList : undefined, - }; + public restoreNode = async (args: { password: string, seedMnemonicList: string[] }) => { + const { password, seedMnemonicList } = args; + if (seedMnemonicList.length !== 24) { + throw errors.INVALID_ARGUMENT('mnemonic must be exactly 24 words'); + } + + this.newWalletValidation(password); + await this.prepareCall(); + + try { + const seedBytes = await encipher(seedMnemonicList); + + // the seed is 33 bytes, the first byte of which is the version + // so we use the remaining 32 bytes to generate our private key + // TODO: use seedutil tool to derive a child private key from deciphered seed key? + const privKey = Buffer.from(seedBytes.slice(1)); + const nodeKey = new NodeKey(privKey); + + // use this seed to restore any lnd wallets that are uninitialized + const initWalletResult = await this.swapClientManager.initWallets(password, seedMnemonicList); + const restoredLndWallets = initWalletResult.initializedLndWallets; + const restoredRaiden = initWalletResult.initializedRaiden; + + await nodeKey.toFile(this.nodeKeyPath, password); + this.emit('nodekey', nodeKey); + return { + restoredLndWallets, + restoredRaiden, + }; + } finally { + this.pendingCall = false; + } } - public unlockNode = async (args: { password: string }) => { - const { password } = args; - if (!this.nodeKeyExists) { + private newWalletValidation = (password: string) => { + if (this.nodeKeyExists) { throw errors.UNIMPLEMENTED; } + if (password.length < 8) { + // lnd requires 8+ character passwords, so we must as well + throw errors.INVALID_ARGUMENT('password must be at least 8 characters'); + } + if (this.swapClientManager.misconfiguredClientLabels.length > 0) { + throw swapErrors.SWAP_CLIENT_MISCONFIGURED(this.swapClientManager.misconfiguredClientLabels); + } + } + + private prepareCall = async () => { if (this.pendingCall) { throw errors.PENDING_CALL_CONFLICT; } @@ -83,11 +131,6 @@ class InitService extends EventEmitter { // wait briefly for all lnd instances to be available await this.swapClientManager.waitForLnd(); - - const nodeKey = await NodeKey.fromFile(this.nodeKeyPath, password); - this.emit('nodekey', nodeKey); - - return this.swapClientManager.unlockWallets(password); } } diff --git a/lib/swaps/SwapClientManager.ts b/lib/swaps/SwapClientManager.ts index b2927563e..ef01808bf 100644 --- a/lib/swaps/SwapClientManager.ts +++ b/lib/swaps/SwapClientManager.ts @@ -2,17 +2,17 @@ import { EventEmitter } from 'events'; import Config from '../Config'; import { SwapClientType } from '../constants/enums'; import { Models } from '../db/DB'; +import lndErrors from '../lndclient/errors'; import LndClient from '../lndclient/LndClient'; import { LndInfo } from '../lndclient/types'; import { Loggers } from '../Logger'; import { Currency } from '../orderbook/types'; import Peer from '../p2p/Peer'; import RaidenClient from '../raidenclient/RaidenClient'; -import seedutil from '../utils/seedutil'; +import { keystore } from '../utils/seedutil'; import { UnitConverter } from '../utils/UnitConverter'; import errors from './errors'; import SwapClient, { ClientStatus } from './SwapClient'; -import lndErrors from '../lndclient/errors'; export function isRaidenClient(swapClient: SwapClient): swapClient is RaidenClient { return (swapClient.type === SwapClientType.Raiden); @@ -203,7 +203,7 @@ class SwapClientManager extends EventEmitter { // TODO: we are setting the raiden keystore as an empty string until raiden // allows for decrypting the keystore without needing to save the password // to disk in plain text - const keystorePromise = seedutil(seedMnemonic, '', keystorepath).then(() => { + const keystorePromise = keystore(seedMnemonic, '', keystorepath).then(() => { this.raidenClient.logger.info(`created raiden keystore with master seed and empty password in ${keystorepath}`); initializedRaiden = true; }).catch((err) => { diff --git a/lib/utils/seedutil.ts b/lib/utils/seedutil.ts index e37463d51..af2c843d4 100644 --- a/lib/utils/seedutil.ts +++ b/lib/utils/seedutil.ts @@ -5,14 +5,14 @@ import { promisify } from 'util'; const exec = promisify(childProcessExec); /** - * Attempts to execute the seedutil tool which will generate an ethereum keystore - * according to a given mnemonic and password at the specified path + * Executes the seedutil tool to generate an ethereum keystore from the given + * mnemonic and password at the specified path. * @param mnemonic the 24 seed recovery mnemonic * @param password the password to protect the keystore * @param path the path in which to create the keystore directory */ -const seedutil = async (mnemonic: string[], password: string, path: string) => { - const { stdout, stderr } = await exec(`./seedutil/seedutil -pass=${password} -path=${path} ${mnemonic.join(' ')}`); +async function keystore(mnemonic: string[], password: string, path: string) { + const { stdout, stderr } = await exec(`./seedutil/seedutil keystore -pass=${password} -path=${path} ${mnemonic.join(' ')}`); if (stderr) { throw new Error(stderr); @@ -21,6 +21,21 @@ const seedutil = async (mnemonic: string[], password: string, path: string) => { if (!stdout.includes('Keystore created')) { throw new Error(stdout); } -}; +} -export default seedutil; +/** + * Executes the seedutil tool to encipher a seed mnemonic into bytes. + * @param mnemonic the 24 seed recovery mnemonic + */ +async function encipher(mnemonic: string[]) { + const { stdout, stderr } = await exec(`./seedutil/seedutil encipher ${mnemonic.join(' ')}`); + + if (stderr) { + throw new Error(stderr); + } + + const encipheredSeed = stdout.trim(); + return Buffer.from(encipheredSeed, 'hex'); +} + +export { keystore, encipher }; diff --git a/proto/xudrpc.proto b/proto/xudrpc.proto index 3f3a83325..ce1cecaa7 100644 --- a/proto/xudrpc.proto +++ b/proto/xudrpc.proto @@ -32,6 +32,9 @@ service XudInit { /* Unlocks and decrypts the xud node key and any underlying wallets. */ rpc UnlockNode(UnlockNodeRequest) returns (UnlockNodeResponse) { } + + /* Restores an xud instance and underlying wallets from a seed. */ + rpc RestoreNode(RestoreNodeRequest) returns (RestoreNodeResponse) { } } message CreateNodeRequest { @@ -62,6 +65,20 @@ message UnlockNodeResponse { repeated string locked_lnds = 3; } +message RestoreNodeRequest { + // The 24 word mnemonic to recover the xud identity key and underlying wallets + repeated string seed_mnemonic = 1; + // The password in utf-8 with which to encrypt the restored xud node key as well + // as any restored underlying wallets. + string password = 2; +} +message RestoreNodeResponse { + // The list of lnd clients that were initialized. + repeated string restored_lnds = 1; + // Whether raiden was initialized. + bool restored_raiden = 2; +} + service Xud { /* Adds a currency to the list of supported currencies. Once added, the currency may be used for * new trading pairs. */ diff --git a/seedutil/README.md b/seedutil/README.md index 9e51ecc28..98f7c6da2 100644 --- a/seedutil/README.md +++ b/seedutil/README.md @@ -12,10 +12,18 @@ Go 1.12 or greater must be installed and added to your `PATH` to build the seedu It is recommended to use this tool on the command line ONLY for development purposes. -`seedutil [-pass=encryption password] [-path=optional/keystore/path] [-aezeedpass=optional_seed_pass] ` +### Generate Ethereum Keystore + +`seedutil keystore [-pass=encryption password] [-path=optional/keystore/path] [-aezeedpass=optional_seed_pass] ` By default the `keystore` folder will be created in the execution directory and the aezeed password will be `aezeed`. +### Encipher seed + +Prints an enciphered seed in hex format from a provided mnemonic. + +`seedutil encipher [-aezeedpass=optional_seed_pass] ` + ## Tests `npm run test:seedutil` diff --git a/seedutil/SeedUtil.spec.ts b/seedutil/SeedUtil.spec.ts index 7c6fd3f1b..3fb4849a6 100644 --- a/seedutil/SeedUtil.spec.ts +++ b/seedutil/SeedUtil.spec.ts @@ -42,7 +42,7 @@ const deleteDir = async (path: string) => { const SUCCESS_KEYSTORE_CREATED = 'Keystore created'; const ERRORS = { - INVALID_ARGS_LENGTH: 'expecting password and 24-word mnemonic seed separated by spaces', + INVALID_ARGS_LENGTH: 'expecting 24-word mnemonic seed separated by spaces', MISSING_ENCRYPTION_PASSWORD: 'expecting encryption password', INVALID_AEZEED: 'invalid aezeed', KEYSTORE_FILE_ALREADY_EXISTS: 'account already exists', @@ -73,30 +73,59 @@ const VALID_SEED_NO_PASS = { const DEFAULT_KEYSTORE_PATH = `${process.cwd()}/seedutil/keystore`; -describe('SeedUtil', () => { +describe('SeedUtil encipher', () => { + test('it errors with no arguments', async () => { + await expect(executeCommand('./seedutil/seedutil encipher')) + .rejects.toThrow(ERRORS.INVALID_ARGS_LENGTH); + }); + + test('it errors with 23 words', async () => { + const cmd = `./seedutil/seedutil encipher ${VALID_SEED.seedWords.slice(0, 23).join(' ')}`; + await expect(executeCommand(cmd)) + .rejects.toThrow(ERRORS.INVALID_ARGS_LENGTH); + }); + + test('it errors with 24 words and invalid aezeed password', async () => { + const cmd = `./seedutil/seedutil encipher ${VALID_SEED.seedWords.join(' ')}`; + await expect(executeCommand(cmd)) + .rejects.toThrow(ERRORS.INVALID_AEZEED); + }); + + test('it succeeds with 24 words, valid aezeed password', async () => { + const cmd = `./seedutil/seedutil encipher -aezeedpass=${VALID_SEED.seedPassword} ${VALID_SEED.seedWords.join(' ')}`; + await expect(executeCommand(cmd)).resolves.toMatchSnapshot(); + }); + + test('it succeeds with 24 words, no aezeed password', async () => { + const cmd = `./seedutil/seedutil encipher ${VALID_SEED_NO_PASS.seedWords.join(' ')}`; + await expect(executeCommand(cmd)).resolves.toMatchSnapshot(); + }); +}); + +describe('SeedUtil keystore', () => { beforeEach(async () => { await deleteDir(DEFAULT_KEYSTORE_PATH); }); test('it errors with no arguments', async () => { - await expect(executeCommand('./seedutil/seedutil')) + await expect(executeCommand('./seedutil/seedutil keystore')) .rejects.toThrow(ERRORS.INVALID_ARGS_LENGTH); }); test('it errors with 23 words', async () => { - const cmd = `./seedutil/seedutil ${VALID_SEED.seedWords.slice(0, 23).join(' ')}`; + const cmd = `./seedutil/seedutil keystore ${VALID_SEED.seedWords.slice(0, 23).join(' ')}`; await expect(executeCommand(cmd)) .rejects.toThrow(ERRORS.INVALID_ARGS_LENGTH); }); test('it errors with 24 words and invalid aezeed password', async () => { - const cmd = `./seedutil/seedutil ${VALID_SEED.seedWords.join(' ')}`; + const cmd = `./seedutil/seedutil keystore ${VALID_SEED.seedWords.join(' ')}`; await expect(executeCommand(cmd)) .rejects.toThrow(ERRORS.INVALID_AEZEED); }); test('it succeeds with 24 words, valid aezeed password', async () => { - const cmd = `./seedutil/seedutil -aezeedpass=${VALID_SEED.seedPassword} ${VALID_SEED.seedWords.join(' ')}`; + const cmd = `./seedutil/seedutil keystore -aezeedpass=${VALID_SEED.seedPassword} ${VALID_SEED.seedWords.join(' ')}`; await expect(executeCommand(cmd)) .resolves.toMatch(SUCCESS_KEYSTORE_CREATED); // Read our keystore file @@ -113,7 +142,7 @@ describe('SeedUtil', () => { }); test('it succeeds with 24 words, no aezeed password', async () => { - const cmd = `./seedutil/seedutil ${VALID_SEED_NO_PASS.seedWords.join(' ')}`; + const cmd = `./seedutil/seedutil keystore ${VALID_SEED_NO_PASS.seedWords.join(' ')}`; await expect(executeCommand(cmd)) .resolves.toMatch(SUCCESS_KEYSTORE_CREATED); // Read our keystore file @@ -126,7 +155,7 @@ describe('SeedUtil', () => { }); test('it succeeds with 24 word and encryption password', async () => { - const cmd = `./seedutil/seedutil -pass=${PASSWORD} ${VALID_SEED_NO_PASS.seedWords.join(' ')}`; + const cmd = `./seedutil/seedutil keystore -pass=${PASSWORD} ${VALID_SEED_NO_PASS.seedWords.join(' ')}`; await expect(executeCommand(cmd)) .resolves.toMatch(SUCCESS_KEYSTORE_CREATED); // Read our keystore file @@ -140,7 +169,7 @@ describe('SeedUtil', () => { test('it allows custom keystore save path', async () => { const CUSTOM_PATH = `${process.cwd()}/seedutil/custom`; - const cmd = `./seedutil/seedutil -path=${CUSTOM_PATH} -aezeedpass=${VALID_SEED.seedPassword} ${VALID_SEED.seedWords.join(' ')}`; + const cmd = `./seedutil/seedutil keystore -path=${CUSTOM_PATH} -aezeedpass=${VALID_SEED.seedPassword} ${VALID_SEED.seedWords.join(' ')}`; await expect(executeCommand(cmd)) .resolves.toMatch(SUCCESS_KEYSTORE_CREATED); // cleanup custom path diff --git a/seedutil/__snapshots__/SeedUtil.spec.ts.snap b/seedutil/__snapshots__/SeedUtil.spec.ts.snap new file mode 100644 index 000000000..32276df69 --- /dev/null +++ b/seedutil/__snapshots__/SeedUtil.spec.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SeedUtil encipher it succeeds with 24 words, no aezeed password 1`] = ` +"00738860374692022c462027a35aaaef3c3289aa0a057e2600000000002cad2e2b +" +`; + +exports[`SeedUtil encipher it succeeds with 24 words, valid aezeed password 1`] = ` +"00a71642b0ace8f523a977950005c71220ea460b423a0a9f000000000079ef937c +" +`; diff --git a/seedutil/go.mod b/seedutil/go.mod index 21d4d51b4..930e44b8e 100644 --- a/seedutil/go.mod +++ b/seedutil/go.mod @@ -3,11 +3,7 @@ module ExchangeUnion/xud/seedutil go 1.12 require ( - git.schwanenlied.me/yawning/bsaes.git v0.0.0-20190320102049-26d1add596b6 // indirect - github.com/VictoriaMetrics/fastcache v1.5.4 // indirect - github.com/Yawning/aez v0.0.0-20180408160647-ec7426b44926 // indirect - github.com/allegro/bigcache v1.2.1 // indirect - github.com/aristanetworks/goarista v0.0.0-20191106175434-873d404c7f40 // indirect + github.com/aristanetworks/goarista v0.0.0-20190522000302-1c90cea262d3 // indirect github.com/cespare/cp v1.1.1 // indirect github.com/deckarep/golang-set v1.7.1 // indirect github.com/elastic/gosigar v0.10.5 // indirect @@ -19,9 +15,5 @@ require ( github.com/onsi/gomega v1.7.1 // indirect github.com/pborman/uuid v1.2.0 // indirect github.com/rjeczalik/notify v0.9.2 // indirect - golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e // indirect - golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect - golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect - gopkg.in/yaml.v2 v2.2.7 // indirect + golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f // indirect ) diff --git a/seedutil/go.sum b/seedutil/go.sum index a8d38875c..00b87a78b 100644 --- a/seedutil/go.sum +++ b/seedutil/go.sum @@ -1,8 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e h1:F2x1bq7RaNCIuqYpswggh1+c1JmwdnkHNC9wy1KDip0= git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo= -git.schwanenlied.me/yawning/bsaes.git v0.0.0-20190320102049-26d1add596b6 h1:zOrl5/RvK48MxMrif6Z+/OpuYyRnvB+ZTrQWEV9VYb0= -git.schwanenlied.me/yawning/bsaes.git v0.0.0-20190320102049-26d1add596b6/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= @@ -17,40 +15,28 @@ github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ= github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/Shopify/sarama v1.23.1/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.5.2 h1:Erd8iIuBAL9kke8JzM4+WxkKuFkHh3ktwLanJvDgR44= github.com/VictoriaMetrics/fastcache v1.5.2/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= -github.com/VictoriaMetrics/fastcache v1.5.4 h1:0BaXbRH01RycJk79OOBwMCXlNryko9z4yEf6RqbP+Xo= -github.com/VictoriaMetrics/fastcache v1.5.4/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2 h1:2be4ykKKov3M1yISM2E8gnGXZ/N2SsPawfnGiXxaYEU= github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= -github.com/Yawning/aez v0.0.0-20180408160647-ec7426b44926 h1:oBNaMAcYxOjYhHIXs5U3Dz1bODLJTpP7mEI5yzhyu1k= -github.com/Yawning/aez v0.0.0-20180408160647-ec7426b44926/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= -github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/aristanetworks/fsnotify v1.4.2/go.mod h1:D/rtu7LpjYM8tRJphJ0hUBYpjai8SfX+aSNsWDTq/Ks= -github.com/aristanetworks/glog v0.0.0-20180419172825-c15b03b3054f/go.mod h1:KASm+qXFKs/xjSoWn30NrWBBvdTTQq+UjkhjEJHfSFA= github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= -github.com/aristanetworks/goarista v0.0.0-20191106175434-873d404c7f40 h1:ZdRuixFqR3mfx4FHzclG3COrRgWrYq0VhNgIoYoObcM= -github.com/aristanetworks/goarista v0.0.0-20191106175434-873d404c7f40/go.mod h1:Z4RTxGAuYhPzcq8+EdRM+R8M48Ssle2TsWtwRKa+vns= -github.com/aristanetworks/splunk-hec-go v0.3.3/go.mod h1:1VHO9r17b0K7WmOlLb9nTk/2YanvOEnLMUgsFrxBROc= +github.com/aristanetworks/goarista v0.0.0-20190522000302-1c90cea262d3 h1:FSygxmvmJtxSPety1FflKx/TG1ODy0JKmXwgbByKFF8= +github.com/aristanetworks/goarista v0.0.0-20190522000302-1c90cea262d3/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= @@ -81,8 +67,6 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18 h1:pl4eWIqvFe/Kg3zkn7NxevNzILnZYWDCG7qbA1CJik0= github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18/go.mod h1:HD5P3vAIAh+Y2GAxg0PrPN1P8WkepXGpjbUPDHJqqKM= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -97,11 +81,7 @@ github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14y github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa h1:XKAhUk/dtp+CV0VO6mhG2V7jA9vbcGcnYF/Ay9NjZrY= github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= github.com/elastic/gosigar v0.10.5 h1:GzPQ+78RaAb4J63unidA/JavQRKrB6s8IOzN6Ib59jo= github.com/elastic/gosigar v0.10.5/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= @@ -112,7 +92,6 @@ github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+ github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -129,16 +108,12 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c h1:zqAKixg3cTcIasAMJV+EcfVbWwLpOZ7LeoWJvcuD/5Q= github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= @@ -148,22 +123,17 @@ github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v0.0.0-20170724004829-f2862b476edc/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= -github.com/influxdata/influxdb1-client v0.0.0-20190809212627-fc22c7df067e/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= @@ -180,8 +150,6 @@ github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFF github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -207,10 +175,6 @@ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= @@ -220,46 +184,33 @@ github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc= -github.com/openconfig/reference v0.0.0-20190727015836-8dfd928c9696/go.mod h1:ym2A+zigScwkSEb/cVQB0/ZMpU3rqiH6X7WRRsxgOGw= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= -github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= @@ -268,7 +219,6 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -287,51 +237,36 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= -github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= -github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= -github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xtaci/kcp-go v5.4.5+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= -github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e h1:egKlR8l7Nu9vHGWbcUV8lqR4987UfUbBd7GbhqGzNYU= -golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk= -golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -340,15 +275,11 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= -golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -358,37 +289,23 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190912185636-87d9f09c5d89/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= -gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= -gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= -gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= -gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA= gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= -gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -396,9 +313,7 @@ gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHO gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/seedutil/main.go b/seedutil/main.go index 617dd687e..cba9fab78 100644 --- a/seedutil/main.go +++ b/seedutil/main.go @@ -3,6 +3,7 @@ package main import ( "crypto/hmac" "crypto/sha512" + "encoding/hex" "flag" "fmt" "os" @@ -25,15 +26,9 @@ var ( defaultKeyStorePath = filepath.Join(filepath.Dir(os.Args[0])) ) -func main() { - password := flag.String("pass", "", "encryption password") - keystorePath := flag.String("path", defaultKeyStorePath, "path to create keystore dir") - aezeedPassphrase := flag.String("aezeedpass", defaultAezeedPassphrase, "aezeed passphrase") - flag.Parse() - args := flag.Args() - +func parseSeed(args []string, aezeedPassphrase *string) *aezeed.CipherSeed { if len(args) < aezeed.NummnemonicWords { - fmt.Fprintf(os.Stderr, "\nerror: expecting password and %v-word mnemonic seed separated by spaces\n", aezeed.NummnemonicWords) + fmt.Fprintf(os.Stderr, "\nerror: expecting %v-word mnemonic seed separated by spaces\n", aezeed.NummnemonicWords) os.Exit(1) } @@ -49,37 +44,74 @@ func main() { os.Exit(1) } - // derive 64-byte key from cipherSeed's 16 bytes of entropy - hmac512 := hmac.New(sha512.New, masterKey) - hmac512.Write(cipherSeed.Entropy[:]) - lr := hmac512.Sum(nil) + return cipherSeed +} - // we don't care about the chain code because we're not deriving - // extended keys from the master - masterSecretKey := lr[:len(lr)/2] +func main() { + keystoreCommand := flag.NewFlagSet("keystore", flag.ExitOnError) + encipherCommand := flag.NewFlagSet("encipher", flag.ExitOnError) - // perform validations and convert bytes to ecdsa.PrivateKey - privateKey, err := crypto.ToECDSA(masterSecretKey) - if err != nil { - fmt.Fprintln(os.Stderr, "\nerror: failed to convert masterSecretKey bytes to ecdsa.PrivateKey") + if len(os.Args) < 2 { + fmt.Println("keystore or encipher subcommand is required") os.Exit(1) } - dir, err := filepath.Abs(filepath.Join(*keystorePath, "keystore")) - if err != nil { - fmt.Fprintln(os.Stderr, "\nerror: failed to get directory for keystore") - os.Exit(1) - } + var args []string + switch os.Args[1] { + case "keystore": + password := keystoreCommand.String("pass", "", "encryption password") + keystorePath := keystoreCommand.String("path", defaultKeyStorePath, "path to create keystore dir") + aezeedPassphrase := keystoreCommand.String("aezeedpass", defaultAezeedPassphrase, "aezeed passphrase") + keystoreCommand.Parse(os.Args[2:]) + args = keystoreCommand.Args() - // generate keystore file - ks := keystore.NewKeyStore(dir, keystore.StandardScryptN, keystore.StandardScryptP) + cipherSeed := parseSeed(args, aezeedPassphrase) - // import our ecdsa.PrivateKey to our new keystore - _, err = ks.ImportECDSA(privateKey, *password) - if err != nil { - fmt.Fprintf(os.Stderr, "\nerror: failed to import key to keystore - %v\n", err) + // derive 64-byte key from cipherSeed's 16 bytes of entropy + hmac512 := hmac.New(sha512.New, masterKey) + hmac512.Write(cipherSeed.Entropy[:]) + lr := hmac512.Sum(nil) + + // we don't care about the chain code because we're not deriving + // extended keys from the master + masterSecretKey := lr[:len(lr)/2] + + // perform validations and convert bytes to ecdsa.PrivateKey + privateKey, err := crypto.ToECDSA(masterSecretKey) + if err != nil { + fmt.Fprintln(os.Stderr, "\nerror: failed to convert masterSecretKey bytes to ecdsa.PrivateKey") + os.Exit(1) + } + + dir, err := filepath.Abs(filepath.Join(*keystorePath, "keystore")) + if err != nil { + fmt.Fprintln(os.Stderr, "\nerror: failed to get directory for keystore") + os.Exit(1) + } + + // generate keystore file + ks := keystore.NewKeyStore(dir, keystore.StandardScryptN, keystore.StandardScryptP) + + // import our ecdsa.PrivateKey to our new keystore + _, err = ks.ImportECDSA(privateKey, *password) + if err != nil { + fmt.Fprintf(os.Stderr, "\nerror: failed to import key to keystore - %v\n", err) + os.Exit(1) + } + + fmt.Println("Keystore created in", dir) + case "encipher": + aezeedPassphrase := encipherCommand.String("aezeedpass", defaultAezeedPassphrase, "aezeed passphrase") + encipherCommand.Parse(os.Args[2:]) + args = encipherCommand.Args() + + cipherSeed := parseSeed(args, aezeedPassphrase) + encipheredSeed, _ := cipherSeed.Encipher([]byte(*aezeedPassphrase)) + enciphedSeedArr := make([]byte, 33) + copy(enciphedSeedArr[:], encipheredSeed[:]) + fmt.Println(hex.EncodeToString(enciphedSeedArr)) + default: + flag.PrintDefaults() os.Exit(1) } - - fmt.Println("\nKeystore created in", dir) }