Skip to content

RISC0

Primitive choices

Risc0 VM

  • Frontend Language: Rust
  • Instruction Set Architecture (ISA): RISC-V

The Risc0 Resource Machine (RM) inherits some security properties from the underlying Risc0 Virtual Machine (VM) upon which is built. We highlight these below:

Proof system

  • Arithmetization: Plonkish (UltraPlonk in particular). The arithmetization consists of customised gates, high-degree polynomial relations, many witness wires, and lookup arguments.
  • Polynomial evaluation and commitment (PCS): FRI (Fast Reed-Solomon Interactive Proof of Proximity).
  • Prime field \(\mathbb{F}\): The computation is defined over the BabyBear field (modulus \(15 \cdot 2^27 + 1\)) and a degree-four extension is used during the algebraic holographic proving. Each BabyBear element stores one byte of the data. In other words, a 32-bit integer uses four BabyBear elements.
  • Memory checking: Each access to the main memory always incurs only a constant overhead. Pages are authenticated using Merkle Trees.
  • STARK-to-SNARK: Proof generation results in STARK proofs that can be further compressed—without revealing information and in a trustless way—into very succinct SNARK proofs through Groth16.

Security analysis

Prover Cryptographic Assumptions Bits of Security Quantum Safe?
RISC-V Prover - Random Oracle Model
- Toy Problem Conjecture
97 Yes
Recursion Prover - Random Oracle Model
- Toy Problem Conjecture
99 Yes
STARK-to-SNARK Prover - Security of elliptic curve pairing over BN254
- Knowledge of Exponent assumption
- Integrity of Groth16 Trusted Setup Ceremony
99+ No

On-chain verifier contracts target 97 bits of security.

The best known attack vector against our STARK to SNARK Prover is to attack the underlying elliptic curve pairing used with BN254. This primitive has been heavily battle-tested: it's part of the core cryptography on Zcash and it's included as a precompile on Ethereum (see EIP-197).

Risc0 RM

Resource primary fields

Since the Risc0 proving system is based on hashes and not on elliptic curves, the cryptographic primitive used for compressing, hiding and binding data in Merkle tree structures such as the resource commitment tree, or others such as the nullifier set in the Risc0 RM is sha256. Furthermore, whenever we encounter a PRF in the ARM specs, we can substitute it for sha256 in the Risc0 RM.

If homomorphism is required, we operate on the secp256k1 curve. We use lowercase for fields (Babybear) and uppercase for points in curve (secp256k1). For instance, \([x] \cdot G\) denotes scalar multiplication of a curve point \(G\).

Field Computation Type/size Description
l sha256(verifying_key(RL)) Digest (\(256\) bits) Application's RL verifying key. Used to identify the application the resource belongs to. As the verifying key itself is large, resources only store a commitment to it.
label user defined unsigned integer (\(256\) bits) Contains the application data that affects fungibility of the resource. Along with \( l \), it is used to derive the resource's kind.
q user defined unsigned integer (\(256\) bits) The quantity of fungible value.
v user defined unsigned integer (\(256\) bits) Resource value is a commitment to the resource's extra data that doesn't affect the resource's fungibility.
eph user defined bool (1 bit) Ephemeral resource flag. It indicates whether the resource's commitment Merkle path should be checked when consuming the resource.
nonce n \(\overset{\$}{\leftarrow} \mathbb{F}\); sha256(n) Digest (\(256\) bits) Guarantees the uniqueness of the later derived computable fields.
npk sha256(nsk) Digest (\(256\) bits) Commitment to the nullifier key \( nk \) that will be used to derive the resource's nullifier.
rseed \(\overset{\$}{\leftarrow} u256\) unsigned integer (\(256\) bits) A random commitment trapdoor.

Resource computable fields

Computable fields are fields derived by applying some computation on the resource primary fields listed above.

Field Computation Type/size Description
K secp256k1::hash_to_point(l, label) secp256k1 point Resource kind
cm sha256(l, label, q, v, eph, npk, nonce, npk, rseed) Digest (\(256\) bits) Resource commitment. It allows to prove the existence of the resource without revealing the resource plaintext.
nf sha256(nk, nonce, cm) Digest (\(256\) bits) Resource nullifier. Revealing the resource's nullifier invalidates the resource. All nullifiers are stored in a global append-only nullifier set.
D \([q_1] \cdot K_1 - [q_2] \cdot K_2 + [rcd] \cdot R\) secp256k1 point Resource delta used to ensure balance across the resources (\(r_1\), \(r_2\)) in a transaction. \(rcd\) is some random value in \(0 ... 2^{256}-1\) and \(R\) is a secp256k1 point of unknown discrete log.

Cryptographic algorithms

Verifiable encryption

We want the encryption to be verifiable to make sure the receiver of the resources can decrypt them.

Since the Risc0 proving system uses a small field, bit-wise operations' efficiency is acceptable. Thus we use the AES encryption algorithm.

Encoding choices

Resource

pub struct Resource {
    // a succinct representation of the predicate associated with the resource
    pub l: Digest,
    // specifies the fungibility domain for the resource
    pub label: [u8; 32],
    // number representing the quantity of the resource
    pub quantity: [u8; 32],
    // the fungible data of the resource
    pub value: [u8; 32],
    // flag that reflects the resource ephemerality
    pub eph: bool,
    // guarantees the uniqueness of the resource computable components
    pub nonce: Digest,
    // nullifier public key
    pub npk: Npk,
    // randomness seed used to derive whatever randomness needed
    pub rseed: [u8; 32],
}

where Npk is just a wrapper over Digest (which is in turn a wrapper over an unsigned integer of 256 bits) used for type safety.

Compliance circuit

pub struct Compliance<const COMMITMENT_TREE_DEPTH: usize> {
    /// The input resource
    pub input_resource: Resource,
    /// The output resource
    pub output_resource: Resource,
    /// The path from the output commitment to the root in the resource commitment tree
    pub merkle_path: [(Digest, bool); COMMITMENT_TREE_DEPTH],
    /// Random scalar for delta commitment
    pub rcv: ScalarWrapper,
    /// Nullifier secret key
    pub nsk: Nsk,
}

where ScalarWrapper is just a wrapper over an unsigned integer of 256 bits, and Nsk is also a wrapper over Digest. Nsk and Npk are related as follows:

pub struct Nsk(Digest);
pub struct Npk(Digest);

impl Nsk {
    pub fn new(nsk: Digest) -> Nsk {
        Nsk(nsk)
    }
    /// Compute the corresponding nullifier public key
    pub fn public_key(&self) -> Npk {
        let bytes: [u8; DIGEST_BYTES] = *self.0.as_ref();
        Npk(*Impl::hash_bytes(&bytes))
    }
}

Commitment tree

  • Data Structure
  • Operations & Complexity
  • Hash function

Nullifier set

  • Data structure
  • Operations & Complexity
  • Hash function