feat: init

This commit is contained in:
rymnc
2024-03-13 14:01:15 +05:30
commit 33502bd39a
9 changed files with 25643 additions and 0 deletions

140
rln/poseidon.go Normal file
View 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

File diff suppressed because it is too large Load Diff

83
rln/poseidon_test.go Normal file
View 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
View 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
View 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),
})
}