-
Notifications
You must be signed in to change notification settings - Fork 38
feat: allow specifying num callers for simulator #143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
🟡 Heimdall Review Status
|
runner/payload/simulator/worker.go
Dismissed
| keys := make([]*ecdsa.PrivateKey, numCallers) | ||
| addrs := make([]common.Address, numCallers) | ||
| for i := 0; i < numCallers; i++ { | ||
| key, err := ecdsa.GenerateKey(crypto.S256(), src) |
Check failure
Code scanning / CodeQL
Use of insufficient randomness as the key of a cryptographic algorithm High
random number
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 days ago
In general, cryptographic keys must be generated using a cryptographically secure pseudo-random number generator (CSPRNG), such as Go’s crypto/rand, and must not depend on math/rand. If deterministic derivation from a master key is desired (e.g., for reproducible simulations), it should be implemented using deterministic key-derivation techniques (e.g., hashing plus modular reduction) rather than by plugging a weak PRNG into a cryptographic key-generation API.
For this specific code, the simplest, least invasive fix is:
- Stop using
math/randas theio.Readerpassed toecdsa.GenerateKey. - Instead, derive each new private key deterministically from the prefunded key using a cryptographic hash, and construct the ECDSA private key directly on the secp256k1 curve:
- For each caller index
i, computeskInt = keccak256(prefundedKey.D || i) mod N, whereNis the curve order. - Ensure
skIntis non-zero; if zero, tweak it (e.g., hash again with a suffix) or fall back to a minimal non-zero value. - Set
privKey := &ecdsa.PrivateKey{D: skInt, PublicKey: *pubKey}wherepubKeyis computed viacrypto.S256().ScalarBaseMult(skInt.Bytes()).
- For each caller index
- This preserves determinism while relying only on cryptographic primitives, not on
math/rand, and removes the taintedrand.NewSourcepath entirely.
Concretely in runner/payload/simulator/worker.go:
- Remove the use of
math/randinsidegenerateCallerAccounts(we can leave the import alone if we’re not allowed to touch unused imports, but we will not use it). - Add a small helper inside
generateCallerAccounts(or inline logic) to compute a derived scalar fromprefundedKey.Dand the index usingcrypto.Keccak256andnew(big.Int).Mod(...). - Use that derived scalar to populate
keys[i]andaddrs[i]instead of callingecdsa.GenerateKeywithsrc.
No new external dependencies are needed; we only use crypto/ecdsa, crypto/elliptic via crypto.S256(), math/big, and github.com/ethereum/go-ethereum/crypto (already imported).
-
Copy modified lines R207-R210 -
Copy modified lines R214-R230 -
Copy modified lines R232-R244 -
Copy modified line R246
| @@ -204,20 +204,46 @@ | ||
| return []*ecdsa.PrivateKey{prefundedKey}, []common.Address{crypto.PubkeyToAddress(prefundedKey.PublicKey)} | ||
| } | ||
|
|
||
| // Use deterministic random source seeded from prefunded key | ||
| seed := int64(prefundedKey.D.Uint64()) | ||
| src := rand.New(rand.NewSource(seed)) | ||
| // Deterministically derive additional caller keys from the prefunded key using a cryptographic hash. | ||
| // This avoids using a non-cryptographic PRNG as the entropy source for key generation. | ||
| curve := crypto.S256() | ||
| N := curve.Params().N | ||
|
|
||
| keys := make([]*ecdsa.PrivateKey, numCallers) | ||
| addrs := make([]common.Address, numCallers) | ||
| for i := 0; i < numCallers; i++ { | ||
| key, err := ecdsa.GenerateKey(crypto.S256(), src) | ||
| if err != nil { | ||
| panic(fmt.Sprintf("failed to generate caller key: %v", err)) | ||
|
|
||
| // First caller uses the prefunded key directly for consistency with the numCallers == 1 case. | ||
| keys[0] = prefundedKey | ||
| addrs[0] = crypto.PubkeyToAddress(prefundedKey.PublicKey) | ||
|
|
||
| for i := 1; i < numCallers; i++ { | ||
| // Derive a new private scalar from prefundedKey.D and the index using keccak256, reduced mod N. | ||
| // Encode index deterministically as base-10 string bytes. | ||
| indexBytes := []byte(strconv.Itoa(i)) | ||
| baseDBytes := prefundedKey.D.Bytes() | ||
| derivedHash := crypto.Keccak256(baseDBytes, indexBytes) | ||
|
|
||
| d := new(big.Int).SetBytes(derivedHash) | ||
| d.Mod(d, N) | ||
| if d.Sign() == 0 { | ||
| // Extremely unlikely, but ensure we never use zero as a private scalar. | ||
| d.Add(d, big.NewInt(1)) | ||
| } | ||
| keys[i] = key | ||
| addrs[i] = crypto.PubkeyToAddress(key.PublicKey) | ||
|
|
||
| x, y := curve.ScalarBaseMult(d.Bytes()) | ||
| priv := &ecdsa.PrivateKey{ | ||
| D: d, | ||
| PublicKey: ecdsa.PublicKey{ | ||
| Curve: curve, | ||
| X: x, | ||
| Y: y, | ||
| }, | ||
| } | ||
|
|
||
| keys[i] = priv | ||
| addrs[i] = crypto.PubkeyToAddress(priv.PublicKey) | ||
| } | ||
|
|
||
| return keys, addrs | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used for testing - we want a deterministic key here.
This allows testing things like Block-STM where the number of callers affects performance. If we use a single caller, every transaction conflicts with the next transaction on the balance/nonce of the caller.
518daed to
e4aab51
Compare
This allows testing things like Block-STM where the number of callers affects performance. If we use a single caller, every transaction conflicts with the next transaction on the balance/nonce of the caller.
Description
Testing