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.
The repository's cert-lock guidance recommends running the all-game check explicitly when investigating hash drift:
pnpm nx run-many -t verify-cert-hashes --skip-nx-cache
This checks every game with a committed lock, even if a moving CI base ref no longer considers that game's source affected. CERT_SKIP_HASH_CHECK=1 is a deliberate bypass, not a way to resolve a mismatch.
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. In particular, webpack may derive deterministic module IDs from resolved pnpm paths, which include dependency versions. 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.
The math-file conventions guide explains the exact math section names, supported weight-table shapes/naming, reel-set/layout assumptions, and per-game trace-label documentation expected by the tooling. These details affect trace interpretability even though the runtime hash itself is only computed from the built critical.js and math JSON bytes.
For Carpathian specifically, the R4 JSON declares multiple reel sets and selector weights, but the current engine always generates the base board from BG.ReelSet_1. When reviewing a trace, compare its assumptions and labels to the engine's actual path; do not infer that a JSON field is active merely because it exists.
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.
Separate Signatures in Other Services
There are signature mechanisms elsewhere in the workspace, but they are separate from request-body Base64 decoding and artifact hashes:
| Mechanism | Location and purpose |
|---|---|
HMAC-SHA256 (W-Signature) | The RGS FEG wallet strategy signs serialized outbound wallet request bodies using secretKey; the receiver can authenticate the sender and check message integrity. |
RSA-SHA256 (Casino-Signature) | Shared feg_be_common helpers create and verify signatures over serialized request data using a private/public key pair. |
MD5/SHA-1 (cert.lock) | Unkeyed checksums used to compare certified build/math bytes; not wallet request authentication. |
Signatures provide authentication/integrity properties; they do not encrypt or hide the request body. The game backend's Base64 decoder, RGS wallet HMAC, Casino RSA signatures, and certification checksums are distinct mechanisms owned by different layers.
7. What Is Not Covered Here
- The certified RNG provider's internals and its own certification evidence.
- Detailed key provisioning, rotation, and signature verification policy in RGS/common services.
- Network TLS internals.
- 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
- Math-file conventions
- Carpathian Excel parser command
- Carpathian Excel workbook helpers
- 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
- RGS FEG wallet signing implementation
- Shared HMAC helper
- Shared Casino RSA signature helpers
- RGS backend overview
- Shared common package overview
- FEG wallet signing design