From 5edf64f4c5a7cba82e59b17fa92d0652c5f60c89 Mon Sep 17 00:00:00 2001 From: ziggie Date: Sat, 3 Dec 2022 16:44:43 +0100 Subject: [PATCH] lncli: add sign/verify message with an address adds the functionality in the lncli walletkit to sign and verify a message with a single address. Signing works only with lnd default wallet account. Verifying signature on messages with external addresses is also supported. --- cmd/lncli/walletrpc_active.go | 196 ++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) diff --git a/cmd/lncli/walletrpc_active.go b/cmd/lncli/walletrpc_active.go index 676305d9e..16c99cb09 100644 --- a/cmd/lncli/walletrpc_active.go +++ b/cmd/lncli/walletrpc_active.go @@ -52,6 +52,8 @@ var ( Usage: "Interact with wallet addresses.", Subcommands: []cli.Command{ listAddressesCommand, + signMessageWithAddrCommand, + verifyMessageWithAddrCommand, }, } ) @@ -1193,6 +1195,200 @@ func listAddresses(ctx *cli.Context) error { return nil } +var signMessageWithAddrCommand = cli.Command{ + Name: "signmessage", + Usage: "Sign a message with the private key of the provided " + + "address.", + ArgsUsage: "address msg", + Description: ` + Sign a message with the private key of the specified address, and + return the signature. Signing is solely done in the ECDSA compact + signature format. This is also done when signing with a P2TR address + meaning that the private key of the P2TR address (internal key) is used + to sign the provided message with the ECDSA format. Only addresses are + accepted which are owned by the internal lnd wallet. + `, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "address", + Usage: "specify the address which private key " + + "will be used to sign the message", + }, + cli.StringFlag{ + Name: "msg", + Usage: "the message to sign for", + }, + }, + Action: actionDecorator(signMessageWithAddr), +} + +func signMessageWithAddr(ctx *cli.Context) error { + ctxc := getContext() + + // Display the command's help message if we do not have the expected + // number of arguments/flags. + if ctx.NArg() > 2 || ctx.NumFlags() > 2 { + return cli.ShowCommandHelp(ctx, "signmessagewithaddr") + } + + walletClient, cleanUp := getWalletClient(ctx) + defer cleanUp() + + var ( + args = ctx.Args() + addr string + msg []byte + ) + + switch { + case ctx.IsSet("address"): + addr = ctx.String("address") + + case ctx.Args().Present(): + addr = args.First() + args = args.Tail() + + default: + return fmt.Errorf("address argument missing") + } + + switch { + case ctx.IsSet("msg"): + msg = []byte(ctx.String("msg")) + + case ctx.Args().Present(): + msg = []byte(args.First()) + args = args.Tail() + + default: + return fmt.Errorf("msg argument missing") + } + + resp, err := walletClient.SignMessageWithAddr( + ctxc, + &walletrpc.SignMessageWithAddrRequest{ + Msg: msg, + Addr: addr, + }, + ) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +var verifyMessageWithAddrCommand = cli.Command{ + Name: "verifymessage", + Usage: "Verify a message signed with the private key of the " + + "provided address.", + ArgsUsage: "address sig msg", + Description: ` + Verify a message signed with the signature of the public key + of the provided address. The signature must be in compact ECDSA format + The verification is independent whether the address belongs to the + wallet or not. This is achieved by only accepting ECDSA compacted + signatures. When verifying a signature with a taproot address, the + signature still has to be in the ECDSA compact format and no tapscript + has to be included in the P2TR address. + Supports address types P2PKH, P2WKH, NP2WKH, P2TR. + + Besides whether the signature is valid or not, the recoverd public key + of the compact ECDSA signature is returned. + `, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "address", + Usage: "specify the address which corresponding" + + "public key will be used", + }, + cli.StringFlag{ + Name: "sig", + Usage: "the base64 encoded compact signature " + + "of the message", + }, + cli.StringFlag{ + Name: "msg", + Usage: "the message to sign", + }, + }, + Action: actionDecorator(verifyMessageWithAddr), +} + +func verifyMessageWithAddr(ctx *cli.Context) error { + ctxc := getContext() + + // Display the command's help message if we do not have the expected + // number of arguments/flags. + if ctx.NArg() > 3 || ctx.NumFlags() > 3 { + return cli.ShowCommandHelp(ctx, "signmessagewithaddr") + } + + walletClient, cleanUp := getWalletClient(ctx) + defer cleanUp() + + var ( + args = ctx.Args() + addr string + sig string + msg []byte + ) + + switch { + case ctx.IsSet("address"): + addr = ctx.String("address") + + case args.Present(): + addr = args.First() + args = args.Tail() + + default: + return fmt.Errorf("address argument missing") + } + + switch { + case ctx.IsSet("sig"): + sig = ctx.String("sig") + + case ctx.Args().Present(): + sig = args.First() + args = args.Tail() + + default: + return fmt.Errorf("sig argument missing") + } + + switch { + case ctx.IsSet("msg"): + msg = []byte(ctx.String("msg")) + + case ctx.Args().Present(): + msg = []byte(args.First()) + args = args.Tail() + + default: + return fmt.Errorf("msg argument missing") + } + + resp, err := walletClient.VerifyMessageWithAddr( + ctxc, + &walletrpc.VerifyMessageWithAddrRequest{ + Msg: msg, + Signature: sig, + Addr: addr, + }, + ) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + var importAccountCommand = cli.Command{ Name: "import", Usage: "Import an on-chain account into the wallet through its " +