Skip to main content

Sha256

Struct Sha256 

Source
#[non_exhaustive]
pub struct Sha256(/* private fields */);
Expand description

SHA-256 digest carried on the JMAP wire as a 64-character lowercase hex string (draft-atwood-jmap-cid-00 §2).

§Example

use jmap_cid_types::Sha256;

// The SHA-256 of the empty string (FIPS 180-4 published vector).
let hex = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
let d: Sha256 = hex.parse()?;
assert_eq!(d.as_str(), hex);

// Round-trips bit-for-bit through serde as a bare JSON string.
let json = serde_json::to_string(&d)?;
assert_eq!(json, format!("\"{hex}\""));
let d2: Sha256 = serde_json::from_str(&json)?;
assert_eq!(d, d2);

§Construction

All four ABNF-validating paths share the same parse logic from Sha256::from_hex:

One infallible conversion:

  • From<[u8; 32]> / From<&[u8; 32]> for Sha256 — format 32 raw digest bytes into the canonical lowercase-hex string. Infallible because every byte produces two lowercase-hex nibbles by construction. The input is the output of a hash function (the raw digest), NOT arbitrary input data to be hashed — this crate carries no hash computation. See the From<&[u8; 32]> impl for byte-ordering details.

§Wire format

The bare 64-character hex string. #[serde(try_from = "String")] routes every deserialize path through the validating TryFrom<String> impl, so every deserialize path applies the same ABNF check Sha256::from_hex does. Serialize uses a manual impl Serialize that emits the inner hex string via serializer.collect_str (canonical-template parity with the jmap-types::Id newtype; see bd:JMAP-sf5h.20).

§Allocation bounds

Sha256 does not bound allocation before validation. The #[serde(try_from = "String")] adapter routes every deserialize path through TryFrom<String>, which means serde_json (and any other serde data format) materialises the full input string before the 64-byte ABNF cap rejects oversize values. A hostile JMAP peer responding with {"sha256": "aaaa…"} where the payload is gigabytes of 'a' will force the consumer to allocate O(payload-length) bytes per deserialize attempt before the WrongLength error fires.

Consumers deserialising Sha256 (directly or transitively, e.g. via jmap-base-client’s BlobUploadResponse.sha256 field) from an untrusted or partially-trusted JMAP peer MUST enforce a body-size limit at the transport layer:

  • reqwest::Response::bytes() does not bound by default; use Response::bytes_stream() plus a wrapping take(N) byte cap, or check Response::content_length() against a policy maximum before reading.
  • tokio_tungstenite WebSocket frames default to a 64 MiB limit per frame (WebSocketConfig::max_frame_size); JMAP push frames carrying a digest field do not need 64 MiB and the limit can be tightened.
  • Hand-rolled HTTP clients reading a Body MUST cap with a take(N) adapter before calling serde_json::from_reader.

In-scope threats:

  • A JMAP client connected to a malicious or compromised JMAP server. The server controls the response body.
  • A JMAP middleware (federation peer, mirror, cache) processing responses from a peer it does not fully trust.
  • A JMAP client behind a proxy that rewrites response bodies (less likely on TLS, but possible on metadata or via injection).

Out of scope: a trusted server returning oversize bodies by accident. Transport-layer bounding handles both cases uniformly.

This crate intentionally does not switch to a custom Deserialize impl with Visitor::visit_str length pre-check — the workspace canonical pattern keeps validation centralised in Sha256::from_hex so the ABNF check is a single source of truth (see bd:JMAP-sf5h.9 for the decision record). Self-bounding at the type level would shift validation responsibility into the Deserialize impl and away from from_hex; the workspace prefers transport-layer bounding over per-type bounding because the transport bound applies uniformly to every field on a hostile response, not just sha256 ones.

§Equality and threat model

PartialEq and Eq on Sha256 inherit the standard variable-time String compare — short-circuits on the first byte mismatch. This is appropriate for the in-scope JMAP CID use case (draft-atwood-jmap-cid-00): the digest is the SHA-256 of a public blob broadcast to every party with read access, so there is no secret comparison and the timing channel reveals nothing an attacker does not already have.

Consumers repurposing Sha256 for secret-derived comparisons — MAC-style verification, commitment opening, capability tokens — MUST use a constant-time compare via subtle::ConstantTimeEq on the underlying bytes. Extract via Sha256::as_str().as_bytes(). The variable-time short-circuit otherwise leaks the prefix length of a matching digest to a remote attacker on the classic Bleichenbacher-class timing pattern.

This crate intentionally does not ship a constant-time comparison helper because the workspace policy is that MAC/HMAC-shaped verification belongs in a separate type (MacTag, SecretDigest) so the choice of constant-time equality is part of the type contract, not a per-call-site opt-in on a wire-format type.

Implementations§

Source§

