-
Notifications
You must be signed in to change notification settings - Fork 275
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
Truncate secret hash #722
Truncate secret hash #722
Conversation
This PR fixes the question asked here: https://github.com/rust-bitcoin/rust-bitcoin/blob/b05cf43d22d36bddd90f9e512ce15158736c4812/bitcoin/src/crypto/key.rs#L1529 |
This is non-urgent so I'll just wait till #699 goes in to debug CI failure. |
It is but I think many people perceive it mainly as string. Though if we were to truncate it it's unclear if we should truncate the beginning or the end. |
0540116
to
371ec9b
Compare
A hash is not an integer. In no sense is a hash an integer. You can interpret a hash as an integer but then you need to make a bunch of decisions about endianness. A hash is an opaque bytestring. If you truncate it then you should take the prefix not the suffix. This is what git does, this is what Bitcoin does in multiple places (e.g. bip32 fingerprints, the sha256 "checksum" on base58 addresses, etc), and this is what's done to construct "truncated hashes". |
@apoelstra this is also what GPG doesn't do - the short key identifiers are suffixes. ;) Yes, I hate it. Anyway, I do think we should allow it. |
Hah, TIL another crazy thing about GPG. (I guess I did know this actually, and have been annoyed about it because how hard it is visually to compare the short ID to the long fingerprint.) |
TIL, thanks. Raised issue: rust-bitcoin/hex-conservative#101 |
371ec9b
to
701a70e
Compare
In order correctly truncate the secret data we need to use recent version of `bitcoin_hashes`. Remove the range dependency and update the lock files.
Currently we are attempting to truncate the hash created using `bitcoin_hashes` by using the "width" formatting parameter instead of the "precision" parameter. `hex-conservative` truncates with the "precision" parameter as is expected since a hash is not an integral type. Use the formatting string `"{:.16}"` which is the "precision" formatting parameter.
701a70e
to
af997d8
Compare
Buuuuut, we'd have to remove the range dependency on |
Let's just remove the range dependency. It doesn't work properly anyway. See #673. I thought we'd removed it already because of that issue. |
Not sure what's up with CI. I think maybe nightly has broken something xargo-related? |
Seems so, this works RUSTUP_TOOLCHAIN=nightly-2024-08-04 xargo run --release --target=x86_64-unknown-linux-gnu |
That is the nightly version currently used in #699 |
@@ -37,7 +37,7 @@ global-context-less-secure = ["global-context"] | |||
secp256k1-sys = { version = "0.10.0", default-features = false, path = "./secp256k1-sys" } | |||
serde = { version = "1.0.103", default-features = false, optional = true } | |||
|
|||
hashes = { package = "bitcoin_hashes", version = ">= 0.12, <= 0.14", default-features = false, optional = true } | |||
hashes = { package = "bitcoin_hashes", version = "0.14", default-features = false, optional = true } |
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 believe moving the lower bound from 12 to 13 would've been enough - 0.13 already uses hex-conservative
. But maybe it requires certain version to work so in that case we would need:
[features]
hashes = ["dep:hashes", "dep:hex-conservative"]
[dependencies]
hex-conservative = { version = ">=0.?", optional = true }
hashes = { package = "bitcoin_hashes", version = ">= 0.13, <= 0.14", default-features = false, optional = true }
Where ?
in the version would be replaced with the one that had truncating implemented already. Note however that ranged version was broken by #723 so we have to fix it too if we want ranged versions.
But also, we have hex encoding/decoding in the crate anyway, so why not just have it directly as a dependency and use it for all formatting/decoding? Maybe optional with it being off making Debug
slower and Display
/FromStr
not present.
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.
To be clear, this is not why I've requested changes, you don't have to do it. It's just the wrong formatting that's a problem.
src/secret.rs
Outdated
@@ -28,7 +28,7 @@ macro_rules! impl_display_secret { | |||
hasher.write(&self.secret_bytes()); | |||
let hash = hasher.finish(); | |||
|
|||
f.debug_tuple(stringify!($thing)).field(&format_args!("#{:016x}", hash)).finish() | |||
f.debug_tuple(stringify!($thing)).field(&format_args!("#{:.16x}", hash)).finish() |
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 removes zero-padding since the type is just u64
. Also the commit message is wrong - the type returned from finish
is u64
, not Hasher
. We should just drop this commit.
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.
No sweat, dropped.
af997d8
to
15b3452
Compare
src/key.rs
Outdated
#[cfg(feature = "std")] | ||
{ | ||
let got = format!("{:?}", sk); | ||
let want = "SecretKey(#2f4d520bd6874292)"; |
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've just realized, I don't believe this value is stable - different compiler version/OSes/... may produce something different. We need to check these properties instead:
got.len() == im_too_lazy_to_count_it;
got.starts_with("SecretKey(#");
got.ends_with(")");
got[11..(got.len() - 1)].chars().all(|c| c.is_ascii_hexdigit());
!sk.display_secret().to_string().contains(&got[11..(got.len() - 1)]);
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.
Unrelated to this PR :)
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 is related. You've added a clearly broken test and the only reason it didn't manifest as broken is that nobody changed the hash algo between 1.63 and 1.80. The doc specifically says: "The internal algorithm is not specified, and so it and its hashes should not be relied upon over releases."
If you don't want to property-test it then please at least remove it. (with a comment that's why) Also it might be prudent to use RandomState::new().build_hasher()
at least in tests to make sure nobody adds the test back by accident but potentially to also frustrate an attacker that would like to somehow recover some bits of the private key. (Does it make sense @apoelstra ?)
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.
oh woops I can't read diffs, I was thinking that because I did not touch the std
behaviour I did not write the test. My bad. I dropped the test, I think we are all convinced that this PR does what it says it does, re truncation. (I was very testy [0] yesterday afternoon.)
[0] definition of 'testy': https://duckduckgo.com/?q=testy+def&t=canonical&ia=web
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.
@Kixunil I think, ultimately Rust's reduced-siphash would have to be super broken for it to actually leak any bits of the key, and in the limit it can only leak 64 bits because that's how big its output is, but it does make me a little nervous to treat it as a random oracle like this.
Using a randomized hasher would help with this, at the cost of making the "key fingerprints" no longer work as fingerprints when they occur in logs. I think that would be super frustrating and likely even confusing, so we shouldn't do it in non-test contexts. (And yes, I appreciate that users already shouldn't be expecting these "fingerprints" to stay consistent because stdlib might change them....but in practice they probably will.)
Honestly I'm inclined to get rid of the Hasher
stuff entirely and just put a <secret key; please enable
hashes in rust-secp to see a fingerprint>
type message in its place, just as we do for nostd users. I definitely wouldn't have written code that uses siphash like this and it looks like it originates in a time when I was much less strict in reviewed code than I would be with my own.
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.
But will ACK/merge this since it's a strict improvement on the status quo.
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.
All good points, just note that the output of Debug
is normally not considered stable.
just put a
<secret key; please enable
hashesin rust-secp to see a fingerprint>
type message in its place
That sounds great to me! Let's do it.
15b3452
to
3d1ce0d
Compare
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.
ACK 3d1ce0d
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.
ACK 3d1ce0d successfully ran local tests
The
core::hash::Hasher
andbitcoin_hashes
hash types implement formatting traits slightly differentlycore
defaults to using base 10Anywho, this PR fixes the secret display truncation.