From 0b943caaa85bbfc8a87eddb6f6a3570ea3bec2c8 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Fri, 18 Aug 2023 17:38:30 -0400 Subject: [PATCH] refactor: remove credential registering from waku --- cmd/waku/flags_rln.go | 9 - cmd/waku/node_rln.go | 9 - cmd/waku/options.go | 1 - cmd/waku/rlngenerate/command_rln.go | 5 +- examples/chat2/exec.go | 23 --- examples/chat2/flags.go | 9 - examples/chat2/options.go | 1 - examples/rln/main.go | 45 +---- waku/v2/node/wakunode2_rln.go | 2 - waku/v2/node/wakuoptions.go | 3 - waku/v2/node/wakuoptions_rln.go | 6 +- .../rln/group_manager/dynamic/dynamic.go | 76 +------- .../rln/group_manager/dynamic/web3.go | 73 -------- waku/v2/protocol/rln/keystore/keystore.go | 22 ++- waku/v2/protocol/rln/onchain_test.go | 166 +++++++++++------- 15 files changed, 129 insertions(+), 321 deletions(-) diff --git a/cmd/waku/flags_rln.go b/cmd/waku/flags_rln.go index 4765a4cf..e87094d3 100644 --- a/cmd/waku/flags_rln.go +++ b/cmd/waku/flags_rln.go @@ -64,15 +64,6 @@ func rlnFlags() []cli.Flag { Usage: "the index of credentials to use", Destination: &options.RLNRelay.CredentialsIndex, }, - // TODO: this is a good candidate option for subcommands - // TODO: consider accepting a private key file and passwd - &cli.GenericFlag{ - Name: "rln-relay-eth-account-private-key", - Usage: "Ethereum account private key used for registering in member contract", - Value: &wcli.PrivateKeyValue{ - Value: &options.RLNRelay.ETHPrivateKey, - }, - }, &cli.StringFlag{ Name: "rln-relay-eth-client-address", Usage: "Ethereum testnet client address", diff --git a/cmd/waku/node_rln.go b/cmd/waku/node_rln.go index 7c0e38df..094ca037 100644 --- a/cmd/waku/node_rln.go +++ b/cmd/waku/node_rln.go @@ -4,7 +4,6 @@ package main import ( - "crypto/ecdsa" "errors" "github.com/waku-org/go-waku/waku/v2/node" @@ -20,12 +19,6 @@ func checkForRLN(logger *zap.Logger, options NodeOptions, nodeOpts *[]node.WakuN if !options.RLNRelay.Dynamic { *nodeOpts = append(*nodeOpts, node.WithStaticRLNRelay(options.RLNRelay.PubsubTopic, options.RLNRelay.ContentTopic, rln.MembershipIndex(options.RLNRelay.MembershipGroupIndex), nil)) } else { - - var ethPrivKey *ecdsa.PrivateKey - if options.RLNRelay.ETHPrivateKey != nil { - ethPrivKey = options.RLNRelay.ETHPrivateKey - } - // TODO: too many parameters in this function // consider passing a config struct instead *nodeOpts = append(*nodeOpts, node.WithDynamicRLNRelay( @@ -39,8 +32,6 @@ func checkForRLN(logger *zap.Logger, options NodeOptions, nodeOpts *[]node.WakuN rln.MembershipIndex(options.RLNRelay.MembershipGroupIndex), nil, options.RLNRelay.ETHClientAddress, - ethPrivKey, - nil, )) } } diff --git a/cmd/waku/options.go b/cmd/waku/options.go index 03865004..c6286400 100644 --- a/cmd/waku/options.go +++ b/cmd/waku/options.go @@ -43,7 +43,6 @@ type RLNRelayOptions struct { PubsubTopic string ContentTopic string Dynamic bool - ETHPrivateKey *ecdsa.PrivateKey ETHClientAddress string MembershipContractAddress common.Address } diff --git a/cmd/waku/rlngenerate/command_rln.go b/cmd/waku/rlngenerate/command_rln.go index 95f0ad67..c689d9a4 100644 --- a/cmd/waku/rlngenerate/command_rln.go +++ b/cmd/waku/rlngenerate/command_rln.go @@ -130,12 +130,13 @@ func persistCredentials(identityCredential *rln.IdentityCredential, membershipIn }, } - keystoreIndex, membershipGroupIndex, err := keystore.AddMembershipCredentials(options.CredentialsPath, identityCredential, membershipGroup, options.CredentialsPassword, dynamic.RLNAppInfo, keystore.DefaultSeparator) + membershipGroupIndex, err := keystore.AddMembershipCredentials(options.CredentialsPath, identityCredential, membershipGroup, options.CredentialsPassword, dynamic.RLNAppInfo, keystore.DefaultSeparator) if err != nil { return fmt.Errorf("failed to persist credentials: %w", err) } - logger.Info("persisted credentials succesfully", zap.Int("keystoreIndex", keystoreIndex), zap.Int("membershipGroupIndex", membershipGroupIndex)) + // TODO: obtain keystore index? + logger.Info("persisted credentials succesfully", zap.Uint("membershipGroupIndex", membershipGroupIndex)) return nil } diff --git a/examples/chat2/exec.go b/examples/chat2/exec.go index 1f32af12..05893182 100644 --- a/examples/chat2/exec.go +++ b/examples/chat2/exec.go @@ -6,7 +6,6 @@ import ( "net" tea "github.com/charmbracelet/bubbletea" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/libp2p/go-libp2p/core/protocol" "github.com/multiformats/go-multiaddr" @@ -48,26 +47,6 @@ func execute(options Options) { return nil } - registrationHandler := func(tx *types.Transaction) { - chainID := tx.ChainId().Int64() - url := "" - switch chainID { - case 1: - url = "https://etherscan.io" - case 5: - url = "https://goerli.etherscan.io" - case 11155111: - url = "https://sepolia.etherscan.io" - - } - - if url != "" { - fmt.Println(fmt.Sprintf("You are registered to the rln membership contract, find details of your registration transaction in %s/tx/%s", url, tx.Hash())) - } else { - fmt.Println(fmt.Sprintf("You are registered to the rln membership contract. Transaction hash: %s", url, tx.Hash())) - } - } - if options.RLNRelay.Dynamic { fmt.Println("Setting up dynamic rln...") opts = append(opts, node.WithDynamicRLNRelay( @@ -81,8 +60,6 @@ func execute(options Options) { uint(options.RLNRelay.MembershipIndex), spamHandler, options.RLNRelay.ETHClientAddress, - options.RLNRelay.ETHPrivateKey, - registrationHandler, )) } else { opts = append(opts, node.WithStaticRLNRelay( diff --git a/examples/chat2/flags.go b/examples/chat2/flags.go index e269d556..48c3d086 100644 --- a/examples/chat2/flags.go +++ b/examples/chat2/flags.go @@ -227,15 +227,6 @@ func getFlags() []cli.Flag { Usage: "Password for encrypting RLN credentials", Destination: &options.RLNRelay.CredentialsPassword, }, - // TODO: this is a good candidate option for subcommands - // TODO: consider accepting a private key file and passwd - &cli.GenericFlag{ - Name: "rln-relay-eth-account-private-key", - Usage: "Ethereum Goerli testnet account private key used for registering in member contract", - Value: &wcli.PrivateKeyValue{ - Value: &options.RLNRelay.ETHPrivateKey, - }, - }, &cli.StringFlag{ Name: "rln-relay-eth-client-address", Usage: "Ethereum testnet client address", diff --git a/examples/chat2/options.go b/examples/chat2/options.go index 3360e01a..b43c9f0d 100644 --- a/examples/chat2/options.go +++ b/examples/chat2/options.go @@ -37,7 +37,6 @@ type RLNRelayOptions struct { PubsubTopic string ContentTopic string Dynamic bool - ETHPrivateKey *ecdsa.PrivateKey ETHClientAddress string MembershipContractAddress common.Address } diff --git a/examples/rln/main.go b/examples/rln/main.go index 9f317cdf..2e9daea7 100644 --- a/examples/rln/main.go +++ b/examples/rln/main.go @@ -12,7 +12,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/waku-org/go-waku/waku/v2/node" "github.com/waku-org/go-waku/waku/v2/payload" @@ -27,10 +26,11 @@ var log = utils.Logger().Named("rln") // Update these values // ============================================================================ const ethClientAddress = "wss://sepolia.infura.io/ws/v3/API_KEY_GOES_HERE" -const ethPrivateKey = "PRIVATE_KEY_GOES_HERE" const contractAddress = "0x9C09146844C1326c2dBC41c451766C7138F88155" -const credentialsPath = "" // Empty to store in current folder -const credentialsPassword = "" // Empty to use default +const keystorePath = "" // Empty to store in current folder +const keystorePassword = "" // Empty to use default +const keystoreIndex = 0 +const membershipGroupIndex = 0 var contentTopic = protocol.NewContentTopic("rln", 1, "test", "proto").String() var pubsubTopic = protocol.DefaultPubsubTopic() @@ -57,34 +57,6 @@ func main() { return nil } - registrationHandler := func(tx *types.Transaction) { - chainID := tx.ChainId().Int64() - url := "" - switch chainID { - case 1: - url = "https://etherscan.io" - case 5: - url = "https://goerli.etherscan.io" - case 11155111: - url = "https://sepolia.etherscan.io" - - } - - if url != "" { - fmt.Println(fmt.Sprintf("You are registered to the rln membership contract, find details of your registration transaction in %s/tx/%s", url, tx.Hash())) - } else { - fmt.Println(fmt.Sprintf("You are registered to the rln membership contract. Transaction hash: %s", url, tx.Hash())) - } - } - - // TODO: set configuration values in constants - - ethPrivKey, err := crypto.HexToECDSA(ethPrivateKey) - if err != nil { - log.Error("Could not convert hex into ecdsa key", zap.Error(err)) - return - } - wakuNode, err := node.New( node.WithPrivateKey(prvKey), node.WithHostAddress(hostAddr), @@ -93,13 +65,14 @@ func main() { node.WithDynamicRLNRelay( pubsubTopic.String(), contentTopic, - credentialsPath, - credentialsPassword, + keystorePath, + keystorePassword, + keystoreIndex, + "", // Will use default tree path common.HexToAddress(contractAddress), + membershipGroupIndex, spamHandler, ethClientAddress, - ethPrivKey, - registrationHandler, ), ) if err != nil { diff --git a/waku/v2/node/wakunode2_rln.go b/waku/v2/node/wakunode2_rln.go index 4d3c022f..70f7aebd 100644 --- a/waku/v2/node/wakunode2_rln.go +++ b/waku/v2/node/wakunode2_rln.go @@ -48,14 +48,12 @@ func (w *WakuNode) mountRlnRelay(ctx context.Context) error { groupManager, err = dynamic.NewDynamicGroupManager( w.opts.rlnETHClientAddress, - w.opts.rlnETHPrivateKey, w.opts.rlnMembershipContractAddress, w.opts.rlnRelayMemIndex, w.opts.keystorePath, w.opts.keystorePassword, w.opts.keystoreIndex, true, - w.opts.rlnRegistrationHandler, w.log, ) if err != nil { diff --git a/waku/v2/node/wakuoptions.go b/waku/v2/node/wakuoptions.go index f6319067..edee8b24 100644 --- a/waku/v2/node/wakuoptions.go +++ b/waku/v2/node/wakuoptions.go @@ -9,7 +9,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/p2p/enode" logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p" @@ -100,14 +99,12 @@ type WakuNodeParameters struct { rlnRelayContentTopic string rlnRelayDynamic bool rlnSpamHandler func(message *pb.WakuMessage) error - rlnETHPrivateKey *ecdsa.PrivateKey rlnETHClientAddress string keystorePath string keystorePassword string keystoreIndex uint rlnTreePath string rlnMembershipContractAddress common.Address - rlnRegistrationHandler func(tx *types.Transaction) keepAliveInterval time.Duration diff --git a/waku/v2/node/wakuoptions_rln.go b/waku/v2/node/wakuoptions_rln.go index a8cf212e..f0b5715f 100644 --- a/waku/v2/node/wakuoptions_rln.go +++ b/waku/v2/node/wakuoptions_rln.go @@ -4,8 +4,6 @@ package node import ( - "crypto/ecdsa" - "github.com/ethereum/go-ethereum/common" "github.com/waku-org/go-waku/waku/v2/protocol/rln" r "github.com/waku-org/go-zerokit-rln/rln" @@ -27,7 +25,7 @@ func WithStaticRLNRelay(pubsubTopic string, contentTopic string, memberIndex r.M // WithDynamicRLNRelay enables the Waku V2 RLN protocol in onchain mode. // Requires the `gowaku_rln` build constrain (or the env variable RLN=true if building go-waku) -func WithDynamicRLNRelay(pubsubTopic string, contentTopic string, keystorePath string, keystorePassword string, keystoreIndex uint, treePath string, membershipContract common.Address, membershipGroupIndex uint, spamHandler rln.SpamHandler, ethClientAddress string, ethPrivateKey *ecdsa.PrivateKey, registrationHandler rln.RegistrationHandler) WakuNodeOption { +func WithDynamicRLNRelay(pubsubTopic string, contentTopic string, keystorePath string, keystorePassword string, keystoreIndex uint, treePath string, membershipContract common.Address, membershipGroupIndex uint, spamHandler rln.SpamHandler, ethClientAddress string) WakuNodeOption { return func(params *WakuNodeParameters) error { params.enableRLN = true params.rlnRelayDynamic = true @@ -38,9 +36,7 @@ func WithDynamicRLNRelay(pubsubTopic string, contentTopic string, keystorePath s params.rlnRelayContentTopic = contentTopic params.rlnSpamHandler = spamHandler params.rlnETHClientAddress = ethClientAddress - params.rlnETHPrivateKey = ethPrivateKey params.rlnMembershipContractAddress = membershipContract - params.rlnRegistrationHandler = registrationHandler params.rlnRelayMemIndex = membershipGroupIndex params.rlnTreePath = treePath return nil diff --git a/waku/v2/protocol/rln/group_manager/dynamic/dynamic.go b/waku/v2/protocol/rln/group_manager/dynamic/dynamic.go index ad0bfae6..981432b8 100644 --- a/waku/v2/protocol/rln/group_manager/dynamic/dynamic.go +++ b/waku/v2/protocol/rln/group_manager/dynamic/dynamic.go @@ -2,7 +2,6 @@ package dynamic import ( "context" - "crypto/ecdsa" "errors" "fmt" "math/big" @@ -43,17 +42,10 @@ type DynamicGroupManager struct { lastBlockProcessed uint64 - // 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 - eventHandler RegistrationEventHandler - registrationHandler RegistrationHandler - chainId *big.Int - rlnContract *contracts.RLN - membershipFee *big.Int + chainId *big.Int + rlnContract *contracts.RLN saveKeystore bool keystorePath string @@ -120,14 +112,12 @@ type RegistrationHandler = func(tx *types.Transaction) func NewDynamicGroupManager( ethClientAddr string, - ethAccountPrivateKey *ecdsa.PrivateKey, memContractAddr common.Address, membershipGroupIndex uint, keystorePath string, keystorePassword string, keystoreIndex uint, saveKeystore bool, - registrationHandler RegistrationHandler, log *zap.Logger, ) (*DynamicGroupManager, error) { log = log.Named("rln-dynamic") @@ -148,8 +138,6 @@ func NewDynamicGroupManager( membershipGroupIndex: membershipGroupIndex, membershipContractAddress: memContractAddr, ethClientAddress: ethClientAddr, - ethAccountPrivateKey: ethAccountPrivateKey, - registrationHandler: registrationHandler, eventHandler: handler, saveKeystore: saveKeystore, keystorePath: path, @@ -193,7 +181,7 @@ func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN, } // check if the contract exists by calling a static function - gm.membershipFee, err = gm.getMembershipFee(ctx) + _, err = gm.getMembershipFee(ctx) if err != nil { return err } @@ -227,34 +215,6 @@ func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN, } } - if gm.identityCredential == nil && gm.ethAccountPrivateKey == nil { - return errors.New("either a credentials path or a private key must be specified") - } - - // prepare rln membership key pair - if gm.identityCredential == nil && gm.ethAccountPrivateKey != nil { - gm.log.Info("no rln-relay key is provided, generating one") - identityCredential, err := rlnInstance.MembershipKeyGen() - if err != nil { - return err - } - - gm.identityCredential = identityCredential - - // register the rln-relay peer to the membership contract - gm.membershipIndex, err = gm.Register(ctx) - if err != nil { - return err - } - - err = gm.persistCredentials() - if err != nil { - return err - } - - gm.log.Info("registered peer into the membership contract") - } - if gm.identityCredential == nil || gm.membershipIndex == nil { return errors.New("no credentials available") } @@ -266,31 +226,6 @@ func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN, return nil } -func (gm *DynamicGroupManager) persistCredentials() error { - if !gm.saveKeystore { - return nil - } - - if gm.identityCredential == nil || gm.membershipIndex == nil { - return errors.New("no credentials to persist") - } - - membershipGroup := keystore.MembershipGroup{ - TreeIndex: *gm.membershipIndex, - MembershipContract: keystore.MembershipContract{ - ChainId: fmt.Sprintf("0x%X", gm.chainId), - Address: gm.membershipContractAddress.String(), - }, - } - - _, _, err := keystore.AddMembershipCredentials(gm.keystorePath, gm.identityCredential, membershipGroup, gm.keystorePassword, RLNAppInfo, keystore.DefaultSeparator) - if err != nil { - return fmt.Errorf("failed to persist credentials: %w", err) - } - - return nil -} - func (gm *DynamicGroupManager) InsertMembers(toInsert *om.OrderedMap) error { for pair := toInsert.Oldest(); pair != nil; pair = pair.Next() { events := pair.Value.([]*contracts.RLNMemberRegistered) // TODO: should these be sortered by index? we assume all members arrive in order @@ -345,11 +280,6 @@ func (gm *DynamicGroupManager) IdentityCredentials() (rln.IdentityCredential, er return *gm.identityCredential, nil } -func (gm *DynamicGroupManager) SetCredentials(identityCredential *rln.IdentityCredential, index *rln.MembershipIndex) { - gm.identityCredential = identityCredential - gm.membershipIndex = index -} - func (gm *DynamicGroupManager) MembershipIndex() (rln.MembershipIndex, error) { if gm.membershipIndex == nil { return 0, errors.New("membership index has not been setup") diff --git a/waku/v2/protocol/rln/group_manager/dynamic/web3.go b/waku/v2/protocol/rln/group_manager/dynamic/web3.go index 92f8f2b3..e8e15f47 100644 --- a/waku/v2/protocol/rln/group_manager/dynamic/web3.go +++ b/waku/v2/protocol/rln/group_manager/dynamic/web3.go @@ -2,90 +2,17 @@ package dynamic import ( "context" - "crypto/ecdsa" "errors" - "math/big" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/rpc" "github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts" - "github.com/waku-org/go-zerokit-rln/rln" "go.uber.org/zap" ) -func register(ctx context.Context, backend *ethclient.Client, membershipFee *big.Int, idComm rln.IDCommitment, ethAccountPrivateKey *ecdsa.PrivateKey, rlnContract *contracts.RLN, chainID *big.Int, registrationHandler RegistrationHandler, log *zap.Logger) (*rln.MembershipIndex, error) { - auth, err := bind.NewKeyedTransactorWithChainID(ethAccountPrivateKey, chainID) - if err != nil { - return nil, err - } - auth.Value = membershipFee - auth.Context = ctx - - log.Debug("registering an id commitment", zap.Binary("idComm", idComm[:])) - - // registers the idComm into the membership contract whose address is in rlnPeer.membershipContractAddress - tx, err := rlnContract.Register(auth, rln.Bytes32ToBigInt(idComm)) - if err != nil { - return nil, err - } - - log.Info("transaction broadcasted", zap.String("transactionHash", tx.Hash().Hex())) - - if registrationHandler != nil { - registrationHandler(tx) - } - - txReceipt, err := bind.WaitMined(ctx, backend, tx) - if err != nil { - return nil, err - } - - if txReceipt.Status != types.ReceiptStatusSuccessful { - return nil, errors.New("transaction reverted") - } - - // the receipt topic holds the hash of signature of the raised events - evt, err := rlnContract.ParseMemberRegistered(*txReceipt.Logs[0]) - if err != nil { - return nil, err - } - - var eventIDComm rln.IDCommitment = rln.BigIntToBytes32(evt.Pubkey) - - log.Debug("the identity commitment key extracted from tx log", zap.Binary("eventIDComm", eventIDComm[:])) - - if eventIDComm != idComm { - return nil, errors.New("invalid id commitment key") - } - - result := new(rln.MembershipIndex) - *result = rln.MembershipIndex(uint(evt.Index.Int64())) - - // debug "the index of registered identity commitment key", eventIndex=eventIndex - - log.Debug("the index of registered identity commitment key", zap.Uint("eventIndex", uint(*result))) - - return result, nil -} - -// Register registers the public key of the rlnPeer which is rlnPeer.membershipKeyPair.publicKey -// into the membership contract whose address is in rlnPeer.membershipContractAddress -func (gm *DynamicGroupManager) Register(ctx context.Context) (*rln.MembershipIndex, error) { - return register(ctx, - gm.ethClient, - gm.membershipFee, - gm.identityCredential.IDCommitment, - gm.ethAccountPrivateKey, - gm.rlnContract, - gm.chainId, - gm.registrationHandler, - gm.log) -} - // the types of inputs to this handler matches the MemberRegistered event/proc defined in the MembershipContract interface type RegistrationEventHandler = func(*DynamicGroupManager, []*contracts.RLNMemberRegistered) error diff --git a/waku/v2/protocol/rln/keystore/keystore.go b/waku/v2/protocol/rln/keystore/keystore.go index f108aae2..28d8996f 100644 --- a/waku/v2/protocol/rln/keystore/keystore.go +++ b/waku/v2/protocol/rln/keystore/keystore.go @@ -220,10 +220,10 @@ func GetMembershipCredentials(logger *zap.Logger, credentialsPath string, passwo } // Adds a membership credential to the keystore matching the application, appIdentifier and version filters. -func AddMembershipCredentials(path string, newIdentityCredential *rln.IdentityCredential, newMembershipGroup MembershipGroup, password string, appInfo AppInfo, separator string) (keystoreIndex int, membershipGroupIndex int, err error) { +func AddMembershipCredentials(path string, newIdentityCredential *rln.IdentityCredential, newMembershipGroup MembershipGroup, password string, appInfo AppInfo, separator string) (membershipGroupIndex uint, err error) { k, err := LoadAppKeystore(path, appInfo, DefaultSeparator) if err != nil { - return 0, 0, err + return 0, err } // A flag to tell us if the keystore contains a credential associated to the input identity credential, i.e. membershipCredential @@ -266,12 +266,12 @@ func AddMembershipCredentials(path string, newIdentityCredential *rln.IdentityCr // we re-encrypt creating a new keyfile b, err := json.Marshal(updatedCredential) if err != nil { - return 0, 0, err + return 0, err } encryptedCredentials, err := keystore.EncryptDataV3(b, []byte(password), keystore.StandardScryptN, keystore.StandardScryptP) if err != nil { - return 0, 0, err + return 0, err } // we update the original credential field in keystoreCredentials @@ -280,11 +280,10 @@ func AddMembershipCredentials(path string, newIdentityCredential *rln.IdentityCr found = true // We setup the return values - membershipGroupIndex = len(allMemberships) - keystoreIndex = i + membershipGroupIndex = uint(len(allMemberships)) for mIdx, mg := range updatedCredential.MembershipGroups { if mg.MembershipContract.Equals(newMembershipGroup.MembershipContract) { - membershipGroupIndex = mIdx + membershipGroupIndex = uint(mIdx) break } } @@ -302,21 +301,20 @@ func AddMembershipCredentials(path string, newIdentityCredential *rln.IdentityCr b, err := json.Marshal(newCredential) if err != nil { - return 0, 0, err + return 0, err } encryptedCredentials, err := keystore.EncryptDataV3(b, []byte(password), keystore.StandardScryptN, keystore.StandardScryptP) if err != nil { - return 0, 0, err + return 0, err } k.Credentials = append(k.Credentials, AppKeystoreCredential{Crypto: encryptedCredentials}) - keystoreIndex = len(k.Credentials) - 1 - membershipGroupIndex = len(newCredential.MembershipGroups) - 1 + membershipGroupIndex = uint(len(newCredential.MembershipGroups) - 1) } - return keystoreIndex, membershipGroupIndex, save(k, path, separator) + return membershipGroupIndex, save(k, path, separator) } // Safely saves a Keystore's JsonNode to disk. diff --git a/waku/v2/protocol/rln/onchain_test.go b/waku/v2/protocol/rln/onchain_test.go index fd427d11..cad5e7f5 100644 --- a/waku/v2/protocol/rln/onchain_test.go +++ b/waku/v2/protocol/rln/onchain_test.go @@ -4,11 +4,10 @@ package rln import ( - "bytes" "context" "crypto/ecdsa" "crypto/rand" - "errors" + "fmt" "math/big" "os" "testing" @@ -16,9 +15,11 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/waku-org/go-zerokit-rln/rln" + "go.uber.org/zap" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" "github.com/stretchr/testify/suite" @@ -27,11 +28,13 @@ import ( "github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts" "github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager" "github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager/dynamic" + "github.com/waku-org/go-waku/waku/v2/protocol/rln/keystore" "github.com/waku-org/go-waku/waku/v2/timesource" "github.com/waku-org/go-waku/waku/v2/utils" ) -var MEMBERSHIP_FEE = big.NewInt(1000000000000000) // wei - 0.001 eth +var membershipFee = big.NewInt(1000000000000000) // wei - 0.001 eth +const keystorePassword = "test" func TestWakuRLNRelayDynamicSuite(t *testing.T) { suite.Run(t, new(WakuRLNRelayDynamicSuite)) @@ -49,11 +52,10 @@ type WakuRLNRelayDynamicSuite struct { u1PrivKey *ecdsa.PrivateKey u2PrivKey *ecdsa.PrivateKey - u3PrivKey *ecdsa.PrivateKey - u4PrivKey *ecdsa.PrivateKey - u5PrivKey *ecdsa.PrivateKey } +// TODO: on teardown, remove credentials + func (s *WakuRLNRelayDynamicSuite) SetupTest() { s.clientAddr = os.Getenv("GANACHE_NETWORK_RPC_URL") @@ -73,12 +75,6 @@ func (s *WakuRLNRelayDynamicSuite) SetupTest() { s.Require().NoError(err) s.u2PrivKey, err = crypto.ToECDSA(common.FromHex("0xa00da43843ad6b5161ddbace48f293ac3f82f8a8257af34de4c32900bb6e9a97")) s.Require().NoError(err) - s.u3PrivKey, err = crypto.ToECDSA(common.FromHex("0xa4c8d3ed78cd722521fac9d734c45187a4f5e887570be1f707a7bbce054c01ea")) - s.Require().NoError(err) - s.u4PrivKey, err = crypto.ToECDSA(common.FromHex("0x6b11ba548a7fd1958eb156877cc7bdd02d99d876b55381aa9b106c16b0b7a805")) - s.Require().NoError(err) - s.u5PrivKey, err = crypto.ToECDSA(common.FromHex("0x0410196287d0af405e5c16f610de52416bd48be74836dbca93d73e24bffb5a81")) - s.Require().NoError(err) s.backend = backend s.chainID = chainID @@ -87,35 +83,63 @@ func (s *WakuRLNRelayDynamicSuite) SetupTest() { auth, err := bind.NewKeyedTransactorWithChainID(s.u1PrivKey, chainID) s.Require().NoError(err) + // TODO: update rln contract + poseidonHasherAddr, _, _, err := contracts.DeployPoseidonHasher(auth, backend) s.Require().NoError(err) - rlnAddr, _, rlnContract, err := contracts.DeployRLN(auth, backend, MEMBERSHIP_FEE, big.NewInt(20), poseidonHasherAddr) + rlnAddr, _, rlnContract, err := contracts.DeployRLN(auth, backend, membershipFee, big.NewInt(20), poseidonHasherAddr) s.Require().NoError(err) s.rlnAddr = rlnAddr s.rlnContract = rlnContract } -func (s *WakuRLNRelayDynamicSuite) register(privKey *ecdsa.PrivateKey, commitment *big.Int, handler func(evt *contracts.RLNMemberRegistered) error) { +func (s *WakuRLNRelayDynamicSuite) removeCredentials(path string) { + err := os.Remove(path) + if err != nil { + utils.Logger().Warn("could not remove credentials", zap.String("path", path)) + } +} + +func (s *WakuRLNRelayDynamicSuite) generateCredentials(rlnInstance *rln.RLN) *rln.IdentityCredential { + identityCredential, err := rlnInstance.MembershipKeyGen() + s.Require().NoError(err) + return identityCredential +} + +func (s *WakuRLNRelayDynamicSuite) register(identityCredential *rln.IdentityCredential, privKey *ecdsa.PrivateKey, keystorePath string) (rln.MembershipIndex, uint) { + auth, err := bind.NewKeyedTransactorWithChainID(privKey, s.chainID) s.Require().NoError(err) - auth.Value = MEMBERSHIP_FEE + auth.Value = membershipFee auth.Context = context.TODO() - tx, err := s.rlnContract.Register(auth, commitment) + tx, err := s.rlnContract.Register(auth, rln.Bytes32ToBigInt(identityCredential.IDCommitment)) s.Require().NoError(err) - receipt, err := bind.WaitMined(context.TODO(), s.backend, tx) + txReceipt, err := bind.WaitMined(context.TODO(), s.backend, tx) s.Require().NoError(err) - evt, err := s.rlnContract.ParseMemberRegistered(*receipt.Logs[0]) + s.Require().Equal(txReceipt.Status, types.ReceiptStatusSuccessful) + + evt, err := s.rlnContract.ParseMemberRegistered(*txReceipt.Logs[0]) s.Require().NoError(err) - if handler != nil { - err = handler(evt) - s.Require().NoError(err) + membershipIndex := rln.MembershipIndex(uint(evt.Index.Int64())) + + membershipGroup := keystore.MembershipGroup{ + TreeIndex: membershipIndex, + MembershipContract: keystore.MembershipContract{ + ChainId: fmt.Sprintf("0x%X", s.chainID.Int64()), + Address: s.rlnAddr.String(), + }, } + + membershipGroupIndex, err := keystore.AddMembershipCredentials(keystorePath, identityCredential, membershipGroup, keystorePassword, dynamic.RLNAppInfo, keystore.DefaultSeparator) + s.Require().NoError(err) + + return membershipIndex, membershipGroupIndex } func (s *WakuRLNRelayDynamicSuite) TestDynamicGroupManagement() { @@ -138,7 +162,12 @@ func (s *WakuRLNRelayDynamicSuite) TestDynamicGroupManagement() { rt, err := group_manager.NewMerkleRootTracker(5, rlnInstance) s.Require().NoError(err) - gm, err := dynamic.NewDynamicGroupManager(s.clientAddr, s.u1PrivKey, s.rlnAddr, 0, "./test_onchain.json", "", 0, false, nil, utils.Logger()) + u1Credentials := s.generateCredentials(rlnInstance) + keystorePath1 := "./test_onchain.json" + _, membershipGroupIndex := s.register(u1Credentials, s.u1PrivKey, keystorePath1) + defer s.removeCredentials(keystorePath1) + + gm, err := dynamic.NewDynamicGroupManager(s.clientAddr, s.rlnAddr, membershipGroupIndex, keystorePath1, keystorePassword, 0, false, utils.Logger()) s.Require().NoError(err) // initialize the WakuRLNRelay @@ -151,47 +180,47 @@ func (s *WakuRLNRelayDynamicSuite) TestDynamicGroupManagement() { nullifierLog: make(map[rln.MerkleNode][]rln.ProofMetadata), } - // generate another membership key pair - keyPair2, err := rlnInstance.MembershipKeyGen() + err = rlnRelay.Start(context.TODO()) s.Require().NoError(err) - err = rlnRelay.Start(context.Background()) + u2Credentials := s.generateCredentials(rlnInstance) + keystorePath2 := "./test_onchain2.json" + membershipIndex, _ := s.register(u2Credentials, s.u2PrivKey, keystorePath2) + defer s.removeCredentials(keystorePath2) + + time.Sleep(1 * time.Second) + + treeCommitment, err := rlnInstance.GetLeaf(membershipIndex) s.Require().NoError(err) - - // register user - gm.Register(context.TODO()) - - handler := func(evt *contracts.RLNMemberRegistered) error { - pubkey := rln.BigIntToBytes32(evt.Pubkey) - if !bytes.Equal(pubkey[:], keyPair2.IDCommitment[:]) { - return errors.New("not found") - } - - return rlnInstance.InsertMember(pubkey) - } - - // register member with contract - s.register(s.u2PrivKey, rln.Bytes32ToBigInt(keyPair2.IDCommitment), handler) - - time.Sleep(2 * time.Second) + s.Require().Equal(u2Credentials.IDCommitment, treeCommitment) } func (s *WakuRLNRelayDynamicSuite) TestInsertKeyMembershipContract() { + // Create a RLN instance + rlnInstance, err := rln.NewRLN() + s.Require().NoError(err) - s.register(s.u1PrivKey, big.NewInt(20), nil) + credentials1 := s.generateCredentials(rlnInstance) + credentials2 := s.generateCredentials(rlnInstance) + credentials3 := s.generateCredentials(rlnInstance) + + keystorePath1 := "./test_onchain.json" + s.register(credentials1, s.u1PrivKey, keystorePath1) + defer s.removeCredentials(keystorePath1) // Batch Register auth, err := bind.NewKeyedTransactorWithChainID(s.u2PrivKey, s.chainID) s.Require().NoError(err) - auth.Value = MEMBERSHIP_FEE.Mul(big.NewInt(2), MEMBERSHIP_FEE) + auth.Value = membershipFee.Mul(big.NewInt(2), membershipFee) auth.Context = context.TODO() - tx, err := s.rlnContract.RegisterBatch(auth, []*big.Int{big.NewInt(20), big.NewInt(21)}) + tx, err := s.rlnContract.RegisterBatch(auth, []*big.Int{rln.Bytes32ToBigInt(credentials2.IDCommitment), rln.Bytes32ToBigInt(credentials3.IDCommitment)}) s.Require().NoError(err) - _, err = bind.WaitMined(context.TODO(), s.backend, tx) + txReceipt, err := bind.WaitMined(context.TODO(), s.backend, tx) s.Require().NoError(err) + s.Require().Equal(txReceipt.Status, types.ReceiptStatusSuccessful) } func (s *WakuRLNRelayDynamicSuite) TestMerkleTreeConstruction() { @@ -199,16 +228,13 @@ func (s *WakuRLNRelayDynamicSuite) TestMerkleTreeConstruction() { rlnInstance, err := rln.NewRLN() s.Require().NoError(err) - keyPair1, err := rlnInstance.MembershipKeyGen() + credentials1 := s.generateCredentials(rlnInstance) + credentials2 := s.generateCredentials(rlnInstance) + + err = rlnInstance.InsertMember(credentials1.IDCommitment) s.Require().NoError(err) - keyPair2, err := rlnInstance.MembershipKeyGen() - s.Require().NoError(err) - - err = rlnInstance.InsertMember(keyPair1.IDCommitment) - s.Require().NoError(err) - - err = rlnInstance.InsertMember(keyPair2.IDCommitment) + err = rlnInstance.InsertMember(credentials2.IDCommitment) s.Require().NoError(err) // get the Merkle root @@ -216,8 +242,8 @@ func (s *WakuRLNRelayDynamicSuite) TestMerkleTreeConstruction() { s.Require().NoError(err) // register the members to the contract - s.register(s.u1PrivKey, rln.Bytes32ToBigInt(keyPair1.IDCommitment), nil) - s.register(s.u1PrivKey, rln.Bytes32ToBigInt(keyPair2.IDCommitment), nil) + _, membershipGroupIndex := s.register(credentials1, s.u1PrivKey, "./test_onchain.json") + _, membershipGroupIndex = s.register(credentials2, s.u1PrivKey, "./test_onchain.json") // Creating relay port, err := tests.FindFreePort(s.T(), "", 5) @@ -239,16 +265,15 @@ func (s *WakuRLNRelayDynamicSuite) TestMerkleTreeConstruction() { defer sub.Unsubscribe() // mount the rln relay protocol in the on-chain/dynamic mode - gm, err := dynamic.NewDynamicGroupManager(s.clientAddr, s.u1PrivKey, s.rlnAddr, 0, "./test_onchain.json", "", 0, false, nil, utils.Logger()) + // TODO: This assumes the keystoreIndex is 0, but there are two possible credentials in this keystore due to using the same contract address + // when credentials1 and credentials2 were registered. We should remove this hardcoded value and obtain the correct value when the credentials are persisted + keystoreIndex := uint(0) + gm, err := dynamic.NewDynamicGroupManager(s.clientAddr, s.rlnAddr, membershipGroupIndex, "./test_onchain.json", keystorePassword, keystoreIndex, false, utils.Logger()) s.Require().NoError(err) rlnRelay, err := New(relay, gm, "test-merkle-tree.db", RLNRELAY_PUBSUB_TOPIC, RLNRELAY_CONTENT_TOPIC, nil, timesource.NewDefaultClock(), utils.Logger()) s.Require().NoError(err) - // PreRegistering the keypair - membershipIndex := rln.MembershipIndex(0) - gm.SetCredentials(keyPair1, &membershipIndex) - err = rlnRelay.Start(context.TODO()) s.Require().NoError(err) @@ -264,6 +289,9 @@ func (s *WakuRLNRelayDynamicSuite) TestMerkleTreeConstruction() { } func (s *WakuRLNRelayDynamicSuite) TestCorrectRegistrationOfPeers() { + // Creating an RLN instance (just for generating membership keys) + rlnInstance, err := rln.NewRLN() + s.Require().NoError(err) // Node 1 ============================================================ port1, err := tests.FindFreePort(s.T(), "", 5) @@ -282,8 +310,14 @@ func (s *WakuRLNRelayDynamicSuite) TestCorrectRegistrationOfPeers() { s.Require().NoError(err) defer sub1.Unsubscribe() + // Register credentials1 in contract and keystore1 + credentials1 := s.generateCredentials(rlnInstance) + keystorePath1 := "./test_onchain.json" + _, membershipGroupIndex := s.register(credentials1, s.u1PrivKey, keystorePath1) + defer s.removeCredentials(keystorePath1) + // mount the rln relay protocol in the on-chain/dynamic mode - gm1, err := dynamic.NewDynamicGroupManager(s.clientAddr, s.u1PrivKey, s.rlnAddr, 0, "./test_onchain.json", "", 0, false, nil, utils.Logger()) + gm1, err := dynamic.NewDynamicGroupManager(s.clientAddr, s.rlnAddr, membershipGroupIndex, keystorePath1, keystorePassword, 0, false, utils.Logger()) s.Require().NoError(err) rlnRelay1, err := New(relay1, gm1, "test-correct-registration-1.db", RLNRELAY_PUBSUB_TOPIC, RLNRELAY_CONTENT_TOPIC, nil, timesource.NewDefaultClock(), utils.Logger()) @@ -308,8 +342,14 @@ func (s *WakuRLNRelayDynamicSuite) TestCorrectRegistrationOfPeers() { s.Require().NoError(err) defer sub2.Unsubscribe() + // Register credentials2 in contract and keystore2 + credentials2 := s.generateCredentials(rlnInstance) + keystorePath2 := "./test_onchain2.json" + _, membershipGroupIndex = s.register(credentials2, s.u2PrivKey, keystorePath2) + defer s.removeCredentials(keystorePath2) + // mount the rln relay protocol in the on-chain/dynamic mode - gm2, err := dynamic.NewDynamicGroupManager(s.clientAddr, s.u2PrivKey, s.rlnAddr, 0, "./test_onchain.json", "", 0, false, nil, utils.Logger()) + gm2, err := dynamic.NewDynamicGroupManager(s.clientAddr, s.rlnAddr, membershipGroupIndex, keystorePath2, keystorePassword, 0, false, utils.Logger()) s.Require().NoError(err) rlnRelay2, err := New(relay2, gm2, "test-correct-registration-2.db", RLNRELAY_PUBSUB_TOPIC, RLNRELAY_CONTENT_TOPIC, nil, timesource.NewDefaultClock(), utils.Logger())