Add GenerateRLNProofWithWitness (#19)

This commit is contained in:
Alvaro Revuelta 2024-01-24 16:31:36 +01:00 committed by GitHub
parent 7e086e8f89
commit 14960f3aff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 534 additions and 85 deletions

14
go.mod
View File

@ -3,16 +3,20 @@ module github.com/waku-org/go-zerokit-rln
go 1.19 go 1.19
require ( require (
github.com/stretchr/testify v1.7.2 github.com/consensys/gnark-crypto v0.12.1
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116135015-f6f595c7b8ef github.com/stretchr/testify v1.8.4
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240116134931-a8b8c6ab4b80 github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240124080743-37fbb869c330
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240116135046-2875fec12afc github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240124081101-5e4387508113
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240124081123-f90cfc88a1dc
golang.org/x/crypto v0.18.0
) )
require ( require (
github.com/bits-and-blooms/bitset v1.10.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.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 gopkg.in/yaml.v3 v3.0.1 // indirect
) )

45
go.sum
View File

@ -1,36 +1,31 @@
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/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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
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/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b h1:KgZVhsLkxsj5gb/FfndSCQu6VYwALrCOgYI3poR95yE= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240124080743-37fbb869c330 h1:TJmn6GQ5HpxdZraZn6DjUqWy8UV+8pB4yWcsWFAngqE=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116092850-ad40ef83a1b5 h1:yqco0Gy1zdBDkHMAPqJ95jSlPVUxAsxK5xVeiUdxQN8= github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240124080743-37fbb869c330/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116092850-ad40ef83a1b5/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-apple v0.0.0-20240116121347-ee5a1d931442 h1:xDs9CAyhgOq9PXo06zTnd8VsPjngAyTGujeDTfsTwGg= github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240124081101-5e4387508113/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116121347-ee5a1d931442/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240124081123-f90cfc88a1dc h1:GUZlr25hXLu/PeASqm8P5dPOyD4CdfvkzyEtXEBLbr8=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116135015-f6f595c7b8ef h1:MAkZryAeRhiH3TKHRK2h+WztZI1VqfQ/oeXMIxKZNy0= 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-apple v0.0.0-20240116135015-f6f595c7b8ef/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065 h1:Sd7QD/1Yo2o2M1MY49F8Zr4KNBPUEK5cz5HoXQVJbrs= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240116134931-a8b8c6ab4b80 h1:3KObRaYJnTI41U0reNBk7DYr5PVCTq8T9gJLXGfPfaY= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240116134931-a8b8c6ab4b80/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1 h1:4HSdWMFMufpRo3ECTX6BrvA+VzKhXZf7mS0rTa5cCWU=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240116135046-2875fec12afc h1:YjIgIrqlNVC+hXNy2ykYy3JvBKima64J9jXbKM/ULpw=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240116135046-2875fec12afc/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -91,6 +91,10 @@ func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) {
return i.ffi.GenerateRLNProof(input) 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) { func (i RLNWrapper) VerifyWithRoots(input []byte, roots []byte) (bool, error) {
return i.ffi.VerifyWithRoots(input, roots) return i.ffi.VerifyWithRoots(input, roots)
} }

View File

@ -90,6 +90,10 @@ func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) {
return i.ffi.GenerateRLNProof(input) 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) { func (i RLNWrapper) VerifyWithRoots(input []byte, roots []byte) (bool, error) {
return i.ffi.VerifyWithRoots(input, roots) return i.ffi.VerifyWithRoots(input, roots)
} }

View File

@ -91,6 +91,10 @@ func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) {
return i.ffi.GenerateRLNProof(input) 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) { func (i RLNWrapper) VerifyWithRoots(input []byte, roots []byte) (bool, error) {
return i.ffi.VerifyWithRoots(input, roots) return i.ffi.VerifyWithRoots(input, roots)
} }

View File

