mirror of
https://github.com/vacp2p/gnark-rln.git
synced 2026-01-09 04:58:05 -05:00
feat: init
This commit is contained in:
140
rln/poseidon.go
Normal file
140
rln/poseidon.go
Normal file
@@ -0,0 +1,140 @@
|
||||
// forked from https://raw.githubusercontent.com/AlpinYukseloglu/poseidon-gnark/main/circuits/poseidon.go
|
||||
|
||||
package rln
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/consensys/gnark/frontend"
|
||||
)
|
||||
|
||||
func Sigma(api frontend.API, in frontend.Variable) frontend.Variable {
|
||||
return api.Mul(in, in, in, in, in)
|
||||
}
|
||||
|
||||
func Ark(api frontend.API, in []frontend.Variable, c []*big.Int, r int) []frontend.Variable {
|
||||
for i := range in {
|
||||
in[i] = api.Add(in[i], c[i+r])
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
// Shared logic of multiplication and addition
|
||||
func multiplyAndAdd(api frontend.API, in []frontend.Variable, factors []*big.Int) frontend.Variable {
|
||||
result := frontend.Variable(0)
|
||||
for i, val := range in {
|
||||
result = api.Add(result, api.Mul(factors[i], val))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Helper function to create factors slice for Mix
|
||||
func createMixFactors(in []frontend.Variable, m [][]*big.Int, index int) []*big.Int {
|
||||
factors := make([]*big.Int, len(in))
|
||||
for i := range in {
|
||||
factors[i] = m[i][index]
|
||||
}
|
||||
return factors
|
||||
}
|
||||
|
||||
func Mix(api frontend.API, in []frontend.Variable, m [][]*big.Int) []frontend.Variable {
|
||||
out := make([]frontend.Variable, len(in))
|
||||
for i := range in {
|
||||
out[i] = multiplyAndAdd(api, in, createMixFactors(in, m, i))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func MixLast(api frontend.API, in []frontend.Variable, m [][]*big.Int, s int) frontend.Variable {
|
||||
return multiplyAndAdd(api, in, createMixFactors(in, m, s))
|
||||
}
|
||||
|
||||
func MixS(api frontend.API, in []frontend.Variable, s []*big.Int, r int) []frontend.Variable {
|
||||
out := make([]frontend.Variable, len(in))
|
||||
for i := range in {
|
||||
out[0] = api.Add(out[0], api.Mul(s[(len(in)*2-1)*r+i], in[i]))
|
||||
}
|
||||
for i := 1; i < len(in); i++ {
|
||||
out[i] = api.Add(in[i], api.Mul(in[0], s[(len(in)*2-1)*r+len(in)+i-1]))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func PoseidonEx(api frontend.API, inputs []frontend.Variable, initialState frontend.Variable, nOuts int) []frontend.Variable {
|
||||
out := make([]frontend.Variable, nOuts)
|
||||
|
||||
// Using recommended parameters from whitepaper https://eprint.iacr.org/2019/458.pdf (table 2, table 8)
|
||||
// Generated by https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/calc_round_numbers.py
|
||||
// And rounded up to nearest integer that divides by t
|
||||
nRoundsPC := [16]int{56, 57, 56, 60, 60, 63, 64, 63, 60, 66, 60, 65, 70, 60, 64, 68}
|
||||
t := len(inputs) + 1
|
||||
nRoundsF := 8
|
||||
nRoundsP := nRoundsPC[t-2]
|
||||
c := POSEIDON_C(t)
|
||||
s := POSEIDON_S(t)
|
||||
m := POSEIDON_M(t)
|
||||
p := POSEIDON_P(t)
|
||||
|
||||
state := make([]frontend.Variable, t)
|
||||
for j := 0; j < t; j++ {
|
||||
if j == 0 {
|
||||
state[0] = initialState
|
||||
} else {
|
||||
state[j] = inputs[j-1]
|
||||
}
|
||||
}
|
||||
state = Ark(api, state, c, 0)
|
||||
|
||||
for r := 0; r < nRoundsF/2-1; r++ {
|
||||
for j := 0; j < t; j++ {
|
||||
state[j] = Sigma(api, state[j])
|
||||
}
|
||||
state = Ark(api, state, c, (r+1)*t)
|
||||
state = Mix(api, state, m)
|
||||
}
|
||||
|
||||
for j := 0; j < t; j++ {
|
||||
state[j] = Sigma(api, state[j])
|
||||
}
|
||||
state = Ark(api, state, c, nRoundsF/2*t)
|
||||
state = Mix(api, state, p)
|
||||
|
||||
for r := 0; r < nRoundsP; r++ {
|
||||
|
||||
state[0] = Sigma(api, state[0])
|
||||
|
||||
state[0] = api.Add(state[0], c[(nRoundsF/2+1)*t+r])
|
||||
newState0 := frontend.Variable(0)
|
||||
for j := 0; j < len(state); j++ {
|
||||
mul := api.Mul(s[(t*2-1)*r+j], state[j])
|
||||
newState0 = api.Add(newState0, mul)
|
||||
}
|
||||
|
||||
for k := 1; k < t; k++ {
|
||||
state[k] = api.Add(state[k], api.Mul(state[0], s[(t*2-1)*r+t+k-1]))
|
||||
}
|
||||
state[0] = newState0
|
||||
}
|
||||
|
||||
for r := 0; r < nRoundsF/2-1; r++ {
|
||||
for j := 0; j < t; j++ {
|
||||
state[j] = Sigma(api, state[j])
|
||||
}
|
||||
state = Ark(api, state, c, (nRoundsF/2+1)*t+nRoundsP+r*t)
|
||||
state = Mix(api, state, m)
|
||||
}
|
||||
|
||||
for j := 0; j < t; j++ {
|
||||
state[j] = Sigma(api, state[j])
|
||||
}
|
||||
|
||||
for i := 0; i < nOuts; i++ {
|
||||
out[i] = MixLast(api, state, m, i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func Poseidon(api frontend.API, inputs []frontend.Variable) frontend.Variable {
|
||||
out := PoseidonEx(api, inputs, 0, 1)
|
||||
return out[0]
|
||||
}
|
||||
25061
rln/poseidon_constants.go
Normal file
25061
rln/poseidon_constants.go
Normal file
File diff suppressed because it is too large
Load Diff
83
rln/poseidon_test.go
Normal file
83
rln/poseidon_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package rln
|
||||
|
||||
import (
|
||||
"github.com/consensys/gnark/frontend"
|
||||
)
|
||||
|
||||
// TestPoseidon tests the Gnark Poseidon implementation against Iden3's Go implementation on all the test
|
||||
// vectors outlined in the original paper's reference repository, which can be found here: https://extgit.iaik.tugraz.at/krypto/hadeshash/-/tree/master/code
|
||||
//
|
||||
// The actual test vectors are outlined here: https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/test_vectors.txt
|
||||
// We have included more for the sake of robustness.
|
||||
// Note that our implementation is focused on the 3-input variant with an x^5 S-box, so not all the test vectors apply.
|
||||
// func TestPoseidon(t *testing.T) {
|
||||
// tests := map[string]struct {
|
||||
// gnarkPoseidonInput [3]frontend.Variable
|
||||
// referencePoseidonInput []*big.Int
|
||||
// }{
|
||||
// "happy path: basic input": {
|
||||
// gnarkPoseidonInput: [3]frontend.Variable{1, 2, 3},
|
||||
// referencePoseidonInput: []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)},
|
||||
// },
|
||||
// "official test vector: poseidonperm_x5_254_3": {
|
||||
// gnarkPoseidonInput: [3]frontend.Variable{0, 1, 2},
|
||||
// referencePoseidonInput: []*big.Int{big.NewInt(0), big.NewInt(1), big.NewInt(2)},
|
||||
// },
|
||||
// "zero vector": {
|
||||
// gnarkPoseidonInput: [3]frontend.Variable{0, 0, 0},
|
||||
// referencePoseidonInput: []*big.Int{big.NewInt(0), big.NewInt(0), big.NewInt(0)},
|
||||
// },
|
||||
// "larger inputs": {
|
||||
// gnarkPoseidonInput: [3]frontend.Variable{129048, 990217, 2234383333},
|
||||
// referencePoseidonInput: []*big.Int{big.NewInt(129048), big.NewInt(990217), big.NewInt(2234383333)},
|
||||
// },
|
||||
// "decreasing vector inputs": {
|
||||
// gnarkPoseidonInput: [3]frontend.Variable{10000000, 10000, 100},
|
||||
// referencePoseidonInput: []*big.Int{big.NewInt(10000000), big.NewInt(10000), big.NewInt(100)},
|
||||
// },
|
||||
// }
|
||||
|
||||
// for name, testCase := range tests {
|
||||
|
||||
// assert := test.NewAssert(t)
|
||||
// var circuit circuitPoseidon
|
||||
|
||||
// // Compute reference hash to test against
|
||||
// referenceHash, err := poseidon.Hash(testCase.referencePoseidonInput)
|
||||
// if err != nil {
|
||||
// t.Fatal(err, "Failed to compute reference poseidon hash for test case: ", name)
|
||||
// }
|
||||
// t.Logf("Reference hash: %s", referenceHash.String())
|
||||
|
||||
// // Generate poseidon hash using gnark implementation
|
||||
// assert.ProverSucceeded(&circuit, &circuitPoseidon{
|
||||
// A: testCase.gnarkPoseidonInput,
|
||||
// Hash: referenceHash,
|
||||
// }, test.WithCurves(ecc.BN254), test.WithBackends(backend.GROTH16))
|
||||
|
||||
// // Ensure output correctly compiles
|
||||
// _r1cs, err := frontend.Compile(big.NewInt(1), r1cs.NewBuilder, &circuit)
|
||||
// if err != nil {
|
||||
// t.Fatal(err, "Failed to compile computed poseidon hash for test case: ", name)
|
||||
// }
|
||||
|
||||
// // Sanity check and debugging support for internal variables
|
||||
// internal, secret, public := _r1cs.GetNbVariables()
|
||||
// t.Logf("Public, secret, internal %v, %v, %v\n", public, secret, internal)
|
||||
// }
|
||||
// }
|
||||
|
||||
// --- Test Helpers ---
|
||||
|
||||
type circuitPoseidon struct {
|
||||
A [3]frontend.Variable `gnark:",public"`
|
||||
Hash frontend.Variable `gnark:",public"`
|
||||
}
|
||||
|
||||
func (t *circuitPoseidon) Define(api frontend.API) error {
|
||||
hash := Poseidon(api, t.A[:])
|
||||
api.Println(t.Hash)
|
||||
api.Println(hash)
|
||||
api.AssertIsEqual(hash, t.Hash)
|
||||
return nil
|
||||
}
|
||||
73
rln/rln.go
Normal file
73
rln/rln.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package rln
|
||||
|
||||
import (
|
||||
"github.com/consensys/gnark/frontend"
|
||||
"github.com/consensys/gnark/std/rangecheck"
|
||||
)
|
||||
|
||||
// Circuit defines a simple circuit
|
||||
// x**3 + x + 5 == y
|
||||
type RlnCircuit struct {
|
||||
X frontend.Variable `gnark:"x, public"` // message hash
|
||||
ExternalNullifier frontend.Variable `gnark:"externalNullifier, public"` // external nullifier
|
||||
IdentitySecret frontend.Variable `gnark:"identitySecret,secret"` // identity secret
|
||||
MessageId frontend.Variable `gnark:"messageId,secret"` // message id
|
||||
UserMessageLimit frontend.Variable `gnark:"userMessageLimit,secret"` // user message limit
|
||||
PathElements [20]frontend.Variable `gnark:"pathElements,secret"` // path elements
|
||||
IdentityPathIndex [20]frontend.Variable `gnark:"identityPathIndex,secret"` // identity path index
|
||||
Y frontend.Variable `gnark:"y,public"`
|
||||
Root frontend.Variable `gnark:"root, public"`
|
||||
Nullifier frontend.Variable `gnark:"nullifier, public"`
|
||||
}
|
||||
|
||||
func (circuit RlnCircuit) Define(api frontend.API) error {
|
||||
var identity_commitment_input [1]frontend.Variable
|
||||
identity_commitment_input[0] = circuit.IdentitySecret
|
||||
|
||||
identity_commitment := Poseidon(api, identity_commitment_input[:])
|
||||
api.AssertIsEqual(identity_commitment, identity_commitment)
|
||||
var rate_commitment_input [2]frontend.Variable
|
||||
rate_commitment_input[0] = identity_commitment
|
||||
rate_commitment_input[1] = circuit.UserMessageLimit
|
||||
rate_commitment := Poseidon(api, rate_commitment_input[:])
|
||||
api.AssertIsEqual(rate_commitment, rate_commitment)
|
||||
|
||||
levels := len(circuit.IdentityPathIndex)
|
||||
hashes := make([]frontend.Variable, levels+1)
|
||||
|
||||
hashes[0] = rate_commitment
|
||||
for i := 0; i < levels; i++ {
|
||||
api.AssertIsBoolean(circuit.IdentityPathIndex[i])
|
||||
var left_hash_input [2]frontend.Variable
|
||||
left_hash_input[0] = hashes[i]
|
||||
left_hash_input[1] = circuit.PathElements[i]
|
||||
var right_hash_input [2]frontend.Variable
|
||||
right_hash_input[0] = circuit.PathElements[i]
|
||||
right_hash_input[1] = hashes[i]
|
||||
|
||||
left_hash := Poseidon(api, left_hash_input[:])
|
||||
right_hash := Poseidon(api, right_hash_input[:])
|
||||
hashes[i+1] = api.Select(circuit.IdentityPathIndex[i], right_hash, left_hash)
|
||||
}
|
||||
circuit.Root = hashes[levels]
|
||||
api.AssertIsEqual(circuit.Root, circuit.Root)
|
||||
|
||||
rangeChecker := rangecheck.New(api)
|
||||
rangeChecker.Check(circuit.MessageId, 16)
|
||||
api.AssertIsLessOrEqual(circuit.MessageId, circuit.UserMessageLimit)
|
||||
|
||||
var a1_input [3]frontend.Variable
|
||||
a1_input[0] = circuit.IdentitySecret
|
||||
a1_input[1] = circuit.ExternalNullifier
|
||||
a1_input[2] = circuit.MessageId
|
||||
a1 := Poseidon(api, a1_input[:])
|
||||
circuit.Y = api.Mul(api.Add(circuit.IdentitySecret, a1), circuit.X)
|
||||
api.AssertIsEqual(circuit.Y, circuit.Y)
|
||||
|
||||
var nullifier_input [1]frontend.Variable
|
||||
nullifier_input[0] = a1
|
||||
circuit.Nullifier = Poseidon(api, nullifier_input[:])
|
||||
api.AssertIsEqual(circuit.Nullifier, circuit.Nullifier)
|
||||
|
||||
return nil
|
||||
}
|
||||
44
rln/rln_test.go
Normal file
44
rln/rln_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package rln
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/consensys/gnark/frontend"
|
||||
"github.com/consensys/gnark/test"
|
||||
)
|
||||
|
||||
func TestRlnCircuit(t *testing.T) {
|
||||
assert := test.NewAssert(t)
|
||||
|
||||
var rlnCircuit RlnCircuit
|
||||
|
||||
var identityPathIndex [20]frontend.Variable
|
||||
for i := 0; i < 20; i++ {
|
||||
var direction frontend.Variable
|
||||
if i%2 == 0 {
|
||||
direction = frontend.Variable(1)
|
||||
} else {
|
||||
direction = frontend.Variable(0)
|
||||
}
|
||||
identityPathIndex[i] = direction
|
||||
}
|
||||
|
||||
var pathElements [20]frontend.Variable
|
||||
for i := 0; i < 20; i++ {
|
||||
pathElements[i] = frontend.Variable(10)
|
||||
}
|
||||
|
||||
assert.ProverSucceeded(&rlnCircuit, &RlnCircuit{
|
||||
X: frontend.Variable(10),
|
||||
ExternalNullifier: frontend.Variable(10),
|
||||
IdentitySecret: frontend.Variable(10),
|
||||
MessageId: frontend.Variable(10),
|
||||
UserMessageLimit: frontend.Variable(20),
|
||||
PathElements: pathElements,
|
||||
IdentityPathIndex: identityPathIndex,
|
||||
Y: frontend.Variable(0),
|
||||
Root: frontend.Variable(0),
|
||||
Nullifier: frontend.Variable(0),
|
||||
})
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user