···11+import Foundation
22+@preconcurrency import CryptoKit
33+44+/// Abstraction over a DPoP signing key. v1 ships an in-memory ES256 impl
55+/// via CryptoKit; a future impl can back the key with the Secure Enclave.
66+public protocol DPoPKey: Sendable {
77+ /// The public JWK (JSON Web Key) for this key, as a JSON-compatible
88+ /// `[String: Any]` dict. Consumers embed this in the DPoP JWT header.
99+ func publicJWK() async throws -> [String: Any]
1010+1111+ /// Signs the given message with ES256, returning the raw r||s concatenation
1212+ /// (64 bytes). Callers convert to JWS b64url as needed.
1313+ func sign(_ message: Data) async throws -> Data
1414+1515+ /// Returns the underlying CryptoKit public key for verification in tests.
1616+ func publicKey() async throws -> CryptoKit.P256.Signing.PublicKey
1717+}
1818+1919+/// In-memory ES256 keypair for dev and tests. The host app will eventually
2020+/// provide a Secure Enclave-backed implementation under the same protocol.
2121+public final class InMemoryES256DPoPKey: DPoPKey, Sendable {
2222+ private let privateKey: CryptoKit.P256.Signing.PrivateKey
2323+2424+ public static func generate() throws -> InMemoryES256DPoPKey {
2525+ InMemoryES256DPoPKey(privateKey: CryptoKit.P256.Signing.PrivateKey())
2626+ }
2727+2828+ init(privateKey: CryptoKit.P256.Signing.PrivateKey) {
2929+ self.privateKey = privateKey
3030+ }
3131+3232+ public func publicJWK() async throws -> [String: Any] {
3333+ let rep = privateKey.publicKey.x963Representation
3434+ // x963 format: 0x04 || X (32 bytes) || Y (32 bytes)
3535+ let x = rep.subdata(in: 1..<33)
3636+ let y = rep.subdata(in: 33..<65)
3737+ return [
3838+ "kty": "EC",
3939+ "crv": "P-256",
4040+ "alg": "ES256",
4141+ "x": PKCE.base64URLEncode(x),
4242+ "y": PKCE.base64URLEncode(y),
4343+ ]
4444+ }
4545+4646+ public func sign(_ message: Data) async throws -> Data {
4747+ let signature = try privateKey.signature(for: message)
4848+ return signature.rawRepresentation
4949+ }
5050+5151+ public func publicKey() async throws -> CryptoKit.P256.Signing.PublicKey {
5252+ privateKey.publicKey
5353+ }
5454+}