GUMBA Tutorial
Build a private chat room in 15 minutes
Learn to create and manage GUMBA-protected spaces with step-by-step instructions.
Prerequisites
- • YAKMESH node installed and running
- • Basic understanding of Node.js and async/await
- • DOKO identity created (see DOKO documentation)
Tutorial 1: Create a Private Chat Room
In this tutorial, you'll create a GUMBA-protected chat room, add members, and handle access requests from visitors.
1 Import GUMBA Components
First, import the necessary GUMBA components:
import {
GumbaHub,
GumbaProof,
GUMBA_ROLE,
GUMBA_PROOF_TYPE,
} from 'yakmesh/mesh/gumba.js';
// You'll also need your node's identity
import { loadIdentity } from 'yakmesh/mesh/identity.js';
2 Initialize the GUMBA Hub
Create a GumbaHub with your node's identity:
// Load your node's identity
const identity = await loadIdentity();
// Create the GUMBA hub
// The second parameter is an ANNEX instance for secure delivery (optional)
const hub = new GumbaHub(identity, null);
console.log('GUMBA Hub initialized');
3 Create a Bundle (Chat Room)
Create a new encrypted bundle for your chat room:
// Create a new bundle with a unique ID
hub.createBundle('team-chat', {
name: 'Team Chat Room',
description: 'Private discussions for the team',
});
// Get the bundle reference
const bundle = hub.getBundle('team-chat');
console.log('Bundle created:', bundle.getInfo());
The bundle ID must be unique within your hub. The bundle is automatically encrypted with a key derived from your node's secret.
4 Add Members
Add members with appropriate roles:
// Add Alice as an admin
bundle.addMember(
'doko-alice-abc123', // Alice's DOKO ID
GUMBA_ROLE.ADMIN, // Role
identity.identity.dokoId // Your DOKO ID (as owner)
);
// Add Bob as a regular member
bundle.addMember(
'doko-bob-def456',
GUMBA_ROLE.MEMBER,
identity.identity.dokoId
);
// Add Carol as read-only
bundle.addMember(
'doko-carol-ghi789',
GUMBA_ROLE.READER,
identity.identity.dokoId
);
console.log('Members added:', bundle.memberTree.getMembers());
5 Register Public Keys
Register member public keys for signature verification:
// Register public keys for each member
// In production, these would come from KHATA network lookup
hub.registerPublicKey('doko-alice-abc123', alicePublicKey);
hub.registerPublicKey('doko-bob-def456', bobPublicKey);
hub.registerPublicKey('doko-carol-ghi789', carolPublicKey);
In production, public keys are resolved via the KHATA protocol. Manual registration is for testing or pre-cached keys only.
6 Add Some Messages
Add initial messages to the chat room:
// Add messages (as the owner)
bundle.addMessage(
{ text: 'Welcome to the team chat!', type: 'text' },
identity.identity.dokoId
);
bundle.addMessage(
{ text: 'Please introduce yourselves.', type: 'text' },
identity.identity.dokoId
);
console.log('Messages added');
7 Handle Access Requests
When a visitor wants to access the chat room:
// Visitor requests access (on their node)
// Step 1: Issue a challenge
const challenge = hub.issueChallenge('team-chat', visitorDokoId);
// Step 2: Send challenge to visitor (via mesh network)
// ... challenge is sent to visitor node ...
// Step 3: Visitor signs the challenge (on their node)
const signedProof = GumbaProof.signChallenge(challenge, visitorSecretKey);
// Step 4: Visitor sends signed proof back
// ... signed proof is received ...
// Step 5: Verify and grant access (on your node)
const access = await hub.handleAccessRequest(
'team-chat',
signedProof,
'visitor-node-id'
);
if (access.granted) {
console.log('Access granted!');
console.log('Session ID:', access.sessionId);
console.log('Role:', access.role);
} else {
console.log('Access denied:', access.reason);
}
8 Serve Messages
Retrieve and deliver messages for an active session:
// Get messages for a session
const result = await hub.getMessages(access.sessionId);
if (result.error) {
console.log('Error:', result.error);
} else {
console.log('Messages:', result.messages);
// Messages are decrypted locally before delivery
}
// Post a message (if the session has write permission)
const postResult = await hub.postMessage(access.sessionId, {
text: 'Hello from visitor!',
type: 'text',
});
console.log('Message posted:', postResult.success);
Tutorial 2: Attestation-Based Access
Learn how existing members can vouch for new members without owner involvement.
1 Create an Attestation
An existing member with sufficient role creates an attestation:
// Alice (admin) creates attestation for Dave
const attestation = GumbaProof.createAttestation({
bundleId: 'team-chat',
grantorDokoId: alice.dokoId,
granteeDokoId: 'doko-dave-xyz999',
grantorSecretKey: alice.secretKey,
grantedRole: GUMBA_ROLE.MEMBER, // Alice can grant up to MEMBER
expiry: Date.now() + 24 * 60 * 60 * 1000, // Valid for 24 hours
});
// Alice sends attestation to Dave out-of-band
// (email, secure message, QR code, etc.)
2 Use Attestation for Access
Dave uses the attestation to request access:
// Dave presents the attestation to the hub
const access = await hub.handleAccessRequest(
'team-chat',
attestation, // Use attestation as proof
'dave-node-id'
);
if (access.granted) {
console.log('Dave granted access via attestation!');
console.log('Role:', access.role); // Should be MEMBER
}
Attestation Rules
- • Grantors can only attest roles equal to or below their own role
- • OWNERs can attest any role except OWNER
- • ADMINs can attest MEMBER or READER
- • MEMBERs can only attest READER
- • READERs cannot create attestations
Tutorial 3: Anonymous Access
Use Merkle proofs for privacy-preserving access where identity should remain hidden.
1 Generate Merkle Proof
Member generates a proof of their membership:
// Get the bundle's member tree
const bundle = hub.getBundle('whistleblower-channel');
// Generate Merkle proof (proves membership without revealing to others)
const merkleProof = GumbaProof.createMerkleProof(
alice.dokoId,
bundle.memberTree
);
// The proof contains:
// - type: MERKLE
// - dokoId: (can be hidden for true ZK)
// - proof: Merkle path to root
// - timestamp
2 Verify Merkle Proof
Verify membership without knowing the full member list:
// Verify the proof against the known root
const verified = GumbaProof.verifyMerkleProof(
merkleProof,
bundle.memberTree.getRoot()
);
if (verified) {
console.log('Member verified via Merkle proof');
// Grant read access without logging identity
}
Privacy Considerations
True zero-knowledge Merkle proofs require additional work:
- • The dokoId in the proof can reveal identity
- • For full anonymity, use commitment schemes
- • Consider timing attacks on proof generation
Tutorial 4: Managing Members
Add, Promote, and Remove Members
const bundle = hub.getBundle('team-chat');
const ownerDokoId = identity.identity.dokoId;
// Add a new member
bundle.addMember('doko-eve-new123', GUMBA_ROLE.READER, ownerDokoId);
// Members can be added with different roles
// (subject to the adder's own role level)
// Remove a member
bundle.removeMember('doko-eve-new123', ownerDokoId);
// Note: Owner cannot be removed!
// This will throw an error:
// bundle.removeMember(ownerDokoId, ownerDokoId); // Error!
// Check membership
if (bundle.memberTree.isMember('doko-alice-abc123')) {
const role = bundle.memberTree.getRole('doko-alice-abc123');
console.log('Alice is a member with role:', role);
}
// List all members
const members = bundle.memberTree.getMembers();
console.log('All members:', members);
Complete Example
Full Chat Room Implementation
import { GumbaHub, GumbaProof, GUMBA_ROLE } from 'yakmesh/mesh/gumba.js';
import { loadIdentity } from 'yakmesh/mesh/identity.js';
async function createPrivateChatRoom() {
// 1. Load identity and create hub
const identity = await loadIdentity();
const hub = new GumbaHub(identity, null);
// 2. Create the chat room
hub.createBundle('private-chat', {
name: 'Private Chat',
});
const bundle = hub.getBundle('private-chat');
const ownerDokoId = identity.identity.dokoId;
// 3. Add initial members (you'd get these from somewhere)
const members = [
{ dokoId: 'doko-alice-123', role: GUMBA_ROLE.ADMIN, publicKey: '...' },
{ dokoId: 'doko-bob-456', role: GUMBA_ROLE.MEMBER, publicKey: '...' },
];
members.forEach(m => {
bundle.addMember(m.dokoId, m.role, ownerDokoId);
hub.registerPublicKey(m.dokoId, m.publicKey);
});
// 4. Add welcome message
bundle.addMessage({ text: 'Welcome to the chat!' }, ownerDokoId);
// 5. Handle incoming access requests
async function handleAccessRequest(visitorDokoId, signedProof, visitorNodeId) {
// Verify and grant access
const result = await hub.handleAccessRequest(
'private-chat',
signedProof,
visitorNodeId
);
if (result.granted) {
// Send session token to visitor
return {
success: true,
sessionId: result.sessionId,
role: result.role,
};
} else {
return {
success: false,
reason: result.reason,
};
}
}
// 6. Handle message requests
async function handleGetMessages(sessionId, options = {}) {
return await hub.getMessages(sessionId, options);
}
async function handlePostMessage(sessionId, content) {
return await hub.postMessage(sessionId, content);
}
return {
hub,
bundle,
handleAccessRequest,
handleGetMessages,
handlePostMessage,
};
}
// Usage
const chatRoom = await createPrivateChatRoom();
console.log('Chat room ready!');