ᚠ ᛫ ᛟ ᛫ ᚱ ᛫ ᛒ ᛫ ᛟ ᛫ ᚲ

classified.forboc.ai

Internal documentation — Layer 3

Soul Storage, Encryption, and Trading-Card Architecture

Sóul_Desíḡn // Stórage_&_Encryptíon

ᚠ ᛫ ᛟ ᛫ ᚱ ᛫ ᛒ ᛫ ᛟ ᛫ ᚲ

This document is the authoritative design for how Souls (portable agent identities) are stored, encrypted, compressed, and traded. There are no gaps: all decisions are explicit. Implementations MUST follow this specification.


1. Purpose and Scope

1.1 What This Document Defines

1.2 What a Soul Is

A Soul is a serializable snapshot of an agent:

Souls evolve through play. Each export is the state at that moment. The same character (same NFT) may be re-exported many times as the player progresses; each export overwrites the canonical reference so that only one current version exists.

1.3 Design Principles (Non-Negotiable)

Principle Meaning
One canonical version There is exactly one “current” Soul per character. Old exports are not referenced; they are orphaned. No “Bilbo at 37” and “Bilbo at 42” as two valid characters.
Buy the card, own the card No ongoing fees. No pinning subscriptions. Pay once per mint/upload; storage is permanent.
Encrypted Soul content MUST be encrypted before leaving the client. Only the owner (or key holder) can decrypt. No plaintext Souls in storage.
API has no storage The ForbocAI API is stateless. It does NOT store Soul blobs. Storage is external (Arweave). This is intentional.
Tradeable When an NFT (Soul) is traded, the new owner MUST be able to decrypt and import the Soul without the seller’s cooperation.

2. API’s Role: No Storage

2.1 Explicit Statement

The ForbocAI API MUST NOT persist Soul content. It has no database for Souls. It does not cache decrypted Souls. It does not host blob storage.

2.2 Why This Is a Feature

2.3 What the API May Do


3. Storage Backend: Arweave

3.1 Why Arweave (Not IPFS)

Criterion IPFS Arweave
Persistence Content persists only while pinned. If no one pins, content can disappear. Permanent by design. Pay once; data remains in the weave.
Cost model Pinning services charge ongoing fees (monthly or similar). Like renting. One-time fee per upload. No subscription.
Owner experience “I must keep paying to keep my character.” “I paid once; I own it forever.”

We require pay once, own forever. Therefore the canonical storage backend for Soul blobs is Arweave.

3.2 What Is Stored on Arweave

3.3 Arweave Transaction ID (TX ID)


4. One Canonical Version (Single Identity)

4.1 Identity = NFT

The identity of a character (e.g. “Bilbo”) is the Solana NFT (Metaplex Core or equivalent). There is one NFT per character. The NFT is the single source of truth for ownership.

4.2 Current Version = Metadata Pointer

The current version of the Soul is the blob pointed to by the NFT’s metadata (e.g. metadata.uri or a dedicated field). That URI MUST point to the latest Arweave transaction containing the encrypted Soul.

4.3 Re-Export (Evolution)

When the owner exports an updated Soul:

  1. The client (SDK) produces the new Soul JSON.
  2. The client compresses it (per §6).
  3. The client encrypts it (per §5, §6).
  4. The client uploads the ciphertext to Arweave; receives TX ID.
  5. The client (or a trusted service) updates the NFT metadata on Solana to set the Soul URI to the new Arweave TX ID.
  6. The previous Arweave TX is not updated or deleted. It remains on Arweave but is orphaned: no official reference points to it. From the protocol’s perspective, “Bilbo” is only the blob at the current TX ID.

4.4 No Forking


5. Encryption

5.1 Requirement

Soul content MUST be encrypted before upload to Arweave. Anyone may fetch the blob by TX ID; only the holder of the decryption key MAY read the content.

5.2 Algorithm

5.3 Key Derivation (Owner-Based)

The encryption key MUST be derivable only by the current owner of the NFT (or by someone who has been given the key explicitly). Two approved approaches:

Option A — Wallet signature (recommended)

  1. The owner signs a deterministic message (e.g. "ForbocAI Soul Key" + NFT mint address) with their wallet private key.
  2. The signature (or a hash of it) is passed through a KDF (e.g. HKDF-SHA256 or scrypt) to produce a 256-bit key.
  3. That key is used for AES-256-GCM. No key is stored on-chain; only the owner can re-derive it by signing again.

