// Package ucan provides User-Controlled Authorization Networks (UCAN) implementation // for decentralized authorization and capability delegation in the Sonr network. // This package handles JWT-based tokens, cryptographic verification, and resource capabilities. package ucan import ( "encoding/json" "fmt" "strings" "time" ) // Token represents a UCAN JWT token with parsed claims type Token struct { Raw string `json:"raw"` Issuer string `json:"iss"` Audience string `json:"aud"` ExpiresAt int64 `json:"exp,omitempty"` NotBefore int64 `json:"nbf,omitempty"` Attenuations []Attenuation `json:"att"` Proofs []Proof `json:"prf,omitempty"` Facts []Fact `json:"fct,omitempty"` } // Attenuation represents a UCAN capability attenuation type Attenuation struct { Capability Capability `json:"can"` Resource Resource `json:"with"` } // Proof represents a UCAN delegation proof (either JWT or CID) type Proof string // Fact represents arbitrary facts in UCAN tokens type Fact struct { Data json.RawMessage `json:"data"` } // Capability defines what actions can be performed type Capability interface { // GetActions returns the list of actions this capability grants GetActions() []string // Grants checks if this capability grants the required abilities Grants(abilities []string) bool // Contains checks if this capability contains another capability Contains(other Capability) bool // String returns a string representation String() string } // Resource defines what resource the capability applies to type Resource interface { // GetScheme returns the resource scheme (e.g., "https", "ipfs") GetScheme() string // GetValue returns the resource value/path GetValue() string // GetURI returns the full URI string GetURI() string // Matches checks if this resource matches another resource Matches(other Resource) bool } // SimpleCapability implements Capability for single actions type SimpleCapability struct { Action string `json:"action"` } // GetActions returns the single action func (c *SimpleCapability) GetActions() []string { return []string{c.Action} } // Grants checks if the capability grants all required abilities func (c *SimpleCapability) Grants(abilities []string) bool { if len(abilities) != 1 { return false } return c.Action == abilities[0] || c.Action == "*" } // Contains checks if this capability contains another capability func (c *SimpleCapability) Contains(other Capability) bool { if c.Action == "*" { return true } otherActions := other.GetActions() if len(otherActions) != 1 { return false } return c.Action == otherActions[0] } // String returns string representation func (c *SimpleCapability) String() string { return c.Action } // MultiCapability implements Capability for multiple actions type MultiCapability struct { Actions []string `json:"actions"` } // GetActions returns all actions func (c *MultiCapability) GetActions() []string { return c.Actions } // Grants checks if the capability grants all required abilities func (c *MultiCapability) Grants(abilities []string) bool { actionSet := make(map[string]bool) for _, action := range c.Actions { actionSet[action] = true } // Check if we have wildcard permission if actionSet["*"] { return true } // Check each required ability for _, ability := range abilities { if !actionSet[ability] { return false } } return true } // Contains checks if this capability contains another capability func (c *MultiCapability) Contains(other Capability) bool { actionSet := make(map[string]bool) for _, action := range c.Actions { actionSet[action] = true } // Wildcard contains everything if actionSet["*"] { return true } // Check if all other actions are contained for _, otherAction := range other.GetActions() { if !actionSet[otherAction] { return false } } return true } // String returns string representation func (c *MultiCapability) String() string { return strings.Join(c.Actions, ",") } // SimpleResource implements Resource for basic URI resources type SimpleResource struct { Scheme string `json:"scheme"` Value string `json:"value"` URI string `json:"uri"` } // GetScheme returns the resource scheme func (r *SimpleResource) GetScheme() string { return r.Scheme } // GetValue returns the resource value func (r *SimpleResource) GetValue() string { return r.Value } // GetURI returns the full URI func (r *SimpleResource) GetURI() string { return r.URI } // Matches checks if resources are equivalent func (r *SimpleResource) Matches(other Resource) bool { return r.URI == other.GetURI() } // VaultResource represents vault-specific resources with metadata type VaultResource struct { SimpleResource VaultAddress string `json:"vault_address,omitempty"` EnclaveDataCID string `json:"enclave_data_cid,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } // ServiceResource represents service-specific resources type ServiceResource struct { SimpleResource ServiceID string `json:"service_id"` Domain string `json:"domain"` Metadata map[string]string `json:"metadata,omitempty"` } // CreateSimpleAttenuation creates a basic attenuation func CreateSimpleAttenuation(action, resourceURI string) Attenuation { return Attenuation{ Capability: &SimpleCapability{Action: action}, Resource: parseResourceURI(resourceURI), } } // CreateMultiAttenuation creates an attenuation with multiple actions func CreateMultiAttenuation(actions []string, resourceURI string) Attenuation { return Attenuation{ Capability: &MultiCapability{Actions: actions}, Resource: parseResourceURI(resourceURI), } } // CreateVaultAttenuation creates a vault-specific attenuation func CreateVaultAttenuation(actions []string, enclaveDataCID, vaultAddress string) Attenuation { resource := &VaultResource{ SimpleResource: SimpleResource{ Scheme: "ipfs", Value: enclaveDataCID, URI: fmt.Sprintf("ipfs://%s", enclaveDataCID), }, VaultAddress: vaultAddress, EnclaveDataCID: enclaveDataCID, } return Attenuation{ Capability: &MultiCapability{Actions: actions}, Resource: resource, } } // CreateServiceAttenuation creates a service-specific attenuation func CreateServiceAttenuation(actions []string, serviceID, domain string) Attenuation { resourceURI := fmt.Sprintf("service://%s", serviceID) resource := &ServiceResource{ SimpleResource: SimpleResource{ Scheme: "service", Value: serviceID, URI: resourceURI, }, ServiceID: serviceID, Domain: domain, } return Attenuation{ Capability: &MultiCapability{Actions: actions}, Resource: resource, } } // parseResourceURI creates a Resource from URI string func parseResourceURI(uri string) Resource { parts := strings.SplitN(uri, "://", 2) if len(parts) != 2 { return &SimpleResource{ Scheme: "unknown", Value: uri, URI: uri, } } return &SimpleResource{ Scheme: parts[0], Value: parts[1], URI: uri, } } // CapabilityTemplate provides validation and construction utilities type CapabilityTemplate struct { AllowedActions map[string][]string `json:"allowed_actions"` // resource_type -> []actions DefaultExpiration time.Duration `json:"default_expiration"` // default token lifetime MaxExpiration time.Duration `json:"max_expiration"` // maximum allowed lifetime } // NewCapabilityTemplate creates a new capability template func NewCapabilityTemplate() *CapabilityTemplate { return &CapabilityTemplate{ AllowedActions: make(map[string][]string), DefaultExpiration: 24 * time.Hour, MaxExpiration: 30 * 24 * time.Hour, // 30 days } } // AddAllowedActions adds allowed actions for a resource type func (ct *CapabilityTemplate) AddAllowedActions(resourceType string, actions []string) { ct.AllowedActions[resourceType] = actions } // ValidateAttenuation validates an attenuation against the template func (ct *CapabilityTemplate) ValidateAttenuation(att Attenuation) error { resourceType := att.Resource.GetScheme() allowedActions, exists := ct.AllowedActions[resourceType] if !exists { // Allow unknown resource types for backward compatibility return nil } // Create action set for efficient lookup actionSet := make(map[string]bool) for _, action := range allowedActions { actionSet[action] = true } // Check if all capability actions are allowed for _, action := range att.Capability.GetActions() { if action == "*" { // Wildcard requires explicit permission if !actionSet["*"] { return fmt.Errorf("wildcard action not allowed for resource type %s", resourceType) } continue } if !actionSet[action] { return fmt.Errorf("action %s not allowed for resource type %s", action, resourceType) } } return nil } // ValidateExpiration validates token expiration time func (ct *CapabilityTemplate) ValidateExpiration(expiresAt int64) error { if expiresAt == 0 { return nil // No expiration is allowed } now := time.Now() expiry := time.Unix(expiresAt, 0) if expiry.Before(now) { return fmt.Errorf("token expiration is in the past") } if expiry.Sub(now) > ct.MaxExpiration { return fmt.Errorf("token expiration exceeds maximum allowed duration") } return nil } // GetDefaultExpirationTime returns the default expiration timestamp func (ct *CapabilityTemplate) GetDefaultExpirationTime() int64 { return time.Now().Add(ct.DefaultExpiration).Unix() } // StandardVaultTemplate returns a standard template for vault operations func StandardVaultTemplate() *CapabilityTemplate { template := NewCapabilityTemplate() template.AddAllowedActions( "ipfs", []string{"read", "write", "sign", "export", "import", "delete", VaultAdminAction}, ) template.AddAllowedActions( "vault", []string{"read", "write", "sign", "export", "import", "delete", "admin", "*"}, ) return template } // StandardServiceTemplate returns a standard template for service operations func StandardServiceTemplate() *CapabilityTemplate { template := NewCapabilityTemplate() template.AddAllowedActions( "service", []string{"read", "write", "admin", "register", "update", "delete"}, ) template.AddAllowedActions("https", []string{"read", "write"}) template.AddAllowedActions("http", []string{"read", "write"}) return template } // AttenuationList provides utilities for working with multiple attenuations type AttenuationList []Attenuation // Contains checks if the list contains attenuations for a specific resource func (al AttenuationList) Contains(resourceURI string) bool { for _, att := range al { if att.Resource.GetURI() == resourceURI { return true } } return false } // GetCapabilitiesForResource returns all capabilities for a specific resource func (al AttenuationList) GetCapabilitiesForResource(resourceURI string) []Capability { var capabilities []Capability for _, att := range al { if att.Resource.GetURI() == resourceURI { capabilities = append(capabilities, att.Capability) } } return capabilities } // CanPerform checks if the attenuations allow specific actions on a resource func (al AttenuationList) CanPerform(resourceURI string, actions []string) bool { capabilities := al.GetCapabilitiesForResource(resourceURI) for _, cap := range capabilities { if cap.Grants(actions) { return true } } return false } // IsSubsetOf checks if this list is a subset of another list func (al AttenuationList) IsSubsetOf(parent AttenuationList) bool { for _, childAtt := range al { if !parent.containsAttenuation(childAtt) { return false } } return true } // containsAttenuation checks if the list contains an equivalent attenuation func (al AttenuationList) containsAttenuation(att Attenuation) bool { for _, parentAtt := range al { if parentAtt.Resource.Matches(att.Resource) { if parentAtt.Capability.Contains(att.Capability) { return true } } } return false } // Module-Specific Capability Types // DIDCapability implements Capability for DID module operations type DIDCapability struct { Action string `json:"action"` Actions []string `json:"actions,omitempty"` Caveats []string `json:"caveats,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } // GetActions returns the actions this DID capability grants func (c *DIDCapability) GetActions() []string { if len(c.Actions) > 0 { return c.Actions } return []string{c.Action} } // Grants checks if this capability grants the required abilities func (c *DIDCapability) Grants(abilities []string) bool { if c.Action == "*" { return true } grantedActions := make(map[string]bool) for _, action := range c.GetActions() { grantedActions[action] = true } for _, ability := range abilities { if !grantedActions[ability] { return false } } return true } // Contains checks if this capability contains another capability func (c *DIDCapability) Contains(other Capability) bool { if c.Action == "*" { return true } ourActions := make(map[string]bool) for _, action := range c.GetActions() { ourActions[action] = true } for _, otherAction := range other.GetActions() { if !ourActions[otherAction] { return false } } return true } // String returns string representation func (c *DIDCapability) String() string { if len(c.Actions) > 1 { return strings.Join(c.Actions, ",") } return c.Action } // DWNCapability implements Capability for DWN module operations type DWNCapability struct { Action string `json:"action"` Actions []string `json:"actions,omitempty"` Caveats []string `json:"caveats,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } // GetActions returns the actions this DWN capability grants func (c *DWNCapability) GetActions() []string { if len(c.Actions) > 0 { return c.Actions } return []string{c.Action} } // Grants checks if this capability grants the required abilities func (c *DWNCapability) Grants(abilities []string) bool { if c.Action == "*" { return true } grantedActions := make(map[string]bool) for _, action := range c.GetActions() { grantedActions[action] = true } for _, ability := range abilities { if !grantedActions[ability] { return false } } return true } // Contains checks if this capability contains another capability func (c *DWNCapability) Contains(other Capability) bool { if c.Action == "*" { return true } ourActions := make(map[string]bool) for _, action := range c.GetActions() { ourActions[action] = true } for _, otherAction := range other.GetActions() { if !ourActions[otherAction] { return false } } return true } // String returns string representation func (c *DWNCapability) String() string { if len(c.Actions) > 1 { return strings.Join(c.Actions, ",") } return c.Action } // DEXCapability implements Capability for DEX module operations type DEXCapability struct { Action string `json:"action"` Actions []string `json:"actions,omitempty"` Caveats []string `json:"caveats,omitempty"` MaxAmount string `json:"max_amount,omitempty"` // For swap limits Metadata map[string]string `json:"metadata,omitempty"` } // GetActions returns the actions this DEX capability grants func (c *DEXCapability) GetActions() []string { if len(c.Actions) > 0 { return c.Actions } return []string{c.Action} } // Grants checks if this capability grants the required abilities func (c *DEXCapability) Grants(abilities []string) bool { if c.Action == "*" { return true } grantedActions := make(map[string]bool) for _, action := range c.GetActions() { grantedActions[action] = true } for _, ability := range abilities { if !grantedActions[ability] { return false } } return true } // Contains checks if this capability contains another capability func (c *DEXCapability) Contains(other Capability) bool { if c.Action == "*" { return true } ourActions := make(map[string]bool) for _, action := range c.GetActions() { ourActions[action] = true } for _, otherAction := range other.GetActions() { if !ourActions[otherAction] { return false } } return true } // String returns string representation func (c *DEXCapability) String() string { if len(c.Actions) > 1 { return strings.Join(c.Actions, ",") } return c.Action } // Module-Specific Resource Types // DIDResource represents DID-specific resources type DIDResource struct { SimpleResource DIDMethod string `json:"did_method,omitempty"` DIDSubject string `json:"did_subject,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } // DWNResource represents DWN-specific resources type DWNResource struct { SimpleResource RecordType string `json:"record_type,omitempty"` Protocol string `json:"protocol,omitempty"` Owner string `json:"owner,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } // DEXResource represents DEX-specific resources type DEXResource struct { SimpleResource PoolID string `json:"pool_id,omitempty"` AssetPair string `json:"asset_pair,omitempty"` OrderID string `json:"order_id,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } // Enhanced ServiceResource adds delegation capabilities func (r *ServiceResource) SupportsDelegate() bool { return r.Metadata != nil && r.Metadata["supports_delegation"] == "true" } // Module-Specific Capability Templates // StandardDIDTemplate returns a standard template for DID operations func StandardDIDTemplate() *CapabilityTemplate { template := NewCapabilityTemplate() template.AddAllowedActions("did", []string{ "create", "register", "update", "deactivate", "revoke", "add-verification-method", "remove-verification-method", "add-service", "remove-service", "issue-credential", "revoke-credential", "link-wallet", "register-webauthn", "*", }) return template } // StandardDWNTemplate returns a standard template for DWN operations func StandardDWNTemplate() *CapabilityTemplate { template := NewCapabilityTemplate() template.AddAllowedActions("dwn", []string{ "records-write", "records-delete", "protocols-configure", "permissions-grant", "permissions-revoke", "create", "read", "update", "delete", "*", }) return template } // EnhancedServiceTemplate returns enhanced service template with delegation support func EnhancedServiceTemplate() *CapabilityTemplate { template := NewCapabilityTemplate() template.AddAllowedActions("service", []string{ "register", "update", "delete", "verify-domain", "initiate-domain-verification", "delegate", "*", }) template.AddAllowedActions("svc", []string{ "register", "verify-domain", "delegate", "*", }) template.AddAllowedActions("https", []string{"read", "write"}) template.AddAllowedActions("http", []string{"read", "write"}) return template } // StandardDEXTemplate returns a standard template for DEX operations func StandardDEXTemplate() *CapabilityTemplate { template := NewCapabilityTemplate() template.AddAllowedActions("dex", []string{ "register-account", "swap", "provide-liquidity", "remove-liquidity", "create-limit-order", "cancel-order", "*", }) template.AddAllowedActions("pool", []string{ "swap", "provide-liquidity", "remove-liquidity", "*", }) return template } // Module-Specific Attenuation Constructors // CreateDIDAttenuation creates a DID-specific attenuation func CreateDIDAttenuation(actions []string, didPattern string, caveats []string) Attenuation { resourceURI := fmt.Sprintf("did:%s", didPattern) resource := &DIDResource{ SimpleResource: SimpleResource{ Scheme: "did", Value: didPattern, URI: resourceURI, }, } return Attenuation{ Capability: &DIDCapability{ Actions: actions, Caveats: caveats, }, Resource: resource, } } // CreateDWNAttenuation creates a DWN-specific attenuation func CreateDWNAttenuation(actions []string, recordPattern string, caveats []string) Attenuation { resourceURI := fmt.Sprintf("dwn:records/%s", recordPattern) resource := &DWNResource{ SimpleResource: SimpleResource{ Scheme: "dwn", Value: fmt.Sprintf("records/%s", recordPattern), URI: resourceURI, }, RecordType: recordPattern, } return Attenuation{ Capability: &DWNCapability{ Actions: actions, Caveats: caveats, }, Resource: resource, } } // CreateDEXAttenuation creates a DEX-specific attenuation func CreateDEXAttenuation(actions []string, poolPattern string, caveats []string, maxAmount string) Attenuation { resourceURI := fmt.Sprintf("dex:pool/%s", poolPattern) resource := &DEXResource{ SimpleResource: SimpleResource{ Scheme: "dex", Value: fmt.Sprintf("pool/%s", poolPattern), URI: resourceURI, }, PoolID: poolPattern, } return Attenuation{ Capability: &DEXCapability{ Actions: actions, Caveats: caveats, MaxAmount: maxAmount, }, Resource: resource, } } // Cross-Module Capability Composition // CrossModuleCapability allows composing capabilities across modules type CrossModuleCapability struct { Modules map[string]Capability `json:"modules"` } // GetActions returns all actions across all modules func (c *CrossModuleCapability) GetActions() []string { var actions []string for _, cap := range c.Modules { actions = append(actions, cap.GetActions()...) } return actions } // Grants checks if required abilities are granted across modules func (c *CrossModuleCapability) Grants(abilities []string) bool { allActions := make(map[string]bool) for _, cap := range c.Modules { for _, action := range cap.GetActions() { allActions[action] = true } } for _, ability := range abilities { if !allActions[ability] { return false } } return true } // Contains checks if this cross-module capability contains another func (c *CrossModuleCapability) Contains(other Capability) bool { // For cross-module capabilities, check each module if otherCross, ok := other.(*CrossModuleCapability); ok { for module, otherCap := range otherCross.Modules { if ourCap, exists := c.Modules[module]; exists { if !ourCap.Contains(otherCap) { return false } } else { return false } } return true } // For single capabilities, check if any module contains it for _, cap := range c.Modules { if cap.Contains(other) { return true } } return false } // String returns string representation func (c *CrossModuleCapability) String() string { var moduleStrs []string for module, cap := range c.Modules { moduleStrs = append(moduleStrs, fmt.Sprintf("%s:%s", module, cap.String())) } return strings.Join(moduleStrs, ";") } // Gasless Transaction Support // GaslessCapability wraps other capabilities with gasless transaction support type GaslessCapability struct { Capability AllowGasless bool `json:"allow_gasless"` GasLimit uint64 `json:"gas_limit,omitempty"` } // SupportsGasless returns whether this capability supports gasless transactions func (c *GaslessCapability) SupportsGasless() bool { return c.AllowGasless } // GetGasLimit returns the gas limit for gasless transactions func (c *GaslessCapability) GetGasLimit() uint64 { return c.GasLimit }