Skip to main content

Getting Started with the Sindri TypeScript SDK

Build your first ZKP circuit with the Sindri TypeScript SDK

The Sindri TypeScript SDK makes it easy for developers to programmatically interact with the Sindri API using their choice of TypeScript or vanilla JavaScript. It works in both node and browser environments, and is fully compatible with CommonJS and ES Module style imports. In this guide, we'll walk through the basics of using the SDK to compile circuits and generate proofs. You can also refer to the TypeScript SDK Reference Documentation which covers the full breadth of SDK's API.

Prerequisites

  • The SDK and CLI are compatible with NodeJS v18.13.0 or newer. You can either use the NodeJS Installer, your system's package manager, or a node version manager such as nvm, fnm, n, or asdf to manage your node installation.
  • A Sindri account is required to interact with the API. You can create an account if you don't already have one.

Create a TypeScript Project

Let's start by creating a new Node.js project and installing the Sindri SDK as a dependency.

# Create a new project to house your circuit and surrounding code.
mkdir my-project
cd my-project
npm init --yes
npm pkg set type="module"

# Install the Sindri SDK.
npm install sindri

We'll also need some minimal TypeScript development tooling to allow us to write TypeScript code. In addition to TypeScript itself, we'll install the tsx which provides a TypeScript REPL and allows us to run .ts scripts without transpiling them to JavaScript first.

# Install TypeScript tooling.
npm install --save-dev tsx typescript
info

Don't use TypeScript?

The SDK is fully compatible with vanilla JavaScript and supports both CommonJS and ES Module style imports. You can skip the TypeScript tooling installation and write your code in JavaScript if you prefer. Simply change the file extensions from .ts to .mjs and use node instead of npx tsx to run your scripts; most of them should work without further modification.

Initialize a Circuit Project

We can use the CLI to quickly scaffold a new circuit project as a subdirectory of our main project (see the CLI Quick Start Guide and sindri init for more information). You could alternatively write your circuit from scratch, use one of the circuits from the Sindri-Resources GitHub Repository, or clone a public circuit using the sindri clone command.

# Initialize a new circuit project using the CLI.
npx sindri init is-equal
Command Output
? Circuit Name: is-equal
? Proving Framework: Noir
? Noir Package Name: is_equal
? Noir Version: 0.23.0
? Proving Scheme: Barretenberg
[14:41:08.932] INFO: Proceeding to generate scaffolded project in "/private/tmp/my-project/is-equal".
[14:41:08.949] INFO: Project scaffolding successful.
? Would you like to initialize a git repository in "/private/tmp/my-project/is-equal"? no

This generated circuit takes a private input X and a public input Y, then proves that X is equal to Y.

Authenticate the Client

