Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(agent): add new agent derivation path #150

Merged
merged 1 commit into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading