Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/noir-lang/noir/llms.txt

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

Oracles let a Noir program call out to an external process during witness generation and use the returned value in its computation. They bridge the self-contained world of a ZK circuit with data that lives outside it — on-chain state, databases, or arbitrary host logic.
Oracles are not proven. They run during witness generation, outside the constraint system. If you use an oracle’s return value without constraining it, your program makes no claim about that value. Always constrain oracle outputs in your circuit.

How oracles work

When Noir executes your program to build a witness, it can pause at an oracle call and ask the host environment for a value. The host resolves the call and returns a value that the Noir program then uses. The circuit itself only records the constraint you place on that value — not where it came from. This model is analogous to unconstrained functions: the work happens outside the proof, and correctness is enforced by the constraints you write around the result.
Witness generation

├── constrained Noir code runs
├── oracle call encountered  ──▶  host resolver called
│                            ◀──  value returned
└── circuit constrains the returned value

Declaring an oracle in Noir

An oracle consists of two parts:
  1. A function decorated with #[oracle(<name>)] — this is the raw oracle declaration.
  2. An unconstrained wrapper function that calls it — this is what your constrained code calls.
#[oracle(getSqrt)]
unconstrained fn sqrt(number: Field) -> Field {}

unconstrained fn get_sqrt(number: Field) -> Field {
    sqrt(number)
}
The name passed to #[oracle(...)] must match the method name your host resolver exposes. The body of the oracle-decorated function is always empty; the host provides the implementation.

Calling the oracle from a constrained function

Call the unconstrained wrapper with an unsafe block, then immediately constrain the return value:
fn main(input: Field) {
    // Safety: sqrt output is constrained below by squaring
    let sqrt = unsafe { get_sqrt(input) };
    assert(sqrt.pow_32(2) as u64 == input as u64);
}
The unsafe block makes it explicit that you are calling unconstrained code. The // Safety: comment is required by the compiler’s lint — it must explain why the unconstrained call is safe. Without a constraint on the return value, your proof is unsound.

Array parameters

Oracles support single values and arrays. For example, an oracle that accepts two field elements and returns their square roots:
#[oracle(getSqrt)]
unconstrained fn sqrt(input: [Field; 2]) -> [Field; 2] {}

unconstrained fn get_sqrt(input: [Field; 2]) -> [Field; 2] {
    sqrt(input)
}

fn main(input: [Field; 2]) {
    // Safety: each element of sqrt is constrained below
    let sqrt = unsafe { get_sqrt(input) };
    assert(sqrt[0].pow_32(2) as u64 == input[0] as u64);
    assert(sqrt[1].pow_32(2) as u64 == input[1] as u64);
}
Oracles cannot return references (&Field, &mut T, etc.). Values are always copied into the caller’s context.

Step-by-step: integrating an oracle

1

Declare the oracle in Noir

Add the oracle declaration and its unconstrained wrapper to your Noir source. The example below offloads square root computation to the host and verifies the result by squaring:
#[oracle(getSqrt)]
unconstrained fn sqrt(input: [Field; 2]) -> [Field; 2] {}

unconstrained fn get_sqrt(input: [Field; 2]) -> [Field; 2] {
    sqrt(input)
}

fn main(input: [Field; 2]) {
    // Safety: result is verified by squaring each element below
    let sqrt = unsafe { get_sqrt(input) };
    assert(sqrt[0].pow_32(2) as u64 == input[0] as u64);
    assert(sqrt[1].pow_32(2) as u64 == input[1] as u64);
}

#[test]
fn test() {
    let input = [4, 16];
    main(input);
}
2

Write a JSON-RPC server

Nargo resolves oracles by calling a JSON-RPC 2.0 server. The server must implement a resolve_foreign_call method that dispatches on the function name.
oracle-server.js
import { JSONRPCServer } from "json-rpc-2.0";
import express from "express";
import bodyParser from "body-parser";

const app = express();
app.use(bodyParser.json());

const server = new JSONRPCServer();

server.addMethod("resolve_foreign_call", async (params) => {
    if (params[0].function !== "getSqrt") {
        throw Error("Unexpected foreign call");
    }
    const values = params[0].inputs[0].map((field) => {
        return `${Math.sqrt(parseInt(field, 16))}`;
    });
    return { values: [values] };
});

app.post("/", (req, res) => {
    server.receive(req.body).then((jsonRPCResponse) => {
        if (jsonRPCResponse) {
            res.json(jsonRPCResponse);
        } else {
            res.sendStatus(204);
        }
    });
});

app.listen(5555);
The expected return shape for TypeScript users:
types.ts
export type ForeignCallSingle = string;
export type ForeignCallArray = string[];
export type ForeignCallResult = {
    values: (ForeignCallSingle | ForeignCallArray)[];
};
If your oracle returns a multidimensional array such as [['1','2'],['3','4']], return the values flattened: ['1', '2', '3', '4']. The Noir runtime reconstructs the nested shape automatically.
3

Run with Nargo

Pass the RPC server URL to nargo test or nargo execute with the --oracle-resolver flag:
nargo test --oracle-resolver http://localhost:5555
nargo execute --oracle-resolver http://localhost:5555
4

Use a custom handler in NoirJS

In a JavaScript environment you don’t need an RPC server. Pass a foreignCallHandler callback directly to noir.execute:
noirjs-handler.js
import { JSONRPCClient } from "json-rpc-2.0";

const client = new JSONRPCClient((jsonRPCRequest) =>
    fetch("http://localhost:5555", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify(jsonRPCRequest),
    }).then((response) => {
        if (response.status === 200) {
            return response.json().then((res) => client.receive(res));
        } else if (jsonRPCRequest.id !== undefined) {
            return Promise.reject(new Error(response.statusText));
        }
    })
);

const foreignCallHandler = async (name, input) => {
    const inputs = input[0].map((i) => i.toString("hex"));
    const oracleReturn = await client.request("resolve_foreign_call", [
        { function: name, inputs: [inputs] },
    ]);
    return [oracleReturn.values[0]];
};

const input = { input: [4, 16] };
const { witness } = await noir.execute(input, foreignCallHandler);
For a pure in-browser oracle that doesn’t need a server at all, you can provide any callback. For example, to supply random bytes: const foreignCallHandler = (name, inputs) => crypto.randomBytes(16).

Security considerations

Oracles produce values that the prover supplies. The verifier has no direct visibility into the oracle call — only into the constraints you place on its output. This means:
  • You must constrain every oracle return value. An unconstrained oracle output is a free variable: the prover can set it to anything.
  • Constraints must be meaningful. For example, proving that a value belongs to a Merkle tree, or that it matches a known commitment, provides real security. Proving that a value is non-zero does not.
  • Oracles don’t prove anything on their own. As the source material puts it: “Oracles don’t prove anything. Your Noir program does.”
The Noir compiler includes a security pass that checks whether Brillig (unconstrained) call results are covered by constraints. If the check fails, you will see a compiler bug report such as:
**bug**: Brillig function call isn't properly covered by a manual constraint
A good pattern: use an oracle to fetch a value, then verify that value against a known public commitment (hash, Merkle root, or on-chain state root) inside your circuit.

Oracle timeout

When using an external RPC resolver, the default timeout can be overridden by setting the NARGO_FOREIGN_CALL_TIMEOUT environment variable (value in milliseconds):
NARGO_FOREIGN_CALL_TIMEOUT=10000 nargo execute --oracle-resolver http://localhost:5555

Build docs developers (and LLMs) love