@ -6,11 +6,14 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/big"
"github.com/waku-org/go-zerokit-rln/rln/link" "github.com/waku-org/go-zerokit-rln/rln/link"
) )
// 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}
// RLN represents the context used for rln. // RLN represents the context used for rln.
type RLN struct { type RLN struct {
w *link.RLNWrapper w *link.RLNWrapper
@ -131,13 +134,20 @@ func (r *RLN) SeededMembershipKeyGen(seed []byte) (*IdentityCredential, error) {
// appendLength returns length prefixed version of the input with the following format // 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 // [len<8>|input<var>], the len is a 8 byte value serialized in little endian
func appendLength(input []byte) []byte { func appendLength(input []byte) []byte {
inputLen := make([]byte, 8) inputLen := make([]byte, 8)
binary.LittleEndian.PutUint64(inputLen, uint64(len(input))) binary.LittleEndian.PutUint64(inputLen, uint64(len(input)))
return append(inputLen, 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) { func (r *RLN) Sha256(data []byte) (MerkleNode, error) {
lenPrefData := appendLength(data) lenPrefData := appendLength(data)
@ -233,6 +243,56 @@ func (r *RLN) GenerateProof(data []byte, key IdentityCredential, index Membershi
}, nil }, 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
// input [ id_secret_hash<32> | num_elements<8> | path_elements<var1> | num_indexes<8> | path_indexes<var2> | x<32> | epoch<32> | rln_identifier<32> ]
// output [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ]
func (r *RLN) GenerateRLNProofWithWitness(witness RLNWitnessInput) (*RateLimitProof, error) {
proofBytes, err := r.w.GenerateRLNProofWithWitness(witness.serialize())
if err != nil {
return nil, err
}
if len(proofBytes) != 320 {
return nil, errors.New("invalid proof generated")
}
// parse the proof as [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ]
proofOffset := 128
rootOffset := proofOffset + 32
epochOffset := rootOffset + 32
shareXOffset := epochOffset + 32
shareYOffset := shareXOffset + 32
nullifierOffset := shareYOffset + 32
rlnIdentifierOffset := nullifierOffset + 32
var zkproof ZKSNARK
var proofRoot, shareX, shareY MerkleNode
var epochR Epoch
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(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,
}, nil
}
func serialize32(roots [][32]byte) []byte { func serialize32(roots [][32]byte) []byte {
var result []byte var result []byte
for _, r := range roots { for _, r := range roots {
@ -292,7 +352,7 @@ func (r *RLN) Verify(data []byte, proof RateLimitProof, roots ...[32]byte) (bool
return false, err return false, err
} }
return bool(res), nil return res, nil
} }
// RecoverIDSecret returns an IDSecret having obtained before two proofs // RecoverIDSecret returns an IDSecret having obtained before two proofs
@ -406,58 +466,10 @@ func (r *RLN) GetMerkleProof(index MembershipIndex) (MerkleProof, error) {
return MerkleProof{}, err return MerkleProof{}, err
} }
// Check if we can read the first byte
if len(proofBytes) < 8 {
return MerkleProof{}, errors.New(fmt.Sprintf("wrong output size: %d", len(proofBytes)))
}
var result MerkleProof var result MerkleProof
var numElements big.Int err = result.deserialize(proofBytes)
var numIndexes big.Int if err != nil {
return MerkleProof{}, err
offset := 0
// Get amounf of elements in the proof
numElements.SetBytes(revert(proofBytes[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(proofBytes) != expectedLen {
return MerkleProof{}, errors.New(fmt.Sprintf("wrong output size expected: %d, current: %d",
expectedLen,
len(proofBytes)))
}
result.PathElements = make([]MerkleNode, numElements.Uint64())
for i := uint64(0); i < numElements.Uint64(); i++ {
copy(result.PathElements[i][:], proofBytes[offset:offset+32])
offset += 32
}
// Get amount of indexes in the path
numIndexes.SetBytes(revert(proofBytes[offset : offset+8]))
offset += 8
// Both numElements and numIndexes shall be equal and match the tree depth.
if numIndexes.Uint64() != numElements.Uint64() {
return MerkleProof{}, errors.New(fmt.Sprintf("amount of values in path and indexes do not match: %s vs %s",
numElements.String(), numIndexes.String()))
}
// TODO: Depth check, but currently not accesible
result.PathIndexes = make([]uint8, numIndexes.Uint64())
for i := uint64(0); i < numIndexes.Uint64(); i++ {
result.PathIndexes[i] = proofBytes[offset]
offset += 1
}
if offset != len(proofBytes) {
return MerkleProof{}, errors.New(
fmt.Sprintf("error parsing proof read: %d, length; %d", offset, len(proofBytes)))
} }
return result, nil return result, nil

View File

@ -350,6 +350,157 @@ func (s *RLNSuite) TestGetMerkleProof() {
} }
} }
func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesOK() {
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)
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)
rlnWitness := CreateWitness(
treeElements[memberIndex].IDSecretHash,
message,
epoch,
merkleProof)
// 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)
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.Epoch, proofRes2.Epoch)
s.Equal(proofRes1.ShareX, proofRes2.ShareX)
s.Equal(proofRes1.ShareY, proofRes2.ShareY)
s.Equal(proofRes1.Nullifier, proofRes2.Nullifier)
s.Equal(proofRes1.RLNIdentifier, proofRes2.RLNIdentifier)
}
}
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)
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)
rlnWitness1 := CreateWitness(
treeElements[memberIndex].IDSecretHash,
message,
epoch,
merkleProof)
// 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 epoch, does not verify
proofRes1.Epoch = ToEpoch(999)
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 := CreateWitness(
treeElements[memberIndex].IDSecretHash,
message,
epoch,
merkleProof)
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 := CreateWitness(
memberKeys.IDSecretHash,
[]byte("some rln protected message"),
ToEpoch(999),
merkleProof4)
proofRes4, err := rln.GenerateRLNProofWithWitness(rlnWitness4)
s.NoError(err)
verified4, err := rln.Verify(message, *proofRes4, root)
s.NoError(err)
s.False(verified4)
}
}
func (s *RLNSuite) TestEpochConsistency() { func (s *RLNSuite) TestEpochConsistency() {
// check edge cases // check edge cases
var epoch uint64 = math.MaxUint64 var epoch uint64 = math.MaxUint64

View File

@ -1,6 +1,11 @@
package rln package rln
import "encoding/binary" import (
"encoding/binary"
"errors"
"fmt"
"math/big"
)
// serialize converts a RateLimitProof and the data to a byte seq // serialize converts a RateLimitProof and the data to a byte seq
// this conversion is used in the proofGen function // this conversion is used in the proofGen function
@ -42,3 +47,85 @@ func (r RateLimitProof) serialize() []byte {
proofBytes = append(proofBytes, r.RLNIdentifier[:]...) proofBytes = append(proofBytes, r.RLNIdentifier[:]...)
return proofBytes return proofBytes
} }
func (r *RLNWitnessInput) serialize() []byte {
output := make([]byte, 0)
output = append(output, r.IDSecretHash[:]...)
output = append(output, r.MerkleProof.serialize()...)
output = append(output, r.X[:]...)
output = append(output, r.Epoch[:]...)
output = append(output, r.RlnIdentifier[:]...)
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
}

64
rln/serialize_test.go Normal file
View File

@ -0,0 +1,64 @@
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(),
MerkleProof: mProof,
X: [32]byte{0x00},
Epoch: ToEpoch(10),
RlnIdentifier: [32]byte{0x00},
}
ser := witness.serialize()
require.Equal(t, 32+8+depth*32+depth+8+32+32+32, len(ser))
}

