Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.3.2 - 2026-01-28

- Add `hex!` macro for const hex literal parsing.

# 0.3.1 - 2025-11-24

- Remove `doc_auto_cfg` because it breaks the docs builds on crates.io
Expand Down
2 changes: 1 addition & 1 deletion Cargo-minimal.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ checksum = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f"

[[package]]
name = "hex-conservative"
version = "0.3.1"
version = "0.3.2"
dependencies = [
"arrayvec",
"serde",
Expand Down
2 changes: 1 addition & 1 deletion Cargo-recent.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"

[[package]]
name = "hex-conservative"
version = "0.3.1"
version = "0.3.2"
dependencies = [
"arrayvec",
"serde",
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hex-conservative"
version = "0.3.1"
version = "0.3.2"
authors = ["Martin Habovštiak <martin.habovstiak@gmail.com>", "Andrew Poelstra <apoelstra@wpsoftware.net>"]
license = "CC0-1.0"
repository = "https://github.com/rust-bitcoin/hex-conservative"
Expand Down
67 changes: 64 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@
//!
//! General purpose hex encoding/decoding library with a conservative MSRV and dependency policy.
//!
//! ## Basic Usage
//! ## Const hex literals
//!
//! ```
//! use hex_conservative::hex;
//!
//! const GENESIS: [u8; 32] = hex!("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
//! ```
//!
//! ## Runtime hex parsing
//!
//! ```
//! # #[cfg(feature = "alloc")] {
//! // In your manifest use the `package` key to improve import ergonomics.
Expand Down Expand Up @@ -59,6 +68,38 @@ pub mod parse;
#[cfg(feature = "serde")]
pub mod serde;

/// Parses hex strings in const contexts.
///
/// Returns `[u8; N]` arrays. The string must have even length.
#[macro_export]
macro_rules! hex {
($hex:expr) => {{
const _: () = assert!($hex.len() % 2 == 0, "hex string must have even length");

const fn decode_digit(digit: u8) -> u8 {
match digit {
b'0'..=b'9' => digit - b'0',
b'a'..=b'f' => digit - b'a' + 10,
b'A'..=b'F' => digit - b'A' + 10,
_ => panic!("invalid hex digit"),
}
}

let mut output = [0u8; $hex.len() / 2];
let bytes = $hex.as_bytes();

let mut i = 0;
while i < output.len() {
let high = decode_digit(bytes[i * 2]);
let low = decode_digit(bytes[i * 2 + 1]);
output[i] = (high << 4) | low;
i += 1;
}

output
}};
}

/// Re-exports of the common crate traits.
pub mod prelude {
#[doc(inline)]
Expand Down Expand Up @@ -156,13 +197,33 @@ mod table {
macro_rules! test_hex_unwrap (($hex:expr) => (<Vec<u8> as $crate::FromHex>::from_hex($hex).unwrap()));

#[cfg(test)]
#[cfg(feature = "alloc")]
mod tests {
use crate::test_hex_unwrap as hex;
use alloc::vec::Vec;

#[test]
fn parse_hex_into_vector() {
let got = hex!("deadbeef");
let got = crate::test_hex_unwrap!("deadbeef");
let want = vec![0xde, 0xad, 0xbe, 0xef];
assert_eq!(got, want)
}

#[test]
fn hex_macro() {
let data = hex!("deadbeef");
assert_eq!(data, [0xde, 0xad, 0xbe, 0xef]);
}

#[test]
fn hex_macro_case_insensitive() {
assert_eq!(hex!("DEADBEEF"), hex!("deadbeef"));
}

#[test]
fn hex_macro_const_context() {
const HASH: [u8; 32] =
hex!("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
assert_eq!(HASH[0], 0x00);
assert_eq!(HASH[31], 0x6f);
}
}