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
+92
View File
@@ -0,0 +1,92 @@
import { CID } from 'multiformats/cid'
import { decodeNode } from './pb-decode.js'
import { encodeNode } from './pb-encode.js'
import { prepare, validate, createNode, createLink, toByteView } from './util.js'
/**
* @template T
* @typedef {import('multiformats/codecs/interface').ByteView<T>} ByteView
*/
/**
* @template T
* @typedef {import('multiformats/codecs/interface').ArrayBufferView<T>} ArrayBufferView
*/
/**
* @typedef {import('./interface.js').PBLink} PBLink
* @typedef {import('./interface.js').PBNode} PBNode
*/
export const name = 'dag-pb'
export const code = 0x70
/**
* @param {PBNode} node
* @returns {ByteView<PBNode>}
*/
export function encode (node) {
validate(node)
const pbn = {}
if (node.Links) {
pbn.Links = node.Links.map((l) => {
const link = {}
if (l.Hash) {
link.Hash = l.Hash.bytes // cid -> bytes
}
if (l.Name !== undefined) {
link.Name = l.Name
}
if (l.Tsize !== undefined) {
link.Tsize = l.Tsize
}
return link
})
}
if (node.Data) {
pbn.Data = node.Data
}
return encodeNode(pbn)
}
/**
* @param {ByteView<PBNode> | ArrayBufferView<PBNode>} bytes
* @returns {PBNode}
*/
export function decode (bytes) {
const buf = toByteView(bytes)
const pbn = decodeNode(buf)
const node = {}
if (pbn.Data) {
node.Data = pbn.Data
}
if (pbn.Links) {
node.Links = pbn.Links.map((l) => {
const link = {}
try {
link.Hash = CID.decode(l.Hash)
} catch {
// ignore parse fail
}
if (!link.Hash) {
throw new Error('Invalid Hash field found in link, expected CID')
}
if (l.Name !== undefined) {
link.Name = l.Name
}
if (l.Tsize !== undefined) {
link.Tsize = l.Tsize
}
return link
})
}
return node
}
export { prepare, validate, createNode, createLink }
+33
View File
@@ -0,0 +1,33 @@
import type { CID } from 'multiformats/cid'
/*
PBNode and PBLink match the DAG-PB logical format, as described at:
https://github.com/ipld/specs/blob/master/block-layer/codecs/dag-pb.md#logical-format
*/
export interface PBLink {
Name?: string
Tsize?: number
Hash: CID
}
export interface PBNode {
Data?: Uint8Array
Links: PBLink[]
}
// Raw versions of PBNode and PBLink used internally to deal with the underlying
// encode/decode byte interface.
// A future iteration could make pb-encode.js and pb-decode.js aware of PBNode
// and PBLink specifics (including CID and optionals).
export interface RawPBLink {
Name: string
Tsize: number
Hash: Uint8Array
}
export interface RawPBNode {
Data: Uint8Array
Links: RawPBLink[]
}
+193
View File
@@ -0,0 +1,193 @@
const textDecoder = new TextDecoder()
/**
* @typedef {import('./interface.js').RawPBLink} RawPBLink
*/
/**
* @typedef {import('./interface.js').RawPBNode} RawPBNode
*/
/**
* @param {Uint8Array} bytes
* @param {number} offset
* @returns {[number, number]}
*/
function decodeVarint (bytes, offset) {
let v = 0
for (let shift = 0; ; shift += 7) {
/* c8 ignore next 3 */
if (shift >= 64) {
throw new Error('protobuf: varint overflow')
}
/* c8 ignore next 3 */
if (offset >= bytes.length) {
throw new Error('protobuf: unexpected end of data')
}
const b = bytes[offset++]
v += shift < 28 ? (b & 0x7f) << shift : (b & 0x7f) * (2 ** shift)
if (b < 0x80) {
break
}
}
return [v, offset]
}
/**
* @param {Uint8Array} bytes
* @param {number} offset
* @returns {[Uint8Array, number]}
*/
function decodeBytes (bytes, offset) {
let byteLen
;[byteLen, offset] = decodeVarint(bytes, offset)
const postOffset = offset + byteLen
/* c8 ignore next 3 */
if (byteLen < 0 || postOffset < 0) {
throw new Error('protobuf: invalid length')
}
/* c8 ignore next 3 */
if (postOffset > bytes.length) {
throw new Error('protobuf: unexpected end of data')
}
return [bytes.subarray(offset, postOffset), postOffset]
}
/**
* @param {Uint8Array} bytes
* @param {number} index
* @returns {[number, number, number]}
*/
function decodeKey (bytes, index) {
let wire
;[wire, index] = decodeVarint(bytes, index)
// [wireType, fieldNum, newIndex]
return [wire & 0x7, wire >> 3, index]
}
/**
* @param {Uint8Array} bytes
* @returns {RawPBLink}
*/
function decodeLink (bytes) {
/** @type {RawPBLink} */
const link = {}
const l = bytes.length
let index = 0
while (index < l) {
let wireType, fieldNum
;[wireType, fieldNum, index] = decodeKey(bytes, index)
if (fieldNum === 1) {
if (link.Hash) {
throw new Error('protobuf: (PBLink) duplicate Hash section')
}
if (wireType !== 2) {
throw new Error(`protobuf: (PBLink) wrong wireType (${wireType}) for Hash`)
}
if (link.Name !== undefined) {
throw new Error('protobuf: (PBLink) invalid order, found Name before Hash')
}
if (link.Tsize !== undefined) {
throw new Error('protobuf: (PBLink) invalid order, found Tsize before Hash')
}
[link.Hash, index] = decodeBytes(bytes, index)
} else if (fieldNum === 2) {
if (link.Name !== undefined) {
throw new Error('protobuf: (PBLink) duplicate Name section')
}
if (wireType !== 2) {
throw new Error(`protobuf: (PBLink) wrong wireType (${wireType}) for Name`)
}
if (link.Tsize !== undefined) {
throw new Error('protobuf: (PBLink) invalid order, found Tsize before Name')
}
let byts
;[byts, index] = decodeBytes(bytes, index)
link.Name = textDecoder.decode(byts)
} else if (fieldNum === 3) {
if (link.Tsize !== undefined) {
throw new Error('protobuf: (PBLink) duplicate Tsize section')
}
if (wireType !== 0) {
throw new Error(`protobuf: (PBLink) wrong wireType (${wireType}) for Tsize`)
}
[link.Tsize, index] = decodeVarint(bytes, index)
} else {
throw new Error(`protobuf: (PBLink) invalid fieldNumber, expected 1, 2 or 3, got ${fieldNum}`)
}
}
/* c8 ignore next 3 */
if (index > l) {
throw new Error('protobuf: (PBLink) unexpected end of data')
}
return link
}
/**
* @param {Uint8Array} bytes
* @returns {RawPBNode}
*/
export function decodeNode (bytes) {
const l = bytes.length
let index = 0
/** @type {RawPBLink[]|void} */
let links = undefined // eslint-disable-line no-undef-init
let linksBeforeData = false
/** @type {Uint8Array|void} */
let data = undefined // eslint-disable-line no-undef-init
while (index < l) {
let wireType, fieldNum
;[wireType, fieldNum, index] = decodeKey(bytes, index)
if (wireType !== 2) {
throw new Error(`protobuf: (PBNode) invalid wireType, expected 2, got ${wireType}`)
}
if (fieldNum === 1) {
if (data) {
throw new Error('protobuf: (PBNode) duplicate Data section')
}
[data, index] = decodeBytes(bytes, index)
if (links) {
linksBeforeData = true
}
} else if (fieldNum === 2) {
if (linksBeforeData) { // interleaved Links/Data/Links
throw new Error('protobuf: (PBNode) duplicate Links section')
} else if (!links) {
links = []
}
let byts
;[byts, index] = decodeBytes(bytes, index)
links.push(decodeLink(byts))
} else {
throw new Error(`protobuf: (PBNode) invalid fieldNumber, expected 1 or 2, got ${fieldNum}`)
}
}
/* c8 ignore next 3 */
if (index > l) {
throw new Error('protobuf: (PBNode) unexpected end of data')
}
/** @type {RawPBNode} */
const node = {}
if (data) {
node.Data = data
}
node.Links = links || []
return node
}
+214
View File
@@ -0,0 +1,214 @@
const textEncoder = new TextEncoder()
const maxInt32 = 2 ** 32
const maxUInt32 = 2 ** 31
/**
* @typedef {import('./interface.js').RawPBLink} RawPBLink
*/
/**
* @typedef {import('./interface.js').RawPBNode} RawPBNode
*/
// the encoders work backward from the end of the bytes array
/**
* encodeLink() is passed a slice of the parent byte array that ends where this
* link needs to end, so it packs to the right-most part of the passed `bytes`
*
* @param {RawPBLink} link
* @param {Uint8Array} bytes
* @returns {number}
*/
function encodeLink (link, bytes) {
let i = bytes.length
if (typeof link.Tsize === 'number') {
if (link.Tsize < 0) {
throw new Error('Tsize cannot be negative')
}
if (!Number.isSafeInteger(link.Tsize)) {
throw new Error('Tsize too large for encoding')
}
i = encodeVarint(bytes, i, link.Tsize) - 1
bytes[i] = 0x18
}
if (typeof link.Name === 'string') {
const nameBytes = textEncoder.encode(link.Name)
i -= nameBytes.length
bytes.set(nameBytes, i)
i = encodeVarint(bytes, i, nameBytes.length) - 1
bytes[i] = 0x12
}
if (link.Hash) {
i -= link.Hash.length
bytes.set(link.Hash, i)
i = encodeVarint(bytes, i, link.Hash.length) - 1
bytes[i] = 0xa
}
return bytes.length - i
}
/**
* Encodes a PBNode into a new byte array of precisely the correct size
*
* @param {RawPBNode} node
* @returns {Uint8Array}
*/
export function encodeNode (node) {
const size = sizeNode(node)
const bytes = new Uint8Array(size)
let i = size
if (node.Data) {
i -= node.Data.length
bytes.set(node.Data, i)
i = encodeVarint(bytes, i, node.Data.length) - 1
bytes[i] = 0xa
}
if (node.Links) {
for (let index = node.Links.length - 1; index >= 0; index--) {
const size = encodeLink(node.Links[index], bytes.subarray(0, i))
i -= size
i = encodeVarint(bytes, i, size) - 1
bytes[i] = 0x12
}
}
return bytes
}
/**
* work out exactly how many bytes this link takes up
*
* @param {RawPBLink} link
* @returns
*/
function sizeLink (link) {
let n = 0
if (link.Hash) {
const l = link.Hash.length
n += 1 + l + sov(l)
}
if (typeof link.Name === 'string') {
const l = textEncoder.encode(link.Name).length
n += 1 + l + sov(l)
}
if (typeof link.Tsize === 'number') {
n += 1 + sov(link.Tsize)
}
return n
}
/**
* Work out exactly how many bytes this node takes up
*
* @param {RawPBNode} node
* @returns {number}
*/
function sizeNode (node) {
let n = 0
if (node.Data) {
const l = node.Data.length
n += 1 + l + sov(l)
}
if (node.Links) {
for (const link of node.Links) {
const l = sizeLink(link)
n += 1 + l + sov(l)
}
}
return n
}
/**
* @param {Uint8Array} bytes
* @param {number} offset
* @param {number} v
* @returns {number}
*/
function encodeVarint (bytes, offset, v) {
offset -= sov(v)
const base = offset
while (v >= maxUInt32) {
bytes[offset++] = (v & 0x7f) | 0x80
v /= 128
}
while (v >= 128) {
bytes[offset++] = (v & 0x7f) | 0x80
v >>>= 7
}
bytes[offset] = v
return base
}
/**
* size of varint
*
* @param {number} x
* @returns {number}
*/
function sov (x) {
if (x % 2 === 0) {
x++
}
return Math.floor((len64(x) + 6) / 7)
}
/**
* golang math/bits, how many bits does it take to represent this integer?
*
* @param {number} x
* @returns {number}
*/
function len64 (x) {
let n = 0
if (x >= maxInt32) {
x = Math.floor(x / maxInt32)
n = 32
}
if (x >= (1 << 16)) {
x >>>= 16
n += 16
}
if (x >= (1 << 8)) {
x >>>= 8
n += 8
}
return n + len8tab[x]
}
// golang math/bits
const len8tab = [
0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
]
+258
View File
@@ -0,0 +1,258 @@
import { CID } from 'multiformats/cid'
/* eslint-disable complexity, no-nested-ternary */
/**
* @typedef {import('./interface.js').PBLink} PBLink
* @typedef {import('./interface.js').PBNode} PBNode
*/
/**
* @template T
* @typedef {import('multiformats/codecs/interface').ByteView<T>} ByteView
*/
/**
* @template T
* @typedef {import('multiformats/codecs/interface').ArrayBufferView<T>} ArrayBufferView
*/
const pbNodeProperties = ['Data', 'Links']
const pbLinkProperties = ['Hash', 'Name', 'Tsize']
const textEncoder = new TextEncoder()
/**
* @param {PBLink} a
* @param {PBLink} b
* @returns {number}
*/
function linkComparator (a, b) {
if (a === b) {
return 0
}
const abuf = a.Name ? textEncoder.encode(a.Name) : []
const bbuf = b.Name ? textEncoder.encode(b.Name) : []
let x = abuf.length
let y = bbuf.length
for (let i = 0, len = Math.min(x, y); i < len; ++i) {
if (abuf[i] !== bbuf[i]) {
x = abuf[i]
y = bbuf[i]
break
}
}
return x < y ? -1 : y < x ? 1 : 0
}
/**
* @param {any} node
* @param {string[]} properties
* @returns {boolean}
*/
function hasOnlyProperties (node, properties) {
return !Object.keys(node).some((p) => !properties.includes(p))
}
/**
* Converts a CID, or a PBLink-like object to a PBLink
*
* @param {any} link
* @returns {PBLink}
*/
function asLink (link) {
if (typeof link.asCID === 'object') {
const Hash = CID.asCID(link)
if (!Hash) {
throw new TypeError('Invalid DAG-PB form')
}
return { Hash }
}
if (typeof link !== 'object' || Array.isArray(link)) {
throw new TypeError('Invalid DAG-PB form')
}
const pbl = {}
if (link.Hash) {
let cid = CID.asCID(link.Hash)
try {
if (!cid) {
if (typeof link.Hash === 'string') {
cid = CID.parse(link.Hash)
} else if (link.Hash instanceof Uint8Array) {
cid = CID.decode(link.Hash)
}
}
} catch (/** @type {any} */ e) {
throw new TypeError(`Invalid DAG-PB form: ${e.message}`)
}
if (cid) {
pbl.Hash = cid
}
}
if (!pbl.Hash) {
throw new TypeError('Invalid DAG-PB form')
}
if (typeof link.Name === 'string') {
pbl.Name = link.Name
}
if (typeof link.Tsize === 'number') {
pbl.Tsize = link.Tsize
}
return pbl
}
/**
* @param {any} node
* @returns {PBNode}
*/
export function prepare (node) {
if (node instanceof Uint8Array || typeof node === 'string') {
node = { Data: node }
}
if (typeof node !== 'object' || Array.isArray(node)) {
throw new TypeError('Invalid DAG-PB form')
}
/** @type {PBNode} */
const pbn = {}
if (node.Data !== undefined) {
if (typeof node.Data === 'string') {
pbn.Data = textEncoder.encode(node.Data)
} else if (node.Data instanceof Uint8Array) {
pbn.Data = node.Data
} else {
throw new TypeError('Invalid DAG-PB form')
}
}
if (node.Links !== undefined) {
if (Array.isArray(node.Links)) {
pbn.Links = node.Links.map(asLink)
pbn.Links.sort(linkComparator)
} else {
throw new TypeError('Invalid DAG-PB form')
}
} else {
pbn.Links = []
}
return pbn
}
/**
* @param {PBNode} node
*/
export function validate (node) {
/*
type PBLink struct {
Hash optional Link
Name optional String
Tsize optional Int
}
type PBNode struct {
Links [PBLink]
Data optional Bytes
}
*/
// @ts-ignore private property for TS
if (!node || typeof node !== 'object' || Array.isArray(node) || node instanceof Uint8Array || (node['/'] && node['/'] === node.bytes)) {
throw new TypeError('Invalid DAG-PB form')
}
if (!hasOnlyProperties(node, pbNodeProperties)) {
throw new TypeError('Invalid DAG-PB form (extraneous properties)')
}
if (node.Data !== undefined && !(node.Data instanceof Uint8Array)) {
throw new TypeError('Invalid DAG-PB form (Data must be bytes)')
}
if (!Array.isArray(node.Links)) {
throw new TypeError('Invalid DAG-PB form (Links must be a list)')
}
for (let i = 0; i < node.Links.length; i++) {
const link = node.Links[i]
// @ts-ignore private property for TS
if (!link || typeof link !== 'object' || Array.isArray(link) || link instanceof Uint8Array || (link['/'] && link['/'] === link.bytes)) {
throw new TypeError('Invalid DAG-PB form (bad link)')
}
if (!hasOnlyProperties(link, pbLinkProperties)) {
throw new TypeError('Invalid DAG-PB form (extraneous properties on link)')
}
if (link.Hash === undefined) {
throw new TypeError('Invalid DAG-PB form (link must have a Hash)')
}
// @ts-ignore private property for TS
if (link.Hash == null || !link.Hash['/'] || link.Hash['/'] !== link.Hash.bytes) {
throw new TypeError('Invalid DAG-PB form (link Hash must be a CID)')
}
if (link.Name !== undefined && typeof link.Name !== 'string') {
throw new TypeError('Invalid DAG-PB form (link Name must be a string)')
}
if (link.Tsize !== undefined) {
if (typeof link.Tsize !== 'number' || link.Tsize % 1 !== 0) {
throw new TypeError('Invalid DAG-PB form (link Tsize must be an integer)')
}
if (link.Tsize < 0) {
throw new TypeError('Invalid DAG-PB form (link Tsize cannot be negative)')
}
}
if (i > 0 && linkComparator(link, node.Links[i - 1]) === -1) {
throw new TypeError('Invalid DAG-PB form (links must be sorted by Name bytes)')
}
}
}
/**
* @param {Uint8Array} data
* @param {PBLink[]} [links]
* @returns {PBNode}
*/
export function createNode (data, links = []) {
return prepare({ Data: data, Links: links })
}
/**
* @param {string} name
* @param {number} size
* @param {CID} cid
* @returns {PBLink}
*/
export function createLink (name, size, cid) {
return asLink({ Hash: cid, Name: name, Tsize: size })
}
/**
* @template T
* @param {ByteView<T> | ArrayBufferView<T>} buf
* @returns {ByteView<T>}
*/
export function toByteView (buf) {
if (buf instanceof ArrayBuffer) {
return new Uint8Array(buf, 0, buf.byteLength)
}
return buf
}