Refactor backend structure: remove unused files, add NimGroth16 support, and enhance prover functionality

This commit is contained in:
Dmitriy Ryajov 2025-05-28 19:04:03 -06:00
parent d791cd0498
commit f25facd264
No known key found for this signature in database
GPG Key ID: DA8C680CE7C657A4
10 changed files with 349 additions and 263 deletions

View File

@ -1,82 +0,0 @@
import os
import strutils
import pkg/chronos
import pkg/chronicles
import pkg/questionable
import pkg/confutils/defs
import pkg/stew/io2
import pkg/ethers
import ../../conf
import ./backends
import ./backendutils
proc initializeFromConfig(config: CodexConf, utils: BackendUtils): ?!AnyBackend =
if not fileAccessible($config.circomR1cs, {AccessFlags.Read}) or
not endsWith($config.circomR1cs, ".r1cs"):
return failure("Circom R1CS file not accessible")
if not fileAccessible($config.circomWasm, {AccessFlags.Read}) or
not endsWith($config.circomWasm, ".wasm"):
return failure("Circom wasm file not accessible")
if not fileAccessible($config.circomZkey, {AccessFlags.Read}) or
not endsWith($config.circomZkey, ".zkey"):
return failure("Circom zkey file not accessible")
trace "Initialized prover backend from cli config"
success(
utils.initializeCircomBackend(
$config.circomR1cs, $config.circomWasm, $config.circomZkey
)
)
proc r1csFilePath(config: CodexConf): string =
config.circuitDir / "proof_main.r1cs"
proc wasmFilePath(config: CodexConf): string =
config.circuitDir / "proof_main.wasm"
proc zkeyFilePath(config: CodexConf): string =
config.circuitDir / "proof_main.zkey"
proc initializeFromCircuitDirFiles(
config: CodexConf, utils: BackendUtils
): ?!AnyBackend {.gcsafe.} =
if fileExists(config.r1csFilePath) and fileExists(config.wasmFilePath) and
fileExists(config.zkeyFilePath):
trace "Initialized prover backend from local files"
return success(
utils.initializeCircomBackend(
config.r1csFilePath, config.wasmFilePath, config.zkeyFilePath
)
)
failure("Circuit files not found")
proc suggestDownloadTool(config: CodexConf) =
without address =? config.marketplaceAddress:
raise (ref Defect)(
msg: "Proving backend initializing while marketplace address not set."
)
let
tokens = ["cirdl", "\"" & $config.circuitDir & "\"", config.ethProvider, $address]
instructions = "'./" & tokens.join(" ") & "'"
warn "Proving circuit files are not found. Please run the following to download them:",
instructions
proc initializeBackend*(
config: CodexConf, utils: BackendUtils = BackendUtils()
): ?!AnyBackend =
without backend =? initializeFromConfig(config, utils), cliErr:
info "Could not initialize prover backend from CLI options...", msg = cliErr.msg
without backend =? initializeFromCircuitDirFiles(config, utils), localErr:
info "Could not initialize prover backend from circuit dir files...",
msg = localErr.msg
suggestDownloadTool(config)
return failure("CircuitFilesNotFound")
# Unexpected: value of backend does not survive leaving each scope. (definition does though...)
return success(backend)
return success(backend)

View File

@ -1,5 +1,4 @@
import ./backends/circomcompat
import ./backends/nimgroth16
export circomcompat
type AnyBackend* = CircomCompat
export circomcompat, nimgroth16

View File

@ -1,8 +0,0 @@
import ./backends
type BackendUtils* = ref object of RootObj
method initializeCircomBackend*(
self: BackendUtils, r1csFile: string, wasmFile: string, zKeyFile: string
): AnyBackend {.base, gcsafe.} =
CircomCompat.init(r1csFile, wasmFile, zKeyFile)

View File

