479 lines
9.9 KiB
Go
479 lines
9.9 KiB
Go
package accounts
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
)
|
|
|
|
var (
|
|
errKeycardDbTransactionIsNil = errors.New("keycard: database transaction is nil")
|
|
errCannotAddKeycardForUnknownKeypair = errors.New("keycard: cannot add keycard for an unknown keyapir")
|
|
ErrNoKeycardForPassedKeycardUID = errors.New("keycard: no keycard for the passed keycard uid")
|
|
)
|
|
|
|
type Keycard struct {
|
|
KeycardUID string `json:"keycard-uid"`
|
|
KeycardName string `json:"keycard-name"`
|
|
KeycardLocked bool `json:"keycard-locked"`
|
|
AccountsAddresses []types.Address `json:"accounts-addresses"`
|
|
KeyUID string `json:"key-uid"`
|
|
Position uint64
|
|
}
|
|
|
|
func (kp *Keycard) ToSyncKeycard() *protobuf.SyncKeycard {
|
|
kc := &protobuf.SyncKeycard{
|
|
Uid: kp.KeycardUID,
|
|
Name: kp.KeycardName,
|
|
Locked: kp.KeycardLocked,
|
|
KeyUid: kp.KeyUID,
|
|
Position: kp.Position,
|
|
}
|
|
|
|
for _, addr := range kp.AccountsAddresses {
|
|
kc.Addresses = append(kc.Addresses, addr.Bytes())
|
|
}
|
|
|
|
return kc
|
|
}
|
|
|
|
func (kp *Keycard) FromSyncKeycard(kc *protobuf.SyncKeycard) {
|
|
kp.KeycardUID = kc.Uid
|
|
kp.KeycardName = kc.Name
|
|
kp.KeycardLocked = kc.Locked
|
|
kp.KeyUID = kc.KeyUid
|
|
kp.Position = kc.Position
|
|
|
|
for _, addr := range kc.Addresses {
|
|
kp.AccountsAddresses = append(kp.AccountsAddresses, types.BytesToAddress(addr))
|
|
}
|
|
}
|
|
|
|
func containsAddress(addresses []types.Address, address types.Address) bool {
|
|
for _, addr := range addresses {
|
|
if addr == address {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (db *Database) processResult(rows *sql.Rows) ([]*Keycard, error) {
|
|
keycards := []*Keycard{}
|
|
for rows.Next() {
|
|
keycard := &Keycard{}
|
|
var accAddress sql.NullString
|
|
err := rows.Scan(&keycard.KeycardUID, &keycard.KeycardName, &keycard.KeycardLocked, &accAddress, &keycard.KeyUID,
|
|
&keycard.Position)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
addr := types.Address{}
|
|
if accAddress.Valid {
|
|
addr = types.BytesToAddress([]byte(accAddress.String))
|
|
}
|
|
|
|
foundAtIndex := -1
|
|
for i := range keycards {
|
|
if keycards[i].KeycardUID == keycard.KeycardUID {
|
|
foundAtIndex = i
|
|
break
|
|
}
|
|
}
|
|
if foundAtIndex == -1 {
|
|
keycard.AccountsAddresses = append(keycard.AccountsAddresses, addr)
|
|
keycards = append(keycards, keycard)
|
|
} else {
|
|
if containsAddress(keycards[foundAtIndex].AccountsAddresses, addr) {
|
|
continue
|
|
}
|
|
keycards[foundAtIndex].AccountsAddresses = append(keycards[foundAtIndex].AccountsAddresses, addr)
|
|
}
|
|
}
|
|
|
|
return keycards, nil
|
|
}
|
|
|
|
func (db *Database) getKeycards(tx *sql.Tx, keyUID string, keycardUID string) ([]*Keycard, error) {
|
|
query := `
|
|
SELECT
|
|
kc.keycard_uid,
|
|
kc.keycard_name,
|
|
kc.keycard_locked,
|
|
ka.account_address,
|
|
kc.key_uid,
|
|
kc.position
|
|
FROM
|
|
keycards AS kc
|
|
LEFT JOIN
|
|
keycards_accounts AS ka
|
|
ON
|
|
kc.keycard_uid = ka.keycard_uid
|
|
LEFT JOIN
|
|
keypairs_accounts AS kpa
|
|
ON
|
|
ka.account_address = kpa.address
|
|
%s
|
|
ORDER BY
|
|
kc.position, kpa.position`
|
|
|
|
var where string
|
|
var args []interface{}
|
|
|
|
if keyUID != "" {
|
|
where = "WHERE kc.key_uid = ?"
|
|
args = append(args, keyUID)
|
|
if keycardUID != "" {
|
|
where += " AND kc.keycard_uid = ?"
|
|
args = append(args, keycardUID)
|
|
}
|
|
} else if keycardUID != "" {
|
|
where = "WHERE kc.keycard_uid = ?"
|
|
args = append(args, keycardUID)
|
|
}
|
|
|
|
query = fmt.Sprintf(query, where)
|
|
|
|
var (
|
|
stmt *sql.Stmt
|
|
err error
|
|
)
|
|
if tx == nil {
|
|
stmt, err = db.db.Prepare(query)
|
|
} else {
|
|
stmt, err = tx.Prepare(query)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
rows, err := stmt.Query(args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
return db.processResult(rows)
|
|
}
|
|
|
|
func (db *Database) getKeycardByKeycardUID(tx *sql.Tx, keycardUID string) (*Keycard, error) {
|
|
keycards, err := db.getKeycards(tx, "", keycardUID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(keycards) == 0 {
|
|
return nil, ErrNoKeycardForPassedKeycardUID
|
|
}
|
|
|
|
return keycards[0], nil
|
|
}
|
|
|
|
func (db *Database) GetAllKnownKeycards() ([]*Keycard, error) {
|
|
return db.getKeycards(nil, "", "")
|
|
}
|
|
|
|
func (db *Database) GetKeycardsWithSameKeyUID(keyUID string) ([]*Keycard, error) {
|
|
return db.getKeycards(nil, keyUID, "")
|
|
}
|
|
|
|
func (db *Database) GetKeycardByKeycardUID(keycardUID string) (*Keycard, error) {
|
|
return db.getKeycardByKeycardUID(nil, keycardUID)
|
|
}
|
|
|
|
func (db *Database) saveOrUpdateKeycardAccounts(tx *sql.Tx, kcUID string, accountsAddresses []types.Address) (err error) {
|
|
if tx == nil {
|
|
return errKeycardDbTransactionIsNil
|
|
}
|
|
|
|
for i := range accountsAddresses {
|
|
addr := accountsAddresses[i]
|
|
|
|
_, err = tx.Exec(`
|
|
INSERT OR IGNORE INTO
|
|
keycards_accounts
|
|
(
|
|
keycard_uid,
|
|
account_address
|
|
)
|
|
VALUES
|
|
(?, ?);
|
|
`, kcUID, addr)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *Database) deleteKeycard(tx *sql.Tx, kcUID string) (err error) {
|
|
if tx == nil {
|
|
return errKeycardDbTransactionIsNil
|
|
}
|
|
|
|
delete, err := tx.Prepare(`
|
|
DELETE
|
|
FROM
|
|
keycards
|
|
WHERE
|
|
keycard_uid = ?
|
|
`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer delete.Close()
|
|
|
|
_, err = delete.Exec(kcUID)
|
|
|
|
return err
|
|
}
|
|
|
|
func (db *Database) deleteAllKeycardsWithKeyUID(tx *sql.Tx, keyUID string) (err error) {
|
|
if tx == nil {
|
|
return errKeycardDbTransactionIsNil
|
|
}
|
|
|
|
delete, err := tx.Prepare(`
|
|
DELETE
|
|
FROM
|
|
keycards
|
|
WHERE
|
|
key_uid = ?
|
|
`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer delete.Close()
|
|
|
|
_, err = delete.Exec(keyUID)
|
|
return err
|
|
}
|
|
|
|
func (db *Database) deleteKeycardAccounts(tx *sql.Tx, kcUID string, accountAddresses []types.Address) (err error) {
|
|
if tx == nil {
|
|
return errKeycardDbTransactionIsNil
|
|
}
|
|
|
|
inVector := strings.Repeat(",?", len(accountAddresses)-1)
|
|
//nolint: gosec
|
|
query := `
|
|
DELETE
|
|
FROM
|
|
keycards_accounts
|
|
WHERE
|
|
keycard_uid = ?
|
|
AND
|
|
account_address IN (?` + inVector + `)`
|
|
|
|
delete, err := tx.Prepare(query)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer delete.Close()
|
|
|
|
args := make([]interface{}, len(accountAddresses)+1)
|
|
args[0] = kcUID
|
|
for i, addr := range accountAddresses {
|
|
args[i+1] = addr
|
|
}
|
|
|
|
_, err = delete.Exec(args...)
|
|
|
|
return err
|
|
}
|
|
|
|
func (db *Database) SaveOrUpdateKeycard(keycard Keycard, clock uint64, updateKeypairClock bool) error {
|
|
tx, err := db.db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err == nil {
|
|
err = tx.Commit()
|
|
return
|
|
}
|
|
_ = tx.Rollback()
|
|
}()
|
|
|
|
relatedKeypairExists, err := db.keypairExists(tx, keycard.KeyUID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !relatedKeypairExists {
|
|
return errCannotAddKeycardForUnknownKeypair
|
|
}
|
|
|
|
_, err = tx.Exec(`
|
|
INSERT OR IGNORE INTO
|
|
keycards
|
|
(
|
|
keycard_uid,
|
|
keycard_name,
|
|
key_uid
|
|
)
|
|
VALUES
|
|
(?, ?, ?);
|
|
|
|
UPDATE
|
|
keycards
|
|
SET
|
|
keycard_name = ?,
|
|
keycard_locked = ?,
|
|
position = ?
|
|
WHERE
|
|
keycard_uid = ?;
|
|
`, keycard.KeycardUID, keycard.KeycardName, keycard.KeyUID,
|
|
keycard.KeycardName, keycard.KeycardLocked, keycard.Position, keycard.KeycardUID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = db.saveOrUpdateKeycardAccounts(tx, keycard.KeycardUID, keycard.AccountsAddresses)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if updateKeypairClock {
|
|
return db.updateKeypairClock(tx, keycard.KeyUID, clock)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *Database) execKeycardUpdateQuery(kcUID string, clock uint64, field string, value interface{}) (err error) {
|
|
tx, err := db.db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err == nil {
|
|
err = tx.Commit()
|
|
return
|
|
}
|
|
_ = tx.Rollback()
|
|
}()
|
|
|
|
keycard, err := db.getKeycardByKeycardUID(tx, kcUID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sql := fmt.Sprintf(`UPDATE keycards SET %s = ? WHERE keycard_uid = ?`, field) // nolint: gosec
|
|
_, err = tx.Exec(sql, value, kcUID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return db.updateKeypairClock(tx, keycard.KeyUID, clock)
|
|
}
|
|
|
|
func (db *Database) KeycardLocked(kcUID string, clock uint64) (err error) {
|
|
return db.execKeycardUpdateQuery(kcUID, clock, "keycard_locked", true)
|
|
}
|
|
|
|
func (db *Database) KeycardUnlocked(kcUID string, clock uint64) (err error) {
|
|
return db.execKeycardUpdateQuery(kcUID, clock, "keycard_locked", false)
|
|
}
|
|
|
|
func (db *Database) UpdateKeycardUID(oldKcUID string, newKcUID string, clock uint64) (err error) {
|
|
return db.execKeycardUpdateQuery(oldKcUID, clock, "keycard_uid", newKcUID)
|
|
}
|
|
|
|
func (db *Database) SetKeycardName(kcUID string, kpName string, clock uint64) (err error) {
|
|
return db.execKeycardUpdateQuery(kcUID, clock, "keycard_name", kpName)
|
|
}
|
|
|
|
func (db *Database) DeleteKeycardAccounts(kcUID string, accountAddresses []types.Address, clock uint64) (err error) {
|
|
tx, err := db.db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err == nil {
|
|
err = tx.Commit()
|
|
return
|
|
}
|
|
_ = tx.Rollback()
|
|
}()
|
|
|
|
keycard, err := db.getKeycardByKeycardUID(tx, kcUID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = db.deleteKeycardAccounts(tx, kcUID, accountAddresses)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return db.updateKeypairClock(tx, keycard.KeyUID, clock)
|
|
}
|
|
|
|
func (db *Database) DeleteKeycard(kcUID string, clock uint64) (err error) {
|
|
tx, err := db.db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err == nil {
|
|
err = tx.Commit()
|
|
return
|
|
}
|
|
_ = tx.Rollback()
|
|
}()
|
|
|
|
keycard, err := db.getKeycardByKeycardUID(tx, kcUID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = db.deleteKeycard(tx, kcUID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return db.updateKeypairClock(tx, keycard.KeyUID, clock)
|
|
}
|
|
|
|
func (db *Database) DeleteAllKeycardsWithKeyUID(keyUID string, clock uint64) (err error) {
|
|
tx, err := db.db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err == nil {
|
|
err = tx.Commit()
|
|
return
|
|
}
|
|
_ = tx.Rollback()
|
|
}()
|
|
|
|
err = db.deleteAllKeycardsWithKeyUID(tx, keyUID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return db.updateKeypairClock(tx, keyUID, clock)
|
|
}
|
|
|
|
func (db *Database) GetPositionForNextNewKeycard() (uint64, error) {
|
|
var pos sql.NullInt64
|
|
err := db.db.QueryRow("SELECT MAX(position) FROM keycards").Scan(&pos)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if pos.Valid {
|
|
return uint64(pos.Int64) + 1, nil
|
|
}
|
|
return 0, nil
|
|
}
|