Skip to content

Commit

Permalink
lightningd/hsm_control.c: Implement getsharedsecret.
Browse files Browse the repository at this point in the history
ChangeLog-Added: New `getsharedsecret` command, which lets you compute a shared secret with this node knowing only a public point. This implements the BOLT standard of hashing the ECDH point, and is incompatible with ECIES.
  • Loading branch information
ZmnSCPxj authored and rustyrussell committed Feb 28, 2020
1 parent 3e04f96 commit 5b30b81
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 2 deletions.
3 changes: 3 additions & 0 deletions common/jsonrpc_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,7 @@ static const errcode_t INVOICE_HINTS_GAVE_NO_ROUTES = 902;
static const errcode_t INVOICE_EXPIRED_DURING_WAIT = 903;
static const errcode_t INVOICE_WAIT_TIMED_OUT = 904;

/* Errors from HSM crypto operations. */
static const errcode_t HSM_ECDH_FAILED = 800;

#endif /* LIGHTNING_COMMON_JSONRPC_ERRORS_H */
12 changes: 12 additions & 0 deletions contrib/pyln-client/pyln/client/lightning.py
Original file line number Diff line number Diff line change
Expand Up @@ -1126,3 +1126,15 @@ def checkmessage(self, message, zbase, pubkey=None):
"pubkey": pubkey,
}
return self.call("checkmessage", payload)

def getsharedsecret(self, point, **kwargs):
"""
Compute the hash of the Elliptic Curve Diffie Hellman shared
secret point from this node private key and an
input {point}.
"""
payload = {
"point": point
}
payload.update({k: v for k, v in kwargs.items()})
return self.call("getsharedsecret", payload)
1 change: 1 addition & 0 deletions doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ MANPAGES := doc/lightning-cli.1 \
doc/lightning-fundchannel_complete.7 \
doc/lightning-fundchannel_cancel.7 \
doc/lightning-getroute.7 \
doc/lightning-getsharedsecret.7 \
doc/lightning-invoice.7 \
doc/lightning-listchannels.7 \
doc/lightning-listforwards.7 \
Expand Down
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ c-lightning Documentation
lightning-fundchannel_complete <lightning-fundchannel_complete.7.md>
lightning-fundchannel_start <lightning-fundchannel_start.7.md>
lightning-getroute <lightning-getroute.7.md>
lightning-getsharedsecret <lightning-getsharedsecret.7.md>
lightning-invoice <lightning-invoice.7.md>
lightning-listchannels <lightning-listchannels.7.md>
lightning-listforwards <lightning-listforwards.7.md>
Expand Down
96 changes: 96 additions & 0 deletions doc/lightning-getsharedsecret.7

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

93 changes: 93 additions & 0 deletions doc/lightning-getsharedsecret.7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
lightning-getsharedsecret -- Command for computing an ECDH
==========================================================

SYNOPSIS
--------

**getsharedsecret** *point*

DESCRIPTION
-----------

The **getsharedsecret** RPC command computes a shared secret from a
given public *point*, and the secret key of this node.
The *point* is a hexadecimal string of the compressed public
key DER-encoding of the SECP256K1 point.

RETURN VALUE
------------

On success, **getsharedsecret** returns a field *shared\_secret*,
which is a hexadecimal string of the 256-bit SHA-2 of the
compressed public key DER-encoding of the SECP256K1 point
that is the shared secret generated using the
Elliptic Curve Diffie-Hellman algorithm.
This field is 32 bytes (64 hexadecimal characters in a string).

This command may fail if communications with the HSM has a
problem;
by default lightningd uses a software "HSM" which should
never fail in this way.
(As of the time of this writing there is no true hardware
HSM that lightningd can use, but we are leaving this
possibilty open in the future.)
In that case, it will return with an error code of 800.

CRYPTOGRAPHIC STANDARDS
-----------------------

This serves as a key agreement scheme in elliptic-curve based
cryptographic standards.

However, note that most key agreement schemes based on
Elliptic-Curve Diffie-Hellman do not hash the DER-compressed
point.
Standards like SECG SEC-1 ECIES specify using the X coordinate
of the point instead.
The Lightning BOLT standard (which `lightningd` uses), unlike
most other cryptographic standards, specifies the SHA-256 hash
of the DER-compressed encoding of the point.

It is not possible to extract the X coordinate of the ECDH point
via this API, since there is no known way to reverse the 256-bit
SHA-2 hash function.
Thus there is no way to implement ECIES and similar standards using
this API.

If you know the secret key behind *point*, you do not need to
even call **getsharedsecret**, you can just multiply the secret key
with the node public key.

Typically, a sender will generate an ephemeral secret key
and multiply it with the node public key,
then use the result to derive an encryption key
for a symmetric encryption scheme
to encrypt a message that can be read only by that node.
Then the ephemeral secret key is multiplied
by the standard generator point,
and the ephemeral public key and the encrypted message is
sent to the node,
which then uses **getsharedsecret** to derive the same key.

