Logs
@@ -214,32 +119,26 @@ Our app won't run like this, of course. We need some working HTML, at least. Let
```
-It _could_ be a beautiful UI... Depending on which universe you live in.
-
-## Some good old vanilla Javascript
-
-Our love for Noir needs undivided attention, so let's just open `main.js` and delete everything (this is where the romantic scenery becomes a bit creepy).
+It _could_ be a beautiful UI... Depending on which universe you live in. In any case, we're using some scary CSS to make two boxes that will show cool things on the screen.
-Start by pasting in this boilerplate code:
+As for the JS, real madmen could just `console.log` everything, but let's say we want to see things happening (the true initial purpose of JS... right?). Here's some boilerplate for that. Just paste it in `index.js`:
```js
-function display(container, msg) {
- const c = document.getElementById(container);
- const p = document.createElement('p');
- p.textContent = msg;
- c.appendChild(p);
-}
+const show = (id, content) => {
+ const container = document.getElementById(id);
+ container.appendChild(document.createTextNode(content));
+ container.appendChild(document.createElement("br"));
+};
-document.getElementById('submitGuess').addEventListener('click', async () => {
- try {
- // here's where love happens
- } catch (err) {
- display('logs', 'Oh π Wrong guess');
- }
+document.getElementById("submit").addEventListener("click", async () => {
+ try {
+ // noir goes here
+ } catch {
+ show("logs", "Oh π");
+ }
});
-```
-The display function doesn't do much. We're simply manipulating our website to see stuff happening. For example, if the proof fails, it will simply log a broken heart π’
+```
:::info
@@ -248,30 +147,56 @@ At this point in the tutorial, your folder structure should look like this:
```tree
.
βββ circuit
- βββ ...same as above
-βββ vite-project
- βββ vite.config.js
- βββ main.js
- βββ package.json
- βββ index.html
+ βββ src
+ βββ main.nr
+ Nargo.toml
+ index.js
+ package.json
+ index.html
+ ...etc
```
-You'll see other files and folders showing up (like `package-lock.json`, `node_modules`) but you shouldn't have to care about those.
-
:::
-## Some NoirJS
+## Compile compile compile
-We're starting with the good stuff now. If you've compiled the circuit as described above, you should have a `json` file we want to import at the very top of our `main.js` file:
+Finally we're up for something cool. But before we can execute a Noir program, we need to compile it into ACIR: an abstract representation. Here's where `noir_wasm` comes in.
-```ts
-import circuit from '../circuit/target/circuit.json';
+`noir_wasm` expects a filesystem so it can resolve dependencies. While we could use the `public` folder, let's just import those using the nice `?url` syntax provided by vite. At the top of the file:
+
+```js
+import { compile, createFileManager } from "@noir-lang/noir_wasm"
+
+import main from "./circuit/src/main.nr?url";
+import nargoToml from "./circuit/Nargo.toml?url";
```
-[Noir is backend-agnostic](../index.mdx#whats-new-about-noir). We write Noir, but we also need a proving backend. That's why we need to import and instantiate the two dependencies we installed above: `BarretenbergBackend` and `Noir`. Let's import them right below:
+Compiling on the browser is common enough that `createFileManager` already gives us a nice in-memory filesystem we can use. So all we need to compile is fetching these files, writing them to our filesystem, and compile. Add this function:
```js
-import { BarretenbergBackend, BarretenbergVerifier as Verifier } from '@noir-lang/backend_barretenberg';
+export async function getCircuit() {
+ const fm = createFileManager("/");
+ const { body } = await fetch(main);
+ const { body: nargoTomlBody } = await fetch(nargoToml);
+
+ fm.writeFile("./src/main.nr", body);
+ fm.writeFile("./Nargo.toml", nargoTomlBody);
+ return await compile(fm);
+}
+```
+
+:::tip
+
+As you can imagine, with `node` it's all conveniently easier since you get native access to `fs`...
+
+:::
+
+## Some more JS
+
+We're starting with the good stuff now. We want to execute our circuit to get the witness, and then feed that witness to Barretenberg. Luckily, both packages are quite easy to work with. Let's import them at the top of the file:
+
+```js
+import { UltraHonkBackend } from '@aztec/bb.js';
import { Noir } from '@noir-lang/noir_js';
```
@@ -279,88 +204,103 @@ And instantiate them inside our try-catch block:
```ts
// try {
-const backend = new BarretenbergBackend(circuit);
-const noir = new Noir(circuit);
+const { program } = await getCircuit();
+const noir = new Noir(program);
+const backend = new UltraHonkBackend(program.bytecode);
// }
```
-:::note
+:::warning
-For the remainder of the tutorial, everything will be happening inside the `try` block
+WASMs are not always easy to work with. In our case, `vite` likes serving them with the wrong MIME type. There are different fixes but we found the easiest one is just YOLO instantiating the WASMs manually. Paste this at the top of the file, just below the other imports, and it will work just fine:
+
+```js
+import initNoirC from "@noir-lang/noirc_abi";
+import initACVM from "@noir-lang/acvm_js";
+import acvm from "@noir-lang/acvm_js/web/acvm_js_bg.wasm?url";
+import noirc from "@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm?url";
+await Promise.all([initACVM(fetch(acvm)), initNoirC(fetch(noirc))]);
+```
:::
-## Our app
+## Executing and proving
-Now for the app itself. We're capturing whatever is in the input when people press the submit button. Just add this:
+Now for the app itself. We're capturing whatever is in the input when people press the submit button. Inside our `try` block, let's just grab that input and get its value. Noir will gladly execute it, and give us a witness:
```js
-const x = parseInt(document.getElementById('guessInput').value);
-const input = { x, y: 2 };
+const age = document.getElementById("age").value;
+show("logs", "Generating witness... β³");
+const { witness } = await noir.execute({ age });
+show("logs", "Generated witness... β
");
+
```
+:::note
+
+For the remainder of the tutorial, everything will be happening inside the `try` block
+
+:::
+
Now we're ready to prove stuff! Let's feed some inputs to our circuit and calculate the proof:
```js
-await setup(); // let's squeeze our wasm inits here
-
-display('logs', 'Generating proof... β');
-const { witness } = await noir.execute(input);
+show("logs", "Generating proof... β³");
const proof = await backend.generateProof(witness);
-display('logs', 'Generating proof... β
');
-display('results', proof.proof);
+show("logs", "Generated proof... β
");
+show("results", proof.proof);
```
-You're probably eager to see stuff happening, so go and run your app now!
+Our program is technically **done** . You're probably eager to see stuff happening! To serve this in a convenient way, we can use a bundler like `vite` by creating a `vite.config.js` file:
+
+```bash
+touch vite.config.js
+```
-From your terminal, run `npm run dev`. If it doesn't open a browser for you, just visit `localhost:5173`. You should now see the worst UI ever, with an ugly input.
+`vite` helps us with a little catch: `bb.js` in particular uses top-level awaits which aren't supported everywhere. So we can add this to the `vite.config.js` to make the bundler optimize them:
-![Getting Started 0](@site/static/img/noir_getting_started_1.png)
+```js
+export default { optimizeDeps: { esbuildOptions: { target: "esnext" } } };
+```
+
+This should be enough for vite. We don't even need to install it, just run:
+
+```bash
+bunx vite
+```
-Now, our circuit says `fn main(x: Field, y: pub Field)`. This means only the `y` value is public, and it's hardcoded above: `input = { x, y: 2 }`. In other words, you won't need to send your secret`x` to the verifier!
+If it doesn't open a browser for you, just visit `localhost:5173`. You should now see the worst UI ever, with an ugly input.
-By inputting any number other than 2 in the input box and clicking "submit", you should get a valid proof. Otherwise the proof won't even generate correctly. By the way, if you're human, you shouldn't be able to understand anything on the "proof" box. That's OK. We like you, human β€οΈ.
+![Noir Webapp UI](@site/static/img/tutorials/noirjs_webapp/webapp1.png)
+
+Now, our circuit requires a private input `fn main(age: u8)`, and fails if it is less than 18. Let's see if it works. Submit any number above 18 (as long as it fits in 8 bits) and you should get a valid proof. Otherwise the proof won't even generate correctly.
+
+By the way, if you're human, you shouldn't be able to understand anything on the "proof" box. That's OK. We like you, human β€οΈ.
## Verifying
Time to celebrate, yes! But we shouldn't trust machines so blindly. Let's add these lines to see our proof being verified:
```js
-display('logs', 'Verifying proof... β');
+show('logs', 'Verifying proof... β');
const isValid = await backend.verifyProof(proof);
-
-// or to cache and use the verification key:
-// const verificationKey = await backend.getVerificationKey();
-// const verifier = new Verifier();
-// const isValid = await verifier.verifyProof(proof, verificationKey);
-
-if (isValid) display('logs', 'Verifying proof... β
');
+show("logs", `Proof is ${isValid ? "valid" : "invalid"}... β
`);
```
You have successfully generated a client-side Noir web app!
![coded app without math knowledge](@site/static/img/memes/flextape.jpeg)
-## Further Reading
-
-You can see how noirjs is used in a full stack Next.js hardhat application in the [noir-starter repo here](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat). The example shows how to calculate a proof in the browser and verify it with a deployed Solidity verifier contract from noirjs.
-
-You should also check out the more advanced examples in the [noir-examples repo](https://github.com/noir-lang/noir-examples), where you'll find reference usage for some cool apps.
+## Next steps
-## UltraHonk Backend
+At this point, you have a working ZK app that works on the browser. Actually, it works on a mobile phone too!
-Barretenberg has recently exposed a new UltraHonk backend. We can use UltraHonk in NoirJS after version 0.33.0. Everything will be the same as the tutorial above, except that the class we need to import will change:
+If you want to continue learning by doing, here are some challenges for you:
-```js
-import { UltraHonkBackend, UltraHonkVerifier as Verifier } from '@noir-lang/backend_barretenberg';
-```
-
-The backend will then be instantiated as such:
-
-```js
-const backend = new UltraHonkBackend(circuit);
-```
+- Install [nargo](https://noir-lang.org/docs/getting_started/noir_installation) and write [Noir tests](../tooling/testing)
+- Change the circuit to accept a [public input](../noir/concepts/data_types/#private--public-types) as the cutoff age. It could be different depending on the purpose, for example!
+- Enjoy Noir's Rust-like syntax and write a struct `Country` that implements a trait `MinAge` with a method `get_min_age`. Then, make a struct `Person` have an `u8` as its age and a country of type `Country`. You can pass a `person` in JS just like a JSON object `person: { age, country: { min_age: 18 }}`
-Then all the commands to prove and verify your circuit will be same.
+The world is your stage, just have fun with ZK! You can see how noirjs is used in a full stack Next.js hardhat application in the [noir-starter repo here](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat). The example shows how to calculate a proof in the browser and verify it with a deployed Solidity verifier contract from noirjs.
-The only feature currently unsupported with UltraHonk are [recursive proofs](../explainers/explainer-recursion.md).
+Check out other starters, tools, or just cool projects in the [awesome noir repository](https://github.com/noir-lang/awesome-noir).