Namespaces¶
A namespace is an independent mapping between original values and their pseudonyms. Each one fixes, at creation, how its pseudonyms are generated: the generation method, the pseudonym length, an optional prefix and suffix, and an optional regular expression every original value has to match. Namespaces are immutable once created.
Pseudonym generation methods¶
| Method | Pseudonyms |
|---|---|
PSEUDONYM_GENERATION_METHOD_SECURE_RANDOM_BASE64URL_ENCODED |
The default. The first 4 bytes of the SHA-256-hashed host name, the 4 least significant bytes of the millisecond tick count since system start, and pseudonymLength cryptographically secure random bytes, encoded as URL-safe Base64. |
PSEUDONYM_GENERATION_METHOD_FULL_RANDOM_HEX_ENCODED |
A cryptographically secure random string, hex-encoded. |
PSEUDONYM_GENERATION_METHOD_FULL_RANDOM_BASE62_ENCODED |
A cryptographically secure random string, Base62-encoded. |
PSEUDONYM_GENERATION_METHOD_FULL_RANDOM_BASE32_ENCODED |
A cryptographically secure random string, Base32-encoded with the RFC 4648 ยง6 alphabet. |
PSEUDONYM_GENERATION_METHOD_UUID4 |
A version 4 UUID (RFC 4122). |
PSEUDONYM_GENERATION_METHOD_UUID7 |
A version 7 UUID (RFC 9562). |
PSEUDONYM_GENERATION_METHOD_VOPRF |
Derived deterministically from the original value by a separate key-holding service that never sees it. See VOPRF pseudonyms. |
Every method except VOPRF is random: the same original value only maps to the same pseudonym
because vfps stores the pair. The full set of request fields is documented in
namespaces.proto.
Multiple pseudonyms per original value¶
A namespace created with "allowsMultiplePseudonyms": true lets a single Create call store more
than one distinct pseudonym for the same original value, via the request's optional count field
(omitted or 1 preserves the default single-pseudonym behavior for every other namespace):
grpcurl \
-plaintext \
-d '{"name": "multi-psn-example", "pseudonymGenerationMethod": "PSEUDONYM_GENERATION_METHOD_FULL_RANDOM_HEX_ENCODED", "pseudonymLength": 32, "allowsMultiplePseudonyms": true}' \
127.0.0.1:8081 \
vfps.api.v1.NamespaceService/Create
grpcurl \
-plaintext \
-d '{"namespace": "multi-psn-example", "originalValue": "to be pseudonymized", "count": 3}' \
127.0.0.1:8081 \
vfps.api.v1.PseudonymService/Create
The response's pseudonyms array holds the full set (pseudonym is kept, populated with the
first one, for callers that only read a single value). The stored set for a given original value
only ever grows: calling Create again with a count at or below what's already stored returns
the existing set unchanged, while a larger count adds exactly the missing pseudonyms, up to a
maximum count of 10,000. CSV pseudonymization jobs and the FHIR $create-pseudonym operation
don't support requesting more than one pseudonym - both always operate on the first
(sequenceNumber: 0) pseudonym for a multi-psn namespace.
Multi-level namespaces¶
Namespaces can form a hierarchy: a namespace created with a parentName is a child namespace,
whose original values are pseudonym values produced by its parent. This is how multiple levels of
pseudonymization are built - e.g. an MPI is pseudonymized in a root namespace, and that pseudonym
is then re-pseudonymized in a per-study child namespace, so a study never sees a value that
resolves directly to the MPI.
Each namespace keeps its own generation configuration; nothing is inherited from the parent.
Setting parentValidationMode to PARENT_VALIDATION_MODE_ENSURE_EXISTS additionally requires
every original value to already exist as a pseudonym in the parent namespace, rejecting anything
else with a FAILED_PRECONDITION error:
grpcurl \
-plaintext \
-d '{"name": "study-a", "pseudonymGenerationMethod": "PSEUDONYM_GENERATION_METHOD_FULL_RANDOM_HEX_ENCODED", "pseudonymLength": 32, "parentName": "test", "parentValidationMode": "PARENT_VALIDATION_MODE_ENSURE_EXISTS"}' \
127.0.0.1:8081 \
vfps.api.v1.NamespaceService/Create
Chaining a pseudonym into the child namespace is an ordinary Create call whose originalValue
is the parent's pseudonym value. A namespace's direct children can be listed with ListChildren
(GET /v1/namespaces/{name}/children), which is deliberately non-recursive - call it once per
level to walk a whole tree.
The parent link is set at creation and can't be changed afterwards, like every other namespace field, which also makes hierarchy cycles impossible. A namespace that still has children can't be deleted; delete the children first. Deleting a pseudonym level doesn't cascade to any other level.