209 lines
5.6 KiB
Nim

## Nim-Codex
## Copyright (c) 2025 Status Research & Development GmbH
## Licensed under either of
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
## at your option.
## This file may not be copied, modified, or distributed except according to
## those terms.
{.push raises: [].}
import std/sugar
import std/isolation
import std/atomics
import pkg/chronos
import pkg/chronos/threadsync
import pkg/taskpools
import pkg/questionable/results
import pkg/groth16
import pkg/nim/circom_witnessgen
import pkg/nim/circom_witnessgen/load
import pkg/nim/circom_witnessgen/witness
import ../../types
import ../../../stores
import ../../../contracts
import ./converters
export converters
const DefaultCurve* = "bn128"
type
NimGroth16Backend* = object
curve: string # curve name
slotDepth: int # max depth of the slot tree
datasetDepth: int # max depth of dataset tree
blkDepth: int # depth of the block merkle tree (pow2 for now)
cellElms: int # number of field elements per cell
numSamples: int # number of samples per slot
r1cs: R1CS # path to the r1cs file
zkey: ZKey # path to the zkey file
graph*: Graph # path to the graph file generated with circom-witnesscalc
tp: Taskpool # taskpool for async operations
NimGroth16BackendRef* = ref NimGroth16Backend
ProofTask* = object
proof: Isolated[Proof]
self: ptr NimGroth16Backend
inputs: Inputs
signal: ThreadSignalPtr
ok: Atomic[bool]
proc release*(self: NimGroth16BackendRef) =
## Release the ctx
##
discard
proc normalizeInput[SomeHash](
self: NimGroth16BackendRef, input: ProofInputs[SomeHash]
): Inputs =
## Map inputs to witnessgen inputs
##
var normSlotProof = input.slotProof
normSlotProof.setLen(self.datasetDepth)
{
"slotDepth": @[self.slotDepth.toF],
"datasetDepth": @[self.datasetDepth.toF],
"blkDepth": @[self.blkDepth.toF],
"cellElms": @[self.cellElms.toF],
"numSamples": @[self.numSamples.toF],
"entropy": @[input.entropy],
"dataSetRoot": @[input.datasetRoot],
"slotIndex": @[input.slotIndex.toF],
"slotRoot": @[input.slotRoot],
"nCellsPerSlot": @[input.nCellsPerSlot.toF],
"nSlotsPerDataSet": @[input.nSlotsPerDataSet.toF],
"slotProof": normSlotProof,
"cellData": input.samples.mapIt(it.cellData).concat,
"merklePaths": input.samples.mapIt(
block:
var mekrlePaths = it.merklePaths
mekrlePaths.setLen(self.slotDepth)
mekrlePaths
).concat,
}.toTable
proc generateProofTask(task: ptr ProofTask) =
defer:
if task[].signal != nil:
discard task[].signal.fireSync()
try:
trace "Generating witness"
let
witnessValues = generateWitness(task[].self[].graph, task[].inputs)
witness = Witness(
curve: task[].self[].curve,
r: task[].self[].r1cs.r,
nvars: task[].self[].r1cs.cfg.nWires,
values: witnessValues,
)
trace "Generating nim groth16 proof"
var proof = generateProof(task[].self[].zkey, witness, task[].self[].tp)
trace "Proof generated, copying to main thread"
var isolatedProof = isolate(proof)
task[].proof = move isolatedProof
task[].ok.store true
except CatchableError as e:
error "Failed to generate proof", err = e.msg
task[].ok.store false
proc prove*[SomeHash](
self: NimGroth16BackendRef, input: ProofInputs[SomeHash]
): Future[?!NimGroth16Proof] {.async: (raises: [CancelledError]).} =
## Prove a statement using backend.
##
var
signalPtr = ?ThreadSignalPtr.new().mapFailure
task = ProofTask(
self: cast[ptr NimGroth16Backend](self),
signal: signalPtr,
inputs: self.normalizeInput(input),
)
defer:
if signalPtr != nil:
?signalPtr.close().mapFailure
signalPtr = nil
self.tp.spawn generateProofTask(task.addr)
let taskFut = signalPtr.wait()
if err =? catch(await taskFut.join()).errorOption:
# XXX: we need this because there is no way to cancel a task
# and without waiting for it to finish, we'll be writting to free'd
# memory in the task
warn "Error while generating proof, awaiting task to finish", err = err.msg
?catch(await noCancel taskFut)
if err of CancelledError: # reraise cancelled error
trace "Task was cancelled"
raise (ref CancelledError) err
trace "Task failed with error", err = err.msg
return failure err
defer:
task.proof = default(Isolated[Proof])
if not task.ok.load:
trace "Task failed, no proof generated"
return failure("Failed to generate proof")
var proof = task.proof.extract
trace "Task finished successfully, proof generated"
success proof
proc verify*(
self: NimGroth16BackendRef, proof: NimGroth16Proof
): Future[?!bool] {.async: (raises: [CancelledError]).} =
let
vKey = self.zkey.extractVKey
verified = ?verifyProof(vKey, proof).catch
success verified
proc new*(
_: type NimGroth16BackendRef,
graphPath: string,
r1csPath: string,
zkeyPath: string,
curve = DefaultCurve,
slotDepth = DefaultMaxSlotDepth,
datasetDepth = DefaultMaxDatasetDepth,
blkDepth = DefaultBlockDepth,
cellElms = DefaultCellElms,
numSamples = DefaultSamplesNum,
tp: Taskpool,
): ?!NimGroth16BackendRef =
## Create a new ctx
##
let
graph = ?loadGraph(graphPath).catch
r1cs = ?parseR1CS(r1csPath).catch
zkey = ?parseZKey(zkeyPath).catch
success NimGroth16BackendRef(
graph: graph,
r1cs: r1cs,
zkey: zkey,
slotDepth: slotDepth,
datasetDepth: datasetDepth,
blkDepth: blkDepth,
cellElms: cellElms,
numSamples: numSamples,
curve: curve,
tp: tp,
)