Skip to content

Commit a5147bb

Browse files
authored
Merge pull request #206 from sgeisler/2020-04-hashes
Add optional bitcoin_hashes feature to implement ThirtyTwoByteHash
2 parents a184212 + 8979a93 commit a5147bb

File tree

3 files changed

+91
-6
lines changed

3 files changed

+91
-6
lines changed

.travis.yml

+2
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ script:
3030
cargo generate-lockfile --verbose && cargo update -p cc --precise "1.0.41" --verbose;
3131
fi
3232
- cargo build --verbose --no-default-features
33+
- cargo build --verbose --no-default-features --features="bitcoin_hashes"
3334
- cargo build --verbose --no-default-features --features="serde"
3435
- cargo build --verbose --no-default-features --features="lowmemory"
3536
- cargo build --verbose --no-default-features --features="rand"
3637
- cargo build --verbose --no-default-features --features="rand serde recovery endomorphism"
3738
- cargo build --verbose --no-default-features --features="fuzztarget recovery"
3839
- cargo build --verbose --features=rand
3940
- cargo test --no-run --features=fuzztarget
41+
- cargo test --verbose --features="bitcoin_hashes"
4042
- cargo test --verbose --features=rand
4143
- cargo test --verbose --features="rand rand-std"
4244
- cargo test --verbose --features="rand serde"

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ bitcoin_hashes = "0.7"
5050
wasm-bindgen-test = "0.3"
5151
rand = { version = "0.6", features = ["wasm-bindgen"] }
5252

53+
[dependencies.bitcoin_hashes]
54+
version = "0.7"
55+
optional = true
56+
5357
[dependencies.rand]
5458
version = "0.6"
5559
optional = true

src/lib.rs

+85-6
Original file line numberDiff line numberDiff line change
@@ -38,28 +38,31 @@
3838
//!
3939
//! ```rust
4040
//! extern crate secp256k1;
41+
//! # #[cfg(feature="bitcoin_hashes")]
42+
//! extern crate bitcoin_hashes;
4143
//! # #[cfg(feature="rand")]
4244
//! extern crate rand;
4345
//!
4446
//! #
4547
//! # fn main() {
46-
//! # #[cfg(feature="rand")] {
47-
//! use rand::OsRng;
48+
//! # #[cfg(all(feature="rand", feature="bitcoin_hashes"))] {
49+
//! use rand::rngs::OsRng;
4850
//! use secp256k1::{Secp256k1, Message};
51+
//! use bitcoin_hashes::sha256;
4952
//!
5053
//! let secp = Secp256k1::new();
5154
//! let mut rng = OsRng::new().expect("OsRng");
5255
//! let (secret_key, public_key) = secp.generate_keypair(&mut rng);
53-
//! let message = Message::from_slice(&[0xab; 32]).expect("32 bytes");
56+
//! let message = Message::from_hashed_data::<sha256::Hash>("Hello World!".as_bytes());
5457
//!
5558
//! let sig = secp.sign(&message, &secret_key);
5659
//! assert!(secp.verify(&message, &sig, &public_key).is_ok());
5760
//! # } }
5861
//! ```
5962
//!
60-
//! The above code requires `rust-secp256k1` to be compiled with the `rand`
63+
//! The above code requires `rust-secp256k1` to be compiled with the `rand` and `bitcoin_hashes`
6164
//! feature enabled, to get access to [`generate_keypair`](struct.Secp256k1.html#method.generate_keypair)
62-
//! Alternately, keys can be parsed from slices, like
65+
//! Alternately, keys and messages can be parsed from slices, like
6366
//!
6467
//! ```rust
6568
//! # fn main() {
@@ -68,6 +71,8 @@
6871
//! let secp = Secp256k1::new();
6972
//! let secret_key = SecretKey::from_slice(&[0xcd; 32]).expect("32 bytes, within curve order");
7073
//! let public_key = PublicKey::from_secret_key(&secp, &secret_key);
74+
//! // This is unsafe unless the supplied byte slice is the output of a cryptographic hash function.
75+
//! // See the above example for how to use this library together with bitcoin_hashes.
7176
//! let message = Message::from_slice(&[0xab; 32]).expect("32 bytes");
7277
//!
7378
//! let sig = secp.sign(&message, &secret_key);
@@ -147,6 +152,7 @@
147152
pub extern crate secp256k1_sys;
148153
pub use secp256k1_sys as ffi;
149154

155+
#[cfg(feature = "bitcoin_hashes")] extern crate bitcoin_hashes;
150156
#[cfg(all(test, feature = "unstable"))] extern crate test;
151157
#[cfg(any(test, feature = "rand"))] pub extern crate rand;
152158
#[cfg(any(test))] extern crate rand_core;
@@ -173,6 +179,9 @@ use core::marker::PhantomData;
173179
use core::ops::Deref;
174180
use ffi::CPtr;
175181

