feat: store credentials in a file

This commit is contained in:
Richard Ramos 2022-08-08 20:02:08 -04:00
parent f486236d0d
commit fb6aa64442
9 changed files with 226 additions and 33 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
*.db-shm
*.db-wal
nodekey
rlnCredentials.txt
# Binaries for programs and plugins
*.exe

View File

@ -79,6 +79,7 @@ func main() {
rlnRelayEthAccountPrivKeyFlag := flag.String("eth-account-privatekey", "", "Account private key for an Ethereum testnet")
rlnRelayEthClientAddressFlag := flag.String("eth-client-address", "ws://localhost:8545/", "Ethereum testnet client address")
rlnRelayEthMemContractAddressFlag := flag.String("eth-mem-contract-address", "", "Address of membership contract on an Ethereum testnet")
rlnRelayCredentialsFile := flag.String("rln-relay-credentials-file", "rlnCredentials.txt", "RLN credentials file")
flag.Parse()
@ -127,23 +128,16 @@ func main() {
panic(err)
}
var idKey *rln.IDKey
if *rlnRelayIdKeyFlag != "" {
idKey = new(rln.IDKey)
copy((*idKey)[:], common.FromHex(*rlnRelayIdKeyFlag))
}
var idCommitment *rln.IDCommitment
if *rlnRelayIdCommitmentKeyFlag != "" {
idCommitment = new(rln.IDCommitment)
copy((*idCommitment)[:], common.FromHex(*rlnRelayIdCommitmentKeyFlag))
idKey, idCommitment, index, err := getMembershipCredentials(*rlnRelayCredentialsFile, *rlnRelayIdKeyFlag, *rlnRelayIdCommitmentKeyFlag, *rlnRelayMemIndexFlag)
if err != nil {
panic(err)
}
fmt.Println("Setting up dynamic rln")
opts = append(opts, node.WithDynamicRLNRelay(
*rlnRelayPubsubTopicFlag,
*rlnRelayContentTopicFlag,
rln.MembershipIndex(*rlnRelayMemIndexFlag),
index,
idKey,
idCommitment,
spamHandler,
@ -183,6 +177,14 @@ func main() {
panic(err)
}
if *rlnRelayFlag && *rlnRelayDynamicFlag {
err := writeRLNMembershipCredentialsToFile(*rlnRelayCredentialsFile, wakuNode.RLNRelay().MembershipKeyPair(), wakuNode.RLNRelay().MembershipIndex())
if err != nil {
panic(err)
}
fmt.Printf("Wrote credentials in file %s\n", *rlnRelayCredentialsFile)
}
// use the nickname from the cli flag, or a default if blank
nick := *nickFlag
if len(nick) == 0 {

View File

@ -0,0 +1,86 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/go-rln/rln"
)
type membershipCredentials struct {
Keypair rln.MembershipKeyPair `json:"keypair"`
Index rln.MembershipIndex `json:"index"`
}
func fileExists(path string) bool {
if _, err := os.Stat(path); err == nil {
return false
} else if errors.Is(err, os.ErrNotExist) {
return false
} else {
return false
}
}
func writeRLNMembershipCredentialsToFile(path string, keyPair rln.MembershipKeyPair, idx rln.MembershipIndex) error {
if fileExists(path) {
return nil
}
credentialsJSON, err := json.Marshal(membershipCredentials{
Keypair: keyPair,
Index: idx,
})
if err != nil {
return err
}
return ioutil.WriteFile(path, credentialsJSON, 0600)
}
func loadMembershipCredentialsFromFile(path string) (rln.MembershipKeyPair, rln.MembershipIndex, error) {
src, err := ioutil.ReadFile(path)
if err != nil {
return rln.MembershipKeyPair{}, rln.MembershipIndex(0), err
}
var credentials membershipCredentials
err = json.Unmarshal(src, &credentials)
if err != nil {
return rln.MembershipKeyPair{}, rln.MembershipIndex(0), err
}
return credentials.Keypair, credentials.Index, err
}
func getMembershipCredentials(path string, rlnIDKey string, rlnIDCommitment string, rlnMembershipIndex int) (idKey *rln.IDKey, idCommitment *rln.IDCommitment, index rln.MembershipIndex, err error) {
if _, err = os.Stat(path); err == nil {
if keyPair, index, err := loadMembershipCredentialsFromFile(path); err != nil {
return nil, nil, rln.MembershipIndex(0), fmt.Errorf("could not read membership credentials file: %w", err)
} else {
return &keyPair.IDKey, &keyPair.IDCommitment, index, nil
}
}
if os.IsNotExist(err) {
if rlnIDKey != "" {
idKey = new(rln.IDKey)
copy((*idKey)[:], common.FromHex(rlnIDKey))
}
if rlnIDCommitment != "" {
idCommitment = new(rln.IDCommitment)
copy((*idCommitment)[:], common.FromHex(rlnIDCommitment))
}
return idKey, idCommitment, rln.MembershipIndex(rlnMembershipIndex), nil
}
return nil, nil, rln.MembershipIndex(0), fmt.Errorf("could not read membership credentials file: %w", err)
}

View File

@ -317,6 +317,12 @@ func main() {
Usage: "Rln relay identity commitment key as a Hex string",
Destination: &options.RLNRelay.IDCommitment,
},
&cli.StringFlag{
Name: "rln-relay-membership-credentials-file",
Usage: "RLN relay membership credentials file",
Value: "rlnCredentials.txt",
Destination: &options.RLNRelay.CredentialsFile,
},
// TODO: this is a good candidate option for subcommands
// TODO: consider accepting a private key file and passwd
&cli.StringFlag{

View File

@ -256,6 +256,7 @@ func Execute(options Options) {
nodeOpts = append(nodeOpts, node.WithDiscoveryV5(options.DiscV5.Port, bootnodes, options.DiscV5.AutoUpdate, pubsub.WithDiscoveryOpts(discovery.Limit(45), discovery.TTL(time.Duration(20)*time.Second))))
}
loadedCredentialsFromFile := false
if options.RLNRelay.Enable {
if !options.Relay.Enable {
failOnErr(errors.New("relay not available"), "Could not enable RLN Relay")
@ -272,22 +273,15 @@ func Execute(options Options) {
ethPrivKey = k
}
var idKey *rln.IDKey
if options.RLNRelay.IDCommitment != "" {
idKey = new(rln.IDKey)
copy((*idKey)[:], common.FromHex(options.RLNRelay.IDKey))
}
loaded, idKey, idCommitment, membershipIndex, err := getMembershipCredentials(options)
failOnErr(err, "Invalid membership credentials")
var idCommitment *rln.IDCommitment
if options.RLNRelay.IDCommitment != "" {
idCommitment = new(rln.IDCommitment)
copy((*idCommitment)[:], common.FromHex(options.RLNRelay.IDCommitment))
}
loadedCredentialsFromFile = loaded
nodeOpts = append(nodeOpts, node.WithDynamicRLNRelay(
options.RLNRelay.PubsubTopic,
options.RLNRelay.ContentTopic,
rln.MembershipIndex(options.RLNRelay.MembershipIndex),
membershipIndex,
idKey,
idCommitment,
nil,
@ -354,6 +348,11 @@ func Execute(options Options) {
}
}
if options.RLNRelay.Enable && options.RLNRelay.Dynamic && !loadedCredentialsFromFile {
err := writeRLNMembershipCredentialsToFile(wakuNode.RLNRelay().MembershipKeyPair(), wakuNode.RLNRelay().MembershipIndex(), options.RLNRelay.CredentialsFile, []byte(options.KeyPasswd), options.Overwrite)
failOnErr(err, "Could not write membership credentials file")
}
var rpcServer *rpc.WakuRpc
if options.RPCServer.Enable {
rpcServer = rpc.NewWakuRpc(wakuNode, options.RPCServer.Address, options.RPCServer.Port, options.RPCServer.Admin, options.RPCServer.Private, logger)
@ -432,7 +431,7 @@ func loadPrivateKeyFromFile(path string, passwd string) (*ecdsa.PrivateKey, erro
return crypto.ToECDSA(pKey)
}
func checkForPrivateKeyFile(path string, overwrite bool) error {
func checkForFileExistence(path string, overwrite bool) error {
_, err := os.Stat(path)
if err == nil && !overwrite {
@ -456,7 +455,7 @@ func generatePrivateKey() ([]byte, error) {
}
func writePrivateKeyToFile(path string, passwd []byte, overwrite bool) error {
if err := checkForPrivateKeyFile(path, overwrite); err != nil {
if err := checkForFileExistence(path, overwrite); err != nil {
return err
}

View File

@ -41,6 +41,7 @@ type RelayOptions struct {
type RLNRelayOptions struct {
Enable bool
CredentialsFile string
MembershipIndex int
PubsubTopic string
ContentTopic string

96
waku/rln-credentials.go Normal file
View File

@ -0,0 +1,96 @@
package waku
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/go-rln/rln"
)
type membershipCredentials struct {
Keypair rln.MembershipKeyPair `json:"keypair"`
Index rln.MembershipIndex `json:"index"`
}
func writeRLNMembershipCredentialsToFile(keyPair rln.MembershipKeyPair, idx rln.MembershipIndex, path string, passwd []byte, overwrite bool) error {
if err := checkForFileExistence(path, overwrite); err != nil {
return err
}
credentialsJSON, err := json.Marshal(membershipCredentials{
Keypair: keyPair,
Index: idx,
})
if err != nil {
return err
}
encryptedCredentials, err := keystore.EncryptDataV3(credentialsJSON, passwd, keystore.StandardScryptN, keystore.StandardScryptP)
if err != nil {
return err
}
output, err := json.Marshal(encryptedCredentials)
if err != nil {
return err
}
return ioutil.WriteFile(path, output, 0600)
}
func loadMembershipCredentialsFromFile(path string, passwd string) (rln.MembershipKeyPair, rln.MembershipIndex, error) {
src, err := ioutil.ReadFile(path)
if err != nil {
return rln.MembershipKeyPair{}, rln.MembershipIndex(0), err
}
var encryptedK keystore.CryptoJSON
err = json.Unmarshal(src, &encryptedK)
if err != nil {
return rln.MembershipKeyPair{}, rln.MembershipIndex(0), err
}
credentialsBytes, err := keystore.DecryptDataV3(encryptedK, passwd)
if err != nil {
return rln.MembershipKeyPair{}, rln.MembershipIndex(0), err
}
var credentials membershipCredentials
err = json.Unmarshal(credentialsBytes, &credentials)
if err != nil {
return rln.MembershipKeyPair{}, rln.MembershipIndex(0), err
}
return credentials.Keypair, credentials.Index, err
}
func getMembershipCredentials(options Options) (fromFile bool, idKey *rln.IDKey, idCommitment *rln.IDCommitment, index rln.MembershipIndex, err error) {
if _, err = os.Stat(options.RLNRelay.CredentialsFile); err == nil {
if keyPair, index, err := loadMembershipCredentialsFromFile(options.RLNRelay.CredentialsFile, options.KeyPasswd); err != nil {
return false, nil, nil, rln.MembershipIndex(0), fmt.Errorf("could not read membership credentials file: %w", err)
} else {
return true, &keyPair.IDKey, &keyPair.IDCommitment, index, nil
}
}
if os.IsNotExist(err) {
if options.RLNRelay.IDKey != "" {
idKey = new(rln.IDKey)
copy((*idKey)[:], common.FromHex(options.RLNRelay.IDKey))
}
if options.RLNRelay.IDCommitment != "" {
idCommitment = new(rln.IDCommitment)
copy((*idCommitment)[:], common.FromHex(options.RLNRelay.IDCommitment))
}
return false, idKey, idCommitment, rln.MembershipIndex(options.RLNRelay.MembershipIndex), nil
}
return false, nil, nil, rln.MembershipIndex(0), fmt.Errorf("could not read membership credentials file: %w", err)
}

View File

@ -67,8 +67,6 @@ type WakuNodeParameters struct {
shouldResume bool
storeMsgs bool
messageProvider store.MessageProvider
maxMessages int
maxDuration time.Duration
swapMode int
swapDisconnectThreshold int

View File

@ -101,9 +101,9 @@ func (rln *WakuRLNRelay) Register(ctx context.Context) (*r.MembershipIndex, erro
// the types of inputs to this handler matches the MemberRegistered event/proc defined in the MembershipContract interface
type RegistrationEventHandler = func(pubkey r.IDCommitment, index r.MembershipIndex) error
func processLogs(evt *contracts.RLNMemberRegistered, handler RegistrationEventHandler) {
func processLogs(evt *contracts.RLNMemberRegistered, handler RegistrationEventHandler) error {
if evt == nil {
return
return nil
}
var pubkey r.IDCommitment
@ -111,7 +111,7 @@ func processLogs(evt *contracts.RLNMemberRegistered, handler RegistrationEventHa
index := r.MembershipIndex(uint(evt.Index.Int64()))
handler(pubkey, index)
return handler(pubkey, index)
}
// HandleGroupUpdates mounts the supplied handler for the registration events emitting from the membership contract
@ -129,11 +129,15 @@ func (rln *WakuRLNRelay) HandleGroupUpdates(handler RegistrationEventHandler) er
return err
}
// TODO: process log should have a channel that consumes logs and has a buffer to receive a lot of events
// TODO: an error channel is required
err = rln.loadOldEvents(rlnContract, handler)
if err != nil {
return err
}
rln.loadOldEvents(rlnContract, handler)
rln.watchNewEvents(rlnContract, handler, rln.log)
err = rln.watchNewEvents(rlnContract, handler, rln.log)
if err != nil {
return err
}
return nil
}