Summary
ContractDeployer.randomSalt() builds the 32-byte salt used for contract ID derivation byte-by-byte using Math.random(). Math.random() is not a CSPRNG — its output is predictable enough that an attacker observing prior deployments (or just guessing in a low-entropy range) can precompute or correlate contract IDs.
Where
packages/sdk/src/deployer/ContractDeployer.ts:554-557
private randomSalt(): Buffer {
const buf = Buffer.alloc(32);
for (let i = 0; i < 32; i++) buf[i] = Math.floor(Math.random() * 256);
return buf;
}
This salt is passed into xdr.ContractIdPreimageFromAddress and ultimately determines the deployed contract's address (see deriveContractId at line 541-551 of the same file). Anything that depends on contract-address unpredictability (e.g. front-running prevention, pre-funding a derived address before deploy) is undermined.
Impact
- The salt's entropy is bounded by the JS engine's
Math.random state (typically 53 bits, often less, and shared global state across the process).
- An attacker who can observe the deployer's prior
Math.random() outputs (via any colocated code path that consumes the same RNG) can predict subsequent salts and the resulting contract IDs.
- Even without observation, the entropy is dramatically lower than the 256 bits the field implies.
Fix
Use Node's crypto.randomBytes (already a transitive dep via @stellar/stellar-sdk):
import { randomBytes } from 'node:crypto';
private randomSalt(): Buffer {
return randomBytes(32);
}
For browser-targeted builds, crypto.getRandomValues should be used instead — worth checking the SDK's bundling target before picking one.
Acceptance criteria
Summary
ContractDeployer.randomSalt()builds the 32-byte salt used for contract ID derivation byte-by-byte usingMath.random().Math.random()is not a CSPRNG — its output is predictable enough that an attacker observing prior deployments (or just guessing in a low-entropy range) can precompute or correlate contract IDs.Where
packages/sdk/src/deployer/ContractDeployer.ts:554-557This salt is passed into
xdr.ContractIdPreimageFromAddressand ultimately determines the deployed contract's address (seederiveContractIdat line 541-551 of the same file). Anything that depends on contract-address unpredictability (e.g. front-running prevention, pre-funding a derived address before deploy) is undermined.Impact
Math.randomstate (typically 53 bits, often less, and shared global state across the process).Math.random()outputs (via any colocated code path that consumes the same RNG) can predict subsequent salts and the resulting contract IDs.Fix
Use Node's
crypto.randomBytes(already a transitive dep via@stellar/stellar-sdk):For browser-targeted builds,
crypto.getRandomValuesshould be used instead — worth checking the SDK's bundling target before picking one.Acceptance criteria
randomSalt()uses a cryptographically secure source (crypto.randomBytesorcrypto.getRandomValues).Math.random()calls remain inpackages/sdk/src/deployer/.randomSalt()calls return different 32-byte buffers and are not derivable from a seededMath.random.