Compare commits

..

20 Commits

Author SHA1 Message Date
Alvaro Revuelta
a8e8aab76c
Remove hardcoded epoch size (#24) 2024-06-14 12:20:49 +02:00
Alvaro Revuelta
88462cf654
Add InsertRawLeaf (#23)
Co-authored-by: richΛrd <info@richardramos.me>
2024-05-31 07:11:54 +02:00
Alvaro Revuelta
84d12e61d9
Custom witness proof with RLN v2 (#22) 2024-05-30 14:53:54 +02:00
Alvaro Revuelta
54bb48f178
Update to RLN v2 (#21) 2024-05-24 10:17:29 +02:00
Alvaro Revuelta
14960f3aff
Add GenerateRLNProofWithWitness (#19) 2024-01-24 16:31:36 +01:00
Alvaro Revuelta
7e086e8f89
Add GetMerkleProof (#17) 2024-01-16 16:33:45 +01:00
Alvaro Revuelta
06e6fa3fd1
Add CI for tests (#18) 2024-01-16 15:31:15 +01:00
Prem Chaitanya Prathi
fa738c0bdf fix: make rln rate limit spec compliant 2024-01-02 10:52:50 -04:00
Richard Ramos
e0f344a581
chore: update README.md 2023-12-15 08:50:18 -04:00
Richard Ramos
4b71a4fcf1
chore: update README.md with instructions on how to update zerokit 2023-12-15 08:34:52 -04:00
Richard Ramos
d284a3d8f2
chore: add reference to library folders for go mod vendor 2023-09-16 13:32:59 -04:00
Richard Ramos
e0ebce7c29
feat: upgrade to zerokit 0.3.4 2023-09-14 19:40:36 -04:00
Richard Ramos
ca686a02e8
feat: upgrade to zerokit 0.3.2 2023-09-05 17:46:45 -04:00
Richard Ramos
a706089284
chore: use flush_every_ms and change datatype to time.Duration 2023-08-23 11:08:36 -04:00
Richard Ramos
8167006b94
fix: invalid apple commit 2023-08-21 12:37:54 -04:00
Richard Ramos
1ccba817b5
chore: add Bytes32ToBigInt 2023-08-17 18:19:53 -04:00
Richard Ramos
3b5c0bbefb
chore: add BigIntToBytes32 utils function 2023-08-14 07:57:45 -04:00
Richard Ramos
47b8b17401 refactor: only expose tree config 2023-08-11 08:46:33 -04:00
Richard Ramos
fd3fa1222b refactor: remove embedded resource and add depth and mode enums 2023-08-11 08:46:33 -04:00
Richard Ramos
655973b243 chore: allow passing configs when instantiating RLN 2023-08-07 15:54:39 -04:00
20 changed files with 1285 additions and 669 deletions

20
.github/workflows/tests.yml vendored Normal file
View File

@ -0,0 +1,20 @@
on: [push, pull_request]
name: Tests
jobs:
test:
strategy:
matrix:
go-version: [1.19.x]
# Disabled: windows-latest
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Run tests
run: |
go test ./... -v

4
.gitignore vendored
View File

@ -13,3 +13,7 @@
# Dependency directories (remove the comment below to include it)
# vendor/
# Tree persistence
snap.*
blobs/

View File

@ -1,3 +1,82 @@
# go-zerokit-rln
Go wrappers for [zerokit's RLN](https://github.com/vacp2p/zerokit)
### Updating vacp2p/zerokit
To overcome the limit of 500mb github has for repositories, go-zerokit-rln depends on 3 projects:
- https://github.com/waku-org/go-zerokit-rln-apple
- https://github.com/waku-org/go-zerokit-rln-arm
- https://github.com/waku-org/go-zerokit-rln-x86_64
Zerokit must be updated in these 3 repositories. The instructions are the same for each of the architectures,
except for `-apple` which require macos to be executed. You need to have docker and rust installed.
```bash
export GO_RLN_ARCH=x86_64 # Replace this for x86_64, arm or apple
export ZEROKIT_COMMIT=master # Use a commit, branch or tag
git clone https://github.com/waku-org/go-zerokit-rln_${GO_RLN_ARCH}
cd go-zerokit-rln-${GO_RLN_ARCH}
git submodule init
git submodule update --recursive
cd zerokit
git pull
git checkout ${ZEROKIT_COMMIT}
cd ..
make
git add zerokit
git add libs/*/librln.a
git commit -m "chore: bump zerokit"
git push
```
Once you execute the previous commands for each one of the architectures, update go.mod:
```bash
cd /path/to/go-zerokit-rln
go get github.com/waku-org/go-zerokit-rln-apple@latest
go get github.com/waku-org/go-zerokit-rln-arm@latest
go get github.com/waku-org/go-zerokit-rln-x86_64@latest
git checkout master
git add go.mod
git add go.sum
git commit -m "chore: bump zerokit"
git push
```
And later in go-waku, update the go-zerokit-rln dependency with
```
cd /path/to/go-waku
git fetch
git checkout -b `date +"%Y%m%d%H%M%S"-bump-zerokit` origin/master
go get github.com/waku-org/go-zerokit-rln@latest
git add go.mod
git add go.sum
git commit -m "chore: bump go-zerokit-rln"
git push
````
And create a PR
# Adding a new architecture
1. Depending on the architecture/platform you want to add, clone one of these repositories.
- https://github.com/waku-org/go-zerokit-rln-apple
- https://github.com/waku-org/go-zerokit-rln-arm
- https://github.com/waku-org/go-zerokit-rln-x86_64
2. Edit `./scripts/build.sh` to add the new architecture. Refer to this list of [supported targets](https://github.com/cross-rs/cross#supported-targets)
3. Edit `./rln/link.go` to add a **cgo** build tag for the new architecture
4. Commit the changes and then execute
```bash
export GO_RLN_ARCH=x86_64 # Replace this for the platform you just updated: x86_64, arm or apple
cd /path/to/go-zerokit-rln
git checkout master
git pull
go get github.com/waku-org/go-zerokit-rln-${GO_RLN_ARCH}@latest
git add go.mod
git add go.sum
git commit -m "chore: bump go-zerokit-rln-${GO_RLN_ARCH}"
git push
```

17
go.mod
View File

@ -1,18 +1,23 @@
module github.com/waku-org/go-zerokit-rln
go 1.18
go 1.19
require (
github.com/stretchr/testify v1.7.2
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230803113401-9a7ef94d120e
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230801152407-8101ff87ee0a
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230801140722-0a4e68d0b8f5
github.com/consensys/gnark-crypto v0.12.1
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240529153423-5df5db48b69f
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240529153432-be2c8ac0a840
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240529153442-f5fb416605f5
golang.org/x/crypto v0.18.0
)
require (
github.com/bits-and-blooms/bitset v1.10.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
golang.org/x/sys v0.16.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

53
go.sum
View File

@ -1,26 +1,53 @@
github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88=
github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M=
github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230803113401-9a7ef94d120e h1:Ad0rJod5F1FuYCJ8SUB/bQZsQwirNHQRE0IcaVloxZo=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230803113401-9a7ef94d120e/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230801152407-8101ff87ee0a h1:10cre+P76QvnLeyeCVAM8WDbUCri/y5xY3LtwI9Y5DE=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230801152407-8101ff87ee0a/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230801140722-0a4e68d0b8f5 h1:GseAHwGMixJ2zlY1kFYr3z1Ts0dREIYbgW4yIji9Ksw=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230801140722-0a4e68d0b8f5/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240522110429-626138029176 h1:ezeAofaW3B6tfqS06FwKAKKXpNkimWnIwKjDU0dDPKE=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240522110429-626138029176/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240523161310-d005fe7ba59c h1:/eGH8EAt5/zGfNRBQ0nJMrfZDeXRSJrm8E8uCPlsC3A=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240523161310-d005fe7ba59c/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240528140707-ed6b40a98d7b h1:LEa2s1p+Z8SN475dVr3XDmvmGyKzIDKPcAQ+6hTyVwA=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240528140707-ed6b40a98d7b/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240529153423-5df5db48b69f h1:CEBW4vu8I60OakKExZUE7G4oY7Z/glQXxPYedpZ4Sq8=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240529153423-5df5db48b69f/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240124081101-5e4387508113 h1:dPwc4LAWLXb4Pssej/NtGA9A0UMQwi+JafQPdnhjRWM=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240124081101-5e4387508113/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240523161300-8203361a01d0 h1:IvtkZOcApOkEmHkT/drDmMtY6fdYpF7x4sesWyIURpI=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240523161300-8203361a01d0/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240529153432-be2c8ac0a840 h1:DKub+sG+vfKqwOCaKrthhJA/bP7gTZWxbdrFV86Q5Ms=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240529153432-be2c8ac0a840/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240124081123-f90cfc88a1dc h1:GUZlr25hXLu/PeASqm8P5dPOyD4CdfvkzyEtXEBLbr8=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240124081123-f90cfc88a1dc/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240523161247-6f16d12c5a86 h1:PN1WSt3u/DEIn4hX5Oqrm9bm5nf5VBfenfXmbX4mg60=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240523161247-6f16d12c5a86/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240529153442-f5fb416605f5 h1:ZhrzpAjIUZHD6gSKPA8zwHjIys9/GTGN3hPKtwMORSA=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240529153442-f5fb416605f5/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -5,22 +5,24 @@
package link
import r "github.com/waku-org/go-zerokit-rln-apple/rln"
import (
r "github.com/waku-org/go-zerokit-rln-apple/rln"
)
type RLNWrapper struct {
ffi *r.RLN
}
func NewWithParams(depth int, wasm []byte, zkey []byte, verifKey []byte) (*RLNWrapper, error) {
rln, err := r.NewWithParams(depth, wasm, zkey, verifKey)
func NewWithParams(depth int, wasm []byte, zkey []byte, verifKey []byte, treeConfig []byte) (*RLNWrapper, error) {
rln, err := r.NewWithParams(depth, wasm, zkey, verifKey, treeConfig)
if err != nil {
return nil, err
}
return &RLNWrapper{ffi: rln}, nil
}
func NewWithFolder(depth int, resourcesFolderPath string) (*RLNWrapper, error) {
rln, err := r.NewWithFolder(depth, resourcesFolderPath)
func New(depth int, config []byte) (*RLNWrapper, error) {
rln, err := r.New(uint(depth), config)
if err != nil {
return nil, err
}
@ -83,10 +85,18 @@ func (i RLNWrapper) GetLeaf(index uint) ([]byte, error) {
return i.ffi.GetLeaf(index)
}
func (i RLNWrapper) GetMerkleProof(index uint) ([]byte, error) {
return i.ffi.GetMerkleProof(index)
}
func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) {
return i.ffi.GenerateRLNProof(input)
}
func (i RLNWrapper) GenerateRLNProofWithWitness(input []byte) ([]byte, error) {
return i.ffi.GenerateRLNProofWithWitness(input)
}
func (i RLNWrapper) VerifyWithRoots(input []byte, roots []byte) (bool, error) {
return i.ffi.VerifyWithRoots(input, roots)
}
@ -95,6 +105,10 @@ func (i RLNWrapper) AtomicOperation(index uint, leaves []byte, indices []byte) b
return i.ffi.AtomicOperation(index, leaves, indices)
}
func (i RLNWrapper) SeqAtomicOperation(leaves []byte, indices []byte) bool {
return i.ffi.SeqAtomicOperation(leaves, indices)
}
func (i RLNWrapper) RecoverIDSecret(proof1 []byte, proof2 []byte) ([]byte, error) {
return i.ffi.RecoverIDSecret(proof1, proof2)
}
@ -106,3 +120,11 @@ func (i RLNWrapper) SetMetadata(metadata []byte) bool {
func (i RLNWrapper) GetMetadata() ([]byte, error) {
return i.ffi.GetMetadata()
}
func (i RLNWrapper) Flush() bool {
return i.ffi.Flush()
}
func (i RLNWrapper) LeavesSet() uint {
return i.ffi.LeavesSet()
}

View File

@ -4,22 +4,24 @@
package link
import r "github.com/waku-org/go-zerokit-rln-arm/rln"
import (
r "github.com/waku-org/go-zerokit-rln-arm/rln"
)
type RLNWrapper struct {
ffi *r.RLN
}
func NewWithParams(depth int, wasm []byte, zkey []byte, verifKey []byte) (*RLNWrapper, error) {
rln, err := r.NewWithParams(depth, wasm, zkey, verifKey)
func NewWithParams(depth int, wasm []byte, zkey []byte, verifKey []byte, treeConfig []byte) (*RLNWrapper, error) {
rln, err := r.NewWithParams(depth, wasm, zkey, verifKey, treeConfig)
if err != nil {
return nil, err
}
return &RLNWrapper{ffi: rln}, nil
}
func NewWithFolder(depth int, resourcesFolderPath string) (*RLNWrapper, error) {
rln, err := r.NewWithFolder(depth, resourcesFolderPath)
func New(depth int, config []byte) (*RLNWrapper, error) {
rln, err := r.New(uint(depth), config)
if err != nil {
return nil, err
}
@ -82,10 +84,18 @@ func (i RLNWrapper) GetLeaf(index uint) ([]byte, error) {
return i.ffi.GetLeaf(index)
}
func (i RLNWrapper) GetMerkleProof(index uint) ([]byte, error) {
return i.ffi.GetMerkleProof(index)
}
func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) {
return i.ffi.GenerateRLNProof(input)
}
func (i RLNWrapper) GenerateRLNProofWithWitness(input []byte) ([]byte, error) {
return i.ffi.GenerateRLNProofWithWitness(input)
}
func (i RLNWrapper) VerifyWithRoots(input []byte, roots []byte) (bool, error) {
return i.ffi.VerifyWithRoots(input, roots)
}
@ -94,6 +104,10 @@ func (i RLNWrapper) AtomicOperation(index uint, leaves []byte, indices []byte) b
return i.ffi.AtomicOperation(index, leaves, indices)
}
func (i RLNWrapper) SeqAtomicOperation(leaves []byte, indices []byte) bool {
return i.ffi.SeqAtomicOperation(leaves, indices)
}
func (i RLNWrapper) RecoverIDSecret(proof1 []byte, proof2 []byte) ([]byte, error) {
return i.ffi.RecoverIDSecret(proof1, proof2)
}
@ -105,3 +119,11 @@ func (i RLNWrapper) SetMetadata(metadata []byte) bool {
func (i RLNWrapper) GetMetadata() ([]byte, error) {
return i.ffi.GetMetadata()
}
func (i RLNWrapper) Flush() bool {
return i.ffi.Flush()
}
func (i RLNWrapper) LeavesSet() uint {
return i.ffi.LeavesSet()
}

View File

@ -5,22 +5,24 @@
package link
import r "github.com/waku-org/go-zerokit-rln-x86_64/rln"
import (
r "github.com/waku-org/go-zerokit-rln-x86_64/rln"
)
type RLNWrapper struct {
ffi *r.RLN
}
func NewWithParams(depth int, wasm []byte, zkey []byte, verifKey []byte) (*RLNWrapper, error) {
rln, err := r.NewWithParams(depth, wasm, zkey, verifKey)
func NewWithParams(depth int, wasm []byte, zkey []byte, verifKey []byte, treeConfig []byte) (*RLNWrapper, error) {
rln, err := r.NewWithParams(depth, wasm, zkey, verifKey, treeConfig)
if err != nil {
return nil, err
}
return &RLNWrapper{ffi: rln}, nil
}
func NewWithFolder(depth int, resourcesFolderPath string) (*RLNWrapper, error) {
rln, err := r.NewWithFolder(depth, resourcesFolderPath)
func New(depth int, config []byte) (*RLNWrapper, error) {
rln, err := r.New(uint(depth), config)
if err != nil {
return nil, err
}
@ -83,10 +85,18 @@ func (i RLNWrapper) GetLeaf(index uint) ([]byte, error) {
return i.ffi.GetLeaf(index)
}
func (i RLNWrapper) GetMerkleProof(index uint) ([]byte, error) {
return i.ffi.GetMerkleProof(index)
}
func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) {
return i.ffi.GenerateRLNProof(input)
}
func (i RLNWrapper) GenerateRLNProofWithWitness(input []byte) ([]byte, error) {
return i.ffi.GenerateRLNProofWithWitness(input)
}
func (i RLNWrapper) VerifyWithRoots(input []byte, roots []byte) (bool, error) {
return i.ffi.VerifyWithRoots(input, roots)
}
@ -95,6 +105,10 @@ func (i RLNWrapper) AtomicOperation(index uint, leaves []byte, indices []byte) b
return i.ffi.AtomicOperation(index, leaves, indices)
}
func (i RLNWrapper) SeqAtomicOperation(leaves []byte, indices []byte) bool {
return i.ffi.SeqAtomicOperation(leaves, indices)
}
func (i RLNWrapper) RecoverIDSecret(proof1 []byte, proof2 []byte) ([]byte, error) {
return i.ffi.RecoverIDSecret(proof1, proof2)
}
@ -106,3 +120,11 @@ func (i RLNWrapper) SetMetadata(metadata []byte) bool {
func (i RLNWrapper) GetMetadata() ([]byte, error) {
return i.ffi.GetMetadata()
}
func (i RLNWrapper) Flush() bool {
return i.ffi.Flush()
}
func (i RLNWrapper) LeavesSet() uint {
return i.ffi.LeavesSet()
}

File diff suppressed because one or more lines are too long

View File

@ -1,3 +0,0 @@
package resources
//go:generate go-bindata -pkg resources -o ./bindata.go ./...

View File

@ -1,119 +0,0 @@
{
"protocol": "groth16",
"curve": "bn128",
"nPublic": 6,
"vk_alpha_1": [
"20124996762962216725442980738609010303800849578410091356605067053491763969391",
"9118593021526896828671519912099489027245924097793322973632351264852174143923",
"1"
],
"vk_beta_2": [
[
"4693952934005375501364248788849686435240706020501681709396105298107971354382",
"14346958885444710485362620645446987998958218205939139994511461437152241966681"
],
[
"16851772916911573982706166384196538392731905827088356034885868448550849804972",
"823612331030938060799959717749043047845343400798220427319188951998582076532"
],
[
"1",
"0"
]
],
"vk_gamma_2": [
[
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
],
[
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
],
[
"1",
"0"
]
],
"vk_delta_2": [
[
"8353516066399360694538747105302262515182301251524941126222712285088022964076",
"9329524012539638256356482961742014315122377605267454801030953882967973561832"
],
[
"16805391589556134376869247619848130874761233086443465978238468412168162326401",
"10111259694977636294287802909665108497237922060047080343914303287629927847739"
],
[
"1",
"0"
]
],
"vk_alphabeta_12": [
[
[
"12608968655665301215455851857466367636344427685631271961542642719683786103711",
"9849575605876329747382930567422916152871921500826003490242628251047652318086"
],
[
"6322029441245076030714726551623552073612922718416871603535535085523083939021",
"8700115492541474338049149013125102281865518624059015445617546140629435818912"
],
[
"10674973475340072635573101639867487770811074181475255667220644196793546640210",
"2926286967251299230490668407790788696102889214647256022788211245826267484824"
]
],
[
[
"9660441540778523475944706619139394922744328902833875392144658911530830074820",
"19548113127774514328631808547691096362144426239827206966690021428110281506546"
],
[
"1870837942477655969123169532603615788122896469891695773961478956740992497097",
"12536105729661705698805725105036536744930776470051238187456307227425796690780"
],
[
"21811903352654147452884857281720047789720483752548991551595462057142824037334",
"19021616763967199151052893283384285352200445499680068407023236283004353578353"
]
]
],
"IC": [
[
"11992897507809711711025355300535923222599547639134311050809253678876341466909",
"17181525095924075896332561978747020491074338784673526378866503154966799128110",
"1"
],
[
"17018665030246167677911144513385572506766200776123272044534328594850561667818",
"18601114175490465275436712413925513066546725461375425769709566180981674884464",
"1"
],
[
"18799470100699658367834559797874857804183288553462108031963980039244731716542",
"13064227487174191981628537974951887429496059857753101852163607049188825592007",
"1"
],
[
"17432501889058124609368103715904104425610382063762621017593209214189134571156",
"13406815149699834788256141097399354592751313348962590382887503595131085938635",
"1"
],
[
"10320964835612716439094703312987075811498239445882526576970512041988148264481",
"9024164961646353611176283204118089412001502110138072989569118393359029324867",
"1"
],
[
"718355081067365548229685160476620267257521491773976402837645005858953849298",
"14635482993933988261008156660773180150752190597753512086153001683711587601974",
"1"
],
[
"11777720285956632126519898515392071627539405001940313098390150593689568177535",
"8483603647274280691250972408211651407952870456587066148445913156086740744515",
"1"
]
]
}

View File

@ -3,55 +3,49 @@ package rln
import "C"
import (
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"github.com/waku-org/go-zerokit-rln/rln/link"
"github.com/waku-org/go-zerokit-rln/rln/resources"
)
// Same as: https://github.com/vacp2p/zerokit/blob/v0.3.5/rln/src/public.rs#L35
// Prevents a RLN ZK proof generated for one application to be re-used in another one.
var RLN_IDENTIFIER = [32]byte{166, 140, 43, 8, 8, 22, 206, 113, 151, 128, 118, 40, 119, 197, 218, 174, 11, 117, 84, 228, 96, 211, 212, 140, 145, 104, 146, 99, 24, 192, 217, 4}
var DEFAULT_USER_MESSAGE_LIMIT = uint32(10)
// RLN represents the context used for rln.
type RLN struct {
w *link.RLNWrapper
}
func getResourcesFolder(depth TreeDepth) string {
return fmt.Sprintf("tree_height_%d", depth)
}
// NewRLN generates an instance of RLN. An instance supports both zkSNARKs logics
// and Merkle tree data structure and operations. It uses a depth of 20 by default
func NewRLN() (*RLN, error) {
wasm, err := resources.Asset("tree_height_20/rln.wasm")
if err != nil {
return nil, err
}
zkey, err := resources.Asset("tree_height_20/rln_final.zkey")
if err != nil {
return nil, err
}
verifKey, err := resources.Asset("tree_height_20/verification_key.json")
if err != nil {
return nil, err
}
r := &RLN{}
depth := 20
r.w, err = link.NewWithParams(depth, wasm, zkey, verifKey)
if err != nil {
return nil, err
}
return r, nil
return NewWithConfig(DefaultTreeDepth, nil)
}
// NewRLNWithParams generates an instance of RLN. An instance supports both zkSNARKs logics
// and Merkle tree data structure and operations. The parameter `depth“ indicates the depth of Merkle tree
func NewRLNWithParams(depth int, wasm []byte, zkey []byte, verifKey []byte) (*RLN, error) {
func NewRLNWithParams(depth int, wasm []byte, zkey []byte, verifKey []byte, treeConfig *TreeConfig) (*RLN, error) {
r := &RLN{}
var err error
r.w, err = link.NewWithParams(depth, wasm, zkey, verifKey)
treeConfigBytes := []byte{}
if treeConfig != nil {
treeConfigBytes, err = json.Marshal(treeConfig)
if err != nil {
return nil, err
}
}
r.w, err = link.NewWithParams(depth, wasm, zkey, verifKey, treeConfigBytes)
if err != nil {
return nil, err
}
@ -59,15 +53,21 @@ func NewRLNWithParams(depth int, wasm []byte, zkey []byte, verifKey []byte) (*RL
return r, nil
}
// NewRLNWithFolder generates an instance of RLN. An instance supports both zkSNARKs logics
// and Merkle tree data structure and operations. The parameter `deptk` indicates the depth of Merkle tree
// The parameter “
func NewRLNWithFolder(depth int, resourcesFolderPath string) (*RLN, error) {
// NewWithConfig generates an instance of RLN. An instance supports both zkSNARKs logics
// and Merkle tree data structure and operations. The parameter `depth` indicates the depth of Merkle tree
func NewWithConfig(depth TreeDepth, treeConfig *TreeConfig) (*RLN, error) {
r := &RLN{}
var err error
r.w, err = link.NewWithFolder(depth, resourcesFolderPath)
configBytes, err := json.Marshal(config{
ResourcesFolder: getResourcesFolder(depth),
TreeConfig: treeConfig,
})
if err != nil {
return nil, err
}
r.w, err = link.New(int(depth), configBytes)
if err != nil {
return nil, err
}
@ -93,12 +93,14 @@ func (r *RLN) InitTreeWithMembers(idComms []IDCommitment) error {
return nil
}
func toIdentityCredential(generatedKeys []byte) (*IdentityCredential, error) {
func toIdentityCredential(generatedKeys []byte, userMessageLimit uint32) (*IdentityCredential, error) {
// add user message limit
key := &IdentityCredential{
IDTrapdoor: [32]byte{},
IDNullifier: [32]byte{},
IDSecretHash: [32]byte{},
IDCommitment: [32]byte{},
IDTrapdoor: [32]byte{},
IDNullifier: [32]byte{},
IDSecretHash: [32]byte{},
IDCommitment: [32]byte{},
UserMessageLimit: userMessageLimit,
}
if len(generatedKeys) != 32*4 {
@ -115,34 +117,63 @@ func toIdentityCredential(generatedKeys []byte) (*IdentityCredential, error) {
// MembershipKeyGen generates a IdentityCredential that can be used for the
// registration into the rln membership contract. Returns an error if the key generation fails
func (r *RLN) MembershipKeyGen() (*IdentityCredential, error) {
// Accepts an optional parameter that sets the user message limit which defaults
// to DEFAULT_USER_MESSAGE_LIMIT
func (r *RLN) MembershipKeyGen(userMessageLimitParam ...uint32) (*IdentityCredential, error) {
var userMessageLimit uint32
if len(userMessageLimitParam) == 1 {
userMessageLimit = userMessageLimitParam[0]
} else if len(userMessageLimitParam) == 0 {
userMessageLimit = DEFAULT_USER_MESSAGE_LIMIT
} else {
return nil, errors.New("just one user message limit is allowed")
}
generatedKeys := r.w.ExtendedKeyGen()
if generatedKeys == nil {
return nil, errors.New("error in key generation")
}
return toIdentityCredential(generatedKeys)
return toIdentityCredential(generatedKeys, userMessageLimit)
}
// SeededMembershipKeyGen generates a deterministic IdentityCredential using a seed
// that can be used for the registration into the rln membership contract.
// Returns an error if the key generation fails
func (r *RLN) SeededMembershipKeyGen(seed []byte) (*IdentityCredential, error) {
// Accepts an optional parameter that sets the user message limit which defaults
// to DEFAULT_USER_MESSAGE_LIMIT
func (r *RLN) SeededMembershipKeyGen(seed []byte, userMessageLimitParam ...uint32) (*IdentityCredential, error) {
var userMessageLimit uint32
if len(userMessageLimitParam) == 1 {
userMessageLimit = userMessageLimitParam[0]
} else if len(userMessageLimitParam) == 0 {
userMessageLimit = DEFAULT_USER_MESSAGE_LIMIT
} else {
return nil, errors.New("just one user message limit is allowed")
}
generatedKeys := r.w.ExtendedSeededKeyGen(seed)
if generatedKeys == nil {
return nil, errors.New("error in key generation")
}
return toIdentityCredential(generatedKeys)
return toIdentityCredential(generatedKeys, userMessageLimit)
}
// appendLength returns length prefixed version of the input with the following format
// [len<8>|input<var>], the len is a 8 byte value serialized in little endian
func appendLength(input []byte) []byte {
inputLen := make([]byte, 8)
binary.LittleEndian.PutUint64(inputLen, uint64(len(input)))
return append(inputLen, input...)
}
// Similar to appendLength but for 32 byte values. The length that is prepended is
// the length of elements that are 32 bytes long each
func appendLength32(input []byte) []byte {
inputLen := make([]byte, 8)
binary.LittleEndian.PutUint64(inputLen, uint64(len(input)/32))
return append(inputLen, input...)
}
func (r *RLN) Sha256(data []byte) (MerkleNode, error) {
lenPrefData := appendLength(data)
@ -176,65 +207,131 @@ func (r *RLN) Poseidon(input ...[]byte) (MerkleNode, error) {
return result, nil
}
func (r *RLN) ExtractMetadata(proof RateLimitProof) (ProofMetadata, error) {
externalNullifierRes, err := r.Poseidon(proof.Epoch[:], proof.RLNIdentifier[:])
if err != nil {
return ProofMetadata{}, fmt.Errorf("could not construct the external nullifier: %w", err)
}
return ProofMetadata{
Nullifier: proof.Nullifier,
ShareX: proof.ShareX,
ShareY: proof.ShareY,
ExternalNullifier: externalNullifierRes,
}, nil
}
// GenerateProof generates a proof for the RLN given a KeyPair and the index in a merkle tree.
// The output will containt the proof data and should be parsed as |proof<128>|root<32>|epoch<32>|share_x<32>|share_y<32>|nullifier<32>|
// integers wrapped in <> indicate value sizes in bytes
func (r *RLN) GenerateProof(data []byte, key IdentityCredential, index MembershipIndex, epoch Epoch) (*RateLimitProof, error) {
input := serialize(key.IDSecretHash, index, epoch, data)
func (r *RLN) GenerateProof(
data []byte,
key IdentityCredential,
index MembershipIndex,
epoch Epoch,
messageId uint32) (*RateLimitProof, error) {
externalNullifierInput, err := r.Poseidon(epoch[:], RLN_IDENTIFIER[:])
if err != nil {
return nil, fmt.Errorf("could not construct the external nullifier: %w", err)
}
input := serialize(key.IDSecretHash, index, key.UserMessageLimit, messageId, externalNullifierInput, data)
proofBytes, err := r.w.GenerateRLNProof(input)
if err != nil {
return nil, err
}
if len(proofBytes) != 320 {
return nil, errors.New("invalid proof generated")
expectedBytes := 288
if len(proofBytes) != expectedBytes {
return nil, fmt.Errorf("invalid proof generated. size: %d expected: %d",
len(proofBytes), expectedBytes)
}
// parse the proof as [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ]
// parse proof taken from: https://github.com/vacp2p/zerokit/blob/v0.5.0/rln/src/public.rs#L750
// [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]
proofOffset := 128
rootOffset := proofOffset + 32
epochOffset := rootOffset + 32
shareXOffset := epochOffset + 32
externalNullifierOffset := rootOffset + 32
shareXOffset := externalNullifierOffset + 32
shareYOffset := shareXOffset + 32
nullifierOffset := shareYOffset + 32
rlnIdentifierOffset := nullifierOffset + 32
var zkproof ZKSNARK
var proofRoot, shareX, shareY MerkleNode
var epochR Epoch
var externalNullifier Nullifier
var nullifier Nullifier
var rlnIdentifier RLNIdentifier
copy(zkproof[:], proofBytes[0:proofOffset])
copy(proofRoot[:], proofBytes[proofOffset:rootOffset])
copy(epochR[:], proofBytes[rootOffset:epochOffset])
copy(shareX[:], proofBytes[epochOffset:shareXOffset])
copy(externalNullifier[:], proofBytes[rootOffset:externalNullifierOffset])
copy(shareX[:], proofBytes[externalNullifierOffset:shareXOffset])
copy(shareY[:], proofBytes[shareXOffset:shareYOffset])
copy(nullifier[:], proofBytes[shareYOffset:nullifierOffset])
copy(rlnIdentifier[:], proofBytes[nullifierOffset:rlnIdentifierOffset])
return &RateLimitProof{
Proof: zkproof,
MerkleRoot: proofRoot,
Epoch: epochR,
ShareX: shareX,
ShareY: shareY,
Nullifier: nullifier,
RLNIdentifier: rlnIdentifier,
Proof: zkproof,
MerkleRoot: proofRoot,
ExternalNullifier: externalNullifier,
ShareX: shareX,
ShareY: shareY,
Nullifier: nullifier,
}, nil
}
// Returns a RLN proof with a custom witness, so no tree is required in the RLN instance
// to calculate such proof. The witness can be created with GetMerkleProof data.
func (r *RLN) GenerateRLNProofWithWitness(witness RLNWitnessInput) (*RateLimitProof, error) {
// serialized as: https://github.com/vacp2p/zerokit/blob/v0.5.0/rln/src/protocol.rs#L127
// input [ id_secret_hash<32> | user_message_limit<32> | message_id<32> | num_elements<8> | path_elements<var1> | num_indexes<8> | path_indexes<var2> | external_nullifier<32> ]
proofBytes, err := r.w.GenerateRLNProofWithWitness(witness.serialize())
if err != nil {
return nil, err
}
expectedBytes := 288
if len(proofBytes) != expectedBytes {
return nil, fmt.Errorf("invalid proof generated. size: %d expected: %d",
len(proofBytes), expectedBytes)
}
// parse proof taken from: https://github.com/vacp2p/zerokit/blob/v0.5.0/rln/src/public.rs#L750
// [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]
proofOffset := 128
rootOffset := proofOffset + 32
externalNullifierOffset := rootOffset + 32
shareXOffset := externalNullifierOffset + 32
shareYOffset := shareXOffset + 32
nullifierOffset := shareYOffset + 32
var zkproof ZKSNARK
var proofRoot, shareX, shareY MerkleNode
var externalNullifier Nullifier
var nullifier Nullifier
copy(zkproof[:], proofBytes[0:proofOffset])
copy(proofRoot[:], proofBytes[proofOffset:rootOffset])
copy(externalNullifier[:], proofBytes[rootOffset:externalNullifierOffset])
copy(shareX[:], proofBytes[externalNullifierOffset:shareXOffset])
copy(shareY[:], proofBytes[shareXOffset:shareYOffset])
copy(nullifier[:], proofBytes[shareYOffset:nullifierOffset])
return &RateLimitProof{
Proof: zkproof,
MerkleRoot: proofRoot,
ExternalNullifier: externalNullifier,
ShareX: shareX,
ShareY: shareY,
Nullifier: nullifier,
}, nil
}
func (r *RLN) CreateWitness(
idSecretHash IDSecretHash,
userMessageLimit uint32,
messageId uint32,
data []byte,
epoch [32]byte,
merkleProof MerkleProof) (RLNWitnessInput, error) {
externalNullifier, err := r.Poseidon(epoch[:], RLN_IDENTIFIER[:])
if err != nil {
return RLNWitnessInput{}, fmt.Errorf("could not construct the external nullifier: %w", err)
}
return RLNWitnessInput{
IDSecretHash: idSecretHash,
UserMessageLimit: userMessageLimit,
MessageId: messageId,
MerkleProof: merkleProof,
X: HashToBN255(data),
ExternalNullifier: externalNullifier,
}, nil
}
@ -297,7 +394,7 @@ func (r *RLN) Verify(data []byte, proof RateLimitProof, roots ...[32]byte) (bool
return false, err
}
return bool(res), nil
return res, nil
}
// RecoverIDSecret returns an IDSecret having obtained before two proofs
@ -313,15 +410,31 @@ func (r *RLN) RecoverIDSecret(proof1 RateLimitProof, proof2 RateLimitProof) (IDS
return result, nil
}
// InsertMember adds the member to the tree
func (r *RLN) InsertMember(idComm IDCommitment) error {
insertionSuccess := r.w.SetNextLeaf(idComm[:])
// InsertMember adds the member to the tree. The leaf is made of
// the id commitment and the user message limit
func (r *RLN) InsertMember(idComm IDCommitment, userMessageLimit uint32) error {
userMessageLimitBytes := SerializeUint32(userMessageLimit)
hashedLeaf, err := r.Poseidon(idComm[:], userMessageLimitBytes[:])
if err != nil {
return err
}
insertionSuccess := r.w.SetNextLeaf(hashedLeaf[:])
if !insertionSuccess {
return errors.New("could not insert member")
}
return nil
}
func (r *RLN) InsertRawLeaf(rawLeaf MerkleNode) error {
insertionSuccess := r.w.SetNextLeaf(rawLeaf[:])
if !insertionSuccess {
return errors.New("could not insert raw leaf")
}
return nil
}
// Insert multiple members i.e., identity commitments starting from index
// This proc is atomic, i.e., if any of the insertions fails, all the previous insertions are rolled back
func (r *RLN) InsertMembers(index MembershipIndex, idComms []IDCommitment) error {
@ -399,10 +512,31 @@ func (r *RLN) GetLeaf(index MembershipIndex) (IDCommitment, error) {
return result, nil
}
// GetMerkleProof returns the Merkle proof for the element at the specified index
// The output should be parsed as: num_elements<8>|path_elements<var1>|num_indexes<8>|path_indexes<var2>
// where num_elements indicate var1 array size and num_indexes indicate var2 array size.
// Both num_elements and num_indexes shall be equal and match the tree depth.
// A tree with depth 20 has 676 bytes = 8 + 32 * 20 + 8 + 20 * 1
// Proof elements are stored as little endian
func (r *RLN) GetMerkleProof(index MembershipIndex) (MerkleProof, error) {
proofBytes, err := r.w.GetMerkleProof(index)
if err != nil {
return MerkleProof{}, err
}
var result MerkleProof
err = result.deserialize(proofBytes)
if err != nil {
return MerkleProof{}, err
}
return result, nil
}
// AddAll adds members to the Merkle tree
func (r *RLN) AddAll(list []IDCommitment) error {
func (r *RLN) AddAll(list []IdentityCredential) error {
for _, member := range list {
if err := r.InsertMember(member); err != nil {
if err := r.InsertMember(member.IDCommitment, member.UserMessageLimit); err != nil {
return err
}
}
@ -444,7 +578,7 @@ func CreateMembershipList(n int) ([]IdentityCredential, MerkleNode, error) {
output = append(output, *keypair)
// insert the key to the Merkle tree
if err := rln.InsertMember(keypair.IDCommitment); err != nil {
if err := rln.InsertMember(keypair.IDCommitment, keypair.UserMessageLimit); err != nil {
return nil, MerkleNode{}, err
}
}
@ -481,3 +615,17 @@ func (r *RLN) AtomicOperation(index MembershipIndex, idCommsToInsert []IDCommitm
}
return nil
}
// Flush
func (r *RLN) Flush() error {
success := r.w.Flush()
if !success {
return errors.New("cannot flush db")
}
return nil
}
// LeavesSet indicates how many elements have been inserted in the merkle tree
func (r *RLN) LeavesSet() uint {
return r.w.LeavesSet()
}

View File

@ -17,6 +17,23 @@ type RLNSuite struct {
suite.Suite
}
func (s *RLNSuite) TestNew() {
rln, err := NewRLN()
s.NoError(err)
root1, err := rln.GetMerkleRoot()
s.NoError(err)
s.Len(root1, 32)
rln2, err := NewWithConfig(DefaultTreeDepth, nil)
s.NoError(err)
root2, err := rln2.GetMerkleRoot()
s.NoError(err)
s.Len(root2, 32)
s.Equal(root1, root2)
}
func (s *RLNSuite) TestMembershipKeyGen() {
rln, err := NewRLN()
s.NoError(err)
@ -59,19 +76,57 @@ func (s *RLNSuite) TestInsertMember() {
keypair, err := rln.MembershipKeyGen()
s.NoError(err)
err = rln.InsertMember(keypair.IDCommitment)
err = rln.InsertMember(keypair.IDCommitment, keypair.UserMessageLimit)
s.NoError(err)
}
func (s *RLNSuite) TestInsertRawLeaf() {
rln, err := NewRLN()
s.NoError(err)
for i := 0; i < 10; i++ {
// Generate a membership
memKeys, err := rln.MembershipKeyGen(10)
s.NoError(err)
// Calculate the leaf ourselves
userMessageLimitBytes := SerializeUint32(memKeys.UserMessageLimit)
hashedLeaf, err := rln.Poseidon(memKeys.IDCommitment[:], userMessageLimitBytes[:])
s.NoError(err)
// Insert the leaf as it is
err = rln.InsertRawLeaf(hashedLeaf)
s.NoError(err)
// Get it from the tree
retrievedLeaf, err := rln.GetLeaf(uint(i))
s.NoError(err)
// Check the retrieved matches the one we added
s.Equal(hashedLeaf, retrievedLeaf)
// Check tree size matches
numLeaves := rln.LeavesSet()
s.Equal(uint(i+1), numLeaves)
}
}
func (s *RLNSuite) TestInsertMembers() {
rln, err := NewRLN()
s.NoError(err)
keypair, err := rln.MembershipKeyGen()
var commitments []IDCommitment
for i := 0; i < 10; i++ {
keypair, err := rln.MembershipKeyGen()
s.NoError(err)
commitments = append(commitments, keypair.IDCommitment)
}
err = rln.InsertMembers(0, commitments)
s.NoError(err)
err = rln.InsertMembers(0, []IDCommitment{keypair.IDCommitment})
s.NoError(err)
numLeaves := rln.LeavesSet()
s.Equal(uint(10), numLeaves)
}
func (s *RLNSuite) TestRemoveMember() {
@ -81,7 +136,7 @@ func (s *RLNSuite) TestRemoveMember() {
keypair, err := rln.MembershipKeyGen()
s.NoError(err)
err = rln.InsertMember(keypair.IDCommitment)
err = rln.InsertMember(keypair.IDCommitment, keypair.UserMessageLimit)
s.NoError(err)
err = rln.DeleteMember(MembershipIndex(0))
@ -99,7 +154,7 @@ func (s *RLNSuite) TestMerkleTreeConsistenceBetweenDeletionAndInsertion() {
keypair, err := rln.MembershipKeyGen()
s.NoError(err)
err = rln.InsertMember(keypair.IDCommitment)
err = rln.InsertMember(keypair.IDCommitment, keypair.UserMessageLimit)
s.NoError(err)
// read the Merkle Tree root after insertion
@ -183,37 +238,64 @@ func (s *RLNSuite) TestCheckCorrectness() {
s.Equal(expectedRoot, root[:])
}
func (s *RLNSuite) TestGetLeaf() {
rln, err := NewRLN()
s.NoError(err)
amountLeafs := int(31)
for i := 0; i < amountLeafs; i++ {
// allowed messages per epoch of the membership
// using different values between 1 and 7
userMessageLimit := uint32(amountLeafs%7 + 1)
// generate membership
memKeys, err := rln.MembershipKeyGen(userMessageLimit)
s.NoError(err)
// insert membership
err = rln.InsertMember(memKeys.IDCommitment, memKeys.UserMessageLimit)
s.NoError(err)
// retrieve the leaf
retrievedLeaf, err := rln.GetLeaf(uint(i))
s.NoError(err)
// calculate the leaf we would expect
userMessageLimitBytes := SerializeUint32(userMessageLimit)
hashedLeaf, err := rln.Poseidon(memKeys.IDCommitment[:], userMessageLimitBytes[:])
s.NoError(err)
// assert it matches
s.Equal(hashedLeaf, retrievedLeaf)
}
}
func (s *RLNSuite) TestValidProof() {
rln, err := NewRLN()
s.NoError(err)
memKeys, err := rln.MembershipKeyGen()
s.NoError(err)
// allowed messages per epoch of the membership
userMessageLimit := uint32(10)
//peer's index in the Merkle Tree
index := uint(5)
memKeys, err := rln.MembershipKeyGen(userMessageLimit)
s.NoError(err)
// Create a Merkle tree with random members
for i := uint(0); i < 10; i++ {
if i == index {
// insert the current peer's pk
err = rln.InsertMember(memKeys.IDCommitment)
err = rln.InsertMember(memKeys.IDCommitment, memKeys.UserMessageLimit)
s.NoError(err)
fifthIndexLeaf, err := rln.GetLeaf(index)
s.NoError(err)
s.Equal(memKeys.IDCommitment, fifthIndexLeaf)
} else {
// create a new key pair
memberKeys, err := rln.MembershipKeyGen()
memberKeys, err := rln.MembershipKeyGen(userMessageLimit)
s.NoError(err)
err = rln.InsertMember(memberKeys.IDCommitment)
err = rln.InsertMember(memberKeys.IDCommitment, memberKeys.UserMessageLimit)
s.NoError(err)
leaf, err := rln.GetLeaf(i)
s.NoError(err)
s.Equal(memberKeys.IDCommitment, leaf)
}
}
@ -221,24 +303,75 @@ func (s *RLNSuite) TestValidProof() {
msg := []byte("Hello")
// prepare the epoch
var epoch Epoch
var epoch Epoch = SerializeUint32(1000)
// generate proof
proofRes, err := rln.GenerateProof(msg, *memKeys, MembershipIndex(index), epoch)
// generate multiple valid proofs for the same epoch
for i := uint32(0); i < userMessageLimit; i++ {
// message sequence within the epoch
messageId := uint32(i)
// generate proof
proofRes, err := rln.GenerateProof(msg, *memKeys, MembershipIndex(index), epoch, messageId)
s.NoError(err)
// verify the proof
verified, err := rln.Verify(msg, *proofRes)
s.NoError(err)
s.True(verified)
// verify with roots
root, err := rln.GetMerkleRoot()
s.NoError(err)
verified, err = rln.Verify(msg, *proofRes, root)
s.NoError(err)
s.True(verified)
}
}
func (s *RLNSuite) TestProofBeyondLimit() {
rln, err := NewRLN()
s.NoError(err)
// verify the proof
verified, err := rln.Verify(msg, *proofRes)
s.NoError(err)
s.True(verified)
// allowed messages per epoch of the membership
userMessageLimit := uint32(10)
// verify with roots
root, err := rln.GetMerkleRoot()
//peer's index in the Merkle Tree
index := uint(5)
memKeys, err := rln.MembershipKeyGen(userMessageLimit)
s.NoError(err)
verified, err = rln.Verify(msg, *proofRes, root)
s.NoError(err)
s.True(verified)
// Create a Merkle tree with random members
for i := uint(0); i < 10; i++ {
if i == index {
err = rln.InsertMember(memKeys.IDCommitment, memKeys.UserMessageLimit)
s.NoError(err)
} else {
// create a new key pair
memberKeys, err := rln.MembershipKeyGen(userMessageLimit)
s.NoError(err)
err = rln.InsertMember(memberKeys.IDCommitment, memberKeys.UserMessageLimit)
s.NoError(err)
}
}
// prepare the message
msg := []byte("Hello")
// prepare the epoch
var epoch Epoch = SerializeUint32(876543456)
// TODO;:
for i := uint32(userMessageLimit + 1); i < (userMessageLimit + 10); i++ {
// message sequence within the epoch
messageId := uint32(i)
// generate proof TODO:Errors!
_, err := rln.GenerateProof(msg, *memKeys, MembershipIndex(index), epoch, messageId)
s.Error(err)
}
}
func (s *RLNSuite) TestInvalidProof() {
@ -255,14 +388,14 @@ func (s *RLNSuite) TestInvalidProof() {
for i := 0; i < 10; i++ {
if i == index {
// insert the current peer's pk
err := rln.InsertMember(memKeys.IDCommitment)
err := rln.InsertMember(memKeys.IDCommitment, memKeys.UserMessageLimit)
s.NoError(err)
} else {
// create a new key pair
memberKeys, err := rln.MembershipKeyGen()
s.NoError(err)
err = rln.InsertMember(memberKeys.IDCommitment)
err = rln.InsertMember(memberKeys.IDCommitment, memberKeys.UserMessageLimit)
s.NoError(err)
}
}
@ -278,8 +411,11 @@ func (s *RLNSuite) TestInvalidProof() {
badIndex := 4
// message sequence within the epoch
messageId := uint32(1)
// generate proof
proofRes, err := rln.GenerateProof(msg, *memKeys, MembershipIndex(badIndex), epoch)
proofRes, err := rln.GenerateProof(msg, *memKeys, MembershipIndex(badIndex), epoch, messageId)
s.NoError(err)
// verify the proof (should not be verified)
@ -288,6 +424,232 @@ func (s *RLNSuite) TestInvalidProof() {
s.False(verified)
}
func (s *RLNSuite) TestGetMerkleProof() {
for _, treeDepth := range []TreeDepth{TreeDepth15, TreeDepth19, TreeDepth20} {
treeDepthInt := int(treeDepth)
rln, err := NewWithConfig(treeDepth, nil)
s.NoError(err)
leaf0 := [32]byte{0x00}
leaf1 := [32]byte{0x01}
leaf5 := [32]byte{0x05}
rln.InsertMemberAt(0, leaf0)
rln.InsertMemberAt(1, leaf1)
rln.InsertMemberAt(5, leaf5)
b1, err := rln.GetMerkleProof(0)
s.NoError(err)
s.Equal(treeDepthInt, len(b1.PathElements))
s.Equal(treeDepthInt, len(b1.PathIndexes))
// First path is right leaf [0, 1]
s.EqualValues(leaf1, b1.PathElements[0])
b2, err := rln.GetMerkleProof(4)
s.NoError(err)
s.Equal(treeDepthInt, len(b2.PathElements))
s.Equal(treeDepthInt, len(b2.PathIndexes))
// First path is right leaf [4, 5]
s.EqualValues(leaf5, b2.PathElements[0])
b3, err := rln.GetMerkleProof(10)
s.NoError(err)
s.Equal(treeDepthInt, len(b3.PathElements))
s.Equal(treeDepthInt, len(b3.PathIndexes))
// First path is right leaf. But its empty
s.EqualValues([32]byte{0x00}, b3.PathElements[0])
}
}
func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesOK() {
treeSize := 20
userMessageLimit := uint32(100)
message := []byte("some rln protected message")
rln, err := NewRLN()
s.NoError(err)
treeElements := make([]IdentityCredential, 0)
// Create a Merkle tree with random members
for i := 0; i < treeSize; i++ {
memberKeys, err := rln.MembershipKeyGen(userMessageLimit)
s.NoError(err)
err = rln.InsertMember(memberKeys.IDCommitment, memberKeys.UserMessageLimit)
s.NoError(err)
treeElements = append(treeElements, *memberKeys)
}
// For different leafs (with a custom witness)
for _, memberIndex := range []uint{0, 10, 13} {
root, err := rln.GetMerkleRoot()
s.NoError(err)
// We provide out custom witness
merkleProof, err := rln.GetMerkleProof(memberIndex)
s.NoError(err)
// For different epochs
for _, epoch := range []Epoch{ToEpoch(1), ToEpoch(9998765)} {
// For some possible message ids
for _, messageId := range []uint32{0, 50, 99} {
rlnWitness, err := rln.CreateWitness(
treeElements[memberIndex].IDSecretHash,
userMessageLimit,
messageId,
message,
epoch,
merkleProof)
s.NoError(err)
// Generate a proof with our custom witness (Merkle Path of the memberIndex)
proofRes1, err := rln.GenerateRLNProofWithWitness(rlnWitness)
s.NoError(err)
verified1, err := rln.Verify(message, *proofRes1, root)
s.NoError(err)
s.True(verified1)
// Generate a proof without our custom witness, to ensure they match
proofRes2, err := rln.GenerateProof(message, treeElements[memberIndex], MembershipIndex(memberIndex), epoch, messageId)
s.NoError(err)
// Ensure we have the same root
s.Equal(root, proofRes1.MerkleRoot)
// Proof generate with custom witness match the proof generate with the witness
// from zerokit. Proof itself is not asserted, can be different.
s.Equal(proofRes1.MerkleRoot, proofRes2.MerkleRoot)
s.Equal(proofRes1.ExternalNullifier, proofRes2.ExternalNullifier)
s.Equal(proofRes1.ShareX, proofRes2.ShareX)
s.Equal(proofRes1.ShareY, proofRes2.ShareY)
s.Equal(proofRes1.Nullifier, proofRes2.Nullifier)
}
}
}
}
func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesNOK() {
treeSize := 20
rln, err := NewRLN()
s.NoError(err)
treeElements := make([]IdentityCredential, 0)
// Create a Merkle tree with random members
for i := 0; i < treeSize; i++ {
memberKeys, err := rln.MembershipKeyGen()
s.NoError(err)
err = rln.InsertMember(memberKeys.IDCommitment, memberKeys.UserMessageLimit)
s.NoError(err)
treeElements = append(treeElements, *memberKeys)
}
// We generate proofs with a custom witness aquired outside zerokit for diferent indexes
for _, memberIndex := range []uint{0, 10, 13, 15} {
root, err := rln.GetMerkleRoot()
s.NoError(err)
// We provide out custom witness
merkleProof, err := rln.GetMerkleProof(memberIndex)
s.NoError(err)
message := []byte("some rln protected message")
epoch := ToEpoch(1000)
userMessageLimit := uint32(10)
messageId := uint32(1)
rlnWitness1, err := rln.CreateWitness(
treeElements[memberIndex].IDSecretHash,
userMessageLimit,
messageId,
message,
epoch,
merkleProof)
s.NoError(err)
// Generate a proof with our custom witness (Merkle Path of the memberIndex)
proofRes1, err := rln.GenerateRLNProofWithWitness(rlnWitness1)
s.NoError(err)
// 1) Message changed, does not verify
verified1, err := rln.Verify([]byte("different message"), *proofRes1, root)
s.NoError(err)
s.False(verified1)
// 2) Different nullifier (epoch or rln id), does not verify
proofRes1.ExternalNullifier = [32]byte{0x11}
verified2, err := rln.Verify(message, *proofRes1, root)
s.NoError(err)
s.False(verified2)
// 3) Merkle proof in provided witness is wrong, does not verify
merkleProof.PathElements[0] = [32]byte{0x11}
rlnWitness2, err := rln.CreateWitness(
treeElements[memberIndex].IDSecretHash,
userMessageLimit,
messageId,
message,
epoch,
merkleProof)
s.NoError(err)
proofRes3, err := rln.GenerateRLNProofWithWitness(rlnWitness2)
s.NoError(err)
verified3, err := rln.Verify(message, *proofRes3, root)
s.NoError(err)
s.False(verified3)
// 4) Membership does not match the index (and not part of tree), does not verify
merkleProof4, err := rln.GetMerkleProof(memberIndex)
s.NoError(err)
// Membership that does not match the index
memberKeys, err := rln.MembershipKeyGen()
s.NoError(err)
// Proof proves memberIndex inclusion, but provided membership is different
rlnWitness4, err := rln.CreateWitness(
memberKeys.IDSecretHash,
userMessageLimit,
messageId,
[]byte("some rln protected message"),
ToEpoch(999),
merkleProof4)
s.NoError(err)
proofRes4, err := rln.GenerateRLNProofWithWitness(rlnWitness4)
s.NoError(err)
verified4, err := rln.Verify(message, *proofRes4, root)
s.NoError(err)
s.False(verified4)
// 5) Message id goes beyond the userMessageLimit, does not generate
wrongMessageId := uint32(1000)
rlnWitness5, err := rln.CreateWitness(
treeElements[memberIndex].IDSecretHash,
userMessageLimit,
wrongMessageId,
message,
epoch,
merkleProof)
s.NoError(err)
_, err = rln.GenerateRLNProofWithWitness(rlnWitness5)
s.Error(err)
}
}
func (s *RLNSuite) TestEpochConsistency() {
// check edge cases
var epoch uint64 = math.MaxUint64

View File

@ -1,20 +1,37 @@
package rln
import "encoding/binary"
import (
"encoding/binary"
"errors"
"fmt"
"math/big"
)
// serialize converts a RateLimitProof and the data to a byte seq
// this conversion is used in the proofGen function
// the serialization is done as instructed in https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L146
// [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
func serialize(idKey IDSecretHash, memIndex MembershipIndex, epoch Epoch, msg []byte) []byte {
// format taken from: https://github.com/vacp2p/zerokit/blob/v0.5.0/rln/src/public.rs#L747
// [identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal<var> ]
func serialize(
idKey IDSecretHash,
memIndex MembershipIndex,
userMessageLimit uint32,
messageId uint32,
externalNullifier [32]byte,
msg []byte) []byte {
memIndexBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(memIndexBytes, uint64(memIndex))
lenPrefMsg := appendLength(msg)
var userMessageLimitByte [32]byte
var messageIdByte [32]byte
binary.LittleEndian.PutUint32(userMessageLimitByte[0:], userMessageLimit)
binary.LittleEndian.PutUint32(messageIdByte[0:], messageId)
output := append(idKey[:], memIndexBytes...)
output = append(output, epoch[:]...)
output = append(output, userMessageLimitByte[:]...)
output = append(output, messageIdByte[:]...)
output = append(output, externalNullifier[:]...)
output = append(output, lenPrefMsg...)
return output
@ -23,7 +40,7 @@ func serialize(idKey IDSecretHash, memIndex MembershipIndex, epoch Epoch, msg []
// serialize converts a RateLimitProof and data to a byte seq
// this conversion is used in the proof verification proc
// the order of serialization is based on https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L205
// [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> | signal_len<8> | signal<var> ]
// [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var> ]
func (r RateLimitProof) serializeWithData(data []byte) []byte {
lenPrefMsg := appendLength(data)
proofBytes := r.serialize()
@ -32,13 +49,102 @@ func (r RateLimitProof) serializeWithData(data []byte) []byte {
}
// serialize converts a RateLimitProof to a byte seq
// [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32>
// [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]
func (r RateLimitProof) serialize() []byte {
proofBytes := append(r.Proof[:], r.MerkleRoot[:]...)
proofBytes = append(proofBytes, r.Epoch[:]...)
proofBytes = append(proofBytes, r.ExternalNullifier[:]...)
proofBytes = append(proofBytes, r.ShareX[:]...)
proofBytes = append(proofBytes, r.ShareY[:]...)
proofBytes = append(proofBytes, r.Nullifier[:]...)
proofBytes = append(proofBytes, r.RLNIdentifier[:]...)
return proofBytes
}
// serialize converts a RLNWitnessInput to a byte seq
// [ id_secret_hash<32> | user_message_limit<32> | message_id<32> | num_elements<8> | path_elements<var1> | num_indexes<8> | path_indexes<var2> | external_nullifier<32> ]
func (r *RLNWitnessInput) serialize() []byte {
output := make([]byte, 0)
var userMessageLimitByte [32]byte
var messageIdByte [32]byte
binary.LittleEndian.PutUint32(userMessageLimitByte[0:], r.UserMessageLimit)
binary.LittleEndian.PutUint32(messageIdByte[0:], r.MessageId)
output = append(output, r.IDSecretHash[:]...)
output = append(output, userMessageLimitByte[:]...)
output = append(output, messageIdByte[:]...)
output = append(output, r.MerkleProof.serialize()...)
output = append(output, r.X[:]...)
output = append(output, r.ExternalNullifier[:]...)
return output
}
func (r *RLNWitnessInput) deserialize(b []byte) error {
return errors.New("not implemented")
}
func (r *MerkleProof) serialize() []byte {
output := make([]byte, 0)
output = append(output, appendLength32(Flatten(r.PathElements))...)
output = append(output, appendLength(r.PathIndexes)...)
return output
}
func (r *MerkleProof) deserialize(b []byte) error {
// Check if we can read the first byte
if len(b) < 8 {
return errors.New(fmt.Sprintf("wrong input size: %d", len(b)))
}
var numElements big.Int
var numIndexes big.Int
offset := 0
// Get amounf of elements in the proof
numElements.SetBytes(revert(b[offset : offset+8]))
offset += 8
// With numElements we can determine the expected length of the proof.
expectedLen := 8 + int(32*numElements.Uint64()) + 8 + int(numElements.Uint64())
if len(b) != expectedLen {
return errors.New(fmt.Sprintf("wrong input size expected: %d, current: %d",
expectedLen,
len(b)))
}
r.PathElements = make([]MerkleNode, numElements.Uint64())
for i := uint64(0); i < numElements.Uint64(); i++ {
copy(r.PathElements[i][:], b[offset:offset+32])
offset += 32
}
// Get amount of indexes in the path
numIndexes.SetBytes(revert(b[offset : offset+8]))
offset += 8
// Both numElements and numIndexes shall be equal and match the tree depth.
if numIndexes.Uint64() != numElements.Uint64() {
return errors.New(fmt.Sprintf("amount of values in path and indexes do not match: %s vs %s",
numElements.String(), numIndexes.String()))
}
r.PathIndexes = make([]uint8, numIndexes.Uint64())
for i := uint64(0); i < numIndexes.Uint64(); i++ {
r.PathIndexes[i] = b[offset]
offset += 1
}
if offset != len(b) {
return errors.New(
fmt.Sprintf("error parsing proof read: %d, length; %d", offset, len(b)))
}
return nil
}

65
rln/serialize_test.go Normal file
View File

@ -0,0 +1,65 @@
package rln
import (
"math/rand"
"testing"
"github.com/stretchr/testify/require"
)
func random32() [32]byte {
var randomBytes [32]byte
_, _ = rand.Read(randomBytes[:])
return randomBytes
}
func TestMerkleProofSerDe(t *testing.T) {
for _, testSize := range []int{0, 1, 8, 16, 20} {
mProof := MerkleProof{
PathElements: []MerkleNode{},
PathIndexes: []uint8{},
}
for i := 0; i < testSize; i++ {
mProof.PathElements = append(mProof.PathElements, random32())
mProof.PathIndexes = append(mProof.PathIndexes, uint8(i%2))
}
// Check the size is the expected
ser := mProof.serialize()
require.Equal(t, 8+testSize*32+testSize+8, len(ser))
// Deserialize and check its matches the original
desProof := MerkleProof{}
err := desProof.deserialize(ser)
require.NoError(t, err)
require.Equal(t, mProof, desProof)
}
}
func TestRLNWitnessInputSerDe(t *testing.T) {
depth := 20
mProof := MerkleProof{
PathElements: []MerkleNode{},
PathIndexes: []uint8{},
}
for i := 0; i < depth; i++ {
mProof.PathElements = append(mProof.PathElements, random32())
mProof.PathIndexes = append(mProof.PathIndexes, uint8(i%2))
}
witness := RLNWitnessInput{
IDSecretHash: random32(),
UserMessageLimit: 8,
MessageId: 7,
MerkleProof: mProof,
X: [32]byte{0x00},
ExternalNullifier: [32]byte{0x00},
}
ser := witness.serialize()
require.Equal(t, 32+32+32+8+depth*32+depth+8+32+32, len(ser))
}

View File

@ -3,6 +3,7 @@ package rln
import (
"bytes"
"encoding/binary"
"encoding/json"
"time"
)
@ -35,12 +36,19 @@ type IdentityCredential = struct {
// Poseidon hash function implemented in rln lib
// more details in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Membership
IDCommitment IDCommitment `json:"idCommitment"`
// user's allowed messages per epoch, added in RLN v2
UserMessageLimit uint32 `json:"userMessageLimit"`
}
func IdentityCredentialEquals(i IdentityCredential, i2 IdentityCredential) bool {
return bytes.Equal(i.IDTrapdoor[:], i2.IDTrapdoor[:]) && bytes.Equal(i.IDNullifier[:], i2.IDNullifier[:]) && bytes.Equal(i.IDSecretHash[:], i2.IDSecretHash[:]) && bytes.Equal(i.IDCommitment[:], i2.IDCommitment[:])
return bytes.Equal(i.IDTrapdoor[:], i2.IDTrapdoor[:]) &&
bytes.Equal(i.IDNullifier[:], i2.IDNullifier[:]) &&
bytes.Equal(i.IDSecretHash[:], i2.IDSecretHash[:]) &&
bytes.Equal(i.IDCommitment[:], i2.IDCommitment[:]) &&
i.UserMessageLimit == i2.UserMessageLimit
}
// Equivalent plus proof: https://github.com/vacp2p/zerokit/blob/v0.5.0/rln/src/protocol.rs#L52
type RateLimitProof struct {
// RateLimitProof holds the public inputs to rln circuit as
// defined in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Public-Inputs
@ -49,7 +57,7 @@ type RateLimitProof struct {
// the root of Merkle tree used for the generation of the `proof`
MerkleRoot MerkleNode `json:"root"`
// the epoch used for the generation of the `proof`
Epoch Epoch `json:"epoch"`
ExternalNullifier Nullifier `json:"external_nullifier"`
// shareX and shareY are shares of user's identity key
// these shares are created using Shamir secret sharing scheme
// see details in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Linear-Equation-amp-SSS
@ -58,8 +66,68 @@ type RateLimitProof struct {
// nullifier enables linking two messages published during the same epoch
// see details in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Nullifiers
Nullifier Nullifier `json:"nullifier"`
// Application specific RLN Identifier
RLNIdentifier RLNIdentifier `json:"rlnIdentifier"`
}
type MerkleProof struct {
PathElements []MerkleNode `json:"pathElements"`
PathIndexes []uint8 `json:"pathIndexes"`
}
// Equivalent: https://github.com/vacp2p/zerokit/blob/v0.5.0/rln/src/protocol.rs#L35
type RLNWitnessInput struct {
IDSecretHash IDSecretHash `json:"identitySecretHash"`
UserMessageLimit uint32 `json:"userMessageLimit"`
MessageId uint32 `json:"messageId"`
MerkleProof MerkleProof `json:"merkleProof"`
X [32]byte `json:"x"`
ExternalNullifier Nullifier `json:"externalNullifier"`
}
type TreeDepth int
const (
TreeDepth20 TreeDepth = 20
TreeDepth15 TreeDepth = 15
TreeDepth19 TreeDepth = 19
)
const DefaultTreeDepth = TreeDepth20
type TreeMode string
const (
HighThroughput TreeMode = "HighThroughput"
LowSpace TreeMode = "LowSpace"
)
type TreeConfig struct {
CacheCapacity int
Mode TreeMode
Compression bool
FlushInterval time.Duration
Path string
}
func (t TreeConfig) MarshalJSON() ([]byte, error) {
output := struct {
CacheCapacity int `json:"cache_capacity"`
Mode TreeMode `json:"mode"`
Compression bool `json:"compression"`
FlushInterval uint `json:"flush_every_ms"`
Path string `json:"path"`
}{
CacheCapacity: t.CacheCapacity,
Mode: t.Mode,
Compression: t.Compression,
FlushInterval: uint(t.FlushInterval) / uint(time.Millisecond),
Path: t.Path,
}
return json.Marshal(output)
}
type config struct {
ResourcesFolder string `json:"resources_folder"`
TreeConfig *TreeConfig `json:"tree_config,omitempty"`
}
type MembershipIndex = uint
@ -106,8 +174,6 @@ func init() {
// the root is created locally, using createMembershipList proc from waku_rln_relay_utils module, and the result is hardcoded in here
const STATIC_GROUP_MERKLE_ROOT = "ca7290e49680fa14eeaeea709e4742a8a074a1bcbfd50a4b3976742ae8a6ca25"
const EPOCH_UNIT_SECONDS = uint64(10) // the rln-relay epoch length in seconds
type Epoch [32]byte
func BytesToEpoch(b []byte) Epoch {
@ -127,13 +193,13 @@ func (e Epoch) Uint64() uint64 {
}
// CalcEpoch returns the corresponding rln `Epoch` value for a time.Time
func CalcEpoch(t time.Time) Epoch {
return ToEpoch(uint64(t.Unix()) / EPOCH_UNIT_SECONDS)
func CalcEpoch(t time.Time, epochSize uint64) Epoch {
return ToEpoch(uint64(t.Unix()) / epochSize)
}
// GetCurrentEpoch gets the current rln Epoch time
func GetCurrentEpoch() Epoch {
return CalcEpoch(time.Now())
func GetCurrentEpoch(epochSize uint64) Epoch {
return CalcEpoch(time.Now(), epochSize)
}
// Diff returns the difference between the two rln `Epoch`s `e1` and `e2`
@ -143,6 +209,6 @@ func Diff(e1, e2 Epoch) int64 {
return int64(epoch1) - int64(epoch2)
}
func (e Epoch) Time() time.Time {
return time.Unix(int64(e.Uint64()*EPOCH_UNIT_SECONDS), 0)
func (e Epoch) Time(epochSize uint64) time.Time {
return time.Unix(int64(e.Uint64()*epochSize), 0)
}

View File

@ -1,7 +1,14 @@
package rln
import (
"encoding/binary"
"encoding/hex"
"hash"
"math/big"
"sync"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
"golang.org/x/crypto/sha3"
)
func ToIdentityCredentials(groupKeys [][]string) ([]IdentityCredential, error) {
@ -55,6 +62,14 @@ func Bytes128(b []byte) [128]byte {
return result
}
func Flatten(b [][32]byte) []byte {
result := make([]byte, len(b)*32)
for i, v := range b {
copy(result[i*32:(i+1)*32], v[:])
}
return result
}
func ToBytes32LE(hexStr string) ([32]byte, error) {
b, err := hex.DecodeString(hexStr)
@ -62,9 +77,100 @@ func ToBytes32LE(hexStr string) ([32]byte, error) {
return [32]byte{}, err
}
for i := 0; i < len(b)/2; i++ {
b[i], b[len(b)-i-1] = b[len(b)-i-1], b[i]
bLen := len(b)
for i := 0; i < bLen/2; i++ {
b[i], b[bLen-i-1] = b[bLen-i-1], b[i]
}
return Bytes32(b), nil
}
func revert(b []byte) []byte {
bLen := len(b)
for i := 0; i < bLen/2; i++ {
b[i], b[bLen-i-1] = b[bLen-i-1], b[i]
}
return b
}
// BigIntToBytes32 takes a *big.Int (which uses big endian) and converts it into a little endian 32 byte array
// Notice that is the *big.Int value contains an integer <= 2^248 - 1 (a 7 bytes value with all bits on), it will right-pad the result with 0s until
// the result has 32 bytes, i.e.:
// for a some bigInt whose `Bytes()` are {0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF}, using this function will return
// {0xEF, 0xCD, 0xAB, 0x90, 0x78, 0x56, 0x34, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
func BigIntToBytes32(value *big.Int) [32]byte {
b := revert(value.Bytes())
tmp := make([]byte, 32)
copy(tmp[0:len(b)], b)
return Bytes32(tmp)
}
// Bytes32ToBigInt takes a little endian 32 byte array and returns a *big.Int (which uses big endian)
func Bytes32ToBigInt(value [32]byte) *big.Int {
b := revert(value[:])
result := new(big.Int)
result.SetBytes(b)
return result
}
// Keccak functions take from here. To avoid unnecessary dependency to go-ethereum.
// https://github.com/ethereum/go-ethereum/blob/v1.13.11/crypto/crypto.go#L62-L84
// KeccakState wraps sha3.state. In addition to the usual hash methods, it also supports
// Read to get a variable amount of data from the hash state. Read is faster than Sum
// because it doesn't copy the internal state, but also modifies the internal state.
type KeccakState interface {
hash.Hash
Read([]byte) (int, error)
}
// Avoids multiple allocations if used frequently
var keccak256Pool = sync.Pool{New: func() interface{} {
return NewKeccakState()
}}
// NewKeccakState creates a new KeccakState
func NewKeccakState() KeccakState {
return sha3.NewLegacyKeccak256().(KeccakState)
}
// Keccak256 calculates and returns the Keccak256 hash of the input data.
func Keccak256(data ...[]byte) []byte {
b := make([]byte, 32)
h, ok := keccak256Pool.Get().(KeccakState)
if !ok {
h = NewKeccakState()
}
defer keccak256Pool.Put(h)
h.Reset()
for _, b := range data {
h.Write(b)
}
h.Read(b)
return b
}
// Hashes a byte array to a field element in BN254, as used by zerokit.
// Equivalent to: https://github.com/vacp2p/zerokit/blob/v0.3.4/rln/src/hashers.rs
func HashToBN255(data []byte) [32]byte {
// Hash is fixed to 32 bytes
hashed := Keccak256(data[:])
// Convert to field element
var frBN254 fr.Element
frBN254.Unmarshal(revert(hashed))
frBN254Bytes := frBN254.Bytes()
// Return fixed size
fixexLen := [32]byte{}
copy(fixexLen[:], revert(frBN254Bytes[:]))
return fixexLen
}
func SerializeUint32(input uint32) [32]byte {
var byte32Type [32]byte
binary.LittleEndian.PutUint32(byte32Type[0:], input)
return byte32Type
}

53
rln/utils_test.go Normal file
View File

@ -0,0 +1,53 @@
package rln
import (
"bytes"
"math/big"
"testing"
"github.com/stretchr/testify/require"
)
func TestBigInt(t *testing.T) {
base := big.NewInt(2)
value := base.Exp(base, big.NewInt(248), nil)
value = value.Sub(value, big.NewInt(1)) // 2^248 - 1
b32Value := BigIntToBytes32(value)
require.True(t, bytes.Equal(b32Value[:], []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0}))
newValue := Bytes32ToBigInt(b32Value)
require.True(t, bytes.Equal(newValue.Bytes(), value.Bytes()))
}
func TestFlatten(t *testing.T) {
in1 := [][32]byte{[32]byte{}}
in2 := [][32]byte{[32]byte{0x00}, [32]byte{0x01}}
in3 := [][32]byte{[32]byte{0x01, 0x02, 0x03}, [32]byte{0x04, 0x05, 0x06}}
expected1 := []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
expected2 := []byte{
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
expected3 := []byte{
0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x4, 0x5, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
out1 := Flatten(in1)
require.Equal(t, expected1, out1)
out2 := Flatten(in2)
require.Equal(t, expected2, out2)
out3 := Flatten(in3)
require.Equal(t, expected3, out3)
}
func TestHashToBN255(t *testing.T) {
// Inputs for proof generation
msg := []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
out := HashToBN255(msg)
require.Equal(t,
[32]byte{69, 7, 140, 46, 26, 131, 147, 30, 161, 68, 2, 5, 234, 195, 227, 223, 119, 187, 116, 97, 153, 70, 71, 254, 60, 149, 54, 109, 77, 79, 105, 20},
out)
}