mirror of https://github.com/status-im/go-waku.git
refactor: keystore as a data type
This commit is contained in:
parent
229fb7a970
commit
0854edaf3d
|
@ -28,13 +28,13 @@ var Command = cli.Command{
|
|||
Name: "generate-rln-credentials",
|
||||
Usage: "Generate credentials for usage with RLN",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
err := verifyFlags()
|
||||
if err != nil {
|
||||
if options.ETHPrivateKey == nil {
|
||||
err := errors.New("a private key must be specified")
|
||||
logger.Error("validating option flags", zap.Error(err))
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
err = execute(context.Background())
|
||||
err := execute(context.Background())
|
||||
if err != nil {
|
||||
logger.Error("registering RLN credentials", zap.Error(err))
|
||||
return cli.Exit(err, 1)
|
||||
|
@ -45,24 +45,6 @@ var Command = cli.Command{
|
|||
Flags: flags,
|
||||
}
|
||||
|
||||
func verifyFlags() error {
|
||||
if options.CredentialsPath == "" {
|
||||
logger.Warn("keystore: no credentials path set, using default path", zap.String("path", keystore.RLN_CREDENTIALS_FILENAME))
|
||||
options.CredentialsPath = keystore.RLN_CREDENTIALS_FILENAME
|
||||
}
|
||||
|
||||
if options.CredentialsPassword == "" {
|
||||
logger.Warn("keystore: no credentials password set, using default password", zap.String("password", keystore.RLN_CREDENTIALS_PASSWORD))
|
||||
options.CredentialsPassword = keystore.RLN_CREDENTIALS_PASSWORD
|
||||
}
|
||||
|
||||
if options.ETHPrivateKey == nil {
|
||||
return errors.New("a private key must be specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func execute(ctx context.Context) error {
|
||||
ethClient, err := ethclient.Dial(options.ETHClientAddress)
|
||||
if err != nil {
|
||||
|
@ -122,15 +104,20 @@ func execute(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func persistCredentials(identityCredential *rln.IdentityCredential, membershipIndex rln.MembershipIndex, chainID *big.Int) error {
|
||||
appKeystore, err := keystore.New(options.CredentialsPath, dynamic.RLNAppInfo, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
membershipGroup := keystore.MembershipGroup{
|
||||
TreeIndex: membershipIndex,
|
||||
MembershipContract: keystore.MembershipContract{
|
||||
ChainId: fmt.Sprintf("0x%X", chainID.Int64()),
|
||||
ChainID: fmt.Sprintf("0x%X", chainID.Int64()),
|
||||
Address: options.MembershipContractAddress.String(),
|
||||
},
|
||||
}
|
||||
|
||||
membershipGroupIndex, err := keystore.AddMembershipCredentials(options.CredentialsPath, identityCredential, membershipGroup, options.CredentialsPassword, dynamic.RLNAppInfo, keystore.DefaultSeparator)
|
||||
membershipGroupIndex, err := appKeystore.AddMembershipCredentials(identityCredential, membershipGroup, options.CredentialsPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to persist credentials: %w", err)
|
||||
}
|
||||
|
|
|
@ -13,12 +13,12 @@ var flags = []cli.Flag{
|
|||
&cli.PathFlag{
|
||||
Name: "cred-path",
|
||||
Usage: "RLN relay membership credentials file",
|
||||
Value: keystore.RLN_CREDENTIALS_FILENAME,
|
||||
Value: keystore.DefaultCredentialsFilename,
|
||||
Destination: &options.CredentialsPath,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "cred-password",
|
||||
Value: keystore.RLN_CREDENTIALS_PASSWORD,
|
||||
Value: keystore.DefaultCredentialsPassword,
|
||||
Usage: "Password for encrypting RLN credentials",
|
||||
Destination: &options.CredentialsPassword,
|
||||
},
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/waku-org/go-waku/waku/v2/protocol/rln"
|
||||
"github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager/dynamic"
|
||||
"github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager/static"
|
||||
"github.com/waku-org/go-waku/waku/v2/protocol/rln/keystore"
|
||||
r "github.com/waku-org/go-zerokit-rln/rln"
|
||||
)
|
||||
|
||||
|
@ -44,14 +45,18 @@ func (w *WakuNode) setupRLNRelay() error {
|
|||
} else {
|
||||
w.log.Info("setting up waku-rln-relay in on-chain mode")
|
||||
|
||||
appKeystore, err := keystore.New(w.opts.keystorePath, dynamic.RLNAppInfo, w.log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groupManager, err = dynamic.NewDynamicGroupManager(
|
||||
w.opts.rlnETHClientAddress,
|
||||
w.opts.rlnMembershipContractAddress,
|
||||
w.opts.rlnRelayMemIndex,
|
||||
w.opts.keystorePath,
|
||||
appKeystore,
|
||||
w.opts.keystorePassword,
|
||||
w.opts.keystoreIndex,
|
||||
true,
|
||||
w.opts.prometheusReg,
|
||||
w.log,
|
||||
)
|
||||
|
|
|
@ -51,8 +51,7 @@ type DynamicGroupManager struct {
|
|||
chainId *big.Int
|
||||
rlnContract *contracts.RLN
|
||||
|
||||
saveKeystore bool
|
||||
keystorePath string
|
||||
appKeystore *keystore.AppKeystore
|
||||
keystorePassword string
|
||||
keystoreIndex uint
|
||||
|
||||
|
@ -122,35 +121,21 @@ func NewDynamicGroupManager(
|
|||
ethClientAddr string,
|
||||
memContractAddr common.Address,
|
||||
membershipGroupIndex uint,
|
||||
keystorePath string,
|
||||
appKeystore *keystore.AppKeystore,
|
||||
keystorePassword string,
|
||||
keystoreIndex uint,
|
||||
saveKeystore bool,
|
||||
reg prometheus.Registerer,
|
||||
log *zap.Logger,
|
||||
) (*DynamicGroupManager, error) {
|
||||
log = log.Named("rln-dynamic")
|
||||
|
||||
path := keystorePath
|
||||
if path == "" {
|
||||
log.Warn("keystore: no credentials path set, using default path", zap.String("path", keystore.RLN_CREDENTIALS_FILENAME))
|
||||
path = keystore.RLN_CREDENTIALS_FILENAME
|
||||
}
|
||||
|
||||
password := keystorePassword
|
||||
if password == "" {
|
||||
log.Warn("keystore: no credentials password set, using default password", zap.String("password", keystore.RLN_CREDENTIALS_PASSWORD))
|
||||
password = keystore.RLN_CREDENTIALS_PASSWORD
|
||||
}
|
||||
|
||||
return &DynamicGroupManager{
|
||||
membershipGroupIndex: membershipGroupIndex,
|
||||
membershipContractAddress: memContractAddr,
|
||||
ethClientAddress: ethClientAddr,
|
||||
eventHandler: handler,
|
||||
saveKeystore: saveKeystore,
|
||||
keystorePath: path,
|
||||
keystorePassword: password,
|
||||
appKeystore: appKeystore,
|
||||
keystorePassword: keystorePassword,
|
||||
keystoreIndex: keystoreIndex,
|
||||
log: log,
|
||||
metrics: newMetrics(reg),
|
||||
|
@ -196,39 +181,9 @@ func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN,
|
|||
return err
|
||||
}
|
||||
|
||||
if gm.identityCredential == nil && gm.keystorePassword != "" && gm.keystorePath != "" {
|
||||
start := time.Now()
|
||||
credentials, err := keystore.GetMembershipCredentials(gm.log,
|
||||
gm.keystorePath,
|
||||
gm.keystorePassword,
|
||||
RLNAppInfo,
|
||||
nil,
|
||||
[]keystore.MembershipContract{{
|
||||
ChainId: fmt.Sprintf("0x%X", gm.chainId),
|
||||
Address: gm.membershipContractAddress.Hex(),
|
||||
}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gm.metrics.RecordMembershipCredentialsImportDuration(time.Since(start))
|
||||
|
||||
if len(credentials) != 0 {
|
||||
if int(gm.keystoreIndex) <= len(credentials)-1 {
|
||||
credential := credentials[gm.keystoreIndex]
|
||||
gm.identityCredential = credential.IdentityCredential
|
||||
if int(gm.membershipGroupIndex) <= len(credential.MembershipGroups)-1 {
|
||||
gm.membershipIndex = &credential.MembershipGroups[gm.membershipGroupIndex].TreeIndex
|
||||
} else {
|
||||
return errors.New("invalid membership group index")
|
||||
}
|
||||
} else {
|
||||
return errors.New("invalid keystore index")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if gm.identityCredential == nil || gm.membershipIndex == nil {
|
||||
return errors.New("no credentials available")
|
||||
err = gm.loadCredential()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = gm.HandleGroupUpdates(ctx, gm.eventHandler); err != nil {
|
||||
|
@ -238,6 +193,39 @@ func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN,
|
|||
return nil
|
||||
}
|
||||
|
||||
func (gm *DynamicGroupManager) loadCredential() error {
|
||||
start := time.Now()
|
||||
|
||||
credentials, err := gm.appKeystore.GetMembershipCredentials(
|
||||
gm.keystorePassword,
|
||||
nil,
|
||||
[]keystore.MembershipContract{{
|
||||
ChainID: fmt.Sprintf("0x%X", gm.chainId),
|
||||
Address: gm.membershipContractAddress.Hex(),
|
||||
}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gm.metrics.RecordMembershipCredentialsImportDuration(time.Since(start))
|
||||
|
||||
if len(credentials) == 0 {
|
||||
return errors.New("no credentials available")
|
||||
}
|
||||
|
||||
if int(gm.keystoreIndex) > len(credentials)-1 {
|
||||
return errors.New("invalid keystore index")
|
||||
}
|
||||
|
||||
if int(gm.membershipGroupIndex) > len(credentials[gm.keystoreIndex].MembershipGroups)-1 {
|
||||
return errors.New("invalid membership group index")
|
||||
}
|
||||
|
||||
gm.identityCredential = credentials[gm.keystoreIndex].IdentityCredential
|
||||
gm.membershipIndex = &credentials[gm.keystoreIndex].MembershipGroups[gm.membershipGroupIndex].TreeIndex
|
||||
|
||||
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
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
|
@ -14,130 +13,49 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const RLN_CREDENTIALS_FILENAME = "rlnKeystore.json"
|
||||
const RLN_CREDENTIALS_PASSWORD = "password"
|
||||
// DefaultCredentialsFilename is the default filename for the rln credentials keystore
|
||||
const DefaultCredentialsFilename = "rlnKeystore.json"
|
||||
|
||||
type MembershipContract struct {
|
||||
ChainId string `json:"chainId"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
// DefaultCredentialsPassword contains the default password used when no password is specified
|
||||
const DefaultCredentialsPassword = "password"
|
||||
|
||||
type MembershipGroup struct {
|
||||
MembershipContract MembershipContract `json:"membershipContract"`
|
||||
TreeIndex rln.MembershipIndex `json:"treeIndex"`
|
||||
}
|
||||
// New creates a new instance of a rln credentials keystore
|
||||
func New(keystorePath string, appInfo AppInfo, logger *zap.Logger) (*AppKeystore, error) {
|
||||
logger = logger.Named("rln-keystore")
|
||||
|
||||
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 []AppKeystoreCredential `json:"credentials"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type AppKeystoreCredential struct {
|
||||
Crypto keystore.CryptoJSON `json:"crypto"`
|
||||
}
|
||||
|
||||
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{
|
||||
Application: appInfo.Application,
|
||||
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
|
||||
path := keystorePath
|
||||
if path == "" {
|
||||
logger.Warn("keystore: no credentials path set, using default path", zap.String("path", DefaultCredentialsFilename))
|
||||
path = DefaultCredentialsFilename
|
||||
}
|
||||
|
||||
_, 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)
|
||||
err = createAppKeystore(path, appInfo, defaultSeparator)
|
||||
if err != nil {
|
||||
return AppKeystore{}, err
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return AppKeystore{}, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
src, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return AppKeystore{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, keystoreBytes := range bytes.Split(src, []byte(separator)) {
|
||||
for _, keystoreBytes := range bytes.Split(src, []byte(defaultSeparator)) {
|
||||
if len(keystoreBytes) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
keystore := AppKeystore{}
|
||||
err := json.Unmarshal(keystoreBytes, &keystore)
|
||||
keystore := new(AppKeystore)
|
||||
keystore.logger = logger
|
||||
keystore.path = path
|
||||
err := json.Unmarshal(keystoreBytes, keystore)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
@ -147,53 +65,15 @@ func LoadAppKeystore(path string, appInfo AppInfo, separator string) (AppKeystor
|
|||
}
|
||||
}
|
||||
|
||||
return AppKeystore{}, errors.New("no keystore found")
|
||||
return nil, 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
|
||||
// GetMembershipCredentials decrypts and retrieves membership credentials from the keystore applying filters
|
||||
func (k *AppKeystore) GetMembershipCredentials(keystorePassword string, filterIdentityCredentials []MembershipCredentials, filterMembershipContracts []MembershipContract) ([]MembershipCredentials, error) {
|
||||
password := keystorePassword
|
||||
if password == "" {
|
||||
k.logger.Warn("keystore: no credentials password set, using default password", zap.String("password", DefaultCredentialsPassword))
|
||||
password = DefaultCredentialsPassword
|
||||
}
|
||||
|
||||
var result []MembershipCredentials
|
||||
|
@ -220,12 +100,7 @@ func GetMembershipCredentials(logger *zap.Logger, credentialsPath string, passwo
|
|||
}
|
||||
|
||||
// AddMembershipCredentials inserts 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) (membershipGroupIndex uint, err error) {
|
||||
k, err := LoadAppKeystore(path, appInfo, DefaultSeparator)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (k *AppKeystore) AddMembershipCredentials(newIdentityCredential *rln.IdentityCredential, newMembershipGroup MembershipGroup, password string) (membershipGroupIndex uint, err error) {
|
||||
// A flag to tell us if the keystore contains a credential associated to the input identity credential, i.e. membershipCredential
|
||||
found := false
|
||||
for i, existingCredentials := range k.Credentials {
|
||||
|
@ -275,7 +150,7 @@ func AddMembershipCredentials(path string, newIdentityCredential *rln.IdentityCr
|
|||
}
|
||||
|
||||
// we update the original credential field in keystoreCredentials
|
||||
k.Credentials[i] = AppKeystoreCredential{Crypto: encryptedCredentials}
|
||||
k.Credentials[i] = appKeystoreCredential{Crypto: encryptedCredentials}
|
||||
|
||||
found = true
|
||||
|
||||
|
@ -309,28 +184,23 @@ func AddMembershipCredentials(path string, newIdentityCredential *rln.IdentityCr
|
|||
return 0, err
|
||||
}
|
||||
|
||||
k.Credentials = append(k.Credentials, AppKeystoreCredential{Crypto: encryptedCredentials})
|
||||
k.Credentials = append(k.Credentials, appKeystoreCredential{Crypto: encryptedCredentials})
|
||||
|
||||
membershipGroupIndex = uint(len(newCredential.MembershipGroups) - 1)
|
||||
}
|
||||
|
||||
return membershipGroupIndex, save(k, path, separator)
|
||||
return membershipGroupIndex, save(k, k.path)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
func createAppKeystore(path string, appInfo AppInfo, separator string) error {
|
||||
if separator == "" {
|
||||
separator = defaultSeparator
|
||||
}
|
||||
|
||||
if separator == "" {
|
||||
separator = DefaultSeparator
|
||||
keystore := AppKeystore{
|
||||
Application: appInfo.Application,
|
||||
AppIdentifier: appInfo.AppIdentifier,
|
||||
Version: appInfo.Version,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(keystore)
|
||||
|
@ -342,6 +212,75 @@ func save(keystore AppKeystore, path string, separator string) error {
|
|||
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
err = json.Compact(buffer, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(path, buffer.Bytes(), 0600)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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) 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
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.Marshal(keystore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b = append(b, []byte(defaultSeparator)...)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
err = json.Compact(buffer, b)
|
||||
if err != nil {
|
||||
restoreErr := os.Rename(path, path+".bkp")
|
||||
|
@ -351,7 +290,7 @@ func save(keystore AppKeystore, path string, separator string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(path, buffer.Bytes(), 0600)
|
||||
err = os.WriteFile(path, buffer.Bytes(), 0600)
|
||||
if err != nil {
|
||||
restoreErr := os.Rename(path, path+".bkp")
|
||||
if restoreErr != nil {
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package keystore
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/waku-org/go-zerokit-rln/rln"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// MembershipContract contains information about a membership smart contract address and the chain in which it is deployed
|
||||
type MembershipContract struct {
|
||||
ChainID string `json:"chainId"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
// Equals is used to compare MembershipContract
|
||||
func (m MembershipContract) Equals(other MembershipContract) bool {
|
||||
return m.Address == other.Address && m.ChainID == other.ChainID
|
||||
}
|
||||
|
||||
// MembershipGroup contains information about the index in which a credential is stored in the merkle tree and the contract associated to this credential
|
||||
type MembershipGroup struct {
|
||||
MembershipContract MembershipContract `json:"membershipContract"`
|
||||
TreeIndex rln.MembershipIndex `json:"treeIndex"`
|
||||
}
|
||||
|
||||
// Equals is used to compare MembershipGroup
|
||||
func (m MembershipGroup) Equals(other MembershipGroup) bool {
|
||||
return m.MembershipContract.Equals(other.MembershipContract) && m.TreeIndex == other.TreeIndex
|
||||
}
|
||||
|
||||
// MembershipCredentials contains all the information about an RLN Identity Credential and membership group it belongs to
|
||||
type MembershipCredentials struct {
|
||||
IdentityCredential *rln.IdentityCredential `json:"identityCredential"`
|
||||
MembershipGroups []MembershipGroup `json:"membershipGroups"`
|
||||
}
|
||||
|
||||
// Equals is used to compare MembershipCredentials
|
||||
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
|
||||
}
|
||||
|
||||
// AppInfo is a helper structure that contains information about the application that uses these credentials
|
||||
type AppInfo struct {
|
||||
Application string `json:"application"`
|
||||
AppIdentifier string `json:"appIdentifier"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// AppKeystore represents the membership credentials to be used in RLN
|
||||
type AppKeystore struct {
|
||||
Application string `json:"application"`
|
||||
AppIdentifier string `json:"appIdentifier"`
|
||||
Credentials []appKeystoreCredential `json:"credentials"`
|
||||
Version string `json:"version"`
|
||||
|
||||
path string
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
type appKeystoreCredential struct {
|
||||
Crypto keystore.CryptoJSON `json:"crypto"`
|
||||
}
|
||||
|
||||
const defaultSeparator = "\n"
|
Loading…
Reference in New Issue