-
Notifications
You must be signed in to change notification settings - Fork 0
Fix/bugbot security fixes #2
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -100,8 +100,8 @@ impl ZkpVerifier { | |
| /// # Arguments | ||
| /// * `env` - The Soroban environment | ||
| /// * `proof` - The proof hash to verify | ||
| /// * `balance_data` - The balance value as bytes | ||
| /// * `required_amount_data` - The required amount as bytes | ||
| /// * `balance_data` - The balance value as bytes (decimal string, e.g., "1000.50") | ||
| /// * `required_amount_data` - The required amount as bytes (decimal string, e.g., "500.25") | ||
| /// * `salt` - The cryptographic salt | ||
| /// * `hmac_key` - The HMAC secret key | ||
| /// | ||
|
|
@@ -128,9 +128,20 @@ impl ZkpVerifier { | |
| return false; | ||
| } | ||
|
|
||
| // Additional validation: ensure balance >= required_amount | ||
| // This is a simplified check - in production, you'd parse the actual numeric values | ||
| let balance_sufficient = balance_data.len() >= required_amount_data.len(); | ||
| // Parse and compare numeric values | ||
| let balance = Self::parse_decimal_to_scaled(&balance_data); | ||
| let required = Self::parse_decimal_to_scaled(&required_amount_data); | ||
|
|
||
| let balance_sufficient = match (balance, required) { | ||
| (Some(b), Some(r)) => b >= r, | ||
| _ => { | ||
| env.events().publish( | ||
| (Symbol::new(&env, "error"),), | ||
| VerificationError::InvalidInput as u32, | ||
| ); | ||
| false | ||
| } | ||
| }; | ||
|
|
||
| env.events().publish( | ||
| (Symbol::new(&env, "balance_check"),), | ||
|
|
@@ -140,6 +151,78 @@ impl ZkpVerifier { | |
| proof_valid && balance_sufficient | ||
| } | ||
|
|
||
| /// Parses a decimal string (e.g., "1234.56") to a scaled integer for comparison. | ||
| /// Returns None if parsing fails or if no digits are present. | ||
| /// The result is scaled by 10^8 to handle up to 8 decimal places. | ||
| fn parse_decimal_to_scaled(data: &Bytes) -> Option<i128> { | ||
| // Return None for empty input | ||
| if data.len() == 0 { | ||
| return None; | ||
| } | ||
|
|
||
| let mut result: i128 = 0; | ||
| let mut decimal_places: u32 = 0; | ||
| let mut found_decimal = false; | ||
| let mut is_negative = false; | ||
| let mut has_digits = false; // Track if at least one digit was parsed | ||
|
|
||
| for i in 0..data.len() { | ||
| let byte = data.get(i)?; | ||
|
|
||
| // Handle negative sign (only at the start, before any digits) | ||
| if byte == b'-' && !has_digits && !found_decimal { | ||
| is_negative = true; | ||
| continue; | ||
| } | ||
|
|
||
| // Handle decimal point | ||
| if byte == b'.' { | ||
| if found_decimal { | ||
| return None; // Multiple decimal points | ||
| } | ||
| found_decimal = true; | ||
| continue; | ||
| } | ||
|
|
||
| // Handle digits | ||
| if byte >= b'0' && byte <= b'9' { | ||
| has_digits = true; | ||
| let digit = (byte - b'0') as i128; | ||
| result = result.checked_mul(10)?.checked_add(digit)?; | ||
|
|
||
| if found_decimal { | ||
| decimal_places += 1; | ||
| if decimal_places > 8 { | ||
| // Too many decimal places, stop processing | ||
| break; | ||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Decimal parsing fails with more than 8 decimal placesThe |
||
| } else if byte != b' ' { | ||
| // Invalid character (allow spaces to be ignored) | ||
| return None; | ||
| } | ||
| } | ||
|
|
||
| // Must have at least one digit to be valid | ||
| if !has_digits { | ||
| return None; | ||
| } | ||
|
|
||
| // Scale to 8 decimal places for consistent comparison | ||
| const SCALE_FACTOR: u32 = 8; | ||
| let scale_needed = SCALE_FACTOR.saturating_sub(decimal_places); | ||
|
|
||
| for _ in 0..scale_needed { | ||
| result = result.checked_mul(10)?; | ||
| } | ||
|
|
||
| if is_negative { | ||
| result = -result; | ||
| } | ||
|
|
||
| Some(result) | ||
| } | ||
|
|
||
| /// Batch verification of multiple proofs for efficiency. | ||
| /// | ||
| /// # Arguments | ||
|
|
||
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.
Bug: XDR decoder contradicts comment about type discriminant handling
The comment at line 246 explicitly states "Or Type 1 (SCV_VOID which we treat as false)" but the code at lines 261-264 returns
truefortypeDiscriminant == 1. This is a direct contradiction between the documented behavior and the implementation. If type 1 is actuallySCV_VOID(as the comment states and per Stellar's XDR spec), returningtruefor it could cause void/empty contract responses to be incorrectly interpreted as successful verification results.