noir.ts 5.15 KB
Newer Older
John Doe's avatar
John Doe committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
import { decompressSync } from 'fflate';
// @ts-ignore
import { Barretenberg, Crs, RawBuffer, Fr } from '@aztec/bb.js';
import { executeCircuit, compressWitness } from '@noir-lang/acvm_js';
import { ethers } from 'ethers'; // I'm lazy so I'm using ethers to pad my input
import { Ptr } from '@aztec/bb.js/dest/browser/types';


export class Noir {
  circuit: any;
  acir: string = '';
  acirBufferCompressed: Uint8Array = Uint8Array.from([]);
  acirBufferUncompressed: Uint8Array = Uint8Array.from([]);

  api = {} as Barretenberg;
  acirComposer = {} as Ptr;

  constructor(circuit: Object) {
    this.circuit = circuit;
  }

  async init() {
    const isBrowser = typeof window !== 'undefined';
    if (isBrowser) {
      const { default: initACVM } = await import('@noir-lang/acvm_js');
      await initACVM();
    }

    this.acirBufferCompressed = Buffer.from(this.circuit.bytecode, 'base64');
    this.acirBufferUncompressed = decompressSync(this.acirBufferCompressed);

    this.api = await Barretenberg.new(4);

    const [exact, total, subgroup] = await this.api.acirGetCircuitSizes(
      this.acirBufferUncompressed,
    );
    const subgroupSize = Math.pow(2, Math.ceil(Math.log2(total)));
    const crs = await Crs.new(subgroupSize + 1);
    await this.api.commonInitSlabAllocator(subgroupSize);
    await this.api.srsInitSrs(
      new RawBuffer(crs.getG1Data()),
      crs.numPoints,
      new RawBuffer(crs.getG2Data()),
    );

    this.acirComposer = await this.api.acirNewAcirComposer(subgroupSize);
  }

  // Generates the intermediate witnesses by using `input`
  // as the initial set of witnesses and executing these
  // against the circuit.
  async generateWitness(input: any): Promise<Uint8Array> {
    const initialWitness = new Map<number, string>();
    for (let i = 1; i <= input.length; i++) {
      initialWitness.set(i, ethers.utils.hexZeroPad(input[i - 1], 32));
    }

    const witnessMap = await executeCircuit(this.acirBufferCompressed, initialWitness, () => {
      throw Error('unexpected oracle');
    });

    const witnessBuff = compressWitness(witnessMap);
    return witnessBuff;
  }

  // Generates an inner proof. This is the proof that will be verified
  // in another circuit.
  //
  // We set isRecursive to true, which will tell the backend to
  // generate the proof using components that will make the proof
  // easier to verify in a circuit.
  async generateInnerProof(witness: Uint8Array) {
    const makeEasyToVerifyInCircuit = true;
    return this.generateProof(witness, makeEasyToVerifyInCircuit);
  }

  // Generates artifacts that will be passed to the circuit that will verify this proof.
  //
  // Instead of passing the proof and verification key as a byte array, we pass them
  // as fields which makes it cheaper to verify in a circuit.
  //
  // The number of public inputs denotes how many public inputs are in the inner proof.
  async generateInnerProofArtifacts(proof: Uint8Array, numOfPublicInputs: number = 0) {
    console.log('serializing proof');
    const proofAsFields = await this.api.acirSerializeProofIntoFields(
      this.acirComposer,
      proof,
      numOfPublicInputs,
    );
    console.log('proof serialized');
    console.log('serializing vk');
    await this.api.acirInitVerificationKey(this.acirComposer);
    // Note: If you don't init verification key, `acirSerializeVerificationKeyIntoFields`` will just hang on serialization
    const vk = await this.api.acirSerializeVerificationKeyIntoFields(this.acirComposer);
    console.log('vk serialized');

    return {
      proofAsFields: proofAsFields.map(p => p.toString()),
      vkAsFields: vk[0].map(vk => vk.toString()),
      vkHash: vk[1].toString(),
    };
  }

  // Generate an outer proof. This is the proof for the circuit which will verify
  // inner proofs.
  //
  // The settings for this proof are the same as the settings for a "normal" proof
  // ie one that is not in the recursive setting.
  async generateOuterProof(witness: Uint8Array) {
    const makeEasyToVerifyInCircuit = false;
    return this.generateProof(witness, makeEasyToVerifyInCircuit);
  }

  async generateProof(witness: Uint8Array, makeEasyToVerifyInCircuit: boolean) {
    console.log('Creating outer proof');

    const decompressedWitness = decompressSync(witness);

    const proof = await this.api.acirCreateProof(
      this.acirComposer,
      this.acirBufferUncompressed,
      decompressedWitness,
      makeEasyToVerifyInCircuit,
    );

    return proof;
  }

  async verifyInnerProof(proof: Uint8Array) {
    const makeEasyToVerifyInCircuit = true;
    return this.verifyProof(proof, makeEasyToVerifyInCircuit);
  }

  async verifyOuterProof(proof: Uint8Array) {
    const makeEasyToVerifyInCircuit = false;
    console.log('verifying outer proof');
    const verified = await this.verifyProof(proof, makeEasyToVerifyInCircuit);
    console.log(verified);
    return verified;
  }

  async verifyProof(proof: Uint8Array, makeEasyToVerifyInCircuit: boolean) {
    await this.api.acirInitVerificationKey(this.acirComposer);
    const verified = await this.api.acirVerifyProof(
      this.acirComposer,
      proof,
      makeEasyToVerifyInCircuit,
    );
    return verified;
  }

  async destroy() {
    await this.api.destroy();
  }
}