From be09f3f550a32493e165bee4eea4986daa96491d Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Mon, 3 Apr 2023 17:45:19 -0400 Subject: [PATCH] refactor: static RLN relay --- go.mod | 2 +- go.sum | 2 + waku/v2/node/wakunode2_rln.go | 39 ++- waku/v2/protocol/rln/group_manager/main.go | 1 + .../rln/group_manager/static/static.go | 87 ++++++ waku/v2/protocol/rln/rln_relay_builder.go | 4 +- waku/v2/protocol/rln/waku_rln_relay.go | 254 ++++++++---------- 7 files changed, 237 insertions(+), 152 deletions(-) create mode 100644 waku/v2/protocol/rln/group_manager/main.go create mode 100644 waku/v2/protocol/rln/group_manager/static/static.go diff --git a/go.mod b/go.mod index c315fedd..76308b23 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/go-chi/chi/v5 v5.0.0 github.com/lib/pq v1.10.4 github.com/waku-org/go-noise v0.0.4 - github.com/waku-org/go-zerokit-rln v0.1.9 + github.com/waku-org/go-zerokit-rln v0.1.10 ) require ( diff --git a/go.sum b/go.sum index e89faaa9..acbe8c56 100644 --- a/go.sum +++ b/go.sum @@ -1568,6 +1568,8 @@ github.com/waku-org/go-noise v0.0.4 h1:ZfQDcCw8pazm89EBl5SXY7GGAnzDQb9AHFXlw3Ktb github.com/waku-org/go-noise v0.0.4/go.mod h1:+PWRfs2eSOVwKrPcQlfhwDngSh3faL/1QoxvoqggEKc= github.com/waku-org/go-zerokit-rln v0.1.9 h1:eMp43ThdVC8qcr4l398x3oR98RGdzAkMR/1hDlpjciU= github.com/waku-org/go-zerokit-rln v0.1.9/go.mod h1:MUW+wB6Yj7UBMdZrhko7oHfUZeY2wchggXYjpUiMoac= +github.com/waku-org/go-zerokit-rln v0.1.10 h1:H2aLZvIIr00ro8VzHjn+R1v8FA1rBwQTt5VKRkcFnTs= +github.com/waku-org/go-zerokit-rln v0.1.10/go.mod h1:MUW+wB6Yj7UBMdZrhko7oHfUZeY2wchggXYjpUiMoac= github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230331231302-258cacb91327 h1:Q5XQqo+PEmvrybT8D7BEsKCwIYDi80s+00Q49cfm9Gs= github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230331231302-258cacb91327/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230331223149-f90e66aebb0d h1:Kcg85Y2xGU6hqZ/kMfkLQF2jAog8vt+tw1/VNidzNtE= diff --git a/waku/v2/node/wakunode2_rln.go b/waku/v2/node/wakunode2_rln.go index f3f459a0..49011e02 100644 --- a/waku/v2/node/wakunode2_rln.go +++ b/waku/v2/node/wakunode2_rln.go @@ -4,10 +4,12 @@ package node import ( + "bytes" "context" "encoding/hex" "errors" "github.com/waku-org/go-waku/waku/v2/protocol/rln" + "github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager/static" r "github.com/waku-org/go-zerokit-rln/rln" "go.uber.org/zap" ) @@ -40,13 +42,24 @@ func (w *WakuNode) mountRlnRelay(ctx context.Context) error { if !w.opts.rlnRelayDynamic { w.log.Info("setting up waku-rln-relay in off-chain mode") // set up rln relay inputs - groupKeys, memKeyPair, memIndex, err := rln.StaticSetup(w.opts.rlnRelayMemIndex) + groupKeys, idCredential, err := static.Setup(w.opts.rlnRelayMemIndex) if err != nil { return err } - // mount rlnrelay in off-chain mode with a static group of users - rlnRelay, err := rln.RlnRelayStatic(ctx, w.Relay(), groupKeys, memKeyPair, memIndex, w.opts.rlnRelayPubsubTopic, w.opts.rlnRelayContentTopic, w.opts.rlnSpamHandler, w.timesource, w.log) + // rlnrelay in off-chain mode with a static group of user + + groupManager, err := static.NewStaticGroupManager(groupKeys, idCredential, w.opts.rlnRelayMemIndex, w.log) + if err != nil { + return err + } + + rlnRelay, err := rln.New(w.Relay(), groupManager, w.opts.rlnRelayPubsubTopic, w.opts.rlnRelayContentTopic, w.opts.rlnSpamHandler, w.timesource, w.log) + if err != nil { + return err + } + + err = rlnRelay.Start(ctx) if err != nil { return err } @@ -60,21 +73,25 @@ func (w *WakuNode) mountRlnRelay(ctx context.Context) error { return err } - expectedRoot := r.STATIC_GROUP_MERKLE_ROOT - if hex.EncodeToString(root[:]) != expectedRoot { + expectedRoot, err := static.ToBytes32LE(r.STATIC_GROUP_MERKLE_ROOT) + if err != nil { + return err + } + + if !bytes.Equal(expectedRoot[:], root[:]) { return errors.New("root mismatch: something went wrong not in Merkle tree construction") } - w.log.Info("the calculated root", zap.String("root", hex.EncodeToString(root[:]))) + w.log.Debug("the calculated root", zap.String("root", hex.EncodeToString(root[:]))) } else { w.log.Info("setting up waku-rln-relay in on-chain mode") - // check if the peer has provided its rln credentials - var memKeyPair *r.MembershipKeyPair + /*// check if the peer has provided its rln credentials + var memKeyPair *r.IdentityCredential if w.opts.rlnRelayIDCommitment != nil && w.opts.rlnRelayIDKey != nil { - memKeyPair = &r.MembershipKeyPair{ + memKeyPair = &r.IdentityCredential{ IDCommitment: *w.opts.rlnRelayIDCommitment, - IDKey: *w.opts.rlnRelayIDKey, + IDSecretHash: *w.opts.rlnRelayIDKey, } } @@ -83,7 +100,7 @@ func (w *WakuNode) mountRlnRelay(ctx context.Context) error { w.rlnRelay, err = rln.RlnRelayDynamic(ctx, w.Relay(), w.opts.rlnETHClientAddress, w.opts.rlnETHPrivateKey, w.opts.rlnMembershipContractAddress, memKeyPair, w.opts.rlnRelayMemIndex, w.opts.rlnRelayPubsubTopic, w.opts.rlnRelayContentTopic, w.opts.rlnSpamHandler, w.opts.rlnRegistrationHandler, w.timesource, w.log) if err != nil { return err - } + }*/ } w.log.Info("mounted waku RLN relay", zap.String("pubsubTopic", w.opts.rlnRelayPubsubTopic), zap.String("contentTopic", w.opts.rlnRelayContentTopic)) diff --git a/waku/v2/protocol/rln/group_manager/main.go b/waku/v2/protocol/rln/group_manager/main.go new file mode 100644 index 00000000..453d0228 --- /dev/null +++ b/waku/v2/protocol/rln/group_manager/main.go @@ -0,0 +1 @@ +package group_manager diff --git a/waku/v2/protocol/rln/group_manager/static/static.go b/waku/v2/protocol/rln/group_manager/static/static.go new file mode 100644 index 00000000..700989dc --- /dev/null +++ b/waku/v2/protocol/rln/group_manager/static/static.go @@ -0,0 +1,87 @@ +package static + +import ( + "context" + "errors" + + "github.com/waku-org/go-zerokit-rln/rln" + "go.uber.org/zap" +) + +type StaticGroupManager struct { + rln *rln.RLN + log *zap.Logger + + group []rln.IDCommitment +} + +func NewStaticGroupManager( + group []rln.IDCommitment, + memKeyPair rln.IdentityCredential, + memIndex rln.MembershipIndex, + log *zap.Logger, +) (*StaticGroupManager, error) { + // check the peer's index and the inclusion of user's identity commitment in the group + if memKeyPair.IDCommitment != group[int(memIndex)] { + return nil, errors.New("peer's IDCommitment does not match commitment in group") + } + + return &StaticGroupManager{ + log: log.Named("rln-static"), + group: group, + }, nil +} + +func (gm *StaticGroupManager) Start(ctx context.Context, rln *rln.RLN) error { + gm.log.Info("mounting rln-relay in off-chain/static mode") + + gm.rln = rln + + // add members to the Merkle tree + for _, member := range gm.group { + if err := rln.InsertMember(member); err != nil { + return err + } + } + + gm.group = nil // Deleting group to release memory + + return nil +} + +func Setup(index rln.MembershipIndex) ([]rln.IDCommitment, rln.IdentityCredential, error) { + // static group + groupKeys := rln.STATIC_GROUP_KEYS + groupSize := rln.STATIC_GROUP_SIZE + + // validate the user-supplied membership index + if index >= rln.MembershipIndex(groupSize) { + return nil, rln.IdentityCredential{}, errors.New("wrong membership index") + } + + // create a sequence of MembershipKeyPairs from the group keys (group keys are in string format) + credentials, err := rln.ToIdentityCredentials(groupKeys) + if err != nil { + return nil, rln.IdentityCredential{}, errors.New("invalid data on group keypairs") + } + + // extract id commitment keys + var groupOpt []rln.IDCommitment + for _, c := range credentials { + groupOpt = append(groupOpt, c.IDCommitment) + } + + return groupOpt, credentials[index], nil +} + +func (gm *StaticGroupManager) Stop() { + // TODO: +} + +func (gm *StaticGroupManager) GenerateProof(input []byte, epoch rln.Epoch) (*rln.RateLimitProof, error) { + return nil, nil // TODO +} + +func (gm *StaticGroupManager) VerifyProof(input []byte, msgProof *rln.RateLimitProof, ValidMerkleRoots ...rln.MerkleNode) (bool, error) { + return false, nil +} diff --git a/waku/v2/protocol/rln/rln_relay_builder.go b/waku/v2/protocol/rln/rln_relay_builder.go index e5b2052b..20255466 100644 --- a/waku/v2/protocol/rln/rln_relay_builder.go +++ b/waku/v2/protocol/rln/rln_relay_builder.go @@ -48,7 +48,7 @@ func RlnRelayStatic( contentTopic: contentTopic, log: log, timesource: timesource, - nullifierLog: make(map[r.Epoch][]r.ProofMetadata), + nullifierLog: make(map[r.Nullifier][]r.ProofMetadata), } root, err := rlnPeer.RLN.GetMerkleRoot() @@ -114,7 +114,7 @@ func RlnRelayDynamic( contentTopic: contentTopic, log: log, timesource: timesource, - nullifierLog: make(map[r.Epoch][]r.ProofMetadata), + nullifierLog: make(map[r.Nullifier][]r.ProofMetadata), registrationHandler: registrationHandler, lastIndexLoaded: -1, } diff --git a/waku/v2/protocol/rln/waku_rln_relay.go b/waku/v2/protocol/rln/waku_rln_relay.go index 9c6a1baa..4e5522a9 100644 --- a/waku/v2/protocol/rln/waku_rln_relay.go +++ b/waku/v2/protocol/rln/waku_rln_relay.go @@ -3,21 +3,18 @@ package rln import ( "bytes" "context" - "crypto/ecdsa" "errors" "math" "sync" "time" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/peer" "github.com/waku-org/go-waku/waku/v2/protocol/pb" "github.com/waku-org/go-waku/waku/v2/protocol/relay" "github.com/waku-org/go-waku/waku/v2/timesource" - "github.com/waku-org/go-waku/waku/v2/utils" r "github.com/waku-org/go-zerokit-rln/rln" "go.uber.org/zap" proto "google.golang.org/protobuf/proto" @@ -29,101 +26,129 @@ const MAX_CLOCK_GAP_SECONDS = 20 // maximum allowed gap between the epochs of messages' RateLimitProofs const MAX_EPOCH_GAP = int64(MAX_CLOCK_GAP_SECONDS / r.EPOCH_UNIT_SECONDS) -type RegistrationHandler = func(tx *types.Transaction) - +// Acceptable roots for merkle root validation of incoming messages const AcceptableRootWindowSize = 5 +type AppInfo struct { + Application string + AppIdentifier string + Version string +} + +type RegistrationHandler = func(tx *types.Transaction) + +type SpamHandler = func(message *pb.WakuMessage) error + +var RLNAppInfo = AppInfo{ + Application: "go-waku-rln-relay", + AppIdentifier: "01234567890abcdef", + Version: "0.1", +} + +type GroupManager interface { + Start(ctx context.Context, rln *r.RLN) error + GenerateProof(input []byte, epoch r.Epoch) (*r.RateLimitProof, error) + VerifyProof(input []byte, msgProof *r.RateLimitProof, ValidMerkleRoots ...r.MerkleNode) (bool, error) + Stop() +} + type WakuRLNRelay struct { - ctx context.Context timesource timesource.Timesource - membershipKeyPair *r.IdentityCredential + groupManager GroupManager - // membershipIndex denotes the index of a leaf in the Merkle tree - // that contains the pk of the current peer - // this index is used to retrieve the peer's authentication path - membershipIndex r.MembershipIndex - membershipContractAddress common.Address - ethClientAddress string - // ethAccountPrivateKey is required for signing transactions - // TODO may need to erase this ethAccountPrivateKey when is not used - // TODO may need to make ethAccountPrivateKey mandatory - ethAccountPrivateKey *ecdsa.PrivateKey - ethClient *ethclient.Client + // pubsubTopic is the topic for which rln relay is mounted + pubsubTopic string + contentTopic string + relay *relay.WakuRelay + spamHandler SpamHandler RLN *r.RLN - // pubsubTopic is the topic for which rln relay is mounted - pubsubTopic string - contentTopic string - lastIndexLoaded int64 validMerkleRoots []r.MerkleNode // the log of nullifiers and Shamir shares of the past messages grouped per epoch nullifierLogLock sync.RWMutex - nullifierLog map[r.Epoch][]r.ProofMetadata + nullifierLog map[r.Nullifier][]r.ProofMetadata - registrationHandler RegistrationHandler - log *zap.Logger + log *zap.Logger +} + +func New( + relay *relay.WakuRelay, + groupManager GroupManager, + pubsubTopic string, + contentTopic string, + spamHandler SpamHandler, + timesource timesource.Timesource, + log *zap.Logger) (*WakuRLNRelay, error) { + rlnInstance, err := r.NewRLN() + if err != nil { + return nil, err + } + + // create the WakuRLNRelay + rlnPeer := &WakuRLNRelay{ + RLN: rlnInstance, + groupManager: groupManager, + pubsubTopic: pubsubTopic, + contentTopic: contentTopic, + relay: relay, + spamHandler: spamHandler, + log: log, + timesource: timesource, + nullifierLog: make(map[r.MerkleNode][]r.ProofMetadata), + } + + // TODO: pass RLN to group manager + + root, err := rlnPeer.RLN.GetMerkleRoot() + if err != nil { + return nil, err + } + + rlnPeer.validMerkleRoots = append(rlnPeer.validMerkleRoots, root) + + return rlnPeer, nil +} + +func (rln *WakuRLNRelay) Start(ctx context.Context) error { + err := rln.groupManager.Start(ctx, rln.RLN) + if err != nil { + return err + } + + root, err := rln.RLN.GetMerkleRoot() + if err != nil { + return err + } + + rln.validMerkleRoots = append(rln.validMerkleRoots, root) + + // adds a topic validator for the supplied pubsub topic at the relay protocol + // messages published on this pubsub topic will be relayed upon a successful validation, otherwise they will be dropped + // the topic validator checks for the correct non-spamming proof of the message + err = rln.addValidator(rln.relay, rln.pubsubTopic, rln.contentTopic, rln.spamHandler) + if err != nil { + return err + } + + log.Info("rln relay topic validator mounted", zap.String("pubsubTopic", rln.pubsubTopic), zap.String("contentTopic", rln.contentTopic)) + + return nil } func (rln *WakuRLNRelay) Stop() { - if rln.ethClient != nil { - rln.ethClient.Close() - } + rln.groupManager.Stop() } -func StaticSetup(rlnRelayMemIndex r.MembershipIndex) ([]r.IDCommitment, r.IdentityCredential, r.MembershipIndex, error) { - // static group - groupKeys := r.STATIC_GROUP_KEYS - groupSize := r.STATIC_GROUP_SIZE - - // validate the user-supplied membership index - if rlnRelayMemIndex >= r.MembershipIndex(groupSize) { - return nil, r.IdentityCredential{}, 0, errors.New("wrong membership index") - } - - // prepare the outputs from the static group keys - - // create a sequence of MembershipKeyPairs from the group keys (group keys are in string format) - groupKeyPairs, err := toMembershipKeyPairs(groupKeys) - if err != nil { - return nil, r.IdentityCredential{}, 0, errors.New("invalid data on group keypairs") - } - - // extract id commitment keys - var groupOpt []r.IDCommitment - for _, c := range groupKeyPairs { - groupOpt = append(groupOpt, c.IDCommitment) - } - - // user selected membership key pair - memKeyPairOpt := groupKeyPairs[rlnRelayMemIndex] - memIndexOpt := rlnRelayMemIndex - - return groupOpt, memKeyPairOpt, memIndexOpt, nil -} - -func (rln *WakuRLNRelay) HasDuplicate(msg *pb.WakuMessage) (bool, error) { +func (rln *WakuRLNRelay) HasDuplicate(proofMD r.ProofMetadata) (bool, error) { // returns true if there is another message in the `nullifierLog` of the `rlnPeer` with the same // epoch and nullifier as `msg`'s epoch and nullifier but different Shamir secret shares // otherwise, returns false - if msg == nil { - return false, errors.New("nil message") - } - - msgProof := ToRateLimitProof(msg) - - // extract the proof metadata of the supplied `msg` - proofMD := r.ProofMetadata{ - Nullifier: msgProof.Nullifier, - ShareX: msgProof.ShareX, - ShareY: msgProof.ShareY, - } - rln.nullifierLogLock.RLock() - proofs, ok := rln.nullifierLog[msgProof.Epoch] + proofs, ok := rln.nullifierLog[proofMD.ExternalNullifier] rln.nullifierLogLock.RUnlock() // check if the epoch exists @@ -150,42 +175,28 @@ func (rln *WakuRLNRelay) HasDuplicate(msg *pb.WakuMessage) (bool, error) { return matched, nil } -func (rln *WakuRLNRelay) updateLog(msg *pb.WakuMessage) (bool, error) { - // extracts the `ProofMetadata` of the supplied messages `msg` and - // saves it in the `nullifierLog` of the `rlnPeer` - - if msg == nil { - return false, errors.New("nil message") - } - - msgProof := ToRateLimitProof(msg) - - proofMD := r.ProofMetadata{ - Nullifier: msgProof.Nullifier, - ShareX: msgProof.ShareX, - ShareY: msgProof.ShareY, - } - +func (rln *WakuRLNRelay) updateLog(proofMD r.ProofMetadata) (bool, error) { rln.nullifierLogLock.Lock() defer rln.nullifierLogLock.Unlock() - proofs, ok := rln.nullifierLog[msgProof.Epoch] + proofs, ok := rln.nullifierLog[proofMD.ExternalNullifier] // check if the epoch exists if !ok { - rln.nullifierLog[msgProof.Epoch] = []r.ProofMetadata{proofMD} + rln.nullifierLog[proofMD.ExternalNullifier] = []r.ProofMetadata{proofMD} return true, nil } // check if an identical record exists for _, p := range proofs { if p.Equals(proofMD) { + // TODO: slashing logic return true, nil } } // add proofMD to the log proofs = append(proofs, proofMD) - rln.nullifierLog[msgProof.Epoch] = proofs + rln.nullifierLog[proofMD.ExternalNullifier] = proofs return true, nil } @@ -218,6 +229,12 @@ func (rln *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime *time return MessageValidationResult_Invalid, nil } + proofMD, err := r.ExtractMetadata(*msgProof) + if err != nil { + rln.log.Debug("could not extract metadata", zap.Error(err)) + return MessageValidationResult_Invalid, nil + } + // calculate the gaps and validate the epoch gap := r.Diff(epoch, msgProof.Epoch) if int64(math.Abs(float64(gap))) > MAX_EPOCH_GAP { @@ -231,7 +248,7 @@ func (rln *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime *time contentTopicBytes := []byte(msg.ContentTopic) input := append(msg.Payload, contentTopicBytes...) - valid, err := rln.RLN.Verify(input, *msgProof, rln.validMerkleRoots...) + valid, err := rln.groupManager.VerifyProof(input, msgProof, rln.validMerkleRoots...) if err != nil { rln.log.Debug("could not verify proof", zap.Error(err)) return MessageValidationResult_Invalid, nil @@ -244,7 +261,7 @@ func (rln *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime *time } // check if double messaging has happened - hasDup, err := rln.HasDuplicate(msg) + hasDup, err := rln.HasDuplicate(proofMD) if err != nil { rln.log.Debug("validation error", zap.Error(err)) return MessageValidationResult_Unknown, err @@ -258,7 +275,7 @@ func (rln *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime *time // insert the message to the log // the result of `updateLog` is discarded because message insertion is guaranteed by the implementation i.e., // it will never error out - _, err = rln.updateLog(msg) + _, err = rln.updateLog(proofMD) if err != nil { return MessageValidationResult_Unknown, err } @@ -276,13 +293,9 @@ func (rln *WakuRLNRelay) AppendRLNProof(msg *pb.WakuMessage, senderEpochTime tim return errors.New("nil message") } - if rln.membershipKeyPair == nil { - return errors.New("No keypair setup") - } - input := toRLNSignal(msg) - proof, err := rln.RLN.GenerateProof(input, *rln.membershipKeyPair, rln.membershipIndex, r.CalcEpoch(senderEpochTime)) + proof, err := rln.groupManager.GenerateProof(input, r.CalcEpoch(senderEpochTime)) if err != nil { return err } @@ -300,19 +313,7 @@ func (rln *WakuRLNRelay) AppendRLNProof(msg *pb.WakuMessage, senderEpochTime tim return nil } -func (r *WakuRLNRelay) MembershipKeyPair() *r.IdentityCredential { - return r.membershipKeyPair -} - -func (r *WakuRLNRelay) MembershipIndex() r.MembershipIndex { - return r.membershipIndex -} - -func (r *WakuRLNRelay) MembershipContractAddress() common.Address { - return r.membershipContractAddress -} - -func (r *WakuRLNRelay) insertMember(pubkey r.IDCommitment) error { +func (r *WakuRLNRelay) insertMember(pubkey r.IDCommitment) error { // TODO: move to group manager? ######################################################### r.log.Debug("a new key is added", zap.Binary("pubkey", pubkey[:])) // assuming all the members arrive in order err := r.RLN.InsertMember(pubkey) @@ -331,8 +332,6 @@ func (r *WakuRLNRelay) insertMember(pubkey r.IDCommitment) error { return err } -type SpamHandler = func(message *pb.WakuMessage) error - // this function sets a validator for the waku messages published on the supplied pubsubTopic and contentTopic // if contentTopic is empty, then validation takes place for All the messages published on the given pubsubTopic // the message validation logic is according to https://rfc.vac.dev/spec/17/ @@ -413,27 +412,6 @@ func (r *WakuRLNRelay) addValidator( return relay.PubSub().RegisterTopicValidator(pubsubTopic, validator) } -func toMembershipKeyPairs(groupKeys [][]string) ([]r.IdentityCredential, error) { - // groupKeys is sequence of membership key tuples in the form of (identity key, identity commitment) all in the hexadecimal format - // the ToMembershipKeyPairs proc populates a sequence of MembershipKeyPairs using the supplied groupKeys - - groupKeyPairs := []r.IdentityCredential{} - for _, pair := range groupKeys { - idKey, err := utils.DecodeHexString(pair[0]) - if err != nil { - return nil, err - } - idCommitment, err := utils.DecodeHexString(pair[1]) - if err != nil { - return nil, err - } - - groupKeyPairs = append(groupKeyPairs, r.IdentityCredential{IDSecretHash: r.IDSecretHash(r.Bytes32(idKey)), IDCommitment: r.IDCommitment(r.Bytes32(idCommitment))}) - } - - return groupKeyPairs, nil -} - func toRLNSignal(wakuMessage *pb.WakuMessage) []byte { if wakuMessage == nil { return []byte{}