# Introduction

What is HollowDB?

> HollowDB is a decentralized privacy-preserving key-value database on [Arweave](https://www.arweave.org/) network, powered by [Warp Contracts](https://warp.cc/).

A **key-value database** is a non-relational database, where data is simply stored as a collection of pairs: a key and a corresponding value. **Arweave** is a decentralized storage network, essentially providing a decentralized permanent-storage for all kinds of data. You can write smart-contracts in Arweave using **SmartWeave**. In particular, **Warp Contracts** provide much more functionality atop SmartWeave for developers to write more advanced smart-contracts.

HollowDB essentially operates on a smart-contract that provides an interface for a key-value database that lives on Arweave, essentially making it a **decentralized key-value database**. Here is the catch though, if the smart-contract is handling all the operations, how are we going to achieve authenticated operations? That is, we only want owners of data to be able to update them, or remove them.&#x20;

This is where HollowDB utilizes **zero-knowledge proofs**. A zero-knowledge proof is a proof where the prover reveals no extra information other than the fact that some statement is true. Thanks to recent developments within the last decade or so, generating proofs and verifying them have become much more efficient. In particular, proof size is very small and verification happens very quickly. Thanks to this, we can verify proofs at contract level!

In HollowDB, users prove that they **own a key** for some key-value pair, and they do this using a preimage-knowledge proof where the key is the digest from some hash function. In doing so, they do not reveal who they are as the digest is really just a random looking string and the proof reveals nothing about the preimage. This functionality makes HollowDB **privacy-preserving**.

Each smart-contract in HollowDB can be thought of as a separate key-value database. Users may deploy their own contracts using our repository, and then interact with them using the HollowDB SDK. Users will also need to have an Arweave wallet, which is easy to [arweave.app](https://arweave.app/welcome).

In short, HollowDB is made of the following components:

* A SmartWeave smart contract that is the interface to the key-value storage on Arweave.
* A SnarkJS plugin for Warp Contracts, enabling zero-knowledge proof verification at contract level.
* An NPM package that provides a very simple interface to interact with a HollowDB contract, as well as administrative operations such as changing owner & updating state.

## Getting Started

To get started with HollowDB, simply install the package:

```bash
yarn add hollowdb
# or
npm install hollowdb
# or
pnpm add hollowdb
```

{% embed url="<https://www.npmjs.com/package/hollowdb>" %}
HollowDB NPM Package
{% endembed %}


# Quick Start

Get ready to set some keys on Permaweb!

Let's quickly demonstrate with a NodeJS project to see HollowDB in action.&#x20;

## Setup

Open up your favorite terminal and/or IDE and create a new directory for our fresh project:

```bash
mkdir hollowdb-example
cd hollowdb-example
```

Install HollowDB & Warp Contracts packages:

```bash
yarn add hollowdb warp-contracts
```

Finally, create yourself an Arweave wallet at <https://arweave.app/> or use an existing one if you have. Download your wallet (which will be a JSON file) and put it as `wallet.json` in the current directory.

Create a new file called `index.js`. Prepare the code as follows:

```javascript
const { SDK } = require("hollowdb");
const { WarpFactory } = require("warp-contracts");
const fs = require("fs");
 
async function main() {
  // we will write our code here...
}

main(); 
```

## Instantiating HollowDB

To instantiate HollowDB SDK, we need to provide a signer object (that will be our wallet), a Warp instance and a contract transaction id to specify which smart contract we are connecting to. Let's do just that!

Write the following code within your `main` function:

```javascript
// read your wallet
const wallet = JSON.parse(fs.readFileSync("./wallet.json").toString());

// use an existing contract
// to deploy your own, read "Contract Operations" section :)
const contractTxId = "_eVfQYDOLpd-0QX0yt7RV2CXE5D9U0otCz9BNBiJMYY";

// we will use Mainnet
const warp = WarpFactory.forMainnet();
  
// create the SDK!
const sdk = new SDK(wallet, contractTxId, warp);
```

## Generating proofs & computing the key

The contract we are connecting to in this example uses zero-knowledge proofs to update keys. So, we can very much get a value at some key, or put a new key-value pair; but when it comes to updating them we will need to generate proofs!

We can use [HollowDB Prover](/zero-knowledge-proofs/hollowdb-prover) for this:

```javascript
const { Prover, computeKey } = require("hollowdb-prover");
```

## Getting & Setting keys

It's time to put everything to use! Here is what we will do:

* define a secret that is related to our key
* put some value to that key
* update that value using a zero-knowledge proof

First, we will define our `secret`, which can be anything that you'd like. We can say `secret=2023` for this example; although, if you really care about the secret staying that way, you should make a longer secret :)

Let's go back to writing code within our `main` function:

```javascript
const secret = BigInt("0xDEADBEEF"); // or something that only YOU know :)
const key = computeKey(secret);
console.log("Key:", key);
```

We have created our key just like that. Everyone expect you will see that huge number, but only you know that the key was created with the number `2023`.&#x20;

Let's put the string `"hello world"` to this key.

```javascript
// put a value
await sdk.put(key, "hello world");
// get that value
console.log(await sdk.get(key));
```

As simple as that. Now, let's update our value at this key to `"bye bye world"`. To do that, we will need to create a zero-knowledge proof that we know the secret (that is `2023`) which belongs to this key. We have written the Prover utility just for that!

To create the prover object, we need two things:

* [WASM circuit](https://github.com/firstbatchxyz/hollowdb/blob/master/config/circuits/hollow-authz-groth16/hollow-authz.wasm)
* [Prover key](https://github.com/firstbatchxyz/hollowdb/blob/master/config/circuits/hollow-authz-groth16/prover_key.zkey)

Download these files and add them to the project directory. Then, create the prover as follows:

```javascript
const prover = new Prover("./hollow-authz.wasm", "./prover_key.zkey", "groth16");
```

To generate the proof, we provide our secret, along with the current value & next value:

```javascript
const { proof } = await prover.prove(secret, "hello world", "seeya world");
```

We can now update our value using the SDK.

```javascript
// update value
await sdk.update(key, "bye bye world", proof);
// get the new value
console.log(await sdk.get(key));
```

That is it! Our final `index.js` looks like the following:

```javascript
const { SDK } = require("hollowdb");
const { WarpFactory } = require("warp-contracts");
const { Prover, computeKey } = require("hollowdb-prover");
const fs = require("fs");

async function main() {
  // read your wallet
  const wallet = JSON.parse(fs.readFileSync("./wallet.json").toString());

  // use an existing contract
  // to deploy your own, read "Contract Operations" section :)
  const contractTxId = "_eVfQYDOLpd-0QX0yt7RV2CXE5D9U0otCz9BNBiJMYY";

  // we will use Mainnet
  const warp = WarpFactory.forMainnet();

  // create the SDK!
  const sdk = new SDK(wallet, contractTxId, warp);

  const secret = BigInt(2023);
  const key = computeKey(secret);
  console.log("Key:", key);

  console.log("Putting a value...");
  // put a value
  await sdk.put(key, "hello world");
  // get that value
  console.log(await sdk.get(key));

  console.log("Generating a proof for update...");
  // create the prover
  const prover = new Prover("./hollow-authz.wasm", "./prover_key.zkey", "groth16");
  // generate proof
  const { proof } = await prover.prove(secret, "hello world", "seeya world");

  console.log("Updating the value...");
  // update value
  await sdk.update(key, "bye bye world", proof);
  // get the new value
  console.log(await sdk.get(key));
}

main();
```

You can check out the contract we are using at [SonAR](https://sonar.warp.cc/#/app/contract/_eVfQYDOLpd-0QX0yt7RV2CXE5D9U0otCz9BNBiJMYY#), where you will also be able to see your transactions.

## Further reading

To learn more about using HollowDB, continue with the usage section:

{% content-ref url="/pages/PEuT30SkUvEnCKuaSEuJ" %}
[Usage](/hollowdb/usage)
{% endcontent-ref %}

To learn more about zero-knowledge proofs & the zk-circuit used in HollowDB, check out the background section:

{% content-ref url="/pages/U2IQ5JNG7VvOMhoudS6V" %}
[Background](/zero-knowledge-proofs/background)
{% endcontent-ref %}

Or, you can directly jump to application use-cases:

{% content-ref url="/pages/3LzfQk0qUOGW85OuOD8b" %}
[Overview](/use-cases/overview)
{% endcontent-ref %}


# Usage

How to use HollowDB?

HollowDB exposes the following classes:

* an `SDK` class that exposes basic operations, such as `get`, `put`, `update` and `remove`.
* an `Admin` class that additionally exposes higher authorized operations, such as changing the state, transferring ownership, and changing the [mode of operation](/hollowdb/modes-of-operation).

Both these are instantiated the same way:

* a `signer` object that is your account, explained in the next section
* a `contractTxId` to connect to HollowDB SmartWeave contract
* a `warp` object instantiated from `WarpFactory`

```ts
import {SDK, Admin} from 'hollowdb';
import {WarpFactory} from 'warp-contracts';

const signer = /* more info on this later */
const warp = WarpFactory.forMainnet();
const sdk = new SDK(signer, contractTxId, warp);
const admin = new Admin(signer, contractTxId, warp);
```

## SDK

SDK provides the basic CRUD (create, read, update, delete) functionality; we refer to these actions as put, get, update, remove respectively.

```ts
// we will explain these shortly
import {Prover, computeKey} from 'hollowdb-prover';
const prover = new Prover(WASM_PATH, PROVERKEY_PATH, PROTOCOL);

const key = computeKey(yourSecret);

// GET is open to everyone
await sdk.get(key);

// PUT does not require a proof
await sdk.put(key, value);

// UPDATE with a proof
let {proof: updateProof} = await prover.prove(keyPreimage, curValue, newValue);
await sdk.update(key, newValue, updateProof);

// UPDATE without a proof
await sdk.update(key, newValue);

// REMOVE with a proof
let {proof: removalProof} = await prover.prove(keyPreimage, curValue, null);
await sdk.remove(key, removalProof);

// REMOVE without a proof
await sdk.remove(key);

// read state variables
const {cachedValue} = await sdk.readState();
```

To learn more about `hollowdb-prover`, see the following sections:

{% content-ref url="/pages/ViDpsKGJx3yRvHSW6rhL" %}
[HollowDB Prover](/zero-knowledge-proofs/hollowdb-prover)
{% endcontent-ref %}

{% content-ref url="/pages/Y42efh4zyacNwa271Hxx" %}
[Usage with Proofs](/zero-knowledge-proofs/usage-with-proofs)
{% endcontent-ref %}

## Admin

The admin has all capabilities of the SDK, and in addition to those, it can alter the contract state!

```ts
// update the new owner to be the specified address
await admin.updateOwner(address);

// update verification key for a specific circuit
// e.g. update the `auth` circuit's verification key
await admin.updateVerificationKey("auth", verificationKey);

// update proof requirements for a specific circuit
// e.g. disable proof checking for `auth` circuit
await admin.updateProofRequirement("auth", false);

// update whitelisting requirements for a specific list
// e.g. disable whitelist checking for `put` operations
await admin.updateWhitelistRequirement("put", false);

// add addresses to a whitelist
// e.g. add alice & bob to `put` whitelist
await admin.updateWhitelist([aliceAddr, bobAddr], "put", "add");

// remove addresses from a whitelist
// e.g. remove bob from `update` whitelist
await admin.updateWhitelist([bobAddr], "update", "remove");
```

This design is made with making HollowDB extendable in mind; that is, we allow for many more types of lists and multiple circuits to be used in HollowDB! We will have more updates in the future that allows extending HollowDB this way.

Admin class has two static utility functions:

* `Admin.deploy` provides an easy way for you to deploy your own contract to Arweave.
* `Admin.evolve` allows you to evolve an existing contract to a new source code.


# Supported Wallets

Using Arweave & Ethereum wallets with HollowDB.

HollowDB supports both Arweave wallets and Ethereum wallets. In this page, we will show several ways to create the `signer` object.

## Arweave Wallet

An Arweave wallet is defined by a `JWKInterface` object, which is simply a JSON file that you can download from [arweave.app](https://arweave.app/). You can read the JSON object from disk, and pass it as the signer to HollowDB.

```typescript
import {JWKInterface} from 'warp-contracts';

const jwkPath = './some-wallet.json';
const signer = JSON.parse(fs.readFileSync(walletPath).toString()) as JWKInterface;
```

{% hint style="danger" %}
Always `.gitignore` your wallet files!
{% endhint %}

## Injected Arweave Wallet

If you are using HollowDB in browser, you can use [arweave-wallet-connector](https://www.npmjs.com/package/arweave-wallet-connector) to connect to your existing wallet on [arweave.app](https://arweave.app/) and use it as an injected wallet in HollowDB! This is done by providing the string `"use_wallet"` as the signer argument, which internally does the same to connect to Warp Contracts.

Using an injected Arweave wallet will trigger a pop-up on each interaction, similar to MetaMask pop-ups where you sign the transaction.

```typescript
// instantiate Arweave Web Wallet
const arweaveWebWallet = new ArweaveWebWallet(
  {
    name: "your-app-name",
    logo: "your-app-logo",
  },
  {
    state: {
      url: "arweave.app",
    },
  }
);

// connect
await arweaveWebWallet.connect();

// the magic string
const signer = "use_wallet";
```

## Ethereum Wallets

Similar to injected Arweave wallet, you can use an injected EVM-compatible wallet (such as MetaMask) to connect to HollowDB. This is made possible with the [Warp Contract Signature plugin](https://academy.warp.cc/docs/sdk/advanced/plugins/signature). Please refer to their documentation for more details.

Once you are able to obtain the `evmSignature` as described in their documentation, you can use the following `signer` for HollowDB:

```typescript
import { CustomSignature } from "warp-contracts";

// assuming `evmSignature` exists at this point
const signer: CustomSignature = { signer: evmSignature, signatureType: 'ethereum' }
```


# Modes of Operation

Proofs & whitelisting.

HollowDB has two modus operandi: **proofs** and **whitelisting**; both can be enabled together, or separately.

<table><thead><tr><th width="146">Mode</th><th width="129">Put</th><th width="214">Update</th><th width="197">Remove</th><th>Get</th></tr></thead><tbody><tr><td><strong>Proofs</strong></td><td>-</td><td>Zero-Knowledge<br>Proof</td><td>Zero-Knowledge<br>Proof</td><td>-</td></tr><tr><td><strong>Whitelisting</strong></td><td>PUT<br>whitelist</td><td>UPDATE<br>whitelist</td><td>UPDATE<br>whitelist</td><td>-</td></tr></tbody></table>

## Using Proofs

Using zero-knowledge proofs gives full control to the users on updating the value at their key. When proofs are enabled:

* Anyone can **read** and **put**.
* To **update** or **remove** a value at some key, user must provide a zero-knowledge proof (ZKP) of preimage knowledge of that key.

HollowDB makes use of a circuit that we call "HollowDB Authentication" circuit, to which you can find more information at:

{% content-ref url="/pages/K1ubBVfaRO6f3H9LTHdx" %}
[HollowDB Authentication](/zero-knowledge-proofs/hollowdb-authentication)
{% endcontent-ref %}

You can enable or disable proof checking with the following Admin command:

```typescript
const isProofRequired = true; // or false
await admin.updateProofRequirement("auth", isProofRequired);
```

If you are deploying your own contract, you must also provide the verification key that the proof verifier will use. You can find HollowDB's keys [here](https://github.com/firstbatchxyz/hollowdb/tree/master/config/circuits). You can either provide the key in the initial state, or use the Admin to update the verification key at a later time:

```typescript
await admin.updateVerificationKey("auth", newVerificationKey);
```

## Using Whitelisting

Whitelisting is an alternative authorization mechanism that can be used within HollowDB. It can provide a fine-grained control over who can put, update, or remove a value to the database. When whitelisting is enabled:

* Anyone can **read**.
* To **put**, the user must have been put-whitelisted by the contract owner.
* To **update** or **remove**, the user must have been update-whitelisted by the contract owner.

There is a separate whitelist for put and update/remove. You can enable/disable them with the Admin:

```typescript
const isPutWhitelistRequired = true; // or false
const isUpdateWhitelistRequired = true; // or false
await admin.updateWhitelistRequirement('put', isPutWhitelistRequired);
await admin.updateWhitelistRequirement('update', isUpdateWhitelistRequired);
```

You can add & remove users from the whitelist using the Admin:

```typescript
const whitelistType = "put"; // or "update"

// add some users
const addressesToAdd = [address1, address2 /*, ... */];
await admin.updateWhitelist(addressesToAdd, whitelistType, 'add');

// remove some users
const addressesToRemove = [address3, address4 /*, ... */];
await admin.updateWhitelist(addressesToRemove, whitelistType, 'remove');
```


# Usage with Bundlr

Values too large? Meet Bundlr.

Currently [Warp Contracts](https://warp.cc/) only support transactions that are below 2KB in size; however you can put arbitrarily large values with the following trick:

* upload the large value to [Bundlr](https://docs.bundlr.network/) network
* retrieve the transaction id for that value
* put the transaction id in the key-value storage

To retrieve the value, simply get the transaction id from the storage and read the value from Arweave with:

```javascript
const response = await fetch(`https://arweave.net/${transactionId}`);
```

In other words, you will store `key, valueTxId` instead of `key, value`!&#x20;

This will enable you to store arbitrarily large amounts of data, and retrieve them with respect to their transaction ids, also while reducing the overall size of the contract cache. Albeit, it comes with the cost of paying fees for Bundlr.

You can refer to the code below as an example of uploading a value to Bundlr network.

```javascript
const Bundlr = require('@bundlr-network/client');

async function upload(jwk, largeValue) {
  const bundlr = new Bundlr.default('http://node1.bundlr.network', 'arweave', jwk);
  const tags = [{name: 'Content-Type', value: 'application/json'}];
  const transaction = await bundlr.createTransaction(
    JSON.stringify({
      data: largeValue,
    }),
    {
      tags: tags,
    }
  );

  await transaction.sign();
  const txID = transaction.id;

  // you can choose to not await this if you want to upload in the background
  // but if the upload fails, you will not be able to get the data from the txid
  await transaction.upload();

  return txID;
}
```


# Contract Operations

Thinking of deploying your own contract? See here.

We do not immediately provide contract operations from the package; however, if you are to clone the repository, we have prepared some utility scripts that allows you to build, deploy, and evolve a HollowDB contract.

## Building the Contract

```bash
yarn contract:build
```

This command will build the contract from source. The contract is written in TypeScript, but to deploy using Warp you require the contract code in JS. We use ESBuild to compile & bundle the TS contract into a concise single-file JS code. This will generate the bundled contract under `build/hollowdb.js`.

Note that this requires Node version at least 18.

## Deploying a new Contract

```bash
yarn contract:deploy <wallet-name> [<plonk | groth16>]
```

This will deploy your contract which you have just built and is stored under `build/hollowDB/contract.js`. As for the actual wallet to be used, it will look for an Arweave wallet at `./config/wallets/wallet-name.json`.

The optional `groth16` or `plonk` argument, if given, will also provide the verification key in the initial state, so that you don't have to manually set it on deployment.

{% hint style="danger" %}
Always `.gitignore` your wallet files!
{% endhint %}

## Evolving the Contract

```bash
yarn contract:evolve <wallet-name> <contract-tx-id>
```

This command will evolve your contract, it takes a wallet name and the contract transaction id of the old contract. To learn more about evolving, check [Warp docs](https://academy.warp.cc/docs/sdk/basic/evolve).


# Caching Options

Optional caching with LMDB or Redis.

Warp uses LevelDB on Node and IndexedDB on browser for caching by default. It allows overriding the cache to be used via `useStateCache` and `useContractCache`; as well as overriding the underlying key-value storage via `useKVStorageFactory`. These overrides accept any cache interface that supports the [SortKeyCache interface](https://github.com/warp-contracts/warp/blob/main/src/cache/SortKeyCache.ts) of Warp Contracts, see the relevant documentation [here](https://academy.warp.cc/docs/sdk/advanced/kv-storage#implementation-details).

Since HollowDB takes input a warp instance, by applying cache overrides to the warp instance outside and passing that instance to HollowDB, you will be able to use the caching of your choice for HollowDB!

## [LMDB Cache](https://github.com/kriszyp/lmdb-js#readme)

> LMDB is an ultra-fast NodeJS, Bun, and Deno interface to LMDB; probably the fastest and most efficient key-value/database interface that exists for storage and retrieval of structured JS data (objects, arrays, etc.) in a true persisted, scalable, [ACID compliant](https://en.wikipedia.org/wiki/ACID) database

Warp Contracts provide an LMDB cache interface under [warp-contracts-lmdb](https://github.com/warp-contracts/warp-contracts-lmdb) package. It can used as follows:

```ts
import {defaultCacheOptions, WarpFactory} from 'warp-contracts';
import {LmdbCache} from 'warp-contracts-lmdb';

warp = WarpFactory
  .forMainnet()
  .useStateCache(
    new LmdbCache(
      {
        ...defaultCacheOptions,
        dbLocation: './cache/warp/state',
      }
    )
  )
  .useContractCache(
    new LmdbCache({
      ...defaultCacheOptions,
      dbLocation: './cache/warp/contract',
    }),
    new LmdbCache({
      ...defaultCacheOptions,
      dbLocation: './cache/warp/src',
    })
  )
  .useKVStorageFactory(
    (contractTxId: string) =>
      new LmdbCache({
        ...defaultCacheOptions,
        dbLocation: `./cache/warp/kv/lmdb_2/${contractTxId}`,
      })
  );

```

## [Redis Cache](https://github.com/redis/node-redis)

> Redis is an open source (BSD licensed), in-memory **data structure store** used as a database, cache, message broker, and streaming engine. Redis provides [data structures](https://redis.io/docs/data-types/) such as [strings](https://redis.io/docs/data-types/strings/), [hashes](https://redis.io/docs/data-types/hashes/), [lists](https://redis.io/docs/data-types/lists/), [sets](https://redis.io/docs/data-types/sets/), [sorted sets](https://redis.io/docs/data-types/sorted-sets/) with range queries, [bitmaps](https://redis.io/docs/data-types/bitmaps/), [hyperloglogs](https://redis.io/docs/data-types/hyperloglogs/), [geospatial indexes](https://redis.io/docs/data-types/geospatial/), and [streams](https://redis.io/docs/data-types/streams/).

We have prepared a Redis SortKeyCache implementation under [warp-contracts-redis](https://github.com/firstbatchxyz/warp-contracts-redis). It can be used as follows:

```typescript
import {WarpFactory, CacheOptions} from 'warp-contracts';
import {RedisCache, RedisOptions} from 'warp-contracts-redis';

// in case you might use this Redis for multiple contracts,
// it might be best to have a different key for each contract!
const contractTxId = "your-contract-tx-id";

const cacheOptions: CacheOptions = {
  inMemory: true,
  subLevelSeparator: "|",
  dbLocation: "", // we will override this
};
const redisOptions: RedisOptions = {
  url: constants.REDIS_URL,
};
warp = warp
  .forMainnet()
  .useStateCache(
    new RedisCache(
      {
        ...cacheOptions,
        dbLocation: `${contractTxId}.state`,
      },
      redisOptions
    )
  )
  .useContractCache(
    new RedisCache(
      {
        ...cacheOptions,
        dbLocation: `${contractTxId}.contract`,
      },
      redisOptions
    ),
    new RedisCache(
      {
        ...cacheOptions,
        dbLocation: `${contractTxId}.src`,
      },
      redisOptions
    )
  )
  .useKVStorageFactory(
    (contractTxId: string) =>
      new RedisCache(
        {
          ...cacheOptions,
          dbLocation: `${contractTxId}.kv`,
        },
        redisOptions
      )
  );
```

Redis is not exactly like the other cache options (LMDB or the default LevelDB) which are "local". In the case of Redis, we can host a Redis server and connect to it, thereby solving the problem of downloading the entire state on different machines each time. We can also use the same client on multiple cache types, for example a single Redis client in our application can be used for state cache, contract cache and kv-cache at once.

For this purpose, we allow the user to create the client outside, and pass it to the `RedisCache` in the constructor. Here is an example using [ioredis](https://github.com/redis/ioredis) package for the client.

```typescript
// create client
const redisClient = new Redis("connection url", {lazyConnect: true});

// pass the client in constructor
warp = warp
  .useKVStorageFactory(
    (contractTxId: string) =>
      new RedisCache({
          ...cacheOptions,
          dbLocation: `${contractTxId}.kv`,
        },
        { client: redisClient }
      )
  );

// define custom scripts used by RedisCache
RedisCache.defineLuaScripts(client);

// connect to client manually
await redisClient.connect();

// optional: disable persistent memory
// this is what `inMemory: true` normally does
await Redis.setConfigForInMemory(client);

```

The extra steps in the end are normally done internally, but to avoid repeating them in consequent usages of the same client, we expect the user to take the responsibility of doing them. A warning log also notifies the user of this at runtime.


# HollowDB-as-a-Service

Start using HollowDB-as-a-Service.

HollowDB is an open-source project, however due to the nature of working with a blockchain in the background, setting up your own project comes with a slight overhead.

* If you are using a backend, you will have to manage a wallet to do the transactions.
* If you are using Bundlr, you need to fund your Bundlr wallet.
* You might have to do some tricks to improve your performance, to avoid waiting for transaction confirmations and such.
* Or, you simply do not do any of the above, and use HollowDB-as-a-service.

The purpose of our service is to provide a web2-like key-value database, abstracting away all the web3-like problems of wallet managements and blockchain TPS limitations. All that is left for the user is to create an API-key, and start using the key-value database.

See our website here:

{% embed url="<https://hollowdb.xyz/>" %}

## HollowDB Client

We provide an NPM package to get you started with our service.

{% embed url="<https://github.com/firstbatchxyz/hollowdb-client>" %}
HollowDB Client
{% endembed %}

### Installation

HollowDB client is an NPM package. You can install it as:

```sh
yarn add hollowdb-client    # yarn
npm install hollowdb-client # npm
pnpm add hollowdb-client    # pnpm
```

### Usage

Create a new client with:

```ts
client = await HollowClient.new({
  apiKey: 'your-api-key',
  db: 'your-database-name',
});
```

After that, using the client is as simple as it gets:

```ts
// without zero-knowledge proofs
await client.get(KEY);
await client.put(KEY, VALUE);
await client.update(KEY, VALUE);
await client.remove(KEY);
```

If you are connecting to a database that has zero-knowledge proof verifications enabled, you will need to provide proofs along with your requests.

You can use our [HollowDB Prover](https://github.com/firstbatchxyz/hollowdb) utility to generate proofs with minimal development effort. Assuming that a proof is generated for the respective request, the proof shall be provided as an additional argument to these functions.

```ts
// with zero-knowledge proofs
await client.get(KEY);
await client.put(KEY, VALUE);
await client.update(KEY, VALUE, PROOF);
await client.remove(KEY, PROOF);
```

See more about the prover utility here:

{% content-ref url="/pages/ViDpsKGJx3yRvHSW6rhL" %}
[HollowDB Prover](/zero-knowledge-proofs/hollowdb-prover)
{% endcontent-ref %}


# Background

What are zero-knowledge proofs?

HollowDB utilizes [zero-knowledge proofs](https://en.wikipedia.org/wiki/Zero-knowledge_proof) within to provide a zero-knowledge authentication scheme. So, what are they?

## Zero-Knowledge Proofs

Zero-Knowledge Proofs (ZKPs) is a method that allows one to prove that a given statement is true, without revealing any other information other than the statement itself! We usually refer to the proving party as the **Prover**, and the verifying party as the **Verifier**.

Example statements are:

* “I know the solution to some puzzle”, which the prover must prove without showing the solution itself.
* “I know some $$x$$ such that $$f(x) = 0$$”, which the prover must prove without revealing what $$x$$ is.
* “I know the private key that corresponds to some public key”, which the prover must prove without revealing the private key.

Regarding the last example, if you have been into the Web3 for some time you might think “can’t we do that by signing a message with some private key and use `ecrecover` to get the public key?” and you would be right! Indeed, that is a zero-knowledge proof (although a sub-class of it called honest-verifier ZKP).

### Anatomy of a ZKP

Looking at a zero-knowledge protocol as a very high-level diagram, we have the following flow:

<div><img src="https://s3-us-west-2.amazonaws.com/secure.notion-static.com/0c37190e-4d85-4749-b06a-f57a4698ae7b/merm1-l.png" alt=""> <figure><img src="/files/Obh1uf8M44CJtIBh4ykr" alt=""><figcaption><p>Overview of a zero-knowledge proof generation &#x26; verification</p></figcaption></figure></div>

A prover has some **secret inputs** (a witness) that they would like to keep secret, and they might also have some **public inputs**. They feed these into an **algebraic circuit**, which is really like an electric circuit but instead of electricity, it works on non-negative integers (i.e. elements of a finite field) and you can only do addition and multiplication.&#x20;

As a result, they get the **output** of this computation, along with a **proof**. Note that the output is not really necessary too, you could also have a proof without giving any outputs, which is a way of saying “hey I have ran this circuit that you have told me to, and I got no errors”. An example of this is a Sudoku solution prover circuit, where a public puzzle is provided and the user feeds their secret solution to the circuit. The circuit then makes sure the solution is valid, and basically compiles without failures if indeed it is valid.

### Hashing

Before we move on, we also need to describe what “hashing” is, which is used extensively in the zero-knowledge proofs of HollowDB. Hashing is simply a function that takes some arbitrary input, and outputs a fixed-length output. We refer to the input as **preimage**, and the output as **digest** or **hash**.

We expect the following properties from a hash function $$H$$:

* Given some hash $$y$$ such that $$y = H(x)$$ it should be really hard to find what $$x$$ is. This is called **preimage resistance**.
* Given an input $$x\_1$$, it should be really hard to find another input $$x\_2$$ such that $$H(x\_1) = H(x\_2)$$. This is called **second-preimage resistance**.
* Given two inputs $$x\_1$$ and $$x\_2$$, it should be really unlikely that $$H(x\_1) = H(x\_2)$$. This is called **collision resistance**.
* The output of the hash function should appear “random”, i.e. it should be distributed as even as possible. In doing so, even just a slight change in the input should completely change the output. This is commonly referred to as **avalanche effect**.

Note that the input size is arbitrary but the output size is fixed, is that a problem for the properties above? Well it certainly could be; however, in practice the output length of these hash functions are pretty big, such as 256-bits or 512-bits. There are $$2^{256}$$ possible outputs for a 256-bit output, which is a lot more than the number of atoms in the world.

**So how does hashing relate to zero-knowledge proofs?** Imagine that you wrote the entire hash function as an arithmetic circuit, and you provide the preimage as the secret input. The output will be the digest, and you will have a proof that you know what preimage resulted in this digest. In other words, you can prove the statement “I know some $$x$$ such that $$y = H(x)$$ for a publicly known $$y$$" by simply writing the entire hash function $$H$$as a circuit.

There are many different hash functions with varying security levels and output lengths, and the most important thing to note is that not all of them are circuit-friendly. What this means is that, some hash functions (e.g. **SHA256**) are really costly to implement with a circuit. A higher cost means more gates and more constraints, thus requiring a longer proving time and circuit-setup time. Thankfully, there are friendlier hash functions, a well-known one being the **Poseidon** hash.


# HollowDB Authentication

Answering "who dis?" with Zero-Knowledge.

HollowDB is a key-value database, and we want users to have control over their data, i.e. only they should be able to change the value at their respective key. This is achieved by the following:

* User knows some secret $$s$$.
* They hash this secret to obtain a key $$k$$ as in $$k := H(s)$$.

The user will PUT to this key, and only they will be able to UPDATE or REMOVE this key! This is done by requiring a ZKP that whoever wants to update some key knows the preimage $$s$$ of that key. Let’s rewrite the diagram above to show what is happening here:

<div><img src="https://s3-us-west-2.amazonaws.com/secure.notion-static.com/c4ab3c83-5561-410c-9901-4a9478f6fb58/merm2-l.png" alt="merm2-l.png"> <figure><img src="/files/hiDVUp8puZHIsDTRQWpZ" alt=""><figcaption><p>Preimage knowledge proof generation &#x26; verification</p></figcaption></figure></div>

Let’s examine this diagram:

* The client wants to update some `key` that they know the preimage of, with some new `value`.
* Client generates a zero-knowledge proof to prove that they indeed know the preimage. They send the proof, along with the `key` and the `value` to the smart contract, which is HollowDB.
* Within the smart contract, the proof is verified and if it is valid, the key is updated. If proof is invalid, transaction is reverted.

The important point here is that Smart Contract does not see `preimage` at all!

### Security Issues

If you think about this method in practice, it has two security issues:

* **Replay Attack**: If an adversary gets hold of your proof, they can use that proof to claim that they know the preimage to your key even though if they don’t! This is like someone stealing your credit card, and then doing contactless payments that do not ask for password on low amounts. We would like to prevent this.
* **Middle-man Attack**: Another issue is that the proof contains nothing related to the value to be written. If an adversary steals your proof before it gets to the smart contract (perhaps a *middle-man* between you and the smart contract) then they can change the value to be written by simply using your proof.

The solution to these problems are simple: we need to put some constraints within our proof related to the current value and the new value to be written.

* if the proof is only valid for some current value at that key, it will be invalid when that value changes, thus preventing the replay attack.
* if the proof is only valid for the new value that I am going to write to that key, it will be invalid for any other value, thus preventing the middle-man attack.

However, we have said that arithmetic circuits operate on integers, but our values can be anything; so how do we represent our values as integers? The answer is: **hashing**! The client will hash both the current value and the new value separately, obtaining two hashes. So now let’s see the final diagram that shows how how both attacks are mitigated:

<div><img src="https://s3-us-west-2.amazonaws.com/secure.notion-static.com/6c2abd79-96a2-4c0e-b2a2-510b863a46c4/merm3.png" alt="merm3.png"> <figure><img src="/files/wSMFhdlyw9DJkPt32Hpq" alt=""><figcaption><p>HollowDB Authentication proof generation &#x26; verification</p></figcaption></figure></div>

**A technical note must be taken here**: you must ensure that the resulting hash is within the limit of the finite-field used in the circuit. For example, [Circom](https://docs.circom.io/) supports the [Baby JubJub elliptic curve](https://eips.ethereum.org/EIPS/eip-2494) for its arithmetic circuits to be used in Ethereum, and the order of the finite field is:

```jsx
21888242871839275222246405745257275088548364400416034343698204186575808495617
```

This means that any value in your circuit must be less than this number. If you have a larger number, they will wrap back around as in modular arithmetic. You can use different curves and thus different orders, but this is important to keep in mind.&#x20;

The number above is around 254 bits, and if we were to input a 256-bit number, things may work without the way we intend them to. This is especially important if we are trying to use a hash as an input. One could use hash functions with smaller output size, such as [ripemd160](https://en.bitcoin.it/wiki/RIPEMD-160), which has a 160-bit output and is definitely within the limits of our arithmetic circuit which is much larger. There are other methods to "fit" a hash within a field element too.


# HollowDB Prover

Utility package to generate proofs for HollowDB.

To ease proof generation, we provide a prover utility for the circuit used by HollowDB.

{% embed url="<https://github.com/firstbatchxyz/hollowdb-prover>" %}
HollowDB Prover
{% endembed %}

## Usage

To generate proofs, you will need the zero-knowledge circuit WASM file, and a prover key. Both can be found within the repo, see [here](https://github.com/firstbatchxyz/hollowdb-prover/tree/master/circuits). Notice that there are separate files for each protocol, Groth16 and PLONK respectively.

To create the prover:

```typescript
const prover = new Prover(
    "./path-to-circuit-wasm",
    "./path-to-prover-key",
    "groth16" // or "plonk"
);
```

Let us explain the constructor arguments in order:

* `wasmPath` is the relative path to the circuit WASM file. In a web application, this file can be stored under `public`.
* `proverKeyPath` is the relative path to the WASM circuit. In a web application, this file can be stored under `public`.
* `protocol` is the proof system to be used, that is either `groth16` or `plonk`. HollowDB supports both proof systems, and the verifier can determine which one to use by looking at the verification key.

To generate a proof, simply call `prove` function of the newly created `prover`:

```typescript
const {proof, publicSignals} = prover.prove(PREIMAGE, CURRENT_VALUE, NEXT_VALUE);
```

The proof object here shall be provided to HollowDB contract, where it will be checked to verify. Note that public signals are also exported, although we do not use them; the contract obtains them in it's own ways.

The value inputs are "hashed-to-group" and then fed into the circuit. See the [#hash-to-group](#hash-to-group "mention")section below for more information.

For the curious, the public signals is a triple with the following elements in order:

* Current value hash
* Next value hash
* Key, equal to Poseidon hash of the preimage

### Prove with Hashes

The `prove` function takes as input two objects, and it converts them to be circuit-friendly inputs within the function. If you would like to re-use these hashes, or you simply have access to them, you can generate a proof from them too:

```typescript
const {proof} = prover.proveHashed(PREIMAGE, CUR_VAL_HASH, NEXT_VAL_HASH);
```

### Proving In NextJS

Note that to use SnarkJS in a NextJS environment you may need to configure some settings w\.r.t server-side rendering. We suggest adding the following Webpack option to your NextJS config:

```js
webpack: (config, { isServer }) => {
  if (!isServer) {
    config.resolve.alias = {
      ...config.resolve.alias,
      fs: false, // added for SnarkJS
      readline: false, // added for SnarkJS
    };
  }
  // added to run WASM for SnarkJS
  config.experiments = { asyncWebAssembly: true };
  return config;
},
```

You might also have to make some configurations in other frameworks if you have server-side rendering enabled.

## Computing the Key without Proofs

When HollowDB is used with proofs in particular, the `key` is computed by taking the [Poseidon hash](https://www.poseidon-hash.info/) of some secret preimage. The key can be extracted from the `publicSignals` which is in the object that is returned from the `prove` function.

However, if one wants to compute the `key` without creating a proof (e.g. the user just wants to get a value at their own key) they can do so with `computeKey`.

```typescript
import {computeKey} from 'hollowdb-prover'

const key = computeKey(PREIMAGE);
```

## Hash-to-Group

To "embed" the current value and next value within our proofs, we need to map them to a number. This number must be circuit-friendly (to be more technical, it must be within the scalar field of the curve used in our circuit, which is `alt_bn128`).

We provide a `hashToGroup` function for this purpose:

```typescript
import {hashToGroup} from 'hollowdb-prover'

const valueHashed = hashToGroup({foo: "bar", num: 123});
```

Note that the output of this function is a `bigint`, not a string! To store it as a string, you may use `toString` method of the BigInt, with an optional radix. We suggest storing these as hexadecimal strings with `0x` prefix, which allows them to be converted to BigInt easily.

```typescript
const valueHashedStr = '0x' + valueHashed.toString(16);
```


# Usage with Proofs

"Talk is cheap, show me the code."

The proof verification is done within the smart-contract side, so as a developer we will mostly be looking at the proof generation that happens on the client side. [HollowDB Prover](/zero-knowledge-proofs/hollowdb-prover) makes proving stuff dead-simple, just call a `prove` function and that is all.

```tsx
import {SDK} from 'hollowdb';
import {Prover, computeKey} from 'hollowdb-prover';
import {WarpFactory, JWKInterface} from 'warp-contracts';
import fs from 'fs';

// read wallet
const walletPath = __dirname + '/wallet-name.json';
const wallet = JSON.parse(
  fs.readFileSync(walletPath).toString()
) as JWKInterface;

// instantiate SDK
const contractTxId = '<your-contract-txid>';
const sdk = new SDK(wallet, contractTxId, WarpFactory.forMainnet());

// instantiate the prover
const wasmCircuitPath = __dirname + '/circuit.wasm';
const proverKeyPath = __dirname + '/prover_key.zkey';
const prover = new Prover(wasmCircuitPath, proverKeyPath);

// compute your key from secret
const secret = BigInt("0xDEADBEEF");
const key = computeKey(secret);

// generate a proof for UPDATE
const currentValue = await sdk.get(key);
const nextValue = 'this is a new value!';
const {proof} = await prover.prove(secret, currentValue, nextValue);

// update
await sdk.update(key, nextValue, proof);
```

Let’s digest this code step by step:

1. First, we read our Arweave wallet from file, to be used for our transactions. You could also provide the wallet as a JSON object within the code too (but you should be careful not to expose your wallet & accidentally commit them to your repo).
2. Then, we create the HollowDB SDK object. For this, we provide our wallet, we specify the cache type to be LMDB, and we provide the contract transaction id along with a Warp instance. Basically, we are “connecting” to our contract on the Mainnet.
3. We now create our Prover object, which is a wrapper around a few SnarkJS functions to generate a proof. We have to provide a path to our WASM circuit and a prover key to create this object. You can obtain them from our repository, and host them on your side. For example, if you are writing a web application, you could host them under the `public` folder.
4. We need to compute the key, which is the hash of our preimage. You could generate a dummy proof and read the key from it’s output, but that is not really efficient. Instead, HollowDB exports a `computeKey` function for this purpose.
5. Then, we generate our proof by simply calling `prove` with the required arguments, that are the inputs we have shown in the above diagram.
6. Finally, we call `sdk.update` to update the value at our key, using our zero-knowledge proof!


# Proofs from Signatures

Generating Proofs via NextJS + Injected Wallet.

When HollowDB is used together with a dApp, we have the perfect opportunity to use zero-knowledge proofs by making use of the user wallet. In particular, we can derive a secret at client-side using the user wallet, and then generate our key from that derived secret. Here is an example flow for such a use-case:

* **Secret**: A *signature* on a pre-determined a constant string, signed by users for your dApp
* **Preimage**: the secret hashed to a group element (i.e. a circuit-friendly value)
* **Key**: A key derived from the preimage using Poseidon hash (i.e. a zk-friendly hash function)

To try this flow yourself, you can quickly get started with a dApp boilerplate and follow this guide. We think [Rainbowkit](https://www.rainbowkit.com/) provides a really simple boilerplate dApp where you can connect your wallet, so start by setting up the scaffold code described at [Rainbowkit quickstart](https://www.rainbowkit.com/docs/installation#quick-start).

```sh
npm init @rainbow-me/rainbowkit@latest    # npm
pnpm create @rainbow-me/rainbowkit@latest # pnpm
yarn create @rainbow-me/rainbowkit        # yarn
```

We don't really need anything other than the wallet connection button here, so go ahead to `index.tsx` and change the component to the following:

```tsx
return (
  <div className={styles.container}>
    <main className={styles.main}>
      <ConnectButton />
    </main>
  </div>
);
```

We will make use of our prover utility class, so let us install it too:

```sh
yarn add hollowdb-prover # or npm, or pnpm
```

Then, make sure you configure Webpack options, describe at the section: [HollowDB Prover](/zero-knowledge-proofs/hollowdb-prover#proving-in-nextjs).

## Getting the Signature

The first thing we have to do is obtain the user signatures for some string. To do so, add the following to within the component:

```tsx
const [signature, setSignature] = useState<string>();
const { signMessage } = useSignMessage({
  message: "your-message-for-this-dapp",
  onSuccess: (data) => setSignature(data),
  onError: (err) => alert(err.message),
});
```

`useSignMessage` is a hook exported by Wagmi, which Rainbowkit wraps around. When we call `signMessage`, it will cause for instance MetaMask to pop-up and ask for user to sign a message.

Let's add a tiny button to get that signature, just below `<ConnectButton />` we will add:

```tsx
<div>
  <button
    className={styles.button}
    onClick={() => {
      signMessage();
    }}
  >
    Sign
  </button>
</div>
```

We have added a tiny style to our button as well, if you would like to add within your `Home.module.css`:

```css
.button {
  background-color: white;
  color: black;
  border: 2px solid #e7e7e7;
  font-size: larger;
  padding: 5px;
  border-radius: 5px;
  margin: 5px;
}

.button:hover {
  background-color: #e7e7e7;
}
```

## Hashing the Signature

We can hash this signature to a circuit-friendly value; or in a bit more technical term: we can do a hash-to-group operation where the result of hash must conform to some rules. In the simplest case, the resulting digest must be smaller than some value (order of the group).&#x20;

Our HollowDB Prover utility class provides a hash-to-group function:

```ts
const { hashToGroup } = require("hollowdb-prover");
```

Then we add a `useMemo` to calculate this hash whenever signature changes.

```ts
const preimage = useMemo(() => signature && hashToGroup(signature), [signature]);
```

We will use this `preimage` when we are creating zero-knowledge proofs for HollowDB.

You are free to use any other method for the hashing-to-group part, all you have to do is make sure that the resulting digest corresponds to a number that is less than:

```
21888242871839275222246405745257275088548364400416034343698204186575808495617
```

For the curious, that is the order of the scalar field of BN254 curve, which is the finite field that our circuit operates on.

## Generating the Proof

Our utility package also exports a prover class: `Prover`. It is really straightforward to use, you just have to provide paths to the WASM circuit and prover key files. These files can be stored under `public` directory. You can download them from [HollowDB repository](https://github.com/firstbatchxyz/hollowdb/tree/master/config/circuits/hollow-authz-groth16).

Import our Prover above:

```typescript
const { Prover } = require("hollowdb-prover");
```

Let's add our button that will generate the proofs, right inside the same `div` with the previous `button`:

```tsx
<button
  className={styles.button}
  onClick={() => {
    if (!preimage) return alert("Please sign first!");

    // change these based on your application
    const currentValue = { foo: 234 };
    const nextValue = { foo: 456 };

    new Prover("/circuits/hollow-authz.wasm", "/circuits/prover_key.zkey")
      .prove(preimage, currentValue, nextValue)
      .then(({ proof }: { proof: unknown }) => {
        // e.g. make an api call with the proof
        console.log(proof);
        alert("Proof created!");
      });
  }}
>
  Prove
</button>
```

You can see how the `Prover` object is created by providing the paths to WASM circuit and the prover key. Then, we simply call the `prove` function with the preimage, current value and the next value.

The value inputs are "hashed-to-group" and embed within the proof itself, this logic is handled within the function.

## Computing the Key

Can we compute the key without generating a proof? Yes, we have the `computeKey` function for that!

```ts
const { computeKey } = require("hollowdb-prover");
```

We can add another `useMemo` to calculate this final hash whenever the previous hash changes.

```ts
const key = useMemo(() => preimage && computeKey(preimage), [preimage]);
```

The client can use this `key` to read values from HollowDB, without needing to generate proofs.

## Putting it All Together

Here is how `index.tsx` looks like in the end:

```tsx
import { ConnectButton } from "@rainbow-me/rainbowkit";
import type { NextPage } from "next";
import styles from "../styles/Home.module.css";
import { useSignMessage } from "wagmi";
import { useMemo, useState } from "react";
const { hashToGroup, Prover, computeKey } = require("hollowdb-prover");

const Home: NextPage = () => {
  const [signature, setSignature] = useState<string>();
  const { signMessage } = useSignMessage({
    message: "your-message-for-this-dapp",
    onSuccess: (data) => setSignature(data),
    onError: (err) => alert(err.message),
  });
  const preimage = useMemo(() => signature && hashToGroup(signature), [signature]);
  const key = useMemo(() => preimage && computeKey(preimage), [preimage]);

  return (
    <div className={styles.container}>
      <main className={styles.main}>
        <ConnectButton />

        <div>
          <button
            className={styles.button}
            onClick={() => {
              signMessage();
            }}
          >
            Sign
          </button>

          <button
            className={styles.button}
            onClick={() => {
              if (!preimage) return alert("Please sign first!");

              // change these based on your dApp!
              const currentValue = { foo: 234 };
              const nextValue = { foo: 456 };

              new Prover("/circuits/hollow-authz.wasm", "/circuits/prover_key.zkey")
                .prove(preimage, currentValue, nextValue)
                .then(({ proof }: { proof: unknown }) => {
                  // e.g. make an api call with the proof
                  console.log(proof);
                  alert("Proof created!");
                });
            }}
          >
            Prove
          </button>
        </div>
      </main>
    </div>
  );
};

export default Home;
```

You should see something like this in the middle of the screen when you connect your wallet:

<figure><img src="/files/DWMrZFskSXxn9IVBhMin" alt=""><figcaption><p>screenshot of wallet connection and buttons</p></figcaption></figure>


# Overview

In which ways can HollowDB be used?

HollowDB can be used in many ways, with or without a backend and with different types of wallets. Although the interface of HollowDB is very simple, there are several things to consider when you are building an application with HollowDB:

* Side-effects of **lazy evaluation** that is used in [SmartWeave](https://github.com/ArweaveTeam/SmartWeave).
* Value size & usage of [Bundlr](https://docs.bundlr.network/).
* [Modes of operation](/hollowdb/modes-of-operation), i.e. **proofs** & **whitelisting**.

## Lazy Evaluation

> SmartWeave uses lazy-evaluation to move the burden of contract execution from network nodes to smart contract users. Currently, SmartWeave supports JavaScript, using the client's unmodified execution engine.

What this means is that, when a new client wants to make a transaction on a contract, they must download all preceding transactions and evaluate them locally. By doing that, they effectively reach the present state of the contract, upon which they execute their transaction.

The immediate side-effect of this is that if a contract has many interactions, it will take a longer time to lazy-evaluate it. Since HollowDB is a key-value database operated by a SmartWeave smart contract, the more keys & interaction a contract has, the longer it will take for clients to lazy-evaluate it.

Depending on your key-count and the data-size, you might prefer different architectures for your application. Let’s take a look at these scenarios:

<table data-header-hidden><thead><tr><th width="148.33333333333331"></th><th></th><th></th></tr></thead><tbody><tr><td></td><td><strong>Small value</strong><br><strong>per key (&#x3C; 2KB)</strong></td><td><strong>Large value</strong><br><strong>per key (>= 2KB)</strong></td></tr><tr><td><strong>Few keys</strong><br><strong>per user</strong></td><td>Client-side only is probably enough, unless this is a very active app with many users.</td><td>You might use client-side only, but it would require the users to have a funded Bundlr account.</td></tr><tr><td><strong>Many keys</strong><br><strong>per user</strong></td><td>Client-side only may be enough, but consider a backend if there are many users. Or, use several contracts to group users.</td><td>You should use a backend with your own wallet to make the requests &#x26; upload to Bundlr when needed.</td></tr></tbody></table>

## Examples

There are various examples to demonstrate the basic operations of the HollowDB. Check out `example.js` and `exampleBundlr.js` and configure the variables. Then, put your an Arweave wallet (JWK) inside `examples/config/wallet.js`.&#x20;

To run the examples:

```sh
# go to examples folder
cd examples
# install dependencies
yarn
# run the example
node example
```

We also have a simple NextJS application to demonstrate wallet-connections and usage of HollowDB within the browser.

{% embed url="<https://github.com/firstbatchxyz/hollowdb-nextjs-simple>" %}
Simple NextJS App - GitHub
{% endembed %}

For bigger examples with frontend, check out:

{% content-ref url="/pages/OuLSBXBlNWWggCmNaBLW" %}
[Calendar](/use-cases/calendar)
{% endcontent-ref %}

{% content-ref url="/pages/PvMCODtyzqWISjBegkKN" %}
[Persona](/use-cases/persona)
{% endcontent-ref %}

{% content-ref url="/pages/hZMMI2XLN0VgTay8w2NK" %}
[Anonymous Authentication](/use-cases/anonymous-authentication)
{% endcontent-ref %}

If you haven't seen already, check the Quick Start section to see the most basic example usage of HollowDB:

{% content-ref url="/pages/P2oxnbGC17c02DoLCTzm" %}
[Quick Start](/quick-start)
{% endcontent-ref %}


# Calendar

A calendar application using HollowDB.

Perma-Calendar is a demo app for HollowDB. It is client-side only, and every user deploys their contract & their keys. It does not use proofs and only uses whitelisting. \
\
The [FullCalendar](https://fullcalendar.io/) framework is used for calendar rendering. \
\
You can check out the live demo [here](https://hollowdb-nextjs-calendar.vercel.app/).

Head to the [GitHub](https://github.com/firstbatchxyz/hollowdb-nextjs-calendar) repo to see the implementation or jump to the [#code-snippets](#code-snippets "mention") section to have a quick look.&#x20;

## The Calendar App

As HollowDB supports both ZK-based inputs and whitelists, it can be used like any other database. This simple example demonstrates how it can be used as a calendar application only using client side code.

<figure><img src="/files/QyzsPI6ql3T2uLeJOxLQ" alt=""><figcaption><p>Initial View</p></figcaption></figure>

### Connect Wallet

Thanks to Warp contract's [Injected Ethereum Signer,](https://academy.warp.cc/docs/sdk/advanced/plugins/deployment#ethereum) the calendar supports both [Arweave wallet](https://arweave.app/) and [Metamask](https://metamask.io/)

<figure><img src="/files/bWr4ZVqowECrJ4GUU2eu" alt="" width="210"><figcaption><p>Wallet Options</p></figcaption></figure>

<figure><img src="/files/qCbZGZ6gUEMMcYnccDGs" alt="" width="375"><figcaption><p>Arweave Wallet</p></figcaption></figure>

<figure><img src="/files/Jb9s016Zm3I61Uhq3jlz" alt="" width="336"><figcaption><p>Metamask Wallet</p></figcaption></figure>

### Deploy a Contract

As mentioned, for a calendar app every user has to deploy their own HollowDB contract. Users' wallet is added to the whitelist automatically, meaning only they can do write operations. \
\
After you connect your wallet, you will see either a **Deploy** or **Redeploy** button depending on if a previously deployed contract exists or not.

<figure><img src="/files/2cINvNDPGZfwwuJ8h7Jj" alt="" width="356"><figcaption><p>A contract exists</p></figcaption></figure>

<figure><img src="/files/hVQLRx7xDu52MayFWX0k" alt="" width="371"><figcaption><p>Contract not found</p></figcaption></figure>

### Add Calendar Events

You can click on any date to create an event. After submitting the event, the user receives a transaction to write the event to the HollowDB contract. After a successful event creation, the event will be shown on the calendar.

#### Enter Event Name

![](/files/DqUx4dRfPtesSingAA5h)

#### Sign the Transaction

![](/files/NzdltjrzGaiMnQKkNoc3)

#### Ta-da!

![](/files/9pArbN1knOJjZyumTYLo)

#### Delete Event

To delete an event, simply click on the event and sign the transaction.

## Code Snippets

The folder structure of the application is as follows:

```
. 
├─ src 
│       ├── components (header and layout)
│       ├── constants (source tx id of the contract to be deployed)
│       ├── context (wallet connection and contract deploy logic)
│       ├── pages (home page and react/nextjs defaults)
│ 
└── ...
```

#### Contract Deployment

{% code overflow="wrap" %}

```typescript
// srcTx type is created, it requires srcTxID, deployer address and signer
// signer for metamask is retrieved from InjectedEthereumSigner and window.ethereum
// signer for arweave is retrieved from InjectedArweaveSigner

const srcTx: FromSrcTxContractData = generateContractInfo(
      HOLLOWDB_SRCTXID,
      address,
      userSigner
    );

// HollowDB deployed
const deployTx = await warp.deployFromSourceTx(srcTx);

// HollowDB instance is created using SDK and deploy tx information
const hollowdb = new SDK("use_wallet", deployTx.contractTxId, warp);

// The instance is stored and shared accross the web-app
setHollowdb(hollowdb);
```

{% endcode %}

#### Put Operation&#x20;

```typescript
async function put(key: string, value: {}) {
    /* ... some checks ... */
    await hollowdb?.put(key, JSON.stringify(value));
  }

const handleDateSelect = (selectInfo: DateSelectArg) => {
    /* ... some checks ... */

    let title = prompt("Please enter a new title for your event");
    let calendarApi = selectInfo.view.calendar;

    // The event is added to both calendarApi and HollowDB
    if (title) {
      const eventId = createEventId();
      const start = selectInfo.startStr;
      const end = selectInfo.endStr;
      const allDay = selectInfo.allDay;
      calendarApi.addEvent({
        id: eventId,
        title,
        start: start,
        end: end,
        allDay: allDay,
      });
      put(eventId, { title: title, start: start, end: end, allDay: allDay });
    }
  };
```

#### Remove Operation

```typescript
async function remove(key: string) {
    if (!isConnected) {
      return;
    }
    // Empty event object
    // We check for empty events using empty string
    const emptyValue = JSON.stringify({
      title: "",
      start: "",
      end: "",
      allDay: "",
    });
    await hollowdb?.update(key, emptyValue);
  }

const handleEventClick = (clickInfo: EventClickArg) => {
    /* ... some checks ... */
    
    // The event is removed from both calendarApi and HollowDB
    remove(clickInfo.event.id);
    clickInfo.event.remove();
    
  };
```

#### Handling Previously Deployed Contracts

```typescript
const checkPrevEvents = async () => {
      if (hollowdb?.contractTxId == "" || !isConnected) return;

      // clean up the calendar (helps with redeployment)
      removeAll();

      let oldEventKeys: Array<string> = [];
      let oldEvents: Array<any> = [];

      // get all existing keys on the hollowdb contract
      await hollowdb?.getAllKeys().then((keys: []) => {
        if (keys) {
          oldEventKeys = Array.from(keys.values());
        }
      });

      // get all existing values on the hollowdb contract then convert it to an array
      const eventValues = await hollowdb?.getStorageValues(oldEventKeys);
      const mappedEvents = eventValues?.cachedValue;
      if (mappedEvents) {
        oldEvents = Array.from(mappedEvents.values());
      }

      // filter empty events
      oldEvents = oldEvents.filter((elements) => {
        return elements !== null;
      });

      // Obtain a calendar api instance to add previous events to the calendar
      const calendarApi = CalendarRef.current.getApi();
      for (let i = 0; i < oldEvents.length; i++) {
        oldEvents[i] = await JSON.parse(oldEvents[i]);
        const eventId = createEventId();
        if (oldEvents[i].title != "")
          calendarApi.addEvent({
            id: eventId,
            title: oldEvents[i].title,
            start: new Date(oldEvents[i].start),
            end: new Date(oldEvents[i].end),
            allDay: new Date(oldEvents[i].allDay),
          });
      }
    };
```

**Congrats!** You have successfully learned how to deploy and use an HollowDB contract.


# Anonymous Authentication

An online authentication application for storing profiles

Anonymous authentication is another demo app developed using HollowDB, users can create profiles while keeping their identity anonymous and the only way to update the profile is to provide a zero-knowledge proof.\
\
The full structure consists of a frontend developed with Next.js and an Express server.  \
\
Refer to the GitHub repos for implementation details:

* [Authentication Frontend](https://github.com/merdoyovski/hollowdb-next-auth)
* [Express Server](https://github.com/firstbatchxyz/hollowdb-express)
* [HollowDB](https://github.com/firstbatchxyz/HollowDB)<br>

## The Authentication App

This demo is intended to demonstrate the power of ZK-proofs in HollowDB by storing a profile on HollowDB contracts without revealing any information about yourself.

<figure><img src="/files/GVUJmvykwWyBbwqqGKyC" alt=""><figcaption><p>Initial View</p></figcaption></figure>

### Connect Wallet

Wallet infrastructure uses [Rainbowkit](https://www.rainbowkit.com/), you can simply use the **Connect Wallet** button and choose Metamask. \
\
Any chain supported by Metamask is good to go as HollowDB is chain agnostic!

<figure><img src="/files/lP8wSBGrc4JMlovJkWq8" alt=""><figcaption><p>After Wallet Connection</p></figcaption></figure>

### Create Profile

HollowDB supports any type of data to be stored, for the sake of this demo, only a username and an image URL are stored.\ <br>

<figure><img src="/files/uLcTWV7Mb6gsBMSv1oPR" alt="" width="276"><figcaption><p>Profile Form</p></figcaption></figure>

After the **Upload** button is pressed, a pop-up appears requesting your signature. The hashed version of your signature will be used as the key to your profile information.

<figure><img src="/files/Lv7r7aAS4RIKgZ4rXLFG" alt="" width="175"><figcaption><p>Signature Request</p></figcaption></figure>

Here you go! Your profile is safely and anonymously stored on HollowDB. Any application using the same HollowDB contract can access the information.&#x20;

<figure><img src="/files/W2YYKioVI4r7DHo6fj6c" alt=""><figcaption></figcaption></figure>

But what about the **anonymity**?

### Anonymous Authentication

If your information is stored publicly, how can you be anonymous? Through the power of ZK!\
\
If you refresh your page and connect your wallet again, you still won't be able to see your profile because your information can't be tracked through your address.&#x20;

#### Retrieve Profile

A profile must be retrieved by generating the same signature again. As there is no instant way the application can know you own that key.

<figure><img src="/files/oIAnHIIV76qQhOOfoZsP" alt="" width="183"><figcaption></figcaption></figure>

After generating the signature, your profile is retrieved!

<figure><img src="/files/v5b3IG2S9ax8EIloRoL2" alt="" width="183"><figcaption><p>Profile Retrieved</p></figcaption></figure>

#### Anonymity

Let's have a look at an example transaction input to see if there is any sensitive information.

<pre><code><strong>{
</strong>  "data":
  {
    "key":"4735904570539279682275507070929548418220332715061423656258515080175565373207",
    "value":
    {
      "pfp":"https://expertphotography.b-cdn.net/wp-content/uploads/2020/06/stock-photography-trends11.jpg",
      "username":"Merdo"
    }
  }
}
</code></pre>

It only contains the hashed version of your signature, which can't be tracked back as hash functions are one-way. \
\
Even if you look at the [transaction](https://sonar.warp.cc/#/app/interaction/sPlzh1fweYOL3v9aibjXxHC5KQbPytPj-Fns-oMP3fk), you won't be able to see any information related to your address because the transaction was created by the wallet living in the express server.

### Update Profile

When a HollowDB contract state is set to enable ZK-proofs, a proof must be provided to update a key-value pair. The proof requires the pre-image (your secret) of your hashed key, meaning you're safe as long as you keep your private key safe.

Just like creating a profile, enter your new information, press the Upload button and approve the signature request.\ <br>

<figure><img src="/files/Ohfv1k1KiywycnAJyC4c" alt=""><figcaption></figcaption></figure>

Congrats! You have successfully created an anonymous profile using HollowDB.

### Code Snippets

Here are simplified code snippets.

#### Create Profile

```typescript
 const handleUpload = async () => {
    ...
    // Request signature using Wagmi
    const secret = await signMessageAsync();
    const key = await computeKey(valueToBigInt(secret));

    const profile = await getProfile(key);
    const curValue = profile.data.data.value;

    if (!curValue) {
      createProfile(key, form.values)
        .then((res) => {
          if (res.statusText == "OK") {
            setProfileLocal(form.values);
          }
        })
     };
     else {...} // Key exists, try to update
     ...
  };
```

#### Update Profile

```typescript
  const handleUpload = async () => {
    ...
    // Request signature using Wagmi
    const secret = await signMessageAsync();
    const key = await computeKey(valueToBigInt(secret));

    const profile = await getProfile(key);
    const curValue = profile.data.data.value;

    if (!curValue) {...} // Key doesn't exists, try to create a profile
    else {
      // Generate the proof
      const { proof } = await generateProof(
        valueToBigInt(secret),
        curValue,
        form.values
      );

      updateProfile(key, form.values, proof)
        .then((res) => {
          if (res.statusText == "OK") {
            setProfileLocal(form.values);
          }
        })
     }
     ...
  };
```

#### Retrieve Profile

```typescript
 const handleRetrieve = async () => {
    ...
    // Request signature using Wagmi
    const secret = await signMessageAsync();
    const key = await computeKey(valueToBigInt(secret));

    const profile = await getProfile(key);
    const curValue = profile.data.data.value;
    if (curValue) {
      setProfileLocal(curValue);
    } 
    ...
  };
```


# Persona

Where it all started.

Persona app is the flagship product of FirstBatch that uses both [HollowDB](https://github.com/firstbatchxyz/HollowDB) and [DANNY](https://github.com/firstbatchxyz/danny). In this application, the user signs in using a few options:

* Email & Password
* Google Account
* MetaMask

In the first two options, a wallet is created for the user in the background. With the wallet, the user signs a constant reference string, and the signature is SHA256 hashed to obtain a `secret`. The Poseidon hash of this `secret` will be the `key` in HollowDB for this user!

After signing in, an AI model scans the social media profiles and activities of the user and generates a vector embedding from it. The resulting vector is stored in HollowDB at the user's `key` as described previously.

*Here is the catch*: only the user will be able to update & remove their key at HollowDB. They do this by generating a zero-knowledge proof of preimage knowledge of their `key` (which they can because they know the `secret`). HollowDB's smart contract verifies this using SnarkJS, within SmartWeave!

Try out Persona app below:

{% embed url="<https://persona.firstbatch.xyz/>" %}


