mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-29 03:01:52 +01:00
rpc: add SignMessage and VerifyMessage interface
This commit allows users to sign messages with their node's private key with the SignMessage interface. The signatures are zbase32 encoded for human readability/paste-ability. Others users can verify that a message was signed by another node in their channel database with the VerifyMessage interface.
This commit is contained in:
parent
2486097554
commit
2249215260
63
lnd_test.go
63
lnd_test.go
@ -2351,6 +2351,65 @@ func testNodeAnnouncement(net *networkHarness, t *harnessTest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testNodeSignVerify(net *networkHarness, t *harnessTest) {
|
||||||
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
// bob should be able to verify alice's signature
|
||||||
|
|
||||||
|
aliceMsg := []byte("alice msg")
|
||||||
|
|
||||||
|
// alice: sign "alice msg"
|
||||||
|
|
||||||
|
sigReq := &lnrpc.SignMessageRequest{Msg: aliceMsg}
|
||||||
|
sigResp, err := net.Alice.SignMessage(ctxb, sigReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SignMessage rpc call failed: %v", err)
|
||||||
|
}
|
||||||
|
aliceSig := sigResp.Signature
|
||||||
|
|
||||||
|
// bob: verify alice's signature -> should succeed
|
||||||
|
|
||||||
|
verifyReq := &lnrpc.VerifyMessageRequest{
|
||||||
|
Msg: aliceMsg, Signature: aliceSig}
|
||||||
|
verifyResp, err := net.Bob.VerifyMessage(ctxb, verifyReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("VerifyMessage failed: %v", err)
|
||||||
|
}
|
||||||
|
if !verifyResp.Valid {
|
||||||
|
t.Fatalf("alice's signature didn't validate")
|
||||||
|
}
|
||||||
|
|
||||||
|
// carol is a new node that is unconnected to alice or bob
|
||||||
|
|
||||||
|
carol, err := net.NewNode(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create new node: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
carolMsg := []byte("carol msg")
|
||||||
|
|
||||||
|
// carol: sign "carol msg"
|
||||||
|
|
||||||
|
sigReq = &lnrpc.SignMessageRequest{Msg: carolMsg}
|
||||||
|
sigResp, err = carol.SignMessage(ctxb, sigReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SignMessage rpc call failed: %v", err)
|
||||||
|
}
|
||||||
|
carolSig := sigResp.Signature
|
||||||
|
|
||||||
|
// bob: verify carol's signature -> should fail
|
||||||
|
|
||||||
|
verifyReq = &lnrpc.VerifyMessageRequest{
|
||||||
|
Msg: carolMsg, Signature: carolSig}
|
||||||
|
verifyResp, err = net.Bob.VerifyMessage(ctxb, verifyReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("VerifyMessage failed: %v", err)
|
||||||
|
}
|
||||||
|
if verifyResp.Valid {
|
||||||
|
t.Fatalf("carol's signature should not be valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
test func(net *networkHarness, t *harnessTest)
|
test func(net *networkHarness, t *harnessTest)
|
||||||
@ -2420,6 +2479,10 @@ var testsCases = []*testCase{
|
|||||||
name: "revoked uncooperative close retribution",
|
name: "revoked uncooperative close retribution",
|
||||||
test: testRevokedCloseRetribution,
|
test: testRevokedCloseRetribution,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "node sign verify",
|
||||||
|
test: testNodeSignVerify,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLightningNetworkDaemon performs a series of integration tests amongst a
|
// TestLightningNetworkDaemon performs a series of integration tests amongst a
|
||||||
|
@ -37,6 +37,12 @@ description):
|
|||||||
* Returns a new address, the following address types are supported:
|
* Returns a new address, the following address types are supported:
|
||||||
pay-to-public-key-hash (p2pkh), pay-to-witness-key-hash (p2wkh), and
|
pay-to-public-key-hash (p2pkh), pay-to-witness-key-hash (p2wkh), and
|
||||||
nested-pay-to-witness-key-hash (np2wkh).
|
nested-pay-to-witness-key-hash (np2wkh).
|
||||||
|
* SignMessage
|
||||||
|
* Signs a message with the node's identity key and returns a
|
||||||
|
zbase32 encoded signature.
|
||||||
|
* VerifyMessage
|
||||||
|
* Verifies a signature signed by another node on a message. The other node
|
||||||
|
must be an active node in the channel database.
|
||||||
* ConnectPeer
|
* ConnectPeer
|
||||||
* Connects to a peer identified by a public key and host.
|
* Connects to a peer identified by a public key and host.
|
||||||
* DisconnectPeer
|
* DisconnectPeer
|
||||||
|
817
lnrpc/rpc.pb.go
817
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@ -38,6 +38,9 @@ service Lightning {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpc SignMessage (SignMessageRequest) returns (SignMessageResponse);
|
||||||
|
rpc VerifyMessage (VerifyMessageRequest) returns (VerifyMessageResponse);
|
||||||
|
|
||||||
rpc ConnectPeer (ConnectPeerRequest) returns (ConnectPeerResponse) {
|
rpc ConnectPeer (ConnectPeerRequest) returns (ConnectPeerResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/v1/peers"
|
post: "/v1/peers"
|
||||||
@ -247,6 +250,21 @@ message NewAddressResponse {
|
|||||||
string address = 1 [json_name = "address"];
|
string address = 1 [json_name = "address"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SignMessageRequest {
|
||||||
|
bytes msg = 1 [ json_name = "msg" ];
|
||||||
|
}
|
||||||
|
message SignMessageResponse {
|
||||||
|
string signature = 1 [ json_name = "signature" ];
|
||||||
|
}
|
||||||
|
|
||||||
|
message VerifyMessageRequest {
|
||||||
|
bytes msg = 1 [ json_name = "msg" ];
|
||||||
|
string signature = 2 [ json_name = "signature" ];
|
||||||
|
}
|
||||||
|
message VerifyMessageResponse {
|
||||||
|
bool valid = 1 [ json_name = "valid" ];
|
||||||
|
}
|
||||||
|
|
||||||
message ConnectPeerRequest {
|
message ConnectPeerRequest {
|
||||||
LightningAddress addr = 1;
|
LightningAddress addr = 1;
|
||||||
bool perm = 2;
|
bool perm = 2;
|
||||||
|
@ -1706,6 +1706,23 @@
|
|||||||
"lnrpcSetAliasResponse": {
|
"lnrpcSetAliasResponse": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"lnrpcSignMessageRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"msg": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lnrpcSignMessageResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"signature": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"lnrpcStopRequest": {
|
"lnrpcStopRequest": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
@ -1754,6 +1771,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lnrpcVerifyMessageRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"msg": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte"
|
||||||
|
},
|
||||||
|
"signature": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lnrpcVerifyMessageResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"valid": {
|
||||||
|
"type": "boolean",
|
||||||
|
"format": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"lnrpcWalletBalanceRequest": {
|
"lnrpcWalletBalanceRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -23,7 +23,7 @@ func newNodeSigner(key *btcec.PrivateKey) *nodeSigner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SignMessage signs a double-sha256 digest of the passed msg under the
|
// SignMessage signs a double-sha256 digest of the passed msg under the
|
||||||
// resident node private key. If the target public key is _not_ the node's
|
// resident node's private key. If the target public key is _not_ the node's
|
||||||
// private key, then an error will be returned.
|
// private key, then an error will be returned.
|
||||||
func (n *nodeSigner) SignMessage(pubKey *btcec.PublicKey,
|
func (n *nodeSigner) SignMessage(pubKey *btcec.PublicKey,
|
||||||
msg []byte) (*btcec.Signature, error) {
|
msg []byte) (*btcec.Signature, error) {
|
||||||
@ -44,6 +44,33 @@ func (n *nodeSigner) SignMessage(pubKey *btcec.PublicKey,
|
|||||||
return sign, nil
|
return sign, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignCompact signs a double-sha256 digest of the passed msg under the
|
||||||
|
// resident node's private key. If the target public key is _not_ the node's
|
||||||
|
// private key, then an error will be returned. The returned signature is a
|
||||||
|
// pubkey-recoverable signature.
|
||||||
|
func (n *nodeSigner) SignCompact(pubKey *btcec.PublicKey,
|
||||||
|
msg []byte) ([]byte, error) {
|
||||||
|
|
||||||
|
// If this isn't our identity public key, then we'll exit early with an
|
||||||
|
// error as we can't sign with this key.
|
||||||
|
if !pubKey.IsEqual(n.privKey.PubKey()) {
|
||||||
|
return nil, fmt.Errorf("unknown public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we'll sign the dsha256 of the target message.
|
||||||
|
digest := chainhash.DoubleHashB(msg)
|
||||||
|
// Should the signature reference a compressed public key or not.
|
||||||
|
compressedPubKey := false
|
||||||
|
// btcec.SignCompact returns a pubkey-recoverable signature
|
||||||
|
sign, err := btcec.SignCompact(
|
||||||
|
btcec.S256(), n.privKey, digest, compressedPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't sign the message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sign, nil
|
||||||
|
}
|
||||||
|
|
||||||
// A compile time check to ensure that nodeSigner implements the MessageSigner
|
// A compile time check to ensure that nodeSigner implements the MessageSigner
|
||||||
// interface.
|
// interface.
|
||||||
var _ lnwallet.MessageSigner = (*nodeSigner)(nil)
|
var _ lnwallet.MessageSigner = (*nodeSigner)(nil)
|
||||||
|
56
rpcserver.go
56
rpcserver.go
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
"github.com/roasbeef/btcutil"
|
"github.com/roasbeef/btcutil"
|
||||||
"github.com/roasbeef/btcwallet/waddrmgr"
|
"github.com/roasbeef/btcwallet/waddrmgr"
|
||||||
|
"github.com/tv42/zbase32"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -188,6 +189,61 @@ func (r *rpcServer) NewWitnessAddress(ctx context.Context,
|
|||||||
return &lnrpc.NewAddressResponse{Address: addr.String()}, nil
|
return &lnrpc.NewAddressResponse{Address: addr.String()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignMessage signs a message with the resident node's private key. The
|
||||||
|
// returned signature string is zbase32 encoded and pubkey recoverable,
|
||||||
|
// meaning that only the message digest and signature are needed for
|
||||||
|
// verification.
|
||||||
|
func (r *rpcServer) SignMessage(ctx context.Context,
|
||||||
|
in *lnrpc.SignMessageRequest) (*lnrpc.SignMessageResponse, error) {
|
||||||
|
|
||||||
|
if in.Msg == nil {
|
||||||
|
return nil, fmt.Errorf("need a message to sign")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey := r.server.identityPriv.PubKey()
|
||||||
|
sigBytes, err := r.server.nodeSigner.SignCompact(pubkey, in.Msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := zbase32.EncodeToString(sigBytes)
|
||||||
|
return &lnrpc.SignMessageResponse{Signature: sig}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyMessage verifies a signature over a msg. The signature must be
|
||||||
|
// zbase32 encoded and signed by an active node in the resident node's
|
||||||
|
// channel database.
|
||||||
|
func (r *rpcServer) VerifyMessage(ctx context.Context,
|
||||||
|
in *lnrpc.VerifyMessageRequest) (*lnrpc.VerifyMessageResponse, error) {
|
||||||
|
|
||||||
|
if in.Msg == nil {
|
||||||
|
return nil, fmt.Errorf("need a message to verify")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The signature should be zbase32 encoded
|
||||||
|
sig, err := zbase32.DecodeString(in.Signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode signature: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := chainhash.DoubleHashB(in.Msg)
|
||||||
|
|
||||||
|
// RecoverCompact both recovers the pubkey and validates the signature.
|
||||||
|
pubKey, _, err := btcec.RecoverCompact(btcec.S256(), sig, digest)
|
||||||
|
if err != nil {
|
||||||
|
return &lnrpc.VerifyMessageResponse{Valid: false}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
graph := r.server.chanDB.ChannelGraph()
|
||||||
|
// TODO(phlip9): Require valid nodes to have capital in active channels.
|
||||||
|
_, active, err := graph.HasLightningNode(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to query graph: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &lnrpc.VerifyMessageResponse{Valid: active}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ConnectPeer attempts to establish a connection to a remote peer.
|
// ConnectPeer attempts to establish a connection to a remote peer.
|
||||||
func (r *rpcServer) ConnectPeer(ctx context.Context,
|
func (r *rpcServer) ConnectPeer(ctx context.Context,
|
||||||
in *lnrpc.ConnectPeerRequest) (*lnrpc.ConnectPeerResponse, error) {
|
in *lnrpc.ConnectPeerRequest) (*lnrpc.ConnectPeerResponse, error) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user