mirror of https://github.com/status-im/go-waku.git
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"
|
cli "github.com/urfave/cli/v2"
|
||||||
"github.com/urfave/cli/v2/altsrc"
|
"github.com/urfave/cli/v2/altsrc"
|
||||||
"github.com/waku-org/go-waku/cmd/waku/keygen"
|
"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"
|
"github.com/waku-org/go-waku/waku/v2/node"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -114,6 +115,7 @@ func main() {
|
||||||
},
|
},
|
||||||
Commands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
&keygen.Command,
|
&keygen.Command,
|
||||||
|
&rlngenerate.Command,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
},
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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,
|
||||||
|
},
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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 len(credentials) != 0 {
|
||||||
if int(gm.keystoreIndex) <= len(credentials)-1 {
|
if int(gm.keystoreIndex) <= len(credentials)-1 {
|
||||||
credential := credentials[gm.keystoreIndex]
|
credential := credentials[gm.keystoreIndex]
|
||||||
gm.identityCredential = &credential.IdentityCredential
|
gm.identityCredential = credential.IdentityCredential
|
||||||
if int(gm.membershipGroupIndex) <= len(credential.MembershipGroups)-1 {
|
if int(gm.membershipGroupIndex) <= len(credential.MembershipGroups)-1 {
|
||||||
gm.membershipIndex = &credential.MembershipGroups[gm.membershipGroupIndex].TreeIndex
|
gm.membershipIndex = &credential.MembershipGroups[gm.membershipGroupIndex].TreeIndex
|
||||||
} else {
|
} else {
|
||||||
|
@ -275,18 +275,15 @@ func (gm *DynamicGroupManager) persistCredentials() error {
|
||||||
return errors.New("no credentials to persist")
|
return errors.New("no credentials to persist")
|
||||||
}
|
}
|
||||||
|
|
||||||
keystoreCred := keystore.MembershipCredentials{
|
membershipGroup := keystore.MembershipGroup{
|
||||||
IdentityCredential: *gm.identityCredential,
|
TreeIndex: *gm.membershipIndex,
|
||||||
MembershipGroups: []keystore.MembershipGroup{{
|
MembershipContract: keystore.MembershipContract{
|
||||||
TreeIndex: *gm.membershipIndex,
|
ChainId: fmt.Sprintf("0x%X", gm.chainId),
|
||||||
MembershipContract: keystore.MembershipContract{
|
Address: gm.membershipContractAddress.String(),
|
||||||
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to persist credentials: %w", err)
|
return fmt.Errorf("failed to persist credentials: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"github.com/waku-org/go-zerokit-rln/rln"
|
"github.com/waku-org/go-zerokit-rln/rln"
|
||||||
|
@ -27,8 +28,8 @@ type MembershipGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type MembershipCredentials struct {
|
type MembershipCredentials struct {
|
||||||
IdentityCredential rln.IdentityCredential `json:"identityCredential"`
|
IdentityCredential *rln.IdentityCredential `json:"identityCredential"`
|
||||||
MembershipGroups []MembershipGroup `json:"membershipGroups"`
|
MembershipGroups []MembershipGroup `json:"membershipGroups"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppInfo struct {
|
type AppInfo struct {
|
||||||
|
@ -51,7 +52,7 @@ type AppKeystoreCredential struct {
|
||||||
const DefaultSeparator = "\n"
|
const DefaultSeparator = "\n"
|
||||||
|
|
||||||
func (m MembershipCredentials) Equals(other MembershipCredentials) bool {
|
func (m MembershipCredentials) Equals(other MembershipCredentials) bool {
|
||||||
if !rln.IdentityCredentialEquals(m.IdentityCredential, other.IdentityCredential) {
|
if !rln.IdentityCredentialEquals(*m.IdentityCredential, *other.IdentityCredential) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,80 +219,104 @@ func GetMembershipCredentials(logger *zap.Logger, credentialsPath string, passwo
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a sequence of membership credential to the keystore matching the application, appIdentifier and version filters.
|
// Adds a 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 {
|
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)
|
k, err := LoadAppKeystore(path, appInfo, DefaultSeparator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var credentialsToAdd []MembershipCredentials
|
// A flag to tell us if the keystore contains a credential associated to the input identity credential, i.e. membershipCredential
|
||||||
for _, newCredential := range credentials {
|
found := false
|
||||||
// A flag to tell us if the keystore contains a credential associated to the input identity credential, i.e. membershipCredential
|
for i, existingCredentials := range k.Credentials {
|
||||||
found := -1
|
credentialsBytes, err := keystore.DecryptDataV3(existingCredentials.Crypto, password)
|
||||||
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)
|
|
||||||
if err != nil {
|
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)
|
encryptedCredentials, err := keystore.EncryptDataV3(b, []byte(password), keystore.StandardScryptN, keystore.StandardScryptP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
k.Credentials = append(k.Credentials, AppKeystoreCredential{Crypto: encryptedCredentials})
|
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.
|
// Safely saves a Keystore's JsonNode to disk.
|
||||||
|
|
Loading…
Reference in New Issue