refactor(keybase): migrate to UCAN v1.0.0-rc.1 envelope format

This commit is contained in:
2026-01-08 16:37:36 -05:00
parent 1b2a57ca98
commit 4fbfdf1e4d
11 changed files with 1465 additions and 999 deletions

229
TODO.md
View File

@@ -11,7 +11,7 @@ Remaining tasks from [MIGRATION.md](./MIGRATION.md) for the Nebula Key Enclave.
| Generated Code | Complete | `internal/keybase/*.go` |
| Basic Plugin Functions | Complete | `generate`, `load`, `exec`, `query`, `ping` |
| Encryption | Not Started | WebAuthn PRF key derivation needed |
| **UCAN v1.0.0-rc.1** | **CRITICAL** | Go implementation uses deprecated JWT format |
| **UCAN v1.0.0-rc.1** | **In Progress** | Core types, builders, and policies complete. Uses `go-ucan v1.1.0` |
| MPC Key Shares | Not Started | Key share management missing |
| Database Serialization | Incomplete | Export dumps comments only |
@@ -19,114 +19,116 @@ Remaining tasks from [MIGRATION.md](./MIGRATION.md) for the Nebula Key Enclave.
## 1. UCAN v1.0.0-rc.1 Migration (CRITICAL PRIORITY)
> **Breaking Change**: Current Go implementation (`internal/crypto/ucan/`) uses deprecated JWT-based UCAN format. Must migrate to envelope format per v1.0.0-rc.1 spec.
> **Status**: Core implementation complete using `github.com/ucan-wg/go-ucan v1.1.0`. Deprecated JWT-based files deleted. Remaining work is database integration and MPC signing.
### Current State (DEPRECATED - Must Replace)
### Completed Implementation
The following files use the **old JWT-based format** and must be rewritten:
The following files implement UCAN v1.0.0-rc.1 using the official go-ucan library:
| File | Status | Issue |
|------|--------|-------|
| `jwt.go` | DEPRECATED | Uses `github.com/golang-jwt/jwt/v5`, old `can`+`with` format |
| `capability.go` | DEPRECATED | Old Attenuation/Resource/Capability model |
| `verifier.go` | DEPRECATED | JWT parsing, old proof chain format |
| `source.go` | DEPRECATED | JWT token creation with MPC |
| `vault.go` | PARTIAL | VaultCapability needs Policy migration |
| File | Status | Description |
|------|--------|-------------|
| `ucan.go` | ✅ Complete | Type re-exports, Sonr commands, pre-parsed constants |
| `policy.go` | ✅ Complete | PolicyBuilder fluent API, Sonr-specific policy helpers |
| `delegation.go` | ✅ Complete | DelegationBuilder fluent API, Sonr delegation helpers |
| `invocation.go` | ✅ Complete | InvocationBuilder fluent API, Sonr invocation helpers |
| `types.go` | ✅ Complete | ValidationError, Capability, ExecutionResult, Sonr types |
### Reference Implementation (Already Compliant)
### Dependencies Added
These files are already aligned with v1.0.0-rc.1:
- `github.com/ucan-wg/go-ucan v1.1.0` - Official UCAN library
- `github.com/ipld/go-ipld-prime v0.21.0` - IPLD encoding
- `github.com/MetaMask/go-did-it v1.0.0-pre1` - DID handling (indirect)
- `github.com/ipfs/go-cid v0.5.0` - Content addressing (indirect)
- `src/ucan.ts` - TypeScript types with envelope format
- `internal/codec/ucan-schemas.json` - JSON Schema definitions
### Deleted (Deprecated JWT-based)
- ~~`jwt.go`~~ - Removed
- ~~`capability.go`~~ - Removed
- ~~`verifier.go`~~ - Removed
- ~~`source.go`~~ - Removed
- ~~`internal/crypto/mpc/spec/`~~ - Entire directory removed
### 1.1 Core Data Structures
- [ ] Create `internal/crypto/ucan/types.go` - v1.0.0-rc.1 types
- [ ] `DelegationPayload` struct (iss, aud, sub, cmd, pol, nonce, meta, nbf, exp)
- [ ] `InvocationPayload` struct (iss, sub, aud, cmd, args, prf, meta, nonce, exp, iat, cause)
- [ ] `Delegation` type as `[Signature, DelegationSigPayload]` tuple
- [ ] `Invocation` type as `[Signature, InvocationSigPayload]` tuple
- [ ] `Task` struct (sub, cmd, args, nonce)
- [ ] `ReceiptPayload` struct (iss, ran, out, fx, meta, iat)
- [ ] `RevocationPayload` struct
- [x] Create `internal/crypto/ucan/types.go` - v1.0.0-rc.1 types
- [x] Re-export `Delegation` and `Invocation` from go-ucan
- [x] `Task` struct (sub, cmd, args, nonce)
- [x] `ReceiptPayload` struct (iss, ran, out, fx, meta, iat)
- [x] `RevocationPayload` struct
- [x] `ValidationError` with error codes matching TypeScript
- [x] `Capability` struct (sub, cmd, pol)
- [x] `ExecutionResult[T, E]` generic type
- [x] Sonr-specific types: `VaultCapability`, `DIDCapability`, `DWNCapability`
- [ ] Create `internal/crypto/ucan/policy.go` - Policy Language
- [ ] `PolicyStatement` union type
- [ ] `EqualityStatement` - `["==", selector, value]` / `["!=", selector, value]`
- [ ] `InequalityStatement` - `[">", selector, number]` etc.
- [ ] `LikeStatement` - `["like", selector, glob]`
- [ ] `NotStatement` - `["not", statement]`
- [ ] `AndStatement` / `OrStatement` - logical connectives
- [ ] `AllStatement` / `AnyStatement` - quantifiers
- [ ] `Selector` parser (jq-inspired: `.foo`, `.bar[0]`, `.items[-1]`)
- [ ] `GlobPattern` matcher
- [x] Create `internal/crypto/ucan/policy.go` - Policy Language
- [x] `PolicyBuilder` fluent API with all operators
- [x] `Equal`, `NotEqual` - equality statements
- [x] `GreaterThan`, `LessThan`, etc. - inequality statements
- [x] `Like` - glob pattern matching
- [x] `Not`, `And`, `Or` - logical connectives
- [x] `All`, `Any` - quantifiers
- [x] Sonr helpers: `VaultPolicy`, `DIDPolicy`, `ChainPolicy`, `AccountPolicy`
- [ ] Create `internal/crypto/ucan/command.go` - Command types
- [ ] `Command` type with validation (must start with `/`, lowercase, no trailing slash)
- [ ] Standard commands: `/crud/*`, `/msg/*`, `/ucan/revoke`, `/wasm/run`
- [ ] Custom Sonr commands: `/vault/*`, `/did/*`, `/dwn/*`
- [x] Create `internal/crypto/ucan/ucan.go` - Command types
- [x] `Command` type re-exported from go-ucan
- [x] Sonr commands: `/vault/*`, `/did/*`, `/dwn/*`, `/ucan/revoke`
- [x] Pre-parsed command constants: `VaultRead`, `VaultWrite`, `DIDUpdate`, etc.
- [x] `CommandSubsumes()` helper using go-ucan's `Covers()` method
### 1.2 Envelope Format & Encoding
- [ ] Create `internal/crypto/ucan/envelope.go` - Envelope handling
- [ ] `UCANEnvelope[P]` generic type as `[Signature, {h: VarsigHeader} & P]`
- [ ] Encode envelope to CBOR (requires `github.com/fxamacker/cbor/v2`)
- [ ] Decode envelope from CBOR
- [ ] DAG-JSON encoding for interop
- [ ] CID computation (DAG-CBOR codec, SHA-256 multihash, base58btc)
- [x] Envelope handling via go-ucan library
- [x] `ToSealed()` method produces DAG-CBOR bytes + CID
- [x] `ToDagCbor()`, `ToDagJson()` encoding methods
- [x] CID computation handled by go-ucan
- [ ] Create `internal/crypto/ucan/varsig.go` - Varsig v1 headers
- [ ] Varsig header encoding/decoding
- [ ] Algorithm metadata extraction
- [ ] Support Ed25519, P-256, secp256k1
- [x] Varsig support via go-ucan library
- [x] Ed25519, P-256, secp256k1 via `go-did-it/crypto`
### 1.3 Delegation Operations
- [ ] Create `internal/crypto/ucan/delegation.go` - Delegation creation/validation
- [ ] `NewDelegation(issuer, audience, subject, cmd, policy, exp, meta)` builder
- [ ] Sign delegation with issuer private key
- [ ] Validate delegation signature
- [ ] Validate delegation payload (temporal, structural)
- [ ] Extract `Capability` from delegation (sub + cmd + pol)
- [x] Create `internal/crypto/ucan/delegation.go` - Delegation creation/validation
- [x] `DelegationBuilder` fluent API
- [x] `NewDelegation`, `NewRootDelegation`, `NewPowerlineDelegation` re-exports
- [x] `BuildSealed(privKey)` for signing
- [x] Sonr helpers: `NewVaultDelegation`, `NewDIDDelegation`, `NewDWNDelegation`
- [x] Temporal options: `ExpiresAt`, `ExpiresIn`, `NotBefore`, `NotBeforeIn`
### 1.4 Invocation Operations
- [ ] Create `internal/crypto/ucan/invocation.go` - Invocation creation/validation
- [ ] `NewInvocation(issuer, subject, cmd, args, proofs, exp)` builder
- [ ] Sign invocation with invoker private key
- [ ] Validate invocation signature
- [ ] Validate proof chain (CID references to delegations)
- [ ] Evaluate policies against invocation args
- [x] Create `internal/crypto/ucan/invocation.go` - Invocation creation/validation
- [x] `InvocationBuilder` fluent API
- [x] `NewInvocation` re-export
- [x] `BuildSealed(privKey)` for signing
- [x] Proof chain management: `Proof()`, `Proofs()`
- [x] Sonr helpers: `VaultReadInvocation`, `VaultSignInvocation`, `DIDUpdateInvocation`
### 1.5 Policy Evaluation Engine
- [ ] Create `internal/crypto/ucan/eval.go` - Policy evaluation
- [ ] `EvaluatePolicy(policy Policy, args Arguments) (bool, error)`
- [ ] Selector resolution against IPLD data
- [ ] Equality comparison (deep IPLD equality)
- [ ] Numeric comparisons
- [ ] Glob pattern matching for `like` operator
- [ ] Logical connectives (`and`, `or`, `not`)
- [ ] Quantifiers (`all`, `any`) over collections
> Note: go-ucan provides `ExecutionAllowed()` on invocations which validates proofs and evaluates policies.
- [x] Policy evaluation via go-ucan's `invocation.ExecutionAllowed(loader)`
- [ ] Create `internal/crypto/ucan/eval.go` - Additional evaluation helpers (if needed)
- [ ] Custom selector resolution for Sonr-specific args
- [ ] Caching layer for repeated evaluations
### 1.6 Proof Chain Validation
- [ ] Create `internal/crypto/ucan/chain.go` - Chain validation
- [ ] Resolve CID to Delegation (requires delegation store)
- [ ] Validate chain continuity (child.iss == parent.aud)
- [ ] Validate capability attenuation (child.cmd subsumes parent.cmd)
- [ ] Validate policy attenuation (child.pol more restrictive than parent.pol)
- [ ] Validate temporal bounds (child.exp <= parent.exp)
- [ ] Check revocation status for all chain members
> Note: go-ucan handles chain validation internally via `ExecutionAllowed()`.
- [x] Chain validation via go-ucan library
- [ ] Create `internal/crypto/ucan/store.go` - Delegation store
- [ ] Implement `delegation.Loader` interface
- [ ] `GetDelegation(cid.Cid) (*delegation.Token, error)`
- [ ] Cache loaded delegations for performance
### 1.7 Revocation
- [x] `RevocationInvocation()` helper in `invocation.go`
- [ ] Create `internal/crypto/ucan/revocation.go` - Revocation handling
- [ ] `NewRevocation(revoker, delegation_cid)` builder
- [ ] Validate revoker is in delegation's issuer chain
- [ ] Store revocation in database
- [ ] Query revocation status by CID
- [ ] Revocation store implementation
- [ ] `IsRevoked(cid.Cid) (bool, error)` query
- [ ] Integration with chain validation
### 1.8 Database Integration
@@ -141,20 +143,18 @@ These files are already aligned with v1.0.0-rc.1:
- [ ] `InsertInvocation`, `GetInvocationByCID`
- [ ] `InsertRevocation`, `IsRevoked`, `GetRevocationsByDelegation`
### 1.9 Migration from Old Format
### 1.9 MPC Signing Integration
- [ ] Create migration script for existing UCAN data (if any)
- [ ] Remove deprecated files after migration complete:
- [ ] `jwt.go` - Remove entirely
- [ ] `capability.go` - Replace with policy-based capabilities
- [ ] `verifier.go` - Replace with envelope-based verification
- [ ] `source.go` - Replace with envelope-based token creation
- [ ] Create `internal/crypto/ucan/signer.go` - MPC key integration
- [ ] Implement `crypto.PrivateKeySigningBytes` interface for MPC
- [ ] Sign delegations with MPC key shares
- [ ] Sign invocations with MPC key shares
### 1.10 Testing
- [ ] Unit tests for policy evaluation
- [ ] Unit tests for envelope encoding/decoding
- [ ] Unit tests for chain validation
- [ ] Unit tests for builders (DelegationBuilder, InvocationBuilder)
- [ ] Unit tests for policy helpers
- [ ] Unit tests for Sonr-specific invocations
- [ ] Interoperability tests against TypeScript implementation
- [ ] Test vectors from UCAN spec
@@ -478,23 +478,26 @@ These files are already aligned with v1.0.0-rc.1:
## Priority Order
1. **CRITICAL (Spec Compliance)**
- UCAN v1.0.0-rc.1 Migration (Section 1)
- Core data structures (1.1)
- Envelope format (1.2)
- Delegation operations (1.3)
- Policy evaluation (1.5)
1. **CRITICAL (Spec Compliance)** - ✅ Core Complete
- ~~UCAN v1.0.0-rc.1 Migration (Section 1)~~ ✅ Core types, builders, policies done
- ~~Core data structures (1.1)~~ ✅ Using go-ucan v1.1.0
- ~~Envelope format (1.2)~~ ✅ Handled by go-ucan
- ~~Delegation operations (1.3)~~ ✅ DelegationBuilder complete
- ~~Invocation operations (1.4)~~ ✅ InvocationBuilder complete
- Database integration (1.8) - Next priority
- MPC signing integration (1.9) - Next priority
2. **High Priority (Core Functionality)**
- Database Serialization (3.1, 3.2)
- Credential Creation (6.2, 4.7)
- Key Share Actions (4.1)
- Account Actions (4.6)
- UCAN Database Integration (1.8)
3. **Medium Priority (Authorization)**
- Invocation operations (1.4)
- Proof chain validation (1.6)
- Revocation (1.7)
- Delegation store (1.6)
- Revocation store (1.7)
- MPC Signing (1.9)
- Encryption Strategy (2.1, 2.2)
4. **Lower Priority (Enhancement)**
@@ -506,13 +509,39 @@ These files are already aligned with v1.0.0-rc.1:
---
## Completed Items
### UCAN v1.0.0-rc.1 Core (January 2025)
The following was completed using `github.com/ucan-wg/go-ucan v1.1.0`:
- ✅ Type re-exports from go-ucan (Delegation, Invocation, Command, Policy)
- ✅ Sonr command constants (/vault/*, /did/*, /dwn/*)
- ✅ DelegationBuilder fluent API with Sonr-specific helpers
- ✅ InvocationBuilder fluent API with Sonr-specific helpers
- ✅ PolicyBuilder fluent API with all operators
- ✅ Sonr policy helpers (VaultPolicy, DIDPolicy, ChainPolicy)
- ✅ ValidationError types matching TypeScript definitions
- ✅ Capability, ExecutionResult, and related types
### Deleted (Deprecated JWT-based)
- ✅ Deleted `jwt.go` - Old JWT token handling
- ✅ Deleted `capability.go` - Old Attenuation/Resource/Capability model
- ✅ Deleted `verifier.go` - Old JWT verification
- ✅ Deleted `source.go` - Old JWT token creation
- ✅ Deleted `internal/crypto/mpc/spec/` - Old MPC JWT integration
- ✅ Removed `github.com/golang-jwt/jwt/v5` dependency
---
## Deprecated Items (Removed)
The following items from the previous TODO have been removed as they reference the **deprecated JWT-based UCAN format**:
- ~~Section 4.1 "Token Validation" - JWT parsing~~ -> Replaced by envelope validation (1.2, 1.3)
- ~~Section 4.2 "Capability Verification" - `can`/`with` format~~ -> Replaced by policy evaluation (1.5)
- ~~Section 4.3 "Proof Chain Validation" - JWT proof strings~~ -> Replaced by CID-based chain (1.6)
- ~~Section 4.1 "Token Validation" - JWT parsing~~ -> Replaced by go-ucan validation
- ~~Section 4.2 "Capability Verification" - `can`/`with` format~~ -> Replaced by policy evaluation
- ~~Section 4.3 "Proof Chain Validation" - JWT proof strings~~ -> Replaced by CID-based chain
- ~~Section 3.2 "UCAN Token Actions" - Old format~~ -> Replaced by v1.0.0-rc.1 actions (4.2)
- ~~Section 3.3 "Delegation Actions" - Old delegation model~~ -> Merged into Section 1 and 4.2

View File

@@ -72,7 +72,7 @@ func (am *ActionManager) ListAccounts(ctx context.Context) ([]AccountResult, err
AddressIndex: row.AddressIndex,
Label: label,
IsDefault: row.IsDefault == 1,
PublicKey: row.PublicKey,
PublicKey: row.SharePublicKey,
Curve: row.Curve,
CreatedAt: row.CreatedAt,
}
@@ -481,7 +481,7 @@ func (am *ActionManager) ResolveDID(ctx context.Context, did string) (*DIDDocume
AddressIndex: row.AddressIndex,
Label: label,
IsDefault: row.IsDefault == 1,
PublicKey: row.PublicKey,
PublicKey: row.SharePublicKey,
Curve: row.Curve,
CreatedAt: row.CreatedAt,
}

View File

@@ -2,37 +2,46 @@ package keybase
import (
"context"
"encoding/json"
"fmt"
)
// =============================================================================
// DELEGATION ACTIONS (UCAN v1.0.0-rc.1)
// =============================================================================
// DelegationResult represents a delegation in API responses.
type DelegationResult struct {
ID int64 `json:"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"`
Depth int64 `json:"depth"`
Status string `json:"status"`
CreatedAt string `json:"created_at"`
ExpiresAt string `json:"expires_at,omitempty"`
ID int64 `json:"id"`
CID string `json:"cid"`
Issuer string `json:"iss"`
Audience string `json:"aud"`
Subject string `json:"sub,omitempty"`
Command string `json:"cmd"`
Policy string `json:"pol,omitempty"`
NotBefore string `json:"nbf,omitempty"`
Expiration string `json:"exp,omitempty"`
IsRoot bool `json:"is_root"`
IsPowerline bool `json:"is_powerline"`
CreatedAt string `json:"created_at"`
}
type NewDelegationInput struct {
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,omitempty"`
ParentID int64 `json:"parent_id,omitempty"`
Depth int64 `json:"depth"`
ExpiresAt string `json:"expires_at,omitempty"`
// StoreDelegationParams contains parameters for storing a delegation.
type StoreDelegationParams struct {
CID string `json:"cid"`
Envelope []byte `json:"envelope"`
Issuer string `json:"iss"`
Audience string `json:"aud"`
Subject string `json:"sub,omitempty"`
Command string `json:"cmd"`
Policy string `json:"pol,omitempty"`
NotBefore string `json:"nbf,omitempty"`
Expiration string `json:"exp,omitempty"`
IsRoot bool `json:"is_root"`
IsPowerline bool `json:"is_powerline"`
}
func (am *ActionManager) CreateDelegation(ctx context.Context, params NewDelegationInput) (*DelegationResult, error) {
// StoreDelegation stores a new UCAN delegation envelope.
func (am *ActionManager) StoreDelegation(ctx context.Context, params StoreDelegationParams) (*DelegationResult, error) {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
@@ -40,75 +49,78 @@ func (am *ActionManager) CreateDelegation(ctx context.Context, params NewDelegat
return nil, fmt.Errorf("DID not initialized")
}
var parentID *int64
if params.ParentID != 0 {
parentID = &params.ParentID
var sub, pol, nbf, exp *string
if params.Subject != "" {
sub = &params.Subject
}
if params.Policy != "" {
pol = &params.Policy
}
if params.NotBefore != "" {
nbf = &params.NotBefore
}
if params.Expiration != "" {
exp = &params.Expiration
}
var expiresAt *string
if params.ExpiresAt != "" {
expiresAt = &params.ExpiresAt
isRoot := int64(0)
if params.IsRoot {
isRoot = 1
}
caveats := params.Caveats
if caveats == nil {
caveats = json.RawMessage(`{}`)
isPowerline := int64(0)
if params.IsPowerline {
isPowerline = 1
}
d, err := am.kb.queries.CreateDelegation(ctx, CreateDelegationParams{
DidID: am.kb.didID,
UcanID: params.UcanID,
Delegator: params.Delegator,
Delegate: params.Delegate,
Resource: params.Resource,
Action: params.Action,
Caveats: caveats,
ParentID: parentID,
Depth: params.Depth,
ExpiresAt: expiresAt,
DidID: am.kb.didID,
Cid: params.CID,
Envelope: params.Envelope,
Iss: params.Issuer,
Aud: params.Audience,
Sub: sub,
Cmd: params.Command,
Pol: pol,
Nbf: nbf,
Exp: exp,
IsRoot: isRoot,
IsPowerline: isPowerline,
})
if err != nil {
return nil, fmt.Errorf("create delegation: %w", err)
}
return delegationToResult(&d), nil
return delegationToResult(d), nil
}
func (am *ActionManager) ListDelegationsByDelegator(ctx context.Context, delegator string) ([]DelegationResult, error) {
// GetDelegationByCID retrieves a delegation by its CID.
func (am *ActionManager) GetDelegationByCID(ctx context.Context, cid string) (*DelegationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
delegations, err := am.kb.queries.ListDelegationsByDelegator(ctx, delegator)
d, err := am.kb.queries.GetDelegationByCID(ctx, cid)
if err != nil {
return nil, fmt.Errorf("list delegations by delegator: %w", err)
return nil, fmt.Errorf("get delegation: %w", err)
}
results := make([]DelegationResult, len(delegations))
for i, d := range delegations {
results[i] = *delegationToResult(&d)
}
return results, nil
return delegationToResult(d), nil
}
func (am *ActionManager) ListDelegationsByDelegate(ctx context.Context, delegate string) ([]DelegationResult, error) {
// GetDelegationEnvelope retrieves the raw CBOR envelope for a delegation.
func (am *ActionManager) GetDelegationEnvelope(ctx context.Context, cid string) ([]byte, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
delegations, err := am.kb.queries.ListDelegationsByDelegate(ctx, delegate)
envelope, err := am.kb.queries.GetDelegationEnvelopeByCID(ctx, cid)
if err != nil {
return nil, fmt.Errorf("list delegations by delegate: %w", err)
return nil, fmt.Errorf("get delegation envelope: %w", err)
}
results := make([]DelegationResult, len(delegations))
for i, d := range delegations {
results[i] = *delegationToResult(&d)
}
return results, nil
return envelope, nil
}
func (am *ActionManager) ListDelegationsForResource(ctx context.Context, resource string) ([]DelegationResult, error) {
// ListDelegations returns all active delegations for the current DID.
func (am *ActionManager) ListDelegations(ctx context.Context) ([]DelegationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
@@ -116,66 +128,191 @@ func (am *ActionManager) ListDelegationsForResource(ctx context.Context, resourc
return []DelegationResult{}, nil
}
delegations, err := am.kb.queries.ListDelegationsForResource(ctx, ListDelegationsForResourceParams{
DidID: am.kb.didID,
Resource: resource,
})
delegations, err := am.kb.queries.ListDelegationsByDID(ctx, am.kb.didID)
if err != nil {
return nil, fmt.Errorf("list delegations for resource: %w", err)
return nil, fmt.Errorf("list delegations: %w", err)
}
results := make([]DelegationResult, len(delegations))
for i, d := range delegations {
results[i] = *delegationToResult(&d)
results[i] = *delegationToResult(d)
}
return results, nil
}
func (am *ActionManager) GetDelegationChain(ctx context.Context, delegationID int64) ([]DelegationResult, error) {
// ListDelegationsByIssuer returns delegations issued by a specific DID.
func (am *ActionManager) ListDelegationsByIssuer(ctx context.Context, issuer string) ([]DelegationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
delegations, err := am.kb.queries.GetDelegationChain(ctx, GetDelegationChainParams{
ID: delegationID,
ParentID: &delegationID,
})
delegations, err := am.kb.queries.ListDelegationsByIssuer(ctx, issuer)
if err != nil {
return nil, fmt.Errorf("get delegation chain: %w", err)
return nil, fmt.Errorf("list delegations by issuer: %w", err)
}
results := make([]DelegationResult, len(delegations))
for i, d := range delegations {
results[i] = *delegationToResult(&d)
results[i] = *delegationToResult(d)
}
return results, nil
}
func (am *ActionManager) RevokeDelegation(ctx context.Context, delegationID int64) error {
// ListDelegationsByAudience returns delegations granted to a specific DID.
func (am *ActionManager) ListDelegationsByAudience(ctx context.Context, audience string) ([]DelegationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
delegations, err := am.kb.queries.ListDelegationsByAudience(ctx, audience)
if err != nil {
return nil, fmt.Errorf("list delegations by audience: %w", err)
}
results := make([]DelegationResult, len(delegations))
for i, d := range delegations {
results[i] = *delegationToResult(d)
}
return results, nil
}
// ListDelegationsForCommand returns delegations that grant a specific command.
func (am *ActionManager) ListDelegationsForCommand(ctx context.Context, cmd string) ([]DelegationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
if am.kb.didID == 0 {
return []DelegationResult{}, nil
}
delegations, err := am.kb.queries.ListDelegationsForCommand(ctx, ListDelegationsForCommandParams{
DidID: am.kb.didID,
Cmd: cmd,
Cmd_2: cmd,
})
if err != nil {
return nil, fmt.Errorf("list delegations for command: %w", err)
}
results := make([]DelegationResult, len(delegations))
for i, d := range delegations {
results[i] = *delegationToResult(d)
}
return results, nil
}
// IsDelegationRevoked checks if a delegation has been revoked.
func (am *ActionManager) IsDelegationRevoked(ctx context.Context, cid string) (bool, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
revoked, err := am.kb.queries.IsDelegationRevoked(ctx, cid)
if err != nil {
return false, fmt.Errorf("check revocation: %w", err)
}
return revoked == 1, nil
}
// RevokeDelegationParams contains parameters for revoking a delegation.
type RevokeDelegationParams struct {
DelegationCID string `json:"delegation_cid"`
RevokedBy string `json:"revoked_by"`
InvocationCID string `json:"invocation_cid,omitempty"`
Reason string `json:"reason,omitempty"`
}
// RevokeDelegation revokes a delegation by creating a revocation record.
func (am *ActionManager) RevokeDelegation(ctx context.Context, params RevokeDelegationParams) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
return am.kb.queries.RevokeDelegation(ctx, delegationID)
var invocationCID, reason *string
if params.InvocationCID != "" {
invocationCID = &params.InvocationCID
}
if params.Reason != "" {
reason = &params.Reason
}
err := am.kb.queries.CreateRevocation(ctx, CreateRevocationParams{
DelegationCid: params.DelegationCID,
RevokedBy: params.RevokedBy,
InvocationCid: invocationCID,
Reason: reason,
})
if err != nil {
return fmt.Errorf("create revocation: %w", err)
}
return nil
}
func delegationToResult(d *Delegation) *DelegationResult {
expiresAt := ""
if d.ExpiresAt != nil {
expiresAt = *d.ExpiresAt
// DeleteDelegation deletes a delegation from the database.
func (am *ActionManager) DeleteDelegation(ctx context.Context, cid string) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
if am.kb.didID == 0 {
return fmt.Errorf("DID not initialized")
}
err := am.kb.queries.DeleteDelegation(ctx, DeleteDelegationParams{
Cid: cid,
DidID: am.kb.didID,
})
if err != nil {
return fmt.Errorf("delete delegation: %w", err)
}
return nil
}
// CleanExpiredDelegations removes delegations expired more than 30 days ago.
func (am *ActionManager) CleanExpiredDelegations(ctx context.Context) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
if err := am.kb.queries.CleanExpiredDelegations(ctx); err != nil {
return fmt.Errorf("clean expired delegations: %w", err)
}
return nil
}
// delegationToResult converts a UcanDelegation to DelegationResult.
func delegationToResult(d UcanDelegation) *DelegationResult {
subject := ""
if d.Sub != nil {
subject = *d.Sub
}
policy := ""
if d.Pol != nil {
policy = *d.Pol
}
notBefore := ""
if d.Nbf != nil {
notBefore = *d.Nbf
}
expiration := ""
if d.Exp != nil {
expiration = *d.Exp
}
return &DelegationResult{
ID: d.ID,
UcanID: d.UcanID,
Delegator: d.Delegator,
Delegate: d.Delegate,
Resource: d.Resource,
Action: d.Action,
Caveats: d.Caveats,
Depth: d.Depth,
Status: d.Status,
CreatedAt: d.CreatedAt,
ExpiresAt: expiresAt,
ID: d.ID,
CID: d.Cid,
Issuer: d.Iss,
Audience: d.Aud,
Subject: subject,
Command: d.Cmd,
Policy: policy,
NotBefore: notBefore,
Expiration: expiration,
IsRoot: d.IsRoot == 1,
IsPowerline: d.IsPowerline == 1,
CreatedAt: d.CreatedAt,
}
}

View File

@@ -7,11 +7,11 @@ import (
)
type NewGrantInput struct {
ServiceID int64 `json:"service_id"`
UcanID int64 `json:"ucan_id,omitempty"`
Scopes json.RawMessage `json:"scopes"`
Accounts json.RawMessage `json:"accounts"`
ExpiresAt string `json:"expires_at,omitempty"`
ServiceID int64 `json:"service_id"`
DelegationCID string `json:"delegation_cid,omitempty"`
Scopes json.RawMessage `json:"scopes"`
Accounts json.RawMessage `json:"accounts"`
ExpiresAt string `json:"expires_at,omitempty"`
}
func (am *ActionManager) CreateGrant(ctx context.Context, params NewGrantInput) (*GrantResult, error) {
@@ -22,9 +22,9 @@ func (am *ActionManager) CreateGrant(ctx context.Context, params NewGrantInput)
return nil, fmt.Errorf("DID not initialized")
}
var ucanID *int64
if params.UcanID != 0 {
ucanID = &params.UcanID
var delegationCID *string
if params.DelegationCID != "" {
delegationCID = &params.DelegationCID
}
var expiresAt *string
@@ -42,12 +42,12 @@ func (am *ActionManager) CreateGrant(ctx context.Context, params NewGrantInput)
}
g, err := am.kb.queries.CreateGrant(ctx, CreateGrantParams{
DidID: am.kb.didID,
ServiceID: params.ServiceID,
UcanID: ucanID,
Scopes: scopes,
Accounts: accounts,
ExpiresAt: expiresAt,
DidID: am.kb.didID,
ServiceID: params.ServiceID,
DelegationCid: delegationCID,
Scopes: scopes,
Accounts: accounts,
ExpiresAt: expiresAt,
})
if err != nil {
return nil, fmt.Errorf("create grant: %w", err)

View File

@@ -1,205 +0,0 @@
package keybase
import (
"context"
"encoding/json"
"fmt"
)
type UCANResult struct {
ID int64 `json:"id"`
CID string `json:"cid"`
Issuer string `json:"issuer"`
Audience string `json:"audience"`
Subject string `json:"subject,omitempty"`
Capabilities json.RawMessage `json:"capabilities"`
NotBefore string `json:"not_before,omitempty"`
ExpiresAt string `json:"expires_at"`
IsRevoked bool `json:"is_revoked"`
CreatedAt string `json:"created_at"`
}
type NewUCANInput struct {
CID string `json:"cid"`
Issuer string `json:"issuer"`
Audience string `json:"audience"`
Subject string `json:"subject,omitempty"`
Capabilities json.RawMessage `json:"capabilities"`
ProofChain json.RawMessage `json:"proof_chain,omitempty"`
NotBefore string `json:"not_before,omitempty"`
ExpiresAt string `json:"expires_at"`
Nonce string `json:"nonce,omitempty"`
Facts json.RawMessage `json:"facts,omitempty"`
Signature string `json:"signature"`
RawToken string `json:"raw_token"`
}
func (am *ActionManager) CreateUCAN(ctx context.Context, params NewUCANInput) (*UCANResult, error) {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
if am.kb.didID == 0 {
return nil, fmt.Errorf("DID not initialized")
}
var subject, notBefore, nonce *string
if params.Subject != "" {
subject = &params.Subject
}
if params.NotBefore != "" {
notBefore = &params.NotBefore
}
if params.Nonce != "" {
nonce = &params.Nonce
}
proofChain := params.ProofChain
if proofChain == nil {
proofChain = json.RawMessage(`[]`)
}
facts := params.Facts
if facts == nil {
facts = json.RawMessage(`{}`)
}
ucan, err := am.kb.queries.CreateUCAN(ctx, CreateUCANParams{
DidID: am.kb.didID,
Cid: params.CID,
Issuer: params.Issuer,
Audience: params.Audience,
Subject: subject,
Capabilities: params.Capabilities,
ProofChain: proofChain,
NotBefore: notBefore,
ExpiresAt: params.ExpiresAt,
Nonce: nonce,
Facts: facts,
Signature: params.Signature,
RawToken: params.RawToken,
})
if err != nil {
return nil, fmt.Errorf("create ucan: %w", err)
}
return ucanToResult(&ucan), nil
}
func (am *ActionManager) ListUCANs(ctx context.Context) ([]UCANResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
if am.kb.didID == 0 {
return []UCANResult{}, nil
}
ucans, err := am.kb.queries.ListUCANsByDID(ctx, am.kb.didID)
if err != nil {
return nil, fmt.Errorf("list ucans: %w", err)
}
results := make([]UCANResult, len(ucans))
for i, u := range ucans {
results[i] = *ucanToResult(&u)
}
return results, nil
}
func (am *ActionManager) GetUCANByCID(ctx context.Context, cid string) (*UCANResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
ucan, err := am.kb.queries.GetUCANByCID(ctx, cid)
if err != nil {
return nil, fmt.Errorf("get ucan: %w", err)
}
return ucanToResult(&ucan), nil
}
func (am *ActionManager) ListUCANsByAudience(ctx context.Context, audience string) ([]UCANResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
ucans, err := am.kb.queries.ListUCANsByAudience(ctx, audience)
if err != nil {
return nil, fmt.Errorf("list ucans by audience: %w", err)
}
results := make([]UCANResult, len(ucans))
for i, u := range ucans {
results[i] = *ucanToResult(&u)
}
return results, nil
}
func (am *ActionManager) RevokeUCAN(ctx context.Context, cid string) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
return am.kb.queries.RevokeUCAN(ctx, cid)
}
func (am *ActionManager) IsUCANRevoked(ctx context.Context, cid string) (bool, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
revoked, err := am.kb.queries.IsUCANRevoked(ctx, cid)
if err != nil {
return false, fmt.Errorf("check ucan revocation: %w", err)
}
return revoked == 1, nil
}
func (am *ActionManager) CreateRevocation(ctx context.Context, ucanCID string, revokedBy string, reason string) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
var reasonPtr *string
if reason != "" {
reasonPtr = &reason
}
if err := am.kb.queries.RevokeUCAN(ctx, ucanCID); err != nil {
return fmt.Errorf("revoke ucan token: %w", err)
}
return am.kb.queries.CreateRevocation(ctx, CreateRevocationParams{
UcanCid: ucanCID,
RevokedBy: revokedBy,
Reason: reasonPtr,
})
}
func (am *ActionManager) CleanExpiredUCANs(ctx context.Context) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
return am.kb.queries.CleanExpiredUCANs(ctx)
}
func ucanToResult(u *UcanToken) *UCANResult {
subject := ""
if u.Subject != nil {
subject = *u.Subject
}
notBefore := ""
if u.NotBefore != nil {
notBefore = *u.NotBefore
}
return &UCANResult{
ID: u.ID,
CID: u.Cid,
Issuer: u.Issuer,
Audience: u.Audience,
Subject: subject,
Capabilities: u.Capabilities,
NotBefore: notBefore,
ExpiresAt: u.ExpiresAt,
IsRevoked: u.IsRevoked == 1,
CreatedAt: u.CreatedAt,
}
}

View File

@@ -10,10 +10,10 @@ import (
)
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
ExecContext(context.Context, string, ...interface{}) (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
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {

View File

@@ -40,22 +40,6 @@ type Credential struct {
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"`
@@ -68,16 +52,16 @@ type DidDocument struct {
}
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"`
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
ServiceID int64 `json:"service_id"`
DelegationCid *string `json:"delegation_cid"`
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 {
@@ -131,31 +115,47 @@ type SyncCheckpoint struct {
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 UcanDelegation struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
Cid string `json:"cid"`
Envelope []byte `json:"envelope"`
Iss string `json:"iss"`
Aud string `json:"aud"`
Sub *string `json:"sub"`
Cmd string `json:"cmd"`
Pol *string `json:"pol"`
Nbf *string `json:"nbf"`
Exp *string `json:"exp"`
IsRoot int64 `json:"is_root"`
IsPowerline int64 `json:"is_powerline"`
CreatedAt string `json:"created_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 UcanInvocation struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
Cid string `json:"cid"`
Envelope []byte `json:"envelope"`
Iss string `json:"iss"`
Sub string `json:"sub"`
Aud *string `json:"aud"`
Cmd string `json:"cmd"`
Prf string `json:"prf"`
Exp *string `json:"exp"`
Iat *string `json:"iat"`
ExecutedAt *string `json:"executed_at"`
ResultCid *string `json:"result_cid"`
CreatedAt string `json:"created_at"`
}
type UcanRevocation struct {
ID int64 `json:"id"`
DelegationCid string `json:"delegation_cid"`
RevokedBy string `json:"revoked_by"`
InvocationCid *string `json:"invocation_cid"`
Reason *string `json:"reason"`
RevokedAt string `json:"revoked_at"`
}
type VerificationMethod struct {

View File

@@ -10,22 +10,27 @@ import (
type Querier interface {
ArchiveKeyShare(ctx context.Context, id int64) error
CleanExpiredUCANs(ctx context.Context) error
CleanExpiredDelegations(ctx context.Context) error
CleanOldInvocations(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)
CreateDelegation(ctx context.Context, arg CreateDelegationParams) (UcanDelegation, error)
CreateGrant(ctx context.Context, arg CreateGrantParams) (Grant, error)
CreateInvocation(ctx context.Context, arg CreateInvocationParams) (UcanInvocation, error)
CreateKeyShare(ctx context.Context, arg CreateKeyShareParams) (KeyShare, error)
// =============================================================================
// UCAN REVOCATION QUERIES
// =============================================================================
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
DeleteDelegation(ctx context.Context, arg DeleteDelegationParams) error
DeleteExpiredSessions(ctx context.Context) error
DeleteKeyShare(ctx context.Context, arg DeleteKeyShareParams) error
DeleteSession(ctx context.Context, id int64) error
@@ -39,10 +44,20 @@ type Querier interface {
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)
// =============================================================================
// UCAN DELEGATION QUERIES (v1.0.0-rc.1)
// =============================================================================
GetDelegationByCID(ctx context.Context, cid string) (UcanDelegation, error)
GetDelegationEnvelopeByCID(ctx context.Context, cid string) ([]byte, error)
GetGrantByService(ctx context.Context, arg GetGrantByServiceParams) (Grant, error)
// =============================================================================
// UCAN INVOCATION QUERIES (v1.0.0-rc.1)
// =============================================================================
GetInvocationByCID(ctx context.Context, cid string) (UcanInvocation, error)
GetInvocationEnvelopeByCID(ctx context.Context, cid string) ([]byte, error)
GetKeyShareByID(ctx context.Context, shareID string) (KeyShare, error)
GetKeyShareByKeyID(ctx context.Context, arg GetKeyShareByKeyIDParams) (KeyShare, error)
GetRevocation(ctx context.Context, delegationCid string) (UcanRevocation, error)
GetServiceByID(ctx context.Context, id int64) (Service, error)
// =============================================================================
// SERVICE QUERIES
@@ -53,9 +68,8 @@ type Querier interface {
// 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)
IsDelegationRevoked(ctx context.Context, delegationCid string) (int64, error)
ListAccountsByChain(ctx context.Context, arg ListAccountsByChainParams) ([]Account, error)
// =============================================================================
// ACCOUNT QUERIES
@@ -66,41 +80,41 @@ type Querier interface {
// 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)
ListDelegationsByAudience(ctx context.Context, aud string) ([]UcanDelegation, error)
ListDelegationsByDID(ctx context.Context, didID int64) ([]UcanDelegation, error)
ListDelegationsByIssuer(ctx context.Context, iss string) ([]UcanDelegation, error)
ListDelegationsBySubject(ctx context.Context, sub *string) ([]UcanDelegation, error)
ListDelegationsForCommand(ctx context.Context, arg ListDelegationsForCommandParams) ([]UcanDelegation, error)
// =============================================================================
// GRANT QUERIES
// =============================================================================
ListGrantsByDID(ctx context.Context, didID int64) ([]ListGrantsByDIDRow, error)
ListInvocationsByDID(ctx context.Context, arg ListInvocationsByDIDParams) ([]UcanInvocation, error)
ListInvocationsByIssuer(ctx context.Context, arg ListInvocationsByIssuerParams) ([]UcanInvocation, error)
ListInvocationsBySubject(ctx context.Context, arg ListInvocationsBySubjectParams) ([]UcanInvocation, error)
ListInvocationsForCommand(ctx context.Context, arg ListInvocationsForCommandParams) ([]UcanInvocation, error)
// =============================================================================
// KEY SHARE QUERIES
// =============================================================================
ListKeySharesByDID(ctx context.Context, didID int64) ([]KeyShare, error)
ListPendingInvocations(ctx context.Context, didID int64) ([]UcanInvocation, error)
ListPowerlineDelegations(ctx context.Context, didID int64) ([]UcanDelegation, error)
ListRevocationsByRevoker(ctx context.Context, revokedBy string) ([]UcanRevocation, error)
ListRootDelegations(ctx context.Context, didID int64) ([]UcanDelegation, 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)
MarkInvocationExecuted(ctx context.Context, arg MarkInvocationExecutedParams) 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

File diff suppressed because it is too large Load Diff

View File

@@ -108,7 +108,7 @@ DELETE FROM key_shares WHERE id = ? AND did_id = ?;
-- =============================================================================
-- name: ListAccountsByDID :many
SELECT a.*, k.public_key, k.curve
SELECT a.*, k.public_key as share_public_key, k.curve
FROM accounts a
JOIN key_shares k ON a.key_share_id = k.id
WHERE a.did_id = ?
@@ -140,42 +140,137 @@ UPDATE accounts SET label = ? WHERE id = ?;
DELETE FROM accounts WHERE id = ? AND did_id = ?;
-- =============================================================================
-- UCAN TOKEN QUERIES
-- UCAN DELEGATION QUERIES (v1.0.0-rc.1)
-- =============================================================================
-- 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: GetDelegationByCID :one
SELECT * FROM ucan_delegations WHERE cid = ? LIMIT 1;
-- name: ListUCANsByAudience :many
SELECT * FROM ucan_tokens
WHERE audience = ? AND is_revoked = 0 AND expires_at > datetime('now')
ORDER BY created_at DESC;
-- name: GetDelegationEnvelopeByCID :one
SELECT envelope FROM ucan_delegations WHERE cid = ? LIMIT 1;
-- 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
-- name: CreateDelegation :one
INSERT INTO ucan_delegations (
did_id, cid, envelope, iss, aud, sub, cmd, pol, nbf, exp, is_root, is_powerline
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING *;
-- name: RevokeUCAN :exec
UPDATE ucan_tokens SET is_revoked = 1 WHERE cid = ?;
-- name: ListDelegationsByDID :many
SELECT * FROM ucan_delegations
WHERE did_id = ? AND (exp IS NULL OR exp > datetime('now'))
ORDER BY created_at DESC;
-- name: IsUCANRevoked :one
SELECT EXISTS(SELECT 1 FROM ucan_revocations WHERE ucan_cid = ?) as revoked;
-- name: ListDelegationsByIssuer :many
SELECT * FROM ucan_delegations
WHERE iss = ? AND (exp IS NULL OR exp > datetime('now'))
ORDER BY created_at DESC;
-- name: ListDelegationsByAudience :many
SELECT * FROM ucan_delegations
WHERE aud = ? AND (exp IS NULL OR exp > datetime('now'))
ORDER BY created_at DESC;
-- name: ListDelegationsBySubject :many
SELECT * FROM ucan_delegations
WHERE sub = ? AND (exp IS NULL OR exp > datetime('now'))
ORDER BY created_at DESC;
-- name: ListDelegationsForCommand :many
SELECT * FROM ucan_delegations
WHERE did_id = ?
AND (cmd = ? OR cmd = '/' OR ? LIKE cmd || '/%')
AND (exp IS NULL OR exp > datetime('now'))
ORDER BY created_at DESC;
-- name: ListRootDelegations :many
SELECT * FROM ucan_delegations
WHERE did_id = ? AND is_root = 1 AND (exp IS NULL OR exp > datetime('now'))
ORDER BY created_at DESC;
-- name: ListPowerlineDelegations :many
SELECT * FROM ucan_delegations
WHERE did_id = ? AND is_powerline = 1 AND (exp IS NULL OR exp > datetime('now'))
ORDER BY created_at DESC;
-- name: DeleteDelegation :exec
DELETE FROM ucan_delegations WHERE cid = ? AND did_id = ?;
-- name: CleanExpiredDelegations :exec
DELETE FROM ucan_delegations WHERE exp < datetime('now', '-30 days');
-- =============================================================================
-- UCAN INVOCATION QUERIES (v1.0.0-rc.1)
-- =============================================================================
-- name: GetInvocationByCID :one
SELECT * FROM ucan_invocations WHERE cid = ? LIMIT 1;
-- name: GetInvocationEnvelopeByCID :one
SELECT envelope FROM ucan_invocations WHERE cid = ? LIMIT 1;
-- name: CreateInvocation :one
INSERT INTO ucan_invocations (
did_id, cid, envelope, iss, sub, aud, cmd, prf, exp, iat
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING *;
-- name: ListInvocationsByDID :many
SELECT * FROM ucan_invocations
WHERE did_id = ?
ORDER BY created_at DESC
LIMIT ?;
-- name: ListInvocationsByIssuer :many
SELECT * FROM ucan_invocations
WHERE iss = ?
ORDER BY created_at DESC
LIMIT ?;
-- name: ListInvocationsBySubject :many
SELECT * FROM ucan_invocations
WHERE sub = ?
ORDER BY created_at DESC
LIMIT ?;
-- name: ListInvocationsForCommand :many
SELECT * FROM ucan_invocations
WHERE did_id = ? AND cmd = ?
ORDER BY created_at DESC
LIMIT ?;
-- name: MarkInvocationExecuted :exec
UPDATE ucan_invocations
SET executed_at = datetime('now'), result_cid = ?
WHERE cid = ?;
-- name: ListPendingInvocations :many
SELECT * FROM ucan_invocations
WHERE did_id = ? AND executed_at IS NULL AND (exp IS NULL OR exp > datetime('now'))
ORDER BY created_at ASC;
-- name: CleanOldInvocations :exec
DELETE FROM ucan_invocations WHERE created_at < datetime('now', '-90 days');
-- =============================================================================
-- UCAN REVOCATION QUERIES
-- =============================================================================
-- name: CreateRevocation :exec
INSERT INTO ucan_revocations (ucan_cid, revoked_by, reason)
VALUES (?, ?, ?);
INSERT INTO ucan_revocations (delegation_cid, revoked_by, invocation_cid, reason)
VALUES (?, ?, ?, ?);
-- name: CleanExpiredUCANs :exec
DELETE FROM ucan_tokens WHERE expires_at < datetime('now', '-30 days');
-- name: IsDelegationRevoked :one
SELECT EXISTS(SELECT 1 FROM ucan_revocations WHERE delegation_cid = ?) as revoked;
-- name: GetRevocation :one
SELECT * FROM ucan_revocations WHERE delegation_cid = ? LIMIT 1;
-- name: ListRevocationsByRevoker :many
SELECT * FROM ucan_revocations
WHERE revoked_by = ?
ORDER BY revoked_at DESC;
-- =============================================================================
-- SESSION QUERIES
@@ -251,7 +346,7 @@ ORDER BY g.last_used DESC NULLS LAST;
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)
INSERT INTO grants (did_id, service_id, delegation_cid, scopes, accounts, expires_at)
VALUES (?, ?, ?, ?, ?, ?)
RETURNING *;
@@ -273,41 +368,6 @@ 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
-- =============================================================================

View File

@@ -1,6 +1,7 @@
-- =============================================================================
-- NEBULA KEY ENCLAVE SCHEMA
-- Encrypted SQLite database for sensitive wallet data
-- UCAN v1.0.0-rc.1 compliant
-- =============================================================================
PRAGMA foreign_keys = ON;
@@ -112,44 +113,97 @@ CREATE INDEX idx_accounts_address ON accounts(address);
CREATE INDEX idx_accounts_chain_id ON accounts(chain_id);
-- =============================================================================
-- UCAN AUTHORIZATION
-- UCAN AUTHORIZATION (v1.0.0-rc.1)
-- =============================================================================
-- UCAN Tokens: Capability authorization tokens
CREATE TABLE IF NOT EXISTS ucan_tokens (
-- UCAN Delegations: v1.0.0-rc.1 delegation envelopes
-- Stores sealed DAG-CBOR envelopes with extracted fields for indexing
CREATE TABLE IF NOT EXISTS ucan_delegations (
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,
-- Content Identifier (immutable, unique)
cid TEXT NOT NULL UNIQUE,
-- Sealed envelope (DAG-CBOR encoded)
envelope BLOB NOT NULL,
-- Extracted fields for indexing/queries
iss TEXT NOT NULL, -- Issuer DID
aud TEXT NOT NULL, -- Audience DID
sub TEXT, -- Subject DID (null = powerline)
cmd TEXT NOT NULL, -- Command (e.g., "/vault/read")
-- Policy stored as JSON for inspection
pol TEXT DEFAULT '[]', -- Policy JSON
-- Temporal fields
nbf TEXT, -- Not before (ISO8601)
exp TEXT, -- Expiration (ISO8601, null = never)
-- Metadata
is_root INTEGER NOT NULL DEFAULT 0, -- iss == sub
is_powerline INTEGER NOT NULL DEFAULT 0, -- sub IS NULL
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);
CREATE INDEX idx_ucan_delegations_cid ON ucan_delegations(cid);
CREATE INDEX idx_ucan_delegations_did_id ON ucan_delegations(did_id);
CREATE INDEX idx_ucan_delegations_iss ON ucan_delegations(iss);
CREATE INDEX idx_ucan_delegations_aud ON ucan_delegations(aud);
CREATE INDEX idx_ucan_delegations_sub ON ucan_delegations(sub);
CREATE INDEX idx_ucan_delegations_cmd ON ucan_delegations(cmd);
CREATE INDEX idx_ucan_delegations_exp ON ucan_delegations(exp);
-- UCAN Revocations: Revoked UCAN tokens
-- UCAN Invocations: v1.0.0-rc.1 invocation envelopes (audit log)
CREATE TABLE IF NOT EXISTS ucan_invocations (
id INTEGER PRIMARY KEY,
did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE,
-- Content Identifier
cid TEXT NOT NULL UNIQUE,
-- Sealed envelope (DAG-CBOR encoded)
envelope BLOB NOT NULL,
-- Extracted fields for indexing
iss TEXT NOT NULL, -- Invoker DID
sub TEXT NOT NULL, -- Subject DID
aud TEXT, -- Executor DID (if different from sub)
cmd TEXT NOT NULL, -- Command invoked
-- Proof chain (JSON array of delegation CIDs)
prf TEXT NOT NULL DEFAULT '[]',
-- Temporal
exp TEXT, -- Expiration
iat TEXT, -- Issued at
-- Execution tracking
executed_at TEXT, -- When actually executed
result_cid TEXT, -- CID of receipt (if executed)
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_ucan_invocations_cid ON ucan_invocations(cid);
CREATE INDEX idx_ucan_invocations_did_id ON ucan_invocations(did_id);
CREATE INDEX idx_ucan_invocations_iss ON ucan_invocations(iss);
CREATE INDEX idx_ucan_invocations_sub ON ucan_invocations(sub);
CREATE INDEX idx_ucan_invocations_cmd ON ucan_invocations(cmd);
-- UCAN Revocations: Track revoked delegations
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
delegation_cid TEXT NOT NULL UNIQUE, -- CID of revoked delegation
revoked_by TEXT NOT NULL, -- Revoker DID
invocation_cid TEXT, -- CID of revocation invocation
reason TEXT,
revoked_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_ucan_revocations_cid ON ucan_revocations(ucan_cid);
CREATE INDEX idx_ucan_revocations_delegation_cid ON ucan_revocations(delegation_cid);
CREATE INDEX idx_ucan_revocations_revoked_by ON ucan_revocations(revoked_by);
-- =============================================================================
-- DEVICE SESSIONS
@@ -191,12 +245,12 @@ CREATE TABLE IF NOT EXISTS services (
CREATE INDEX idx_services_origin ON services(origin);
-- Grants: User grants to services
-- Grants: User grants to services (backed by UCAN delegations)
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,
delegation_cid TEXT REFERENCES ucan_delegations(cid) ON DELETE SET NULL, -- v1.0.0-rc.1 delegation
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')),
@@ -209,32 +263,7 @@ CREATE TABLE IF NOT EXISTS grants (
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);
CREATE INDEX idx_grants_delegation_cid ON grants(delegation_cid);
-- =============================================================================
-- SYNC STATE