/// <inheritdoc /> public virtual async Task RevokeCredentialAsync(IAgentContext agentContext, string credentialId) { var credentialRecord = await GetAsync(agentContext, credentialId); if (credentialRecord.State != CredentialState.Issued) { throw new AriesFrameworkException(ErrorCode.RecordInInvalidState, $"Credential state was invalid. Expected '{CredentialState.Issued}', found '{credentialRecord.State}'"); } var provisioning = await ProvisioningService.GetProvisioningAsync(agentContext.Wallet); // Check if the state machine is valid for revocation await credentialRecord.TriggerAsync(CredentialTrigger.Revoke); var revocationRecord = await RecordService.GetAsync <RevocationRegistryRecord>(agentContext.Wallet, credentialRecord.RevocationRegistryId); // Revoke the credential var tailsReader = await TailsService.OpenTailsAsync(revocationRecord.TailsFile); var revocRegistryDeltaJson = await AnonCreds.IssuerRevokeCredentialAsync( agentContext.Wallet, tailsReader, revocationRecord.Id, credentialRecord.CredentialRevocationId); var paymentInfo = await PaymentService.GetTransactionCostAsync(agentContext, TransactionTypes.REVOC_REG_ENTRY); // Write the delta state on the ledger for the corresponding revocation registry await LedgerService.SendRevocationRegistryEntryAsync( context : agentContext, issuerDid : provisioning.IssuerDid, revocationRegistryDefinitionId : revocationRecord.Id, revocationDefinitionType : "CL_ACCUM", value : revocRegistryDeltaJson, paymentInfo : paymentInfo); if (paymentInfo != null) { await RecordService.UpdateAsync(agentContext.Wallet, paymentInfo.PaymentAddress); } // Update local credential record await RecordService.UpdateAsync(agentContext.Wallet, credentialRecord); }
/// <inheritdoc /> public async Task <(IssuerCreateAndStoreRevocRegResult, RevocationRegistryRecord)> CreateRevocationRegistryAsync( IAgentContext context, string tag, DefinitionRecord definitionRecord) { var tailsHandle = await TailsService.CreateTailsAsync(); var revocationRegistryDefinitionJson = new { issuance_type = "ISSUANCE_BY_DEFAULT", max_cred_num = definitionRecord.MaxCredentialCount }.ToJson(); var revocationRegistry = await AnonCreds.IssuerCreateAndStoreRevocRegAsync( wallet : context.Wallet, issuerDid : definitionRecord.IssuerDid, type : null, tag : tag, credDefId : definitionRecord.Id, configJson : revocationRegistryDefinitionJson, tailsWriter : tailsHandle); var revocationRecord = new RevocationRegistryRecord { Id = revocationRegistry.RevRegId, CredentialDefinitionId = definitionRecord.Id }; // Update tails location URI var revocationDefinition = JObject.Parse(revocationRegistry.RevRegDefJson); var tailsfile = Path.GetFileName(revocationDefinition["value"]["tailsLocation"].ToObject <string>()); var tailsLocation = Url.Combine( AgentOptions.EndpointUri, AgentOptions.RevocationRegistryUriPath, tailsfile); revocationDefinition["value"]["tailsLocation"] = tailsLocation; revocationRecord.TailsFile = tailsfile; revocationRecord.TailsLocation = tailsLocation; //paymentInfo = await paymentService.GetTransactionCostAsync(context, TransactionTypes.REVOC_REG_DEF); await LedgerService.RegisterRevocationRegistryDefinitionAsync( context : context, submitterDid : definitionRecord.IssuerDid, data : revocationDefinition.ToString(), paymentInfo : null); await RecordService.AddAsync(context.Wallet, revocationRecord); await LedgerService.SendRevocationRegistryEntryAsync( context : context, issuerDid : definitionRecord.IssuerDid, revocationRegistryDefinitionId : revocationRegistry.RevRegId, revocationDefinitionType : "CL_ACCUM", value : revocationRegistry.RevRegEntryJson, paymentInfo : null); return(revocationRegistry, revocationRecord); }
/// <inheritdoc /> public virtual async Task <string> CreateCredentialDefinitionAsync(Pool pool, Wallet wallet, string schemaId, string issuerDid, string tag, bool supportsRevocation, int maxCredentialCount, Uri tailsBaseUri) { var definitionRecord = new DefinitionRecord(); var schema = await LedgerService.LookupSchemaAsync(pool, schemaId); var credentialDefinition = await AnonCreds.IssuerCreateAndStoreCredentialDefAsync(wallet, issuerDid, schema.ObjectJson, tag, null, new { support_revocation = supportsRevocation }.ToJson()); await LedgerService.RegisterCredentialDefinitionAsync(wallet, pool, issuerDid, credentialDefinition.CredDefJson); definitionRecord.SupportsRevocation = supportsRevocation; definitionRecord.Id = credentialDefinition.CredDefId; definitionRecord.SchemaId = schemaId; if (supportsRevocation) { definitionRecord.MaxCredentialCount = maxCredentialCount; var tailsHandle = await TailsService.CreateTailsAsync(); var revRegDefConfig = new { issuance_type = "ISSUANCE_ON_DEMAND", max_cred_num = maxCredentialCount }.ToJson(); var revocationRegistry = await AnonCreds.IssuerCreateAndStoreRevocRegAsync(wallet, issuerDid, null, "Tag2", credentialDefinition.CredDefId, revRegDefConfig, tailsHandle); // Update tails location URI var revocationDefinition = JObject.Parse(revocationRegistry.RevRegDefJson); var tailsfile = Path.GetFileName(revocationDefinition["value"]["tailsLocation"].ToObject <string>()); revocationDefinition["value"]["tailsLocation"] = new Uri(tailsBaseUri, tailsfile).ToString(); await LedgerService.RegisterRevocationRegistryDefinitionAsync(wallet, pool, issuerDid, revocationDefinition.ToString()); await LedgerService.SendRevocationRegistryEntryAsync(wallet, pool, issuerDid, revocationRegistry.RevRegId, "CL_ACCUM", revocationRegistry.RevRegEntryJson); var revocationRecord = new RevocationRegistryRecord { Id = revocationRegistry.RevRegId, TailsFile = tailsfile, CredentialDefinitionId = credentialDefinition.CredDefId }; await RecordService.AddAsync(wallet, revocationRecord); } await RecordService.AddAsync(wallet, definitionRecord); return(credentialDefinition.CredDefId); }
/// <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 <(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> CreateCredentialDefinitionAsync(IAgentContext context, string schemaId, string issuerDid, string tag, bool supportsRevocation, int maxCredentialCount, Uri tailsBaseUri) { var definitionRecord = new DefinitionRecord(); var schema = await LedgerService.LookupSchemaAsync(await context.Pool, schemaId); var credentialDefinition = await AnonCreds.IssuerCreateAndStoreCredentialDefAsync(context.Wallet, issuerDid, schema.ObjectJson, tag, null, new { support_revocation = supportsRevocation }.ToJson()); var paymentInfo = await paymentService.GetTransactionCostAsync(context, TransactionTypes.CRED_DEF); await LedgerService.RegisterCredentialDefinitionAsync(context : context, submitterDid : issuerDid, data : credentialDefinition.CredDefJson, paymentInfo : paymentInfo); if (paymentInfo != null) { await RecordService.UpdateAsync(context.Wallet, paymentInfo.PaymentAddress); } definitionRecord.SupportsRevocation = supportsRevocation; definitionRecord.Id = credentialDefinition.CredDefId; definitionRecord.SchemaId = schemaId; if (supportsRevocation) { definitionRecord.MaxCredentialCount = maxCredentialCount; var tailsHandle = await TailsService.CreateTailsAsync(); var revRegDefConfig = new { issuance_type = "ISSUANCE_ON_DEMAND", max_cred_num = maxCredentialCount }.ToJson(); var revocationRegistry = await AnonCreds.IssuerCreateAndStoreRevocRegAsync(context.Wallet, issuerDid, null, "Tag2", credentialDefinition.CredDefId, revRegDefConfig, tailsHandle); // Update tails location URI var revocationDefinition = JObject.Parse(revocationRegistry.RevRegDefJson); var tailsfile = Path.GetFileName(revocationDefinition["value"]["tailsLocation"].ToObject <string>()); revocationDefinition["value"]["tailsLocation"] = new Uri(tailsBaseUri, tailsfile).ToString(); paymentInfo = await paymentService.GetTransactionCostAsync(context, TransactionTypes.REVOC_REG_DEF); await LedgerService.RegisterRevocationRegistryDefinitionAsync(context : context, submitterDid : issuerDid, data : revocationDefinition.ToString(), paymentInfo : paymentInfo); if (paymentInfo != null) { await RecordService.UpdateAsync(context.Wallet, paymentInfo.PaymentAddress); } paymentInfo = await paymentService.GetTransactionCostAsync(context, TransactionTypes.REVOC_REG_ENTRY); await LedgerService.SendRevocationRegistryEntryAsync(context : context, issuerDid : issuerDid, revocationRegistryDefinitionId : revocationRegistry.RevRegId, revocationDefinitionType : "CL_ACCUM", value : revocationRegistry.RevRegEntryJson, paymentInfo : paymentInfo); if (paymentInfo != null) { await RecordService.UpdateAsync(context.Wallet, paymentInfo.PaymentAddress); } var revocationRecord = new RevocationRegistryRecord { Id = revocationRegistry.RevRegId, TailsFile = tailsfile, CredentialDefinitionId = credentialDefinition.CredDefId }; await RecordService.AddAsync(context.Wallet, revocationRecord); } await RecordService.AddAsync(context.Wallet, definitionRecord); return(credentialDefinition.CredDefId); }
/// <inheritdoc /> public virtual async Task IssueCredentialAsync(Pool pool, Wallet wallet, string issuerDid, string credentialId, Dictionary <string, string> values) { var credentialRecord = await RecordService.GetAsync <CredentialRecord>(wallet, credentialId); if (values != null && values.Count > 0) { credentialRecord.ValuesJson = CredentialUtils.FormatCredentialValues(values); } var definitionRecord = await SchemaService.GetCredentialDefinitionAsync(wallet, credentialRecord.CredentialDefinitionId); var connection = await ConnectionService.GetAsync(wallet, credentialRecord.ConnectionId); if (credentialRecord.State != CredentialState.Requested) { throw new Exception( $"Credential sate was invalid. Expected '{CredentialState.Requested}', found '{credentialRecord.State}'"); } string revocationRegistryId = null; BlobStorageReader tailsReader = null; if (definitionRecord.SupportsRevocation) { var revocationRecordSearch = await RecordService.SearchAsync <RevocationRegistryRecord>( wallet, new SearchRecordQuery { { TagConstants.CredentialDefinitionId, definitionRecord.DefinitionId } }, null, 1); var revocationRecord = revocationRecordSearch.First(); revocationRegistryId = revocationRecord.RevocationRegistryId; tailsReader = await TailsService.OpenTailsAsync(revocationRecord.TailsFile); } var issuedCredential = await AnonCreds.IssuerCreateCredentialAsync(wallet, credentialRecord.OfferJson, credentialRecord.RequestJson, credentialRecord.ValuesJson, revocationRegistryId, tailsReader); if (definitionRecord.SupportsRevocation) { await LedgerService.SendRevocationRegistryEntryAsync(wallet, pool, issuerDid, revocationRegistryId, "CL_ACCUM", issuedCredential.RevocRegDeltaJson); credentialRecord.CredentialRevocationId = issuedCredential.RevocId; } var credentialDetails = new CredentialDetails { CredentialJson = issuedCredential.CredentialJson, RevocationRegistryId = revocationRegistryId }; await credentialRecord.TriggerAsync(CredentialTrigger.Issue); await RecordService.UpdateAsync(wallet, credentialRecord); var credential = await MessageSerializer.PackSealedAsync <CredentialMessage>(credentialDetails, wallet, connection.MyVk, connection.TheirVk); credential.Type = MessageUtils.FormatDidMessageType(connection.TheirDid, MessageTypes.Credential); await RouterService.ForwardAsync(new ForwardEnvelopeMessage { Content = credential.ToJson(), Type = MessageUtils.FormatDidMessageType(connection.TheirDid, MessageTypes.Forward) }, connection.Endpoint); }