@peerbit/trusted-network 1.0.2

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 dao.xyz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # Trusted network
2
+ ## 🚧 Experimental state 🚧
3
+
4
+ A store that lets you build trusted networks of identities
5
+ The store is defined by the "root trust" which have the responsibility in the beginning to trust additional identities. Later, these identities can add more identities to the network.
6
+ Trusted identities can also be revoked.
7
+
8
+ Distributing content among untrusted peers will be unreliable and not resilient to malicious parties that starts to participate in the replication process with large amount (>> min replicas) of nodes and shutting them down simultaneously (no way for the original peers recover all lost data). To mitigate this you can launch your program in a "Network", which is basically a list of nodes that trust each other. Symbolically you could thing of this as a VPC.
9
+
10
+ To do this, you only have to implement the "Network" interface:
11
+ ```typescript
12
+ import { Peerbit, Network } from 'peerbit'
13
+ import { Log } from '@peerbit/log'
14
+ import { Program } from '@peerbit/program'
15
+ import { TrustedNetwork } from '@peerbit/trusted-network'
16
+ import { field, variant } from '@dao-xyz/borst-ts'
17
+
18
+ @variant("string_store")
19
+ @network({property: 'network'})
20
+ class StringStore extends Program
21
+ {
22
+ @field({type: Store})
23
+ log: Log<Uint8Array>
24
+
25
+ @field({type: TrustedNetwork})
26
+ network: TrustedNetwork // this is a database storing all peers. Peers that are trusted can add new peers
27
+
28
+ constructor(properties:{ log: Store<any>, network: TrustedNetwork }) {
29
+
30
+ this.log = properties.store
31
+ this.network = properties.network;
32
+
33
+ }
34
+
35
+ async setup()
36
+ {
37
+ await store.setup({ encoding: ... , canAppend: ..., canRead: ...})
38
+ await trustedNetwork.setup()
39
+ }
40
+ }
41
+
42
+
43
+ // Later
44
+ const peer1 = await Peerbit.create(LIBP2P_CLIENT, {... options ...})
45
+ const peer2 = await Peerbit.create(LIBP2P_CLIENT_2, {... options ...})
46
+
47
+ const programPeer1 = await peer1.open(new StringStore({log: new Log(), network: new TrustedNetwork()}), {... options ...})
48
+
49
+ // add trust to another peer
50
+ await program.network.add(peer2.identity.publicKey)
51
+
52
+
53
+ // peer2 also has to "join" the network, in practice this means that peer2 adds a record telling that its Peer ID trusts its libp2p Id
54
+ const programPeer2 = await peer2.open(programPeer1.address, {... options ...})
55
+ await peer2.join(programPeer2) // This might fail with "AccessError" if you do this too quickly after "open", because it has not yet recieved the full trust graph from peer1 yet
56
+ ```
57
+
58
+ See [this test(s)](./src/__tests__/network.test.ts) for working examples
@@ -0,0 +1,57 @@
1
+ import { Documents, Operation } from "@peerbit/document";
2
+ import { AppendOptions, Entry } from "@peerbit/log";
3
+ import { PublicSignKey } from "@peerbit/crypto";
4
+ import { IdentityRelation } from "./identity-graph.js";
5
+ import { Program } from "@peerbit/program";
6
+ import { CanRead } from "@peerbit/rpc";
7
+ import { PeerId } from "@libp2p/interface-peer-id";
8
+ import { SubscriptionType } from "@peerbit/shared-log";
9
+ type IdentityGraphArgs = {
10
+ canRead?: CanRead;
11
+ role?: SubscriptionType;
12
+ };
13
+ export declare class IdentityGraph extends Program<IdentityGraphArgs> {
14
+ relationGraph: Documents<IdentityRelation>;
15
+ constructor(props?: {
16
+ id?: Uint8Array;
17
+ relationGraph?: Documents<IdentityRelation>;
18
+ });
19
+ canAppend(entry: Entry<Operation<IdentityRelation>>): Promise<boolean>;
20
+ open(options?: IdentityGraphArgs): Promise<void>;
21
+ addRelation(to: PublicSignKey | PeerId, options?: AppendOptions<Operation<IdentityRelation>>): Promise<void>;
22
+ }
23
+ /**
24
+ * Not shardeable since we can not query trusted relations, because this would lead to a recursive problem where we then need to determine whether the responder is trusted or not
25
+ */
26
+ type TrustedNetworkArgs = {
27
+ role?: SubscriptionType;
28
+ };
29
+ export declare class TrustedNetwork extends Program<TrustedNetworkArgs> {
30
+ rootTrust: PublicSignKey;
31
+ trustGraph: Documents<IdentityRelation>;
32
+ constructor(props: {
33
+ id?: Uint8Array;
34
+ rootTrust: PublicSignKey | PeerId;
35
+ });
36
+ open(options?: TrustedNetworkArgs): Promise<void>;
37
+ canAppend(entry: Entry<Operation<IdentityRelation>>): Promise<boolean>;
38
+ canRead(_key?: PublicSignKey): Promise<boolean>;
39
+ add(trustee: PublicSignKey | PeerId): Promise<IdentityRelation | undefined>;
40
+ hasRelation(trustee: PublicSignKey | PeerId, truster?: PublicSignKey | PeerId): Promise<boolean>;
41
+ getRelation(trustee: PublicSignKey | PeerId, truster?: PublicSignKey | PeerId): Promise<IdentityRelation | undefined>;
42
+ /**
43
+ * Follow trust path back to trust root.
44
+ * Trust root is always trusted.
45
+ * Hence if
46
+ * Root trust A trust B trust C
47
+ * C is trusted by Root
48
+ * @param trustee
49
+ * @param truster, the truster "root", if undefined defaults to the root trust
50
+ * @returns true, if trusted
51
+ */
52
+ isTrusted(trustee: PublicSignKey, truster?: PublicSignKey): Promise<boolean>;
53
+ _isTrustedLocal(trustee: PublicSignKey, truster?: PublicSignKey): Promise<boolean>;
54
+ getTrusted(): Promise<PublicSignKey[]>;
55
+ hashCode(): string;
56
+ }
57
+ export {};
@@ -0,0 +1,211 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { deserialize, field, serialize, variant } from "@dao-xyz/borsh";
11
+ import { SearchRequest, Documents, PutOperation, } from "@peerbit/document";
12
+ import { PublicSignKey, getPublicKeyFromPeerId } from "@peerbit/crypto";
13
+ import { DeleteOperation } from "@peerbit/document";
14
+ import { IdentityRelation, createIdentityGraphStore, getPathGenerator, hasPath, getFromByTo, getToByFrom, getRelation, AbstractRelation, } from "./identity-graph.js";
15
+ import { Program } from "@peerbit/program";
16
+ import { sha256Base64Sync } from "@peerbit/crypto";
17
+ import { Replicator } from "@peerbit/shared-log";
18
+ const coercePublicKey = (publicKey) => {
19
+ return publicKey instanceof PublicSignKey
20
+ ? publicKey
21
+ : getPublicKeyFromPeerId(publicKey);
22
+ };
23
+ const canAppendByRelation = async (entry, isTrusted) => {
24
+ // verify the payload
25
+ const operation = await entry.getPayloadValue();
26
+ if (operation instanceof PutOperation ||
27
+ operation instanceof DeleteOperation) {
28
+ /* const relation: Relation = operation.value || deserialize(operation.data, Relation); */
29
+ const keys = await entry.getPublicKeys();
30
+ const checkKey = async (key) => {
31
+ if (operation instanceof PutOperation) {
32
+ // TODO, this clause is only applicable when we modify the identityGraph, but it does not make sense that the canAppend method does not know what the payload will
33
+ // be, upon deserialization. There should be known in the `canAppend` method whether we are appending to the identityGraph.
34
+ const relation = operation._value || deserialize(operation.data, AbstractRelation);
35
+ operation._value = relation;
36
+ if (relation instanceof IdentityRelation) {
37
+ if (!relation.from.equals(key)) {
38
+ return false;
39
+ }
40
+ }
41
+ // else assume the payload is accepted
42
+ }
43
+ if (isTrusted) {
44
+ const trusted = await isTrusted(key);
45
+ return trusted;
46
+ }
47
+ else {
48
+ return true;
49
+ }
50
+ };
51
+ for (const key of keys) {
52
+ const result = await checkKey(key);
53
+ if (result) {
54
+ return true;
55
+ }
56
+ }
57
+ return false;
58
+ }
59
+ else {
60
+ return false;
61
+ }
62
+ };
63
+ export let IdentityGraph = class IdentityGraph extends Program {
64
+ relationGraph;
65
+ constructor(props) {
66
+ super();
67
+ if (props) {
68
+ this.relationGraph =
69
+ props.relationGraph || createIdentityGraphStore(props?.id);
70
+ }
71
+ }
72
+ async canAppend(entry) {
73
+ return canAppendByRelation(entry);
74
+ }
75
+ async open(options) {
76
+ await this.relationGraph.open({
77
+ type: IdentityRelation,
78
+ canAppend: this.canAppend.bind(this),
79
+ canRead: options?.canRead,
80
+ index: {
81
+ fields: (obj, _entry) => {
82
+ return {
83
+ from: obj.from.hashcode(),
84
+ to: obj.to.hashcode(),
85
+ };
86
+ },
87
+ },
88
+ role: options?.role,
89
+ }); // self referencing access controller
90
+ }
91
+ async addRelation(to, options) {
92
+ /* trustee = PublicKey.from(trustee); */
93
+ await this.relationGraph.put(new IdentityRelation({
94
+ to: coercePublicKey(to),
95
+ from: options?.identity?.publicKey || this.node.identity.publicKey,
96
+ }), options);
97
+ }
98
+ };
99
+ __decorate([
100
+ field({ type: Documents }),
101
+ __metadata("design:type", Documents)
102
+ ], IdentityGraph.prototype, "relationGraph", void 0);
103
+ IdentityGraph = __decorate([
104
+ variant("relations"),
105
+ __metadata("design:paramtypes", [Object])
106
+ ], IdentityGraph);
107
+ export let TrustedNetwork = class TrustedNetwork extends Program {
108
+ rootTrust;
109
+ trustGraph;
110
+ constructor(props) {
111
+ super();
112
+ this.trustGraph = createIdentityGraphStore(props.id);
113
+ this.rootTrust = coercePublicKey(props.rootTrust);
114
+ }
115
+ async open(options) {
116
+ await this.trustGraph.open({
117
+ type: IdentityRelation,
118
+ canAppend: this.canAppend.bind(this),
119
+ canRead: this.canRead.bind(this),
120
+ role: options?.role,
121
+ index: {
122
+ fields: (obj, _entry) => {
123
+ return {
124
+ from: obj.from.hashcode(),
125
+ to: obj.to.hashcode(),
126
+ };
127
+ },
128
+ },
129
+ }); // self referencing access controller
130
+ }
131
+ async canAppend(entry) {
132
+ return canAppendByRelation(entry, (key) => this.isTrusted(key));
133
+ }
134
+ async canRead(_key) {
135
+ return true; // TODO should we have read access control?
136
+ }
137
+ async add(trustee) {
138
+ const key = trustee instanceof PublicSignKey
139
+ ? trustee
140
+ : getPublicKeyFromPeerId(trustee);
141
+ const existingRelation = await this.getRelation(key, this.node.identity.publicKey);
142
+ if (!existingRelation) {
143
+ const relation = new IdentityRelation({
144
+ to: key,
145
+ from: this.node.identity.publicKey,
146
+ });
147
+ await this.trustGraph.put(relation);
148
+ return relation;
149
+ }
150
+ return existingRelation;
151
+ }
152
+ async hasRelation(trustee, truster = this.rootTrust) {
153
+ return !!(await this.getRelation(trustee, truster));
154
+ }
155
+ getRelation(trustee, truster = this.rootTrust) {
156
+ return getRelation(coercePublicKey(truster), coercePublicKey(trustee), this.trustGraph);
157
+ }
158
+ /**
159
+ * Follow trust path back to trust root.
160
+ * Trust root is always trusted.
161
+ * Hence if
162
+ * Root trust A trust B trust C
163
+ * C is trusted by Root
164
+ * @param trustee
165
+ * @param truster, the truster "root", if undefined defaults to the root trust
166
+ * @returns true, if trusted
167
+ */
168
+ async isTrusted(trustee, truster = this.rootTrust) {
169
+ if (trustee.equals(this.rootTrust)) {
170
+ return true;
171
+ }
172
+ if (this.trustGraph.log.role instanceof Replicator) {
173
+ return this._isTrustedLocal(trustee, truster);
174
+ }
175
+ else {
176
+ this.trustGraph.index.search(new SearchRequest({ query: [] }), {
177
+ remote: { sync: true },
178
+ });
179
+ return this._isTrustedLocal(trustee, truster);
180
+ }
181
+ }
182
+ async _isTrustedLocal(trustee, truster = this.rootTrust) {
183
+ const trustPath = await hasPath(trustee, truster, this.trustGraph, getFromByTo);
184
+ return !!trustPath;
185
+ }
186
+ async getTrusted() {
187
+ const current = this.rootTrust;
188
+ const participants = [current];
189
+ const generator = getPathGenerator(current, this.trustGraph, getToByFrom);
190
+ for await (const next of generator) {
191
+ participants.push(next.to);
192
+ }
193
+ return participants;
194
+ }
195
+ hashCode() {
196
+ return sha256Base64Sync(serialize(this));
197
+ }
198
+ };
199
+ __decorate([
200
+ field({ type: PublicSignKey }),
201
+ __metadata("design:type", PublicSignKey)
202
+ ], TrustedNetwork.prototype, "rootTrust", void 0);
203
+ __decorate([
204
+ field({ type: Documents }),
205
+ __metadata("design:type", Documents)
206
+ ], TrustedNetwork.prototype, "trustGraph", void 0);
207
+ TrustedNetwork = __decorate([
208
+ variant("trusted_network"),
209
+ __metadata("design:paramtypes", [Object])
210
+ ], TrustedNetwork);
211
+ //# sourceMappingURL=controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.js","sourceRoot":"","sources":["../../src/controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import { Documents } from "@peerbit/document";
2
+ import { PublicSignKey } from "@peerbit/crypto";
3
+ export type RelationResolver = {
4
+ resolve: (key: PublicSignKey, db: Documents<IdentityRelation>) => Promise<IdentityRelation[]>;
5
+ next: (relation: IdentityRelation) => PublicSignKey;
6
+ };
7
+ export declare const getFromByTo: RelationResolver;
8
+ export declare const getToByFrom: RelationResolver;
9
+ export declare function getPathGenerator(from: PublicSignKey, db: Documents<IdentityRelation>, resolver: RelationResolver): AsyncGenerator<IdentityRelation, void, unknown>;
10
+ /**
11
+ * Get path, to target.
12
+ * @param start
13
+ * @param target
14
+ * @param db
15
+ * @returns
16
+ */
17
+ export declare const hasPathToTarget: (start: PublicSignKey, target: (key: PublicSignKey) => boolean, db: Documents<IdentityRelation>, resolver: RelationResolver) => Promise<boolean>;
18
+ export declare abstract class AbstractRelation {
19
+ id: Uint8Array;
20
+ }
21
+ export declare class IdentityRelation extends AbstractRelation {
22
+ _from: PublicSignKey;
23
+ _to: PublicSignKey;
24
+ constructor(properties?: {
25
+ to: PublicSignKey;
26
+ from: PublicSignKey;
27
+ });
28
+ get from(): PublicSignKey;
29
+ get to(): PublicSignKey;
30
+ initializeId(): void;
31
+ static id(to: PublicSignKey, from: PublicSignKey): Uint8Array;
32
+ }
33
+ export declare const hasPath: (start: PublicSignKey, end: PublicSignKey, db: Documents<IdentityRelation>, resolver: RelationResolver) => Promise<boolean>;
34
+ export declare const getRelation: (from: PublicSignKey, to: PublicSignKey, db: Documents<IdentityRelation>) => Promise<IdentityRelation | undefined>;
35
+ export declare const createIdentityGraphStore: (id?: Uint8Array) => Documents<IdentityRelation>;
@@ -0,0 +1,151 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var IdentityRelation_1;
11
+ import { field, fixedArray, serialize, variant } from "@dao-xyz/borsh";
12
+ import { Documents, DocumentIndex, SearchRequest, StringMatch, } from "@peerbit/document";
13
+ import { PublicSignKey } from "@peerbit/crypto";
14
+ import { concat } from "uint8arrays";
15
+ import { RPC } from "@peerbit/rpc";
16
+ import { sha256Sync } from "@peerbit/crypto";
17
+ export const getFromByTo = {
18
+ resolve: async (to, db) => {
19
+ return Promise.all(await db.index.search(new SearchRequest({
20
+ query: [
21
+ new StringMatch({
22
+ key: "to",
23
+ value: to.hashcode(),
24
+ }),
25
+ ],
26
+ }), {
27
+ local: true,
28
+ remote: false,
29
+ }));
30
+ },
31
+ next: (relation) => relation.from,
32
+ };
33
+ export const getToByFrom = {
34
+ resolve: async (from, db) => {
35
+ return Promise.all(await db.index.search(new SearchRequest({
36
+ query: [
37
+ new StringMatch({
38
+ key: "from",
39
+ value: from.hashcode(),
40
+ }),
41
+ ],
42
+ }), {
43
+ local: true,
44
+ remote: false,
45
+ }));
46
+ },
47
+ next: (relation) => relation.to,
48
+ };
49
+ export async function* getPathGenerator(from, db, resolver) {
50
+ let iter = [from];
51
+ const visited = new Set();
52
+ while (iter.length > 0) {
53
+ const newIter = [];
54
+ for (const value of iter) {
55
+ const results = await resolver.resolve(value, db);
56
+ for (const result of results) {
57
+ if (result instanceof IdentityRelation) {
58
+ if (visited.has(result.id)) {
59
+ return;
60
+ }
61
+ visited.add(result.id);
62
+ yield result;
63
+ newIter.push(resolver.next(result));
64
+ }
65
+ }
66
+ }
67
+ iter = newIter;
68
+ }
69
+ }
70
+ /**
71
+ * Get path, to target.
72
+ * @param start
73
+ * @param target
74
+ * @param db
75
+ * @returns
76
+ */
77
+ export const hasPathToTarget = async (start, target, db, resolver) => {
78
+ if (!db) {
79
+ throw new Error("Not initalized");
80
+ }
81
+ const current = start;
82
+ if (target(current)) {
83
+ return true;
84
+ }
85
+ const iterator = getPathGenerator(current, db, resolver);
86
+ for await (const relation of iterator) {
87
+ if (target(relation.from)) {
88
+ return true;
89
+ }
90
+ }
91
+ return false;
92
+ };
93
+ export let AbstractRelation = class AbstractRelation {
94
+ id;
95
+ };
96
+ __decorate([
97
+ field({ type: fixedArray("u8", 32) }),
98
+ __metadata("design:type", Uint8Array)
99
+ ], AbstractRelation.prototype, "id", void 0);
100
+ AbstractRelation = __decorate([
101
+ variant(0)
102
+ ], AbstractRelation);
103
+ export let IdentityRelation = IdentityRelation_1 = class IdentityRelation extends AbstractRelation {
104
+ _from;
105
+ _to;
106
+ constructor(properties) {
107
+ super();
108
+ if (properties) {
109
+ this._from = properties.from;
110
+ this._to = properties.to;
111
+ this.initializeId();
112
+ }
113
+ }
114
+ get from() {
115
+ return this._from;
116
+ }
117
+ get to() {
118
+ return this._to;
119
+ }
120
+ initializeId() {
121
+ this.id = IdentityRelation_1.id(this.to, this.from);
122
+ }
123
+ static id(to, from) {
124
+ return sha256Sync(concat([serialize(to), serialize(from)]));
125
+ }
126
+ };
127
+ __decorate([
128
+ field({ type: PublicSignKey }),
129
+ __metadata("design:type", PublicSignKey)
130
+ ], IdentityRelation.prototype, "_from", void 0);
131
+ __decorate([
132
+ field({ type: PublicSignKey }),
133
+ __metadata("design:type", PublicSignKey)
134
+ ], IdentityRelation.prototype, "_to", void 0);
135
+ IdentityRelation = IdentityRelation_1 = __decorate([
136
+ variant(0),
137
+ __metadata("design:paramtypes", [Object])
138
+ ], IdentityRelation);
139
+ export const hasPath = async (start, end, db, resolver) => {
140
+ return hasPathToTarget(start, (key) => end.equals(key), db, resolver);
141
+ };
142
+ export const getRelation = async (from, to, db) => {
143
+ return db.index.get(new IdentityRelation({ from, to }).id);
144
+ };
145
+ export const createIdentityGraphStore = (id) => new Documents({
146
+ index: new DocumentIndex({
147
+ query: new RPC(),
148
+ }),
149
+ id,
150
+ });
151
+ //# sourceMappingURL=identity-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-graph.js","sourceRoot":"","sources":["../../src/identity-graph.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EACN,SAAS,EACT,aAAa,EACb,aAAa,EACb,WAAW,GACX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAU7C,MAAM,CAAC,MAAM,WAAW,GAAqB;IAC5C,OAAO,EAAE,KAAK,EAAE,EAAiB,EAAE,EAA+B,EAAE,EAAE;QACrE,OAAO,OAAO,CAAC,GAAG,CACjB,MAAM,EAAE,CAAC,KAAK,CAAC,MAAM,CACpB,IAAI,aAAa,CAAC;YACjB,KAAK,EAAE;gBACN,IAAI,WAAW,CAAC;oBACf,GAAG,EAAE,IAAI;oBACT,KAAK,EAAE,EAAE,CAAC,QAAQ,EAAE;iBACpB,CAAC;aACF;SACD,CAAC,EACF;YACC,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,KAAK;SACb,CACD,CACD,CAAC;IACH,CAAC;IACD,IAAI,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI;CACjC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAqB;IAC5C,OAAO,EAAE,KAAK,EAAE,IAAmB,EAAE,EAA+B,EAAE,EAAE;QACvE,OAAO,OAAO,CAAC,GAAG,CACjB,MAAM,EAAE,CAAC,KAAK,CAAC,MAAM,CACpB,IAAI,aAAa,CAAC;YACjB,KAAK,EAAE;gBACN,IAAI,WAAW,CAAC;oBACf,GAAG,EAAE,MAAM;oBACX,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE;iBACtB,CAAC;aACF;SACD,CAAC,EACF;YACC,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,KAAK;SACb,CACD,CACD,CAAC;IACH,CAAC;IACD,IAAI,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE;CAC/B,CAAC;AAEF,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,gBAAgB,CACtC,IAAmB,EACnB,EAA+B,EAC/B,QAA0B;IAE1B,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;IAC1B,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE;QACvB,MAAM,OAAO,GAAoB,EAAE,CAAC;QACpC,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE;YACzB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAClD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE;gBAC7B,IAAI,MAAM,YAAY,gBAAgB,EAAE;oBACvC,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE;wBAC3B,OAAO;qBACP;oBACD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;oBACvB,MAAM,MAAM,CAAC;oBAEb,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;iBACpC;aACD;SACD;QACD,IAAI,GAAG,OAAO,CAAC;KACf;AACF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EACnC,KAAoB,EACpB,MAAuC,EACvC,EAA+B,EAC/B,QAA0B,EACP,EAAE;IACrB,IAAI,CAAC,EAAE,EAAE;QACR,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;KAClC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC;IACtB,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE;QACpB,OAAO,IAAI,CAAC;KACZ;IAED,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;IACzD,IAAI,KAAK,EAAE,MAAM,QAAQ,IAAI,QAAQ,EAAE;QACtC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;YAC1B,OAAO,IAAI,CAAC;SACZ;KACD;IACD,OAAO,KAAK,CAAC;AACd,CAAC,CAAC;AAGK,WAAe,gBAAgB,GAA/B,MAAe,gBAAgB;IAErC,EAAE,CAAa;CACf,CAAA;AADA;IADC,KAAK,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;8BAClC,UAAU;4CAAC;AAFM,gBAAgB;IADrC,OAAO,CAAC,CAAC,CAAC;GACW,gBAAgB,CAGrC;AAGM,WAAM,gBAAgB,wBAAtB,MAAM,gBAAiB,SAAQ,gBAAgB;IAErD,KAAK,CAAgB;IAGrB,GAAG,CAAgB;IAEnB,YAAY,UAGX;QACA,KAAK,EAAE,CAAC;QACR,IAAI,UAAU,EAAE;YACf,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC;YAC7B,IAAI,CAAC,GAAG,GAAG,UAAU,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,YAAY,EAAE,CAAC;SACpB;IACF,CAAC;IAED,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB,CAAC;IAED,IAAI,EAAE;QACL,OAAO,IAAI,CAAC,GAAG,CAAC;IACjB,CAAC;IAED,YAAY;QACX,IAAI,CAAC,EAAE,GAAG,kBAAgB,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,CAAC,EAAE,CAAC,EAAiB,EAAE,IAAmB;QAC/C,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;CACD,CAAA;AAhCA;IADC,KAAK,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BACxB,aAAa;+CAAC;AAGrB;IADC,KAAK,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BAC1B,aAAa;6CAAC;AALP,gBAAgB;IAD5B,OAAO,CAAC,CAAC,CAAC;;GACE,gBAAgB,CAkC5B;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,EAC3B,KAAoB,EACpB,GAAkB,EAClB,EAA+B,EAC/B,QAA0B,EACP,EAAE;IACrB,OAAO,eAAe,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;AACvE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAC/B,IAAmB,EACnB,EAAiB,EACjB,EAA+B,EACS,EAAE;IAC1C,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,gBAAgB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAC5D,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,EAAe,EAAE,EAAE,CAC3D,IAAI,SAAS,CAAmB;IAC/B,KAAK,EAAE,IAAI,aAAa,CAAC;QACxB,KAAK,EAAE,IAAI,GAAG,EAAE;KAChB,CAAC;IACF,EAAE;CACF,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./controller.js";
2
+ export * from "./identity-graph.js";
@@ -0,0 +1,3 @@
1
+ export * from "./controller.js";
2
+ export * from "./identity-graph.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@peerbit/trusted-network",
3
+ "version": "1.0.2",
4
+ "description": "Access controller that operates on a DB",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "module": "lib/esm/index.js",
8
+ "types": "lib/esm/index.d.ts",
9
+ "exports": {
10
+ "import": "./lib/esm/index.js",
11
+ "require": "./lib/cjs/index.js"
12
+ },
13
+ "files": [
14
+ "lib",
15
+ "src",
16
+ "!src/**/__tests__",
17
+ "!lib/**/__tests__",
18
+ "LICENSE"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "scripts": {
24
+ "clean": "shx rm -rf lib/*",
25
+ "build": "yarn clean && tsc -p tsconfig.json",
26
+ "postbuild": "echo '{\"type\":\"module\"} ' | node ../../../../node_modules/.bin/json > lib/esm/package.json",
27
+ "test": "node ../../../../node_modules/.bin/jest test -c ../../../../jest.config.ts --runInBand --forceExit",
28
+ "test:unit": "node ../../../../node_modules/.bin/jest test -c ../../../../jest.config.unit.ts --runInBand --forceExit",
29
+ "test:integration": "node ../node_modules/.bin/jest test -c ../../../../jest.config.integration.ts --runInBand --forceExit"
30
+ },
31
+ "author": "dao.xyz",
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "@dao-xyz/borsh": "^5.1.5",
35
+ "@peerbit/crypto": "1.0.1",
36
+ "@peerbit/document": "1.0.2"
37
+ },
38
+ "devDependencies": {
39
+ "@ethersproject/wallet": "^5.7.0",
40
+ "@peerbit/test-utils": "^1.0.2",
41
+ "@peerbit/time": "1.0.0"
42
+ },
43
+ "gitHead": "595db9f1efebf604393eddfff5f678f5d8f16142"
44
+ }
@@ -0,0 +1,271 @@
1
+ import { deserialize, field, serialize, variant, vec } from "@dao-xyz/borsh";
2
+ import {
3
+ SearchRequest,
4
+ Documents,
5
+ Operation,
6
+ PutOperation,
7
+ } from "@peerbit/document";
8
+ import { AppendOptions, Entry } from "@peerbit/log";
9
+ import { PublicSignKey, getPublicKeyFromPeerId } from "@peerbit/crypto";
10
+ import { DeleteOperation } from "@peerbit/document";
11
+ import {
12
+ IdentityRelation,
13
+ createIdentityGraphStore,
14
+ getPathGenerator,
15
+ hasPath,
16
+ getFromByTo,
17
+ getToByFrom,
18
+ getRelation,
19
+ AbstractRelation,
20
+ } from "./identity-graph.js";
21
+ import { Program } from "@peerbit/program";
22
+ import { CanRead } from "@peerbit/rpc";
23
+ import { sha256Base64Sync } from "@peerbit/crypto";
24
+ import { PeerId } from "@libp2p/interface-peer-id";
25
+ import { Replicator, SubscriptionType } from "@peerbit/shared-log";
26
+
27
+ const coercePublicKey = (publicKey: PublicSignKey | PeerId) => {
28
+ return publicKey instanceof PublicSignKey
29
+ ? publicKey
30
+ : getPublicKeyFromPeerId(publicKey);
31
+ };
32
+ const canAppendByRelation = async (
33
+ entry: Entry<Operation<IdentityRelation>>,
34
+ isTrusted?: (key: PublicSignKey) => Promise<boolean>
35
+ ): Promise<boolean> => {
36
+ // verify the payload
37
+ const operation = await entry.getPayloadValue();
38
+ if (
39
+ operation instanceof PutOperation ||
40
+ operation instanceof DeleteOperation
41
+ ) {
42
+ /* const relation: Relation = operation.value || deserialize(operation.data, Relation); */
43
+
44
+ const keys = await entry.getPublicKeys();
45
+ const checkKey = async (key: PublicSignKey): Promise<boolean> => {
46
+ if (operation instanceof PutOperation) {
47
+ // TODO, this clause is only applicable when we modify the identityGraph, but it does not make sense that the canAppend method does not know what the payload will
48
+ // be, upon deserialization. There should be known in the `canAppend` method whether we are appending to the identityGraph.
49
+
50
+ const relation: AbstractRelation =
51
+ operation._value || deserialize(operation.data, AbstractRelation);
52
+ operation._value = relation;
53
+
54
+ if (relation instanceof IdentityRelation) {
55
+ if (!relation.from.equals(key)) {
56
+ return false;
57
+ }
58
+ }
59
+
60
+ // else assume the payload is accepted
61
+ }
62
+ if (isTrusted) {
63
+ const trusted = await isTrusted(key);
64
+ return trusted;
65
+ } else {
66
+ return true;
67
+ }
68
+ };
69
+ for (const key of keys) {
70
+ const result = await checkKey(key);
71
+ if (result) {
72
+ return true;
73
+ }
74
+ }
75
+ return false;
76
+ } else {
77
+ return false;
78
+ }
79
+ };
80
+
81
+ type IdentityGraphArgs = { canRead?: CanRead; role?: SubscriptionType };
82
+ @variant("relations")
83
+ export class IdentityGraph extends Program<IdentityGraphArgs> {
84
+ @field({ type: Documents })
85
+ relationGraph: Documents<IdentityRelation>;
86
+
87
+ constructor(props?: {
88
+ id?: Uint8Array;
89
+ relationGraph?: Documents<IdentityRelation>;
90
+ }) {
91
+ super();
92
+ if (props) {
93
+ this.relationGraph =
94
+ props.relationGraph || createIdentityGraphStore(props?.id);
95
+ }
96
+ }
97
+
98
+ async canAppend(entry: Entry<Operation<IdentityRelation>>): Promise<boolean> {
99
+ return canAppendByRelation(entry);
100
+ }
101
+
102
+ async open(options?: IdentityGraphArgs) {
103
+ await this.relationGraph.open({
104
+ type: IdentityRelation,
105
+ canAppend: this.canAppend.bind(this),
106
+ canRead: options?.canRead,
107
+ index: {
108
+ fields: (obj, _entry) => {
109
+ return {
110
+ from: obj.from.hashcode(),
111
+ to: obj.to.hashcode(),
112
+ };
113
+ },
114
+ },
115
+ role: options?.role,
116
+ }); // self referencing access controller
117
+ }
118
+
119
+ async addRelation(
120
+ to: PublicSignKey | PeerId,
121
+ options?: AppendOptions<Operation<IdentityRelation>>
122
+ ) {
123
+ /* trustee = PublicKey.from(trustee); */
124
+ await this.relationGraph.put(
125
+ new IdentityRelation({
126
+ to: coercePublicKey(to),
127
+ from: options?.identity?.publicKey || this.node.identity.publicKey,
128
+ }),
129
+ options
130
+ );
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Not shardeable since we can not query trusted relations, because this would lead to a recursive problem where we then need to determine whether the responder is trusted or not
136
+ */
137
+
138
+ type TrustedNetworkArgs = { role?: SubscriptionType };
139
+ @variant("trusted_network")
140
+ export class TrustedNetwork extends Program<TrustedNetworkArgs> {
141
+ @field({ type: PublicSignKey })
142
+ rootTrust: PublicSignKey;
143
+
144
+ @field({ type: Documents })
145
+ trustGraph: Documents<IdentityRelation>;
146
+
147
+ constructor(props: { id?: Uint8Array; rootTrust: PublicSignKey | PeerId }) {
148
+ super();
149
+ this.trustGraph = createIdentityGraphStore(props.id);
150
+ this.rootTrust = coercePublicKey(props.rootTrust);
151
+ }
152
+
153
+ async open(options?: TrustedNetworkArgs) {
154
+ await this.trustGraph.open({
155
+ type: IdentityRelation,
156
+ canAppend: this.canAppend.bind(this),
157
+ canRead: this.canRead.bind(this),
158
+ role: options?.role,
159
+ index: {
160
+ fields: (obj, _entry) => {
161
+ return {
162
+ from: obj.from.hashcode(),
163
+ to: obj.to.hashcode(),
164
+ };
165
+ },
166
+ },
167
+ }); // self referencing access controller
168
+ }
169
+
170
+ async canAppend(entry: Entry<Operation<IdentityRelation>>): Promise<boolean> {
171
+ return canAppendByRelation(entry, (key) => this.isTrusted(key));
172
+ }
173
+
174
+ async canRead(_key?: PublicSignKey): Promise<boolean> {
175
+ return true; // TODO should we have read access control?
176
+ }
177
+
178
+ async add(
179
+ trustee: PublicSignKey | PeerId
180
+ ): Promise<IdentityRelation | undefined> {
181
+ const key =
182
+ trustee instanceof PublicSignKey
183
+ ? trustee
184
+ : getPublicKeyFromPeerId(trustee);
185
+
186
+ const existingRelation = await this.getRelation(
187
+ key,
188
+ this.node.identity.publicKey
189
+ );
190
+ if (!existingRelation) {
191
+ const relation = new IdentityRelation({
192
+ to: key,
193
+ from: this.node.identity.publicKey,
194
+ });
195
+ await this.trustGraph.put(relation);
196
+ return relation;
197
+ }
198
+ return existingRelation;
199
+ }
200
+
201
+ async hasRelation(
202
+ trustee: PublicSignKey | PeerId,
203
+ truster: PublicSignKey | PeerId = this.rootTrust
204
+ ) {
205
+ return !!(await this.getRelation(trustee, truster));
206
+ }
207
+ getRelation(
208
+ trustee: PublicSignKey | PeerId,
209
+ truster: PublicSignKey | PeerId = this.rootTrust
210
+ ) {
211
+ return getRelation(
212
+ coercePublicKey(truster),
213
+ coercePublicKey(trustee),
214
+ this.trustGraph
215
+ );
216
+ }
217
+
218
+ /**
219
+ * Follow trust path back to trust root.
220
+ * Trust root is always trusted.
221
+ * Hence if
222
+ * Root trust A trust B trust C
223
+ * C is trusted by Root
224
+ * @param trustee
225
+ * @param truster, the truster "root", if undefined defaults to the root trust
226
+ * @returns true, if trusted
227
+ */
228
+ async isTrusted(
229
+ trustee: PublicSignKey,
230
+ truster: PublicSignKey = this.rootTrust
231
+ ): Promise<boolean> {
232
+ if (trustee.equals(this.rootTrust)) {
233
+ return true;
234
+ }
235
+ if (this.trustGraph.log.role instanceof Replicator) {
236
+ return this._isTrustedLocal(trustee, truster);
237
+ } else {
238
+ this.trustGraph.index.search(new SearchRequest({ query: [] }), {
239
+ remote: { sync: true },
240
+ });
241
+ return this._isTrustedLocal(trustee, truster);
242
+ }
243
+ }
244
+
245
+ async _isTrustedLocal(
246
+ trustee: PublicSignKey,
247
+ truster: PublicSignKey = this.rootTrust
248
+ ): Promise<boolean> {
249
+ const trustPath = await hasPath(
250
+ trustee,
251
+ truster,
252
+ this.trustGraph,
253
+ getFromByTo
254
+ );
255
+ return !!trustPath;
256
+ }
257
+
258
+ async getTrusted(): Promise<PublicSignKey[]> {
259
+ const current = this.rootTrust;
260
+ const participants: PublicSignKey[] = [current];
261
+ const generator = getPathGenerator(current, this.trustGraph, getToByFrom);
262
+ for await (const next of generator) {
263
+ participants.push(next.to);
264
+ }
265
+ return participants;
266
+ }
267
+
268
+ hashCode(): string {
269
+ return sha256Base64Sync(serialize(this));
270
+ }
271
+ }
@@ -0,0 +1,189 @@
1
+ import { field, fixedArray, serialize, variant } from "@dao-xyz/borsh";
2
+ import {
3
+ Documents,
4
+ DocumentIndex,
5
+ SearchRequest,
6
+ StringMatch,
7
+ } from "@peerbit/document";
8
+ import { PublicSignKey } from "@peerbit/crypto";
9
+ import { concat } from "uint8arrays";
10
+ import { RPC } from "@peerbit/rpc";
11
+ import { sha256Sync } from "@peerbit/crypto";
12
+
13
+ export type RelationResolver = {
14
+ resolve: (
15
+ key: PublicSignKey,
16
+ db: Documents<IdentityRelation>
17
+ ) => Promise<IdentityRelation[]>;
18
+ next: (relation: IdentityRelation) => PublicSignKey;
19
+ };
20
+
21
+ export const getFromByTo: RelationResolver = {
22
+ resolve: async (to: PublicSignKey, db: Documents<IdentityRelation>) => {
23
+ return Promise.all(
24
+ await db.index.search(
25
+ new SearchRequest({
26
+ query: [
27
+ new StringMatch({
28
+ key: "to",
29
+ value: to.hashcode(),
30
+ }),
31
+ ],
32
+ }),
33
+ {
34
+ local: true,
35
+ remote: false,
36
+ }
37
+ )
38
+ );
39
+ },
40
+ next: (relation) => relation.from,
41
+ };
42
+
43
+ export const getToByFrom: RelationResolver = {
44
+ resolve: async (from: PublicSignKey, db: Documents<IdentityRelation>) => {
45
+ return Promise.all(
46
+ await db.index.search(
47
+ new SearchRequest({
48
+ query: [
49
+ new StringMatch({
50
+ key: "from",
51
+ value: from.hashcode(),
52
+ }),
53
+ ],
54
+ }),
55
+ {
56
+ local: true,
57
+ remote: false,
58
+ }
59
+ )
60
+ );
61
+ },
62
+ next: (relation) => relation.to,
63
+ };
64
+
65
+ export async function* getPathGenerator(
66
+ from: PublicSignKey,
67
+ db: Documents<IdentityRelation>,
68
+ resolver: RelationResolver
69
+ ) {
70
+ let iter = [from];
71
+ const visited = new Set();
72
+ while (iter.length > 0) {
73
+ const newIter: PublicSignKey[] = [];
74
+ for (const value of iter) {
75
+ const results = await resolver.resolve(value, db);
76
+ for (const result of results) {
77
+ if (result instanceof IdentityRelation) {
78
+ if (visited.has(result.id)) {
79
+ return;
80
+ }
81
+ visited.add(result.id);
82
+ yield result;
83
+
84
+ newIter.push(resolver.next(result));
85
+ }
86
+ }
87
+ }
88
+ iter = newIter;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Get path, to target.
94
+ * @param start
95
+ * @param target
96
+ * @param db
97
+ * @returns
98
+ */
99
+ export const hasPathToTarget = async (
100
+ start: PublicSignKey,
101
+ target: (key: PublicSignKey) => boolean,
102
+ db: Documents<IdentityRelation>,
103
+ resolver: RelationResolver
104
+ ): Promise<boolean> => {
105
+ if (!db) {
106
+ throw new Error("Not initalized");
107
+ }
108
+
109
+ const current = start;
110
+ if (target(current)) {
111
+ return true;
112
+ }
113
+
114
+ const iterator = getPathGenerator(current, db, resolver);
115
+ for await (const relation of iterator) {
116
+ if (target(relation.from)) {
117
+ return true;
118
+ }
119
+ }
120
+ return false;
121
+ };
122
+
123
+ @variant(0)
124
+ export abstract class AbstractRelation {
125
+ @field({ type: fixedArray("u8", 32) })
126
+ id: Uint8Array;
127
+ }
128
+
129
+ @variant(0)
130
+ export class IdentityRelation extends AbstractRelation {
131
+ @field({ type: PublicSignKey })
132
+ _from: PublicSignKey;
133
+
134
+ @field({ type: PublicSignKey })
135
+ _to: PublicSignKey;
136
+
137
+ constructor(properties?: {
138
+ to: PublicSignKey; // signed by truster
139
+ from: PublicSignKey;
140
+ }) {
141
+ super();
142
+ if (properties) {
143
+ this._from = properties.from;
144
+ this._to = properties.to;
145
+ this.initializeId();
146
+ }
147
+ }
148
+
149
+ get from(): PublicSignKey {
150
+ return this._from;
151
+ }
152
+
153
+ get to(): PublicSignKey {
154
+ return this._to;
155
+ }
156
+
157
+ initializeId() {
158
+ this.id = IdentityRelation.id(this.to, this.from);
159
+ }
160
+
161
+ static id(to: PublicSignKey, from: PublicSignKey) {
162
+ return sha256Sync(concat([serialize(to), serialize(from)]));
163
+ }
164
+ }
165
+
166
+ export const hasPath = async (
167
+ start: PublicSignKey,
168
+ end: PublicSignKey,
169
+ db: Documents<IdentityRelation>,
170
+ resolver: RelationResolver
171
+ ): Promise<boolean> => {
172
+ return hasPathToTarget(start, (key) => end.equals(key), db, resolver);
173
+ };
174
+
175
+ export const getRelation = async (
176
+ from: PublicSignKey,
177
+ to: PublicSignKey,
178
+ db: Documents<IdentityRelation>
179
+ ): Promise<IdentityRelation | undefined> => {
180
+ return db.index.get(new IdentityRelation({ from, to }).id);
181
+ };
182
+
183
+ export const createIdentityGraphStore = (id?: Uint8Array) =>
184
+ new Documents<IdentityRelation>({
185
+ index: new DocumentIndex({
186
+ query: new RPC(),
187
+ }),
188
+ id,
189
+ });
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./controller.js";
2
+ export * from "./identity-graph.js";