2023-04-05 19:44:46 +00:00
package keystore
import (
"bytes"
2023-08-24 18:42:50 +00:00
"encoding/hex"
2023-04-05 19:44:46 +00:00
"encoding/json"
"errors"
"fmt"
"os"
2023-08-24 18:42:50 +00:00
"strings"
2023-04-05 19:44:46 +00:00
"github.com/ethereum/go-ethereum/accounts/keystore"
2023-08-24 18:42:50 +00:00
"github.com/waku-org/go-waku/waku/v2/hash"
2023-04-05 19:44:46 +00:00
"github.com/waku-org/go-zerokit-rln/rln"
"go.uber.org/zap"
)
2023-08-24 18:42:50 +00:00
// DefaultCredentialsFilename is the suggested default filename for the rln credentials keystore
const DefaultCredentialsFilename = "./rlnKeystore.json"
2023-04-05 19:44:46 +00:00
2023-08-24 18:42:50 +00:00
// DefaultCredentialsPassword is the suggested default password for the rln credentials store
2023-08-23 20:50:25 +00:00
const DefaultCredentialsPassword = "password"
2023-04-05 19:44:46 +00:00
2023-08-23 20:50:25 +00:00
// New creates a new instance of a rln credentials keystore
2023-08-24 18:42:50 +00:00
func New ( path string , appInfo AppInfo , logger * zap . Logger ) ( * AppKeystore , error ) {
2023-08-23 20:50:25 +00:00
logger = logger . Named ( "rln-keystore" )
2023-04-05 19:44:46 +00:00
2023-08-23 20:50:25 +00:00
if path == "" {
logger . Warn ( "keystore: no credentials path set, using default path" , zap . String ( "path" , DefaultCredentialsFilename ) )
path = DefaultCredentialsFilename
2023-04-05 19:44:46 +00:00
}
_ , 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
2023-08-23 20:50:25 +00:00
err = createAppKeystore ( path , appInfo , defaultSeparator )
2023-04-05 19:44:46 +00:00
if err != nil {
2023-08-23 20:50:25 +00:00
return nil , err
2023-04-05 19:44:46 +00:00
}
} else {
2023-08-23 20:50:25 +00:00
return nil , err
2023-04-05 19:44:46 +00:00
}
}
src , err := os . ReadFile ( path )
if err != nil {
2023-08-23 20:50:25 +00:00
return nil , err
2023-04-05 19:44:46 +00:00
}
2023-08-23 20:50:25 +00:00
for _ , keystoreBytes := range bytes . Split ( src , [ ] byte ( defaultSeparator ) ) {
2023-04-05 19:44:46 +00:00
if len ( keystoreBytes ) == 0 {
continue
}
2023-08-23 20:50:25 +00:00
keystore := new ( AppKeystore )
err := json . Unmarshal ( keystoreBytes , keystore )
2023-04-05 19:44:46 +00:00
if err != nil {
continue
}
2023-08-24 18:42:50 +00:00
keystore . logger = logger
keystore . path = path
if keystore . Credentials == nil {
keystore . Credentials = map [ Key ] appKeystoreCredential { }
}
2023-04-05 19:44:46 +00:00
if keystore . AppIdentifier == appInfo . AppIdentifier && keystore . Application == appInfo . Application && keystore . Version == appInfo . Version {
return keystore , nil
}
}
2023-08-23 20:50:25 +00:00
return nil , errors . New ( "no keystore found" )
2023-04-05 19:44:46 +00:00
}
2023-08-24 18:42:50 +00:00
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
}
2023-08-23 20:50:25 +00:00
// GetMembershipCredentials decrypts and retrieves membership credentials from the keystore applying filters
2023-09-04 21:44:41 +00:00
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 ) == 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
}
2023-04-05 19:44:46 +00:00
}
2023-08-24 18:42:50 +00:00
credential , ok := k . Credentials [ key ]
if ! ok {
return nil , nil
}
2023-04-05 19:44:46 +00:00
2023-08-24 18:42:50 +00:00
credentialsBytes , err := keystore . DecryptDataV3 ( credential . Crypto , keystorePassword )
if err != nil {
return nil , err
}
2023-04-05 19:44:46 +00:00
2023-08-24 18:42:50 +00:00
credentials := new ( MembershipCredentials )
err = json . Unmarshal ( credentialsBytes , credentials )
if err != nil {
return nil , err
2023-04-05 19:44:46 +00:00
}
2023-08-24 18:42:50 +00:00
return credentials , nil
2023-04-05 19:44:46 +00:00
}
2023-08-21 19:10:32 +00:00
// AddMembershipCredentials inserts a membership credential to the keystore matching the application, appIdentifier and version filters.
2023-08-24 18:42:50 +00:00
func ( k * AppKeystore ) AddMembershipCredentials ( newCredential MembershipCredentials , password string ) error {
2023-09-04 21:44:41 +00:00
credentials , err := k . GetMembershipCredentials ( password , & newCredential . TreeIndex , newCredential . MembershipContractInfo )
2023-08-24 18:42:50 +00:00
if err != nil {
return err
2023-04-05 19:44:46 +00:00
}
2023-08-24 18:42:50 +00:00
key , err := getKey ( newCredential . TreeIndex , newCredential . MembershipContractInfo )
if err != nil {
return err
}
2023-04-05 19:44:46 +00:00
2023-09-04 21:44:41 +00:00
if credentials != nil && credentials . TreeIndex == newCredential . TreeIndex && credentials . MembershipContractInfo . Equals ( newCredential . MembershipContractInfo ) {
2023-08-24 18:42:50 +00:00
return errors . New ( "credential already present" )
}
2023-04-05 19:44:46 +00:00
2023-08-24 18:42:50 +00:00
b , err := json . Marshal ( newCredential )
if err != nil {
return err
}
2023-08-18 21:24:04 +00:00
2023-08-24 18:42:50 +00:00
encryptedCredentials , err := keystore . EncryptDataV3 ( b , [ ] byte ( password ) , keystore . StandardScryptN , keystore . StandardScryptP )
if err != nil {
return err
2023-04-05 19:44:46 +00:00
}
2023-08-24 18:42:50 +00:00
k . Credentials [ key ] = appKeystoreCredential { Crypto : encryptedCredentials }
return save ( k , k . path )
2023-08-23 20:50:25 +00:00
}
func createAppKeystore ( path string , appInfo AppInfo , separator string ) error {
if separator == "" {
separator = defaultSeparator
}
keystore := AppKeystore {
Application : appInfo . Application ,
AppIdentifier : appInfo . AppIdentifier ,
Version : appInfo . Version ,
2023-08-24 18:42:50 +00:00
Credentials : make ( map [ Key ] appKeystoreCredential ) ,
2023-08-23 20:50:25 +00:00
}
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 )
}
2023-04-05 19:44:46 +00:00
// 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
2023-08-23 20:50:25 +00:00
func save ( keystore * AppKeystore , path string ) error {
2023-04-05 19:44:46 +00:00
// 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
}
2023-08-23 20:50:25 +00:00
b = append ( b , [ ] byte ( defaultSeparator ) ... )
2023-04-05 19:44:46 +00:00
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
}
2023-08-23 20:50:25 +00:00
err = os . WriteFile ( path , buffer . Bytes ( ) , 0600 )
2023-04-05 19:44:46 +00:00
if err != nil {
restoreErr := os . Rename ( path , path + ".bkp" )
if restoreErr != nil {
return fmt . Errorf ( "could not restore backup file: %w" , restoreErr )
}
return err
}
2023-04-10 15:20:07 +00:00
// 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
}
}
2023-04-05 19:44:46 +00:00
return nil
}