182+
#[cfg(feature = "bitcoin_hashes")]
183+
use bitcoin_hashes::Hash;
184+
176185
/// An ECDSA signature
177186
#[derive(Copy, Clone, PartialEq, Eq)]
178187
pub struct Signature(ffi::Signature);
@@ -219,6 +228,27 @@ pub trait ThirtyTwoByteHash {
219228
fn into_32(self) -> [u8; 32];
220229
}
221230

231+
#[cfg(feature = "bitcoin_hashes")]
232+
impl ThirtyTwoByteHash for bitcoin_hashes::sha256::Hash {
233+
fn into_32(self) -> [u8; 32] {
234+
self.into_inner()
235+
}
236+
}
237+
238+
#[cfg(feature = "bitcoin_hashes")]
239+
impl ThirtyTwoByteHash for bitcoin_hashes::sha256d::Hash {
240+
fn into_32(self) -> [u8; 32] {
241+
self.into_inner()
242+
}
243+
}
244+
245+
#[cfg(feature = "bitcoin_hashes")]
246+
impl<T: bitcoin_hashes::sha256t::Tag> ThirtyTwoByteHash for bitcoin_hashes::sha256t::Hash<T> {
247+
fn into_32(self) -> [u8; 32] {
248+
self.into_inner()
249+
}
250+
}
251+
222252
impl SerializedSignature {
223253
/// Get a pointer to the underlying data with the specified capacity.
224254
pub(crate) fn get_data_mut_ptr(&mut self) -> *mut u8 {
@@ -451,7 +481,12 @@ impl_array_newtype!(Message, u8, constants::MESSAGE_SIZE);
451481
impl_pretty_debug!(Message);
452482

453483
impl Message {
454-
/// Converts a `MESSAGE_SIZE`-byte slice to a message object
484+
/// **If you just want to sign an arbitrary message use `Message::from_hashed_data` instead.**
485+
///
486+
/// Converts a `MESSAGE_SIZE`-byte slice to a message object. **WARNING:** the slice has to be a
487+
/// cryptographically secure hash of the actual message that's going to be signed. Otherwise
488+
/// the result of signing isn't a
489+
/// [secure signature](https://twitter.com/pwuille/status/1063582706288586752).
455490
#[inline]
456491
pub fn from_slice(data: &[u8]) -> Result<Message, Error> {
457492
if data == [0; constants::MESSAGE_SIZE] {
@@ -467,6 +502,25 @@ impl Message {
467502
_ => Err(Error::InvalidMessage)
468503
}
469504
}
505+
506+
/// Constructs a `Message` by hashing `data` with hash algorithm `H`. This requires the feature
507+
/// `bitcoin_hashes` to be enabled.
508+
/// ```rust
509+
/// extern crate bitcoin_hashes;
510+
/// use secp256k1::Message;
511+
/// use bitcoin_hashes::sha256;
512+
/// use bitcoin_hashes::Hash;
513+
///
514+
/// let m1 = Message::from_hashed_data::<sha256::Hash>("Hello world!".as_bytes());
515+
/// // is equivalent to
516+
/// let m2 = Message::from(sha256::Hash::hash("Hello world!".as_bytes()));
517+
///
518+
/// assert_eq!(m1, m2);
519+
/// ```
520+
#[cfg(feature = "bitcoin_hashes")]
521+
pub fn from_hashed_data<H: ThirtyTwoByteHash + bitcoin_hashes::Hash>(data: &[u8]) -> Self {
522+
<H as bitcoin_hashes::Hash>::hash(data).into()
523+
}
470524
}
471525

472526
impl<T: ThirtyTwoByteHash> From<T> for Message {
@@ -1110,6 +1164,31 @@ mod tests {
11101164
test_bad_slice();
11111165
test_low_s();
11121166
}
1167+
1168+
#[cfg(feature = "bitcoin_hashes")]
1169+
#[test]
1170+
fn test_from_hash() {
1171+
use bitcoin_hashes;
1172+
use bitcoin_hashes::Hash;
1173+
1174+
let test_bytes = "Hello world!".as_bytes();
1175+
1176+
let hash = bitcoin_hashes::sha256::Hash::hash(test_bytes);
1177+
let msg = Message::from(hash);
1178+
assert_eq!(msg.0, hash.into_inner());
1179+
assert_eq!(
1180+
msg,
1181+
Message::from_hashed_data::<bitcoin_hashes::sha256::Hash>(test_bytes)
1182+
);
1183+
1184+
let hash = bitcoin_hashes::sha256d::Hash::hash(test_bytes);
1185+
let msg = Message::from(hash);
1186+
assert_eq!(msg.0, hash.into_inner());
1187+
assert_eq!(
1188+
msg,
1189+
Message::from_hashed_data::<bitcoin_hashes::sha256d::Hash>(test_bytes)
1190+
);
1191+
}
11131192
}
11141193

11151194
#[cfg(all(test, feature = "unstable"))]

0 commit comments

Comments
 (0)