├── .github
├── dependabot.yml
└── workflows
│ ├── generated-pr.yml
│ ├── js-test-and-release.yml
│ ├── semantic-pull-request.yml
│ └── stale.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── examples
├── block-interface.js
├── cid-interface.js
├── multicodec-interface.js
└── multihash-interface.js
├── package.json
├── src
├── bases
│ ├── base.ts
│ ├── base10.ts
│ ├── base16.ts
│ ├── base2.ts
│ ├── base256emoji.ts
│ ├── base32.ts
│ ├── base36.ts
│ ├── base58.ts
│ ├── base64.ts
│ ├── base8.ts
│ ├── identity.ts
│ └── interface.ts
├── basics.ts
├── block.ts
├── block
│ └── interface.ts
├── bytes.ts
├── cid.ts
├── codecs
│ ├── interface.ts
│ ├── json.ts
│ └── raw.ts
├── hashes
│ ├── digest.ts
│ ├── hasher.ts
│ ├── identity.ts
│ ├── interface.ts
│ ├── sha1-browser.ts
│ ├── sha1.ts
│ ├── sha2-browser.ts
│ └── sha2.ts
├── index.ts
├── interface.ts
├── link.ts
├── link
│ └── interface.ts
├── traversal.ts
├── varint.ts
└── vendor
│ ├── base-x.d.ts
│ ├── base-x.js
│ ├── varint.d.ts
│ └── varint.js
├── test
├── fixtures
│ ├── invalid-multihash.ts
│ └── valid-multihash.ts
├── test-block.spec.ts
├── test-bytes.spec.ts
├── test-cid.spec.ts
├── test-link.spec.ts
├── test-multibase-spec.spec.ts
├── test-multibase.spec.ts
├── test-multicodec.spec.ts
├── test-multihash.spec.ts
├── test-traversal.spec.ts
└── test-varint.spec.ts
├── tsconfig.json
└── typedoc.json
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "10:00"
8 | open-pull-requests-limit: 10
9 | commit-message:
10 | prefix: "deps"
11 | prefix-development: "deps(dev)"
12 | - package-ecosystem: "github-actions"
13 | directory: "/"
14 | schedule:
15 | interval: "weekly"
16 |
--------------------------------------------------------------------------------
/.github/workflows/generated-pr.yml:
--------------------------------------------------------------------------------
1 | name: Close Generated PRs
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 * * *'
6 | workflow_dispatch:
7 |
8 | permissions:
9 | issues: write
10 | pull-requests: write
11 |
12 | jobs:
13 | stale:
14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1
15 |
--------------------------------------------------------------------------------
/.github/workflows/js-test-and-release.yml:
--------------------------------------------------------------------------------
1 | name: test & maybe release
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | workflow_dispatch:
9 |
10 | permissions:
11 | contents: write
12 | id-token: write
13 | packages: write
14 | pull-requests: write
15 |
16 | concurrency:
17 | group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }}
18 | cancel-in-progress: true
19 |
20 | jobs:
21 | js-test-and-release:
22 | uses: ipdxco/unified-github-workflows/.github/workflows/js-test-and-release.yml@v1.0
23 | secrets:
24 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
25 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
26 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
27 | UCI_GITHUB_TOKEN: ${{ secrets.UCI_GITHUB_TOKEN }}
28 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
29 |
--------------------------------------------------------------------------------
/.github/workflows/semantic-pull-request.yml:
--------------------------------------------------------------------------------
1 | name: Semantic PR
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - opened
7 | - edited
8 | - synchronize
9 |
10 | jobs:
11 | main:
12 | uses: pl-strflt/.github/.github/workflows/reusable-semantic-pull-request.yml@v0.3
13 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: Close Stale Issues
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 * * *'
6 | workflow_dispatch:
7 |
8 | permissions:
9 | issues: write
10 | pull-requests: write
11 |
12 | jobs:
13 | stale:
14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | dist
4 | .docs
5 | .coverage
6 | node_modules
7 | package-lock.json
8 | yarn.lock
9 | .vscode
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This project is dual licensed under MIT and Apache-2.0.
2 |
3 | MIT: https://www.opensource.org/licenses/mit
4 | Apache-2.0: https://www.apache.org/licenses/license-2.0
5 |
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
2 |
3 | http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
6 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # multiformats
2 |
3 | [](http://multiformats.io)
4 | [](https://codecov.io/gh/multiformats/js-multiformats)
5 | [](https://github.com/multiformats/js-multiformats/actions/workflows/js-test-and-release.yml?query=branch%3Amaster)
6 |
7 | > Interface for multihash, multicodec, multibase and CID
8 |
9 | # About
10 |
11 | This library defines common interfaces and low level building blocks for various interrelated multiformat technologies (multicodec, multihash, multibase, and CID). They can be used to implement custom base encoders / decoders / codecs, codec encoders /decoders and multihash hashers that comply to the interface that layers above assume.
12 |
13 | This library provides implementations for most basics and many others can be found in linked repositories.
14 |
15 | ```TypeScript
16 | import { CID } from 'multiformats/cid'
17 | import * as json from 'multiformats/codecs/json'
18 | import { sha256 } from 'multiformats/hashes/sha2'
19 |
20 | const bytes = json.encode({ hello: 'world' })
21 |
22 | const hash = await sha256.digest(bytes)
23 | const cid = CID.create(1, json.code, hash)
24 | //> CID(bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea)
25 | ```
26 |
27 | ## Creating Blocks
28 |
29 | ```TypeScript
30 | import * as Block from 'multiformats/block'
31 | import * as codec from '@ipld/dag-cbor'
32 | import { sha256 as hasher } from 'multiformats/hashes/sha2'
33 |
34 | const value = { hello: 'world' }
35 |
36 | // encode a block
37 | let block = await Block.encode({ value, codec, hasher })
38 |
39 | block.value // { hello: 'world' }
40 | block.bytes // Uint8Array
41 | block.cid // CID() w/ sha2-256 hash address and dag-cbor codec
42 |
43 | // you can also decode blocks from their binary state
44 | block = await Block.decode({ bytes: block.bytes, codec, hasher })
45 |
46 | // if you have the cid you can also verify the hash on decode
47 | block = await Block.create({ bytes: block.bytes, cid: block.cid, codec, hasher })
48 | ```
49 |
50 | ## Multibase Encoders / Decoders / Codecs
51 |
52 | CIDs can be serialized to string representation using multibase encoders that implement [`MultibaseEncoder`](https://github.com/multiformats/js-multiformats/blob/master/src/bases/interface.ts) interface. This library provides quite a few implementations that can be imported:
53 |
54 | ```TypeScript
55 | import { base64 } from "multiformats/bases/base64"
56 | cid.toString(base64.encoder)
57 | //> 'mAYAEEiCTojlxqRTl6svwqNJRVM2jCcPBxy+7mRTUfGDzy2gViA'
58 | ```
59 |
60 | Parsing CID string serialized CIDs requires multibase decoder that implements [`MultibaseDecoder`](https://github.com/multiformats/js-multiformats/blob/master/src/bases/interface.ts) interface. This library provides a decoder for every encoder it provides:
61 |
62 | ```TypeScript
63 | CID.parse('mAYAEEiCTojlxqRTl6svwqNJRVM2jCcPBxy+7mRTUfGDzy2gViA', base64.decoder)
64 | //> CID(bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea)
65 | ```
66 |
67 | Dual of multibase encoder & decoder is defined as multibase codec and it exposes
68 | them as `encoder` and `decoder` properties. For added convenience codecs also
69 | implement `MultibaseEncoder` and `MultibaseDecoder` interfaces so they could be
70 | used as either or both:
71 |
72 | ```TypeScript
73 | cid.toString(base64)
74 | CID.parse(cid.toString(base64), base64)
75 | ```
76 |
77 | **Note:** CID implementation comes bundled with `base32`, `base36`, and `base58btc`
78 | multibase codecs so that CIDs can be base serialized to (version specific)
79 | default base encoding and parsed without having to supply base encoders/decoders:
80 |
81 | ```TypeScript
82 | const v1 = CID.parse('bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea')
83 | v1.toString()
84 | //> 'bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea'
85 |
86 | const v0 = CID.parse('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n')
87 | v0.toString()
88 | //> 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
89 | v0.toV1().toString()
90 | //> 'bafybeihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku'
91 | ```
92 |
93 | ## Multicodec Encoders / Decoders / Codecs
94 |
95 | This library defines [`BlockEncoder`, `BlockDecoder` and `BlockCodec` interfaces](https://github.com/multiformats/js-multiformats/blob/master/src/codecs/interface.ts).
96 | Codec implementations should conform to the `BlockCodec` interface which implements both `BlockEncoder` and `BlockDecoder`.
97 | Here is an example implementation of JSON `BlockCodec`.
98 |
99 | ```TypeScript
100 | export const { name, code, encode, decode } = {
101 | name: 'json',
102 | code: 0x0200,
103 | encode: json => new TextEncoder().encode(JSON.stringify(json)),
104 | decode: bytes => JSON.parse(new TextDecoder().decode(bytes))
105 | }
106 | ```
107 |
108 | ## Multihash Hashers
109 |
110 | This library defines [`MultihashHasher` and `MultihashDigest` interfaces](https://github.com/multiformats/js-multiformats/blob/master/src/hashes/interface.ts) and convinient function for implementing them:
111 |
112 | ```TypeScript
113 | import * as hasher from 'multiformats/hashes/hasher'
114 |
115 | const sha256 = hasher.from({
116 | // As per multiformats table
117 | // https://github.com/multiformats/multicodec/blob/master/table.csv#L9
118 | name: 'sha2-256',
119 | code: 0x12,
120 |
121 | encode: (input) => new Uint8Array(crypto.createHash('sha256').update(input).digest())
122 | })
123 |
124 | const hash = await sha256.digest(json.encode({ hello: 'world' }))
125 | CID.create(1, json.code, hash)
126 |
127 | //> CID(bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea)
128 | ```
129 |
130 | ## Traversal
131 |
132 | This library contains higher-order functions for traversing graphs of data easily.
133 |
134 | `walk()` walks through the links in each block of a DAG calling a user-supplied loader function for each one, in depth-first order with no duplicate block visits. The loader should return a `Block` object and can be used to inspect and collect block ordering for a full DAG walk. The loader should `throw` on error, and return `null` if a block should be skipped by `walk()`.
135 |
136 | ```TypeScript
137 | import { walk } from 'multiformats/traversal'
138 | import * as Block from 'multiformats/block'
139 | import * as codec from 'multiformats/codecs/json'
140 | import { sha256 as hasher } from 'multiformats/hashes/sha2'
141 |
142 | // build a DAG (a single block for this simple example)
143 | const value = { hello: 'world' }
144 | const block = await Block.encode({ value, codec, hasher })
145 | const { cid } = block
146 | console.log(cid)
147 | //> CID(bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea)
148 |
149 | // create a loader function that also collects CIDs of blocks in
150 | // their traversal order
151 | const load = (cid, blocks) => async (cid) => {
152 | // fetch a block using its cid
153 | // e.g.: const block = await fetchBlockByCID(cid)
154 | blocks.push(cid)
155 | return block
156 | }
157 |
158 | // collect blocks in this DAG starting from the root `cid`
159 | const blocks = []
160 | await walk({ cid, load: load(cid, blocks) })
161 |
162 | console.log(blocks)
163 | //> [CID(bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea)]
164 | ```
165 |
166 | ## Legacy interface
167 |
168 | [`blockcodec-to-ipld-format`](https://github.com/ipld/js-blockcodec-to-ipld-format) converts a multiformats [`BlockCodec`](https://github.com/multiformats/js-multiformats/blob/master/src/codecs/interface.ts#L21) into an
169 | [`interface-ipld-format`](https://github.com/ipld/interface-ipld-format) for use with the [`ipld`](https://github.com/ipld/ipld) package. This can help bridge IPLD codecs implemented using the structure and interfaces defined here to existing code that assumes, or requires `interface-ipld-format`. This bridge also includes the relevant TypeScript definitions.
170 |
171 | ## Implementations
172 |
173 | By default, no base encodings (other than base32 & base58btc), hash functions,
174 | or codec implementations are exposed by `multiformats`, you need to
175 | import the ones you need yourself.
176 |
177 | ### Multibase codecs
178 |
179 | | bases | import | repo |
180 | | ------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------- |
181 | | `base16` | `multiformats/bases/base16` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) |
182 | | `base32`, `base32pad`, `base32hex`, `base32hexpad`, `base32z` | `multiformats/bases/base32` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) |
183 | | `base64`, `base64pad`, `base64url`, `base64urlpad` | `multiformats/bases/base64` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) |
184 | | `base58btc`, `base58flick4` | `multiformats/bases/base58` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) |
185 |
186 | Other (less useful) bases implemented in [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) include: `base2`, `base8`, `base10`, `base36` and `base256emoji`.
187 |
188 | ### Multihash hashers
189 |
190 | | hashes | import | repo |
191 | | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
192 | | `sha2-256`, `sha2-512` | `multiformats/hashes/sha2` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/src/hashes) |
193 | | `sha3-224`, `sha3-256`, `sha3-384`,`sha3-512`, `shake-128`, `shake-256`, `keccak-224`, `keccak-256`, `keccak-384`, `keccak-512` | `@multiformats/sha3` | [multiformats/js-sha3](https://github.com/multiformats/js-sha3) |
194 | | `identity` | `multiformats/hashes/identity` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/src/hashes/identity.js) |
195 | | `murmur3-128`, `murmur3-32` | `@multiformats/murmur3` | [multiformats/js-murmur3](https://github.com/multiformats/js-murmur3) |
196 | | `blake2b-*`, `blake2s-*` | `@multiformats/blake2` | [multiformats/js-blake2](https://github.com/multiformats/js-blake2) |
197 |
198 | ### IPLD codecs (multicodec)
199 |
200 | | codec | import | repo |
201 | | ---------- | -------------------------- | ------------------------------------------------------------------------------------------------------ |
202 | | `raw` | `multiformats/codecs/raw` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/src/codecs) |
203 | | `json` | `multiformats/codecs/json` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/src/codecs) |
204 | | `dag-cbor` | `@ipld/dag-cbor` | [ipld/js-dag-cbor](https://github.com/ipld/js-dag-cbor) |
205 | | `dag-json` | `@ipld/dag-json` | [ipld/js-dag-json](https://github.com/ipld/js-dag-json) |
206 | | `dag-pb` | `@ipld/dag-pb` | [ipld/js-dag-pb](https://github.com/ipld/js-dag-pb) |
207 | | `dag-jose` | `dag-jose` | [ceramicnetwork/js-dag-jose](https://github.com/ceramicnetwork/js-dag-jose) |
208 |
209 | # Install
210 |
211 | ```console
212 | $ npm i multiformats
213 | ```
214 |
215 | ## Browser `
221 | ```
222 |
223 | # API Docs
224 |
225 | -
226 |
227 | # License
228 |
229 | Licensed under either of
230 |
231 | - Apache 2.0, ([LICENSE-APACHE](https://github.com/multiformats/js-multiformats/LICENSE-APACHE) / )
232 | - MIT ([LICENSE-MIT](https://github.com/multiformats/js-multiformats/LICENSE-MIT) / )
233 |
234 | # Contribution
235 |
236 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
237 |
--------------------------------------------------------------------------------
/examples/block-interface.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import * as codec from '@ipld/dag-cbor'
4 | import * as Block from 'multiformats/block'
5 | import { sha256 as hasher } from 'multiformats/hashes/sha2'
6 |
7 | async function run () {
8 | const value = { hello: 'world' }
9 |
10 | // encode a block
11 | const block = await Block.encode({ value, codec, hasher })
12 |
13 | // block.value --> { hello: 'world' }
14 | // block.bytes --> Uint8Array
15 | // block.cid --> CID() w/ sha2-256 hash address and dag-cbor codec
16 |
17 | console.log('Example block CID: ' + block.cid.toString())
18 |
19 | // you can also decode blocks from their binary state
20 | const block2 = await Block.decode({ bytes: block.bytes, codec, hasher })
21 |
22 | // check for equivalency using cid interface
23 | console.log('Example block CID equal to decoded binary block: ' + block.cid.equals(block2.cid))
24 |
25 | // if you have the cid you can also verify the hash on decode
26 | const block3 = await Block.create({ bytes: block.bytes, cid: block.cid, codec, hasher })
27 | console.log('Example block CID equal to block created from CID + bytes: ' + block.cid.equals(block3.cid))
28 | }
29 |
30 | run().catch((err) => {
31 | console.error(err)
32 | process.exit(1)
33 | })
34 |
--------------------------------------------------------------------------------
/examples/cid-interface.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import assert from 'assert'
3 | import { base64 } from 'multiformats/bases/base64'
4 | import { CID } from 'multiformats/cid'
5 | import * as json from 'multiformats/codecs/json'
6 | import { sha256 } from 'multiformats/hashes/sha2'
7 |
8 | async function run () {
9 | // ** PART 1: CREATING A NEW CID **
10 |
11 | // Arbitrary input value
12 | const value = { hello: 'world' }
13 |
14 | // Encoded Uint8array representation of `value` using the plain JSON IPLD codec
15 | const bytes = json.encode(value)
16 |
17 | // Hash Uint8array representation
18 | const hash = await sha256.digest(bytes)
19 |
20 | // Create CID (default base32)
21 | const cid = CID.create(1, json.code, hash)
22 |
23 | // cid.code --> 512 (0x0200) JSON IPLD codec)
24 | // cid.version --> 1
25 | // cid.multihash --> digest, including code (18 for sha2-256), digest size (32 bytes)
26 | // cid.bytes --> byte representation`
27 |
28 | console.log('Example CID: ' + cid.toString())
29 | // 'bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea'
30 |
31 | // ** PART 2: MULTIBASE ENCODERS / DECODERS / CODECS **
32 |
33 | // Encode CID from part 1 to base64, decode back to base32
34 | const cidBase64 = cid.toString(base64.encoder)
35 | console.log('base64 encoded CID: ' + cidBase64)
36 | // 'mAYAEEiCTojlxqRTl6svwqNJRVM2jCcPBxy+7mRTUfGDzy2gViA'
37 |
38 | const cidBase32 = CID.parse(cidBase64, base64.decoder)
39 | // 'bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea'
40 |
41 | // test decoded CID against original
42 | assert.strictEqual(cidBase32.toString(), cid.toString(), 'Warning: decoded base32 CID does not match original')
43 | console.log('Decoded CID equal to original base32: ' + cidBase32.equals(cid)) // alternatively, use more robust built-in function to test equivalence
44 |
45 | // Multibase codec exposes both encoder and decoder properties
46 | cid.toString(base64)
47 | CID.parse(cid.toString(base64), base64)
48 |
49 | // ** PART 3: CID BASE CONFIGURATIONS **
50 |
51 | // CID v1 default encoding is base32
52 | const v1 = CID.parse('bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea')
53 | v1.toString()
54 | // 'bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea'
55 |
56 | // CID v0 default encoding is base58btc
57 | const v0 = CID.parse('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n')
58 | v0.toString()
59 | // 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
60 | v0.toV1().toString()
61 | // 'bafybeihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku'
62 | }
63 |
64 | run().catch((err) => {
65 | console.error(err)
66 | process.exit(1)
67 | })
68 |
--------------------------------------------------------------------------------
/examples/multicodec-interface.js:
--------------------------------------------------------------------------------
1 | // Example of multicodec implementation for JSON (UTF-8-encoded)
2 | // Codec implementations should conform to the BlockCodec interface which implements both BlockEncoder and BlockDecoder
3 |
4 | /**
5 | * @template T
6 | * @type {BlockCodec<0x0200, T>}
7 | */
8 | export const { name, code, encode, decode } = {
9 | name: 'json',
10 | code: 0x0200,
11 | encode: json => new TextEncoder().encode(JSON.stringify(json)),
12 | decode: bytes => JSON.parse(new TextDecoder().decode(bytes))
13 | }
14 |
--------------------------------------------------------------------------------
/examples/multihash-interface.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import crypto from 'crypto'
3 | import { CID } from 'multiformats/cid'
4 | import * as json from 'multiformats/codecs/json'
5 | import * as hasher from 'multiformats/hashes/hasher'
6 |
7 | // ** Example 1: sha2-256 hasher **
8 |
9 | const sha256 = hasher.from({
10 | // As per multiformats table
11 | // https://github.com/multiformats/multicodec/blob/master/table.csv#L9
12 | name: 'sha2-256',
13 | code: 0x12,
14 |
15 | encode: (input) => new Uint8Array(crypto.createHash('sha256').update(input).digest())
16 | })
17 |
18 | async function run1 () {
19 | const hash = await sha256.digest(json.encode({ hello: 'world' }))
20 | const cid = CID.create(1, json.code, hash)
21 |
22 | console.log(cid.multihash.size) // should equal 32 bytes for sha256
23 | console.log(cid)
24 | // CID(bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea)
25 | }
26 |
27 | run1().catch((err) => {
28 | console.error(err)
29 | process.exit(1)
30 | })
31 |
32 | // ** Example 2: sha3-512 hasher **
33 |
34 | const sha512 = hasher.from({
35 | // As per multiformats table
36 | // https://github.com/multiformats/multicodec/blob/master/table.csv#L9
37 | name: 'sha3-512',
38 | code: 0x14,
39 |
40 | encode: (input) => new Uint8Array(crypto.createHash('sha512').update(input).digest())
41 | })
42 |
43 | async function run2 () {
44 | const hash2 = await sha512.digest(json.encode({ hello: 'world' }))
45 | const cid2 = CID.create(1, json.code, hash2)
46 |
47 | console.log(cid2.multihash.size) // should equal 64 bytes for sha512
48 | console.log(cid2)
49 | // CID(bagaaifca7d5wrebdi6rmqkgtrqyodq3bo6gitrqtemxtliymakwswbazbu7ai763747ljp7ycqfv7aqx4xlgiugcx62quo2te45pcgjbg4qjsvq)
50 | }
51 |
52 | run2().catch((err) => {
53 | console.error(err)
54 | process.exit(1)
55 | })
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "multiformats",
3 | "version": "13.3.6",
4 | "description": "Interface for multihash, multicodec, multibase and CID",
5 | "author": "Mikeal Rogers (https://www.mikealrogers.com/)",
6 | "license": "Apache-2.0 OR MIT",
7 | "homepage": "https://github.com/multiformats/js-multiformats#readme",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/multiformats/js-multiformats.git"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/multiformats/js-multiformats/issues"
14 | },
15 | "publishConfig": {
16 | "access": "public",
17 | "provenance": true
18 | },
19 | "keywords": [
20 | "ipfs",
21 | "ipld",
22 | "multiformats"
23 | ],
24 | "type": "module",
25 | "types": "./dist/src/index.d.ts",
26 | "typesVersions": {
27 | "*": {
28 | "*": [
29 | "*",
30 | "dist/*",
31 | "dist/src/*",
32 | "dist/src/*/index"
33 | ],
34 | "src/*": [
35 | "*",
36 | "dist/*",
37 | "dist/src/*",
38 | "dist/src/*/index"
39 | ]
40 | }
41 | },
42 | "files": [
43 | "src",
44 | "dist",
45 | "!dist/test",
46 | "!**/*.tsbuildinfo"
47 | ],
48 | "exports": {
49 | ".": {
50 | "types": "./dist/src/index.d.ts",
51 | "import": "./dist/src/index.js"
52 | },
53 | "./bases/base10": {
54 | "types": "./dist/src/bases/base10.d.ts",
55 | "import": "./dist/src/bases/base10.js"
56 | },
57 | "./bases/base16": {
58 | "types": "./dist/src/bases/base16.d.ts",
59 | "import": "./dist/src/bases/base16.js"
60 | },
61 | "./bases/base2": {
62 | "types": "./dist/src/bases/base2.d.ts",
63 | "import": "./dist/src/bases/base2.js"
64 | },
65 | "./bases/base256emoji": {
66 | "types": "./dist/src/bases/base256emoji.d.ts",
67 | "import": "./dist/src/bases/base256emoji.js"
68 | },
69 | "./bases/base32": {
70 | "types": "./dist/src/bases/base32.d.ts",
71 | "import": "./dist/src/bases/base32.js"
72 | },
73 | "./bases/base36": {
74 | "types": "./dist/src/bases/base36.d.ts",
75 | "import": "./dist/src/bases/base36.js"
76 | },
77 | "./bases/base58": {
78 | "types": "./dist/src/bases/base58.d.ts",
79 | "import": "./dist/src/bases/base58.js"
80 | },
81 | "./bases/base64": {
82 | "types": "./dist/src/bases/base64.d.ts",
83 | "import": "./dist/src/bases/base64.js"
84 | },
85 | "./bases/base8": {
86 | "types": "./dist/src/bases/base8.d.ts",
87 | "import": "./dist/src/bases/base8.js"
88 | },
89 | "./bases/identity": {
90 | "types": "./dist/src/bases/identity.d.ts",
91 | "import": "./dist/src/bases/identity.js"
92 | },
93 | "./bases/interface": {
94 | "types": "./dist/src/bases/interface.d.ts",
95 | "import": "./dist/src/bases/interface.js"
96 | },
97 | "./basics": {
98 | "types": "./dist/src/basics.d.ts",
99 | "import": "./dist/src/basics.js"
100 | },
101 | "./block": {
102 | "types": "./dist/src/block.d.ts",
103 | "import": "./dist/src/block.js"
104 | },
105 | "./block/interface": {
106 | "types": "./dist/src/block/interface.d.ts",
107 | "import": "./dist/src/block/interface.js"
108 | },
109 | "./bytes": {
110 | "types": "./dist/src/bytes.d.ts",
111 | "import": "./dist/src/bytes.js"
112 | },
113 | "./cid": {
114 | "types": "./dist/src/cid.d.ts",
115 | "import": "./dist/src/cid.js"
116 | },
117 | "./codecs/interface": {
118 | "types": "./dist/src/codecs/interface.d.ts",
119 | "import": "./dist/src/codecs/interface.js"
120 | },
121 | "./codecs/json": {
122 | "types": "./dist/src/codecs/json.d.ts",
123 | "import": "./dist/src/codecs/json.js"
124 | },
125 | "./codecs/raw": {
126 | "types": "./dist/src/codecs/raw.d.ts",
127 | "import": "./dist/src/codecs/raw.js"
128 | },
129 | "./hashes/digest": {
130 | "types": "./dist/src/hashes/digest.d.ts",
131 | "import": "./dist/src/hashes/digest.js"
132 | },
133 | "./hashes/hasher": {
134 | "types": "./dist/src/hashes/hasher.d.ts",
135 | "import": "./dist/src/hashes/hasher.js"
136 | },
137 | "./hashes/identity": {
138 | "types": "./dist/src/hashes/identity.d.ts",
139 | "import": "./dist/src/hashes/identity.js"
140 | },
141 | "./hashes/interface": {
142 | "types": "./dist/src/hashes/interface.d.ts",
143 | "import": "./dist/src/hashes/interface.js"
144 | },
145 | "./hashes/sha1": {
146 | "types": "./dist/types/src/hashes/sha1.d.ts",
147 | "browser": "./dist/src/hashes/sha1-browser.js",
148 | "import": "./dist/src/hashes/sha1.js"
149 | },
150 | "./hashes/sha2": {
151 | "types": "./dist/src/hashes/sha2.d.ts",
152 | "browser": "./dist/src/hashes/sha2-browser.js",
153 | "import": "./dist/src/hashes/sha2.js"
154 | },
155 | "./interface": {
156 | "types": "./dist/src/interface.d.ts",
157 | "import": "./dist/src/interface.js"
158 | },
159 | "./link": {
160 | "types": "./dist/src/link.d.ts",
161 | "import": "./dist/src/link.js"
162 | },
163 | "./link/interface": {
164 | "types": "./dist/src/link/interface.d.ts",
165 | "import": "./dist/src/link/interface.js"
166 | },
167 | "./traversal": {
168 | "types": "./dist/src/traversal.d.ts",
169 | "import": "./dist/src/traversal.js"
170 | }
171 | },
172 | "eslintConfig": {
173 | "extends": "ipfs",
174 | "parserOptions": {
175 | "project": true,
176 | "sourceType": "module"
177 | }
178 | },
179 | "release": {
180 | "branches": [
181 | "master"
182 | ],
183 | "plugins": [
184 | [
185 | "@semantic-release/commit-analyzer",
186 | {
187 | "preset": "conventionalcommits",
188 | "releaseRules": [
189 | {
190 | "breaking": true,
191 | "release": "major"
192 | },
193 | {
194 | "revert": true,
195 | "release": "patch"
196 | },
197 | {
198 | "type": "feat",
199 | "release": "minor"
200 | },
201 | {
202 | "type": "fix",
203 | "release": "patch"
204 | },
205 | {
206 | "type": "docs",
207 | "release": "patch"
208 | },
209 | {
210 | "type": "test",
211 | "release": "patch"
212 | },
213 | {
214 | "type": "deps",
215 | "release": "patch"
216 | },
217 | {
218 | "scope": "no-release",
219 | "release": false
220 | }
221 | ]
222 | }
223 | ],
224 | [
225 | "@semantic-release/release-notes-generator",
226 | {
227 | "preset": "conventionalcommits",
228 | "presetConfig": {
229 | "types": [
230 | {
231 | "type": "feat",
232 | "section": "Features"
233 | },
234 | {
235 | "type": "fix",
236 | "section": "Bug Fixes"
237 | },
238 | {
239 | "type": "chore",
240 | "section": "Trivial Changes"
241 | },
242 | {
243 | "type": "docs",
244 | "section": "Documentation"
245 | },
246 | {
247 | "type": "deps",
248 | "section": "Dependencies"
249 | },
250 | {
251 | "type": "test",
252 | "section": "Tests"
253 | }
254 | ]
255 | }
256 | }
257 | ],
258 | "@semantic-release/changelog",
259 | "@semantic-release/npm",
260 | "@semantic-release/github",
261 | [
262 | "@semantic-release/git",
263 | {
264 | "assets": [
265 | "CHANGELOG.md",
266 | "package.json"
267 | ]
268 | }
269 | ]
270 | ]
271 | },
272 | "scripts": {
273 | "clean": "aegir clean",
274 | "lint": "aegir lint",
275 | "build": "aegir build",
276 | "release": "aegir release",
277 | "docs": "aegir docs",
278 | "test": "npm run lint && npm run test:node && npm run test:chrome",
279 | "test:node": "aegir test -t node --cov",
280 | "test:chrome": "aegir test -t browser --cov",
281 | "test:chrome-webworker": "aegir test -t webworker",
282 | "test:firefox": "aegir test -t browser -- --browser firefox",
283 | "test:firefox-webworker": "aegir test -t webworker -- --browser firefox",
284 | "test:electron-main": "aegir test -t electron-main"
285 | },
286 | "devDependencies": {
287 | "@stablelib/sha256": "^2.0.0",
288 | "@stablelib/sha512": "^2.0.0",
289 | "@types/node": "^22.0.0",
290 | "aegir": "^47.0.7",
291 | "buffer": "^6.0.3",
292 | "cids": "^1.1.9",
293 | "crypto-hash": "^3.0.0"
294 | },
295 | "aegir": {
296 | "test": {
297 | "target": [
298 | "node",
299 | "browser"
300 | ]
301 | }
302 | },
303 | "browser": {
304 | "./hashes/sha1": "./dist/src/hashes/sha1-browser.js",
305 | "./dist/src/hashes/sha1.js": "./dist/src/hashes/sha1-browser.js",
306 | "./hashes/sha2": "./dist/src/hashes/sha2-browser.js",
307 | "./dist/src/hashes/sha2.js": "./dist/src/hashes/sha2-browser.js"
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/src/bases/base.ts:
--------------------------------------------------------------------------------
1 | import { coerce } from '../bytes.js'
2 | import basex from '../vendor/base-x.js'
3 | import type { BaseCodec, BaseDecoder, BaseEncoder, CombobaseDecoder, Multibase, MultibaseCodec, MultibaseDecoder, MultibaseEncoder, UnibaseDecoder } from './interface.js'
4 |
5 | interface EncodeFn { (bytes: Uint8Array): string }
6 | interface DecodeFn { (text: string): Uint8Array }
7 |
8 | /**
9 | * Class represents both BaseEncoder and MultibaseEncoder meaning it
10 | * can be used to encode to multibase or base encode without multibase
11 | * prefix.
12 | */
13 | class Encoder implements MultibaseEncoder, BaseEncoder {
14 | readonly name: Base
15 | readonly prefix: Prefix
16 | readonly baseEncode: EncodeFn
17 |
18 | constructor (name: Base, prefix: Prefix, baseEncode: EncodeFn) {
19 | this.name = name
20 | this.prefix = prefix
21 | this.baseEncode = baseEncode
22 | }
23 |
24 | encode (bytes: Uint8Array): Multibase {
25 | if (bytes instanceof Uint8Array) {
26 | return `${this.prefix}${this.baseEncode(bytes)}`
27 | } else {
28 | throw Error('Unknown type, must be binary type')
29 | }
30 | }
31 | }
32 |
33 | /**
34 | * Class represents both BaseDecoder and MultibaseDecoder so it could be used
35 | * to decode multibases (with matching prefix) or just base decode strings
36 | * with corresponding base encoding.
37 | */
38 | class Decoder implements MultibaseDecoder, UnibaseDecoder, BaseDecoder {
39 | readonly name: Base
40 | readonly prefix: Prefix
41 | readonly baseDecode: DecodeFn
42 | private readonly prefixCodePoint: number
43 |
44 | constructor (name: Base, prefix: Prefix, baseDecode: DecodeFn) {
45 | this.name = name
46 | this.prefix = prefix
47 | const prefixCodePoint = prefix.codePointAt(0)
48 | /* c8 ignore next 3 */
49 | if (prefixCodePoint === undefined) {
50 | throw new Error('Invalid prefix character')
51 | }
52 | this.prefixCodePoint = prefixCodePoint
53 | this.baseDecode = baseDecode
54 | }
55 |
56 | decode (text: string): Uint8Array {
57 | if (typeof text === 'string') {
58 | if (text.codePointAt(0) !== this.prefixCodePoint) {
59 | throw Error(`Unable to decode multibase string ${JSON.stringify(text)}, ${this.name} decoder only supports inputs prefixed with ${this.prefix}`)
60 | }
61 | return this.baseDecode(text.slice(this.prefix.length))
62 | } else {
63 | throw Error('Can only multibase decode strings')
64 | }
65 | }
66 |
67 | or (decoder: UnibaseDecoder | ComposedDecoder): ComposedDecoder {
68 | return or(this, decoder)
69 | }
70 | }
71 |
72 | type Decoders = Record>
73 |
74 | class ComposedDecoder implements MultibaseDecoder, CombobaseDecoder {
75 | readonly decoders: Decoders
76 |
77 | constructor (decoders: Decoders) {
78 | this.decoders = decoders
79 | }
80 |
81 | or (decoder: UnibaseDecoder | ComposedDecoder): ComposedDecoder {
82 | return or(this, decoder)
83 | }
84 |
85 | decode (input: string): Uint8Array {
86 | const prefix = input[0] as Prefix
87 | const decoder = this.decoders[prefix]
88 | if (decoder != null) {
89 | return decoder.decode(input)
90 | } else {
91 | throw RangeError(`Unable to decode multibase string ${JSON.stringify(input)}, only inputs prefixed with ${Object.keys(this.decoders)} are supported`)
92 | }
93 | }
94 | }
95 |
96 | export function or (left: UnibaseDecoder | CombobaseDecoder, right: UnibaseDecoder | CombobaseDecoder): ComposedDecoder {
97 | return new ComposedDecoder({
98 | ...(left.decoders ?? { [(left as UnibaseDecoder).prefix]: left }),
99 | ...(right.decoders ?? { [(right as UnibaseDecoder).prefix]: right })
100 | } as Decoders)
101 | }
102 |
103 | export class Codec implements MultibaseCodec, MultibaseEncoder, MultibaseDecoder, BaseCodec, BaseEncoder, BaseDecoder {
104 | readonly name: Base
105 | readonly prefix: Prefix
106 | readonly baseEncode: EncodeFn
107 | readonly baseDecode: DecodeFn
108 | readonly encoder: Encoder
109 | readonly decoder: Decoder
110 |
111 | constructor (name: Base, prefix: Prefix, baseEncode: EncodeFn, baseDecode: DecodeFn) {
112 | this.name = name
113 | this.prefix = prefix
114 | this.baseEncode = baseEncode
115 | this.baseDecode = baseDecode
116 | this.encoder = new Encoder(name, prefix, baseEncode)
117 | this.decoder = new Decoder(name, prefix, baseDecode)
118 | }
119 |
120 | encode (input: Uint8Array): string {
121 | return this.encoder.encode(input)
122 | }
123 |
124 | decode (input: string): Uint8Array {
125 | return this.decoder.decode(input)
126 | }
127 | }
128 |
129 | export function from ({ name, prefix, encode, decode }: { name: Base, prefix: Prefix, encode: EncodeFn, decode: DecodeFn }): Codec {
130 | return new Codec(name, prefix, encode, decode)
131 | }
132 |
133 | export function baseX ({ name, prefix, alphabet }: { name: Base, prefix: Prefix, alphabet: string }): Codec {
134 | const { encode, decode } = basex(alphabet, name)
135 | return from({
136 | prefix,
137 | name,
138 | encode,
139 | decode: (text: string): Uint8Array => coerce(decode(text))
140 | })
141 | }
142 |
143 | function decode (string: string, alphabetIdx: Record, bitsPerChar: number, name: string): Uint8Array {
144 | // Count the padding bytes:
145 | let end = string.length
146 | while (string[end - 1] === '=') {
147 | --end
148 | }
149 |
150 | // Allocate the output:
151 | const out = new Uint8Array((end * bitsPerChar / 8) | 0)
152 |
153 | // Parse the data:
154 | let bits = 0 // Number of bits currently in the buffer
155 | let buffer = 0 // Bits waiting to be written out, MSB first
156 | let written = 0 // Next byte to write
157 | for (let i = 0; i < end; ++i) {
158 | // Read one character from the string:
159 | const value = alphabetIdx[string[i]]
160 | if (value === undefined) {
161 | throw new SyntaxError(`Non-${name} character`)
162 | }
163 |
164 | // Append the bits to the buffer:
165 | buffer = (buffer << bitsPerChar) | value
166 | bits += bitsPerChar
167 |
168 | // Write out some bits if the buffer has a byte's worth:
169 | if (bits >= 8) {
170 | bits -= 8
171 | out[written++] = 0xff & (buffer >> bits)
172 | }
173 | }
174 |
175 | // Verify that we have received just enough bits:
176 | if (bits >= bitsPerChar || (0xff & (buffer << (8 - bits))) !== 0) {
177 | throw new SyntaxError('Unexpected end of data')
178 | }
179 |
180 | return out
181 | }
182 |
183 | function encode (data: Uint8Array, alphabet: string, bitsPerChar: number): string {
184 | const pad = alphabet[alphabet.length - 1] === '='
185 | const mask = (1 << bitsPerChar) - 1
186 | let out = ''
187 |
188 | let bits = 0 // Number of bits currently in the buffer
189 | let buffer = 0 // Bits waiting to be written out, MSB first
190 | for (let i = 0; i < data.length; ++i) {
191 | // Slurp data into the buffer:
192 | buffer = (buffer << 8) | data[i]
193 | bits += 8
194 |
195 | // Write out as much as we can:
196 | while (bits > bitsPerChar) {
197 | bits -= bitsPerChar
198 | out += alphabet[mask & (buffer >> bits)]
199 | }
200 | }
201 |
202 | // Partial character:
203 | if (bits !== 0) {
204 | out += alphabet[mask & (buffer << (bitsPerChar - bits))]
205 | }
206 |
207 | // Add padding characters until we hit a byte boundary:
208 | if (pad) {
209 | while (((out.length * bitsPerChar) & 7) !== 0) {
210 | out += '='
211 | }
212 | }
213 |
214 | return out
215 | }
216 |
217 | function createAlphabetIdx (alphabet: string): Record {
218 | // Build the character lookup table:
219 | const alphabetIdx: Record = {}
220 | for (let i = 0; i < alphabet.length; ++i) {
221 | alphabetIdx[alphabet[i]] = i
222 | }
223 | return alphabetIdx
224 | }
225 |
226 | /**
227 | * RFC4648 Factory
228 | */
229 | export function rfc4648 ({ name, prefix, bitsPerChar, alphabet }: { name: Base, prefix: Prefix, bitsPerChar: number, alphabet: string }): Codec {
230 | const alphabetIdx = createAlphabetIdx(alphabet)
231 | return from({
232 | prefix,
233 | name,
234 | encode (input: Uint8Array): string {
235 | return encode(input, alphabet, bitsPerChar)
236 | },
237 | decode (input: string): Uint8Array {
238 | return decode(input, alphabetIdx, bitsPerChar, name)
239 | }
240 | })
241 | }
242 |
--------------------------------------------------------------------------------
/src/bases/base10.ts:
--------------------------------------------------------------------------------
1 | import { baseX } from './base.js'
2 |
3 | export const base10 = baseX({
4 | prefix: '9',
5 | name: 'base10',
6 | alphabet: '0123456789'
7 | })
8 |
--------------------------------------------------------------------------------
/src/bases/base16.ts:
--------------------------------------------------------------------------------
1 | import { rfc4648 } from './base.js'
2 |
3 | export const base16 = rfc4648({
4 | prefix: 'f',
5 | name: 'base16',
6 | alphabet: '0123456789abcdef',
7 | bitsPerChar: 4
8 | })
9 |
10 | export const base16upper = rfc4648({
11 | prefix: 'F',
12 | name: 'base16upper',
13 | alphabet: '0123456789ABCDEF',
14 | bitsPerChar: 4
15 | })
16 |
--------------------------------------------------------------------------------
/src/bases/base2.ts:
--------------------------------------------------------------------------------
1 | import { rfc4648 } from './base.js'
2 |
3 | export const base2 = rfc4648({
4 | prefix: '0',
5 | name: 'base2',
6 | alphabet: '01',
7 | bitsPerChar: 1
8 | })
9 |
--------------------------------------------------------------------------------
/src/bases/base256emoji.ts:
--------------------------------------------------------------------------------
1 | import { from } from './base.js'
2 |
3 | const alphabet = Array.from('🚀🪐☄🛰🌌🌑🌒🌓🌔🌕🌖🌗🌘🌍🌏🌎🐉☀💻🖥💾💿😂❤😍🤣😊🙏💕😭😘👍😅👏😁🔥🥰💔💖💙😢🤔😆🙄💪😉☺👌🤗💜😔😎😇🌹🤦🎉💞✌✨🤷😱😌🌸🙌😋💗💚😏💛🙂💓🤩😄😀🖤😃💯🙈👇🎶😒🤭❣😜💋👀😪😑💥🙋😞😩😡🤪👊🥳😥🤤👉💃😳✋😚😝😴🌟😬🙃🍀🌷😻😓⭐✅🥺🌈😈🤘💦✔😣🏃💐☹🎊💘😠☝😕🌺🎂🌻😐🖕💝🙊😹🗣💫💀👑🎵🤞😛🔴😤🌼😫⚽🤙☕🏆🤫👈😮🙆🍻🍃🐶💁😲🌿🧡🎁⚡🌞🎈❌✊👋😰🤨😶🤝🚶💰🍓💢🤟🙁🚨💨🤬✈🎀🍺🤓😙💟🌱😖👶🥴▶➡❓💎💸⬇😨🌚🦋😷🕺⚠🙅😟😵👎🤲🤠🤧📌🔵💅🧐🐾🍒😗🤑🌊🤯🐷☎💧😯💆👆🎤🙇🍑❄🌴💣🐸💌📍🥀🤢👅💡💩👐📸👻🤐🤮🎼🥵🚩🍎🍊👼💍📣🥂')
4 | const alphabetBytesToChars: string[] = (alphabet.reduce((p, c, i) => { p[i] = c; return p }, ([])))
5 | const alphabetCharsToBytes: number[] = (alphabet.reduce((p, c, i) => {
6 | const codePoint = c.codePointAt(0)
7 | if (codePoint == null) {
8 | throw new Error(`Invalid character: ${c}`)
9 | }
10 | p[codePoint] = i
11 | return p
12 | }, ([])))
13 |
14 | function encode (data: Uint8Array): string {
15 | return data.reduce((p, c) => {
16 | p += alphabetBytesToChars[c]
17 | return p
18 | }, '')
19 | }
20 |
21 | function decode (str: string): Uint8Array {
22 | const byts = []
23 | for (const char of str) {
24 | const codePoint = char.codePointAt(0)
25 | if (codePoint == null) {
26 | throw new Error(`Invalid character: ${char}`)
27 | }
28 | const byt = alphabetCharsToBytes[codePoint]
29 | if (byt == null) {
30 | throw new Error(`Non-base256emoji character: ${char}`)
31 | }
32 | byts.push(byt)
33 | }
34 | return new Uint8Array(byts)
35 | }
36 |
37 | export const base256emoji = from({
38 | prefix: '🚀',
39 | name: 'base256emoji',
40 | encode,
41 | decode
42 | })
43 |
--------------------------------------------------------------------------------
/src/bases/base32.ts:
--------------------------------------------------------------------------------
1 | import { rfc4648 } from './base.js'
2 |
3 | export const base32 = rfc4648({
4 | prefix: 'b',
5 | name: 'base32',
6 | alphabet: 'abcdefghijklmnopqrstuvwxyz234567',
7 | bitsPerChar: 5
8 | })
9 |
10 | export const base32upper = rfc4648({
11 | prefix: 'B',
12 | name: 'base32upper',
13 | alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
14 | bitsPerChar: 5
15 | })
16 |
17 | export const base32pad = rfc4648({
18 | prefix: 'c',
19 | name: 'base32pad',
20 | alphabet: 'abcdefghijklmnopqrstuvwxyz234567=',
21 | bitsPerChar: 5
22 | })
23 |
24 | export const base32padupper = rfc4648({
25 | prefix: 'C',
26 | name: 'base32padupper',
27 | alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=',
28 | bitsPerChar: 5
29 | })
30 |
31 | export const base32hex = rfc4648({
32 | prefix: 'v',
33 | name: 'base32hex',
34 | alphabet: '0123456789abcdefghijklmnopqrstuv',
35 | bitsPerChar: 5
36 | })
37 |
38 | export const base32hexupper = rfc4648({
39 | prefix: 'V',
40 | name: 'base32hexupper',
41 | alphabet: '0123456789ABCDEFGHIJKLMNOPQRSTUV',
42 | bitsPerChar: 5
43 | })
44 |
45 | export const base32hexpad = rfc4648({
46 | prefix: 't',
47 | name: 'base32hexpad',
48 | alphabet: '0123456789abcdefghijklmnopqrstuv=',
49 | bitsPerChar: 5
50 | })
51 |
52 | export const base32hexpadupper = rfc4648({
53 | prefix: 'T',
54 | name: 'base32hexpadupper',
55 | alphabet: '0123456789ABCDEFGHIJKLMNOPQRSTUV=',
56 | bitsPerChar: 5
57 | })
58 |
59 | export const base32z = rfc4648({
60 | prefix: 'h',
61 | name: 'base32z',
62 | alphabet: 'ybndrfg8ejkmcpqxot1uwisza345h769',
63 | bitsPerChar: 5
64 | })
65 |
--------------------------------------------------------------------------------
/src/bases/base36.ts:
--------------------------------------------------------------------------------
1 | import { baseX } from './base.js'
2 |
3 | export const base36 = baseX({
4 | prefix: 'k',
5 | name: 'base36',
6 | alphabet: '0123456789abcdefghijklmnopqrstuvwxyz'
7 | })
8 |
9 | export const base36upper = baseX({
10 | prefix: 'K',
11 | name: 'base36upper',
12 | alphabet: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
13 | })
14 |
--------------------------------------------------------------------------------
/src/bases/base58.ts:
--------------------------------------------------------------------------------
1 | import { baseX } from './base.js'
2 |
3 | export const base58btc = baseX({
4 | name: 'base58btc',
5 | prefix: 'z',
6 | alphabet: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
7 | })
8 |
9 | export const base58flickr = baseX({
10 | name: 'base58flickr',
11 | prefix: 'Z',
12 | alphabet: '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'
13 | })
14 |
--------------------------------------------------------------------------------
/src/bases/base64.ts:
--------------------------------------------------------------------------------
1 | import { rfc4648 } from './base.js'
2 |
3 | export const base64 = rfc4648({
4 | prefix: 'm',
5 | name: 'base64',
6 | alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
7 | bitsPerChar: 6
8 | })
9 |
10 | export const base64pad = rfc4648({
11 | prefix: 'M',
12 | name: 'base64pad',
13 | alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
14 | bitsPerChar: 6
15 | })
16 |
17 | export const base64url = rfc4648({
18 | prefix: 'u',
19 | name: 'base64url',
20 | alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
21 | bitsPerChar: 6
22 | })
23 |
24 | export const base64urlpad = rfc4648({
25 | prefix: 'U',
26 | name: 'base64urlpad',
27 | alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=',
28 | bitsPerChar: 6
29 | })
30 |
--------------------------------------------------------------------------------
/src/bases/base8.ts:
--------------------------------------------------------------------------------
1 | import { rfc4648 } from './base.js'
2 |
3 | export const base8 = rfc4648({
4 | prefix: '7',
5 | name: 'base8',
6 | alphabet: '01234567',
7 | bitsPerChar: 3
8 | })
9 |
--------------------------------------------------------------------------------
/src/bases/identity.ts:
--------------------------------------------------------------------------------
1 | import { fromString, toString } from '../bytes.js'
2 | import { from } from './base.js'
3 |
4 | export const identity = from({
5 | prefix: '\x00',
6 | name: 'identity',
7 | encode: (buf) => toString(buf),
8 | decode: (str) => fromString(str)
9 | })
10 |
--------------------------------------------------------------------------------
/src/bases/interface.ts:
--------------------------------------------------------------------------------
1 | // Base encoders / decoders just base encode / decode between binary and
2 | // textual representation. They are unaware of multibase.
3 |
4 | /**
5 | * Base encoder just encodes bytes into base encoded string.
6 | */
7 | export interface BaseEncoder {
8 | /**
9 | * Base encodes to a **plain** (and not a multibase) string. Unlike
10 | * `encode` no multibase prefix is added.
11 | */
12 | baseEncode(bytes: Uint8Array): string
13 | }
14 |
15 | /**
16 | * Base decoder decodes encoded with matching base encoding into bytes.
17 | */
18 | export interface BaseDecoder {
19 | /**
20 | * Decodes **plain** (and not a multibase) string. Unlike
21 | * decode
22 | */
23 | baseDecode(text: string): Uint8Array
24 | }
25 |
26 | /**
27 | * Base codec is just dual of encoder and decoder.
28 | */
29 | export interface BaseCodec {
30 | encoder: BaseEncoder
31 | decoder: BaseDecoder
32 | }
33 |
34 | /**
35 | * Multibase represents base encoded strings with a prefix first character
36 | * describing it's encoding.
37 | */
38 | export type Multibase =
39 | | string
40 | | string & { [0]: Prefix }
41 |
42 | /**
43 | * Multibase encoder for the specific base encoding encodes bytes into
44 | * multibase of that encoding.
45 | */
46 | export interface MultibaseEncoder {
47 | /**
48 | * Name of the encoding.
49 | */
50 | name: string
51 | /**
52 | * Prefix character for that base encoding.
53 | */
54 | prefix: Prefix
55 | /**
56 | * Encodes binary data into **multibase** string (which will have a
57 | * prefix added).
58 | */
59 | encode(bytes: Uint8Array): Multibase
60 | }
61 |
62 | /**
63 | * Interface implemented by multibase decoder, that takes multibase strings
64 | * to bytes. It may support single encoding like base32 or multiple encodings
65 | * like base32, base58btc, base64. If passed multibase is incompatible it will
66 | * throw an exception.
67 | */
68 | export interface MultibaseDecoder {
69 | /**
70 | * Decodes **multibase** string (which must have a multibase prefix added).
71 | * If prefix does not match
72 | */
73 | decode(multibase: Multibase): Uint8Array
74 | }
75 |
76 | /**
77 | * Dual of multibase encoder and decoder.
78 | */
79 | export interface MultibaseCodec {
80 | name: string
81 | prefix: Prefix
82 | encoder: MultibaseEncoder
83 | decoder: MultibaseDecoder
84 | }
85 |
86 | export interface UnibaseDecoder extends MultibaseDecoder {
87 | // Reserve this property so it can be used to derive type.
88 | readonly decoders?: null
89 |
90 | readonly prefix: Prefix
91 | }
92 |
93 | export interface CombobaseDecoder extends MultibaseDecoder {
94 | readonly decoders: Record>
95 | }
96 |
--------------------------------------------------------------------------------
/src/basics.ts:
--------------------------------------------------------------------------------
1 | import * as base10 from './bases/base10.js'
2 | import * as base16 from './bases/base16.js'
3 | import * as base2 from './bases/base2.js'
4 | import * as base256emoji from './bases/base256emoji.js'
5 | import * as base32 from './bases/base32.js'
6 | import * as base36 from './bases/base36.js'
7 | import * as base58 from './bases/base58.js'
8 | import * as base64 from './bases/base64.js'
9 | import * as base8 from './bases/base8.js'
10 | import * as identityBase from './bases/identity.js'
11 | import * as json from './codecs/json.js'
12 | import * as raw from './codecs/raw.js'
13 | import * as identity from './hashes/identity.js'
14 | import * as sha2 from './hashes/sha2.js'
15 | import { CID, hasher, digest, varint, bytes } from './index.js'
16 |
17 | export const bases = { ...identityBase, ...base2, ...base8, ...base10, ...base16, ...base32, ...base36, ...base58, ...base64, ...base256emoji }
18 | export const hashes = { ...sha2, ...identity }
19 | export const codecs = { raw, json }
20 |
21 | export { CID, hasher, digest, varint, bytes }
22 |
--------------------------------------------------------------------------------
/src/block.ts:
--------------------------------------------------------------------------------
1 | import { bytes as binary, CID } from './index.js'
2 | import type * as API from './interface.js'
3 |
4 | function readonly ({ enumerable = true, configurable = false } = {}): { enumerable: boolean, configurable: boolean, writable: false } {
5 | return { enumerable, configurable, writable: false }
6 | }
7 |
8 | function * linksWithin (path: [string | number, string], value: any): Iterable<[string, CID]> {
9 | if (value != null && typeof value === 'object') {
10 | if (Array.isArray(value)) {
11 | for (const [index, element] of value.entries()) {
12 | const elementPath = [...path, index]
13 | const cid = CID.asCID(element)
14 | if (cid != null) {
15 | yield [elementPath.join('/'), cid]
16 | } else if (typeof element === 'object') {
17 | yield * links(element, elementPath)
18 | }
19 | }
20 | } else {
21 | const cid = CID.asCID(value)
22 | if (cid != null) {
23 | yield [path.join('/'), cid]
24 | } else {
25 | yield * links(value, path)
26 | }
27 | }
28 | }
29 | }
30 |
31 | function * links (source: T, base: Array): Iterable<[string, CID]> {
32 | if (source == null || source instanceof Uint8Array) {
33 | return
34 | }
35 | const cid = CID.asCID(source)
36 | if (cid != null) {
37 | yield [base.join('/'), cid]
38 | }
39 | for (const [key, value] of Object.entries(source)) {
40 | const path = [...base, key] as [string | number, string]
41 | yield * linksWithin(path, value)
42 | }
43 | }
44 |
45 | function * treeWithin (path: [string | number, string], value: any): Iterable {
46 | if (Array.isArray(value)) {
47 | for (const [index, element] of value.entries()) {
48 | const elementPath = [...path, index]
49 | yield elementPath.join('/')
50 | if (typeof element === 'object' && (CID.asCID(element) == null)) {
51 | yield * tree(element, elementPath)
52 | }
53 | }
54 | } else {
55 | yield * tree(value, path)
56 | }
57 | }
58 |
59 | function * tree (source: T, base: Array): Iterable {
60 | if (source == null || typeof source !== 'object') {
61 | return
62 | }
63 | for (const [key, value] of Object.entries(source)) {
64 | const path = [...base, key] as [string | number, string]
65 | yield path.join('/')
66 | if (value != null && !(value instanceof Uint8Array) && typeof value === 'object' && (CID.asCID(value) == null)) {
67 | yield * treeWithin(path, value)
68 | }
69 | }
70 | }
71 |
72 | function get (source: T, path: string[]): API.BlockCursorView {
73 | let node = source as Record
74 | for (const [index, key] of path.entries()) {
75 | node = node[key]
76 | if (node == null) {
77 | throw new Error(`Object has no property at ${path.slice(0, index + 1).map(part => `[${JSON.stringify(part)}]`).join('')}`)
78 | }
79 | const cid = CID.asCID(node)
80 | if (cid != null) {
81 | return { value: cid, remaining: path.slice(index + 1).join('/') }
82 | }
83 | }
84 | return { value: node }
85 | }
86 |
87 | /**
88 | * @template T - Logical type of the data encoded in the block
89 | * @template C - multicodec code corresponding to codec used to encode the block
90 | * @template A - multicodec code corresponding to the hashing algorithm used in CID creation.
91 | * @template V - CID version
92 | */
93 | export class Block implements API.BlockView {
94 | readonly cid: CID
95 | readonly bytes: API.ByteView
96 | readonly value: T
97 | readonly asBlock: this
98 |
99 | constructor ({ cid, bytes, value }: { cid: CID, bytes: API.ByteView, value: T }) {
100 | if (cid == null || bytes == null || typeof value === 'undefined') { throw new Error('Missing required argument') }
101 |
102 | this.cid = cid
103 | this.bytes = bytes
104 | this.value = value
105 | this.asBlock = this
106 |
107 | // Mark all the properties immutable
108 | Object.defineProperties(this, {
109 | cid: readonly(),
110 | bytes: readonly(),
111 | value: readonly(),
112 | asBlock: readonly()
113 | })
114 | }
115 |
116 | links (): Iterable<[string, CID]> {
117 | return links(this.value, [])
118 | }
119 |
120 | tree (): Iterable {
121 | return tree(this.value, [])
122 | }
123 |
124 | get (path = '/'): API.BlockCursorView {
125 | return get(this.value, path.split('/').filter(Boolean))
126 | }
127 | }
128 |
129 | interface EncodeInput {
130 | value: T
131 | codec: API.BlockEncoder
132 | hasher: API.MultihashHasher
133 | }
134 |
135 | /**
136 | * @template T - Logical type of the data encoded in the block
137 | * @template Code - multicodec code corresponding to codec used to encode the block
138 | * @template Alg - multicodec code corresponding to the hashing algorithm used in CID creation.
139 | */
140 | export async function encode ({ value, codec, hasher }: EncodeInput): Promise> {
141 | if (typeof value === 'undefined') { throw new Error('Missing required argument "value"') }
142 | if (codec == null || hasher == null) { throw new Error('Missing required argument: codec or hasher') }
143 |
144 | const bytes = codec.encode(value)
145 | const hash = await hasher.digest(bytes)
146 |
147 | const cid = CID.create(
148 | 1,
149 | codec.code,
150 | hash
151 | ) as CID
152 |
153 | return new Block({ value, bytes, cid })
154 | }
155 |
156 | interface DecodeInput {
157 | bytes: API.ByteView
158 | codec: API.BlockDecoder
159 | hasher: API.MultihashHasher
160 | }
161 |
162 | /**
163 | * @template T - Logical type of the data encoded in the block
164 | * @template Code - multicodec code corresponding to codec used to encode the block
165 | * @template Alg - multicodec code corresponding to the hashing algorithm used in CID creation.
166 | */
167 | export async function decode ({ bytes, codec, hasher }: DecodeInput): Promise> {
168 | if (bytes == null) { throw new Error('Missing required argument "bytes"') }
169 | if (codec == null || hasher == null) { throw new Error('Missing required argument: codec or hasher') }
170 |
171 | const value = codec.decode(bytes)
172 | const hash = await hasher.digest(bytes)
173 |
174 | const cid = CID.create(1, codec.code, hash) as CID
175 |
176 | return new Block({ value, bytes, cid })
177 | }
178 |
179 | type CreateUnsafeInput = {
180 | cid: API.Link
181 | value: T
182 | codec?: API.BlockDecoder
183 | bytes: API.ByteView
184 | } | {
185 | cid: API.Link
186 | value?: undefined
187 | codec: API.BlockDecoder
188 | bytes: API.ByteView
189 | }
190 |
191 | /**
192 | * @template T - Logical type of the data encoded in the block
193 | * @template Code - multicodec code corresponding to codec used to encode the block
194 | * @template Alg - multicodec code corresponding to the hashing algorithm used in CID creation.
195 | * @template V - CID version
196 | */
197 | export function createUnsafe ({ bytes, cid, value: maybeValue, codec }: CreateUnsafeInput): API.BlockView {
198 | const value = maybeValue !== undefined
199 | ? maybeValue
200 | : (codec?.decode(bytes))
201 |
202 | if (value === undefined) { throw new Error('Missing required argument, must either provide "value" or "codec"') }
203 |
204 | return new Block({
205 | cid: cid as CID,
206 | bytes,
207 | value
208 | })
209 | }
210 |
211 | interface CreateInput {
212 | bytes: API.ByteView
213 | cid: API.Link
214 | hasher: API.MultihashHasher
215 | codec: API.BlockDecoder
216 | }
217 |
218 | /**
219 | * @template T - Logical type of the data encoded in the block
220 | * @template Code - multicodec code corresponding to codec used to encode the block
221 | * @template Alg - multicodec code corresponding to the hashing algorithm used in CID creation.
222 | * @template V - CID version
223 | */
224 | export async function create ({ bytes, cid, hasher, codec }: CreateInput): Promise> {
225 | if (bytes == null) { throw new Error('Missing required argument "bytes"') }
226 | if (hasher == null) { throw new Error('Missing required argument "hasher"') }
227 | const value = codec.decode(bytes)
228 | const hash = await hasher.digest(bytes)
229 | if (!binary.equals(cid.multihash.bytes, hash.bytes)) {
230 | throw new Error('CID hash does not match bytes')
231 | }
232 |
233 | return createUnsafe({
234 | bytes,
235 | cid,
236 | value,
237 | codec
238 | })
239 | }
240 |
--------------------------------------------------------------------------------
/src/block/interface.ts:
--------------------------------------------------------------------------------
1 | import type { CID } from '../cid.js'
2 | import type { Link, Version } from '../link/interface.js'
3 |
4 | /**
5 | * A byte-encoded representation of some type of `Data`.
6 | *
7 | * A `ByteView` is essentially a `Uint8Array` that's been "tagged" with
8 | * a `Data` type parameter indicating the type of encoded data.
9 | *
10 | * For example, a `ByteView<{ hello: "world" }>` is a `Uint8Array` containing a
11 | * binary representation of a `{hello: "world"}`.
12 | */
13 | export interface ByteView extends Uint8Array, Phantom {}
14 |
15 | /**
16 | * Similar to ByteView but extends ArrayBuffer.
17 | */
18 | export interface ArrayBufferView extends ArrayBuffer, Phantom {}
19 |
20 | declare const Marker: unique symbol
21 |
22 | /**
23 | * A utility type to retain an unused type parameter `T`.
24 | * Similar to [phantom type parameters in Rust](https://doc.rust-lang.org/rust-by-example/generics/phantom.html).
25 | *
26 | * Capturing unused type parameters allows us to define "nominal types," which
27 | * TypeScript does not natively support. Nominal types in turn allow us to capture
28 | * semantics not represented in the actual type structure, without requiring us to define
29 | * new classes or pay additional runtime costs.
30 | *
31 | * For a concrete example, see {@link ByteView}, which extends the `Uint8Array` type to capture
32 | * type information about the structure of the data encoded into the array.
33 | */
34 | export interface Phantom {
35 | // This field can not be represented because field name is non-existent
36 | // unique symbol. But given that field is optional any object will valid
37 | // type constraint.
38 | [Marker]?: T
39 | }
40 |
41 | /**
42 | * Represents an IPLD block (including its CID) that can be decoded to data of
43 | * type `T`.
44 | *
45 | * @template T - Logical type of the data encoded in the block
46 | * @template C - multicodec code corresponding to codec used to encode the block
47 | * @template A - multicodec code corresponding to the hashing algorithm used in CID creation.
48 | * @template V - CID version
49 | */
50 | export interface Block<
51 | T = unknown,
52 | C extends number = number,
53 | A extends number = number,
54 | V extends Version = 1
55 | > {
56 | bytes: ByteView
57 | cid: Link
58 | }
59 |
60 | export type BlockCursorView =
61 | | { value: T, remaining?: undefined }
62 | | { value: CID, remaining: string }
63 |
64 | export interface BlockView<
65 | T = unknown,
66 | C extends number = number,
67 | A extends number = number,
68 | V extends Version = 1
69 | > extends Block {
70 | cid: CID
71 | value: T
72 |
73 | links(): Iterable<[string, CID]>
74 | tree(): Iterable
75 | get(path: string): BlockCursorView
76 | }
77 |
--------------------------------------------------------------------------------
/src/bytes.ts:
--------------------------------------------------------------------------------
1 | export const empty = new Uint8Array(0)
2 |
3 | export function toHex (d: Uint8Array): string {
4 | return d.reduce((hex, byte) => hex + byte.toString(16).padStart(2, '0'), '')
5 | }
6 |
7 | export function fromHex (hex: string): Uint8Array {
8 | const hexes = hex.match(/../g)
9 | return hexes != null ? new Uint8Array(hexes.map(b => parseInt(b, 16))) : empty
10 | }
11 |
12 | export function equals (aa: Uint8Array, bb: Uint8Array): boolean {
13 | if (aa === bb) { return true }
14 | if (aa.byteLength !== bb.byteLength) {
15 | return false
16 | }
17 |
18 | for (let ii = 0; ii < aa.byteLength; ii++) {
19 | if (aa[ii] !== bb[ii]) {
20 | return false
21 | }
22 | }
23 |
24 | return true
25 | }
26 |
27 | export function coerce (o: ArrayBufferView | ArrayBuffer | Uint8Array): Uint8Array {
28 | if (o instanceof Uint8Array && o.constructor.name === 'Uint8Array') { return o }
29 | if (o instanceof ArrayBuffer) { return new Uint8Array(o) }
30 | if (ArrayBuffer.isView(o)) {
31 | return new Uint8Array(o.buffer, o.byteOffset, o.byteLength)
32 | }
33 | throw new Error('Unknown type, must be binary type')
34 | }
35 |
36 | export function isBinary (o: unknown): o is ArrayBuffer | ArrayBufferView {
37 | return o instanceof ArrayBuffer || ArrayBuffer.isView(o)
38 | }
39 |
40 | export function fromString (str: string): Uint8Array {
41 | return new TextEncoder().encode(str)
42 | }
43 |
44 | export function toString (b: Uint8Array): string {
45 | return new TextDecoder().decode(b)
46 | }
47 |
--------------------------------------------------------------------------------
/src/cid.ts:
--------------------------------------------------------------------------------
1 | import { base32 } from './bases/base32.js'
2 | import { base36 } from './bases/base36.js'
3 | import { base58btc } from './bases/base58.js'
4 | import { coerce } from './bytes.js'
5 | import * as Digest from './hashes/digest.js'
6 | import * as varint from './varint.js'
7 | import type * as API from './link/interface.js'
8 |
9 | // This way TS will also expose all the types from module
10 | export * from './link/interface.js'
11 |
12 | export function format , Prefix extends string> (link: T, base?: API.MultibaseEncoder): API.ToString {
13 | const { bytes, version } = link
14 | switch (version) {
15 | case 0:
16 | return toStringV0(
17 | bytes,
18 | baseCache(link),
19 | base as API.MultibaseEncoder<'z'> ?? base58btc.encoder
20 | )
21 | default:
22 | return toStringV1(
23 | bytes,
24 | baseCache(link),
25 | (base ?? base32.encoder) as API.MultibaseEncoder
26 | )
27 | }
28 | }
29 |
30 | export function toJSON (link: Link): API.LinkJSON {
31 | return {
32 | '/': format(link)
33 | }
34 | }
35 |
36 | export function fromJSON (json: API.LinkJSON): CID {
37 | return CID.parse(json['/'])
38 | }
39 |
40 | const cache = new WeakMap>()
41 |
42 | function baseCache (cid: API.UnknownLink): Map {
43 | const baseCache = cache.get(cid)
44 | if (baseCache == null) {
45 | const baseCache = new Map()
46 | cache.set(cid, baseCache)
47 | return baseCache
48 | }
49 | return baseCache
50 | }
51 |
52 | export class CID implements API.Link {
53 | readonly code: Format
54 | readonly version: Version
55 | readonly multihash: API.MultihashDigest
56 | readonly bytes: Uint8Array
57 | readonly '/': Uint8Array
58 |
59 | /**
60 | * @param version - Version of the CID
61 | * @param code - Code of the codec content is encoded in, see https://github.com/multiformats/multicodec/blob/master/table.csv
62 | * @param multihash - (Multi)hash of the of the content.
63 | */
64 | constructor (version: Version, code: Format, multihash: API.MultihashDigest, bytes: Uint8Array) {
65 | this.code = code
66 | this.version = version
67 | this.multihash = multihash
68 | this.bytes = bytes
69 |
70 | // flag to serializers that this is a CID and
71 | // should be treated specially
72 | this['/'] = bytes
73 | }
74 |
75 | /**
76 | * Signalling `cid.asCID === cid` has been replaced with `cid['/'] === cid.bytes`
77 | * please either use `CID.asCID(cid)` or switch to new signalling mechanism
78 | *
79 | * @deprecated
80 | */
81 | get asCID (): this {
82 | return this
83 | }
84 |
85 | // ArrayBufferView
86 | get byteOffset (): number {
87 | return this.bytes.byteOffset
88 | }
89 |
90 | // ArrayBufferView
91 | get byteLength (): number {
92 | return this.bytes.byteLength
93 | }
94 |
95 | toV0 (): CID {
96 | switch (this.version) {
97 | case 0: {
98 | return this as CID
99 | }
100 | case 1: {
101 | const { code, multihash } = this
102 |
103 | if (code !== DAG_PB_CODE) {
104 | throw new Error('Cannot convert a non dag-pb CID to CIDv0')
105 | }
106 |
107 | // sha2-256
108 | if (multihash.code !== SHA_256_CODE) {
109 | throw new Error('Cannot convert non sha2-256 multihash CID to CIDv0')
110 | }
111 |
112 | return (
113 | CID.createV0(
114 | multihash as API.MultihashDigest
115 | )
116 | )
117 | }
118 | default: {
119 | throw Error(
120 | `Can not convert CID version ${this.version} to version 0. This is a bug please report`
121 | )
122 | }
123 | }
124 | }
125 |
126 | toV1 (): CID {
127 | switch (this.version) {
128 | case 0: {
129 | const { code, digest } = this.multihash
130 | const multihash = Digest.create(code, digest)
131 | return (
132 | CID.createV1(this.code, multihash)
133 | )
134 | }
135 | case 1: {
136 | return this as CID
137 | }
138 | default: {
139 | throw Error(
140 | `Can not convert CID version ${this.version} to version 1. This is a bug please report`
141 | )
142 | }
143 | }
144 | }
145 |
146 | equals (other: unknown): other is CID {
147 | return CID.equals(this, other)
148 | }
149 |
150 | static equals (self: API.Link, other: unknown): other is CID {
151 | const unknown = other as { code?: unknown, version?: unknown, multihash?: unknown }
152 | return (
153 | unknown != null &&
154 | self.code === unknown.code &&
155 | self.version === unknown.version &&
156 | Digest.equals(self.multihash, unknown.multihash)
157 | )
158 | }
159 |
160 | toString (base?: API.MultibaseEncoder): string {
161 | return format(this, base)
162 | }
163 |
164 | toJSON (): API.LinkJSON {
165 | return { '/': format(this) }
166 | }
167 |
168 | link (): this {
169 | return this
170 | }
171 |
172 | readonly [Symbol.toStringTag] = 'CID';
173 |
174 | // Legacy
175 |
176 | [Symbol.for('nodejs.util.inspect.custom')] (): string {
177 | return `CID(${this.toString()})`
178 | }
179 |
180 | /**
181 | * Takes any input `value` and returns a `CID` instance if it was
182 | * a `CID` otherwise returns `null`. If `value` is instanceof `CID`
183 | * it will return value back. If `value` is not instance of this CID
184 | * class, but is compatible CID it will return new instance of this
185 | * `CID` class. Otherwise returns null.
186 | *
187 | * This allows two different incompatible versions of CID library to
188 | * co-exist and interop as long as binary interface is compatible.
189 | */
190 | static asCID (input: API.Link | U): CID | null {
191 | if (input == null) {
192 | return null
193 | }
194 |
195 | const value = input as any
196 | if (value instanceof CID) {
197 | // If value is instance of CID then we're all set.
198 | return value
199 | } else if ((value['/'] != null && value['/'] === value.bytes) || value.asCID === value) {
200 | // If value isn't instance of this CID class but `this.asCID === this` or
201 | // `value['/'] === value.bytes` is true it is CID instance coming from a
202 | // different implementation (diff version or duplicate). In that case we
203 | // rebase it to this `CID` implementation so caller is guaranteed to get
204 | // instance with expected API.
205 | const { version, code, multihash, bytes } = value
206 | return new CID(
207 | version,
208 | code,
209 | multihash as API.MultihashDigest,
210 | bytes ?? encodeCID(version, code, multihash.bytes)
211 | )
212 | } else if (value[cidSymbol] === true) {
213 | // If value is a CID from older implementation that used to be tagged via
214 | // symbol we still rebase it to the this `CID` implementation by
215 | // delegating that to a constructor.
216 | const { version, multihash, code } = value
217 | const digest = Digest.decode(multihash) as API.MultihashDigest
218 | return CID.create(version, code, digest)
219 | } else {
220 | // Otherwise value is not a CID (or an incompatible version of it) in
221 | // which case we return `null`.
222 | return null
223 | }
224 | }
225 |
226 | /**
227 | * @param version - Version of the CID
228 | * @param code - Code of the codec content is encoded in, see https://github.com/multiformats/multicodec/blob/master/table.csv
229 | * @param digest - (Multi)hash of the of the content.
230 | */
231 | static create (version: Version, code: Format, digest: API.MultihashDigest): CID {
232 | if (typeof code !== 'number') {
233 | throw new Error('String codecs are no longer supported')
234 | }
235 |
236 | if (!(digest.bytes instanceof Uint8Array)) {
237 | throw new Error('Invalid digest')
238 | }
239 |
240 | switch (version) {
241 | case 0: {
242 | if (code !== DAG_PB_CODE) {
243 | throw new Error(
244 | `Version 0 CID must use dag-pb (code: ${DAG_PB_CODE}) block encoding`
245 | )
246 | } else {
247 | return new CID(version, code, digest, digest.bytes)
248 | }
249 | }
250 | case 1: {
251 | const bytes = encodeCID(version, code, digest.bytes)
252 | return new CID(version, code, digest, bytes)
253 | }
254 | default: {
255 | throw new Error('Invalid version')
256 | }
257 | }
258 | }
259 |
260 | /**
261 | * Simplified version of `create` for CIDv0.
262 | */
263 | static createV0 (digest: API.MultihashDigest): CID {
264 | return CID.create(0, DAG_PB_CODE, digest)
265 | }
266 |
267 | /**
268 | * Simplified version of `create` for CIDv1.
269 | *
270 | * @param code - Content encoding format code.
271 | * @param digest - Multihash of the content.
272 | */
273 | static createV1 (code: Code, digest: API.MultihashDigest): CID {
274 | return CID.create(1, code, digest)
275 | }
276 |
277 | /**
278 | * Decoded a CID from its binary representation. The byte array must contain
279 | * only the CID with no additional bytes.
280 | *
281 | * An error will be thrown if the bytes provided do not contain a valid
282 | * binary representation of a CID.
283 | */
284 | static decode (bytes: API.ByteView>): CID {
285 | const [cid, remainder] = CID.decodeFirst(bytes)
286 | if (remainder.length !== 0) {
287 | throw new Error('Incorrect length')
288 | }
289 | return cid
290 | }
291 |
292 | /**
293 | * Decoded a CID from its binary representation at the beginning of a byte
294 | * array.
295 | *
296 | * Returns an array with the first element containing the CID and the second
297 | * element containing the remainder of the original byte array. The remainder
298 | * will be a zero-length byte array if the provided bytes only contained a
299 | * binary CID representation.
300 | */
301 | static decodeFirst (bytes: API.ByteView>): [CID, Uint8Array] {
302 | const specs = CID.inspectBytes(bytes)
303 | const prefixSize = specs.size - specs.multihashSize
304 | const multihashBytes = coerce(
305 | bytes.subarray(prefixSize, prefixSize + specs.multihashSize)
306 | )
307 | if (multihashBytes.byteLength !== specs.multihashSize) {
308 | throw new Error('Incorrect length')
309 | }
310 | const digestBytes = multihashBytes.subarray(
311 | specs.multihashSize - specs.digestSize
312 | )
313 | const digest = new Digest.Digest(
314 | specs.multihashCode,
315 | specs.digestSize,
316 | digestBytes,
317 | multihashBytes
318 | )
319 | const cid =
320 | specs.version === 0
321 | ? CID.createV0(digest as API.MultihashDigest)
322 | : CID.createV1(specs.codec, digest)
323 | return [cid as CID, bytes.subarray(specs.size)]
324 | }
325 |
326 | /**
327 | * Inspect the initial bytes of a CID to determine its properties.
328 | *
329 | * Involves decoding up to 4 varints. Typically this will require only 4 to 6
330 | * bytes but for larger multicodec code values and larger multihash digest
331 | * lengths these varints can be quite large. It is recommended that at least
332 | * 10 bytes be made available in the `initialBytes` argument for a complete
333 | * inspection.
334 | */
335 | static inspectBytes (initialBytes: API.ByteView>): { version: V, codec: C, multihashCode: A, digestSize: number, multihashSize: number, size: number } {
336 | let offset = 0
337 | const next = (): number => {
338 | const [i, length] = varint.decode(initialBytes.subarray(offset))
339 | offset += length
340 | return i
341 | }
342 |
343 | let version = next() as V
344 | let codec = DAG_PB_CODE as C
345 | if (version as number === 18) {
346 | // CIDv0
347 | version = 0 as V
348 | offset = 0
349 | } else {
350 | codec = next() as C
351 | }
352 |
353 | if (version !== 0 && version !== 1) {
354 | throw new RangeError(`Invalid CID version ${version}`)
355 | }
356 |
357 | const prefixSize = offset
358 | const multihashCode = next() as A // multihash code
359 | const digestSize = next() // multihash length
360 | const size = offset + digestSize
361 | const multihashSize = size - prefixSize
362 |
363 | return { version, codec, multihashCode, digestSize, multihashSize, size }
364 | }
365 |
366 | /**
367 | * Takes cid in a string representation and creates an instance. If `base`
368 | * decoder is not provided will use a default from the configuration. It will
369 | * throw an error if encoding of the CID is not compatible with supplied (or
370 | * a default decoder).
371 | */
372 | static parse (source: API.ToString, Prefix>, base?: API.MultibaseDecoder): CID {
373 | const [prefix, bytes] = parseCIDtoBytes(source, base)
374 |
375 | const cid = CID.decode(bytes)
376 |
377 | if (cid.version === 0 && source[0] !== 'Q') {
378 | throw Error('Version 0 CID string must not include multibase prefix')
379 | }
380 |
381 | // Cache string representation to avoid computing it on `this.toString()`
382 | baseCache(cid).set(prefix, source)
383 |
384 | return cid
385 | }
386 | }
387 |
388 | function parseCIDtoBytes (source: API.ToString, Prefix>, base?: API.MultibaseDecoder): [Prefix, API.ByteView>] {
389 | switch (source[0]) {
390 | // CIDv0 is parsed differently
391 | case 'Q': {
392 | const decoder = base ?? base58btc
393 | return [
394 | base58btc.prefix as Prefix,
395 | decoder.decode(`${base58btc.prefix}${source}`)
396 | ]
397 | }
398 | case base58btc.prefix: {
399 | const decoder = base ?? base58btc
400 | return [base58btc.prefix as Prefix, decoder.decode(source)]
401 | }
402 | case base32.prefix: {
403 | const decoder = base ?? base32
404 | return [base32.prefix as Prefix, decoder.decode(source)]
405 | }
406 | case base36.prefix: {
407 | const decoder = base ?? base36
408 | return [base36.prefix as Prefix, decoder.decode(source)]
409 | }
410 | default: {
411 | if (base == null) {
412 | throw Error(
413 | 'To parse non base32, base36 or base58btc encoded CID multibase decoder must be provided'
414 | )
415 | }
416 | return [source[0] as Prefix, base.decode(source)]
417 | }
418 | }
419 | }
420 |
421 | function toStringV0 (bytes: Uint8Array, cache: Map, base: API.MultibaseEncoder<'z'>): string {
422 | const { prefix } = base
423 | if (prefix !== base58btc.prefix) {
424 | throw Error(`Cannot string encode V0 in ${base.name} encoding`)
425 | }
426 |
427 | const cid = cache.get(prefix)
428 | if (cid == null) {
429 | const cid = base.encode(bytes).slice(1)
430 | cache.set(prefix, cid)
431 | return cid
432 | } else {
433 | return cid
434 | }
435 | }
436 |
437 | function toStringV1 (bytes: Uint8Array, cache: Map, base: API.MultibaseEncoder): string {
438 | const { prefix } = base
439 | const cid = cache.get(prefix)
440 | if (cid == null) {
441 | const cid = base.encode(bytes)
442 | cache.set(prefix, cid)
443 | return cid
444 | } else {
445 | return cid
446 | }
447 | }
448 |
449 | const DAG_PB_CODE = 0x70
450 | const SHA_256_CODE = 0x12
451 |
452 | function encodeCID (version: API.Version, code: number, multihash: Uint8Array): Uint8Array {
453 | const codeOffset = varint.encodingLength(version)
454 | const hashOffset = codeOffset + varint.encodingLength(code)
455 | const bytes = new Uint8Array(hashOffset + multihash.byteLength)
456 | varint.encodeTo(version, bytes, 0)
457 | varint.encodeTo(code, bytes, codeOffset)
458 | bytes.set(multihash, hashOffset)
459 | return bytes
460 | }
461 |
462 | const cidSymbol = Symbol.for('@ipld/js-cid/CID')
463 |
--------------------------------------------------------------------------------
/src/codecs/interface.ts:
--------------------------------------------------------------------------------
1 | import type { ArrayBufferView, ByteView } from '../block/interface.js'
2 |
3 | /**
4 | * IPLD encoder part of the codec.
5 | */
6 | export interface BlockEncoder {
7 | name: string
8 | code: Code
9 | encode(data: T): ByteView
10 | }
11 |
12 | /**
13 | * IPLD decoder part of the codec.
14 | */
15 | export interface BlockDecoder {
16 | code: Code
17 | decode(bytes: ByteView | ArrayBufferView): T
18 | }
19 |
20 | /**
21 | * An IPLD codec is a combination of both encoder and decoder.
22 | */
23 | export interface BlockCodec extends BlockEncoder, BlockDecoder {}
24 |
25 | export type { ArrayBufferView, ByteView }
26 |
--------------------------------------------------------------------------------
/src/codecs/json.ts:
--------------------------------------------------------------------------------
1 | import type { ArrayBufferView, ByteView } from './interface.js'
2 |
3 | const textEncoder = new TextEncoder()
4 | const textDecoder = new TextDecoder()
5 |
6 | export const name = 'json'
7 | export const code = 0x0200
8 |
9 | export function encode (node: T): ByteView {
10 | return textEncoder.encode(JSON.stringify(node))
11 | }
12 |
13 | export function decode (data: ByteView | ArrayBufferView): T {
14 | return JSON.parse(textDecoder.decode(data))
15 | }
16 |
--------------------------------------------------------------------------------
/src/codecs/raw.ts:
--------------------------------------------------------------------------------
1 | import { coerce } from '../bytes.js'
2 | import type { ArrayBufferView, ByteView } from './interface.js'
3 |
4 | export const name = 'raw'
5 | export const code = 0x55
6 |
7 | export function encode (node: Uint8Array): ByteView {
8 | return coerce(node)
9 | }
10 |
11 | export function decode (data: ByteView | ArrayBufferView): Uint8Array {
12 | return coerce(data)
13 | }
14 |
--------------------------------------------------------------------------------
/src/hashes/digest.ts:
--------------------------------------------------------------------------------
1 | import { coerce, equals as equalBytes } from '../bytes.js'
2 | import * as varint from '../varint.js'
3 | import type { MultihashDigest } from './interface.js'
4 |
5 | /**
6 | * Creates a multihash digest.
7 | */
8 | export function create (code: Code, digest: Uint8Array): Digest {
9 | const size = digest.byteLength
10 | const sizeOffset = varint.encodingLength(code)
11 | const digestOffset = sizeOffset + varint.encodingLength(size)
12 |
13 | const bytes = new Uint8Array(digestOffset + size)
14 | varint.encodeTo(code, bytes, 0)
15 | varint.encodeTo(size, bytes, sizeOffset)
16 | bytes.set(digest, digestOffset)
17 |
18 | return new Digest(code, size, digest, bytes)
19 | }
20 |
21 | /**
22 | * Turns bytes representation of multihash digest into an instance.
23 | */
24 | export function decode (multihash: Uint8Array): MultihashDigest {
25 | const bytes = coerce(multihash)
26 | const [code, sizeOffset] = varint.decode(bytes)
27 | const [size, digestOffset] = varint.decode(bytes.subarray(sizeOffset))
28 | const digest = bytes.subarray(sizeOffset + digestOffset)
29 |
30 | if (digest.byteLength !== size) {
31 | throw new Error('Incorrect length')
32 | }
33 |
34 | return new Digest(code, size, digest, bytes)
35 | }
36 |
37 | export function equals (a: MultihashDigest, b: unknown): b is MultihashDigest {
38 | if (a === b) {
39 | return true
40 | } else {
41 | const data = b as { code?: unknown, size?: unknown, bytes?: unknown }
42 |
43 | return (
44 | a.code === data.code &&
45 | a.size === data.size &&
46 | data.bytes instanceof Uint8Array &&
47 | equalBytes(a.bytes, data.bytes)
48 | )
49 | }
50 | }
51 |
52 | /**
53 | * Represents a multihash digest which carries information about the
54 | * hashing algorithm and an actual hash digest.
55 | */
56 | export class Digest implements MultihashDigest {
57 | readonly code: Code
58 | readonly size: Size
59 | readonly digest: Uint8Array
60 | readonly bytes: Uint8Array
61 |
62 | /**
63 | * Creates a multihash digest.
64 | */
65 | constructor (code: Code, size: Size, digest: Uint8Array, bytes: Uint8Array) {
66 | this.code = code
67 | this.size = size
68 | this.digest = digest
69 | this.bytes = bytes
70 | }
71 | }
72 |
73 | /**
74 | * Used to check that the passed multihash has the passed code
75 | */
76 | export function hasCode (digest: MultihashDigest, code: T): digest is MultihashDigest {
77 | return digest.code === code
78 | }
79 |
--------------------------------------------------------------------------------
/src/hashes/hasher.ts:
--------------------------------------------------------------------------------
1 | import * as Digest from './digest.js'
2 | import type { MultihashHasher } from './interface.js'
3 |
4 | type Await = Promise | T
5 |
6 | export function from ({ name, code, encode }: { name: Name, code: Code, encode(input: Uint8Array): Await }): Hasher {
7 | return new Hasher(name, code, encode)
8 | }
9 |
10 | /**
11 | * Hasher represents a hashing algorithm implementation that produces as
12 | * `MultihashDigest`.
13 | */
14 | export class Hasher implements MultihashHasher {
15 | readonly name: Name
16 | readonly code: Code
17 | readonly encode: (input: Uint8Array) => Await
18 |
19 | constructor (name: Name, code: Code, encode: (input: Uint8Array) => Await) {
20 | this.name = name
21 | this.code = code
22 | this.encode = encode
23 | }
24 |
25 | digest (input: Uint8Array): Await> {
26 | if (input instanceof Uint8Array) {
27 | const result = this.encode(input)
28 | return result instanceof Uint8Array
29 | ? Digest.create(this.code, result)
30 | /* c8 ignore next 1 */
31 | : result.then(digest => Digest.create(this.code, digest))
32 | } else {
33 | throw Error('Unknown type, must be binary type')
34 | /* c8 ignore next 1 */
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/hashes/identity.ts:
--------------------------------------------------------------------------------
1 | import { coerce } from '../bytes.js'
2 | import * as Digest from './digest.js'
3 |
4 | const code: 0x0 = 0x0
5 | const name = 'identity'
6 |
7 | const encode: (input: Uint8Array) => Uint8Array = coerce
8 |
9 | function digest (input: Uint8Array): Digest.Digest {
10 | return Digest.create(code, encode(input))
11 | }
12 |
13 | export const identity = { code, name, encode, digest }
14 |
--------------------------------------------------------------------------------
/src/hashes/interface.ts:
--------------------------------------------------------------------------------
1 | // # Multihash
2 |
3 | /**
4 | * Represents a multihash digest which carries information about the
5 | * hashing algorithm and an actual hash digest.
6 | */
7 | // Note: In the current version there is no first class multihash
8 | // representation (plain Uint8Array is used instead) instead there seems to be
9 | // a bunch of places that parse it to extract (code, digest, size). By creating
10 | // this first class representation we avoid reparsing and things generally fit
11 | // really nicely.
12 | export interface MultihashDigest {
13 | /**
14 | * Code of the multihash
15 | */
16 | code: Code
17 |
18 | /**
19 | * Raw digest (without a hashing algorithm info)
20 | */
21 | digest: Uint8Array
22 |
23 | /**
24 | * byte length of the `this.digest`
25 | */
26 | size: number
27 |
28 | /**
29 | * Binary representation of this multihash digest.
30 | */
31 | bytes: Uint8Array
32 | }
33 |
34 | /**
35 | * Hasher represents a hashing algorithm implementation that produces as
36 | * `MultihashDigest`.
37 | */
38 | export interface MultihashHasher {
39 | /**
40 | * Takes binary `input` and returns it (multi) hash digest. Return value is
41 | * either promise of a digest or a digest. This way general use can `await`
42 | * while performance critical code may asses return value to decide whether
43 | * await is needed.
44 | */
45 | digest(input: Uint8Array): Promise> | MultihashDigest
46 |
47 | /**
48 | * Name of the multihash
49 | */
50 | name: string
51 |
52 | /**
53 | * Code of the multihash
54 | */
55 | code: Code
56 | }
57 |
58 | /**
59 | * Sync variant of `MultihashHasher` that refines return type of the `digest`
60 | * to `MultihashDigest`. It is subtype of `MultihashHasher` so implementations
61 | * of this interface can be passed anywhere `MultihashHasher` is expected,
62 | * allowing consumer to either `await` or check the return type to decide
63 | * whether to await or proceed with return value.
64 | *
65 | * `SyncMultihashHasher` is useful in certain APIs where async hashing would be
66 | * impractical e.g. implementation of Hash Array Mapped Trie (HAMT).
67 | */
68 | export interface SyncMultihashHasher extends MultihashHasher {
69 | digest(input: Uint8Array): MultihashDigest
70 | }
71 |
--------------------------------------------------------------------------------
/src/hashes/sha1-browser.ts:
--------------------------------------------------------------------------------
1 | /* global crypto */
2 |
3 | import { from } from './hasher.js'
4 |
5 | const sha = (name: AlgorithmIdentifier) =>
6 | async (data: Uint8Array) => new Uint8Array(await crypto.subtle.digest(name, data))
7 |
8 | export const sha1 = from({
9 | name: 'sha-1',
10 | code: 0x11,
11 | encode: sha('SHA-1')
12 | })
13 |
--------------------------------------------------------------------------------
/src/hashes/sha1.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto'
2 | import { coerce } from '../bytes.js'
3 | import { from } from './hasher.js'
4 |
5 | export const sha1 = from({
6 | name: 'sha-1',
7 | code: 0x11,
8 | encode: (input) => coerce(crypto.createHash('sha1').update(input).digest())
9 | })
10 |
--------------------------------------------------------------------------------
/src/hashes/sha2-browser.ts:
--------------------------------------------------------------------------------
1 | /* global crypto */
2 |
3 | import { from } from './hasher.js'
4 |
5 | function sha (name: AlgorithmIdentifier): (data: Uint8Array) => Promise {
6 | return async data => new Uint8Array(await crypto.subtle.digest(name, data))
7 | }
8 |
9 | export const sha256 = from({
10 | name: 'sha2-256',
11 | code: 0x12,
12 | encode: sha('SHA-256')
13 | })
14 |
15 | export const sha512 = from({
16 | name: 'sha2-512',
17 | code: 0x13,
18 | encode: sha('SHA-512')
19 | })
20 |
--------------------------------------------------------------------------------
/src/hashes/sha2.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto'
2 | import { coerce } from '../bytes.js'
3 | import { from } from './hasher.js'
4 |
5 | export const sha256 = from({
6 | name: 'sha2-256',
7 | code: 0x12,
8 | encode: (input) => coerce(crypto.createHash('sha256').update(input).digest())
9 | })
10 |
11 | export const sha512 = from({
12 | name: 'sha2-512',
13 | code: 0x13,
14 | encode: input => coerce(crypto.createHash('sha512').update(input).digest())
15 | })
16 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @packageDocumentation
3 | *
4 | * This library defines common interfaces and low level building blocks for various interrelated multiformat technologies (multicodec, multihash, multibase, and CID). They can be used to implement custom base encoders / decoders / codecs, codec encoders /decoders and multihash hashers that comply to the interface that layers above assume.
5 | *
6 | * This library provides implementations for most basics and many others can be found in linked repositories.
7 | *
8 | * ```TypeScript
9 | * import { CID } from 'multiformats/cid'
10 | * import * as json from 'multiformats/codecs/json'
11 | * import { sha256 } from 'multiformats/hashes/sha2'
12 | *
13 | * const bytes = json.encode({ hello: 'world' })
14 | *
15 | * const hash = await sha256.digest(bytes)
16 | * const cid = CID.create(1, json.code, hash)
17 | * //> CID(bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea)
18 | * ```
19 | *
20 | * ## Creating Blocks
21 | *
22 | * ```TypeScript
23 | * import * as Block from 'multiformats/block'
24 | * import * as codec from '@ipld/dag-cbor'
25 | * import { sha256 as hasher } from 'multiformats/hashes/sha2'
26 | *
27 | * const value = { hello: 'world' }
28 | *
29 | * // encode a block
30 | * let block = await Block.encode({ value, codec, hasher })
31 | *
32 | * block.value // { hello: 'world' }
33 | * block.bytes // Uint8Array
34 | * block.cid // CID() w/ sha2-256 hash address and dag-cbor codec
35 | *
36 | * // you can also decode blocks from their binary state
37 | * block = await Block.decode({ bytes: block.bytes, codec, hasher })
38 | *
39 | * // if you have the cid you can also verify the hash on decode
40 | * block = await Block.create({ bytes: block.bytes, cid: block.cid, codec, hasher })
41 | * ```
42 | *
43 | * ## Multibase Encoders / Decoders / Codecs
44 | *
45 | * CIDs can be serialized to string representation using multibase encoders that implement [`MultibaseEncoder`](https://github.com/multiformats/js-multiformats/blob/master/src/bases/interface.ts) interface. This library provides quite a few implementations that can be imported:
46 | *
47 | * ```TypeScript
48 | * import { base64 } from "multiformats/bases/base64"
49 | * cid.toString(base64.encoder)
50 | * //> 'mAYAEEiCTojlxqRTl6svwqNJRVM2jCcPBxy+7mRTUfGDzy2gViA'
51 | * ```
52 | *
53 | * Parsing CID string serialized CIDs requires multibase decoder that implements [`MultibaseDecoder`](https://github.com/multiformats/js-multiformats/blob/master/src/bases/interface.ts) interface. This library provides a decoder for every encoder it provides:
54 | *
55 | * ```TypeScript
56 | * CID.parse('mAYAEEiCTojlxqRTl6svwqNJRVM2jCcPBxy+7mRTUfGDzy2gViA', base64.decoder)
57 | * //> CID(bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea)
58 | * ```
59 | *
60 | * Dual of multibase encoder & decoder is defined as multibase codec and it exposes
61 | * them as `encoder` and `decoder` properties. For added convenience codecs also
62 | * implement `MultibaseEncoder` and `MultibaseDecoder` interfaces so they could be
63 | * used as either or both:
64 | *
65 | * ```TypeScript
66 | * cid.toString(base64)
67 | * CID.parse(cid.toString(base64), base64)
68 | * ```
69 | *
70 | * **Note:** CID implementation comes bundled with `base32` and `base58btc`
71 | * multibase codecs so that CIDs can be base serialized to (version specific)
72 | * default base encoding and parsed without having to supply base encoders/decoders:
73 | *
74 | * ```TypeScript
75 | * const v1 = CID.parse('bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea')
76 | * v1.toString()
77 | * //> 'bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea'
78 | *
79 | * const v0 = CID.parse('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n')
80 | * v0.toString()
81 | * //> 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
82 | * v0.toV1().toString()
83 | * //> 'bafybeihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku'
84 | * ```
85 | *
86 | * ## Multicodec Encoders / Decoders / Codecs
87 | *
88 | * This library defines [`BlockEncoder`, `BlockDecoder` and `BlockCodec` interfaces](https://github.com/multiformats/js-multiformats/blob/master/src/codecs/interface.ts).
89 | * Codec implementations should conform to the `BlockCodec` interface which implements both `BlockEncoder` and `BlockDecoder`.
90 | * Here is an example implementation of JSON `BlockCodec`.
91 | *
92 | * ```TypeScript
93 | * export const { name, code, encode, decode } = {
94 | * name: 'json',
95 | * code: 0x0200,
96 | * encode: json => new TextEncoder().encode(JSON.stringify(json)),
97 | * decode: bytes => JSON.parse(new TextDecoder().decode(bytes))
98 | * }
99 | * ```
100 | *
101 | * ## Multihash Hashers
102 | *
103 | * This library defines [`MultihashHasher` and `MultihashDigest` interfaces](https://github.com/multiformats/js-multiformats/blob/master/src/hashes/interface.ts) and convinient function for implementing them:
104 | *
105 | * ```TypeScript
106 | * import * as hasher from 'multiformats/hashes/hasher'
107 | *
108 | * const sha256 = hasher.from({
109 | * // As per multiformats table
110 | * // https://github.com/multiformats/multicodec/blob/master/table.csv#L9
111 | * name: 'sha2-256',
112 | * code: 0x12,
113 | *
114 | * encode: (input) => new Uint8Array(crypto.createHash('sha256').update(input).digest())
115 | * })
116 | *
117 | * const hash = await sha256.digest(json.encode({ hello: 'world' }))
118 | * CID.create(1, json.code, hash)
119 | *
120 | * //> CID(bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea)
121 | * ```
122 | *
123 | * ## Traversal
124 | *
125 | * This library contains higher-order functions for traversing graphs of data easily.
126 | *
127 | * `walk()` walks through the links in each block of a DAG calling a user-supplied loader function for each one, in depth-first order with no duplicate block visits. The loader should return a `Block` object and can be used to inspect and collect block ordering for a full DAG walk. The loader should `throw` on error, and return `null` if a block should be skipped by `walk()`.
128 | *
129 | * ```TypeScript
130 | * import { walk } from 'multiformats/traversal'
131 | * import * as Block from 'multiformats/block'
132 | * import * as codec from 'multiformats/codecs/json'
133 | * import { sha256 as hasher } from 'multiformats/hashes/sha2'
134 | *
135 | * // build a DAG (a single block for this simple example)
136 | * const value = { hello: 'world' }
137 | * const block = await Block.encode({ value, codec, hasher })
138 | * const { cid } = block
139 | * console.log(cid)
140 | * //> CID(bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea)
141 | *
142 | * // create a loader function that also collects CIDs of blocks in
143 | * // their traversal order
144 | * const load = (cid, blocks) => async (cid) => {
145 | * // fetch a block using its cid
146 | * // e.g.: const block = await fetchBlockByCID(cid)
147 | * blocks.push(cid)
148 | * return block
149 | * }
150 | *
151 | * // collect blocks in this DAG starting from the root `cid`
152 | * const blocks = []
153 | * await walk({ cid, load: load(cid, blocks) })
154 | *
155 | * console.log(blocks)
156 | * //> [CID(bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea)]
157 | * ```
158 | *
159 | * ## Legacy interface
160 | *
161 | * [`blockcodec-to-ipld-format`](https://github.com/ipld/js-blockcodec-to-ipld-format) converts a multiformats [`BlockCodec`](https://github.com/multiformats/js-multiformats/blob/master/src/codecs/interface.ts#L21) into an
162 | * [`interface-ipld-format`](https://github.com/ipld/interface-ipld-format) for use with the [`ipld`](https://github.com/ipld/ipld) package. This can help bridge IPLD codecs implemented using the structure and interfaces defined here to existing code that assumes, or requires `interface-ipld-format`. This bridge also includes the relevant TypeScript definitions.
163 | *
164 | * ## Implementations
165 | *
166 | * By default, no base encodings (other than base32 & base58btc), hash functions,
167 | * or codec implementations are exposed by `multiformats`, you need to
168 | * import the ones you need yourself.
169 | *
170 | * ### Multibase codecs
171 | *
172 | * | bases | import | repo |
173 | * | ------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------- |
174 | * | `base16` | `multiformats/bases/base16` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) |
175 | * | `base32`, `base32pad`, `base32hex`, `base32hexpad`, `base32z` | `multiformats/bases/base32` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) |
176 | * | `base64`, `base64pad`, `base64url`, `base64urlpad` | `multiformats/bases/base64` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) |
177 | * | `base58btc`, `base58flick4` | `multiformats/bases/base58` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) |
178 | *
179 | * Other (less useful) bases implemented in [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) include: `base2`, `base8`, `base10`, `base36` and `base256emoji`.
180 | *
181 | * ### Multihash hashers
182 | *
183 | * | hashes | import | repo |
184 | * | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
185 | * | `sha2-256`, `sha2-512` | `multiformats/hashes/sha2` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/src/hashes) |
186 | * | `sha3-224`, `sha3-256`, `sha3-384`,`sha3-512`, `shake-128`, `shake-256`, `keccak-224`, `keccak-256`, `keccak-384`, `keccak-512` | `@multiformats/sha3` | [multiformats/js-sha3](https://github.com/multiformats/js-sha3) |
187 | * | `identity` | `multiformats/hashes/identity` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/src/hashes/identity.js) |
188 | * | `murmur3-128`, `murmur3-32` | `@multiformats/murmur3` | [multiformats/js-murmur3](https://github.com/multiformats/js-murmur3) |
189 | * | `blake2b-*`, `blake2s-*` | `@multiformats/blake2` | [multiformats/js-blake2](https://github.com/multiformats/js-blake2) |
190 | *
191 | * ### IPLD codecs (multicodec)
192 | *
193 | * | codec | import | repo |
194 | * | ---------- | -------------------------- | ------------------------------------------------------------------------------------------------------ |
195 | * | `raw` | `multiformats/codecs/raw` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/src/codecs) |
196 | * | `json` | `multiformats/codecs/json` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/src/codecs) |
197 | * | `dag-cbor` | `@ipld/dag-cbor` | [ipld/js-dag-cbor](https://github.com/ipld/js-dag-cbor) |
198 | * | `dag-json` | `@ipld/dag-json` | [ipld/js-dag-json](https://github.com/ipld/js-dag-json) |
199 | * | `dag-pb` | `@ipld/dag-pb` | [ipld/js-dag-pb](https://github.com/ipld/js-dag-pb) |
200 | * | `dag-jose` | `dag-jose` | [ceramicnetwork/js-dag-jose](https://github.com/ceramicnetwork/js-dag-jose) |
201 | */
202 |
203 | import * as bytes from './bytes.js'
204 | import { CID } from './cid.js'
205 | import * as digest from './hashes/digest.js'
206 | import * as hasher from './hashes/hasher.js'
207 | import * as varint from './varint.js'
208 |
209 | // This way TS will also expose all the types from module
210 | export * from './interface.js'
211 |
212 | export { CID, hasher, digest, varint, bytes }
213 |
--------------------------------------------------------------------------------
/src/interface.ts:
--------------------------------------------------------------------------------
1 | export * from './bases/interface.js'
2 | export * from './hashes/interface.js'
3 | export * from './codecs/interface.js'
4 | export * from './link/interface.js'
5 | export * from './block/interface.js'
6 |
--------------------------------------------------------------------------------
/src/link.ts:
--------------------------------------------------------------------------------
1 | import { CID, format, toJSON, fromJSON } from './cid.js'
2 | import type * as API from './link/interface.js'
3 | // This way TS will also expose all the types from module
4 | export * from './link/interface.js'
5 |
6 | const DAG_PB_CODE = 0x70
7 | // eslint-disable-next-line
8 | const SHA_256_CODE = 0x12
9 |
10 | /**
11 | * Simplified version of `create` for CIDv0.
12 | */
13 | export function createLegacy (digest: API.MultihashDigest): API.LegacyLink {
14 | return CID.create(0, DAG_PB_CODE, digest)
15 | }
16 |
17 | /**
18 | * Simplified version of `create` for CIDv1.
19 | *
20 | * @param code - Content encoding format code.
21 | * @param digest - Miltihash of the content.
22 | */
23 | export function create (code: Code, digest: API.MultihashDigest): API.Link {
24 | return CID.create(1, code, digest)
25 | }
26 |
27 | /**
28 | * Type predicate returns true if value is the link.
29 | */
30 | export function isLink > (value: unknown | L): value is L & CID {
31 | if (value == null) {
32 | return false
33 | }
34 |
35 | const withSlash = value as { '/'?: Uint8Array, bytes: Uint8Array }
36 |
37 | if (withSlash['/'] != null && withSlash['/'] === withSlash.bytes) {
38 | return true
39 | }
40 |
41 | const withAsCID = value as { asCID?: unknown }
42 |
43 | if (withAsCID.asCID === value) {
44 | return true
45 | }
46 |
47 | return false
48 | }
49 |
50 | /**
51 | * Takes cid in a string representation and creates an instance. If `base`
52 | * decoder is not provided will use a default from the configuration. It will
53 | * throw an error if encoding of the CID is not compatible with supplied (or
54 | * a default decoder).
55 | */
56 | export function parse (source: API.ToString, Prefix>, base?: API.MultibaseDecoder): API.Link {
57 | return CID.parse(source, base)
58 | }
59 |
60 | export { format, toJSON, fromJSON }
61 |
62 | /**
63 | * Decoded a CID from its binary representation. The byte array must contain
64 | * only the CID with no additional bytes.
65 | *
66 | * An error will be thrown if the bytes provided do not contain a valid
67 | * binary representation of a CID.
68 | */
69 | export function decode (bytes: API.ByteView>): API.Link {
70 | return CID.decode(bytes)
71 | }
72 |
--------------------------------------------------------------------------------
/src/link/interface.ts:
--------------------------------------------------------------------------------
1 | import type { MultibaseEncoder, MultibaseDecoder, Multibase } from '../bases/interface.js'
2 | import type { Phantom, ByteView } from '../block/interface.js'
3 | import type { MultihashDigest } from '../hashes/interface.js'
4 |
5 | export type { MultihashDigest, MultibaseEncoder, MultibaseDecoder }
6 | export type Version = 0 | 1
7 |
8 | export type DAG_PB = 0x70
9 | export type SHA_256 = 0x12
10 |
11 | /**
12 | * Represents an IPLD link to a specific data of type `T`.
13 | *
14 | * @template T - Logical type of the data being linked to.
15 | * @template C - multicodec code corresponding to a codec linked data is encoded with
16 | * @template A - multicodec code corresponding to the hashing algorithm of the CID
17 | * @template V - CID version
18 | */
19 | export interface Link<
20 | Data extends unknown = unknown,
21 | Format extends number = number,
22 | Alg extends number = number,
23 | V extends Version = 1
24 | > extends Phantom {
25 | readonly version: V
26 | readonly code: Format
27 | readonly multihash: MultihashDigest
28 |
29 | readonly byteOffset: number
30 | readonly byteLength: number
31 | readonly bytes: ByteView>
32 |
33 | equals(other: unknown): other is Link
34 |
35 | toString(base?: MultibaseEncoder): ToString, Prefix>
36 | link(): Link
37 |
38 | toV1(): Link
39 | }
40 |
41 | export interface LinkJSON {
42 | '/': ToString
43 | }
44 |
45 | export interface LegacyLink extends Link {
46 | }
47 |
48 | export type UnknownLink =
49 | | LegacyLink
50 | | Link
51 |
52 | export type ToString = Multibase & Phantom
53 |
54 | export type { ByteView }
55 |
--------------------------------------------------------------------------------
/src/traversal.ts:
--------------------------------------------------------------------------------
1 | import { base58btc } from './bases/base58.js'
2 | import type { BlockView as _BlockView } from './block/interface.js'
3 | import type { CID, Version } from './cid.js'
4 |
5 | type BlockView = _BlockView
6 |
7 | export async function walk ({ cid, load, seen }: { cid: CID, load(cid: CID): Promise, seen?: Set }): Promise {
8 | seen = seen ?? new Set()
9 | const b58Cid = cid.toString(base58btc)
10 | if (seen.has(b58Cid)) {
11 | return
12 | }
13 |
14 | const block = await load(cid)
15 | seen.add(b58Cid)
16 |
17 | if (block === null) { // the loader signals with `null` that we should skip this block
18 | return
19 | }
20 |
21 | for (const [, cid] of block.links()) {
22 | await walk({ cid, load, seen })
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/varint.ts:
--------------------------------------------------------------------------------
1 | import varint from './vendor/varint.js'
2 |
3 | export function decode (data: Uint8Array, offset = 0): [number, number] {
4 | const code = varint.decode(data, offset)
5 | return [code, varint.decode.bytes]
6 | }
7 |
8 | export function encodeTo (int: number, target: Uint8Array, offset = 0): Uint8Array {
9 | varint.encode(int, target, offset)
10 | return target
11 | }
12 |
13 | export function encodingLength (int: number): number {
14 | return varint.encodingLength(int)
15 | }
16 |
--------------------------------------------------------------------------------
/src/vendor/base-x.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | export interface BaseConverter {
4 | encode(buffer: Uint8Array | number[]): string;
5 | decodeUnsafe(string: string): Uint8Array | undefined;
6 | decode(string: string): Uint8Array;
7 | }
8 |
9 | export default function base(ALPHABET: string, name: string): BaseConverter
10 |
--------------------------------------------------------------------------------
/src/vendor/base-x.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // base-x encoding / decoding
3 | // Copyright (c) 2018 base-x contributors
4 | // Copyright (c) 2014-2018 The Bitcoin Core developers (base58.cpp)
5 | // Distributed under the MIT software license, see the accompanying
6 | // file LICENSE or http://www.opensource.org/licenses/mit-license.php.
7 | /**
8 | * @param {string} ALPHABET
9 | * @param {any} name
10 | */
11 | function base (ALPHABET, name) {
12 | if (ALPHABET.length >= 255) { throw new TypeError('Alphabet too long') }
13 | var BASE_MAP = new Uint8Array(256);
14 | for (var j = 0; j < BASE_MAP.length; j++) {
15 | BASE_MAP[j] = 255;
16 | }
17 | for (var i = 0; i < ALPHABET.length; i++) {
18 | var x = ALPHABET.charAt(i);
19 | var xc = x.charCodeAt(0);
20 | if (BASE_MAP[xc] !== 255) { throw new TypeError(x + ' is ambiguous') }
21 | BASE_MAP[xc] = i;
22 | }
23 | var BASE = ALPHABET.length;
24 | var LEADER = ALPHABET.charAt(0);
25 | var FACTOR = Math.log(BASE) / Math.log(256); // log(BASE) / log(256), rounded up
26 | var iFACTOR = Math.log(256) / Math.log(BASE); // log(256) / log(BASE), rounded up
27 | /**
28 | * @param {any[] | Iterable} source
29 | */
30 | function encode (source) {
31 | // @ts-ignore
32 | if (source instanceof Uint8Array) ; else if (ArrayBuffer.isView(source)) {
33 | source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
34 | } else if (Array.isArray(source)) {
35 | source = Uint8Array.from(source);
36 | }
37 | if (!(source instanceof Uint8Array)) { throw new TypeError('Expected Uint8Array') }
38 | if (source.length === 0) { return '' }
39 | // Skip & count leading zeroes.
40 | var zeroes = 0;
41 | var length = 0;
42 | var pbegin = 0;
43 | var pend = source.length;
44 | while (pbegin !== pend && source[pbegin] === 0) {
45 | pbegin++;
46 | zeroes++;
47 | }
48 | // Allocate enough space in big-endian base58 representation.
49 | var size = ((pend - pbegin) * iFACTOR + 1) >>> 0;
50 | var b58 = new Uint8Array(size);
51 | // Process the bytes.
52 | while (pbegin !== pend) {
53 | var carry = source[pbegin];
54 | // Apply "b58 = b58 * 256 + ch".
55 | var i = 0;
56 | for (var it1 = size - 1; (carry !== 0 || i < length) && (it1 !== -1); it1--, i++) {
57 | carry += (256 * b58[it1]) >>> 0;
58 | b58[it1] = (carry % BASE) >>> 0;
59 | carry = (carry / BASE) >>> 0;
60 | }
61 | if (carry !== 0) { throw new Error('Non-zero carry') }
62 | length = i;
63 | pbegin++;
64 | }
65 | // Skip leading zeroes in base58 result.
66 | var it2 = size - length;
67 | while (it2 !== size && b58[it2] === 0) {
68 | it2++;
69 | }
70 | // Translate the result into a string.
71 | var str = LEADER.repeat(zeroes);
72 | for (; it2 < size; ++it2) { str += ALPHABET.charAt(b58[it2]); }
73 | return str
74 | }
75 | /**
76 | * @param {string | string[]} source
77 | */
78 | function decodeUnsafe (source) {
79 | if (typeof source !== 'string') { throw new TypeError('Expected String') }
80 | if (source.length === 0) { return new Uint8Array() }
81 | var psz = 0;
82 | // Skip leading spaces.
83 | if (source[psz] === ' ') { return }
84 | // Skip and count leading '1's.
85 | var zeroes = 0;
86 | var length = 0;
87 | while (source[psz] === LEADER) {
88 | zeroes++;
89 | psz++;
90 | }
91 | // Allocate enough space in big-endian base256 representation.
92 | var size = (((source.length - psz) * FACTOR) + 1) >>> 0; // log(58) / log(256), rounded up.
93 | var b256 = new Uint8Array(size);
94 | // Process the characters.
95 | while (source[psz]) {
96 | // Decode character
97 | var carry = BASE_MAP[source.charCodeAt(psz)];
98 | // Invalid character
99 | if (carry === 255) { return }
100 | var i = 0;
101 | for (var it3 = size - 1; (carry !== 0 || i < length) && (it3 !== -1); it3--, i++) {
102 | carry += (BASE * b256[it3]) >>> 0;
103 | b256[it3] = (carry % 256) >>> 0;
104 | carry = (carry / 256) >>> 0;
105 | }
106 | if (carry !== 0) { throw new Error('Non-zero carry') }
107 | length = i;
108 | psz++;
109 | }
110 | // Skip trailing spaces.
111 | if (source[psz] === ' ') { return }
112 | // Skip leading zeroes in b256.
113 | var it4 = size - length;
114 | while (it4 !== size && b256[it4] === 0) {
115 | it4++;
116 | }
117 | var vch = new Uint8Array(zeroes + (size - it4));
118 | var j = zeroes;
119 | while (it4 !== size) {
120 | vch[j++] = b256[it4++];
121 | }
122 | return vch
123 | }
124 | /**
125 | * @param {string | string[]} string
126 | */
127 | function decode (string) {
128 | var buffer = decodeUnsafe(string);
129 | if (buffer) { return buffer }
130 | throw new Error(`Non-${name} character`)
131 | }
132 | return {
133 | encode: encode,
134 | decodeUnsafe: decodeUnsafe,
135 | decode: decode
136 | }
137 | }
138 | var src = base;
139 |
140 | var _brrp__multiformats_scope_baseX = src;
141 |
142 | export default _brrp__multiformats_scope_baseX;
143 |
--------------------------------------------------------------------------------
/src/vendor/varint.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // Type definitions for varint 5.0
3 | // Project: https://github.com/chrisdickinson/varint#readme
4 | // Definitions by: David Brockman Smoliansky
5 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
6 |
7 | interface Varint {
8 | encode: {
9 | /**
10 | * Encodes `num` into `buffer` starting at `offset`. returns `buffer`, with the encoded varint written into it.
11 | * `varint.encode.bytes` will now be set to the number of bytes modified.
12 | */
13 | (num: number, buffer: Uint8Array, offset?: number): Buffer;
14 |
15 | /**
16 | * Encodes `num` into `array` starting at `offset`. returns `array`, with the encoded varint written into it.
17 | * If `array` is not provided, it will default to a new array.
18 | * `varint.encode.bytes` will now be set to the number of bytes modified.
19 | */
20 | (num: number, array?: number[], offset?: number): number[]
21 |
22 | /**
23 | * Similar to `decode.bytes` when encoding a number it can be useful to know how many bytes where written (especially if you pass an output array).
24 | * You can access this via `varint.encode.bytes` which holds the number of bytes written in the last encode.
25 | */
26 | bytes: number
27 | },
28 |
29 | decode: {
30 | /**
31 | * Decodes `data`, which can be either a buffer or array of integers, from position `offset` or default 0 and returns the decoded original integer.
32 | * Throws a `RangeError` when `data` does not represent a valid encoding.
33 | */
34 | (buf: Uint8Array | number[], offset?: number): number
35 |
36 | /**
37 | * If you also require the length (number of bytes) that were required to decode the integer you can access it via `varint.decode.bytes`.
38 | * This is an integer property that will tell you the number of bytes that the last .decode() call had to use to decode.
39 | */
40 | bytes: number
41 | },
42 |
43 | /**
44 | * returns the number of bytes this number will be encoded as, up to a maximum of 8.
45 | */
46 | encodingLength(num: number): number
47 | }
48 |
49 | declare const varint:Varint
50 | export default varint
51 |
--------------------------------------------------------------------------------
/src/vendor/varint.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | var encode_1 = encode;
3 |
4 | var MSB = 0x80
5 | , REST = 0x7F
6 | , MSBALL = ~REST
7 | , INT = Math.pow(2, 31);
8 |
9 | /**
10 | * @param {number} num
11 | * @param {number[]} out
12 | * @param {number} offset
13 | */
14 | function encode(num, out, offset) {
15 | out = out || [];
16 | offset = offset || 0;
17 | var oldOffset = offset;
18 |
19 | while(num >= INT) {
20 | out[offset++] = (num & 0xFF) | MSB;
21 | num /= 128;
22 | }
23 | while(num & MSBALL) {
24 | out[offset++] = (num & 0xFF) | MSB;
25 | num >>>= 7;
26 | }
27 | out[offset] = num | 0;
28 |
29 | // @ts-ignore
30 | encode.bytes = offset - oldOffset + 1;
31 |
32 | return out
33 | }
34 |
35 | var decode = read;
36 |
37 | var MSB$1 = 0x80
38 | , REST$1 = 0x7F;
39 |
40 | /**
41 | * @param {string | any[]} buf
42 | * @param {number} offset
43 | */
44 | function read(buf, offset) {
45 | var res = 0
46 | , offset = offset || 0
47 | , shift = 0
48 | , counter = offset
49 | , b
50 | , l = buf.length;
51 |
52 | do {
53 | if (counter >= l) {
54 | // @ts-ignore
55 | read.bytes = 0;
56 | throw new RangeError('Could not decode varint')
57 | }
58 | b = buf[counter++];
59 | res += shift < 28
60 | ? (b & REST$1) << shift
61 | : (b & REST$1) * Math.pow(2, shift);
62 | shift += 7;
63 | } while (b >= MSB$1)
64 |
65 | // @ts-ignore
66 | read.bytes = counter - offset;
67 |
68 | return res
69 | }
70 |
71 | var N1 = Math.pow(2, 7);
72 | var N2 = Math.pow(2, 14);
73 | var N3 = Math.pow(2, 21);
74 | var N4 = Math.pow(2, 28);
75 | var N5 = Math.pow(2, 35);
76 | var N6 = Math.pow(2, 42);
77 | var N7 = Math.pow(2, 49);
78 | var N8 = Math.pow(2, 56);
79 | var N9 = Math.pow(2, 63);
80 |
81 | var length = function (/** @type {number} */ value) {
82 | return (
83 | value < N1 ? 1
84 | : value < N2 ? 2
85 | : value < N3 ? 3
86 | : value < N4 ? 4
87 | : value < N5 ? 5
88 | : value < N6 ? 6
89 | : value < N7 ? 7
90 | : value < N8 ? 8
91 | : value < N9 ? 9
92 | : 10
93 | )
94 | };
95 |
96 | var varint = {
97 | encode: encode_1
98 | , decode: decode
99 | , encodingLength: length
100 | };
101 |
102 | var _brrp_varint = varint;
103 |
104 | export default _brrp_varint;
105 |
--------------------------------------------------------------------------------
/test/fixtures/invalid-multihash.ts:
--------------------------------------------------------------------------------
1 | export default [{
2 | code: 0x00,
3 | size: 32,
4 | hex: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33',
5 | message: 'Incorrect length'
6 | }, {
7 | code: 0x11,
8 | size: 21,
9 | hex: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33',
10 | message: 'Incorrect length'
11 | }, {
12 | code: 0x11,
13 | size: 20,
14 | hex: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a',
15 | message: 'Incorrect length'
16 | }, {
17 | code: 0x11,
18 | size: 20,
19 | hex: '',
20 | message: 'Could not decode varint'
21 | }, {
22 | code: 0x31,
23 | size: 20,
24 | hex: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33',
25 | message: 'Incorrect length'
26 | }, {
27 | code: 0x12,
28 | size: 32,
29 | hex: '2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7',
30 | message: 'Incorrect length'
31 | }, {
32 | code: 0x12,
33 | size: 32,
34 | hex: '1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad0000',
35 | message: 'Incorrect length'
36 | }]
37 |
--------------------------------------------------------------------------------
/test/fixtures/valid-multihash.ts:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | encoding: {
4 | code: 0x11,
5 | name: 'sha1'
6 | },
7 | hex: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33',
8 | size: 20
9 | }, {
10 | encoding: {
11 | code: 0x11,
12 | name: 'sha1'
13 | },
14 | hex: '0beec7b8',
15 | size: 4
16 | }, {
17 | encoding: {
18 | code: 0x12,
19 | name: 'sha2-256'
20 | },
21 | hex: '2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae',
22 | size: 32
23 | }, {
24 | encoding: {
25 | code: 0x12,
26 | name: 'sha2-256'
27 | },
28 | hex: '2c26b46b',
29 | size: 4
30 | }, {
31 | encoding: {
32 | code: 0x0,
33 | name: 'identity'
34 | },
35 | hex: '7465737420737472696e6720f09f918d',
36 | size: 16
37 | },
38 | {
39 | encoding: {
40 | code: 0x0,
41 | name: 'identity'
42 | },
43 | hex: '2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae',
44 | size: 32
45 | },
46 | {
47 | encoding: {
48 | code: 0x00,
49 | name: 'identity'
50 | },
51 | hex: '',
52 | size: 0
53 | },
54 | {
55 | encoding: {
56 | code: 0x11,
57 | name: 'sha1'
58 | },
59 | hex: '0beec7b5',
60 | size: 4
61 | },
62 | {
63 | encoding: {
64 | code: 0xb240,
65 | name: 'blake2b-512',
66 | varint: 'c0e402'
67 | },
68 | hex: '2c26b46b68ffc68ff99b453c1d30413413',
69 | size: 17
70 | },
71 | {
72 | encoding: {
73 | code: 0x22,
74 | name: 'murmur3-128'
75 | },
76 | hex: '243ddb9e',
77 | size: 4
78 | },
79 | {
80 | encoding: {
81 | code: 0x1b,
82 | name: 'keccak-256'
83 | },
84 | hex: 'f00ba4',
85 | size: 3
86 | },
87 | {
88 | encoding: {
89 | code: 0x18,
90 | name: 'shake-128'
91 | },
92 | hex: 'f84e95cb5fbd2038863ab27d3cdeac295ad2d4ab96ad1f4b070c0bf36078ef08',
93 | size: 32
94 | },
95 | {
96 | encoding: {
97 | code: 0x19,
98 | name: 'shake-256'
99 | },
100 | hex: '1af97f7818a28edfdfce5ec66dbdc7e871813816d7d585fe1f12475ded5b6502b7723b74e2ee36f2651a10a8eaca72aa9148c3c761aaceac8f6d6cc64381ed39',
101 | size: 64
102 | },
103 | {
104 | encoding: {
105 | code: 0x14,
106 | name: 'sha3-512'
107 | },
108 | hex: '4bca2b137edc580fe50a88983ef860ebaca36c857b1f492839d6d7392452a63c82cbebc68e3b70a2a1480b4bb5d437a7cba6ecf9d89f9ff3ccd14cd6146ea7e7',
109 | size: 64
110 | },
111 | {
112 | encoding: {
113 | code: 0xd5,
114 | name: 'md5',
115 | varint: 'd501'
116 | },
117 | hex: 'd41d8cd98f00b204e9800998ecf8427e',
118 | size: 16
119 | }
120 | ]
121 |
--------------------------------------------------------------------------------
/test/test-block.spec.ts:
--------------------------------------------------------------------------------
1 | /* globals describe, it */
2 |
3 | import { assert } from 'aegir/chai'
4 | import * as main from '../src/block.js'
5 | import * as codec from '../src/codecs/json.js'
6 | import { sha256 as hasher } from '../src/hashes/sha2.js'
7 | import { CID, bytes } from '../src/index.js'
8 |
9 | const fixture = { hello: 'world' }
10 | const link = CID.parse('bafyreidykglsfhoixmivffc5uwhcgshx4j465xwqntbmu43nb2dzqwfvae')
11 | const buff = bytes.fromString('sadf')
12 |
13 | describe('block', () => {
14 | it('basic encode/decode roundtrip', async () => {
15 | const block = await main.encode({ value: fixture, codec, hasher })
16 | const block2 = await main.decode({ bytes: block.bytes, codec, hasher })
17 | assert.deepStrictEqual(block.cid.equals(block2.cid), true)
18 | assert.deepStrictEqual(block.cid.equals(block2.cid), true)
19 | assert.deepStrictEqual(fixture, block2.value)
20 | const block3 = await main.create({ bytes: block.bytes, cid: block.cid, codec, hasher })
21 | assert.deepStrictEqual(block3.cid.equals(block2.cid), true)
22 | })
23 |
24 | it('createUnsafe', async () => {
25 | const block = await main.encode({ value: fixture, codec, hasher })
26 | const block2 = main.createUnsafe({ bytes: block.bytes, cid: block.cid, codec })
27 | assert.deepStrictEqual(block.cid.equals(block2.cid), true)
28 | })
29 |
30 | describe('reader', () => {
31 | const value = {
32 | link,
33 | nope: 'skip',
34 | arr: [link],
35 | obj: { arr: [{ obj: {} }] },
36 | bytes: Uint8Array.from('1234')
37 | }
38 | // @ts-expect-error - 'boolean' is not assignable to type 'CID'
39 | const block = main.createUnsafe({ value, codec, hasher, cid: true, bytes: true })
40 |
41 | it('links', () => {
42 | const expected = ['link', 'arr/0']
43 | for (const [path, cid] of block.links()) {
44 | assert.deepStrictEqual(path, expected.shift())
45 | assert.deepStrictEqual(cid.toString(), link.toString())
46 | }
47 | })
48 |
49 | it('tree', () => {
50 | const expected = ['link', 'nope', 'arr', 'arr/0', 'obj', 'obj/arr', 'obj/arr/0', 'obj/arr/0/obj', 'bytes']
51 | for (const path of block.tree()) {
52 | assert.deepStrictEqual(path, expected.shift())
53 | }
54 | })
55 |
56 | it('get', () => {
57 | let ret = block.get('link/test')
58 | assert.deepStrictEqual(ret.remaining, 'test')
59 | assert.deepStrictEqual(String(ret.value), link.toString())
60 | ret = block.get('nope')
61 |
62 | assert.deepStrictEqual(ret, { value: 'skip' })
63 | })
64 |
65 | it('null links/tree', () => {
66 | const block = main.createUnsafe({
67 | value: null,
68 | codec,
69 | hasher,
70 | // @ts-expect-error - 'boolean' is not assignable to type 'ByteView'
71 | bytes: true,
72 | // @ts-expect-error - 'boolean' is not assignable to type 'CID'
73 | cid: true
74 | })
75 | // eslint-disable-next-line
76 | for (const x of block.tree()) {
77 | throw new Error(`tree should have nothing, got "${x}"`)
78 | }
79 | // eslint-disable-next-line
80 | for (const x of block.links()) {
81 | throw new Error(`links should have nothing, got "${x}"`)
82 | }
83 | })
84 | })
85 |
86 | it('links of a block that is a CID', async () => {
87 | const block = await main.encode({ value: link, codec, hasher })
88 | const links = []
89 | for (const link of block.links()) {
90 | links.push(link)
91 | }
92 | assert.equal(links.length, 1)
93 | assert.equal(links[0][0], '')
94 | assert.equal(links[0][1].toString(), link.toString())
95 | })
96 |
97 | it('kitchen sink', () => {
98 | const sink = { one: { two: { arr: [true, false, null], three: 3, buff, link } } }
99 | const block = main.createUnsafe({
100 | value: sink,
101 | codec,
102 | // @ts-expect-error - 'boolean' is not assignable to type 'ByteView'
103 | bytes: true,
104 | // @ts-expect-error - 'boolean' is not assignable to type 'CID'
105 | cid: true
106 | })
107 | assert.deepStrictEqual(sink, block.value)
108 | })
109 |
110 | describe('errors', () => {
111 | it('constructor missing args', () => {
112 | assert.throws(
113 | // @ts-expect-error - missing properties
114 | () => new main.Block({}),
115 | 'Missing required argument'
116 | )
117 | })
118 |
119 | it('encode', async () => {
120 | // @ts-expect-error testing invalid usage
121 | await assert.isRejected(main.encode({}), 'Missing required argument "value"')
122 | // @ts-expect-error testing invalid usage
123 | await assert.isRejected(main.encode({ value: true }), 'Missing required argument: codec or hasher')
124 | })
125 |
126 | it('decode', async () => {
127 | // @ts-expect-error testing invalid usage
128 | await assert.isRejected(main.decode({}), 'Missing required argument "bytes"')
129 | // @ts-expect-error testing invalid usage
130 | await assert.isRejected(main.decode({ bytes: true }), 'Missing required argument: codec or hasher')
131 | })
132 |
133 | it('createUnsafe', async () => {
134 | // @ts-expect-error testing invalid usage
135 | assert.throws(() => main.createUnsafe({}), 'Missing required argument, must either provide "value" or "codec"')
136 | })
137 |
138 | it('create', async () => {
139 | // @ts-expect-error testing invalid usage
140 | await assert.isRejected(main.create({}), 'Missing required argument "bytes"')
141 | // @ts-expect-error testing invalid usage
142 | await assert.isRejected(main.create({ bytes: true }), 'Missing required argument "hasher"')
143 | const block = await main.encode({ value: fixture, codec, hasher })
144 | const block2 = await main.encode({ value: { ...fixture, test: 'blah' }, codec, hasher })
145 | await assert.isRejected(main.create({ bytes: block.bytes, cid: block2.cid, codec, hasher }), 'CID hash does not match bytes')
146 | })
147 |
148 | it('get', async () => {
149 | const block = await main.encode({ value: fixture, codec, hasher })
150 | assert.throws(() => block.get('/asd/fs/dfasd/f'), 'Object has no property at ["asd"]')
151 | })
152 | })
153 | })
154 |
--------------------------------------------------------------------------------
/test/test-bytes.spec.ts:
--------------------------------------------------------------------------------
1 | /* globals describe, it */
2 |
3 | import { assert } from 'aegir/chai'
4 | import * as bytes from '../src/bytes.js'
5 |
6 | describe('bytes', () => {
7 | it('isBinary', () => {
8 | assert.deepStrictEqual(bytes.isBinary(new ArrayBuffer(0)), true)
9 | assert.deepStrictEqual(bytes.isBinary(new DataView(new ArrayBuffer(0))), true)
10 | })
11 |
12 | it('coerce', () => {
13 | const fixture = bytes.fromString('test')
14 | // @ts-expect-error
15 | assert.deepStrictEqual(bytes.coerce(fixture.buffer), fixture)
16 | assert.deepStrictEqual(bytes.coerce(new DataView(fixture.buffer)), fixture)
17 | })
18 |
19 | it('equals', () => {
20 | const fixture = bytes.fromString('test')
21 | assert.deepStrictEqual(bytes.equals(fixture, bytes.fromString('asdfadf')), false)
22 | })
23 |
24 | it('toString()', () => {
25 | const fixture = 'hello world'
26 | assert.deepStrictEqual(bytes.toString(bytes.fromString(fixture)), fixture)
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/test/test-cid.spec.ts:
--------------------------------------------------------------------------------
1 | /* globals describe, it */
2 |
3 | import { assert } from 'aegir/chai'
4 | import OLDCID from 'cids'
5 | import { base32 } from '../src/bases/base32.js'
6 | import { base36 } from '../src/bases/base36.js'
7 | import { base58btc } from '../src/bases/base58.js'
8 | import { base64 } from '../src/bases/base64.js'
9 | import { fromHex, toHex, equals } from '../src/bytes.js'
10 | import { sha256, sha512 } from '../src/hashes/sha2.js'
11 | import { varint, CID } from '../src/index.js'
12 | import invalidMultihash from './fixtures/invalid-multihash.js'
13 | import type { MultihashDigest } from '../src/index.js'
14 |
15 | const textEncoder = new TextEncoder()
16 |
17 | describe('CID', () => {
18 | describe('v0', () => {
19 | it('handles B58Str multihash', () => {
20 | const mhStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
21 | const cid = CID.parse(mhStr)
22 |
23 | assert.deepStrictEqual(cid.version, 0)
24 | assert.deepStrictEqual(cid.code, 112)
25 | assert.deepStrictEqual(cid.multihash.bytes, base58btc.baseDecode(mhStr))
26 |
27 | assert.deepStrictEqual(cid.toString(), mhStr)
28 | })
29 |
30 | it('create by parts', async () => {
31 | const hash = await sha256.digest(textEncoder.encode('abc'))
32 | const cid = CID.create(0, 112, hash)
33 |
34 | assert.deepStrictEqual(cid.code, 112)
35 | assert.deepStrictEqual(cid.version, 0)
36 | assert.deepStrictEqual(cid.multihash, hash)
37 | assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes))
38 | })
39 |
40 | it('CID.createV0', async () => {
41 | const hash = await sha256.digest(textEncoder.encode('abc'))
42 | const cid = CID.createV0(hash)
43 |
44 | assert.deepStrictEqual(cid.code, 112)
45 | assert.deepStrictEqual(cid.version, 0)
46 | assert.deepStrictEqual(cid.multihash, hash)
47 | assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes))
48 | })
49 |
50 | it('create from multihash', async () => {
51 | const hash = await sha256.digest(textEncoder.encode('abc'))
52 |
53 | const cid = CID.decode(hash.bytes)
54 |
55 | assert.deepStrictEqual(cid.code, 112)
56 | assert.deepStrictEqual(cid.version, 0)
57 | assert.deepStrictEqual(cid.multihash.digest, hash.digest)
58 | assert.deepStrictEqual(
59 | { ...cid.multihash, digest: null },
60 | { ...hash, digest: null }
61 | )
62 | cid.toString()
63 | assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes))
64 | })
65 |
66 | it('throws on invalid BS58Str multihash ', async () => {
67 | const msg = 'Non-base58btc character'
68 | assert.throws(
69 | () => CID.parse('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII'),
70 | msg
71 | )
72 | })
73 |
74 | it('throws on trying to create a CIDv0 with a codec other than dag-pb', async () => {
75 | const hash = await sha256.digest(textEncoder.encode('abc'))
76 | const msg = 'Version 0 CID must use dag-pb (code: 112) block encoding'
77 | assert.throws(() => CID.create(0, 113, hash), msg)
78 | })
79 |
80 | it('throws on trying to base encode CIDv0 in other base than base58btc', async () => {
81 | const mhStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
82 | const cid = CID.parse(mhStr)
83 | const msg = 'Cannot string encode V0 in base32 encoding'
84 | assert.throws(() => cid.toString(base32), msg)
85 | })
86 |
87 | it('throws on CIDv0 string with explicit multibase prefix', async () => {
88 | const str = 'zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
89 | const msg = 'Version 0 CID string must not include multibase prefix'
90 | assert.throws(() => CID.parse(str), msg)
91 | })
92 |
93 | it('.bytes', async () => {
94 | const hash = await sha256.digest(textEncoder.encode('abc'))
95 | const codec = 112
96 | const cid = CID.create(0, codec, hash)
97 | const bytes = cid.bytes
98 | assert.ok(bytes)
99 | const str = toHex(bytes)
100 | assert.deepStrictEqual(
101 | str,
102 | '1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
103 | )
104 | })
105 |
106 | it('should construct from an old CID', () => {
107 | const cidStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
108 | const oldCid = CID.parse(cidStr)
109 | const newCid = CID.asCID(oldCid)
110 | assert.deepStrictEqual(newCid?.toString(), cidStr)
111 | })
112 |
113 | it('inspect bytes', () => {
114 | const byts = fromHex(
115 | '1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
116 | )
117 | const inspected = CID.inspectBytes(byts.subarray(0, 10)) // should only need the first few bytes
118 | assert.deepStrictEqual(
119 | {
120 | version: 0,
121 | codec: 0x70,
122 | multihashCode: 0x12,
123 | multihashSize: 34,
124 | digestSize: 32,
125 | size: 34
126 | },
127 | inspected
128 | )
129 | })
130 |
131 | describe('decodeFirst', () => {
132 | it('no remainder', () => {
133 | const byts = fromHex(
134 | '1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
135 | )
136 | const [cid, remainder] = CID.decodeFirst(byts)
137 | assert.deepStrictEqual(
138 | cid.toString(),
139 | 'QmatYkNGZnELf8cAGdyJpUca2PyY4szai3RHyyWofNY1pY'
140 | )
141 | assert.deepStrictEqual(remainder.byteLength, 0)
142 | })
143 |
144 | it('remainder', () => {
145 | const byts = fromHex(
146 | '1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad0102030405'
147 | )
148 | const [cid, remainder] = CID.decodeFirst(byts)
149 | assert.deepStrictEqual(
150 | cid.toString(),
151 | 'QmatYkNGZnELf8cAGdyJpUca2PyY4szai3RHyyWofNY1pY'
152 | )
153 | assert.deepStrictEqual(toHex(remainder), '0102030405')
154 | })
155 | })
156 | })
157 |
158 | describe('v1', () => {
159 | it('handles CID String (multibase encoded)', () => {
160 | const cidStr = 'zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS'
161 | const cid = CID.parse(cidStr)
162 | assert.deepStrictEqual(cid.code, 112)
163 | assert.deepStrictEqual(cid.version, 1)
164 | assert.ok(cid.multihash)
165 | assert.deepStrictEqual(cid.toString(), base32.encode(cid.bytes))
166 | })
167 |
168 | it('handles CID (no multibase)', () => {
169 | const cidStr =
170 | 'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u'
171 | const cidBuf = fromHex(
172 | '017012207252523e6591fb8fe553d67ff55a86f84044b46a3e4176e10c58fa529a4aabd5'
173 | )
174 | const cid = CID.decode(cidBuf)
175 | assert.deepStrictEqual(cid.code, 112)
176 | assert.deepStrictEqual(cid.version, 1)
177 | assert.deepStrictEqual(cid.toString(), cidStr)
178 | })
179 |
180 | it('create by parts', async () => {
181 | const hash = await sha256.digest(textEncoder.encode('abc'))
182 | const cid = CID.create(1, 0x71, hash)
183 | assert.deepStrictEqual(cid.code, 0x71)
184 | assert.deepStrictEqual(cid.version, 1)
185 | equalDigest(cid.multihash, hash)
186 | })
187 |
188 | it('CID.createV1', async () => {
189 | const hash = await sha256.digest(textEncoder.encode('abc'))
190 | const cid = CID.createV1(0x71, hash)
191 |
192 | assert.deepStrictEqual(cid.code, 0x71)
193 | assert.deepStrictEqual(cid.version, 1)
194 | equalDigest(cid.multihash, hash)
195 | })
196 |
197 | it('can roundtrip through cid.toString()', async () => {
198 | const hash = await sha256.digest(textEncoder.encode('abc'))
199 | const cid1 = CID.create(1, 0x71, hash)
200 | const cid2 = CID.parse(cid1.toString())
201 |
202 | assert.deepStrictEqual(cid1.code, cid2.code)
203 | assert.deepStrictEqual(cid1.version, cid2.version)
204 | assert.deepStrictEqual(cid1.multihash.digest, cid2.multihash.digest)
205 | assert.deepStrictEqual(cid1.multihash.bytes, cid2.multihash.bytes)
206 | const clear = { digest: null, bytes: null }
207 | assert.deepStrictEqual(
208 | { ...cid1.multihash, ...clear },
209 | { ...cid2.multihash, ...clear }
210 | )
211 | })
212 |
213 | it('.bytes', async () => {
214 | const hash = await sha256.digest(textEncoder.encode('abc'))
215 | const code = 0x71
216 | const cid = CID.create(1, code, hash)
217 | const bytes = cid.bytes
218 | assert.ok(bytes)
219 | const str = toHex(bytes)
220 | assert.deepStrictEqual(
221 | str,
222 | '01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
223 | )
224 | })
225 |
226 | it('should construct from an old CID without a multibaseName', () => {
227 | const cidStr =
228 | 'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u'
229 | const oldCid = CID.parse(cidStr)
230 | const newCid = CID.asCID(oldCid)
231 | assert.deepStrictEqual(newCid?.toString(), cidStr)
232 | })
233 |
234 | it('.link() should return this CID', () => {
235 | const cid = CID.parse('bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u')
236 | assert.equal(cid, cid.link())
237 | })
238 | })
239 |
240 | describe('utilities', () => {
241 | const h1 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
242 | const h2 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1o'
243 | const h3 = 'zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS'
244 |
245 | it('.equals v0 to v0', () => {
246 | const cid1 = CID.parse(h1)
247 | assert.deepStrictEqual(cid1.equals(CID.parse(h1)), true)
248 | assert.deepStrictEqual(
249 | cid1.equals(CID.create(cid1.version, cid1.code, cid1.multihash)),
250 | true
251 | )
252 |
253 | const cid2 = CID.parse(h2)
254 | assert.deepStrictEqual(cid1.equals(CID.parse(h2)), false)
255 | assert.deepStrictEqual(
256 | cid1.equals(CID.create(cid2.version, cid2.code, cid2.multihash)),
257 | false
258 | )
259 | })
260 |
261 | it('.equals v0 to v1 and vice versa', () => {
262 | const cidV1 = CID.parse(h3)
263 |
264 | const cidV0 = cidV1.toV0()
265 |
266 | assert.deepStrictEqual(cidV0.equals(cidV1), false)
267 | assert.deepStrictEqual(cidV1.equals(cidV0), false)
268 |
269 | assert.deepStrictEqual(cidV1.multihash, cidV0.multihash)
270 | })
271 |
272 | it('.equals v1 to v1', () => {
273 | const cid1 = CID.parse(h3)
274 |
275 | assert.deepStrictEqual(cid1.equals(CID.parse(h3)), true)
276 | assert.deepStrictEqual(
277 | cid1.equals(CID.create(cid1.version, cid1.code, cid1.multihash)),
278 | true
279 | )
280 | })
281 |
282 | it('works with deepEquals', () => {
283 | const ch1 = CID.parse(h1)
284 | assert.deepStrictEqual(ch1, CID.parse(h1))
285 | assert.notDeepEqual(ch1, CID.parse(h2))
286 | })
287 | })
288 |
289 | describe('throws on invalid inputs', () => {
290 | const parse = [
291 | 'hello world',
292 | 'QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT3L'
293 | ]
294 |
295 | for (const i of parse) {
296 | const name = `CID.parse(${JSON.stringify(i)})`
297 | it(name, async () => { assert.throws(() => CID.parse(i)) })
298 | }
299 |
300 | const decode = [
301 | textEncoder.encode('hello world'),
302 | textEncoder.encode('QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT')
303 | ]
304 |
305 | for (const i of decode) {
306 | const name = `CID.decode(textEncoder.encode(${JSON.stringify(
307 | i.toString()
308 | )}))`
309 | it(name, async () => { assert.throws(() => CID.decode(i)) })
310 | }
311 |
312 | const create = [
313 | ...[...parse, ...decode].map((i) => [0, 112, i]),
314 | ...[...parse, ...decode].map((i) => [1, 112, i]),
315 | [18, 112, 'QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT3L']
316 | ]
317 |
318 | for (const [version, code, hash] of create) {
319 | const form = JSON.stringify(hash.toString())
320 | const mh =
321 | hash instanceof Uint8Array ? `textEncoder.encode(${form})` : form
322 | const name = `CID.create(${version}, ${code}, ${mh})`
323 | // @ts-expect-error - version issn't always 0|1
324 | it(name, async () => { assert.throws(() => CID.create(version, code, hash)) })
325 | }
326 |
327 | it('invalid fixtures', async () => {
328 | for (const test of invalidMultihash) {
329 | const buff = fromHex(`0171${test.hex}`)
330 | assert.throws(() => CID.decode(buff), new RegExp(test.message))
331 | }
332 | })
333 | })
334 |
335 | describe('idempotence', () => {
336 | const h1 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
337 | const cid1 = CID.parse(h1)
338 | const cid2 = CID.asCID(cid1)
339 |
340 | it('constructor accept constructed instance', () => {
341 | assert.deepStrictEqual(cid1 === cid2, true)
342 | })
343 | })
344 |
345 | describe('conversion v0 <-> v1', () => {
346 | it('should convert v0 to v1', async () => {
347 | const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
348 | const cid = CID.create(0, 112, hash).toV1()
349 | assert.deepStrictEqual(cid.version, 1)
350 | })
351 |
352 | it('should convert v1 to v0', async () => {
353 | const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
354 | const cid = CID.create(1, 112, hash).toV0()
355 | assert.deepStrictEqual(cid.version, 0)
356 | })
357 |
358 | it('should not convert v1 to v0 if not dag-pb codec', async () => {
359 | const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
360 | const cid = CID.create(1, 0x71, hash)
361 | assert.throws(
362 | () => cid.toV0(),
363 | 'Cannot convert a non dag-pb CID to CIDv0'
364 | )
365 | })
366 |
367 | it('should not convert v1 to v0 if not sha2-256 multihash', async () => {
368 | const hash = await sha512.digest(textEncoder.encode(`TEST${Date.now()}`))
369 | const cid = CID.create(1, 112, hash)
370 | assert.throws(
371 | () => cid.toV0(),
372 | 'Cannot convert non sha2-256 multihash CID to CIDv0'
373 | )
374 | })
375 |
376 | it('should return assert.deepStrictEqual instance when converting v1 to v1', async () => {
377 | const hash = await sha512.digest(textEncoder.encode(`TEST${Date.now()}`))
378 | const cid = CID.create(1, 112, hash)
379 |
380 | assert.deepStrictEqual(cid.toV1() === cid, true)
381 | })
382 |
383 | it('should return assert.deepStrictEqual instance when converting v0 to v0', async () => {
384 | const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
385 | const cid = CID.create(0, 112, hash)
386 | assert.deepStrictEqual(cid.toV0() === cid, true)
387 | })
388 |
389 | it('should fail to convert unknown version', async () => {
390 | const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
391 | const cid = CID.create(0, 112, hash)
392 | const cid1 = Object.assign(Object.create(Object.getPrototypeOf(cid)), {
393 | ...cid
394 | })
395 | const cid2 = Object.assign(Object.create(Object.getPrototypeOf(cid)), {
396 | ...cid,
397 | version: 3
398 | })
399 |
400 | assert.deepStrictEqual(cid1.toV0().version, 0)
401 | assert.deepStrictEqual(cid1.toV1().version, 1)
402 | assert.equal(cid2.version, 3)
403 |
404 | assert.throws(
405 | () => cid2.toV1(),
406 | /Can not convert CID version 3 to version 1/
407 | )
408 | assert.throws(
409 | () => cid2.toV0(),
410 | /Can not convert CID version 3 to version 0/
411 | )
412 | })
413 | })
414 |
415 | describe('caching', () => {
416 | it('should cache CID as buffer', async () => {
417 | const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
418 | const cid = CID.create(1, 112, hash)
419 | assert.ok(cid.bytes)
420 | assert.deepStrictEqual(cid.bytes, cid.bytes)
421 | })
422 |
423 | it('should cache string representation when it matches the multibaseName it was constructed with', async () => {
424 | const hash = await sha256.digest(textEncoder.encode('abc'))
425 | const cid = CID.create(1, 112, hash)
426 |
427 | const b32 = {
428 | ...base32,
429 | callCount: 0,
430 | encode (bytes: Uint8Array): string {
431 | this.callCount += 1
432 | return base32.encode(bytes) + '!'
433 | }
434 | }
435 |
436 | const b64 = {
437 | ...base64,
438 | callCount: 0,
439 | encode (bytes: Uint8Array): string {
440 | this.callCount += 1
441 | return base64.encode(bytes)
442 | }
443 | }
444 |
445 | const base32String =
446 | 'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
447 | assert.deepEqual(cid.toString(b32), `${base32String}!`)
448 | assert.deepEqual(b32.callCount, 1)
449 | assert.deepEqual(cid.toString(), `${base32String}!`)
450 | assert.deepEqual(b32.callCount, 1)
451 |
452 | assert.deepStrictEqual(
453 | cid.toString(b64),
454 | 'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt'
455 | )
456 | assert.equal(b64.callCount, 1)
457 | assert.deepStrictEqual(
458 | cid.toString(b64),
459 | 'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt'
460 | )
461 | assert.equal(b64.callCount, 1)
462 | })
463 |
464 | it('should cache string representation when constructed with one', () => {
465 | const base32String =
466 | 'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
467 | const cid = CID.parse(base32String)
468 |
469 | assert.deepStrictEqual(
470 | cid.toString({
471 | ...base32,
472 | encode () {
473 | throw Error('Should not call decode')
474 | }
475 | }),
476 | base32String
477 | )
478 | })
479 | })
480 |
481 | it('toJSON()', async () => {
482 | const hash = await sha256.digest(textEncoder.encode('abc'))
483 | const cid = CID.create(1, 112, hash)
484 |
485 | assert.deepStrictEqual(cid.toJSON(), {
486 | '/': cid.toString()
487 | })
488 | })
489 |
490 | it('asCID', async () => {
491 | const hash = await sha256.digest(textEncoder.encode('abc'))
492 | class IncompatibleCID {
493 | readonly version: number
494 | readonly code: number
495 | readonly multihash: MultihashDigest
496 | readonly asCID: this
497 |
498 | constructor (version: number, code: number, multihash: MultihashDigest) {
499 | this.version = version
500 | this.code = code
501 | this.multihash = multihash
502 | this.asCID = this
503 | }
504 |
505 | get [Symbol.for('@ipld/js-cid/CID')] (): boolean {
506 | return true
507 | }
508 | }
509 |
510 | const version = 1
511 | const code = 112
512 |
513 | const incompatibleCID = new IncompatibleCID(version, code, hash)
514 |
515 | assert.strictEqual(incompatibleCID.toString(), '[object Object]')
516 | // @ts-expect-error - no such method
517 | assert.strictEqual(typeof incompatibleCID.toV0, 'undefined')
518 |
519 | const cid1 = CID.asCID(incompatibleCID)
520 | assert.ok(cid1 instanceof CID)
521 | assert.strictEqual(cid1.code, code)
522 | assert.strictEqual(cid1.version, version)
523 | assert.ok(equals(cid1.multihash.bytes, hash.bytes))
524 |
525 | const cid2 = CID.asCID({ version, code, hash })
526 | assert.strictEqual(cid2, null)
527 |
528 | const duckCID = { version, code, multihash: hash }
529 | // @ts-expect-error - no such property
530 | duckCID.asCID = duckCID
531 | const cid3 = CID.asCID(duckCID) as CID
532 | assert.ok(cid3 instanceof CID)
533 | assert.strictEqual(cid3.code, code)
534 | assert.strictEqual(cid3.version, version)
535 | assert.ok(equals(cid3.multihash.bytes, hash.bytes))
536 |
537 | const cid4 = CID.asCID(cid3)
538 | assert.strictEqual(cid3, cid4)
539 |
540 | const cid5 = (
541 | CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))
542 | )
543 | assert.ok(cid5 instanceof CID)
544 | assert.strictEqual(cid5.version, 1)
545 | assert.ok(equals(cid5.multihash.bytes, hash.bytes))
546 | assert.strictEqual(cid5.code, 85)
547 | })
548 |
549 | const digestsame = (x: CID, y: CID): void => {
550 | // @ts-expect-error not sure what this supposed to be
551 | assert.deepStrictEqual(x.hash, y.hash)
552 | assert.deepStrictEqual(x.bytes, y.bytes)
553 | if (x.multihash != null) {
554 | equalDigest(x.multihash, y.multihash)
555 | }
556 | const empty = { hash: null, bytes: null, digest: null, multihash: null }
557 | assert.deepStrictEqual({ ...x, ...empty }, { ...y, ...empty })
558 | }
559 |
560 | const equalDigest = (x?: MultihashDigest, y?: MultihashDigest): void => {
561 | assert.ok(x)
562 | assert.ok(y)
563 | assert.deepStrictEqual(x.digest, y.digest)
564 | assert.deepStrictEqual(x.code, y.code)
565 | assert.deepStrictEqual(x.digest, y.digest)
566 | }
567 |
568 | describe('CID.parse', async () => {
569 | it('parse 32 encoded CIDv1', async () => {
570 | const hash = await sha256.digest(textEncoder.encode('abc'))
571 | const cid = CID.create(1, 112, hash)
572 |
573 | const parsed = CID.parse(cid.toString(base32))
574 | digestsame(cid, parsed)
575 | })
576 |
577 | it('parse 36 encoded CIDv1', async () => {
578 | const hash = await sha256.digest(textEncoder.encode('abc'))
579 | const cid = CID.create(1, 112, hash)
580 |
581 | const parsed = CID.parse(cid.toString(base36))
582 | digestsame(cid, parsed)
583 | })
584 |
585 | it('parse base58btc encoded CIDv1', async () => {
586 | const hash = await sha256.digest(textEncoder.encode('abc'))
587 | const cid = CID.create(1, 112, hash)
588 |
589 | const parsed = CID.parse(cid.toString(base58btc))
590 | digestsame(cid, parsed)
591 | })
592 |
593 | it('parse base58btc encoded CIDv0', async () => {
594 | const hash = await sha256.digest(textEncoder.encode('abc'))
595 | const cid = CID.create(0, 112, hash)
596 |
597 | const parsed = CID.parse(cid.toString())
598 | digestsame(cid, parsed)
599 | })
600 |
601 | it('fails to parse base64 encoded CIDv1', async () => {
602 | const hash = await sha256.digest(textEncoder.encode('abc'))
603 | const cid = CID.create(1, 112, hash)
604 | const msg =
605 | 'To parse non base32, base36 or base58btc encoded CID multibase decoder must be provided'
606 |
607 | assert.throws(() => CID.parse(cid.toString(base64)), msg)
608 | })
609 |
610 | it('parses base64 encoded CIDv1 if base64 is provided', async () => {
611 | const hash = await sha256.digest(textEncoder.encode('abc'))
612 | const cid = CID.create(1, 112, hash)
613 |
614 | const parsed = CID.parse(cid.toString(base64), base64)
615 | digestsame(cid, parsed)
616 | })
617 | })
618 |
619 | it('inspect bytes', () => {
620 | const byts = fromHex(
621 | '01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
622 | )
623 | const inspected = CID.inspectBytes(byts.subarray(0, 10)) // should only need the first few bytes
624 | assert.deepStrictEqual(
625 | {
626 | version: 1,
627 | codec: 0x71,
628 | multihashCode: 0x12,
629 | multihashSize: 34,
630 | digestSize: 32,
631 | size: 36
632 | },
633 | inspected
634 | )
635 |
636 | describe('decodeFirst', () => {
637 | it('no remainder', () => {
638 | const byts = fromHex(
639 | '01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
640 | )
641 | const [cid, remainder] = CID.decodeFirst(byts)
642 | assert.deepStrictEqual(
643 | cid.toString(),
644 | 'bafyreif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
645 | )
646 | assert.deepStrictEqual(remainder.byteLength, 0)
647 | })
648 |
649 | it('remainder', () => {
650 | const byts = fromHex(
651 | '01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad0102030405'
652 | )
653 | const [cid, remainder] = CID.decodeFirst(byts)
654 | assert.deepStrictEqual(
655 | cid.toString(),
656 | 'bafyreif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
657 | )
658 | assert.deepStrictEqual(toHex(remainder), '0102030405')
659 | })
660 | })
661 | })
662 |
663 | it('new CID from old CID', async () => {
664 | const hash = await sha256.digest(textEncoder.encode('abc'))
665 | const cid = (
666 | CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))
667 | )
668 | assert.deepStrictEqual(cid?.version, 1)
669 |
670 | equalDigest(cid?.multihash, hash)
671 | assert.deepStrictEqual(cid?.code, 85)
672 | })
673 |
674 | it('util.inspect', async () => {
675 | const hash = await sha256.digest(textEncoder.encode('abc'))
676 | const cid = CID.create(1, 112, hash)
677 | assert.deepStrictEqual(
678 | // @ts-expect-error - no such method is known
679 | typeof cid[Symbol.for('nodejs.util.inspect.custom')],
680 | 'function'
681 | )
682 | assert.deepStrictEqual(
683 | // @ts-expect-error - no such method is known
684 | cid[Symbol.for('nodejs.util.inspect.custom')](),
685 | 'CID(bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu)'
686 | )
687 | })
688 |
689 | it('invalid CID version', async () => {
690 | const encoded = varint.encodeTo(2, new Uint8Array(32))
691 | assert.throws(() => CID.decode(encoded), 'Invalid CID version 2')
692 | })
693 |
694 | it('CID can be moved across JS realms', async () => {
695 | const cid = CID.parse('bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu')
696 | const { port1: sender, port2: receiver } = new MessageChannel()
697 | sender.postMessage(cid)
698 | const cid2 = await new Promise((resolve) => {
699 | receiver.onmessage = (event) => { resolve(event.data) }
700 | })
701 | sender.close()
702 | receiver.close()
703 | assert.strictEqual(cid2['/'], cid2.bytes)
704 | })
705 |
706 | describe('decode', () => {
707 | const tests = {
708 | v0: 'QmTFHZL5CkgNz19MdPnSuyLAi6AVq9fFp81zmPpaL2amED',
709 | v1: 'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
710 | }
711 |
712 | Object.entries(tests).forEach(([version, cidString]) => {
713 | it(`decode ${version} from bytes`, () => {
714 | const cid1 = CID.parse(cidString)
715 | const cid2 = CID.decode(cid1.bytes)
716 |
717 | assert.deepStrictEqual(cid1, cid2)
718 | })
719 |
720 | it(`decode ${version} from subarray`, () => {
721 | const cid1 = CID.parse(cidString)
722 | // a byte array with an extra byte at the start and end
723 | const bytes = new Uint8Array(cid1.bytes.length + 2)
724 | bytes.set(cid1.bytes, 1)
725 | // slice the cid bytes out of the middle to have a subarray with a non-zero .byteOffset
726 | const subarray = bytes.subarray(1, cid1.bytes.length + 1)
727 | const cid2 = CID.decode(subarray)
728 |
729 | assert.deepStrictEqual(cid1, cid2)
730 | assert.equal(cid1.byteLength, cid2.byteLength)
731 | assert.equal(typeof cid2.byteOffset, 'number')
732 | })
733 | })
734 | })
735 | })
736 |
--------------------------------------------------------------------------------
/test/test-link.spec.ts:
--------------------------------------------------------------------------------
1 | /* globals describe, it */
2 |
3 | import { assert } from 'aegir/chai'
4 | import { sha256 } from '../src/hashes/sha2.js'
5 | import * as Link from '../src/link.js'
6 |
7 | const utf8 = new TextEncoder()
8 |
9 | const h1 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
10 | const h4 = 'bafyreidykglsfhoixmivffc5uwhcgshx4j465xwqntbmu43nb2dzqwfvae'
11 | const CBOR = 0x71
12 |
13 | const sh1 =
14 | Link.parse(h4).multihash as Link.MultihashDigest
15 |
16 | describe('Link', () => {
17 | it('isLink', () => {
18 | assert.equal(Link.isLink(0), false)
19 | assert.equal(Link.isLink(false), false)
20 | })
21 |
22 | describe('create', () => {
23 | it('create v1', async () => {
24 | const hash = await sha256.digest(utf8.encode('abc'))
25 | const link = Link.create(0x71, hash)
26 | const code = link.code
27 | assert.deepStrictEqual(code, 0x71)
28 |
29 | const version = link.version
30 | assert.deepEqual(version, 1)
31 |
32 | const multihash = link.multihash
33 | assert.deepStrictEqual(multihash, hash)
34 | })
35 |
36 | it('create v0', async () => {
37 | const hash = await sha256.digest(utf8.encode('abc'))
38 | const link = Link.createLegacy(hash)
39 |
40 | const code = link.code
41 | assert.deepStrictEqual(code, 0x70)
42 |
43 | const version = link.version
44 | assert.deepEqual(version, 0)
45 |
46 | const multihash = link.multihash
47 | assert.deepStrictEqual(multihash, hash)
48 | })
49 | })
50 |
51 | describe('parse', () => {
52 | it('can parse any string', () => {
53 | const link = Link.parse(h1)
54 |
55 | const t1 = link as Link.Link
56 | assert.ok(t1)
57 |
58 | // it is possible to manually cast
59 | const t2 = link as Link.LegacyLink