mirror of https://github.com/status-im/go-waku.git
234 lines
5.9 KiB
Go
234 lines
5.9 KiB
Go
package keystore
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
|
"github.com/waku-org/go-waku/waku/v2/hash"
|
|
"github.com/waku-org/go-zerokit-rln/rln"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// New creates a new instance of a rln credentials keystore
|
|
func New(path string, appInfo AppInfo, logger *zap.Logger) (*AppKeystore, error) {
|
|
logger = logger.Named("rln-keystore")
|
|
|
|
_, 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, defaultSeparator)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
src, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, keystoreBytes := range bytes.Split(src, []byte(defaultSeparator)) {
|
|
if len(keystoreBytes) == 0 {
|
|
continue
|
|
}
|
|
|
|
keystore := new(AppKeystore)
|
|
err := json.Unmarshal(keystoreBytes, keystore)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
keystore.logger = logger
|
|
keystore.path = path
|
|
|
|
if keystore.Credentials == nil {
|
|
keystore.Credentials = map[Key]appKeystoreCredential{}
|
|
}
|
|
|
|
if keystore.AppIdentifier == appInfo.AppIdentifier && keystore.Application == appInfo.Application && keystore.Version == appInfo.Version {
|
|
return keystore, nil
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("no keystore found")
|
|
}
|
|
|
|
func getKey(treeIndex rln.MembershipIndex, filterMembershipContract MembershipContractInfo) (Key, error) {
|
|
keyStr := fmt.Sprintf("%s%s%d", filterMembershipContract.ChainID, filterMembershipContract.Address, treeIndex)
|
|
hash := hash.SHA256([]byte(keyStr))
|
|
return Key(strings.ToUpper(hex.EncodeToString(hash))), nil
|
|
}
|
|
|
|
// GetMembershipCredentials decrypts and retrieves membership credentials from the keystore applying filters
|
|
func (k *AppKeystore) GetMembershipCredentials(keystorePassword string, index *rln.MembershipIndex, filterMembershipContract MembershipContractInfo) (*MembershipCredentials, error) {
|
|
// If there is only one, and index to laod nil, assume 0,
|
|
// if there is more than one, complain if the index to load is nil
|
|
|
|
var key Key
|
|
var err error
|
|
|
|
if len(k.Credentials) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
if len(k.Credentials) == 1 {
|
|
// Only one credential, the tree index does not matter.
|
|
k.logger.Warn("automatically loading the only credential found on the keystore")
|
|
for k := range k.Credentials {
|
|
key = k // Obtain the first c
|
|
break
|
|
}
|
|
} else {
|
|
treeIndex := uint(0)
|
|
if index != nil {
|
|
treeIndex = *index
|
|
} else {
|
|
return nil, errors.New("the index of the onchain commitment to use was not specified")
|
|
}
|
|
|
|
key, err = getKey(treeIndex, filterMembershipContract)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
credential, ok := k.Credentials[key]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
credentialsBytes, err := keystore.DecryptDataV3(credential.Crypto, keystorePassword)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
credentials := new(MembershipCredentials)
|
|
err = json.Unmarshal(credentialsBytes, credentials)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return credentials, nil
|
|
}
|
|
|
|
// AddMembershipCredentials inserts a membership credential to the keystore matching the application, appIdentifier and version filters.
|
|
func (k *AppKeystore) AddMembershipCredentials(newCredential MembershipCredentials, password string) error {
|
|
credentials, err := k.GetMembershipCredentials(password, &newCredential.TreeIndex, newCredential.MembershipContractInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
key, err := getKey(newCredential.TreeIndex, newCredential.MembershipContractInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if credentials != nil && credentials.TreeIndex == newCredential.TreeIndex && credentials.MembershipContractInfo.Equals(newCredential.MembershipContractInfo) {
|
|
return errors.New("credential already present")
|
|
}
|
|
|
|
b, err := json.Marshal(newCredential)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
encryptedCredentials, err := keystore.EncryptDataV3(b, []byte(password), keystore.StandardScryptN, keystore.StandardScryptP)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
k.Credentials[key] = appKeystoreCredential{Crypto: encryptedCredentials}
|
|
|
|
return save(k, k.path)
|
|
}
|
|
|
|
func createAppKeystore(path string, appInfo AppInfo, separator string) error {
|
|
if separator == "" {
|
|
separator = defaultSeparator
|
|
}
|
|
|
|
keystore := AppKeystore{
|
|
Application: appInfo.Application,
|
|
AppIdentifier: appInfo.AppIdentifier,
|
|
Version: appInfo.Version,
|
|
Credentials: make(map[Key]appKeystoreCredential),
|
|
}
|
|
|
|
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 os.WriteFile(path, buffer.Bytes(), 0600)
|
|
}
|
|
|
|
// 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")
|
|
if restoreErr != nil {
|
|
return fmt.Errorf("could not restore backup file: %w", restoreErr)
|
|
}
|
|
return err
|
|
}
|
|
|
|
err = os.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
|
|
}
|
|
|
|
// The write went fine, so we can remove the backup keystore
|
|
_, err = os.Stat(path + ".bkp")
|
|
if err == nil {
|
|
err := os.Remove(path + ".bkp")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|