Specification Version 0.1.0
This document specifies an algorithm for bidirectional encoding of a non-negative 64-bit integer identifier and an optional 32-bit integer namespace into a valid Universally Unique Identifier (UUID) as defined by RFC 9562 [RFC9562], using the Version 8 (custom) format. The encoding is deterministic, reversible, and produces non-sequential UUIDs that obscure the underlying integer values while guaranteeing uniqueness within a given namespace.
Many applications use auto-incrementing integers as primary keys in relational databases. Exposing these identifiers directly in public APIs or URLs reveals information about the system: the total number of records, the rate of creation, and the ability to enumerate all records by iterating through sequential values. A common mitigation is to present a UUID as a proxy identifier in public-facing contexts.
This specification defines an algorithm that converts an integer identifier and an optional namespace integer into a UUID at runtime, without requiring the UUID to be stored, indexed, or otherwise persisted alongside the original integer. The conversion is fully reversible: given the UUID, the original integer values can be recovered. Within a given namespace, each integer produces a unique UUID, and each valid UUID decodes to exactly one integer pair.
The algorithm uses the UUID Version 8 format defined in RFC 9562 [RFC9562], Section 5.8, which is designated for experimental or vendor-specific use. The resulting UUIDs are structurally valid and will be recognized as Version 8 by any compliant UUID parser.
The algorithm is designed to satisfy the following properties:
(a) Determinism: The same (id, namespace) pair MUST always produce the same UUID.
(b) Reversibility: Any UUID produced by this algorithm MUST be decodable back to the original (id, namespace) pair.
(c) Uniqueness: Within a given namespace, distinct id values MUST produce distinct UUIDs. Across namespaces, distinct namespace values for the same id MUST produce distinct UUIDs.
(d) Non-sequentiality: Consecutive id values (e.g., 1, 2, 3) SHOULD produce UUIDs that appear unrelated to a casual observer.
(e) Integrity checking: The algorithm MUST detect and reject UUIDs that were not produced by this algorithm, even if they are otherwise valid Version 8 UUIDs.
This algorithm does NOT provide:
-
Encryption. The integer values are encoded, not encrypted. A motivated party with knowledge of this specification can recover the original values from any UUID produced by the algorithm.
-
Cryptographic integrity. The checksum seed provides error detection, not tamper resistance.
-
Universal uniqueness. UUIDs produced by this algorithm are unique only within the scope of this encoding scheme. They are not globally unique across UUID generation methods.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
The following terms are used throughout this specification:
id: A non-negative integer to be encoded. Referred to as the "identifier" or "id value".
namespace: A non-negative 32-bit integer that partitions the id space. Defaults to 0 when not specified.
seed: A 4-byte value derived from the hash of the packed id and namespace, with UUID Version and Variant bits embedded. Serves as both an encoding parameter and a checksum for decoding validation.
packed: The big-endian byte representation of an integer value.
XOR: The bitwise exclusive-or operation applied byte-by-byte to two byte strings of equal length.
||: Concatenation of byte strings.
The UUID format was originally standardized in RFC 4122 [RFC4122], published in July 2005. RFC 4122 defined UUID versions 1 through 5 and established the variant and version field layout that remains in use today.
In May 2024, RFC 9562 [RFC9562] was published as a Standards Track document that obsoletes RFC 4122. RFC 9562 retains full backward compatibility with RFC 4122 and adds three new UUID versions:
Version 6: Reordered Gregorian time-based (Section 5.6) Version 7: Unix Epoch time-based (Section 5.7) Version 8: Custom, vendor-specific (Section 5.8)
Many existing UUID libraries and tools still reference RFC 4122 in their documentation and APIs. This does not affect interoperability: the binary format, variant field, and version field semantics are identical between the two RFCs. UUID libraries that predate RFC 9562 will correctly parse the variant and version fields of a Version 8 UUID, though they may report the version as "unknown" if they were written before Version 8 was standardized.
Implementors SHOULD reference RFC 9562 as the authoritative specification for UUID format and semantics.
RFC 9562, Section 5.8 defines Version 8 as follows:
UUIDv8 provides an RFC-compatible format for experimental or vendor-specific use cases. The only requirement is that the variant and version bits are set as specified in Sections 4.1 and 4.2 of RFC 9562.
The 128-bit UUID contains 6 bits reserved for the version (4 bits) and variant (2 bits) fields. The remaining 122 bits are available for application-defined content. This specification uses those 122 bits to encode the id, namespace, and seed values.
Per RFC 9562, Section 4:
Version field: The 4 most significant bits of octet 6 (bits 48-51 of the UUID). For Version 8, these bits MUST be 0b1000 (decimal 8).
Variant field: The 2 most significant bits of octet 8 (bits 64-65 of the UUID). For the RFC 9562 variant, these bits MUST be 0b10.
In the standard UUID string representation, the version appears as the first hex digit of the third group, and the variant appears as the first hex digit of the fourth group (constrained to 8, 9, a, or b):
xxxxxxxx-xxxx-Vxxx-Nxxx-xxxxxxxxxxxx ^ ^ | variant (8/9/a/b) version (8)
The id parameter is a non-negative integer with the following constraints:
Minimum value: 0 Maximum value: 9,223,372,036,854,775,807 (2^63 - 1)
The maximum corresponds to the largest value representable by a signed 64-bit integer, as used by the reference implementation's host language (PHP). Implementations in languages with native unsigned 64-bit integers MAY choose to support the full range of 0 to 2^64 - 1, but MUST support at least the range specified above for interoperability with the reference implementation.
The id is packed as an unsigned 64-bit big-endian integer for all internal operations, regardless of the host language's native integer representation.
The namespace parameter is a non-negative integer with the following constraints:
Minimum value: 0 Maximum value: 4,294,967,295 (2^32 - 1)
When not specified, the namespace defaults to 0. The namespace is packed as an unsigned 32-bit big-endian integer.
All hashing in this specification uses the XXH3 algorithm, the 64-bit variant of xxHash version 3, as defined by the xxHash reference implementation [XXHASH].
Input: An arbitrary-length byte string Output: A 64-bit digest value Seed: Default (0) Secret: None (default)
The 64-bit hash digest MUST be serialized as an 8-byte string in big-endian (network) byte order before use in any byte-level operation defined by this specification.
This is a critical interoperability requirement. Different language bindings for xxHash may return the digest as a native integer, a byte array in platform-native order, or a hexadecimal string. Implementations MUST ensure that the 8-byte representation matches the big-endian serialization of the 64-bit integer digest.
Implementations SHOULD verify their XXH3 configuration by checking:
XXH3_64bits("hello, world") = 0x302cd5fba73d006c
As an 8-byte big-endian string: 30 2c d5 fb a7 3d 00 6c
If the computed value does not match, the implementation has a byte order or algorithm configuration error and will not produce compatible output.
Throughout this specification, the notation hash(x) refers to the XXH3 function applied to byte string x, with the result serialized as 8 bytes in big-endian order.
All integer-to-byte conversions in this specification use unsigned big-endian (network byte order) encoding:
pack_u64(value): Encode a 64-bit unsigned integer as 8 bytes in big-endian order.
pack_u32(value): Encode a 32-bit unsigned integer as 4 bytes in big-endian order.
unpack_u64(bytes): Decode 8 big-endian bytes as a 64-bit unsigned integer.
unpack_u32(bytes): Decode 4 big-endian bytes as a 32-bit unsigned integer.
For reference, the PHP pack format codes are 'J' (unsigned 64-bit big-endian) and 'N' (unsigned 32-bit big-endian).
The standard UUID bit layout per RFC 9562, Section 4:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time_low |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time_mid | time_hi_and_version |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|clk_seq_hi_res | clk_seq_low | node (0-1) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| node (2-5) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note: The field names time_low, time_mid, etc. are inherited from UUID Version 1 and do not carry temporal semantics in Version 8. They are used here solely to identify octet positions within the UUID.
This specification maps the encoded data onto the 16-byte UUID as follows:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| encoded namespace (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| encoded id [0..1] (16) | seed [0..1] (16) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| seed [2..3] (16) | encoded id [2..3] (16) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| encoded id [4..7] (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Byte positions within the 16-byte UUID:
Octets 0-3: Encoded namespace (4 bytes) Octets 4-5: Encoded id, bytes 0-1 (2 bytes) Octets 6-9: Seed (4 bytes, contains Version and Variant bits) Octets 10-15: Encoded id, bytes 2-7 (6 bytes)
The seed occupies octets 6-9, which span the time_hi_and_version, clock_seq_hi_and_reserved, and clock_seq_low fields of the standard UUID layout. This is the region that contains the Version and Variant bits, which the seed computation embeds via bitmask (see Section 8.2).
encode(id: uint64, namespace: uint32 = 0) -> UUID
Implementations MUST validate input parameters against the ranges defined in Section 4 before proceeding.
The seed is a 4-byte value that serves two purposes: it contributes deterministic pseudo-randomness to the XOR encoding steps, and it acts as a checksum during decoding to verify that a UUID was produced by this algorithm.
compute_seed(packed_id: bytes[8], packed_ns: bytes[4]) -> bytes[4]
Step 1: Compute the hash of the concatenated packed values:
full_hash = hash(packed_id || packed_ns)
This produces an 8-byte hash digest.
Step 2: Extract the first 4 bytes as a 32-bit unsigned integer:
seed_int = unpack_u32(full_hash[0..3])
Step 3: Apply the Version and Variant bitmask:
seed_int = (seed_int AND 0x0FFF3FFF) OR 0x80008000
The AND mask 0x0FFF3FFF clears 6 bits at positions
corresponding to the UUID Version and Variant fields.
The OR mask 0x80008000 sets those bits to the required
values:
Bits 31-28: Set to 1000 (Version 8)
Bits 15-14: Set to 10 (Variant, RFC 9562)
This leaves 26 bits of hash-derived content in the seed.
Step 4: Pack the masked integer back to 4 bytes:
seed = pack_u32(seed_int)
Note on operator precedence: The expression uses bitwise AND before bitwise OR. Implementations SHOULD use explicit parentheses for clarity, regardless of the host language's operator precedence rules.
Given input values id (uint64) and namespace (uint32):
Step 1: Pack input integers to byte strings.
id_bytes = pack_u64(id) // 8 bytes
ns_bytes = pack_u32(namespace) // 4 bytes
Step 2: Compute the seed from the packed input values.
seed = compute_seed(id_bytes, ns_bytes) // 4 bytes
Step 3: Encode the id.
XOR the packed id with the hash of the concatenated
namespace and seed:
id_bytes = id_bytes XOR hash(ns_bytes || seed)
Both operands are 8 bytes; this is a full-length XOR.
Step 4: Encode the namespace.
XOR the packed namespace with the first 4 bytes of the
hash of the seed:
ns_bytes = ns_bytes XOR hash(seed)[0..3]
The hash output is 8 bytes but the namespace is only 4
bytes. Implementations MUST truncate (slice) the hash
output to 4 bytes before applying XOR. Only the first 4
bytes of the hash are used; the remaining 4 bytes are
discarded.
Note: In the PHP reference implementation, this truncation
occurs implicitly because the XOR operator on strings of
unequal length produces a result with the length of the
shorter operand. Other languages MUST perform explicit
truncation.
Step 5: Assemble the 16-byte UUID.
Concatenate the encoded components in the order defined
by Section 7.2:
uuid_bytes = ns_bytes
|| id_bytes[0..1]
|| seed
|| id_bytes[2..7]
Step 6: Format as a UUID string.
Encode the 16 bytes as 32 lowercase hexadecimal characters
and insert hyphens at the standard positions to produce
the 8-4-4-4-12 grouping:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
decode(uuid: string) -> (id: uint64, namespace: uint32)
Before decoding, the UUID string MUST be validated as described in Section 10. If validation fails, implementations MUST raise an error.
Given a validated UUID string:
Step 1: Parse the UUID string to a 16-byte array.
Strip hyphens and decode the 32 hexadecimal characters to
16 bytes.
Step 2: Extract the encoded components.
Using the layout defined in Section 7.2:
seed = uuid_bytes[6..9] // 4 bytes
enc_ns = uuid_bytes[0..3] // 4 bytes
enc_id = uuid_bytes[4..5] || uuid_bytes[10..15] // 8 bytes
Step 3: Decode the namespace.
ns_bytes = enc_ns XOR hash(seed)[0..3]
As in encoding, only the first 4 bytes of the 8-byte
hash output are used.
Step 4: Decode the id.
id_bytes = enc_id XOR hash(ns_bytes || seed)
Step 5: Verify the checksum.
Recompute the seed from the decoded byte strings:
check_seed = compute_seed(id_bytes, ns_bytes)
If check_seed does not equal seed (byte-for-byte), the
UUID does not contain data encoded by this algorithm.
Implementations MUST raise an error.
Step 6: Unpack the integer values.
id = unpack_u64(id_bytes)
namespace = unpack_u32(ns_bytes)
Return the decoded id and namespace.
Before decoding, implementations MUST verify that the UUID string conforms to the RFC 9562 Version 8 format. The following regular expression validates the required structure:
/^[0-9a-f]{8}-[0-9a-f]{4}-8[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
This expression checks:
(a) Correct hyphenated grouping: 8-4-4-4-12 hexadecimal characters. (b) Lowercase hexadecimal only (a-f, not A-F). (c) Version nibble (position 13) is '8'. (d) Variant nibble (position 17) is one of '8', '9', 'a', or 'b'.
Implementations MAY choose to accept uppercase input by normalizing to lowercase before validation.
Even when the format validation succeeds, the UUID may be a valid Version 8 UUID that was not produced by this algorithm. The seed checksum verification in Step 5 of Section 9.3 detects this condition.
Conforming implementations MUST raise errors for the following conditions:
(a) Invalid id range: id < 0 or id > 9,223,372,036,854,775,807.
(b) Invalid namespace range: namespace < 0 or namespace > 4,294,967,295.
(c) Invalid UUID format: The input string does not match the Version 8 validation pattern defined in Section 10.1.
(d) Checksum mismatch: The seed extracted from the UUID does not match the seed recomputed from the decoded id and namespace byte strings. This indicates the UUID was not produced by this algorithm.
The specific error types and messages are left to the implementation.
This document has no IANA actions.
This algorithm provides encoding, not encryption. The XOR-based obfuscation and the deterministic seed provide no cryptographic security. Any party with knowledge of this specification and the xxHash algorithm can decode any UUID produced by this algorithm.
The algorithm is intended to mitigate casual user-enumeration attacks (e.g., incrementing an ID in a URL to discover other records) but does not protect against a determined adversary.
XXH3 is a non-cryptographic hash function optimized for speed. It does not provide collision resistance, preimage resistance, or any other property required of cryptographic hash functions. Its use here is appropriate because the algorithm requires only determinism and good distribution, not cryptographic strength.
The UUID does not directly reveal the encoded integers, but the encoding is deterministic: the same input always produces the same output. An attacker who can observe multiple UUIDs and who knows the namespace in use can attempt to brute-force the id values, which is computationally trivial for small id spaces.
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, https://www.rfc-editor.org/rfc/rfc2119.
[RFC8174] Leite, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, https://www.rfc-editor.org/rfc/rfc8174.
[RFC9562] Davis, K., Peabody, B., and P. Leach, "Universally Unique IDentifiers (UUIDs)", RFC 9562, DOI 10.17487/RFC9562, May 2024, https://www.rfc-editor.org/rfc/rfc9562.
[XXHASH] Collet, Y., "xxHash - Extremely fast non-cryptographic hash algorithm", https://github.com/Cyan4973/xxHash.
[RFC4122] Leach, P., Mealling, M., and R. Salz, "A Universally Unique IDentifier (UUID) URN Namespace", RFC 4122, DOI 10.17487/RFC4122, July 2005, https://www.rfc-editor.org/rfc/rfc4122.
Note: RFC 4122 has been obsoleted by RFC 9562 [RFC9562].
It is referenced here because many existing UUID
libraries and tools continue to cite RFC 4122 in their
documentation and source code. The binary UUID format,
version field, and variant field are identical between
the two specifications. UUID Version 8, used by this
algorithm, was introduced in RFC 9562.
[REFIMPL] WickedByte, "int-to-uuid: Integer ID To RFC 9562 UUID Converter", PHP, https://github.com/wickedbyte/int-to-uuid.
The following test vectors are generated by the PHP reference implementation [REFIMPL] and MUST be reproduced exactly by all conforming implementations. Each entry consists of an id value, a namespace value, and the expected UUID string.
id=0, namespace=0: 5fec6701-fc03-8499-8f91-4168936d858f
id=1, namespace=0: 764bb373-b10f-8f8a-b66c-3e7930926aed
id=0, namespace=1: b06454be-e102-87f8-a348-d80b99568ff2
id=1, namespace=1: 22cdb468-b863-86d6-afb9-481e9df6cd53
id=2, namespace=0: d3162c0a-1529-89ed-a19f-0793f955d3da
id=10, namespace=0: e73a6fb1-0bbf-8d27-8fae-23961165b446
id=42, namespace=0: 99c45a05-a33b-8544-8024-f4be69401069
id=100, namespace=0: 05f7c1b1-f4c6-8779-8dfa-68ea5169967a
id=255, namespace=0: b42736ec-ecbe-808b-b44d-443e30d92215
id=256, namespace=0: 422d289a-e2d9-8464-826f-9632bed9cf80
id=1000, namespace=0: 51763062-68bf-8c4c-8c2e-b94bc694ad81
id=65535, namespace=0: 2d69cd93-4d99-8e94-9c1a-046c899c24d1
id=1, namespace=2: 4c0e091e-3123-8646-9a5c-da6c0a4d0534
id=1, namespace=100: 0f2503af-a932-8e6b-9c55-4c8df212c6c1
id=1, namespace=255: 5e2ca316-ead3-83ea-9152-06659d2d54cb
id=1, namespace=65535: 0a4a2e43-ff0e-8383-af12-e5597447ed20
id=1, namespace=1000000: a9e4d1e3-6df6-8f3a-836d-1e56db53cccd
id=12, namespace=0: c81f423b-2ca0-8963-aefa-f067a191123f
id=42, namespace=12: dee5e9d2-c3e4-8273-b0d5-b3b5307bf749
id=100, namespace=200: 94cf7b60-09f0-814e-9305-37f7eb693984
id=12345, namespace=6789: d5deff2c-1aca-8da8-9bb6-0e05d4f2dea6
id=999999, namespace=999999: f2648a6c-3df9-84e4-afe1-0dd5633d5d7e
id=2147483647 (2^31 - 1), namespace=0: fce9e64f-576e-8f75-8ca5-d2679725ba9f
id=2147483648 (2^31), namespace=0: 96c28777-fb80-8b44-8f3a-58f41da7dab9
id=4294967295 (2^32 - 1), namespace=0: f42237ec-018c-83f2-af4d-bf0034f41734
id=4294967296 (2^32), namespace=0: ef80231a-3165-8468-b04e-21869ead8e1f
id=1000000, namespace=0: 0c4b6717-2df3-88b2-b0c2-32cd92760377
id=1000000000, namespace=0: 2e07e8e9-3f04-8c17-bdaf-266326b01b0a
id=9223372036854775807 (2^63 - 1), namespace=0: 977507dc-c58c-8cc5-b9f9-1b7bc1e798c2
id=0, namespace=4294967295 (2^32 - 1): 6d5fa5ad-754f-8082-ada3-61de1b09aa35
id=1, namespace=4294967295: 36354b02-3ed5-829b-9616-aef9c7a135d9
id=4294967295, namespace=4294967295: 08c906ac-bb85-8843-a4fa-8e015c174532
id=9223372036854775807, namespace=4294967295: 019e07d3-805d-88a4-ba7f-9ce33620cdff
id=42, namespace=2147483647 (2^31 - 1): 63133744-f68d-8e67-8870-946b4d157ee3
id=42, namespace=2147483648 (2^31): f01330f6-3ec8-8a67-91fd-1639930088c6
id=42, namespace=4294967295 (2^32 - 1): 20d66b23-150b-8c66-9b1c-f8f7d9212bd7
The test vectors in Appendix A were generated by running the following script against the PHP reference implementation [REFIMPL]:
<?php
require __DIR__ . '/vendor/autoload.php';
use WickedByte\IntToUuid\IntegerId;
use WickedByte\IntToUuid\IntToUuid;
$vectors = [
// Zero / minimum values
[0, 0],
[1, 0],
[0, 1],
[1, 1],
// Small id values, default namespace
[2, 0],
[10, 0],
[42, 0],
[100, 0],
[255, 0],
[256, 0],
[1000, 0],
[65535, 0],
// Small id, various namespaces
[1, 2],
[1, 100],
[1, 255],
[1, 65535],
[1, 1000000],
// Mixed values (README examples)
[12, 0],
[42, 12],
// Medium values
[100, 200],
[12345, 6789],
[999999, 999999],
// 32-bit boundary values
[2147483647, 0], // INT32_MAX
[2147483648, 0], // INT32_MAX + 1
[4294967295, 0], // UINT32_MAX
[4294967296, 0], // UINT32_MAX + 1
// Large id values
[1000000, 0],
[1000000000, 0],
[9223372036854775807, 0], // PHP_INT_MAX (2^63 - 1)
// Maximum namespace values
[0, 4294967295], // min id, max namespace
[1, 4294967295],
[4294967295, 4294967295], // UINT32_MAX id, max namespace
[9223372036854775807, 4294967295], // max id, max namespace
// Namespace boundary values
[42, 2147483647], // INT32_MAX namespace
[42, 2147483648], // INT32_MAX + 1 namespace
[42, 4294967295], // UINT32_MAX namespace
];
$output = [];
foreach ($vectors as [$id, $ns]) {
$integer_id = IntegerId::make($id, $ns);
$uuid = IntToUuid::encode($integer_id);
$decoded = IntToUuid::decode($uuid);
assert($decoded->value === $id);
assert($decoded->namespace === $ns);
$output[] = ['id' => $id, 'namespace' => $ns, 'uuid' => (string)$uuid];
}
echo json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";The most common source of cross-language incompatibility is incorrect byte order when converting the XXH3 digest integer to a byte string. The 64-bit integer MUST be serialized in big-endian order. Language- specific guidance:
PHP: hash('xxh3', $data, true) returns big-endian bytes.
Python: xxhash.xxh3_64_intdigest(data).to_bytes(8, 'big') Do NOT use xxh3_64_digest() without verifying byte order.
Rust: xxh3::xxh3_64(data).to_be_bytes()
TypeScript/Node.js: Buffer.alloc(8) with writeBigUInt64BE(xxh3.xxh64(data))
In Steps 4 and 3 of the encoding and decoding algorithms respectively, the namespace is 4 bytes but the hash output is 8 bytes. The XOR MUST use only the first 4 bytes of the hash. Implementations MUST explicitly truncate (slice) the hash before applying XOR, unless the host language's XOR semantics guarantee truncation to the shorter operand (as PHP does for string XOR).
JavaScript and some other languages do not have native 64-bit integer types. Implementations in such languages SHOULD use BigInt (or the equivalent arbitrary-precision integer type) for the id parameter to avoid precision loss. The namespace value fits within a standard IEEE 754 double-precision float and does not require special handling.
Andy Snell WickedByte
Email: andy@wickedbyte.com