-
Notifications
You must be signed in to change notification settings - Fork 624
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
More complete MessageSignature.is_signed_by_address
#684
More complete MessageSignature.is_signed_by_address
#684
Conversation
revocery_id is for convenience. segewit_type will aid with signature verification.
I noticed that only 1 address type was supported so this is an attempt to make signature vreification less specific. Mostly ported from: https://github.com/bitcoinjs/bitcoinjs-message/blob/c43430f4c03c292c719e7801e425d887cbdf7464/index.js#L181-L233
358f8d9
to
4f06288
Compare
Makes more sense with how it is being used.
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.
Thanks for the PR! I spent a bunch of time reviewing this one for you but I'm concerned I might have come across as overly picky. I'm not a core rust-bitcoin
dev in any way so this review is more stylistic than reviewing the logic. Normally I wouldn't do this until the logic was reviewed by others but since you said you were new to Rust I thought I'd take the chance to help you and help myself get deeper into rust-bitcoin
also. I hope this helps.
FTR I can confirm that cargo +1.36 test --features=secp-recovery,base64 test_message_signature
fails with the minimal diff below. (Which is what this PR fixes).
diff --git a/src/util/misc.rs b/src/util/misc.rs
index 553fa6a..9657a9c 100644
--- a/src/util/misc.rs
+++ b/src/util/misc.rs
@@ -333,9 +333,9 @@ mod tests {
let p2pkh = ::Address::p2pkh(&pubkey, ::Network::Bitcoin);
assert_eq!(signature2.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(true));
let p2wpkh = ::Address::p2wpkh(&pubkey, ::Network::Bitcoin).unwrap();
- assert_eq!(signature2.is_signed_by_address(&secp, &p2wpkh, msg_hash), Ok(false));
+ assert_eq!(signature2.is_signed_by_address(&secp, &p2wpkh, msg_hash), Ok(true));
let p2shwpkh = ::Address::p2shwpkh(&pubkey, ::Network::Bitcoin).unwrap();
- assert_eq!(signature2.is_signed_by_address(&secp, &p2shwpkh, msg_hash), Ok(false));
+ assert_eq!(signature2.is_signed_by_address(&secp, &p2shwpkh, msg_hash), Ok(true));
}
}
/// Segwit type realted to this signature | ||
pub segwit_type: Option<SegwitType>, | ||
/// Recovery Id for easy access | ||
pub recovery_id: RecoveryId, |
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.
Perhaps pub recid: RecoveryId
would be equally as descriptive?
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.
recid
sounds like something to do with receiving. recovery_id
is a great name.
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 kept as recovery_id
but I was using recid
in a few places. In 5ea6b6a I made it so I consistently call it recovery_id
src/util/misc.rs
Outdated
}) | ||
|
||
// Use .serialize because we always want compact serialization | ||
let pubkey_hash = super::hash160(&pubkey.key.serialize().to_vec()).to_vec(); |
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.
Rust note, usage of super
is, as far as I understand, typically restricted to test modules (use super::*
) or cases where it really makes sense [1]. Use here is likely to make moving this code harder, so makes the code less maintainable. This might be just personal opinion but I'd prefer to see util::hash160
here and a use statement use crate::util
. Just mentioning since you said you were newish to Rust, hope this is helpful :)
- For example the import you add in this PR that brings functions from outside this module, but in this file, into scope seems like a sane usage to me.
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.
Interesting, I was never under the impression that super::
is restricted to anything despite being quite long and deep in Rust. Maybe I missed 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.
Also now that I had a bit of time to look at it better, I noticed .to_vec()
. I think with a bit of effort the allocation can be avoided if hash160
accepts any reader.
Also I'm not convinced that the latter to_vec()
is needed.
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.
@tcharding I'm going to go with
use crate::util::misc::{bech32_decode, segwit_redeem_hash, hash160, get_payload_bytes};
and remove the use of super
outside of the tests. Let me know if that is sufficient.
Will look into to_vec()
usage shortly
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.
Resolved super
usage in 5ea6b6a
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.
Will need to look at it better to write the exact code (hopefully tomorrow) but generally, hasher can be considered a writer and data can be considered reader.
The latter to_vec
shouldn't be needed because you only need slice later and borrowing rules allow it. (Unless I'm missing something very subtle - LMK if you get a compile 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.
Actually, serialize()
produces Vec<u8>
this is not great but proves to_vec
is not needed because it's essentially a clone()
.
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.
That link is PublicKey.serialize
which is easily confused with PublicKey.key.serialize()
(and PublicKey.key.serialize_uncompressed()). PublickKey.key
is a secpk256k1::PublicKey
. Removing the to_vec()
in &pubkey.key.serialize().to_vec()
results in
error[E0308]: mismatched types
--> src/util/misc.rs:200:39
|
200 | let pubkey_hash = hash160(&pubkey.key.serialize()).to_vec();
| ^^^^^^^^^^^^^^^^^^^^^^^ expected struct `std::vec::Vec`, found array `[u8; 33]`
|
= note: expected reference `&std::vec::Vec<u8>`
found reference `&[u8; 33]`
error: aborting due to previous 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.
Implemented it here: Kixunil@858c83f (I can't make a PR to your branch, no clue why).
is easily confused with
PublicKey.key.serialize()
Oh, that's even better as there's no Vec
, so no more work needed to remove the allocation!
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.
Nice! Thanks. This is great to see.
I can't make a PR to your branch, no clue why
Probably a permission issue on my fork? Anyway I manage to create a PR and merge it (Shatnerz#1)
assert_eq!(signature2.is_signed_by_address(&secp, &p2shwpkh, msg_hash), Ok(true)); | ||
} | ||
|
||
mod is_signed_by_address { |
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.
Great to see you adding unit tests, and please don't take this the wrong way; what are these tests testing that is not already tested above in test_message_signature
?
It would have been super nice if the changes above to test_message_signature
were in a separate patch after the patch that does the work. Then reviewers could quickly re-arrange the patches and run the tests to see them fail. Admittedly this might be an overkill in this example because its trivial to just change the true
to false
myself but thought I'd note it for your future patches!
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.
Honestly, I don't think they technically add that much. They just happened to be values that I had on hand that I new were accurate and should work. I also like that they are each separate tests and every signature type isn't forced into single unit test. Imo it helps debug when something inevitably breaks the tests.
I'm willing to change or remove anything related to my tests here. It just helped me get off the ground and test my changes as I went along.
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.
Yes, having them split up is a good thing. Didn't review so much that I could comment on anything else.
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 definitely like to see one test one test function :)
@tcharding This is fantastic feedback. Thanks. I will try to resolve within the next 72 hours. Hopefully today |
Changes: - Change `from_flag_bytes` response to just an Option - `recid` -> `recovery_id` - remove superfluous parentheses - remove usage of `super` outside of testing - cleaner if/else usage in `bech32_from_words` - remoe nested import Thank you @tcharding and @Kixunil for the suggestions
Changes: - Change `from_flag_bytes` response to just an Option - `recid` -> `recovery_id` - remove superfluous parentheses - remove usage of `super` outside of testing - cleaner if/else usage in `bech32_from_words` - remoe nested import - use checked_sub when setting flag_byte Thank you @tcharding and @Kixunil for the suggestions
37d3cd8
to
5ea6b6a
Compare
Addressed all feedback as of this comment. I closed threads on anything with direct actionable feedback. Hopefully that's not a faux pas. |
@Kixunil's suggestion
b2f2564
to
368c506
Compare
as suggested by @tcharding
Note: Did not fix the existing minor violations
|
This removes a bunch of `to_vec()` calls elliminating allocations and simplifies the implementation of `hash160` helper function by using hash from `bitcoin_hashes`.
Remove needless allocations and simplify the code
I can't seem to reproduce this CI result locally: https://github.com/rust-bitcoin/rust-bitcoin/runs/4081690675?check_suite_focus=true Can anyone offer assistance so I fixing this doesn't require such long cycle times? I assume the 1.29.0 is the rust version. I tried
edit: pushed attempted fix. I'll go through the workflow file when I have a chance and make sure I can recreate the CI results |
- accessing extern crates through prelude - use of "crate" in path
c6565b8
to
4314c43
Compare
If there is ever indication that this might get reviewed, I can also start looking into a follow up MR to support taproot addresses when using |
Sorry for leaving this PR for some time w/o review; all maintainers are currently focused on completing Taproot support as highest priority. I will be able to review this PR as soon as Taproot release is out and I can read the specs on how the message signing should be done. But for sure I'd like to have this functionality into the library - especially with Taproot follow-up |
@dr-orlovsky sounds good and no worries.I forgot that the taproot launch was so soon (lol days ago now). Hopefully I can get some time to look into taproot signature verification |
Existing bit manipulation was off. Changed 4 -> 12
I don't quite understand this comment. Does that actively change anything about verifying a signature using an address? Are you referring to a specific piece of code. Sorry some time has passed so I may need a refresher. |
No, nothing has changed about verifying a siganture using an address since it was implemented in Bitcoin Core in 2011. |
Changes: - Change `from_flag_bytes` response to just an Option - `recid` -> `recovery_id` - remove superfluous parentheses - remove usage of `super` outside of testing - cleaner if/else usage in `bech32_from_words` - remoe nested import - use checked_sub when setting flag_byte Thank you @tcharding and @Kixunil for the suggestions
Do a bunch of refactorings while playing around with the original PR. ref: rust-bitcoin#684
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.
Thanks for your patience. I had another play around with this. I did a bunch of minor changes but I don't want to derail your PR with too fine a review before some of the other lads, with more knowledge than myself, do their higher level reviews. I pushed a branch [1] with the same name to my own fork with a single refactoring patch that you can look at if it amuses you. My main contribution was refactoring all the unit tests into small concise tests (incl. helper functions), feel free to use any, or none, of my ideas as your own if you wish. Apart from that there was a few re-names (that are kind of personal preference) and a few Rust/stlylistic things).
In future, it would probably be easier to review if there were less patches and later patches didn't make changes to earlier patches. Instead rebase and squash makes reviewing easier. I know this one has been open a long time so don't feel obliged to do that here, I just wanted to mention it. Thanks for your effort, its super appreciated.
[1] https://github.com/tcharding/rust-bitcoin/tree/feat/proper-is-signed-by-address
} | ||
|
||
/// Convert u5 vec into u8 vec | ||
fn bech32_from_words(words: &[bech32::u5]) -> Result<Vec<u8>, Bech32DecodingError> { |
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 whole function is already provided by bech32
library. Function implementation can be replaced by
bech32::convert_bits(words, 5, 8, true).map_err(|e| Bech32DecodingError::InvalidEncoding(e))
EDIT: the map_err
is just to make it build, you may want to do something different.
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.
Thank you, good catch
concept NACK this PR. There is no signmessage standard for non-pkh address types. |
Agree with @apoelstra on concept NACK but would accept a PR (or two PRs) that returns |
When I get funds, some non-profits are asking me to sign with my address - and I am hesitant to move to the old P2PKH just because there is not standard to sign with an address for P2WPKH or P2TR. If there is no standard for that, let's make it. @Shatnerz you are welcome to do this contribution to a |
Yes, let's make it! I think this is BIP-worthy. |
Have you looked at BIP-322? bitcoin/bitcoin#24058 |
I would ACK a correctly written implementation of BIP-322 or, preferably, required additional methods to make it implementable in a separate crate. We need to stop the scope creep. |
Also found this: rust-bitcoin/rust-miniscript#240 |
Sorry guys. The timing is unfortunate because I'm incredibly busy this month. I'll try to review the above comments more closely when I get a chance. It looks like a negative response to this PR. I'm probably missing something at a glance. Comments seem to talk about signing when this PR only affects verification. Do you have any suggestions on fixing |
I'm just closing due to inactivity. Feel free to reach out if you ever need a hand expanding |
Inspired by this comment: rust-bitcoin#684 (comment)
Inspired by this comment: rust-bitcoin#684 (comment)
Inspired by this comment: rust-bitcoin#684 (comment)
Inspired by this comment: rust-bitcoin#684 (comment)
Inspired by this comment: rust-bitcoin#684 (comment)
Thanks for showing an interest in this @Shatnerz, I look forward to your future PRs! |
Inspired by this comment: rust-bitcoin#684 (comment)
Inspired by this comment: rust-bitcoin#684 (comment)
Inspired by this comment: rust-bitcoin#684 (comment)
Inspired by this comment: rust-bitcoin#684 (comment)
Inspired by this comment: rust-bitcoin#684 (comment)
…_address()` 79cee4c fix: Error on unsuported addresses in `is_signed_by_address` (Andrew Ahlers) Pull request description: This is a direct response to this comment: #684 (comment) Currently unsupported addresses simply return `false` which is highly misleading as it is entirely possible that the keys related to a given address were used to sign the message. If this is successful I can follow up with > a method to check if a PublicKey is associated with an address ACKs for top commit: apoelstra: ACK 79cee4c Kixunil: ACK 79cee4c Tree-SHA512: 0c2f15696c63272e8ad1ecc0959c828f2d6c4aa16a7d099235c3f6bd287a0c20e034752331644c4bcc3af61ba4d739ad6795cbcea61c9e615c1eb7b43ebf0eeb
51fef76 feat: Add Address.is_related_to_pubkey() (Andrew Ahlers) Pull request description: ## Motivation This is addressing the second half of this comment: #684 (comment) > but would accept a PR (or two PRs) that returns Result<bool, UnsupportedAddress> and a method to check if a PublicKey is associated with an address. (The first half was addressed [here](#819)) These changes will help build out and improve message signature verification. We don't necessarily need to add it to this crate but it allows for easy verification with something such as: 1. recovering a pubkey 2. checking if that pubkey relates to the given address ## Possible Improvements - There is likely a better name than `is_related_to_secp256k1_key()` - This could drop the `secp256k1` part of the name and take in a Pubkey enum that also supports Schnorr pubkeys and then this could be used for taproot addresses as well. This felt like a much larger change that will likely get turned down. Verifying taproot is simple enough and if absolutely desired, similar functions can be added for schnorr keys (tweaked and untweaked) ACKs for top commit: Kixunil: ACK 51fef76 for merging after TR apoelstra: ACK 51fef76 Tree-SHA512: c9ab8c0f101fb4c647713e7f500656617025d8741676e8eb8a3132009dde9937d50cf9ac3d8055feb14452324a292397e46639cbaca71cac77af4b06dc42d09d
I noticed that the current version only seems to support p2pkh and all other address types default to false with no error thrown.
I effectively just ported this piece from bitcoinjs-message: https://github.com/bitcoinjs/bitcoinjs-message/blob/c43430f4c03c292c719e7801e425d887cbdf7464/index.js#L181-L233
I'm fairly new to rust, but I'm open to all suggestions to learn and to get this merged.
Testing
I included unit tests for
I also amended the
test_message_signature
test which was assertingfalse
on values that should clearly betrue
, but were just returningfalse
due to lack of support.