PRF providers
The built-in PasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProvider covers the common case. Reach for this page when:
- You need platform-specific provider options (iOS
URLSession/ presentation anchor, AndroidActivitywiring, webauthenticatorAttachment). - You're integrating Python, Go, or C# (no built-in
PasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderships for those bindings). - You need a custom
PrfProviderPrfProviderPrfProviderPrfProviderPrfProviderPrfProviderPrfProviderPrfProviderPrfProvider(CLI YubiKey, FIDO2, air-gapped backup file, hardware module).
Built-in PasskeyProvider options
The built-in PasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProvider takes a PasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptions:
| Field | Default | Description |
|---|---|---|
rp_idrp_idrpIdrpIdrpIdrpIdrpIdRpIdRpId | Breez shared RP | Relying Party ID. Your app's domain, or PasskeyProvider.BREEZ_RP_ID (keys.breez.technology) if Breez-registered. Changing it makes existing passkeys derive a different seed (see migration considerations). |
rp_namerp_namerpNamerpNamerpNamerpNamerpNameRpNameRpName | "Breez" | Display name for your app, shown in some authenticator UIs. Registration-only. |
user_nameuser_nameuserNameuserNameuserNameuserNameuserNameUserNameUserName | rp_namerp_namerpNamerpNamerpNamerpNamerpNameRpNameRpName | Account identifier shown beneath the display name in the OS picker, e.g. john@doe.com. Pass a stable per-user value so each registration is a distinct entry (Apple Passwords dedupes by (rpId, user.name)). Registration-only. |
user_display_nameuser_display_nameuserDisplayNameuserDisplayNameuserDisplayNameuserDisplayNameuserDisplayNameUserDisplayNameUserDisplayName | user_nameuser_nameuserNameuserNameuserNameuserNameuserNameUserNameUserName | Human-friendly name shown most prominently, e.g. John Doe. Registration-only. |
The same PasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptionsPasskeyProviderOptions is settable on passkey_configpasskey_configpasskeyConfigpasskeyConfigpasskeyConfigpasskeyConfigpasskeyConfigPasskeyConfigPasskeyConfig via provider_optionsprovider_optionsproviderOptionsproviderOptionsproviderOptionsproviderOptionsproviderOptionsProviderOptionsProviderOptions, which builds the provider for you (see Initialization). Construct PasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProvider directly only for platform-specific options (iOS URLSession, web authenticatorAttachment) or a custom backend.
C# / Go / Python limitation
The SDK does not ship a built-in PasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProviderPasskeyProvider for C#, Go, or Python (no native passkey API to wrap). On those bindings, implement your own PrfProviderPrfProviderPrfProviderPrfProviderPrfProviderPrfProviderPrfProviderPrfProviderPrfProvider and pass it to PasskeyClientPasskeyClientPasskeyClientPasskeyClientPasskeyClientPasskeyClientPasskeyClientPasskeyClientPasskeyClient.
Custom PrfProvider
To support a custom authenticator (hardware security key, FIDO2/CTAP2 transport, air-gapped backup file), implement the PrfProviderPrfProviderPrfProviderPrfProviderPrfProviderPrfProviderPrfProviderPrfProviderPrfProvider interface directly. The Breez CLI ships YubiKey, FIDO2, and file-based implementations as references.
/// Implement PrfProvider for a custom authenticator (hardware key, FIDO2,
/// file-backed). Only derive_seeds and is_supported are required.
struct CustomPrfProvider;
#[async_trait::async_trait]
impl PrfProvider for CustomPrfProvider {
async fn derive_seeds(
&self,
_request: DeriveSeedsRequest,
) -> Result<DeriveSeedsOutput, PrfProviderError> {
// Return one 32-byte PRF output per salt, in input order.
todo!("Implement using WebAuthn or native passkey APIs")
}
async fn is_supported(&self) -> Result<bool, PrfProviderError> {
todo!("Check platform passkey availability")
}
async fn create_passkey(
&self,
_exclude_credentials: Vec<Vec<u8>>,
) -> Result<PasskeyCredential, PrfProviderError> {
// Register a credential and return its ID plus attestation.
todo!("Implement registration via WebAuthn create() / native API")
}
async fn check_domain_association(&self) -> Result<DomainAssociation, PrfProviderError> {
// Custom providers without a verification source return Skipped.
Ok(DomainAssociation::Skipped {
reason: "CustomPrfProvider does not verify domain association".to_string(),
})
}
}
// Implement PrfProvider for a custom authenticator (hardware key, FIDO2,
// file-backed). Only deriveSeeds and isSupported are required.
class CustomPrfProvider: PrfProvider {
func deriveSeeds(request: DeriveSeedsRequest) async throws -> DeriveSeedsOutput {
// Return one 32-byte PRF output per salt, in input order.
fatalError("Implement using WebAuthn or native passkey APIs")
}
func isSupported() async throws -> Bool {
fatalError("Check platform passkey availability")
}
func createPasskey(excludeCredentials: [Data]) async throws -> PasskeyCredential {
// Register a credential and return its ID plus attestation.
fatalError("Implement registration via WebAuthn create() / native API")
}
func checkDomainAssociation() async throws -> DomainAssociation {
return .skipped(reason: "CustomPrfProvider does not verify domain association")
}
}
// Implement PrfProvider for a custom authenticator. Only deriveSeeds and
// isSupported are required.
class CustomPrfProvider : PrfProvider {
override suspend fun deriveSeeds(request: DeriveSeedsRequest): DeriveSeedsOutput {
// Return one 32-byte PRF output per salt, in input order.
TODO("Implement using WebAuthn or native passkey APIs")
}
override suspend fun isSupported(): Boolean {
TODO("Check platform passkey availability")
}
override suspend fun createPasskey(excludeCredentials: List<ByteArray>): PasskeyCredential {
// Register a credential and return its ID plus attestation.
TODO("Implement registration via native passkey API")
}
override suspend fun checkDomainAssociation(): DomainAssociation {
return DomainAssociation.Skipped("CustomPrfProvider does not verify domain association")
}
}
// Implement PrfProvider for a custom authenticator (hardware key, FIDO2,
// file-backed). Only DeriveSeeds and IsSupported are required.
class CustomPrfProvider : PrfProvider
{
public async Task<DeriveSeedsOutput> DeriveSeeds(DeriveSeedsRequest request)
{
// Return one 32-byte PRF output per salt, in input order.
throw new NotImplementedException("Implement using WebAuthn or native passkey APIs");
}
public async Task<bool> IsSupported()
{
throw new NotImplementedException("Check platform passkey availability");
}
public async Task<PasskeyCredential> CreatePasskey(byte[][] excludeCredentials)
{
// Register a credential and return its ID plus attestation.
throw new NotImplementedException("Implement registration via native passkey API");
}
public async Task<DomainAssociation> CheckDomainAssociation()
{
return await Task.FromResult<DomainAssociation>(
new DomainAssociation.Skipped("CustomPrfProvider does not verify domain association"));
}
}
// Implement PrfProvider for a custom authenticator (hardware key, FIDO2,
// file-backed). Only deriveSeeds and isSupported are required.
class CustomPrfProvider {
deriveSeeds = async (
salts: string[]
): Promise<{ seeds: Uint8Array[], credentialId: Uint8Array | null }> => {
// Return one 32-byte PRF output per salt, in input order.
throw new Error('Implement using WebAuthn or native passkey APIs')
}
createPasskey = async (
_excludeCredentials: Uint8Array[]
): Promise<PasskeyCredential> => {
// Register a credential and return its ID plus attestation.
throw new Error('Implement registration via WebAuthn create() / native API')
}
isSupported = async (): Promise<boolean> => {
throw new Error('Check platform passkey availability')
}
}
// Implement PrfProvider for a custom authenticator (hardware key, FIDO2,
// file-backed). Only deriveSeeds and isSupported are required.
class CustomPrfProvider {
deriveSeeds = async (
_request: { salts: string[] }
): Promise<{ seeds: Uint8Array[], credentialId?: Uint8Array }> => {
// Return one 32-byte PRF output per salt, in input order.
throw new Error('Implement using WebAuthn or native passkey APIs')
}
createPasskey = async (
_excludeCredentials: Uint8Array[]
): Promise<PasskeyCredential> => {
// Register a credential and return its ID plus attestation.
throw new Error('Implement registration via native passkey API')
}
isSupported = async (): Promise<boolean> => {
throw new Error('Check platform passkey availability')
}
}
// Implement custom callbacks if the built-in PasskeyProvider doesn't
// fit your needs. Pass them to PasskeyClient.fromCallbacks instead
// of going through PasskeyClientBuilder.withPrfProvider.
Future<DeriveSeedsOutput> deriveSeeds(DeriveSeedsRequest request) async {
// Return one 32-byte PRF output per salt, in input order.
throw UnimplementedError('Implement using platform passkey APIs');
}
Future<PasskeyCredential> createPasskey(List<Uint8List> excludeCredentials) async {
// Register a credential and return its ID plus attestation.
throw UnimplementedError('Implement registration via native passkey API');
}
Future<bool> isSupported() async {
throw UnimplementedError('Check platform passkey availability');
}
# Implement the PrfProvider trait for custom logic if no built-in
# PasskeyProvider ships for your target. Three required methods:
# derive_seeds for derivation, is_supported for the capability probe;
# create_passkey for registration is optional.
class CustomPrfProvider(PrfProvider):
async def derive_seeds(self, request: DeriveSeedsRequest) -> DeriveSeedsOutput:
# Return one 32-byte PRF output per salt, in input order.
raise NotImplementedError("Implement using WebAuthn or native passkey APIs")
async def is_supported(self) -> bool:
raise NotImplementedError("Check platform passkey availability")
async def create_passkey(self, exclude_credentials: list[bytes]) -> PasskeyCredential:
# Register a credential and return its ID plus attestation.
raise NotImplementedError("Implement registration via native passkey API")
async def check_domain_association(self) -> DomainAssociation:
# Optional: verify the app's identity against the platform's
# domain verification source. Custom providers without a
# verification source return SKIPPED, which tells callers
# "proceed with WebAuthn as normal". The UniFFI-generated
# variant classes are reparented to DomainAssociation at
# runtime but mypy can't see that, hence the cast.
return cast(
DomainAssociation,
DomainAssociation.SKIPPED(
reason="CustomPrfProvider does not verify domain association"
),
)
// Implement the PrfProvider interface for a custom authenticator (hardware
// key, FIDO2, file-backed). Only DeriveSeeds and IsSupported are required.
type CustomPrfProvider struct{}
func (p *CustomPrfProvider) DeriveSeeds(
request breez_sdk_spark.DeriveSeedsRequest,
) (breez_sdk_spark.DeriveSeedsOutput, error) {
// Return one 32-byte PRF output per salt, in input order.
panic("Implement using WebAuthn or native passkey APIs")
}
func (p *CustomPrfProvider) IsSupported() (bool, error) {
panic("Check platform passkey availability")
}
func (p *CustomPrfProvider) CreatePasskey(
excludeCredentials [][]byte,
) (breez_sdk_spark.PasskeyCredential, error) {
// Register a credential and return its ID plus attestation.
panic("Implement registration via native passkey API")
}
func (p *CustomPrfProvider) CheckDomainAssociation() (breez_sdk_spark.DomainAssociation, error) {
return breez_sdk_spark.DomainAssociationSkipped{
Reason: "CustomPrfProvider does not verify domain association",
}, nil
}