There are three ways to authenticate the client with your API key.

  1. Run npx sindri login and enter your username and password to create a new API key that is stored persistently in a configuration file in your home directory (see sindri login). This configuration file is sourced by both the CLI and the SDK by default.
  2. Set the SINDRI_API_KEY environment variable to your API key (see API Key Creation and Management. This method is particularly useful when injecting secrets into a CI/CD pipeline or production environment.
  3. Pass the API key directly to the SDK client constructor as an apiKey property (see the SindriClient class).

Each of these methods takes precedence over the previous ones, so explicitly passing the key to the client will override any other configuration.

Use the first method to authenticate the client by running the following command and entering your credentials when prompted.

npx sindri login
Command Output
? Username: my-username
? Password: ***************
? New API Key Name: my-computer-sdk
? Select a Organization: my-team
[15:50:20.713] INFO: You have successfully authorized the client with your Sindri account.

The newly created API key will be used automatically from now on.

Compile the Circuit

Create a new file named compile.ts in the root of your project and add the following code to compile the is-equal circuit.

compile.ts
import sindri from 'sindri';

// Compile the circuit in the `is-equal` directory.
const circuit = await sindri.createCircuit('is-equal');

// Log out the circuit object as JSON.
console.log(JSON.stringify(circuit, null, 2));

You can then run this script with tsx to compile the circuit and log the output.

npx tsx compile.ts
Output - Compile
Command Output
{
"circuit_id": "4b389643-70d4-4fdb-8fe4-1dc11ca163f2",
"circuit_name": "is-equal",
"circuit_type": "noir",
"date_created": "2024-04-03T13:10:39.372Z",
"num_proofs": 0,
"proving_scheme": "barretenberg",
"public": false,
"status": "Ready",
"team": "my-team",
"compute_time": "P0DT00H00M01.154906S",
"compute_time_sec": 1.154906,
"compute_times": {
"total": 1.15491,
"clean_up": 0.0384,
"file_setup": 0.14133,
"nargo_checks": 0.45313,
"save_results": 0.00591,
"solidity_contract_generation": 0.51613
},
"file_size": 1665,
"queue_time": "P0DT00H00M00.550668S",
"queue_time_sec": 0.550668,
"uploaded_file_name": "is-equal.tar.gz",
"verification_key": null,
"error": null,
"acir_opcodes": 1,
"circuit_size": 7,
"curve": "bn254",
"nargo_package_name": "is_equal",
"noir_version": "0.23.0"
}

Generate a Proof

Now create a file named prove.ts in the root of your project and add the following code to generate a proof for the is-equal circuit.

prove.ts
import sindri from 'sindri';

// Generate a proof for the `latest` tag of the `is-equal` circuit.
// The `is-equal` circuit name is specified in the `name` field of `./is-equal/sindri.json`.
const circuitIdentifier = 'is-equal:latest';
const proofInput = 'X=5\nY=5';
const proof = await sindri.proveCircuit(circuitIdentifier, proofInput);

// Log out the proof object as JSON.
console.log(JSON.stringify(proof, null, 2));

We'll again use tsx to run the script, this time creating a proof for the circuit.

npx tsx prove.ts
Output - Prove
Command Output
{
"proof_id": "3f27e3e6-f39a-47f7-9135-f9ab212fbc73",
"circuit_name": "is-equal",
"circuit_id": "4b389643-70d4-4fdb-8fe4-1dc11ca163f2",
"circuit_type": "noir",
"date_created": "2024-04-03T13:22:13.895Z",
"perform_verify": false,
"status": "Ready",
"team": "evan-sangaline",
"compute_time": "P0DT00H00M01.024465S",
"compute_time_sec": 1.024465,
"compute_times": {
"prove": 0.88806,
"total": 1.02447,
"clean_up": 0.01131,
"file_setup": 0.1224,
"save_results": 0.00269
},
"file_size": 4398,
"proof": {
"proof": "<removed-for-brevity>"
},
"public": {
"Verifier.toml": "Y = \"0x0000000000000000000000000000000000000000000000000000000000000005\"\n"
},
"queue_time": "P0DT00H00M00.737052S",
"queue_time_sec": 0.737052,
"smart_contract_calldata": null,
"verification_key": {},
"error": null
}

Nice, you've just generated your first proof!

Generate Proofs in Parallel

Now that we've covered the basics, we can demonstrate more of the power of Sindri and the SDK by generating multiple proofs in parallel. In this example, we'll create a set of random X and Y pairs and generate proofs for each pair concurrently. Since the proof is being generated on remote infrastructure, your client code is able to orchestrate the generation of as many proofs as you want. The example here is contrived, but it demonstrates how you can use the SDK to scale up your proof generation as needed.

To try it yourself, add the following code to a new file named parallel-prove.ts in the root of your project.

parallel-prove.ts
import { randomInt } from 'node:crypto';

import sindri from 'sindri';

const circuitIdentifier = 'is-equal:latest';

// Create a set of random X/Y pairs to use as inputs to proofs.
const inputs = Array(10)
.fill()
.map((x) => ({ x: randomInt(1, 2 + 1), y: randomInt(1, 2 + 1) }));

// Create proofs for each input pair in parallel.
const proofs = await Promise.all(
inputs.map(({ x, y }) => sindri.proveCircuit(circuitIdentifier, `X=${x}\nY=${y}`)),
);

// Log out the status of each proof.
inputs.forEach(({ x, y }, index) => {
console.log(`X=${x}, Y=${y}: ${proofs[index].status}`);
});

Then run the script with tsx to generate proofs for each pair of inputs in parallel and output the results.

npx tsx parallel-prove.ts
Command Output
X=2, Y=2: Ready
X=2, Y=1: Failed
X=2, Y=2: Ready
X=2, Y=2: Ready
X=1, Y=2: Failed
X=1, Y=1: Ready
X=2, Y=2: Ready
X=2, Y=2: Ready
X=2, Y=2: Ready
X=2, Y=1: Failed

You can see here that some of the proofs failed because the X and Y input values were not equal. Our circuit asserts that X is equal to Y, so the proofs failed when this condition was not met. Looks like our circuit is working as expected!

Catch Bugs with TypeScript

Most of what we've looked at so far would apply equally to TypeScript or JavaScript. The SDK is written in native TypeScript, so it provides type definitions that supercharge IDE features and can help you catch bugs in your code before you even run it. Let's take a look at an example of code that has a bug and how TypeScript can help you catch it. Create a new file named buggy-code.ts in the root of your project with the following contents.

buggy-code.ts
import sindri from 'sindri';

// Compile the circuit in the `is-equal` directory.
const circuit = await sindri.createCircuit('is-equal');

// Try to log out the circuit ID, but there's a bug!
// It should be `circuit.circuit_id`, not `circuit.id`.
console.log(circuit.id);

You can then use the TypeScript compiler to check this file for errors. We need to pass a few configuration options because we skipped creating a tsconfig.json file earlier, but this will work just fine for a one-off check.

npx tsc \
--target es2022 \
--module es2022 \
--moduleResolution node \
--noEmit \
buggy-code.ts
Command Output
buggy-code.ts:8:21 - error TS2339: Property 'id' does not exist on type 'CircuitInfoResponse'.
Property 'id' does not exist on type 'CircomCircuitInfoResponse'.

8 console.log(circuit.id);
~~


Found 1 error in buggy-code.ts:8

You can see here that the TypeScript compiler successfully identifies that we're trying to access the wrong property on the circuit object. The TypeScript SDK can help you catch these kinds of bugs early in your development process, saving you time and frustration later on.

Next Steps

The sindri object is an instance of the SindriClient class. Checking out the reference documentation for this class is a great next step to learn more about the SDK's capabilities. You can also browse the rest of the documentation to learn more about writing circuits, the frameworks we support, and other ways to interact with the Sindri API.