impl Sha256

Source

pub fn to_base64(&self) -> String

The digest as padded RFC 4648 §4 base64, the encoding of the RFC 9404 digest:sha-256 property.

Source

pub fn from_base64(s: &str) -> Result<Self, Sha256Base64Error>

Parse a padded RFC 4648 §4 base64 digest, such as an RFC 9404 digest:sha-256 value, into its Sha256.

Source§

impl Sha256

Source

pub fn to_cidv1_bytes(&self) -> [u8; 36]

The binary CIDv1 for this digest: 0x01 0x55 0x12 0x20 followed by the 32 digest bytes.

Source

pub fn to_cidv1(&self) -> String

The CIDv1 string form for this digest: b followed by the binary CIDv1 as lowercase base32 without padding.

Source

pub fn from_cidv1_bytes(bytes: &[u8]) -> Result<Self, CidV1Error>

Parse a binary CIDv1 back into its Sha256.

Rejects anything that is not exactly 36 bytes starting with 0x01 0x55 0x12 0x20.

Source

pub fn from_cidv1(s: &str) -> Result<Self, CidV1Error>

Parse a CIDv1 string (b + lowercase unpadded base32) back into its Sha256.

Source§

impl Sha256

Source

pub fn from_hex(s: &str) -> Result<Self, Sha256DigestError>

Parse a candidate hex string into a Sha256.

Returns Sha256DigestError when the candidate is not exactly 64 bytes long or contains any byte outside the lowercase-hex set [0-9 a-f]. Errors report position so a caller can surface a precise diagnostic.

Source

pub fn as_str(&self) -> &str

Borrow the inner 64-character lowercase-hex string.

Source

pub fn into_inner(self) -> String

Consume the value and return the inner String.

Source

pub fn to_bytes(&self) -> [u8; 32]

The 32 raw digest bytes, most significant first: the inverse of From<[u8; 32]> for Sha256.

Trait Implementations§

Source§

impl AsRef<str> for Sha256

Source§

fn as_ref(&self) -> &str

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl Borrow<str> for Sha256

Source§

fn borrow(&self) -> &str

Immutably borrows from an owned value. Read more
Source§

impl Clone for Sha256

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Sha256

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Sha256

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Display for Sha256

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for Sha256

Source§

impl From<&[u8; 32]> for Sha256

Source§

fn from(b: &[u8; 32]) -> Self

Format 32 raw digest bytes as a canonical lowercase-hex Sha256. Infallible because every byte produces two lowercase-hex nibbles by construction.

The input is the output of a SHA-256 hash function — e.g. sha2::Sha256::digest(data).into() — not the data to be hashed. This crate intentionally carries no hash computation.

§Byte ordering

The 32 input bytes are taken in the canonical FIPS 180-4 SHA-256 output order: the most significant byte of the digest first. b[0] occupies positions 0..=1 of the resulting hex string and b[31] occupies positions 62..=63. This matches the output of sha2::Sha256::digest, ring::digest::digest(&SHA256, ...), and openssl::sha::sha256. Consumers feeding digest output from a non-standard source (HSM in non-standard endianness, pre-reversed archive format) must reorder bytes before the conversion or the wire value will be wrong.

Source§

impl From<[u8; 32]> for Sha256

Source§

fn from(b: [u8; 32]) -> Self

Ergonomic owned-array conversion. Delegates to From<&[u8; 32]> for Sha256 — see that impl for byte-ordering details. Provided so callers with an owned digest array (e.g. sha2::Sha256::digest(data).into()) can write Sha256::from(bytes) or bytes.into() without reaching for &bytes at the call site.

Source§

impl FromStr for Sha256

Source§

type Err = Sha256DigestError

The associated error which can be returned from parsing.
Source§

fn from_str(s: &str) -> Result<Self, Self::Err>

Parses a string s to return a value of this type. Read more
Source§

impl Hash for Sha256

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for Sha256

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl PartialEq<&str> for Sha256

Source§

fn eq(&self, other: &&str) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl PartialEq<str> for Sha256

Source§

fn eq(&self, other: &str) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for Sha256

Source§

fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for Sha256

Source§

impl TryFrom<&str> for Sha256

Source§

type Error = Sha256DigestError

The type returned in the event of a conversion error.
Source§

fn try_from(s: &str) -> Result<Self, Self::Error>

Performs the conversion.
Source§

impl TryFrom<String> for Sha256

Source§

fn try_from(s: String) -> Result<Self, Self::Error>

Validate the owned String against the ABNF and move it into the Sha256 on success — no second allocation. This is the path #[serde(try_from = "String")] takes, so every serde_json::from_str::<Sha256>(...) deserialize avoids the double-alloc that from_hex(&s) would incur.

Source§

type Error = Sha256DigestError

The type returned in the event of a conversion error.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.