Vero Entangled Vector Specification¶
Version: 1.0.0 Status: Normative Scope: All platforms (iOS, Android, Web)
This document defines the exact dimensionality, layout, fusion order, rotation algorithm, and normalization procedures for the Vero entangled latent identity vector. Two independent implementations following this specification MUST produce vectors that are directly comparable via cosine similarity.
1. Overview¶
The Vero CNN encoder produces an entangled latent identity vector that fuses three distinct feature subspaces into a single vector. This vector encodes the presenter's facial geometry, depth information, and the reflected light signal from the session-specific color sequence. The entanglement ensures that the vector is useful only for the session in which it was generated.
2. Vector Dimensions¶
2.1 Total Dimensionality¶
| Component | Dimensions | Range (indices) | Description |
|---|---|---|---|
| Face Embedding | 128 | [0, 127] | ArcFace-derived facial identity features |
| Depth Features | 32 | [128, 159] | 3D geometry and depth map features |
| Light Features | 71 | [160, 230] | Session-specific light response features |
| Total | 231 | [0, 230] |
The canonical vector is a 231-dimensional array of 32-bit IEEE 754 floating-point values.
2.2 Wire Format¶
Byte offset 0: float32[0] = face_dim_0
Byte offset 4: float32[1] = face_dim_1
...
Byte offset 508: float32[127] = face_dim_127
Byte offset 512: float32[128] = depth_dim_0
...
Byte offset 636: float32[159] = depth_dim_31
Byte offset 640: float32[160] = light_dim_0
...
Byte offset 920: float32[230] = light_dim_70
Total size: 231 * 4 = 924 bytes
Byte order: little-endian (matching IEEE 754 convention on ARM and x86).
3. Face Embedding Subspace (Dimensions 0-127)¶
3.1 Architecture¶
The face embedding is produced by an ArcFace-compatible encoder backbone. The reference architecture is based on MobileFaceNet or ResNet-50, pre-trained on VGGFace2, with early-layer modifications to preserve color and illumination detail.
3.2 Semantic Layout¶
| Dimension Range | Cluster | Description |
|---|---|---|
| [0, 31] | Core Identity Features | Primary facial geometry ratios, key landmark relationships, fundamental bone structure markers |
| [32, 63] | Biometric Signatures | Unique identifying characteristics, asymmetry patterns, fine-grained geometric features |
| [64, 95] | Environmental Context | Lighting adaptation, pose and angle compensation, session-specific parameters |
| [96, 127] | Authentication Markers | Session key integration features, light pattern response characteristics, temporal synchronization data |
3.3 Training Loss¶
The face embedding is trained with ArcFace additive angular margin loss:
L_identity = -log(exp(s * cos(theta_yi + m)) / (exp(s * cos(theta_yi + m)) + sum_j!=yi(exp(s * cos(theta_j)))))
Where:
- s = 64.0 (feature scale)
- m = 0.5 (angular margin)
- theta_yi = angle between the feature vector and the weight vector of the ground-truth class
3.4 Input¶
A sequence of T video frames I_t (t = 1..T) capturing the face illuminated by the time-varying blink signal S_k. Each frame provides color, spatial, and temporal information.
4. Depth Feature Subspace (Dimensions 128-159)¶
4.1 Sources¶
- 3D mode (Presenter): Extracted from TrueDepth / structured-light depth sensor, or multi-frame parallax estimation. Contains geometric surface normals, curvature estimates, and volumetric features.
- 2D mode (Verifier): Estimated from monocular depth prediction using the CNN's learned depth head. Lower fidelity but sufficient for comparison.
4.2 Feature Content¶
| Dimension Range | Feature Type |
|---|---|
| [128, 135] | Facial surface normals (8D) |
| [136, 143] | Curvature and depth gradients (8D) |
| [144, 151] | Inter-landmark depth ratios (8D) |
| [152, 159] | Volumetric/contour features (8D) |
4.3 Dimensional Verification Flag¶
Dimension 159 (the last depth dimension) encodes a 3D confidence score in the range [0.0, 1.0]:
- Values > 0.7 indicate high confidence the vector originated from a genuine 3D scan
- Values < 0.3 indicate the vector was likely derived from a 2D source
- The verifier uses this value as an input to the authentication decision logic (see comparison-spec.md)
The score MUST be a measurement of the scene, not of the sensor.
(Revision 2026-08.) The reference producer fits a least-squares plane to
the depth samples at the 68 face landmarks and scores the RMS residual
(the scene's relief) against a reference relief: a real face carries
centimetres of structure a plane cannot absorb, while a photograph or
screen is planar to roughly a millimetre — tilting a flat spoof only
tilts the fitted plane. Sensor metadata (e.g. AVDepthData.accuracy)
MUST NOT be used as the score: a photo held to a working sensor inherits
the sensor's full accuracy claim. Producers without real depth data MUST
report at most 0.2. The score survives the wire exactly — see §7.1 for
the normalization exemption and the recovery formula.
The thresholds above apply to the RECOVERED value. Threshold calibration
for the verifier-side flatness gate is tracked in
docs/FIELD_TEST_CHECKLIST.md section F; the pre-revision field numbers
(genuine 0.076–0.136) were artifacts of the double normalization and must
not be reused.
5. Light Feature Subspace (Dimensions 160-230)¶
5.1 Purpose¶
The light features encode the precise timing, color, and intensity of the reflected session-key light sequence as captured on the presenter's face during the scan. This subspace provides the entanglement between facial identity and the ephemeral session key.
5.2 Feature Layout¶
| Dimension Range | Feature Type | Count |
|---|---|---|
| [160, 179] | Per-pulse color response (one per bit, max 20) | 20 |
| [180, 199] | Per-pulse timing offsets (one per bit, max 20) | 20 |
| [200, 219] | Spectral reflection coefficients | 20 |
| [220, 225] | Aggregate temporal statistics | 6 |
| [226, 228] | Color channel dominance ratios (R/G/B) | 3 |
| [229, 230] | Sequence fidelity and temporal consistency scores | 2 |
5.3 Blink Reconstruction¶
An auxiliary decoder head D is trained to reconstruct the original blink sequence from the light feature subspace:
L_blink = ||D(z_light) - k_target||^2
Where k_target is the session key encoded as a vector of target signal values.
5.4 Variable-Length Key Handling¶
The light feature subspace supports session keys up to 20 bits (5 hex characters at 1-bit mode). For shorter keys, unused per-pulse dimensions are zero-filled. For longer keys (up to 64 bits / 16 hex chars), the per-pulse features encode aggregated multi-pulse statistics rather than individual pulse data.
6. Session-Key Vector Rotation¶
To ensure that vectors from different sessions are not directly comparable (preventing cross-session correlation attacks), each vector is rotated using a session-specific orthogonal transformation derived from the session key.
6.1 Rotation Algorithm¶
function rotateVector(rawVector: float32[231], sessionKey: bytes) -> float32[231]:
// sessionKey bytes are the UTF-8 encoding of the LOWERCASE hex
// session-key string itself (NOT nibble-decoded — session keys may be
// odd-length, e.g. the default 5 hex chars). All platforms MUST use
// this rule or derived rotation matrices will not match.
// Step 1: Derive PRNG seed from session key using HKDF
seed = HKDF-SHA256(
ikm = sessionKey,
salt = UTF8("vero-vector-rotation-v1"),
info = UTF8("xoshiro256-seed"),
length = 32 // 256 bits = 4 x uint64 state
)
// Step 2: Initialize xoshiro256** PRNG with the seed
state = [
littleEndianUint64(seed[0:8]),
littleEndianUint64(seed[8:16]),
littleEndianUint64(seed[16:24]),
littleEndianUint64(seed[24:32])
]
prng = Xoshiro256StarStar(state)
// Step 3: Generate a random 231x231 matrix.
// NORMATIVE FILL ORDER: COLUMN-MAJOR. The PRNG fills column by column
// (outer loop = column, inner loop = row): the k-th draw lands at
// M[k mod 231][k div 231]. Both reference implementations (Swift and TS)
// fill this way; a row-major reading derives the TRANSPOSE, whose QR is a
// completely different orthogonal matrix (QR(M) != QR(M^T)) and fails 100%
// of comparisons. See PYR-9162.
M = float64[231][231]
for col in 0..230:
for row in 0..230:
// Generate uniform random float64 in (-1, 1) from PRNG
M[row][col] = prng.nextDouble() * 2.0 - 1.0
// Step 4: QR decomposition to obtain orthogonal matrix
Q, R = QRDecomposition(M)
// Step 5: Canonicalize Q so both platforms derive the SAME matrix.
// Multiply each column i of Q by the sign of R[i][i], making R's diagonal
// non-negative (the unique/canonical Householder QR). Note this does NOT
// guarantee det(Q) = +1 — det(Q) may be +/-1; that is fine, because
// de-rotation uses Q^T (Step 6.4) which inverts either sign.
for i in 0..230:
if R[i][i] < 0:
for j in 0..230:
Q[j][i] = -Q[j][i]
// Step 6: Apply rotation
rotatedVector = Q * rawVector // Matrix-vector multiply
return float32(rotatedVector) // Downcast to float32
6.2 xoshiro256** PRNG Specification¶
The PRNG used is xoshiro256** (xoshiro256 star-star), chosen for its statistical quality, speed, and deterministic reproducibility across platforms.
State: 4 x uint64 (256 bits total)
Next value generation:
function xoshiro256StarStar_next(state: uint64[4]) -> uint64:
result = rotateLeft(state[1] * 5, 7) * 9
t = state[1] << 17
state[2] ^= state[0]
state[3] ^= state[1]
state[1] ^= state[2]
state[0] ^= state[3]
state[2] ^= t
state[3] = rotateLeft(state[3], 45)
return result
Converting to double [0, 1):
function nextDouble(state) -> float64:
return (xoshiro256StarStar_next(state) >> 11) * 0x1.0p-53
6.3 QR Decomposition¶
Implementations MUST use Householder-reflection-based QR decomposition (not Gram-Schmidt, which is numerically unstable). The decomposition MUST be performed in float64 precision to maintain orthogonality.
6.4 De-rotation¶
The verifier, who also knows the session key, generates the same orthogonal matrix Q and applies the inverse (transpose, since Q is orthogonal):
originalVector = Q^T * rotatedVector
Both the presenter and verifier MUST apply the same rotation/de-rotation for comparison to work.
7. Normalization¶
7.1 Subspace Normalization¶
Before fusion, each subspace is independently L2-normalized:
function l2Normalize(v: float32[N]) -> float32[N]:
norm = sqrt(sum(v[i]^2 for i in 0..N-1))
if norm < 1e-10:
return v // avoid division by zero
return [v[i] / norm for i in 0..N-1]
Apply to:
- face_embedding[0:128] -> L2-normalized
- depth_features[0:31] -> L2-normalized (the 31 depth FEATURES only)
- depth_features[31] -> NOT normalized. The §4.3 confidence score is
clamped to [0.0, 1.0] and appended unscaled between the normalized depth
features and the light subspace.
- light_features[0:71] -> L2-normalized
Revision 2026-08 — why the confidence is exempt. The original scheme normalized
z_depth[31]twice: once by the raw depth-feature norm‖d‖(this section) and once by the full-vector norm (§7.2).‖d‖is not transmitted and is destroyed by normalization, so the [0,1] semantic of §4.3 could not be recovered by the verifier — genuine scans measured 0.076–0.095 against a3D_HIGHthreshold of 0.70 that was mathematically unreachable. With the confidence exempt from subspace normalization, the full-vector normalization scales it by exactly the same factor as every unit-norm subspace, and the verifier recovers it exactly:c = derotated[159] / l2Norm(derotated[0:128]) // face-subspace normIf the face subspace is degenerate (norm < 1e-3 — a populated subspace always reads ≥ 0.5; anything smaller is numerical dust from float32 rotation), fall back to the light-subspace norm; if both are degenerate the confidence is defined as 0. The 1e-3 threshold is normative: float32 implementations return ~1e-6 of noise for a zero subspace after rotate/de-rotate, and dividing by that noise misreads as full confidence.
7.2 Fusion-Level Normalization¶
After concatenation into the 231D vector and after session-key rotation, the full vector is L2-normalized a final time:
finalVector = l2Normalize(rotatedVector)
This ensures all vectors lie on the unit hypersphere, making cosine similarity equivalent to the dot product.
8. Conformance Requirements¶
- The vector MUST be exactly 231 dimensions in the order: face[128] + depth[32] + light[71]
- All values MUST be 32-bit IEEE 754 floats in little-endian byte order on the wire
- The rotation matrix MUST be generated using the exact HKDF -> xoshiro256** -> QR pipeline specified
- Subspace L2 normalization MUST be applied before concatenation, with dimension 159 exempt per §7.1 (clamped, unscaled)
- Full-vector L2 normalization MUST be applied after rotation
- The final vector MUST have unit L2 norm (within float32 precision tolerance of 1e-6)
9. Test Vectors¶
9.1 PRNG Determinism Test¶
Given HKDF output (seed) of 32 zero bytes:
state = [0, 0, 0, 0]
HKDF test:
- IKM: 0x48656C6C6F ("Hello")
- Salt: UTF8("vero-vector-rotation-v1")
- Info: UTF8("xoshiro256-seed")
- Length: 32
Implementations MUST verify that two independent platforms produce the identical 32-byte seed for this input.
9.2 Normalization Test¶
Given raw subspace [3.0, 4.0]:
norm = sqrt(9 + 16) = 5.0
normalized = [0.6, 0.8]
9.3 Rotation Reversibility Test¶
For any session key k and any vector v:
Q = generateRotationMatrix(k)
rotated = Q * v
recovered = Q^T * rotated
assert ||v - recovered|| < 1e-5