Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 26 additions & 4 deletions provider-swift/Sources/ProviderCore/ProviderLoop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public struct ProviderLoopConfig: Sendable {
public actor ProviderLoop {
private let loopConfig: ProviderLoopConfig
private let keyPair: NodeKeyPair
private let seIdentity: SecureEnclaveIdentity?
private let signer: (any AttestationSigner)?
private let attestationBuilder: AttestationBuilder?
private let scheduler: BatchScheduler
private let stats: AtomicProviderStats
Expand Down Expand Up @@ -113,8 +113,8 @@ public actor ProviderLoop {
self.loopConfig = config
NodeKeyPair.purgeLegacyFiles()
self.keyPair = NodeKeyPair.generate()
self.seIdentity = try SecureEnclaveIdentity.createEphemeral()
self.attestationBuilder = seIdentity.map { AttestationBuilder(identity: $0) }
self.signer = Self.createAttestationSigner()
self.attestationBuilder = signer.map { AttestationBuilder(identity: $0) }
self.stats = AtomicProviderStats()
self.state = ProviderState()
self.cancellationRegistry = InferenceCancellationRegistry()
Expand All @@ -125,6 +125,28 @@ public actor ProviderLoop {
)
}

/// Try persistent keychain-backed SE key first; fall back to ephemeral CryptoKit key.
private static func createAttestationSigner() -> (any AttestationSigner)? {
let log = ProviderLogger(subsystem: "dev.darkbloom.provider", category: "loop")

if PersistentEnclaveKey.isAvailable {
do {
let key = try PersistentEnclaveKey.loadOrCreate()
log.info("Using persistent keychain-backed Secure Enclave key for attestation")
return key
} catch {
log.warning("Persistent SE key unavailable (\(error)), falling back to ephemeral")
}
}

do {
return try SecureEnclaveIdentity.createEphemeral()
} catch {
log.warning("Ephemeral SE identity also unavailable: \(error)")
return nil
}
}

// MARK: - Main Run Loop

public func run() async throws {
Expand Down Expand Up @@ -416,7 +438,7 @@ public actor ProviderLoop {
let providerStats = self.stats
let providerState = self.state
let registry = self.cancellationRegistry
let signingIdentity = self.seIdentity
let signingIdentity = self.signer
let log = self.logger

// 7. Spawn inference task
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,17 +144,21 @@ public enum StatusCanonical {

// MARK: - Builder

/// Builds and signs attestation blobs using a Secure Enclave identity.
/// Builds and signs attestation blobs using a Secure Enclave signing key.
///
/// Accepts any `AttestationSigner` -- either the ephemeral
/// `SecureEnclaveIdentity` (CryptoKit) or the persistent
/// `PersistentEnclaveKey` (Security framework, keychain-backed).
///
/// Usage:
/// 1. Create or load a SecureEnclaveIdentity
/// 2. Create an AttestationBuilder with that identity
/// 1. Create or load a signing key (ephemeral or persistent)
/// 2. Create an AttestationBuilder with that signer
/// 3. Call `buildAttestation()` to get a SignedAttestation
/// 4. Serialize to JSON and include in the Register message
public final class AttestationBuilder: @unchecked Sendable {
private let identity: SecureEnclaveIdentity
private let identity: any AttestationSigner

public init(identity: SecureEnclaveIdentity) {
public init(identity: any AttestationSigner) {
self.identity = identity
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// AttestationSigner -- protocol abstracting over ephemeral and persistent
/// Secure Enclave signing keys for attestation.
///
/// Both `SecureEnclaveIdentity` (CryptoKit, ephemeral) and
/// `PersistentEnclaveKey` (Security framework, keychain-backed) conform.
/// `AttestationBuilder` and `ProviderLoop` use this protocol to accept
/// either implementation.

import Foundation

public protocol AttestationSigner: Sendable {
/// Sign arbitrary data, returning a DER-encoded ECDSA signature.
func sign(_ data: Data) throws -> Data

/// Base64-encoded P-256 public key (raw 64 bytes: X || Y).
var publicKeyBase64: String { get }
}

// MARK: - Conformances

extension SecureEnclaveIdentity: AttestationSigner {}

extension PersistentEnclaveKey: AttestationSigner {}
Loading
Loading