Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.opengdp.network/llms.txt

Use this file to discover all available pages before exploring further.

Privacy-Preserving Transactions

OpenGDP’s privacy-preserving transactions are a novel encryption mechanism that allow for tokenized dollars to be sent and received in the form of stablecoins with complete privacy. Transactions remain hidden from anyone monitoring or indexing the blockchain, so no third party can trace or see any activity. To begin, shield your public onchain balance into a private pool. Once shielded, your balance is encrypted and only visible to you. No one else can see it, even onchain. When you send funds from your shielded balance, the transfer happens entirely within the private pool using zero-knowledge proofs, so the recipient receives the funds without exposing any transaction details. If you want to move funds back to a public balance, you can unshield them by submitting a zero-knowledge proof that verifies your ownership of the amount in the pool. The onchain contracts then withdraw that portion and send it to the public address of your choosing.

Flow

Shield

The user sends funds from their public balance to the pool contract. Once the transaction is confirmed onchain, the funds are encrypted and added to the user’s shielded balance within the pool.

Transfer and Unshield

Both transfer and unshield follow the same flow:
  1. The user initiates the action from the dashboard, specifying the recipient and amount.
  2. The application calls the backend to fetch favorable notes (UTXOs) and their corresponding Merkle roots.
  3. A zero-knowledge proof is generated locally using the fetched data.
  4. The user signs the transaction with their connected wallet, and it is submitted to the chain along with the proof.
  5. The onchain contract verifies the proof and executes the operation. For a transfer, the funds move to the recipient within the shielded pool. For an unshield, the funds are withdrawn to a specified public address.
For ease of use and auditing, the demo uses the user’s viewing key. The indexer uses this key to decrypt and track shielded activity, reducing the processing load on the frontend client.

Primitives

The pool holds notes tuples of (owner, token, amount) committed to an onchain Merkle tree as Poseidon hashes. Spending a note publishes a nullifier (poseidon(nullifyingKey, leafIndex)), which the contract tracks to prevent double-spending without revealing which commitment was consumed. The spending key signs transactions and never leaves the client. The viewing key decrypts notes and can be delegated; it carries no spend authority. From these two keys, the SDK derives a nullifying key, a master public key (tags note ownership), a shielded address (bech32-encoded, what senders pay to), and a shareable viewing key (handed to the indexer to authorize decryption). Get started start here

Onchain Events

EventPurpose
ShieldDeposit: new commitments plus ciphertext keyed to the depositor
TransactPrivate transfer: new commitments plus ciphertext keyed to both parties via blinded viewing keys
UnshieldWithdraw: notes owned by the withdrawer are nullified and the value is sent to a public address
NullifiedReports consumed nullifiers

Note Encryption

Shield ciphertext is AES-GCM under a key derived from ECDH (Elliptic Curve Diffie-Hellman) between the depositor’s viewing key and a per-deposit shield key. The notes store data like the value, token, and other fields in the form of encrypted values. The data is encrypted using a shared key that is made using blinded viewing keys. The sender encrypts via ECDH with the blinded receiver key, and the receiver decrypts via ECDH with the blinded sender key. Both sides converge on the same shared secret. An outside observer sees only opaque curve points. Merkle Tree diagram

Zero-Knowledge Proof

Proofs are Groth16 over a Poseidon Merkle circuit built using Circom, generated client-side with snarkjs. Public inputs are the Merkle root, a hash of bound parameters, the published nullifiers, and the new output commitments. Private inputs include note values, salts, Merkle paths, keys, and output details. The circuit enforces that every input’s Merkle path hashes to the public root, every nullifier is correctly derived, every output commitment matches its declared note, value is conserved (sum(in) == sum(out) + unshieldAmount), and an EdDSA signature verifies against the spending public key. The contract sees only the proof and public inputs.