Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions contracts/crypto/YulECDSARecover.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title YulECDSARecover
/// @notice Gas-efficient ECDSA recovery with malleability protection, no intermediate memory copies.
library YulECDSARecover {
// secp256k1n / 2
uint256 internal constant MALLEABILITY_THRESHOLD =
0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0;

error InvalidSignatureS();
error RecoverFailed();

function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (address signer) {
if (uint256(s) > MALLEABILITY_THRESHOLD) revert InvalidSignatureS();

assembly {
let ptr := mload(0x40)
mstore(ptr, hash)
mstore(add(ptr, 0x20), v)
mstore(add(ptr, 0x40), r)
mstore(add(ptr, 0x60), s)
let ok := staticcall(gas(), 0x01, ptr, 0x80, ptr, 0x20)
if iszero(ok) {
revert(0, 0)
}
signer := mload(ptr)
}
if (signer == address(0)) revert RecoverFailed();
}

/// @dev Unpacks r, s, v directly from a 65-byte calldata signature slice.
function recoverFromCalldata(bytes32 hash, bytes calldata signature) internal view returns (address) {
bytes32 r = bytes32(signature[0:32]);
bytes32 s = bytes32(signature[32:64]);
uint8 v = uint8(signature[64]);
return recover(hash, v, r, s);
}
}
35 changes: 35 additions & 0 deletions contracts/precompiles/PrecompileForwarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title PrecompileForwarder
/// @notice Minimal Yul proxy for the ecrecover (0x01) and sha256 (0x02) precompiles.
/// @dev Starter implementation; modexp (0x05) and bn256Pairing (0x08) forwarding follow in a later PR.
library PrecompileForwarder {
error PrecompileCallFailed(address precompile);

function ecrecoverRaw(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (address signer) {
assembly {
let ptr := mload(0x40)
mstore(ptr, hash)
mstore(add(ptr, 0x20), v)
mstore(add(ptr, 0x40), r)
mstore(add(ptr, 0x60), s)
let ok := staticcall(gas(), 0x01, ptr, 0x80, ptr, 0x20)
if iszero(ok) {
revert(0, 0)
}
signer := mload(ptr)
}
}

function sha256Raw(bytes memory data) internal view returns (bytes32 digest) {
assembly {
let len := mload(data)
let ok := staticcall(gas(), 0x02, add(data, 0x20), len, 0x00, 0x20)
if iszero(ok) {
revert(0, 0)
}
digest := mload(0x00)
}
}
}
49 changes: 49 additions & 0 deletions gasguard-cli/src/fuzzer/bytecode_mutator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! Closes #668: bytecode mutation engine (starter) for gas-spike fuzzing.
//! Flips bytes in compiled runtime opcodes to generate mutated variants for
//! gas-runner execution against warm/cold storage state. No external deps.

/// Deterministic xorshift-style generator, avoids pulling in the `rand` crate
/// for this starter implementation.
struct SimpleRng(u64);

impl SimpleRng {
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
}

/// Produces `count` mutated copies of `bytecode`, each with a single byte
/// flipped at a pseudo-random offset.
pub fn mutate_bytecode(bytecode: &[u8], count: usize, seed: u64) -> Vec<Vec<u8>> {
if bytecode.is_empty() {
return Vec::new();
}
let mut rng = SimpleRng(seed | 1);
let mut variants = Vec::with_capacity(count);
for _ in 0..count {
let mut mutated = bytecode.to_vec();
let idx = (rng.next() as usize) % mutated.len();
mutated[idx] ^= 0xFF;
variants.push(mutated);
}
variants
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn produces_requested_variant_count() {
let variants = mutate_bytecode(&[0x60, 0x01, 0x60, 0x02], 5, 42);
assert_eq!(variants.len(), 5);
}

#[test]
fn empty_input_yields_no_variants() {
assert!(mutate_bytecode(&[], 3, 1).is_empty());
}
}
Loading