refactor: credentials

This commit is contained in:
Richard Ramos 2023-04-05 15:44:46 -04:00 committed by RichΛrd
parent 04c90657cd
commit 42c0e123d9
13 changed files with 487 additions and 224 deletions

View File

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

View File

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

View File

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

View File

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

View 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)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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