Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 22 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,33 @@ This allows you to pass around an interface containing only the code you need
which can greatly reduce dependencies and bundle size.

```js
import { create } from 'multiformats'
import sha2 from 'multiformats/hashes/sha2'
import * as CID from 'multiformats/cid'
import { sha256 } from 'multiformats/hashes/sha2'
import dagcbor from '@ipld/dag-cbor'
const { multihash, multicodec, CID } = create()
multihash.add(sha2)
multicodec.add(dagcbor)
import { base32 } from 'multiformats/bases/base32'
import { base58btc } from 'multiformats/bases/base58'

const buffer = multicodec.encode({ hello, 'world' }, 'dag-cbor')
const hash = await multihash.hash(buffer, 'sha2-256')
const bytes = dagcbor.encode({ hello: 'world' })

const hash = await sha256.digest(bytes)
// raw codec is the only codec that is there by default
const cid = new CID(1, 'raw', hash)
const cid = CID.create(1, dagcbor.code, hash, {
base: base32,
base58btc
})
Comment on lines +27 to +29

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this mean exactly?

i think i’d prefer this to be { base32, base58btc }

we actually need the default behavior of toString() without a requested base encoding to be stable, so we shouldn’t have a way to set a different default base encoding here. so simply passing in the implementations should be sufficient.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this mean exactly?

Comments in the ./cid/interface.ts attempt to clarify that. Inlining here for convenience:

export interface Config {
  /**
   * Multibase codec used by CID to encode / decode to and out of
   * string representation.
   */
  base: MultibaseCodec<any>
  /**
   * CIDv0 requires base58btc encoding decoding so CID must be
   * provided means to perform that task.
   */
  base58btc: MultibaseCodec<'z'>
} 

i think i’d prefer this to be { base32, base58btc }

Well base supposed to represent base codec for whatever encoding you choose to use for the CID been created. If you make it base32 that would mean CIDs could only be in base32 encoding. In fact if anything base58btc is kind of outlier here, which only there to support toV0 and I kind of wish passing it was unnecessary.

we actually need the default behavior of toString() without a requested base encoding to be stable, so we shouldn’t have a way to set a different default base encoding here. so simply passing in the implementations should be sufficient.

Are you saying user should not be able to create CID with a different base encoding ? Or simply that decision about encoding should be deferred until toString() is called and it should default to base32 if no encodnig is passed ?

If later, I understand your argument. However this config is also used for parsing CID in string representations so that when you do following:

const c1 = cid.parse('mAXASIDHD1XCA2EY6PGOykj31odQK16c+rloUr1hCE+X1BKwz', {
  base: base64,
  base58btc: base58btc
})
c1.toString() // mAXASIDHD1XCA2EY6PGOykj31odQK16c+rloUr1hCE+X1BKwz

Would you expect c1.toString() to print bafybeibrypkxbagyiy5dyy5ssi67lioubll2opvolikk6wcccps7kbfmgm instead ?

If you expect to print in base32 then { base32, base58btc } as CID config makes sense. If you expect it to print in base64 than I'd say current config makes more sense.

```

However, if you're doing this much you should probably use multiformats
with the `Block` API.

```js
// Import basics package with dep-free codecs, hashes, and base encodings
import multiformats from 'multiformats/basics'
import { block } from 'multiformats/basics'
import dagcbor from '@ipld/dag-cbor'
import { create } from '@ipld/block' // Yet to be released Block interface
multiformats.multicodec.add(dagcbor)
const Block = create(multiformats)
const block = Block.encoder({ hello: world }, 'dag-cbor')
const cid = await block.cid()

const encoder = block.encoder(dagcbor)
const hello = encoder.encode({ hello: 'world' })
const cid = await hello.cid()
```

# Plugins
Expand Down Expand Up @@ -83,35 +85,17 @@ Returns a new multiformats interface.

Can optionally pass in a table of multiformat entries.

# multihash

## multihash.encode

## multihash.decode

## multihash.validate

## multihash.add

## multihash.hash

# multicodec

## multicodec.encode

## multicodec.decode

## multicodec.add
## multiformats.configure

# multibase
## multiformats.varint

## multibase.encode
## multiformats.bytes

## multibase.decode
## multiformats.digest

## multibase.add
## multiformats.hasher

# CID
# multiformats.CID