@ -12,6 +12,7 @@ import pkg/chronos
import pkg/chronicles
import pkg/circomcompat
import pkg/poseidon2
import pkg/taskpools
import pkg/questionable/results
import pkg/libp2p/cid
@ -35,22 +36,24 @@ logScope:
topics = "codex prover"
type
AnyProof* = CircomProof
AnySampler* = Poseidon2Sampler
# add any other generic type here, eg. Poseidon2Sampler | ReinforceConcreteSampler
AnyBuilder* = Poseidon2Builder
# add any other generic type here, eg. Poseidon2Builder | ReinforceConcreteBuilder
AnyProofInputs* = ProofInputs[Poseidon2Hash]
Prover* = ref object of RootObj
backend: AnyBackend
store: BlockStore
Prover* = ref object
case backendKind: ProverBackendCmd
of ProverBackendCmd.nimGroth16:
groth16Backend*: NimGroth16BackendRef
of ProverBackendCmd.circomCompat:
circomCompatBackend*: CircomCompatBackendRef
nSamples: int
tp: Taskpool
proc prove*(
self: Prover, slotIdx: int, manifest: Manifest, challenge: ProofChallenge
): Future[?!(AnyProofInputs, AnyProof)] {.async: (raises: [CancelledError]).} =
AnyBackend* = NimGroth16BackendRef or CircomCompatBackendRef
proc prove*[SomeSampler](
self: Prover,
sampler: SomeSampler,
manifest: Manifest,
challenge: ProofChallenge,
verify = false,
): Future[?!(Groth16Proof, ?bool)] {.async: (raises: [CancelledError]).} =
## Prove a statement using backend.
## Returns a future that resolves to a proof.
@ -61,33 +64,46 @@ proc prove*(
trace "Received proof challenge"
without builder =? AnyBuilder.new(self.store, manifest), err:
error "Unable to create slots builder", err = err.msg
return failure(err)
let
proofInput = ?await sampler.getProofInput(challenge, self.nSamples)
# prove slot
without sampler =? AnySampler.new(slotIdx, self.store, builder), err:
error "Unable to create data sampler", err = err.msg
return failure(err)
without proofInput =? await sampler.getProofInput(challenge, self.nSamples), err:
error "Unable to get proof input for slot", err = err.msg
return failure(err)
# prove slot
without proof =? self.backend.prove(proofInput), err:
error "Unable to prove slot", err = err.msg
return failure(err)
success (proofInput, proof)
proc verify*(
self: Prover, proof: AnyProof, inputs: AnyProofInputs
): Future[?!bool] {.async: (raises: [CancelledError]).} =
## Prove a statement using backend.
## Returns a future that resolves to a proof.
self.backend.verify(proof, inputs)
case self.backendKind
of ProverBackendCmd.nimGroth16:
let
proof = ?await self.groth16Backend.prove(proofInput)
verified =
if verify:
(?await self.groth16Backend.verify(proof)).some
else:
bool.none
return success (proof.toGroth16Proof, verified)
of ProverBackendCmd.circomCompat:
let
proof = ?await self.circomCompatBackend.prove(proofInput)
verified =
if verify:
(?await self.circomCompatBackend.verify(proof, proofInput)).some
else:
bool.none
return success (proof.toGroth16Proof, verified)
proc new*(
_: type Prover, store: BlockStore, backend: AnyBackend, nSamples: int
_: type Prover, backend: CircomCompatBackendRef, nSamples: int, tp: Taskpool
): Prover =
Prover(store: store, backend: backend, nSamples: nSamples)
Prover(
circomCompatBackend: backend,
backendKind: ProverBackendCmd.circomCompat,
nSamples: nSamples,
tp: tp,
)
proc new*(
_: type Prover, backend: NimGroth16BackendRef, nSamples: int, tp: Taskpool
): Prover =
Prover(
groth16Backend: backend,
backendKind: ProverBackendCmd.nimGroth16,
nSamples: nSamples,
tp: tp,
)

View File

@ -0,0 +1,138 @@
{.push raises: [].}
import os
import strutils
import pkg/chronos
import pkg/chronicles
import pkg/questionable
import pkg/confutils/defs
import pkg/stew/io2
import pkg/ethers
import pkg/taskpools
import ../../conf
import ./backends
import ./prover
template graphFilePath(config: CodexConf): string =
config.circuitDir / "proof_main.bin"
template r1csFilePath(config: CodexConf): string =
config.circuitDir / "proof_main.r1cs"
template wasmFilePath(config: CodexConf): string =
config.circuitDir / "proof_main.wasm"
template zkeyFilePath(config: CodexConf): string =
config.circuitDir / "proof_main.zkey"
proc getGraphFile*(config: CodexConf): ?!string =
if fileAccessible($config.circomGraph, {AccessFlags.Read}) and
endsWith($config.circomGraph, ".bin"):
success $config.circomGraph
elif fileAccessible(config.graphFilePath, {AccessFlags.Read}) and
endsWith(config.graphFilePath, ".bin"):
success config.graphFilePath
else:
failure("Graph file not accessible or not found")
proc getR1csFile*(config: CodexConf): ?!string =
if fileAccessible($config.circomR1cs, {AccessFlags.Read}) and
endsWith($config.circomR1cs, ".r1cs"):
success $config.circomR1cs
elif fileAccessible(config.r1csFilePath, {AccessFlags.Read}) and
endsWith(config.r1csFilePath, ".r1cs"):
success config.r1csFilePath
else:
failure("R1CS file not accessible or not found")
proc getWasmFile*(config: CodexConf): ?!string =
if fileAccessible($config.circomWasm, {AccessFlags.Read}) and
endsWith($config.circomWasm, ".wasm"):
success $config.circomWasm
elif fileAccessible(config.wasmFilePath, {AccessFlags.Read}) and
endsWith(config.wasmFilePath, ".wasm"):
success config.wasmFilePath
else:
failure("WASM file not accessible or not found")
proc getZkeyFile*(config: CodexConf): ?!string =
if fileAccessible($config.circomZkey, {AccessFlags.Read}) and
endsWith($config.circomZkey, ".zkey"):
success $config.circomZkey
elif fileAccessible(config.zkeyFilePath, {AccessFlags.Read}) and
endsWith(config.zkeyFilePath, ".zkey"):
success config.zkeyFilePath
else:
failure("ZKey file not accessible or not found")
proc suggestDownloadTool(config: CodexConf) =
without address =? config.marketplaceAddress:
raise (ref Defect)(
msg: "Proving backend initializing while marketplace address not set."
)
let
tokens = ["cirdl", "\"" & $config.circuitDir & "\"", config.ethProvider, $address]
instructions = "'./" & tokens.join(" ") & "'"
warn "Proving circuit files are not found. Please run the following to download them:",
instructions
proc initializeNimGroth16Backend(
config: CodexConf, tp: Taskpool
): ?!NimGroth16BackendRef =
let
graphFile = ?getGraphFile(config)
r1csFile = ?getR1csFile(config)
zkeyFile = ?getZkeyFile(config)
return NimGroth16BackendRef.new(
$graphFile,
$r1csFile,
$zkeyFile,
config.nimGroth16Curve,
config.maxSlotDepth,
config.maxDatasetDepth,
config.maxBlockDepth,
config.maxCellElms,
config.numProofSamples,
tp,
)
proc initializeCircomCompatBackend(
config: CodexConf, tp: Taskpool
): ?!CircomCompatBackendRef =
let
r1csFile = ?getR1csFile(config)
wasmFile = ?getWasmFile(config)
zkeyFile = ?getZkeyFile(config)
return CircomCompatBackendRef.new(
$r1csFile,
$wasmFile,
$zkeyFile,
config.maxSlotDepth,
config.maxDatasetDepth,
config.maxBlockDepth,
config.maxCellElms,
config.numProofSamples,
)
proc initializeProver*(config: CodexConf, tp: Taskpool): ?!Prover =
let prover =
case config.proverBackendCmd
of ProverBackendCmd.nimGroth16:
without backend =? initializeNimGroth16Backend(config, tp), err:
suggestDownloadTool(config)
return failure("Unable to initialize NimGroth16 backend")
Prover.new(backend, config.numProofSamples, tp)
of ProverBackendCmd.circomCompat:
without backend =? initializeCircomCompatBackend(config, tp), err:
suggestDownloadTool(config)
return failure("Unable to initialize CircomCompat backend")
Prover.new(backend, config.numProofSamples, tp)
success prover

View File

@ -6,6 +6,7 @@ import pkg/libp2p/cid
import pkg/codex/codextypes
import pkg/codex/stores
import pkg/codex/merkletree
import pkg/codex/utils/poseidon2digest
import pkg/codex/manifest
import pkg/codex/blocktype as bt
import pkg/codex/chunker

View File

@ -1,97 +0,0 @@
import os
import ../../asynctest
import pkg/chronos
import pkg/confutils/defs
import pkg/codex/conf
import pkg/codex/slots/proofs/backends
import pkg/codex/slots/proofs/backendfactory
import pkg/codex/slots/proofs/backendutils
import pkg/codex/utils/natutils
import ../helpers
import ../examples
type BackendUtilsMock = ref object of BackendUtils
argR1csFile: string
argWasmFile: string
argZKeyFile: string
method initializeCircomBackend*(
self: BackendUtilsMock, r1csFile: string, wasmFile: string, zKeyFile: string
): AnyBackend =
self.argR1csFile = r1csFile
self.argWasmFile = wasmFile
self.argZKeyFile = zKeyFile
# We return a backend with *something* that's not nil that we can check for.
var
key = VerifyingKey(icLen: 123)
vkpPtr: ptr VerifyingKey = key.addr
return CircomCompat(vkp: vkpPtr)
suite "Test BackendFactory":
let
utilsMock = BackendUtilsMock()
circuitDir = "testecircuitdir"
setup:
createDir(circuitDir)
teardown:
removeDir(circuitDir)
test "Should create backend from cli config":
let
config = CodexConf(
cmd: StartUpCmd.persistence,
nat: NatConfig(hasExtIp: false, nat: NatNone),
metricsAddress: parseIpAddress("127.0.0.1"),
persistenceCmd: PersistenceCmd.prover,
marketplaceAddress: EthAddress.example.some,
circomR1cs: InputFile("tests/circuits/fixtures/proof_main.r1cs"),
circomWasm: InputFile("tests/circuits/fixtures/proof_main.wasm"),
circomZkey: InputFile("tests/circuits/fixtures/proof_main.zkey"),
)
backend = config.initializeBackend(utilsMock).tryGet
check:
backend.vkp != nil
utilsMock.argR1csFile == $config.circomR1cs
utilsMock.argWasmFile == $config.circomWasm
utilsMock.argZKeyFile == $config.circomZkey
test "Should create backend from local files":
let
config = CodexConf(
cmd: StartUpCmd.persistence,
nat: NatConfig(hasExtIp: false, nat: NatNone),
metricsAddress: parseIpAddress("127.0.0.1"),
persistenceCmd: PersistenceCmd.prover,
marketplaceAddress: EthAddress.example.some,
# Set the circuitDir such that the tests/circuits/fixtures/ files
# will be picked up as local files:
circuitDir: OutDir("tests/circuits/fixtures"),
)
backend = config.initializeBackend(utilsMock).tryGet
check:
backend.vkp != nil
utilsMock.argR1csFile == config.circuitDir / "proof_main.r1cs"
utilsMock.argWasmFile == config.circuitDir / "proof_main.wasm"
utilsMock.argZKeyFile == config.circuitDir / "proof_main.zkey"
test "Should suggest usage of downloader tool when files not available":
let
config = CodexConf(
cmd: StartUpCmd.persistence,
nat: NatConfig(hasExtIp: false, nat: NatNone),
metricsAddress: parseIpAddress("127.0.0.1"),
persistenceCmd: PersistenceCmd.prover,
marketplaceAddress: EthAddress.example.some,
circuitDir: OutDir(circuitDir),
)
backendResult = config.initializeBackend(utilsMock)
check:
backendResult.isErr

View File

@ -1,3 +1,4 @@
import ./backends/testcircomcompat
import ./backends/testnimgroth16
{.warning[UnusedImport]: off.}

View File

@ -13,17 +13,19 @@ import pkg/confutils/defs
import pkg/poseidon2/io
import pkg/codex/utils/poseidon2digest
import pkg/codex/nat
import pkg/taskpools
import pkg/codex/utils/natutils
import ./helpers
import ../helpers
suite "Test Prover":
suite "Test CircomCompat Prover":
let
samples = 5
blockSize = DefaultBlockSize
cellSize = DefaultCellSize
repoTmp = TempLevelDb.new()
metaTmp = TempLevelDb.new()
tp = Taskpool.new()
challenge = 1234567.toF.toBytes.toArray32
var
@ -34,55 +36,60 @@ suite "Test Prover":
let
repoDs = repoTmp.newDb()
metaDs = metaTmp.newDb()
config = CodexConf(
cmd: StartUpCmd.persistence,
nat: NatConfig(hasExtIp: false, nat: NatNone),
metricsAddress: parseIpAddress("127.0.0.1"),
persistenceCmd: PersistenceCmd.prover,
circomR1cs: InputFile("tests/circuits/fixtures/proof_main.r1cs"),
circomWasm: InputFile("tests/circuits/fixtures/proof_main.wasm"),
circomZkey: InputFile("tests/circuits/fixtures/proof_main.zkey"),
numProofSamples: samples,
)
backend = config.initializeBackend().tryGet()
backend = CircomCompatBackendRef.new(
r1csPath = "tests/circuits/fixtures/proof_main.r1cs",
wasmPath = "tests/circuits/fixtures/proof_main.wasm",
zkeyPath = "tests/circuits/fixtures/proof_main.zkey",
).tryGet
tp = Taskpool.new()
store = RepoStore.new(repoDs, metaDs)
prover = Prover.new(store, backend, config.numProofSamples)
prover = Prover.new(backend, samples, tp)
teardown:
await repoTmp.destroyDb()
await metaTmp.destroyDb()
test "Should sample and prove a slot":
let (_, _, verifiable) = await createVerifiableManifest(
store,
8, # number of blocks in the original dataset (before EC)
5, # ecK
3, # ecM
blockSize,
cellSize,
)
let
(_, _, verifiable) = await createVerifiableManifest(
store,
8, # number of blocks in the original dataset (before EC)
5, # ecK
3, # ecM
blockSize,
cellSize,
)
let (inputs, proof) = (await prover.prove(1, verifiable, challenge)).tryGet
builder =
Poseidon2Builder.new(store, verifiable, verifiable.verifiableStrategy).tryGet
sampler = Poseidon2Sampler.new(1, store, builder).tryGet
(_, checked) =
(await prover.prove(sampler, verifiable, challenge, verify = true)).tryGet
check:
(await prover.verify(proof, inputs)).tryGet == true
checked.isSome and checked.get == true
test "Should generate valid proofs when slots consist of single blocks":
# To get single-block slots, we just need to set the number of blocks in
# the original dataset to be the same as ecK. The total number of blocks
# after generating random data for parity will be ecK + ecM, which will
# match the number of slots.
let (_, _, verifiable) = await createVerifiableManifest(
store,
2, # number of blocks in the original dataset (before EC)
2, # ecK
1, # ecM
blockSize,
cellSize,
)
let
(_, _, verifiable) = await createVerifiableManifest(
store,
2, # number of blocks in the original dataset (before EC)
2, # ecK
1, # ecM
blockSize,
cellSize,
)
let (inputs, proof) = (await prover.prove(1, verifiable, challenge)).tryGet
builder =
Poseidon2Builder.new(store, verifiable, verifiable.verifiableStrategy).tryGet
sampler = Poseidon2Sampler.new(1, store, builder).tryGet
(_, checked) =
(await prover.prove(sampler, verifiable, challenge, verify = true)).tryGet
check:
(await prover.verify(proof, inputs)).tryGet == true
checked.isSome and checked.get == true

View File

@ -0,0 +1,111 @@
import os
import ../../asynctest
import pkg/chronos
import pkg/taskpools
import pkg/confutils/defs
import pkg/codex/conf
import pkg/codex/slots/proofs/backends
import pkg/codex/slots/proofs/proverfactory {.all.}
import pkg/codex/utils/natutils
import ../helpers
import ../examples
suite "Test BackendFactory":
let circuitDir = "testecircuitdir"
setup:
createDir(circuitDir)
teardown:
removeDir(circuitDir)
test "Should initialize with correct nimGroth16 config files":
let config = CodexConf(
cmd: StartUpCmd.persistence,
nat: NatConfig(hasExtIp: false, nat: NatNone),
metricsAddress: parseIpAddress("127.0.0.1"),
persistenceCmd: PersistenceCmd.prover,
marketplaceAddress: EthAddress.example.some,
proverBackendCmd: ProverBackendCmd.nimGroth16,
circomGraph: InputFile("tests/circuits/fixtures/proof_main.bin"),
circomR1cs: InputFile("tests/circuits/fixtures/proof_main.r1cs"),
circomZkey: InputFile("tests/circuits/fixtures/proof_main.zkey"),
)
check:
getGraphFile(config).tryGet == $config.circomGraph
getR1csFile(config).tryGet == $config.circomR1cs
getZkeyFile(config).tryGet == $config.circomZkey
test "Should initialize with correct circom compat config files":
let config = CodexConf(
cmd: StartUpCmd.persistence,
nat: NatConfig(hasExtIp: false, nat: NatNone),
metricsAddress: parseIpAddress("127.0.0.1"),
persistenceCmd: PersistenceCmd.prover,
marketplaceAddress: EthAddress.example.some,
proverBackendCmd: ProverBackendCmd.circomCompat,
circomWasm: InputFile("tests/circuits/fixtures/proof_main.wasm"),
circomR1cs: InputFile("tests/circuits/fixtures/proof_main.r1cs"),
circomZkey: InputFile("tests/circuits/fixtures/proof_main.zkey"),
)
check:
getWasmFile(config).tryGet == $config.circomWasm
getR1csFile(config).tryGet == $config.circomR1cs
getZkeyFile(config).tryGet == $config.circomZkey
test "Should initialize circom compat from local directory":
let config = CodexConf(
cmd: StartUpCmd.persistence,
nat: NatConfig(hasExtIp: false, nat: NatNone),
metricsAddress: parseIpAddress("127.0.0.1"),
persistenceCmd: PersistenceCmd.prover,
marketplaceAddress: EthAddress.example.some,
proverBackendCmd: ProverBackendCmd.circomCompat,
# Set the circuitDir such that the tests/circuits/fixtures/ files
# will be picked up as local files:
circuitDir: OutDir("tests/circuits/fixtures"),
)
check:
getR1csFile(config).tryGet == config.circuitDir / "proof_main.r1cs"
getWasmFile(config).tryGet == config.circuitDir / "proof_main.wasm"
getZKeyFile(config).tryGet == config.circuitDir / "proof_main.zkey"
test "Should initialize nim groth16 from local directory":
let config = CodexConf(
cmd: StartUpCmd.persistence,
nat: NatConfig(hasExtIp: false, nat: NatNone),
metricsAddress: parseIpAddress("127.0.0.1"),
persistenceCmd: PersistenceCmd.prover,
marketplaceAddress: EthAddress.example.some,
proverBackendCmd: ProverBackendCmd.nimGroth16,
# Set the circuitDir such that the tests/circuits/fixtures/ files
# will be picked up as local files:
circuitDir: OutDir("tests/circuits/fixtures"),
)
check:
getGraphFile(config).tryGet == config.circuitDir / "proof_main.bin"
getR1csFile(config).tryGet == config.circuitDir / "proof_main.r1cs"
getZKeyFile(config).tryGet == config.circuitDir / "proof_main.zkey"
test "Should suggest usage of downloader tool when files not available":
let
config = CodexConf(
cmd: StartUpCmd.persistence,
nat: NatConfig(hasExtIp: false, nat: NatNone),
metricsAddress: parseIpAddress("127.0.0.1"),
persistenceCmd: PersistenceCmd.prover,
proverBackendCmd: ProverBackendCmd.nimGroth16,
marketplaceAddress: EthAddress.example.some,
circuitDir: OutDir(circuitDir),
)
proverResult = config.initializeProver(Taskpool.new())
check:
proverResult.isErr