Skip to content

sdk(deployer): replace Math.random() salt with crypto.randomBytes (insecure RNG for contract ID derivation) #254

@pragmaticAweds

Description

@pragmaticAweds

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

  • randomSalt() uses a cryptographically secure source (crypto.randomBytes or crypto.getRandomValues).
  • No Math.random() calls remain in packages/sdk/src/deployer/.
  • Unit test asserts two consecutive randomSalt() calls return different 32-byte buffers and are not derivable from a seeded Math.random.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Stellar WaveIssues in the Stellar wave programbugSomething isn't workingdifficulty: easySmall, bounded changessdkIssues related to the SDK package

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions