From 54bb48f178cbbbd9f52bd4110f67f3b881b88399 Mon Sep 17 00:00:00 2001 From: Alvaro Revuelta Date: Fri, 24 May 2024 10:17:29 +0200 Subject: [PATCH] Update to RLN v2 (#21) --- go.mod | 7 +- go.sum | 18 ++++- rln/link/apple.go | 9 ++- rln/link/arm.go | 9 ++- rln/link/x86_64.go | 9 ++- rln/rln.go | 142 +++++++++++++++++++++++---------------- rln/rln_test.go | 163 ++++++++++++++++++++++++++++++++++----------- rln/serialize.go | 29 +++++--- rln/types.go | 13 ++-- rln/utils.go | 8 +++ 10 files changed, 289 insertions(+), 118 deletions(-) diff --git a/go.mod b/go.mod index 4ab3934..b661fa1 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,11 @@ go 1.19 require ( 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-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 + github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240523161310-d005fe7ba59c + github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240523161300-8203361a01d0 + github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240523161247-6f16d12c5a86 golang.org/x/crypto v0.18.0 ) diff --git a/go.sum b/go.sum index 43ca8e9..66788d9 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6 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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -13,19 +14,32 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/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-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-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-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-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= 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/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= diff --git a/rln/link/apple.go b/rln/link/apple.go index c09ae08..1db5378 100644 --- a/rln/link/apple.go +++ b/rln/link/apple.go @@ -5,7 +5,11 @@ package link -import r "github.com/waku-org/go-zerokit-rln-apple/rln" +import ( + "errors" + + r "github.com/waku-org/go-zerokit-rln-apple/rln" +) type RLNWrapper struct { ffi *r.RLN @@ -92,7 +96,8 @@ func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) { } func (i RLNWrapper) GenerateRLNProofWithWitness(input []byte) ([]byte, error) { - return i.ffi.GenerateRLNProofWithWitness(input) + return nil, errors.New("not implemented") + //return i.ffi.GenerateRLNProofWithWitness(input) } func (i RLNWrapper) VerifyWithRoots(input []byte, roots []byte) (bool, error) { diff --git a/rln/link/arm.go b/rln/link/arm.go index f7db9a7..2536f6c 100644 --- a/rln/link/arm.go +++ b/rln/link/arm.go @@ -4,7 +4,11 @@ package link -import r "github.com/waku-org/go-zerokit-rln-arm/rln" +import ( + "errors" + + r "github.com/waku-org/go-zerokit-rln-arm/rln" +) type RLNWrapper struct { ffi *r.RLN @@ -91,7 +95,8 @@ func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) { } func (i RLNWrapper) GenerateRLNProofWithWitness(input []byte) ([]byte, error) { - return i.ffi.GenerateRLNProofWithWitness(input) + return nil, errors.New("not implemented") + //return i.ffi.GenerateRLNProofWithWitness(input) } func (i RLNWrapper) VerifyWithRoots(input []byte, roots []byte) (bool, error) { diff --git a/rln/link/x86_64.go b/rln/link/x86_64.go index d151518..6847ab3 100644 --- a/rln/link/x86_64.go +++ b/rln/link/x86_64.go @@ -5,7 +5,11 @@ package link -import r "github.com/waku-org/go-zerokit-rln-x86_64/rln" +import ( + "errors" + + r "github.com/waku-org/go-zerokit-rln-x86_64/rln" +) type RLNWrapper struct { ffi *r.RLN @@ -92,7 +96,8 @@ func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) { } func (i RLNWrapper) GenerateRLNProofWithWitness(input []byte) ([]byte, error) { - return i.ffi.GenerateRLNProofWithWitness(input) + return nil, errors.New("not implemented") + //return i.ffi.GenerateRLNProofWithWitness(input) } func (i RLNWrapper) VerifyWithRoots(input []byte, roots []byte) (bool, error) { diff --git a/rln/rln.go b/rln/rln.go index 80b5353..ace17d8 100644 --- a/rln/rln.go +++ b/rln/rln.go @@ -14,6 +14,8 @@ import ( // 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 @@ -91,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 { @@ -113,23 +117,45 @@ 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 @@ -181,65 +207,61 @@ 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 } @@ -248,6 +270,8 @@ func (r *RLN) GenerateProof(data []byte, key IdentityCredential, index Membershi // input [ id_secret_hash<32> | num_elements<8> | path_elements | num_indexes<8> | path_indexes | 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) { + // TODO: Will be implemented once custom witness is supported in RLN v2 + return nil, errors.New("not implemented") proofBytes, err := r.w.GenerateRLNProofWithWitness(witness.serialize()) if err != nil { @@ -282,13 +306,11 @@ func (r *RLN) GenerateRLNProofWithWitness(witness RLNWitnessInput) (*RateLimitPr 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, + ShareX: shareX, + ShareY: shareY, + Nullifier: nullifier, }, nil } @@ -368,9 +390,17 @@ 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") } @@ -476,9 +506,9 @@ func (r *RLN) GetMerkleProof(index MembershipIndex) (MerkleProof, error) { } // 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 } } @@ -520,7 +550,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 } } diff --git a/rln/rln_test.go b/rln/rln_test.go index f751cdb..cdc481f 100644 --- a/rln/rln_test.go +++ b/rln/rln_test.go @@ -76,7 +76,7 @@ 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) } @@ -105,7 +105,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)) @@ -123,7 +123,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 @@ -207,37 +207,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) } } @@ -245,24 +272,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() { @@ -279,14 +357,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) } } @@ -302,8 +380,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) @@ -351,6 +432,7 @@ func (s *RLNSuite) TestGetMerkleProof() { } func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesOK() { + s.T().Skip("Skipped until proof generation with witness is implemented for RLNv2") treeSize := 20 rln, err := NewRLN() @@ -363,7 +445,7 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesOK() { memberKeys, err := rln.MembershipKeyGen() s.NoError(err) - err = rln.InsertMember(memberKeys.IDCommitment) + err = rln.InsertMember(memberKeys.IDCommitment, memberKeys.UserMessageLimit) s.NoError(err) treeElements = append(treeElements, *memberKeys) } @@ -393,8 +475,11 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesOK() { s.NoError(err) s.True(verified1) + // message sequence within the epoch + messageId := uint32(1) + // Generate a proof without our custom witness, to ensure they match - proofRes2, err := rln.GenerateProof(message, treeElements[memberIndex], MembershipIndex(memberIndex), epoch) + proofRes2, err := rln.GenerateProof(message, treeElements[memberIndex], MembershipIndex(memberIndex), epoch, messageId) s.NoError(err) // Ensure we have the same root @@ -403,15 +488,17 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesOK() { // 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.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) + //s.Equal(proofRes1.RLNIdentifier, proofRes2.RLNIdentifier) } } func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesNOK() { + s.T().Skip("Skipped until proof generation with witness is implemented for RLNv2") + treeSize := 20 rln, err := NewRLN() @@ -424,7 +511,7 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesNOK() { memberKeys, err := rln.MembershipKeyGen() s.NoError(err) - err = rln.InsertMember(memberKeys.IDCommitment) + err = rln.InsertMember(memberKeys.IDCommitment, memberKeys.UserMessageLimit) s.NoError(err) treeElements = append(treeElements, *memberKeys) } @@ -457,7 +544,7 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesNOK() { s.False(verified1) // 2) Different epoch, does not verify - proofRes1.Epoch = ToEpoch(999) + //proofRes1.Epoch = ToEpoch(999) verified2, err := rln.Verify(message, *proofRes1, root) s.NoError(err) s.False(verified2) diff --git a/rln/serialize.go b/rln/serialize.go index b10664b..e5579ad 100644 --- a/rln/serialize.go +++ b/rln/serialize.go @@ -8,18 +8,30 @@ import ( ) // 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 ] -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 ] +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 @@ -28,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 ] +// [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal ] func (r RateLimitProof) serializeWithData(data []byte) []byte { lenPrefMsg := appendLength(data) proofBytes := r.serialize() @@ -37,14 +49,13 @@ 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 } diff --git a/rln/types.go b/rln/types.go index 7d8f0dc..dbdb2c6 100644 --- a/rln/types.go +++ b/rln/types.go @@ -36,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 @@ -50,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 @@ -59,8 +66,6 @@ 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 { diff --git a/rln/utils.go b/rln/utils.go index d87313d..a35b3cf 100644 --- a/rln/utils.go +++ b/rln/utils.go @@ -1,6 +1,7 @@ package rln import ( + "encoding/binary" "encoding/hex" "hash" "math/big" @@ -181,3 +182,10 @@ func HashToBN255(data []byte) [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 +}