Kyndex
Guides

Deriving The Login Bucket

How to derive login_bidx using the OPRF challenge endpoint — the prerequisite step before every login and registration.

Zero-Knowledge Guarantee

Your email address never reaches the server in any form during this step. Your device blinds it cryptographically before transmission. The server evaluates a transformed point and returns a result — without learning the input. You unblind the result locally to derive your login bucket. Neither party learns the other's secret.

What This Step Is For

Every login and registration requires a login_bidx — a 13-bit integer (0–8191) that tells the server which bucket of candidate records to return. Deriving it requires a single round-trip to POST /v1/auth/challenges.

This step happens before any OPAQUE handshake. You cannot call authenticate-start or register-start without a valid login_bidx.

Overview

StepWhereWhat Happens
1. NormalizeYour deviceTrim whitespace, apply Unicode NFC, lowercase
2. BlindYour deviceHash email to a curve point, multiply by a random scalar
3. EvaluateServerMultiply blinded point by server's secret key
4. UnblindYour deviceRemove the blinding scalar, hash the result, truncate to 13 bits

The server sees only a random-looking curve point. You see only the server's transformed result. The original email and the server's key remain private to their respective owners throughout.

Step 1 — Normalize Your Email

Before blinding, normalize the email so the same address always produces the same bucket regardless of how it was typed:

  • Trim leading and trailing whitespace
  • Apply Unicode NFC normalization
  • Convert to lowercase

This matches the normalization applied server-side for consistency. Do not strip + tags or Gmail dots — those are provider-specific and are preserved.

Step 2 — Blind The Email

Hash the normalized email to a point on the ristretto255 group using hash-to-curve (RFC 9380, XMD:SHA-512) with the domain separation tag "kyndex-oprf-v1". Then multiply that point by a random scalar r:

blinded_element = r * hash_to_curve(normalized_email)

r is your blinding scalar — keep it in memory. It is never sent anywhere. Without it, the server's response cannot be unblinded.

Encode the resulting 32-byte point as standard base64 for transmission.

Step 3 — Call The Challenge Endpoint

curl -X POST https://api.kyndex.co/v1/auth/challenges \
  -H 'Content-Type: application/json' \
  -d '{
    "blinded_element": "<base64-32-byte-ristretto255-point>"
  }'

No authentication is required. The endpoint is rate-limited — repeated failures will result in 429 Too Many Requests.

Request body:

FieldTypeDescription
blinded_elementstringBase64-encoded 32-byte ristretto255 point (your blinded email)

Response (200 OK):

{
  "evaluated_element": "<base64-32-byte-ristretto255-point>"
}
FieldDescription
evaluated_elementBase64-encoded 32-byte ristretto255 point (server's OPRF evaluation)

Step 4 — Unblind And Derive The Login Bucket

Decode evaluated_element from base64. Remove your blinding scalar by multiplying by its modular inverse:

unblinded = (1/r) * evaluated_element

Then finalize to produce login_bidx:

digest     = SHA-256(unblinded_bytes || "kyndex-oprf-finalize-v1")
login_bidx = (digest[0] | (digest[1] << 8)) & 0x1FFF

Take the first two bytes of the SHA-256 digest as a little-endian 16-bit integer, then mask to 13 bits. The result is an integer in [0, 8191] — your login_bidx.

Pass this value as the login_bidx field in register-start or authenticate-start.

Error Handling

StatusMeaningWhat To Do
400Invalid blinded elementThe submitted point is not a valid ristretto255 point. Check your encoding and curve arithmetic.
429Rate limitedBack off and retry. This endpoint enforces strict rate limits.
500Server errorRetry with exponential backoff.

Invalid points always return 400 — the server never returns 422 or 500 for a malformed point. This is intentional: a response that distinguishes valid from invalid curve points would act as an oracle. If you receive a 400, the issue is in your point encoding, not the server.

Security Properties

The server cannot learn your email. The blinding scalar r transforms your email hash into a point indistinguishable from random. Without r, the server's evaluation reveals nothing about the input.

The server observes the bucket number, not the email. The login_bidx value is sent in the clear to authenticate-start. The server uses it to return a padded set of candidate records — the response always contains the same number of entries regardless of how many real records exist in that bucket. The bucket number alone reveals nothing about the email that produced it.

Your device cannot learn the server's key. The server multiplies by its secret scalar without exposing it. You receive only the transformed output.

The login bucket is non-reversible. The finalization step (SHA-256 with a domain separator, truncated to 13 bits) destroys any residual information about the unblinded point.

Cross-protocol separation. The domain separation tag "kyndex-oprf-v1" ensures that the email OPRF output is cryptographically distinct from any other use of the same curve in the platform.

Think of it like a sealed envelope through a stamping machine. Your device seals the envelope (blinding). The server stamps it through the slot without opening it (OPRF evaluation). Your device opens the envelope and reads the stamp (unblinding). The server stamped something — but never read what was inside.

  • Authentication — registration and login flows that consume login_bidx
  • Zero-Knowledge Model — why the server is structured to never see your credentials
  • Blind Routing — how other identifiers are similarly protected throughout the platform

On this page