mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-20 13:54:36 +01:00
lightningd/hsm_control.c: Implement getsharedsecret
.
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.
This commit is contained in:
parent
1b0807444b
commit
d9b2482415
10 changed files with 284 additions and 2 deletions
|
@ -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 */
|
||||
|
|
|
@ -1126,3 +1126,15 @@ class LightningRpc(UnixDomainSocketRpc):
|
|||
"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)
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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>
|
||||
|
|
96
doc/lightning-getsharedsecret.7
generated
Normal file
96
doc/lightning-getsharedsecret.7
generated
Normal file
|
@ -0,0 +1,96 @@
|
|||
.TH "LIGHTNING-GETSHAREDSECRET" "7" "" "" "lightning-getsharedsecret"
|
||||
.SH NAME
|
||||
lightning-getsharedsecret - Command for computing an ECDH
|
||||
.SH SYNOPSIS
|
||||
|
||||
\fBgetsharedsecret\fR \fIpoint\fR
|
||||
|
||||
.SH DESCRIPTION
|
||||
|
||||
The \fBgetsharedsecret\fR RPC command computes a shared secret from a
|
||||
given public \fIpoint\fR, and the secret key of this node\.
|
||||
The \fIpoint\fR is a hexadecimal string of the compressed public
|
||||
key DER-encoding of the SECP256K1 point\.
|
||||
|
||||
.SH RETURN VALUE
|
||||
|
||||
On success, \fBgetsharedsecret\fR returns a field \fIshared_secret\fR,
|
||||
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\.
|
||||
|
||||
.SH 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 \fBlightningd\fR 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 \fIpoint\fR, you do not need to
|
||||
even call \fBgetsharedsecret\fR, 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 \fBgetsharedsecret\fR 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\.
|
||||
|
||||
.SH AUTHOR
|
||||
|
||||
ZmnSCPxj \fI<ZmnSCPxj@protonmail.com\fR> is mainly responsible\.
|
||||
|
||||
.SH SEE ALSO
|
||||
.SH RESOURCES
|
||||
.RS
|
||||
.IP \[bu]
|
||||
BOLT 4: \fIhttps://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#shared-secret\fR
|
||||
.IP \[bu]
|
||||
BOLT 8: \fIhttps://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#handshake-state\fR
|
||||
.IP \[bu]
|
||||
SECG SEC-1 ECIES: \fIhttps://secg.org/sec1-v2.pdf\fR
|
||||
.IP \[bu]
|
||||
Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
|
||||
|
||||
.RE
|
93
doc/lightning-getsharedsecret.7.md
Normal file
93
doc/lightning-getsharedsecret.7.md
Normal file
|
@ -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 <<ZmnSCPxj@protonmail.com>> 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
doc/lightning-listpeers.7
generated
7
doc/lightning-listpeers.7
generated
|
@ -205,11 +205,16 @@ a number followed by a string unit\.
|
|||
The peer imposes this on us, default is 1% of the total channel capacity\.
|
||||
.IP \[bu]
|
||||
\fIspendable_msat\fR: A string describing an \fB\fIestimate\fR\fR of how much we
|
||||
can send out over this channel;
|
||||
can send out over this channel in a single payment (or payment-part for
|
||||
multi-part payments);
|
||||
a number followed by a string unit\.
|
||||
This is an \fB\fIestimate\fR\fR, which can be wrong because adding HTLCs requires
|
||||
an increase in fees paid to onchain miners, and onchain fees change
|
||||
dynamically according to onchain activity\.
|
||||
For a sufficiently-large channel with capacity on your side, this can
|
||||
be limited by the rules imposed under certain blockchains;
|
||||
for example, individual Bitcoin mainnet payment-parts cannot exceed
|
||||
42\.94967295 mBTC\.
|
||||
.IP \[bu]
|
||||
\fIminimum_htlc_in_msat\fR: A string describing the minimum amount that
|
||||
an HTLC must have before we accept it\.
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
#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>
|
||||
|
@ -12,6 +15,8 @@
|
|||
#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>
|
||||
|
@ -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", ¶m_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);
|
||||
|
|
|
@ -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"])
|
||||
|
|
Loading…
Add table
Reference in a new issue