Skip to content

Commit

Permalink
feat: Implemented EIP-55 address checksum calculation and verification
Browse files Browse the repository at this point in the history
  • Loading branch information
orlowskilp committed Oct 24, 2024
1 parent 137a5ec commit 48b24d0
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 12 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ exclude = [
]

[dependencies]
hex = "0.4.3"
sha3 = "0.10.8"
secp256k1 = { version = "0.29.0", features = ["recovery"] }
rlp = "0.5.2"
Expand Down
96 changes: 84 additions & 12 deletions src/evm_account/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use std::{
fmt::{Debug, Write},
io::{Error, ErrorKind},
io::{self, Error, ErrorKind},
string::String,
};

use hex;
use rlp::{Encodable, RlpStream};
use serde::{Deserialize, Deserializer, Serialize};
use sha3::{Digest, Keccak256};

/// Implementation of access list with necessary encoding and serialization logic.
pub mod access_list;
Expand All @@ -20,6 +22,7 @@ use crate::evm_account::{Keccak256Digest, SignatureComponent};
use access_list::Access;

const HEX_PREFIX: &str = "0x";
const HEX_RADIX: u32 = 16;
const ADDRESS_LENGTH: usize = 20;
// Maximum transaction type value (see EIP-2718).
const MAX_TX_TYPE_ID: u8 = 0x7f;
Expand Down Expand Up @@ -140,7 +143,6 @@ where
}

fn hex_data_string_to_bytes(hex_data: &str) -> Result<Vec<u8>, Error> {
const HEX_RADIX: u32 = 16;
const STEP_BY: usize = 2;

let hex_data = hex_data.trim_start_matches(HEX_PREFIX);
Expand All @@ -165,8 +167,47 @@ where
})
}

fn _validate_address_checksum(_address: &str) -> bool {
todo!("Validate recipient address")
fn _compute_address_checksum(address: &str) -> Result<String, io::Error> {
let address_ascii_lowercase = address.trim_start_matches(HEX_PREFIX).to_ascii_lowercase();

// Compute the hash of the address and represent it as a string of hex digits
let mut hasher = Keccak256::new();
hasher.update(address_ascii_lowercase.clone());
let hex_hash = hex::encode(hasher.finalize());

let mut address_checksum = HEX_PREFIX.to_string();

for (i, ch) in address_ascii_lowercase.chars().enumerate() {
address_checksum.push(match ch {
'0'..='9' => ch,
'a'..='f' => {
let hex_hash_nibble = u8::from_str_radix(&hex_hash[i..i + 1], HEX_RADIX)
.expect("Invalid hex digit in address hash: This was not supposed to happen!");
if hex_hash_nibble > 7 {
ch.to_ascii_uppercase()
} else {
ch
}
}
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid character in address",
))
}
});
}

Ok(address_checksum)
}

fn _validate_address_checksum(address: &str) -> bool {
// TODO: Figure out how to differentiate invalid checksum from other errors
// if this is even necessary, and how to treat all lowercase addresses
match _compute_address_checksum(address) {
Ok(checksum) => checksum == address,
Err(_) => false,
}
}

fn deserialize_address_string<'de, D>(deserializer: D) -> Result<AccountAddress, D::Error>
Expand Down Expand Up @@ -214,7 +255,9 @@ where
mod unit_tests {
use super::*;

const TEST_ADDR_STR: &str = "0xa9d89186cAA663C8Ef0352Fd1Db3596280625573";
const TEST_ADDR_STR_1: &str = "0xa9d89186cAA663C8Ef0352Fd1Db3596280625573";
const TEST_ADDR_STR_2: &str = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed";
const TEST_ADDR_STR_3: &str = "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359";

const TEST_ADDR_BYTES: AccountAddress = [
0xa9, 0xd8, 0x91, 0x86, 0xca, 0xa6, 0x63, 0xc8, 0xef, 0x03, 0x52, 0xfd, 0x1d, 0xb3, 0x59,
Expand All @@ -223,7 +266,7 @@ mod unit_tests {

#[test]
fn evm_address_to_bytes_test() {
let input = TEST_ADDR_STR;
let input = TEST_ADDR_STR_1;
let left = TEST_ADDR_BYTES.to_vec();

let right = hex_data_string_to_bytes(input).unwrap();
Expand All @@ -232,18 +275,47 @@ mod unit_tests {
}

#[test]
fn validate_recipient_address_test_succeed() {
let input = TEST_ADDR_STR;
fn validate_recipient_address_test_1_succeed() {
let input = TEST_ADDR_STR_1;

assert!(_validate_address_checksum(input));
}

#[test]
#[should_panic]
fn validate_recipient_address_test_fail() {
// TODO: Provide invalid address
let input = "";
fn validate_recipient_address_test_2_succeed() {
let input = TEST_ADDR_STR_2;

assert!(_validate_address_checksum(input));
}

#[test]
fn validate_recipient_address_test_3_succeed() {
let input = TEST_ADDR_STR_3;

assert!(_validate_address_checksum(input));
}

#[test]
#[should_panic]
fn validate_recipient_address_test_1_fail() {
let input = TEST_ADDR_STR_1.to_ascii_lowercase();

assert!(_validate_address_checksum(&input));
}

#[test]
#[should_panic]
fn validate_recipient_address_test_2_fail() {
let input = TEST_ADDR_STR_2.to_ascii_lowercase();

assert!(_validate_address_checksum(&input));
}

#[test]
#[should_panic]
fn validate_recipient_address_test_3_fail() {
let input = TEST_ADDR_STR_3.to_ascii_lowercase();

assert!(_validate_address_checksum(&input));
}
}

0 comments on commit 48b24d0

Please sign in to comment.