Mark mail that was signed by someone your user is connected to, right in the inbox.
createContentStatement · verifySignedContent · external-keys/resolve-signer · query-reachable
Signed messages guide
People and agents vouch for whoever they trust, with their own device keys. Your app asks whether trust reaches someone, and a yes comes with a zero-knowledge proof.
Or write to hello@rebellion.systems
const policy = { scope: "identity" as const, minTrust: 50, maxHops: 3 };
const query = { sourceGraphKeyId: me, targetGraphKeyId: them, policy };
const authorization = await signer.signRead({ operation: "query-reachable", graphKeyId: myDevice, query });
const answer = await graph.queryReachability({ ...query, authorization });
const result = await createZkVerifier().verifyAttestation({ attestation: answer.attestation,
verificationKey: wotPathVerificationKey(), jwks: pinnedJwks,
question: { sourceGraphKeyId: me, targetGraphKeyId: them, ...policy } });
if (!result.verified) throw new Error(`Answer rejected: ${result.code}`);
const { reachable, pathLength } = result.verdict; // read the verdict only after the gate
const policy = {
scope: "identity" as const,
minTrust: 50, maxHops: 3 };
const query = {
sourceGraphKeyId: me,
targetGraphKeyId: them, policy };
const authorization =
await signer.signRead({
operation: "query-reachable",
graphKeyId: myDevice, query });
const answer = await graph
.queryReachability({
...query, authorization });
const result = await createZkVerifier()
.verifyAttestation({
attestation: answer.attestation,
verificationKey:
wotPathVerificationKey(),
jwks: pinnedJwks,
question: {
sourceGraphKeyId: me,
targetGraphKeyId: them,
...policy } });
if (!result.verified)
throw new Error(result.code);
const { reachable, pathLength } =
result.verdict;
{ "attestation": { // signed: read the verdict from here
"scheme": "ultrahonk-wot-path-v2",
"publicInputs": { "reachable": true, "pathLength": 3, "minTrust": 50, "maxHops": 3,
"graphEpochRoot": "…", … },
"zkProof": "…", "verificationKeyHash": "6ef19d67…", "proof": "…",
"assertion": { …, "establishes": [ … ], "doesNotEstablish": [ … ] } }, "reachable": true, "pathLength": 3, … } // unsigned conveniences
{ "attestation": {
"scheme":
"ultrahonk-wot-path-v2",
"publicInputs": {
"reachable": true,
"pathLength": 3,
"minTrust": 50,
"maxHops": 3,
"graphEpochRoot": "…", … },
"zkProof": "…",
"verificationKeyHash":
"6ef19d67…",
"proof": "…",
"assertion": { … } }, "reachable": true,
"pathLength": 3, … }
Anywhere your user meets a sender, a signer or an agent, your app can ask whether their trust reaches them.
Mark mail that was signed by someone your user is connected to, right in the inbox.
createContentStatement · verifySignedContent · external-keys/resolve-signer · query-reachable
Signed messages guide
Label signed messages from people your trust reaches, in a Telegram or Signal filter.
formatSignedContent · parseContentMessage · verifySignedContent · query-reachable
Signed messages guide
Check whether a commit’s signer is someone your trust reaches.
graph-keys/resolve-signer · device certificate + status · query-reachable
Git signers guide
Trust an agent because someone you trust vouches for its node.
nodeKind: "agent" · owner → agent vouch · query-reachable
Agents guide
Show whether the person on a call is connected to you.
Needs a fresh challenge tied to the meeting.
Calls conceptLet calls through only from people your trust reaches.
Needs signed evidence that arrives before the call rings.
Call screening conceptA device joins a persona once. People vouch from their own devices. Your app asks from your user’s node and checks the answer itself.
Each device makes its own key and binds it through an AuthenSee sign-in. Finalizing needs two one-time proofs.
Score someone from 0 to 100. The device that vouches signs it, and only its source can change it.
Ask from your own node with a minimum score and up to three hops. Check the proof locally against keys you pin.
A verifier learns that a qualifying path exists in the cited graph epoch, and nothing about who is on it. If proving fails, the request fails: there is no signature-only positive.
A negative answer is signed by the server and carries no proof, stated in machine-readable form. A circuit can show that a path exists; it can’t show that none does.
const query = {
sourceGraphKeyId: myGraphNodeId,
targetGraphKeyId: otherGraphNodeId,
policy: { scope: "identity" as const, minTrust: 50, maxHops: 3 },
};
const authorization = await signBrowserGraphRead(key.privateKey, {
operation: "query-reachable", graphKeyId: myGraphKeyId, query,
});
const response = await graph.queryReachability({ ...query, authorization });
const { createZkVerifier, wotPathVerificationKey } =
await import("@rebellion-systems/trust-graph-sdk/zk");
const result = await createZkVerifier().verifyAttestation({
verificationKey: wotPathVerificationKey(),
attestation: response.attestation,
jwks: pinnedAttestationJwks, // pinned by your app, never fetched
question: { sourceGraphKeyId: query.sourceGraphKeyId,
targetGraphKeyId: query.targetGraphKeyId, ...query.policy },
});
if (!result.verified) throw new Error(`Answer rejected: ${result.code}`);
console.log(result.verdict); // { reachable, pathLength }, signed and checked
“The network for humans who want to trust each other.”
Meet people your friends already trust. Omnibus is one app built on TrustGraph: it uses TrustGraph’s vouches and the trust paths between people.
A mail plugin, a message filter, a call tool, an agent. Tell us what you’re building.
Request accessMore in the docs: account deletion · the full API
The SDK is in early access, and a public package is coming soon. Email us, and we’ll add your app’s origin to the server allowlist by hand so the sign-in ceremony can return to your app.
hello@rebellion.systemsDevelop and test against staging with your early-access setup.
trust-staging.rebellion.systemsShip against production, pinning production’s own attestation key. It is separate from staging’s.
trust.rebellion.systemsThe service computes the path and knows it. The proof hides the people in between from whoever verifies the answer. A negative answer is a signed report, not a proof.
The field names and the hidden in-between people are how a real answer looks. The names and values here are examples.
Only attestation is signed. The top-level reachable, pathLength and policy fields are unsigned conveniences, so read the verdict from the attestation after you verify it.
The message carries it: a signed TrustGraph statement, or an OpenPGP signature from a key attached to the sender’s persona. There’s no lookup from an email address to a person.
Only messages that carry a TrustGraph signature. An unsigned message can’t be attributed to anyone. Each check is a signed read from your user’s own node, so a bot can’t ask on their behalf without their device key.
The signing key’s current membership of a persona, certified by the TrustGraph operator, and whether your trust reaches that persona. Commit dates are set by the signer, and the check says nothing about who wrote or reviewed the code.
A node’s kind is declared when its key is bound. It doesn’t verify who runs an agent, and there’s no separate agent enrolment. Trust in an agent comes from the vouches pointing at it.
A signed statement proves its content and the signer’s device membership, not when it was signed or that the device is on this call. Calls need a fresh challenge tied to the meeting, which doesn’t exist yet. Help us design it.
An ordinary phone call carries no TrustGraph evidence, and there’s no lookup from a phone number to a person. A calling app would need to carry a signed statement before the call rings.
A reachability query must be signed by a device of the node it starts from, so it always asks from your user’s own node. A bearer token or app credential doesn’t authorize it.
One proof stays with the app that started the bind; the other comes back from the completed sign-in. Requiring both means a link forwarded to someone else can’t produce a bound key.
The person you vouch for sees that you trust them, not your score. Private tags on your vouches are hidden from other users, but the service can read them.
Every graph change is signed by a device key. Reachability queries and private edge listings need a fresh device signature for your own node, inside a five-minute window; a bearer token doesn’t authorize them. The one exception for changes: an owner can revoke a lost device without having it.
If proving fails, a positive answer is refused rather than sent without a proof. The server won’t start without its compiled circuit and pinned verification key.
Each graph state has an epoch root, and the event log is a hash chain, so you can check that today’s log extends a commitment you saw before. That check’s answer isn’t signed: keep the roots you observe.
Request logs are built from an allowlist. Headers, bodies, signatures and query-string values never reach a log line.
The operator metrics endpoint reports only aggregates over routes and status codes. No graph state ever becomes a label.
Server and proxy logs on the operated service rotate hourly and are vacuumed at 29 days; any record older than 30 days is treated as an incident. Exported logs and traces are kept 15 days. This is a log policy, not a database or backup deletion policy.
Omnibus’s feed, nearby and profile features are its own app layer, built for Omnibus on top of TrustGraph. They aren’t part of the TrustGraph API.