Skip to content

Commit

Permalink
feat(agent): add new agent derivation path
Browse files Browse the repository at this point in the history
Signed-off-by: goncalo-frade-iohk <goncalo.frade@iohk.io>
  • Loading branch information
goncalo-frade-iohk committed Jul 5, 2024
1 parent fa46536 commit 0b302f5
Show file tree
Hide file tree
Showing 23 changed files with 326 additions and 35 deletions.
38 changes: 30 additions & 8 deletions EdgeAgentSDK/Apollo/Sources/ApolloImpl+KeyRestoration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,37 @@ extension ApolloImpl: KeyRestoration {
}

public func restorePrivateKey(_ key: StorableKey) throws -> PrivateKey {
let derivationPath: DerivationPath?
if let derivationStr = key.queryDerivationPath {
derivationPath = try DerivationPath(string: derivationStr)
} else if let index = key.index {
derivationPath = DerivationPath(index: index)
} else {
derivationPath = nil
}
switch key.restorationIdentifier {
case "secp256k1+priv":
guard let index = key.index else {
guard let derivationPath else {
throw ApolloError.restoratonFailedNoIdentifierOrInvalid
}
return Secp256k1PrivateKey(
identifier: key.identifier,
internalKey: .init(raw: key.storableData.toKotlinByteArray()), derivationPath: DerivationPath(index: index)
internalKey: .init(raw: key.storableData.toKotlinByteArray()),
derivationPath: derivationPath
)
case "x25519+priv":
return try CreateX25519KeyPairOperation(logger: Self.logger)
.compute(
identifier: key.identifier,
fromPrivateKey: key.storableData
fromPrivateKey: key.storableData,
derivationPath: derivationPath
)
case "ed25519+priv":
return try CreateEd25519KeyPairOperation(logger: Self.logger)
.compute(
identifier: key.identifier,
fromPrivateKey: key.storableData
fromPrivateKey: key.storableData,
derivationPath: derivationPath
)
default:
throw ApolloError.restoratonFailedNoIdentifierOrInvalid
Expand Down Expand Up @@ -65,26 +76,37 @@ extension ApolloImpl: KeyRestoration {
}

public func restoreKey(_ key: StorableKey) async throws -> Key {
let derivationPath: DerivationPath?
if let derivationStr = key.queryDerivationPath {
derivationPath = try DerivationPath(string: derivationStr)
} else if let index = key.index {
derivationPath = DerivationPath(index: index)
} else {
derivationPath = nil
}
switch key.restorationIdentifier {
case "secp256k1+priv":
guard let index = key.index else {
guard let derivationPath else {
throw ApolloError.restoratonFailedNoIdentifierOrInvalid
}
return Secp256k1PrivateKey(
identifier: key.identifier,
internalKey: .init(raw: key.storableData.toKotlinByteArray()), derivationPath: DerivationPath(index: index)
internalKey: .init(raw: key.storableData.toKotlinByteArray()),
derivationPath: derivationPath
)
case "x25519+priv":
return try CreateX25519KeyPairOperation(logger: Self.logger)
.compute(
identifier: key.identifier,
fromPrivateKey: key.storableData
fromPrivateKey: key.storableData,
derivationPath: derivationPath
)
case "ed25519+priv":
return try CreateEd25519KeyPairOperation(logger: Self.logger)
.compute(
identifier: key.identifier,
fromPrivateKey: key.storableData
fromPrivateKey: key.storableData,
derivationPath: derivationPath
)
case "secp256k1+pub":
return Secp256k1PublicKey(
Expand Down
4 changes: 3 additions & 1 deletion EdgeAgentSDK/Apollo/Sources/ApolloImpl+Public.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ extension ApolloImpl: Apollo {
if
let keyData = parameters[KeyProperties.rawKey.rawValue].flatMap({ Data(base64Encoded: $0) })
{
let derivationPath = try parameters[KeyProperties.derivationPath.rawValue].map { try DerivationPath(string: $0) } ?? DerivationPath(index:0)
let derivationPath = try parameters[KeyProperties.derivationPath.rawValue].map {
try DerivationPath(string: $0)
} ?? DerivationPath()
return Secp256k1PrivateKey(raw: keyData, derivationPath: derivationPath)
} else {
guard
Expand Down
14 changes: 10 additions & 4 deletions EdgeAgentSDK/Apollo/Sources/Model/Ed25519Key.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@ import Foundation
struct Ed25519PrivateKey: PrivateKey {
private let internalKey: ApolloLibrary.KMMEdPrivateKey
let keyType: String = "EC"
let keySpecifications: [String : String] = [
"curve" : "Ed25519"
]
let keySpecifications: [String : String]
let derivationPath: Domain.DerivationPath?
var identifier: String
var size: Int { raw.count }
var raw: Data { internalKey.raw.toData() }

init(
identifier: String = UUID().uuidString,
internalKey: ApolloLibrary.KMMEdPrivateKey
internalKey: ApolloLibrary.KMMEdPrivateKey,
derivationPath: Domain.DerivationPath? = nil
) {
self.identifier = identifier
self.internalKey = internalKey
var keySpecifications = ["curve" : "Ed25519"]
derivationPath.map { keySpecifications[KeyProperties.derivationPath.rawValue] = $0.keyPathString() }
self.keySpecifications = keySpecifications
self.derivationPath = derivationPath
}

func publicKey() -> PublicKey {
Expand Down Expand Up @@ -46,6 +50,7 @@ extension Ed25519PrivateKey: KeychainStorableKey {
var restorationIdentifier: String { "ed25519+priv" }
var storableData: Data { raw }
var index: Int? { nil }
var queryDerivationPath: String? { derivationPath?.keyPathString() }
var type: Domain.KeychainStorableKeyProperties.KeyAlgorithm { .rawKey }
var keyClass: Domain.KeychainStorableKeyProperties.KeyType { .privateKey }
var accessiblity: Domain.KeychainStorableKeyProperties.Accessability? { .firstUnlock(deviceOnly: true) }
Expand Down Expand Up @@ -83,6 +88,7 @@ extension Ed25519PublicKey: KeychainStorableKey {
var restorationIdentifier: String { "ed25519+pub" }
var storableData: Data { raw }
var index: Int? { nil }
var queryDerivationPath: String? { nil }
var type: Domain.KeychainStorableKeyProperties.KeyAlgorithm { .rawKey }
var keyClass: Domain.KeychainStorableKeyProperties.KeyType { .publicKey }
var accessiblity: Domain.KeychainStorableKeyProperties.Accessability? { .firstUnlock(deviceOnly: true) }
Expand Down
1 change: 1 addition & 0 deletions EdgeAgentSDK/Apollo/Sources/Model/LinkSecret.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ extension LinkSecret: KeychainStorableKey {
var restorationIdentifier: String { "linkSecret+key" }
var storableData: Data { raw }
var index: Int? { nil }
var queryDerivationPath: String? { nil }
var type: Domain.KeychainStorableKeyProperties.KeyAlgorithm { .rawKey }
var keyClass: Domain.KeychainStorableKeyProperties.KeyType { .privateKey }
var accessiblity: Domain.KeychainStorableKeyProperties.Accessability? { .firstUnlock(deviceOnly: true) }
Expand Down
2 changes: 2 additions & 0 deletions EdgeAgentSDK/Apollo/Sources/Model/Secp256k1Key.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ extension Secp256k1PrivateKey: KeychainStorableKey {
var restorationIdentifier: String { "secp256k1+priv" }
var storableData: Data { raw }
var index: Int? { derivationPath.index }
var queryDerivationPath: String? { derivationPath.keyPathString() }
var type: Domain.KeychainStorableKeyProperties.KeyAlgorithm { .rawKey }
var keyClass: Domain.KeychainStorableKeyProperties.KeyType { .privateKey }
var accessiblity: Domain.KeychainStorableKeyProperties.Accessability? { .firstUnlock(deviceOnly: true) }
Expand Down Expand Up @@ -112,6 +113,7 @@ extension Secp256k1PublicKey: KeychainStorableKey {
var restorationIdentifier: String { "secp256k1+pub" }
var storableData: Data { raw }
var index: Int? { nil }
var queryDerivationPath: String? { nil }
var type: Domain.KeychainStorableKeyProperties.KeyAlgorithm { .rawKey }
var keyClass: Domain.KeychainStorableKeyProperties.KeyType { .publicKey }
var accessiblity: Domain.KeychainStorableKeyProperties.Accessability? { .firstUnlock(deviceOnly: true) }
Expand Down
14 changes: 10 additions & 4 deletions EdgeAgentSDK/Apollo/Sources/Model/X25519Key.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@ import Foundation
struct X25519PrivateKey: PrivateKey {
private let internalKey: ApolloLibrary.KMMX25519PrivateKey
let keyType: String = "EC"
let keySpecifications: [String : String] = [
"curve" : "x25519"
]
let keySpecifications: [String : String]
let derivationPath: Domain.DerivationPath?
var identifier:String
var size: Int { raw.count }
var raw: Data { internalKey.raw.toData() }

init(
identifier: String = UUID().uuidString,
internalKey: ApolloLibrary.KMMX25519PrivateKey
internalKey: ApolloLibrary.KMMX25519PrivateKey,
derivationPath: Domain.DerivationPath? = nil
) {
self.identifier = identifier
self.internalKey = internalKey
var keySpecifications = ["curve" : "x25519"]
derivationPath.map { keySpecifications[KeyProperties.derivationPath.rawValue] = $0.keyPathString() }
self.keySpecifications = keySpecifications
self.derivationPath = derivationPath
}

func publicKey() -> PublicKey {
Expand All @@ -34,6 +38,7 @@ extension X25519PrivateKey: KeychainStorableKey {
var restorationIdentifier: String { "x25519+priv" }
var storableData: Data { raw }
var index: Int? { nil }
var queryDerivationPath: String? { derivationPath?.keyPathString() }
var type: Domain.KeychainStorableKeyProperties.KeyAlgorithm { .rawKey }
var keyClass: Domain.KeychainStorableKeyProperties.KeyType { .privateKey }
var accessiblity: Domain.KeychainStorableKeyProperties.Accessability? { .firstUnlock(deviceOnly: true) }
Expand Down Expand Up @@ -71,6 +76,7 @@ extension X25519PublicKey: KeychainStorableKey {
var restorationIdentifier: String { "x25519+pub" }
var storableData: Data { raw }
var index: Int? { nil }
var queryDerivationPath: String? { nil }
var type: Domain.KeychainStorableKeyProperties.KeyAlgorithm { .rawKey }
var keyClass: Domain.KeychainStorableKeyProperties.KeyType { .publicKey }
var accessiblity: Domain.KeychainStorableKeyProperties.Accessability? { .firstUnlock(deviceOnly: true) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ struct CreateEd25519KeyPairOperation {

}

func compute(identifier: String = UUID().uuidString, fromPrivateKey: Data) throws -> PrivateKey {
func compute(
identifier: String = UUID().uuidString,
fromPrivateKey: Data,
derivationPath: Domain.DerivationPath? = nil
) throws -> PrivateKey {
return Ed25519PrivateKey(
identifier: identifier,
internalKey: KMMEdPrivateKey(raw: fromPrivateKey.toKotlinByteArray())
internalKey: KMMEdPrivateKey(raw: fromPrivateKey.toKotlinByteArray()),
derivationPath: derivationPath
)
}

Expand All @@ -27,6 +32,9 @@ struct CreateEd25519KeyPairOperation {
.derive(path: keyPath.keyPathString()
)

return Ed25519PrivateKey(internalKey: .init(raw: derivedHdKey.privateKey))
return Ed25519PrivateKey(
internalKey: .init(raw: derivedHdKey.privateKey),
derivationPath: keyPath
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ struct CreateX25519KeyPairOperation {

}

func compute(identifier: String = UUID().uuidString, fromPrivateKey: Data) throws -> PrivateKey {
func compute(
identifier: String = UUID().uuidString,
fromPrivateKey: Data,
derivationPath: Domain.DerivationPath? = nil
) throws -> PrivateKey {
let privateKey = KMMX25519PrivateKey(raw: fromPrivateKey.toKotlinByteArray())
return X25519PrivateKey(
identifier: identifier,
internalKey: privateKey
internalKey: privateKey,
derivationPath: derivationPath
)
}

Expand All @@ -29,6 +34,9 @@ struct CreateX25519KeyPairOperation {
.derive(path: keyPath.keyPathString()
)

return X25519PrivateKey(internalKey: KMMEdPrivateKey(raw: derivedHdKey.privateKey).x25519PrivateKey())
return X25519PrivateKey(
internalKey: KMMEdPrivateKey(raw: derivedHdKey.privateKey).x25519PrivateKey(),
derivationPath: keyPath
)
}
}
2 changes: 1 addition & 1 deletion EdgeAgentSDK/Castor/Tests/PrismDIDPublicKeyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ final class PrismDIDPublicKeyTests: XCTestCase {
KeyProperties.type.rawValue: "EC",
KeyProperties.curve.rawValue: KnownKeyCurves.secp256k1.rawValue,
KeyProperties.seed.rawValue: seed.value.base64Encoded(),
KeyProperties.derivationPath.rawValue: DerivationPath(index: 0).keyPathString()
KeyProperties.derivationPath.rawValue: DerivationPath().keyPathString()
])
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,60 @@ import Foundation

/// `DerivationPath` is a structure that represents the path for key derivation in hierarchical deterministic (HD) wallets.
public struct DerivationPath {

public enum Axis: RawRepresentable {
case normal(Int)
case hardened(Int)

public init?(rawValue: Int) {
if rawValue < 0 {
return nil
}
if rawValue >= 0 && rawValue < (1 << 31) {
self = .normal(rawValue)
} else if rawValue >= (1 << 31) && rawValue < (1 << 32) {
self = .hardened(rawValue - (1 << 31))
} else {
return nil
}
}

public var rawValue: Int {
switch self {
case .normal(let int):
return int
case .hardened(let int):
return int
}
}

public var string: String {
switch self {
case .normal(let int):
return "\(int)"
case .hardened(let int):
return "\(int)'"
}
}
}

/// The index of the key in the path.
@available(*, deprecated, renamed: "axis", message: "Use axis instead this property will be removed on a future version")
public let index: Int

public let axis: [Axis]

/// Creates a new `DerivationPath` instance from a given index.
/// - Parameter index: The index of the key in the path.
@available(*, deprecated, renamed: "init(axis:)", message: "Use init(axis:) instead this method will be removed on a future version")
public init(index: Int) {
self.index = index
self.axis = [.hardened(index), .hardened(0), .hardened(0)]
}

public init(axis: [Axis] = [.hardened(0), .hardened(0), .hardened(0)]) {
self.axis = axis
self.index = axis.first?.rawValue ?? 0
}

/// Creates a new `DerivationPath` instance from a path string.
Expand All @@ -26,16 +73,29 @@ public struct DerivationPath {
let parsingRegex = try NSRegularExpression(pattern: "\\d+", options: [])
let matches = parsingRegex.matches(in: string, options: [], range: NSRange(location: 0, length: string.utf16.count))

guard let firstMatch = matches.first else { throw CommonError.invalidRegex(regex: pattern, invalid: string) }
guard let firstMatch = matches.first else {
throw CommonError.invalidRegex(regex: pattern, invalid: string)
}
let range = Range(firstMatch.range, in: string)!
guard let index = Int(String(string[range])) else { throw CommonError.invalidRegex(regex: pattern, invalid: string) }
guard let index = Int(String(string[range])) else {
throw CommonError.invalidRegex(regex: pattern, invalid: string)
}
self.index = index
self.axis = try matches.map {
let range = Range($0.range, in: string)!
guard
let index = Int(String(string[range]))
else {
throw CommonError.invalidRegex(regex: pattern, invalid: string)
}
return Axis.hardened(index)
}
}

/// Returns a string representation of this `DerivationPath`.
/// - Returns: A string in the format of "m/<index>'/0'/0'".
public func keyPathString() -> String {
return "m/\(index)'/0'/0'"
return (["m"] + axis.map(\.string)).joined(separator: "/")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ public protocol StorableKey {
var storableData: Data { get }

/// Indexation of the key is useful to keep track of a derivation index
@available(*, deprecated, renamed: "derivationPath", message: "Use derivationPath instead this property will be removed on a future version")
var index: Int? { get }

/// Derivation path used for this key is useful to keep track of a derivation index
var queryDerivationPath: String? { get }
}

/// Extension of the `Key` protocol to provide additional functionality related to storage.
Expand Down
Loading

0 comments on commit 0b302f5

Please sign in to comment.