Skip to content
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

(fix): correctly redact private keys in traces #6995 #7049

Merged
merged 8 commits into from
Feb 8, 2024
Merged
Changes from 7 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
115 changes: 104 additions & 11 deletions crates/evm/traces/src/decoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,26 +362,39 @@ impl CallTraceDecoder {
fn decode_cheatcode_inputs(&self, func: &Function, data: &[u8]) -> Option<Vec<String>> {
match func.name.as_str() {
"expectRevert" => Some(vec![decode::decode_revert(data, Some(&self.errors), None)]),
"rememberKey" | "startBroadcast" | "broadcast" => {
// these functions accept a private key as uint256, which should not be
// converted to plain text
if !func.inputs.is_empty() && func.inputs[0].ty != "uint256" {
// redact private key input
"addr" | "createWallet" | "deriveKey" | "rememberKey" => {
// Redact private key in all cases
Some(vec!["<pk>".to_string()])
}
"broadcast" | "startBroadcast" => {
// Redact private key if defined
if !func.inputs.is_empty() && func.inputs[0].ty == "uint256" {
Some(vec!["<pk>".to_string()])
} else {
None
}
}
"getNonce" => {
// Redact private key if defined
zerosnacks marked this conversation as resolved.
Show resolved Hide resolved
if !func.inputs.is_empty() && func.inputs[0].ty == "tuple" {
Some(vec!["<pk>".to_string()])
} else {
None
}
}
"sign" => {
// sign(uint256,bytes32)
"sign" | "signP256" => {
let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?;
if !decoded.is_empty() && func.inputs[0].ty != "uint256" {

// Redact private key and replace in trace
// sign(uint256,bytes32) / signP256(uint256,bytes32) / sign(Wallet,bytes32)
if !decoded.is_empty() &&
(func.inputs[0].ty == "uint256" || func.inputs[0].ty == "tuple")
{
decoded[0] = DynSolValue::String("<pk>".to_string());
}

Some(decoded.iter().map(format_token).collect())
}
"addr" => Some(vec!["<pk>".to_string()]),
"deriveKey" => Some(vec!["<pk>".to_string()]),
"parseJson" |
"parseJsonUint" |
"parseJsonUintArray" |
Expand Down Expand Up @@ -460,7 +473,7 @@ impl CallTraceDecoder {
fn decode_cheatcode_outputs(&self, func: &Function) -> Option<String> {
match func.name.as_str() {
s if s.starts_with("env") => Some("<env var value>"),
"deriveKey" => Some("<pk>"),
"createWallet" | "deriveKey" => Some("<pk>"),
"parseJson" if self.verbosity < 5 => Some("<encoded JSON value>"),
"readFile" if self.verbosity < 5 => Some("<file>"),
_ => None,
Expand Down Expand Up @@ -560,3 +573,83 @@ fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec<DynSolValue>
fn indexed_inputs(event: &Event) -> usize {
event.inputs.iter().filter(|param| param.indexed).count()
}

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

#[test]
fn test_should_redact_pk() {
let decoder = CallTraceDecoder::new();

// [function_signature, data, expected]
let cheatcode_input_test_cases = vec![
// Should redact private key from traces in all cases:
("addr(uint256)", vec![], Some(vec!["<pk>".to_string()])),
("createWallet(string)", vec![], Some(vec!["<pk>".to_string()])),
("createWallet(uint256)", vec![], Some(vec!["<pk>".to_string()])),
("deriveKey(string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
("deriveKey(string,string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
("deriveKey(string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
("deriveKey(string,string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
("rememberKey(uint256)", vec![], Some(vec!["<pk>".to_string()])),
//
// Should redact private key from traces in specific cases with exceptions:
("broadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
("broadcast()", vec![], None), // Ignore: `private key` is not passed.
("startBroadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
("startBroadcast()", vec![], None), // Ignore: `private key` is not passed.
("getNonce((address,uint256,uint256,uint256))", vec![], Some(vec!["<pk>".to_string()])),
("getNonce(address)", vec![], None), // Ignore: `address` is public.
//
// Should redact private key and replace in trace in cases:
(
"sign(uint256,bytes32)",
vec![
zerosnacks marked this conversation as resolved.
Show resolved Hide resolved
227, 65, 234, 164, 124, 133, 33, 24, 41, 78, 81, 230, 83, 113, 42, 129, 224,
88, 0, 244, 25, 20, 23, 81, 190, 88, 246, 5, 195, 113, 225, 81, 65, 176, 7,
166, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
],
Some(vec![
"\"<pk>\"".to_string(),
"0x0000000000000000000000000000000000000000000000000000000000000000"
.to_string(),
]),
),
(
"signP256(uint256,bytes32)",
vec![
131, 33, 27, 64, 124, 133, 33, 24, 41, 78, 81, 230, 83, 113, 42, 129, 224, 88,
0, 244, 25, 20, 23, 81, 190, 88, 246, 5, 195, 113, 225, 81, 65, 176, 7, 166, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
],
Some(vec![
"\"<pk>\"".to_string(),
"0x0000000000000000000000000000000000000000000000000000000000000000"
.to_string(),
]),
),
];

// [function_signature, expected]
let cheatcode_output_test_cases = vec![
// Should redact private key on output in all cases:
("createWallet(string)", Some("<pk>".to_string())),
("deriveKey(string,uint32)", Some("<pk>".to_string())),
];

for (function_signature, data, expected) in cheatcode_input_test_cases {
let function = Function::parse(function_signature).unwrap();
let result = decoder.decode_cheatcode_inputs(&function, &data);
assert_eq!(result, expected, "Input case failed for: {}", function_signature);
}

for (function_signature, expected) in cheatcode_output_test_cases {
let function = Function::parse(function_signature).unwrap();
let result = Some(decoder.decode_cheatcode_outputs(&function).unwrap_or_default());
assert_eq!(result, expected, "Output case failed for: {}", function_signature);
}
}
}
Loading