Bytes and Opaque Types

Bytes<n>

Bytes<n> represents a fixed-length byte array of exactly n bytes. According to the docs, Bytes types are used in the Compact standard library for hashing, and string literals also have a Bytes type (where n is the number of bytes in the UTF-8 encoding).

Common Uses (guidance)

export pure circuit shapes(
    hash: Bytes<32>,   // Hashes (32 bytes = 256 bits)
    id: Bytes<8>,      // Short identifiers
    data: Bytes<256>   // Longer data
): Bytes<32> {
    return hash;
}

Note: The “hashes / identifiers” wording above is convention, not mandated by the docs.

Creating Bytes

In practice, Bytes values often come from:

  • Witnesses (private input)
  • Hashing functions
  • String literals (automatically typed as Bytes<n>)
  • Parameters passed to circuits
witness getHash(): Bytes<32>;

export circuit example(): [] {
    const h = getHash();
    // use h...
}

Bytes Operations

Bytes values support equality comparison with == and != (verified with toolchain 0.31.1):

export pure circuit compareBytes(hash1: Bytes<32>, hash2: Bytes<32>): Boolean {
    const same = hash1 == hash2;
    const different = hash1 != hash2;
    return same;
}

Hashing to Bytes

The standard library’s persistentHash<T> hashes a value of type T and returns Bytes<32> — no cast needed (verified with toolchain 0.31.1):

export pure circuit hashIt(someValue: Bytes<32>): Bytes<32> {
    const hash: Bytes<32> = persistentHash<Bytes<32>>(someValue);
    return hash;
}

Opaque Types

Opaque<s> represents opaque values tagged by a string literal s. Opaque values can be manipulated in witnesses but they are opaque to circuits—they are represented in circuits as their hash.

Note: The only allowed tags are currently "string" and "Uint8Array".

Opaque<“string”>

For string data:

// Declare a field that holds string data
export ledger message: Opaque<"string">;

Opaque<“Uint8Array”>

For arbitrary binary data:

export ledger binaryData: Opaque<"Uint8Array">;

Important Limitations

⚠️ Opaque values are opaque to circuits!

They are represented in circuits as their hash, so you cannot inspect their actual content in circuit logic.

Note: Opaque values are only opaque within Compact. In your DApp’s TypeScript code they are just string or Uint8Array, and on-chain they are stored as bytes/UTF-8 (not encrypted).

Source: Opaque data

// Opaque values can be stored and compared by hash
// (assuming a Map with Opaque value type)
export ledger messages: Map<Bytes<32>, Opaque<"string">>;

export circuit storeMessage(key: Bytes<32>, opaqueValue: Opaque<"string">): [] {
    // Parameters are private inputs — writing them to public ledger
    // state requires an explicit disclose()
    messages.insert(disclose(key), disclose(opaqueValue));
}

When to Use What (guidance)

TypeUse Case
Bytes<32>Hashes, fixed-size identifiers
Bytes<n>Fixed-size binary data you compute on
Opaque<"string">Human-readable text (opaque to circuits)
Opaque<"Uint8Array">Binary blobs (opaque to circuits)

Sources: Primitive types; Opaque data

Exercises

Complete exercises/02_types/03_bytes/.