@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 +22 -0
- package/README.md +58 -0
- package/lib/esm/controller.d.ts +57 -0
- package/lib/esm/controller.js +211 -0
- package/lib/esm/controller.js.map +1 -0
- package/lib/esm/identity-graph.d.ts +35 -0
- package/lib/esm/identity-graph.js +151 -0
- package/lib/esm/identity-graph.js.map +1 -0
- package/lib/esm/index.d.ts +2 -0
- package/lib/esm/index.js +3 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/package.json +3 -0
- package/package.json +44 -0
- package/src/controller.ts +271 -0
- package/src/identity-graph.ts +189 -0
- package/src/index.ts +2 -0
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":";;;;;;;;;AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAO,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EACN,aAAa,EACb,SAAS,EAET,YAAY,GACZ,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EACN,gBAAgB,EAChB,wBAAwB,EACxB,gBAAgB,EAChB,OAAO,EACP,WAAW,EACX,WAAW,EACX,WAAW,EACX,gBAAgB,GAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAE,UAAU,EAAoB,MAAM,qBAAqB,CAAC;AAEnE,MAAM,eAAe,GAAG,CAAC,SAAiC,EAAE,EAAE;IAC7D,OAAO,SAAS,YAAY,aAAa;QACxC,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;AACtC,CAAC,CAAC;AACF,MAAM,mBAAmB,GAAG,KAAK,EAChC,KAAyC,EACzC,SAAoD,EACjC,EAAE;IACrB,qBAAqB;IACrB,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,eAAe,EAAE,CAAC;IAChD,IACC,SAAS,YAAY,YAAY;QACjC,SAAS,YAAY,eAAe,EACnC;QACD,2FAA2F;QAE3F,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,aAAa,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,KAAK,EAAE,GAAkB,EAAoB,EAAE;YAC/D,IAAI,SAAS,YAAY,YAAY,EAAE;gBACtC,kKAAkK;gBAClK,2HAA2H;gBAE3H,MAAM,QAAQ,GACb,SAAS,CAAC,MAAM,IAAI,WAAW,CAAC,SAAS,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;gBACnE,SAAS,CAAC,MAAM,GAAG,QAAQ,CAAC;gBAE5B,IAAI,QAAQ,YAAY,gBAAgB,EAAE;oBACzC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;wBAC/B,OAAO,KAAK,CAAC;qBACb;iBACD;gBAED,sCAAsC;aACtC;YACD,IAAI,SAAS,EAAE;gBACd,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;gBACrC,OAAO,OAAO,CAAC;aACf;iBAAM;gBACN,OAAO,IAAI,CAAC;aACZ;QACF,CAAC,CAAC;QACF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE;YACvB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,MAAM,EAAE;gBACX,OAAO,IAAI,CAAC;aACZ;SACD;QACD,OAAO,KAAK,CAAC;KACb;SAAM;QACN,OAAO,KAAK,CAAC;KACb;AACF,CAAC,CAAC;AAIK,WAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,OAA0B;IAE5D,aAAa,CAA8B;IAE3C,YAAY,KAGX;QACA,KAAK,EAAE,CAAC;QACR,IAAI,KAAK,EAAE;YACV,IAAI,CAAC,aAAa;gBACjB,KAAK,CAAC,aAAa,IAAI,wBAAwB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;SAC5D;IACF,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,KAAyC;QACxD,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAA2B;QACrC,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;YAC7B,IAAI,EAAE,gBAAgB;YACtB,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;YACpC,OAAO,EAAE,OAAO,EAAE,OAAO;YACzB,KAAK,EAAE;gBACN,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;oBACvB,OAAO;wBACN,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE;wBACzB,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE;qBACrB,CAAC;gBACH,CAAC;aACD;YACD,IAAI,EAAE,OAAO,EAAE,IAAI;SACnB,CAAC,CAAC,CAAC,qCAAqC;IAC1C,CAAC;IAED,KAAK,CAAC,WAAW,CAChB,EAA0B,EAC1B,OAAoD;QAEpD,yCAAyC;QACzC,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAC3B,IAAI,gBAAgB,CAAC;YACpB,EAAE,EAAE,eAAe,CAAC,EAAE,CAAC;YACvB,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS;SAClE,CAAC,EACF,OAAO,CACP,CAAC;IACH,CAAC;CACD,CAAA;AA/CA;IADC,KAAK,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;8BACZ,SAAS;oDAAmB;AAF/B,aAAa;IADzB,OAAO,CAAC,WAAW,CAAC;;GACR,aAAa,CAiDzB;AAQM,WAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,OAA2B;IAE9D,SAAS,CAAgB;IAGzB,UAAU,CAA8B;IAExC,YAAY,KAA6D;QACxE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,UAAU,GAAG,wBAAwB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACrD,IAAI,CAAC,SAAS,GAAG,eAAe,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAA4B;QACtC,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAC1B,IAAI,EAAE,gBAAgB;YACtB,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;YACpC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAChC,IAAI,EAAE,OAAO,EAAE,IAAI;YACnB,KAAK,EAAE;gBACN,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;oBACvB,OAAO;wBACN,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE;wBACzB,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE;qBACrB,CAAC;gBACH,CAAC;aACD;SACD,CAAC,CAAC,CAAC,qCAAqC;IAC1C,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,KAAyC;QACxD,OAAO,mBAAmB,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAoB;QACjC,OAAO,IAAI,CAAC,CAAC,2CAA2C;IACzD,CAAC;IAED,KAAK,CAAC,GAAG,CACR,OAA+B;QAE/B,MAAM,GAAG,GACR,OAAO,YAAY,aAAa;YAC/B,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;QAEpC,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,WAAW,CAC9C,GAAG,EACH,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAC5B,CAAC;QACF,IAAI,CAAC,gBAAgB,EAAE;YACtB,MAAM,QAAQ,GAAG,IAAI,gBAAgB,CAAC;gBACrC,EAAE,EAAE,GAAG;gBACP,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS;aAClC,CAAC,CAAC;YACH,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACpC,OAAO,QAAQ,CAAC;SAChB;QACD,OAAO,gBAAgB,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,WAAW,CAChB,OAA+B,EAC/B,UAAkC,IAAI,CAAC,SAAS;QAEhD,OAAO,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,WAAW,CACV,OAA+B,EAC/B,UAAkC,IAAI,CAAC,SAAS;QAEhD,OAAO,WAAW,CACjB,eAAe,CAAC,OAAO,CAAC,EACxB,eAAe,CAAC,OAAO,CAAC,EACxB,IAAI,CAAC,UAAU,CACf,CAAC;IACH,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,SAAS,CACd,OAAsB,EACtB,UAAyB,IAAI,CAAC,SAAS;QAEvC,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE;YACnC,OAAO,IAAI,CAAC;SACZ;QACD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,YAAY,UAAU,EAAE;YACnD,OAAO,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;SAC9C;aAAM;YACN,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAAE;gBAC9D,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;aACtB,CAAC,CAAC;YACH,OAAO,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;SAC9C;IACF,CAAC;IAED,KAAK,CAAC,eAAe,CACpB,OAAsB,EACtB,UAAyB,IAAI,CAAC,SAAS;QAEvC,MAAM,SAAS,GAAG,MAAM,OAAO,CAC9B,OAAO,EACP,OAAO,EACP,IAAI,CAAC,UAAU,EACf,WAAW,CACX,CAAC;QACF,OAAO,CAAC,CAAC,SAAS,CAAC;IACpB,CAAC;IAED,KAAK,CAAC,UAAU;QACf,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;QAC/B,MAAM,YAAY,GAAoB,CAAC,OAAO,CAAC,CAAC;QAChD,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QAC1E,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,SAAS,EAAE;YACnC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;SAC3B;QACD,OAAO,YAAY,CAAC;IACrB,CAAC;IAED,QAAQ;QACP,OAAO,gBAAgB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1C,CAAC;CACD,CAAA;AAjIA;IADC,KAAK,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BACpB,aAAa;iDAAC;AAGzB;IADC,KAAK,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;8BACf,SAAS;kDAAmB;AAL5B,cAAc;IAD1B,OAAO,CAAC,iBAAiB,CAAC;;GACd,cAAc,CAmI1B"}
|
|
@@ -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":"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"}
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
|
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