-
Notifications
You must be signed in to change notification settings - Fork 421
Adds lightning message signing/verification/pk_recovery #844
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
TheBlueMatt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a number of issues in the zbase32 implementation, but I went ahead and fixed those while writing new tests at https://git.bitcoin.ninja/?p=rust-lightning;a=shortlog;h=refs/heads/fix-zbase32 so you can just pull the commits off that branch.
There's a few comments in the message_signing stuff too, but mostly just documentation and API questions.
| Ok(zbase32::encode(&sigrec_encode(sig))) | ||
| } | ||
|
|
||
| pub fn recover_pk(msg: &[u8], sig: String) -> Result<PublicKey, Error>{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be pub?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may so it can be bound to foreign langs, doesn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, my question is what's the use for it - do users have a direct need for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohh, there is.
lnd's verifymessage returns a bool + the recovered public key.
Furthermore, in the Eye of Satoshi case, we actually use this to authenticate users. All requests are signed but public keys are only shared when the user is registered, so EC recovery is used elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, ok! May be useful to rename this function, then - something that includes the word "verify" in it, indicating that the message is valid if the public key matches. Needs docs either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking this further I think there's a need for the three of them to be public.
lnd's implementation verifies the message by checking if the recovered public key matches the id of any of the nodes in the node's database.
c-lightning's allows you to provide a public key to verify. If you do, then it verifies the signature against the provided key, otherwise it does the same as lnd's, and checks it agains the known nodes.
How you check if the recovered key is part of a set of known nodes looks implementation dependant to me, so it feels making this public makes the most sense. Furthermore, in order to allow users to check this against something different than the nodes in the network (e.g. to allow ephemeral identities or identities created from key tweaking) this would be required. This is the exact use case for teos.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kinda like having the verify one, its certainly a much, much safer API, whereas this API its possible to forget to compare the public key against something known-good. Obviously if there's a use for it, then we should keep this, but having the server version feels nice too.
ariard
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK.
I just made it up. Since there is no spec I don't think there are any standard test vectors, but I can replicate the ones used by c-lightning and lnd so we can make sure those pass at least
If you feel it, maybe just propose a spec against the BOLT/BIPs ? It should be easy to land if you have test vectors and other implementations already have support.
|
|
||
| /// Decodes a zbase32 string to the original bytes, failing if the string was not encoded by a | ||
| /// proper zbase32 encoder. | ||
| pub fn decode(data: &str) -> Result<Vec<u8>, ()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This work well pub(super), no need to make them public ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made it public so it can be easily bound with methods that import lightning instead of working within the same repo for the bindings, like the Python bindings do. Happy do discuss it though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does that mean you need zbase32 for other uses, or just generally you think people may need zbase32 for other things and want it via bindings? I don't think there's any difference between bindings-vs-not when deciding if something should be pub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may not be needed for anything but signing, so I guess we could make it private.
|
Addressed formatting issue, nits, docs missing and added additional tests.
Wouldn't mind proposing adding this to the specs, but not sure where it would fit best. Any suggestions? |
Presumably a new BOLT? I dunno. Note that CI is failing because the |
|
I don't think we should have to wait on a new BOLT for this, though one would certainly be nice. I think the only thing still needed is some module-docs on zbase32. |
|
I've changed zbase32 to pub(crate) based on #844 (comment). If you feel there may be an additional use case that would require this to be public I'm happy to rebase and add docs. |
Codecov Report
@@ Coverage Diff @@
## main #844 +/- ##
==========================================
- Coverage 90.66% 90.23% -0.44%
==========================================
Files 51 57 +6
Lines 27135 29207 +2072
==========================================
+ Hits 24603 26355 +1752
- Misses 2532 2852 +320
Continue to review full report at Codecov.
|
|
|
||
| pub(crate) mod byte_utils; | ||
| pub(crate) mod chacha20; | ||
| pub(crate) mod zbase32; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, the fuzztarget mode needs it, see CI.
| pub(crate) mod zbase32; | |
| #[cfg(feature = "fuzztarget")] | |
| pub mod zbase32; | |
| #[cfg(not(feature = "fuzztarget"))] | |
| pub(crate) mod zbase32; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added it in cf384a3 and changed zbase32::{encode, decode} to pub so they can be used in fuzzing.
| #[cfg(feature = "fuzztarget")] | ||
| pub mod zbase32; | ||
| #[cfg(not(feature = "fuzztarget"))] | ||
| pub(crate) mod zbase32; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The compile error in CI is that poly1305 is no longer #[cfg(not(feature = "fuzztarget"))] (ie not compiled in fuzzing). You need to duplicate the cfg line, above.
valentinewallace
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry to nit you to death on grammar, wouldn't be so pressed if it weren't public docs.
Lmk if the testing instructions are unclear or if I'm in the wrong there
|
|
||
| /// Verifies a message was signed by a PrivateKey that derives to a given PublicKey, given a message, a signature, | ||
| /// and the PublicKey. | ||
| pub fn verify(msg: &[u8], sig: &str, pk: PublicKey) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested this with the sample and lnd's telling me the message is invalid (the pubkey's correct): https://i.imgur.com/lizoFib.png
Haven't tested further.
Instructions to reproduce/test further (I used Polar but feel free to use whatever you want):
- clone this branch of the sample: https://github.com/valentinewallace/ldk-sample/tree/for-testing-signing (it uses a branch of
rust-lightningthat's just your branch + one commit to expose a struct that's needed) - I used Polar: https://github.com/jamaljsr/polar by cloning and running
yarn dev - start a new test network in Polar and add an
lndnode - start the sample:
cargo run polaruser:[email protected]:18443 ./ 9738 regtest - in the sample CLI:
connectpeer <lnd node>, wherelnd node's found underalice > Connect > P2P External - in the sample CLI:
signmessage(the message is hardcoded, see: https://github.com/valentinewallace/ldk-sample/blob/for-testing-signing/src/cli.rs#L378) - copy this output, then open a terminal for the lnd node under
alice > Actions > Terminal > Launch lncli verifymessage --msg abcdefghijklmnopqrstuvwxyz1234567890 --sig <sig you just copied>
Also added verifymessage to the sample: https://github.com/valentinewallace/ldk-sample/blob/for-testing-signing/src/cli.rs#L390 if you wanna test that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested this with the sample and lnd's telling me the message is invalid (the pubkey's correct): https://i.imgur.com/lizoFib.png
Wouldn't this mean the two nodes are not properly connected (or the node that originated the signature is not on the verifiers database?). I'm wondering since you point that the recovered public key is correct, so the signature is actually valid.
From lnd docs:
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. In addition to returning the validity of the signature, VerifyMessage also returns the recovered pubkey from the signature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@valentinewallace I double checked this against lnd and c-lightning. Both return:
{
"pubkey": our_public_key,
"verified": false
}
However, in c-lightning, if our public key is passed as optional parameter then the message is verified.
Checking c-lightning's docs, we can find the following:
As a special case, if pubkey is not specified, we will try every known node key (as per listnodes), and verification succeeds if it matches for any one of them.
And, If I check listnodes, the list is empty (even though the node is one of our peers), so I'm guessing that's the root of the issue. The pubkey is correctly recovered in both cases, but the node seems not to be part of neither of the peers known nodes, so it fails validation against known nodes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Furthermore, querying describegraph in lnd's node does not show our testing node, which I'd say backs the aforementioned claim.
I'm not sure why that's happening though. Any thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Asked around #c-lightning on IRC, looks like a channel announced is required to show-up in listnodes, and I'm assuming the same may apply to describegraph in lnd:
20:12 <sr_gi> Hey guys, what is needed for a node to show in listnodes? I was doing some testing using Polar and
connecting a c-lightning node to a rust-lightning node and even though they were connected the peer did no show up
in the list. Is a channel required or something on those lines?
20:14 <@cdecker> An announced channel is required, yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed offline, this seems like a reasonable explanation for why lnd's giving verified: false :) Though it might be reasonable to test at some point with a public channel using the sample.
No worries, I actually appreciated it. Addressed the grammar issues you pointed out and a couple more related I found. |
| // You may not use this file except in accordance with one or both of these | ||
| // licenses. | ||
|
|
||
| //! Ligthning message signing and verification lives here. These tools can be used to sign messages using the node's |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lightning, transpose the t and h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😅
The original work is licensed dual MIT+Apache-2 just like us, so the license header should not only mention MIT.
ariard
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review ACK 7bcf5a1
| use bitcoin::secp256k1::recovery::{RecoverableSignature, RecoveryId}; | ||
| use bitcoin::secp256k1::{Error, Message, PublicKey, Secp256k1, SecretKey}; | ||
|
|
||
| static LN_MESSAGE_PREFIX: &[u8] = b"Lightning Signed Message:"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bind message to a lightning signing context to avoid replay of signed data in another context with same key material ? Though w.r.t to this concern our message API signing is safe as this is always committed by the implementation and not an argument caller, not sure if it's worthy to document.
Do we have risks of harmful API misuses we should document ? I think we're good, can't foresee real ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's the purpose of the prefix AFAIK. I can write a comment there stating the purpose of the prefix if you feel it's necessary.
Do we have risks of harmful API misuses we should document ? I think we're good, can't foresee real ones.
I cannot see any either.
Adds message signing, verification and public key recovery.
Signatures are pk-recoverable zbase32-encoded, following:
SigRechas first byte 31 + recovery id, followed by 64 bytesig.close #843