mirror of
https://github.com/status-im/go-waku.git
synced 2025-01-12 23:04:45 +00:00
feat: create generate-rln-credentials
subcommand
This commit is contained in:
parent
8cc92dfdef
commit
f088e49075
@ -6,6 +6,7 @@ import (
|
||||
cli "github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
"github.com/waku-org/go-waku/cmd/waku/keygen"
|
||||
"github.com/waku-org/go-waku/cmd/waku/rlngenerate"
|
||||
"github.com/waku-org/go-waku/waku/v2/node"
|
||||
)
|
||||
|
||||
@ -114,6 +115,7 @@ func main() {
|
||||
},
|
||||
Commands: []*cli.Command{
|
||||
&keygen.Command,
|
||||
&rlngenerate.Command,
|
||||
},
|
||||
}
|
||||
|
||||
|
19
cmd/waku/rlngenerate/command_no_rln.go
Normal file
19
cmd/waku/rlngenerate/command_no_rln.go
Normal file
@ -0,0 +1,19 @@
|
||||
//go:build !gowaku_rln
|
||||
// +build !gowaku_rln
|
||||
|
||||
package rlngenerate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
cli "github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Command generates a key file used to generate the node's peerID, encrypted with an optional password
|
||||
var Command = cli.Command{
|
||||
Name: "generate-rln-credentials",
|
||||
Usage: "Generate credentials for usage with RLN",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
return errors.New("not available. Execute `make RLN=true` to add RLN support to go-waku")
|
||||
},
|
||||
}
|
141
cmd/waku/rlngenerate/command_rln.go
Normal file
141
cmd/waku/rlngenerate/command_rln.go
Normal file
@ -0,0 +1,141 @@
|
||||
//go:build gowaku_rln
|
||||
// +build gowaku_rln
|
||||
|
||||
package rlngenerate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
cli "github.com/urfave/cli/v2"
|
||||
"github.com/waku-org/go-waku/logging"
|
||||
"github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts"
|
||||
"github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager/dynamic"
|
||||
"github.com/waku-org/go-waku/waku/v2/protocol/rln/keystore"
|
||||
"github.com/waku-org/go-waku/waku/v2/utils"
|
||||
"github.com/waku-org/go-zerokit-rln/rln"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var options Options
|
||||
var logger = utils.Logger().Named("rln-credentials")
|
||||
|
||||
// Command generates a key file used to generate the node's peerID, encrypted with an optional password
|
||||
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 {
|
||||
logger.Error("validating option flags", zap.Error(err))
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
err = execute(context.Background())
|
||||
if err != nil {
|
||||
logger.Error("registering RLN credentials", zap.Error(err))
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
rlnInstance, err := rln.NewRLN()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chainID, err := ethClient.ChainID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rlnContract, err := contracts.NewRLN(options.MembershipContractAddress, ethClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// prepare rln membership key pair
|
||||
logger.Info("generating rln credential")
|
||||
identityCredential, err := rlnInstance.MembershipKeyGen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// register the rln-relay peer to the membership contract
|
||||
membershipIndex, err := register(ctx, ethClient, rlnContract, identityCredential.IDCommitment, chainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: clean private key from memory
|
||||
|
||||
err = persistCredentials(identityCredential, membershipIndex, chainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if logger.Level() == zap.DebugLevel {
|
||||
logger.Info("registered credentials into the membership contract",
|
||||
logging.HexString("IDCommitment", identityCredential.IDCommitment[:]),
|
||||
logging.HexString("IDNullifier", identityCredential.IDNullifier[:]),
|
||||
logging.HexString("IDSecretHash", identityCredential.IDSecretHash[:]),
|
||||
logging.HexString("IDTrapDoor", identityCredential.IDTrapdoor[:]),
|
||||
zap.Uint("index", membershipIndex),
|
||||
)
|
||||
} else {
|
||||
logger.Info("registered credentials into the membership contract", logging.HexString("idCommitment", identityCredential.IDCommitment[:]), zap.Uint("index", membershipIndex))
|
||||
}
|
||||
|
||||
ethClient.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func persistCredentials(identityCredential *rln.IdentityCredential, membershipIndex rln.MembershipIndex, chainID *big.Int) error {
|
||||
membershipGroup := keystore.MembershipGroup{
|
||||
TreeIndex: membershipIndex,
|
||||
MembershipContract: keystore.MembershipContract{
|
||||
ChainId: fmt.Sprintf("0x%X", chainID.Int64()),
|
||||
Address: options.MembershipContractAddress.String(),
|
||||
},
|
||||
}
|
||||
|
||||
keystoreIndex, membershipGroupIndex, err := keystore.AddMembershipCredentials(options.CredentialsPath, identityCredential, membershipGroup, options.CredentialsPassword, dynamic.RLNAppInfo, keystore.DefaultSeparator)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to persist credentials: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("persisted credentials succesfully", zap.Int("keystoreIndex", keystoreIndex), zap.Int("membershipGroupIndex", membershipGroupIndex))
|
||||
|
||||
return nil
|
||||
}
|
75
cmd/waku/rlngenerate/flags.go
Normal file
75
cmd/waku/rlngenerate/flags.go
Normal file
@ -0,0 +1,75 @@
|
||||
//go:build gowaku_rln
|
||||
// +build gowaku_rln
|
||||
|
||||
package rlngenerate
|
||||
|
||||
import (
|
||||
cli "github.com/urfave/cli/v2"
|
||||
wcli "github.com/waku-org/go-waku/waku/cliutils"
|
||||
"github.com/waku-org/go-waku/waku/v2/protocol/rln/keystore"
|
||||
)
|
||||
|
||||
var flags = []cli.Flag{
|
||||
&cli.PathFlag{
|
||||
Name: "cred-path",
|
||||
Usage: "RLN relay membership credentials file",
|
||||
Value: keystore.RLN_CREDENTIALS_FILENAME,
|
||||
Destination: &options.CredentialsPath,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "cred-password",
|
||||
Value: keystore.RLN_CREDENTIALS_PASSWORD,
|
||||
Usage: "Password for encrypting RLN credentials",
|
||||
Destination: &options.CredentialsPassword,
|
||||
},
|
||||
&cli.GenericFlag{
|
||||
Name: "eth-account-private-key",
|
||||
Usage: "Ethereum account private key used for registering in member contract",
|
||||
Value: &wcli.PrivateKeyValue{
|
||||
Value: &options.ETHPrivateKey,
|
||||
},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "eth-client-address",
|
||||
Usage: "Ethereum testnet client address",
|
||||
Value: "ws://localhost:8545",
|
||||
Destination: &options.ETHClientAddress,
|
||||
},
|
||||
&cli.GenericFlag{
|
||||
Name: "eth-contract-address",
|
||||
Usage: "Address of membership contract",
|
||||
Value: &wcli.AddressValue{
|
||||
Value: &options.MembershipContractAddress,
|
||||
},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "eth-nonce",
|
||||
Value: "",
|
||||
Usage: "Set an specific ETH transaction nonce. Leave empty to calculate the nonce automatically",
|
||||
Destination: &options.ETHNonce,
|
||||
},
|
||||
&cli.Uint64Flag{
|
||||
Name: "eth-gas-limit",
|
||||
Value: 0,
|
||||
Usage: "Gas limit to set for the transaction execution (0 = estimate)",
|
||||
Destination: &options.ETHGasLimit,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "eth-gas-price",
|
||||
Value: "",
|
||||
Usage: "Gas price in wei to use for the transaction execution (empty = gas price oracle)",
|
||||
Destination: &options.ETHGasPrice,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "eth-gas-fee-cap",
|
||||
Value: "",
|
||||
Usage: "Gas fee cap in wei to use for the 1559 transaction execution (empty = gas price oracle)",
|
||||
Destination: &options.ETHGasFeeCap,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "eth-gas-tip-cap",
|
||||
Value: "",
|
||||
Usage: "Gas priority fee cap in wei to use for the 1559 transaction execution (empty = gas price oracle)",
|
||||
Destination: &options.ETHGasTipCap,
|
||||
},
|
||||
}
|
21
cmd/waku/rlngenerate/options.go
Normal file
21
cmd/waku/rlngenerate/options.go
Normal file
@ -0,0 +1,21 @@
|
||||
package rlngenerate
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// Options are settings used to create RLN credentials.
|
||||
type Options struct {
|
||||
CredentialsPath string
|
||||
CredentialsPassword string
|
||||
ETHPrivateKey *ecdsa.PrivateKey
|
||||
ETHClientAddress string
|
||||
MembershipContractAddress common.Address
|
||||
ETHGasLimit uint64
|
||||
ETHNonce string
|
||||
ETHGasPrice string
|
||||
ETHGasFeeCap string
|
||||
ETHGasTipCap string
|
||||
}
|
125
cmd/waku/rlngenerate/web3.go
Normal file
125
cmd/waku/rlngenerate/web3.go
Normal file
@ -0,0 +1,125 @@
|
||||
//go:build gowaku_rln
|
||||
// +build gowaku_rln
|
||||
|
||||
package rlngenerate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/waku-org/go-waku/logging"
|
||||
"github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts"
|
||||
"github.com/waku-org/go-zerokit-rln/rln"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func getMembershipFee(ctx context.Context, rlnContract *contracts.RLN) (*big.Int, error) {
|
||||
return rlnContract.MEMBERSHIPDEPOSIT(&bind.CallOpts{Context: ctx})
|
||||
}
|
||||
|
||||
func register(ctx context.Context, ethClient *ethclient.Client, rlnContract *contracts.RLN, idComm rln.IDCommitment, chainID *big.Int) (rln.MembershipIndex, error) {
|
||||
// check if the contract exists by calling a static function
|
||||
membershipFee, err := getMembershipFee(ctx, rlnContract)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
auth, err := bind.NewKeyedTransactorWithChainID(options.ETHPrivateKey, chainID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
auth.Value = membershipFee
|
||||
auth.Context = ctx
|
||||
auth.GasLimit = options.ETHGasLimit
|
||||
|
||||
var ok bool
|
||||
|
||||
if options.ETHNonce != "" {
|
||||
nonce := &big.Int{}
|
||||
auth.Nonce, ok = nonce.SetString(options.ETHNonce, 10)
|
||||
if !ok {
|
||||
return 0, errors.New("invalid nonce value")
|
||||
}
|
||||
}
|
||||
|
||||
if options.ETHGasFeeCap != "" {
|
||||
gasFeeCap := &big.Int{}
|
||||
auth.GasFeeCap, ok = gasFeeCap.SetString(options.ETHGasFeeCap, 10)
|
||||
if !ok {
|
||||
return 0, errors.New("invalid gas fee cap value")
|
||||
}
|
||||
}
|
||||
|
||||
if options.ETHGasTipCap != "" {
|
||||
gasTipCap := &big.Int{}
|
||||
auth.GasTipCap, ok = gasTipCap.SetString(options.ETHGasTipCap, 10)
|
||||
if !ok {
|
||||
return 0, errors.New("invalid gas tip cap value")
|
||||
}
|
||||
}
|
||||
|
||||
if options.ETHGasPrice != "" {
|
||||
gasPrice := &big.Int{}
|
||||
auth.GasPrice, ok = gasPrice.SetString(options.ETHGasPrice, 10)
|
||||
if !ok {
|
||||
return 0, errors.New("invalid gas price value")
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("registering an id commitment", zap.Binary("idComm", idComm[:]))
|
||||
|
||||
// registers the idComm into the membership contract whose address is in rlnPeer.membershipContractAddress
|
||||
tx, err := rlnContract.Register(auth, rln.Bytes32ToBigInt(idComm))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("transaction error: %w", err)
|
||||
}
|
||||
|
||||
url := ""
|
||||
switch chainID.Int64() {
|
||||
case 1:
|
||||
url = "https://etherscan.io"
|
||||
case 5:
|
||||
url = "https://goerli.etherscan.io"
|
||||
case 11155111:
|
||||
url = "https://sepolia.etherscan.io"
|
||||
}
|
||||
|
||||
if url != "" {
|
||||
logger.Info(fmt.Sprintf("transaction broadcasted, find details of your registration transaction in %s/tx/%s", url, tx.Hash()))
|
||||
} else {
|
||||
logger.Info("transaction broadcasted.", zap.String("transactionHash", tx.Hash().String()))
|
||||
}
|
||||
|
||||
logger.Warn("waiting for transaction to be mined...")
|
||||
|
||||
txReceipt, err := bind.WaitMined(ctx, ethClient, tx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("transaction error: %w", err)
|
||||
}
|
||||
|
||||
if txReceipt.Status != types.ReceiptStatusSuccessful {
|
||||
return 0, errors.New("transaction reverted")
|
||||
}
|
||||
|
||||
// the receipt topic holds the hash of signature of the raised events
|
||||
evt, err := rlnContract.ParseMemberRegistered(*txReceipt.Logs[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var eventIDComm rln.IDCommitment = rln.BigIntToBytes32(evt.Pubkey)
|
||||
|
||||
log.Debug("information extracted from tx log", zap.Uint64("blockNumber", evt.Raw.BlockNumber), logging.HexString("idCommitment", eventIDComm[:]), zap.Uint64("index", evt.Index.Uint64()))
|
||||
|
||||
if eventIDComm != idComm {
|
||||
return 0, errors.New("invalid id commitment key")
|
||||
}
|
||||
|
||||
return rln.MembershipIndex(uint(evt.Index.Int64())), nil
|
||||
}
|
@ -215,7 +215,7 @@ func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN,
|
||||
if len(credentials) != 0 {
|
||||
if int(gm.keystoreIndex) <= len(credentials)-1 {
|
||||
credential := credentials[gm.keystoreIndex]
|
||||
gm.identityCredential = &credential.IdentityCredential
|
||||
gm.identityCredential = credential.IdentityCredential
|
||||
if int(gm.membershipGroupIndex) <= len(credential.MembershipGroups)-1 {
|
||||
gm.membershipIndex = &credential.MembershipGroups[gm.membershipGroupIndex].TreeIndex
|
||||
} else {
|
||||
@ -275,18 +275,15 @@ func (gm *DynamicGroupManager) persistCredentials() error {
|
||||
return errors.New("no credentials to persist")
|
||||
}
|
||||
|
||||
keystoreCred := keystore.MembershipCredentials{
|
||||
IdentityCredential: *gm.identityCredential,
|
||||
MembershipGroups: []keystore.MembershipGroup{{
|
||||
TreeIndex: *gm.membershipIndex,
|
||||
MembershipContract: keystore.MembershipContract{
|
||||
ChainId: fmt.Sprintf("0x%X", gm.chainId),
|
||||
Address: gm.membershipContractAddress.String(),
|
||||
},
|
||||
}},
|
||||
membershipGroup := keystore.MembershipGroup{
|
||||
TreeIndex: *gm.membershipIndex,
|
||||
MembershipContract: keystore.MembershipContract{
|
||||
ChainId: fmt.Sprintf("0x%X", gm.chainId),
|
||||
Address: gm.membershipContractAddress.String(),
|
||||
},
|
||||
}
|
||||
|
||||
err := keystore.AddMembershipCredentials(gm.keystorePath, []keystore.MembershipCredentials{keystoreCred}, gm.keystorePassword, RLNAppInfo, keystore.DefaultSeparator)
|
||||
_, _, err := keystore.AddMembershipCredentials(gm.keystorePath, gm.identityCredential, membershipGroup, gm.keystorePassword, RLNAppInfo, keystore.DefaultSeparator)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to persist credentials: %w", err)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/waku-org/go-zerokit-rln/rln"
|
||||
@ -27,8 +28,8 @@ type MembershipGroup struct {
|
||||
}
|
||||
|
||||
type MembershipCredentials struct {
|
||||
IdentityCredential rln.IdentityCredential `json:"identityCredential"`
|
||||
MembershipGroups []MembershipGroup `json:"membershipGroups"`
|
||||
IdentityCredential *rln.IdentityCredential `json:"identityCredential"`
|
||||
MembershipGroups []MembershipGroup `json:"membershipGroups"`
|
||||
}
|
||||
|
||||
type AppInfo struct {
|
||||
@ -51,7 +52,7 @@ type AppKeystoreCredential struct {
|
||||
const DefaultSeparator = "\n"
|
||||
|
||||
func (m MembershipCredentials) Equals(other MembershipCredentials) bool {
|
||||
if !rln.IdentityCredentialEquals(m.IdentityCredential, other.IdentityCredential) {
|
||||
if !rln.IdentityCredentialEquals(*m.IdentityCredential, *other.IdentityCredential) {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -218,80 +219,104 @@ func GetMembershipCredentials(logger *zap.Logger, credentialsPath string, passwo
|
||||
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 {
|
||||
// Adds a membership credential to the keystore matching the application, appIdentifier and version filters.
|
||||
func AddMembershipCredentials(path string, newIdentityCredential *rln.IdentityCredential, newMembershipGroup MembershipGroup, password string, appInfo AppInfo, separator string) (keystoreIndex int, membershipGroupIndex int, err error) {
|
||||
k, err := LoadAppKeystore(path, appInfo, DefaultSeparator)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, 0, 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.Crypto, 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] = AppKeystoreCredential{Crypto: 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)
|
||||
// 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 {
|
||||
credentialsBytes, err := keystore.DecryptDataV3(existingCredentials.Crypto, password)
|
||||
if err != nil {
|
||||
return err
|
||||
continue
|
||||
}
|
||||
|
||||
var credentials MembershipCredentials
|
||||
err = json.Unmarshal(credentialsBytes, &credentials)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if rln.IdentityCredentialEquals(*credentials.IdentityCredential, *newIdentityCredential) {
|
||||
// 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)
|
||||
allMembershipsMap := make(map[MembershipGroup]struct{})
|
||||
for _, m := range credentials.MembershipGroups {
|
||||
allMembershipsMap[m] = struct{}{}
|
||||
}
|
||||
allMembershipsMap[newMembershipGroup] = struct{}{}
|
||||
|
||||
// We sort membership groups, otherwise we will not have deterministic results in tests
|
||||
var allMemberships []MembershipGroup
|
||||
for k := range allMembershipsMap {
|
||||
allMemberships = append(allMemberships, k)
|
||||
}
|
||||
sort.Slice(allMemberships, func(i, j int) bool {
|
||||
return allMemberships[i].MembershipContract.Address < allMemberships[j].MembershipContract.Address
|
||||
})
|
||||
|
||||
// we define the updated credential with the updated membership sets
|
||||
updatedCredential := MembershipCredentials{
|
||||
IdentityCredential: newIdentityCredential,
|
||||
MembershipGroups: allMemberships,
|
||||
}
|
||||
|
||||
// we re-encrypt creating a new keyfile
|
||||
b, err := json.Marshal(updatedCredential)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
encryptedCredentials, err := keystore.EncryptDataV3(b, []byte(password), keystore.StandardScryptN, keystore.StandardScryptP)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
// we update the original credential field in keystoreCredentials
|
||||
k.Credentials[i] = AppKeystoreCredential{Crypto: encryptedCredentials}
|
||||
|
||||
found = true
|
||||
|
||||
// We setup the return values
|
||||
membershipGroupIndex = len(allMemberships)
|
||||
keystoreIndex = i
|
||||
for mIdx, mg := range updatedCredential.MembershipGroups {
|
||||
if mg.MembershipContract.Equals(newMembershipGroup.MembershipContract) {
|
||||
membershipGroupIndex = mIdx
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// We stop decrypting other credentials in the keystore
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found { // Not found
|
||||
newCredential := MembershipCredentials{
|
||||
IdentityCredential: newIdentityCredential,
|
||||
MembershipGroups: []MembershipGroup{newMembershipGroup},
|
||||
}
|
||||
|
||||
b, err := json.Marshal(newCredential)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
encryptedCredentials, err := keystore.EncryptDataV3(b, []byte(password), keystore.StandardScryptN, keystore.StandardScryptP)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
k.Credentials = append(k.Credentials, AppKeystoreCredential{Crypto: encryptedCredentials})
|
||||
|
||||
keystoreIndex = len(k.Credentials) - 1
|
||||
membershipGroupIndex = len(newCredential.MembershipGroups) - 1
|
||||
}
|
||||
|
||||
return save(k, path, separator)
|
||||
return keystoreIndex, membershipGroupIndex, save(k, path, separator)
|
||||
}
|
||||
|
||||
// Safely saves a Keystore's JsonNode to disk.
|
||||
|
Loading…
x
Reference in New Issue
Block a user