lnrpc: revamp the WalletUnlocker service to have a two-stage init

In this commit, we revamp the WalletUnlocker service to now have a
two-stage init process.

The first (optional) is for the user instantiating a new lnd instance to
call the GenSeed method with an optional aezeed passphrase. The response
to this will be a freshly generated aezeed mnemonic along with the
original enciphered seed.

The second step will be the actual wallet initaliztion. By separating
this step from seed generation, UI's will be able to ensure that the
user has written down the seed, before proceeding and committing the
seed to the internal wallet. The new method InitWallet accepts a wallet
passphrase, the aezeed mnemonic, and the optional passphrase.
This commit is contained in:
Olaoluwa Osuntokun 2018-02-01 20:16:40 -08:00
parent 9d34fa8c1e
commit 7f953bf0b9
No known key found for this signature in database
GPG Key ID: 964EA263DD637C21
4 changed files with 883 additions and 506 deletions

File diff suppressed because it is too large Load Diff

View File

@ -28,15 +28,32 @@ var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
func request_WalletUnlocker_CreateWallet_0(ctx context.Context, marshaler runtime.Marshaler, client WalletUnlockerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CreateWalletRequest
var (
filter_WalletUnlocker_GenSeed_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_WalletUnlocker_GenSeed_0(ctx context.Context, marshaler runtime.Marshaler, client WalletUnlockerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GenSeedRequest
var metadata runtime.ServerMetadata
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_WalletUnlocker_GenSeed_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GenSeed(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func request_WalletUnlocker_InitWallet_0(ctx context.Context, marshaler runtime.Marshaler, client WalletUnlockerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq InitWalletRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.CreateWallet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
msg, err := client.InitWallet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
@ -564,7 +581,7 @@ func RegisterWalletUnlockerHandlerFromEndpoint(ctx context.Context, mux *runtime
func RegisterWalletUnlockerHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
client := NewWalletUnlockerClient(conn)
mux.Handle("POST", pattern_WalletUnlocker_CreateWallet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
mux.Handle("GET", pattern_WalletUnlocker_GenSeed_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
if cn, ok := w.(http.CloseNotifier); ok {
@ -582,14 +599,43 @@ func RegisterWalletUnlockerHandler(ctx context.Context, mux *runtime.ServeMux, c
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletUnlocker_CreateWallet_0(rctx, inboundMarshaler, client, req, pathParams)
resp, md, err := request_WalletUnlocker_GenSeed_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletUnlocker_CreateWallet_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WalletUnlocker_GenSeed_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletUnlocker_InitWallet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
if cn, ok := w.(http.CloseNotifier); ok {
go func(done <-chan struct{}, closed <-chan bool) {
select {
case <-done:
case <-closed:
cancel()
}
}(ctx.Done(), cn.CloseNotify())
}
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletUnlocker_InitWallet_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletUnlocker_InitWallet_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@ -626,13 +672,17 @@ func RegisterWalletUnlockerHandler(ctx context.Context, mux *runtime.ServeMux, c
}
var (
pattern_WalletUnlocker_CreateWallet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "createwallet"}, ""))
pattern_WalletUnlocker_GenSeed_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "genseed"}, ""))
pattern_WalletUnlocker_InitWallet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "initwallet"}, ""))
pattern_WalletUnlocker_UnlockWallet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "unlockwallet"}, ""))
)
var (
forward_WalletUnlocker_CreateWallet_0 = runtime.ForwardResponseMessage
forward_WalletUnlocker_GenSeed_0 = runtime.ForwardResponseMessage
forward_WalletUnlocker_InitWallet_0 = runtime.ForwardResponseMessage
forward_WalletUnlocker_UnlockWallet_0 = runtime.ForwardResponseMessage
)

View File

