From 84d12e61d9181a15897044326273eb0d4466876d Mon Sep 17 00:00:00 2001 From: Alvaro Revuelta Date: Thu, 30 May 2024 14:53:54 +0200 Subject: [PATCH] Custom witness proof with RLN v2 (#22) --- go.mod | 6 +-- go.sum | 8 ++++ rln/link/apple.go | 5 +- rln/link/arm.go | 5 +- rln/link/x86_64.go | 5 +- rln/rln.go | 64 ++++++++++++++++--------- rln/rln_test.go | 107 +++++++++++++++++++++++++++--------------- rln/serialize.go | 12 ++++- rln/serialize_test.go | 13 ++--- rln/types.go | 13 ++--- rln/utils.go | 15 ------ 11 files changed, 149 insertions(+), 104 deletions(-) diff --git a/go.mod b/go.mod index b661fa1..37f385f 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,9 @@ 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-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 + github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240529153423-5df5db48b69f + github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240529153432-be2c8ac0a840 + github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240529153442-f5fb416605f5 golang.org/x/crypto v0.18.0 ) diff --git a/go.sum b/go.sum index 66788d9..3f6fa3e 100644 --- a/go.sum +++ b/go.sum @@ -25,14 +25,22 @@ github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240522110429-626138029176 h1:e github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240522110429-626138029176/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240523161310-d005fe7ba59c h1:/eGH8EAt5/zGfNRBQ0nJMrfZDeXRSJrm8E8uCPlsC3A= github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240523161310-d005fe7ba59c/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240528140707-ed6b40a98d7b h1:LEa2s1p+Z8SN475dVr3XDmvmGyKzIDKPcAQ+6hTyVwA= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240528140707-ed6b40a98d7b/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240529153423-5df5db48b69f h1:CEBW4vu8I60OakKExZUE7G4oY7Z/glQXxPYedpZ4Sq8= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240529153423-5df5db48b69f/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240124081101-5e4387508113 h1:dPwc4LAWLXb4Pssej/NtGA9A0UMQwi+JafQPdnhjRWM= github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240124081101-5e4387508113/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4= github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240523161300-8203361a01d0 h1:IvtkZOcApOkEmHkT/drDmMtY6fdYpF7x4sesWyIURpI= github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240523161300-8203361a01d0/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4= +github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240529153432-be2c8ac0a840 h1:DKub+sG+vfKqwOCaKrthhJA/bP7gTZWxbdrFV86Q5Ms= +github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240529153432-be2c8ac0a840/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4= github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240124081123-f90cfc88a1dc h1:GUZlr25hXLu/PeASqm8P5dPOyD4CdfvkzyEtXEBLbr8= github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240124081123-f90cfc88a1dc/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y= github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240523161247-6f16d12c5a86 h1:PN1WSt3u/DEIn4hX5Oqrm9bm5nf5VBfenfXmbX4mg60= github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240523161247-6f16d12c5a86/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y= +github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240529153442-f5fb416605f5 h1:ZhrzpAjIUZHD6gSKPA8zwHjIys9/GTGN3hPKtwMORSA= +github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240529153442-f5fb416605f5/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/rln/link/apple.go b/rln/link/apple.go index 1db5378..0731e61 100644 --- a/rln/link/apple.go +++ b/rln/link/apple.go @@ -6,8 +6,6 @@ package link import ( - "errors" - r "github.com/waku-org/go-zerokit-rln-apple/rln" ) @@ -96,8 +94,7 @@ func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) { } func (i RLNWrapper) GenerateRLNProofWithWitness(input []byte) ([]byte, error) { - return nil, errors.New("not implemented") - //return i.ffi.GenerateRLNProofWithWitness(input) + 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 2536f6c..470067f 100644 --- a/rln/link/arm.go +++ b/rln/link/arm.go @@ -5,8 +5,6 @@ package link import ( - "errors" - r "github.com/waku-org/go-zerokit-rln-arm/rln" ) @@ -95,8 +93,7 @@ func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) { } func (i RLNWrapper) GenerateRLNProofWithWitness(input []byte) ([]byte, error) { - return nil, errors.New("not implemented") - //return i.ffi.GenerateRLNProofWithWitness(input) + 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 6847ab3..c6fea82 100644 --- a/rln/link/x86_64.go +++ b/rln/link/x86_64.go @@ -6,8 +6,6 @@ package link import ( - "errors" - r "github.com/waku-org/go-zerokit-rln-x86_64/rln" ) @@ -96,8 +94,7 @@ func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) { } func (i RLNWrapper) GenerateRLNProofWithWitness(input []byte) ([]byte, error) { - return nil, errors.New("not implemented") - //return i.ffi.GenerateRLNProofWithWitness(input) + 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 ace17d8..7049c54 100644 --- a/rln/rln.go +++ b/rln/rln.go @@ -266,53 +266,73 @@ func (r *RLN) GenerateProof( } // 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 | 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> ] +// to calculate such proof. The witness can be created with GetMerkleProof data. 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") - + // serialized as: https://github.com/vacp2p/zerokit/blob/v0.5.0/rln/src/protocol.rs#L127 + // input [ id_secret_hash<32> | user_message_limit<32> | message_id<32> | num_elements<8> | path_elements | num_indexes<8> | path_indexes | external_nullifier<32> ] proofBytes, err := r.w.GenerateRLNProofWithWitness(witness.serialize()) 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, - ShareX: shareX, - ShareY: shareY, - Nullifier: nullifier, + Proof: zkproof, + MerkleRoot: proofRoot, + ExternalNullifier: externalNullifier, + ShareX: shareX, + ShareY: shareY, + Nullifier: nullifier, }, nil +} +func (r *RLN) CreateWitness( + idSecretHash IDSecretHash, + userMessageLimit uint32, + messageId uint32, + data []byte, + epoch [32]byte, + merkleProof MerkleProof) (RLNWitnessInput, error) { + + externalNullifier, err := r.Poseidon(epoch[:], RLN_IDENTIFIER[:]) + if err != nil { + return RLNWitnessInput{}, fmt.Errorf("could not construct the external nullifier: %w", err) + } + + return RLNWitnessInput{ + IDSecretHash: idSecretHash, + UserMessageLimit: userMessageLimit, + MessageId: messageId, + MerkleProof: merkleProof, + X: HashToBN255(data), + ExternalNullifier: externalNullifier, + }, nil } func serialize32(roots [][32]byte) []byte { diff --git a/rln/rln_test.go b/rln/rln_test.go index cdc481f..6fbace9 100644 --- a/rln/rln_test.go +++ b/rln/rln_test.go @@ -432,8 +432,9 @@ func (s *RLNSuite) TestGetMerkleProof() { } func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesOK() { - s.T().Skip("Skipped until proof generation with witness is implemented for RLNv2") treeSize := 20 + userMessageLimit := uint32(100) + message := []byte("some rln protected message") rln, err := NewRLN() s.NoError(err) @@ -442,7 +443,7 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesOK() { // Create a Merkle tree with random members for i := 0; i < treeSize; i++ { - memberKeys, err := rln.MembershipKeyGen() + memberKeys, err := rln.MembershipKeyGen(userMessageLimit) s.NoError(err) err = rln.InsertMember(memberKeys.IDCommitment, memberKeys.UserMessageLimit) @@ -450,8 +451,8 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesOK() { 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} { + // For different leafs (with a custom witness) + for _, memberIndex := range []uint{0, 10, 13} { root, err := rln.GetMerkleRoot() s.NoError(err) @@ -459,45 +460,48 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesOK() { merkleProof, err := rln.GetMerkleProof(memberIndex) s.NoError(err) - message := []byte("some rln protected message") - epoch := ToEpoch(1000) + // For different epochs + for _, epoch := range []Epoch{ToEpoch(1), ToEpoch(9998765)} { - rlnWitness := CreateWitness( - treeElements[memberIndex].IDSecretHash, - message, - epoch, - merkleProof) + // For some possible message ids + for _, messageId := range []uint32{0, 50, 99} { - // 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) + rlnWitness, err := rln.CreateWitness( + treeElements[memberIndex].IDSecretHash, + userMessageLimit, + messageId, + message, + epoch, + merkleProof) + s.NoError(err) - // message sequence within the epoch - messageId := uint32(1) + // Generate a proof with our custom witness (Merkle Path of the memberIndex) + proofRes1, err := rln.GenerateRLNProofWithWitness(rlnWitness) + s.NoError(err) + verified1, err := rln.Verify(message, *proofRes1, root) + s.NoError(err) + s.True(verified1) - // Generate a proof without our custom witness, to ensure they match - proofRes2, err := rln.GenerateProof(message, treeElements[memberIndex], MembershipIndex(memberIndex), epoch, messageId) - s.NoError(err) + // Generate a proof without our custom witness, to ensure they match + proofRes2, err := rln.GenerateProof(message, treeElements[memberIndex], MembershipIndex(memberIndex), epoch, messageId) + s.NoError(err) - // Ensure we have the same root - s.Equal(root, proofRes1.MerkleRoot) + // 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) + // Proof generate with custom witness match the proof generate with the witness + // from zerokit. Proof itself is not asserted, can be different. + s.Equal(proofRes1.MerkleRoot, proofRes2.MerkleRoot) + s.Equal(proofRes1.ExternalNullifier, proofRes2.ExternalNullifier) + s.Equal(proofRes1.ShareX, proofRes2.ShareX) + s.Equal(proofRes1.ShareY, proofRes2.ShareY) + s.Equal(proofRes1.Nullifier, proofRes2.Nullifier) + } + } } } func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesNOK() { - s.T().Skip("Skipped until proof generation with witness is implemented for RLNv2") treeSize := 20 @@ -528,11 +532,17 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesNOK() { message := []byte("some rln protected message") epoch := ToEpoch(1000) - rlnWitness1 := CreateWitness( + userMessageLimit := uint32(10) + messageId := uint32(1) + + rlnWitness1, err := rln.CreateWitness( treeElements[memberIndex].IDSecretHash, + userMessageLimit, + messageId, message, epoch, merkleProof) + s.NoError(err) // Generate a proof with our custom witness (Merkle Path of the memberIndex) proofRes1, err := rln.GenerateRLNProofWithWitness(rlnWitness1) @@ -543,19 +553,23 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesNOK() { s.NoError(err) s.False(verified1) - // 2) Different epoch, does not verify - //proofRes1.Epoch = ToEpoch(999) + // 2) Different nullifier (epoch or rln id), does not verify + proofRes1.ExternalNullifier = [32]byte{0x11} verified2, err := rln.Verify(message, *proofRes1, root) s.NoError(err) s.False(verified2) // 3) Merkle proof in provided witness is wrong, does not verify merkleProof.PathElements[0] = [32]byte{0x11} - rlnWitness2 := CreateWitness( + + rlnWitness2, err := rln.CreateWitness( treeElements[memberIndex].IDSecretHash, + userMessageLimit, + messageId, message, epoch, merkleProof) + s.NoError(err) proofRes3, err := rln.GenerateRLNProofWithWitness(rlnWitness2) s.NoError(err) @@ -573,11 +587,14 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesNOK() { s.NoError(err) // Proof proves memberIndex inclusion, but provided membership is different - rlnWitness4 := CreateWitness( + rlnWitness4, err := rln.CreateWitness( memberKeys.IDSecretHash, + userMessageLimit, + messageId, []byte("some rln protected message"), ToEpoch(999), merkleProof4) + s.NoError(err) proofRes4, err := rln.GenerateRLNProofWithWitness(rlnWitness4) s.NoError(err) @@ -585,6 +602,20 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness_VerifiesNOK() { verified4, err := rln.Verify(message, *proofRes4, root) s.NoError(err) s.False(verified4) + + // 5) Message id goes beyond the userMessageLimit, does not generate + wrongMessageId := uint32(1000) + rlnWitness5, err := rln.CreateWitness( + treeElements[memberIndex].IDSecretHash, + userMessageLimit, + wrongMessageId, + message, + epoch, + merkleProof) + s.NoError(err) + + _, err = rln.GenerateRLNProofWithWitness(rlnWitness5) + s.Error(err) } } diff --git a/rln/serialize.go b/rln/serialize.go index e5579ad..8db3f5b 100644 --- a/rln/serialize.go +++ b/rln/serialize.go @@ -59,14 +59,22 @@ func (r RateLimitProof) serialize() []byte { return proofBytes } +// serialize converts a RLNWitnessInput to a byte seq +// [ id_secret_hash<32> | user_message_limit<32> | message_id<32> | num_elements<8> | path_elements | num_indexes<8> | path_indexes | external_nullifier<32> ] func (r *RLNWitnessInput) serialize() []byte { output := make([]byte, 0) + var userMessageLimitByte [32]byte + var messageIdByte [32]byte + binary.LittleEndian.PutUint32(userMessageLimitByte[0:], r.UserMessageLimit) + binary.LittleEndian.PutUint32(messageIdByte[0:], r.MessageId) + output = append(output, r.IDSecretHash[:]...) + output = append(output, userMessageLimitByte[:]...) + output = append(output, messageIdByte[:]...) output = append(output, r.MerkleProof.serialize()...) output = append(output, r.X[:]...) - output = append(output, r.Epoch[:]...) - output = append(output, r.RlnIdentifier[:]...) + output = append(output, r.ExternalNullifier[:]...) return output } diff --git a/rln/serialize_test.go b/rln/serialize_test.go index 78a97c5..da5e78b 100644 --- a/rln/serialize_test.go +++ b/rln/serialize_test.go @@ -52,13 +52,14 @@ func TestRLNWitnessInputSerDe(t *testing.T) { } witness := RLNWitnessInput{ - IDSecretHash: random32(), - MerkleProof: mProof, - X: [32]byte{0x00}, - Epoch: ToEpoch(10), - RlnIdentifier: [32]byte{0x00}, + IDSecretHash: random32(), + UserMessageLimit: 8, + MessageId: 7, + MerkleProof: mProof, + X: [32]byte{0x00}, + ExternalNullifier: [32]byte{0x00}, } ser := witness.serialize() - require.Equal(t, 32+8+depth*32+depth+8+32+32+32, len(ser)) + require.Equal(t, 32+32+32+8+depth*32+depth+8+32+32, len(ser)) } diff --git a/rln/types.go b/rln/types.go index dbdb2c6..d0126c6 100644 --- a/rln/types.go +++ b/rln/types.go @@ -73,13 +73,14 @@ type MerkleProof struct { PathIndexes []uint8 `json:"pathIndexes"` } -// Equivalent: https://github.com/vacp2p/zerokit/blob/v0.3.5/rln/src/protocol.rs#L33-L40 +// Equivalent: https://github.com/vacp2p/zerokit/blob/v0.5.0/rln/src/protocol.rs#L35 type RLNWitnessInput struct { - IDSecretHash IDSecretHash `json:"identitySecretHash"` - MerkleProof MerkleProof `json:"merkleProof"` - X [32]byte `json:"x"` - Epoch Epoch `json:"epoch"` - RlnIdentifier RLNIdentifier `json:"rlnIdentifier"` + IDSecretHash IDSecretHash `json:"identitySecretHash"` + UserMessageLimit uint32 `json:"userMessageLimit"` + MessageId uint32 `json:"messageId"` + MerkleProof MerkleProof `json:"merkleProof"` + X [32]byte `json:"x"` + ExternalNullifier Nullifier `json:"externalNullifier"` } type TreeDepth int diff --git a/rln/utils.go b/rln/utils.go index a35b3cf..79b3996 100644 --- a/rln/utils.go +++ b/rln/utils.go @@ -11,21 +11,6 @@ import ( "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