Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Commit

Permalink
Implement web5 did:jwk test vectors
Browse files Browse the repository at this point in the history
  • Loading branch information
amika-sq committed Jan 4, 2024
1 parent d70c16d commit 7499b3c
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 59 deletions.
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ let package = Package(
resources: [
.copy("TestVectors/ed25519"),
.copy("TestVectors/secp256k1"),
.copy("TestVectors/did_jwk"),
]
),
]
Expand Down
65 changes: 8 additions & 57 deletions Sources/tbDEX/Dids/DidDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import Foundation
/// repository services.
///
/// A DID Document can be retrieved by _resolving_ a DID URI
struct DidDocument: Codable {
struct DidDocument: Codable, Equatable {

let context: String?
let context: OneOrMany<String>?

/// The DID URI for a particular DID subject is expressed using the id property in the DID document.
let id: String
Expand All @@ -26,7 +26,7 @@ struct DidDocument: Codable {
/// A DID controller is an entity that is authorized to make changes to a
/// DID document. The process of authorizing a DID controller is defined
/// by the DID method.
var controller: DidController?
var controller: OneOrMany<String>?

/// Cryptographic public keys, which can be used to authenticate or authorize
/// interactions with the DID subject or associated parties.
Expand Down Expand Up @@ -80,10 +80,10 @@ struct DidDocument: Codable {
var capabilityInvocation: [String]?

init(
context: String? = nil,
context: OneOrMany<String>? = nil,
id: String,
alsoKnownAs: [String]? = nil,
controller: DidController? = nil,
controller: OneOrMany<String>? = nil,
verificationMethod: [DidVerificationMethod]? = nil,
service: [DidService]? = nil,
assertionMethod: [String]? = nil,
Expand Down Expand Up @@ -125,7 +125,7 @@ struct DidDocument: Codable {
/// changes, as it represents metadata about the DID document.
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#dfn-diddocumentmetadata)
struct Metadata: Codable {
struct Metadata: Codable, Equatable {

/// Timestamp of the Create operation. The value of the property MUST be a
/// string formatted as an XML Datetime normalized to UTC 00:00:00 and
Expand Down Expand Up @@ -195,55 +195,6 @@ struct DidDocument: Codable {
}
}

/// DID Controller
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#did-controller)
/// This is necessary, as the controller can be either a String, or a set of strings.
/// Swift does not allow multiple types, and must handle both cases when encoding/decoding.
struct DidController: Codable {
var value: Either<String, [String]>

init(_ value: Either<String, [String]>) {
self.value = value
}

enum CodingKeys: CodingKey {
case value
}

enum Either<A, B> {
case left(A)
case right(B)
}

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let singleValue = try? container.decode(String.self) {
self.value = .left(singleValue)
} else if let arrayValue = try? container.decode([String].self) {
self.value = .right(arrayValue)
} else {
throw DecodingError.typeMismatch(
DidController.self,
DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Expected either String or [String]"
)
)
}
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch value {
case .left(let singleValue):
try container.encode(singleValue)
case .right(let arrayValue):
try container.encode(arrayValue)
}
}
}

/// A DID document can express verification methods, such as cryptographic
/// public keys, which can be used to authenticate or authorize interactions
/// with the DID subject or associated parties. For example,
Expand All @@ -252,7 +203,7 @@ struct DidController: Codable {
/// signer could use the associated cryptographic private key
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#verification-methods)
struct DidVerificationMethod: Codable {
struct DidVerificationMethod: Codable, Equatable {
let id: String
let type: String
let controller: String
Expand All @@ -279,7 +230,7 @@ struct DidVerificationMethod: Codable {
/// A service can be any type of service the DID subject wants to advertise.
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#services)
struct DidService: Codable {
struct DidService: Codable, Equatable {
let id: String
let type: String
let serviceEndpoint: String
Expand Down
4 changes: 4 additions & 0 deletions Sources/tbDEX/Dids/DidJwk.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ struct DidJwk: Did {
)

let didDocument = DidDocument(
context: .many([
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1",
]),
id: didUri,
verificationMethod: [verifiationMethod],
assertionMethod: [verifiationMethod.id],
Expand Down
4 changes: 2 additions & 2 deletions Sources/tbDEX/Dids/DidResolution.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ enum DidResolution {
/// Representation of the result of a DID (Decentralized Identifier) resolution
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#resolution)
struct Result {
struct Result: Codable, Equatable {

/// The metadata associated with the DID resolution process.
///
Expand Down Expand Up @@ -52,7 +52,7 @@ enum DidResolution {
/// the resolution process itself
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#dfn-didresolutionmetadata)
struct Metadata: Codable {
struct Metadata: Codable, Equatable {

/// The Media Type of the returned didDocumentStream. This property is
/// REQUIRED if resolution is successful and if the resolveRepresentation
Expand Down
33 changes: 33 additions & 0 deletions Sources/tbDEX/Utilities/OneOrMany.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation

enum OneOrMany<T: Codable & Equatable>: Codable, Equatable {
case one(T)
case many([T])

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let singleValue = try? container.decode(T.self) {
self = .one(singleValue)
} else if let arrayValue = try? container.decode([T].self) {
self = .many(arrayValue)
} else {
throw DecodingError.typeMismatch(
OneOrMany.self,
DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Expected either \(T.self) or [\(T.self)]"
)
)
}
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .one(let singleValue):
try container.encode(singleValue)
case .many(let arrayValue):
try container.encode(arrayValue)
}
}
}
21 changes: 21 additions & 0 deletions Tests/tbDEXTests/Dids/Web5TestVectorsDidJwk.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import CustomDump
import XCTest

@testable import tbDEX

final class Web5TestVectorsDidJwk: XCTestCase {

func test_resolve() throws {
let testVector: TestVector<String, DidResolution.Result> = try loadTestVector(
fileName: "resolve",
subdirectory: "did_jwk"
)

for vector in testVector.vectors {
let didUri = vector.input
let result = DidJwk.resolve(didUri: didUri)
XCTAssertNoDifference(result, vector.output)
}
}

}
107 changes: 107 additions & 0 deletions Tests/tbDEXTests/TestVectors/did_jwk/resolve.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{
"description": "did:jwk resolution test vectors",
"vectors": [
{
"description": "resolves did:jwk 1",
"input": "did:jwk:eyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6InNlY3AyNTZrMSIsImtpZCI6ImkzU1BSQnRKS292SEZzQmFxTTkydGk2eFFDSkxYM0U3WUNld2lIVjJDU2ciLCJ4IjoidmRyYnoyRU96dmJMRFZfLWtMNGVKdDdWSS04VEZaTm1BOVlnV3p2aGg3VSIsInkiOiJWTEZxUU1aUF9Bc3B1Y1hvV1gyLWJHWHBBTzFmUTVMbjE5VjVSQXhyZ3ZVIiwiYWxnIjoiRVMyNTZLIn0",
"output": {
"@context": "https://w3id.org/did-resolution/v1",
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:jwk:eyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6InNlY3AyNTZrMSIsImtpZCI6ImkzU1BSQnRKS292SEZzQmFxTTkydGk2eFFDSkxYM0U3WUNld2lIVjJDU2ciLCJ4IjoidmRyYnoyRU96dmJMRFZfLWtMNGVKdDdWSS04VEZaTm1BOVlnV3p2aGg3VSIsInkiOiJWTEZxUU1aUF9Bc3B1Y1hvV1gyLWJHWHBBTzFmUTVMbjE5VjVSQXhyZ3ZVIiwiYWxnIjoiRVMyNTZLIn0",
"verificationMethod": [
{
"type": "JsonWebKey2020",
"id": "did:jwk:eyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6InNlY3AyNTZrMSIsImtpZCI6ImkzU1BSQnRKS292SEZzQmFxTTkydGk2eFFDSkxYM0U3WUNld2lIVjJDU2ciLCJ4IjoidmRyYnoyRU96dmJMRFZfLWtMNGVKdDdWSS04VEZaTm1BOVlnV3p2aGg3VSIsInkiOiJWTEZxUU1aUF9Bc3B1Y1hvV1gyLWJHWHBBTzFmUTVMbjE5VjVSQXhyZ3ZVIiwiYWxnIjoiRVMyNTZLIn0#0",
"controller": "did:jwk:eyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6InNlY3AyNTZrMSIsImtpZCI6ImkzU1BSQnRKS292SEZzQmFxTTkydGk2eFFDSkxYM0U3WUNld2lIVjJDU2ciLCJ4IjoidmRyYnoyRU96dmJMRFZfLWtMNGVKdDdWSS04VEZaTm1BOVlnV3p2aGg3VSIsInkiOiJWTEZxUU1aUF9Bc3B1Y1hvV1gyLWJHWHBBTzFmUTVMbjE5VjVSQXhyZ3ZVIiwiYWxnIjoiRVMyNTZLIn0",
"publicKeyJwk": {
"kty": "EC",
"use": "sig",
"crv": "secp256k1",
"kid": "i3SPRBtJKovHFsBaqM92ti6xQCJLX3E7YCewiHV2CSg",
"x": "vdrbz2EOzvbLDV_-kL4eJt7VI-8TFZNmA9YgWzvhh7U",
"y": "VLFqQMZP_AspucXoWX2-bGXpAO1fQ5Ln19V5RAxrgvU",
"alg": "ES256K"
}
}
],
"authentication": [
"did:jwk:eyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6InNlY3AyNTZrMSIsImtpZCI6ImkzU1BSQnRKS292SEZzQmFxTTkydGk2eFFDSkxYM0U3WUNld2lIVjJDU2ciLCJ4IjoidmRyYnoyRU96dmJMRFZfLWtMNGVKdDdWSS04VEZaTm1BOVlnV3p2aGg3VSIsInkiOiJWTEZxUU1aUF9Bc3B1Y1hvV1gyLWJHWHBBTzFmUTVMbjE5VjVSQXhyZ3ZVIiwiYWxnIjoiRVMyNTZLIn0#0"
],
"assertionMethod": [
"did:jwk:eyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6InNlY3AyNTZrMSIsImtpZCI6ImkzU1BSQnRKS292SEZzQmFxTTkydGk2eFFDSkxYM0U3WUNld2lIVjJDU2ciLCJ4IjoidmRyYnoyRU96dmJMRFZfLWtMNGVKdDdWSS04VEZaTm1BOVlnV3p2aGg3VSIsInkiOiJWTEZxUU1aUF9Bc3B1Y1hvV1gyLWJHWHBBTzFmUTVMbjE5VjVSQXhyZ3ZVIiwiYWxnIjoiRVMyNTZLIn0#0"
],
"capabilityInvocation": [
"did:jwk:eyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6InNlY3AyNTZrMSIsImtpZCI6ImkzU1BSQnRKS292SEZzQmFxTTkydGk2eFFDSkxYM0U3WUNld2lIVjJDU2ciLCJ4IjoidmRyYnoyRU96dmJMRFZfLWtMNGVKdDdWSS04VEZaTm1BOVlnV3p2aGg3VSIsInkiOiJWTEZxUU1aUF9Bc3B1Y1hvV1gyLWJHWHBBTzFmUTVMbjE5VjVSQXhyZ3ZVIiwiYWxnIjoiRVMyNTZLIn0#0"
],
"capabilityDelegation": [
"did:jwk:eyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6InNlY3AyNTZrMSIsImtpZCI6ImkzU1BSQnRKS292SEZzQmFxTTkydGk2eFFDSkxYM0U3WUNld2lIVjJDU2ciLCJ4IjoidmRyYnoyRU96dmJMRFZfLWtMNGVKdDdWSS04VEZaTm1BOVlnV3p2aGg3VSIsInkiOiJWTEZxUU1aUF9Bc3B1Y1hvV1gyLWJHWHBBTzFmUTVMbjE5VjVSQXhyZ3ZVIiwiYWxnIjoiRVMyNTZLIn0#0"
]
},
"didDocumentMetadata": {},
"didResolutionMetadata": {}
},
"errors": false
},
{
"description": "resolves did:jwk 2",
"input": "did:jwk:eyJrdHkiOiJPS1AiLCJ1c2UiOiJzaWciLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiVnRTSFhQbEtEdzFFRW9PajVYTjNYV2hqU1BZVk52WC1lNHZqUk8weVlKQSIsIngiOiJpejcwc3ZTTHhOWmhzRHhlSlFfam5PVmJYM0tGTmtjQmNNaldqWm1YRXNBIiwiYWxnIjoiRWREU0EifQ",
"output": {
"@context": "https://w3id.org/did-resolution/v1",
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:jwk:eyJrdHkiOiJPS1AiLCJ1c2UiOiJzaWciLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiVnRTSFhQbEtEdzFFRW9PajVYTjNYV2hqU1BZVk52WC1lNHZqUk8weVlKQSIsIngiOiJpejcwc3ZTTHhOWmhzRHhlSlFfam5PVmJYM0tGTmtjQmNNaldqWm1YRXNBIiwiYWxnIjoiRWREU0EifQ",
"verificationMethod": [
{
"type": "JsonWebKey2020",
"id": "did:jwk:eyJrdHkiOiJPS1AiLCJ1c2UiOiJzaWciLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiVnRTSFhQbEtEdzFFRW9PajVYTjNYV2hqU1BZVk52WC1lNHZqUk8weVlKQSIsIngiOiJpejcwc3ZTTHhOWmhzRHhlSlFfam5PVmJYM0tGTmtjQmNNaldqWm1YRXNBIiwiYWxnIjoiRWREU0EifQ#0",
"controller": "did:jwk:eyJrdHkiOiJPS1AiLCJ1c2UiOiJzaWciLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiVnRTSFhQbEtEdzFFRW9PajVYTjNYV2hqU1BZVk52WC1lNHZqUk8weVlKQSIsIngiOiJpejcwc3ZTTHhOWmhzRHhlSlFfam5PVmJYM0tGTmtjQmNNaldqWm1YRXNBIiwiYWxnIjoiRWREU0EifQ",
"publicKeyJwk": {
"kty": "OKP",
"use": "sig",
"crv": "Ed25519",
"kid": "VtSHXPlKDw1EEoOj5XN3XWhjSPYVNvX-e4vjRO0yYJA",
"x": "iz70svSLxNZhsDxeJQ_jnOVbX3KFNkcBcMjWjZmXEsA",
"alg": "EdDSA"
}
}
],
"authentication": [
"did:jwk:eyJrdHkiOiJPS1AiLCJ1c2UiOiJzaWciLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiVnRTSFhQbEtEdzFFRW9PajVYTjNYV2hqU1BZVk52WC1lNHZqUk8weVlKQSIsIngiOiJpejcwc3ZTTHhOWmhzRHhlSlFfam5PVmJYM0tGTmtjQmNNaldqWm1YRXNBIiwiYWxnIjoiRWREU0EifQ#0"
],
"assertionMethod": [
"did:jwk:eyJrdHkiOiJPS1AiLCJ1c2UiOiJzaWciLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiVnRTSFhQbEtEdzFFRW9PajVYTjNYV2hqU1BZVk52WC1lNHZqUk8weVlKQSIsIngiOiJpejcwc3ZTTHhOWmhzRHhlSlFfam5PVmJYM0tGTmtjQmNNaldqWm1YRXNBIiwiYWxnIjoiRWREU0EifQ#0"
],
"capabilityInvocation": [
"did:jwk:eyJrdHkiOiJPS1AiLCJ1c2UiOiJzaWciLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiVnRTSFhQbEtEdzFFRW9PajVYTjNYV2hqU1BZVk52WC1lNHZqUk8weVlKQSIsIngiOiJpejcwc3ZTTHhOWmhzRHhlSlFfam5PVmJYM0tGTmtjQmNNaldqWm1YRXNBIiwiYWxnIjoiRWREU0EifQ#0"
],
"capabilityDelegation": [
"did:jwk:eyJrdHkiOiJPS1AiLCJ1c2UiOiJzaWciLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiVnRTSFhQbEtEdzFFRW9PajVYTjNYV2hqU1BZVk52WC1lNHZqUk8weVlKQSIsIngiOiJpejcwc3ZTTHhOWmhzRHhlSlFfam5PVmJYM0tGTmtjQmNNaldqWm1YRXNBIiwiYWxnIjoiRWREU0EifQ#0"
]
},
"didDocumentMetadata": {},
"didResolutionMetadata": {}
},
"errors": false
},
{
"description": "resolution for invalid did",
"input": "did:jwk:hehe",
"output": {
"@context": "https://w3id.org/did-resolution/v1",
"didDocument": null,
"didResolutionMetadata": {
"error": "invalidDid"
},
"didDocumentMetadata": {}
},
"errors": false
}
]
}

0 comments on commit 7499b3c

Please sign in to comment.