Files
sonr/x/did/keeper/did_document.go
T

307 lines
8.9 KiB
Go
Raw Normal View History

2025-10-03 14:45:52 -04:00
package keeper
import (
"context"
"encoding/base64"
"fmt"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/sonr-io/sonr/x/did/types"
)
// min returns the minimum of two integers
func min(a, b int) int {
if a < b {
return a
}
return b
}
// CreateEnhancedDIDDocument creates a DID document with proper controller and verification methods
// This is used during WebAuthn registration to create a complete DID document
func (k Keeper) CreateEnhancedDIDDocument(
ctx context.Context,
did string,
controllerAddress string,
webauthnCredential *types.WebAuthnCredential,
assertionType string,
assertionValue string,
enclavePublicKey []byte,
) (*types.DIDDocument, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
// Derive controller DID from enclave public key
controllerDID := k.deriveControllerDID(enclavePublicKey)
// Create WebAuthn authentication method
webauthnMethod := &types.VerificationMethod{
Id: fmt.Sprintf("%s#webauthn-1", did),
Controller: did,
VerificationMethodKind: "WebAuthnCredential2024",
WebauthnCredential: webauthnCredential,
}
// Create assertion method based on type (email/tel)
var assertionMethod *types.VerificationMethod
if assertionType == "email" || assertionType == "tel" {
assertionMethod = &types.VerificationMethod{
Id: fmt.Sprintf("%s#%s-assertion", did, assertionType),
Controller: did,
VerificationMethodKind: "AssertionMethod2024",
BlockchainAccountId: fmt.Sprintf("did:%s:%s", assertionType, types.HashAssertionValue(assertionValue)),
}
}
// Create Sonr account assertion method
sonrAccountMethod := &types.VerificationMethod{
Id: fmt.Sprintf("%s#sonr-account", did),
Controller: did,
VerificationMethodKind: "BlockchainAccountId2024",
BlockchainAccountId: fmt.Sprintf("sonr:%s", controllerAddress),
}
// Create enclave key agreement method if public key is provided
var enclaveMethod *types.VerificationMethod
if len(enclavePublicKey) > 0 {
// Create JWK string representation
jwkString := fmt.Sprintf(`{"kty":"EC","crv":"secp256k1","x":"%s","y":"%s"}`,
base64.URLEncoding.EncodeToString(enclavePublicKey[:min(32, len(enclavePublicKey))]),
base64.URLEncoding.EncodeToString(enclavePublicKey[min(32, len(enclavePublicKey)):]),
)
enclaveMethod = &types.VerificationMethod{
Id: fmt.Sprintf("%s#enclave-key", did),
Controller: did,
VerificationMethodKind: "JsonWebKey2020",
PublicKeyJwk: jwkString,
}
}
// Build verification methods array
verificationMethods := []*types.VerificationMethod{
webauthnMethod,
sonrAccountMethod,
}
if assertionMethod != nil {
verificationMethods = append(verificationMethods, assertionMethod)
}
if enclaveMethod != nil {
verificationMethods = append(verificationMethods, enclaveMethod)
}
// Create verification method references
authRefs := []*types.VerificationMethodReference{
{VerificationMethodId: webauthnMethod.Id},
}
assertRefs := []*types.VerificationMethodReference{
{VerificationMethodId: sonrAccountMethod.Id},
}
if assertionMethod != nil {
assertRefs = append(assertRefs, &types.VerificationMethodReference{
VerificationMethodId: assertionMethod.Id,
})
}
keyAgreementRefs := []*types.VerificationMethodReference{}
if enclaveMethod != nil {
keyAgreementRefs = append(keyAgreementRefs, &types.VerificationMethodReference{
VerificationMethodId: enclaveMethod.Id,
})
}
capabilityInvocationRefs := []*types.VerificationMethodReference{
{VerificationMethodId: webauthnMethod.Id},
}
// Add service endpoints
services := k.createDefaultServices(did)
// Create the DID document
didDoc := &types.DIDDocument{
Id: did,
PrimaryController: controllerDID,
VerificationMethod: verificationMethods,
Authentication: authRefs,
AssertionMethod: assertRefs,
KeyAgreement: keyAgreementRefs,
CapabilityInvocation: capabilityInvocationRefs,
CapabilityDelegation: []*types.VerificationMethodReference{},
Service: services,
AlsoKnownAs: k.generateAlsoKnownAs(assertionType, assertionValue),
CreatedAt: sdkCtx.BlockHeight(),
UpdatedAt: sdkCtx.BlockHeight(),
Version: 1,
Deactivated: false,
}
return didDoc, nil
}
// deriveControllerDID derives a controller DID from enclave public key
func (k Keeper) deriveControllerDID(enclavePublicKey []byte) string {
if len(enclavePublicKey) == 0 {
// If no enclave key, use a default controller pattern
return "did:sonr:controller"
}
// Create deterministic controller DID from public key
// Use first 16 bytes of public key for identifier
identifier := base64.URLEncoding.EncodeToString(enclavePublicKey[:16])
identifier = strings.TrimRight(identifier, "=") // Remove padding
return fmt.Sprintf("did:sonr:idx%s", identifier)
}
// createDefaultServices creates default service endpoints for a DID
func (k Keeper) createDefaultServices(did string) []*types.Service {
return []*types.Service{
{
Id: fmt.Sprintf("%s#dwn", did),
ServiceKind: "DecentralizedWebNode",
SingleEndpoint: "https://dwn.sonr.io",
},
{
Id: fmt.Sprintf("%s#messaging", did),
ServiceKind: "MessagingService",
SingleEndpoint: "https://msg.sonr.io",
},
}
}
// generateAlsoKnownAs generates alternative identifiers for the DID
func (k Keeper) generateAlsoKnownAs(assertionType string, assertionValue string) []string {
alsoKnownAs := []string{}
if assertionType == "email" {
// Add email-based identifier
alsoKnownAs = append(alsoKnownAs, fmt.Sprintf("mailto:%s", assertionValue))
} else if assertionType == "tel" {
// Add phone-based identifier
alsoKnownAs = append(alsoKnownAs, fmt.Sprintf("tel:%s", assertionValue))
}
return alsoKnownAs
}
// UpdateDIDDocumentWithUCAN updates a DID document with UCAN delegation chain reference
func (k Keeper) UpdateDIDDocumentWithUCAN(
ctx context.Context,
did string,
ucanRootProof string,
ucanOriginToken string,
) error {
// Get existing DID document
ormDoc, err := k.OrmDB.DIDDocumentTable().Get(ctx, did)
if err != nil {
return fmt.Errorf("failed to get DID document: %w", err)
}
didDoc := types.DIDDocumentFromORM(ormDoc)
// Add UCAN service endpoint to indicate UCAN support
ucanService := &types.Service{
Id: fmt.Sprintf("%s#ucan", did),
ServiceKind: "UCANDelegation",
SingleEndpoint: "ucan:enabled:true",
}
// Check if service already exists
serviceExists := false
for _, svc := range didDoc.Service {
if svc.ServiceKind == "UCANDelegation" {
serviceExists = true
break
}
}
if !serviceExists {
didDoc.Service = append(didDoc.Service, ucanService)
}
// Update version and timestamp
sdkCtx := sdk.UnwrapSDKContext(ctx)
didDoc.UpdatedAt = sdkCtx.BlockHeight()
didDoc.Version = didDoc.Version + 1
// Store updated document
ormUpdated := didDoc.ToORM()
if err := k.OrmDB.DIDDocumentTable().Update(ctx, ormUpdated); err != nil {
return fmt.Errorf("failed to update DID document with UCAN: %w", err)
}
return nil
}
// GetDIDDocumentWithEnhancements retrieves a DID document with all enhancements
func (k Keeper) GetDIDDocumentWithEnhancements(
ctx context.Context,
did string,
) (*types.DIDDocument, error) {
// Get DID document from ORM
ormDoc, err := k.OrmDB.DIDDocumentTable().Get(ctx, did)
if err != nil {
return nil, fmt.Errorf("DID document not found: %s", did)
}
didDoc := types.DIDDocumentFromORM(ormDoc)
// Ensure all required fields are populated
if didDoc.PrimaryController == "" {
// Try to derive from verification methods
for _, vm := range didDoc.VerificationMethod {
if vm.Controller != "" {
didDoc.PrimaryController = vm.Controller
break
}
}
}
return didDoc, nil
}
// ValidateDIDDocumentStructure validates the structure of an enhanced DID document
func (k Keeper) ValidateDIDDocumentStructure(didDoc *types.DIDDocument) error {
// Check required fields
if didDoc.Id == "" {
return fmt.Errorf("DID document must have an ID")
}
// Verify controller
if didDoc.PrimaryController == "" {
return fmt.Errorf("DID document must have a primary controller")
}
// Check verification methods
if len(didDoc.VerificationMethod) == 0 {
return fmt.Errorf("DID document must have at least one verification method")
}
// Verify authentication methods
if len(didDoc.Authentication) == 0 {
return fmt.Errorf("DID document must have at least one authentication method")
}
// Verify assertion methods (should have at least 2: Sonr account + email/tel)
if len(didDoc.AssertionMethod) < 1 {
return fmt.Errorf("DID document must have at least one assertion method")
}
// Check for WebAuthn credential
hasWebAuthn := false
for _, vm := range didDoc.VerificationMethod {
if vm.WebauthnCredential != nil {
hasWebAuthn = true
break
}
}
if !hasWebAuthn {
return fmt.Errorf("DID document must have a WebAuthn credential for authentication")
}
return nil
}