mirror of https://github.com/status-im/go-waku.git
refactor: credentials
This commit is contained in:
parent
04c90657cd
commit
42c0e123d9
|
@ -125,14 +125,6 @@ func execute(options Options) {
|
|||
return
|
||||
}
|
||||
|
||||
if options.RLNRelay.Enable && options.RLNRelay.Dynamic {
|
||||
err := node.WriteRLNMembershipCredentialsToFile(wakuNode.RLNRelay().MembershipKeyPair(), wakuNode.RLNRelay().MembershipIndex(), wakuNode.RLNRelay().MembershipContractAddress(), options.RLNRelay.CredentialsPath, []byte(options.RLNRelay.CredentialsPassword))
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
chat := NewChat(ctx, wakuNode, options)
|
||||
p := tea.NewProgram(chat.ui)
|
||||
if err := p.Start(); err != nil {
|
||||
|
|
|
@ -331,8 +331,6 @@ func Execute(options Options) {
|
|||
}
|
||||
}
|
||||
|
||||
onStartRLN(wakuNode, options)
|
||||
|
||||
var rpcServer *rpc.WakuRpc
|
||||
if options.RPCServer.Enable {
|
||||
rpcServer = rpc.NewWakuRpc(wakuNode, options.RPCServer.Address, options.RPCServer.Port, options.RPCServer.Admin, options.RPCServer.Private, options.PProf, options.RPCServer.RelayCacheCapacity, logger)
|
||||
|
|
|
@ -11,7 +11,3 @@ import (
|
|||
func checkForRLN(logger *zap.Logger, options Options, nodeOpts *[]node.WakuNodeOption) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
func onStartRLN(wakuNode *node.WakuNode, options Options) {
|
||||
// Do nothing
|
||||
}
|
||||
|
|
|
@ -25,19 +25,13 @@ func checkForRLN(logger *zap.Logger, options Options, nodeOpts *[]node.WakuNodeO
|
|||
if options.RLNRelay.ETHPrivateKey != nil {
|
||||
ethPrivKey = options.RLNRelay.ETHPrivateKey
|
||||
}
|
||||
membershipCredentials, err := node.GetMembershipCredentials(
|
||||
logger,
|
||||
options.RLNRelay.CredentialsPath,
|
||||
options.RLNRelay.CredentialsPassword,
|
||||
options.RLNRelay.MembershipContractAddress,
|
||||
uint(options.RLNRelay.MembershipIndex),
|
||||
)
|
||||
failOnErr(err, "Invalid membership credentials")
|
||||
|
||||
*nodeOpts = append(*nodeOpts, node.WithDynamicRLNRelay(
|
||||
options.RLNRelay.PubsubTopic,
|
||||
options.RLNRelay.ContentTopic,
|
||||
membershipCredentials,
|
||||
options.RLNRelay.CredentialsPath,
|
||||
options.RLNRelay.CredentialsPassword,
|
||||
options.RLNRelay.MembershipContractAddress,
|
||||
nil,
|
||||
options.RLNRelay.ETHClientAddress,
|
||||
ethPrivKey,
|
||||
|
@ -46,10 +40,3 @@ func checkForRLN(logger *zap.Logger, options Options, nodeOpts *[]node.WakuNodeO
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func onStartRLN(wakuNode *node.WakuNode, options Options) {
|
||||
if options.RLNRelay.Enable && options.RLNRelay.Dynamic && options.RLNRelay.CredentialsPath != "" {
|
||||
err := node.WriteRLNMembershipCredentialsToFile(wakuNode.RLNRelay().MembershipKeyPair(), wakuNode.RLNRelay().MembershipIndex(), wakuNode.RLNRelay().MembershipContractAddress(), options.RLNRelay.CredentialsPath, []byte(options.RLNRelay.CredentialsPassword))
|
||||
failOnErr(err, "Could not write membership credentials file")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
//go:build gowaku_rln
|
||||
// +build gowaku_rln
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/waku-org/go-zerokit-rln/rln"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const RLN_CREDENTIALS_FILENAME = "rlnCredentials.txt"
|
||||
|
||||
func WriteRLNMembershipCredentialsToFile(keyPair *rln.MembershipKeyPair, idx rln.MembershipIndex, contractAddress common.Address, path string, passwd []byte) error {
|
||||
if path == "" {
|
||||
return nil // we dont want to use a credentials file
|
||||
}
|
||||
|
||||
if keyPair == nil {
|
||||
return nil // no credentials to store
|
||||
}
|
||||
|
||||
credentialsJSON, err := json.Marshal(MembershipCredentials{
|
||||
Keypair: keyPair,
|
||||
Index: idx,
|
||||
Contract: contractAddress,
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
path = filepath.Join(path, RLN_CREDENTIALS_FILENAME)
|
||||
|
||||
return ioutil.WriteFile(path, output, 0600)
|
||||
}
|
||||
|
||||
func loadMembershipCredentialsFromFile(credentialsFilePath string, passwd string) (MembershipCredentials, error) {
|
||||
src, err := ioutil.ReadFile(credentialsFilePath)
|
||||
if err != nil {
|
||||
return MembershipCredentials{}, err
|
||||
}
|
||||
|
||||
var encryptedK keystore.CryptoJSON
|
||||
err = json.Unmarshal(src, &encryptedK)
|
||||
if err != nil {
|
||||
return MembershipCredentials{}, err
|
||||
}
|
||||
|
||||
credentialsBytes, err := keystore.DecryptDataV3(encryptedK, passwd)
|
||||
if err != nil {
|
||||
return MembershipCredentials{}, err
|
||||
}
|
||||
|
||||
var credentials MembershipCredentials
|
||||
err = json.Unmarshal(credentialsBytes, &credentials)
|
||||
|
||||
return credentials, err
|
||||
}
|
||||
|
||||
func GetMembershipCredentials(logger *zap.Logger, credentialsPath string, password string, membershipContract common.Address, membershipIndex uint) (credentials MembershipCredentials, err error) {
|
||||
if credentialsPath == "" { // Not using a file
|
||||
return MembershipCredentials{
|
||||
Contract: membershipContract,
|
||||
}, nil
|
||||
}
|
||||
|
||||
credentialsFilePath := filepath.Join(credentialsPath, RLN_CREDENTIALS_FILENAME)
|
||||
if _, err = os.Stat(credentialsFilePath); err == nil {
|
||||
if credentials, err := loadMembershipCredentialsFromFile(credentialsFilePath, password); err != nil {
|
||||
return MembershipCredentials{}, fmt.Errorf("could not read membership credentials file: %w", err)
|
||||
} else {
|
||||
logger.Info("loaded rln credentials", zap.String("filepath", credentialsFilePath))
|
||||
if (bytes.Equal(credentials.Contract.Bytes(), common.Address{}.Bytes())) {
|
||||
credentials.Contract = membershipContract
|
||||
}
|
||||
if (bytes.Equal(membershipContract.Bytes(), common.Address{}.Bytes())) {
|
||||
return MembershipCredentials{}, errors.New("no contract address specified")
|
||||
}
|
||||
return credentials, nil
|
||||
}
|
||||
}
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
return MembershipCredentials{
|
||||
Keypair: nil,
|
||||
Index: membershipIndex,
|
||||
Contract: membershipContract,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
return MembershipCredentials{}, fmt.Errorf("could not read membership credentials file: %w", err)
|
||||
}
|
|
@ -58,21 +58,13 @@ func (w *WakuNode) mountRlnRelay(ctx context.Context) error {
|
|||
} 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.IdentityCredential
|
||||
if w.opts.rlnRelayIDCommitment != nil && w.opts.rlnRelayIDKey != nil {
|
||||
memKeyPair = &r.IdentityCredential{
|
||||
IDCommitment: *w.opts.rlnRelayIDCommitment,
|
||||
IDSecretHash: *w.opts.rlnRelayIDKey,
|
||||
}
|
||||
}
|
||||
|
||||
groupManager, err = dynamic.NewDynamicGroupManager(
|
||||
w.opts.rlnETHClientAddress,
|
||||
w.opts.rlnETHPrivateKey,
|
||||
w.opts.rlnMembershipContractAddress,
|
||||
memKeyPair,
|
||||
w.opts.rlnRelayMemIndex,
|
||||
w.opts.keystorePath,
|
||||
w.opts.keystorePassword,
|
||||
true,
|
||||
w.opts.rlnRegistrationHandler,
|
||||
w.log,
|
||||
)
|
||||
|
|
|
@ -100,10 +100,10 @@ type WakuNodeParameters struct {
|
|||
rlnRelayContentTopic string
|
||||
rlnRelayDynamic bool
|
||||
rlnSpamHandler func(message *pb.WakuMessage) error
|
||||
rlnRelayIDKey *[32]byte
|
||||
rlnRelayIDCommitment *[32]byte
|
||||
rlnETHPrivateKey *ecdsa.PrivateKey
|
||||
rlnETHClientAddress string
|
||||
keystorePath string
|
||||
keystorePassword string
|
||||
rlnMembershipContractAddress common.Address
|
||||
rlnRegistrationHandler func(tx *types.Transaction)
|
||||
|
||||
|
|
|
@ -25,29 +25,20 @@ func WithStaticRLNRelay(pubsubTopic string, contentTopic string, memberIndex r.M
|
|||
}
|
||||
}
|
||||
|
||||
type MembershipCredentials struct {
|
||||
Contract common.Address `json:"contract"`
|
||||
Keypair *r.MembershipKeyPair `json:"membershipKeyPair"`
|
||||
Index r.MembershipIndex `json:"rlnIndex"`
|
||||
}
|
||||
|
||||
// WithStaticRLNRelay enables the Waku V2 RLN protocol in onchain mode.
|
||||
// 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, membershipCredentials MembershipCredentials, spamHandler rln.SpamHandler, ethClientAddress string, ethPrivateKey *ecdsa.PrivateKey, registrationHandler rln.RegistrationHandler) WakuNodeOption {
|
||||
func WithDynamicRLNRelay(pubsubTopic string, contentTopic string, keystorePath string, keystorePassword string, membershipContract common.Address, spamHandler rln.SpamHandler, ethClientAddress string, ethPrivateKey *ecdsa.PrivateKey, registrationHandler rln.RegistrationHandler) WakuNodeOption {
|
||||
return func(params *WakuNodeParameters) error {
|
||||
params.enableRLN = true
|
||||
params.rlnRelayDynamic = true
|
||||
params.rlnRelayMemIndex = membershipCredentials.Index
|
||||
if membershipCredentials.Keypair != nil {
|
||||
params.rlnRelayIDKey = &membershipCredentials.Keypair.IDKey
|
||||
params.rlnRelayIDCommitment = &membershipCredentials.Keypair.IDCommitment
|
||||
}
|
||||
params.keystorePassword = keystorePassword
|
||||
params.keystorePath = keystorePath
|
||||
params.rlnRelayPubsubTopic = pubsubTopic
|
||||
params.rlnRelayContentTopic = contentTopic
|
||||
params.rlnSpamHandler = spamHandler
|
||||
params.rlnETHClientAddress = ethClientAddress
|
||||
params.rlnETHPrivateKey = ethPrivateKey
|
||||
params.rlnMembershipContractAddress = membershipCredentials.Contract
|
||||
params.rlnMembershipContractAddress = membershipContract
|
||||
params.rlnRegistrationHandler = registrationHandler
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -24,12 +24,6 @@ const MAX_EPOCH_GAP = int64(MAX_CLOCK_GAP_SECONDS / rln.EPOCH_UNIT_SECONDS)
|
|||
// 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
|
||||
|
|
|
@ -4,17 +4,27 @@ import (
|
|||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"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/ethclient"
|
||||
"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/keystore"
|
||||
"github.com/waku-org/go-zerokit-rln/rln"
|
||||
r "github.com/waku-org/go-zerokit-rln/rln"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var RLNAppInfo = keystore.AppInfo{
|
||||
Application: "go-waku-rln-relay",
|
||||
AppIdentifier: "01234567890abcdef",
|
||||
Version: "0.1",
|
||||
}
|
||||
|
||||
type DynamicGroupManager struct {
|
||||
rln *rln.RLN
|
||||
log *zap.Logger
|
||||
|
@ -22,8 +32,9 @@ type DynamicGroupManager struct {
|
|||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
|
||||
identityCredential *rln.IdentityCredential
|
||||
membershipIndex *rln.MembershipIndex
|
||||
identityCredential *rln.IdentityCredential
|
||||
membershipIndex *rln.MembershipIndex
|
||||
|
||||
membershipContractAddress common.Address
|
||||
ethClientAddress string
|
||||
ethClient *ethclient.Client
|
||||
|
@ -34,8 +45,15 @@ type DynamicGroupManager struct {
|
|||
ethAccountPrivateKey *ecdsa.PrivateKey
|
||||
|
||||
registrationHandler RegistrationHandler
|
||||
chainId *big.Int
|
||||
rlnContract *contracts.RLN
|
||||
membershipFee *big.Int
|
||||
lastIndexLoaded int64
|
||||
|
||||
saveKeystore bool
|
||||
keystorePath string
|
||||
keystorePassword string
|
||||
|
||||
rootTracker *group_manager.MerkleRootTracker
|
||||
}
|
||||
|
||||
|
@ -45,22 +63,34 @@ func NewDynamicGroupManager(
|
|||
ethClientAddr string,
|
||||
ethAccountPrivateKey *ecdsa.PrivateKey,
|
||||
memContractAddr common.Address,
|
||||
identityCredential *rln.IdentityCredential,
|
||||
index rln.MembershipIndex,
|
||||
keystorePath string,
|
||||
keystorePassword string,
|
||||
saveKeystore bool,
|
||||
registrationHandler RegistrationHandler,
|
||||
log *zap.Logger,
|
||||
) (*DynamicGroupManager, error) {
|
||||
return &DynamicGroupManager{
|
||||
identityCredential: identityCredential,
|
||||
membershipIndex: &index,
|
||||
membershipContractAddress: memContractAddr,
|
||||
ethClientAddress: ethClientAddr,
|
||||
ethAccountPrivateKey: ethAccountPrivateKey,
|
||||
registrationHandler: registrationHandler,
|
||||
lastIndexLoaded: -1,
|
||||
saveKeystore: saveKeystore,
|
||||
keystorePath: keystorePath,
|
||||
keystorePassword: keystorePassword,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (gm *DynamicGroupManager) getMembershipFee(ctx context.Context) (*big.Int, error) {
|
||||
auth, err := bind.NewKeyedTransactorWithChainID(gm.ethAccountPrivateKey, gm.chainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth.Context = ctx
|
||||
|
||||
return gm.rlnContract.MEMBERSHIPDEPOSIT(&bind.CallOpts{Context: ctx})
|
||||
}
|
||||
|
||||
func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN, rootTracker *group_manager.MerkleRootTracker) error {
|
||||
if gm.cancel != nil {
|
||||
return errors.New("already started")
|
||||
|
@ -71,14 +101,55 @@ func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN,
|
|||
|
||||
gm.log.Info("mounting rln-relay in on-chain/dynamic mode")
|
||||
|
||||
backend, err := ethclient.Dial(gm.ethClientAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gm.ethClient = backend
|
||||
|
||||
gm.rln = rlnInstance
|
||||
gm.rootTracker = rootTracker
|
||||
|
||||
err := rootTracker.Sync()
|
||||
gm.chainId, err = backend.ChainID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gm.rlnContract, err = contracts.NewRLN(gm.membershipContractAddress, backend)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if the contract exists by calling a static function
|
||||
gm.membershipFee, err = gm.getMembershipFee(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = rootTracker.Sync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gm.keystorePassword != "" && gm.keystorePath != "" {
|
||||
credentials, err := keystore.GetMembershipCredentials(gm.log,
|
||||
gm.keystorePath,
|
||||
gm.keystorePassword,
|
||||
RLNAppInfo,
|
||||
nil,
|
||||
[]keystore.MembershipContract{{
|
||||
ChainId: gm.chainId.String(),
|
||||
Address: gm.membershipContractAddress.Hex(),
|
||||
}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: accept an index from the config
|
||||
gm.identityCredential = &credentials[0].IdentityCredential
|
||||
gm.membershipIndex = &credentials[0].MembershipGroups[0].TreeIndex
|
||||
}
|
||||
|
||||
// prepare rln membership key pair
|
||||
if gm.identityCredential == nil && gm.ethAccountPrivateKey != nil {
|
||||
gm.log.Debug("no rln-relay key is provided, generating one")
|
||||
|
@ -90,16 +161,23 @@ func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN,
|
|||
gm.identityCredential = identityCredential
|
||||
|
||||
// register the rln-relay peer to the membership contract
|
||||
membershipIndex, err := gm.Register(ctx)
|
||||
gm.membershipIndex, err = gm.Register(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gm.membershipIndex = membershipIndex
|
||||
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")
|
||||
}
|
||||
|
||||
handler := func(pubkey r.IDCommitment, index r.MembershipIndex) error {
|
||||
return gm.InsertMember(pubkey)
|
||||
}
|
||||
|
@ -115,6 +193,46 @@ 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")
|
||||
}
|
||||
|
||||
path := gm.keystorePath
|
||||
if path == "" {
|
||||
gm.log.Warn("keystore: no credentials path set, using default path", zap.String("path", keystore.RLN_CREDENTIALS_FILENAME))
|
||||
path = keystore.RLN_CREDENTIALS_FILENAME
|
||||
}
|
||||
|
||||
password := gm.keystorePassword
|
||||
if password == "" {
|
||||
gm.log.Warn("keystore: no credentials password set, using default password", zap.String("password", keystore.RLN_CREDENTIALS_PASSWORD))
|
||||
password = keystore.RLN_CREDENTIALS_PASSWORD
|
||||
}
|
||||
|
||||
keystoreCred := keystore.MembershipCredentials{
|
||||
IdentityCredential: *gm.identityCredential,
|
||||
MembershipGroups: []keystore.MembershipGroup{{
|
||||
TreeIndex: *gm.membershipIndex,
|
||||
MembershipContract: keystore.MembershipContract{
|
||||
ChainId: gm.chainId.String(),
|
||||
Address: gm.membershipContractAddress.String(),
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
err := keystore.AddMembershipCredentials(path, []keystore.MembershipCredentials{keystoreCred}, password, RLNAppInfo, keystore.DefaultSeparator)
|
||||
if err != nil {
|
||||
return errors.New("failed to persist credentials")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gm *DynamicGroupManager) InsertMember(pubkey rln.IDCommitment) error {
|
||||
gm.log.Debug("a new key is added", zap.Binary("pubkey", pubkey[:]))
|
||||
// assuming all the members arrive in order
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"time"
|
||||
|
||||
"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/ethclient"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
|
@ -26,18 +25,7 @@ func toBigInt(i []byte) *big.Int {
|
|||
return result
|
||||
}
|
||||
|
||||
func register(ctx context.Context, idComm r.IDCommitment, ethAccountPrivateKey *ecdsa.PrivateKey, ethClientAddress string, membershipContractAddress common.Address, registrationHandler RegistrationHandler, log *zap.Logger) (*r.MembershipIndex, error) {
|
||||
backend, err := ethclient.Dial(ethClientAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer backend.Close()
|
||||
|
||||
chainID, err := backend.ChainID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func register(ctx context.Context, backend *ethclient.Client, idComm r.IDCommitment, ethAccountPrivateKey *ecdsa.PrivateKey, rlnContract *contracts.RLN, chainID *big.Int, registrationHandler RegistrationHandler, log *zap.Logger) (*r.MembershipIndex, error) {
|
||||
auth, err := bind.NewKeyedTransactorWithChainID(ethAccountPrivateKey, chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -45,11 +33,6 @@ func register(ctx context.Context, idComm r.IDCommitment, ethAccountPrivateKey *
|
|||
auth.Value = MEMBERSHIP_FEE
|
||||
auth.Context = ctx
|
||||
|
||||
rlnContract, err := contracts.NewRLN(membershipContractAddress, backend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("registering an id commitment", zap.Binary("idComm", idComm[:]))
|
||||
|
||||
// registers the idComm into the membership contract whose address is in rlnPeer.membershipContractAddress
|
||||
|
@ -100,8 +83,14 @@ func register(ctx context.Context, idComm r.IDCommitment, ethAccountPrivateKey *
|
|||
// 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) (*r.MembershipIndex, error) {
|
||||
pk := gm.identityCredential.IDCommitment
|
||||
return register(ctx, pk, gm.ethAccountPrivateKey, gm.ethClientAddress, gm.membershipContractAddress, gm.registrationHandler, gm.log)
|
||||
return register(ctx,
|
||||
gm.ethClient,
|
||||
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
|
||||
|
@ -129,24 +118,13 @@ func (gm *DynamicGroupManager) processLogs(evt *contracts.RLNMemberRegistered, h
|
|||
func (gm *DynamicGroupManager) HandleGroupUpdates(ctx context.Context, handler RegistrationEventHandler) error {
|
||||
defer gm.wg.Done()
|
||||
|
||||
backend, err := ethclient.Dial(gm.ethClientAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gm.ethClient = backend
|
||||
|
||||
rlnContract, err := contracts.NewRLN(gm.membershipContractAddress, backend)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = gm.loadOldEvents(ctx, rlnContract, handler)
|
||||
err := gm.loadOldEvents(ctx, gm.rlnContract, handler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errCh := make(chan error)
|
||||
go gm.watchNewEvents(ctx, rlnContract, handler, gm.log, errCh)
|
||||
go gm.watchNewEvents(ctx, gm.rlnContract, handler, gm.log, errCh)
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,336 @@
|
|||
package keystore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/waku-org/go-zerokit-rln/rln"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const RLN_CREDENTIALS_FILENAME = "rlnCredentials.json"
|
||||
const RLN_CREDENTIALS_PASSWORD = "password"
|
||||
|
||||
type MembershipContract struct {
|
||||
ChainId string `json:"chainId"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type MembershipGroup struct {
|
||||
MembershipContract MembershipContract `json:"membershipContract"`
|
||||
TreeIndex rln.MembershipIndex `json:"treeIndex"`
|
||||
}
|
||||
|
||||
type MembershipCredentials struct {
|
||||
IdentityCredential rln.IdentityCredential `json:"identityCredential"`
|
||||
MembershipGroups []MembershipGroup `json:"membershipGroups"`
|
||||
}
|
||||
|
||||
type AppInfo struct {
|
||||
Application string `json:"application"`
|
||||
AppIdentifier string `json:"appIdentifier"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type AppKeystore struct {
|
||||
Application string `json:"application"`
|
||||
AppIdentifier string `json:"appIdentifier"`
|
||||
Credentials []keystore.CryptoJSON `json:"credentials"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
const DefaultSeparator = "\n"
|
||||
|
||||
func (m MembershipCredentials) Equals(other MembershipCredentials) bool {
|
||||
if !rln.IdentityCredentialEquals(m.IdentityCredential, other.IdentityCredential) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, x := range m.MembershipGroups {
|
||||
found := false
|
||||
for _, y := range other.MembershipGroups {
|
||||
if x.Equals(y) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (m MembershipGroup) Equals(other MembershipGroup) bool {
|
||||
return m.MembershipContract.Equals(other.MembershipContract) && m.TreeIndex == other.TreeIndex
|
||||
}
|
||||
|
||||
func (m MembershipContract) Equals(other MembershipContract) bool {
|
||||
return m.Address == other.Address && m.ChainId == other.ChainId
|
||||
}
|
||||
|
||||
func CreateAppKeystore(path string, appInfo AppInfo, separator string) error {
|
||||
if separator == "" {
|
||||
separator = DefaultSeparator
|
||||
}
|
||||
|
||||
keystore := AppKeystore{
|
||||
AppIdentifier: appInfo.AppIdentifier,
|
||||
Version: appInfo.Version,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(keystore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b = append(b, []byte(separator)...)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
err = json.Compact(buffer, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(path, buffer.Bytes(), 0600)
|
||||
}
|
||||
|
||||
func LoadAppKeystore(path string, appInfo AppInfo, separator string) (AppKeystore, error) {
|
||||
if separator == "" {
|
||||
separator = DefaultSeparator
|
||||
}
|
||||
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// If no keystore exists at path we create a new empty one with passed keystore parameters
|
||||
err = CreateAppKeystore(path, appInfo, separator)
|
||||
if err != nil {
|
||||
return AppKeystore{}, err
|
||||
}
|
||||
} else {
|
||||
return AppKeystore{}, err
|
||||
}
|
||||
}
|
||||
|
||||
src, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return AppKeystore{}, err
|
||||
}
|
||||
|
||||
for _, keystoreBytes := range bytes.Split(src, []byte(separator)) {
|
||||
if len(keystoreBytes) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
keystore := AppKeystore{}
|
||||
err := json.Unmarshal(keystoreBytes, &keystore)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if keystore.AppIdentifier == appInfo.AppIdentifier && keystore.Application == appInfo.Application && keystore.Version == appInfo.Version {
|
||||
return keystore, nil
|
||||
}
|
||||
}
|
||||
|
||||
return AppKeystore{}, errors.New("no keystore found")
|
||||
}
|
||||
|
||||
func filterCredential(credential MembershipCredentials, filterIdentityCredentials []MembershipCredentials, filterMembershipContracts []MembershipContract) *MembershipCredentials {
|
||||
if len(filterIdentityCredentials) != 0 {
|
||||
found := false
|
||||
for _, filterCreds := range filterIdentityCredentials {
|
||||
if filterCreds.Equals(credential) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(filterMembershipContracts) != 0 {
|
||||
var membershipGroupsIntersection []MembershipGroup
|
||||
for _, filterContract := range filterMembershipContracts {
|
||||
for _, credentialGroups := range credential.MembershipGroups {
|
||||
if filterContract.Equals(credentialGroups.MembershipContract) {
|
||||
membershipGroupsIntersection = append(membershipGroupsIntersection, credentialGroups)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(membershipGroupsIntersection) != 0 {
|
||||
// If we have a match on some groups, we return the credential with filtered groups
|
||||
return &MembershipCredentials{
|
||||
IdentityCredential: credential.IdentityCredential,
|
||||
MembershipGroups: membershipGroupsIntersection,
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// We hit this return only if
|
||||
// - filterIdentityCredentials.len() == 0 and filterMembershipContracts.len() == 0 (no filter)
|
||||
// - filterIdentityCredentials.len() != 0 and filterMembershipContracts.len() == 0 (filter only on identity credential)
|
||||
// Indeed, filterMembershipContracts.len() != 0 will have its exclusive return based on all values of membershipGroupsIntersection.len()
|
||||
return &credential
|
||||
}
|
||||
|
||||
func GetMembershipCredentials(logger *zap.Logger, credentialsPath string, password string, appInfo AppInfo, filterIdentityCredentials []MembershipCredentials, filterMembershipContracts []MembershipContract) ([]MembershipCredentials, error) {
|
||||
k, err := LoadAppKeystore(credentialsPath, appInfo, DefaultSeparator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []MembershipCredentials
|
||||
|
||||
for _, credential := range k.Credentials {
|
||||
credentialsBytes, err := keystore.DecryptDataV3(credential, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var credentials MembershipCredentials
|
||||
err = json.Unmarshal(credentialsBytes, &credentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filteredCredential := filterCredential(credentials, filterIdentityCredentials, filterMembershipContracts)
|
||||
if filterIdentityCredentials != nil {
|
||||
result = append(result, *filteredCredential)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Adds a sequence of membership credential to the keystore matching the application, appIdentifier and version filters.
|
||||
func AddMembershipCredentials(path string, credentials []MembershipCredentials, password string, appInfo AppInfo, separator string) error {
|
||||
k, err := LoadAppKeystore(path, appInfo, DefaultSeparator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var credentialsToAdd []MembershipCredentials
|
||||
for _, newCredential := range credentials {
|
||||
// A flag to tell us if the keystore contains a credential associated to the input identity credential, i.e. membershipCredential
|
||||
found := -1
|
||||
for i, existingCredentials := range k.Credentials {
|
||||
credentialsBytes, err := keystore.DecryptDataV3(existingCredentials, password)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var credentials MembershipCredentials
|
||||
err = json.Unmarshal(credentialsBytes, &credentials)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if rln.IdentityCredentialEquals(credentials.IdentityCredential, newCredential.IdentityCredential) {
|
||||
// idCredential is present in keystore. We add the input credential membership group to the one contained in the decrypted keystore credential (we deduplicate groups using sets)
|
||||
allMemberships := append(credentials.MembershipGroups, newCredential.MembershipGroups...)
|
||||
|
||||
// we define the updated credential with the updated membership sets
|
||||
updatedCredential := MembershipCredentials{
|
||||
IdentityCredential: newCredential.IdentityCredential,
|
||||
MembershipGroups: allMemberships,
|
||||
}
|
||||
|
||||
// we re-encrypt creating a new keyfile
|
||||
b, err := json.Marshal(updatedCredential)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptedCredentials, err := keystore.EncryptDataV3(b, []byte(password), keystore.StandardScryptN, keystore.StandardScryptP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// we update the original credential field in keystoreCredentials
|
||||
k.Credentials[i] = encryptedCredentials
|
||||
|
||||
found = i
|
||||
|
||||
// We stop decrypting other credentials in the keystore
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found == -1 {
|
||||
credentialsToAdd = append(credentialsToAdd, newCredential)
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range credentialsToAdd {
|
||||
b, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptedCredentials, err := keystore.EncryptDataV3(b, []byte(password), keystore.StandardScryptN, keystore.StandardScryptP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k.Credentials = append(k.Credentials, encryptedCredentials)
|
||||
}
|
||||
|
||||
return save(k, path, separator)
|
||||
}
|
||||
|
||||
// Safely saves a Keystore's JsonNode to disk.
|
||||
// If exists, the destination file is renamed with extension .bkp; the file is written at its destination and the .bkp file is removed if write is successful, otherwise is restored
|
||||
func save(keystore AppKeystore, path string, separator string) error {
|
||||
// We first backup the current keystore
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
err := os.Rename(path, path+".bkp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if separator == "" {
|
||||
separator = DefaultSeparator
|
||||
}
|
||||
|
||||
b, err := json.Marshal(keystore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b = append(b, []byte(separator)...)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
err = json.Compact(buffer, b)
|
||||
if err != nil {
|
||||
restoreErr := os.Rename(path, path+".bkp")
|
||||
if restoreErr != nil {
|
||||
return fmt.Errorf("could not restore backup file: %w", restoreErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(path, buffer.Bytes(), 0600)
|
||||
if err != nil {
|
||||
restoreErr := os.Rename(path, path+".bkp")
|
||||
if restoreErr != nil {
|
||||
return fmt.Errorf("could not restore backup file: %w", restoreErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -21,12 +21,6 @@ import (
|
|||
proto "google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var RLNAppInfo = AppInfo{
|
||||
Application: "go-waku-rln-relay",
|
||||
AppIdentifier: "01234567890abcdef",
|
||||
Version: "0.1",
|
||||
}
|
||||
|
||||
type GroupManager interface {
|
||||
Start(ctx context.Context, rln *rln.RLN, rootTracker *group_manager.MerkleRootTracker) error
|
||||
IdentityCredentials() (rln.IdentityCredential, error)
|
||||
|
|
Loading…
Reference in New Issue