Option B — Random key, encrypt key for owner (normative for tradeable Souls)

  1. Generate a random 256-bit key K for AES-256-GCM.
  2. Encrypt the Soul with K.
  3. Encrypt K with the current owner’s public key (e.g. Solana public key) using a public-key scheme (e.g. X25519 + ECIES, or NaCl box). Store the encrypted key in the NFT metadata.
  4. On trade: the seller (or relayer) decrypts K with the seller’s key, re-encrypts K for the buyer’s public key, and updates metadata. The Arweave blob stays the same.

Normative: For tradeable Souls, implementations MUST use Option B so that key transfer on trade does not require re-uploading the blob. Option A (wallet-signature key derivation) does not allow the new owner to decrypt without the seller re-exporting and re-encrypting the Soul for the buyer; Option A MAY be used only for non-tradeable or single-owner scenarios.

5.4 Key Transfer on Trade

Chosen design: A random content key K is used per Soul blob. K is encrypted for the current owner’s public key and stored in NFT metadata. The Arweave blob (ciphertext) never contains K. When ownership transfers, K is re-encrypted for the new owner and the metadata is updated; the blob on Arweave is unchanged. This avoids re-uploading the Soul on every trade.

When the NFT is sold or transferred to a new owner:

5.5 Ciphertext Format (Normative)

The blob stored on Arweave MUST be a byte sequence with the following structure (all multi-byte integers little-endian unless specified):

  1. Version (1 byte): 0x01 for this format.
  2. IV/Nonce (12 bytes): Random nonce for AES-GCM.
  3. Ciphertext (variable): Output of AES-256-GCM (encrypted Soul payload; after compression if compression is used—see §6).
  4. Auth tag (16 bytes): GCM authentication tag appended to ciphertext (so total ciphertext length = plaintext length + 16).

Decryption: use the same key, IV, and ciphertext+tag; AES-GCM decrypt; then decompress if compressed (see §6).

5.6 Key Storage in NFT Metadata (Normative)

The NFT metadata (on Solana) MUST include:

When the NFT is transferred, soulEncryptedKey MUST be updated to be encrypted for the new owner. soulUri MAY remain unchanged (same Arweave TX).


6. Compression

6.1 When to Compress

Soul JSON is typically small (see §6.2). Compression is optional but RECOMMENDED to reduce Arweave cost and fetch size. The same format (version byte, etc.) SHALL support an optional compression step: compress (Soul JSON → compressed bytes) then encrypt (compressed bytes → ciphertext).

6.2 Soul Size (Reference)

Typical total: 5 KB – 100 KB uncompressed JSON. Compression (e.g. gzip or Brotli) can often achieve 2–5× reduction. Implementations MAY use compression; if used, the format MUST record that the plaintext (before encryption) is compressed (see §6.4).

6.3 Algorithm

6.4 Format With Compression (Normative)

If compression is used, the plaintext input to encryption is:

  1. Compression flag (1 byte): 0x01 = compressed, 0x00 = not compressed.
  2. Payload: If compressed, gzip(Brotli)-compressed UTF-8 JSON; otherwise raw UTF-8 JSON.

That plaintext is then encrypted per §5.5 (IV, AES-GCM, tag). So the Arweave blob is: version, IV, ciphertext (which contains encrypted compression-flag + payload), tag.

Decryption: decrypt → read first byte (compression flag) → if 0x01, decompress remainder, then parse JSON; else parse remainder as JSON.


7. End-to-End Flows

7.1 First Export (Mint)

  1. Owner has Soul JSON in client (SDK).
  2. (Optional) Compress per §6. Compose plaintext: compression flag + payload.
  3. Generate random K (256-bit). Encrypt plaintext with K (AES-256-GCM); produce IV, ciphertext, tag. Format per §5.5.
  4. Encrypt K for owner’s public key; obtain soulEncryptedKey.
  5. Upload ciphertext blob to Arweave; obtain TX ID.
  6. Mint NFT on Solana (Metaplex). Set metadata: soulUri = https://arweave.net/<TX_ID>, soulEncryptedKey = <base64>.
  7. (Optional) Client clears or marks local Soul as “persisted.”