@ -28,13 +28,39 @@ package lnrpc;
// The WalletUnlocker service is used to set up a wallet password for
// lnd at first startup, and unlock a previously set up wallet.
service WalletUnlocker {
/** lncli: `create`
CreateWallet is used at lnd startup to set the encryption password for
the wallet database.
/**
GenSeed is the first method that should be used to instantiate a new lnd
instance. This method allows a caller to generate a new aezeed cipher seed
given an optional passphrase. If provided, the passphrase will be necessary
to decrypt the cipherseed to expose the internal wallet seed.
Once the cipherseed is obtained and verified by the user, the InitWallet
method should be used to commit the newly generated seed, and create the
wallet.
*/
rpc CreateWallet(CreateWalletRequest) returns (CreateWalletResponse) {
rpc GenSeed(GenSeedRequest) returns (GenSeedResponse) {
option (google.api.http) = {
post: "/v1/createwallet"
get: "/v1/genseed"
};
}
/** lncli: `init`
InitWallet is used when lnd is starting up for the first time to fully
initialize the daemon and its internal wallet. At the very least a wallet
password must be provided. This will be used to encrypt sensitive material
on disk.
In the case of a recovery scenario, the user can also specify their aezeed
mnemonic and passphrase. If set, then the daemon will use this prior state
to initialize its internal wallet.
Alternatively, this can be used along with the GenSeed RPC to obtain a
seed, then present it to the user. Once it has been verified by the user,
the seed can be fed into this RPC in order to commit the new wallet.
*/
rpc InitWallet(InitWalletRequest) returns (InitWalletResponse) {
option (google.api.http) = {
post: "/v1/initwallet"
body: "*"
};
}
@ -51,20 +77,74 @@ service WalletUnlocker {
}
}
message CreateWalletRequest {
bytes password = 1;
}
message CreateWalletResponse {}
message GenSeedRequest {
/**
aezeed_passphrase is an optional user provided passphrase that will be used
to encrypt the generated aezeed cipher seed.
*/
bytes aezeed_passphrase = 1;
/**
seed_entropy is an optional 16-bytes generated via CSPRNG. If not
specified, then a fresh set of randomness will be used to create the seed.
*/
bytes seed_entropy = 2;
}
message GenSeedResponse {
/**
cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
cipher seed obtained by the user. This field is optional, as if not
provided, then the daemon will generate a new cipher seed for the user.
Otherwise, then the daemon will attempt to recover the wallet state linked
to this cipher seed.
*/
repeated string cipher_seed_mnemonic = 1;
/**
enciphered_seed are the raw aezeed cipher seed bytes. This is the raw
cipher text before run through our mnemonic encoding scheme.
*/
bytes enciphered_seed = 2;
}
message InitWalletRequest {
/**
wallet_password is the passphrase that should be used to encrypt the
wallet. This MUST be at least 8 chars in length. After creation, this
password is required to unlock the daemon.
*/
bytes wallet_password = 1;
/**
cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
cipher seed obtained by the user. This may have been generated by the
GenSeed method, or be an existing seed.
*/
repeated string cipher_seed_mnemonic = 2;
/**
aezeed_passphrase is an optional user provided passphrase that will be used
to encrypt the generated aezeed cipher seed.
*/
bytes aezeed_passphrase = 3;
}
message InitWalletResponse {
}
message UnlockWalletRequest {
bytes password = 1;
/**
wallet_password should be the current valid passphrase for the daemon. This
will be required to decrypt on-disk material that the daemon requires to
function properly.
*/
bytes wallet_password = 1;
}
message UnlockWalletResponse {}
service Lightning {
/** lncli: `walletbalance`
WalletBalance returns total unspent outputs(confirmed and unconfirmed), all confirmed unspent outputs and all unconfirmed unspent outputs under control
WalletBalance returns total unspent outputs(confirmed and unconfirmed), all
confirmed unspent outputs and all unconfirmed unspent outputs under control
by the wallet. This method can be modified by having the request specify
only witness outputs should be factored into the final output sum.
*/

View File

@ -17,7 +17,7 @@
"paths": {
"/v1/balance/blockchain": {
"get": {
"summary": "* lncli: `walletbalance`\nWalletBalance returns total unspent outputs(confirmed and unconfirmed), all confirmed unspent outputs and all unconfirmed unspent outputs under control\nby the wallet. This method can be modified by having the request specify\nonly witness outputs should be factored into the final output sum.",
"summary": "* lncli: `walletbalance`\nWalletBalance returns total unspent outputs(confirmed and unconfirmed), all\nconfirmed unspent outputs and all unconfirmed unspent outputs under control\nby the wallet. This method can be modified by having the request specify\nonly witness outputs should be factored into the final output sum.",
"operationId": "WalletBalance",
"responses": {
"200": {
@ -204,33 +204,6 @@
]
}
},
"/v1/createwallet": {
"post": {
"summary": "* lncli: `create`\nCreateWallet is used at lnd startup to set the encryption password for\nthe wallet database.",
"operationId": "CreateWallet",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/lnrpcCreateWalletResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/lnrpcCreateWalletRequest"
}
}
],
"tags": [
"WalletUnlocker"
]
}
},
"/v1/fees": {
"get": {
"summary": "* lncli: `feereport`\nFeeReport allows the caller to obtain a report detailing the current fee\nschedule enforced by the node globally for each channel.",
@ -248,6 +221,42 @@
]
}
},
"/v1/genseed": {
"get": {
"summary": "*\nGenSeed is the first method that should be used to instantiate a new lnd\ninstance. This method allows a caller to generate a new aezeed cipher seed\ngiven an optional passphrase. If provided, the passphrase will be necessary\nto decrypt the cipherseed to expose the internal wallet seed.",
"description": "Once the cipherseed is obtained and verified by the user, the InitWallet\nmethod should be used to commit the newly generated seed, and create the\nwallet.",
"operationId": "GenSeed",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/lnrpcGenSeedResponse"
}
}
},
"parameters": [
{
"name": "aezeed_passphrase",
"description": "*\naezeed_passphrase is an optional user provided passphrase that will be used\nto encrypt the generated aezeed cipher seed.",
"in": "query",
"required": false,
"type": "string",
"format": "byte"
},
{
"name": "seed_entropy",
"description": "*\nseed_entropy is an optional 16-bytes generated via CSPRNG. If not\nspecified, then a fresh set of randomness will be used to create the seed.",
"in": "query",
"required": false,
"type": "string",
"format": "byte"
}
],
"tags": [
"WalletUnlocker"
]
}
},
"/v1/getinfo": {
"get": {
"summary": "* lncli: `getinfo`\nGetInfo returns general information concerning the lightning node including\nit's identity pubkey, alias, the chains it is connected to, and information\nconcerning the number of open+pending channels.",
@ -390,6 +399,34 @@
]
}
},
"/v1/initwallet": {
"post": {
"summary": "* lncli: `init`\nInitWallet is used when lnd is starting up for the first time to fully\ninitialize the daemon and its internal wallet. At the very least a wallet\npassword must be provided. This will be used to encrypt sensitive material\non disk.",
"description": "In the case of a recovery scenario, the user can also specify their aezeed\nmnemonic and passphrase. If set, then the daemon will use this prior state\nto initialize its internal wallet.\n\nAlternatively, this can be used along with the GenSeed RPC to obtain a\nseed, then present it to the user. Once it has been verified by the user,\nthe seed can be fed into this RPC in order to commit the new wallet.",
"operationId": "InitWallet",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/lnrpcInitWalletResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/lnrpcInitWalletRequest"
}
}
],
"tags": [
"WalletUnlocker"
]
}
},
"/v1/invoice/{r_hash_str}": {
"get": {
"summary": "* lncli: `lookupinvoice`\nLookupInvoice attempts to look up an invoice according to its payment hash.\nThe passed payment hash *must* be exactly 32 bytes, if not, an error is\nreturned.",
@ -1129,18 +1166,6 @@
"lnrpcConnectPeerResponse": {
"type": "object"
},
"lnrpcCreateWalletRequest": {
"type": "object",
"properties": {
"password": {
"type": "string",
"format": "byte"
}
}
},
"lnrpcCreateWalletResponse": {
"type": "object"
},
"lnrpcDebugLevelResponse": {
"type": "object",
"properties": {
@ -1167,6 +1192,23 @@
}
}
},
"lnrpcGenSeedResponse": {
"type": "object",
"properties": {
"cipher_seed_mnemonic": {
"type": "array",
"items": {
"type": "string"
},
"description": "*\ncipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed\ncipher seed obtained by the user. This field is optional, as if not\nprovided, then the daemon will generate a new cipher seed for the user.\nOtherwise, then the daemon will attempt to recover the wallet state linked\nto this cipher seed."
},
"enciphered_seed": {
"type": "string",
"format": "byte",
"description": "*\nenciphered_seed are the raw aezeed cipher seed bytes. This is the raw\ncipher text before run through our mnemonic encoding scheme."
}
}
},
"lnrpcGetInfoResponse": {
"type": "object",
"properties": {
@ -1303,6 +1345,31 @@
}
}
},
"lnrpcInitWalletRequest": {
"type": "object",
"properties": {
"wallet_password": {
"type": "string",
"format": "byte",
"description": "*\nwallet_password is the passphrase that should be used to encrypt the\nwallet. This MUST be at least 8 chars in length. After creation, this\npassword is required to unlock the daemon."
},
"cipher_seed_mnemonic": {
"type": "array",
"items": {
"type": "string"
},
"description": "*\ncipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed\ncipher seed obtained by the user. This may have been generated by the\nGenSeed method, or be an existing seed."
},
"aezeed_passphrase": {
"type": "string",
"format": "byte",
"description": "*\naezeed_passphrase is an optional user provided passphrase that will be used\nto encrypt the generated aezeed cipher seed."
}
}
},
"lnrpcInitWalletResponse": {
"type": "object"
},
"lnrpcInvoice": {
"type": "object",
"properties": {
@ -2062,9 +2129,10 @@
"lnrpcUnlockWalletRequest": {
"type": "object",
"properties": {
"password": {
"wallet_password": {
"type": "string",
"format": "byte"
"format": "byte",
"description": "*\nwallet_password should be the current valid passphrase for the daemon. This\nwill be required to decrypt on-disk material that the daemon requires to\nfunction properly."
}
}
},