diff --git a/Sources/Web5/Dids/BearerDID.swift b/Sources/Web5/Dids/BearerDID.swift index b954cd1..deb1899 100644 --- a/Sources/Web5/Dids/BearerDID.swift +++ b/Sources/Web5/Dids/BearerDID.swift @@ -45,6 +45,35 @@ public struct BearerDID { return did[keyPath: member] } + /// Returns a `BearerDIDSigner` that can be used to sign messages, credentials, or arbitrary data + /// + /// If given, the `verificationMethodID` parameter is used to select a key from the + /// verification methods present in the `DIDDocument`. If `verificationMethodID` is not + /// provided, the first verificationMethod in the `DIDDocument` will be used. + /// + /// - Parameters: + /// - keyAlias: Alias of the key that will be used for + public func getSigner(verificationMethodID: String? = nil) throws -> BearerDIDSigner { + let verificationMethod: VerificationMethod? + + if let verificationMethodID { + verificationMethod = document.verificationMethod?.first { $0.id == verificationMethodID } + } else { + verificationMethod = document.verificationMethod?.first + } + + guard let verificationMethod else { + throw Error.getSignerError("No verificationMethod found") + } + + guard let publicKey = verificationMethod.publicKeyJwk else { + throw Error.getSignerError("VerificationMethod \(verificationMethod.id) does not contain a publicKeyJwk") + } + + let keyAlias = try keyManager.getDeterministicAlias(key: publicKey) + return BearerDIDSigner(keyAlias: keyAlias, keyManager: keyManager) + } + /// Exports the `BearerDID` into a portable format that contains the DID's URI in addition /// to every private key associated with a verifification method. public func export() throws -> PortableDID { @@ -84,6 +113,7 @@ extension BearerDID { public enum Error: LocalizedError { case keyManagerNotExporter(KeyManager) case keyManagerNotImporter(KeyManager) + case getSignerError(String) case exportError(String) public var errorDescription: String? { @@ -92,9 +122,30 @@ extension BearerDID { return "\(String(describing: type(of: keyManager))) does not support exporting keys" case let .keyManagerNotImporter(keyManager): return "\(String(describing: type(of: keyManager))) does not support importing keys" - case let .exportError(error): - return "Export error: \(error)" + case let .getSignerError(reason): + return "Error getting signer: \(reason)" + case let .exportError(reason): + return "Error exporting: \(reason)" } } } } + +// MARK: - BearerDIDSigner + +public struct BearerDIDSigner { + + let keyAlias: String + let keyManager: KeyManager + + public func sign
(payload: P) throws -> Data + where P: DataProtocol { + return try self.keyManager.sign(keyAlias: keyAlias, payload: payload) + } + + public func verify
(payload: P, signature: S) throws -> Bool + where P: DataProtocol, S: DataProtocol { + let publicKey = try keyManager.getPublicKey(keyAlias: keyAlias) + return try Crypto.verify(payload: payload, signature: signature, publicKey: publicKey) + } +} diff --git a/Tests/Web5Tests/Dids/BearerDIDTests.swift b/Tests/Web5Tests/Dids/BearerDIDTests.swift index 64b70dd..9fbd58b 100644 --- a/Tests/Web5Tests/Dids/BearerDIDTests.swift +++ b/Tests/Web5Tests/Dids/BearerDIDTests.swift @@ -5,13 +5,43 @@ import XCTest final class BearerDIDTests: XCTestCase { - func test_export() async throws { + func test_export() throws { let didJWK = try DIDJWK.create(keyManager: InMemoryKeyManager()) - let portableDID = try await didJWK.export() + let portableDID = try didJWK.export() XCTAssertNoDifference(portableDID.uri, didJWK.uri) XCTAssertNoDifference(portableDID.document, didJWK.document) XCTAssertNoDifference(portableDID.privateKeys.count, 1) XCTAssertNil(portableDID.metadata) } + + func test_getSigner() throws { + let payload = "Hello, world!".data(using: .utf8)! + + let didJWK = try DIDJWK.create(keyManager: InMemoryKeyManager()) + let signer = try didJWK.getSigner() + + let signature = try signer.sign(payload: payload) + let isValid = try signer.verify(payload: payload, signature: signature) + + XCTAssertTrue(isValid) + } + + func test_getSigner_verificationMethodID() throws { + let payload = "Hello, world!".data(using: .utf8)! + + let didJWK = try DIDJWK.create(keyManager: InMemoryKeyManager()) + let verificationMethodID = try XCTUnwrap(didJWK.document.verificationMethod?.first?.id) + + let signer = try didJWK.getSigner(verificationMethodID: verificationMethodID) + let signature = try signer.sign(payload: payload) + let isValid = try signer.verify(payload: payload, signature: signature) + + XCTAssertTrue(isValid) + } + + func test_getSigner_invalidVerificationMethodID() throws { + let didJWK = try DIDJWK.create(keyManager: InMemoryKeyManager()) + XCTAssertThrowsError(try didJWK.getSigner(verificationMethodID: "not-real")) + } }