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!');