Small · Sortable · Secure ID generator
pikaid is the official C++ implementation of a compact, chronologically sortable, collision-resistant identifier format for databases, APIs, and distributed systems.
This is the immutable, unversioned pikaid format, documented in specification revision 1.0.4.
- Short: Compact, readable IDs for URLs, logs, and API resources.
- Sortable: Lexicographically ordered by timestamp, with one-second precision.
- Collision-resistant: 96 bits of cryptographic randomness make collisions extremely unlikely, never impossible.
- Storage-friendly: Use
BINARY(17)or readableCHAR(26). - Distributed-friendly: No central sequence generator or coordination required.
A pikaid contains 26 lowercase Base36 characters: a 7-character Unix-seconds timestamp followed by 19 characters encoding 96 bits of cryptographic randomness. Its binary form is 17 bytes: 5 unsigned big-endian timestamp bytes followed by 12 randomness bytes.
Ordering: Different seconds sort chronologically. Within the same second, randomness—not issuance order—decides the order.
Security: These are identifiers, not secrets, session credentials, authentication tokens, or API keys.
- A C++17 compiler. The GitHub Actions matrix builds and tests GCC and Clang on Linux, AppleClang on macOS, and MSVC on Windows; see the Actions tab for the current status. Other conforming C++17 compilers work where one of the supported CSPRNG backends is available.
- CMake 3.16 or newer.
- Ninja is optional; any CMake generator works.
- No external runtime dependencies. On Windows the library links the system
bcryptlibrary.
From a checkout of the public source repository:
git clone https://github.com/pikaid/pikaid-cpp.git
cd pikaid-cppcmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failureThe example is built by default and can be run directly:
./build/pikaid_exampleExample output (values differ on every run):
id: 0to3mjy1v5pu5yjpvydwsdxfpe
timestamp: 1793999086
randomness: 3e2c70cb0c50b22784c82ac2
valid: yes
round trip: ok
Install and consume from another CMake project:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cmake --install build --prefix /some/prefixfind_package(pikaid 1.0 CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE pikaid::pikaid)The package follows semantic versioning: the installed
pikaidConfigVersion.cmake uses SameMajorVersion compatibility, and the
shared library sets SOVERSION 1.
Source and binary archives can be produced with CPack; both exclude the
local-only .ai/ workspace, VCS metadata and build trees:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cmake --build build --target package # binary archives
cmake --build build --target package_source # source archiveBuild options:
| Option | Default | Effect |
|---|---|---|
PIKAID_BUILD_TESTS |
ON when top level |
Build the test suite and register CTest tests |
PIKAID_BUILD_EXAMPLES |
ON when top level |
Build pikaid_example |
PIKAID_WARNINGS_AS_ERRORS |
OFF |
Add -Werror or /WX |
PIKAID_SANITIZE |
OFF |
Add AddressSanitizer and UndefinedBehaviorSanitizer (not MSVC) |
PIKAID_COVERAGE |
OFF |
Add Clang or GCC coverage instrumentation |
#include <pikaid/pikaid.hpp>
#include <iostream>
#include <string>
int main()
{
const std::string id = pikaid::generate(); // current Unix time
const std::string fixed = pikaid::generate(1700000000);
if (!pikaid::is_valid(id)) {
return 1;
}
const std::uint64_t seconds = pikaid::timestamp(id);
const pikaid::randomness_bytes entropy = pikaid::randomness(id);
const pikaid::binary bytes = pikaid::to_binary(id);
std::cout << id << '\n'
<< fixed << '\n'
<< pikaid::from_binary(bytes) << '\n'
<< seconds << ' ' << pikaid::randomness_hex(id) << '\n';
return pikaid::from_binary(bytes) == id ? 0 : 1;
}Use pikaid to identify users, orders, invoices, uploads, events, and API resources. Independent services can generate compact IDs without a shared counter, while timestamp-based ordering helps with indexing, sorting, and logs.
Specification revisions describe the format; they do not change the encoded identifiers.
| Function | Purpose |
|---|---|
secure_randomness() |
Read 12 bytes from the operating system CSPRNG |
generate() |
Generate text using the current Unix time |
generate(timestamp) |
Generate text using an explicit timestamp |
generate(timestamp, source) |
Deterministic generation for tests and migrations |
generate_binary(), generate_binary(timestamp[, source]) |
Binary equivalents |
is_well_formed(id) |
Shape check only: 26 characters from [0-9a-z] |
is_valid(id) |
Full check: shape plus a randomness value within 96 bits |
validate_timestamp(timestamp) |
Validate a timestamp for generation |
parse(id) |
Return {timestamp, randomness} or throw pikaid::error |
timestamp(id), randomness(id), randomness_hex(id) |
Typed accessors |
to_binary(id), from_binary(binary) |
Lossless conversion |
compare(a, b), compare_binary(a, b) |
-1, 0, or 1 |
is_well_formed and is_valid are deliberately different. A 19-character
Base36 suffix can encode values above 2^96 - 1; is_well_formed only checks
the shape, while is_valid also rejects those non-canonical values. Prefer
is_valid at trust boundaries, or call parse/to_binary, which throw
pikaid::error with a stable error_code.
try {
const pikaid::parts decoded = pikaid::parse(candidate);
// ...
} catch (const pikaid::error& error) {
switch (error.code()) {
case pikaid::error_code::invalid_string_length: /* ... */ break;
case pikaid::error_code::invalid_character: /* ... */ break;
case pikaid::error_code::randomness_overflow: /* ... */ break;
default: /* ... */ break;
}
}pikaid::error derives from std::invalid_argument, so existing code that
catches std::invalid_argument or std::exception continues to work. Invalid
input is never trimmed, case-folded, truncated, or normalized.
- Randomness comes from the operating system CSPRNG (
BCryptGenRandomon Windows,getrandom(2)on Linux,getentropy(2)on Apple/BSD platforms, and/dev/urandomas a POSIX fallback). The library never usesstd::random_devicealone, and fails closed witherror_code::randomness_failureon CSPRNG errors or short reads. - The 96-bit randomness field gives collision resistance up to the usual birthday bound; pikaid makes no absolute uniqueness guarantee. Check a database uniqueness constraint if duplicates are unacceptable.
- pikaid is an identifier format, not an authentication token, capability, or encrypted value. It is not secret: the timestamp is plaintext and the randomness is not a signature. Do not use it for authorization decisions.
- The timestamp comes from the system clock and is not trusted input. Clock skew or rollback affects ordering; pikaid is not monotonic within a second.
- The
randomness_sourceoverloads exist for deterministic tests and data migrations. Never supply predictable randomness in production.
An independent-review packet with the exact file/line map, entropy-provider matrix, failure semantics and evidence template is in docs/CSPRNG_REVIEW_PACKET.md. It records the two outstanding manual audits; it is not an approval and does not certify entropy security.
Text and 17-byte binary values preserve the canonical timestamp-then-randomness ordering. Same-second values are not ordered by issuance.
| Representation | Size | Recommended for |
|---|---|---|
| BINARY(17) | 17 bytes | Compact primary keys and indexes |
| CHAR(26) | 26 characters | Readable SQL, debugging, URLs, and logs |
Use an ASCII bytewise/binary collation for text columns (for example, ascii_bin on MariaDB/MySQL). Case-insensitive or locale-aware sorting is not a substitute for canonical bytewise comparison.
The CI workflow runs native tests, pinned automated conformance checks, and language-specific quality gates. To run the native checks locally from a checkout: ctest --test-dir build --output-on-failure.
Automated passing tests are not a security certification. The independent CSPRNG source and failure-path audits (P-GENERATE-RNG-SOURCE and P-GENERATE-RNG-FAILURE) still require separate human evidence.
Platform, tooling, and conformance details
The suite runs every official vector from the reference implementation and
adds independent known-answer cases for both conversion and deterministic
generation, malformed-input cases (including CRLF and zero-width Unicode),
overflow, boundary, property, cross-representation, generated-identifier, and
CSPRNG-failure cases. The official fixture is read at run time and pinned by
SHA-256; see tests/vectors/README.md for provenance.
Local results on macOS with AppleClang 21.0.0:
45 test cases, 46550 checks, 0 failing checks
Line coverage of src/pikaid.cpp is 95.74% (95.97% of regions, 97.14% of
branches); the public header is fully covered. The 12 uncovered lines are
CSPRNG failure branches that cannot be triggered on a healthy macOS host, plus
one defensive digit < 0 return that is unreachable after the shape check.
Sanitizer builds (ASan + UBSan) pass.
The GitHub Actions workflow builds and tests the same suite with warnings as
errors on Linux GCC, Linux Clang, macOS AppleClang, and Windows MSVC, runs
ASan+UBSan on Linux and macOS, and enforces a line-coverage floor of 90%. A
packaging job additionally proves that .ai/ is untracked, that the CPack
source archive contains exactly the tracked file set, and that static and
shared binary packages install and are consumed by an out-of-tree CMake
project through find_package(pikaid 1.0 CONFIG REQUIRED). The workflow and
its public run history are in the Actions tab; local results alone are not a
substitute for that cross-platform matrix.
Local packaging checks (macOS, AppleClang 21.0.0, CMake 4.4.5) compared two fresh static and shared TGZ builds:
- The CPack source archive contains exactly the
git ls-filesset and no.ai/,.git/, or.DS_Storeentry. - Both source archives have identical file lists and identical extracted content.
- Both static and shared packages have identical file lists, and the shared
libpikaidbytes are identical across builds. - The static
libpikaid.abytes differ only in archive-member timestamps; the extractedpikaid.cpp.ois byte-for-byte identical.
Archive SHA-256 values therefore differ between builds because CPack/tar and
ar record timestamps. Neither this repository nor the official specification
states a byte-for-byte reproducible-archive contract, so this project does not
claim reproducibility of archive files. The supported guarantees are the file
manifest above, identical source/object/binary content, and successful
find_package(pikaid 1.0 CONFIG REQUIRED) consumption of the extracted static
and shared packages.
This library is validated against the official cross-language conformance
suite (pikaid/pikaid-specs) pinned at commit
059780cc0e5b113443812720d95c630649bf3aa3 (suite package revision 1.0.0,
identifier specification revision 1.0.4). tools/conformance/ contains the
test-only JSON-Lines adapter that the suite drives; it is never installed or
shipped. Results are reported exactly as the suite emits them, never upgraded:
run_adapter.py --automated-onlycurrently exits0with statusAUTOMATED_PASS_AUDIT_NOT_PERFORMED. Every automated vector and property passed, but the run deliberately did not perform the manual security audits, and its own output statescertification: NOT_A_CONFORMANCE_CERTIFICATION.- The default invocation (without
--automated-only) currently exits3with statusAUTOMATED_PASS_AUDIT_REQUIRED, because the two manual CSPRNG review obligationsP-GENERATE-RNG-SOURCEandP-GENERATE-RNG-FAILUREremainMANUAL_EVIDENCE_REQUIRED. Completing them requires independent human review evidence; an automated run cannot claim them.
A green --automated-only run, and this README, are therefore not proof that
the manual security audits were completed or that conformance was certified.
Registry publication is not implied by a green CI: use the installation method documented above.
Contributions are always welcome! 💙
Whether you're fixing a typo, improving documentation, adding tests, or making pikaid even better, every contribution is appreciated.
Want to help? Here's how:
- 🐛 Found a bug? Open an issue with clear reproduction steps.
- 💡 Have an idea? Start with an issue before making major changes.
- 🛠️ Ready to contribute? Open a pull request explaining your changes.
- 🧪 Keep it tested. Add meaningful regression tests for behavior changes and follow the existing conventions.
- ✅ Make sure CI passes. All required checks must be green before a pull request is merged. No shortcuts!
One important rule: pikaid's identifier format is immutable. Contributions must preserve the canonical specification (revision 1.0.4).
🔐 Found a security vulnerability? Report it privately using GitHub's security reporting (when available), or contact the maintainer—not through a public issue.
Every improvement matters, big or small. Thanks for helping make pikaid better! 🐇
The implementation is available under the MIT License.
For licensing or project-related questions: larananas.dev@proton.me.