Skip to content

Commit

Permalink
Fix revocation issues (#73)
Browse files Browse the repository at this point in the history
Signed-off-by: conanoc <conanoc@gmail.com>
  • Loading branch information
conanoc committed Nov 23, 2023
1 parent 50b8def commit 8192551
Show file tree
Hide file tree
Showing 17 changed files with 392 additions and 42 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
- name: Build
run: swift build
- name: Run tests
run: swift test --skip AgentTest --skip CredentialsTest --skip LedgerServiceTest --skip OobTest --skip ProofsTest | xcpretty && exit ${PIPESTATUS[0]}
run: swift test --skip AgentTest --skip CredentialsTest --skip LedgerServiceTest --skip OobTest --skip ProofsTest --skip RevocationTest | xcpretty && exit ${PIPESTATUS[0]}
2 changes: 1 addition & 1 deletion .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
excluded:
- AriesFramework/Pods
- .build
- Sample
disabled_rules:
- line_length
Expand Down
3 changes: 2 additions & 1 deletion BasicTests.xctestplan
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"LedgerServiceTest",
"OobTest\/testCreateWithOfferCredentialMessage()",
"OobTest\/testCredentialOffer()",
"ProofsTest"
"ProofsTest",
"RevocationTest",
],
"target" : {
"containerPath" : "container:",
Expand Down
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/hyperledger/aries-uniffi-wrappers",
"state" : {
"revision" : "1b31293edaacc1b3b1fba150703527fba35754e7",
"version" : "0.1.0"
"revision" : "686509329b80a5235dcabbc054ec1ce4f31f62d9",
"version" : "0.1.1"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ let package = Package(
targets: ["AriesFramework"])
],
dependencies: [
.package(url: "https://github.com/hyperledger/aries-uniffi-wrappers", exact: "0.1.0"),
.package(url: "https://github.com/hyperledger/aries-uniffi-wrappers", exact: "0.1.1"),
.package(url: "https://github.com/bhsw/concurrent-ws", exact: "0.5.0"),
.package(url: "https://github.com/JohnSundell/CollectionConcurrencyKit", exact: "0.2.0"),
.package(url: "https://github.com/keefertaylor/Base58Swift", exact: "2.1.7"),
Expand Down
2 changes: 2 additions & 0 deletions Sources/AriesFramework/agent/Agent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class Agent {
public var didCommMessageRepository: DidCommMessageRepository!
public var ledgerService: LedgerService!
public var credentialDefinitionRepository: CredentialDefinitionRepository!
public var revocationRegistryRepository: RevocationRegistryRepository!
public var anoncredsService: AnoncredsService!
public var revocationService: RevocationService!
public var credentialService: CredentialService!
Expand Down Expand Up @@ -53,6 +54,7 @@ public class Agent {
self.didCommMessageRepository = DidCommMessageRepository(agent: self)
self.ledgerService = LedgerService(agent: self)
self.credentialDefinitionRepository = CredentialDefinitionRepository(agent: self)
self.revocationRegistryRepository = RevocationRegistryRepository(agent: self)
self.anoncredsService = AnoncredsService(agent: self)
self.revocationService = RevocationService(agent: self)
self.credentialService = CredentialService(agent: self)
Expand Down
4 changes: 4 additions & 0 deletions Sources/AriesFramework/agent/AgentConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public struct AgentConfig: Codable {
connectionImageUrl: String? = nil,
autoAcceptCredential: AutoAcceptCredential = .always,
autoAcceptProof: AutoAcceptProof = .always,
ignoreRevocationCheck: Bool = false,
useLedgerService: Bool = true,
useLegacyDidSovPrefix: Bool = true,
publicDidSeed: String? = nil,
Expand All @@ -34,6 +35,7 @@ public struct AgentConfig: Codable {
self.connectionImageUrl = connectionImageUrl
self.autoAcceptCredential = autoAcceptCredential
self.autoAcceptProof = autoAcceptProof
self.ignoreRevocationCheck = ignoreRevocationCheck
self.useLedgerService = useLedgerService
self.useLegacyDidSovPrefix = useLegacyDidSovPrefix
self.publicDidSeed = publicDidSeed
Expand Down Expand Up @@ -80,6 +82,8 @@ public struct AgentConfig: Codable {
public var publicDidSeed: String?
/// The agent endpoints to use for testing.
public var agentEndpoints: [String]?
/// Whether to ignore revocation checks when creating a presentation. Default is false.
public var ignoreRevocationCheck: Bool

/// The endpoints of the agent. Read only.
public var endpoints: [String] {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

import Foundation

public struct RevocationRegistryRecord: BaseRecord {
public var id: String
var createdAt: Date
var updatedAt: Date?
public var tags: Tags?

public var credDefId: String
public var revocRegId: String
public var revocRegDef: String
public var revocRegPrivate: String
public var revocStatusList: String
public var registryIndex: Int = 0

public static let type = "RevocationRegistryRecord"
}

extension RevocationRegistryRecord: Codable {
enum CodingKeys: String, CodingKey {
case id, createdAt, updatedAt, tags, credDefId, revocRegId, revocRegDef, revocRegPrivate, revocStatusList, registryIndex
}

init(
tags: Tags? = nil,
credDefId: String,
revocRegId: String,
revocRegDef: String,
revocRegPrivate: String,
revocStatusList: String) {

self.id = RevocationRegistryRecord.generateId()
self.createdAt = Date()
self.tags = tags
self.credDefId = credDefId
self.revocRegId = revocRegId
self.revocRegDef = revocRegDef
self.revocRegPrivate = revocRegPrivate
self.revocStatusList = revocStatusList
}

public func getTags() -> Tags {
var tags = self.tags ?? [:]

tags["credDefId"] = self.credDefId
tags["revocRegId"] = self.revocRegId

return tags
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

import Foundation

public class RevocationRegistryRepository: Repository<RevocationRegistryRecord> {
public func findByCredDefId(_ credDefId: String) async throws -> RevocationRegistryRecord? {
return try await findSingleByQuery("{\"credDefId\": \"\(credDefId)\"}")
}

// We don't need lock here because this is for testing only.
public func incrementRegistryIndex(credDefId: String) async throws -> Int {
var record = try await getSingleByQuery("{\"credDefId\": \"\(credDefId)\"}")
record.registryIndex += 1
try await update(record)
return record.registryIndex
}
}
17 changes: 14 additions & 3 deletions Sources/AriesFramework/credentials/CredentialService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,18 @@ public class CredentialService {

let offer = try CredentialOffer(json: offerAttachment!.getDataAsString())
let request = try CredentialRequest(json: requestAttachment!.getDataAsString())
let credentialDefinitionRecord = try await agent.credentialDefinitionRepository.getByCredDefId(offer.credDefId())
let credDefId = offer.credDefId()
let credentialDefinitionRecord = try await agent.credentialDefinitionRepository.getByCredDefId(credDefId)

var revocationConfig: CredentialRevocationConfig?
if let revocationRecord = try await agent.revocationRegistryRepository.findByCredDefId(credDefId) {
let registryIndex = try await agent.revocationRegistryRepository.incrementRegistryIndex(credDefId: credDefId)
revocationConfig = CredentialRevocationConfig(
regDef: try RevocationRegistryDefinition(json: revocationRecord.revocRegDef),
regDefPrivate: try RevocationRegistryDefinitionPrivate(json: revocationRecord.revocRegPrivate),
statusList: try Anoncreds.RevocationStatusList(json: revocationRecord.revocStatusList),
registryIndex: UInt32(registryIndex))
}

let credential = try Issuer().createCredential(
credDef: try CredentialDefinition(json: credentialDefinitionRecord.credDef),
Expand All @@ -276,7 +287,7 @@ public class CredentialService {
credRequest: request,
attrRawValues: credentialRecord.getCredentialInfo()!.claims,
attrEncValues: nil,
revocationConfig: nil)
revocationConfig: revocationConfig)

let attachment = Attachment.fromData(credential.toJson().data(using: .utf8)!, id: IssueCredentialMessage.INDY_CREDENTIAL_ATTACHMENT_ID)
let issueMessage = IssueCredentialMessage( comment: options.comment, credentialAttachments: [attachment])
Expand Down Expand Up @@ -319,7 +330,7 @@ public class CredentialService {
let revocationRegistry = revocationRegistryJson != nil ? try RevocationRegistryDefinition(json: revocationRegistryJson!) : nil
if revocationRegistry != nil {
Task {
_ = try await agent.revocationService.downloadTails(revocationRegistryDefinition: revocationRegistry!)
_ = try agent.revocationService.downloadTails(revocationRegistryDefinition: revocationRegistry!)
}
}

Expand Down
116 changes: 102 additions & 14 deletions Sources/AriesFramework/ledger/LedgerService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ public struct CredentialDefinitionTemplate {
public let seqNo: Int
}

public struct RevocationRegistryDefinitionTemplate {
public let credDefId: String
public let tag: String
public let maxCredNum: Int
public let tailsDirPath: String? = nil
}

public class LedgerService {
let agent: Agent
let logger = Logger(subsystem: "AriesFramework", category: "LedgerService")
Expand Down Expand Up @@ -152,15 +159,69 @@ public class LedgerService {
return String(data: credDefJson, encoding: .utf8)!
}

public func registerRevocationRegistryDefinition(did: DidInfo, revRegDefTemplate: RevocationRegistryDefinitionTemplate) async throws -> String {
logger.debug("registering revocation registry definition")
let credentialDefinitionRecord = try await agent.credentialDefinitionRepository.getByCredDefId(revRegDefTemplate.credDefId)
let credDef = try CredentialDefinition(json: credentialDefinitionRecord.credDef)
let regDefTuple = try issuer.createRevocationRegistryDef(
credDef: credDef,
credDefId: revRegDefTemplate.credDefId,
tag: revRegDefTemplate.tag,
maxCredNum: UInt32(revRegDefTemplate.maxCredNum),
tailsDirPath: revRegDefTemplate.tailsDirPath)
let revRegId = regDefTuple.revRegDef.revRegId()
let revocationStatusList = try issuer.createRevocationStatusList(
credDef: credDef,
revRegDefId: revRegId,
revRegDef: regDefTuple.revRegDef,
revRegPriv: regDefTuple.revRegDefPriv,
timestamp: UInt64(Date().timeIntervalSince1970),
issuanceByDefault: true)

// swiftlint:disable:next force_cast
var regDef = try JSONSerialization.jsonObject(with: regDefTuple.revRegDef.toJson().data(using: .utf8)!) as! [String: Any]
regDef["id"] = revRegId
regDef["ver"] = "1.0"
guard var value = regDef["value"] as? [String: Any] else {
throw AriesFrameworkError.frameworkError("Invalid RevocationRegistryDefinition. value is missing.")
}
value["issuanceType"] = "ISSUANCE_BY_DEFAULT"
regDef["value"] = value
let regDefJson = try JSONSerialization.data(withJSONObject: regDef)
var request = try ledger.buildRevocRegDefRequest(
submitterDid: did.did,
revRegDef: String(data: regDefJson, encoding: .utf8)!)
try await submitWriteRequest(request, did: did)

let statusList = try JSONDecoder().decode(RevocationStatusList.self, from: revocationStatusList.toJson().data(using: .utf8)!)
let regDelta = RevocationRegistryDelta(prevAccum: nil, accum: statusList.currentAccumulator, issued: nil, revoked: nil)
request = try ledger.buildRevocRegEntryRequest(
submitterDid: did.did,
revRegDefId: revRegId,
entry: regDelta.toVersionedJson())
try await submitWriteRequest(request, did: did)

let record = RevocationRegistryRecord(
credDefId: revRegDefTemplate.credDefId,
revocRegId: revRegId,
revocRegDef: regDefTuple.revRegDef.toJson(),
revocRegPrivate: regDefTuple.revRegDefPriv.toJson(),
revocStatusList: revocationStatusList.toJson())
try await agent.revocationRegistryRepository.save(record)

return revRegId
}

public func getRevocationRegistryDefinition(id: String) async throws -> String {
logger.debug("Get RevocationRegistryDefinition with id: \(id)")
let request = try ledger.buildGetRevocRegDefRequest(submitterDid: nil, revRegId: id)
let response = try await submitReadRequest(request)
let json = try JSONSerialization.jsonObject(with: response.data(using: .utf8)!) as? [String: Any]
guard let result = json?["result"] as? [String: Any],
let data = result["data"] else {
var data = result["data"] as? [String: Any] else {
throw AriesFrameworkError.frameworkError("Invalid rev reg def response")
}
data["issuerId"] = id.split(separator: ":")[0]
let revocationRegistryDefinition = try JSONSerialization.data(withJSONObject: data)

return String(data: revocationRegistryDefinition, encoding: .utf8)!
Expand All @@ -171,17 +232,13 @@ public class LedgerService {
let request = try ledger.buildGetRevocRegDeltaRequest(submitterDid: nil, revRegId: id, from: Int64(from), to: Int64(to))
let res = try await submitReadRequest(request)
let response = try JSONDecoder().decode(RevRegDeltaResponse.self, from: res.data(using: .utf8)!)
var revocationRegistryDelta = [
"accum": response.result.data.value.accum_to.value.accum,
"issued": response.result.data.value.issued,
"revoked": response.result.data.value.revoked
] as [String: Any]
if let accum_from = response.result.data.value.accum_from {
revocationRegistryDelta["prev_accum"] = accum_from.value.accum
}
let revocationRegistryDeltaJson = try JSONSerialization.data(withJSONObject: revocationRegistryDelta)
let revocationRegistryDelta = RevocationRegistryDelta(
prevAccum: response.result.data.value.accum_from?.value.accum,
accum: response.result.data.value.accum_to.value.accum,
issued: response.result.data.value.issued,
revoked: response.result.data.value.revoked)
let deltaTimestamp = response.result.data.value.accum_to.txnTime
return (String(data: revocationRegistryDeltaJson, encoding: .utf8)!, deltaTimestamp)
return (revocationRegistryDelta.toJsonString(), deltaTimestamp)
}

public func getRevocationRegistry(id: String, timestamp: Int) async throws -> (String, Int) {
Expand All @@ -190,14 +247,45 @@ public class LedgerService {
let response = try await submitReadRequest(request)
let json = try JSONSerialization.jsonObject(with: response.data(using: .utf8)!) as? [String: Any]
guard let result = json?["result"] as? [String: Any],
let data = result["data"],
let data = result["data"] as? [String: Any],
let value = data["value"] as? [String: String],
let txnTime = result["txnTime"] as? Int else {
throw AriesFrameworkError.frameworkError("Invalid rev reg response")
throw AriesFrameworkError.frameworkError("Invalid rev reg response: \(response)")
}
let revocationRegistry = try JSONSerialization.data(withJSONObject: data)
let revocationRegistry = try JSONSerialization.data(withJSONObject: value)
return (String(data: revocationRegistry, encoding: .utf8)!, txnTime)
}

public func revokeCredential(did: DidInfo, credDefId: String, revocationIndex: Int) async throws {
logger.debug("Revoking credential with index: \(revocationIndex)")
let credentialDefinitionRecord = try await agent.credentialDefinitionRepository.getByCredDefId(credDefId)
guard var revocationRecord = try await agent.revocationRegistryRepository.findByCredDefId(credDefId) else {
throw AriesFrameworkError.frameworkError("No revocation registry found for credential definition id: \(credDefId)")
}

let currentStatusList = try Anoncreds.RevocationStatusList(json: revocationRecord.revocStatusList)
let revokedStatusList = try issuer.updateRevocationStatusList(
credDef: try CredentialDefinition(json: credentialDefinitionRecord.credDef),
timestamp: UInt64(Date().timeIntervalSince1970),
issued: nil,
revoked: [UInt32(revocationIndex)],
revRegDef: try RevocationRegistryDefinition(json: revocationRecord.revocRegDef),
revRegPriv: try RevocationRegistryDefinitionPrivate(json: revocationRecord.revocRegPrivate),
currentList: currentStatusList)

let currentList = try JSONDecoder().decode(RevocationStatusList.self, from: currentStatusList.toJson().data(using: .utf8)!)
let revokedList = try JSONDecoder().decode(RevocationStatusList.self, from: revokedStatusList.toJson().data(using: .utf8)!)
let regDelta = RevocationRegistryDelta(prevAccum: currentList.currentAccumulator, accum: revokedList.currentAccumulator, issued: nil, revoked: [revocationIndex])
let request = try ledger.buildRevocRegEntryRequest(
submitterDid: did.did,
revRegDefId: revocationRecord.revocRegId,
entry: regDelta.toVersionedJson())
try await submitWriteRequest(request, did: did)

revocationRecord.revocStatusList = revokedStatusList.toJson()
try await agent.revocationRegistryRepository.update(revocationRecord)
}

func validateResponse(_ response: String) throws {
let indyResponse = try JSONDecoder().decode(IndyResponse.self, from: response.data(using: .utf8)!)
if indyResponse.op != "REPLY" {
Expand Down
6 changes: 5 additions & 1 deletion Sources/AriesFramework/proofs/ProofService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ public class ProofService {
if nonRevokedAttributes.count == 0 {
throw AriesFrameworkError.frameworkError("Cannot find non-revoked credentials for attribute '\(attributeName)'.")
}
requestedCredentials.requestedAttributes[attributeName] = attributeArray[0]
requestedCredentials.requestedAttributes[attributeName] = nonRevokedAttributes[0]
}

try retrievedCredentials.requestedPredicates.keys.forEach { predicateName in
Expand Down Expand Up @@ -343,6 +343,10 @@ public class ProofService {
return (nil, nil)
}

if agent.agentConfig.ignoreRevocationCheck {
return (false, requestNonRevoked.to)
}

return try await agent.revocationService.getRevocationStatus(
credentialRevocationId: credentialRevocationId,
revocationRegistryId: revocationRegistryId,
Expand Down
Loading

0 comments on commit 8192551

Please sign in to comment.