Files
hanzoai_agent/control-plane/internal/services/vc_service.go
zeekay 63bde13037 fix: replace invalid hyphenated Go identifiers with camelCase
The bulk rename from agentfield to hanzo-agents introduced hyphens into
Go variable names (e.g. hanzo-agentsHome), which are not valid Go
identifiers. Fixed 361 occurrences across 36 files to use camelCase
(e.g. hanzoAgentsHome). String literals like ".hanzo-agents" are
preserved as-is.
2026-02-13 12:43:38 -08:00

1646 lines
53 KiB
Go

package services
import (
"context"
"crypto/ed25519"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/hanzoai/agents/control-plane/internal/config"
"github.com/hanzoai/agents/control-plane/internal/logger"
"github.com/hanzoai/agents/control-plane/internal/storage"
"github.com/hanzoai/agents/control-plane/pkg/types"
)
// VCService handles verifiable credential generation, verification, and management.
type VCService struct {
config *config.DIDConfig
didService *DIDService
vcStorage *VCStorage
}
// NewVCService creates a new VC service instance with database storage.
func NewVCService(cfg *config.DIDConfig, didService *DIDService, storageProvider storage.StorageProvider) *VCService {
return &VCService{
config: cfg,
didService: didService,
vcStorage: NewVCStorageWithStorage(storageProvider),
}
}
// Initialize initializes the VC service.
func (s *VCService) Initialize() error {
if !s.config.Enabled {
return nil
}
return s.vcStorage.Initialize()
}
// GetDIDService returns the DID service instance for DID resolution operations.
func (s *VCService) GetDIDService() *DIDService {
return s.didService
}
// IsExecutionVCEnabled reports whether execution VC generation should run
// based on DID being enabled and the execution VC requirement flag.
func (s *VCService) IsExecutionVCEnabled() bool {
if s == nil || s.config == nil {
return false
}
if !s.config.Enabled {
return false
}
return s.config.VCRequirements.RequireVCForExecution
}
// ShouldPersistExecutionVC reports whether execution VCs should be persisted after generation.
func (s *VCService) ShouldPersistExecutionVC() bool {
if s == nil || s.config == nil {
return false
}
if !s.config.Enabled {
return false
}
return s.config.VCRequirements.PersistExecutionVC
}
// GetWorkflowVCStatusSummaries returns lightweight VC status summaries for the provided workflows.
func (s *VCService) GetWorkflowVCStatusSummaries(workflowIDs []string) (map[string]*types.WorkflowVCStatusSummary, error) {
summaries := make(map[string]*types.WorkflowVCStatusSummary, len(workflowIDs))
uniqueIDs := make([]string, 0, len(workflowIDs))
seen := make(map[string]struct{}, len(workflowIDs))
for _, id := range workflowIDs {
if id == "" {
continue
}
if _, exists := summaries[id]; !exists {
summaries[id] = types.DefaultWorkflowVCStatusSummary(id)
}
if _, exists := seen[id]; !exists {
seen[id] = struct{}{}
uniqueIDs = append(uniqueIDs, id)
}
}
if len(uniqueIDs) == 0 {
return summaries, nil
}
if s == nil || s.config == nil || !s.config.Enabled || s.vcStorage == nil {
return summaries, nil
}
ctx := context.Background()
aggregations, err := s.vcStorage.ListWorkflowVCStatusSummaries(ctx, uniqueIDs)
if err != nil {
return nil, err
}
for _, agg := range aggregations {
if agg == nil {
continue
}
summary := types.DefaultWorkflowVCStatusSummary(agg.WorkflowID)
summary.VCCount = agg.VCCount
summary.VerifiedCount = agg.VerifiedCount
summary.FailedCount = agg.FailedCount
summary.HasVCs = agg.VCCount > 0
if agg.LastCreatedAt != nil {
summary.LastVCCreated = agg.LastCreatedAt.UTC().Format(time.RFC3339)
}
switch {
case agg.VCCount == 0:
summary.VerificationStatus = "none"
case agg.FailedCount > 0:
summary.VerificationStatus = "failed"
case agg.VerifiedCount == agg.VCCount:
summary.VerificationStatus = "verified"
default:
summary.VerificationStatus = "pending"
}
summaries[agg.WorkflowID] = summary
}
return summaries, nil
}
// GenerateExecutionVC generates a verifiable credential for an execution.
func (s *VCService) GenerateExecutionVC(ctx *types.ExecutionContext, inputData, outputData []byte, status string, errorMessage *string, durationMS int) (*types.ExecutionVC, error) {
if !s.config.Enabled {
return nil, fmt.Errorf("DID system is disabled")
}
if !s.config.VCRequirements.RequireVCForExecution {
// VC generation is disabled by configuration - return nil without error
return nil, nil
}
// Basic validation with consistent null handling
processedInputData := marshalDataOrNull(inputData)
processedOutputData := marshalDataOrNull(outputData)
// Simple error message handling
var processedErrorMessage *string
if errorMessage != nil {
// Basic length limit for error messages
msg := *errorMessage
if len(msg) > 500 {
msg = msg[:500] + "...[truncated]"
}
processedErrorMessage = &msg
}
// Resolve caller DID
callerIdentity, err := s.didService.ResolveDID(ctx.CallerDID)
if err != nil {
return nil, fmt.Errorf("failed to resolve caller DID: %w", err)
}
// Handle optional target DID
var targetIdentity *types.DIDIdentity
if ctx.TargetDID != "" && ctx.TargetDID != "did:key:" {
targetIdentity, err = s.didService.ResolveDID(ctx.TargetDID)
if err != nil {
// Target DID resolution failure is not critical - continue without target identity
targetIdentity = nil
}
}
// Generate hashes for processed data
inputHash := s.hashData(processedInputData)
outputHash := s.hashData(processedOutputData)
// Create VC document with processed data
vcDoc := s.createVCDocument(ctx, callerIdentity, targetIdentity, inputHash, outputHash, status, processedErrorMessage, durationMS)
// Sign the VC
signature, err := s.signVC(vcDoc, callerIdentity)
if err != nil {
return nil, fmt.Errorf("failed to sign VC: %w", err)
}
// Add proof to VC document
vcDoc.Proof = types.VCProof{
Type: "Ed25519Signature2020",
Created: time.Now().UTC().Format(time.RFC3339),
VerificationMethod: fmt.Sprintf("%s#key-1", ctx.CallerDID),
ProofPurpose: "assertionMethod",
ProofValue: signature,
}
// Simple VC document serialization
vcDocBytes, err := json.Marshal(vcDoc)
if err != nil {
return nil, fmt.Errorf("failed to marshal VC document: %w", err)
}
// Persist canonical execution status for VC metadata
dbStatus := types.NormalizeExecutionStatus(status)
// Create execution VC
executionVC := &types.ExecutionVC{
VCID: s.generateVCID(),
ExecutionID: ctx.ExecutionID,
WorkflowID: ctx.WorkflowID,
SessionID: ctx.SessionID,
IssuerDID: ctx.CallerDID,
TargetDID: ctx.TargetDID,
CallerDID: ctx.CallerDID,
VCDocument: json.RawMessage(vcDocBytes),
Signature: signature,
StorageURI: "",
DocumentSize: int64(len(vcDocBytes)),
InputHash: inputHash,
OutputHash: outputHash,
Status: dbStatus,
CreatedAt: time.Now(),
}
// Store VC
if s.ShouldPersistExecutionVC() {
ctxBg := context.Background()
if err := s.vcStorage.StoreExecutionVC(ctxBg, executionVC); err != nil {
return nil, fmt.Errorf("failed to store execution VC: %w", err)
}
} else {
logger.Logger.Debug().Str("execution_id", ctx.ExecutionID).Msg("Execution VC persistence skipped by policy")
}
return executionVC, nil
}
// VerifyVC verifies a verifiable credential.
func (s *VCService) VerifyVC(vcDocument json.RawMessage) (*types.VCVerificationResponse, error) {
if !s.config.Enabled {
return &types.VCVerificationResponse{
Valid: false,
Error: "DID system is disabled",
}, nil
}
var vcDoc types.VCDocument
if err := json.Unmarshal(vcDocument, &vcDoc); err != nil {
return &types.VCVerificationResponse{
Valid: false,
Error: fmt.Sprintf("failed to parse VC document: %v", err),
}, nil
}
// Resolve issuer DID
issuerIdentity, err := s.didService.ResolveDID(vcDoc.Issuer)
if err != nil {
return &types.VCVerificationResponse{
Valid: false,
Error: fmt.Sprintf("failed to resolve issuer DID: %v", err),
}, nil
}
// Verify signature
valid, err := s.verifyVCSignature(&vcDoc, issuerIdentity)
if err != nil {
return &types.VCVerificationResponse{
Valid: false,
Error: fmt.Sprintf("failed to verify signature: %v", err),
}, nil
}
if !valid {
return &types.VCVerificationResponse{
Valid: false,
Message: "Invalid signature",
}, nil
}
return &types.VCVerificationResponse{
Valid: true,
IssuerDID: vcDoc.Issuer,
IssuedAt: vcDoc.IssuanceDate,
Message: "VC verified successfully",
}, nil
}
// GetWorkflowVCChain retrieves the complete VC chain for a workflow.
func (s *VCService) GetWorkflowVCChain(workflowID string) (*types.WorkflowVCChainResponse, error) {
logger.Logger.Debug().Msgf("🔍 GetWorkflowVCChain called for workflow: %s", workflowID)
logger.Logger.Debug().Msgf("🔍 DID system enabled: %v", s.config.Enabled)
if !s.config.Enabled {
logger.Logger.Debug().Msg("🔍 DID system is disabled")
return nil, fmt.Errorf("DID system is disabled")
}
// Get all execution VCs for the workflow
logger.Logger.Debug().Msgf("🔍 Querying execution VCs for workflow: %s", workflowID)
executionVCs, err := s.vcStorage.GetExecutionVCsByWorkflow(workflowID)
if err != nil {
logger.Logger.Debug().Err(err).Msg("🔍 Failed to get execution VCs")
return nil, fmt.Errorf("failed to get execution VCs: %w", err)
}
logger.Logger.Debug().Msgf("🔍 Found %d execution VCs for workflow %s", len(executionVCs), workflowID)
// Generate WorkflowVC on-demand with current state
logger.Logger.Debug().Msgf("🔍 Generating WorkflowVC on-demand for workflow: %s", workflowID)
workflowVC, err := s.generateWorkflowVCDocument(workflowID, executionVCs)
if err != nil {
logger.Logger.Debug().Err(err).Msg("🔍 Failed to generate WorkflowVC")
return nil, fmt.Errorf("failed to generate workflow VC: %w", err)
}
logger.Logger.Debug().Msgf("🔍 Generated WorkflowVC with ID: %s, status: %s", workflowVC.WorkflowVCID, workflowVC.Status)
// Collect DID resolution bundle for offline verification
logger.Logger.Debug().Msgf("🔍 Collecting DID resolution bundle for workflow: %s", workflowID)
didResolutionBundle, err := s.collectDIDResolutionBundle(executionVCs, workflowVC)
if err != nil {
logger.Logger.Debug().Err(err).Msg("🔍 Failed to collect DID resolution bundle")
// Don't fail the entire request if DID resolution fails - just log and continue without bundle
didResolutionBundle = make(map[string]types.DIDResolutionEntry)
}
logger.Logger.Debug().Msgf("🔍 Collected %d DID resolution entries", len(didResolutionBundle))
logger.Logger.Debug().Msgf("🔍 Returning VC chain with %d execution VCs and workflow status: %s", len(executionVCs), workflowVC.Status)
return &types.WorkflowVCChainResponse{
WorkflowID: workflowID,
ComponentVCs: executionVCs,
WorkflowVC: *workflowVC,
TotalSteps: len(executionVCs),
Status: workflowVC.Status,
DIDResolutionBundle: didResolutionBundle,
}, nil
}
// CreateWorkflowVC creates a workflow-level VC that aggregates execution VCs.
func (s *VCService) CreateWorkflowVC(workflowID, sessionID string, executionVCIDs []string) (*types.WorkflowVC, error) {
if !s.config.Enabled {
return nil, fmt.Errorf("DID system is disabled")
}
workflowVC := &types.WorkflowVC{
WorkflowID: workflowID,
SessionID: sessionID,
ComponentVCs: executionVCIDs,
WorkflowVCID: s.generateVCID(),
Status: string(types.ExecutionStatusSucceeded),
StartTime: time.Now(), // TODO: Get actual start time from first execution
EndTime: &[]time.Time{time.Now()}[0],
TotalSteps: len(executionVCIDs),
CompletedSteps: len(executionVCIDs),
StorageURI: "",
DocumentSize: 0,
}
// Store workflow VC
if s.ShouldPersistExecutionVC() {
ctx := context.Background()
if err := s.vcStorage.StoreWorkflowVC(ctx, workflowVC); err != nil {
return nil, fmt.Errorf("failed to store workflow VC: %w", err)
}
} else {
logger.Logger.Debug().Str("workflow_id", workflowID).Msg("Workflow VC persistence skipped by policy")
}
return workflowVC, nil
}
// createVCDocument creates a VC document for an execution.
func (s *VCService) createVCDocument(ctx *types.ExecutionContext, callerIdentity, targetIdentity *types.DIDIdentity, inputHash, outputHash, status string, errorMessage *string, durationMS int) *types.VCDocument {
vcID := s.generateVCID()
credentialSubject := types.VCCredentialSubject{
ExecutionID: ctx.ExecutionID,
WorkflowID: ctx.WorkflowID,
SessionID: ctx.SessionID,
Caller: types.VCCaller{
DID: ctx.CallerDID,
Type: callerIdentity.ComponentType,
AgentNodeDID: ctx.AgentNodeDID,
},
Target: types.VCTarget{
DID: ctx.TargetDID,
AgentNodeDID: ctx.AgentNodeDID,
FunctionName: func() string {
if targetIdentity != nil {
return targetIdentity.FunctionName
}
return "" // No target for standalone/root/leaf executions
}(),
},
Execution: types.VCExecution{
InputHash: inputHash,
OutputHash: outputHash,
Timestamp: ctx.Timestamp.UTC().Format(time.RFC3339),
DurationMS: durationMS,
Status: status,
},
Audit: types.VCAudit{
InputDataHash: inputHash,
OutputDataHash: outputHash,
Metadata: map[string]interface{}{
"hanzo_agents_version": "1.0.0",
"vc_version": "1.0",
},
},
}
if errorMessage != nil {
credentialSubject.Execution.ErrorMessage = *errorMessage
}
return &types.VCDocument{
Context: []string{
"https://www.w3.org/2018/credentials/v1",
"https://hanzo-agents.example.com/contexts/execution/v1",
},
Type: []string{
"VerifiableCredential",
"HanzoAgentsExecutionCredential",
},
ID: fmt.Sprintf("urn:hanzo-agents:vc:%s", vcID),
Issuer: ctx.CallerDID,
IssuanceDate: time.Now().UTC().Format(time.RFC3339),
CredentialSubject: credentialSubject,
}
}
// signVC signs a VC document using the caller's private key.
func (s *VCService) signVC(vcDoc *types.VCDocument, callerIdentity *types.DIDIdentity) (string, error) {
// Create canonical representation for signing
vcCopy := *vcDoc
vcCopy.Proof = types.VCProof{} // Remove proof for signing
canonicalBytes, err := json.Marshal(vcCopy)
if err != nil {
return "", fmt.Errorf("failed to marshal VC for signing: %w", err)
}
// Parse private key from JWK
var jwk map[string]interface{}
if err := json.Unmarshal([]byte(callerIdentity.PrivateKeyJWK), &jwk); err != nil {
return "", fmt.Errorf("failed to parse private key JWK: %w", err)
}
dValue, ok := jwk["d"].(string)
if !ok {
return "", fmt.Errorf("invalid private key JWK: missing 'd' parameter")
}
privateKeySeed, err := base64.RawURLEncoding.DecodeString(dValue)
if err != nil {
return "", fmt.Errorf("failed to decode private key seed: %w", err)
}
privateKey := ed25519.NewKeyFromSeed(privateKeySeed)
// Sign the canonical representation
signature := ed25519.Sign(privateKey, canonicalBytes)
return base64.RawURLEncoding.EncodeToString(signature), nil
}
// verifyVCSignature verifies the signature of a VC document.
func (s *VCService) verifyVCSignature(vcDoc *types.VCDocument, issuerIdentity *types.DIDIdentity) (bool, error) {
// Create canonical representation for verification
vcCopy := *vcDoc
vcCopy.Proof = types.VCProof{} // Remove proof for verification
canonicalBytes, err := json.Marshal(vcCopy)
if err != nil {
return false, fmt.Errorf("failed to marshal VC for verification: %w", err)
}
// Parse public key from JWK
var jwk map[string]interface{}
if err := json.Unmarshal([]byte(issuerIdentity.PublicKeyJWK), &jwk); err != nil {
return false, fmt.Errorf("failed to parse public key JWK: %w", err)
}
xValue, ok := jwk["x"].(string)
if !ok {
return false, fmt.Errorf("invalid public key JWK: missing 'x' parameter")
}
publicKeyBytes, err := base64.RawURLEncoding.DecodeString(xValue)
if err != nil {
return false, fmt.Errorf("failed to decode public key: %w", err)
}
publicKey := ed25519.PublicKey(publicKeyBytes)
// Decode signature
signatureBytes, err := base64.RawURLEncoding.DecodeString(vcDoc.Proof.ProofValue)
if err != nil {
return false, fmt.Errorf("failed to decode signature: %w", err)
}
// Verify signature
return ed25519.Verify(publicKey, canonicalBytes, signatureBytes), nil
}
// hashData creates a SHA-256 hash of data.
func (s *VCService) hashData(data []byte) string {
if !s.config.VCRequirements.HashSensitiveData {
return ""
}
hash := sha256.Sum256(data)
return base64.RawURLEncoding.EncodeToString(hash[:])
}
// generateVCID generates a unique VC ID.
func (s *VCService) generateVCID() string {
// Simple UUID-like generation for now
// In production, use proper UUID library
return fmt.Sprintf("vc-%d", time.Now().UnixNano())
}
// generateWorkflowVCDocument creates a WorkflowVC document on-demand.
func (s *VCService) generateWorkflowVCDocument(workflowID string, executionVCs []types.ExecutionVC) (*types.WorkflowVC, error) {
if !s.config.Enabled {
return nil, fmt.Errorf("DID system is disabled")
}
// Determine workflow status based on execution VCs
status := s.determineWorkflowStatus(executionVCs)
// Extract component VC IDs
componentVCIDs := make([]string, len(executionVCs))
for i, vc := range executionVCs {
componentVCIDs[i] = vc.VCID
}
// Determine session ID from first execution VC
sessionID := ""
if len(executionVCs) > 0 {
sessionID = executionVCs[0].SessionID
}
// Calculate start and end times
var startTime time.Time
var endTime *time.Time
if len(executionVCs) > 0 {
startTime = executionVCs[0].CreatedAt
latestTime := executionVCs[0].CreatedAt
for _, vc := range executionVCs {
if vc.CreatedAt.Before(startTime) {
startTime = vc.CreatedAt
}
if vc.CreatedAt.After(latestTime) {
latestTime = vc.CreatedAt
}
}
if types.IsTerminalExecutionStatus(status) {
endTime = &latestTime
}
} else {
startTime = time.Now()
}
// Get af server DID as issuer using dynamic resolution
hanzoAgentsServerID, err := s.didService.GetHanzoAgentsServerID()
if err != nil {
return nil, fmt.Errorf("failed to get af server ID: %w", err)
}
registry, err := s.didService.GetRegistry(hanzoAgentsServerID)
if err != nil {
return nil, fmt.Errorf("failed to get af server DID: %w", err)
}
issuerDID := registry.RootDID
if len(executionVCs) > 0 {
// Use the issuer from the first execution VC if available
issuerDID = executionVCs[0].IssuerDID
}
// Create WorkflowVC document
workflowVCDoc := s.createWorkflowVCDocument(workflowID, sessionID, componentVCIDs, status, startTime, endTime, issuerDID)
// Sign the WorkflowVC
issuerIdentity, err := s.didService.ResolveDID(issuerDID)
if err != nil {
return nil, fmt.Errorf("failed to resolve issuer DID: %w", err)
}
signature, err := s.signWorkflowVC(workflowVCDoc, issuerIdentity)
if err != nil {
return nil, fmt.Errorf("failed to sign workflow VC: %w", err)
}
// Add proof to VC document
workflowVCDoc.Proof = types.VCProof{
Type: "Ed25519Signature2020",
Created: time.Now().UTC().Format(time.RFC3339),
VerificationMethod: fmt.Sprintf("%s#key-1", issuerDID),
ProofPurpose: "assertionMethod",
ProofValue: signature,
}
// Serialize VC document
vcDocBytes, err := json.Marshal(workflowVCDoc)
if err != nil {
return nil, fmt.Errorf("failed to marshal workflow VC document: %w", err)
}
// Create WorkflowVC
workflowVC := &types.WorkflowVC{
WorkflowID: workflowID,
SessionID: sessionID,
ComponentVCs: componentVCIDs,
WorkflowVCID: s.generateVCID(),
Status: status,
StartTime: startTime,
EndTime: endTime,
TotalSteps: len(executionVCs),
CompletedSteps: s.countCompletedSteps(executionVCs),
VCDocument: json.RawMessage(vcDocBytes),
Signature: signature,
IssuerDID: issuerDID,
SnapshotTime: time.Now(),
StorageURI: "",
DocumentSize: int64(len(vcDocBytes)),
}
return workflowVC, nil
}
// createWorkflowVCDocument creates a WorkflowVC document.
func (s *VCService) createWorkflowVCDocument(workflowID, sessionID string, componentVCIDs []string, status string, startTime time.Time, endTime *time.Time, issuerDID string) *types.WorkflowVCDocument {
vcID := s.generateVCID()
credentialSubject := types.WorkflowVCCredentialSubject{
WorkflowID: workflowID,
SessionID: sessionID,
ComponentVCIDs: componentVCIDs,
TotalSteps: len(componentVCIDs),
CompletedSteps: len(componentVCIDs), // For now, assume all are completed
Status: status,
StartTime: startTime.UTC().Format(time.RFC3339),
SnapshotTime: time.Now().UTC().Format(time.RFC3339),
Orchestrator: types.VCCaller{
DID: issuerDID,
Type: "hanzo_agents_server",
AgentNodeDID: issuerDID,
},
Audit: types.VCAudit{
InputDataHash: "", // Workflow-level doesn't have specific input/output
OutputDataHash: "",
Metadata: map[string]interface{}{
"hanzo_agents_version": "1.0.0",
"vc_version": "1.0",
"workflow_type": "agent_execution_chain",
"total_executions": len(componentVCIDs),
},
},
}
if endTime != nil {
endTimeStr := endTime.UTC().Format(time.RFC3339)
credentialSubject.EndTime = &endTimeStr
}
return &types.WorkflowVCDocument{
Context: []string{
"https://www.w3.org/2018/credentials/v1",
"https://hanzo-agents.example.com/contexts/workflow/v1",
},
Type: []string{
"VerifiableCredential",
"HanzoAgentsWorkflowCredential",
},
ID: fmt.Sprintf("urn:hanzo-agents:workflow-vc:%s", vcID),
Issuer: issuerDID,
IssuanceDate: time.Now().UTC().Format(time.RFC3339),
CredentialSubject: credentialSubject,
}
}
// signWorkflowVC signs a WorkflowVC document.
func (s *VCService) signWorkflowVC(vcDoc *types.WorkflowVCDocument, issuerIdentity *types.DIDIdentity) (string, error) {
// Create canonical representation for signing
vcCopy := *vcDoc
vcCopy.Proof = types.VCProof{} // Remove proof for signing
canonicalBytes, err := json.Marshal(vcCopy)
if err != nil {
return "", fmt.Errorf("failed to marshal workflow VC for signing: %w", err)
}
// Parse private key from JWK
var jwk map[string]interface{}
if err := json.Unmarshal([]byte(issuerIdentity.PrivateKeyJWK), &jwk); err != nil {
return "", fmt.Errorf("failed to parse private key JWK: %w", err)
}
dValue, ok := jwk["d"].(string)
if !ok {
return "", fmt.Errorf("invalid private key JWK: missing 'd' parameter")
}
privateKeySeed, err := base64.RawURLEncoding.DecodeString(dValue)
if err != nil {
return "", fmt.Errorf("failed to decode private key seed: %w", err)
}
privateKey := ed25519.NewKeyFromSeed(privateKeySeed)
// Sign the canonical representation
signature := ed25519.Sign(privateKey, canonicalBytes)
return base64.RawURLEncoding.EncodeToString(signature), nil
}
// determineWorkflowStatus determines the overall status of a workflow based on execution VCs.
func (s *VCService) determineWorkflowStatus(executionVCs []types.ExecutionVC) string {
if len(executionVCs) == 0 {
return string(types.ExecutionStatusPending)
}
hasRunning := false
hasQueued := false
hasPending := false
hasFailed := false
hasCancelled := false
hasTimeout := false
hasUnknown := false
for _, vc := range executionVCs {
normalized := types.NormalizeExecutionStatus(vc.Status)
switch normalized {
case string(types.ExecutionStatusFailed):
hasFailed = true
case string(types.ExecutionStatusCancelled):
hasCancelled = true
case string(types.ExecutionStatusTimeout):
hasTimeout = true
case string(types.ExecutionStatusRunning):
hasRunning = true
case string(types.ExecutionStatusQueued):
hasQueued = true
case string(types.ExecutionStatusPending):
hasPending = true
case string(types.ExecutionStatusUnknown):
hasUnknown = true
}
}
switch {
case hasFailed:
return string(types.ExecutionStatusFailed)
case hasTimeout:
return string(types.ExecutionStatusTimeout)
case hasCancelled:
return string(types.ExecutionStatusCancelled)
case hasRunning:
return string(types.ExecutionStatusRunning)
case hasQueued:
return string(types.ExecutionStatusQueued)
case hasPending:
return string(types.ExecutionStatusPending)
case hasUnknown:
return string(types.ExecutionStatusUnknown)
default:
return string(types.ExecutionStatusSucceeded)
}
}
// countCompletedSteps counts the number of completed execution VCs.
func (s *VCService) countCompletedSteps(executionVCs []types.ExecutionVC) int {
count := 0
for _, vc := range executionVCs {
if types.NormalizeExecutionStatus(vc.Status) == string(types.ExecutionStatusSucceeded) {
count++
}
}
return count
}
// QueryExecutionVCs queries execution VCs with filters.
func (s *VCService) QueryExecutionVCs(filters *types.VCFilters) ([]types.ExecutionVC, error) {
if !s.config.Enabled {
return nil, fmt.Errorf("DID system is disabled")
}
return s.vcStorage.QueryExecutionVCs(filters)
}
// GetExecutionVCByExecutionID retrieves a single execution VC by execution identifier.
func (s *VCService) GetExecutionVCByExecutionID(executionID string) (*types.ExecutionVC, error) {
if !s.config.Enabled {
return nil, fmt.Errorf("DID system is disabled")
}
return s.vcStorage.GetExecutionVCByExecutionID(executionID)
}
// ListWorkflowVCs lists all workflow VCs.
func (s *VCService) ListWorkflowVCs() ([]*types.WorkflowVC, error) {
if !s.config.Enabled {
return nil, fmt.Errorf("DID system is disabled")
}
return s.vcStorage.ListWorkflowVCs()
}
// collectDIDResolutionBundle collects all unique DIDs from the VC chain and resolves their public keys.
func (s *VCService) collectDIDResolutionBundle(executionVCs []types.ExecutionVC, workflowVC *types.WorkflowVC) (map[string]types.DIDResolutionEntry, error) {
bundle := make(map[string]types.DIDResolutionEntry)
resolvedAt := time.Now().UTC().Format(time.RFC3339)
// Collect unique DIDs from execution VCs
uniqueDIDs := make(map[string]bool)
for _, vc := range executionVCs {
if vc.IssuerDID != "" && vc.IssuerDID != "did:key:" {
uniqueDIDs[vc.IssuerDID] = true
}
if vc.CallerDID != "" && vc.CallerDID != "did:key:" {
uniqueDIDs[vc.CallerDID] = true
}
if vc.TargetDID != "" && vc.TargetDID != "did:key:" {
uniqueDIDs[vc.TargetDID] = true
}
}
// Add workflow VC issuer DID
if workflowVC.IssuerDID != "" && workflowVC.IssuerDID != "did:key:" {
uniqueDIDs[workflowVC.IssuerDID] = true
}
// Resolve each unique DID and collect public keys
for did := range uniqueDIDs {
if did == "" || did == "did:key:" || len(strings.TrimSpace(did)) == 0 {
continue // Skip empty or incomplete DIDs
}
identity, err := s.didService.ResolveDID(did)
if err != nil {
continue // Skip DIDs that can't be resolved
}
// Determine DID method from the DID string
method := "key" // Default to "key" method
if len(did) > 4 && did[:4] == "did:" {
parts := strings.Split(did, ":")
if len(parts) >= 2 {
method = parts[1]
}
}
// Parse the public key JWK string into a JSON object
var publicKeyJWK map[string]interface{}
if err := json.Unmarshal([]byte(identity.PublicKeyJWK), &publicKeyJWK); err != nil {
continue // Skip DIDs with invalid public key JWK
}
// Create resolution entry with properly parsed public key JWK
bundle[did] = types.DIDResolutionEntry{
Method: method,
PublicKeyJWK: json.RawMessage(identity.PublicKeyJWK), // Keep as raw JSON
ResolvedFrom: "bundled",
ResolvedAt: resolvedAt,
}
}
return bundle, nil
}
// VerificationIssue represents a specific verification problem
type VerificationIssue struct {
Type string `json:"type"`
Severity string `json:"severity"` // "critical", "warning", "info"
Component string `json:"component"`
Field string `json:"field"`
Expected string `json:"expected"`
Actual string `json:"actual"`
Description string `json:"description"`
}
// ComprehensiveVCVerificationResult provides detailed verification results
type ComprehensiveVCVerificationResult struct {
Valid bool `json:"valid"`
OverallScore float64 `json:"overall_score"` // 0-100
CriticalIssues []VerificationIssue `json:"critical_issues"`
Warnings []VerificationIssue `json:"warnings"`
IntegrityChecks IntegrityCheckResults `json:"integrity_checks"`
SecurityAnalysis SecurityAnalysis `json:"security_analysis"`
ComplianceChecks ComplianceChecks `json:"compliance_checks"`
VerificationTimestamp string `json:"verification_timestamp"`
}
// IntegrityCheckResults represents various integrity verification results
type IntegrityCheckResults struct {
MetadataConsistency bool `json:"metadata_consistency"`
FieldConsistency bool `json:"field_consistency"`
TimestampValidation bool `json:"timestamp_validation"`
HashValidation bool `json:"hash_validation"`
StructuralIntegrity bool `json:"structural_integrity"`
Issues []VerificationIssue `json:"issues"`
}
// SecurityAnalysis represents security-focused verification results
type SecurityAnalysis struct {
SignatureStrength string `json:"signature_strength"`
KeyValidation bool `json:"key_validation"`
DIDAuthenticity bool `json:"did_authenticity"`
ReplayProtection bool `json:"replay_protection"`
TamperEvidence []string `json:"tamper_evidence"`
SecurityScore float64 `json:"security_score"`
Issues []VerificationIssue `json:"issues"`
}
// ComplianceChecks represents compliance and audit verification results
type ComplianceChecks struct {
W3CCompliance bool `json:"w3c_compliance"`
HanzoAgentsStandardCompliance bool `json:"hanzo_agents_standard_compliance"`
AuditTrailIntegrity bool `json:"audit_trail_integrity"`
DataIntegrityChecks bool `json:"data_integrity_checks"`
Issues []VerificationIssue `json:"issues"`
}
// VerifyExecutionVCComprehensive performs comprehensive verification of an execution VC
func (s *VCService) VerifyExecutionVCComprehensive(executionID string) (*ComprehensiveVCVerificationResult, error) {
if !s.config.Enabled {
return &ComprehensiveVCVerificationResult{
Valid: false,
OverallScore: 0,
CriticalIssues: []VerificationIssue{{Type: "system_disabled", Severity: "critical", Description: "DID system is disabled"}},
VerificationTimestamp: time.Now().UTC().Format(time.RFC3339),
}, nil
}
// Get the execution VC
filters := &types.VCFilters{Limit: 1000}
executionVCs, err := s.vcStorage.QueryExecutionVCs(filters)
if err != nil {
return nil, fmt.Errorf("failed to query execution VCs: %w", err)
}
var executionVC *types.ExecutionVC
for _, vc := range executionVCs {
if vc.ExecutionID == executionID {
executionVC = &vc
break
}
}
if executionVC == nil {
return &ComprehensiveVCVerificationResult{
Valid: false,
OverallScore: 0,
CriticalIssues: []VerificationIssue{{Type: "vc_not_found", Severity: "critical", Description: "VC not found for execution"}},
VerificationTimestamp: time.Now().UTC().Format(time.RFC3339),
}, nil
}
result := &ComprehensiveVCVerificationResult{
VerificationTimestamp: time.Now().UTC().Format(time.RFC3339),
CriticalIssues: []VerificationIssue{},
Warnings: []VerificationIssue{},
}
// Parse VC document
var vcDoc types.VCDocument
if err := json.Unmarshal(executionVC.VCDocument, &vcDoc); err != nil {
result.CriticalIssues = append(result.CriticalIssues, VerificationIssue{
Type: "parse_error",
Severity: "critical",
Component: executionVC.VCID,
Description: fmt.Sprintf("Failed to parse VC document: %v", err),
})
result.Valid = false
result.OverallScore = 0
return result, nil
}
// Perform comprehensive verification checks
result.IntegrityChecks = s.performIntegrityChecks(executionVC, &vcDoc)
result.SecurityAnalysis = s.performSecurityAnalysis(executionVC, &vcDoc)
result.ComplianceChecks = s.performComplianceChecks(&vcDoc)
// Collect all issues
allIssues := []VerificationIssue{}
allIssues = append(allIssues, result.IntegrityChecks.Issues...)
allIssues = append(allIssues, result.SecurityAnalysis.Issues...)
allIssues = append(allIssues, result.ComplianceChecks.Issues...)
// Separate critical issues and warnings
for _, issue := range allIssues {
if issue.Severity == "critical" {
result.CriticalIssues = append(result.CriticalIssues, issue)
} else if issue.Severity == "warning" {
result.Warnings = append(result.Warnings, issue)
}
}
// Calculate overall validity and score
result.Valid = len(result.CriticalIssues) == 0
result.OverallScore = s.calculateOverallScore(result)
return result, nil
}
// performIntegrityChecks performs various integrity checks on the VC
func (s *VCService) performIntegrityChecks(execVC *types.ExecutionVC, vcDoc *types.VCDocument) IntegrityCheckResults {
result := IntegrityCheckResults{
MetadataConsistency: true,
FieldConsistency: true,
TimestampValidation: true,
HashValidation: true,
StructuralIntegrity: true,
Issues: []VerificationIssue{},
}
// CRITICAL CHECK 1: Metadata consistency between top-level and VC document
if execVC.IssuerDID != vcDoc.Issuer {
result.MetadataConsistency = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "issuer_mismatch",
Severity: "critical",
Component: execVC.VCID,
Field: "issuer_did",
Expected: execVC.IssuerDID,
Actual: vcDoc.Issuer,
Description: "Issuer DID mismatch between metadata and VC document",
})
}
// CRITICAL CHECK 2: Execution ID consistency
if execVC.ExecutionID != vcDoc.CredentialSubject.ExecutionID {
result.FieldConsistency = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "execution_id_mismatch",
Severity: "critical",
Component: execVC.VCID,
Field: "execution_id",
Expected: execVC.ExecutionID,
Actual: vcDoc.CredentialSubject.ExecutionID,
Description: "Execution ID mismatch between metadata and VC document",
})
}
// CRITICAL CHECK 3: Workflow ID consistency
if execVC.WorkflowID != vcDoc.CredentialSubject.WorkflowID {
result.FieldConsistency = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "workflow_id_mismatch",
Severity: "critical",
Component: execVC.VCID,
Field: "workflow_id",
Expected: execVC.WorkflowID,
Actual: vcDoc.CredentialSubject.WorkflowID,
Description: "Workflow ID mismatch between metadata and VC document",
})
}
// CRITICAL CHECK 4: Session ID consistency
if execVC.SessionID != vcDoc.CredentialSubject.SessionID {
result.FieldConsistency = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "session_id_mismatch",
Severity: "critical",
Component: execVC.VCID,
Field: "session_id",
Expected: execVC.SessionID,
Actual: vcDoc.CredentialSubject.SessionID,
Description: "Session ID mismatch between metadata and VC document",
})
}
// CRITICAL CHECK 5: Caller DID consistency
if execVC.CallerDID != vcDoc.CredentialSubject.Caller.DID {
result.FieldConsistency = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "caller_did_mismatch",
Severity: "critical",
Component: execVC.VCID,
Field: "caller_did",
Expected: execVC.CallerDID,
Actual: vcDoc.CredentialSubject.Caller.DID,
Description: "Caller DID mismatch between metadata and VC document",
})
}
// CRITICAL CHECK 6: Target DID consistency
if execVC.TargetDID != vcDoc.CredentialSubject.Target.DID {
result.FieldConsistency = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "target_did_mismatch",
Severity: "critical",
Component: execVC.VCID,
Field: "target_did",
Expected: execVC.TargetDID,
Actual: vcDoc.CredentialSubject.Target.DID,
Description: "Target DID mismatch between metadata and VC document",
})
}
// CRITICAL CHECK 7: Status consistency (with HanzoAgents system status mapping)
if !s.isStatusConsistent(execVC.Status, vcDoc.CredentialSubject.Execution.Status) {
result.FieldConsistency = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "status_mismatch",
Severity: "critical",
Component: execVC.VCID,
Field: "status",
Expected: execVC.Status,
Actual: vcDoc.CredentialSubject.Execution.Status,
Description: "Status mismatch between metadata and VC document",
})
}
// CRITICAL CHECK 8: Hash consistency
if execVC.InputHash != vcDoc.CredentialSubject.Execution.InputHash {
result.HashValidation = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "input_hash_mismatch",
Severity: "critical",
Component: execVC.VCID,
Field: "input_hash",
Expected: execVC.InputHash,
Actual: vcDoc.CredentialSubject.Execution.InputHash,
Description: "Input hash mismatch between metadata and VC document",
})
}
if execVC.OutputHash != vcDoc.CredentialSubject.Execution.OutputHash {
result.HashValidation = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "output_hash_mismatch",
Severity: "critical",
Component: execVC.VCID,
Field: "output_hash",
Expected: execVC.OutputHash,
Actual: vcDoc.CredentialSubject.Execution.OutputHash,
Description: "Output hash mismatch between metadata and VC document",
})
}
// CRITICAL CHECK 9: Signature consistency
if execVC.Signature != vcDoc.Proof.ProofValue {
result.StructuralIntegrity = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "signature_mismatch",
Severity: "critical",
Component: execVC.VCID,
Field: "signature",
Expected: execVC.Signature,
Actual: vcDoc.Proof.ProofValue,
Description: "Signature mismatch between metadata and VC document",
})
}
// CRITICAL CHECK 10: Timestamp validation
if err := s.validateTimestamp(vcDoc.IssuanceDate); err != nil {
result.TimestampValidation = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "invalid_timestamp",
Severity: "critical",
Component: execVC.VCID,
Field: "issuance_date",
Description: fmt.Sprintf("Invalid timestamp: %v", err),
})
}
// CRITICAL CHECK 11: VC structure validation
if err := s.validateVCStructure(vcDoc); err != nil {
result.StructuralIntegrity = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "invalid_structure",
Severity: "critical",
Component: execVC.VCID,
Description: fmt.Sprintf("Invalid VC structure: %v", err),
})
}
return result
}
// performSecurityAnalysis performs security-focused analysis
func (s *VCService) performSecurityAnalysis(execVC *types.ExecutionVC, vcDoc *types.VCDocument) SecurityAnalysis {
result := SecurityAnalysis{
SignatureStrength: "Ed25519",
KeyValidation: true,
DIDAuthenticity: true,
ReplayProtection: true,
TamperEvidence: []string{},
SecurityScore: 100.0,
Issues: []VerificationIssue{},
}
// CRITICAL CHECK: Cryptographic signature verification
issuerIdentity, err := s.didService.ResolveDID(vcDoc.Issuer)
if err != nil {
result.DIDAuthenticity = false
result.SecurityScore -= 50.0
result.Issues = append(result.Issues, VerificationIssue{
Type: "did_resolution_failed",
Severity: "critical",
Component: execVC.VCID,
Description: fmt.Sprintf("Failed to resolve issuer DID: %v", err),
})
} else {
valid, err := s.verifyVCSignature(vcDoc, issuerIdentity)
if err != nil || !valid {
result.KeyValidation = false
result.SecurityScore -= 40.0
result.Issues = append(result.Issues, VerificationIssue{
Type: "signature_verification_failed",
Severity: "critical",
Component: execVC.VCID,
Description: fmt.Sprintf("Signature verification failed: %v", err),
})
}
}
// Check for tamper evidence
if evidence := s.detectTamperEvidence(execVC, vcDoc); len(evidence) > 0 {
result.TamperEvidence = evidence
result.SecurityScore -= 20.0
result.Issues = append(result.Issues, VerificationIssue{
Type: "tamper_evidence",
Severity: "warning",
Component: execVC.VCID,
Description: fmt.Sprintf("Tamper evidence detected: %v", evidence),
})
}
return result
}
// performComplianceChecks performs compliance verification
func (s *VCService) performComplianceChecks(vcDoc *types.VCDocument) ComplianceChecks {
result := ComplianceChecks{
W3CCompliance: true,
HanzoAgentsStandardCompliance: true,
AuditTrailIntegrity: true,
DataIntegrityChecks: true,
Issues: []VerificationIssue{},
}
// Check W3C compliance
if !s.checkW3CCompliance(vcDoc) {
result.W3CCompliance = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "w3c_compliance_failure",
Severity: "warning",
Component: vcDoc.ID,
Description: "VC does not meet W3C standards",
})
}
// Check HanzoAgents standard compliance
if !s.checkHanzoAgentsStandardCompliance(vcDoc) {
result.HanzoAgentsStandardCompliance = false
result.Issues = append(result.Issues, VerificationIssue{
Type: "hanzo_agents_compliance_failure",
Severity: "warning",
Component: vcDoc.ID,
Description: "VC does not meet HanzoAgents standard requirements",
})
}
return result
}
// Helper methods for verification checks
func (s *VCService) validateTimestamp(timestamp string) error {
_, err := time.Parse(time.RFC3339, timestamp)
return err
}
func (s *VCService) validateVCStructure(vcDoc *types.VCDocument) error {
// Check required fields
if len(vcDoc.Context) == 0 {
return fmt.Errorf("missing @context")
}
if len(vcDoc.Type) == 0 {
return fmt.Errorf("missing type")
}
if vcDoc.ID == "" {
return fmt.Errorf("missing id")
}
if vcDoc.Issuer == "" {
return fmt.Errorf("missing issuer")
}
if vcDoc.IssuanceDate == "" {
return fmt.Errorf("missing issuanceDate")
}
return nil
}
// marshalDataOrNull marshals data to JSON or returns null JSON if nil/error
func marshalDataOrNull(data interface{}) []byte {
if data == nil {
return []byte("null")
}
if jsonData, err := json.Marshal(data); err == nil {
return jsonData
}
return []byte("null")
}
func (s *VCService) detectTamperEvidence(execVC *types.ExecutionVC, vcDoc *types.VCDocument) []string {
evidence := []string{}
// Check for inconsistencies that indicate tampering
if execVC.IssuerDID != vcDoc.Issuer {
evidence = append(evidence, "issuer_did_inconsistency")
}
if execVC.ExecutionID != vcDoc.CredentialSubject.ExecutionID {
evidence = append(evidence, "execution_id_inconsistency")
}
if execVC.Signature != vcDoc.Proof.ProofValue {
evidence = append(evidence, "signature_inconsistency")
}
return evidence
}
func (s *VCService) checkW3CCompliance(vcDoc *types.VCDocument) bool {
// Check W3C VC standard compliance
requiredContexts := []string{"https://www.w3.org/2018/credentials/v1"}
for _, required := range requiredContexts {
found := false
for _, context := range vcDoc.Context {
if context == required {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func (s *VCService) checkHanzoAgentsStandardCompliance(vcDoc *types.VCDocument) bool {
// Check HanzoAgents-specific compliance requirements
requiredTypes := []string{"VerifiableCredential", "HanzoAgentsExecutionCredential"}
for _, required := range requiredTypes {
found := false
for _, vcType := range vcDoc.Type {
if vcType == required {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func (s *VCService) calculateOverallScore(result *ComprehensiveVCVerificationResult) float64 {
score := 100.0
// Deduct points for critical issues
score -= float64(len(result.CriticalIssues)) * 25.0
// Deduct points for warnings
score -= float64(len(result.Warnings)) * 5.0
// Factor in security score
score = (score + result.SecurityAnalysis.SecurityScore) / 2.0
if score < 0 {
score = 0
}
return score
}
// isStatusConsistent checks if status values are consistent, accounting for HanzoAgents system status mapping
func (s *VCService) isStatusConsistent(metadataStatus, vcDocStatus string) bool {
return types.NormalizeExecutionStatus(metadataStatus) == types.NormalizeExecutionStatus(vcDocStatus)
}
// VerifyWorkflowVCComprehensive performs comprehensive verification of a workflow VC chain
func (s *VCService) VerifyWorkflowVCComprehensive(workflowID string) (*ComprehensiveVCVerificationResult, error) {
if !s.config.Enabled {
return &ComprehensiveVCVerificationResult{
Valid: false,
OverallScore: 0,
CriticalIssues: []VerificationIssue{{Type: "system_disabled", Severity: "critical", Description: "DID system is disabled"}},
VerificationTimestamp: time.Now().UTC().Format(time.RFC3339),
}, nil
}
// Get the workflow VC chain
vcChain, err := s.GetWorkflowVCChain(workflowID)
if err != nil {
return &ComprehensiveVCVerificationResult{
Valid: false,
OverallScore: 0,
CriticalIssues: []VerificationIssue{{Type: "workflow_chain_error", Severity: "critical", Description: fmt.Sprintf("Failed to get workflow VC chain: %v", err)}},
VerificationTimestamp: time.Now().UTC().Format(time.RFC3339),
}, nil
}
result := &ComprehensiveVCVerificationResult{
VerificationTimestamp: time.Now().UTC().Format(time.RFC3339),
CriticalIssues: []VerificationIssue{},
Warnings: []VerificationIssue{},
}
// Verify each execution VC in the workflow
allIntegrityChecks := IntegrityCheckResults{
MetadataConsistency: true,
FieldConsistency: true,
TimestampValidation: true,
HashValidation: true,
StructuralIntegrity: true,
Issues: []VerificationIssue{},
}
allSecurityAnalysis := SecurityAnalysis{
SignatureStrength: "Ed25519",
KeyValidation: true,
DIDAuthenticity: true,
ReplayProtection: true,
TamperEvidence: []string{},
SecurityScore: 100.0,
Issues: []VerificationIssue{},
}
allComplianceChecks := ComplianceChecks{
W3CCompliance: true,
HanzoAgentsStandardCompliance: true,
AuditTrailIntegrity: true,
DataIntegrityChecks: true,
Issues: []VerificationIssue{},
}
// Verify each execution VC in the workflow
for _, execVC := range vcChain.ComponentVCs {
// Parse VC document
var vcDoc types.VCDocument
if err := json.Unmarshal(execVC.VCDocument, &vcDoc); err != nil {
result.CriticalIssues = append(result.CriticalIssues, VerificationIssue{
Type: "parse_error",
Severity: "critical",
Component: execVC.VCID,
Description: fmt.Sprintf("Failed to parse VC document: %v", err),
})
continue
}
// Perform verification checks for this execution VC
integrityChecks := s.performIntegrityChecks(&execVC, &vcDoc)
securityAnalysis := s.performSecurityAnalysis(&execVC, &vcDoc)
complianceChecks := s.performComplianceChecks(&vcDoc)
// Aggregate results
if !integrityChecks.MetadataConsistency {
allIntegrityChecks.MetadataConsistency = false
}
if !integrityChecks.FieldConsistency {
allIntegrityChecks.FieldConsistency = false
}
if !integrityChecks.TimestampValidation {
allIntegrityChecks.TimestampValidation = false
}
if !integrityChecks.HashValidation {
allIntegrityChecks.HashValidation = false
}
if !integrityChecks.StructuralIntegrity {
allIntegrityChecks.StructuralIntegrity = false
}
if !securityAnalysis.KeyValidation {
allSecurityAnalysis.KeyValidation = false
}
if !securityAnalysis.DIDAuthenticity {
allSecurityAnalysis.DIDAuthenticity = false
}
if !securityAnalysis.ReplayProtection {
allSecurityAnalysis.ReplayProtection = false
}
if !complianceChecks.W3CCompliance {
allComplianceChecks.W3CCompliance = false
}
if !complianceChecks.HanzoAgentsStandardCompliance {
allComplianceChecks.HanzoAgentsStandardCompliance = false
}
if !complianceChecks.AuditTrailIntegrity {
allComplianceChecks.AuditTrailIntegrity = false
}
if !complianceChecks.DataIntegrityChecks {
allComplianceChecks.DataIntegrityChecks = false
}
// Collect all issues
allIntegrityChecks.Issues = append(allIntegrityChecks.Issues, integrityChecks.Issues...)
allSecurityAnalysis.Issues = append(allSecurityAnalysis.Issues, securityAnalysis.Issues...)
allComplianceChecks.Issues = append(allComplianceChecks.Issues, complianceChecks.Issues...)
// Collect tamper evidence
allSecurityAnalysis.TamperEvidence = append(allSecurityAnalysis.TamperEvidence, securityAnalysis.TamperEvidence...)
// Update security score (take minimum)
if securityAnalysis.SecurityScore < allSecurityAnalysis.SecurityScore {
allSecurityAnalysis.SecurityScore = securityAnalysis.SecurityScore
}
}
// Verify workflow VC itself if it exists
if vcChain.WorkflowVC.VCDocument != nil {
var workflowVCDoc types.WorkflowVCDocument
if err := json.Unmarshal(vcChain.WorkflowVC.VCDocument, &workflowVCDoc); err != nil {
result.CriticalIssues = append(result.CriticalIssues, VerificationIssue{
Type: "workflow_vc_parse_error",
Severity: "critical",
Component: vcChain.WorkflowVC.WorkflowVCID,
Description: fmt.Sprintf("Failed to parse workflow VC document: %v", err),
})
} else {
// Verify workflow VC signature
issuerIdentity, err := s.didService.ResolveDID(workflowVCDoc.Issuer)
if err != nil {
allSecurityAnalysis.DIDAuthenticity = false
allSecurityAnalysis.Issues = append(allSecurityAnalysis.Issues, VerificationIssue{
Type: "workflow_did_resolution_failed",
Severity: "critical",
Component: vcChain.WorkflowVC.WorkflowVCID,
Description: fmt.Sprintf("Failed to resolve workflow VC issuer DID: %v", err),
})
} else {
valid, err := s.verifyWorkflowVCSignature(&workflowVCDoc, issuerIdentity)
if err != nil || !valid {
allSecurityAnalysis.KeyValidation = false
allSecurityAnalysis.Issues = append(allSecurityAnalysis.Issues, VerificationIssue{
Type: "workflow_signature_verification_failed",
Severity: "critical",
Component: vcChain.WorkflowVC.WorkflowVCID,
Description: fmt.Sprintf("Workflow VC signature verification failed: %v", err),
})
}
}
// Check workflow VC compliance
if !s.checkWorkflowVCCompliance(&workflowVCDoc) {
allComplianceChecks.HanzoAgentsStandardCompliance = false
allComplianceChecks.Issues = append(allComplianceChecks.Issues, VerificationIssue{
Type: "workflow_compliance_failure",
Severity: "warning",
Component: vcChain.WorkflowVC.WorkflowVCID,
Description: "Workflow VC does not meet HanzoAgents standard requirements",
})
}
}
}
// Set aggregated results
result.IntegrityChecks = allIntegrityChecks
result.SecurityAnalysis = allSecurityAnalysis
result.ComplianceChecks = allComplianceChecks
// Collect all issues
allIssues := []VerificationIssue{}
allIssues = append(allIssues, result.IntegrityChecks.Issues...)
allIssues = append(allIssues, result.SecurityAnalysis.Issues...)
allIssues = append(allIssues, result.ComplianceChecks.Issues...)
// Separate critical issues and warnings
for _, issue := range allIssues {
if issue.Severity == "critical" {
result.CriticalIssues = append(result.CriticalIssues, issue)
} else if issue.Severity == "warning" {
result.Warnings = append(result.Warnings, issue)
}
}
// Calculate overall validity and score
result.Valid = len(result.CriticalIssues) == 0
result.OverallScore = s.calculateOverallScore(result)
return result, nil
}
// verifyWorkflowVCSignature verifies the signature of a WorkflowVC document
func (s *VCService) verifyWorkflowVCSignature(vcDoc *types.WorkflowVCDocument, issuerIdentity *types.DIDIdentity) (bool, error) {
// Create canonical representation for verification
vcCopy := *vcDoc
vcCopy.Proof = types.VCProof{} // Remove proof for verification
canonicalBytes, err := json.Marshal(vcCopy)
if err != nil {
return false, fmt.Errorf("failed to marshal workflow VC for verification: %w", err)
}
// Parse public key from JWK
var jwk map[string]interface{}
if err := json.Unmarshal([]byte(issuerIdentity.PublicKeyJWK), &jwk); err != nil {
return false, fmt.Errorf("failed to parse public key JWK: %w", err)
}
xValue, ok := jwk["x"].(string)
if !ok {
return false, fmt.Errorf("invalid public key JWK: missing 'x' parameter")
}
publicKeyBytes, err := base64.RawURLEncoding.DecodeString(xValue)
if err != nil {
return false, fmt.Errorf("failed to decode public key: %w", err)
}
publicKey := ed25519.PublicKey(publicKeyBytes)
// Decode signature
signatureBytes, err := base64.RawURLEncoding.DecodeString(vcDoc.Proof.ProofValue)
if err != nil {
return false, fmt.Errorf("failed to decode signature: %w", err)
}
// Verify signature
return ed25519.Verify(publicKey, canonicalBytes, signatureBytes), nil
}
// checkWorkflowVCCompliance checks if a workflow VC meets HanzoAgents standard compliance
func (s *VCService) checkWorkflowVCCompliance(vcDoc *types.WorkflowVCDocument) bool {
// Check HanzoAgents-specific compliance requirements for workflow VCs
requiredTypes := []string{"VerifiableCredential", "HanzoAgentsWorkflowCredential"}
for _, required := range requiredTypes {
found := false
for _, vcType := range vcDoc.Type {
if vcType == required {
found = true
break
}
}
if !found {
return false
}
}
return true
}