View File

@ -68,6 +68,15 @@ type MerkleProof struct {
PathIndexes []uint8 `json:"pathIndexes"` PathIndexes []uint8 `json:"pathIndexes"`
} }
// Equivalent: https://github.com/vacp2p/zerokit/blob/v0.3.5/rln/src/protocol.rs#L33-L40
type RLNWitnessInput struct {
IDSecretHash IDSecretHash `json:"identitySecretHash"`
MerkleProof MerkleProof `json:"merkleProof"`
X [32]byte `json:"x"`
Epoch Epoch `json:"epoch"`
RlnIdentifier RLNIdentifier `json:"rlnIdentifier"`
}
type TreeDepth int type TreeDepth int
const ( const (

View File

@ -2,9 +2,29 @@ package rln
import ( import (
"encoding/hex" "encoding/hex"
"hash"
"math/big" "math/big"
"sync"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
"golang.org/x/crypto/sha3"
) )
func CreateWitness(
idSecretHash IDSecretHash,
data []byte,
epoch [32]byte,
merkleProof MerkleProof) RLNWitnessInput {
return RLNWitnessInput{
IDSecretHash: idSecretHash,
MerkleProof: merkleProof,
X: HashToBN255(data),
Epoch: epoch,
RlnIdentifier: RLN_IDENTIFIER,
}
}
func ToIdentityCredentials(groupKeys [][]string) ([]IdentityCredential, error) { func ToIdentityCredentials(groupKeys [][]string) ([]IdentityCredential, error) {
// groupKeys is sequence of membership key tuples in the form of (identity key, identity commitment) all in the hexadecimal format // groupKeys is sequence of membership key tuples in the form of (identity key, identity commitment) all in the hexadecimal format
// the toIdentityCredentials proc populates a sequence of IdentityCredentials using the supplied groupKeys // the toIdentityCredentials proc populates a sequence of IdentityCredentials using the supplied groupKeys
@ -56,6 +76,14 @@ func Bytes128(b []byte) [128]byte {
return result 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) { func ToBytes32LE(hexStr string) ([32]byte, error) {
b, err := hex.DecodeString(hexStr) b, err := hex.DecodeString(hexStr)
@ -98,3 +126,58 @@ func Bytes32ToBigInt(value [32]byte) *big.Int {
result.SetBytes(b) result.SetBytes(b)
return result 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
}

View File

@ -19,3 +19,35 @@ func TestBigInt(t *testing.T) {
newValue := Bytes32ToBigInt(b32Value) newValue := Bytes32ToBigInt(b32Value)
require.True(t, bytes.Equal(newValue.Bytes(), value.Bytes())) 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)
}