Skip to content

Latest commit

 

History

History
316 lines (232 loc) · 12.5 KB

orbisdb.md

File metadata and controls

316 lines (232 loc) · 12.5 KB

OrbisDB

This guide walks through how to use OrbisDB and TACo in combination, using the DeForum web application and repo as an example. OrbisDB is an easy-to-use SQL interface for querying data stored via the Ceramic network, and alongside TACo, another fundmental building block for the Web3 stack.

{% hint style="info" %} DeForum is a decentralized web forum that allows end-users to create token-gated posts, make comments, upload images, and build personal profiles. There are two synced reference repos, one hosted by nucypher (TACo) and one by ceramicstudio (OrbisDB). {% endhint %}

OrbisDB overview

Built upon Ceramic, OrbisDB is an open-source relational database designed for Web3 applications and beyond. OrbisDB offers a flexible and developer-friendly experience via a (1) web app & SDK for storing and managing datasets, (2) support for SQL and GraphQL queries, and (3) hosted nodes to facilitate DevOps. The Ceramic network, as with ComposeDB, enables sovereign data ownership, composability and scalability. Decentralization is achieved through a population of node operators numbering in the hundreds, that anyone can permissionlessly join to provide data services.

Use case ideas

  • DeSci. Even movements centered around open access necessitate private channels, including draft papers, raw data analysis, peer reviews, funding, and other forms of collaboration. OrbisDB and TACo together offer storage and sharing of scientific work that maximizes the researcher's control and sovereignty. Additionally, the combination enables scientific data marketplaces with no trusted intermediary facilitating monetization.
  • AI datasets. Leverage OrbisBD's provenance verification tooling and TACo's granular (row-level) conditionality to enable safe collaboration on sensitive training data while simultaneously verifying data integrity and quality.
  • Shared knowledge bases. Combine Ceramic's verifiable credentials with credential-based secrets management to enforce fine-grained, per-page access to organizational resources. OrbisDB enables migration of non-public external data (e.g. via an API or static CSV) which can then be encrypted via TACo for specific group members (e.g. stakers in a network).

Example application & repo

DeForum illustrates how TACo and OrbisD can be combined in a browser-based forum app, and also provides a template for virtually any form of token-gated content platform. The demo can run on a public node instance provided by OrbisDB studio for testing, or you can set up your own node in order to save data on the Ceramic network in production. The demo also requires a ThirdWeb account to help with image file upload and storage on IPFS.

Skip to Section II below for an in-depth guide to leveraging TACo in the context of the DeForum app.


Section I – Integration steps

This section covers how TACo can be used in concert with OrbisDB across domains and use cases. Follow these instructions to integrate TACo into your OrbisDB app.

1. Install required dependencies

Ensure that you have the required packages in your package.json by running:

npm install @nucypher/taco @nucypher/taco-auth ethers

2. Update network configuration

Next, update the chain configuration:

import { getDefaultConfig } from '@rainbow-me/rainbowkit';
import { polygonAmoy } from 'wagmi/chains';

const config = getDefaultConfig({
  appName: 'test-app',
  projectId,
  chains: [polygonAmoy],
  ssr: true,
});

Polygon Amoy is the L2 for TACo's testnet domains, so encryption/decryption calls from your app go via this network.

3. Initialize TACo

Next, intialize TACo by loading WASM modules:

{% code title="app/context/TACoContext.tsx" %}

import { useEffect, useState } from "react";
import { initialize } from '@nucypher/taco';

const [isInitialized, setIsInitialized] = useState(false);

const initializeTACo = useCallback(() => {
  initialize().then(() => setIsInitialized(true));
}, [setIsInitialized]);

useEffect(() => {
  if (!isInitialized) {
    initializeTACo();
  }
},[initializeTACo, isInitialized]);

{% endcode %}

TACo initialization is required on application startup. For Next.js applications, it is recommended to use a React Context to check if TACo has been initialized before to avoid repeating this process unnecessarily.

4. Define access/decryption conditions

Next, define the conditions for accessing private data:

{% code title="components/sections/newPost-modules.tsx" %}

import { conditions } from "@nucypher/taco";

const condition = new conditions.base.rpc.RpcCondition({
  chain: 80002,
  method: "eth_getBalance",
  parameters: [":userAddressExternalEIP4361"],
  returnValueTest: {
    comparator: ">",
    value: 0,
  },
});

{% endcode %}

Access conditions are specified at data encryption time. The various types of conditions can be found in the Access Control section.

In the code snippet above, we specified that only those data consumer accounts with a positive POL balance on Polygon Amoy (chain ID 80002) will qualify to decrypt the data. You can hardcode conditions, or create a UI for users to choose their own requirements for data access.

5. (Optional) Add TACo encryption and decryption utilities

It's possible to integrate the encrypt and decrypt functions into a Next.js application by creating a new React Custom Hook. The two core functions to implement are encryptWithTACo and decryptWithTACo.

In this example, these functions ensure that posts are encrypted before being stored in OrbisDB and decrypted after being queried.

First, create auxiliary functions – encoding/decoding base64 strings – to be used with the main functions later:

{% code title="app/hooks/useTaco.ts" %}

function encodeB64(uint8Array: any) {
  return Buffer.from(uint8Array).toString("base64") as String;
}

function decodeB64(b64String: any) {
  return new Uint8Array(Buffer.from(b64String, "base64"));
}

{% endcode %}

Next, define the two main encryption and decryption functions:

