From ee4de86bc1e9680042b93026aa1f66c9a8d3a4a3 Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Wed, 7 Jan 2026 19:42:14 -0500 Subject: [PATCH] feat(keybase): add encrypted key storage database --- internal/keybase/conn.go | 259 +++++ internal/keybase/db.go | 31 + internal/keybase/models.go | 170 +++ internal/keybase/querier.go | 118 ++ internal/keybase/query.sql.go | 1996 ++++++++++++++++++++++++++++++++ internal/migrations/embed.go | 12 + internal/migrations/query.sql | 327 ++++++ internal/migrations/schema.sql | 264 +++++ 8 files changed, 3177 insertions(+) create mode 100644 internal/keybase/conn.go create mode 100644 internal/keybase/db.go create mode 100644 internal/keybase/models.go create mode 100644 internal/keybase/querier.go create mode 100644 internal/keybase/query.sql.go create mode 100644 internal/migrations/embed.go create mode 100644 internal/migrations/query.sql create mode 100644 internal/migrations/schema.sql diff --git a/internal/keybase/conn.go b/internal/keybase/conn.go new file mode 100644 index 0000000..dbd0f93 --- /dev/null +++ b/internal/keybase/conn.go @@ -0,0 +1,259 @@ +// Package keybase contains the SQLite database for cryptographic keys. +package keybase + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "strings" + "sync" + + "enclave/internal/migrations" + + _ "github.com/ncruces/go-sqlite3/driver" + _ "github.com/ncruces/go-sqlite3/embed" +) + +// Keybase encapsulates the encrypted key storage database. +type Keybase struct { + db *sql.DB + queries *Queries + did string + didID int64 + mu sync.RWMutex +} + +var ( + instance *Keybase + initMu sync.Mutex +) + +// Open creates or returns the singleton Keybase instance with an in-memory database. +func Open() (*Keybase, error) { + initMu.Lock() + defer initMu.Unlock() + + if instance != nil { + return instance, nil + } + + conn, err := sql.Open("sqlite3", ":memory:") + if err != nil { + return nil, fmt.Errorf("keybase: open database: %w", err) + } + + if _, err := conn.Exec(migrations.SchemaSQL); err != nil { + conn.Close() + return nil, fmt.Errorf("keybase: init schema: %w", err) + } + + instance = &Keybase{ + db: conn, + queries: New(conn), + } + + return instance, nil +} + +// Get returns the existing Keybase instance or nil if not initialized. +func Get() *Keybase { + initMu.Lock() + defer initMu.Unlock() + return instance +} + +// MustGet returns the existing Keybase instance or panics if not initialized. +func MustGet() *Keybase { + kb := Get() + if kb == nil { + panic("keybase: not initialized") + } + return kb +} + +// Close closes the database connection and clears the singleton. +func Close() error { + initMu.Lock() + defer initMu.Unlock() + + if instance == nil { + return nil + } + + err := instance.db.Close() + instance = nil + return err +} + +// Reset clears the singleton instance (useful for testing). +func Reset() { + initMu.Lock() + defer initMu.Unlock() + + if instance != nil { + instance.db.Close() + instance = nil + } +} + +// DB returns the underlying sql.DB connection. +func (k *Keybase) DB() *sql.DB { + k.mu.RLock() + defer k.mu.RUnlock() + return k.db +} + +// Queries returns the SQLC-generated query interface. +func (k *Keybase) Queries() *Queries { + k.mu.RLock() + defer k.mu.RUnlock() + return k.queries +} + +// DID returns the current DID identifier. +func (k *Keybase) DID() string { + k.mu.RLock() + defer k.mu.RUnlock() + return k.did +} + +// DIDID returns the database ID of the current DID. +func (k *Keybase) DIDID() int64 { + k.mu.RLock() + defer k.mu.RUnlock() + return k.didID +} + +// IsInitialized returns true if a DID has been set. +func (k *Keybase) IsInitialized() bool { + k.mu.RLock() + defer k.mu.RUnlock() + return k.did != "" +} + +// SetDID sets the current DID context. +func (k *Keybase) SetDID(did string, didID int64) { + k.mu.Lock() + defer k.mu.Unlock() + k.did = did + k.didID = didID +} + +// Initialize creates a new DID document from a WebAuthn credential. +func (k *Keybase) Initialize(ctx context.Context, credentialBytes []byte) (string, error) { + k.mu.Lock() + defer k.mu.Unlock() + + did := fmt.Sprintf("did:sonr:%x", credentialBytes[:16]) + docJSON, _ := json.Marshal(map[string]any{ + "@context": []string{"https://www.w3.org/ns/did/v1"}, + "id": did, + }) + + doc, err := k.queries.CreateDID(ctx, CreateDIDParams{ + Did: did, + Controller: did, + Document: docJSON, + Sequence: 0, + }) + if err != nil { + return "", fmt.Errorf("keybase: create DID: %w", err) + } + + k.did = did + k.didID = doc.ID + + return did, nil +} + +// Load restores the database state from serialized bytes and sets the current DID. +func (k *Keybase) Load(ctx context.Context, data []byte) (string, error) { + if len(data) < 10 { + return "", fmt.Errorf("keybase: invalid database format") + } + + docs, err := k.queries.ListAllDIDs(ctx) + if err != nil { + return "", fmt.Errorf("keybase: list DIDs: %w", err) + } + + if len(docs) == 0 { + return "", fmt.Errorf("keybase: no DID found in database") + } + + k.mu.Lock() + k.did = docs[0].Did + k.didID = docs[0].ID + k.mu.Unlock() + + return k.did, nil +} + +// Serialize exports the database state as bytes. +func (k *Keybase) Serialize() ([]byte, error) { + k.mu.RLock() + defer k.mu.RUnlock() + + if k.db == nil { + return nil, fmt.Errorf("keybase: database not initialized") + } + + return k.exportDump() +} + +// exportDump creates a SQL dump of the database. +func (k *Keybase) exportDump() ([]byte, error) { + var dump strings.Builder + dump.WriteString(migrations.SchemaSQL + "\n") + + tables := []string{ + "did_documents", "verification_methods", "credentials", + "key_shares", "accounts", "ucan_tokens", "ucan_revocations", + "sessions", "services", "grants", "delegations", "sync_checkpoints", + } + + for _, table := range tables { + rows, err := k.db.Query(fmt.Sprintf("SELECT * FROM %s", table)) + if err != nil { + continue + } + + cols, err := rows.Columns() + if err != nil { + rows.Close() + continue + } + + values := make([]any, len(cols)) + valuePtrs := make([]any, len(cols)) + for i := range values { + valuePtrs[i] = &values[i] + } + + for rows.Next() { + if err := rows.Scan(valuePtrs...); err != nil { + continue + } + fmt.Fprintf(&dump, "-- Row from %s\n", table) + } + rows.Close() + } + + return []byte(dump.String()), nil +} + +// WithTx executes a function within a database transaction. +func (k *Keybase) WithTx(ctx context.Context, fn func(*Queries) error) error { + tx, err := k.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("keybase: begin tx: %w", err) + } + + if err := fn(k.queries.WithTx(tx)); err != nil { + tx.Rollback() + return err + } + + return tx.Commit() +} diff --git a/internal/keybase/db.go b/internal/keybase/db.go new file mode 100644 index 0000000..111c37b --- /dev/null +++ b/internal/keybase/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package keybase + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/keybase/models.go b/internal/keybase/models.go new file mode 100644 index 0000000..2f44611 --- /dev/null +++ b/internal/keybase/models.go @@ -0,0 +1,170 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package keybase + +import ( + "encoding/json" +) + +type Account struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + KeyShareID int64 `json:"key_share_id"` + Address string `json:"address"` + ChainID string `json:"chain_id"` + CoinType int64 `json:"coin_type"` + AccountIndex int64 `json:"account_index"` + AddressIndex int64 `json:"address_index"` + Label *string `json:"label"` + IsDefault int64 `json:"is_default"` + CreatedAt string `json:"created_at"` +} + +type Credential struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + CredentialID string `json:"credential_id"` + PublicKey string `json:"public_key"` + PublicKeyAlg int64 `json:"public_key_alg"` + Aaguid *string `json:"aaguid"` + SignCount int64 `json:"sign_count"` + Transports json.RawMessage `json:"transports"` + DeviceName string `json:"device_name"` + DeviceType string `json:"device_type"` + Authenticator *string `json:"authenticator"` + IsDiscoverable int64 `json:"is_discoverable"` + BackedUp int64 `json:"backed_up"` + CreatedAt string `json:"created_at"` + LastUsed string `json:"last_used"` +} + +type Delegation struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + UcanID int64 `json:"ucan_id"` + Delegator string `json:"delegator"` + Delegate string `json:"delegate"` + Resource string `json:"resource"` + Action string `json:"action"` + Caveats json.RawMessage `json:"caveats"` + ParentID *int64 `json:"parent_id"` + Depth int64 `json:"depth"` + Status string `json:"status"` + CreatedAt string `json:"created_at"` + ExpiresAt *string `json:"expires_at"` +} + +type DidDocument struct { + ID int64 `json:"id"` + Did string `json:"did"` + Controller string `json:"controller"` + Document json.RawMessage `json:"document"` + Sequence int64 `json:"sequence"` + LastSynced string `json:"last_synced"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type Grant struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + ServiceID int64 `json:"service_id"` + UcanID *int64 `json:"ucan_id"` + Scopes json.RawMessage `json:"scopes"` + Accounts json.RawMessage `json:"accounts"` + Status string `json:"status"` + GrantedAt string `json:"granted_at"` + LastUsed *string `json:"last_used"` + ExpiresAt *string `json:"expires_at"` +} + +type KeyShare struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + ShareID string `json:"share_id"` + KeyID string `json:"key_id"` + PartyIndex int64 `json:"party_index"` + Threshold int64 `json:"threshold"` + TotalParties int64 `json:"total_parties"` + Curve string `json:"curve"` + ShareData string `json:"share_data"` + PublicKey string `json:"public_key"` + ChainCode *string `json:"chain_code"` + DerivationPath *string `json:"derivation_path"` + Status string `json:"status"` + CreatedAt string `json:"created_at"` + RotatedAt *string `json:"rotated_at"` +} + +type Service struct { + ID int64 `json:"id"` + Origin string `json:"origin"` + Name string `json:"name"` + Description *string `json:"description"` + LogoUrl *string `json:"logo_url"` + Did *string `json:"did"` + IsVerified int64 `json:"is_verified"` + Metadata json.RawMessage `json:"metadata"` + CreatedAt string `json:"created_at"` +} + +type Session struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + CredentialID int64 `json:"credential_id"` + SessionID string `json:"session_id"` + DeviceInfo json.RawMessage `json:"device_info"` + IsCurrent int64 `json:"is_current"` + LastActivity string `json:"last_activity"` + ExpiresAt string `json:"expires_at"` + CreatedAt string `json:"created_at"` +} + +type SyncCheckpoint struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + ResourceType string `json:"resource_type"` + LastBlock int64 `json:"last_block"` + LastTxHash *string `json:"last_tx_hash"` + LastSynced string `json:"last_synced"` +} + +type UcanRevocation struct { + ID int64 `json:"id"` + UcanCid string `json:"ucan_cid"` + RevokedBy string `json:"revoked_by"` + Reason *string `json:"reason"` + RevokedAt string `json:"revoked_at"` +} + +type UcanToken struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + Cid string `json:"cid"` + Issuer string `json:"issuer"` + Audience string `json:"audience"` + Subject *string `json:"subject"` + Capabilities json.RawMessage `json:"capabilities"` + ProofChain json.RawMessage `json:"proof_chain"` + NotBefore *string `json:"not_before"` + ExpiresAt string `json:"expires_at"` + Nonce *string `json:"nonce"` + Facts json.RawMessage `json:"facts"` + Signature string `json:"signature"` + RawToken string `json:"raw_token"` + IsRevoked int64 `json:"is_revoked"` + CreatedAt string `json:"created_at"` +} + +type VerificationMethod struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + MethodID string `json:"method_id"` + MethodType string `json:"method_type"` + Controller string `json:"controller"` + PublicKey string `json:"public_key"` + Purpose string `json:"purpose"` + CreatedAt string `json:"created_at"` +} diff --git a/internal/keybase/querier.go b/internal/keybase/querier.go new file mode 100644 index 0000000..c04bbb9 --- /dev/null +++ b/internal/keybase/querier.go @@ -0,0 +1,118 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package keybase + +import ( + "context" +) + +type Querier interface { + ArchiveKeyShare(ctx context.Context, id int64) error + CleanExpiredUCANs(ctx context.Context) error + CountActiveGrants(ctx context.Context, didID int64) (int64, error) + CountCredentialsByDID(ctx context.Context, didID int64) (int64, error) + CreateAccount(ctx context.Context, arg CreateAccountParams) (Account, error) + CreateCredential(ctx context.Context, arg CreateCredentialParams) (Credential, error) + CreateDID(ctx context.Context, arg CreateDIDParams) (DidDocument, error) + CreateDelegation(ctx context.Context, arg CreateDelegationParams) (Delegation, error) + CreateGrant(ctx context.Context, arg CreateGrantParams) (Grant, error) + CreateKeyShare(ctx context.Context, arg CreateKeyShareParams) (KeyShare, error) + CreateRevocation(ctx context.Context, arg CreateRevocationParams) error + CreateService(ctx context.Context, arg CreateServiceParams) (Service, error) + CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) + CreateUCAN(ctx context.Context, arg CreateUCANParams) (UcanToken, error) + CreateVerificationMethod(ctx context.Context, arg CreateVerificationMethodParams) (VerificationMethod, error) + DeleteAccount(ctx context.Context, arg DeleteAccountParams) error + DeleteCredential(ctx context.Context, arg DeleteCredentialParams) error + DeleteExpiredSessions(ctx context.Context) error + DeleteKeyShare(ctx context.Context, arg DeleteKeyShareParams) error + DeleteSession(ctx context.Context, id int64) error + DeleteVerificationMethod(ctx context.Context, id int64) error + GetAccountByAddress(ctx context.Context, address string) (Account, error) + GetCredentialByID(ctx context.Context, credentialID string) (Credential, error) + GetCurrentSession(ctx context.Context, didID int64) (Session, error) + // ============================================================================= + // DID DOCUMENT QUERIES + // ============================================================================= + GetDIDByDID(ctx context.Context, did string) (DidDocument, error) + GetDIDByID(ctx context.Context, id int64) (DidDocument, error) + GetDefaultAccount(ctx context.Context, arg GetDefaultAccountParams) (Account, error) + GetDelegationChain(ctx context.Context, arg GetDelegationChainParams) ([]Delegation, error) + GetGrantByService(ctx context.Context, arg GetGrantByServiceParams) (Grant, error) + GetKeyShareByID(ctx context.Context, shareID string) (KeyShare, error) + GetKeyShareByKeyID(ctx context.Context, arg GetKeyShareByKeyIDParams) (KeyShare, error) + GetServiceByID(ctx context.Context, id int64) (Service, error) + // ============================================================================= + // SERVICE QUERIES + // ============================================================================= + GetServiceByOrigin(ctx context.Context, origin string) (Service, error) + GetSessionByID(ctx context.Context, sessionID string) (Session, error) + // ============================================================================= + // SYNC QUERIES + // ============================================================================= + GetSyncCheckpoint(ctx context.Context, arg GetSyncCheckpointParams) (SyncCheckpoint, error) + GetUCANByCID(ctx context.Context, cid string) (UcanToken, error) + GetVerificationMethod(ctx context.Context, arg GetVerificationMethodParams) (VerificationMethod, error) + IsUCANRevoked(ctx context.Context, ucanCid string) (int64, error) + ListAccountsByChain(ctx context.Context, arg ListAccountsByChainParams) ([]Account, error) + // ============================================================================= + // ACCOUNT QUERIES + // ============================================================================= + ListAccountsByDID(ctx context.Context, didID int64) ([]ListAccountsByDIDRow, error) + ListAllDIDs(ctx context.Context) ([]DidDocument, error) + // ============================================================================= + // CREDENTIAL QUERIES + // ============================================================================= + ListCredentialsByDID(ctx context.Context, didID int64) ([]Credential, error) + ListDelegationsByDelegate(ctx context.Context, delegate string) ([]Delegation, error) + // ============================================================================= + // DELEGATION QUERIES + // ============================================================================= + ListDelegationsByDelegator(ctx context.Context, delegator string) ([]Delegation, error) + ListDelegationsForResource(ctx context.Context, arg ListDelegationsForResourceParams) ([]Delegation, error) + // ============================================================================= + // GRANT QUERIES + // ============================================================================= + ListGrantsByDID(ctx context.Context, didID int64) ([]ListGrantsByDIDRow, error) + // ============================================================================= + // KEY SHARE QUERIES + // ============================================================================= + ListKeySharesByDID(ctx context.Context, didID int64) ([]KeyShare, error) + // ============================================================================= + // SESSION QUERIES + // ============================================================================= + ListSessionsByDID(ctx context.Context, didID int64) ([]ListSessionsByDIDRow, error) + ListSyncCheckpoints(ctx context.Context, didID int64) ([]SyncCheckpoint, error) + ListUCANsByAudience(ctx context.Context, audience string) ([]UcanToken, error) + // ============================================================================= + // UCAN TOKEN QUERIES + // ============================================================================= + ListUCANsByDID(ctx context.Context, didID int64) ([]UcanToken, error) + // ============================================================================= + // VERIFICATION METHOD QUERIES + // ============================================================================= + ListVerificationMethods(ctx context.Context, didID int64) ([]VerificationMethod, error) + ListVerifiedServices(ctx context.Context) ([]Service, error) + ReactivateGrant(ctx context.Context, id int64) error + RenameCredential(ctx context.Context, arg RenameCredentialParams) error + RevokeDelegation(ctx context.Context, id int64) error + RevokeDelegationChain(ctx context.Context, arg RevokeDelegationChainParams) error + RevokeGrant(ctx context.Context, id int64) error + RevokeUCAN(ctx context.Context, cid string) error + RotateKeyShare(ctx context.Context, id int64) error + SetCurrentSession(ctx context.Context, arg SetCurrentSessionParams) error + SetDefaultAccount(ctx context.Context, arg SetDefaultAccountParams) error + SuspendGrant(ctx context.Context, id int64) error + UpdateAccountLabel(ctx context.Context, arg UpdateAccountLabelParams) error + UpdateCredentialCounter(ctx context.Context, arg UpdateCredentialCounterParams) error + UpdateDIDDocument(ctx context.Context, arg UpdateDIDDocumentParams) error + UpdateGrantLastUsed(ctx context.Context, id int64) error + UpdateGrantScopes(ctx context.Context, arg UpdateGrantScopesParams) error + UpdateService(ctx context.Context, arg UpdateServiceParams) error + UpdateSessionActivity(ctx context.Context, id int64) error + UpsertSyncCheckpoint(ctx context.Context, arg UpsertSyncCheckpointParams) error +} + +var _ Querier = (*Queries)(nil) diff --git a/internal/keybase/query.sql.go b/internal/keybase/query.sql.go new file mode 100644 index 0000000..09086ce --- /dev/null +++ b/internal/keybase/query.sql.go @@ -0,0 +1,1996 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: query.sql + +package keybase + +import ( + "context" + "encoding/json" +) + +const archiveKeyShare = `-- name: ArchiveKeyShare :exec +UPDATE key_shares SET status = 'archived' WHERE id = ? +` + +func (q *Queries) ArchiveKeyShare(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, archiveKeyShare, id) + return err +} + +const cleanExpiredUCANs = `-- name: CleanExpiredUCANs :exec +DELETE FROM ucan_tokens WHERE expires_at < datetime('now', '-30 days') +` + +func (q *Queries) CleanExpiredUCANs(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, cleanExpiredUCANs) + return err +} + +const countActiveGrants = `-- name: CountActiveGrants :one +SELECT COUNT(*) FROM grants WHERE did_id = ? AND status = 'active' +` + +func (q *Queries) CountActiveGrants(ctx context.Context, didID int64) (int64, error) { + row := q.db.QueryRowContext(ctx, countActiveGrants, didID) + var count int64 + err := row.Scan(&count) + return count, err +} + +const countCredentialsByDID = `-- name: CountCredentialsByDID :one +SELECT COUNT(*) FROM credentials WHERE did_id = ? +` + +func (q *Queries) CountCredentialsByDID(ctx context.Context, didID int64) (int64, error) { + row := q.db.QueryRowContext(ctx, countCredentialsByDID, didID) + var count int64 + err := row.Scan(&count) + return count, err +} + +const createAccount = `-- name: CreateAccount :one +INSERT INTO accounts (did_id, key_share_id, address, chain_id, coin_type, account_index, address_index, label) +VALUES (?, ?, ?, ?, ?, ?, ?, ?) +RETURNING id, did_id, key_share_id, address, chain_id, coin_type, account_index, address_index, label, is_default, created_at +` + +type CreateAccountParams struct { + DidID int64 `json:"did_id"` + KeyShareID int64 `json:"key_share_id"` + Address string `json:"address"` + ChainID string `json:"chain_id"` + CoinType int64 `json:"coin_type"` + AccountIndex int64 `json:"account_index"` + AddressIndex int64 `json:"address_index"` + Label *string `json:"label"` +} + +func (q *Queries) CreateAccount(ctx context.Context, arg CreateAccountParams) (Account, error) { + row := q.db.QueryRowContext(ctx, createAccount, + arg.DidID, + arg.KeyShareID, + arg.Address, + arg.ChainID, + arg.CoinType, + arg.AccountIndex, + arg.AddressIndex, + arg.Label, + ) + var i Account + err := row.Scan( + &i.ID, + &i.DidID, + &i.KeyShareID, + &i.Address, + &i.ChainID, + &i.CoinType, + &i.AccountIndex, + &i.AddressIndex, + &i.Label, + &i.IsDefault, + &i.CreatedAt, + ) + return i, err +} + +const createCredential = `-- name: CreateCredential :one +INSERT INTO credentials ( + did_id, credential_id, public_key, public_key_alg, aaguid, + transports, device_name, device_type, authenticator, is_discoverable, backed_up +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +RETURNING id, did_id, credential_id, public_key, public_key_alg, aaguid, sign_count, transports, device_name, device_type, authenticator, is_discoverable, backed_up, created_at, last_used +` + +type CreateCredentialParams struct { + DidID int64 `json:"did_id"` + CredentialID string `json:"credential_id"` + PublicKey string `json:"public_key"` + PublicKeyAlg int64 `json:"public_key_alg"` + Aaguid *string `json:"aaguid"` + Transports json.RawMessage `json:"transports"` + DeviceName string `json:"device_name"` + DeviceType string `json:"device_type"` + Authenticator *string `json:"authenticator"` + IsDiscoverable int64 `json:"is_discoverable"` + BackedUp int64 `json:"backed_up"` +} + +func (q *Queries) CreateCredential(ctx context.Context, arg CreateCredentialParams) (Credential, error) { + row := q.db.QueryRowContext(ctx, createCredential, + arg.DidID, + arg.CredentialID, + arg.PublicKey, + arg.PublicKeyAlg, + arg.Aaguid, + arg.Transports, + arg.DeviceName, + arg.DeviceType, + arg.Authenticator, + arg.IsDiscoverable, + arg.BackedUp, + ) + var i Credential + err := row.Scan( + &i.ID, + &i.DidID, + &i.CredentialID, + &i.PublicKey, + &i.PublicKeyAlg, + &i.Aaguid, + &i.SignCount, + &i.Transports, + &i.DeviceName, + &i.DeviceType, + &i.Authenticator, + &i.IsDiscoverable, + &i.BackedUp, + &i.CreatedAt, + &i.LastUsed, + ) + return i, err +} + +const createDID = `-- name: CreateDID :one +INSERT INTO did_documents (did, controller, document, sequence) +VALUES (?, ?, ?, ?) +RETURNING id, did, controller, document, sequence, last_synced, created_at, updated_at +` + +type CreateDIDParams struct { + Did string `json:"did"` + Controller string `json:"controller"` + Document json.RawMessage `json:"document"` + Sequence int64 `json:"sequence"` +} + +func (q *Queries) CreateDID(ctx context.Context, arg CreateDIDParams) (DidDocument, error) { + row := q.db.QueryRowContext(ctx, createDID, + arg.Did, + arg.Controller, + arg.Document, + arg.Sequence, + ) + var i DidDocument + err := row.Scan( + &i.ID, + &i.Did, + &i.Controller, + &i.Document, + &i.Sequence, + &i.LastSynced, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const createDelegation = `-- name: CreateDelegation :one +INSERT INTO delegations ( + did_id, ucan_id, delegator, delegate, resource, action, caveats, parent_id, depth, expires_at +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +RETURNING id, did_id, ucan_id, delegator, delegate, resource, "action", caveats, parent_id, depth, status, created_at, expires_at +` + +type CreateDelegationParams struct { + DidID int64 `json:"did_id"` + UcanID int64 `json:"ucan_id"` + Delegator string `json:"delegator"` + Delegate string `json:"delegate"` + Resource string `json:"resource"` + Action string `json:"action"` + Caveats json.RawMessage `json:"caveats"` + ParentID *int64 `json:"parent_id"` + Depth int64 `json:"depth"` + ExpiresAt *string `json:"expires_at"` +} + +func (q *Queries) CreateDelegation(ctx context.Context, arg CreateDelegationParams) (Delegation, error) { + row := q.db.QueryRowContext(ctx, createDelegation, + arg.DidID, + arg.UcanID, + arg.Delegator, + arg.Delegate, + arg.Resource, + arg.Action, + arg.Caveats, + arg.ParentID, + arg.Depth, + arg.ExpiresAt, + ) + var i Delegation + err := row.Scan( + &i.ID, + &i.DidID, + &i.UcanID, + &i.Delegator, + &i.Delegate, + &i.Resource, + &i.Action, + &i.Caveats, + &i.ParentID, + &i.Depth, + &i.Status, + &i.CreatedAt, + &i.ExpiresAt, + ) + return i, err +} + +const createGrant = `-- name: CreateGrant :one +INSERT INTO grants (did_id, service_id, ucan_id, scopes, accounts, expires_at) +VALUES (?, ?, ?, ?, ?, ?) +RETURNING id, did_id, service_id, ucan_id, scopes, accounts, status, granted_at, last_used, expires_at +` + +type CreateGrantParams struct { + DidID int64 `json:"did_id"` + ServiceID int64 `json:"service_id"` + UcanID *int64 `json:"ucan_id"` + Scopes json.RawMessage `json:"scopes"` + Accounts json.RawMessage `json:"accounts"` + ExpiresAt *string `json:"expires_at"` +} + +func (q *Queries) CreateGrant(ctx context.Context, arg CreateGrantParams) (Grant, error) { + row := q.db.QueryRowContext(ctx, createGrant, + arg.DidID, + arg.ServiceID, + arg.UcanID, + arg.Scopes, + arg.Accounts, + arg.ExpiresAt, + ) + var i Grant + err := row.Scan( + &i.ID, + &i.DidID, + &i.ServiceID, + &i.UcanID, + &i.Scopes, + &i.Accounts, + &i.Status, + &i.GrantedAt, + &i.LastUsed, + &i.ExpiresAt, + ) + return i, err +} + +const createKeyShare = `-- name: CreateKeyShare :one +INSERT INTO key_shares ( + did_id, share_id, key_id, party_index, threshold, total_parties, + curve, share_data, public_key, chain_code, derivation_path +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +RETURNING id, did_id, share_id, key_id, party_index, threshold, total_parties, curve, share_data, public_key, chain_code, derivation_path, status, created_at, rotated_at +` + +type CreateKeyShareParams struct { + DidID int64 `json:"did_id"` + ShareID string `json:"share_id"` + KeyID string `json:"key_id"` + PartyIndex int64 `json:"party_index"` + Threshold int64 `json:"threshold"` + TotalParties int64 `json:"total_parties"` + Curve string `json:"curve"` + ShareData string `json:"share_data"` + PublicKey string `json:"public_key"` + ChainCode *string `json:"chain_code"` + DerivationPath *string `json:"derivation_path"` +} + +func (q *Queries) CreateKeyShare(ctx context.Context, arg CreateKeyShareParams) (KeyShare, error) { + row := q.db.QueryRowContext(ctx, createKeyShare, + arg.DidID, + arg.ShareID, + arg.KeyID, + arg.PartyIndex, + arg.Threshold, + arg.TotalParties, + arg.Curve, + arg.ShareData, + arg.PublicKey, + arg.ChainCode, + arg.DerivationPath, + ) + var i KeyShare + err := row.Scan( + &i.ID, + &i.DidID, + &i.ShareID, + &i.KeyID, + &i.PartyIndex, + &i.Threshold, + &i.TotalParties, + &i.Curve, + &i.ShareData, + &i.PublicKey, + &i.ChainCode, + &i.DerivationPath, + &i.Status, + &i.CreatedAt, + &i.RotatedAt, + ) + return i, err +} + +const createRevocation = `-- name: CreateRevocation :exec +INSERT INTO ucan_revocations (ucan_cid, revoked_by, reason) +VALUES (?, ?, ?) +` + +type CreateRevocationParams struct { + UcanCid string `json:"ucan_cid"` + RevokedBy string `json:"revoked_by"` + Reason *string `json:"reason"` +} + +func (q *Queries) CreateRevocation(ctx context.Context, arg CreateRevocationParams) error { + _, err := q.db.ExecContext(ctx, createRevocation, arg.UcanCid, arg.RevokedBy, arg.Reason) + return err +} + +const createService = `-- name: CreateService :one +INSERT INTO services (origin, name, description, logo_url, did, is_verified, metadata) +VALUES (?, ?, ?, ?, ?, ?, ?) +RETURNING id, origin, name, description, logo_url, did, is_verified, metadata, created_at +` + +type CreateServiceParams struct { + Origin string `json:"origin"` + Name string `json:"name"` + Description *string `json:"description"` + LogoUrl *string `json:"logo_url"` + Did *string `json:"did"` + IsVerified int64 `json:"is_verified"` + Metadata json.RawMessage `json:"metadata"` +} + +func (q *Queries) CreateService(ctx context.Context, arg CreateServiceParams) (Service, error) { + row := q.db.QueryRowContext(ctx, createService, + arg.Origin, + arg.Name, + arg.Description, + arg.LogoUrl, + arg.Did, + arg.IsVerified, + arg.Metadata, + ) + var i Service + err := row.Scan( + &i.ID, + &i.Origin, + &i.Name, + &i.Description, + &i.LogoUrl, + &i.Did, + &i.IsVerified, + &i.Metadata, + &i.CreatedAt, + ) + return i, err +} + +const createSession = `-- name: CreateSession :one +INSERT INTO sessions (did_id, credential_id, session_id, device_info, is_current, expires_at) +VALUES (?, ?, ?, ?, ?, ?) +RETURNING id, did_id, credential_id, session_id, device_info, is_current, last_activity, expires_at, created_at +` + +type CreateSessionParams struct { + DidID int64 `json:"did_id"` + CredentialID int64 `json:"credential_id"` + SessionID string `json:"session_id"` + DeviceInfo json.RawMessage `json:"device_info"` + IsCurrent int64 `json:"is_current"` + ExpiresAt string `json:"expires_at"` +} + +func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) { + row := q.db.QueryRowContext(ctx, createSession, + arg.DidID, + arg.CredentialID, + arg.SessionID, + arg.DeviceInfo, + arg.IsCurrent, + arg.ExpiresAt, + ) + var i Session + err := row.Scan( + &i.ID, + &i.DidID, + &i.CredentialID, + &i.SessionID, + &i.DeviceInfo, + &i.IsCurrent, + &i.LastActivity, + &i.ExpiresAt, + &i.CreatedAt, + ) + return i, err +} + +const createUCAN = `-- name: CreateUCAN :one +INSERT INTO ucan_tokens ( + did_id, cid, issuer, audience, subject, capabilities, + proof_chain, not_before, expires_at, nonce, facts, signature, raw_token +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +RETURNING id, did_id, cid, issuer, audience, subject, capabilities, proof_chain, not_before, expires_at, nonce, facts, signature, raw_token, is_revoked, created_at +` + +type CreateUCANParams struct { + DidID int64 `json:"did_id"` + Cid string `json:"cid"` + Issuer string `json:"issuer"` + Audience string `json:"audience"` + Subject *string `json:"subject"` + Capabilities json.RawMessage `json:"capabilities"` + ProofChain json.RawMessage `json:"proof_chain"` + NotBefore *string `json:"not_before"` + ExpiresAt string `json:"expires_at"` + Nonce *string `json:"nonce"` + Facts json.RawMessage `json:"facts"` + Signature string `json:"signature"` + RawToken string `json:"raw_token"` +} + +func (q *Queries) CreateUCAN(ctx context.Context, arg CreateUCANParams) (UcanToken, error) { + row := q.db.QueryRowContext(ctx, createUCAN, + arg.DidID, + arg.Cid, + arg.Issuer, + arg.Audience, + arg.Subject, + arg.Capabilities, + arg.ProofChain, + arg.NotBefore, + arg.ExpiresAt, + arg.Nonce, + arg.Facts, + arg.Signature, + arg.RawToken, + ) + var i UcanToken + err := row.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Issuer, + &i.Audience, + &i.Subject, + &i.Capabilities, + &i.ProofChain, + &i.NotBefore, + &i.ExpiresAt, + &i.Nonce, + &i.Facts, + &i.Signature, + &i.RawToken, + &i.IsRevoked, + &i.CreatedAt, + ) + return i, err +} + +const createVerificationMethod = `-- name: CreateVerificationMethod :one +INSERT INTO verification_methods (did_id, method_id, method_type, controller, public_key, purpose) +VALUES (?, ?, ?, ?, ?, ?) +RETURNING id, did_id, method_id, method_type, controller, public_key, purpose, created_at +` + +type CreateVerificationMethodParams struct { + DidID int64 `json:"did_id"` + MethodID string `json:"method_id"` + MethodType string `json:"method_type"` + Controller string `json:"controller"` + PublicKey string `json:"public_key"` + Purpose string `json:"purpose"` +} + +func (q *Queries) CreateVerificationMethod(ctx context.Context, arg CreateVerificationMethodParams) (VerificationMethod, error) { + row := q.db.QueryRowContext(ctx, createVerificationMethod, + arg.DidID, + arg.MethodID, + arg.MethodType, + arg.Controller, + arg.PublicKey, + arg.Purpose, + ) + var i VerificationMethod + err := row.Scan( + &i.ID, + &i.DidID, + &i.MethodID, + &i.MethodType, + &i.Controller, + &i.PublicKey, + &i.Purpose, + &i.CreatedAt, + ) + return i, err +} + +const deleteAccount = `-- name: DeleteAccount :exec +DELETE FROM accounts WHERE id = ? AND did_id = ? +` + +type DeleteAccountParams struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` +} + +func (q *Queries) DeleteAccount(ctx context.Context, arg DeleteAccountParams) error { + _, err := q.db.ExecContext(ctx, deleteAccount, arg.ID, arg.DidID) + return err +} + +const deleteCredential = `-- name: DeleteCredential :exec +DELETE FROM credentials WHERE id = ? AND did_id = ? +` + +type DeleteCredentialParams struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` +} + +func (q *Queries) DeleteCredential(ctx context.Context, arg DeleteCredentialParams) error { + _, err := q.db.ExecContext(ctx, deleteCredential, arg.ID, arg.DidID) + return err +} + +const deleteExpiredSessions = `-- name: DeleteExpiredSessions :exec +DELETE FROM sessions WHERE expires_at < datetime('now') +` + +func (q *Queries) DeleteExpiredSessions(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, deleteExpiredSessions) + return err +} + +const deleteKeyShare = `-- name: DeleteKeyShare :exec +DELETE FROM key_shares WHERE id = ? AND did_id = ? +` + +type DeleteKeyShareParams struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` +} + +func (q *Queries) DeleteKeyShare(ctx context.Context, arg DeleteKeyShareParams) error { + _, err := q.db.ExecContext(ctx, deleteKeyShare, arg.ID, arg.DidID) + return err +} + +const deleteSession = `-- name: DeleteSession :exec +DELETE FROM sessions WHERE id = ? +` + +func (q *Queries) DeleteSession(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, deleteSession, id) + return err +} + +const deleteVerificationMethod = `-- name: DeleteVerificationMethod :exec +DELETE FROM verification_methods WHERE id = ? +` + +func (q *Queries) DeleteVerificationMethod(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, deleteVerificationMethod, id) + return err +} + +const getAccountByAddress = `-- name: GetAccountByAddress :one +SELECT id, did_id, key_share_id, address, chain_id, coin_type, account_index, address_index, label, is_default, created_at FROM accounts WHERE address = ? LIMIT 1 +` + +func (q *Queries) GetAccountByAddress(ctx context.Context, address string) (Account, error) { + row := q.db.QueryRowContext(ctx, getAccountByAddress, address) + var i Account + err := row.Scan( + &i.ID, + &i.DidID, + &i.KeyShareID, + &i.Address, + &i.ChainID, + &i.CoinType, + &i.AccountIndex, + &i.AddressIndex, + &i.Label, + &i.IsDefault, + &i.CreatedAt, + ) + return i, err +} + +const getCredentialByID = `-- name: GetCredentialByID :one +SELECT id, did_id, credential_id, public_key, public_key_alg, aaguid, sign_count, transports, device_name, device_type, authenticator, is_discoverable, backed_up, created_at, last_used FROM credentials WHERE credential_id = ? LIMIT 1 +` + +func (q *Queries) GetCredentialByID(ctx context.Context, credentialID string) (Credential, error) { + row := q.db.QueryRowContext(ctx, getCredentialByID, credentialID) + var i Credential + err := row.Scan( + &i.ID, + &i.DidID, + &i.CredentialID, + &i.PublicKey, + &i.PublicKeyAlg, + &i.Aaguid, + &i.SignCount, + &i.Transports, + &i.DeviceName, + &i.DeviceType, + &i.Authenticator, + &i.IsDiscoverable, + &i.BackedUp, + &i.CreatedAt, + &i.LastUsed, + ) + return i, err +} + +const getCurrentSession = `-- name: GetCurrentSession :one +SELECT id, did_id, credential_id, session_id, device_info, is_current, last_activity, expires_at, created_at FROM sessions WHERE did_id = ? AND is_current = 1 LIMIT 1 +` + +func (q *Queries) GetCurrentSession(ctx context.Context, didID int64) (Session, error) { + row := q.db.QueryRowContext(ctx, getCurrentSession, didID) + var i Session + err := row.Scan( + &i.ID, + &i.DidID, + &i.CredentialID, + &i.SessionID, + &i.DeviceInfo, + &i.IsCurrent, + &i.LastActivity, + &i.ExpiresAt, + &i.CreatedAt, + ) + return i, err +} + +const getDIDByDID = `-- name: GetDIDByDID :one + +SELECT id, did, controller, document, sequence, last_synced, created_at, updated_at FROM did_documents WHERE did = ? LIMIT 1 +` + +// ============================================================================= +// DID DOCUMENT QUERIES +// ============================================================================= +func (q *Queries) GetDIDByDID(ctx context.Context, did string) (DidDocument, error) { + row := q.db.QueryRowContext(ctx, getDIDByDID, did) + var i DidDocument + err := row.Scan( + &i.ID, + &i.Did, + &i.Controller, + &i.Document, + &i.Sequence, + &i.LastSynced, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getDIDByID = `-- name: GetDIDByID :one +SELECT id, did, controller, document, sequence, last_synced, created_at, updated_at FROM did_documents WHERE id = ? LIMIT 1 +` + +func (q *Queries) GetDIDByID(ctx context.Context, id int64) (DidDocument, error) { + row := q.db.QueryRowContext(ctx, getDIDByID, id) + var i DidDocument + err := row.Scan( + &i.ID, + &i.Did, + &i.Controller, + &i.Document, + &i.Sequence, + &i.LastSynced, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getDefaultAccount = `-- name: GetDefaultAccount :one +SELECT id, did_id, key_share_id, address, chain_id, coin_type, account_index, address_index, label, is_default, created_at FROM accounts WHERE did_id = ? AND chain_id = ? AND is_default = 1 LIMIT 1 +` + +type GetDefaultAccountParams struct { + DidID int64 `json:"did_id"` + ChainID string `json:"chain_id"` +} + +func (q *Queries) GetDefaultAccount(ctx context.Context, arg GetDefaultAccountParams) (Account, error) { + row := q.db.QueryRowContext(ctx, getDefaultAccount, arg.DidID, arg.ChainID) + var i Account + err := row.Scan( + &i.ID, + &i.DidID, + &i.KeyShareID, + &i.Address, + &i.ChainID, + &i.CoinType, + &i.AccountIndex, + &i.AddressIndex, + &i.Label, + &i.IsDefault, + &i.CreatedAt, + ) + return i, err +} + +const getDelegationChain = `-- name: GetDelegationChain :many +SELECT id, did_id, ucan_id, delegator, delegate, resource, "action", caveats, parent_id, depth, status, created_at, expires_at FROM delegations WHERE id = ? OR parent_id = ? ORDER BY depth DESC +` + +type GetDelegationChainParams struct { + ID int64 `json:"id"` + ParentID *int64 `json:"parent_id"` +} + +func (q *Queries) GetDelegationChain(ctx context.Context, arg GetDelegationChainParams) ([]Delegation, error) { + rows, err := q.db.QueryContext(ctx, getDelegationChain, arg.ID, arg.ParentID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Delegation{} + for rows.Next() { + var i Delegation + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.UcanID, + &i.Delegator, + &i.Delegate, + &i.Resource, + &i.Action, + &i.Caveats, + &i.ParentID, + &i.Depth, + &i.Status, + &i.CreatedAt, + &i.ExpiresAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getGrantByService = `-- name: GetGrantByService :one +SELECT id, did_id, service_id, ucan_id, scopes, accounts, status, granted_at, last_used, expires_at FROM grants WHERE did_id = ? AND service_id = ? LIMIT 1 +` + +type GetGrantByServiceParams struct { + DidID int64 `json:"did_id"` + ServiceID int64 `json:"service_id"` +} + +func (q *Queries) GetGrantByService(ctx context.Context, arg GetGrantByServiceParams) (Grant, error) { + row := q.db.QueryRowContext(ctx, getGrantByService, arg.DidID, arg.ServiceID) + var i Grant + err := row.Scan( + &i.ID, + &i.DidID, + &i.ServiceID, + &i.UcanID, + &i.Scopes, + &i.Accounts, + &i.Status, + &i.GrantedAt, + &i.LastUsed, + &i.ExpiresAt, + ) + return i, err +} + +const getKeyShareByID = `-- name: GetKeyShareByID :one +SELECT id, did_id, share_id, key_id, party_index, threshold, total_parties, curve, share_data, public_key, chain_code, derivation_path, status, created_at, rotated_at FROM key_shares WHERE share_id = ? LIMIT 1 +` + +func (q *Queries) GetKeyShareByID(ctx context.Context, shareID string) (KeyShare, error) { + row := q.db.QueryRowContext(ctx, getKeyShareByID, shareID) + var i KeyShare + err := row.Scan( + &i.ID, + &i.DidID, + &i.ShareID, + &i.KeyID, + &i.PartyIndex, + &i.Threshold, + &i.TotalParties, + &i.Curve, + &i.ShareData, + &i.PublicKey, + &i.ChainCode, + &i.DerivationPath, + &i.Status, + &i.CreatedAt, + &i.RotatedAt, + ) + return i, err +} + +const getKeyShareByKeyID = `-- name: GetKeyShareByKeyID :one +SELECT id, did_id, share_id, key_id, party_index, threshold, total_parties, curve, share_data, public_key, chain_code, derivation_path, status, created_at, rotated_at FROM key_shares WHERE did_id = ? AND key_id = ? AND status = 'active' LIMIT 1 +` + +type GetKeyShareByKeyIDParams struct { + DidID int64 `json:"did_id"` + KeyID string `json:"key_id"` +} + +func (q *Queries) GetKeyShareByKeyID(ctx context.Context, arg GetKeyShareByKeyIDParams) (KeyShare, error) { + row := q.db.QueryRowContext(ctx, getKeyShareByKeyID, arg.DidID, arg.KeyID) + var i KeyShare + err := row.Scan( + &i.ID, + &i.DidID, + &i.ShareID, + &i.KeyID, + &i.PartyIndex, + &i.Threshold, + &i.TotalParties, + &i.Curve, + &i.ShareData, + &i.PublicKey, + &i.ChainCode, + &i.DerivationPath, + &i.Status, + &i.CreatedAt, + &i.RotatedAt, + ) + return i, err +} + +const getServiceByID = `-- name: GetServiceByID :one +SELECT id, origin, name, description, logo_url, did, is_verified, metadata, created_at FROM services WHERE id = ? LIMIT 1 +` + +func (q *Queries) GetServiceByID(ctx context.Context, id int64) (Service, error) { + row := q.db.QueryRowContext(ctx, getServiceByID, id) + var i Service + err := row.Scan( + &i.ID, + &i.Origin, + &i.Name, + &i.Description, + &i.LogoUrl, + &i.Did, + &i.IsVerified, + &i.Metadata, + &i.CreatedAt, + ) + return i, err +} + +const getServiceByOrigin = `-- name: GetServiceByOrigin :one + +SELECT id, origin, name, description, logo_url, did, is_verified, metadata, created_at FROM services WHERE origin = ? LIMIT 1 +` + +// ============================================================================= +// SERVICE QUERIES +// ============================================================================= +func (q *Queries) GetServiceByOrigin(ctx context.Context, origin string) (Service, error) { + row := q.db.QueryRowContext(ctx, getServiceByOrigin, origin) + var i Service + err := row.Scan( + &i.ID, + &i.Origin, + &i.Name, + &i.Description, + &i.LogoUrl, + &i.Did, + &i.IsVerified, + &i.Metadata, + &i.CreatedAt, + ) + return i, err +} + +const getSessionByID = `-- name: GetSessionByID :one +SELECT id, did_id, credential_id, session_id, device_info, is_current, last_activity, expires_at, created_at FROM sessions WHERE session_id = ? LIMIT 1 +` + +func (q *Queries) GetSessionByID(ctx context.Context, sessionID string) (Session, error) { + row := q.db.QueryRowContext(ctx, getSessionByID, sessionID) + var i Session + err := row.Scan( + &i.ID, + &i.DidID, + &i.CredentialID, + &i.SessionID, + &i.DeviceInfo, + &i.IsCurrent, + &i.LastActivity, + &i.ExpiresAt, + &i.CreatedAt, + ) + return i, err +} + +const getSyncCheckpoint = `-- name: GetSyncCheckpoint :one + +SELECT id, did_id, resource_type, last_block, last_tx_hash, last_synced FROM sync_checkpoints WHERE did_id = ? AND resource_type = ? LIMIT 1 +` + +type GetSyncCheckpointParams struct { + DidID int64 `json:"did_id"` + ResourceType string `json:"resource_type"` +} + +// ============================================================================= +// SYNC QUERIES +// ============================================================================= +func (q *Queries) GetSyncCheckpoint(ctx context.Context, arg GetSyncCheckpointParams) (SyncCheckpoint, error) { + row := q.db.QueryRowContext(ctx, getSyncCheckpoint, arg.DidID, arg.ResourceType) + var i SyncCheckpoint + err := row.Scan( + &i.ID, + &i.DidID, + &i.ResourceType, + &i.LastBlock, + &i.LastTxHash, + &i.LastSynced, + ) + return i, err +} + +const getUCANByCID = `-- name: GetUCANByCID :one +SELECT id, did_id, cid, issuer, audience, subject, capabilities, proof_chain, not_before, expires_at, nonce, facts, signature, raw_token, is_revoked, created_at FROM ucan_tokens WHERE cid = ? LIMIT 1 +` + +func (q *Queries) GetUCANByCID(ctx context.Context, cid string) (UcanToken, error) { + row := q.db.QueryRowContext(ctx, getUCANByCID, cid) + var i UcanToken + err := row.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Issuer, + &i.Audience, + &i.Subject, + &i.Capabilities, + &i.ProofChain, + &i.NotBefore, + &i.ExpiresAt, + &i.Nonce, + &i.Facts, + &i.Signature, + &i.RawToken, + &i.IsRevoked, + &i.CreatedAt, + ) + return i, err +} + +const getVerificationMethod = `-- name: GetVerificationMethod :one +SELECT id, did_id, method_id, method_type, controller, public_key, purpose, created_at FROM verification_methods WHERE did_id = ? AND method_id = ? LIMIT 1 +` + +type GetVerificationMethodParams struct { + DidID int64 `json:"did_id"` + MethodID string `json:"method_id"` +} + +func (q *Queries) GetVerificationMethod(ctx context.Context, arg GetVerificationMethodParams) (VerificationMethod, error) { + row := q.db.QueryRowContext(ctx, getVerificationMethod, arg.DidID, arg.MethodID) + var i VerificationMethod + err := row.Scan( + &i.ID, + &i.DidID, + &i.MethodID, + &i.MethodType, + &i.Controller, + &i.PublicKey, + &i.Purpose, + &i.CreatedAt, + ) + return i, err +} + +const isUCANRevoked = `-- name: IsUCANRevoked :one +SELECT EXISTS(SELECT 1 FROM ucan_revocations WHERE ucan_cid = ?) as revoked +` + +func (q *Queries) IsUCANRevoked(ctx context.Context, ucanCid string) (int64, error) { + row := q.db.QueryRowContext(ctx, isUCANRevoked, ucanCid) + var revoked int64 + err := row.Scan(&revoked) + return revoked, err +} + +const listAccountsByChain = `-- name: ListAccountsByChain :many +SELECT id, did_id, key_share_id, address, chain_id, coin_type, account_index, address_index, label, is_default, created_at FROM accounts WHERE did_id = ? AND chain_id = ? ORDER BY account_index, address_index +` + +type ListAccountsByChainParams struct { + DidID int64 `json:"did_id"` + ChainID string `json:"chain_id"` +} + +func (q *Queries) ListAccountsByChain(ctx context.Context, arg ListAccountsByChainParams) ([]Account, error) { + rows, err := q.db.QueryContext(ctx, listAccountsByChain, arg.DidID, arg.ChainID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Account{} + for rows.Next() { + var i Account + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.KeyShareID, + &i.Address, + &i.ChainID, + &i.CoinType, + &i.AccountIndex, + &i.AddressIndex, + &i.Label, + &i.IsDefault, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAccountsByDID = `-- name: ListAccountsByDID :many + +SELECT a.id, a.did_id, a.key_share_id, a.address, a.chain_id, a.coin_type, a.account_index, a.address_index, a.label, a.is_default, a.created_at, k.public_key, k.curve +FROM accounts a +JOIN key_shares k ON a.key_share_id = k.id +WHERE a.did_id = ? +ORDER BY a.is_default DESC, a.created_at +` + +type ListAccountsByDIDRow struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + KeyShareID int64 `json:"key_share_id"` + Address string `json:"address"` + ChainID string `json:"chain_id"` + CoinType int64 `json:"coin_type"` + AccountIndex int64 `json:"account_index"` + AddressIndex int64 `json:"address_index"` + Label *string `json:"label"` + IsDefault int64 `json:"is_default"` + CreatedAt string `json:"created_at"` + PublicKey string `json:"public_key"` + Curve string `json:"curve"` +} + +// ============================================================================= +// ACCOUNT QUERIES +// ============================================================================= +func (q *Queries) ListAccountsByDID(ctx context.Context, didID int64) ([]ListAccountsByDIDRow, error) { + rows, err := q.db.QueryContext(ctx, listAccountsByDID, didID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAccountsByDIDRow{} + for rows.Next() { + var i ListAccountsByDIDRow + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.KeyShareID, + &i.Address, + &i.ChainID, + &i.CoinType, + &i.AccountIndex, + &i.AddressIndex, + &i.Label, + &i.IsDefault, + &i.CreatedAt, + &i.PublicKey, + &i.Curve, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAllDIDs = `-- name: ListAllDIDs :many +SELECT id, did, controller, document, sequence, last_synced, created_at, updated_at FROM did_documents ORDER BY created_at DESC +` + +func (q *Queries) ListAllDIDs(ctx context.Context) ([]DidDocument, error) { + rows, err := q.db.QueryContext(ctx, listAllDIDs) + if err != nil { + return nil, err + } + defer rows.Close() + items := []DidDocument{} + for rows.Next() { + var i DidDocument + if err := rows.Scan( + &i.ID, + &i.Did, + &i.Controller, + &i.Document, + &i.Sequence, + &i.LastSynced, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listCredentialsByDID = `-- name: ListCredentialsByDID :many + +SELECT id, did_id, credential_id, public_key, public_key_alg, aaguid, sign_count, transports, device_name, device_type, authenticator, is_discoverable, backed_up, created_at, last_used FROM credentials WHERE did_id = ? ORDER BY last_used DESC +` + +// ============================================================================= +// CREDENTIAL QUERIES +// ============================================================================= +func (q *Queries) ListCredentialsByDID(ctx context.Context, didID int64) ([]Credential, error) { + rows, err := q.db.QueryContext(ctx, listCredentialsByDID, didID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Credential{} + for rows.Next() { + var i Credential + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.CredentialID, + &i.PublicKey, + &i.PublicKeyAlg, + &i.Aaguid, + &i.SignCount, + &i.Transports, + &i.DeviceName, + &i.DeviceType, + &i.Authenticator, + &i.IsDiscoverable, + &i.BackedUp, + &i.CreatedAt, + &i.LastUsed, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listDelegationsByDelegate = `-- name: ListDelegationsByDelegate :many +SELECT id, did_id, ucan_id, delegator, delegate, resource, "action", caveats, parent_id, depth, status, created_at, expires_at FROM delegations +WHERE delegate = ? AND status = 'active' AND (expires_at IS NULL OR expires_at > datetime('now')) +ORDER BY created_at DESC +` + +func (q *Queries) ListDelegationsByDelegate(ctx context.Context, delegate string) ([]Delegation, error) { + rows, err := q.db.QueryContext(ctx, listDelegationsByDelegate, delegate) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Delegation{} + for rows.Next() { + var i Delegation + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.UcanID, + &i.Delegator, + &i.Delegate, + &i.Resource, + &i.Action, + &i.Caveats, + &i.ParentID, + &i.Depth, + &i.Status, + &i.CreatedAt, + &i.ExpiresAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listDelegationsByDelegator = `-- name: ListDelegationsByDelegator :many + +SELECT id, did_id, ucan_id, delegator, delegate, resource, "action", caveats, parent_id, depth, status, created_at, expires_at FROM delegations +WHERE delegator = ? AND status = 'active' +ORDER BY created_at DESC +` + +// ============================================================================= +// DELEGATION QUERIES +// ============================================================================= +func (q *Queries) ListDelegationsByDelegator(ctx context.Context, delegator string) ([]Delegation, error) { + rows, err := q.db.QueryContext(ctx, listDelegationsByDelegator, delegator) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Delegation{} + for rows.Next() { + var i Delegation + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.UcanID, + &i.Delegator, + &i.Delegate, + &i.Resource, + &i.Action, + &i.Caveats, + &i.ParentID, + &i.Depth, + &i.Status, + &i.CreatedAt, + &i.ExpiresAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listDelegationsForResource = `-- name: ListDelegationsForResource :many +SELECT id, did_id, ucan_id, delegator, delegate, resource, "action", caveats, parent_id, depth, status, created_at, expires_at FROM delegations +WHERE did_id = ? AND resource = ? AND status = 'active' +ORDER BY depth, created_at +` + +type ListDelegationsForResourceParams struct { + DidID int64 `json:"did_id"` + Resource string `json:"resource"` +} + +func (q *Queries) ListDelegationsForResource(ctx context.Context, arg ListDelegationsForResourceParams) ([]Delegation, error) { + rows, err := q.db.QueryContext(ctx, listDelegationsForResource, arg.DidID, arg.Resource) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Delegation{} + for rows.Next() { + var i Delegation + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.UcanID, + &i.Delegator, + &i.Delegate, + &i.Resource, + &i.Action, + &i.Caveats, + &i.ParentID, + &i.Depth, + &i.Status, + &i.CreatedAt, + &i.ExpiresAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listGrantsByDID = `-- name: ListGrantsByDID :many + +SELECT g.id, g.did_id, g.service_id, g.ucan_id, g.scopes, g.accounts, g.status, g.granted_at, g.last_used, g.expires_at, s.name as service_name, s.origin as service_origin, s.logo_url as service_logo +FROM grants g +JOIN services s ON g.service_id = s.id +WHERE g.did_id = ? AND g.status = 'active' +ORDER BY g.last_used DESC NULLS LAST +` + +type ListGrantsByDIDRow struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + ServiceID int64 `json:"service_id"` + UcanID *int64 `json:"ucan_id"` + Scopes json.RawMessage `json:"scopes"` + Accounts json.RawMessage `json:"accounts"` + Status string `json:"status"` + GrantedAt string `json:"granted_at"` + LastUsed *string `json:"last_used"` + ExpiresAt *string `json:"expires_at"` + ServiceName string `json:"service_name"` + ServiceOrigin string `json:"service_origin"` + ServiceLogo *string `json:"service_logo"` +} + +// ============================================================================= +// GRANT QUERIES +// ============================================================================= +func (q *Queries) ListGrantsByDID(ctx context.Context, didID int64) ([]ListGrantsByDIDRow, error) { + rows, err := q.db.QueryContext(ctx, listGrantsByDID, didID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListGrantsByDIDRow{} + for rows.Next() { + var i ListGrantsByDIDRow + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.ServiceID, + &i.UcanID, + &i.Scopes, + &i.Accounts, + &i.Status, + &i.GrantedAt, + &i.LastUsed, + &i.ExpiresAt, + &i.ServiceName, + &i.ServiceOrigin, + &i.ServiceLogo, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listKeySharesByDID = `-- name: ListKeySharesByDID :many + +SELECT id, did_id, share_id, key_id, party_index, threshold, total_parties, curve, share_data, public_key, chain_code, derivation_path, status, created_at, rotated_at FROM key_shares WHERE did_id = ? AND status = 'active' ORDER BY created_at +` + +// ============================================================================= +// KEY SHARE QUERIES +// ============================================================================= +func (q *Queries) ListKeySharesByDID(ctx context.Context, didID int64) ([]KeyShare, error) { + rows, err := q.db.QueryContext(ctx, listKeySharesByDID, didID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []KeyShare{} + for rows.Next() { + var i KeyShare + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.ShareID, + &i.KeyID, + &i.PartyIndex, + &i.Threshold, + &i.TotalParties, + &i.Curve, + &i.ShareData, + &i.PublicKey, + &i.ChainCode, + &i.DerivationPath, + &i.Status, + &i.CreatedAt, + &i.RotatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listSessionsByDID = `-- name: ListSessionsByDID :many + +SELECT s.id, s.did_id, s.credential_id, s.session_id, s.device_info, s.is_current, s.last_activity, s.expires_at, s.created_at, c.device_name, c.authenticator +FROM sessions s +JOIN credentials c ON s.credential_id = c.id +WHERE s.did_id = ? AND s.expires_at > datetime('now') +ORDER BY s.last_activity DESC +` + +type ListSessionsByDIDRow struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + CredentialID int64 `json:"credential_id"` + SessionID string `json:"session_id"` + DeviceInfo json.RawMessage `json:"device_info"` + IsCurrent int64 `json:"is_current"` + LastActivity string `json:"last_activity"` + ExpiresAt string `json:"expires_at"` + CreatedAt string `json:"created_at"` + DeviceName string `json:"device_name"` + Authenticator *string `json:"authenticator"` +} + +// ============================================================================= +// SESSION QUERIES +// ============================================================================= +func (q *Queries) ListSessionsByDID(ctx context.Context, didID int64) ([]ListSessionsByDIDRow, error) { + rows, err := q.db.QueryContext(ctx, listSessionsByDID, didID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListSessionsByDIDRow{} + for rows.Next() { + var i ListSessionsByDIDRow + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.CredentialID, + &i.SessionID, + &i.DeviceInfo, + &i.IsCurrent, + &i.LastActivity, + &i.ExpiresAt, + &i.CreatedAt, + &i.DeviceName, + &i.Authenticator, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listSyncCheckpoints = `-- name: ListSyncCheckpoints :many +SELECT id, did_id, resource_type, last_block, last_tx_hash, last_synced FROM sync_checkpoints WHERE did_id = ? +` + +func (q *Queries) ListSyncCheckpoints(ctx context.Context, didID int64) ([]SyncCheckpoint, error) { + rows, err := q.db.QueryContext(ctx, listSyncCheckpoints, didID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []SyncCheckpoint{} + for rows.Next() { + var i SyncCheckpoint + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.ResourceType, + &i.LastBlock, + &i.LastTxHash, + &i.LastSynced, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listUCANsByAudience = `-- name: ListUCANsByAudience :many +SELECT id, did_id, cid, issuer, audience, subject, capabilities, proof_chain, not_before, expires_at, nonce, facts, signature, raw_token, is_revoked, created_at FROM ucan_tokens +WHERE audience = ? AND is_revoked = 0 AND expires_at > datetime('now') +ORDER BY created_at DESC +` + +func (q *Queries) ListUCANsByAudience(ctx context.Context, audience string) ([]UcanToken, error) { + rows, err := q.db.QueryContext(ctx, listUCANsByAudience, audience) + if err != nil { + return nil, err + } + defer rows.Close() + items := []UcanToken{} + for rows.Next() { + var i UcanToken + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Issuer, + &i.Audience, + &i.Subject, + &i.Capabilities, + &i.ProofChain, + &i.NotBefore, + &i.ExpiresAt, + &i.Nonce, + &i.Facts, + &i.Signature, + &i.RawToken, + &i.IsRevoked, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listUCANsByDID = `-- name: ListUCANsByDID :many + +SELECT id, did_id, cid, issuer, audience, subject, capabilities, proof_chain, not_before, expires_at, nonce, facts, signature, raw_token, is_revoked, created_at FROM ucan_tokens +WHERE did_id = ? AND is_revoked = 0 AND expires_at > datetime('now') +ORDER BY created_at DESC +` + +// ============================================================================= +// UCAN TOKEN QUERIES +// ============================================================================= +func (q *Queries) ListUCANsByDID(ctx context.Context, didID int64) ([]UcanToken, error) { + rows, err := q.db.QueryContext(ctx, listUCANsByDID, didID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []UcanToken{} + for rows.Next() { + var i UcanToken + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.Cid, + &i.Issuer, + &i.Audience, + &i.Subject, + &i.Capabilities, + &i.ProofChain, + &i.NotBefore, + &i.ExpiresAt, + &i.Nonce, + &i.Facts, + &i.Signature, + &i.RawToken, + &i.IsRevoked, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listVerificationMethods = `-- name: ListVerificationMethods :many + +SELECT id, did_id, method_id, method_type, controller, public_key, purpose, created_at FROM verification_methods WHERE did_id = ? ORDER BY created_at +` + +// ============================================================================= +// VERIFICATION METHOD QUERIES +// ============================================================================= +func (q *Queries) ListVerificationMethods(ctx context.Context, didID int64) ([]VerificationMethod, error) { + rows, err := q.db.QueryContext(ctx, listVerificationMethods, didID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []VerificationMethod{} + for rows.Next() { + var i VerificationMethod + if err := rows.Scan( + &i.ID, + &i.DidID, + &i.MethodID, + &i.MethodType, + &i.Controller, + &i.PublicKey, + &i.Purpose, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listVerifiedServices = `-- name: ListVerifiedServices :many +SELECT id, origin, name, description, logo_url, did, is_verified, metadata, created_at FROM services WHERE is_verified = 1 ORDER BY name +` + +func (q *Queries) ListVerifiedServices(ctx context.Context) ([]Service, error) { + rows, err := q.db.QueryContext(ctx, listVerifiedServices) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Service{} + for rows.Next() { + var i Service + if err := rows.Scan( + &i.ID, + &i.Origin, + &i.Name, + &i.Description, + &i.LogoUrl, + &i.Did, + &i.IsVerified, + &i.Metadata, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const reactivateGrant = `-- name: ReactivateGrant :exec +UPDATE grants SET status = 'active' WHERE id = ? AND status = 'suspended' +` + +func (q *Queries) ReactivateGrant(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, reactivateGrant, id) + return err +} + +const renameCredential = `-- name: RenameCredential :exec +UPDATE credentials SET device_name = ? WHERE id = ? +` + +type RenameCredentialParams struct { + DeviceName string `json:"device_name"` + ID int64 `json:"id"` +} + +func (q *Queries) RenameCredential(ctx context.Context, arg RenameCredentialParams) error { + _, err := q.db.ExecContext(ctx, renameCredential, arg.DeviceName, arg.ID) + return err +} + +const revokeDelegation = `-- name: RevokeDelegation :exec +UPDATE delegations SET status = 'revoked' WHERE id = ? +` + +func (q *Queries) RevokeDelegation(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, revokeDelegation, id) + return err +} + +const revokeDelegationChain = `-- name: RevokeDelegationChain :exec +UPDATE delegations SET status = 'revoked' WHERE id = ? OR parent_id = ? +` + +type RevokeDelegationChainParams struct { + ID int64 `json:"id"` + ParentID *int64 `json:"parent_id"` +} + +func (q *Queries) RevokeDelegationChain(ctx context.Context, arg RevokeDelegationChainParams) error { + _, err := q.db.ExecContext(ctx, revokeDelegationChain, arg.ID, arg.ParentID) + return err +} + +const revokeGrant = `-- name: RevokeGrant :exec +UPDATE grants SET status = 'revoked' WHERE id = ? +` + +func (q *Queries) RevokeGrant(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, revokeGrant, id) + return err +} + +const revokeUCAN = `-- name: RevokeUCAN :exec +UPDATE ucan_tokens SET is_revoked = 1 WHERE cid = ? +` + +func (q *Queries) RevokeUCAN(ctx context.Context, cid string) error { + _, err := q.db.ExecContext(ctx, revokeUCAN, cid) + return err +} + +const rotateKeyShare = `-- name: RotateKeyShare :exec +UPDATE key_shares +SET status = 'rotating', rotated_at = datetime('now') +WHERE id = ? +` + +func (q *Queries) RotateKeyShare(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, rotateKeyShare, id) + return err +} + +const setCurrentSession = `-- name: SetCurrentSession :exec +UPDATE sessions +SET is_current = CASE WHEN id = ? THEN 1 ELSE 0 END +WHERE did_id = ? +` + +type SetCurrentSessionParams struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` +} + +func (q *Queries) SetCurrentSession(ctx context.Context, arg SetCurrentSessionParams) error { + _, err := q.db.ExecContext(ctx, setCurrentSession, arg.ID, arg.DidID) + return err +} + +const setDefaultAccount = `-- name: SetDefaultAccount :exec +UPDATE accounts +SET is_default = CASE WHEN id = ? THEN 1 ELSE 0 END +WHERE did_id = ? AND chain_id = ? +` + +type SetDefaultAccountParams struct { + ID int64 `json:"id"` + DidID int64 `json:"did_id"` + ChainID string `json:"chain_id"` +} + +func (q *Queries) SetDefaultAccount(ctx context.Context, arg SetDefaultAccountParams) error { + _, err := q.db.ExecContext(ctx, setDefaultAccount, arg.ID, arg.DidID, arg.ChainID) + return err +} + +const suspendGrant = `-- name: SuspendGrant :exec +UPDATE grants SET status = 'suspended' WHERE id = ? +` + +func (q *Queries) SuspendGrant(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, suspendGrant, id) + return err +} + +const updateAccountLabel = `-- name: UpdateAccountLabel :exec +UPDATE accounts SET label = ? WHERE id = ? +` + +type UpdateAccountLabelParams struct { + Label *string `json:"label"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateAccountLabel(ctx context.Context, arg UpdateAccountLabelParams) error { + _, err := q.db.ExecContext(ctx, updateAccountLabel, arg.Label, arg.ID) + return err +} + +const updateCredentialCounter = `-- name: UpdateCredentialCounter :exec +UPDATE credentials +SET sign_count = ?, last_used = datetime('now') +WHERE id = ? +` + +type UpdateCredentialCounterParams struct { + SignCount int64 `json:"sign_count"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateCredentialCounter(ctx context.Context, arg UpdateCredentialCounterParams) error { + _, err := q.db.ExecContext(ctx, updateCredentialCounter, arg.SignCount, arg.ID) + return err +} + +const updateDIDDocument = `-- name: UpdateDIDDocument :exec +UPDATE did_documents +SET document = ?, sequence = ?, last_synced = datetime('now') +WHERE id = ? +` + +type UpdateDIDDocumentParams struct { + Document json.RawMessage `json:"document"` + Sequence int64 `json:"sequence"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateDIDDocument(ctx context.Context, arg UpdateDIDDocumentParams) error { + _, err := q.db.ExecContext(ctx, updateDIDDocument, arg.Document, arg.Sequence, arg.ID) + return err +} + +const updateGrantLastUsed = `-- name: UpdateGrantLastUsed :exec +UPDATE grants SET last_used = datetime('now') WHERE id = ? +` + +func (q *Queries) UpdateGrantLastUsed(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, updateGrantLastUsed, id) + return err +} + +const updateGrantScopes = `-- name: UpdateGrantScopes :exec +UPDATE grants SET scopes = ?, accounts = ? WHERE id = ? +` + +type UpdateGrantScopesParams struct { + Scopes json.RawMessage `json:"scopes"` + Accounts json.RawMessage `json:"accounts"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateGrantScopes(ctx context.Context, arg UpdateGrantScopesParams) error { + _, err := q.db.ExecContext(ctx, updateGrantScopes, arg.Scopes, arg.Accounts, arg.ID) + return err +} + +const updateService = `-- name: UpdateService :exec +UPDATE services +SET name = ?, description = ?, logo_url = ?, metadata = ? +WHERE id = ? +` + +type UpdateServiceParams struct { + Name string `json:"name"` + Description *string `json:"description"` + LogoUrl *string `json:"logo_url"` + Metadata json.RawMessage `json:"metadata"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateService(ctx context.Context, arg UpdateServiceParams) error { + _, err := q.db.ExecContext(ctx, updateService, + arg.Name, + arg.Description, + arg.LogoUrl, + arg.Metadata, + arg.ID, + ) + return err +} + +const updateSessionActivity = `-- name: UpdateSessionActivity :exec +UPDATE sessions SET last_activity = datetime('now') WHERE id = ? +` + +func (q *Queries) UpdateSessionActivity(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, updateSessionActivity, id) + return err +} + +const upsertSyncCheckpoint = `-- name: UpsertSyncCheckpoint :exec +INSERT INTO sync_checkpoints (did_id, resource_type, last_block, last_tx_hash) +VALUES (?, ?, ?, ?) +ON CONFLICT(did_id, resource_type) DO UPDATE SET + last_block = excluded.last_block, + last_tx_hash = excluded.last_tx_hash, + last_synced = datetime('now') +` + +type UpsertSyncCheckpointParams struct { + DidID int64 `json:"did_id"` + ResourceType string `json:"resource_type"` + LastBlock int64 `json:"last_block"` + LastTxHash *string `json:"last_tx_hash"` +} + +func (q *Queries) UpsertSyncCheckpoint(ctx context.Context, arg UpsertSyncCheckpointParams) error { + _, err := q.db.ExecContext(ctx, upsertSyncCheckpoint, + arg.DidID, + arg.ResourceType, + arg.LastBlock, + arg.LastTxHash, + ) + return err +} diff --git a/internal/migrations/embed.go b/internal/migrations/embed.go new file mode 100644 index 0000000..682eb47 --- /dev/null +++ b/internal/migrations/embed.go @@ -0,0 +1,12 @@ +// Package migrations contains migration scripts for the database schema. +package migrations + +import ( + _ "embed" +) + +//go:embed schema.sql +var SchemaSQL string + +//go:embed query.sql +var QuerySQL string diff --git a/internal/migrations/query.sql b/internal/migrations/query.sql new file mode 100644 index 0000000..43caf05 --- /dev/null +++ b/internal/migrations/query.sql @@ -0,0 +1,327 @@ +-- ============================================================================= +-- DID DOCUMENT QUERIES +-- ============================================================================= + +-- name: GetDIDByDID :one +SELECT * FROM did_documents WHERE did = ? LIMIT 1; + +-- name: GetDIDByID :one +SELECT * FROM did_documents WHERE id = ? LIMIT 1; + +-- name: CreateDID :one +INSERT INTO did_documents (did, controller, document, sequence) +VALUES (?, ?, ?, ?) +RETURNING *; + +-- name: UpdateDIDDocument :exec +UPDATE did_documents +SET document = ?, sequence = ?, last_synced = datetime('now') +WHERE id = ?; + +-- name: ListAllDIDs :many +SELECT * FROM did_documents ORDER BY created_at DESC; + +-- ============================================================================= +-- VERIFICATION METHOD QUERIES +-- ============================================================================= + +-- name: ListVerificationMethods :many +SELECT * FROM verification_methods WHERE did_id = ? ORDER BY created_at; + +-- name: GetVerificationMethod :one +SELECT * FROM verification_methods WHERE did_id = ? AND method_id = ? LIMIT 1; + +-- name: CreateVerificationMethod :one +INSERT INTO verification_methods (did_id, method_id, method_type, controller, public_key, purpose) +VALUES (?, ?, ?, ?, ?, ?) +RETURNING *; + +-- name: DeleteVerificationMethod :exec +DELETE FROM verification_methods WHERE id = ?; + +-- ============================================================================= +-- CREDENTIAL QUERIES +-- ============================================================================= + +-- name: ListCredentialsByDID :many +SELECT * FROM credentials WHERE did_id = ? ORDER BY last_used DESC; + +-- name: GetCredentialByID :one +SELECT * FROM credentials WHERE credential_id = ? LIMIT 1; + +-- name: CreateCredential :one +INSERT INTO credentials ( + did_id, credential_id, public_key, public_key_alg, aaguid, + transports, device_name, device_type, authenticator, is_discoverable, backed_up +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +RETURNING *; + +-- name: UpdateCredentialCounter :exec +UPDATE credentials +SET sign_count = ?, last_used = datetime('now') +WHERE id = ?; + +-- name: RenameCredential :exec +UPDATE credentials SET device_name = ? WHERE id = ?; + +-- name: DeleteCredential :exec +DELETE FROM credentials WHERE id = ? AND did_id = ?; + +-- name: CountCredentialsByDID :one +SELECT COUNT(*) FROM credentials WHERE did_id = ?; + +-- ============================================================================= +-- KEY SHARE QUERIES +-- ============================================================================= + +-- name: ListKeySharesByDID :many +SELECT * FROM key_shares WHERE did_id = ? AND status = 'active' ORDER BY created_at; + +-- name: GetKeyShareByID :one +SELECT * FROM key_shares WHERE share_id = ? LIMIT 1; + +-- name: GetKeyShareByKeyID :one +SELECT * FROM key_shares WHERE did_id = ? AND key_id = ? AND status = 'active' LIMIT 1; + +-- name: CreateKeyShare :one +INSERT INTO key_shares ( + did_id, share_id, key_id, party_index, threshold, total_parties, + curve, share_data, public_key, chain_code, derivation_path +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +RETURNING *; + +-- name: RotateKeyShare :exec +UPDATE key_shares +SET status = 'rotating', rotated_at = datetime('now') +WHERE id = ?; + +-- name: ArchiveKeyShare :exec +UPDATE key_shares SET status = 'archived' WHERE id = ?; + +-- name: DeleteKeyShare :exec +DELETE FROM key_shares WHERE id = ? AND did_id = ?; + +-- ============================================================================= +-- ACCOUNT QUERIES +-- ============================================================================= + +-- name: ListAccountsByDID :many +SELECT a.*, k.public_key, k.curve +FROM accounts a +JOIN key_shares k ON a.key_share_id = k.id +WHERE a.did_id = ? +ORDER BY a.is_default DESC, a.created_at; + +-- name: ListAccountsByChain :many +SELECT * FROM accounts WHERE did_id = ? AND chain_id = ? ORDER BY account_index, address_index; + +-- name: GetAccountByAddress :one +SELECT * FROM accounts WHERE address = ? LIMIT 1; + +-- name: GetDefaultAccount :one +SELECT * FROM accounts WHERE did_id = ? AND chain_id = ? AND is_default = 1 LIMIT 1; + +-- name: CreateAccount :one +INSERT INTO accounts (did_id, key_share_id, address, chain_id, coin_type, account_index, address_index, label) +VALUES (?, ?, ?, ?, ?, ?, ?, ?) +RETURNING *; + +-- name: SetDefaultAccount :exec +UPDATE accounts +SET is_default = CASE WHEN id = ? THEN 1 ELSE 0 END +WHERE did_id = ? AND chain_id = ?; + +-- name: UpdateAccountLabel :exec +UPDATE accounts SET label = ? WHERE id = ?; + +-- name: DeleteAccount :exec +DELETE FROM accounts WHERE id = ? AND did_id = ?; + +-- ============================================================================= +-- UCAN TOKEN QUERIES +-- ============================================================================= + +-- name: ListUCANsByDID :many +SELECT * FROM ucan_tokens +WHERE did_id = ? AND is_revoked = 0 AND expires_at > datetime('now') +ORDER BY created_at DESC; + +-- name: ListUCANsByAudience :many +SELECT * FROM ucan_tokens +WHERE audience = ? AND is_revoked = 0 AND expires_at > datetime('now') +ORDER BY created_at DESC; + +-- name: GetUCANByCID :one +SELECT * FROM ucan_tokens WHERE cid = ? LIMIT 1; + +-- name: CreateUCAN :one +INSERT INTO ucan_tokens ( + did_id, cid, issuer, audience, subject, capabilities, + proof_chain, not_before, expires_at, nonce, facts, signature, raw_token +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +RETURNING *; + +-- name: RevokeUCAN :exec +UPDATE ucan_tokens SET is_revoked = 1 WHERE cid = ?; + +-- name: IsUCANRevoked :one +SELECT EXISTS(SELECT 1 FROM ucan_revocations WHERE ucan_cid = ?) as revoked; + +-- name: CreateRevocation :exec +INSERT INTO ucan_revocations (ucan_cid, revoked_by, reason) +VALUES (?, ?, ?); + +-- name: CleanExpiredUCANs :exec +DELETE FROM ucan_tokens WHERE expires_at < datetime('now', '-30 days'); + +-- ============================================================================= +-- SESSION QUERIES +-- ============================================================================= + +-- name: ListSessionsByDID :many +SELECT s.*, c.device_name, c.authenticator +FROM sessions s +JOIN credentials c ON s.credential_id = c.id +WHERE s.did_id = ? AND s.expires_at > datetime('now') +ORDER BY s.last_activity DESC; + +-- name: GetSessionByID :one +SELECT * FROM sessions WHERE session_id = ? LIMIT 1; + +-- name: GetCurrentSession :one +SELECT * FROM sessions WHERE did_id = ? AND is_current = 1 LIMIT 1; + +-- name: CreateSession :one +INSERT INTO sessions (did_id, credential_id, session_id, device_info, is_current, expires_at) +VALUES (?, ?, ?, ?, ?, ?) +RETURNING *; + +-- name: UpdateSessionActivity :exec +UPDATE sessions SET last_activity = datetime('now') WHERE id = ?; + +-- name: SetCurrentSession :exec +UPDATE sessions +SET is_current = CASE WHEN id = ? THEN 1 ELSE 0 END +WHERE did_id = ?; + +-- name: DeleteSession :exec +DELETE FROM sessions WHERE id = ?; + +-- name: DeleteExpiredSessions :exec +DELETE FROM sessions WHERE expires_at < datetime('now'); + +-- ============================================================================= +-- SERVICE QUERIES +-- ============================================================================= + +-- name: GetServiceByOrigin :one +SELECT * FROM services WHERE origin = ? LIMIT 1; + +-- name: GetServiceByID :one +SELECT * FROM services WHERE id = ? LIMIT 1; + +-- name: CreateService :one +INSERT INTO services (origin, name, description, logo_url, did, is_verified, metadata) +VALUES (?, ?, ?, ?, ?, ?, ?) +RETURNING *; + +-- name: UpdateService :exec +UPDATE services +SET name = ?, description = ?, logo_url = ?, metadata = ? +WHERE id = ?; + +-- name: ListVerifiedServices :many +SELECT * FROM services WHERE is_verified = 1 ORDER BY name; + +-- ============================================================================= +-- GRANT QUERIES +-- ============================================================================= + +-- name: ListGrantsByDID :many +SELECT g.*, s.name as service_name, s.origin as service_origin, s.logo_url as service_logo +FROM grants g +JOIN services s ON g.service_id = s.id +WHERE g.did_id = ? AND g.status = 'active' +ORDER BY g.last_used DESC NULLS LAST; + +-- name: GetGrantByService :one +SELECT * FROM grants WHERE did_id = ? AND service_id = ? LIMIT 1; + +-- name: CreateGrant :one +INSERT INTO grants (did_id, service_id, ucan_id, scopes, accounts, expires_at) +VALUES (?, ?, ?, ?, ?, ?) +RETURNING *; + +-- name: UpdateGrantScopes :exec +UPDATE grants SET scopes = ?, accounts = ? WHERE id = ?; + +-- name: UpdateGrantLastUsed :exec +UPDATE grants SET last_used = datetime('now') WHERE id = ?; + +-- name: RevokeGrant :exec +UPDATE grants SET status = 'revoked' WHERE id = ?; + +-- name: SuspendGrant :exec +UPDATE grants SET status = 'suspended' WHERE id = ?; + +-- name: ReactivateGrant :exec +UPDATE grants SET status = 'active' WHERE id = ? AND status = 'suspended'; + +-- name: CountActiveGrants :one +SELECT COUNT(*) FROM grants WHERE did_id = ? AND status = 'active'; + +-- ============================================================================= +-- DELEGATION QUERIES +-- ============================================================================= + +-- name: ListDelegationsByDelegator :many +SELECT * FROM delegations +WHERE delegator = ? AND status = 'active' +ORDER BY created_at DESC; + +-- name: ListDelegationsByDelegate :many +SELECT * FROM delegations +WHERE delegate = ? AND status = 'active' AND (expires_at IS NULL OR expires_at > datetime('now')) +ORDER BY created_at DESC; + +-- name: ListDelegationsForResource :many +SELECT * FROM delegations +WHERE did_id = ? AND resource = ? AND status = 'active' +ORDER BY depth, created_at; + +-- name: GetDelegationChain :many +SELECT * FROM delegations WHERE id = ? OR parent_id = ? ORDER BY depth DESC; + +-- name: CreateDelegation :one +INSERT INTO delegations ( + did_id, ucan_id, delegator, delegate, resource, action, caveats, parent_id, depth, expires_at +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +RETURNING *; + +-- name: RevokeDelegation :exec +UPDATE delegations SET status = 'revoked' WHERE id = ?; + +-- name: RevokeDelegationChain :exec +UPDATE delegations SET status = 'revoked' WHERE id = ? OR parent_id = ?; + +-- ============================================================================= +-- SYNC QUERIES +-- ============================================================================= + +-- name: GetSyncCheckpoint :one +SELECT * FROM sync_checkpoints WHERE did_id = ? AND resource_type = ? LIMIT 1; + +-- name: UpsertSyncCheckpoint :exec +INSERT INTO sync_checkpoints (did_id, resource_type, last_block, last_tx_hash) +VALUES (?, ?, ?, ?) +ON CONFLICT(did_id, resource_type) DO UPDATE SET + last_block = excluded.last_block, + last_tx_hash = excluded.last_tx_hash, + last_synced = datetime('now'); + +-- name: ListSyncCheckpoints :many +SELECT * FROM sync_checkpoints WHERE did_id = ?; diff --git a/internal/migrations/schema.sql b/internal/migrations/schema.sql new file mode 100644 index 0000000..cb78c4b --- /dev/null +++ b/internal/migrations/schema.sql @@ -0,0 +1,264 @@ +-- ============================================================================= +-- NEBULA KEY ENCLAVE SCHEMA +-- Encrypted SQLite database for sensitive wallet data +-- ============================================================================= + +PRAGMA foreign_keys = ON; + +-- ============================================================================= +-- IDENTITY +-- ============================================================================= + +-- DID Documents: Local cache of Sonr DID state +CREATE TABLE IF NOT EXISTS did_documents ( + id INTEGER PRIMARY KEY, + did TEXT NOT NULL UNIQUE, -- did:sonr:abc123... + controller TEXT NOT NULL, -- Controller DID + document TEXT NOT NULL, -- Full DID Document (JSON) + sequence INTEGER NOT NULL DEFAULT 0, -- On-chain sequence number + last_synced TEXT NOT NULL DEFAULT (datetime('now')), + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX idx_did_documents_did ON did_documents(did); + +-- Verification Methods: Keys associated with DID +CREATE TABLE IF NOT EXISTS verification_methods ( + id INTEGER PRIMARY KEY, + did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE, + method_id TEXT NOT NULL, -- did:sonr:abc#key-1 + method_type TEXT NOT NULL, -- Ed25519VerificationKey2020, etc. + controller TEXT NOT NULL, + public_key TEXT NOT NULL, -- Base64 encoded public key + purpose TEXT NOT NULL DEFAULT 'authentication', -- authentication, assertion, keyAgreement, capabilityInvocation, capabilityDelegation + created_at TEXT NOT NULL DEFAULT (datetime('now')), + UNIQUE(did_id, method_id) +); + +CREATE INDEX idx_verification_methods_did_id ON verification_methods(did_id); + +-- ============================================================================= +-- WEBAUTHN CREDENTIALS +-- ============================================================================= + +-- Credentials: WebAuthn credential storage +CREATE TABLE IF NOT EXISTS credentials ( + id INTEGER PRIMARY KEY, + did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE, + credential_id TEXT NOT NULL UNIQUE, -- WebAuthn credential ID (base64) + public_key TEXT NOT NULL, -- COSE public key (base64) + public_key_alg INTEGER NOT NULL, -- COSE algorithm (-7 = ES256, -257 = RS256) + aaguid TEXT, -- Authenticator AAGUID + sign_count INTEGER NOT NULL DEFAULT 0, -- Signature counter + transports TEXT DEFAULT '[]', -- JSON array: ["internal", "usb", "nfc", "ble"] + device_name TEXT NOT NULL, -- User-assigned name + device_type TEXT NOT NULL DEFAULT 'platform', -- platform, cross-platform + authenticator TEXT, -- Touch ID, Face ID, Windows Hello, YubiKey + is_discoverable INTEGER NOT NULL DEFAULT 1, -- Resident key / passkey + backed_up INTEGER NOT NULL DEFAULT 0, -- Credential backed up (BE flag) + created_at TEXT NOT NULL DEFAULT (datetime('now')), + last_used TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX idx_credentials_did_id ON credentials(did_id); +CREATE INDEX idx_credentials_credential_id ON credentials(credential_id); + +-- ============================================================================= +-- MPC KEY SHARES +-- ============================================================================= + +-- Key Shares: MPC/TSS key share storage +CREATE TABLE IF NOT EXISTS key_shares ( + id INTEGER PRIMARY KEY, + did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE, + share_id TEXT NOT NULL UNIQUE, -- Unique identifier for this share + key_id TEXT NOT NULL, -- Identifier for the full key (shared across parties) + party_index INTEGER NOT NULL, -- This party's index (1, 2, 3...) + threshold INTEGER NOT NULL, -- Minimum shares needed to sign + total_parties INTEGER NOT NULL, -- Total number of parties + curve TEXT NOT NULL DEFAULT 'secp256k1', -- secp256k1, ed25519 + share_data TEXT NOT NULL, -- Encrypted key share (base64) + public_key TEXT NOT NULL, -- Full public key (base64) + chain_code TEXT, -- BIP32 chain code for derivation + derivation_path TEXT, -- BIP44 path: m/44'/60'/0'/0 + status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'rotating', 'archived')), + created_at TEXT NOT NULL DEFAULT (datetime('now')), + rotated_at TEXT, + UNIQUE(did_id, key_id, party_index) +); + +CREATE INDEX idx_key_shares_did_id ON key_shares(did_id); +CREATE INDEX idx_key_shares_key_id ON key_shares(key_id); + +-- Derived Accounts: Wallet accounts derived from key shares +CREATE TABLE IF NOT EXISTS accounts ( + id INTEGER PRIMARY KEY, + did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE, + key_share_id INTEGER NOT NULL REFERENCES key_shares(id) ON DELETE CASCADE, + address TEXT NOT NULL, -- Derived address + chain_id TEXT NOT NULL, -- sonr-mainnet-1, ethereum, etc. + coin_type INTEGER NOT NULL, -- BIP44 coin type (118=cosmos, 60=eth) + account_index INTEGER NOT NULL DEFAULT 0, -- BIP44 account index + address_index INTEGER NOT NULL DEFAULT 0, -- BIP44 address index + label TEXT DEFAULT '', -- User-assigned label + is_default INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + UNIQUE(key_share_id, chain_id, account_index, address_index) +); + +CREATE INDEX idx_accounts_did_id ON accounts(did_id); +CREATE INDEX idx_accounts_address ON accounts(address); +CREATE INDEX idx_accounts_chain_id ON accounts(chain_id); + +-- ============================================================================= +-- UCAN AUTHORIZATION +-- ============================================================================= + +-- UCAN Tokens: Capability authorization tokens +CREATE TABLE IF NOT EXISTS ucan_tokens ( + id INTEGER PRIMARY KEY, + did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE, + cid TEXT NOT NULL UNIQUE, -- Content ID of UCAN (for dedup) + issuer TEXT NOT NULL, -- iss: DID of issuer + audience TEXT NOT NULL, -- aud: DID of recipient + subject TEXT, -- sub: DID token is about (optional) + capabilities TEXT NOT NULL, -- JSON array of capabilities + proof_chain TEXT DEFAULT '[]', -- JSON array of parent UCAN CIDs + not_before TEXT, -- nbf: validity start + expires_at TEXT NOT NULL, -- exp: expiration time + nonce TEXT, -- Replay protection + facts TEXT DEFAULT '{}', -- Additional facts (JSON) + signature TEXT NOT NULL, -- Base64 encoded signature + raw_token TEXT NOT NULL, -- Full encoded UCAN token + is_revoked INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX idx_ucan_tokens_did_id ON ucan_tokens(did_id); +CREATE INDEX idx_ucan_tokens_issuer ON ucan_tokens(issuer); +CREATE INDEX idx_ucan_tokens_audience ON ucan_tokens(audience); +CREATE INDEX idx_ucan_tokens_expires_at ON ucan_tokens(expires_at); + +-- UCAN Revocations: Revoked UCAN tokens +CREATE TABLE IF NOT EXISTS ucan_revocations ( + id INTEGER PRIMARY KEY, + ucan_cid TEXT NOT NULL UNIQUE, -- CID of revoked UCAN + revoked_by TEXT NOT NULL, -- DID that revoked + reason TEXT, + revoked_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX idx_ucan_revocations_cid ON ucan_revocations(ucan_cid); + +-- ============================================================================= +-- DEVICE SESSIONS +-- ============================================================================= + +-- Sessions: Active device sessions +CREATE TABLE IF NOT EXISTS sessions ( + id INTEGER PRIMARY KEY, + did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE, + credential_id INTEGER NOT NULL REFERENCES credentials(id) ON DELETE CASCADE, + session_id TEXT NOT NULL UNIQUE, -- Opaque session identifier + device_info TEXT DEFAULT '{}', -- JSON: {browser, os, ip, etc.} + is_current INTEGER NOT NULL DEFAULT 0, -- Is this the current session + last_activity TEXT NOT NULL DEFAULT (datetime('now')), + expires_at TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX idx_sessions_did_id ON sessions(did_id); +CREATE INDEX idx_sessions_session_id ON sessions(session_id); +CREATE INDEX idx_sessions_expires_at ON sessions(expires_at); + +-- ============================================================================= +-- SERVICE CONNECTIONS +-- ============================================================================= + +-- Services: Connected third-party services/dApps +CREATE TABLE IF NOT EXISTS services ( + id INTEGER PRIMARY KEY, + origin TEXT NOT NULL UNIQUE, -- https://app.example.com + name TEXT NOT NULL, + description TEXT, + logo_url TEXT, + did TEXT, -- Service's DID (if known) + is_verified INTEGER NOT NULL DEFAULT 0, + metadata TEXT DEFAULT '{}', -- Additional service metadata + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX idx_services_origin ON services(origin); + +-- Grants: User grants to services +CREATE TABLE IF NOT EXISTS grants ( + id INTEGER PRIMARY KEY, + did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE, + service_id INTEGER NOT NULL REFERENCES services(id) ON DELETE CASCADE, + ucan_id INTEGER REFERENCES ucan_tokens(id) ON DELETE SET NULL, + scopes TEXT NOT NULL DEFAULT '[]', -- JSON array of granted scopes + accounts TEXT NOT NULL DEFAULT '[]', -- JSON array of account IDs exposed + status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'revoked')), + granted_at TEXT NOT NULL DEFAULT (datetime('now')), + last_used TEXT, + expires_at TEXT, + UNIQUE(did_id, service_id) +); + +CREATE INDEX idx_grants_did_id ON grants(did_id); +CREATE INDEX idx_grants_service_id ON grants(service_id); +CREATE INDEX idx_grants_status ON grants(status); + +-- ============================================================================= +-- CAPABILITY DELEGATIONS +-- ============================================================================= + +-- Delegations: Capability delegation chains +CREATE TABLE IF NOT EXISTS delegations ( + id INTEGER PRIMARY KEY, + did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE, + ucan_id INTEGER NOT NULL REFERENCES ucan_tokens(id) ON DELETE CASCADE, + delegator TEXT NOT NULL, -- DID that delegated + delegate TEXT NOT NULL, -- DID that received delegation + resource TEXT NOT NULL, -- Resource URI (e.g., "sonr://vault/*") + action TEXT NOT NULL, -- Action (e.g., "sign", "read", "write") + caveats TEXT DEFAULT '{}', -- JSON: restrictions/conditions + parent_id INTEGER REFERENCES delegations(id), -- Parent delegation (for chains) + depth INTEGER NOT NULL DEFAULT 0, -- Delegation depth (0 = root) + status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked', 'expired')), + created_at TEXT NOT NULL DEFAULT (datetime('now')), + expires_at TEXT +); + +CREATE INDEX idx_delegations_did_id ON delegations(did_id); +CREATE INDEX idx_delegations_delegator ON delegations(delegator); +CREATE INDEX idx_delegations_delegate ON delegations(delegate); +CREATE INDEX idx_delegations_resource ON delegations(resource); + +-- ============================================================================= +-- SYNC STATE +-- ============================================================================= + +-- Sync Checkpoints: Track sync state with Sonr protocol +CREATE TABLE IF NOT EXISTS sync_checkpoints ( + id INTEGER PRIMARY KEY, + did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE, + resource_type TEXT NOT NULL, -- 'did', 'credentials', 'grants', etc. + last_block INTEGER NOT NULL DEFAULT 0, -- Last synced block height + last_tx_hash TEXT, -- Last processed transaction + last_synced TEXT NOT NULL DEFAULT (datetime('now')), + UNIQUE(did_id, resource_type) +); + +CREATE INDEX idx_sync_checkpoints_did_id ON sync_checkpoints(did_id); + +-- ============================================================================= +-- TRIGGERS +-- ============================================================================= + +CREATE TRIGGER IF NOT EXISTS did_documents_updated_at + AFTER UPDATE ON did_documents + BEGIN + UPDATE did_documents SET updated_at = datetime('now') WHERE id = NEW.id; + END;