cow_app_data/cid.rs
1//! IPFS `CIDv1` conversion helpers for `CoW` Protocol app-data.
2//!
3//! Every `CoW` Protocol order's `appData` hash can be mapped to an IPFS
4//! Content Identifier (CID) so that the full JSON document is retrievable
5//! from any IPFS gateway. This module handles the bidirectional conversion
6//! between the 32-byte `appDataHex` stored on-chain and the `CIDv1` string
7//! used by IPFS.
8//!
9//! The modern encoding uses `keccak256` with the `raw` multicodec (`0x55`)
10//! and multibase base16 (lowercase, prefix `f`). Legacy helpers using
11//! `dag-pb` / `sha2-256` are preserved for backwards compatibility but are
12//! deprecated.
13//!
14//! # Key functions
15//!
16//! | Function | Direction |
17//! |---|---|
18//! | [`appdata_hex_to_cid`] | `appDataHex` → `CIDv1` string |
19//! | [`cid_to_appdata_hex`] | `CIDv1` string → `appDataHex` |
20//! | [`parse_cid`] | `CIDv1` string → [`CidComponents`] |
21//! | [`decode_cid`] | raw CID bytes → [`CidComponents`] |
22//! | [`extract_digest`] | `CIDv1` string → digest hex |
23
24use cow_errors::CowError;
25
26// CIDv1 constants (modern encoding)
27const CID_VERSION: u8 = 0x01;
28const MULTICODEC_RAW: u8 = 0x55;
29const HASH_KECCAK256: u8 = 0x1b;
30const HASH_LEN: u8 = 0x20; // 32 bytes
31
32// CIDv1 constants (legacy encoding: dag-pb + sha2-256)
33const MULTICODEC_DAG_PB: u8 = 0x70;
34const HASH_SHA2_256: u8 = 0x12;
35
36/// Convert an `appDataHex` value (the 32-byte `keccak256` stored in the
37/// order struct) into a `CIDv1` string.
38///
39/// The CID is built by hashing the raw bytes of `app_data_hex` with
40/// `keccak256`, then wrapping the digest in a `CIDv1` envelope:
41/// `[version=0x01, codec=0x55 (raw), hash_fn=0x1b (keccak256), len=0x20, ...digest]`.
42/// The result is returned as a multibase base16 string (prefix `f`).
43///
44/// This is the inverse of [`cid_to_appdata_hex`].
45///
46/// Mirrors `appDataHexToCid` from the `@cowprotocol/app-data` `TypeScript`
47/// package.
48///
49/// # Parameters
50///
51/// * `app_data_hex` — the `appData` value, with or without `0x` prefix.
52///
53/// # Returns
54///
55/// A base16 `CIDv1` string prefixed with `f` (e.g.
56/// `f015501201b20...`).
57///
58/// # Errors
59///
60/// Returns [`CowError::AppData`] if `app_data_hex` is not valid hex.
61///
62/// # Example
63///
64/// ```
65/// use cow_app_data::{appdata_hex_to_cid, cid_to_appdata_hex};
66///
67/// let hex = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
68/// let cid = appdata_hex_to_cid(hex).unwrap();
69/// assert!(cid.starts_with('f')); // multibase base16
70/// ```
71pub fn appdata_hex_to_cid(app_data_hex: &str) -> Result<String, CowError> {
72 let hex = app_data_hex.strip_prefix("0x").map_or(app_data_hex, |s| s);
73 let bytes = alloy_primitives::hex::decode(hex)
74 .map_err(|e| CowError::AppData(format!("invalid hex: {e}")))?;
75
76 if bytes.len() != HASH_LEN as usize {
77 return Err(CowError::AppData(format!(
78 "appDataHex must be {} bytes, got {}",
79 HASH_LEN,
80 bytes.len()
81 )));
82 }
83
84 // The appDataHex is already the keccak256 hash of the canonical JSON
85 // document, so it is used verbatim as the CID multihash digest. The
86 // `HASH_KECCAK256` byte in the header declares the hash function that
87 // produced that digest — re-hashing would break round-trips and diverge
88 // from the TypeScript SDK's `appDataHexToCid`.
89 let mut cid = Vec::with_capacity(4 + HASH_LEN as usize);
90 cid.push(CID_VERSION);
91 cid.push(MULTICODEC_RAW);
92 cid.push(HASH_KECCAK256);
93 cid.push(HASH_LEN);
94 cid.extend_from_slice(&bytes);
95
96 // Multibase base16 lowercase: prefix 'f'
97 Ok(format!("f{}", alloy_primitives::hex::encode(&cid)))
98}
99
100/// Extract the digest from a `CIDv1` base16 string and return it as
101/// `0x`-prefixed hex.
102///
103/// This is the inverse of [`appdata_hex_to_cid`]: given a CID stored
104/// alongside an order, recover the 32-byte digest embedded in the CID
105/// header. The returned value can be used as the `appData` field in an
106/// on-chain order struct.
107///
108/// Only base16 CIDs (prefix `f` or `F`) are supported; other multibase
109/// encodings will return an error.
110///
111/// Mirrors `cidToAppDataHex` from the `@cowprotocol/app-data` `TypeScript`
112/// package.
113///
114/// # Parameters
115///
116/// * `cid` — a base16 multibase CID string (e.g. `"f015501201b20..."`).
117///
118/// # Returns
119///
120/// A `0x`-prefixed, lowercase hex string of the 32-byte digest.
121///
122/// # Errors
123///
124/// Returns [`CowError::AppData`] if the CID is not base16, not valid hex,
125/// or shorter than 36 bytes (4-byte header + 32-byte digest).
126///
127/// # Example
128///
129/// ```
130/// use cow_app_data::{appdata_hex_to_cid, cid_to_appdata_hex};
131///
132/// let hex = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
133/// let cid = appdata_hex_to_cid(hex).unwrap();
134/// let recovered = cid_to_appdata_hex(&cid).unwrap();
135/// assert!(recovered.starts_with("0x"));
136/// assert_eq!(recovered.len(), 66); // "0x" + 64 hex chars
137/// ```
138pub fn cid_to_appdata_hex(cid: &str) -> Result<String, CowError> {
139 let lower = cid.to_ascii_lowercase();
140 let hex = lower
141 .strip_prefix('f')
142 .ok_or_else(|| CowError::AppData("only base16 CIDs are supported (prefix 'f')".into()))?;
143
144 let bytes = alloy_primitives::hex::decode(hex)
145 .map_err(|e| CowError::AppData(format!("invalid CID hex: {e}")))?;
146
147 // Skip CIDv1 header: version(1) + codec(1) + hash_fn(1) + hash_len(1) = 4 bytes
148 if bytes.len() < 4 + 32 {
149 return Err(CowError::AppData("CID too short".into()));
150 }
151 let digest = &bytes[4..4 + 32];
152 Ok(format!("0x{}", alloy_primitives::hex::encode(digest)))
153}
154
155// ── Legacy CID helpers ──────────────────────────────────────────────────────
156
157/// Internal helper: build CID bytes from the given multicodec and hash
158/// algorithm parameters.
159///
160/// This is the Rust equivalent of the `TypeScript` SDK's `_toCidBytes`.
161fn to_cid_bytes(
162 version: u8,
163 multicodec: u8,
164 hashing_algorithm: u8,
165 hashing_length: u8,
166 multihash_hex: &str,
167) -> Result<Vec<u8>, CowError> {
168 let hex = multihash_hex.strip_prefix("0x").map_or(multihash_hex, |s| s);
169 let hash_bytes = alloy_primitives::hex::decode(hex)
170 .map_err(|e| CowError::AppData(format!("invalid hex: {e}")))?;
171
172 let mut cid = Vec::with_capacity(4 + hash_bytes.len());
173 cid.push(version);
174 cid.push(multicodec);
175 cid.push(hashing_algorithm);
176 cid.push(hashing_length);
177 cid.extend_from_slice(&hash_bytes);
178 Ok(cid)
179}
180
181/// Internal helper: convert an `appDataHex` to a `CIDv1` string using the
182/// legacy encoding (`sha2-256` + `dag-pb` multicodec).
183///
184/// **Note**: Legacy CIDs used `CIDv0` (`base58btc`) in the `TypeScript` SDK. This Rust
185/// implementation returns the CID as base16 (prefix `f`) since the crate does not
186/// include a `base58` encoder. Callers requiring `CIDv0` format should convert externally.
187///
188/// This is the Rust equivalent of `_appDataHexToCidLegacy` in the `TypeScript` SDK.
189fn app_data_hex_to_cid_legacy_aux(app_data_hex: &str) -> Result<String, CowError> {
190 let cid_bytes =
191 to_cid_bytes(CID_VERSION, MULTICODEC_DAG_PB, HASH_SHA2_256, HASH_LEN, app_data_hex)?;
192 // Return as base16 since we don't have base58 encoding
193 Ok(format!("f{}", alloy_primitives::hex::encode(&cid_bytes)))
194}
195
196/// Validate that a CID string is non-empty.
197///
198/// A simple guard used after CID derivation to ensure the conversion did
199/// not silently produce an empty string. If `cid` is empty, returns an
200/// error that includes the original `app_data_hex` for debugging.
201///
202/// Mirrors `_assertCid` from the `@cowprotocol/app-data` `TypeScript` package.
203///
204/// # Parameters
205///
206/// * `cid` — the CID string to validate.
207/// * `app_data_hex` — the source hex, included in the error message on failure.
208///
209/// # Errors
210///
211/// Returns [`CowError::AppData`] if `cid` is empty.
212pub fn assert_cid(cid: &str, app_data_hex: &str) -> Result<(), CowError> {
213 if cid.is_empty() {
214 return Err(CowError::AppData(format!("Error getting CID from appDataHex: {app_data_hex}")));
215 }
216 Ok(())
217}
218
219/// Convert an `appDataHex` to a `CIDv1` string using the legacy encoding.
220///
221/// Uses `dag-pb` multicodec with `sha2-256` hashing, matching the original
222/// IPFS CID generation before `CoW` Protocol switched to `keccak256`.
223///
224/// **Note**: The `TypeScript` SDK returns a `CIDv0` (`base58btc`) string. This Rust
225/// implementation returns base16 (prefix `f`) since no `base58` encoder is bundled.
226///
227/// # Errors
228///
229/// Returns [`CowError::AppData`] if `app_data_hex` cannot be decoded.
230#[deprecated(
231 note = "Use appdata_hex_to_cid instead — legacy CID encoding is no longer used by CoW Protocol"
232)]
233pub fn app_data_hex_to_cid_legacy(app_data_hex: &str) -> Result<String, CowError> {
234 let cid = app_data_hex_to_cid_legacy_aux(app_data_hex)?;
235 assert_cid(&cid, app_data_hex)?;
236 Ok(cid)
237}
238
239/// Parsed components of an IPFS Content Identifier (CID).
240///
241/// A CID encodes four header fields followed by the raw hash digest:
242///
243/// ```text
244/// ┌─────────┬───────┬──────────────┬────────────┬──────────────┐
245/// │ version │ codec │ hash_function│ hash_length│ digest │
246/// │ (1 B) │ (1 B) │ (1 B) │ (1 B) │ (N bytes) │
247/// └─────────┴───────┴──────────────┴────────────┴──────────────┘
248/// ```
249///
250/// Use [`parse_cid`] to obtain this from a multibase string, or
251/// [`decode_cid`] to obtain it from raw bytes.
252///
253/// # Example
254///
255/// ```
256/// use cow_app_data::{appdata_hex_to_cid, parse_cid};
257///
258/// let hex = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
259/// let cid = appdata_hex_to_cid(hex).unwrap();
260/// let components = parse_cid(&cid).unwrap();
261/// assert_eq!(components.version, 0x01); // CIDv1
262/// assert_eq!(components.codec, 0x55); // raw multicodec
263/// assert_eq!(components.hash_function, 0x1b); // keccak256
264/// assert_eq!(components.hash_length, 0x20); // 32 bytes
265/// assert_eq!(components.digest.len(), 32);
266/// ```
267#[derive(Debug, Clone)]
268pub struct CidComponents {
269 /// CID version (e.g. `1` for `CIDv1`).
270 pub version: u8,
271 /// Multicodec code (e.g. `0x55` for raw, `0x70` for dag-pb).
272 pub codec: u8,
273 /// Multihash function code (e.g. `0x1b` for keccak256, `0x12` for sha2-256).
274 pub hash_function: u8,
275 /// Hash digest length in bytes (typically `32`).
276 pub hash_length: u8,
277 /// The raw hash digest bytes.
278 pub digest: Vec<u8>,
279}
280
281/// Parse a CID string into its constituent [`CidComponents`].
282///
283/// Decodes the multibase prefix, strips it, hex-decodes the remainder, and
284/// splits the resulting bytes into the four header fields plus the digest.
285///
286/// Currently supports base16 multibase encoding (prefix `f` or `F`). Other
287/// multibase encodings (e.g. `base58btc` starting with `Qm`) return an
288/// error.
289///
290/// Mirrors `parseCid` from the `@cowprotocol/app-data` `TypeScript` package.
291///
292/// # Parameters
293///
294/// * `ipfs_hash` — a multibase-encoded CID string (e.g. `"f015501201b20..."`).
295///
296/// # Returns
297///
298/// A [`CidComponents`] struct with the parsed version, codec, hash function,
299/// hash length, and raw digest bytes.
300///
301/// # Errors
302///
303/// Returns [`CowError::AppData`] if the CID encoding is unsupported, the
304/// hex is invalid, or the payload is shorter than 4 bytes.
305///
306/// # Example
307///
308/// ```
309/// use cow_app_data::{appdata_hex_to_cid, parse_cid};
310///
311/// let hex = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
312/// let cid = appdata_hex_to_cid(hex).unwrap();
313/// let c = parse_cid(&cid).unwrap();
314/// assert_eq!(c.version, 1);
315/// assert_eq!(c.digest.len(), 32);
316/// ```
317pub fn parse_cid(ipfs_hash: &str) -> Result<CidComponents, CowError> {
318 let lower = ipfs_hash.to_ascii_lowercase();
319 let hex = lower
320 .strip_prefix('f')
321 .ok_or_else(|| CowError::AppData("only base16 CIDs are supported (prefix 'f')".into()))?;
322
323 let bytes = alloy_primitives::hex::decode(hex)
324 .map_err(|e| CowError::AppData(format!("invalid CID hex: {e}")))?;
325
326 if bytes.len() < 4 {
327 return Err(CowError::AppData("CID too short".into()));
328 }
329
330 let version = bytes[0];
331 let codec = bytes[1];
332 let hash_function = bytes[2];
333 let hash_length = bytes[3];
334 let digest = bytes[4..].to_vec();
335
336 Ok(CidComponents { version, codec, hash_function, hash_length, digest })
337}
338
339/// Decode raw CID bytes into their constituent [`CidComponents`].
340///
341/// Unlike [`parse_cid`], this function operates on raw bytes rather than a
342/// multibase-encoded string. Use it when you already have the CID as a byte
343/// slice (e.g. from a binary protocol or a database column).
344///
345/// Mirrors `decodeCid` from the `@cowprotocol/app-data` `TypeScript` package.
346///
347/// # Parameters
348///
349/// * `bytes` — raw CID bytes: `[version, codec, hash_fn, hash_len, ...digest]`.
350///
351/// # Returns
352///
353/// A [`CidComponents`] struct with the parsed fields.
354///
355/// # Errors
356///
357/// Returns [`CowError::AppData`] if the byte slice is shorter than 4 bytes
358/// (the minimum CID header size).
359///
360/// # Example
361///
362/// ```
363/// use cow_app_data::decode_cid;
364///
365/// let mut bytes = vec![0x01, 0x55, 0x1b, 0x20];
366/// bytes.extend_from_slice(&[0u8; 32]); // 32 digest bytes
367/// let c = decode_cid(&bytes).unwrap();
368/// assert_eq!(c.version, 1);
369/// assert_eq!(c.codec, 0x55);
370/// assert_eq!(c.digest.len(), 32);
371/// ```
372pub fn decode_cid(bytes: &[u8]) -> Result<CidComponents, CowError> {
373 if bytes.len() < 4 {
374 return Err(CowError::AppData("CID bytes too short".into()));
375 }
376
377 Ok(CidComponents {
378 version: bytes[0],
379 codec: bytes[1],
380 hash_function: bytes[2],
381 hash_length: bytes[3],
382 digest: bytes[4..].to_vec(),
383 })
384}
385
386/// Extract the multihash digest from a CID string and return it as
387/// `0x`-prefixed hex.
388///
389/// Parses the CID via [`parse_cid`], then returns only the raw digest
390/// portion as a `0x`-prefixed hex string. This is useful when you have a
391/// CID from IPFS and need to recover the hash digest to match against
392/// on-chain `appData` values.
393///
394/// Note: the digest extracted here is the hash **inside** the CID, not the
395/// original `appDataHex`. For round-trip conversion use [`cid_to_appdata_hex`].
396///
397/// Mirrors `extractDigest` from the `@cowprotocol/app-data` `TypeScript`
398/// package.
399///
400/// # Parameters
401///
402/// * `cid` — a base16 multibase CID string.
403///
404/// # Returns
405///
406/// A `0x`-prefixed hex string of the raw digest bytes.
407///
408/// # Errors
409///
410/// Returns [`CowError::AppData`] if the CID cannot be parsed.
411///
412/// # Example
413///
414/// ```
415/// use cow_app_data::{appdata_hex_to_cid, extract_digest};
416///
417/// let hex = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
418/// let cid = appdata_hex_to_cid(hex).unwrap();
419/// let digest = extract_digest(&cid).unwrap();
420/// assert!(digest.starts_with("0x"));
421/// assert_eq!(digest.len(), 66); // "0x" + 64 hex chars
422/// ```
423pub fn extract_digest(cid: &str) -> Result<String, CowError> {
424 let components = parse_cid(cid)?;
425 Ok(format!("0x{}", alloy_primitives::hex::encode(&components.digest)))
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431
432 const SAMPLE_HEX: &str = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
433
434 #[test]
435 fn appdata_hex_to_cid_produces_base16_cid() {
436 let cid = appdata_hex_to_cid(SAMPLE_HEX).unwrap_or_default();
437 assert!(cid.starts_with('f'));
438 // CID header (4 bytes) + digest (32 bytes) = 36 bytes → 72 hex chars + 'f' prefix
439 assert_eq!(cid.len(), 1 + 72);
440 }
441
442 #[test]
443 fn appdata_hex_to_cid_without_0x_prefix() {
444 let hex = SAMPLE_HEX.strip_prefix("0x").unwrap_or_else(|| SAMPLE_HEX);
445 let cid = appdata_hex_to_cid(hex).unwrap_or_default();
446 assert!(cid.starts_with('f'));
447 }
448
449 #[test]
450 fn cid_to_appdata_hex_roundtrip() {
451 let cid = appdata_hex_to_cid(SAMPLE_HEX).unwrap();
452 let recovered = cid_to_appdata_hex(&cid).unwrap();
453 assert!(recovered.starts_with("0x"));
454 assert_eq!(recovered.len(), 66);
455 assert_eq!(recovered, SAMPLE_HEX);
456 }
457
458 #[test]
459 fn appdata_hex_to_cid_uses_input_as_digest() {
460 // The appDataHex is already a keccak256; it must become the CID digest
461 // verbatim (no extra hashing), matching the TypeScript SDK.
462 let cid = appdata_hex_to_cid(SAMPLE_HEX).unwrap();
463 let components = parse_cid(&cid).unwrap();
464 let expected = alloy_primitives::hex::decode(SAMPLE_HEX.trim_start_matches("0x")).unwrap();
465 assert_eq!(components.digest, expected);
466 }
467
468 #[test]
469 fn appdata_hex_to_cid_rejects_wrong_length() {
470 assert!(appdata_hex_to_cid("0xdeadbeef").is_err());
471 }
472
473 #[test]
474 fn cid_to_appdata_hex_rejects_non_base16() {
475 assert!(cid_to_appdata_hex("Qmabc123").is_err());
476 assert!(cid_to_appdata_hex("babc123").is_err());
477 }
478
479 #[test]
480 fn cid_to_appdata_hex_rejects_too_short() {
481 assert!(cid_to_appdata_hex("f0155").is_err());
482 }
483
484 #[test]
485 fn parse_cid_components() {
486 let cid = appdata_hex_to_cid(SAMPLE_HEX).unwrap_or_default();
487 let c = parse_cid(&cid).unwrap_or_else(|_| CidComponents {
488 version: 0,
489 codec: 0,
490 hash_function: 0,
491 hash_length: 0,
492 digest: vec![],
493 });
494 assert_eq!(c.version, CID_VERSION);
495 assert_eq!(c.codec, MULTICODEC_RAW);
496 assert_eq!(c.hash_function, HASH_KECCAK256);
497 assert_eq!(c.hash_length, HASH_LEN);
498 assert_eq!(c.digest.len(), 32);
499 }
500
501 #[test]
502 fn parse_cid_rejects_non_base16() {
503 assert!(parse_cid("not_a_cid").is_err());
504 }
505
506 #[test]
507 fn parse_cid_rejects_too_short() {
508 assert!(parse_cid("f01").is_err());
509 }
510
511 #[test]
512 fn decode_cid_from_bytes() {
513 let mut bytes = vec![0x01, 0x55, 0x1b, 0x20];
514 bytes.extend_from_slice(&[0xaa; 32]);
515 let c = decode_cid(&bytes).unwrap_or_else(|_| CidComponents {
516 version: 0,
517 codec: 0,
518 hash_function: 0,
519 hash_length: 0,
520 digest: vec![],
521 });
522 assert_eq!(c.version, 1);
523 assert_eq!(c.codec, 0x55);
524 assert_eq!(c.digest.len(), 32);
525 }
526
527 #[test]
528 fn decode_cid_rejects_short_bytes() {
529 assert!(decode_cid(&[0x01, 0x02, 0x03]).is_err());
530 assert!(decode_cid(&[]).is_err());
531 }
532
533 #[test]
534 fn extract_digest_returns_0x_prefixed() {
535 let cid = appdata_hex_to_cid(SAMPLE_HEX).unwrap_or_default();
536 let digest = extract_digest(&cid).unwrap_or_default();
537 assert!(digest.starts_with("0x"));
538 assert_eq!(digest.len(), 66);
539 }
540
541 #[test]
542 fn assert_cid_accepts_nonempty() {
543 assert!(assert_cid("f01234", "0xabc").is_ok());
544 }
545
546 #[test]
547 fn assert_cid_rejects_empty() {
548 assert!(assert_cid("", "0xabc").is_err());
549 }
550
551 #[test]
552 #[allow(deprecated, reason = "testing legacy API surface")]
553 fn legacy_cid_produces_base16_string() {
554 let cid = app_data_hex_to_cid_legacy(SAMPLE_HEX).unwrap_or_default();
555 assert!(cid.starts_with('f'));
556 }
557
558 #[test]
559 fn appdata_hex_to_cid_invalid_hex() {
560 assert!(appdata_hex_to_cid("0xZZZZ").is_err());
561 }
562
563 #[test]
564 fn deterministic_output() {
565 let cid1 = appdata_hex_to_cid(SAMPLE_HEX).unwrap_or_default();
566 let cid2 = appdata_hex_to_cid(SAMPLE_HEX).unwrap_or_default();
567 assert_eq!(cid1, cid2);
568 }
569
570 #[test]
571 fn cid_to_appdata_hex_invalid_hex() {
572 assert!(cid_to_appdata_hex("fZZZZinvalid").is_err());
573 }
574
575 #[test]
576 fn parse_cid_uppercase_f_prefix() {
577 let cid = appdata_hex_to_cid(SAMPLE_HEX).unwrap();
578 // Replace lowercase 'f' prefix with uppercase 'F'
579 let upper = format!("F{}", &cid[1..]);
580 let c = parse_cid(&upper).unwrap();
581 assert_eq!(c.version, CID_VERSION);
582 }
583
584 #[test]
585 fn to_cid_bytes_without_0x() {
586 let hex = SAMPLE_HEX.strip_prefix("0x").unwrap();
587 let bytes = to_cid_bytes(CID_VERSION, MULTICODEC_RAW, HASH_KECCAK256, HASH_LEN, hex);
588 assert!(bytes.is_ok());
589 }
590
591 #[test]
592 fn to_cid_bytes_invalid_hex() {
593 let result = to_cid_bytes(CID_VERSION, MULTICODEC_RAW, HASH_KECCAK256, HASH_LEN, "ZZZZ");
594 assert!(result.is_err());
595 }
596}