Add comprehensive installation and setup documentation

- Add GETTING_STARTED.md with quick start guide and development modes
- Add INSTALL.sh automated installation script
- Add INSTALLATION_CHECKLIST.md, INSTALLATION_SUCCESS.md, and INSTALLATION_SUMMARY.md
- Add QUICK_REFERENCE.md for common commands
- Add SETUP_GUIDE.md with detailed setup instructions
- Update README.md with improved project overview
- Add did-wallet app dependencies and node_modules
This commit is contained in:
Dorian
2026-01-27 17:18:21 +00:00
parent a81f655133
commit 0d073fa89e
22658 changed files with 4494151 additions and 6 deletions
@@ -0,0 +1,36 @@
export default [{
code: 0x00,
size: 32,
hex: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33',
message: 'Incorrect length'
}, {
code: 0x11,
size: 21,
hex: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33',
message: 'Incorrect length'
}, {
code: 0x11,
size: 20,
hex: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a',
message: 'Incorrect length'
}, {
code: 0x11,
size: 20,
hex: '',
message: 'Could not decode varint'
}, {
code: 0x31,
size: 20,
hex: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33',
message: 'Incorrect length'
}, {
code: 0x12,
size: 32,
hex: '2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7',
message: 'Incorrect length'
}, {
code: 0x12,
size: 32,
hex: '1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad0000',
message: 'Incorrect length'
}]
@@ -0,0 +1,120 @@
export default [
{
encoding: {
code: 0x11,
name: 'sha1'
},
hex: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33',
size: 20
}, {
encoding: {
code: 0x11,
name: 'sha1'
},
hex: '0beec7b8',
size: 4
}, {
encoding: {
code: 0x12,
name: 'sha2-256'
},
hex: '2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae',
size: 32
}, {
encoding: {
code: 0x12,
name: 'sha2-256'
},
hex: '2c26b46b',
size: 4
}, {
encoding: {
code: 0x0,
name: 'identity'
},
hex: '7465737420737472696e6720f09f918d',
size: 16
},
{
encoding: {
code: 0x0,
name: 'identity'
},
hex: '2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae',
size: 32
},
{
encoding: {
code: 0x00,
name: 'identity'
},
hex: '',
size: 0
},
{
encoding: {
code: 0x11,
name: 'sha1'
},
hex: '0beec7b5',
size: 4
},
{
encoding: {
code: 0xb240,
name: 'blake2b-512',
varint: 'c0e402'
},
hex: '2c26b46b68ffc68ff99b453c1d30413413',
size: 17
},
{
encoding: {
code: 0x22,
name: 'murmur3-128'
},
hex: '243ddb9e',
size: 4
},
{
encoding: {
code: 0x1b,
name: 'keccak-256'
},
hex: 'f00ba4',
size: 3
},
{
encoding: {
code: 0x18,
name: 'shake-128'
},
hex: 'f84e95cb5fbd2038863ab27d3cdeac295ad2d4ab96ad1f4b070c0bf36078ef08',
size: 32
},
{
encoding: {
code: 0x19,
name: 'shake-256'
},
hex: '1af97f7818a28edfdfce5ec66dbdc7e871813816d7d585fe1f12475ded5b6502b7723b74e2ee36f2651a10a8eaca72aa9148c3c761aaceac8f6d6cc64381ed39',
size: 64
},
{
encoding: {
code: 0x14,
name: 'sha3-512'
},
hex: '4bca2b137edc580fe50a88983ef860ebaca36c857b1f492839d6d7392452a63c82cbebc68e3b70a2a1480b4bb5d437a7cba6ecf9d89f9ff3ccd14cd6146ea7e7',
size: 64
},
{
encoding: {
code: 0xd5,
name: 'md5',
varint: 'd501'
},
hex: 'd41d8cd98f00b204e9800998ecf8427e',
size: 16
}
]
+153
View File
@@ -0,0 +1,153 @@
/* globals describe, it */
import * as codec from '../src/codecs/json.js'
import { sha256 as hasher } from '../src/hashes/sha2.js'
import * as main from '../src/block.js'
import { CID, bytes } from '../src/index.js'
import { assert } from 'aegir/chai'
const fixture = { hello: 'world' }
const link = CID.parse('bafyreidykglsfhoixmivffc5uwhcgshx4j465xwqntbmu43nb2dzqwfvae')
const buff = bytes.fromString('sadf')
describe('block', () => {
it('basic encode/decode roundtrip', async () => {
const block = await main.encode({ value: fixture, codec, hasher })
const block2 = await main.decode({ bytes: block.bytes, codec, hasher })
assert.deepStrictEqual(block.cid.equals(block2.cid), true)
assert.deepStrictEqual(block.cid.equals(block2.cid), true)
assert.deepStrictEqual(fixture, block2.value)
const block3 = await main.create({ bytes: block.bytes, cid: block.cid, codec, hasher })
assert.deepStrictEqual(block3.cid.equals(block2.cid), true)
})
it('createUnsafe', async () => {
const block = await main.encode({ value: fixture, codec, hasher })
const block2 = main.createUnsafe({ bytes: block.bytes, cid: block.cid, codec })
assert.deepStrictEqual(block.cid.equals(block2.cid), true)
})
describe('reader', () => {
const value = {
link,
nope: 'skip',
arr: [link],
obj: { arr: [{ obj: {} }] },
// @ts-expect-error - 'string' is not assignable to parameter of type 'ArrayLike<number>'
bytes: Uint8Array.from('1234')
}
// @ts-expect-error - 'boolean' is not assignable to type 'CID'
const block = main.createUnsafe({ value, codec, hasher, cid: true, bytes: true })
it('links', () => {
const expected = ['link', 'arr/0']
for (const [path, cid] of block.links()) {
assert.deepStrictEqual(path, expected.shift())
assert.deepStrictEqual(cid.toString(), link.toString())
}
})
it('tree', () => {
const expected = ['link', 'nope', 'arr', 'arr/0', 'obj', 'obj/arr', 'obj/arr/0', 'obj/arr/0/obj', 'bytes']
for (const path of block.tree()) {
assert.deepStrictEqual(path, expected.shift())
}
})
it('get', () => {
let ret = block.get('link/test')
assert.deepStrictEqual(ret.remaining, 'test')
assert.deepStrictEqual(String(ret.value), link.toString())
ret = block.get('nope')
assert.deepStrictEqual(ret, { value: 'skip' })
})
it('null links/tree', () => {
const block = main.createUnsafe({
value: null,
codec,
hasher,
// @ts-expect-error - 'boolean' is not assignable to type 'ByteView<unknown>'
bytes: true,
// @ts-expect-error - 'boolean' is not assignable to type 'CID'
cid: true
})
// eslint-disable-next-line
for (const x of block.tree()) {
throw new Error(`tree should have nothing, got "${x}"`)
}
// eslint-disable-next-line
for (const x of block.links()) {
throw new Error(`links should have nothing, got "${x}"`)
}
})
})
it('links of a block that is a CID', async () => {
const block = await main.encode({ value: link, codec, hasher })
const links = []
for (const link of block.links()) {
links.push(link)
}
assert.equal(links.length, 1)
assert.equal(links[0][0], '')
assert.equal(links[0][1].toString(), link.toString())
})
it('kitchen sink', () => {
const sink = { one: { two: { arr: [true, false, null], three: 3, buff, link } } }
const block = main.createUnsafe({
value: sink,
codec,
// @ts-expect-error - 'boolean' is not assignable to type 'ByteView<unknown>'
bytes: true,
// @ts-expect-error - 'boolean' is not assignable to type 'CID'
cid: true
})
assert.deepStrictEqual(sink, block.value)
})
describe('errors', () => {
it('constructor missing args', () => {
assert.throws(
// @ts-expect-error - missing properties
() => new main.Block({}),
'Missing required argument'
)
})
it('encode', async () => {
// @ts-expect-error
await assert.isRejected(main.encode({}), 'Missing required argument "value"')
// @ts-expect-error
await assert.isRejected(main.encode({ value: true }), 'Missing required argument: codec or hasher')
})
it('decode', async () => {
// @ts-expect-error
await assert.isRejected(main.decode({}), 'Missing required argument "bytes"')
// @ts-expect-error
await assert.isRejected(main.decode({ bytes: true }), 'Missing required argument: codec or hasher')
})
it('createUnsafe', async () => {
// @ts-expect-error
assert.throws(() => main.createUnsafe({}), 'Missing required argument, must either provide "value" or "codec"')
})
it('create', async () => {
// @ts-expect-error
await assert.isRejected(main.create({}), 'Missing required argument "bytes"')
// @ts-expect-error
await assert.isRejected(main.create({ bytes: true }), 'Missing required argument "hasher"')
const block = await main.encode({ value: fixture, codec, hasher })
const block2 = await main.encode({ value: { ...fixture, test: 'blah' }, codec, hasher })
await assert.isRejected(main.create({ bytes: block.bytes, cid: block2.cid, codec, hasher }), 'CID hash does not match bytes')
})
it('get', async () => {
const block = await main.encode({ value: fixture, codec, hasher })
assert.throws(() => block.get('/asd/fs/dfasd/f'), 'Object has no property at ["asd"]')
})
})
})
+26
View File
@@ -0,0 +1,26 @@
/* globals describe, it */
import * as bytes from '../src/bytes.js'
import { assert } from 'aegir/chai'
describe('bytes', () => {
it('isBinary', () => {
assert.deepStrictEqual(bytes.isBinary(new ArrayBuffer(0)), true)
assert.deepStrictEqual(bytes.isBinary(new DataView(new ArrayBuffer(0))), true)
})
it('coerce', () => {
const fixture = bytes.fromString('test')
assert.deepStrictEqual(bytes.coerce(fixture.buffer), fixture)
assert.deepStrictEqual(bytes.coerce(new DataView(fixture.buffer)), fixture)
})
it('equals', () => {
const fixture = bytes.fromString('test')
assert.deepStrictEqual(bytes.equals(fixture, bytes.fromString('asdfadf')), false)
})
it('toString()', () => {
const fixture = 'hello world'
assert.deepStrictEqual(bytes.toString(bytes.fromString(fixture)), fixture)
})
})
+740
View File
@@ -0,0 +1,740 @@
/* globals describe, it */
import { fromHex, toHex, equals } from '../src/bytes.js'
import { varint, CID } from '../src/index.js'
import { base58btc } from '../src/bases/base58.js'
import { base32 } from '../src/bases/base32.js'
import { base64 } from '../src/bases/base64.js'
import { sha256, sha512 } from '../src/hashes/sha2.js'
import invalidMultihash from './fixtures/invalid-multihash.js'
import OLDCID from 'cids'
import { assert } from 'aegir/chai'
// Linter can see that API is used in types.
// eslint-disable-next-line
import * as API from 'multiformats'
const textEncoder = new TextEncoder()
describe('CID', () => {
describe('v0', () => {
it('handles B58Str multihash', () => {
const mhStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
const cid = CID.parse(mhStr)
assert.deepStrictEqual(cid.version, 0)
assert.deepStrictEqual(cid.code, 112)
assert.deepStrictEqual(cid.multihash.bytes, base58btc.baseDecode(mhStr))
assert.deepStrictEqual(cid.toString(), mhStr)
})
it('create by parts', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = CID.create(0, 112, hash)
assert.deepStrictEqual(cid.code, 112)
assert.deepStrictEqual(cid.version, 0)
assert.deepStrictEqual(cid.multihash, hash)
assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes))
})
it('CID.createV0', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = CID.createV0(hash)
assert.deepStrictEqual(cid.code, 112)
assert.deepStrictEqual(cid.version, 0)
assert.deepStrictEqual(cid.multihash, hash)
assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes))
})
it('create from multihash', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = CID.decode(hash.bytes)
assert.deepStrictEqual(cid.code, 112)
assert.deepStrictEqual(cid.version, 0)
assert.deepStrictEqual(cid.multihash.digest, hash.digest)
assert.deepStrictEqual(
{ ...cid.multihash, digest: null },
{ ...hash, digest: null }
)
cid.toString()
assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes))
})
it('throws on invalid BS58Str multihash ', async () => {
const msg = 'Non-base58btc character'
assert.throws(
() => CID.parse('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII'),
msg
)
})
it('throws on trying to create a CIDv0 with a codec other than dag-pb', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const msg = 'Version 0 CID must use dag-pb (code: 112) block encoding'
assert.throws(() => CID.create(0, 113, hash), msg)
})
it('throws on trying to base encode CIDv0 in other base than base58btc', async () => {
const mhStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
const cid = CID.parse(mhStr)
const msg = 'Cannot string encode V0 in base32 encoding'
assert.throws(() => cid.toString(base32), msg)
})
it('throws on CIDv0 string with explicit multibase prefix', async () => {
const str = 'zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
const msg = 'Version 0 CID string must not include multibase prefix'
assert.throws(() => CID.parse(str), msg)
})
it('.bytes', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const codec = 112
const cid = CID.create(0, codec, hash)
const bytes = cid.bytes
assert.ok(bytes)
const str = toHex(bytes)
assert.deepStrictEqual(
str,
'1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
)
})
it('should construct from an old CID', () => {
const cidStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
const oldCid = CID.parse(cidStr)
const newCid = /** @type {CID} */ (CID.asCID(oldCid))
assert.deepStrictEqual(newCid.toString(), cidStr)
})
it('inspect bytes', () => {
const byts = fromHex(
'1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
)
const inspected = CID.inspectBytes(byts.subarray(0, 10)) // should only need the first few bytes
assert.deepStrictEqual(
{
version: 0,
codec: 0x70,
multihashCode: 0x12,
multihashSize: 34,
digestSize: 32,
size: 34
},
inspected
)
})
describe('decodeFirst', () => {
it('no remainder', () => {
const byts = fromHex(
'1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
)
const [cid, remainder] = CID.decodeFirst(byts)
assert.deepStrictEqual(
cid.toString(),
'QmatYkNGZnELf8cAGdyJpUca2PyY4szai3RHyyWofNY1pY'
)
assert.deepStrictEqual(remainder.byteLength, 0)
})
it('remainder', () => {
const byts = fromHex(
'1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad0102030405'
)
const [cid, remainder] = CID.decodeFirst(byts)
assert.deepStrictEqual(
cid.toString(),
'QmatYkNGZnELf8cAGdyJpUca2PyY4szai3RHyyWofNY1pY'
)
assert.deepStrictEqual(toHex(remainder), '0102030405')
})
})
})
describe('v1', () => {
it('handles CID String (multibase encoded)', () => {
const cidStr = 'zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS'
const cid = CID.parse(cidStr)
assert.deepStrictEqual(cid.code, 112)
assert.deepStrictEqual(cid.version, 1)
assert.ok(cid.multihash)
assert.deepStrictEqual(cid.toString(), base32.encode(cid.bytes))
})
it('handles CID (no multibase)', () => {
const cidStr =
'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u'
const cidBuf = fromHex(
'017012207252523e6591fb8fe553d67ff55a86f84044b46a3e4176e10c58fa529a4aabd5'
)
const cid = CID.decode(cidBuf)
assert.deepStrictEqual(cid.code, 112)
assert.deepStrictEqual(cid.version, 1)
assert.deepStrictEqual(cid.toString(), cidStr)
})
it('create by parts', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = CID.create(1, 0x71, hash)
assert.deepStrictEqual(cid.code, 0x71)
assert.deepStrictEqual(cid.version, 1)
equalDigest(cid.multihash, hash)
})
it('CID.createV1', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = CID.createV1(0x71, hash)
assert.deepStrictEqual(cid.code, 0x71)
assert.deepStrictEqual(cid.version, 1)
equalDigest(cid.multihash, hash)
})
it('can roundtrip through cid.toString()', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid1 = CID.create(1, 0x71, hash)
const cid2 = CID.parse(cid1.toString())
assert.deepStrictEqual(cid1.code, cid2.code)
assert.deepStrictEqual(cid1.version, cid2.version)
assert.deepStrictEqual(cid1.multihash.digest, cid2.multihash.digest)
assert.deepStrictEqual(cid1.multihash.bytes, cid2.multihash.bytes)
const clear = { digest: null, bytes: null }
assert.deepStrictEqual(
{ ...cid1.multihash, ...clear },
{ ...cid2.multihash, ...clear }
)
})
it('.bytes', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const code = 0x71
const cid = CID.create(1, code, hash)
const bytes = cid.bytes
assert.ok(bytes)
const str = toHex(bytes)
assert.deepStrictEqual(
str,
'01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
)
})
it('should construct from an old CID without a multibaseName', () => {
const cidStr =
'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u'
const oldCid = CID.parse(cidStr)
const newCid = /** @type {CID} */ (CID.asCID(oldCid))
assert.deepStrictEqual(newCid.toString(), cidStr)
})
it('.link() should return this CID', () => {
const cid = CID.parse('bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u')
assert.equal(cid, cid.link())
})
})
describe('utilities', () => {
const h1 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
const h2 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1o'
const h3 = 'zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS'
it('.equals v0 to v0', () => {
const cid1 = CID.parse(h1)
assert.deepStrictEqual(cid1.equals(CID.parse(h1)), true)
assert.deepStrictEqual(
cid1.equals(CID.create(cid1.version, cid1.code, cid1.multihash)),
true
)
const cid2 = CID.parse(h2)
assert.deepStrictEqual(cid1.equals(CID.parse(h2)), false)
assert.deepStrictEqual(
cid1.equals(CID.create(cid2.version, cid2.code, cid2.multihash)),
false
)
})
it('.equals v0 to v1 and vice versa', () => {
const cidV1 = CID.parse(h3)
const cidV0 = cidV1.toV0()
assert.deepStrictEqual(cidV0.equals(cidV1), false)
assert.deepStrictEqual(cidV1.equals(cidV0), false)
assert.deepStrictEqual(cidV1.multihash, cidV0.multihash)
})
it('.equals v1 to v1', () => {
const cid1 = CID.parse(h3)
assert.deepStrictEqual(cid1.equals(CID.parse(h3)), true)
assert.deepStrictEqual(
cid1.equals(CID.create(cid1.version, cid1.code, cid1.multihash)),
true
)
})
it('works with deepEquals', () => {
const ch1 = CID.parse(h1)
assert.deepStrictEqual(ch1, CID.parse(h1))
assert.notDeepEqual(ch1, CID.parse(h2))
})
})
describe('throws on invalid inputs', () => {
const parse = [
'hello world',
'QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT3L'
]
for (const i of parse) {
const name = `CID.parse(${JSON.stringify(i)})`
it(name, async () => assert.throws(() => CID.parse(i)))
}
const decode = [
textEncoder.encode('hello world'),
textEncoder.encode('QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT')
]
for (const i of decode) {
const name = `CID.decode(textEncoder.encode(${JSON.stringify(
i.toString()
)}))`
it(name, async () => assert.throws(() => CID.decode(i)))
}
const create = [
...[...parse, ...decode].map((i) => [0, 112, i]),
...[...parse, ...decode].map((i) => [1, 112, i]),
[18, 112, 'QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT3L']
]
for (const [version, code, hash] of create) {
const form = JSON.stringify(hash.toString())
const mh =
hash instanceof Uint8Array ? `textEncoder.encode(${form})` : form
const name = `CID.create(${version}, ${code}, ${mh})`
// @ts-expect-error - version issn't always 0|1
it(name, async () => assert.throws(() => CID.create(version, code, hash)))
}
it('invalid fixtures', async () => {
for (const test of invalidMultihash) {
const buff = fromHex(`0171${test.hex}`)
assert.throws(() => CID.decode(buff), new RegExp(test.message))
}
})
})
describe('idempotence', () => {
const h1 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
const cid1 = CID.parse(h1)
const cid2 = CID.asCID(cid1)
it('constructor accept constructed instance', () => {
assert.deepStrictEqual(cid1 === cid2, true)
})
})
describe('conversion v0 <-> v1', () => {
it('should convert v0 to v1', async () => {
const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
const cid = CID.create(0, 112, hash).toV1()
assert.deepStrictEqual(cid.version, 1)
})
it('should convert v1 to v0', async () => {
const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
const cid = CID.create(1, 112, hash).toV0()
assert.deepStrictEqual(cid.version, 0)
})
it('should not convert v1 to v0 if not dag-pb codec', async () => {
const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
const cid = CID.create(1, 0x71, hash)
assert.throws(
() => cid.toV0(),
'Cannot convert a non dag-pb CID to CIDv0'
)
})
it('should not convert v1 to v0 if not sha2-256 multihash', async () => {
const hash = await sha512.digest(textEncoder.encode(`TEST${Date.now()}`))
const cid = CID.create(1, 112, hash)
assert.throws(
() => cid.toV0(),
'Cannot convert non sha2-256 multihash CID to CIDv0'
)
})
it('should return assert.deepStrictEqual instance when converting v1 to v1', async () => {
const hash = await sha512.digest(textEncoder.encode(`TEST${Date.now()}`))
const cid = CID.create(1, 112, hash)
assert.deepStrictEqual(cid.toV1() === cid, true)
})
it('should return assert.deepStrictEqual instance when converting v0 to v0', async () => {
const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
const cid = CID.create(0, 112, hash)
assert.deepStrictEqual(cid.toV0() === cid, true)
})
it('should fail to convert unknown version', async () => {
const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
const cid = CID.create(0, 112, hash)
const cid1 = Object.assign(Object.create(Object.getPrototypeOf(cid)), {
...cid
})
const cid2 = Object.assign(Object.create(Object.getPrototypeOf(cid)), {
...cid,
version: 3
})
assert.deepStrictEqual(cid1.toV0().version, 0)
assert.deepStrictEqual(cid1.toV1().version, 1)
assert.equal(cid2.version, 3)
assert.throws(
() => cid2.toV1(),
/Can not convert CID version 3 to version 1/
)
assert.throws(
() => cid2.toV0(),
/Can not convert CID version 3 to version 0/
)
})
})
describe('caching', () => {
it('should cache CID as buffer', async () => {
const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
const cid = CID.create(1, 112, hash)
assert.ok(cid.bytes)
assert.deepStrictEqual(cid.bytes, cid.bytes)
})
it('should cache string representation when it matches the multibaseName it was constructed with', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = CID.create(1, 112, hash)
const b32 = {
...base32,
callCount: 0,
/**
* @param {Uint8Array} bytes
*/
encode (bytes) {
this.callCount += 1
return base32.encode(bytes) + '!'
}
}
const b64 = {
...base64,
callCount: 0,
/**
* @param {Uint8Array} bytes
*/
encode (bytes) {
this.callCount += 1
return base64.encode(bytes)
}
}
const base32String =
'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
assert.deepEqual(cid.toString(b32), `${base32String}!`)
assert.deepEqual(b32.callCount, 1)
assert.deepEqual(cid.toString(), `${base32String}!`)
assert.deepEqual(b32.callCount, 1)
assert.deepStrictEqual(
cid.toString(b64),
'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt'
)
assert.equal(b64.callCount, 1)
assert.deepStrictEqual(
cid.toString(b64),
'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt'
)
assert.equal(b64.callCount, 1)
})
it('should cache string representation when constructed with one', () => {
const base32String =
'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
const cid = CID.parse(base32String)
assert.deepStrictEqual(
cid.toString({
...base32,
encode () {
throw Error('Should not call decode')
}
}),
base32String
)
})
})
it('toJSON()', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = CID.create(1, 112, hash)
assert.deepStrictEqual(cid.toJSON(), {
'/': cid.toString()
})
})
it('asCID', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
class IncompatibleCID {
/**
* @param {number} version
* @param {number} code
* @param {import('multiformats/hashes/interface').MultihashDigest} multihash
*/
constructor (version, code, multihash) {
this.version = version
this.code = code
this.multihash = multihash
this.asCID = this
}
get [Symbol.for('@ipld/js-cid/CID')] () {
return true
}
}
const version = 1
const code = 112
const incompatibleCID = new IncompatibleCID(version, code, hash)
assert.strictEqual(incompatibleCID.toString(), '[object Object]')
// @ts-expect-error - no such method
assert.strictEqual(typeof incompatibleCID.toV0, 'undefined')
const cid1 = /** @type {CID} */ (CID.asCID(incompatibleCID))
assert.ok(cid1 instanceof CID)
assert.strictEqual(cid1.code, code)
assert.strictEqual(cid1.version, version)
assert.ok(equals(cid1.multihash.bytes, hash.bytes))
const cid2 = CID.asCID({ version, code, hash })
assert.strictEqual(cid2, null)
const duckCID = { version, code, multihash: hash }
// @ts-expect-error - no such property
duckCID.asCID = duckCID
const cid3 = /** @type {CID} */ (CID.asCID(duckCID))
assert.ok(cid3 instanceof CID)
assert.strictEqual(cid3.code, code)
assert.strictEqual(cid3.version, version)
assert.ok(equals(cid3.multihash.bytes, hash.bytes))
const cid4 = CID.asCID(cid3)
assert.strictEqual(cid3, cid4)
const cid5 = /** @type {CID} */ (
CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))
)
assert.ok(cid5 instanceof CID)
assert.strictEqual(cid5.version, 1)
assert.ok(equals(cid5.multihash.bytes, hash.bytes))
assert.strictEqual(cid5.code, 85)
})
/**
* @param {API.CID} x
* @param {API.CID} y
*/
const digestsame = (x, y) => {
// @ts-ignore - not sure what this supposed to be
assert.deepStrictEqual(x.hash, y.hash)
assert.deepStrictEqual(x.bytes, y.bytes)
if (x.multihash) {
equalDigest(x.multihash, y.multihash)
}
const empty = { hash: null, bytes: null, digest: null, multihash: null }
assert.deepStrictEqual({ ...x, ...empty }, { ...y, ...empty })
}
/**
* @typedef {import('multiformats/hashes/interface').MultihashDigest} MultihashDigest
* @param {MultihashDigest} x
* @param {MultihashDigest} y
*/
const equalDigest = (x, y) => {
assert.deepStrictEqual(x.digest, y.digest)
assert.deepStrictEqual(x.code, y.code)
assert.deepStrictEqual(x.digest, y.digest)
}
describe('CID.parse', async () => {
it('parse 32 encoded CIDv1', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = CID.create(1, 112, hash)
const parsed = CID.parse(cid.toString())
digestsame(cid, parsed)
})
it('parse base58btc encoded CIDv1', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = CID.create(1, 112, hash)
const parsed = CID.parse(cid.toString(base58btc))
digestsame(cid, parsed)
})
it('parse base58btc encoded CIDv0', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = CID.create(0, 112, hash)
const parsed = CID.parse(cid.toString())
digestsame(cid, parsed)
})
it('fails to parse base64 encoded CIDv1', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = CID.create(1, 112, hash)
const msg =
'To parse non base32 or base58btc encoded CID multibase decoder must be provided'
assert.throws(() => CID.parse(cid.toString(base64)), msg)
})
it('parses base64 encoded CIDv1 if base64 is provided', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = CID.create(1, 112, hash)
const parsed = CID.parse(cid.toString(base64), base64)
digestsame(cid, parsed)
})
})
it('inspect bytes', () => {
const byts = fromHex(
'01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
)
const inspected = CID.inspectBytes(byts.subarray(0, 10)) // should only need the first few bytes
assert.deepStrictEqual(
{
version: 1,
codec: 0x71,
multihashCode: 0x12,
multihashSize: 34,
digestSize: 32,
size: 36
},
inspected
)
describe('decodeFirst', () => {
it('no remainder', () => {
const byts = fromHex(
'01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
)
const [cid, remainder] = CID.decodeFirst(byts)
assert.deepStrictEqual(
cid.toString(),
'bafyreif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
)
assert.deepStrictEqual(remainder.byteLength, 0)
})
it('remainder', () => {
const byts = fromHex(
'01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad0102030405'
)
const [cid, remainder] = CID.decodeFirst(byts)
assert.deepStrictEqual(
cid.toString(),
'bafyreif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
)
assert.deepStrictEqual(toHex(remainder), '0102030405')
})
})
})
it('new CID from old CID', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = /** @type {CID} */ (
CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))
)
assert.deepStrictEqual(cid.version, 1)
equalDigest(cid.multihash, hash)
assert.deepStrictEqual(cid.code, 85)
})
it('util.inspect', async () => {
const hash = await sha256.digest(textEncoder.encode('abc'))
const cid = CID.create(1, 112, hash)
assert.deepStrictEqual(
// @ts-expect-error - no such method is known
typeof cid[Symbol.for('nodejs.util.inspect.custom')],
'function'
)
assert.deepStrictEqual(
// @ts-expect-error - no such method is known
cid[Symbol.for('nodejs.util.inspect.custom')](),
'CID(bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu)'
)
})
it('invalid CID version', async () => {
const encoded = varint.encodeTo(2, new Uint8Array(32))
assert.throws(() => CID.decode(encoded), 'Invalid CID version 2')
})
it('CID can be moved across JS realms', async () => {
const cid = CID.parse('bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu')
const { port1: sender, port2: receiver } = new MessageChannel()
sender.postMessage(cid)
const cid2 = await new Promise((resolve) => {
receiver.onmessage = (event) => { resolve(event.data) }
})
sender.close()
receiver.close()
assert.strictEqual(cid2['/'], cid2.bytes)
})
describe('decode', () => {
const tests = {
v0: 'QmTFHZL5CkgNz19MdPnSuyLAi6AVq9fFp81zmPpaL2amED',
v1: 'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
}
Object.entries(tests).forEach(([version, cidString]) => {
it(`decode ${version} from bytes`, () => {
const cid1 = CID.parse(cidString)
const cid2 = CID.decode(cid1.bytes)
assert.deepStrictEqual(cid1, cid2)
})
it(`decode ${version} from subarray`, () => {
const cid1 = CID.parse(cidString)
// a byte array with an extra byte at the start and end
const bytes = new Uint8Array(cid1.bytes.length + 2)
bytes.set(cid1.bytes, 1)
// slice the cid bytes out of the middle to have a subarray with a non-zero .byteOffset
const subarray = bytes.subarray(1, cid1.bytes.length + 1)
const cid2 = CID.decode(subarray)
assert.deepStrictEqual(cid1, cid2)
assert.equal(cid1.byteLength, cid2.byteLength)
assert.equal(typeof cid2.byteOffset, 'number')
})
})
})
})
+149
View File
@@ -0,0 +1,149 @@
/* globals describe, it */
import * as Link from '../src/link.js'
import { assert } from 'aegir/chai'
import { sha256 } from '../src/hashes/sha2.js'
const utf8 = new TextEncoder()
const h1 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
const h4 = 'bafyreidykglsfhoixmivffc5uwhcgshx4j465xwqntbmu43nb2dzqwfvae'
const CBOR = 0x71
// eslint-disable-next-line
const SHA256 = sha256.code
const sh1 = /** @type {Link.MultihashDigest<typeof sha256.code>} */ (
Link.parse(h4).multihash
)
describe('Link', () => {
it('isLink', () => {
assert.equal(Link.isLink(0), false)
assert.equal(Link.isLink(false), false)
})
describe('create', () => {
it('create v1', async () => {
const hash = await sha256.digest(utf8.encode('abc'))
const link = Link.create(0x71, hash)
/** @type {0x71} */
const code = link.code
assert.deepStrictEqual(code, 0x71)
/** @type {1} */
const version = link.version
assert.deepEqual(version, 1)
/** @type {Link.MultihashDigest<typeof sha256.code>}> */
const multihash = link.multihash
assert.deepStrictEqual(multihash, hash)
})
it('create v0', async () => {
const hash = await sha256.digest(utf8.encode('abc'))
const link = Link.createLegacy(hash)
/** @type {0x70} */
const code = link.code
assert.deepStrictEqual(code, 0x70)
/** @type {0} */
const version = link.version
assert.deepEqual(version, 0)
/** @type {Link.MultihashDigest<typeof sha256.code>}> */
const multihash = link.multihash
assert.deepStrictEqual(multihash, hash)
})
})
describe('parse', () => {
it('can parse any string', () => {
const link = Link.parse(h1)
/** @type {Link.Link<unknown, typeof CBOR, typeof SHA256, 1>} */
// @ts-expect-error - types can not be inferred
const t1 = link
assert.ok(t1)
// it is possible to manually cast
const t2 = /** @type {Link.LegacyLink<unknown>} */ (link)
assert.ok(t2)
})
it('parse retains type info', () => {
const original = Link.create(CBOR, sh1)
const source = Link.format(original)
const link = Link.parse(source)
assert.equal(original.equals(link), true, 'format -> parse roundtrips')
// ensure that type info is retained
/** @type {Link.Link<unknown, typeof CBOR, typeof SHA256, 1>} */
const t1 = link
assert.ok(t1)
// ensurate that you can't cast incorrectly
const t2 =
// @ts-expect-error - version is 1 not 0
/** @type {Link.Link<unknown, typeof CBOR, typeof SHA256, 0>} */ (link)
assert.ok(t2)
})
})
describe('toJSON', () => {
assert.deepStrictEqual(Link.toJSON(Link.parse(h1)), {
'/': h1
})
assert.deepStrictEqual(Link.toJSON(Link.parse(h4)), {
'/': h4
})
})
describe('fromJSON', () => {
assert.deepStrictEqual(Link.parse(h1), Link.fromJSON({
'/': h1
}))
assert.deepStrictEqual(Link.parse(h1), Link.fromJSON({
'/': h1,
// @ts-expect-error
foo: 1
}))
assert.deepStrictEqual(Link.parse(h4), Link.fromJSON({
'/': h4
}))
})
describe('JSON.stringify', () => {
assert.equal(JSON.stringify(Link.parse(h1)), JSON.stringify({
'/': h1
}))
assert.equal(JSON.stringify(Link.parse(h4)), JSON.stringify({
'/': h4
}))
})
})
describe('decode', () => {
it('decode', async () => {
const hash = await sha256.digest(utf8.encode('abc'))
const { bytes } = Link.create(0x71, hash)
const link = Link.decode(bytes)
/** @type {0x71} */
const code = link.code
assert.deepStrictEqual(code, 0x71)
/** @type {1} */
const version = link.version
assert.deepEqual(version, 1)
/** @type {Link.MultihashDigest<typeof sha256.code>}> */
const multihash = link.multihash
assert.deepStrictEqual(multihash, hash)
})
})
@@ -0,0 +1,185 @@
/* eslint-env mocha */
import { bases } from '../src/basics.js'
import { fromString } from '../src/bytes.js'
import { assert } from 'aegir/chai'
const encoded = [
{
input: 'Decentralize everything!!',
tests: [
['identity', '\x00Decentralize everything!!'],
['base2', '001000100011001010110001101100101011011100111010001110010011000010110110001101001011110100110010100100000011001010111011001100101011100100111100101110100011010000110100101101110011001110010000100100001'],
['base8', '72106254331267164344605543227514510062566312711713506415133463441102'],
['base10', '9429328951066508984658627669258025763026247056774804621697313'],
['base16', 'f446563656e7472616c697a652065766572797468696e672121'],
['base16upper', 'F446563656E7472616C697A652065766572797468696E672121'],
['base32', 'birswgzloorzgc3djpjssazlwmvzhs5dinfxgoijb'],
['base32upper', 'BIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJB'],
['base32hex', 'v8him6pbeehp62r39f9ii0pbmclp7it38d5n6e891'],
['base32hexupper', 'V8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E891'],
['base32pad', 'cirswgzloorzgc3djpjssazlwmvzhs5dinfxgoijb'],
['base32padupper', 'CIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJB'],
['base32hexpad', 't8him6pbeehp62r39f9ii0pbmclp7it38d5n6e891'],
['base32hexpadupper', 'T8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E891'],
['base32z', 'het1sg3mqqt3gn5djxj11y3msci3817depfzgqejb'],
['base36', 'k343ixo7d49hqj1ium15pgy1wzww5fxrid21td7l'],
['base36upper', 'K343IXO7D49HQJ1IUM15PGY1WZWW5FXRID21TD7L'],
['base58flickr', 'Ztwe7gVTeK8wswS1gf8hrgAua9fcw9reboD'],
['base58btc', 'zUXE7GvtEk8XTXs1GF8HSGbVA9FCX9SEBPe'],
['base64', 'mRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ'],
['base64pad', 'MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ=='],
['base64url', 'uRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ'],
['base64urlpad', 'URGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ=='],
['base256emoji', '🚀💛✋💃✋😻😈🥺🤤🍀🌟💐✋😅✋💦✋🥺🏃😈😴🌟😻😝👏👏']
]
},
{
input: 'yes mani !',
tests: [
['identity', '\x00yes mani !'],
['base2', '001111001011001010111001100100000011011010110000101101110011010010010000000100001'],
['base8', '7362625631006654133464440102'],
['base10', '9573277761329450583662625'],
['base16', 'f796573206d616e692021'],
['base16upper', 'F796573206D616E692021'],
['base32', 'bpfsxgidnmfxgsibb'],
['base32upper', 'BPFSXGIDNMFXGSIBB'],
['base32hex', 'vf5in683dc5n6i811'],
['base32hexupper', 'VF5IN683DC5N6I811'],
['base32pad', 'cpfsxgidnmfxgsibb'],
['base32padupper', 'CPFSXGIDNMFXGSIBB'],
['base32hexpad', 'tf5in683dc5n6i811'],
['base32hexpadupper', 'TF5IN683DC5N6I811'],
['base32z', 'hxf1zgedpcfzg1ebb'],
['base36', 'k2lcpzo5yikidynfl'],
['base36upper', 'K2LCPZO5YIKIDYNFL'],
['base58flickr', 'Z7Pznk19XTTzBtx'],
['base58btc', 'z7paNL19xttacUY'],
['base64', 'meWVzIG1hbmkgIQ'],
['base64pad', 'MeWVzIG1hbmkgIQ=='],
['base64url', 'ueWVzIG1hbmkgIQ'],
['base64urlpad', 'UeWVzIG1hbmkgIQ=='],
['base256emoji', '🚀🏃✋🌈😅🌷🤤😻🌟😅👏']
]
},
{
input: 'hello world',
tests: [
['identity', '\x00hello world'],
['base2', '00110100001100101011011000110110001101111001000000111011101101111011100100110110001100100'],
['base8', '7320625543306744035667562330620'],
['base10', '9126207244316550804821666916'],
['base16', 'f68656c6c6f20776f726c64'],
['base16upper', 'F68656C6C6F20776F726C64'],
['base32', 'bnbswy3dpeb3w64tmmq'],
['base32upper', 'BNBSWY3DPEB3W64TMMQ'],
['base32hex', 'vd1imor3f41rmusjccg'],
['base32hexupper', 'VD1IMOR3F41RMUSJCCG'],
['base32pad', 'cnbswy3dpeb3w64tmmq======'],
['base32padupper', 'CNBSWY3DPEB3W64TMMQ======'],
['base32hexpad', 'td1imor3f41rmusjccg======'],
['base32hexpadupper', 'TD1IMOR3F41RMUSJCCG======'],
['base32z', 'hpb1sa5dxrb5s6hucco'],
['base36', 'kfuvrsivvnfrbjwajo'],
['base36upper', 'KFUVRSIVVNFRBJWAJO'],
['base58flickr', 'ZrTu1dk6cWsRYjYu'],
['base58btc', 'zStV1DL6CwTryKyV'],
['base64', 'maGVsbG8gd29ybGQ'],
['base64pad', 'MaGVsbG8gd29ybGQ='],
['base64url', 'uaGVsbG8gd29ybGQ'],
['base64urlpad', 'UaGVsbG8gd29ybGQ='],
['base256emoji', '🚀😴✋🍀🍀😓😅✔😓🥺🍀😳']
]
},
{
input: '\x00yes mani !',
tests: [
['identity', '\x00\x00yes mani !'],
['base2', '00000000001111001011001010111001100100000011011010110000101101110011010010010000000100001'],
['base8', '7000745453462015530267151100204'],
['base10', '90573277761329450583662625'],
['base16', 'f00796573206d616e692021'],
['base16upper', 'F00796573206D616E692021'],
['base32', 'bab4wk4zanvqw42jaee'],
['base32upper', 'BAB4WK4ZANVQW42JAEE'],
['base32hex', 'v01smasp0dlgmsq9044'],
['base32hexupper', 'V01SMASP0DLGMSQ9044'],
['base32pad', 'cab4wk4zanvqw42jaee======'],
['base32padupper', 'CAB4WK4ZANVQW42JAEE======'],
['base32hexpad', 't01smasp0dlgmsq9044======'],
['base32hexpadupper', 'T01SMASP0DLGMSQ9044======'],
['base32z', 'hybhskh3ypiosh4jyrr'],
['base36', 'k02lcpzo5yikidynfl'],
['base36upper', 'K02LCPZO5YIKIDYNFL'],
['base58flickr', 'Z17Pznk19XTTzBtx'],
['base58btc', 'z17paNL19xttacUY'],
['base64', 'mAHllcyBtYW5pICE'],
['base64pad', 'MAHllcyBtYW5pICE='],
['base64url', 'uAHllcyBtYW5pICE'],
['base64urlpad', 'UAHllcyBtYW5pICE='],
['base256emoji', '🚀🚀🏃✋🌈😅🌷🤤😻🌟😅👏']
]
},
{
input: '\x00\x00yes mani !',
tests: [
['identity', '\x00\x00\x00yes mani !'],
['base2', '0000000000000000001111001011001010111001100100000011011010110000101101110011010010010000000100001'],
['base8', '700000171312714403326055632220041'],
['base10', '900573277761329450583662625'],
['base16', 'f0000796573206d616e692021'],
['base16upper', 'F0000796573206D616E692021'],
['base32', 'baaahszltebwwc3tjeaqq'],
['base32upper', 'BAAAHSZLTEBWWC3TJEAQQ'],
['base32hex', 'v0007ipbj41mm2rj940gg'],
['base32hexupper', 'V0007IPBJ41MM2RJ940GG'],
['base32pad', 'caaahszltebwwc3tjeaqq===='],
['base32padupper', 'CAAAHSZLTEBWWC3TJEAQQ===='],
['base32hexpad', 't0007ipbj41mm2rj940gg===='],
['base32hexpadupper', 'T0007IPBJ41MM2RJ940GG===='],
['base32z', 'hyyy813murbssn5ujryoo'],
['base36', 'k002lcpzo5yikidynfl'],
['base36upper', 'K002LCPZO5YIKIDYNFL'],
['base58flickr', 'Z117Pznk19XTTzBtx'],
['base58btc', 'z117paNL19xttacUY'],
['base64', 'mAAB5ZXMgbWFuaSAh'],
['base64pad', 'MAAB5ZXMgbWFuaSAh'],
['base64url', 'uAAB5ZXMgbWFuaSAh'],
['base64urlpad', 'UAAB5ZXMgbWFuaSAh'],
['base256emoji', '🚀🚀🚀🏃✋🌈😅🌷🤤😻🌟😅👏']
]
}
]
describe('spec test', () => {
let index = 0
for (const { input, tests } of encoded) {
describe(`multibase spec ${index++}`, () => {
for (const [name, output] of tests) {
const base = bases[/** @type {keyof bases} */(name)]
describe(name, () => {
it('should encode buffer', () => {
const out = base.encode(fromString(input))
assert.deepStrictEqual(out, output)
})
it('should decode string', () => {
assert.deepStrictEqual(base.decode(output), fromString(input))
})
})
}
})
}
for (const base of Object.values(bases)) {
it('should fail decode with invalid char', function () {
if (base.name === 'identity') {
return this.skip()
}
assert.throws(() => base.decode(base.prefix + '^!@$%!#$%@#y'), `Non-${base.name} character`)
})
}
})
+177
View File
@@ -0,0 +1,177 @@
/* globals describe, it */
import * as bytes from '../src/bytes.js'
import * as b2 from '../src/bases/base2.js'
import * as b8 from '../src/bases/base8.js'
import * as b10 from '../src/bases/base10.js'
import * as b16 from '../src/bases/base16.js'
import * as b32 from '../src/bases/base32.js'
import * as b36 from '../src/bases/base36.js'
import * as b58 from '../src/bases/base58.js'
import * as b64 from '../src/bases/base64.js'
import { assert } from 'aegir/chai'
const { base16, base32, base58btc, base64 } = { ...b16, ...b32, ...b58, ...b64 }
describe('multibase', () => {
for (const base of [base16, base32, base58btc, base64]) {
describe(`basics ${base.name}`, () => {
it('encode/decode', () => {
const string = base.encode(bytes.fromString('test'))
assert.deepStrictEqual(string[0], base.prefix)
const buffer = base.decode(string)
assert.deepStrictEqual(buffer, bytes.fromString('test'))
})
it('pristine backing buffer', () => {
// some deepEqual() libraries go as deep as the backing buffer, make sure it's pristine
const string = base.encode(bytes.fromString('test'))
const buffer = base.decode(string)
const expected = bytes.fromString('test')
assert.deepStrictEqual(new Uint8Array(buffer).join(','), new Uint8Array(expected.buffer).join(','))
})
it('empty', () => {
const str = base.encode(bytes.fromString(''))
assert.deepStrictEqual(str, base.prefix)
assert.deepStrictEqual(base.decode(str), bytes.fromString(''))
})
it('bad chars', () => {
const str = base.prefix + '#$%^&*&^%$#'
const msg = `Non-${base.name} character`
assert.throws(() => base.decode(str), msg)
})
})
}
it('encode string failure', () => {
const msg = 'Unknown type, must be binary type'
// @ts-expect-error - expects bytes
assert.throws(() => base32.encode('asdf'), msg)
// @ts-expect-error - expects bytes
assert.throws(() => base32.encoder.encode('asdf'), msg)
})
it('decode int failure', () => {
const msg = 'Can only multibase decode strings'
// @ts-expect-error - 'number' is not assignable to parameter of type 'string'
assert.throws(() => base32.decode(1), msg)
// @ts-expect-error - 'number' is not assignable to parameter of type 'string'
assert.throws(() => base32.decoder.decode(1), msg)
})
const buff = bytes.fromString('test')
const nonPrintableBuff = Uint8Array.from([239, 250, 254])
/**
* @param {typeof b2|b8|b10|b16|b32|b36|b58|b64} bases
*/
const baseTest = bases => {
for (const base of Object.values(bases)) {
if (base && base.name) {
it(`encode/decode ${base.name}`, () => {
const encoded = base.encode(buff)
const decoded = base.decode(encoded)
assert.deepStrictEqual(decoded, buff)
assert.deepStrictEqual(encoded, base.encoder.encode(buff))
assert.deepStrictEqual(buff, base.decoder.decode(encoded))
})
it(`encode/decode ${base.name} with non-printable values`, () => {
const encoded = base.encode(nonPrintableBuff)
const decoded = base.decode(encoded)
assert.deepStrictEqual(decoded, nonPrintableBuff)
assert.deepStrictEqual(encoded, base.encoder.encode(nonPrintableBuff))
assert.deepStrictEqual(nonPrintableBuff, base.decoder.decode(encoded))
})
}
}
}
describe('base2', () => {
baseTest(b2)
})
describe('base8', () => {
baseTest(b8)
})
describe('base10', () => {
baseTest(b10)
})
describe('base16', () => {
baseTest(b16)
})
describe('base32', () => {
baseTest(b32)
})
describe('base36', () => {
baseTest(b36)
})
describe('base58', () => {
baseTest(b58)
})
describe('base64', () => {
baseTest(b64)
})
it('multibase mismatch', () => {
const b64 = base64.encode(bytes.fromString('test'))
const msg = `Unable to decode multibase string "${b64}", base32 decoder only supports inputs prefixed with ${base32.prefix}`
assert.throws(() => base32.decode(b64), msg)
})
it('decoder composition', () => {
const base = base32.decoder.or(base58btc.decoder)
const b32 = base32.encode(bytes.fromString('test'))
assert.deepStrictEqual(base.decode(b32), bytes.fromString('test'))
const b58 = base58btc.encode(bytes.fromString('test'))
assert.deepStrictEqual(base.decode(b58), bytes.fromString('test'))
const b64 = base64.encode(bytes.fromString('test'))
const msg = `Unable to decode multibase string "${b64}", only inputs prefixed with ${base32.prefix},${base58btc.prefix} are supported`
assert.throws(() => base.decode(b64), msg)
const baseExt = base.or(base64)
assert.deepStrictEqual(baseExt.decode(b64), bytes.fromString('test'))
// original composition stays intact
assert.throws(() => base.decode(b64), msg)
// non-composed combined with composed
const baseExt2 = base32.decoder.or(base64.decoder.or(base16.decoder))
assert.deepStrictEqual(baseExt2.decode(b64), bytes.fromString('test'))
// composed combined with composed
const baseExt3 = base.or(base64.decoder.or(base16.decoder))
assert.deepStrictEqual(baseExt3.decode(b64), bytes.fromString('test'))
})
it('truncated data', () => {
const b64 = base64.encode(Uint8Array.from([245, 250]))
assert.throws(() => base64.decode(b64.substring(0, b64.length - 1)), 'Unexpected end of data')
})
it('infers prefix and name corretly', () => {
/** @type {'base32'} */
const name = base32.name
/** @type {'base16'} */
// @ts-expect-error - TS catches mismatch
const name2 = base32.name
/** @type {'b'} */
const prefix = base32.prefix
assert.equal(prefix, 'b')
assert.equal(name, 'base32')
assert.equal(name2, name)
})
})
+24
View File
@@ -0,0 +1,24 @@
/* globals describe, it */
import * as bytes from '../src/bytes.js'
import * as raw from '../src/codecs/raw.js'
import * as json from '../src/codecs/json.js'
import { assert } from 'aegir/chai'
describe('multicodec', () => {
it('encode/decode raw', () => {
const buff = raw.encode(bytes.fromString('test'))
assert.deepStrictEqual(buff, bytes.fromString('test'))
assert.deepStrictEqual(raw.decode(buff), bytes.fromString('test'))
})
it('encode/decode json', () => {
const buff = json.encode({ hello: 'world' })
assert.deepStrictEqual(buff, bytes.fromString(JSON.stringify({ hello: 'world' })))
assert.deepStrictEqual(json.decode(buff), { hello: 'world' })
})
it('raw cannot encode string', async () => {
// @ts-expect-error - 'string' is not assignable to parameter of type 'Uint8Array'
assert.throws(() => raw.encode('asdf'), 'Unknown type, must be binary type')
})
})
+138
View File
@@ -0,0 +1,138 @@
/* globals describe, it */
import { fromHex, fromString } from '../src/bytes.js'
import { sha256, sha512 } from '../src/hashes/sha2.js'
import { identity } from '../src/hashes/identity.js'
import { decode as decodeDigest, create as createDigest } from '../src/hashes/digest.js'
import valid from './fixtures/valid-multihash.js'
import invalid from './fixtures/invalid-multihash.js'
import { hash as slSha256 } from '@stablelib/sha256'
import { hash as slSha512 } from '@stablelib/sha512'
import { assert } from 'aegir/chai'
/**
* @param {number|string} code
* @param {number} size
* @param {string} hex
*/
const sample = (code, size, hex) => {
/**
* @param {number|string} i
*/
const toHex = (i) => {
if (typeof i === 'string') return i
const h = i.toString(16)
return h.length % 2 === 1 ? `0${h}` : h
}
return fromHex(`${toHex(code)}${toHex(size)}${hex}`)
}
describe('multihash', () => {
const empty = new Uint8Array(0)
describe('encode', () => {
it('valid', () => {
for (const test of valid) {
const { encoding, hex, size } = test
const { code, varint } = encoding
const buf = sample(varint || code, size, hex)
assert.deepStrictEqual(createDigest(code, hex ? fromHex(hex) : empty).bytes, buf)
}
})
it('hash sha2-256', async () => {
const hash = await sha256.digest(fromString('test'))
assert.deepStrictEqual(hash.code, sha256.code)
assert.deepStrictEqual(hash.digest, slSha256(fromString('test')))
const hash2 = decodeDigest(hash.bytes)
assert.deepStrictEqual(hash2.code, sha256.code)
assert.deepStrictEqual(hash2.bytes, hash.bytes)
})
if (typeof navigator === 'undefined') {
it('sync sha-256', () => {
const hash = sha256.digest(fromString('test'))
if (hash instanceof Promise) {
assert.fail('expected sync result')
} else {
assert.deepStrictEqual(hash.code, sha256.code)
assert.deepStrictEqual(hash.digest, slSha256(fromString('test')))
const hash2 = decodeDigest(hash.bytes)
assert.deepStrictEqual(hash2.code, sha256.code)
assert.deepStrictEqual(hash2.bytes, hash.bytes)
}
})
}
it('hash sha2-512', async () => {
const hash = await sha512.digest(fromString('test'))
assert.deepStrictEqual(hash.code, sha512.code)
assert.deepStrictEqual(hash.digest, slSha512(fromString('test')))
const hash2 = decodeDigest(hash.bytes)
assert.deepStrictEqual(hash2.code, sha512.code)
assert.deepStrictEqual(hash2.bytes, hash.bytes)
})
it('hash identity async', async () => {
const hash = await identity.digest(fromString('test'))
assert.deepStrictEqual(hash.code, identity.code)
assert.deepStrictEqual(identity.code, 0)
assert.deepStrictEqual(hash.digest, fromString('test'))
const hash2 = decodeDigest(hash.bytes)
assert.deepStrictEqual(hash2.code, identity.code)
assert.deepStrictEqual(hash2.bytes, hash.bytes)
})
it('hash identity sync', () => {
const hash = identity.digest(fromString('test'))
assert.deepStrictEqual(hash.code, identity.code)
assert.deepStrictEqual(identity.code, 0)
assert.deepStrictEqual(hash.digest, fromString('test'))
const hash2 = decodeDigest(hash.bytes)
assert.deepStrictEqual(hash2.code, identity.code)
assert.deepStrictEqual(hash2.bytes, hash.bytes)
})
})
describe('decode', () => {
for (const { encoding, hex, size } of valid) {
it(`valid fixture ${hex}`, () => {
const { code, varint } = encoding
const bytes = sample(varint || code, size, hex)
const digest = hex ? fromHex(hex) : empty
const hash = decodeDigest(bytes)
assert.deepStrictEqual(hash.bytes, bytes)
assert.deepStrictEqual(hash.code, code)
assert.deepStrictEqual(hash.size, size)
assert.deepStrictEqual(hash.digest, digest)
})
}
it('get from buffer', async () => {
const hash = await sha256.digest(fromString('test'))
assert.deepStrictEqual(hash.code, 18)
})
})
describe('validate', async () => {
it('invalid fixtures', async () => {
for (const test of invalid) {
const buff = fromHex(test.hex)
assert.throws(() => decodeDigest(buff), test.message)
}
})
})
it('throw on hashing non-buffer', async () => {
try {
// @ts-expect-error - string is incompatible arg
await sha256.digest('asdf')
} catch (error) {
assert.match(String(error), /Unknown type, must be binary type/)
}
})
})
+198
View File
@@ -0,0 +1,198 @@
/* globals describe, it */
import * as codec from '../src/codecs/json.js'
import { sha256 as hasher } from '../src/hashes/sha2.js'
import * as main from '../src/block.js'
import { walk } from '../src/traversal.js'
import { fromString } from '../src/bytes.js'
import { assert } from 'aegir/chai'
/** @typedef {import('../src/cid.js').CID} CID */
// from dag-pb, simplified
/**
* @param {Uint8Array} data
* @param {{Hash:CID, Name:string, Tsize:number}[]} links
* @returns {{Data:Uint8Array, Links:{Hash:CID, Name:string, Tsize:number}[]}}
*/
function createNode (data, links) {
return { Data: data, Links: links }
}
/**
* @param {string} name
* @param {number} size
* @param {CID} cid
* @returns {{Hash:CID, Name:string, Tsize:number}}
*/
function createLink (name, size, cid) {
return { Hash: cid, Name: name, Tsize: size }
}
describe('traversal', () => {
describe('walk', async () => {
// Forming the following DAG for testing
// A
// / \
// B C
// / \ / \
// D D D E
const linksE = /** @type {[]} */([])
const valueE = createNode(fromString('string E qacdswa'), linksE)
const blockE = await main.encode({ value: valueE, codec, hasher })
const cidE = blockE.cid
const linksD = /** @type {[]} */([])
const valueD = createNode(fromString('string D zasa'), linksD)
const blockD = await main.encode({ value: valueD, codec, hasher })
const cidD = blockD.cid
const linksC = [createLink('link1', 100, cidD), createLink('link2', 100, cidE)]
const valueC = createNode(fromString('string C zxc'), linksC)
const blockC = await main.encode({ value: valueC, codec, hasher })
const cidC = blockC.cid
const linksB = [createLink('link1', 100, cidD), createLink('link2', 100, cidD)]
const valueB = createNode(fromString('string B lpokjiasd'), linksB)
const blockB = await main.encode({ value: valueB, codec, hasher })
const cidB = blockB.cid
const linksA = [createLink('link1', 100, cidB), createLink('link2', 100, cidC)]
const valueA = createNode(fromString('string A qwertcfdgshaa'), linksA)
const blockA = await main.encode({ value: valueA, codec, hasher })
const cidA = blockA.cid
/**
* @param {CID} cid
*/
const load = async (cid) => {
if (cid.equals(cidE)) {
return blockE
}
if (cid.equals(cidD)) {
return blockD
}
if (cid.equals(cidC)) {
return blockC
}
if (cid.equals(cidB)) {
return blockB
}
if (cid.equals(cidA)) {
return blockA
}
return null
}
/**
* @param {typeof load} load
* @param {string[]} arr
*/
const loadWrapper = (load, arr = []) =>
/**
* @param {CID} cid
*/
(cid) => {
arr.push(cid.toString())
return load(cid)
}
it('block with no links', async () => {
// Test Case 1
// Input DAG
// D
//
// Expect load to be called with D
const expectedCallArray = [cidD.toString()]
/** @type {string[]} */
const callArray = []
await walk({ cid: cidD, load: loadWrapper(load, callArray) })
expectedCallArray.forEach((value, index) => {
assert.deepStrictEqual(value, callArray[index])
})
})
it('block with links', async () => {
// Test Case 2
// Input
// C
// / \
// D E
//
// Expect load to be called with C, then D, then E
const expectedCallArray = [cidC.toString(), cidD.toString(), cidE.toString()]
/** @type {string[]} */
const callArray = []
await walk({ cid: cidC, load: loadWrapper(load, callArray) })
expectedCallArray.forEach((value, index) => {
assert.deepStrictEqual(value, callArray[index])
})
})
it('block with matching links', async () => {
// Test Case 3
// Input
// B
// / \
// D D
//
// Expect load to be called with B, then D
const expectedCallArray = [cidB.toString(), cidD.toString()]
/** @type {string[]} */
const callArray = []
await walk({ cid: cidB, load: loadWrapper(load, callArray) })
expectedCallArray.forEach((value, index) => {
assert.deepStrictEqual(value, callArray[index])
})
})
it('depth first with duplicated block', async () => {
// Test Case 4
// Input
// A
// / \
// B C
// / \ / \
// D D D E
//
// Expect load to be called with A, then B, then D, then C, then E
const expectedCallArray = [
cidA.toString(),
cidB.toString(),
cidD.toString(),
cidC.toString(),
cidE.toString()
]
/** @type {string[]} */
const callArray = []
await walk({ cid: cidA, load: loadWrapper(load, callArray) })
expectedCallArray.forEach((value, index) => {
assert.deepStrictEqual(value, callArray[index])
})
})
it('null return', async () => {
/** @type {[]} */
const links = []
const value = createNode(fromString('test'), links)
const block = await main.encode({ value, codec, hasher })
const cid = block.cid
const expectedCallArray = [cid.toString()]
/** @type {string[]} */
const callArray = []
await walk({ cid, load: loadWrapper(load, callArray) })
expectedCallArray.forEach((value, index) => {
assert.deepStrictEqual(value, callArray[index])
})
})
})
})
+24
View File
@@ -0,0 +1,24 @@
/* globals describe, it */
import { varint } from '../src/index.js'
import { assert } from 'aegir/chai'
const UTF8 = new TextEncoder()
describe('varint', () => {
it('can decode with offset', () => {
const message = UTF8.encode('hello-world')
const outerTag = 0x55
const innerTag = 0xe3
const outerTagSize = varint.encodingLength(outerTag)
const innerTagSize = varint.encodingLength(innerTag)
const bytes = new Uint8Array(message.byteLength + outerTagSize + innerTagSize)
varint.encodeTo(outerTag, bytes)
varint.encodeTo(innerTag, bytes, outerTagSize)
bytes.set(message, outerTagSize + innerTagSize)
assert.deepStrictEqual(varint.decode(bytes), [outerTag, outerTagSize])
assert.deepStrictEqual(varint.decode(bytes, outerTagSize), [innerTag, innerTagSize])
})
})
+20
View File
@@ -0,0 +1,20 @@
{
"name": "ts-use",
"private": true,
"type": "module",
"dependencies": {
"multiformats": "file:../../"
},
"scripts": {
"test": "npm install && ../../node_modules/.bin/tsc --noEmit"
},
"eslintConfig": {
"extends": "ipfs",
"parserOptions": {
"sourceType": "module",
"project": [
"./test/ts-use/tsconfig.json"
]
}
}
}
+16
View File
@@ -0,0 +1,16 @@
import * as Block from 'multiformats/block'
import { sha256 } from 'multiformats/hashes/sha2'
import * as json from 'multiformats/codecs/json'
const main = async () => {
const block = await Block.encode({
value: { hello: 'world' },
hasher: sha256,
codec: json
})
/* eslint-disable no-console */
console.log(block)
}
export default main
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"strict": true,
"module": "NodeNext",
"noImplicitAny": true,
"incremental": true
}
}