The problem of access control and permissions is solved with centralized CAs (Certificate Authority) in web 2.0. However, such problem is urgent and becomes even more challenging considering a decentralized nature of web 3.0. TrustGraph is our point of view on the solution for this challenge.
TrustGraph is a bottom layer of trust for open p2p networks: every peer may be provided with SSL-like certificates that promoted over the network. Service providers and peers can treat certificate holders differently based on their certificate set.
TrustGraph is a basic component that allows storing and managing certificates without additional logic about how to decide whom to trust and whom to treat as unreliable.
The problem of peer choice and prioritization is very urgent in p2p networks. We can't use the network reliably and predictably without trust to any network participant. We also should mark and avoid malicious peers. In addition we need to control our application access and permissions in runtime, so it performs continuously without interruptions and redeployments.
TrustGraph is basically a directed graph with one root at least, vertices are peer ids, and edges are one of the two types of cryptographic relations: trust and revocation.
Root is a peer id that we unconditionally trust until it is removed, and is defined by the node owner. Every root has characteristics that represent the maximum length for a chain of trusts.
As a path to the root, we consider a path with only trust edges, given the following rule: chain R -> A -> ...-> C
is not a path if A revoked C.
Trust is a cryptographic relation representing that peer A trusts peer B until this trust expires or is revoked. Trust relation is transitive. If peer A trusts peer B, and peer B trusts peer C, it results in peer A trusts peer C transitively. Trust relation means that you trust to connect, compute or store based on your business logic and chosen metrics. For example, if you want to perform some computation and some well-known peers do that, and are trusted by others you trust, so you can safely use them for computing but not to store sensitive information (personal keys, etc).
Trust data structure contains the following fields: - peer id, trust is issued to - creation timestamp - expiration timestamp - a signature of the issuer that contains all of the previous fields signed
So the trust is signed and tamperproof by design.
Certificate is a chain of trusts started with a self-signed root trust. Considering Trust and Certificate data structures, it is possible to track the chain of trust relations: the issued_for
field of the first trust in a chain indicates a root peer id, second — whom root trusts, etc. So if we have a chain R->A->B->C
in the certificate it looks like a chain of the following trusts: R->R
, R->A
, A->B
, B->C
. A certificate is tamperproof since it is a composition of signed trusts.
So peerA is trusted by peerB if there is a path between them in the instance of TrustGraph. The selection of certificates is subjective and defined by a node owner by choice of roots and maximum chain lengths. For now, there are no default metrics for a general case.
Revocation is a cryptographic relation representing that a peer A considers a peer C malicious or unreliable. For example, all chains containing paths from A to C will not be treated as valid. So if A trusts a peer B, and B trusts C, a peer A has no trust to C transitively, it would have otherwise.
Every peer has a weight. A weight signifies a power of 2 or zero. If there is no path from any root to a peer, given revocations, its weight equals zero. The closer to the root, the bigger the weight. Weights are also subjective and relevant in the scope of a local TrustGraph.
TrustGraph is a builtin meaning that every node is bundled with a TrustGraph instance and predefined certificates.
Trust is transitive in terms of cryptographic relations. On the other hand, a subset of trusts and certificates is subjective for each network participant because of the choice of roots.
``` import "@fluencelabs/trust-graph/trust-graph-api.aqua" import "@fluencelabs/trust-graph/trust-graph.aqua"
func myfunction(peerid: string) -> u32: on HOSTPEERID: result <- getweight(peerid) <- result ```
set_root(peer_id: PeerId, max_chain_len: u32) -> SetRootResult
add_root_trust(node: PeerId, peer_id: PeerId, max_chain_len: u32) -> ?Error
Let's set our peer id as a root on our relay and add self-signed trust: ```rust func setmeasroot(maxchainlen): result <- addroottrust(HOSTPEERID, INITPEERID, maxchain_len)
-- if you use peerid different from INITPEERID
-- you should add keypair in your Sig service
if result.success:
-- do smth
Op.noop()
else:
-- handle failure
Op.noop()
``
- also you can use
setroot+
addtrustto achieve the same goal
- [how to add keypair to Sig service](https://doc.fluence.dev/docs/fluence-js/3_in_depth#signing-service)
- roots can be added only by the service owner
-
maxchain_len` specifies a number of trusts in a chain for the root. Zero for chains that contain only root trust.
issue_trust(issuer: PeerId, issued_for: PeerId, expires_at_sec: u64) -> ?Trust, ?Error
import_trust(trust: Trust, issuer: PeerId) -> ?Error
add_trust(node: PeerId, issuer: PeerId, issued_for: PeerId, expires_at_sec: u64) -> ?Error
Let's issue trust, and import it to our relay:
rust
func issue_trust_by_me(issued_for: PeerId, expires_at_sec: u64):
trust, error <- issue_trust(INIT_PEER_ID, issued_for, expires_at_sec)
if trust == nil:
-- handle failure
Op.noop()
else:
on HOST_PEER_ID:
error <- import_trust(trust!, INIT_PEER_ID)
-- handle error
add_trust
is a combination of issue_trust
and import_trust
INIT_PEER_ID
check the Sig service docsissue_revocation(revoked_by: PeerId, revoked: PeerId) -> ?Revocation, ?Error
import_revocation(revocation: Revocation) -> ?Error
revoke(node: PeerId, revoked_by: PeerId, revoked: PeerId) -> ?Error
Let's revoke some peers by our peer id:
rust
func revoke_peer(revoked: PeerId):
revocation, error <- issue_revocation(INIT_PEER_ID, revoked)
if revocation == nil:
-- handle failure
Op.noop()
else:
on HOST_PEER_ID:
error <- import_revocation(revocation!)
-- handle error
revoke
is a combination of issue_revocation
and import_revocation
INIT_PEER_ID
check the Sig service docsget_all_certs(issued_for: PeerId) -> AllCertsResult
get_all_certs_from(issued_for: PeerId, issuer: PeerId) -> AllCertsResult
get_host_certs() -> AllCertsResult
get_host_certs_from(issuer: PeerId) -> AllCertsResult
Let's get all certificates issued by us to our relay peer id (HOSTPEERID):
rust
func get_certs_issued_by_me() -> AllCertsResult:
on HOST_PEER_ID:
result <- get_host_certs_from(INIT_PEER_ID)
<- result
- get_host_certs
is just an alias for get_all_certs(HOST_PEER_ID)
- _from
calls results contain only certificates with trust issued by issuer
get_weight(peer_id: PeerId) -> WeightResult
get_weight_from(peer_id: PeerId, issuer: PeerId) -> WeightResult
Let's get our weight for certificates which contain trust by our relay
rust
func get_our_weight() -> ?u32, ?string:
weight: ?u32
error: ?string
on HOST_PEER_ID:
result <- get_weight_from(INIT_PEER_ID, HOST_PEER_ID)
if result.success:
weight <<- result.weight
else:
error <<- result.error
<- weight, error
get_weight
returns result among all the certificates, on the other hand, get_weight_from
return certificates containing trust by the issuer onlyexport.aqua
as in the Aqua documentationAdd the following to your dependencies
@fluencelabs/trust-graph
@fluencelabs/aqua
@fluencelabs/aqua-lib
@fluencelabs/fluence
@fluencelabs/fluence-network-environment
Import dependencies
typescript
import * as tg from "./generated/export";
import { Fluence, KeyPair } from "@fluencelabs/fluence";
import { krasnodar, Node } from "@fluencelabs/fluence-network-environment";
Create a client (specify keypair if you are node owner link)
typescript
await Fluence.start({ connectTo: relay /*, KeyPair: builtins_keypair*/});
typescript
let peer_id = Fluence.getStatus().peerId;
let relay = Fluence.getStatus().relayPeerId;
assert(peer_id !== null);
assert(relay !== null);
let max_chain_len = 2;
let far_future = 99999999999999;
let error = await tg.add_root_trust(relay, peer_id, max_chain_len, far_future);
if (error !== null) {
console.log(error);
}
typescript
// issue signed trust
let error = await tg.issue_trust(relay, peer_id, issued_for_peer_id, expires_at_sec);
if (error !== null) {
console.log(error);
}
You can organize a subnetwork with peers trusted by your choice or chosen metrics. So you can treat trusts given by a peer (or a key) as evidence.
Let's consider we have peers A, B and C:
- Choose a peer A as an authority, set it as a root for the local TrustGraphs on all peers
- Issue and put self-signed by a peer A trust as a root trust
- Issue trusts by a peer A a to peer B and a peer C, and put them on all peers
- So for a call get_weight_from(targetPeer, peerA)
will reflect whether targetPeer is in a subnetwork ABC
You can specify in runtime who can access service functionality based on the local TrustGraph (certificates, weights). It's possible to check where the proof comes from based on tetraplets. For example, only peers that have a non-zero weight can execute a service function trusted_call(weight: WeightResult) -> u8
.
So if you want to have service permission management you should follow the steps:
- Pass WeightResult
from TrustGraph to the function that you need to control:
rust
...
weight_result <- get_weight(INIT_PEER_ID)
result <- MyService.trusted_call(weight_result)
...
- Inside your service you need to check tetraplets like this to be sure that they are resulted from the local TrustGraph
- Add INIT_PEER_ID
or another choosen key as a root
- Issue trust to peers that can call this function:
rust
func grant_access(issued_for: PeerId, expires_at_sec: u64):
error <- add_trust(INIT_PEER_ID, issued_for, expires_at_sec)
if error != nil
-- handle error
See example:
- How to call trust-graph
functions in TS/JS
- Step-by-step description README
Can a weight change during time?
What does a zero weight mean?
How we can interpret a certificate and/or a peer weight?
How are weights calculated and based on what feedback?
R -> A -> B -> C
, so the corresponding weights of peers are 8
, 4
, 2
, 1
. Weights are the same if there are no changes in the paths.
As long as we have no metrics, all trust/revocation logic is a TrustGraph user's responsibility.How do I set all weights to untrusted and then increase trust in a peer over time?
How do I know that other peers are using the same processes to update weights?
Can I start my own instance of a trust graph or is there only a global version available?
High-level API is defined in the trust-graph-api.aqua module. API Reference soon will be available in the documentation.
src
is the main project with all trust graph logic
keypair
directory is an abstracted cryptographical layer (key pairs, public keys, signatures, etc.)
service
is a package that provides marine
API and could be compiled to a Wasm file. It usesSQLite
as storage
example
is a js
script that shows how to use Trust Graph to label peers
builtin-package
contains blueprint, configs and scripts for generation builtin package locally or via CI
admin
is a js
script used to generate builtin-package/on_start.json
which contains certificates for Fluence Labs nodes