7.2 Re-Export (Evolution, Same Owner)

  1. Owner has updated Soul JSON in client.
  2. (Optional) Compress per §6.
  3. Generate new random K. Encrypt with K per §5.5.
  4. Encrypt K for owner’s public key (unchanged).
  5. Upload new blob to Arweave; obtain new TX ID.
  6. Update NFT metadata on Solana: set soulUri to new TX ID; set soulEncryptedKey (can remain same if same owner, or re-encrypt for same owner for consistency).
  7. Previous Arweave TX is orphaned (no reference). Canonical Soul is now the new TX.

7.3 Trade (Transfer of Ownership)

  1. NFT ownership transfers on Solana (marketplace or direct transfer).
  2. Key transfer (MUST happen for new owner to read Soul): Some actor (seller client, marketplace backend, or relayer) with access to seller’s private key:
    • Fetches NFT metadata; reads soulEncryptedKey.
    • Decrypts with seller’s private key to get K.
    • Re-encrypts K for buyer’s public key.
    • Updates NFT metadata: soulEncryptedKey = new value (encrypted for buyer). soulUri unchanged.
  3. Buyer’s client fetches blob from soulUri, fetches metadata, decrypts soulEncryptedKey with buyer’s private key to get K, decrypts blob with K, optionally decompresses, parses Soul JSON. Import complete.
  4. Seller client: SHOULD clear local Soul data for this NFT so seller does not retain a copy.

7.4 Import (Load Soul Into Game)

  1. User owns NFT (or has permission to use it). Client has NFT metadata (soulUri, soulEncryptedKey).
  2. Client derives K: decrypt soulEncryptedKey with owner’s private key.
  3. Client fetches blob from soulUri (Arweave).
  4. Client decrypts blob (IV, ciphertext, tag) with K. Reads compression flag; if set, decompresses. Parses JSON → Soul.
  5. SDK/engine hydrates agent from Soul (persona, state, memories).

8. Export Frequency and UX

8.1 Why Not Every Frame

Each export costs: (1) Arweave upload (one-time fee), (2) Solana tx to update metadata (if re-export). To keep cost and chain load predictable, exports SHOULD NOT occur on every small state change.

8.2 When to Export (Normative)

Exports MUST occur at least in these cases:

Exports MAY also occur at:

The exact policy (e.g. “export on every explicit Save only”) is implementation-defined; the protocol only requires that the NFT metadata always points to a valid, decryptable blob after any export.


9. Cost Model (Reference)

Event Who pays Type Note
Mint NFT Minter One-time Solana mint + metadata tx
First Soul upload Owner One-time Arweave upload fee (size-dependent)
Re-export Owner One-time per export Arweave upload + Solana metadata update tx
Trade Buyer/Seller (marketplace) One-time Solana transfer + metadata update (key transfer)
Ongoing None No subscription No pinning, no rent

10. Security Considerations

10.1 Key Compromise

If the owner’s wallet private key is compromised, the attacker can decrypt soulEncryptedKey and thus the Soul. This is inherent to owner-based encryption. Users MUST protect wallet keys.

10.2 Re-encryption on Trade

If the seller does not re-encrypt the content key for the buyer, the buyer cannot decrypt. Implementations MUST ensure that key transfer (re-encrypt K for buyer, update metadata) is performed on every ownership transfer, either by the seller’s client or by a trusted relayer/marketplace.

10.3 Metadata Visibility

NFT metadata (including soulUri) is public. Anyone can fetch the ciphertext from Arweave. Without K, the content is unreadable. So exposure of TX ID is acceptable; exposure of K or the private key is not.


11. Implementation Responsibilities

Component Responsibility
SDK Serialize Soul JSON; compress (optional); generate K; encrypt; upload to Arweave; call Solana to mint/update metadata; decrypt on import; key derivation and key transfer on trade.
API No storage of Souls. MAY return URIs or upload tokens; MUST NOT persist Soul blobs.
Client (game) Trigger export at correct times (e.g. before trade, on Save); clear local Soul on transfer out; present UX for Save/Export.
Marketplace / relayer If seller does not perform key transfer, marketplace or relayer MUST perform re-encryption of K for buyer and metadata update.

12. Summary of Normative Requirements


ᚠ ᛫ ᛟ ᛫ ᚱ ᛫ ᛒ ᛫ ᛟ ᛫ ᚲ

Document version: 1.0. Last updated: 2026-02-06.