mirror of
https://github.com/waku-org/go-zerokit-rln.git
synced 2025-03-01 04:30:33 +00:00
Add GenerateRLNProofWithWitness (#19)
This commit is contained in:
parent
7e086e8f89
commit
14960f3aff
14
go.mod
14
go.mod
@ -3,16 +3,20 @@ module github.com/waku-org/go-zerokit-rln
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.7.2
|
||||
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116135015-f6f595c7b8ef
|
||||
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240116134931-a8b8c6ab4b80
|
||||
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240116135046-2875fec12afc
|
||||
github.com/consensys/gnark-crypto v0.12.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240124080743-37fbb869c330
|
||||
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 (
|
||||
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
|
||||
)
|
||||
|
45
go.sum
45
go.sum
@ -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/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/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-20230916172309-ee0ee61dde2b h1:KgZVhsLkxsj5gb/FfndSCQu6VYwALrCOgYI3poR95yE=
|
||||
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-20240116092850-ad40ef83a1b5 h1:yqco0Gy1zdBDkHMAPqJ95jSlPVUxAsxK5xVeiUdxQN8=
|
||||
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116092850-ad40ef83a1b5/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48=
|
||||
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116121347-ee5a1d931442 h1:xDs9CAyhgOq9PXo06zTnd8VsPjngAyTGujeDTfsTwGg=
|
||||
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116121347-ee5a1d931442/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48=
|
||||
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116135015-f6f595c7b8ef h1:MAkZryAeRhiH3TKHRK2h+WztZI1VqfQ/oeXMIxKZNy0=
|
||||
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116135015-f6f595c7b8ef/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48=
|
||||
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065 h1:Sd7QD/1Yo2o2M1MY49F8Zr4KNBPUEK5cz5HoXQVJbrs=
|
||||
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4=
|
||||
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240116134931-a8b8c6ab4b80 h1:3KObRaYJnTI41U0reNBk7DYr5PVCTq8T9gJLXGfPfaY=
|
||||
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=
|
||||
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/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-20240124080743-37fbb869c330 h1:TJmn6GQ5HpxdZraZn6DjUqWy8UV+8pB4yWcsWFAngqE=
|
||||
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240124080743-37fbb869c330/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-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=
|
||||
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.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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -91,6 +91,10 @@ 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)
|
||||
}
|
||||
|
@ -90,6 +90,10 @@ 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)
|
||||
}
|
||||
|
@ -91,6 +91,10 @@ 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)
|
||||
}
|
||||
|
120
rln/rln.go
120
rln/rln.go
@ -6,11 +6,14 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"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.
|
||||
type RLN struct {
|
||||
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
|
||||
// [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)
|
||||
|
||||
@ -233,6 +243,56 @@ func (r *RLN) GenerateProof(data []byte, key IdentityCredential, index Membershi
|
||||
}, 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 {
|
||||
var result []byte
|
||||
for _, r := range roots {
|
||||
@ -292,7 +352,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
|
||||
@ -406,58 +466,10 @@ func (r *RLN) GetMerkleProof(index MembershipIndex) (MerkleProof, error) {
|
||||
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 numElements big.Int
|
||||
var numIndexes big.Int
|
||||
|
||||
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)))
|
||||
err = result.deserialize(proofBytes)
|
||||
if err != nil {
|
||||
return MerkleProof{}, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
151
rln/rln_test.go
151
rln/rln_test.go
@ -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() {
|
||||
// check edge cases
|
||||
var epoch uint64 = math.MaxUint64
|
||||
|
@ -1,6 +1,11 @@
|
||||
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
|
||||
@ -42,3 +47,85 @@ func (r RateLimitProof) serialize() []byte {
|
||||
proofBytes = append(proofBytes, r.RLNIdentifier[:]...)
|
||||
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
64
rln/serialize_test.go
Normal 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))
|
||||
}
|
@ -68,6 +68,15 @@ type MerkleProof struct {
|
||||
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
|
||||
|
||||
const (
|
||||
|
83
rln/utils.go
83
rln/utils.go
@ -2,9 +2,29 @@ package rln
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
"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) {
|
||||
// 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
|
||||
@ -56,6 +76,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)
|
||||
@ -98,3 +126,58 @@ func Bytes32ToBigInt(value [32]byte) *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
|
||||
}
|
||||
|
@ -19,3 +19,35 @@ func TestBigInt(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user