Skip to content

Commit 6d3c829

Browse files
committed
CID support and ensure compatibility
1 parent fb13b03 commit 6d3c829

File tree

7 files changed

+266
-54
lines changed

7 files changed

+266
-54
lines changed

examples/common.js

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { blake2AsU8a } from '@polkadot/util-crypto'
1+
import {blake2AsU8a, keccak256AsU8a, sha256AsU8a} from '@polkadot/util-crypto'
22
import * as multihash from 'multiformats/hashes/digest'
33
import { CID } from 'multiformats/cid'
4+
import * as sha256 from "multiformats/hashes/sha2";
5+
import assert from "assert";
46

57
export async function waitForNewBlock() {
68
// TODO: wait for a new block.
@@ -9,18 +11,83 @@ export async function waitForNewBlock() {
911
}
1012

1113
/**
12-
* helper: create CID for raw data
14+
* Create CID for data.
15+
* Default to `0x55 (raw)` with blake2b_256 hash.
16+
*
17+
* 0xb220:
18+
* - 0xb2 = the multihash algorithm family for BLAKE2b
19+
* - 0x20 = the digest length in bytes (32 bytes = 256 bits)
20+
*
21+
* See: https://github.com/multiformats/multicodec/blob/master/table.csv
1322
*/
14-
export function cidFromBytes(bytes, cidCodec) {
15-
const hash = blake2AsU8a(bytes)
23+
export async function cidFromBytes(bytes, cidCodec = 0x55, mhCode = 0xb220) {
24+
console.log(`Using cidCodec: ${cidCodec} and mhCode: ${mhCode}`);
25+
let mh;
26+
switch (mhCode) {
27+
case 0xb220: // blake2b-256
28+
mh = multihash.create(mhCode, blake2AsU8a(bytes));
29+
break;
30+
case 0x12: // sha2-256
31+
mh = multihash.create(mhCode, sha256AsU8a(bytes));
32+
// Equivalent to:
33+
// import * as sha256 from "multiformats/hashes/sha2";
34+
// mh = sha256.sha256.digest(bytes);
35+
break;
36+
case 0x1b: // keccak-256
37+
mh = multihash.create(mhCode, keccak256AsU8a(bytes));
38+
break;
1639

17-
// 0xb2 = the multihash algorithm family for BLAKE2b
18-
// 0x20 = the digest length in bytes (32 bytes = 256 bits)
19-
const mh = multihash.create(0xb220, hash)
40+
default:
41+
throw new Error("Unhandled multihash code: " + mhCode)
42+
}
43+
console.log("Multihash:", mh);
44+
return CID.createV1(cidCodec, mh)
45+
}
46+
47+
async function test() {
48+
let bytes = new Uint8Array(Buffer.from("Hello, Bulletin with PAPI - Fri Nov 21 2025 11:09:18 GMT+0000"));
49+
let cid;
50+
51+
console.log("\n == blake2b_256 ==");
52+
// Raw with blake2b_256 hash:
53+
cid = await cidFromBytes(bytes);
54+
console.log("Generated CID:", cid.toString(), "\n");
55+
// Generated CID: bafk2bzacedvk4eijklisgdjijnxky24pmkg7jgk5vsct4mwndj3nmx7plzz7m
56+
57+
// DAG-PB with blake2b_256 hash:
58+
cid = await cidFromBytes(bytes, 0x70);
59+
console.log("Generated CID:", cid.toString(), "\n");
60+
// Generated CID: bafykbzacedvk4eijklisgdjijnxky24pmkg7jgk5vsct4mwndj3nmx7plzz7m
2061

21-
// Default to `0x55 (raw)` if cidCodec is not provided.
22-
const codec = cidCodec != null ? cidCodec : 0x55;
62+
console.log("\n == ssha2_256 ==");
63+
// Raw with ssha2_256 hash:
64+
cid = await cidFromBytes(bytes, 0x55, 0x12);
65+
console.log("Generated CID:", cid.toString(), "\n");
2366

24-
console.log("Generate CID - using codec:", codec);
25-
return CID.createV1(codec, mh)
67+
// DAG-PB with ssha2_256 hash:
68+
cid = await cidFromBytes(bytes, 0x70, 0x12);
69+
console.log("Generated CID:", cid.toString(), "\n");
70+
71+
console.log("\n == keccak_256 ==");
72+
// Raw with keccak_256 hash:
73+
cid = await cidFromBytes(bytes, 0x55, 0x1b);
74+
console.log("Generated CID:", cid.toString(), "\n");
75+
76+
// DAG-PB with ssha2_256 hash:
77+
cid = await cidFromBytes(bytes, 0x70, 0x1b);
78+
console.log("Generated CID:", cid.toString(), "\n");
79+
80+
// Make sure sha equivalent works:
81+
console.log("\n\n == SHA256 equivalent ==");
82+
let hash = await sha256.sha256.digest(bytes);
83+
let cid_sha256 = CID.createV1(0x70, hash);
84+
cid = await cidFromBytes(bytes, 0x70, 0x12);
85+
assert.deepStrictEqual(
86+
cid_sha256,
87+
cid,
88+
'❌ SHA CID calculation not compatible!'
89+
);
90+
console.log("Matches!");
2691
}
92+
93+
test().catch(console.error);

pallets/transaction-storage/src/cids.rs

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
//!
1818
//! This module provides types and functions to compute CIDs for raw data or
1919
//! DAG blocks using supported hashing algorithms and codecs.
20+
//!
21+
//! See [`CidData`].
2022
2123
use crate::LOG_TARGET;
24+
use alloc::vec::Vec;
2225
use cid::{multihash::Multihash, CidGeneric};
2326
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
2427
use polkadot_sdk_frame::deps::sp_io;
@@ -66,12 +69,12 @@ impl HashingAlgorithm {
6669
/// Return the multihash code corresponding to this hashing algorithm.
6770
///
6871
/// These codes follow the [multihash table](https://github.com/multiformats/multicodec/blob/master/table.csv):
69-
/// - Blake2b-256 = 0xb2 (178)
70-
/// - SHA2-256 = 0x12 (18)
71-
/// - Keccak-256 = 0x1b (27)
72+
/// - Blake2b-256 = 0xb220
73+
/// - SHA2-256 = 0x12
74+
/// - Keccak-256 = 0x1b
7275
pub fn multihash_code(&self) -> u64 {
7376
match self {
74-
HashingAlgorithm::Blake2b256 => 0xb2,
77+
HashingAlgorithm::Blake2b256 => 0xb220,
7578
HashingAlgorithm::Sha2_256 => 0x12,
7679
HashingAlgorithm::Keccak256 => 0x1b,
7780
}
@@ -98,8 +101,12 @@ pub struct CidConfig {
98101
}
99102

100103
/// Representation of a generated CID.
104+
#[derive(Debug, PartialEq, Eq)]
101105
pub struct CidData {
102106
/// 32-byte content hash of the input data.
107+
///
108+
/// Note: This is used for indexing transactions and retrieving
109+
/// `self.client.indexed_transaction(hash)`. Note: This is equal to `cid.hash().digest()`.
103110
pub content_hash: ContentHash,
104111
/// Hashing algorithm used.
105112
pub hashing: HashingAlgorithm,
@@ -146,5 +153,92 @@ pub fn calculate_cid(data: &[u8], config: Option<CidConfig>) -> Result<CidData,
146153
Ok(CidData { content_hash, hashing, codec, cid: cid_bytes })
147154
}
148155

149-
// TODO: add here more tests for compatibility.
156+
#[cfg(test)]
157+
mod tests {
158+
use super::{calculate_cid, CidConfig, HashingAlgorithm};
159+
use cid::{
160+
multibase::{encode as to_base32, Base},
161+
CidGeneric,
162+
};
163+
use core::str::FromStr;
164+
use polkadot_sdk_frame::deps::sp_io;
165+
166+
#[test]
167+
fn test_cid_raw_blake2b_256_roundtrip_works() {
168+
// Prepare data.
169+
let data = "Hello, Bulletin with PAPI - Fri Nov 21 2025 11:09:18 GMT+0000";
170+
let expected_content_hash = sp_io::hashing::blake2_256(data.as_bytes());
171+
172+
// Expected raw CID calculated for the same data with `examples/common.js`.
173+
let expected_cid_base32 = "bafk2bzacedvk4eijklisgdjijnxky24pmkg7jgk5vsct4mwndj3nmx7plzz7m";
174+
let expected_cid = CidGeneric::<32>::from_str(expected_cid_base32).expect("valid_cid");
175+
assert_eq!(expected_cid.codec(), 0x55);
176+
assert_eq!(expected_cid.hash().code(), 0xb220);
177+
assert_eq!(expected_cid.hash().size(), 0x20);
178+
assert_eq!(expected_cid.hash().digest(), expected_content_hash);
179+
180+
// Calculate CIDv1 with default raw codec and blake2b-256.
181+
let cid_raw = calculate_cid(data.as_ref(), None).expect("valid_cid");
182+
let cid_blake2b_256_raw = calculate_cid(
183+
data.as_ref(),
184+
Some(CidConfig { codec: 0x55, hashing: HashingAlgorithm::Blake2b256 }),
185+
)
186+
.expect("valid_cid");
187+
assert_eq!(cid_raw.cid, expected_cid.to_bytes());
188+
assert_eq!(to_base32(Base::Base32Lower, &cid_raw.cid), expected_cid_base32);
189+
assert_eq!(cid_raw.codec, expected_cid.codec());
190+
assert_eq!(cid_raw.hashing.multihash_code(), expected_cid.hash().code());
191+
assert_eq!(cid_raw.content_hash, expected_cid.hash().digest());
192+
assert_eq!(cid_raw, cid_blake2b_256_raw);
193+
}
194+
195+
/// Return the HashingAlgorithm corresponding to a multihash code.
196+
pub fn from_multihash_code(code: u64) -> HashingAlgorithm {
197+
match code {
198+
0xb220 => HashingAlgorithm::Blake2b256,
199+
0x12 => HashingAlgorithm::Sha2_256,
200+
0x1b => HashingAlgorithm::Keccak256,
201+
code @ _ => panic!("{code} is not supported"),
202+
}
203+
}
150204

205+
#[test]
206+
fn test_cid_various_codecs_and_hashes() {
207+
let data = "Hello, Bulletin with PAPI - Fri Nov 21 2025 11:09:18 GMT+0000";
208+
209+
// Expected results from `examples/common.js`.
210+
let expected_cids = vec![
211+
// raw + blake2b_256
212+
("bafk2bzacedvk4eijklisgdjijnxky24pmkg7jgk5vsct4mwndj3nmx7plzz7m", 0x55, 0xb220),
213+
// DAG-PB + blake2b_256
214+
("bafykbzacedvk4eijklisgdjijnxky24pmkg7jgk5vsct4mwndj3nmx7plzz7m", 0x70, 0xb220),
215+
// Raw + sha2_256
216+
("bafkreig5pw2of63kmkldboh6utfovo3o3czig4yj7eb2ragxwca4c4jlke", 0x55, 0x12),
217+
// DAG-PB + sha2_256
218+
("bafybeig5pw2of63kmkldboh6utfovo3o3czig4yj7eb2ragxwca4c4jlke", 0x70, 0x12),
219+
// Raw + keccak_256
220+
("bafkrwifr4p73tsatchlyp3hivjee4prqqpcqayikzen46bqldwmt5mzd6e", 0x55, 0x1b),
221+
// DAG-PB + keccak_256
222+
("bafybwifr4p73tsatchlyp3hivjee4prqqpcqayikzen46bqldwmt5mzd6e", 0x70, 0x1b),
223+
];
224+
225+
for (expected_cid_str, codec, mh_code) in expected_cids {
226+
let cid = CidGeneric::<32>::from_str(expected_cid_str).expect("valid CID");
227+
// Check codec and multihash code
228+
assert_eq!(cid.codec(), codec);
229+
assert_eq!(cid.hash().code(), mh_code);
230+
231+
// Test `calculate_cid`
232+
let calculated = calculate_cid(
233+
data.as_ref(),
234+
Some(CidConfig { codec, hashing: from_multihash_code(mh_code) }),
235+
)
236+
.expect("calculate_cid succeeded");
237+
238+
assert_eq!(to_base32(Base::Base32Lower, &calculated.cid), expected_cid_str);
239+
assert_eq!(calculated.codec, codec);
240+
assert_eq!(calculated.hashing.multihash_code(), mh_code);
241+
assert_eq!(calculated.content_hash, cid.hash().digest());
242+
}
243+
}
244+
}

pallets/transaction-storage/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
// Ensure we're `no_std` when compiling for Wasm.
2525
#![cfg_attr(not(feature = "std"), no_std)]
2626

27+
extern crate alloc;
28+
2729
mod benchmarking;
2830
pub mod extension;
2931
pub mod weights;
@@ -34,6 +36,7 @@ mod mock;
3436
#[cfg(test)]
3537
mod tests;
3638

39+
use alloc::vec::Vec;
3740
use cids::{calculate_cid, Cid, CidConfig, ContentHash};
3841
use codec::{Decode, Encode, MaxEncodedLen};
3942
use polkadot_sdk_frame::{
@@ -111,7 +114,7 @@ pub struct TransactionInfo {
111114
chunk_root: <BlakeTwo256 as Hash>::Output,
112115

113116
/// Plain hash of indexed data.
114-
content_hash: ContentHash,
117+
pub content_hash: ContentHash,
115118
/// Used hashing algorithm for `content_hash`.
116119
hashing: HashingAlgorithm,
117120
/// Requested codec for CIDs.

runtimes/bulletin-polkadot/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,7 @@ pub type TxExtension = (
697697
frame_system::CheckWeight<Runtime>,
698698
ValidateSigned,
699699
BridgeRejectObsoleteHeadersAndMessages,
700-
pallet_transaction_storage::extension::ProvideCidCodec<Runtime>,
700+
pallet_transaction_storage::extension::ProvideCidConfig<Runtime>,
701701
);
702702

703703
/// Unchecked extrinsic type as expected by this runtime.

0 commit comments

Comments
 (0)