mirror of
https://github.com/lightninglabs/aperture.git
synced 2025-12-19 01:54:20 +01:00
171 lines
4.0 KiB
Go
171 lines
4.0 KiB
Go
package aperturedb
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
|
|
"github.com/lightninglabs/aperture/aperturedb/sqlc"
|
|
"github.com/lightningnetwork/lnd/clock"
|
|
"github.com/lightningnetwork/lnd/tor"
|
|
)
|
|
|
|
type (
|
|
NewOnionPrivateKey = sqlc.UpsertOnionParams
|
|
)
|
|
|
|
// OnionDB is an interface that defines the set of operations that can be
|
|
// executed against the onion database.
|
|
type OnionDB interface {
|
|
// UpsertOnion inserts a new onion private key into the database. If
|
|
// the onion private key already exists in the db this is a NOOP
|
|
// operation.
|
|
UpsertOnion(ctx context.Context, arg NewOnionPrivateKey) error
|
|
|
|
// SelectOnionPrivateKey selects the onion private key from the
|
|
// database.
|
|
SelectOnionPrivateKey(ctx context.Context) ([]byte, error)
|
|
|
|
// DeleteOnionPrivateKey deletes the onion private key from the
|
|
// database.
|
|
DeleteOnionPrivateKey(ctx context.Context) error
|
|
}
|
|
|
|
// OnionTxOptions defines the set of db txn options the OnionStore
|
|
// understands.
|
|
type OnionDBTxOptions struct {
|
|
// readOnly governs if a read only transaction is needed or not.
|
|
readOnly bool
|
|
}
|
|
|
|
// ReadOnly returns true if the transaction should be read only.
|
|
//
|
|
// NOTE: This implements the TxOptions
|
|
func (a *OnionDBTxOptions) ReadOnly() bool {
|
|
return a.readOnly
|
|
}
|
|
|
|
// NewOnionDBReadTx creates a new read transaction option set.
|
|
func NewOnionDBReadTx() OnionDBTxOptions {
|
|
return OnionDBTxOptions{
|
|
readOnly: true,
|
|
}
|
|
}
|
|
|
|
// BatchedOnionDB is a version of the OnionDB that's capable of batched
|
|
// database operations.
|
|
type BatchedOnionDB interface {
|
|
OnionDB
|
|
|
|
BatchedTx[OnionDB]
|
|
}
|
|
|
|
// OnionStore represents a storage backend.
|
|
type OnionStore struct {
|
|
db BatchedOnionDB
|
|
clock clock.Clock
|
|
}
|
|
|
|
// NewOnionStore creates a new OnionStore instance given a open BatchedOnionDB
|
|
// storage backend.
|
|
func NewOnionStore(db BatchedOnionDB) *OnionStore {
|
|
return &OnionStore{
|
|
db: db,
|
|
clock: clock.NewDefaultClock(),
|
|
}
|
|
}
|
|
|
|
// StorePrivateKey stores the private key according to the implementation of
|
|
// the OnionStore interface.
|
|
func (o *OnionStore) StorePrivateKey(privateKey []byte) error {
|
|
ctxt, cancel := context.WithTimeout(
|
|
context.Background(), DefaultStoreTimeout,
|
|
)
|
|
defer cancel()
|
|
|
|
var writeTxOpts OnionDBTxOptions
|
|
err := o.db.ExecTx(ctxt, &writeTxOpts, func(tx OnionDB) error {
|
|
// Only store the private key if it doesn't already exist.
|
|
dbPK, err := tx.SelectOnionPrivateKey(ctxt)
|
|
switch {
|
|
// If there is already a different private key stored in the
|
|
/// database, return an error.
|
|
case dbPK != nil && !bytes.Equal(dbPK, privateKey):
|
|
return fmt.Errorf("private key already exists")
|
|
|
|
case err != nil && err != sql.ErrNoRows:
|
|
return err
|
|
}
|
|
|
|
params := NewOnionPrivateKey{
|
|
PrivateKey: privateKey,
|
|
CreatedAt: o.clock.Now().UTC(),
|
|
}
|
|
|
|
return tx.UpsertOnion(ctxt, params)
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to store private key: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PrivateKey retrieves a stored private key. If it is not found, then
|
|
// ErrNoPrivateKey should be returned.
|
|
func (o *OnionStore) PrivateKey() ([]byte, error) {
|
|
ctxt, cancel := context.WithTimeout(
|
|
context.Background(), DefaultStoreTimeout,
|
|
)
|
|
defer cancel()
|
|
|
|
var (
|
|
privateKey []byte
|
|
)
|
|
|
|
var readTxOpts OnionDBTxOptions
|
|
err := o.db.ExecTx(ctxt, &readTxOpts, func(tx OnionDB) error {
|
|
row, err := o.db.SelectOnionPrivateKey(ctxt)
|
|
switch {
|
|
case err == sql.ErrNoRows:
|
|
return tor.ErrNoPrivateKey
|
|
|
|
case err != nil:
|
|
return err
|
|
}
|
|
|
|
privateKey = make([]byte, len(row))
|
|
copy(privateKey, row)
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve private key: %w",
|
|
err)
|
|
}
|
|
|
|
return privateKey, nil
|
|
}
|
|
|
|
// DeletePrivateKey securely removes the private key from the store.
|
|
func (o *OnionStore) DeletePrivateKey() error {
|
|
ctxt, cancel := context.WithTimeout(
|
|
context.Background(), DefaultStoreTimeout,
|
|
)
|
|
defer cancel()
|
|
|
|
var writeTxOpts OnionDBTxOptions
|
|
err := o.db.ExecTx(ctxt, &writeTxOpts, func(tx OnionDB) error {
|
|
return tx.DeleteOnionPrivateKey(ctxt)
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete private key: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|