Certification and Request Encoding
Certification and Request Encoding
This guide describes two separate platform flows that are easy to confuse:
- Certification packaging and hash guarding: how a game build becomes lab deliverables, what bytes are hashed, and how CI detects drift.
- Request-body decoding: how the shared HTTP bootstrap handles Base64-encoded JSON strings before DTO validation.
It describes the current feg_game_be implementation. It does not describe the internal cryptographic algorithms of external RNG/RGS systems, nor does it claim that Base64 is encryption. For the normal round path, see the shared runtime architecture guide; for game rules and code, see the gameplay guide and code walkthrough.
1. Certification Build: What Is the Certified Artifact?
Each game's webpack.config.js defines two builds:
| Output | Source and purpose |
|---|---|
critical.js | Bundles src/logic.ts and its transitive imports into a standalone CommonJS artifact. This is the core RNG/payout logic submitted for certification. |
index.js | Bundles the plugin/engine entry. Its webpack external maps imports of the game's logic module to require('./critical'), so runtime uses the separate critical.js instead of bundling logic a second time. |
maths/*.json | Copied alongside the build. These are the mode-specific math inputs used by the game. |
For Carpathian Treasures, the build starts from src/logic.ts; the logic imports SDK arithmetic and the win-line calculator. index.js is the runtime plugin package, but the current hash guard covers critical.js and math JSON files, not index.js.
2. Deliverables and Hashes
The per-game generate-deliverables target depends on build. The generator then:
- Reads
dist/games/<gameId>/critical.jsand the game's math JSON files. - Recreates
deliverables/<gameId>/, copiescritical.js, and copies math files intodeliverables/<gameId>/maths/. - Computes raw-byte MD5 and SHA-1 hex digests for
critical.jsand each copied math JSON file. - Writes
md5sum.txt,sha1sum.txt, andgames/<gameId>/cert.lockwith both digests per file. - Adds optional standalone simulator, RNG-trace, and EZU dataset folders when their build outputs already exist.
The checksums identify the exact build/math bytes delivered to a lab. They are not signatures and do not prove who produced the files. The guard compares local build bytes to the repository's committed cert.lock baseline.
cert.lock is a JSON map keyed by relative artifact paths, for example critical.js and maths/<mode-file>.json; each entry contains md5 and sha1 values. Math and code changes that alter these files will change their checksums and require the certification owner to decide whether a recertification/update is intended.
generate-deliverablesremoves and recreates that game's existing deliverables directory and rewritescert.lock. Treat it as a packaging/baseline-update operation, not a harmless read-only check.
3. Hash Verification and CI
verify-cert-hashes.js <gameId> runs after the game's build when invoked through its Nx target.
| Check | Result |
|---|---|
CERT_SKIP_HASH_CHECK=1 | Prints a skip warning and exits successfully. The script describes this bypass as for non-certified builds. |
No games/<gameId>/cert.lock | Prints that the game is not yet enrolled in the guard and exits successfully. This is a guard behavior, not proof of an external lab's certification status. |
dist/games/<gameId> missing | Fails and asks for the game build to run first. |
| A locked file is missing or its MD5/SHA-1 differs | Fails with the affected path and the locked/built digest values. |
| Every locked file matches | Reports a pass and the number of files checked. |
The Bitbucket PR pipeline and Jenkins PR/main paths run pnpm nx run-many -t verify-cert-hashes --skip-nx-cache so every game with a cert.lock is checked, not only projects in the current diff. Jenkins' non-main branch path currently uses an affected-scoped check; the pipeline comments explain why affected checks can miss older drift once a moving base tag advances.
For Carpathian Treasures, project.json defines build, generate-deliverables, and verify-cert-hashes targets, but there is currently no games/carpathiantreasures/cert.lock. As a result, its verifier target builds the game and then the hash script skips it for having no lock file. Do not create a lock file or update deliverables unless that is part of an intentional certification action.
Commands for the configured targets
Run these from the workspace root:
pnpm nx build carpathiantreasures
pnpm nx run carpathiantreasures:verify-cert-hashes --skip-nx-cache
pnpm nx run carpathiantreasures:generate-deliverables
The second command is a comparison; the third regenerates deliverables and the local hash baseline. Confirm the intended certification workflow before running the third command.
4. Byte Stability Matters
Hash verification reads file bytes; line endings and bundler output are part of those bytes. The repository's .gitattributes normalizes source and JSON files to LF to prevent Windows CRLF conversion from causing false hash changes.
Build-tool or transitive dependency changes can also alter critical.js even when game source behavior did not change. The local cert-lock drift skill documents the current dependency closure and recommends rechecking every locked game after relevant dependency/lockfile changes. A mismatch should be investigated; do not automatically regenerate cert.lock just to silence the guard.
5. RNG Trace and Certification Deliverables
The RNG trace records random calls in execution order, labels calls using math weight tables, and can show reel stops, reconstructed boards, and feature calls. It is a review/audit aid: the math JSON and game call order remain the source data being traced.
There are two tracer generations in the repository:
| Generation | Current role |
|---|---|
Original rng-trace / standalone builder | Frozen for games already using the legacy flow so their trace deliverables remain reproducible. Carpathian Treasures currently points to this generation in project.json. |
rng-trace-v2 / v2 standalone builder | The newer gamewise design for new integrations. It derives weight labels from math structure and supports an optional per-game tracer when conventions cannot describe a game's call sequence or board. |
The certification generator only includes rng-trace/ if the standalone trace build output exists before generate-deliverables runs. Likewise, simulator/EZU folders are optional build products. The package's project.json identifies which executor generation and options are active; do not switch a game between generations as routine cleanup.
Carpathian's project currently configures an R4 rng-trace target and a standalone-trace build target. Its standard delivery script is the legacy tools/cert/generate-deliverables.js; newer games may use tools/cert/gamewise_v2/generate-deliverables.js, which assembles per-game README sections and optional artifacts differently.
6. Request-Body Base64 Decoding
Where it runs
Pack main.ts calls PlatformBootstrap.run. The bootstrap installs AsyncSessionInterceptor, then DecryptionInterceptor unless disabled, then ResponseInterceptor unless disabled. Global validation pipes and the HTTP exception filter are also installed. The decryption step therefore sits in shared transport handling, not inside Carpathian's plugin or game logic.
What decryptObject actually does
DecryptionService.decryptObject(obj) recursively copies objects and arrays. For each string property, it attempts Buffer.from(value, 'base64').toString('utf-8'); nested objects are traversed recursively. If a decoded cheat property is still a string, the service also attempts to parse it as JSON. The DecryptionInterceptor applies this to non-empty application/json request bodies.
This is Base64 decoding, which is reversible text encoding. It does not use a key, does not provide confidentiality, and is not cryptographic encryption. The searched game backend contains this inbound decoder but no corresponding encryptObject, cipher, or encrypted-response implementation. Do not describe the response wrapper as encrypting output.
If decryption is disabled through PlatformBootstrap options or DISABLE_DECRYPTION=true, the interceptor passes the body through. Decode/parse failures are logged and the original property/body is allowed to continue, so DTO validation remains the next place malformed values may be rejected. The Base64 implementation should not be treated as authentication or integrity protection.
The decoder also explains why plugins often normalize extraData types: after transport decoding, a numeric or boolean-looking value can arrive as a string, and game code must explicitly coerce/validate game-specific fields where required.
7. What Is Not Covered Here
- The certified RNG provider's internals and its own certification evidence.
- RGS wallet signing, authentication, or network TLS internals.
- Hash/signature algorithms used by other services or repositories.
- Detailed RNG tracer implementation and per-game trace-label authoring.
- Cryptographic encryption/decryption algorithms. The observed game-backend request path is Base64 decoding only.
These are separate platform/security or certification topics. Keep their ownership clear instead of inferring them from critical.js checksums or the request decoder.
8. Source Files
- Carpathian build targets
- Carpathian webpack build
- Carpathian game logic
- Legacy deliverables generator
- Gamewise v2 deliverables generator
- Certificate hash verifier
- Hash drift guidance
- RNG-trace v2 guide
- Original RNG trace entry
- RNG-trace v2 entry
- Pack main
- Platform bootstrap
- Bootstrap options
- Decryption interceptor
- Decryption service
- Game core module
- Response interceptor
- Hash guard pipeline
- CI line-ending policy