Changes from `cids`:

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{
"name": "multiformats",
"version": "0.0.0-dev",
"description": "Interface for multihash, multicodec, multibase and CID.",
"description": "Interface for multihash, multicodec, multibase and CID",
"main": "index.js",
"type": "module",
"scripts": {
"build": "npm_config_yes=true npx ipjs@latest build --tests",
"build:vendor": "npx brrp -x varint > vendor/varint.js",
"publish": "npm_config_yes=true npx ipjs@latest publish",
"lint": "standard",
"test:cjs": "npm run build && mocha dist/cjs/node-test/test-*.js && npm run test:cjs:browser",
Expand Down Expand Up @@ -71,8 +72,7 @@
"dependencies": {
"base-x": "^3.0.8",
"buffer": "^5.6.0",
"cids": "^1.0.0",
"varint": "^5.0.0"
"cids": "^1.0.0"
},
"directories": {
"test": "test"
Expand Down
205 changes: 205 additions & 0 deletions src/bases/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// @ts-check

/**
* @typedef {import('./interface').BaseEncoder} BaseEncoder
* @typedef {import('./interface').BaseDecoder} BaseDecoder
* @typedef {import('./interface').BaseCodec} BaseCodec
*/

/**
* @template T
* @typedef {import('./interface').Multibase<T>} Multibase
*/
/**
* @template T
* @typedef {import('./interface').MultibaseEncoder<T>} MultibaseEncoder
*/

/**
* Class represents both BaseEncoder and MultibaseEncoder meaning it
* can be used to encode to multibase or base encode without multibase
* prefix.
* @class
* @template {string} Base
* @template {string} Prefix
* @implements {MultibaseEncoder<Prefix>}
* @implements {BaseEncoder}
*/
class Encoder {
/**
* @param {Base} name
* @param {Prefix} prefix
* @param {(bytes:Uint8Array) => string} baseEncode
*/
constructor (name, prefix, baseEncode) {
this.name = name
this.prefix = prefix
this.baseEncode = baseEncode
}

/**
* @param {Uint8Array} bytes
* @returns {Multibase<Prefix>}
*/
encode (bytes) {
// @ts-ignore
return `${this.prefix}${this.baseEncode(bytes)}`
}
}

/**
* @template T
* @typedef {import('./interface').MultibaseDecoder<T>} MultibaseDecoder
*/

/**
* Class represents both BaseDecoder and MultibaseDecoder so it could be used
* to decode multibases (with matching prefix) or just base decode strings
* with corresponding base encoding.
* @class
* @template {string} Base
* @template {string} Prefix
* @implements {MultibaseDecoder<Prefix>}
* @implements {BaseDecoder}
*/
class Decoder {
/**
* @param {Base} name
* @param {Prefix} prefix
* @param {(text:string) => Uint8Array} baseDecode
*/
constructor (name, prefix, baseDecode) {
this.name = name
this.prefix = prefix
this.baseDecode = baseDecode
}

/**
* @param {string} text
*/
decode (text) {
switch (text[0]) {
case this.prefix: {
return this.baseDecode(text.slice(1))
}
default: {
throw Error(`${this.name} expects input starting with ${this.prefix} and can not decode "${text}"`)
}
}
}
}

/**
* @template T
* @typedef {import('./interface').MultibaseCodec<T>} MultibaseCodec
*/

/**
* @class
* @template {string} Base
* @template {string} Prefix
* @implements {MultibaseCodec<Prefix>}
* @implements {MultibaseEncoder<Prefix>}
* @implements {MultibaseDecoder<Prefix>}
* @implements {BaseCodec}
* @implements {BaseEncoder}
* @implements {BaseDecoder}
*/
export class Codec {
/**
* @param {Base} name
* @param {Prefix} prefix
* @param {(bytes:Uint8Array) => string} baseEncode
* @param {(text:string) => Uint8Array} baseDecode
*/
constructor (name, prefix, baseEncode, baseDecode) {
this.name = name
this.prefix = prefix
this.baseEncode = baseEncode
this.baseDecode = baseDecode
this.encoder = new Encoder(name, prefix, baseEncode)
this.decoder = new Decoder(name, prefix, baseDecode)
}

/**
* @param {Uint8Array} input
*/
encode (input) {
return this.encoder.encode(input)
}

decode (input) {
return this.decoder.decode(input)
}
}

/**
* @template {string} Base
* @template {string} Prefix
* @param {Object} options
* @param {Base} options.name
* @param {Prefix} options.prefix
* @param {string} options.alphabet
* @param {(input:Uint8Array, alphabet:string) => string} options.encode
* @param {(input:string, alphabet:string) => Uint8Array} options.decode
*/
export const withAlphabet = ({ name, prefix, encode, decode, alphabet }) =>
from({
name,
prefix,
encode: input => encode(input, alphabet),
decode: input => {
for (const char of input) {
if (alphabet.indexOf(char) < 0) {
throw new Error(`invalid ${name} character`)
}
}
return decode(input, alphabet)
}
})

/**
* @template {string} Base
* @template {string} Prefix
* @template Settings
*
* @param {Object} options
* @param {Base} options.name
* @param {Prefix} options.prefix
* @param {Settings} options.settings
* @param {(input:Uint8Array, settings:Settings) => string} options.encode
* @param {(input:string, settings:Settings) => Uint8Array} options.decode
*/

export const withSettings = ({ name, prefix, settings, encode, decode }) =>
from({
name,
prefix,
encode: (input) => encode(input, settings),
decode: (input) => decode(input, settings)
})

/**
* @template {string} Base
* @template {string} Prefix
* @param {Object} options
* @param {Base} options.name
* @param {Prefix} options.prefix
* @param {(bytes:Uint8Array) => string} options.encode
* @param {(input:string) => Uint8Array} options.decode
* @returns {Codec<Base, Prefix>}
*/
export const from = ({ name, prefix, encode, decode }) =>
new Codec(name, prefix, encode, decode)

export const notImplemented = ({ name, prefix }) =>
from({
name,
prefix,
encode: _ => {
throw Error(`No ${name} encoder implementation was provided`)
},
decode: _ => {
throw Error(`No ${name} decoder implemnetation was provided`)
}
})
25 changes: 10 additions & 15 deletions src/bases/base16.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { fromHex, toHex } from '../bytes.js'
// @ts-check

const create = function base16 (alphabet) {
return {
encode: input => toHex(input),
decode (input) {
for (const char of input) {
if (alphabet.indexOf(char) < 0) {
throw new Error('invalid base16 character')
}
}
return fromHex(input)
}
}
}
import { fromHex, toHex } from '../bytes.js'
import { withAlphabet } from './base.js'

export default { prefix: 'f', name: 'base16', ...create('0123456789abcdef') }
export const base16 = withAlphabet({
prefix: 'f',
name: 'base16',
alphabet: '0123456789abcdef',
encode: toHex,
decode: fromHex
})
Loading