├── .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 | [![multiformats.io](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://multiformats.io) 4 | [![codecov](https://img.shields.io/codecov/c/github/multiformats/js-multiformats.svg?style=flat-square)](https://codecov.io/gh/multiformats/js-multiformats) 5 | [![CI](https://img.shields.io/github/actions/workflow/status/multiformats/js-multiformats/js-test-and-release.yml?branch=master\&style=flat-square)](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 60 | assert.ok(t2) 61 | }) 62 | 63 | it('parse retains type info', () => { 64 | const original = Link.create(CBOR, sh1) 65 | const source = Link.format(original) 66 | const link = Link.parse(source) 67 | assert.equal(original.equals(link), true, 'format -> parse roundtrips') 68 | 69 | // ensure that type info is retained 70 | const t1 = link 71 | assert.ok(t1) 72 | 73 | // ensure that you can't cast incorrectly 74 | const t2 = 75 | // @ts-expect-error - version is 1 not 0 76 | link as Link.Link 77 | assert.ok(t2) 78 | }) 79 | }) 80 | 81 | describe('toJSON', () => { 82 | assert.deepStrictEqual(Link.toJSON(Link.parse(h1)), { 83 | '/': h1 84 | }) 85 | 86 | assert.deepStrictEqual(Link.toJSON(Link.parse(h4)), { 87 | '/': h4 88 | }) 89 | }) 90 | 91 | describe('fromJSON', () => { 92 | assert.deepStrictEqual(Link.parse(h1), Link.fromJSON({ 93 | '/': h1 94 | })) 95 | 96 | assert.deepStrictEqual(Link.parse(h1), Link.fromJSON({ 97 | '/': h1, 98 | // @ts-expect-error foo doesn't exist 99 | foo: 1 100 | })) 101 | 102 | assert.deepStrictEqual(Link.parse(h4), Link.fromJSON({ 103 | '/': h4 104 | })) 105 | }) 106 | 107 | describe('JSON.stringify', () => { 108 | assert.equal(JSON.stringify(Link.parse(h1)), JSON.stringify({ 109 | '/': h1 110 | })) 111 | 112 | assert.equal(JSON.stringify(Link.parse(h4)), JSON.stringify({ 113 | '/': h4 114 | })) 115 | }) 116 | }) 117 | 118 | describe('decode', () => { 119 | it('decode', async () => { 120 | const hash = await sha256.digest(utf8.encode('abc')) 121 | const { bytes } = Link.create(0x71, hash) 122 | 123 | const link = Link.decode(bytes) 124 | 125 | const code = link.code 126 | assert.deepStrictEqual(code, 0x71) 127 | 128 | const version = link.version 129 | assert.deepEqual(version, 1) 130 | 131 | const multihash = link.multihash 132 | assert.deepStrictEqual(multihash, hash) 133 | }) 134 | }) 135 | -------------------------------------------------------------------------------- /test/test-multibase-spec.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import { assert } from 'aegir/chai' 4 | import { bases } from '../src/basics.js' 5 | import { fromString } from '../src/bytes.js' 6 | 7 | const encoded = [ 8 | { 9 | input: 'Decentralize everything!!', 10 | tests: [ 11 | ['identity', '\x00Decentralize everything!!'], 12 | ['base2', '001000100011001010110001101100101011011100111010001110010011000010110110001101001011110100110010100100000011001010111011001100101011100100111100101110100011010000110100101101110011001110010000100100001'], 13 | ['base8', '72106254331267164344605543227514510062566312711713506415133463441102'], 14 | ['base10', '9429328951066508984658627669258025763026247056774804621697313'], 15 | ['base16', 'f446563656e7472616c697a652065766572797468696e672121'], 16 | ['base16upper', 'F446563656E7472616C697A652065766572797468696E672121'], 17 | ['base32', 'birswgzloorzgc3djpjssazlwmvzhs5dinfxgoijb'], 18 | ['base32upper', 'BIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJB'], 19 | ['base32hex', 'v8him6pbeehp62r39f9ii0pbmclp7it38d5n6e891'], 20 | ['base32hexupper', 'V8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E891'], 21 | ['base32pad', 'cirswgzloorzgc3djpjssazlwmvzhs5dinfxgoijb'], 22 | ['base32padupper', 'CIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJB'], 23 | ['base32hexpad', 't8him6pbeehp62r39f9ii0pbmclp7it38d5n6e891'], 24 | ['base32hexpadupper', 'T8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E891'], 25 | ['base32z', 'het1sg3mqqt3gn5djxj11y3msci3817depfzgqejb'], 26 | ['base36', 'k343ixo7d49hqj1ium15pgy1wzww5fxrid21td7l'], 27 | ['base36upper', 'K343IXO7D49HQJ1IUM15PGY1WZWW5FXRID21TD7L'], 28 | ['base58flickr', 'Ztwe7gVTeK8wswS1gf8hrgAua9fcw9reboD'], 29 | ['base58btc', 'zUXE7GvtEk8XTXs1GF8HSGbVA9FCX9SEBPe'], 30 | ['base64', 'mRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ'], 31 | ['base64pad', 'MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ=='], 32 | ['base64url', 'uRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ'], 33 | ['base64urlpad', 'URGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ=='], 34 | ['base256emoji', '🚀💛✋💃✋😻😈🥺🤤🍀🌟💐✋😅✋💦✋🥺🏃😈😴🌟😻😝👏👏'] 35 | ] 36 | }, 37 | { 38 | input: 'yes mani !', 39 | tests: [ 40 | ['identity', '\x00yes mani !'], 41 | ['base2', '001111001011001010111001100100000011011010110000101101110011010010010000000100001'], 42 | ['base8', '7362625631006654133464440102'], 43 | ['base10', '9573277761329450583662625'], 44 | ['base16', 'f796573206d616e692021'], 45 | ['base16upper', 'F796573206D616E692021'], 46 | ['base32', 'bpfsxgidnmfxgsibb'], 47 | ['base32upper', 'BPFSXGIDNMFXGSIBB'], 48 | ['base32hex', 'vf5in683dc5n6i811'], 49 | ['base32hexupper', 'VF5IN683DC5N6I811'], 50 | ['base32pad', 'cpfsxgidnmfxgsibb'], 51 | ['base32padupper', 'CPFSXGIDNMFXGSIBB'], 52 | ['base32hexpad', 'tf5in683dc5n6i811'], 53 | ['base32hexpadupper', 'TF5IN683DC5N6I811'], 54 | ['base32z', 'hxf1zgedpcfzg1ebb'], 55 | ['base36', 'k2lcpzo5yikidynfl'], 56 | ['base36upper', 'K2LCPZO5YIKIDYNFL'], 57 | ['base58flickr', 'Z7Pznk19XTTzBtx'], 58 | ['base58btc', 'z7paNL19xttacUY'], 59 | ['base64', 'meWVzIG1hbmkgIQ'], 60 | ['base64pad', 'MeWVzIG1hbmkgIQ=='], 61 | ['base64url', 'ueWVzIG1hbmkgIQ'], 62 | ['base64urlpad', 'UeWVzIG1hbmkgIQ=='], 63 | ['base256emoji', '🚀🏃✋🌈😅🌷🤤😻🌟😅👏'] 64 | ] 65 | }, 66 | { 67 | input: 'hello world', 68 | tests: [ 69 | ['identity', '\x00hello world'], 70 | ['base2', '00110100001100101011011000110110001101111001000000111011101101111011100100110110001100100'], 71 | ['base8', '7320625543306744035667562330620'], 72 | ['base10', '9126207244316550804821666916'], 73 | ['base16', 'f68656c6c6f20776f726c64'], 74 | ['base16upper', 'F68656C6C6F20776F726C64'], 75 | ['base32', 'bnbswy3dpeb3w64tmmq'], 76 | ['base32upper', 'BNBSWY3DPEB3W64TMMQ'], 77 | ['base32hex', 'vd1imor3f41rmusjccg'], 78 | ['base32hexupper', 'VD1IMOR3F41RMUSJCCG'], 79 | ['base32pad', 'cnbswy3dpeb3w64tmmq======'], 80 | ['base32padupper', 'CNBSWY3DPEB3W64TMMQ======'], 81 | ['base32hexpad', 'td1imor3f41rmusjccg======'], 82 | ['base32hexpadupper', 'TD1IMOR3F41RMUSJCCG======'], 83 | ['base32z', 'hpb1sa5dxrb5s6hucco'], 84 | ['base36', 'kfuvrsivvnfrbjwajo'], 85 | ['base36upper', 'KFUVRSIVVNFRBJWAJO'], 86 | ['base58flickr', 'ZrTu1dk6cWsRYjYu'], 87 | ['base58btc', 'zStV1DL6CwTryKyV'], 88 | ['base64', 'maGVsbG8gd29ybGQ'], 89 | ['base64pad', 'MaGVsbG8gd29ybGQ='], 90 | ['base64url', 'uaGVsbG8gd29ybGQ'], 91 | ['base64urlpad', 'UaGVsbG8gd29ybGQ='], 92 | ['base256emoji', '🚀😴✋🍀🍀😓😅✔😓🥺🍀😳'] 93 | ] 94 | }, 95 | { 96 | input: '\x00yes mani !', 97 | tests: [ 98 | ['identity', '\x00\x00yes mani !'], 99 | ['base2', '00000000001111001011001010111001100100000011011010110000101101110011010010010000000100001'], 100 | ['base8', '7000745453462015530267151100204'], 101 | ['base10', '90573277761329450583662625'], 102 | ['base16', 'f00796573206d616e692021'], 103 | ['base16upper', 'F00796573206D616E692021'], 104 | ['base32', 'bab4wk4zanvqw42jaee'], 105 | ['base32upper', 'BAB4WK4ZANVQW42JAEE'], 106 | ['base32hex', 'v01smasp0dlgmsq9044'], 107 | ['base32hexupper', 'V01SMASP0DLGMSQ9044'], 108 | ['base32pad', 'cab4wk4zanvqw42jaee======'], 109 | ['base32padupper', 'CAB4WK4ZANVQW42JAEE======'], 110 | ['base32hexpad', 't01smasp0dlgmsq9044======'], 111 | ['base32hexpadupper', 'T01SMASP0DLGMSQ9044======'], 112 | ['base32z', 'hybhskh3ypiosh4jyrr'], 113 | ['base36', 'k02lcpzo5yikidynfl'], 114 | ['base36upper', 'K02LCPZO5YIKIDYNFL'], 115 | ['base58flickr', 'Z17Pznk19XTTzBtx'], 116 | ['base58btc', 'z17paNL19xttacUY'], 117 | ['base64', 'mAHllcyBtYW5pICE'], 118 | ['base64pad', 'MAHllcyBtYW5pICE='], 119 | ['base64url', 'uAHllcyBtYW5pICE'], 120 | ['base64urlpad', 'UAHllcyBtYW5pICE='], 121 | ['base256emoji', '🚀🚀🏃✋🌈😅🌷🤤😻🌟😅👏'] 122 | ] 123 | }, 124 | { 125 | input: '\x00\x00yes mani !', 126 | tests: [ 127 | ['identity', '\x00\x00\x00yes mani !'], 128 | ['base2', '0000000000000000001111001011001010111001100100000011011010110000101101110011010010010000000100001'], 129 | ['base8', '700000171312714403326055632220041'], 130 | ['base10', '900573277761329450583662625'], 131 | ['base16', 'f0000796573206d616e692021'], 132 | ['base16upper', 'F0000796573206D616E692021'], 133 | ['base32', 'baaahszltebwwc3tjeaqq'], 134 | ['base32upper', 'BAAAHSZLTEBWWC3TJEAQQ'], 135 | ['base32hex', 'v0007ipbj41mm2rj940gg'], 136 | ['base32hexupper', 'V0007IPBJ41MM2RJ940GG'], 137 | ['base32pad', 'caaahszltebwwc3tjeaqq===='], 138 | ['base32padupper', 'CAAAHSZLTEBWWC3TJEAQQ===='], 139 | ['base32hexpad', 't0007ipbj41mm2rj940gg===='], 140 | ['base32hexpadupper', 'T0007IPBJ41MM2RJ940GG===='], 141 | ['base32z', 'hyyy813murbssn5ujryoo'], 142 | ['base36', 'k002lcpzo5yikidynfl'], 143 | ['base36upper', 'K002LCPZO5YIKIDYNFL'], 144 | ['base58flickr', 'Z117Pznk19XTTzBtx'], 145 | ['base58btc', 'z117paNL19xttacUY'], 146 | ['base64', 'mAAB5ZXMgbWFuaSAh'], 147 | ['base64pad', 'MAAB5ZXMgbWFuaSAh'], 148 | ['base64url', 'uAAB5ZXMgbWFuaSAh'], 149 | ['base64urlpad', 'UAAB5ZXMgbWFuaSAh'], 150 | ['base256emoji', '🚀🚀🚀🏃✋🌈😅🌷🤤😻🌟😅👏'] 151 | ] 152 | } 153 | ] 154 | 155 | describe('spec test', () => { 156 | let index = 0 157 | for (const { input, tests } of encoded) { 158 | describe(`multibase spec ${index++}`, () => { 159 | for (const [name, output] of tests) { 160 | const base = bases[name as keyof typeof bases] 161 | 162 | describe(name, () => { 163 | it('should encode buffer', () => { 164 | const out = base.encode(fromString(input)) 165 | assert.deepStrictEqual(out, output) 166 | }) 167 | 168 | it('should decode string', () => { 169 | assert.deepStrictEqual(base.decode(output), fromString(input)) 170 | }) 171 | }) 172 | } 173 | }) 174 | } 175 | 176 | for (const base of Object.values(bases)) { 177 | it('should fail decode with invalid char', function () { 178 | if (base.name === 'identity') { 179 | return this.skip() 180 | } 181 | 182 | assert.throws(() => base.decode(base.prefix + '^!@$%!#$%@#y'), `Non-${base.name} character`) 183 | }) 184 | } 185 | }) 186 | -------------------------------------------------------------------------------- /test/test-multibase.spec.ts: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | 3 | import { assert } from 'aegir/chai' 4 | import * as b10 from '../src/bases/base10.js' 5 | import * as b16 from '../src/bases/base16.js' 6 | import * as b2 from '../src/bases/base2.js' 7 | import * as b32 from '../src/bases/base32.js' 8 | import * as b36 from '../src/bases/base36.js' 9 | import * as b58 from '../src/bases/base58.js' 10 | import * as b64 from '../src/bases/base64.js' 11 | import * as b8 from '../src/bases/base8.js' 12 | import * as bytes from '../src/bytes.js' 13 | 14 | const { base16, base32, base58btc, base64 } = { ...b16, ...b32, ...b58, ...b64 } 15 | 16 | describe('multibase', () => { 17 | for (const base of [base16, base32, base58btc, base64]) { 18 | describe(`basics ${base.name}`, () => { 19 | it('encode/decode', () => { 20 | const string = base.encode(bytes.fromString('test')) 21 | assert.deepStrictEqual(string[0], base.prefix) 22 | const buffer = base.decode(string) 23 | assert.deepStrictEqual(buffer, bytes.fromString('test')) 24 | }) 25 | 26 | it('pristine backing buffer', () => { 27 | // some deepEqual() libraries go as deep as the backing buffer, make sure it's pristine 28 | const string = base.encode(bytes.fromString('test')) 29 | const buffer = base.decode(string) 30 | const expected = bytes.fromString('test') 31 | assert.deepStrictEqual(new Uint8Array(buffer).join(','), new Uint8Array(expected.buffer).join(',')) 32 | }) 33 | 34 | it('empty', () => { 35 | const str = base.encode(bytes.fromString('')) 36 | assert.deepStrictEqual(str, base.prefix) 37 | assert.deepStrictEqual(base.decode(str), bytes.fromString('')) 38 | }) 39 | 40 | it('bad chars', () => { 41 | const str = base.prefix + '#$%^&*&^%$#' 42 | const msg = `Non-${base.name} character` 43 | assert.throws(() => base.decode(str), msg) 44 | }) 45 | }) 46 | } 47 | 48 | it('encode string failure', () => { 49 | const msg = 'Unknown type, must be binary type' 50 | // @ts-expect-error - expects bytes 51 | assert.throws(() => base32.encode('asdf'), msg) 52 | // @ts-expect-error - expects bytes 53 | assert.throws(() => base32.encoder.encode('asdf'), msg) 54 | }) 55 | 56 | it('decode int failure', () => { 57 | const msg = 'Can only multibase decode strings' 58 | // @ts-expect-error - 'number' is not assignable to parameter of type 'string' 59 | assert.throws(() => base32.decode(1), msg) 60 | // @ts-expect-error - 'number' is not assignable to parameter of type 'string' 61 | assert.throws(() => base32.decoder.decode(1), msg) 62 | }) 63 | 64 | const buff = bytes.fromString('test') 65 | const nonPrintableBuff = Uint8Array.from([239, 250, 254]) 66 | 67 | const baseTest = (bases: typeof b2 | typeof b8 | typeof b10 | typeof b16 | typeof b32 | typeof b36 | typeof b58 | typeof b64): void => { 68 | for (const base of Object.values(bases)) { 69 | if (((base as { name: string })?.name) !== '') { 70 | it(`encode/decode ${base.name}`, () => { 71 | const encoded = base.encode(buff) 72 | const decoded = base.decode(encoded) 73 | assert.deepStrictEqual(decoded, buff) 74 | assert.deepStrictEqual(encoded, base.encoder.encode(buff)) 75 | assert.deepStrictEqual(buff, base.decoder.decode(encoded)) 76 | }) 77 | 78 | it(`encode/decode ${base.name} with non-printable values`, () => { 79 | const encoded = base.encode(nonPrintableBuff) 80 | const decoded = base.decode(encoded) 81 | assert.deepStrictEqual(decoded, nonPrintableBuff) 82 | assert.deepStrictEqual(encoded, base.encoder.encode(nonPrintableBuff)) 83 | assert.deepStrictEqual(nonPrintableBuff, base.decoder.decode(encoded)) 84 | }) 85 | } 86 | } 87 | } 88 | 89 | describe('base2', () => { 90 | baseTest(b2) 91 | }) 92 | 93 | describe('base8', () => { 94 | baseTest(b8) 95 | }) 96 | 97 | describe('base10', () => { 98 | baseTest(b10) 99 | }) 100 | 101 | describe('base16', () => { 102 | baseTest(b16) 103 | }) 104 | 105 | describe('base32', () => { 106 | baseTest(b32) 107 | }) 108 | 109 | describe('base36', () => { 110 | baseTest(b36) 111 | }) 112 | 113 | describe('base58', () => { 114 | baseTest(b58) 115 | }) 116 | 117 | describe('base64', () => { 118 | baseTest(b64) 119 | }) 120 | 121 | it('multibase mismatch', () => { 122 | const b64 = base64.encode(bytes.fromString('test')) 123 | const msg = `Unable to decode multibase string "${b64}", base32 decoder only supports inputs prefixed with ${base32.prefix}` 124 | assert.throws(() => base32.decode(b64), msg) 125 | }) 126 | 127 | it('decoder composition', () => { 128 | const base = base32.decoder.or(base58btc.decoder) 129 | 130 | const b32 = base32.encode(bytes.fromString('test')) 131 | assert.deepStrictEqual(base.decode(b32), bytes.fromString('test')) 132 | 133 | const b58 = base58btc.encode(bytes.fromString('test')) 134 | assert.deepStrictEqual(base.decode(b58), bytes.fromString('test')) 135 | 136 | const b64 = base64.encode(bytes.fromString('test')) 137 | const msg = `Unable to decode multibase string "${b64}", only inputs prefixed with ${base32.prefix},${base58btc.prefix} are supported` 138 | assert.throws(() => base.decode(b64), msg) 139 | 140 | const baseExt = base.or(base64) 141 | assert.deepStrictEqual(baseExt.decode(b64), bytes.fromString('test')) 142 | 143 | // original composition stays intact 144 | assert.throws(() => base.decode(b64), msg) 145 | 146 | // non-composed combined with composed 147 | const baseExt2 = base32.decoder.or(base64.decoder.or(base16.decoder)) 148 | assert.deepStrictEqual(baseExt2.decode(b64), bytes.fromString('test')) 149 | 150 | // composed combined with composed 151 | const baseExt3 = base.or(base64.decoder.or(base16.decoder)) 152 | assert.deepStrictEqual(baseExt3.decode(b64), bytes.fromString('test')) 153 | }) 154 | 155 | it('truncated data', () => { 156 | const b64 = base64.encode(Uint8Array.from([245, 250])) 157 | 158 | assert.throws(() => base64.decode(b64.substring(0, b64.length - 1)), 'Unexpected end of data') 159 | }) 160 | 161 | it('infers prefix and name corretly', () => { 162 | const name = base32.name 163 | 164 | // @ts-expect-error - TS catches mismatch 165 | const name2: 'base16' = base32.name 166 | 167 | const prefix = base32.prefix 168 | assert.equal(prefix, 'b') 169 | assert.equal(name, 'base32') 170 | assert.equal(name2, name) 171 | }) 172 | }) 173 | -------------------------------------------------------------------------------- /test/test-multicodec.spec.ts: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | 3 | import { assert } from 'aegir/chai' 4 | import * as bytes from '../src/bytes.js' 5 | import * as json from '../src/codecs/json.js' 6 | import * as raw from '../src/codecs/raw.js' 7 | 8 | describe('multicodec', () => { 9 | it('encode/decode raw', () => { 10 | const buff = raw.encode(bytes.fromString('test')) 11 | assert.deepStrictEqual(buff, bytes.fromString('test')) 12 | assert.deepStrictEqual(raw.decode(buff), bytes.fromString('test')) 13 | }) 14 | 15 | it('encode/decode raw arraybuffer', () => { 16 | const buff = raw.encode(bytes.fromString('test')) 17 | assert.deepStrictEqual(buff, bytes.fromString('test')) 18 | // @ts-expect-error 19 | assert.deepStrictEqual(raw.decode(buff.buffer), bytes.fromString('test')) 20 | }) 21 | 22 | it('encode/decode json', () => { 23 | const buff = json.encode({ hello: 'world' }) 24 | assert.deepStrictEqual(buff, bytes.fromString(JSON.stringify({ hello: 'world' }))) 25 | assert.deepStrictEqual(json.decode(buff), { hello: 'world' }) 26 | }) 27 | 28 | it('encode/decode json arraybuffer', () => { 29 | const buff = json.encode({ hello: 'world' }) 30 | assert.deepStrictEqual(buff, bytes.fromString(JSON.stringify({ hello: 'world' }))) 31 | // @ts-expect-error 32 | assert.deepStrictEqual(json.decode(buff.buffer), { hello: 'world' }) 33 | }) 34 | 35 | it('raw cannot encode string', async () => { 36 | // @ts-expect-error - 'string' is not assignable to parameter of type 'Uint8Array' 37 | assert.throws(() => raw.encode('asdf'), 'Unknown type, must be binary type') 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/test-multihash.spec.ts: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | 3 | import { hash as slSha256 } from '@stablelib/sha256' 4 | import { hash as slSha512 } from '@stablelib/sha512' 5 | import { assert } from 'aegir/chai' 6 | import { sha1 as chSha1 } from 'crypto-hash' 7 | import { fromHex, fromString } from '../src/bytes.js' 8 | import { decode as decodeDigest, create as createDigest, hasCode as digestHasCode } from '../src/hashes/digest.js' 9 | import { identity } from '../src/hashes/identity.js' 10 | import { sha1 } from '../src/hashes/sha1.js' 11 | import { sha256, sha512 } from '../src/hashes/sha2.js' 12 | import invalid from './fixtures/invalid-multihash.js' 13 | import valid from './fixtures/valid-multihash.js' 14 | import type { MultihashDigest } from '../src/cid.js' 15 | 16 | const sample = (code: number | string, size: number, hex: string): Uint8Array => { 17 | const toHex = (i: number | string): string => { 18 | if (typeof i === 'string') { return i } 19 | const h = i.toString(16) 20 | return h.length % 2 === 1 ? `0${h}` : h 21 | } 22 | return fromHex(`${toHex(code)}${toHex(size)}${hex}`) 23 | } 24 | 25 | describe('multihash', () => { 26 | const empty = new Uint8Array(0) 27 | 28 | describe('encode', () => { 29 | it('valid', () => { 30 | for (const test of valid) { 31 | const { encoding, hex, size } = test 32 | const { code, varint } = encoding 33 | const buf = sample(varint ?? code, size, hex) 34 | assert.deepStrictEqual(createDigest(code, (hex !== '') ? fromHex(hex) : empty).bytes, buf) 35 | } 36 | }) 37 | 38 | it('hash sha1', async () => { 39 | const hash = await sha1.digest(fromString('test')) 40 | assert.deepStrictEqual(hash.code, sha1.code) 41 | assert.deepStrictEqual(hash.digest, fromHex(await chSha1(fromString('test')))) 42 | 43 | const hash2 = decodeDigest(hash.bytes) 44 | assert.deepStrictEqual(hash2.code, sha1.code) 45 | assert.deepStrictEqual(hash2.bytes, hash.bytes) 46 | }) 47 | 48 | if (typeof navigator === 'undefined') { 49 | it('sync sha1', async () => { 50 | const hash = sha1.digest(fromString('test')) 51 | if (hash instanceof Promise) { 52 | assert.fail('expected sync result') 53 | } else { 54 | assert.deepStrictEqual(hash.code, sha1.code) 55 | assert.deepStrictEqual(hash.digest, fromHex(await chSha1(fromString('test')))) 56 | 57 | const hash2 = decodeDigest(hash.bytes) 58 | assert.deepStrictEqual(hash2.code, sha1.code) 59 | assert.deepStrictEqual(hash2.bytes, hash.bytes) 60 | } 61 | }) 62 | } 63 | 64 | it('hash sha2-256', async () => { 65 | const hash = await sha256.digest(fromString('test')) 66 | assert.deepStrictEqual(hash.code, sha256.code) 67 | assert.deepStrictEqual(hash.digest, slSha256(fromString('test'))) 68 | 69 | const hash2 = decodeDigest(hash.bytes) 70 | assert.deepStrictEqual(hash2.code, sha256.code) 71 | assert.deepStrictEqual(hash2.bytes, hash.bytes) 72 | }) 73 | 74 | if (typeof navigator === 'undefined') { 75 | it('sync sha-256', () => { 76 | const hash = sha256.digest(fromString('test')) 77 | if (hash instanceof Promise) { 78 | assert.fail('expected sync result') 79 | } else { 80 | assert.deepStrictEqual(hash.code, sha256.code) 81 | assert.deepStrictEqual(hash.digest, slSha256(fromString('test'))) 82 | 83 | const hash2 = decodeDigest(hash.bytes) 84 | assert.deepStrictEqual(hash2.code, sha256.code) 85 | assert.deepStrictEqual(hash2.bytes, hash.bytes) 86 | } 87 | }) 88 | } 89 | 90 | it('hash sha2-512', async () => { 91 | const hash = await sha512.digest(fromString('test')) 92 | assert.deepStrictEqual(hash.code, sha512.code) 93 | assert.deepStrictEqual(hash.digest, slSha512(fromString('test'))) 94 | 95 | const hash2 = decodeDigest(hash.bytes) 96 | assert.deepStrictEqual(hash2.code, sha512.code) 97 | assert.deepStrictEqual(hash2.bytes, hash.bytes) 98 | }) 99 | 100 | it('hash identity async', async () => { 101 | // eslint-disable-next-line @typescript-eslint/await-thenable 102 | const hash = await identity.digest(fromString('test')) 103 | assert.deepStrictEqual(hash.code, identity.code) 104 | assert.deepStrictEqual(identity.code, 0) 105 | assert.deepStrictEqual(hash.digest, fromString('test')) 106 | 107 | const hash2 = decodeDigest(hash.bytes) 108 | assert.deepStrictEqual(hash2.code, identity.code) 109 | assert.deepStrictEqual(hash2.bytes, hash.bytes) 110 | }) 111 | 112 | it('hash identity sync', () => { 113 | const hash = identity.digest(fromString('test')) 114 | assert.deepStrictEqual(hash.code, identity.code) 115 | assert.deepStrictEqual(identity.code, 0) 116 | assert.deepStrictEqual(hash.digest, fromString('test')) 117 | 118 | const hash2 = decodeDigest(hash.bytes) 119 | assert.deepStrictEqual(hash2.code, identity.code) 120 | assert.deepStrictEqual(hash2.bytes, hash.bytes) 121 | }) 122 | }) 123 | describe('decode', () => { 124 | for (const { encoding, hex, size } of valid) { 125 | it(`valid fixture ${hex}`, () => { 126 | const { code, varint } = encoding 127 | const bytes = sample(varint ?? code, size, hex) 128 | const digest = (hex !== '') ? fromHex(hex) : empty 129 | const hash = decodeDigest(bytes) 130 | 131 | assert.deepStrictEqual(hash.bytes, bytes) 132 | assert.deepStrictEqual(hash.code, code) 133 | assert.deepStrictEqual(hash.size, size) 134 | assert.deepStrictEqual(hash.digest, digest) 135 | }) 136 | } 137 | 138 | it('get from buffer', async () => { 139 | const hash = await sha256.digest(fromString('test')) 140 | 141 | assert.deepStrictEqual(hash.code, 18) 142 | }) 143 | }) 144 | 145 | describe('validate', async () => { 146 | it('invalid fixtures', async () => { 147 | for (const test of invalid) { 148 | const buff = fromHex(test.hex) 149 | assert.throws(() => decodeDigest(buff), test.message) 150 | } 151 | }) 152 | }) 153 | 154 | it('throw on hashing non-buffer', async () => { 155 | try { 156 | // @ts-expect-error - string is incompatible arg 157 | await sha256.digest('asdf') 158 | } catch (error) { 159 | assert.match(String(error), /Unknown type, must be binary type/) 160 | } 161 | }) 162 | 163 | describe('hasCode', () => { 164 | it('asserts that a multihash has the expected code', () => { 165 | const buf = Uint8Array.from([0, 1, 2, 3]) 166 | 167 | // remove code type from MultihashDigest 168 | const hash = decodeDigest(identity.digest(buf).bytes) 169 | 170 | // a function that requires a specific type of multihash 171 | function needIdentity (_: MultihashDigest<0x0>): void { 172 | 173 | } 174 | 175 | assert.isTrue(digestHasCode(hash, identity.code)) 176 | 177 | // @ts-expect-error fails to compile as hash is MultihashDigest 178 | needIdentity(hash) 179 | 180 | // hint to tsc that hash is MultihashDigest<0x0> 181 | if (digestHasCode(hash, identity.code)) { 182 | needIdentity(hash) 183 | } 184 | }) 185 | }) 186 | }) 187 | -------------------------------------------------------------------------------- /test/test-traversal.spec.ts: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | 3 | import { assert } from 'aegir/chai' 4 | import * as main from '../src/block.js' 5 | import { fromString } from '../src/bytes.js' 6 | import * as codec from '../src/codecs/json.js' 7 | import { sha256 as hasher } from '../src/hashes/sha2.js' 8 | import { walk } from '../src/traversal.js' 9 | import type { BlockView } from '../src/block/interface.js' 10 | import type { CID } from '../src/cid.js' 11 | 12 | // from dag-pb, simplified 13 | function createNode (data: Uint8Array, links: Array<{ Hash: CID, Name: string, Tsize: number }>): { Data: Uint8Array, Links: Array<{ Hash: CID, Name: string, Tsize: number }> } { 14 | return { Data: data, Links: links } 15 | } 16 | 17 | function createLink (name: string, size: number, cid: CID): { Hash: CID, Name: string, Tsize: number } { 18 | return { Hash: cid, Name: name, Tsize: size } 19 | } 20 | 21 | describe('traversal', () => { 22 | describe('walk', async () => { 23 | // Forming the following DAG for testing 24 | // A 25 | // / \ 26 | // B C 27 | // / \ / \ 28 | // D D D E 29 | const linksE = [] as [] 30 | const valueE = createNode(fromString('string E qacdswa'), linksE) 31 | const blockE = await main.encode({ value: valueE, codec, hasher }) 32 | const cidE = blockE.cid 33 | 34 | const linksD = [] as [] 35 | const valueD = createNode(fromString('string D zasa'), linksD) 36 | const blockD = await main.encode({ value: valueD, codec, hasher }) 37 | const cidD = blockD.cid 38 | 39 | const linksC = [createLink('link1', 100, cidD), createLink('link2', 100, cidE)] 40 | const valueC = createNode(fromString('string C zxc'), linksC) 41 | const blockC = await main.encode({ value: valueC, codec, hasher }) 42 | const cidC = blockC.cid 43 | 44 | const linksB = [createLink('link1', 100, cidD), createLink('link2', 100, cidD)] 45 | const valueB = createNode(fromString('string B lpokjiasd'), linksB) 46 | const blockB = await main.encode({ value: valueB, codec, hasher }) 47 | const cidB = blockB.cid 48 | 49 | const linksA = [createLink('link1', 100, cidB), createLink('link2', 100, cidC)] 50 | const valueA = createNode(fromString('string A qwertcfdgshaa'), linksA) 51 | const blockA = await main.encode({ value: valueA, codec, hasher }) 52 | const cidA = blockA.cid 53 | 54 | const load = async (cid: CID): Promise | null> => { 55 | if (cid.equals(cidE)) { 56 | return blockE 57 | } 58 | if (cid.equals(cidD)) { 59 | return blockD 60 | } 61 | if (cid.equals(cidC)) { 62 | return blockC 63 | } 64 | if (cid.equals(cidB)) { 65 | return blockB 66 | } 67 | if (cid.equals(cidA)) { 68 | return blockA 69 | } 70 | return null 71 | } 72 | 73 | type loadFn = typeof load 74 | 75 | const loadWrapper = (load: loadFn, arr: string[] = []) => 76 | async (cid: CID): Promise | null> => { 77 | arr.push(cid.toString()) 78 | return load(cid) 79 | } 80 | 81 | it('block with no links', async () => { 82 | // Test Case 1 83 | // Input DAG 84 | // D 85 | // 86 | // Expect load to be called with D 87 | const expectedCallArray = [cidD.toString()] 88 | const callArray: string[] = [] 89 | 90 | await walk({ cid: cidD, load: loadWrapper(load, callArray) }) 91 | 92 | expectedCallArray.forEach((value, index) => { 93 | assert.deepStrictEqual(value, callArray[index]) 94 | }) 95 | }) 96 | 97 | it('block with links', async () => { 98 | // Test Case 2 99 | // Input 100 | // C 101 | // / \ 102 | // D E 103 | // 104 | // Expect load to be called with C, then D, then E 105 | const expectedCallArray = [cidC.toString(), cidD.toString(), cidE.toString()] 106 | const callArray: string[] = [] 107 | 108 | await walk({ cid: cidC, load: loadWrapper(load, callArray) }) 109 | 110 | expectedCallArray.forEach((value, index) => { 111 | assert.deepStrictEqual(value, callArray[index]) 112 | }) 113 | }) 114 | 115 | it('block with matching links', async () => { 116 | // Test Case 3 117 | // Input 118 | // B 119 | // / \ 120 | // D D 121 | // 122 | // Expect load to be called with B, then D 123 | const expectedCallArray = [cidB.toString(), cidD.toString()] 124 | const callArray: string[] = [] 125 | 126 | await walk({ cid: cidB, load: loadWrapper(load, callArray) }) 127 | 128 | expectedCallArray.forEach((value, index) => { 129 | assert.deepStrictEqual(value, callArray[index]) 130 | }) 131 | }) 132 | 133 | it('depth first with duplicated block', async () => { 134 | // Test Case 4 135 | // Input 136 | // A 137 | // / \ 138 | // B C 139 | // / \ / \ 140 | // D D D E 141 | // 142 | // Expect load to be called with A, then B, then D, then C, then E 143 | const expectedCallArray = [ 144 | cidA.toString(), 145 | cidB.toString(), 146 | cidD.toString(), 147 | cidC.toString(), 148 | cidE.toString() 149 | ] 150 | const callArray: string[] = [] 151 | 152 | await walk({ cid: cidA, load: loadWrapper(load, callArray) }) 153 | 154 | expectedCallArray.forEach((value, index) => { 155 | assert.deepStrictEqual(value, callArray[index]) 156 | }) 157 | }) 158 | 159 | it('null return', async () => { 160 | const links = [] as [] 161 | const value = createNode(fromString('test'), links) 162 | const block = await main.encode({ value, codec, hasher }) 163 | const cid = block.cid 164 | const expectedCallArray = [cid.toString()] 165 | const callArray: string[] = [] 166 | 167 | await walk({ cid, load: loadWrapper(load, callArray) }) 168 | 169 | expectedCallArray.forEach((value, index) => { 170 | assert.deepStrictEqual(value, callArray[index]) 171 | }) 172 | }) 173 | }) 174 | }) 175 | -------------------------------------------------------------------------------- /test/test-varint.spec.ts: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | 3 | import { assert } from 'aegir/chai' 4 | import { varint } from '../src/index.js' 5 | 6 | const UTF8 = new TextEncoder() 7 | 8 | describe('varint', () => { 9 | it('can decode with offset', () => { 10 | const message = UTF8.encode('hello-world') 11 | const outerTag = 0x55 12 | const innerTag = 0xe3 13 | const outerTagSize = varint.encodingLength(outerTag) 14 | const innerTagSize = varint.encodingLength(innerTag) 15 | 16 | const bytes = new Uint8Array(message.byteLength + outerTagSize + innerTagSize) 17 | varint.encodeTo(outerTag, bytes) 18 | varint.encodeTo(innerTag, bytes, outerTagSize) 19 | bytes.set(message, outerTagSize + innerTagSize) 20 | 21 | assert.deepStrictEqual(varint.decode(bytes), [outerTag, outerTagSize]) 22 | assert.deepStrictEqual(varint.decode(bytes, outerTagSize), [innerTag, innerTagSize]) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "aegir/src/config/tsconfig.aegir.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": [ 7 | "src", 8 | "test" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": [ 3 | "./src/index.ts", 4 | "./src/bases/base10.ts", 5 | "./src/bases/base16.ts", 6 | "./src/bases/base2.ts", 7 | "./src/bases/base256emoji.ts", 8 | "./src/bases/base32.ts", 9 | "./src/bases/base36.ts", 10 | "./src/bases/base58.ts", 11 | "./src/bases/base64.ts", 12 | "./src/bases/base8.ts", 13 | "./src/bases/identity.ts", 14 | "./src/bases/interface.ts", 15 | "./src/basics.ts", 16 | "./src/block.ts", 17 | "./src/block/interface.ts", 18 | "./src/bytes.ts", 19 | "./src/cid.ts", 20 | "./src/codecs/interface.ts", 21 | "./src/codecs/json.ts", 22 | "./src/codecs/raw.ts", 23 | "./src/hashes/digest.ts", 24 | "./src/hashes/hasher.ts", 25 | "./src/hashes/identity.ts", 26 | "./src/hashes/interface.ts", 27 | "./src/hashes/sha1.ts", 28 | "./src/hashes/sha2.ts", 29 | "./src/interface.ts", 30 | "./src/link.ts", 31 | "./src/link/interface.ts", 32 | "./src/traversal.ts" 33 | ], 34 | "includeVersion": true 35 | } 36 | --------------------------------------------------------------------------------