-
Notifications
You must be signed in to change notification settings - Fork 268
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: migrate higher-level APIs for barretenberg to bb.js (#8677)
This PR moves a bunch of the code in `noir_js_backend_barretenberg` into `bb.js` . I've kept this as separate classes for now but we can likely migate more functionality over to the standard `Barretenberg` class (especially with UP being deprecated as UH has less external state which needs to be managed.) `noir_js_backend_barretenberg` still exists as we still need to split off public inputs, etc. to avoid this being a breaking change or creating an inconsistency in `bb.js`. We can look at how to address this in future.
- Loading branch information
1 parent
d5f16cc
commit 0237a20
Showing
10 changed files
with
340 additions
and
295 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
import { BackendOptions, Barretenberg } from './index.js'; | ||
import { RawBuffer } from '../types/raw_buffer.js'; | ||
|
||
export class UltraPlonkBackend { | ||
// These type assertions are used so that we don't | ||
// have to initialize `api` and `acirComposer` in the constructor. | ||
// These are initialized asynchronously in the `init` function, | ||
// constructors cannot be asynchronous which is why we do this. | ||
|
||
protected api!: Barretenberg; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
protected acirComposer: any; | ||
|
||
constructor(protected acirUncompressedBytecode: Uint8Array, protected options: BackendOptions = { threads: 1 }) {} | ||
|
||
/** @ignore */ | ||
async instantiate(): Promise<void> { | ||
if (!this.api) { | ||
if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { | ||
this.options.threads = navigator.hardwareConcurrency; | ||
} else { | ||
try { | ||
const os = await import('os'); | ||
this.options.threads = os.cpus().length; | ||
} catch (e) { | ||
console.log('Could not detect environment. Falling back to one thread.', e); | ||
} | ||
} | ||
const api = await Barretenberg.new(this.options); | ||
|
||
const honkRecursion = false; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const [_exact, _total, subgroupSize] = await api.acirGetCircuitSizes( | ||
this.acirUncompressedBytecode, | ||
honkRecursion, | ||
); | ||
|
||
await api.initSRSForCircuitSize(subgroupSize); | ||
this.acirComposer = await api.acirNewAcirComposer(subgroupSize); | ||
await api.acirInitProvingKey(this.acirComposer, this.acirUncompressedBytecode); | ||
this.api = api; | ||
} | ||
} | ||
|
||
/** @description Generates a proof */ | ||
async generateProof(uncompressedWitness: Uint8Array): Promise<Uint8Array> { | ||
await this.instantiate(); | ||
return this.api.acirCreateProof(this.acirComposer, this.acirUncompressedBytecode, uncompressedWitness); | ||
} | ||
|
||
/** | ||
* Generates artifacts that will be passed to a 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 proof that is passed here will have been created using a circuit | ||
* that has the #[recursive] attribute on its `main` method. | ||
* | ||
* The number of public inputs denotes how many public inputs are in the inner proof. | ||
* | ||
* @example | ||
* ```typescript | ||
* const artifacts = await backend.generateRecursiveProofArtifacts(proof, numOfPublicInputs); | ||
* ``` | ||
*/ | ||
async generateRecursiveProofArtifacts( | ||
proof: Uint8Array, | ||
numOfPublicInputs = 0, | ||
): Promise<{ | ||
proofAsFields: string[]; | ||
vkAsFields: string[]; | ||
vkHash: string; | ||
}> { | ||
await this.instantiate(); | ||
const proofAsFields = ( | ||
await this.api.acirSerializeProofIntoFields(this.acirComposer, proof, numOfPublicInputs) | ||
).slice(numOfPublicInputs); | ||
|
||
// TODO: perhaps we should put this in the init function. Need to benchmark | ||
// TODO how long it takes. | ||
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); | ||
|
||
return { | ||
proofAsFields: proofAsFields.map(p => p.toString()), | ||
vkAsFields: vk[0].map(vk => vk.toString()), | ||
vkHash: vk[1].toString(), | ||
}; | ||
} | ||
|
||
/** @description Verifies a proof */ | ||
async verifyProof(proof: Uint8Array): Promise<boolean> { | ||
await this.instantiate(); | ||
await this.api.acirInitVerificationKey(this.acirComposer); | ||
return await this.api.acirVerifyProof(this.acirComposer, proof); | ||
} | ||
|
||
async getVerificationKey(): Promise<Uint8Array> { | ||
await this.instantiate(); | ||
await this.api.acirInitVerificationKey(this.acirComposer); | ||
return await this.api.acirGetVerificationKey(this.acirComposer); | ||
} | ||
|
||
async destroy(): Promise<void> { | ||
if (!this.api) { | ||
return; | ||
} | ||
await this.api.destroy(); | ||
} | ||
} | ||
|
||
export class UltraHonkBackend { | ||
// These type assertions are used so that we don't | ||
// have to initialize `api` in the constructor. | ||
// These are initialized asynchronously in the `init` function, | ||
// constructors cannot be asynchronous which is why we do this. | ||
|
||
protected api!: Barretenberg; | ||
|
||
constructor(protected acirUncompressedBytecode: Uint8Array, protected options: BackendOptions = { threads: 1 }) {} | ||
|
||
/** @ignore */ | ||
async instantiate(): Promise<void> { | ||
if (!this.api) { | ||
if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { | ||
this.options.threads = navigator.hardwareConcurrency; | ||
} else { | ||
try { | ||
const os = await import('os'); | ||
this.options.threads = os.cpus().length; | ||
} catch (e) { | ||
console.log('Could not detect environment. Falling back to one thread.', e); | ||
} | ||
} | ||
const api = await Barretenberg.new(this.options); | ||
const honkRecursion = true; | ||
await api.acirInitSRS(this.acirUncompressedBytecode, honkRecursion); | ||
|
||
// We don't init a proving key here in the Honk API | ||
// await api.acirInitProvingKey(this.acirComposer, this.acirUncompressedBytecode); | ||
this.api = api; | ||
} | ||
} | ||
|
||
async generateProof(uncompressedWitness: Uint8Array): Promise<Uint8Array> { | ||
await this.instantiate(); | ||
return this.api.acirProveUltraHonk(this.acirUncompressedBytecode, uncompressedWitness); | ||
} | ||
|
||
async verifyProof(proof: Uint8Array): Promise<boolean> { | ||
await this.instantiate(); | ||
const vkBuf = await this.api.acirWriteVkUltraHonk(this.acirUncompressedBytecode); | ||
|
||
return await this.api.acirVerifyUltraHonk(proof, new RawBuffer(vkBuf)); | ||
} | ||
|
||
async getVerificationKey(): Promise<Uint8Array> { | ||
await this.instantiate(); | ||
return await this.api.acirWriteVkUltraHonk(this.acirUncompressedBytecode); | ||
} | ||
|
||
// TODO(https://github.com/noir-lang/noir/issues/5661): Update this to handle Honk recursive aggregation in the browser once it is ready in the backend itself | ||
async generateRecursiveProofArtifacts( | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
_proof: Uint8Array, | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
_numOfPublicInputs: number, | ||
): Promise<{ proofAsFields: string[]; vkAsFields: string[]; vkHash: string }> { | ||
await this.instantiate(); | ||
// TODO(https://github.com/noir-lang/noir/issues/5661): This needs to be updated to handle recursive aggregation. | ||
// There is still a proofAsFields method but we could consider getting rid of it as the proof itself | ||
// is a list of field elements. | ||
// UltraHonk also does not have public inputs directly prepended to the proof and they are still instead | ||
// inserted at an offset. | ||
// const proof = reconstructProofWithPublicInputs(proofData); | ||
// const proofAsFields = (await this.api.acirProofAsFieldsUltraHonk(proof)).slice(numOfPublicInputs); | ||
|
||
// TODO: perhaps we should put this in the init function. Need to benchmark | ||
// TODO how long it takes. | ||
const vkBuf = await this.api.acirWriteVkUltraHonk(this.acirUncompressedBytecode); | ||
const vk = await this.api.acirVkAsFieldsUltraHonk(vkBuf); | ||
|
||
return { | ||
// TODO(https://github.com/noir-lang/noir/issues/5661) | ||
proofAsFields: [], | ||
vkAsFields: vk.map(vk => vk.toString()), | ||
// We use an empty string for the vk hash here as it is unneeded as part of the recursive artifacts | ||
// The user can be expected to hash the vk inside their circuit to check whether the vk is the circuit | ||
// they expect | ||
vkHash: '', | ||
}; | ||
} | ||
|
||
async destroy(): Promise<void> { | ||
if (!this.api) { | ||
return; | ||
} | ||
await this.api.destroy(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { BackendOptions, Barretenberg } from './index.js'; | ||
import { RawBuffer } from '../types/raw_buffer.js'; | ||
|
||
// TODO: once UP is removed we can just roll this into the bas `Barretenberg` class. | ||
|
||
export class BarretenbergVerifier { | ||
// These type assertions are used so that we don't | ||
// have to initialize `api` and `acirComposer` in the constructor. | ||
// These are initialized asynchronously in the `init` function, | ||
// constructors cannot be asynchronous which is why we do this. | ||
|
||
private api!: Barretenberg; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
private acirComposer: any; | ||
|
||
constructor(private options: BackendOptions = { threads: 1 }) {} | ||
|
||
/** @ignore */ | ||
async instantiate(): Promise<void> { | ||
if (!this.api) { | ||
if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) { | ||
this.options.threads = navigator.hardwareConcurrency; | ||
} else { | ||
try { | ||
const os = await import('os'); | ||
this.options.threads = os.cpus().length; | ||
} catch (e) { | ||
console.log('Could not detect environment. Falling back to one thread.', e); | ||
} | ||
} | ||
|
||
const api = await Barretenberg.new(this.options); | ||
await api.initSRSForCircuitSize(0); | ||
|
||
this.acirComposer = await api.acirNewAcirComposer(0); | ||
this.api = api; | ||
} | ||
} | ||
|
||
/** @description Verifies a proof */ | ||
async verifyUltraplonkProof(proof: Uint8Array, verificationKey: Uint8Array): Promise<boolean> { | ||
await this.instantiate(); | ||
// The verifier can be used for a variety of ACIR programs so we should not assume that it | ||
// is preloaded with the correct verification key. | ||
await this.api.acirLoadVerificationKey(this.acirComposer, new RawBuffer(verificationKey)); | ||
|
||
return await this.api.acirVerifyProof(this.acirComposer, proof); | ||
} | ||
|
||
/** @description Verifies a proof */ | ||
async verifyUltrahonkProof(proof: Uint8Array, verificationKey: Uint8Array): Promise<boolean> { | ||
await this.instantiate(); | ||
|
||
return await this.api.acirVerifyUltraHonk(proof, new RawBuffer(verificationKey)); | ||
} | ||
|
||
async destroy(): Promise<void> { | ||
if (!this.api) { | ||
return; | ||
} | ||
await this.api.destroy(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,10 @@ | ||
export { Crs } from './crs/index.js'; | ||
export { Barretenberg, BarretenbergSync } from './barretenberg/index.js'; | ||
export { | ||
BackendOptions, | ||
Barretenberg, | ||
BarretenbergSync, | ||
BarretenbergVerifier, | ||
UltraPlonkBackend, | ||
UltraHonkBackend, | ||
} from './barretenberg/index.js'; | ||
export { RawBuffer, Fr } from './types/index.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.