The encryptWithTACo() function encrypts a message and simultaneously sets the conditions for decrypting it. The returned value is a base64 string that contains the encrypted message and the decryption conditions.

{% code title="app/hooks/useTaco.ts" %}

const domain = "tapir";
const ritualId = 6;

async function encryptWithTACo(
  messageToEncrypt: string,
  condition: conditions.condition.Condition,
  provider: ethers.providers.Provider,
  signer: ethers.Signer,
) {
  const tmk = await encrypt(
    provider,
    domain,
    messageToEncrypt,
    condition,
    ritualId,
    signer,
  );

  return encodeB64(tmk.toBytes());
}

{% endcode %}

The decryptWithTACo() function takes a base64 string with the encrypted data and decrypts the message if the conditions are met.

A creitcal part of the decryption process is the authentication of the data consumer. In this case, the data consumer (or requestor) proves that they own a given Ethereum address – specifically via the reuse of an SIWE authentication. Users have already authenticated themselves on OrbisDB, so this avoids them signing the same message again.

{% code title="app/hooks/useTaco.ts" %}

import { SiweMessage } from "@didtools/cacao";
import { DIDSession } from "did-session";
import {
  SingleSignOnEIP4361AuthProvider,
  USER_ADDRESS_PARAM_EXTERNAL_EIP4361,
} from "@nucypher/taco-auth";
import { ethers } from "ethers";

// Aux function to get the OrbisDB SIWE session from local storage
async function loadSiweFromOrbisSession(): Promise<{
  message: string | undefined;
  signature: string | undefined;
}> {
  const session = localStorage.getItem("orbis:session");
  const didSession = await DIDSession.fromSession(session);
  const siweMessage = SiweMessage.fromCacao(didSession.cacao);
  const message = siweMessage.toMessageEip55();
  const signature = siweMessage.signature;
  return { message, signature };
}

async function decryptWithTACo(
  encryptedMessage: string,
  provider: ethers.providers.Provider,
) {

  const siweInfo = await loadSiweFromOrbisSession();
  const authProvider =
    await SingleSignOnEIP4361AuthProvider.fromExistingSiweInfo(
      siweInfo.message,
      siweInfo.signature,
    );

  const tmk = ThresholdMessageKit.fromBytes(decodeB64(encryptedMessage));
  const conditionContext =
    conditions.context.ConditionContext.fromMessageKit(tmk);
  conditionContext.addAuthProvider(
    USER_ADDRESS_PARAM_EXTERNAL_EIP4361,
    authProvider,
  );

  try {
    const decrypted = await decrypt(provider, domain, tmk, conditionContext);
    return new TextDecoder().decode(decrypted);
  } catch (error) {
    console.error("Decryption failed:", error);
    return "<Decryption failed>";
  }
}

{% endcode %}


Section II – Encrypting & decrypting forum posts via the DeForum web application

Below, forum post bodies are encrypted so only data consumers that satisfy given conditions can view them as plaintext. The following code snippet also executes query to store the posts on OrbisDB in said encrypted format, so that only authenticated and qualifying consumers can access them.

{% code title="components/sections/newPost-modules.tsx" %}

import { conditions } from "@nucypher/taco";
import useTaco from "@/app/hooks/useTaco";

const { encryptWithTACo } = useTaco();

const createPost = async (): Promise<void> => {
    // [...]
    
    // define TACo condition to decrypt the body of the post
    const condition = new conditions.base.rpc.RpcCondition({
      chain: 80002,
      method: "eth_getBalance",
      parameters: [":userAddressExternalEIP4361"],
      returnValueTest: {
        comparator: ">",
        value: 0,
      },
    });

    // encrypt post with TACO
    const encryptedBody = await encryptWithTACo(
      body,
      condition,
      provider,
      signer,
    );

    // [...]

    // Upload the forum post with the encrypted data to OrbisDB
    const createQuery = await orbis
        .insert(POST_ID)
        .value({
            title,
            body: encryptedBody,
            imageid: imageUrl ? imageUrl : "",
            created,
          })
          .context(CONTEXT_ID)
          .run();

    // [...]
}

{% endcode %}

The decryption of the post bodies occurs on the client side after downloading them from OrbisDB:

{% code title="app/(home)/post/[slug]/page.tsx" %}

import useTaco from "@/app/hooks/useTaco";

const [decryptedBody, setDecryptedBody] = useState<string | undefined>(
    undefined,
);

const { decryptWithTACo } = useTaco();

const getPost = async (stream_id: string): Promise<void> => {
    // [...]
    
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    
    // [...]
    // After a SQL query, the post data is stored at postResult[0] variable
    
    decryptWithTACo(postResult[0].body, provider).then((decrypted) => {
        if (decrypted) {
            setDecryptedBody(decrypted.toString());
        }
    });
    
    // [...]
}

{% endcode %}

With a running application, it is possible to check that the encrypted posts are being uploaded to the database with OrbisDB Studio:


Using ComposeDB & TACo in production

  • For Ceramic, connect to Mainnet (domains.MAINNET).
  • For TACo, a funded Mainnet ritualID is required – this connects the encrypt/decrypt API to a cohort of independently operated nodes, and corresponds to a DKG public key generated by independent parties. A dedicated ritualID for Ceramic + TACo projects will be sponsored soon. Watch for updates here or in the Discord #taco channel.

As noted, the parameters specified in this guide are for testing and hacking only. For real-world use cases where uploaded data should remain private & permanent, the production version of TACo is required.