Skip to content

Commit 9d24490

Browse files
feat(castor): now castor and agents can create a did with any keys
Signed-off-by: goncalo-frade-iohk <[email protected]>
1 parent 1ea90cf commit 9d24490

File tree

22 files changed

+435
-110
lines changed

22 files changed

+435
-110
lines changed

EdgeAgentSDK/Castor/Sources/CastorImpl+Public.swift

+27-3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,28 @@ extension CastorImpl: Castor {
2929
// ).compute()
3030
// }
3131

32+
public func createDID(
33+
method: DIDMethod,
34+
keys: [(KeyPurpose, any PublicKey)],
35+
services: [DIDDocument.Service]
36+
) throws -> DID {
37+
switch method {
38+
case "prism":
39+
return try CreatePrismDIDOperation(
40+
apollo: apollo,
41+
keys: keys,
42+
services: services
43+
).compute()
44+
case "peer":
45+
return try CreatePeerDIDOperation(
46+
keys: keys,
47+
services: services
48+
).compute()
49+
default:
50+
throw CastorError.noResolversAvailableForDIDMethod(method: method)
51+
}
52+
}
53+
3254
/// createPrismDID creates a DID for a prism (a device or server that acts as a DID owner and controller) using a given master public key and list of services. This function may throw an error if the master public key or services are invalid.
3355
///
3456
/// - Parameters:
@@ -42,7 +64,7 @@ extension CastorImpl: Castor {
4264
) throws -> DID {
4365
try CreatePrismDIDOperation(
4466
apollo: apollo,
45-
masterPublicKey: masterPublicKey,
67+
keys: [(KeyPurpose.master, masterPublicKey)],
4668
services: services
4769
).compute()
4870
}
@@ -61,8 +83,10 @@ extension CastorImpl: Castor {
6183
services: [DIDDocument.Service]
6284
) throws -> DID {
6385
try CreatePeerDIDOperation(
64-
autenticationPublicKey: authenticationPublicKey,
65-
agreementPublicKey: keyAgreementPublicKey,
86+
keys: [
87+
(KeyPurpose.authentication, authenticationPublicKey),
88+
(KeyPurpose.agreement, keyAgreementPublicKey)
89+
],
6690
services: services
6791
).compute()
6892
}

EdgeAgentSDK/Castor/Sources/DID/PrismDID/PrismDIDPublicKey.swift

+27-15
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ struct PrismDIDPublicKey {
4444
case .issuingKey:
4545
return "issuing\(index)"
4646
case .capabilityDelegationKey:
47-
return "capabilityDelegationKey\(index)"
47+
return "capability-delegationKey\(index)"
4848
case .capabilityInvocationKey:
49-
return "capabilityInvocationKey\(index)"
49+
return "capability-invocationKey\(index)"
5050
case .authenticationKey:
5151
return "authentication\(index)"
5252
case .revocationKey:
5353
return "revocation\(index)"
5454
case .keyAgreementKey:
55-
return "keyAgreement\(index)"
55+
return "key-agreement\(index)"
5656
case .unknownKey:
5757
return "unknown\(index)"
5858
}
@@ -102,22 +102,34 @@ struct PrismDIDPublicKey {
102102
var protoKey = Io_Iohk_Atala_Prism_Protos_PublicKey()
103103
protoKey.id = id
104104
protoKey.usage = usage.toProto()
105-
guard
106-
let pointXStr = keyData.getProperty(.curvePointX),
107-
let pointYStr = keyData.getProperty(.curvePointY),
108-
let pointX = Data(base64URLEncoded: pointXStr),
109-
let pointY = Data(base64URLEncoded: pointYStr)
110-
else {
105+
switch curve {
106+
case "Ed25519", "X25519":
107+
var protoEC = Io_Iohk_Atala_Prism_Protos_CompressedECKeyData()
108+
protoEC.data = keyData.raw
109+
protoEC.curve = curve
110+
protoKey.keyData = .compressedEcKeyData(protoEC)
111+
case "secp256k1":
112+
guard
113+
let pointXStr = keyData.getProperty(.curvePointX),
114+
let pointYStr = keyData.getProperty(.curvePointY),
115+
let pointX = Data(base64URLEncoded: pointXStr),
116+
let pointY = Data(base64URLEncoded: pointYStr)
117+
else {
118+
throw ApolloError.missingKeyParameters(missing: [
119+
KeyProperties.curvePointX.rawValue,
120+
KeyProperties.curvePointY.rawValue
121+
])
122+
}
123+
var protoEC = Io_Iohk_Atala_Prism_Protos_ECKeyData()
124+
protoEC.x = pointX
125+
protoEC.y = pointY
126+
protoEC.curve = curve
127+
protoKey.keyData = .ecKeyData(protoEC)
128+
default:
111129
throw ApolloError.missingKeyParameters(missing: [
112-
KeyProperties.curvePointX.rawValue,
113130
KeyProperties.curvePointY.rawValue
114131
])
115132
}
116-
var protoEC = Io_Iohk_Atala_Prism_Protos_ECKeyData()
117-
protoEC.x = pointX
118-
protoEC.y = pointY
119-
protoEC.curve = curve
120-
protoKey.keyData = .ecKeyData(protoEC)
121133
return protoKey
122134
}
123135
}

EdgeAgentSDK/Castor/Sources/Operations/CreatePeerDIDOperation.swift

+11-4
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,21 @@ import PeerDID
77

88
struct CreatePeerDIDOperation {
99
private let method: DIDMethod = "peer"
10-
let autenticationPublicKey: PublicKey
11-
let agreementPublicKey: PublicKey
10+
let keys: [(KeyPurpose, PublicKey)]
1211
let services: [Domain.DIDDocument.Service]
1312

1413
func compute() throws -> Domain.DID {
14+
let authenticationKeys = try keys
15+
.filter { $0.0 == .authentication }
16+
.map(\.1)
17+
.map(authenticationFromPublicKey(publicKey:))
18+
let agreementKeys = try keys
19+
.filter { $0.0 == .agreement }
20+
.map(\.1)
21+
.map(keyAgreementFromPublicKey(publicKey:))
1522
let did = try PeerDIDHelper.createAlgo2(
16-
authenticationKeys: [authenticationFromPublicKey(publicKey: autenticationPublicKey)],
17-
agreementKeys: [keyAgreementFromPublicKey(publicKey: agreementPublicKey)],
23+
authenticationKeys: authenticationKeys,
24+
agreementKeys: agreementKeys,
1825
services: services.flatMap { service in
1926
service.serviceEndpoint.map {
2027
AnyCodable(dictionaryLiteral:

EdgeAgentSDK/Castor/Sources/Operations/CreatePrismDIDOperation.swift

+42-17
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,33 @@ import Foundation
55
struct CreatePrismDIDOperation {
66
private let method: DIDMethod = "prism"
77
let apollo: Apollo
8-
let masterPublicKey: PublicKey
8+
let keys: [(KeyPurpose, PublicKey)]
99
let services: [DIDDocument.Service]
1010

1111
func compute() throws -> DID {
1212
var operation = Io_Iohk_Atala_Prism_Protos_AtalaOperation()
13-
guard let masterKeyCurve = masterPublicKey.getProperty(.curve) else {
14-
throw CastorError.invalidPublicKeyCoding(didMethod: "prism", curve: "no curve")
13+
guard keys.count(where: { $0.0 == .master} ) == 1 else {
14+
throw CastorError.requiresOneAndJustOneMasterKey
1515
}
16+
let groupByPurpose = Dictionary(grouping: keys, by: { $0.0 })
1617
operation.createDid = try createDIDAtalaOperation(
17-
publicKeys: [PrismDIDPublicKey(
18-
apollo: apollo,
19-
id: PrismDIDPublicKey.Usage.authenticationKey.defaultId,
20-
curve: masterKeyCurve,
21-
usage: .authenticationKey,
22-
keyData: masterPublicKey
23-
),
24-
PrismDIDPublicKey(
25-
apollo: apollo,
26-
id: PrismDIDPublicKey.Usage.masterKey.defaultId,
27-
curve: masterKeyCurve,
28-
usage: .masterKey,
29-
keyData: masterPublicKey
30-
)],
18+
publicKeys: groupByPurpose.flatMap { (key, value) in
19+
try value
20+
.sorted(by: { $0.1.identifier < $1.1.identifier } )
21+
.enumerated()
22+
.map {
23+
guard let curve = $0.element.1.getProperty(.curve) else {
24+
throw CastorError.invalidPublicKeyCoding(didMethod: "prism", curve: "no curve")
25+
}
26+
return PrismDIDPublicKey(
27+
apollo: apollo,
28+
id: key.toPrismDIDKeyPurpose().id(index: $0.offset),
29+
curve: curve,
30+
usage: key.toPrismDIDKeyPurpose(),
31+
keyData: $0.element.1
32+
)
33+
}
34+
},
3135
services: services
3236
)
3337
return try createLongFormFromOperation(method: method, atalaOperation: operation)
@@ -68,3 +72,24 @@ struct CreatePrismDIDOperation {
6872
return DID(method: method, methodId: methodSpecificId.description)
6973
}
7074
}
75+
76+
extension KeyPurpose {
77+
func toPrismDIDKeyPurpose() -> PrismDIDPublicKey.Usage {
78+
switch self {
79+
case .master:
80+
return .masterKey
81+
case .issue:
82+
return .issuingKey
83+
case .authentication:
84+
return .authenticationKey
85+
case .capabilityDelegation:
86+
return .capabilityDelegationKey
87+
case .capabilityInvocation:
88+
return .capabilityInvocationKey
89+
case .agreement:
90+
return .keyAgreementKey
91+
case .revocation:
92+
return .revocationKey
93+
}
94+
}
95+
}

EdgeAgentSDK/Castor/Sources/Resolvers/LongFormPrismDIDResolver.swift

+28-19
Original file line numberDiff line numberDiff line change
@@ -112,25 +112,34 @@ struct LongFormPrismDIDResolver: DIDResolverDomain {
112112
serviceEndpoint: $0.serviceEndpoint.map { .init(uri: $0) }
113113
)
114114
}
115-
116-
let decodedPublicKeys = publicKeys.enumerated().map {
117-
let didUrl = DIDUrl(
118-
did: did,
119-
fragment: $0.element.usage.id(index: $0.offset - 1)
120-
)
121-
122-
let method = DIDDocument.VerificationMethod(
123-
id: didUrl,
124-
controller: did,
125-
type: $0.element.keyData.getProperty(.curve) ?? "",
126-
publicKeyMultibase: $0.element.keyData.raw.base64EncodedString()
127-
)
128-
129-
return PublicKeyDecoded(
130-
id: didUrl.string,
131-
keyType: .init(usage: $0.element.usage),
132-
method: method
133-
)
115+
let groupByPurpose = Dictionary(
116+
// Per specification master keys and revocation keys should not be in the did document
117+
grouping: publicKeys.filter { $0.usage != .masterKey && $0.usage != .revocationKey },
118+
by: { $0.usage }
119+
)
120+
let decodedPublicKeys = groupByPurpose.flatMap { (key, value) in
121+
value
122+
.sorted(by: { $0.id < $1.id } )
123+
.enumerated()
124+
.map {
125+
let didUrl = DIDUrl(
126+
did: did,
127+
fragment: $0.element.usage.id(index: $0.offset)
128+
)
129+
130+
let method = DIDDocument.VerificationMethod(
131+
id: didUrl,
132+
controller: did,
133+
type: $0.element.keyData.getProperty(.curve) ?? "",
134+
publicKeyMultibase: $0.element.keyData.raw.base64EncodedString()
135+
)
136+
137+
return PublicKeyDecoded(
138+
id: didUrl.string,
139+
keyType: .init(usage: $0.element.usage),
140+
method: method
141+
)
142+
}
134143
}
135144

136145
return (decodedPublicKeys, services)

EdgeAgentSDK/Domain/Sources/BBs/Castor.swift

+22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import Foundation
22

3+
public enum KeyPurpose: String, Hashable, Equatable, CaseIterable {
4+
case master
5+
case issue
6+
case capabilityDelegation
7+
case capabilityInvocation
8+
case authentication
9+
case revocation
10+
case agreement
11+
}
12+
313
/// The Castor protocol defines the set of decentralized identifier (DID) operations that are used in the Atala PRISM architecture. It provides a way for users to create, manage, and control their DIDs and associated cryptographic keys.
414
public protocol Castor {
515
/// parseDID parses a string representation of a Decentralized Identifier (DID) into a DID object. This function may throw an error if the string is not a valid DID.
@@ -8,6 +18,18 @@ public protocol Castor {
818
/// - Returns: The DID object
919
/// - Throws: An error if the string is not a valid DID
1020
func parseDID(str: String) throws -> DID
21+
22+
/// createDID creates a DID for a method using a given an array of public keys and list of services. This function may throw an error.
23+
/// - Parameters:
24+
/// - method: DID Method to use (ex: prism, peer)
25+
/// - keys: An array of Tuples with the public key and the key purpose
26+
/// - services: The list of services
27+
/// - Returns: The created DID
28+
func createDID(
29+
method: DIDMethod,
30+
keys: [(KeyPurpose, PublicKey)],
31+
services: [DIDDocument.Service]
32+
) throws -> DID
1133

1234
/// createPrismDID creates a DID for a prism (a device or server that acts as a DID owner and controller) using a given master public key and list of services. This function may throw an error if the master public key or services are invalid.
1335
///

EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public enum CredentialOperationsOptions {
1212
case entropy(String) // Entropy for any randomization operation.
1313
case signableKey(SignableKey) // A key that can be used for signing.
1414
case exportableKey(ExportableKey) // A key that can be exported.
15+
case exportableKeys([ExportableKey]) // A key that can be exported.
1516
case zkpPresentationParams(attributes: [String: Bool], predicates: [String]) // Anoncreds zero-knowledge proof presentation parameters
1617
case disclosingClaims(claims: [String])
1718
case thid(String)

EdgeAgentSDK/Domain/Sources/Models/Errors.swift

+7
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,9 @@ public enum CastorError: KnownPrismError {
377377
/// An error case representing inability to retrieve the public key from a document.
378378
case cannotRetrievePublicKeyFromDocument
379379

380+
/// An error case representing that a master key was not provided or that it had more than one
381+
case requiresOneAndJustOneMasterKey
382+
380383
/// The error code returned by the server.
381384
public var code: Int {
382385
switch self {
@@ -400,6 +403,8 @@ public enum CastorError: KnownPrismError {
400403
return 29
401404
case .cannotRetrievePublicKeyFromDocument:
402405
return 30
406+
case .requiresOneAndJustOneMasterKey:
407+
return 31
403408
}
404409
}
405410

@@ -432,6 +437,8 @@ public enum CastorError: KnownPrismError {
432437
return "No resolvers in castor are able to resolve the method \(method), please provide a resolver"
433438
case .cannotRetrievePublicKeyFromDocument:
434439
return "The public keys in the DIDDocument are not in multibase or the multibase is invalid"
440+
case .requiresOneAndJustOneMasterKey:
441+
return "The array contains none or more than one master key"
435442
}
436443
}
437444
}

EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Credentials.swift

+12-12
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,20 @@ public extension DIDCommAgent {
180180
.first()
181181
.await()
182182

183-
guard let storedPrivateKey = didInfo?.privateKeys.first else { throw EdgeAgentError.cannotFindDIDKeyPairIndex }
183+
let downloader = DownloadDataWithResolver(castor: castor)
184+
guard
185+
let attachment = offer.attachments.first,
186+
let offerFormat = attachment.format
187+
else {
188+
throw PolluxError.unsupportedIssuedMessage
189+
}
190+
191+
guard let storedPrivateKey = didInfo?.privateKeys else { throw EdgeAgentError.cannotFindDIDKeyPairIndex }
184192

185-
let privateKey = try await apollo.restorePrivateKey(storedPrivateKey)
193+
let privateKeys = try await storedPrivateKey.asyncMap { try await apollo.restorePrivateKey($0) }
194+
let exporting = privateKeys.compactMap(\.exporting)
186195

187196
guard
188-
let exporting = privateKey.exporting,
189197
let linkSecret = try await pluto.getLinkSecret().first().await()
190198
else { throw EdgeAgentError.cannotFindDIDKeyPairIndex }
191199

@@ -194,14 +202,6 @@ public extension DIDCommAgent {
194202
let linkSecretString = String(data: restored.raw, encoding: .utf8)
195203
else { throw EdgeAgentError.cannotFindDIDKeyPairIndex }
196204

197-
let downloader = DownloadDataWithResolver(castor: castor)
198-
guard
199-
let attachment = offer.attachments.first,
200-
let offerFormat = attachment.format
201-
else {
202-
throw PolluxError.unsupportedIssuedMessage
203-
}
204-
205205
let jsonData: Data
206206
switch attachment.data {
207207
case let attchedData as AttachmentBase64:
@@ -218,7 +218,7 @@ public extension DIDCommAgent {
218218
type: offerFormat,
219219
offerPayload: jsonData,
220220
options: [
221-
.exportableKey(exporting),
221+
.exportableKeys(exporting),
222222
.subjectDID(did),
223223
.linkSecret(id: did.string, secret: linkSecretString),
224224
.credentialDefinitionDownloader(downloader: downloader),

0 commit comments

Comments
 (0)