From c19b35941c2627182c792a82aa0c0b42a8f9509e Mon Sep 17 00:00:00 2001 From: malakasaray-del Date: Thu, 10 Sep 2026 00:29:41 +0300 Subject: [PATCH] Use Cryptographically Secure Pseudo-Random Generator for Order Salt Generation ### Description This pull request addresses Medium severity cryptographic entropy and signature integrity findings in `py-clob-client-v2` identified during the workspace audit. Previously, `generate_order_salt()` generated entropy using `random.random() * time.time_ns() // 1_000_000`. The pseudo-random generator (Mersenne Twister) lacks cryptographic security and can be reconstructed from historical outputs, making future order salts predictable. This PR enforces the use of the Python `secrets` module and bounds the integer bit-length to avoid IEEE-754 transport truncation. ### Key Changes & Remediations #### Cryptographically Secure Order Salt (`order_utils/utils.py`) * **CSPRNG Implementation:** Replaced the insecure `random.random()` and public nanosecond timestamp formula with `secrets.randbits(SALT_BITS)`, preventing order digest precomputation and hash collision attacks. * **JSON Double Precision Safety:** Constrained salt entropy to 53 bits (`SALT_BITS = 53`). This guarantees that when serialized as JSON numbers, values do not exceed $2^{53} - 1$, preventing float rounding errors that break EIP-712 signature digests upon verification by the exchange. ### How to Review 1. Review `generate_order_salt()` in `order_utils/utils.py` to verify migration to `secrets.randbits`. 2. Confirm the return value remains a string representation of the 53-bit integer for seamless API compatibility. --- py_clob_client_v2/order_utils/utils.py | 30 +++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/py_clob_client_v2/order_utils/utils.py b/py_clob_client_v2/order_utils/utils.py index b2a7a68..70ecfc1 100644 --- a/py_clob_client_v2/order_utils/utils.py +++ b/py_clob_client_v2/order_utils/utils.py @@ -1,6 +1,30 @@ -import random -import time +import secrets + +# Number of random bits used for an order salt. +# +# Capped at 53 because the CLOB wire contract carries `salt` as a JSON number. +# JSON numbers are IEEE-754 doubles for many consumers and represent integers +# exactly only up to 2**53 - 1, so a wider salt could be rounded during transport +# while the EIP-712 signature was produced over the original value. The server +# would then reconstruct a different digest and reject the order as having an +# invalid signature. 53 bits still gives roughly 9.0e15 of collision space. +SALT_BITS = 53 def generate_order_salt() -> str: - return str(int(random.random() * (time.time_ns() // 1_000_000))) + """ + Generate a cryptographically random order salt. + + The salt is the only entropy that distinguishes two otherwise identical + orders in the EIP-712 digest, so it is a security-relevant value: a + predictable salt lets a third party precompute an order hash before it is + broadcast, and a colliding salt produces a duplicate order hash. + + This previously returned ``int(random.random() * time.time_ns() // 1_000_000)``. + ``random.random()`` is the Mersenne Twister, which is not a cryptographically + secure generator -- its entire 19937-bit state can be recovered from 624 + consecutive outputs, after which every future salt is predictable -- and the + millisecond clock it was multiplied by is close to public knowledge. + ``secrets`` draws from the operating system CSPRNG and needs no clock input. + """ + return str(secrets.randbits(SALT_BITS))