Introduction

What is it?

Pandora (Provable and Auditable Notary for Decentralized Operational Record Authentication) is a notarization rollup designed to offer a cost-effective, swift, and secure service for notarizing your data on the blockchain. This documentation will delve into the technical aspects, explaining the various interactions and communications necessary to use Pandora by providing detailed technical information on the purpose of each of its components.

High Level Overview

Pandora Overview

The picture above provides an overview of Pandora's architecture, detailing its various components and possible interactions. There is a clear separation between layer 1 (L1) and layer 2 (L2), along with an off-chain part. Interactions can occur at different levels, whether from users themselves or between Pandora's components. Some interactions are automatic (e.g. kernel/inbox), while others require user actions (such as depositing tokens).

In the following sections of this document, we will focus on three main aspects: the kernel, the proxy/server, and the overall infrastructure, explaining the necessary deployments and possible user interactions.

Kernel

The kernel is essential as it contains all of Pandora's pure execution logic.

At each layer 1 level, it reads from what is called the inbox (the inbox is stored in Tezos' context; users add messages to the rollup inbox via a dedicated manager operation on layer 1). For each read, it sorts the messages intended for it. To do this, the message must have a predefined format known to the kernel, which it can parse. It first tries to detect if the message is external or internal. An external message comes from a real user, while an internal message comes from a smart contract (this is in our case, note that there are other internal messages to the protocol). Internal messages are recognized by the protocol, ensuring an additional layer of security.

The next step is to verify if the destination address of the message matches that of the Pandora rollup. If it does, the kernel processes the message; otherwise, it ignores it. At this stage, there are several possibilities, the message is:

  • a notarization request.
  • a deposit of funds.
  • a withdrawal of funds.
  • an upgrade message.

For a notarization request, since it is an external message, a cryptographic layer is required, and the message must be signed. If the message's signature is valid, the message is kept; otherwise, it is ignored. The kernel will check if the account requesting notarization has enough funds on Pandora. If not, the message is ignored. If sufficient funds are available, the chosen amount is deducted for notarization, and the data is anchored on L2 with a timestamp.

In case it is a deposit of funds, it comes from an internal message (from the bridge contract, which will be detailed in the following sections). The message is trusted, and the account is credited with the amount specified in the message.

If it is a withdrawal of funds, it also comes from an internal message, hence trusted. The kernel checks if the L2 account's balance permits that withdrawal. If not, the message is ignored. If everything goes well, a message is posted in the rollup's outbox. The user must wait for the dispute period (see this article for a quick overview on refutation games) defined by the Tezos protocol to elapse. Once the message is cemented, it can be triggered to retrieve the funds on the bridge contract (more details provided in subsequent sections).

Finally, an admin message can request an upgrade that will allow the rollup to update its logic, add features, or fix bugs. This is one of the biggest strengths of smart optimistic rollups on Tezos, enabling continuous updates to execution logic, unlike immutable smart contracts that cannot update their code in case of issues.

Proxy - Server

Interacting with Pandora is done through an API server. This server communicates with the rollup node and acts as a proxy. That is, it exposes a different API from the one of the rollup node (which is generic and agnostic w.r.t. the rollup) which is instead tailored to the Pandora rollup.

For instance, when the balance (on pandora) is requested, the API server makes an RPC call to the rollup node in order to read the (possibly historical) data contained in the durable storage of the rollup state. The raw data is then translated to a balance information that is returned to the user.

The proxy server can also be configured to sign notarization requests automatically, i.e. on behalf of the users. Of course this means that the signer associated with the server is the one that pays for all notarization requests but this is how Pandora is meant to be used in a semi-decentralized way. This allows to hide all blockchain elements from the users and offer instead a purely traditional RPC based API for notarization with Pandora.

The proxy server is stateless (at the moment) and relies on the rollup node capabilities for a lot of its operations (see below). In particular, notarization requests are sent to the rollup node as L2 messages which will take care of batching them in L1 transactions and injecting those on a Tezos Layer1 node with the best latency.

Pandora Proxy Server

Infrastructure

Pandora's infrastructure is not solely composed of a rollup and a kernel. For proper functionality and to enhance the user experience, other components must be deployed. This includes the proxy-server, detailed in the previous technical section. There are also smart contracts that ensure security in certain critical interactions between a user and Pandora.

In particular, the bridge contract is used for token transfers or withdrawals from the L2. During a transfer, the contract will verify that the amount requested by the deposit entry point is available in the user's L1 account. If so, a message is posted by the smart contract in the inbox and the amount of money specified is withdrawn from the user's L1 account to the smart contract. When the withdrawal entry point is called, a withdrawal request is made by posting a message in the inbox. The contract sends messages with the transaction sender as withdrawer and the verification occurs in the kernel: it directly checks if the balance on the L2 account permits the withdrawal.

For an upgrade request, a unique admin has the right to call the upgrade contract's entry point. If the admin's address is not recognized, the call will fail. Otherwise, an upgrade message is posted in the inbox addressed to Pandora. This message contains what is called a preimage root hash of the upcoming upgrade. The administrator must make the series of preimages available to the rollup so that when the message is read by the kernel, it can reconstruct the code for the next kernel from these images and update itself.

Get Started

During this tutorial, all the needed resources can be found or generated from Pandora's repository.

Setting up Pandora from scratch

To have a better understanding of Pandora, it can be a good exercise to start everything from scratch.

Before starting

To interpret values (e.g. hexadecimal timestamps) and/or create configuration/setup files, some conversions are needed (e.g. from addresses to bytes in hexadecimal format).

A script is provided in Pandora's repository (scripts/converter) that can be used as follows:

./scripts/converter --hex-to-date dd0f686600000000
# mar. 11 juin 2024 08:50:37 UTC
./scripts/converter --hex-to-dec 7c380f0000000000
# 997500
./scripts/converter --address-to-hex tz1UdMeeXHKq6cQxkse9RJ1Yi8CXuD2gXQRu
# 747a3155644d656558484b71366351786b736539524a3159693843587544326758515275
./scripts/converter --hex-to-address 747a3155644d656558484b71366351786b736539524a3159693843587544326758515275
# tz1UdMeeXHKq6cQxkse9RJ1Yi8CXuD2gXQRu

Running an L1 node

Read https://teztnets.com/weeklynet-about and setup a weeklynet node.

./octez-node config init --network https://teztnets.com/weeklynet-20XX-XX-XX --data-dir weeklynet-node
./octez-node run --data-dir ./weeklynet-node --synchronisation-threshold 0 --rpc-addr localhost:8732

Send some money from the faucet to an account that will be used as an admin of Pandora (for the upgrades).

NB: During the rest of the document we'll consider that $ADMIN=tz1UdMeeXHKq6cQxkse9RJ1Yi8CXuD2gXQRu is the admin.

Deploy the upgrader contract

Use the admin's address for the authentified upgrader:

./octez-client originate contract upgrader-weeklynet transferring 0 from $ADMIN running upgrader.tz --init 'Pair "tz1UdMeeXHKq6cQxkse9RJ1Yi8CXuD2gXQRu" None' --burn-cap X.YZ

NB: During the rest of the document we'll consider that $UPGRADER=KT1WTj9XjrSwAEFwAYYVf7HwaNKpZyxUnSEs is the upgrader contract's address.

Deploy the bridge contract

Use the admin's address for the authentified upgrader:

./octez-client originate contract bridge-weeklynet transferring 0 from $ADMIN running bridge.tz --init 'Pair "tz1UdMeeXHKq6cQxkse9RJ1Yi8CXuD2gXQRu" None' --burn-cap X.YZ

NB: During the rest of the document we'll consider that $BRIDGE=KT1JJX88BgW9SMEmVvM7ERaTwZj2oW75EcaC is the bridge contract's address.

Set up Pandora

This section will focus on setting up the main infrastructure.

Create the configuration using the upgrader and bridge contract addresses as well as the notary address

instructions:
  - set:
      # ./scripts/converter --address-to-hex KT1WTj9XjrSwAEFwAYYVf7HwaNKpZyxUnSEs
      value: 4b543158345667455459315847426a7941356d706646723832377958454873314a586737
      to: /pandora/config/upgrade_contract
  - set:
      # ./scripts/converter --address-to-hex KT1JJX88BgW9SMEmVvM7ERaTwZj2oW75EcaC
      value: 4b54314a4a58383842675739534d456d56764d3745526154775a6a326f57373545636143
      to: /pandora/config/bridge_contract
  - set:
      # ./scripts/converter --address-to-hex tz1UdMeeXHKq6cQxkse9RJ1Yi8CXuD2gXQRu
      value: 747a3155644d656558484b71366351786b736539524a3159693843587544326758515275
      to: /pandora/config/notary_address

NB:

  • The configuration is a .yaml file.
  • Note that the contracts' addresses must be in bytes.
  • The notary address can be omitted, in that case fees will be burnt.

Originate the rollup

Use the smart rollup installer to get the kernel installer of pandora and all the necessary preimages.

./smart-rollup-installer get-reveal-installer --upgrade-to pandora_debug.wasm --output pandora_debug_installer.hex --preimages-dir pandora_images --setup-file config/weeklynet.yaml

Originate the rollup using the kernel installer and the create config from the previous step.

./octez-client originate smart rollup weeklynet-rollup from $ADMIN of kind wasm_2_0_0 of type '(or (or (pair (contract unit) nat) (pair (contract unit) nat)) bytes)' with kernel $(cat pandora_debug_installer.hex) --burn-cap 999

NB: During the rest of the document we'll consider that $PANDORA=sr1Bu89iV5Pki7TBSjMeZMEigNfebwQBUTSZ is the rollup's address.

Run the rollup node

Copy the preimages in the /wasm_2_0_0 folder before running the rollup node:

cp pandora_images/* weeklynet-smart-rollup-node/wasm_2_0_0 

Run the rollup node:

./octez-smart-rollup-node run operator for $PANDORA with operators $ADMIN --data-dir weeklynet-smart-rollup-node --rpc-port 8932 --log-kernel-debug

Set Pandora's address in the contracts

Use Pandora's address:

  • For the rollup to upgrade
./octez-client transfer 0 from $ADMIN to $UPGRADER --entrypoint "set_rollup_address" --arg '"sr1Bu89iV5Pki7TBSjMeZMEigNfebwQBUTSZ"' --burn-cap 999
  • For the authentified withdrawer
./octez-client transfer 0 from $ADMIN to $BRIDGE --entrypoint "set_rollup_address" --arg '"sr1Bu89iV5Pki7TBSjMeZMEigNfebwQBUTSZ"' --burn-cap 999

Notarisation

Deposit some money via the bridge contract:

./octez-client transfer 1 from $ADMIN to $BRIDGE --entrypoint "deposit"

Check your balance on Pandora:

./scripts/converter --hex-to-dec $(curl -s http://localhost:8932/global/block/head/durable/wasm_2_0_0/value\?key\=/pandora/state/accounts/2Nfp2p7gGPBfDaNA9jKUscWQ9dox/balance | jq)

1000000 µꜩ = 1 ꜩ

NB: 2Nfp2p7gGPBfDaNA9jKUscWQ9dox is obtained by re-encoding in base58 the bytes of tz1UdMeeXHKq6cQxkse9RJ1Yi8CXuD2gXQRu (=$ADMIN ) after removing the prefix and the checksum. You can use the CLI to obtain that value.

./pandora-cli --compress-pkh $ADMIN

Use the CLI to create an external message to send:

./pandora-cli --hash 00000000000000000000000000000000 --pandora-address $PANDORA --keys-alias <user-alias>
004042c5b539b3111294f555735d3a0b1617746c22006564706b7551636d4b76425243754355567050656d3553396b52584734744c4b39505747677647383268767339536659325452785a5a30303030303030303030303030303030303030303030303030303030303030307369676256344755434d443746316b354572776f69694156444437364a65336950475345516e53453647476342466d6935543451685859585032637268517650657674446f484c4276694d63686961665a586f3945537a3464684b6e52657669

Send an external message to notarise a simple hash:

./octez-client send smart rollup message "hex:[ \"004042c5b539b3111294f555735d3a0b1617746c22006564706b7551636d4b76425243754355567050656d3553396b52584734744c4b39505747677647383268767339536659325452785a5a30303030303030303030303030303030303030303030303030303030303030307369676256344755434d443746316b354572776f69694156444437364a65336950475345516e53453647476342466d6935543451685859585032637268517650657674446f484c4276694d63686961665a586f3945537a3464684b6e52657669\"]" from $ADMIN --fee-cap X.YZ

Get your hash in base58 by doing:

./pandora-cli --to-b58 00000000000000000000000000000000
4F7BsTMVPKFshM1MwLf6y23cid6fL3xMpazVoF9krzUw

Check your hash's timestamp:

curl -s http://localhost:8932/global/block/head/durable/wasm_2_0_0/value\?key\=/pandora/state/hashes/4F7BsTMVPKFshM1MwLf6y23cid6fL3xMpazVoF9krzUw/timestamp | jq

Check your balance on Pandora once again:

./scripts/converter --hex-to-dec $(curl -s http://localhost:8932/global/block/head/durable/wasm_2_0_0/value\?key\=/pandora/state/accounts/2Nfp2p7gGPBfDaNA9jKUscWQ9dox/balance | jq)

997500 µꜩ = 0.9975 ꜩ

Withdraw half of your balance from Pandora by calling the bridge contract (a message will be pending in the rollup's outbox):

NB: amount should be specified in mutez (µꜩ).

./octez-client transfer 0 from $ADMIN to $BRIDGE --entrypoint "withdrawal_request" --arg 500000

Get your balance on L1 before/after the execution of the withdrawal message from the outbox:

./octez-client get balance for $ADMIN 

0.344846 ꜩ

Check that your message is contained within the level where the withdrawal was executed:

 curl -s http://localhost:8932/global/block/head/outbox\?outbox_level\=<level> | jq

Wait for a commitment that contain your withdrawal to be cemented (the duration may vary depending on the network's constant):

# Specify the L1 <level>
curl -s http://localhost:8932/global/block/cemented/outbox/<level>/messages | jq

# For level = 67 for instance you will get something like:
[
  {
    "outbox_level": 67,
    "message_index": "0",
    "message": {
      "transactions": [
        {
          "parameters": {
            "prim": "Pair",
            "args": [
              {
                "int": "500000"
              },
              {
                "bytes": "000062988a79f6f30c7e943033e147fb529b58a88343"
              }
            ]
          },
          "destination": "KT1XpYVREyu1mhP8pkRqPpWbqMdfm62aLPFm",
          "entrypoint": "withdrawal"
        }
      ],
      "kind": "untyped"
    }
  }
]

Recover the message_index from the previous RPC and generate the outbox message's proof:

# Generate/precompute proof of inclusion in the outbox
# Specify the L1 <level> and <index> of the message to execute
curl -s http://localhost:8932/global/block/head/helpers/proofs/outbox/<level>/messages\?index\=<index> | jq -r "\"0x\" + .proof" > outbox.proof

Recover the LCC by doing this RPC from your L1 node:

curl -s http://localhost:8732/chains/main/blocks/head/context/smart_rollups/smart_rollup/sr1Bu89iV5Pki7TBSjMeZMEigNfebwQBUTSZ/last_cemented_commitment_hash_with_level

Execute the outbox message:

# Execute
# Specify last cemented commitment <LCC>
./octez-client execute outbox message of smart rollup $PANDORA from $ADMIN for commitment hash <LCC> and output proof $(cat outbox.proof) --burn-cap 1

Check that your balance is retrieved on L1:

./octez-client get balance for $ADMIN 

0.844846 ꜩ

Optional: Upgrade Pandora

Whenever the kernel has new version that is released (new featues, migration needed, bug fixes), it is necessary to upgrade it.

Use the smart rollup installer to get all the preimages of the new kernel (don't forget to recompile it beforehands):

./smart-rollup-installer get-reveal-installer --upgrade-to pandora_debug.wasm --output will_not_be_used.hex --preimages-dir pandora_images_upgraded --display-root-hash

ROOT_HASH: 00e4b4344ca419f1d3bccc50b4673b08a26b331971523973c4037f469aad4aecbf

Copy the preimages in the /wasm_2_0_0 folder before sending the upgrade message:

cp pandora_images_upgraded/* weeklynet-smart-rollup-node/wasm_2_0_0 

Get Pandora's version to check it before/after the upgrade:

NB: This is only relevant if the kernel was compiled on different commits (:= version).

curl -s http://localhost:8932/global/block/head/durable/wasm_2_0_0/value\?key\=/pandora/state/kernel_version | jq

"63663133653363333661663262313433303830656461623838383961303736343337313537383339"

Get the preimage hash root from the previous command and call the upgrader contract with the appropriate payload to trigger the upgrade:

./octez-client transfer 0 from $ADMIN to $UPGRADER --entrypoint "main" --arg 0x00e4b4344ca419f1d3bccc50b4673b08a26b331971523973c4037f469aad4aecbf

Check that the upgrade happened on the rollup side by checking its version once again:

curl -s http://localhost:8932/global/block/head/durable/wasm_2_0_0/value\?key\=/pandora/state/kernel_version | jq

"35383832653066653836646634303064643835366336336433323931376334643632313835366665"

Notarization with the Proxy Server API

This section will focus on the Proxy Server API, which simplifies a lot of interactions with Pandora.

Configuration

Pandora server must be given a configuration (either in a file or directly on the CLI). This configuration contains the necessary information for it to communicate with the rollup node and sign messages.

The configuration is written in JSON and has the following structure:

{
  "rollup_node_addr" : "http://localhost:8732",
  "rpc_port" : 9090,
  "secret_key" : "edsk3xAiaeVcv2buA3CjwNaSxpgMCRNGc65ZkUoxyH7AWb5EyuH23A" 
}
  • rollup_node_addr contains the URI at which the rollup node RPC is reachable (default http://localhost:8732).
  • rpc_port is the port on which pandora RPC server listens to requests (default 9090).
  • secret_key is either an unencrypted ed25519 secret key in base58 (one can be generated with, e.g. octez-client gen key admin && octez-client show address admin -S) or the name of an environment variable which contains a secret key (defaults to environment variable NOTARIZE_SK).

Running the server

Once one has access to a rollup node for the pandora kernel rollup, Pandora server can be run with

./pandora-server --config config.json

where config.json is a file containing the configuration as described above.

OpenAPI specification

The OpenAPI specification can be generated and view (once the server is running) with, e.g.:

curl localhost:9090/openapi > openapi.json
npx @redocly/cli preview-docs openapi.json

Example of notarization with the server

Start the server

./pandora-server --config '{"rollup_node_addr" : "http://localhost:20932", "rpc_port" : 8088, "secret_key" : "edsk3xAiaeVcv2buA3CjwNaSxpgMCRNGc65ZkUoxyH7AWb5EyuH23A" }'

Notarize a document

Let's say we have a file openapi.json that we want to notarize. We can first get its sha256 hash with

> sha256sum openapi.json
607858f4d8600c4cf62fd5feba532e99f1d8b2d7e1e56c8c178e25ab12d22576  openapi.json

And then call the notarization endpoint with

> curl -X POST -H 'Content-Type: application/json' localhost:8088/notarize/hash -d '"607858f4d8600c4cf62fd5feba532e99f1d8b2d7e1e56c8c178e25ab12d22576"'

After a few seconds, when the notarization is effective, we can check its status with:

> curl -X POST -H 'Content-Type: application/json' localhost:8088/notarize/status -d '"607858f4d8600c4cf62fd5feba532e99f1d8b2d7e1e56c8c178e25ab12d22576"'

{
  "status": "notarized",
  "timetsamp": "2024-06-25T23:28:14-00:00"
}