|
1 | | -// SPDX-License-Identifier: MIT |
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
2 | 2 | pragma solidity ^0.8.28; |
3 | 3 |
|
4 | 4 | import "./CborDecode.sol"; |
5 | 5 |
|
6 | | -using {cidEq as ==, cidNeq as !=} for Cid global; |
| 6 | +using {cidEq as ==, cidNeq as !=, isNull, isFor} for Cid global; |
7 | 7 |
|
8 | | -/** |
9 | | - * we will only encounter Cid v1 dag-cbor sha256, so the entire hash is 32 |
10 | | - * bytes and will fit in a uint256 |
11 | | - */ |
| 8 | +// we will only encounter CID v1 dag-cbor sha256, and sha256 fits a uint256. |
| 9 | +// some CID fields may be nullable, so the zero value identifies a 'null' CID. |
12 | 10 | type Cid is uint256; |
13 | 11 |
|
14 | 12 | function cidEq(Cid a, Cid b) pure returns (bool) { |
| 13 | + require(Cid.unwrap(a) != 0 && Cid.unwrap(b) != 0, "Invalid CID comparison: null CID"); |
15 | 14 | return Cid.unwrap(a) == Cid.unwrap(b); |
16 | 15 | } |
17 | 16 |
|
18 | 17 | function cidNeq(Cid a, Cid b) pure returns (bool) { |
19 | | - return Cid.unwrap(a) != Cid.unwrap(b); |
| 18 | + return !cidEq(a, b); |
| 19 | +} |
| 20 | + |
| 21 | +function isFor(Cid a, bytes memory b) pure returns (bool) { |
| 22 | + require(Cid.unwrap(a) != 0, "Invalid CID check: null CID"); |
| 23 | + require(b.length != 0, "Invalid CID check: no content"); |
| 24 | + return Cid.unwrap(a) == uint256(sha256(b)); |
| 25 | +} |
| 26 | + |
| 27 | +function isNull(Cid a) pure returns (bool) { |
| 28 | + return Cid.unwrap(a) == 0; |
20 | 29 | } |
21 | 30 |
|
22 | 31 | library CidCbor { |
23 | 32 | using CBORDecoder for bytes; |
24 | 33 |
|
25 | | - uint8 private constant TAG_CID = 42; |
| 34 | + // any CIDv1 DAG-CBOR sha-256 will always have this 9-byte header |
| 35 | + // ─────┬───────── |
| 36 | + // hex │ meaning |
| 37 | + // ─────┼───────── |
| 38 | + // D8 │ CBOR major primitive, minor next byte |
| 39 | + // 2A │ CBOR tag value 42 (CID) |
| 40 | + // 58 │ CBOR major bytes, minor next byte |
| 41 | + // 25 │ CBOR bytes length 37 |
| 42 | + // 00 │ multibase format |
| 43 | + // 01 │ multiformat CID version 1 |
| 44 | + // 71 │ multicodec DAG-CBOR |
| 45 | + // 12 │ multihash type sha-256 |
| 46 | + // 20 │ multihash size 32 bytes |
| 47 | + // ─────┴───────── |
| 48 | + bytes4 private constant cbor_tag42_bytes37 = hex"D82A5825"; |
| 49 | + bytes5 private constant multibase_cidv1_dagcbor_sha256 = hex"0001711220"; |
26 | 50 |
|
27 | | - uint8 private constant MULTIBASE_FORMAT = 0x00; |
28 | | - uint8 private constant CID_V1 = 0x01; |
29 | | - uint8 private constant MULTICODEC_DAG_CBOR = 0x71; |
30 | | - uint8 private constant MULTIHASH_SHA_256 = 0x12; |
31 | | - uint8 private constant MULTIHASH_SIZE_32 = 0x20; |
| 51 | + /** |
| 52 | + * @notice Reads a CIDv1 DAG-CBOR sha-256 from CBOR encoded data at the specified byte index |
| 53 | + * @dev Expects a 41-byte CID structure: 4 bytes CBOR header + 5 bytes multibase header + 32 bytes SHA-256 hash |
| 54 | + * Reverts when: |
| 55 | + * - The remaining bytes are less than the expected CID size |
| 56 | + * - The CBOR header is not tag 42 with 37-byte item |
| 57 | + * - The multibase header is not CIDv1 DAG-CBOR sha256 |
| 58 | + * - The hash value is zero |
| 59 | + * @param cborData The CBOR encoded byte array containing the CID |
| 60 | + * @param byteIdx The starting index in the byte array to read from |
| 61 | + * @return Cid The decoded CID |
| 62 | + * @return uint The next byte index after the CID |
| 63 | + */ |
| 64 | + function readCid(bytes memory cborData, uint byteIdx) internal pure returns (Cid, uint) { |
| 65 | + bytes4 cborHead; |
| 66 | + bytes5 multibaseHead; |
| 67 | + uint256 cidSha256; // 32 bytes |
32 | 68 |
|
33 | | - function expectTagCid(bytes memory cborData, uint byteIdx) internal pure returns (uint) { |
34 | | - uint8 head = uint8(cborData[byteIdx]); |
35 | | - uint8 tagValue = uint8(cborData[byteIdx + 1]); |
36 | | - require(head == MajorTag << shiftMajor | MinorExtend1, "expected tag head with 1-byte extension"); |
37 | | - require(tagValue == TAG_CID, "expected tag 42 for CID"); |
38 | | - return byteIdx + 2; |
39 | | - } |
| 69 | + require(byteIdx + 4 + 5 + 32 <= cborData.length, "Expected CID size is out of range"); |
| 70 | + |
| 71 | + assembly ("memory-safe") { |
| 72 | + // cbor header at index |
| 73 | + cborHead := mload(add(cborData, add(0x20, byteIdx))) |
| 74 | + // multibase header at index + cbor header |
| 75 | + multibaseHead := mload(add(cborData, add(0x20, add(4, byteIdx)))) |
| 76 | + // cid hash at index + cbor header + multibase header |
| 77 | + cidSha256 := mload(add(cborData, add(0x20, add(9, byteIdx)))) |
| 78 | + } |
40 | 79 |
|
41 | | - function expect37Bytes(bytes memory cborData, uint byteIdx) internal pure returns (uint) { |
42 | | - require( |
43 | | - uint8(cborData[byteIdx]) == MajorBytes << shiftMajor | MinorExtend1, |
44 | | - "expected byte head with 1-byte extension" |
45 | | - ); |
46 | | - require(uint8(cborData[byteIdx + 1]) == 37, "expected 37 bytes for CID"); |
47 | | - byteIdx += 2; |
48 | | - return byteIdx; |
| 80 | + require(cborHead == cbor_tag42_bytes37, "Expected CBOR tag 42 and 37-byte item"); |
| 81 | + require(multibaseHead == multibase_cidv1_dagcbor_sha256, "Expected multibase CIDv1 DAG-CBOR sha256"); |
| 82 | + require(cidSha256 != 0, "Expected non-zero sha256 hash"); |
| 83 | + |
| 84 | + return (Cid.wrap(cidSha256), byteIdx + 4 + 5 + 32); |
49 | 85 | } |
50 | 86 |
|
| 87 | + /** |
| 88 | + * @notice Reads a CID that may be null from CBOR encoded data at the specified byte index |
| 89 | + * @dev If a CBOR null primitive appears at the byte index, the byte index |
| 90 | + * is advanced appropriately and this function returns a 'zero' CID. |
| 91 | + * @param cborData The CBOR bytes containing the CID or null |
| 92 | + * @param byteIdx The starting index to read from |
| 93 | + * @return Cid The decoded CID, or zero CID if null |
| 94 | + * @return uint The next byte index after the CID or null value |
| 95 | + */ |
51 | 96 | function readNullableCid(bytes memory cborData, uint byteIdx) internal pure returns (Cid, uint) { |
52 | 97 | if (cborData.isNullNext(byteIdx)) { |
53 | 98 | return (Cid.wrap(0), byteIdx + 1); |
| 99 | + } else { |
| 100 | + return readCid(cborData, byteIdx); |
54 | 101 | } |
55 | | - return readCid(cborData, byteIdx); |
56 | | - } |
57 | | - |
58 | | - function readCid(bytes memory cborData, uint byteIdx) internal pure returns (Cid, uint) { |
59 | | - bytes9 cidHead; |
60 | | - assembly ("memory-safe") { |
61 | | - cidHead := mload(add(cborData, add(0x20, byteIdx))) |
62 | | - } |
63 | | - |
64 | | - // all cids encountered will contain this 9-byte header |
65 | | - // D8 2A cbor tag(42) cid |
66 | | - // 58 25 cbor bytes(37) total length |
67 | | - // 00 multibase format |
68 | | - // 01 CID version |
69 | | - // 71 multicodec DAG-CBOR |
70 | | - // 12 multihash sha-256 |
71 | | - // 20 hash size 32 bytes |
72 | | - require(cidHead == hex"D82A58250001711220", "expected CIDv1 dag-cbor sha256 header sequence"); |
73 | | - byteIdx += 9; |
74 | | - uint256 cidHash; |
75 | | - assembly ("memory-safe") { |
76 | | - cidHash := mload(add(cborData, add(0x20, byteIdx))) |
77 | | - } |
78 | | - return (Cid.wrap(cidHash), byteIdx + 32); |
79 | 102 | } |
80 | 103 | } |
0 commit comments