@wovin/core 0.1.35 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -12
- package/dist/applog/applog-helpers.d.ts +12 -12
- package/dist/applog/applog-helpers.d.ts.map +1 -1
- package/dist/applog/applog-utils.d.ts +25 -6
- package/dist/applog/applog-utils.d.ts.map +1 -1
- package/dist/applog/datom-types.d.ts +4 -5
- package/dist/applog/datom-types.d.ts.map +1 -1
- package/dist/applog.d.ts +3 -3
- package/dist/applog.d.ts.map +1 -1
- package/dist/{applog.min.js → applog.js} +6 -7
- package/dist/blockstore.d.ts +1 -1
- package/dist/blockstore.d.ts.map +1 -1
- package/dist/{blockstore.min.js → blockstore.js} +1 -3
- package/dist/{blockstore.min.js.map → blockstore.js.map} +1 -1
- package/dist/{chunk-KXMTKPF4.min.js → chunk-3JZMOEOD.js} +8 -8
- package/dist/chunk-3JZMOEOD.js.map +1 -0
- package/dist/chunk-3WZVG277.js +434 -0
- package/dist/chunk-3WZVG277.js.map +1 -0
- package/dist/chunk-7Z5YDQKK.js +1 -0
- package/dist/chunk-CPSDKFBG.js +147 -0
- package/dist/chunk-CPSDKFBG.js.map +1 -0
- package/dist/chunk-E46VTKTZ.js +1 -0
- package/dist/{chunk-H3VQJP56.min.js → chunk-J2FDHGOZ.js} +9 -9
- package/dist/chunk-J2FDHGOZ.js.map +1 -0
- package/dist/chunk-L5EEEGE6.js +1862 -0
- package/dist/chunk-L5EEEGE6.js.map +1 -0
- package/dist/{chunk-BRC7LSM6.min.js → chunk-PD3C7XUM.js} +5 -5
- package/dist/chunk-PD3C7XUM.js.map +1 -0
- package/dist/chunk-QZXKQCAY.js +1026 -0
- package/dist/chunk-QZXKQCAY.js.map +1 -0
- package/dist/{chunk-QPGEBDMJ.min.js → chunk-YDAKBU6Q.js} +1 -1
- package/dist/chunk-YDAKBU6Q.js.map +1 -0
- package/dist/chunk-ZAADLBSB.js +36 -0
- package/dist/chunk-ZAADLBSB.js.map +1 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/{index.min.js → index.js} +73 -46
- package/dist/ipfs/car.d.ts +11 -11
- package/dist/ipfs/car.d.ts.map +1 -1
- package/dist/ipfs/ipfs-utils.d.ts +2 -2
- package/dist/ipfs/ipfs-utils.d.ts.map +1 -1
- package/dist/ipfs.d.ts +3 -3
- package/dist/ipfs.d.ts.map +1 -1
- package/dist/{ipfs.min.js → ipfs.js} +7 -10
- package/dist/ipns.d.ts +1 -1
- package/dist/ipns.d.ts.map +1 -1
- package/dist/ipns.js +64 -0
- package/dist/ipns.js.map +1 -0
- package/dist/pubsub/pub-pull.d.ts +3 -3
- package/dist/pubsub/pub-pull.d.ts.map +1 -1
- package/dist/pubsub/pubsub-types.d.ts +3 -3
- package/dist/pubsub/pubsub-types.d.ts.map +1 -1
- package/dist/pubsub/snap-push.d.ts +4 -4
- package/dist/pubsub/snap-push.d.ts.map +1 -1
- package/dist/pubsub/ucan.d.ts +1 -1
- package/dist/pubsub/ucan.d.ts.map +1 -1
- package/dist/pubsub.d.ts +4 -4
- package/dist/pubsub.d.ts.map +1 -1
- package/dist/{pubsub.min.js → pubsub.js} +7 -10
- package/dist/query/attr-helpers.d.ts +5 -0
- package/dist/query/attr-helpers.d.ts.map +1 -0
- package/dist/query/basic.d.ts +85 -21
- package/dist/query/basic.d.ts.map +1 -1
- package/dist/query/divergences.d.ts +5 -5
- package/dist/query/divergences.d.ts.map +1 -1
- package/dist/query/entity-collection.d.ts +19 -0
- package/dist/query/entity-collection.d.ts.map +1 -0
- package/dist/query/matchers.d.ts +1 -1
- package/dist/query/matchers.d.ts.map +1 -1
- package/dist/query/memoized.d.ts +66 -0
- package/dist/query/memoized.d.ts.map +1 -0
- package/dist/query/situations.d.ts +2 -1
- package/dist/query/situations.d.ts.map +1 -1
- package/dist/query/subscribable.d.ts +111 -0
- package/dist/query/subscribable.d.ts.map +1 -0
- package/dist/query/types.d.ts +54 -14
- package/dist/query/types.d.ts.map +1 -1
- package/dist/query.d.ts +9 -5
- package/dist/query.d.ts.map +1 -1
- package/dist/{query.min.js → query.js} +51 -32
- package/dist/retrieve/index.d.ts +1 -1
- package/dist/retrieve/index.d.ts.map +1 -1
- package/dist/retrieve/update-thread.d.ts +3 -3
- package/dist/retrieve/update-thread.d.ts.map +1 -1
- package/dist/retrieve.d.ts +1 -1
- package/dist/retrieve.d.ts.map +1 -1
- package/dist/retrieve.js +14 -0
- package/dist/thread/basic.d.ts +15 -19
- package/dist/thread/basic.d.ts.map +1 -1
- package/dist/thread/filters.d.ts +8 -10
- package/dist/thread/filters.d.ts.map +1 -1
- package/dist/thread/indexes.d.ts +56 -0
- package/dist/thread/indexes.d.ts.map +1 -0
- package/dist/thread/mapped.d.ts +40 -11
- package/dist/thread/mapped.d.ts.map +1 -1
- package/dist/thread/utils.d.ts +5 -5
- package/dist/thread/utils.d.ts.map +1 -1
- package/dist/thread/writeable.d.ts +2 -2
- package/dist/thread/writeable.d.ts.map +1 -1
- package/dist/thread.d.ts +6 -5
- package/dist/thread.d.ts.map +1 -1
- package/dist/{thread.min.js → thread.js} +9 -6
- package/dist/types/typescript-utils.d.ts +6 -5
- package/dist/types/typescript-utils.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/{types.min.js → types.js} +3 -4
- package/dist/utils/debug-name.d.ts +13 -0
- package/dist/utils/debug-name.d.ts.map +1 -0
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +9 -0
- package/package.json +32 -23
- package/src/applog/applog-helpers.ts +155 -0
- package/src/applog/applog-utils.test.ts +108 -0
- package/src/applog/applog-utils.ts +507 -0
- package/src/applog/datom-types.ts +148 -0
- package/src/applog.ts +3 -0
- package/src/blockstore/index.ts +36 -0
- package/src/blockstore.ts +1 -0
- package/src/index.ts +8 -0
- package/src/ipfs/car.ts +291 -0
- package/src/ipfs/fetch-snapshot-chain.ts +135 -0
- package/src/ipfs/ipfs-utils.ts +132 -0
- package/src/ipfs.ts +3 -0
- package/src/ipns/ipns-record.ts +115 -0
- package/src/ipns.ts +1 -0
- package/src/pubsub/UCAN Specs Overview.md +217 -0
- package/src/pubsub/connector.ts +9 -0
- package/src/pubsub/pub-pull.ts +31 -0
- package/src/pubsub/pubsub-types.ts +90 -0
- package/src/pubsub/snap-push.ts +277 -0
- package/src/pubsub/ucan-example.ts +61 -0
- package/src/pubsub/ucan.ts +56 -0
- package/src/pubsub.ts +4 -0
- package/src/query/attr-helpers.ts +5 -0
- package/src/query/basic.ts +1245 -0
- package/src/query/divergences.ts +50 -0
- package/src/query/entity-collection.ts +131 -0
- package/src/query/liveFilterAndMap.test.ts +102 -0
- package/src/query/matchers.ts +8 -0
- package/src/query/memoized.test.ts +151 -0
- package/src/query/memoized.ts +180 -0
- package/src/query/query-steps.ts +4 -0
- package/src/query/query.test.ts +538 -0
- package/src/query/situations.ts +261 -0
- package/src/query/subscribable.test.ts +245 -0
- package/src/query/subscribable.ts +234 -0
- package/src/query/types.ts +155 -0
- package/src/query/withoutDeleted.test.ts +204 -0
- package/src/query.ts +9 -0
- package/src/retrieve/index.ts +1 -0
- package/src/retrieve/update-thread.ts +248 -0
- package/src/retrieve.ts +1 -0
- package/src/test/perf/query.1m.perf.test.ts +94 -0
- package/src/test/perf/query.perf.test.ts +389 -0
- package/src/test/perf/query.realdata.perf.test.ts +182 -0
- package/src/thread/basic.ts +209 -0
- package/src/thread/filters.ts +227 -0
- package/src/thread/indexes.ts +250 -0
- package/src/thread/joinThreads.test.ts +304 -0
- package/src/thread/mapped.ts +226 -0
- package/src/thread/utils.ts +144 -0
- package/src/thread/writeable.ts +163 -0
- package/src/thread.ts +6 -0
- package/src/types/typescript-utils.ts +64 -0
- package/src/types.ts +1 -0
- package/src/utils/debug-name.ts +54 -0
- package/src/utils.ts +4 -0
- package/dist/chunk-2Y2PYHGR.min.js +0 -65
- package/dist/chunk-2Y2PYHGR.min.js.map +0 -1
- package/dist/chunk-5MMGBK2U.min.js +0 -1
- package/dist/chunk-7IDQIMQO.min.js +0 -1
- package/dist/chunk-BRC7LSM6.min.js.map +0 -1
- package/dist/chunk-COXXILXC.min.js +0 -512
- package/dist/chunk-COXXILXC.min.js.map +0 -1
- package/dist/chunk-GDX2OO7L.min.js +0 -9080
- package/dist/chunk-GDX2OO7L.min.js.map +0 -1
- package/dist/chunk-H3VQJP56.min.js.map +0 -1
- package/dist/chunk-HYMC7W6S.min.js +0 -1549
- package/dist/chunk-HYMC7W6S.min.js.map +0 -1
- package/dist/chunk-KEHU7HGZ.min.js +0 -5216
- package/dist/chunk-KEHU7HGZ.min.js.map +0 -1
- package/dist/chunk-KXMTKPF4.min.js.map +0 -1
- package/dist/chunk-PHITDXZT.min.js +0 -36
- package/dist/chunk-QO2KMGDN.min.js +0 -3771
- package/dist/chunk-QO2KMGDN.min.js.map +0 -1
- package/dist/chunk-QPGEBDMJ.min.js.map +0 -1
- package/dist/chunk-WXLCBTHX.min.js +0 -1606
- package/dist/chunk-WXLCBTHX.min.js.map +0 -1
- package/dist/ipns.min.js +0 -6419
- package/dist/ipns.min.js.map +0 -1
- package/dist/mobx/mobx-utils.d.ts +0 -82
- package/dist/mobx/mobx-utils.d.ts.map +0 -1
- package/dist/mobx.d.ts +0 -2
- package/dist/mobx.d.ts.map +0 -1
- package/dist/mobx.min.js +0 -141
- package/dist/retrieve.min.js +0 -17
- package/dist/types.min.js.map +0 -1
- package/dist/utils.min.js +0 -10
- package/dist/utils.min.js.map +0 -1
- /package/dist/{applog.min.js.map → applog.js.map} +0 -0
- /package/dist/{chunk-5MMGBK2U.min.js.map → chunk-7Z5YDQKK.js.map} +0 -0
- /package/dist/{chunk-7IDQIMQO.min.js.map → chunk-E46VTKTZ.js.map} +0 -0
- /package/dist/{chunk-PHITDXZT.min.js.map → index.js.map} +0 -0
- /package/dist/{index.min.js.map → ipfs.js.map} +0 -0
- /package/dist/{ipfs.min.js.map → pubsub.js.map} +0 -0
- /package/dist/{mobx.min.js.map → query.js.map} +0 -0
- /package/dist/{pubsub.min.js.map → retrieve.js.map} +0 -0
- /package/dist/{query.min.js.map → thread.js.map} +0 -0
- /package/dist/{retrieve.min.js.map → types.js.map} +0 -0
- /package/dist/{thread.min.js.map → utils.js.map} +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as dagJson from '@ipld/dag-json'
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha2.js'
|
|
3
|
+
import { Logger } from 'besonders-logger'
|
|
4
|
+
import { CID, digest as Digest } from 'multiformats'
|
|
5
|
+
import { encode as multiformatsEncode } from 'multiformats/block'
|
|
6
|
+
// import { encode } from 'multiformats/block';
|
|
7
|
+
import { Applog, ApplogEncNoCid, ApplogNoCid, ApplogOfSomeSort, CidString, IpnsString, isEncryptedApplog } from '../applog/datom-types.ts'
|
|
8
|
+
|
|
9
|
+
import { base36 } from 'multiformats/bases/base36'
|
|
10
|
+
import { sha256 as sha265Hasher } from 'multiformats/hashes/sha2'
|
|
11
|
+
|
|
12
|
+
/* THIS FILE SHOULD NOT DEPEND ON UI STUFF, SO THAT TESTS CAN RUN WITH MINIMAL DEPENDENCIES */
|
|
13
|
+
|
|
14
|
+
const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars
|
|
15
|
+
|
|
16
|
+
export const MULTICODEC_IPNS_KEY = 0x72
|
|
17
|
+
|
|
18
|
+
export function prepareForPub(log: ApplogOfSomeSort, without: string[] = ['cid']) {
|
|
19
|
+
if (!log) throw ERROR('falsy log', log)
|
|
20
|
+
let cid = (log as Applog).cid
|
|
21
|
+
if (isEncryptedApplog(log)) {
|
|
22
|
+
if (!cid) cid = getCidSync(encodeBlock(log as ApplogEncNoCid).bytes).toString()
|
|
23
|
+
WARN('preparing an encrypted applog - really?')
|
|
24
|
+
return { log, cid }
|
|
25
|
+
}
|
|
26
|
+
const logWithout = {}
|
|
27
|
+
for (let [key, val] of Object.entries(log)) {
|
|
28
|
+
if (val === undefined) {
|
|
29
|
+
WARN(`log.${key} is undefined, which is not allowed - encoding as null`, log)
|
|
30
|
+
val = null
|
|
31
|
+
}
|
|
32
|
+
if (!without.includes(key)) {
|
|
33
|
+
logWithout[key] = val // && key === 'pv' ? CID.parse(val) : val //HACK: disabled until clarified: https://discuss.ipfs.tech/t/pin-dag-with-open-ends/17612
|
|
34
|
+
} else {
|
|
35
|
+
VERBOSE('excluding app log', { key, val })
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { log: logWithout as Applog, cid }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function encodeApplogAndGetCid(log: ApplogNoCid) {
|
|
42
|
+
return getCidSync(encodeApplog(log).bytes)
|
|
43
|
+
}
|
|
44
|
+
export function encodeApplog(log: ApplogNoCid | ApplogEncNoCid): { bytes: dagJson.ByteView<any>; cid: CID } {
|
|
45
|
+
return encodeBlock(prepareForPub(log)?.log)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getCidSync(bytes: dagJson.ByteView<any>) {
|
|
49
|
+
// Hacky way to use a sync sha265 lib to create a CID - code inspired by https://github.com/multiformats/js-multiformats#multihash-hashers
|
|
50
|
+
const hash = sha256(bytes)
|
|
51
|
+
const digest = Digest.create(sha265Hasher.code, hash)
|
|
52
|
+
const cid = CID.create(1, dagJson.code, digest)
|
|
53
|
+
VERBOSE(`[getCidSync]`, { bytes, hash, digest, cid })
|
|
54
|
+
return cid
|
|
55
|
+
}
|
|
56
|
+
/** encode the json object into an IPLD block */
|
|
57
|
+
export function encodeBlock(jsonObject: any): { bytes: dagJson.ByteView<any>; cid: CID } {
|
|
58
|
+
DEBUG('[encodeBlock]', jsonObject)
|
|
59
|
+
try {
|
|
60
|
+
const byteView = dagJson.encode(jsonObject)
|
|
61
|
+
return { bytes: byteView, cid: getCidSync(byteView) }
|
|
62
|
+
} catch (err) {
|
|
63
|
+
throw ERROR('[encodeBlock] failed to encode:', jsonObject, err)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function encodeBlockOriginal(jsonObject: any) {
|
|
68
|
+
// HACK re-added this to verify the sync variant is sane
|
|
69
|
+
const encoded = await multiformatsEncode({ value: jsonObject, codec: dagJson, hasher: sha265Hasher })
|
|
70
|
+
const syncVariant = encodeBlock(jsonObject)
|
|
71
|
+
if (syncVariant.cid.toString() !== encoded.cid.toString()) {
|
|
72
|
+
ERROR(`[encodeBlockOriginal] sync cid mismatch`, { jsonObject, encoded, syncVariant })
|
|
73
|
+
}
|
|
74
|
+
return encoded
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function tryParseCID(cidString: CidString) {
|
|
78
|
+
let cid: CID | null = null
|
|
79
|
+
let errors = []
|
|
80
|
+
try {
|
|
81
|
+
cid = CID.parse(cidString)
|
|
82
|
+
} catch (err) {
|
|
83
|
+
VERBOSE(`[retrieveThread] couldn't parse pubID with default base`)
|
|
84
|
+
errors.push(err)
|
|
85
|
+
}
|
|
86
|
+
if (!cid) {
|
|
87
|
+
try {
|
|
88
|
+
cid = CID.parse(cidString, base36) // e.g. for IPNS
|
|
89
|
+
} catch (err) {
|
|
90
|
+
VERBOSE(`[retrieveThread] couldn't parse pubID with base36`)
|
|
91
|
+
errors.push(err)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
cid,
|
|
96
|
+
errors: cid ? null : errors, // we only care about errors if we failed to parse
|
|
97
|
+
isIpns: cid && isIpnsKeyCid(cid),
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export function isIpnsKeyCid(cid: CID) {
|
|
101
|
+
return cid.code === MULTICODEC_IPNS_KEY
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function cidToString(cid: CID) {
|
|
105
|
+
if (cid.code == MULTICODEC_IPNS_KEY) {
|
|
106
|
+
return toIpnsString(cid)
|
|
107
|
+
} else {
|
|
108
|
+
return cid.toString()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export function toIpnsString(cid: CID) {
|
|
112
|
+
if (cid.code !== MULTICODEC_IPNS_KEY) throw ERROR(`Not an IPNS cid (${cid.code}):`, cid.toString())
|
|
113
|
+
return cid.toString(base36) as IpnsString
|
|
114
|
+
}
|
|
115
|
+
export function ensureValidCIDinstance(cidOrStringA: CID | CidString) {
|
|
116
|
+
return typeof cidOrStringA === 'string'
|
|
117
|
+
? CID.parse(cidOrStringA)
|
|
118
|
+
: typeof cidOrStringA.toV1 != 'function'
|
|
119
|
+
? CID.decode(cidOrStringA.bytes)
|
|
120
|
+
: cidOrStringA
|
|
121
|
+
}
|
|
122
|
+
export function areCidsEqual(cidOrStringA: CID | CidString, cidOrStringB: CID | CidString) {
|
|
123
|
+
if (!cidOrStringA || !cidOrStringB) throw new Error(`[areCidsEqual] invalid params: ${cidOrStringA}, ${cidOrStringB}`)
|
|
124
|
+
if (cidOrStringA === cidOrStringB) return true // shortcut if both are strings
|
|
125
|
+
const cidA = ensureValidCIDinstance(cidOrStringA)
|
|
126
|
+
const cidB = ensureValidCIDinstance(cidOrStringB)
|
|
127
|
+
return cidA.toV1().toString() === cidB.toV1().toString()
|
|
128
|
+
}
|
|
129
|
+
export function containsCid(list: (CID | CidString)[] | Set<CidString>, needle: CID | CidString) {
|
|
130
|
+
if (list instanceof Set) return list.has(typeof needle === 'string' ? needle : needle.toV1().toString()) // ? what if the CidString is a different form? (parse and format would cost performance)
|
|
131
|
+
return list.some(cidOrString => areCidsEqual(cidOrString, needle))
|
|
132
|
+
}
|
package/src/ipfs.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createIPNSRecord, marshalIPNSRecord, unmarshalIPNSRecord } from 'ipns'
|
|
2
|
+
import { privateKeyFromRaw } from '@libp2p/crypto/keys'
|
|
3
|
+
import { base36 } from 'multiformats/bases/base36'
|
|
4
|
+
import { base64pad } from 'multiformats/bases/base64'
|
|
5
|
+
import type { CID } from 'multiformats/cid'
|
|
6
|
+
|
|
7
|
+
export interface SignedIPNSRecord {
|
|
8
|
+
recordBytes: Uint8Array // marshalled protobuf, signed — the wire format
|
|
9
|
+
ipnsName: string // k51... string
|
|
10
|
+
value: string // /ipfs/<cid>
|
|
11
|
+
sequence: bigint
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Derive IPNS name string (k51...) from Ed25519 private key bytes */
|
|
15
|
+
export function ipnsNameFromPrivateKey(privKeyBytes: Uint8Array): string {
|
|
16
|
+
const privKey = privateKeyFromRaw(privKeyBytes)
|
|
17
|
+
return privKey.publicKey.toCID().toString(base36)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a signed IPNS record (protobuf wire format).
|
|
22
|
+
* Same bytes can be published to any naming service.
|
|
23
|
+
*/
|
|
24
|
+
export async function createSignedIPNSRecord(
|
|
25
|
+
privateKey: Uint8Array,
|
|
26
|
+
cid: CID,
|
|
27
|
+
sequence: bigint,
|
|
28
|
+
lifetimeMs = 365 * 24 * 60 * 60 * 1000, // 1 year
|
|
29
|
+
): Promise<SignedIPNSRecord> {
|
|
30
|
+
const privKey = privateKeyFromRaw(privateKey)
|
|
31
|
+
const value = `/ipfs/${cid.toV1()}`
|
|
32
|
+
const record = await createIPNSRecord(privKey, value, sequence, lifetimeMs)
|
|
33
|
+
const recordBytes = marshalIPNSRecord(record)
|
|
34
|
+
const ipnsName = privKey.publicKey.toCID().toString(base36)
|
|
35
|
+
return { recordBytes, ipnsName, value, sequence }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { unmarshalIPNSRecord }
|
|
39
|
+
|
|
40
|
+
/** A target that can receive a signed IPNS record */
|
|
41
|
+
export interface IPNSPublishTarget {
|
|
42
|
+
name: string
|
|
43
|
+
publish(ipnsName: string, recordBytes: Uint8Array): Promise<void>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolve current IPNS sequence number from a naming service.
|
|
48
|
+
* Returns null if the name was never published (404).
|
|
49
|
+
* Throws on network/server errors.
|
|
50
|
+
*/
|
|
51
|
+
export async function resolveIPNSSequence(
|
|
52
|
+
nameServiceUrl: string,
|
|
53
|
+
ipnsName: string,
|
|
54
|
+
): Promise<bigint | null> {
|
|
55
|
+
const url = `${nameServiceUrl}/name/${ipnsName}`
|
|
56
|
+
let response: Response
|
|
57
|
+
try {
|
|
58
|
+
response = await fetch(url)
|
|
59
|
+
} catch (err) {
|
|
60
|
+
throw new Error(`Network error resolving IPNS ${ipnsName}: ${err}`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (response.status === 404) return null
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
throw new Error(`HTTP ${response.status} resolving IPNS ${ipnsName}: ${response.statusText}`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { record, value } = await response.json()
|
|
69
|
+
if (!record && !value) return null // never published
|
|
70
|
+
|
|
71
|
+
// If raw record is available, unmarshal to get sequence
|
|
72
|
+
if (record) {
|
|
73
|
+
const bytes = base64pad.baseDecode(record)
|
|
74
|
+
const entry = unmarshalIPNSRecord(bytes)
|
|
75
|
+
return entry.sequence
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Some servers return value but not raw record — can't get sequence
|
|
79
|
+
return 0n
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create a signed IPNS record and publish to all configured targets.
|
|
84
|
+
* Resolves sequence from sequenceServiceUrl, creates record once, fans out.
|
|
85
|
+
* Throws if ALL targets fail; warns on partial failure.
|
|
86
|
+
*/
|
|
87
|
+
export async function publishIPNSRecord(
|
|
88
|
+
privateKey: Uint8Array,
|
|
89
|
+
cid: CID,
|
|
90
|
+
targets: IPNSPublishTarget[],
|
|
91
|
+
sequenceServiceUrl = 'https://name.web3.storage',
|
|
92
|
+
): Promise<SignedIPNSRecord> {
|
|
93
|
+
const ipnsName = ipnsNameFromPrivateKey(privateKey)
|
|
94
|
+
const currentSeq = await resolveIPNSSequence(sequenceServiceUrl, ipnsName)
|
|
95
|
+
const sequence = currentSeq != null ? currentSeq + 1n : 0n
|
|
96
|
+
const signed = await createSignedIPNSRecord(privateKey, cid, sequence)
|
|
97
|
+
|
|
98
|
+
const results = await Promise.allSettled(
|
|
99
|
+
targets.map(t => t.publish(ipnsName, signed.recordBytes))
|
|
100
|
+
)
|
|
101
|
+
const failures = results
|
|
102
|
+
.map((r, i) => ({ r, name: targets[i].name }))
|
|
103
|
+
.filter(({ r }) => r.status === 'rejected') as { r: PromiseRejectedResult; name: string }[]
|
|
104
|
+
|
|
105
|
+
if (failures.length > 0 && failures.length < targets.length) {
|
|
106
|
+
// Partial failure — log but don't throw
|
|
107
|
+
for (const { r, name } of failures) {
|
|
108
|
+
console.warn(`[publishIPNSRecord] target '${name}' failed:`, r.reason)
|
|
109
|
+
}
|
|
110
|
+
} else if (failures.length === targets.length) {
|
|
111
|
+
throw new Error(`All IPNS publish targets failed: ${failures.map(({ r, name }) => `${name}: ${r.reason}`).join('; ')}`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return signed
|
|
115
|
+
}
|
package/src/ipns.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ipns/ipns-record.ts'
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# UCAN Specifications and Implementations (2026 Overview)
|
|
2
|
+
|
|
3
|
+
[via Perplexity](https://www.perplexity.ai/search/ucan-specs-and-implementations-L_I1Xih5Se6CVF.FQS2rFQ)
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
User Controlled Authorization Networks (UCANs) define a distributed, capability-based authorization system centered on verifiable delegation chains and decentralized identifiers (DIDs). The ecosystem consists of a core v1.0.0-rc.1 specification with several sub-specifications plus multiple language implementations at different maturity and spec levels.[^1][^2]
|
|
8
|
+
|
|
9
|
+
## Core UCAN v1 Specification
|
|
10
|
+
|
|
11
|
+
The main UCAN specification v1.0.0-rc.1 describes UCAN as a trustless, secure, local-first, user-originated distributed authorization scheme that uses certificate-style capabilities instead of ACLs. It defines a DAG-CBOR/IPLD-based envelope format with content-addressed CIDs, a cryptographic suite (Ed25519, P-256, secp256k1, SHA-256), and separates lifecycle stages into Delegation, Invocation, Promise, and Revocation sub-specs.[^1]
|
|
12
|
+
|
|
13
|
+
### Key v1 design elements
|
|
14
|
+
|
|
15
|
+
- Capabilities are modeled as triples of subject, command, and policy, capturing what a principal may do to which resource under which constraints.
|
|
16
|
+
- Tokens are encoded as UCAN envelopes (signature bytes plus a signed payload with a type tag like `ucan/<subspec>@version`) instead of traditional JWTs.
|
|
17
|
+
- Payloads include `iss`, `aud`, `sub`, `cmd`, `args`, `nonce`, optional `meta`, and time bounds `nbf`/`exp`, with validation rules over time, principal alignment, and signatures.
|
|
18
|
+
|
|
19
|
+
## Delegation Sub-Specification
|
|
20
|
+
|
|
21
|
+
The UCAN Delegation spec v1.0.0-rc.1 refines how attenuated authority is represented and delegated between principals. It uses the envelope tag `ucan/[email protected]` and defines a Delegation payload with issuer, audience, subject, command, policy, nonce, metadata, and time bounds.
|
|
22
|
+
|
|
23
|
+
### Policy language and selectors
|
|
24
|
+
|
|
25
|
+
Delegation introduces a syntactically driven policy language using predicate trees with operators such as equality, inequality, glob matching (`like`), logical connectives, and quantifiers (`all`/`any`). Policies constrain an eventual invocation’s `args` field using jq-inspired selectors (for example `.to[^1]`, `.a[0:2]`, `.field?`) that always resolve to IPLD values.
|
|
26
|
+
|
|
27
|
+
### Powerline and resource model
|
|
28
|
+
|
|
29
|
+
The spec defines a "Powerline" pattern where `sub: null` allows automatic redelegation of a user’s authority across devices, while still enforcing principal alignment, time bounds, and policy constraints. Resources are treated semantically, with the subject as the default resource, and external resources referenced via conditions in the policy.
|
|
30
|
+
|
|
31
|
+
## Other v1 Sub-Specifications
|
|
32
|
+
|
|
33
|
+
The main spec lists additional required or recommended sub-specifications: Delegation and Invocation are required, while Promise and Revocation are recommended for a complete lifecycle. Invocation describes how to exercise capabilities proven by delegations, Promise covers awaiting invocation results, and Revocation describes undoing delegations to break chains for malicious or compromised principals.[^3]
|
|
34
|
+
|
|
35
|
+
## Pre‑v1 UCAN Specifications (JWT-Based)
|
|
36
|
+
|
|
37
|
+
Before v1, the UCAN ecosystem used a JWT-based format where capabilities were carried in an `att` (attenuation) field and proof chains in a `prf` field. The gobengo/ucanto-spec repository documents UCAN Specification v0.8.1 using JWT headers with `alg` and a UCAN version field, and payload fields like `att`, `aud`, `exp`, `fct`, `iss`, `nbf`, and `prf`.[^4][^5]
|
|
38
|
+
|
|
39
|
+
### Legacy library semantics
|
|
40
|
+
|
|
41
|
+
The ucan-wg/ts-ucan TypeScript library mirrors this earlier spec, describing UCANs as JWTs with a header containing `alg`, `typ`, and `uav` (UCAN version), and a payload including `att`, `aud`, `exp`, `fct`, `iss`, `nbf`, and `prf`. Elixir’s ExUcan library similarly models JWT-based UCANs with header fields and payload fields such as `ucv` (version), `cap` (capabilities), `aud`, `exp`, `iss`, `nbf`, `prf`, and `nnc` (nonce).[^6][^7][^4]
|
|
42
|
+
|
|
43
|
+
## Migration from JWT UCANs to v1 Envelope UCANs
|
|
44
|
+
|
|
45
|
+
The UCAN Library Implementation Guide explains that v1.0.0-rc.1 introduces a new envelope format, IPLD/CBOR encoding, structured capabilities, a richer policy language, and distinct sub-specs, replacing the earlier JWT representation. Pull requests for the Delegation and Invocation specs emphasize the move to IPLD-based tokens and UCAN-specific type tags like `ucan/[email protected]` as part of this migration.[^8][^9][^3]
|
|
46
|
+
|
|
47
|
+
### High-level differences
|
|
48
|
+
|
|
49
|
+
- Encoding: legacy UCANs use JWT (base64url JSON), whereas v1 UCANs use CID-addressed DAG-CBOR/IPLD envelopes.[^5]
|
|
50
|
+
- Capability model: legacy specs use `att` and `prf` arrays, while v1 organizes capabilities as subject/command/policy triples and separates Delegation from Invocation.[^5]
|
|
51
|
+
- Policy: v0.x has relatively simple attenuation semantics; v1 adds a full predicate logic policy language with jq-style selectors and quantifiers.[^5]
|
|
52
|
+
|
|
53
|
+
## Official UCAN Working Group Libraries
|
|
54
|
+
|
|
55
|
+
The UCAN Working Group organization lists libraries in TypeScript (NPM), Rust (Crate), Golang, Haskell, JS IPLD helpers, and UCAN-RPC tools as the primary ecosystem components. It also notes additional specs like a UCAN container and classifies some older specs (HTTP bearer token, IPLD, JWT canonicalization) as outdated or obsolete.[^2]
|
|
56
|
+
|
|
57
|
+
### Rust: rs-ucan and ucan crate
|
|
58
|
+
|
|
59
|
+
The ucan-wg/rs-ucan repository hosts the Rust implementation, providing the `ucan` and `ucan-key-support` crates. The `ucan` crate offers a `UcanBuilder` abstraction for constructing signed tokens and a `ProofChain` for validating chains and reducing capabilities according to domain-specific semantics.[^10][^11]
|
|
60
|
+
|
|
61
|
+
The `ucan-key-support` crate supplies ready-to-use `SigningKey` implementations compatible with the core crate, simplifying cryptographic integration. This stack targets the modern UCAN spec and is referenced from the main spec and the ucan.xyz site as the Rust implementation.[^12][^10]
|
|
62
|
+
|
|
63
|
+
### Go: go-ucan
|
|
64
|
+
|
|
65
|
+
The ucan-wg/go-ucan repository and its documentation describe a Go library designed to help web and decentralized applications integrate UCAN authorization flows. The library explicitly states that it supports the required parts of the UCAN specification—main spec, Delegation, and Invocation—while Revocation and Promise are not yet implemented.[^13][^14]
|
|
66
|
+
|
|
67
|
+
Go-ucan integrates with a companion DID library (`go-did-it`), provides encrypted values in token metadata, and includes a UCAN container format for packaging multiple tokens together. The ucan.xyz Go library page reiterates this scope and positions go-ucan as the reference Go implementation.[^14][^13]
|
|
68
|
+
|
|
69
|
+
### TypeScript (legacy): ts-ucan
|
|
70
|
+
|
|
71
|
+
The ucan-wg/ts-ucan library implements the earlier JWT-based UCAN spec, emphasizing UCANs as JWTs with special keys, DIDs for issuer and audience, and offline/offline-friendly authorization flows. Its README walks through the header structure (`alg`, `typ`, `uav`) and payload fields for attenuation and proofs, matching the v0.8.x spec’s `att` and `prf` fields rather than the v1 envelope.[^4][^6][^5]
|
|
72
|
+
|
|
73
|
+
## iso-ucan JavaScript Implementation
|
|
74
|
+
|
|
75
|
+
Hugo Dias’s iso-ucan package (within the `hugomrdias/iso-repo` monorepo) is an isomorphic JavaScript implementation oriented around the v1 UCAN semantics. It is documented on ucan.xyz as the canonical JavaScript UCAN library and underpins the site’s examples and getting-started guides.[^15][^16]
|
|
76
|
+
|
|
77
|
+
### API and semantics
|
|
78
|
+
|
|
79
|
+
The iso-ucan README shows a usage pattern where capabilities are defined via `Capability.from({ schema, cmd })`, delegations are created with `Capability.delegate({ iss, aud, sub, pol, exp })`, and invocations are produced with `Capability.invoke({ iss, sub, args, store, exp })`. A `Store` abstraction backed by an `iso-kv` driver manages delegations, and signing uses EdDSA keys via `iso-signatures` (`EdDSASigner`).
|
|
80
|
+
|
|
81
|
+
The ucan.xyz JavaScript library page and examples demonstrate the same patterns, using iso-ucan to define typed capabilities, delegate between identities, and create invocations with explicit command paths and argument schemas. This aligns closely with the v1 Delegation and Invocation specs’ focus on `cmd` paths, `pol` policy arrays, and argument-based validation.[^17][^15]
|
|
82
|
+
|
|
83
|
+
## Substrate-System iso-ucan Fork
|
|
84
|
+
|
|
85
|
+
The `substrate-system/iso-ucan` repository is a fork of Hugo Dias’s iso-ucan package, published on NPM as `@substrate-system/iso-ucan`. Its README states explicitly that it is a fork of `hugomrdias/iso-ucan` and focuses on packaging for different module systems and distribution formats.[^18]
|
|
86
|
+
|
|
87
|
+
### Distribution and packaging differences
|
|
88
|
+
|
|
89
|
+
The fork exposes both ESM and CommonJS entry points via the `exports` field, allowing import through either `import '@substrate-system/iso-ucan'` or `require('@substrate-system/iso-ucan')`. It also ships pre-built, minified JavaScript bundles (`dist/module.min.js`) that can be copied into a web server’s public directory and loaded via a `<script type="module" src="./module.min.js"></script>` tag.
|
|
90
|
+
|
|
91
|
+
Functionally, the fork does not describe any behavioral divergence from upstream iso-ucan; it is presented as a repackaging intended to ease consumption in Substrate-related and browser contexts rather than a new spec variant.
|
|
92
|
+
|
|
93
|
+
## Additional JavaScript Implementations
|
|
94
|
+
|
|
95
|
+
Fission’s `@fission-codes/ucan` package provides another JavaScript UCAN library, offering a high-level `UCAN.create` API that accepts an issuer, audience, and a capabilities object mapping resources to allowed actions. The README points developers to Fission’s stack documentation for details and promotes dual Apache-2.0 and MIT licensing.[^19]
|
|
96
|
+
|
|
97
|
+
The ucan.xyz examples and getting-started guide consistently highlight iso-ucan as the recommended JavaScript implementation for current v1 semantics. Legacy JWT-style libraries like ts-ucan remain useful for existing deployments but are not the focus of recent documentation.[^16][^15][^8][^4][^5]
|
|
98
|
+
|
|
99
|
+
## Node.js Wrapper over Rust: @myjoypin/node-ucan
|
|
100
|
+
|
|
101
|
+
The `@myjoypin/node-ucan` NPM package wraps the Rust rs-ucan library in a Node.js module, providing Node 18 binaries for common platforms and building from source elsewhere. Its documentation notes that it targets UCAN Specification v0.10.0, indicating that it lags behind the v1.0.0-rc.1 spec.[^20]
|
|
102
|
+
|
|
103
|
+
The package demonstrates how UCANs can be used as bearer-style tokens and delegated without server involvement, with examples of a server delegating rights to Alice, and Alice delegating a subset of her rights to Bob using `invokeUcan` and `verifyUcan` helpers.[^20]
|
|
104
|
+
|
|
105
|
+
## Other Community Implementations
|
|
106
|
+
|
|
107
|
+
Beyond the core working-group libraries, there are several community UCAN implementations across languages. These typically implement older JWT-based specs or partial subsets of v1 features.[^21][^22]
|
|
108
|
+
|
|
109
|
+
### Go community libraries
|
|
110
|
+
|
|
111
|
+
The `github.com/qri-io/ucan` package provides an early Go implementation of UCAN tokens from Fission, describing them as an authenticated digraph in an authorization space and focusing on attenuation and delegation using nested capabilities. Documentation characterizes it as "under heavy construction" and associates it with Fission’s original whitepaper.[^23][^24]
|
|
112
|
+
|
|
113
|
+
A separate `github.com/vibrantgenius/go-ucan` module advertises itself as aligned with UCAN v1.0.0-rc.1 and provides Go module packaging, though detailed documentation is limited in the high-level listing. The ucan-wg go-ucan library remains the primary reference implementation for current v1 semantics.[^25][^14]
|
|
114
|
+
|
|
115
|
+
### Elixir and other languages
|
|
116
|
+
|
|
117
|
+
The ExUcan library (Elixir) implements decentralized auth with UCANs, following the JWT-based model with header fields and payload fields including `ucv`, `cap`, `aud`, `exp`, `fct`, `nnc`, `iss`, `nbf`, and `prf`. Documentation highlights offline-first authorization and use of DIDs for issuer and audience, mirroring ts-ucan’s conceptual model.[^7]
|
|
118
|
+
|
|
119
|
+
The UCAN Working Group’s organization page notes the existence of Haskell libraries and UCAN IPLD tooling in JavaScript, but detailed documentation for these was not surfaced in the high-level search results. They appear to be earlier or auxiliary efforts relative to the main Rust, Go, and iso-ucan stacks.[^22][^2]
|
|
120
|
+
|
|
121
|
+
## UCAN Library Implementation Guide
|
|
122
|
+
|
|
123
|
+
The UCAN Library Implementation Guide on ucan.xyz defines requirements for a complete v1 library: delegation creation and validation, invocation creation, capability management, cryptographic operations, and envelope handling. It emphasizes that v1 libraries must implement the envelope format, type tags, structured capabilities, IPLD/CBOR encoding, and the policy language.[^8]
|
|
124
|
+
|
|
125
|
+
The guide lists major changes in v1, including the move away from JWT to UCAN-specific envelopes, structured capabilities that separate subject, command, and policy, and distinct Delegation, Invocation, Promise, and Revocation documents. This guidance underpins the design of rs-ucan, go-ucan, and iso-ucan as the v1-aligned libraries.[^8]
|
|
126
|
+
|
|
127
|
+
## Comparison of Specifications
|
|
128
|
+
|
|
129
|
+
### Specification-level differences
|
|
130
|
+
|
|
131
|
+
| Aspect | v0.8.x JWT spec (ucanto-spec, ts-ucan, ExUcan) | v1.0.0-rc.1 UCAN spec and sub-specs |
|
|
132
|
+
|-------|-----------------------------------------------|--------------------------------------|
|
|
133
|
+
| Encoding | JWT (header, payload, signature as base64url JSON) | UCAN envelope: signature bytes plus IPLD DAG-CBOR payload addressed by CID |
|
|
134
|
+
| Capability fields | `att` (attenuation list), `prf` (proof tokens), `cap` in some variants | `sub`, `cmd`, `pol` capabilities; `args` for invocation arguments |
|
|
135
|
+
| Versioning | Header field (`uav`/`ucv`), overall spec version like v0.8.1 | Type tags `ucan/<subspec>@version` (for example `ucan/[email protected]`) and spec version 1.0.0-rc.1 |
|
|
136
|
+
| Policy model | Simple attenuation semantics on `att` and basic field constraints | Full predicate policy language with comparison, glob, logical, and quantifier operators over jq-style selectors |
|
|
137
|
+
| Lifecycle docs | Single spec describing tokens, plus whitepaper | Main spec plus Delegation, Invocation, Promise, Revocation specs with explicit lifecycle and validation rules |
|
|
138
|
+
| Transport | JWT-focused, often HTTP bearer-centric | Transport-agnostic; tokens are IPLD objects identified by CIDs and may be transported via various mechanisms |
|
|
139
|
+
|
|
140
|
+
Sources: v0.8.1 spec, ts-ucan, ExUcan, v1 spec, Delegation spec.[^7][^4][^5]
|
|
141
|
+
|
|
142
|
+
## Comparison of Implementations
|
|
143
|
+
|
|
144
|
+
### Library coverage and focus
|
|
145
|
+
|
|
146
|
+
| Library | Language | Maintainer | Spec family | Encoding | Focus / Notes |
|
|
147
|
+
|--------|----------|-----------|------------|----------|---------------|
|
|
148
|
+
| iso-ucan | JavaScript/TypeScript | Hugo Dias | v1 Delegation/Invocation | IPLD/DAG-CBOR via UCAN envelopes | Isomorphic JS library with typed capabilities, EdDSA signing, and pluggable Stores; showcased on ucan.xyz as main JS library.[^15][^16] |
|
|
149
|
+
| @substrate-system/iso-ucan | JavaScript/TypeScript | substrate-system | v1 (fork of iso-ucan) | Same as iso-ucan | Fork providing ESM/CJS exports and pre-built minified bundles for browser use; behavior follows upstream iso-ucan. |
|
|
150
|
+
| ts-ucan | TypeScript | UCAN Working Group | v0.8.x JWT | JWT | Older library using JWT with `att`/`prf` payload; aligned with gobengo/ucanto v0.8.1 spec.[^4][^6][^5] |
|
|
151
|
+
| @fission-codes/ucan | TypeScript | Fission | Legacy UCAN | Likely JWT (not explicitly stated) | High-level Fission stack library with `UCAN.create` helper and EdDSA signing; docs refer to Fission stack.[^19] |
|
|
152
|
+
| ucan (Rust crate) | Rust | UCAN Working Group | v1 | UCAN envelope | Core Rust implementation with `UcanBuilder` and `ProofChain` for building and validating UCAN chains.[^10][^11] |
|
|
153
|
+
| ucan-key-support | Rust | UCAN Working Group | v1 | UCAN envelope | Auxiliary crate providing ready-made `SigningKey` implementations for the Rust UCAN library.[^12] |
|
|
154
|
+
| go-ucan (wg) | Go | UCAN Working Group | v1 (Delegation & Invocation) | UCAN envelope | Official Go implementation; supports main spec, Delegation, Invocation; Revocation and Promise to come; includes DID support and container format.[^13][^14] |
|
|
155
|
+
| qri-io/ucan | Go | Qri | Early UCAN (JWT-style) | JWT | Early Go implementation of Fission’s UCAN tokens based on the whitepaper; marked under heavy construction.[^23][^24] |
|
|
156
|
+
| @myjoypin/node-ucan | Node.js (Rust FFI) | Community | UCAN v0.10.0 | UCAN envelope / hybrid | Node bindings over rs-ucan; documented as targeting UCAN spec v0.10.0, with examples of delegation and verification in Node.[^20] |
|
|
157
|
+
| ExUcan | Elixir | Community | v0.x JWT | JWT | Elixir library replicating JWT-based UCAN fields such as `ucv`, `cap`, `aud`, `exp`, `fct`, `nnc`, `iss`, `nbf`, `prf`.[^7] |
|
|
158
|
+
|
|
159
|
+
## Practical Implications for Implementers
|
|
160
|
+
|
|
161
|
+
For new projects, the v1.0.0-rc.1 spec family and corresponding libraries (iso-ucan, rs-ucan/ucan crate, and go-ucan) provide the most future-proof foundation, thanks to structured capabilities, a powerful policy language, and CID-addressed IPLD encoding. Developers integrating with existing JWT-based UCAN deployments may still rely on ts-ucan, ExUcan, or qri-io/ucan but should plan for migration if they want to interoperate with v1-only tooling.[^10][^14][^4][^7][^5]
|
|
162
|
+
|
|
163
|
+
The substrate-system/iso-ucan fork is useful where bundling and module system support are primary concerns, since it repackages iso-ucan without altering semantics. Node environments needing Rust-level performance can use @myjoypin/node-ucan, but must be aware that it targets an earlier spec revision (v0.10.0) and may lack newer v1 features.[^20][^8]
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## References
|
|
168
|
+
|
|
169
|
+
1. [User Controlled Authorization Network (UCAN) Specification](https://ucan.xyz/specification/) - User-Controlled Authorization Network (UCAN) is a [trustless], secure, [local-first], user-originate...
|
|
170
|
+
|
|
171
|
+
2. [UCAN Working Group](https://github.com/ucan-wg) - Decentralized Auth — User Controlled Authorization Networks - UCAN Working Group
|
|
172
|
+
|
|
173
|
+
3. [v1.0.0-rc.1: High level invocation spec by expede · Pull Request #21 · ucan-wg/invocation](http://github.com/ucan-wg/invocation/pull/21) - Preview 📜 Supercedes V0.2 #15 TODOs Bump version to 1.0.0-rc.1 Closes Addressing individual task res...
|
|
174
|
+
|
|
175
|
+
4. [ts-ucan/README.md at main - GitHub](https://github.com/ucan-wg/ts-ucan/blob/main/README.md) - UCANs are JWTs that contain special keys. At a high level, UCANs (“User Controlled Authorization Net...
|
|
176
|
+
|
|
177
|
+
5. [GitHub - gobengo/ucanto-spec: User Controlled Authorization Network (UCAN) Specification](https://github.com/gobengo/ucanto-spec) - User Controlled Authorization Network (UCAN) Specification - gobengo/ucanto-spec
|
|
178
|
+
|
|
179
|
+
6. [GitHub - ucan-wg/ts-ucan: Auth tokens for a distributed, user-controlled world](https://github.com/ucan-wg/ts-ucan) - Auth tokens for a distributed, user-controlled world - ucan-wg/ts-ucan
|
|
180
|
+
|
|
181
|
+
7. [GitHub - spawnfest/youcan](https://github.com/spawnfest/youcan) - Contribute to spawnfest/youcan development by creating an account on GitHub.
|
|
182
|
+
|
|
183
|
+
8. [UCAN Library Implementation Guide](https://ucan.xyz/libraries/implementation/) - A comprehensive guide for implementing UCAN libraries in different programming languages
|
|
184
|
+
|
|
185
|
+
9. [v1.0.0-rc.1 by expede · Pull Request #2 · ucan-wg/delegation](http://github.com/ucan-wg/delegation/pull/2) - I may get pilloried for this version. WIP, obviously Preview 📚 Okay, this version switches to IPLD. ...
|
|
186
|
+
|
|
187
|
+
10. [Crate ucan Copy item path](https://docs.rs/ucan/latest/ucan/) - Implement UCAN-based authorization with conciseness and ease!
|
|
188
|
+
|
|
189
|
+
11. [rs-ucan/README.md at main · ucan-wg/rs-ucan](https://github.com/ucan-wg/rs-ucan/blob/main/README.md) - Rust implementation of UCAN. Contribute to ucan-wg/rs-ucan development by creating an account on Git...
|
|
190
|
+
|
|
191
|
+
12. [ucan-key-support — Rust crypto library // Lib.rs](https://lib.rs/crates/ucan-key-support) - Ready to use SigningKey implementations for the ucan crate
|
|
192
|
+
|
|
193
|
+
13. [GitHub - ucan-wg/go-ucan: User-Controlled Authorization Network (UCAN) tokens in go](https://github.com/ucan-wg/go-ucan) - User-Controlled Authorization Network (UCAN) tokens in go - ucan-wg/go-ucan
|
|
194
|
+
|
|
195
|
+
14. [Go Implementation - UCAN](https://ucan.xyz/libraries/go/) - Documentation for Go Implementation
|
|
196
|
+
|
|
197
|
+
15. [iso-ucan | UCAN](https://ucan.xyz/libraries/javascript/) - Documentation for iso-ucan.
|
|
198
|
+
|
|
199
|
+
16. [Getting Started with UCAN](https://ucan.xyz/getting-started/) - import { Capability } from "iso-ucan/capability". import { EdDSASigner } ... User Controlled Authori...
|
|
200
|
+
|
|
201
|
+
17. [UCAN Examples](https://ucan.xyz/guides/examples/) - Note: These examples use the v1.0.0-rc.1 UCAN specification and the iso-ucan JavaScript library. The...
|
|
202
|
+
|
|
203
|
+
18. [@substrate-system/iso-ucan - npm](https://www.npmjs.com/package/@substrate-system%2Fiso-ucan) - UCAN. Latest version: 0.0.2, last published: 2 months ago. Start using @substrate-system/iso-ucan in...
|
|
204
|
+
|
|
205
|
+
19. [@fission-codes/ucan](https://www.npmjs.com/package/@fission-codes/ucan?activeTab=readme) - UCAN (User Controlled Authorization Networks) is a decentralized authorization protocol for the web....
|
|
206
|
+
|
|
207
|
+
20. [@myjoypin/node-ucan](https://www.npmjs.com/package/@myjoypin/node-ucan?activeTab=code) - UCAN for Node.js. Latest version: 0.1.0, last published: 10 months ago. Start using @myjoypin/node-u...
|
|
208
|
+
|
|
209
|
+
21. [Build software better, together](https://github.com/topics/ucan) - GitHub is where people build software. More than 100 million people use GitHub to discover, fork, an...
|
|
210
|
+
|
|
211
|
+
22. [Stabilizing the Object Capability System - ForgeFed](https://forgefed.org/blog/stabilizing-ocaps/) - Existing UCAN implementations in both Go (for Forgejo) and Haskell (for Vervis); Capabilities are cr...
|
|
212
|
+
|
|
213
|
+
23. [README ¶](https://pkg.go.dev/github.com/dholms/ucan) - Package ucan implements User-Controlled Authorization Network tokens by fission: https://whitepaper....
|
|
214
|
+
|
|
215
|
+
24. [ucan package - github.com/qri-io/ucan - Go Packages](https://pkg.go.dev/github.com/qri-io/ucan) - Package ucan implements User-Controlled Authorization Network tokens by fission: https://whitepaper....
|
|
216
|
+
|
|
217
|
+
25. [go-ucan module - github.com/vibrantgenius/go-ucan - Go Packages](https://pkg.go.dev/github.com/vibrantgenius/go-ucan) - github.com/vibrantgenius/go-ucan. go-ucan. module ... go-ucan. UCAN v1.0.0-rc.1 GitHub Tag · Build S...
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CarReader } from '@ipld/car'
|
|
2
|
+
import { CID } from 'multiformats'
|
|
3
|
+
|
|
4
|
+
export interface StorageConnector {
|
|
5
|
+
storeCar(car: Blob, signal?: AbortSignal): Promise<CID>
|
|
6
|
+
}
|
|
7
|
+
export interface RetrievalConnector {
|
|
8
|
+
retrieveCar(cid: CID): Promise<CarReader>
|
|
9
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Logger } from 'besonders-logger'
|
|
2
|
+
import { CID } from 'multiformats'
|
|
3
|
+
import { ensureTsPvAndFinalizeApplog } from '../applog/applog-helpers.ts'
|
|
4
|
+
import { EntityID } from '../applog/datom-types.ts'
|
|
5
|
+
import { Thread } from '../thread.ts'
|
|
6
|
+
|
|
7
|
+
const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars
|
|
8
|
+
|
|
9
|
+
export type PubPullData = {
|
|
10
|
+
cid: CID
|
|
11
|
+
thread: Thread
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function integratePub({ targetThread, agentHash, subID, pubData }: {
|
|
15
|
+
targetThread: Thread
|
|
16
|
+
agentHash: EntityID
|
|
17
|
+
pubData: PubPullData
|
|
18
|
+
subID?: EntityID
|
|
19
|
+
}) {
|
|
20
|
+
const newLogs = pubData.thread.applogs.filter(log => !targetThread.hasApplog(log, false))
|
|
21
|
+
DEBUG(`[integratePub] integrating ${newLogs.length} logs`, { targetThread, subID, pubData })
|
|
22
|
+
let toInsert = newLogs
|
|
23
|
+
if (subID) {
|
|
24
|
+
toInsert = toInsert.concat(ensureTsPvAndFinalizeApplog(
|
|
25
|
+
{ en: subID, at: 'subscription/cid', vl: pubData.cid.toString(), ag: agentHash },
|
|
26
|
+
targetThread,
|
|
27
|
+
))
|
|
28
|
+
}
|
|
29
|
+
targetThread.insertRaw(toInsert)
|
|
30
|
+
return newLogs
|
|
31
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { CID } from 'multiformats/cid'
|
|
2
|
+
import { cyrb53hash } from './../applog/applog-utils.ts'
|
|
3
|
+
import { AgentHash, AgentID, CidString } from '../applog/datom-types.ts'
|
|
4
|
+
import { Tagged } from '../types.ts'
|
|
5
|
+
import { UCANCapMap } from './ucan.ts'
|
|
6
|
+
type AgentString = Tagged<string, 'AgentString'>
|
|
7
|
+
type DIDString = Tagged<string, 'DID'>
|
|
8
|
+
export type { AgentHash, AgentString, DIDString }
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export interface AppAgent {
|
|
12
|
+
ag: AgentHash
|
|
13
|
+
agentString: AgentString
|
|
14
|
+
did: DIDString
|
|
15
|
+
sign? (data: Uint8Array): Promise<Uint8Array>
|
|
16
|
+
ucan?: UCANCapMap
|
|
17
|
+
}
|
|
18
|
+
export interface AppAgentForWorker extends AppAgent {
|
|
19
|
+
signerSecret?: Uint8Array
|
|
20
|
+
}
|
|
21
|
+
export interface SnapRootBlock {
|
|
22
|
+
applogs: CID
|
|
23
|
+
applogsSignature: Uint8Array
|
|
24
|
+
info: CID
|
|
25
|
+
infoSignature: Uint8Array
|
|
26
|
+
prev?: CID
|
|
27
|
+
prevCounter?: number | null // Sequential counter: 0 for first, then +1 for each. null = unknown (legacy/error).
|
|
28
|
+
}
|
|
29
|
+
export interface SnapBlockLogs {
|
|
30
|
+
logs: CID[]
|
|
31
|
+
}
|
|
32
|
+
export interface SnapBlockChunks {
|
|
33
|
+
chunks: CID[]
|
|
34
|
+
}
|
|
35
|
+
export type SnapBlockLogsOrChunks = SnapBlockLogs | SnapBlockChunks
|
|
36
|
+
|
|
37
|
+
// HACK: this is actually note3 types, not wovin
|
|
38
|
+
export interface IShare {
|
|
39
|
+
id?: string // string hash of pub (used as unique id in IDB) `W3Name.create().toString()` starts with k51qzi5uqu5d
|
|
40
|
+
createdAt: string // ISO timestamp of creation
|
|
41
|
+
name: string // nick name for the pub
|
|
42
|
+
isDeleted?: boolean
|
|
43
|
+
purgeBeforePush?: boolean
|
|
44
|
+
|
|
45
|
+
pk: Uint8Array // exported privatekey - needed to create WritableName for publishing //TODO: store as non-extractable / encrypted?
|
|
46
|
+
|
|
47
|
+
autopush: boolean
|
|
48
|
+
lastPush: string | null
|
|
49
|
+
lastCID?: string
|
|
50
|
+
latestLogTs?: string
|
|
51
|
+
pubCounter?: number
|
|
52
|
+
|
|
53
|
+
publishedBy: string // local user appAgent
|
|
54
|
+
selectors?: string[] // to be used as a filter for which applogs to pub
|
|
55
|
+
encryptedFor?: string | null // short agentHash
|
|
56
|
+
encryptedWith?: CryptoKey | null // AES-GCM derived key from ECDH keys (local private and remote public)
|
|
57
|
+
|
|
58
|
+
// HACK WIP #39 - shared encryption
|
|
59
|
+
sharedKey?: CryptoKey | null // AES-GCM derived key from ECDH keys (local private and ipns public)
|
|
60
|
+
sharedAgents?: AgentID[] | null // array of string EntityIDs for the chosen agents (we need public jwkd atoms for each of them)
|
|
61
|
+
sharedKeyMap?: Map<AgentID, string> | null // uses public key from each agent to derive an aes key that is used to encrypt and btoa the sharedKey that is actually used to encrypt and decrypt the applogs
|
|
62
|
+
}
|
|
63
|
+
export interface ISubscription {
|
|
64
|
+
id: string // string hash of pub (used as unique id in IDB) `W3Name.create().toString()` starts with k51qzi5uqu5d
|
|
65
|
+
createdAt: string // ISO timestamp of creation
|
|
66
|
+
name: string // nick name for the pub
|
|
67
|
+
isDeleted: boolean
|
|
68
|
+
|
|
69
|
+
lastPull?: string | null
|
|
70
|
+
lastPullAttempt?: string | null
|
|
71
|
+
autopull: boolean
|
|
72
|
+
realtime?: boolean // enable real-time WebSocket updates via IPNS watcher
|
|
73
|
+
lastCID?: string // ? why not CidString
|
|
74
|
+
lastApplogCID?: string
|
|
75
|
+
publishedBy?: string // remote publisher short agentHash
|
|
76
|
+
encryptedFor?: string | undefined // short agentHash
|
|
77
|
+
encryptedWith?: CryptoKey | undefined // AES-GCM derived key from ECDH keys (local private and remote public)
|
|
78
|
+
}
|
|
79
|
+
export function isShare(obj: any): obj is IShare {
|
|
80
|
+
return obj?.pk !== undefined && obj?.lastPush !== undefined
|
|
81
|
+
}
|
|
82
|
+
export function isSubscription(obj: any): obj is ISubscription {
|
|
83
|
+
return obj?.lastPull !== undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type TShareSub = IShare | ISubscription
|
|
87
|
+
|
|
88
|
+
export function agentToShortHash(agentString: string) {
|
|
89
|
+
return cyrb53hash(agentString, 31, 7) as string
|
|
90
|
+
}
|