initial version of the test input generator (generating inputs for the RLN circuit)

This commit is contained in:
Balazs Komuves 2026-01-15 17:03:22 +01:00
parent c671b860fe
commit 91d11cda21
No known key found for this signature in database
GPG Key ID: F63B7AEF18435562
20 changed files with 816 additions and 29 deletions

View File

@ -13,15 +13,59 @@ Our [`circom`](https://docs.circom.io/) circuit is very similar to the one in [t
TODO after the implementation :)
We use the default circuit parameters:
- `LIMIT_BITS = 16` (max 65536 messages per epoch per user)
- `MERKLE_DEPTH = 20` (max 1 million registered users)
Circuit sizes
- witness size = 5637
- unchanging = 5123
- remaining = 514
So we can see that only less than 10% of the circuit is changing at every proof generation, the rest is changing when a new user registers (and thus the Merkle tree changes).
### Differences from the PSE circuit
Note that we are not generic in the curve/field choice, requiring BN254 curve. This is only a limitation of the implementation of this PoC, it would work exactly the same with eg. BLS12-381.
Note that we are not generic in the curve/field choice, requiring the BN254 curve. This is only a limitation of the implementation of this PoC, it would work exactly the same with eg. BLS12-381.
Actual differences:
- we use Poseidon2 hash instead of `circomlib`'s Poseidon
- we only use the Poseidon2 permutation with fixed width `t=3` (for simplicity)
- when computing `a1`, we use the formula `a1 := H(sk+j|ext)` instead of `H(sk|ext|j)`, as this results in one less hash invocation (but shouldn't really cause any difference)
- when computing `a1`, we use the formula `a1 := H(sk+j|ext)` instead of `H(sk|ext|j)`, as this results in one less `t=3` hash invocation (but this shouldn't really cause any issues, as`sk` is authenticated and `j` is range checked)
- the Merkle root is an input, not an output (you won't forget to check out externally)
- we input the Merkle leaf index directly, not as bits (you need check them to be bits anyway, so this is essentially free; and somewhat simpler to use)
- no external dependencies
Remarks:
1. if one feels "uneasy" with `sk+j` in `a1`, an alternative while still keeping `t=3` would be to use `a1 = H(H(sk|ext)|j)`. This is more expensive (an extra hash invocation), but the inner hash `H(sk|ext)` only changes once per epoch. So technically one could do a three-layered computation: Precompute most of the things when the Merkle root changes; precompute this one hash at the start of epoch; and finish at each message. We don't implement this 3 layers here, as it would add a lot of complexity.
2. the computation `local_null = H(a1)` could be optimized by using a `t=2` Poseidon instance (instead of `t=3`). We don't do that here because the lack of pre-made such Poseidon2 instance.
### Circuit I/O
Public inputs:
- `"merkle_root"` (rarely changes)
- `"ext_null"` (changes once per epoch)
- `"msg_hash"` (changes at each message)
Private inputs:
- `"secret_key"` (never changes)
- `"msg_limit"` (never changes)
- `"leaf_idx"` (normally never changes, though I guess in theory the registry could "garbage collect" once a while)
- `"merkle_path"` (changes only when the Merkle root changes)
- `"msg_idx"` (changes at each message)
Public outputs:
- `"y_value"`
- `"local_null"`
We want to precalculate the part of the witness which only depends on the rarely changing inputs, including `"merkle_path"`.
### Partial witness generation
@ -58,4 +102,3 @@ where $\mathcal{F}\subset[1\dots M]$ is the set of witness indices which are unc
The only other significant computation is computing the quotient polynomial; that's usually done with FFT. Some part of that can be partially precomputed, but probably won't give a significant speedup.

View File

@ -34,6 +34,9 @@ template RangeCheck(limit_bits) {
signal input what;
signal input limit;
assert( limit > 0 );
assert( limit <= 2**limit_bits );
_ <== ToBits(limit_bits)( what ); // 0 <= what < 2^limit_bits
_ <== ToBits(limit_bits)( limit - 1 - what ); // 0 <= limit - 1 - what < 2^limit_bits

View File

@ -8,9 +8,9 @@ include "misc.circom";
template RLN(limit_bits, merkle_depth) {
// public input
signal input msg_hash; // x = hash of the message
signal input ext_null; // the external "nullifier" ext = H(protocol|epoch)
signal input merkle_root; // the Merkle root we check against
signal input ext_null; // the external "nullifier" ext = H(protocol|epoch)
signal input msg_hash; // x = hash of the message
// private input
signal input secret_key; // the user's secret key
@ -24,11 +24,10 @@ template RLN(limit_bits, merkle_depth) {
signal output local_null; // the "epoch-local nullifier" null = H(a1) (to detect repeated a1)
// computations
signal pk <== Compress()( secret_key , msg_limit ); // public key - this doesn't ever change
signal pre_a1 <== Compress()( secret_key , ext_null ); // H(sk|ext) - this doesn't change often
signal a1 <== Compress()( pre_a1 , msg_idx ); // a1 = H(H(sk|ext)|j)
local_null <== Compress()( a1 , 0 ); // H(a1);
y_value <== secret_key + msg_hash * a1; // y = sk + x*a1
signal pk <== Compress()( secret_key , msg_limit ); // public key - this doesn't ever change
signal a1 <== Compress()( secret_key + msg_idx , ext_null ); // a1 = Hs(sk+j|ext)
local_null <== Compress()( a1 , 0 ); // H(a1);
y_value <== secret_key + msg_hash * a1; // y = sk + x*a1
// checks
RangeCheck (limit_bits) ( msg_idx , msg_limit ); // range check for the message index

4
test-input/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.DS_Store
cli
*.circom
*.json

12
test-input/README.md Normal file
View File

@ -0,0 +1,12 @@
create test input data for `rln-fast`
-------------------------------------
Quickstart:
$ nimble build cli
$ ./cli --help
Example:
$ ./cli -v --merkle_depth=18 --limit_bits=12 --circom=main.circom --output=input.json

174
test-input/src/cli.nim Normal file
View File

@ -0,0 +1,174 @@
{. warning[UnusedImport]:off .}
import sugar
import std/strutils
import std/sequtils
import std/os
import std/parseopt
import std/random
import types
import json/bn254
import gen_inputs
import misc
#-------------------------------------------------------------------------------
type FullConfig = object
globCfg: GlobalConfig
seed: int64
outFile: string
circomFile: string
partial: bool
verbose: bool
how_many: int
const defaultFullCfg =
FullConfig( globCfg: defaultGlobalConfig
, seed: 0
, outFile: ""
, circomFile: ""
, partial: false
, verbose: false
, how_many: 1
)
#-------------------------------------------------------------------------------
proc printHelp() =
echo "usage:"
echo "$ ./cli [options] --output=proof_input.json --circom=proof_main.circom"
echo ""
echo "available options:"
echo " -h, --help : print this help"
echo " -v, --verbose : verbose output (print the actual parameters)"
echo " -d, --merkle_depth = <depth> : Merkle tree depth (default: 20)"
echo " -b, --limit_bits = <bits> : log2 of maximum number of messages per epoch (default: 16)"
echo " -s, --seed = <seed> : seed to generate the fake data (eg. 12345; default: random)"
echo " -o, --output = <input.json> : the JSON file into which we write the proof inputs"
echo " -c, --circom = <main.circom> : the circom main component to create with these parameters"
echo " -n, --count = <K> : generate K proof inputs at the same time (instead of 1)"
echo " -p, --partial : generate partial input"
echo ""
quit()
#-------------------------------------------------------------------------------
proc parseCliOptions(): FullConfig =
var globCfg = defaultGlobalConfig
var fullCfg = defaultFullCfg
# randomize the seed
fullCfg.seed = rand(int64)
var argCtr: int = 0
for kind, key, value in getOpt():
case kind
# Positional arguments
of cmdArgument:
# echo ("arg #" & $argCtr & " = " & key)
argCtr += 1
# Switches
of cmdLongOption, cmdShortOption:
case key
of "h", "help" : printHelp()
of "v", "verbose" : fullCfg.verbose = true
of "d", "merkle_depth" : globCfg.merkle_depth = parseInt(value)
of "b", "limit_bits" : globCfg.limit_bits = parseInt(value)
of "s", "seed" : fullCfg.seed = int64(parseInt(value))
of "o", "output" : fullCfg.outFile = value
of "c", "circom" : fullCfg.circomFile = value
of "p", "partial" : fullCfg.partial = true
of "n", "count" : fullCfg.how_many = parseInt(value)
else:
echo "Unknown option: ", key
echo "use --help to get a list of options"
quit()
of cmdEnd:
discard
fullCfg.globCfg = globCfg
assert( globCfg.merkle_depth >= 2 and globCfg.merkle_depth <= 22 )
assert( globCfg.limit_bits >= 2 and globCfg.limit_bits <= 32 )
assert( fullCfg.how_many >= 1 and fullCfg.how_many <= 100 )
return fullCfg
#-------------------------------------------------------------------------------
proc printConfig(fullCfg: FullConfig) =
let globCfg = fullCfg.globCfg
# echo "field = BN254"
# echo "hash function = Poseidon2"
echo "merkle_depth = " & ($globCfg.merkle_depth)
echo "limit_bits = " & ($globCfg.limit_bits)
echo "random seed = " & ($fullCfg.seed)
echo "partial = " & ($fullCfg.partial)
echo "how many inputs = " & ($fullCfg.how_many)
#-------------------------------------------------------------------------------
proc writeCircomMainComponent(fullCfg: FullConfig, fname: string) =
let params: (int,int) =
( fullCfg.globCfg.limit_bits
, fullCfg.globCfg.merkle_depth
)
let f = open(fname, fmWrite)
defer: f.close()
f.writeLine("pragma circom 2.1.1;")
f.writeLine("include \"rln_proof.circom\";")
f.writeLine("// argument order convention: RLN(limit_bits, merkle_depth)")
f.writeLine("component main {public [msg_hash, ext_null, merkle_root]} = RLN" & ($params) & ";")
#-------------------------------------------------------------------------------
when isMainModule:
randomize()
let fullCfg = parseCliOptions()
let globCfg = fullCfg.globCfg
# now use the set seed
randomize(fullCfg.seed)
if fullCfg.verbose:
printConfig(fullCfg)
if fullCfg.circomFile == "" and fullCfg.outFile == "":
echo "nothing to do!"
echo "use --help for getting a list of options"
quit()
if fullCfg.circomFile != "":
let fname = fullCfg.circomFile
echo "writing circom main component into `" & fname & "`"
writeCircomMainComponent(fullCfg, fname)
if fullCfg.outFile != "":
let fname = fullCfg.outFile
let secretTree = genTreeWithSecrets( globCfg )
let prfInput = genProofInput( globCfg , secretTree )
if fullCfg.partial:
echo "writing partial proof input into `" & fname & "`..."
let partial = extractPartialInputs( prfInput )
exportPartialInput( fname, partial )
else:
echo "writing full proof input into `" & fname & "`..."
exportProofInput( fname, prfInput )
echo "done"

View File

@ -0,0 +1,108 @@
import std/random
import poseidon2/compress
import ./types
import ./misc
import ./merkle
import ./simulate
#-------------------------------------------------------------------------------
type
Leaf* = object
secret_key* : F
public_key* : F
msg_limit* : int
TreeWithSecrets* = object
leaves* : seq[Leaf]
tree* : MerkleTree
#-------------------------------------------------------------------------------
proc genTreeWithSecrets*(globCfg: GlobalConfig): TreeWithSecrets =
let N : int = pow2(globCfg.merkle_depth)
let M : int = pow2(globCfg.limit_bits )
let maxMsgLimit = M - 1
var public_keys : seq[F] = newSeq[F ]( N )
var leaves : seq[Leaf] = newSeq[Leaf]( N )
for i in 0..<N:
let secret_key = randomF()
let msg_limit = min( 1 , rand(M) )
let public_key = compress( secret_key, intToBN254(msg_limit) );
assert( msg_limit <= M , "msg limit hard bound failed")
let leaf = Leaf( secret_key : secret_key
, public_key : public_key
, msg_limit : msg_limit
)
public_keys[i] = public_key
leaves[i] = leaf
let tree = buildMerkleTree( public_keys )
return TreeWithSecrets(leaves: leaves, tree: tree)
#-------------------------------------------------------------------------------
proc genProofInput*( globCfg: GlobalConfig, secretTree: TreeWithSecrets): ProofInput =
let tree = secretTree.tree
let N : int = pow2(globCfg.merkle_depth)
let maxLeafIdx : int = N - 1
let leaf_idx : int = rand(maxLeafIdx)
let leaf = secretTree.leaves[leaf_idx]
let msg_limit : int = leaf.msg_limit
let msg_idx : int = rand( msg_limit - 1 )
assert( leaf_idx < N )
assert( msg_idx < msg_limit )
let ext_null = randomF()
let msg_hash = randomF()
let merkle_proof = getMerkleInclusionProof(tree, leaf_idx)
let merkle_root = extractMerkleRoot(tree)
let merkle_path = merkle_proof.merklePath
assert( checkMerkleInclusionProof( merkle_root , merkle_proof ) , "merkle inclusion proof sanity check failed" )
let proof_inputs = ProofInput(
# public inputs
merkle_root : merkle_root
, ext_null : ext_null
, msg_hash : msg_hash
# private inputs
, secret_key : leaf.secret_key
, msg_limit : msg_limit
, leaf_idx : leaf_idx
, merkle_path : merkle_path
, msg_idx : msg_idx
)
let ok = sanityCheckRLNProof( globCfg, proof_inputs, false )
assert( ok , "RLN proof sanity check failed" )
return proof_inputs
#-------------------------------------------------------------------------------
proc genManyProofInputs*( globCfg: GlobalConfig, secretTree: TreeWithSecrets, K: int): seq[ProofInput] =
var inputs : seq[ProofInput] = newSeq[ProofInput]( K )
for i in 0..<K:
inputs[i] = genProofInput( globCfg, secretTree )
return inputs
#-------------------------------------------------------------------------------

View File

@ -0,0 +1,66 @@
#
# export the proof inputs as a JSON file suitable for `snarkjs`
#
import std/streams
import ../types
import shared
#-------------------------------------------------------------------------------
proc writeFieldElems(h: Stream, prefix: string, xs: seq[F]) =
writeList[F]( h, prefix, xs, writeLnF )
#-------------------------------------------------------------------------------
proc writeSingleMerklePath(h: Stream, prefix: string, path: seq[F]) =
writeFieldElems(h, prefix, path)
#-------------------------------------------------------------------------------
#[
// public input
signal input merkle_root; // the Merkle root we check against
signal input ext_null; // the external "nullifier" ext = H(protocol|epoch)
signal input msg_hash; // x = hash of the message
// private input
signal input secret_key; // the user's secret key
signal input msg_limit; // message limit per epoch
signal input msg_idx; // the message index (should be 0 <= msg_idx < msg_limit)
signal input leaf_idx; // leaf index in the Merkle tree
signal input merkle_path[merkle_depth]; // the Merkle inclusion proof
]#
proc exportProofInput*(fname: string, prfInput: ProofInput) =
let h = openFileStream(fname, fmWrite)
defer: h.close()
h.writeLine("{")
h.writeLine(" \"merkle_root\": " & toQuotedDecimalF(prfInput.merkle_root) )
h.writeLine(", \"ext_null\": " & toQuotedDecimalF(prfInput.ext_null ) )
h.writeLine(", \"msg_hash\": " & toQuotedDecimalF(prfInput.msg_hash ) )
h.writeLine(", \"secret_key\": " & toQuotedDecimalF(prfInput.secret_key ) )
h.writeLine(", \"msg_limit\": " & toQuotedDecimalF(intToBN254(prfInput.msg_limit)) )
h.writeLine(", \"msg_idx\": " & toQuotedDecimalF(intToBN254(prfInput.msg_idx )) )
h.writeLine(", \"leaf_idx\": " & toQuotedDecimalF(intToBN254(prfInput.leaf_idx )) )
h.writeLine(", \"merkle_path\":")
writeSingleMerklePath(h, " ", prfInput.merkle_path )
h.writeLine("}")
proc exportPartialInput*(fname: string, prfInput: PartialInput) =
let h = openFileStream(fname, fmWrite)
defer: h.close()
h.writeLine("{")
h.writeLine(" \"merkle_root\": " & toQuotedDecimalF(prfInput.merkle_root) )
h.writeLine(", \"secret_key\": " & toQuotedDecimalF(prfInput.secret_key ) )
h.writeLine(", \"msg_limit\": " & toQuotedDecimalF(intToBN254(prfInput.msg_limit)) )
h.writeLine(", \"leaf_idx\": " & toQuotedDecimalF(intToBN254(prfInput.leaf_idx )) )
h.writeLine(", \"merkle_path\":")
writeSingleMerklePath(h, " ", prfInput.merkle_path )
h.writeLine("}")
#-------------------------------------------------------------------------------

View File

@ -0,0 +1,48 @@
import std/strutils
import std/streams
#-------------------------------------------------------------------------------
func mkIndent*(foo: string): string =
return spaces(foo.len)
#-------------------------------------------------------------------------------
type
WriteFun*[T] = proc (stream: Stream, prefix: string, what: T) {.closure.}
proc writeList*[T](h: Stream, prefix: string, xs: seq[T], writeFun: WriteFun[T]) =
let n = xs.len
let indent = mkIndent(prefix)
for i in 0..<n:
if i==0:
writeFun(h, prefix & "[ ", xs[i])
else:
writeFun(h, indent & ", ", xs[i])
h.writeLine( indent & "]" )
#---------------------------------------
proc writeListList*[T](h: Stream, prefix: string, xs: seq[seq[T]], writeFun: WriteFun[T]) =
let n = xs.len
let indent = mkIndent(prefix)
for i in 0..<n:
if i==0:
h.write( prefix & "[ ")
else:
h.write( indent & ", ")
let ys = xs[i]
let m = ys.len
for j in 0..<m:
if j==0:
writeFun(h, "" , ys[j])
else:
writeFun(h, " , ", ys[j])
h.write("\n")
h.writeLine( indent & "]" )
#-------------------------------------------------------------------------------

115
test-input/src/merkle.nim Normal file
View File

@ -0,0 +1,115 @@
import poseidon2/compress
import ./types
import ./misc
#-------------------------------------------------------------------------------
type
MerkleProof* = object
leafIndex* : int # linear index of the leaf, starting from 0
leafHash* : F # (hash of the value of the leaf
merklePath* : seq[F] # order: from the bottom to the top
depth* : int # depth of the tree
# note: the first layer is the bottom layer, and the last layer is the root
MerkleTree* = object
layers* : seq[seq[F]]
#-------------------------------------------------------------------------------
func merkleTreeDepth*(tree: MerkleTree): int = tree.layers.len - 1
func extractMerkleRoot*(tree: MerkleTree): Hash =
let n = tree.layers.len
let xs = tree.layers[n-1]
assert( xs.len == 1 , "merkle root is not a singleton" )
return xs[0]
#-------------------------------------------------------------------------------
proc getMerkleInclusionProof*(tree: MerkleTree, leaf_idx: int): MerkleProof =
let N = tree.layers[0].len
let D = merkleTreeDepth( tree )
assert( pow2(D) == N , "not a full binary tree or depth/size inconsistency" )
assert( 0 <= leaf_idx and leaf_idx < N , "leaf index out of bounds")
var idx : int = leaf_idx
var path : seq[F] = newSeq[F]( D )
for k in 0..<D:
let is_even = isEven(idx)
let j = (idx shr 1)
if is_even:
path[k] = tree.layers[k][2*j+1]
else:
path[k] = tree.layers[k][2*j]
idx = j
return MerkleProof( leafIndex : leaf_idx
, leafHash : tree.layers[0][leaf_idx]
, merklePath : path
, depth : D
)
#-------------------------------------------------------------------------------
func checkMerkleInclusionProof*(expected_root: F, proof: MerkleProof): bool =
let path: seq[F] = proof.merklePath
var this: F = proof.leafHash
var idx: int = proof.leafIndex
for i in 0..<proof.depth:
var next : F
if isEven(idx):
next = compress( this , path[i] )
else:
next = compress( path[i] , this )
idx = (idx shr 1)
this = next
return (expected_root == this)
#-------------------------------------------------------------------------------
proc buildMerkleTree*( leaf_hashes: seq[F] ): MerkleTree =
let zeroF : F = intToBN254(0)
let depth : int = ceilingLog2( leaf_hashes.len )
var layers : seq[seq[F]] = newSeq[seq[F]](depth+1)
layers[0] = leaf_hashes
var N : int = leaf_hashes.len
var prev : seq[F] = leaf_hashes
var i : int = 0;
while( N > 1 ):
i += 1
let H = (N+1) div 2
let is_even = (H+H == N)
var next: seq[F] = newSeq[F](H)
for k in 0..<H:
let j = 2*k
if j+1 < N:
next[k] = compress( prev[j] , prev[j+1] )
else:
next[k] = compress( prev[j] , zeroF )
layers[i] = next
prev = next
N = H
return MerkleTree(layers: layers)
#-------------------------------------------------------------------------------

48
test-input/src/misc.nim Normal file
View File

@ -0,0 +1,48 @@
#
# helper functions
#
import std/math
import std/bitops
#-------------------------------------------------------------------------------
func isEven*(x : int): bool =
let y = bitand[int]( x , 1 )
return (y == 0)
func isOdd*(x : int): bool =
let y = bitand[int]( x , 1 )
return (y != 0)
#-------------------------------------------------------------------------------
func floorLog2* (x: int): int =
var k = -1
var y = x
while (y > 0):
k += 1
y = y shr 1
return k
func ceilingLog2* (x: int): int =
if (x==0):
return -1
else:
return (floorLog2(x-1) + 1)
func exactLog2*(x: int): int =
let k = ceilingLog2(x)
assert( x == 2^k, "exactLog2: not a power of two" )
return k
func checkPowerOfTwo*(x: int , what: string): int =
let k = ceilingLog2(x)
assert( x == 2^k, ("`" & what & "` is expected to be a power of 2") )
return x
# wtf Nim, serously
func pow2*(k: int): int = 2^k
#-------------------------------------------------------------------------------

View File

@ -0,0 +1,60 @@
# simulate to RLN proof for sanity checking
import std/options
import poseidon2/types
import poseidon2/compress
import ./types
import ./merkle
import ./misc
#-------------------------------------------------------------------------------
type
ProofOutput* = object
y_value* : F
local_null* : F
proc simulateRLNProof*( globCfg: GlobalConfig, inp: ProofInput, verbose: bool): Option[ProofOutput] =
if inp.msg_limit < 1 or inp.msg_limit > pow2(globCfg.limit_bits):
if verbose:
echo "rln proof: FATAL: message upper bound not fitting into limit_bits"
return none(ProofOutput)
if inp.msg_idx < 0 or inp.msg_idx >= inp.msg_limit:
if verbose:
echo "rln proof: message index range check failed"
return none(ProofOutput)
let public_key : F = compress( inp.secret_key , intToBN254(inp.msg_limit) )
let sk_plus_j : F = addF( inp.secret_key , intToBN254(inp.msg_idx) )
let a1 : F = compress( sk_plus_j , inp.ext_null )
let y_value : F = addF( inp.secret_key , mulf( inp.msg_hash , a1 ) )
let local_null : F = compress( a1 , intToBN254(0) )
let merkle_proof = MerkleProof(
leafIndex : inp.leaf_idx
, leafHash : public_key
, merklePath : inp.merkle_path
, depth : globCfg.merkle_depth
)
if not checkMerkleInclusionProof( inp.merkle_root , merkle_proof ):
if verbose:
echo "rln proof: Merkle inclusion check failed"
return none(ProofOutput)
if verbose:
echo "rln proof sanity checked OK."
return some( ProofOutput( y_value: y_value , local_null: local_null ) )
proc sanityCheckRLNProof*( globCfg: GlobalConfig, proof_inputs: ProofInput , verbose: bool ): bool =
isSome( simulateRLNProof( globCfg , proof_inputs , verbose ) )
#-------------------------------------------------------------------------------

99
test-input/src/types.nim Normal file
View File

@ -0,0 +1,99 @@
import std/strutils
import std/streams
import std/random
import
constantine/math/arithmetic,
constantine/math/io/io_fields,
constantine/math/io/io_bigints,
constantine/named/algebras
#from constantine/math/io/io_fields import toDecimal
import poseidon2/types
import poseidon2/io
export types
#-------------------------------------------------------------------------------
type BN254_T* = F
type Entropy* = F
type Hash* = F
type Root* = Hash
#-------------------------------------------------------------------------------
type
Seed* = uint64
GlobalConfig* = object
limit_bits* : int
merkle_depth* : int
ProofInput* = object
# public inputs
merkle_root* : F
ext_null* : F
msg_hash* : F
# private inputs
secret_key* : F
msg_limit* : int
leaf_idx* : int
merkle_path* : seq[F]
msg_idx* : int
PartialInput* = object
merkle_root* : F
secret_key* : F
msg_limit* : int
leaf_idx* : int
merkle_path* : seq[F]
const defaultGlobalConfig* =
GlobalConfig( limit_bits: 16 , merkle_depth: 20 )
func extractPartialInputs*(input: ProofInput): PartialInput =
PartialInput( merkle_root : input.merkle_root
, secret_key : input.secret_key
, msg_limit : input.msg_limit
, leaf_idx : input.leaf_idx
, merkle_path : input.merkle_path
)
#-----------------------------------------------
func intToBN254*(x: int): F = toF(x)
func uint64ToBN254*(x: uint64): F = toF(x)
func toDecimalF*(a : F): string =
var s : string = toDecimal(a)
s = s.strip( leading=true, trailing=false, chars={'0'} )
if s.len == 0: s="0"
return s
func toQuotedDecimalF*(x: F): string =
let s : string = toDecimalF(x)
return ("\"" & s & "\"")
func addF*(x, y: F): F = x + y
func mulF*(x, y: F): F = x * y
# this is stupid, but it doesn't really matter here
proc randomF*(): F =
let mult : F = F.fromHex("0x0000000000000000000000000000000000000000000000010000000000000000")
let a : F = uint64ToBN254(rand(uint64))
let b : F = a * mult + uint64ToBN254(rand(uint64))
let c : F = b * mult + uint64ToBN254(rand(uint64))
let d : F = c * mult + uint64ToBN254(rand(uint64))
return d
proc writeLnF*(h: Stream, prefix: string, x: F) =
h.writeLine(prefix & toQuotedDecimalF(x))
proc writeF*(h: Stream, prefix: string, x: F) =
h.write(prefix & toQuotedDecimalF(x))
#-------------------------------------------------------------------------------

View File

@ -0,0 +1,11 @@
version = "0.0.0"
author = "Balazs Komuves"
description = "create test inputs for the RLN circuit"
license = "MIT or Apache-2.0"
srcDir = "src"
bin = @["cli"]
requires "nim >= 2.0.0"
requires "https://github.com/mratsim/constantine#bc3845aa492b52f7fef047503b1592e830d1a774"
requires "https://github.com/logos-storage/nim-poseidon2#7749c368a9302167f94bd0133fb881cb83392caf"

View File

@ -22,19 +22,18 @@ To have an overview of what all the different steps and files are, see [PROOFS.m
- furthermore install `circom-witnesscalc`: <https://github.com/iden3/circom-witnesscalc/> (note: we need the legacy `build-circuit` version!)
- install Nim: <https://nim-lang.org/>
TODO: fix this:
Build the Nim cli proof input generator:
$ cd ../reference/nim/proof_input/
$ cd ../test-input
$ nimble build -d:release cli
$ cd ../../../workflow
$ cd ../workflow
### Powers of tau setup
Either download a ready-to-use "powers of tau" setup file (section 7), or generate one
youself using `snarkjs` (sections 1..7), see the README here: <https://github.com/iden3/snarkjs>
Size `2^13` (file size about 10MB) should be big enough:
Size `2^13 = 8192` (file size about 10MB) should be big enough:
$ cd ../ceremony
$ wget https://storage.googleapis.com/zkevm/ptau/powersOfTau28_hez_final_13.ptau

View File

@ -5,8 +5,7 @@ MY_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source ${MY_DIR}/params.sh
CLI_ARGS="--merkle_depth=${MERKLE_DEPTH} \
--limit_bits=${LIMIT_BITS} \
--seed=$SEED"
--limit_bits=${LIMIT_BITS}"
if [[ "$1" == "--export" ]]
then

View File

@ -1,6 +1,6 @@
#!/bin/bash
MERKLE_DEPTH=20 # depth of the Merkle tree
LIMIT_BITS=16 # log2 of the maximal possible rate limit per epoch
MERKLE_DEPTH=10 # depth of the Merkle tree
LIMIT_BITS=6 # log2 of the maximal possible rate limit per epoch
SEED=1234567 # seed for creating fake data
# SEED=1234567 # seed for creating fake data

View File

@ -2,11 +2,12 @@
ORIG=`pwd`
NIMCLI_DIR="${ORIG}/../test_data/proof_input/"
PTAU_DIR="${ORIG}/../ceremony"
NIMCLI_DIR="${ORIG}/../test-input/"
PTAU_DIR="${ORIG}/../ceremony/"
CIRCUIT_ROOT="${ORIG}/../circuit/"
CIRCUIT_POS_DIR="${CIRCUIT_ROOT}/poseidon2/"
CIRCUIT_INCLUDES="-l${CIRCUIT_ROOT} -l${CIRCUIT_POS_DIR}"
PTAU_FILE="powersOfTau28_hez_final_13.ptau"
PTAU_PATH="${PTAU_DIR}/${PTAU_FILE}"

View File

@ -12,18 +12,18 @@ cd build
if command -v build-circuit
then
CIRCUIT_INCLUDES="-l${CIRCUIT_LIB_DIR} -l${CIRCUIT_POS_DIR} -l${CIRCUIT_PRF_DIR}"
build-circuit ${CIRCUIT_MAIN}.circom ${CIRCUIT_MAIN}_graph.bin ${CIRCUIT_INCLUDES}
build-circuit ${CIRCUIT_MAIN}.circom ${CIRCUIT_MAIN}.graph ${CIRCUIT_INCLUDES}
else
echo " "
echo "\`circom-witnesscalc\` not found; skipping graph extraction"
echo "\`circom-witnesscalc\` (in particular \`build-circuit\') not found; skipping graph extraction"
fi
# --- generate input for the circuit ---
echo ""
echo "generating the input for the proof circuit..."
${NIMCLI_DIR}/cli $CLI_ARGS -v --output=input.json
${NIMCLI_DIR}/cli $CLI_ARGS --output=input.json
# --- generate the witness ---

View File

@ -10,15 +10,13 @@ cd build
# --- generate the main component ---
### ${NIMCLI_DIR}/cli $CLI_ARGS -v --circom=${CIRCUIT_MAIN}.circom
cp ${CIRCUIT_ROOT}/example_main.circom ./${CIRCUIT_MAIN}.circom
# cp ${CIRCUIT_ROOT}/example_main.circom ./${CIRCUIT_MAIN}.circom
${NIMCLI_DIR}/cli $CLI_ARGS --circom=${CIRCUIT_MAIN}.circom
# --- compile the circuit ---
echo ""
start=`date +%s`
CIRCUIT_INCLUDES="-l${CIRCUIT_ROOT} -l${CIRCUIT_POS_DIR}"
circom --r1cs --wasm --O2 ${CIRCUIT_INCLUDES} ${CIRCUIT_MAIN}.circom
end=`date +%s`
echo "Compiling the circuit took `expr $end - $start` seconds."