The above sketch elides important details like
key derivation function, stream encryption scheme,
message authentication code, and so on.
You should follow an established standard and avoid
rolling your own crypto.

AUTHOR
------

ZmnSCPxj <<[email protected]>> is mainly responsible.

SEE ALSO
--------

RESOURCES
---------

* BOLT 4: <https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#shared-secret>
* BOLT 8: <https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#handshake-state>
* SECG SEC-1 ECIES: <https://secg.org/sec1-v2.pdf>
* Main web site: <https://github.com/ElementsProject/lightning>

7 changes: 6 additions & 1 deletion doc/lightning-listpeers.7

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion hsmd/hsmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -2055,7 +2055,8 @@ int main(int argc, char *argv[])
status_setup_async(status_conn);
uintmap_init(&clients);

master = new_client(NULL, NULL, NULL, 0, HSM_CAP_MASTER | HSM_CAP_SIGN_GOSSIP | HSM_CAP_ECDH,
master = new_client(NULL, NULL, NULL, 0,
HSM_CAP_MASTER | HSM_CAP_SIGN_GOSSIP | HSM_CAP_ECDH,
REQ_FD);

/* First client == lightningd. */
Expand Down
44 changes: 44 additions & 0 deletions lightningd/hsm_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@
#include <ccan/fdpass/fdpass.h>
#include <ccan/io/io.h>
#include <ccan/take/take.h>
#include <common/json.h>
#include <common/jsonrpc_errors.h>
#include <common/param.h>
#include <common/status.h>
#include <common/utils.h>
#include <errno.h>
#include <hsmd/gen_hsm_wire.h>
#include <inttypes.h>
#include <lightningd/bitcoind.h>
#include <lightningd/hsm_control.h>
#include <lightningd/json.h>
#include <lightningd/jsonrpc.h>
#include <lightningd/log.h>
#include <lightningd/log_status.h>
#include <string.h>
Expand Down Expand Up @@ -123,3 +128,42 @@ void hsm_init(struct lightningd *ld)
errx(1, "HSM did not give init reply");
}
}

static struct command_result *json_getsharedsecret(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct lightningd *ld = cmd->ld;
struct pubkey *point;
struct secret ss;
u8 *msg;
struct json_stream *response;

if (!param(cmd, buffer, params,
p_req("point", &param_pubkey, &point),
NULL))
return command_param_failed();

msg = towire_hsm_ecdh_req(NULL, point);
if (!wire_sync_write(ld->hsm_fd, take(msg)))
return command_fail(cmd, HSM_ECDH_FAILED,
"Failed to request ECDH to HSM");
msg = wire_sync_read(tmpctx, ld->hsm_fd);
if (!fromwire_hsm_ecdh_resp(msg, &ss))
return command_fail(cmd, HSM_ECDH_FAILED,
"Failed HSM response for ECDH");

response = json_stream_success(cmd);
json_add_secret(response, "shared_secret", &ss);
return command_success(cmd, response);
}

static const struct json_command getsharedsecret_command = {
"getsharedsecret",
"utility", /* FIXME: Or "crypto"? */
&json_getsharedsecret,
"Compute the hash of the Elliptic Curve Diffie Hellman shared secret point from "
"this node private key and an input {point}."
};
AUTODATA(json_command, &getsharedsecret_command);
26 changes: 26 additions & 0 deletions tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2151,3 +2151,29 @@ def test_sendcustommsg(node_factory):
l4.daemon.wait_for_log(
r'Got a custom message {serialized} from peer {peer_id}'.format(
serialized=serialized, peer_id=l2.info['id']))


@unittest.skipIf(not DEVELOPER, "needs --dev-force-privkey")
def test_getsharedsecret(node_factory):
"""
Test getsharedsecret command.
"""
# From BOLT 8 test vectors.
options = [
{"dev-force-privkey": "1212121212121212121212121212121212121212121212121212121212121212"},
{}
]
l1, l2 = node_factory.get_nodes(2, opts=options)

# Check BOLT 8 test vectors.
shared_secret = l1.rpc.getsharedsecret("028d7500dd4c12685d1f568b4c2b5048e8534b873319f3a8daa612b469132ec7f7")['shared_secret']
assert (shared_secret == "1e2fb3c8fe8fb9f262f649f64d26ecf0f2c0a805a767cf02dc2d77a6ef1fdcc3")

# Clear the forced privkey of l1.
del l1.daemon.opts["dev-force-privkey"]
l1.restart()

# l1 and l2 can generate the same shared secret
# knowing only the public key of the other.
assert (l1.rpc.getsharedsecret(l2.info["id"])["shared_secret"]
== l2.rpc.getsharedsecret(l1.info["id"])["shared_secret"])

0 comments on commit 5b30b81

Please sign in to comment.