refactor: static RLN relay

This commit is contained in:
Richard Ramos 2023-04-03 17:45:19 -04:00 committed by RichΛrd
parent 6796936f5e
commit be09f3f550
7 changed files with 237 additions and 152 deletions

2
go.mod
View File

@ -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 (

2
go.sum
View File

@ -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=

View File

@ -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))

View File

@ -0,0 +1 @@
package group_manager

View File

@ -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
}

View File

@ -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,
}

View File

@ -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{}