Skip to content

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

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 a 3D_HIGH threshold 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 norm

If 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

  1. The vector MUST be exactly 231 dimensions in the order: face[128] + depth[32] + light[71]
  2. All values MUST be 32-bit IEEE 754 floats in little-endian byte order on the wire
  3. The rotation matrix MUST be generated using the exact HKDF -> xoshiro256** -> QR pipeline specified
  4. Subspace L2 normalization MUST be applied before concatenation, with dimension 159 exempt per §7.1 (clamped, unscaled)
  5. Full-vector L2 normalization MUST be applied after rotation
  6. 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]
Note: xoshiro256** with all-zero state produces all zeros. Implementations MUST verify that their HKDF output for any valid session key produces a non-zero seed.

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