/// <inheritdoc /> public virtual async Task ProcessCredentialAsync(Pool pool, Wallet wallet, CredentialMessage credential) { var(didOrKey, _) = MessageUtils.ParseMessageType(credential.Type); var connectionSearch = await ConnectionService.ListAsync(wallet, new SearchRecordQuery { { TagConstants.MyDid, didOrKey } }); if (!connectionSearch.Any()) { throw new Exception($"Can't find connection record for type {credential.Type}"); } var connection = connectionSearch.First(); var(details, _) = await MessageSerializer.UnpackSealedAsync <CredentialDetails>(credential.Content, wallet, connection.MyVk); var offer = JObject.Parse(details.CredentialJson); var definitionId = offer["cred_def_id"].ToObject <string>(); var schemaId = offer["schema_id"].ToObject <string>(); var revRegId = offer["rev_reg_id"]?.ToObject <string>(); var credentialSearch = await RecordService.SearchAsync <CredentialRecord>(wallet, new SearchRecordQuery { { TagConstants.SchemaId, schemaId }, { TagConstants.DefinitionId, definitionId }, { TagConstants.ConnectionId, connection.GetId() } }, null, 1); var credentialRecord = credentialSearch.Single(); // TODO: Should throw or resolve conflict gracefully if multiple credential records are found var credentialDefinition = await LedgerService.LookupDefinitionAsync(pool, connection.MyDid, definitionId); string revocationRegistryDefinitionJson = null; if (!string.IsNullOrEmpty(revRegId)) { // If credential supports revocation, lookup registry definition var revocationRegistry = await LedgerService.LookupRevocationRegistryDefinitionAsync(pool, connection.MyDid, revRegId); revocationRegistryDefinitionJson = revocationRegistry.ObjectJson; } var credentialId = await AnonCreds.ProverStoreCredentialAsync(wallet, null, credentialRecord.CredentialRequestMetadataJson, details.CredentialJson, credentialDefinition.ObjectJson, revocationRegistryDefinitionJson); credentialRecord.CredentialId = credentialId; await credentialRecord.TriggerAsync(CredentialTrigger.Issue); await RecordService.UpdateAsync(wallet, credentialRecord); }
/// <inheritdoc /> public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var item = JObject.Load(reader); var(_, messageType) = MessageUtils.ParseMessageType(item["@type"].ToObject <string>()); IContentMessage message; switch (messageType) { case MessageTypes.ConnectionRequest: message = new ConnectionRequestMessage(); break; case MessageTypes.ConnectionResponse: message = new ConnectionResponseMessage(); break; case MessageTypes.CredentialOffer: message = new CredentialOfferMessage(); break; case MessageTypes.CredentialRequest: message = new CredentialRequestMessage(); break; case MessageTypes.Credential: message = new CredentialMessage(); break; case MessageTypes.ProofRequest: message = new ProofRequestMessage(); break; case MessageTypes.DisclosedProof: message = new ProofMessage(); break; default: throw new TypeLoadException("Unsupported serialization type."); } serializer.Populate(item.CreateReader(), message); return(message); }
/// <inheritdoc /> public virtual async Task IssueCredentialAsync(IAgentContext agentContext, string issuerDid, string credentialId, Dictionary <string, string> values) { var credential = await GetAsync(agentContext, credentialId); if (credential.State != CredentialState.Requested) { throw new AgentFrameworkException(ErrorCode.RecordInInvalidState, $"Credential state was invalid. Expected '{CredentialState.Requested}', found '{credential.State}'"); } var credentialCopy = credential.DeepCopy(); if (values != null && values.Count > 0) { credential.ValuesJson = CredentialUtils.FormatCredentialValues(values); } var definitionRecord = await SchemaService.GetCredentialDefinitionAsync(agentContext.Wallet, credential.CredentialDefinitionId); var connection = await ConnectionService.GetAsync(agentContext, credential.ConnectionId); if (connection.State != ConnectionState.Connected) { throw new AgentFrameworkException(ErrorCode.RecordInInvalidState, $"Connection state was invalid. Expected '{ConnectionState.Connected}', found '{connection.State}'"); } string revocationRegistryId = null; BlobStorageReader tailsReader = null; if (definitionRecord.SupportsRevocation) { var revocationRecordSearch = await RecordService.SearchAsync <RevocationRegistryRecord>( agentContext.Wallet, SearchQuery.Equal(nameof(RevocationRegistryRecord.CredentialDefinitionId), definitionRecord.Id), null, 5); var revocationRecord = revocationRecordSearch.Single(); // TODO: Credential definition can have multiple revocation registries revocationRegistryId = revocationRecord.Id; tailsReader = await TailsService.OpenTailsAsync(revocationRecord.TailsFile); } var issuedCredential = await AnonCreds.IssuerCreateCredentialAsync(agentContext.Wallet, credential.OfferJson, credential.RequestJson, credential.ValuesJson, revocationRegistryId, tailsReader); if (definitionRecord.SupportsRevocation) { await LedgerService.SendRevocationRegistryEntryAsync(agentContext.Wallet, agentContext.Pool, issuerDid, revocationRegistryId, "CL_ACCUM", issuedCredential.RevocRegDeltaJson); credential.CredentialRevocationId = issuedCredential.RevocId; } var msg = new CredentialMessage { CredentialJson = issuedCredential.CredentialJson, RevocationRegistryId = revocationRegistryId }; await credential.TriggerAsync(CredentialTrigger.Issue); await RecordService.UpdateAsync(agentContext.Wallet, credential); try { await MessageService.SendAsync(agentContext.Wallet, msg, connection); } catch (Exception e) { await RecordService.UpdateAsync(agentContext.Wallet, credentialCopy); throw new AgentFrameworkException(ErrorCode.A2AMessageTransmissionError, "Failed to send credential request message", e); } }
/// <inheritdoc /> public virtual async Task <string> ProcessCredentialAsync(IAgentContext agentContext, CredentialMessage credential, ConnectionRecord connection) { var offer = JObject.Parse(credential.CredentialJson); var definitionId = offer["cred_def_id"].ToObject <string>(); var schemaId = offer["schema_id"].ToObject <string>(); var revRegId = offer["rev_reg_id"]?.ToObject <string>(); // TODO: Replace this with thread lookup // Currently, this is unable to process multiple offers and requests reliably var credentialSearch = await RecordService.SearchAsync <CredentialRecord>(agentContext.Wallet, SearchQuery.And( SearchQuery.Equal(nameof(CredentialRecord.SchemaId), schemaId), SearchQuery.Equal(nameof(CredentialRecord.CredentialDefinitionId), definitionId), SearchQuery.Equal(nameof(CredentialRecord.ConnectionId), connection.Id), SearchQuery.Equal(nameof(CredentialRecord.State), CredentialState.Requested.ToString("G")) ), null, 5); if (credentialSearch.Count == 0) { throw new AgentFrameworkException(ErrorCode.RecordNotFound, "Credential record not found"); } var credentialRecord = credentialSearch.Single(); // TODO: Should resolve if multiple credential records are found if (credentialRecord.State != CredentialState.Requested) { throw new AgentFrameworkException(ErrorCode.RecordInInvalidState, $"Credential state was invalid. Expected '{CredentialState.Requested}', found '{credentialRecord.State}'"); } var credentialDefinition = await LedgerService.LookupDefinitionAsync(agentContext.Pool, definitionId); string revocationRegistryDefinitionJson = null; if (!string.IsNullOrEmpty(revRegId)) { // If credential supports revocation, lookup registry definition var revocationRegistry = await LedgerService.LookupRevocationRegistryDefinitionAsync(agentContext.Pool, revRegId); revocationRegistryDefinitionJson = revocationRegistry.ObjectJson; } var credentialId = await AnonCreds.ProverStoreCredentialAsync(agentContext.Wallet, null, credentialRecord.CredentialRequestMetadataJson, credential.CredentialJson, credentialDefinition.ObjectJson, revocationRegistryDefinitionJson); credentialRecord.CredentialId = credentialId; await credentialRecord.TriggerAsync(CredentialTrigger.Issue); await RecordService.UpdateAsync(agentContext.Wallet, credentialRecord); EventAggregator.Publish(new ServiceMessageProcessingEvent { RecordId = credentialRecord.Id, MessageType = credential.Type, }); return(credentialRecord.Id); }
/// <inheritdoc /> public virtual async Task <(CredentialMessage, CredentialRecord)> CreateCredentialAsync(IAgentContext agentContext, string issuerDid, string credentialId, IEnumerable <CredentialPreviewAttribute> values) { var credential = await GetAsync(agentContext, credentialId); if (credential.State != CredentialState.Requested) { throw new AgentFrameworkException(ErrorCode.RecordInInvalidState, $"Credential state was invalid. Expected '{CredentialState.Requested}', found '{credential.State}'"); } if (values != null && values.Any()) { credential.CredentialAttributesValues = values; } var definitionRecord = await SchemaService.GetCredentialDefinitionAsync(agentContext.Wallet, credential.CredentialDefinitionId); var connection = await ConnectionService.GetAsync(agentContext, credential.ConnectionId); if (connection.State != ConnectionState.Connected) { throw new AgentFrameworkException(ErrorCode.RecordInInvalidState, $"Connection state was invalid. Expected '{ConnectionState.Connected}', found '{connection.State}'"); } string revocationRegistryId = null; BlobStorageReader tailsReader = null; if (definitionRecord.SupportsRevocation) { var revocationRecordSearch = await RecordService.SearchAsync <RevocationRegistryRecord>( agentContext.Wallet, SearchQuery.Equal(nameof(RevocationRegistryRecord.CredentialDefinitionId), definitionRecord.Id), null, 5); var revocationRecord = revocationRecordSearch.Single(); // TODO: Credential definition can have multiple revocation registries revocationRegistryId = revocationRecord.Id; tailsReader = await TailsService.OpenTailsAsync(revocationRecord.TailsFile); } var issuedCredential = await AnonCreds.IssuerCreateCredentialAsync(agentContext.Wallet, credential.OfferJson, credential.RequestJson, CredentialUtils.FormatCredentialValues(credential.CredentialAttributesValues), revocationRegistryId, tailsReader); if (definitionRecord.SupportsRevocation) { var paymentInfo = await PaymentService.GetTransactionCostAsync(agentContext, TransactionTypes.REVOC_REG_ENTRY); await LedgerService.SendRevocationRegistryEntryAsync(context : agentContext, issuerDid : issuerDid, revocationRegistryDefinitionId : revocationRegistryId, revocationDefinitionType : "CL_ACCUM", value : issuedCredential.RevocRegDeltaJson, paymentInfo : paymentInfo); credential.CredentialRevocationId = issuedCredential.RevocId; if (paymentInfo != null) { await RecordService.UpdateAsync(agentContext.Wallet, paymentInfo.PaymentAddress); } } await credential.TriggerAsync(CredentialTrigger.Issue); await RecordService.UpdateAsync(agentContext.Wallet, credential); var threadId = credential.GetTag(TagConstants.LastThreadId); var credentialMsg = new CredentialMessage { CredentialJson = issuedCredential.CredentialJson, RevocationRegistryId = revocationRegistryId }; credentialMsg.ThreadFrom(threadId); return(credentialMsg, credential); }
/// <inheritdoc /> public virtual async Task <string> ProcessCredentialAsync(IAgentContext agentContext, CredentialMessage credential, ConnectionRecord connection) { var offer = JObject.Parse(credential.CredentialJson); var definitionId = offer["cred_def_id"].ToObject <string>(); var revRegId = offer["rev_reg_id"]?.ToObject <string>(); var credentialRecord = await this.GetByThreadIdAsync(agentContext, credential.GetThreadId()); if (credentialRecord.State != CredentialState.Requested) { throw new AgentFrameworkException(ErrorCode.RecordInInvalidState, $"Credential state was invalid. Expected '{CredentialState.Requested}', found '{credentialRecord.State}'"); } var credentialDefinition = await LedgerService.LookupDefinitionAsync(await agentContext.Pool, definitionId); string revocationRegistryDefinitionJson = null; if (!string.IsNullOrEmpty(revRegId)) { // If credential supports revocation, lookup registry definition var revocationRegistry = await LedgerService.LookupRevocationRegistryDefinitionAsync(await agentContext.Pool, revRegId); revocationRegistryDefinitionJson = revocationRegistry.ObjectJson; } var credentialId = await AnonCreds.ProverStoreCredentialAsync(agentContext.Wallet, null, credentialRecord.CredentialRequestMetadataJson, credential.CredentialJson, credentialDefinition.ObjectJson, revocationRegistryDefinitionJson); credentialRecord.CredentialId = credentialId; credentialRecord.CredentialAttributesValues = null; await credentialRecord.TriggerAsync(CredentialTrigger.Issue); await RecordService.UpdateAsync(agentContext.Wallet, credentialRecord); EventAggregator.Publish(new ServiceMessageProcessingEvent { RecordId = credentialRecord.Id, MessageType = credential.Type, ThreadId = credential.GetThreadId() }); return(credentialRecord.Id); }