1pub mod abi;
8pub mod eips;
9pub mod keystore;
10pub mod proxy;
11pub mod rlp;
12pub mod safe;
13pub mod siwe;
14pub mod smart_wallet;
15pub mod transaction;
16
17#[cfg(feature = "bls")]
22pub use crate::bls;
23
24use crate::error::SignerError;
25use crate::traits;
26use k256::ecdsa::{RecoveryId, Signature as K256Signature, SigningKey, VerifyingKey};
27use sha3::{Digest, Keccak256};
28use subtle::ConstantTimeEq;
29use zeroize::Zeroizing;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34#[must_use]
35pub struct EthereumSignature {
36 pub r: [u8; 32],
38 pub s: [u8; 32],
40 pub v: u64,
43}
44
45impl core::fmt::Display for EthereumSignature {
46 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 write!(f, "0x")?;
48 for byte in &self.r {
49 write!(f, "{byte:02x}")?;
50 }
51 for byte in &self.s {
52 write!(f, "{byte:02x}")?;
53 }
54 write!(f, "{:x}", self.v)
55 }
56}
57
58impl EthereumSignature {
59 pub fn to_bytes(&self) -> [u8; 65] {
63 let mut out = [0u8; 65];
64 out[..32].copy_from_slice(&self.r);
65 out[32..64].copy_from_slice(&self.s);
66 out[64] = self.v as u8;
67 out
68 }
69
70 pub fn to_bytes_eip155(&self) -> Vec<u8> {
72 let mut out = Vec::with_capacity(72);
73 out.extend_from_slice(&self.r);
74 out.extend_from_slice(&self.s);
75 out.extend_from_slice(&self.v.to_be_bytes());
76 out
77 }
78
79 pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignerError> {
81 if bytes.len() != 65 {
82 return Err(SignerError::InvalidSignature(format!(
83 "expected 65 bytes, got {}",
84 bytes.len()
85 )));
86 }
87 let mut r = [0u8; 32];
88 let mut s = [0u8; 32];
89 r.copy_from_slice(&bytes[..32]);
90 s.copy_from_slice(&bytes[32..64]);
91 Ok(Self { r, s, v: u64::from(bytes[64]) })
92 }
93
94 pub fn recovery_bit(&self) -> u8 {
97 if self.v >= 35 {
98 ((self.v - 35) % 2) as u8
100 } else {
101 (self.v.wrapping_sub(27) & 1) as u8
103 }
104 }
105}
106
107pub struct EthereumSigner {
112 signing_key: SigningKey,
113}
114
115impl Drop for EthereumSigner {
116 fn drop(&mut self) {
117 }
119}
120
121impl EthereumSigner {
122 pub fn address(&self) -> [u8; 20] {
125 let vk = self.signing_key.verifying_key();
126 let point = vk.to_encoded_point(false);
127 let pubkey_bytes = &point.as_bytes()[1..]; let hash = Keccak256::digest(pubkey_bytes);
129 let mut addr = [0u8; 20];
130 addr.copy_from_slice(&hash[12..]);
131 addr
132 }
133
134 fn sign_digest(&self, digest: &[u8; 32]) -> Result<EthereumSignature, SignerError> {
136 let (sig, rec_id) = self
137 .signing_key
138 .sign_prehash_recoverable(digest)
139 .map_err(|e| SignerError::SigningFailed(e.to_string()))?;
140
141 let mut r_bytes = [0u8; 32];
142 let mut s_bytes = [0u8; 32];
143 let sig_bytes = sig.to_bytes();
144 r_bytes.copy_from_slice(&sig_bytes[..32]);
145 s_bytes.copy_from_slice(&sig_bytes[32..]);
146
147 let mut v = rec_id.to_byte();
149 let sig_normalized = sig.normalize_s();
150 if let Some(normalized) = sig_normalized {
151 let norm_bytes = normalized.to_bytes();
152 s_bytes.copy_from_slice(&norm_bytes[32..]);
153 v ^= 1;
155 }
156
157 Ok(EthereumSignature {
158 r: r_bytes,
159 s: s_bytes,
160 v: 27 + u64::from(v),
161 })
162 }
163
164 pub fn sign_typed_data(
171 &self,
172 domain_separator: &[u8; 32],
173 struct_hash: &[u8; 32],
174 ) -> Result<EthereumSignature, SignerError> {
175 let digest = eip712_hash(domain_separator, struct_hash);
176 self.sign_digest(&digest)
177 }
178
179 pub fn personal_sign(&self, message: &[u8]) -> Result<EthereumSignature, SignerError> {
184 let digest = eip191_hash(message);
185 self.sign_digest(&digest)
186 }
187
188 pub fn sign_with_chain_id(
195 &self,
196 message: &[u8],
197 chain_id: u64,
198 ) -> Result<EthereumSignature, SignerError> {
199 let digest = Keccak256::digest(message);
200 let mut hash = [0u8; 32];
201 hash.copy_from_slice(&digest);
202 self.sign_digest_with_chain_id(&hash, chain_id)
203 }
204
205 pub fn sign_digest_with_chain_id(
207 &self,
208 digest: &[u8; 32],
209 chain_id: u64,
210 ) -> Result<EthereumSignature, SignerError> {
211 let mut sig = self.sign_digest(digest)?;
212 let recovery_bit = sig.v - 27; sig.v = recovery_bit
215 .checked_add(
216 chain_id
217 .checked_mul(2)
218 .ok_or_else(|| SignerError::SigningFailed("chain_id overflow".into()))?,
219 )
220 .and_then(|v| v.checked_add(35))
221 .ok_or_else(|| SignerError::SigningFailed("EIP-155 v overflow".into()))?;
222 Ok(sig)
223 }
224
225 pub fn personal_sign_with_chain_id(
227 &self,
228 message: &[u8],
229 chain_id: u64,
230 ) -> Result<EthereumSignature, SignerError> {
231 let digest = eip191_hash(message);
232 self.sign_digest_with_chain_id(&digest, chain_id)
233 }
234
235 pub fn address_checksum(&self) -> String {
237 eip55_checksum(&self.address())
238 }
239
240 pub fn from_mnemonic(phrase: &str, passphrase: &str, index: u32) -> Result<Self, SignerError> {
261 use crate::hd_key::{DerivationPath, ExtendedPrivateKey};
262 use crate::mnemonic::Mnemonic;
263 use crate::traits::KeyPair;
264
265 let mnemonic = Mnemonic::from_phrase(phrase)?;
266 let seed = mnemonic.to_seed(passphrase);
267 let master = ExtendedPrivateKey::from_seed(&*seed)?;
268 let path = DerivationPath::ethereum(index);
269 let child = master.derive_path(&path)?;
270 let private_key = child.private_key_bytes();
271 Self::from_bytes(&private_key)
272 }
273}
274
275pub fn eip55_checksum(address: &[u8; 20]) -> String {
280 let hex_lower: String = address.iter().map(|b| format!("{b:02x}")).collect();
281 let hash = Keccak256::digest(hex_lower.as_bytes());
282 let mut out = String::with_capacity(42);
283 out.push_str("0x");
284 for (i, c) in hex_lower.chars().enumerate() {
285 let hash_nibble = if i % 2 == 0 {
286 (hash[i / 2] >> 4) & 0x0f
287 } else {
288 hash[i / 2] & 0x0f
289 };
290 if hash_nibble >= 8 {
291 out.extend(c.to_uppercase());
292 } else {
293 out.push(c);
294 }
295 }
296 out
297}
298
299pub fn validate_address(address: &str) -> bool {
305 if address.len() != 42 || !address.starts_with("0x") {
306 return false;
307 }
308 let hex_part = &address[2..];
309 if !hex_part.chars().all(|c| c.is_ascii_hexdigit()) {
311 return false;
312 }
313 let has_upper = hex_part.chars().any(|c| c.is_ascii_uppercase());
315 let has_lower = hex_part.chars().any(|c| c.is_ascii_lowercase());
316 if !has_upper || !has_lower {
317 return true; }
319 let lower = hex_part.to_lowercase();
321 let lower_bytes = lower.as_bytes();
322 let mut bytes = [0u8; 20];
323 for (i, chunk) in lower_bytes.chunks_exact(2).enumerate() {
324 let hi = match chunk[0] {
325 b'0'..=b'9' => chunk[0] - b'0',
326 b'a'..=b'f' => chunk[0] - b'a' + 10,
327 _ => return false,
328 };
329 let lo = match chunk[1] {
330 b'0'..=b'9' => chunk[1] - b'0',
331 b'a'..=b'f' => chunk[1] - b'a' + 10,
332 _ => return false,
333 };
334 bytes[i] = (hi << 4) | lo;
335 }
336 let checksummed = eip55_checksum(&bytes);
337 checksummed == address
338}
339
340pub fn ecrecover(message: &[u8], signature: &EthereumSignature) -> Result<[u8; 20], SignerError> {
345 let digest = Keccak256::digest(message);
346 let mut hash = [0u8; 32];
347 hash.copy_from_slice(&digest);
348 ecrecover_digest(&hash, signature)
349}
350
351pub fn ecrecover_digest(
353 digest: &[u8; 32],
354 signature: &EthereumSignature,
355) -> Result<[u8; 20], SignerError> {
356 let rec_id = RecoveryId::try_from(signature.recovery_bit()).map_err(|_| {
357 SignerError::InvalidSignature("invalid recovery id".into())
358 })?;
359
360 let mut sig_bytes = [0u8; 64];
361 sig_bytes[..32].copy_from_slice(&signature.r);
362 sig_bytes[32..].copy_from_slice(&signature.s);
363 let sig = K256Signature::from_bytes((&sig_bytes).into())
364 .map_err(|e| SignerError::InvalidSignature(e.to_string()))?;
365
366 let recovered_key = VerifyingKey::recover_from_prehash(digest, &sig, rec_id)
367 .map_err(|e| SignerError::InvalidSignature(e.to_string()))?;
368
369 let point = recovered_key.to_encoded_point(false);
370 let pubkey_bytes = &point.as_bytes()[1..];
371 let hash = Keccak256::digest(pubkey_bytes);
372 let mut addr = [0u8; 20];
373 addr.copy_from_slice(&hash[12..]);
374 Ok(addr)
375}
376
377pub fn eip191_hash(message: &[u8]) -> [u8; 32] {
382 use core::fmt::Write;
383 let mut prefix_buf = [0u8; 64];
386 let prefix_len = {
387 struct SliceWriter<'a> {
388 buf: &'a mut [u8],
389 pos: usize,
390 }
391 impl<'a> Write for SliceWriter<'a> {
392 fn write_str(&mut self, s: &str) -> core::fmt::Result {
393 let bytes = s.as_bytes();
394 let end = self.pos + bytes.len();
395 if end > self.buf.len() {
396 return Err(core::fmt::Error);
397 }
398 self.buf[self.pos..end].copy_from_slice(bytes);
399 self.pos = end;
400 Ok(())
401 }
402 }
403 let mut w = SliceWriter {
404 buf: &mut prefix_buf,
405 pos: 0,
406 };
407 let _ = write!(w, "\x19Ethereum Signed Message:\n{}", message.len());
409 w.pos
410 };
411 let mut hasher = Keccak256::new();
412 hasher.update(&prefix_buf[..prefix_len]);
413 hasher.update(message);
414 let mut hash = [0u8; 32];
415 hash.copy_from_slice(&hasher.finalize());
416 hash
417}
418
419pub struct Eip712Domain<'a> {
436 pub name: &'a str,
438 pub version: &'a str,
440 pub chain_id: u64,
442 pub verifying_contract: &'a [u8; 20],
444}
445
446impl<'a> Eip712Domain<'a> {
447 pub fn type_hash() -> [u8; 32] {
450 let mut hash = [0u8; 32];
451 hash.copy_from_slice(&Keccak256::digest(
452 b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)",
453 ));
454 hash
455 }
456
457 pub fn separator(&self) -> [u8; 32] {
461 let type_hash = Self::type_hash();
462 let name_hash = Keccak256::digest(self.name.as_bytes());
463 let version_hash = Keccak256::digest(self.version.as_bytes());
464
465 let mut encoded = [0u8; 160];
467 encoded[0..32].copy_from_slice(&type_hash);
468 encoded[32..64].copy_from_slice(&name_hash);
469 encoded[64..96].copy_from_slice(&version_hash);
470
471 encoded[120..128].copy_from_slice(&self.chain_id.to_be_bytes());
473
474 encoded[140..160].copy_from_slice(self.verifying_contract);
476
477 let mut hash = [0u8; 32];
478 hash.copy_from_slice(&Keccak256::digest(encoded));
479 hash
480 }
481}
482
483pub fn eip712_hash(domain_separator: &[u8; 32], struct_hash: &[u8; 32]) -> [u8; 32] {
486 let mut payload = [0u8; 66]; payload[0] = 0x19;
488 payload[1] = 0x01;
489 payload[2..34].copy_from_slice(domain_separator);
490 payload[34..66].copy_from_slice(struct_hash);
491
492 let mut hash = [0u8; 32];
493 hash.copy_from_slice(&Keccak256::digest(payload));
494 hash
495}
496
497impl traits::Signer for EthereumSigner {
498 type Signature = EthereumSignature;
499 type Error = SignerError;
500
501 fn sign(&self, message: &[u8]) -> Result<EthereumSignature, SignerError> {
502 let digest = Keccak256::digest(message);
503 let mut hash = [0u8; 32];
504 hash.copy_from_slice(&digest);
505 self.sign_digest(&hash)
506 }
507
508 fn sign_prehashed(&self, digest: &[u8]) -> Result<EthereumSignature, SignerError> {
509 if digest.len() != 32 {
510 return Err(SignerError::InvalidHashLength {
511 expected: 32,
512 got: digest.len(),
513 });
514 }
515 let mut hash = [0u8; 32];
516 hash.copy_from_slice(digest);
517 self.sign_digest(&hash)
518 }
519
520 fn public_key_bytes(&self) -> Vec<u8> {
521 self.signing_key
522 .verifying_key()
523 .to_encoded_point(true)
524 .as_bytes()
525 .to_vec()
526 }
527
528 fn public_key_bytes_uncompressed(&self) -> Vec<u8> {
529 self.signing_key
530 .verifying_key()
531 .to_encoded_point(false)
532 .as_bytes()
533 .to_vec()
534 }
535}
536
537impl traits::KeyPair for EthereumSigner {
538 fn generate() -> Result<Self, SignerError> {
539 let mut key_bytes = zeroize::Zeroizing::new([0u8; 32]);
540 crate::security::secure_random(&mut *key_bytes)?;
541 let signing_key = SigningKey::from_bytes((&*key_bytes).into())
542 .map_err(|e| SignerError::InvalidPrivateKey(e.to_string()))?;
543 Ok(Self { signing_key })
544 }
545
546 fn from_bytes(private_key: &[u8]) -> Result<Self, SignerError> {
547 if private_key.len() != 32 {
548 return Err(SignerError::InvalidPrivateKey(format!(
549 "expected 32 bytes, got {}",
550 private_key.len()
551 )));
552 }
553 let signing_key = SigningKey::from_bytes(private_key.into())
554 .map_err(|e| SignerError::InvalidPrivateKey(e.to_string()))?;
555 Ok(Self { signing_key })
556 }
557
558 fn private_key_bytes(&self) -> Zeroizing<Vec<u8>> {
559 Zeroizing::new(self.signing_key.to_bytes().to_vec())
560 }
561}
562
563pub struct EthereumVerifier {
565 verifying_key: VerifyingKey,
566}
567
568impl EthereumVerifier {
569 pub fn from_public_key_bytes(bytes: &[u8]) -> Result<Self, SignerError> {
571 let verifying_key = VerifyingKey::from_sec1_bytes(bytes)
572 .map_err(|e| SignerError::InvalidPublicKey(e.to_string()))?;
573 Ok(Self { verifying_key })
574 }
575
576 fn verify_digest(
578 &self,
579 digest: &[u8; 32],
580 signature: &EthereumSignature,
581 ) -> Result<bool, SignerError> {
582 let rec_id = RecoveryId::from_byte(signature.recovery_bit())
583 .ok_or_else(|| SignerError::InvalidSignature("invalid recovery id".into()))?;
584
585 let mut sig_bytes = [0u8; 64];
586 sig_bytes[..32].copy_from_slice(&signature.r);
587 sig_bytes[32..].copy_from_slice(&signature.s);
588
589 let k256_sig = K256Signature::from_bytes((&sig_bytes).into())
590 .map_err(|e| SignerError::InvalidSignature(e.to_string()))?;
591
592 let recovered = VerifyingKey::recover_from_prehash(digest, &k256_sig, rec_id)
593 .map_err(|e| SignerError::InvalidSignature(e.to_string()))?;
594
595 Ok(bool::from(
596 recovered
597 .to_encoded_point(true)
598 .as_bytes()
599 .ct_eq(self.verifying_key.to_encoded_point(true).as_bytes()),
600 ))
601 }
602}
603
604impl traits::Verifier for EthereumVerifier {
605 type Signature = EthereumSignature;
606 type Error = SignerError;
607
608 fn verify(&self, message: &[u8], signature: &EthereumSignature) -> Result<bool, SignerError> {
609 let digest = Keccak256::digest(message);
610 let mut hash = [0u8; 32];
611 hash.copy_from_slice(&digest);
612 self.verify_digest(&hash, signature)
613 }
614
615 fn verify_prehashed(
616 &self,
617 digest: &[u8],
618 signature: &EthereumSignature,
619 ) -> Result<bool, SignerError> {
620 if digest.len() != 32 {
621 return Err(SignerError::InvalidHashLength {
622 expected: 32,
623 got: digest.len(),
624 });
625 }
626 let mut hash = [0u8; 32];
627 hash.copy_from_slice(digest);
628 self.verify_digest(&hash, signature)
629 }
630}
631
632impl EthereumVerifier {
633 pub fn verify_typed_data(
637 &self,
638 domain_separator: &[u8; 32],
639 struct_hash: &[u8; 32],
640 signature: &EthereumSignature,
641 ) -> Result<bool, SignerError> {
642 let digest = eip712_hash(domain_separator, struct_hash);
643 self.verify_digest(&digest, signature)
644 }
645
646 pub fn verify_personal_sign(
650 &self,
651 message: &[u8],
652 signature: &EthereumSignature,
653 ) -> Result<bool, SignerError> {
654 let digest = eip191_hash(message);
655 self.verify_digest(&digest, signature)
656 }
657}
658
659#[cfg(test)]
660#[allow(clippy::unwrap_used, clippy::expect_used)]
661mod tests {
662 use super::*;
663 use crate::traits::{KeyPair, Signer, Verifier};
664
665 #[test]
666 fn test_generate_keypair() {
667 let signer = EthereumSigner::generate().unwrap();
668 let pubkey = signer.public_key_bytes();
669 assert_eq!(pubkey.len(), 33); }
671
672 #[test]
673 fn test_from_bytes_roundtrip() {
674 let signer = EthereumSigner::generate().unwrap();
675 let key_bytes = signer.private_key_bytes();
676 let restored = EthereumSigner::from_bytes(&key_bytes).unwrap();
677 assert_eq!(signer.public_key_bytes(), restored.public_key_bytes());
678 }
679
680 #[test]
681 fn test_sign_verify_roundtrip() {
682 let signer = EthereumSigner::generate().unwrap();
683 let msg = b"hello ethereum";
684 let sig = signer.sign(msg).unwrap();
685 let verifier = EthereumVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
686 assert!(verifier.verify(msg, &sig).unwrap());
687 }
688
689 #[test]
690 fn test_keccak256_hash() {
691 let hash = Keccak256::digest(b"hello");
692 assert_eq!(
693 hex::encode(hash),
694 "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"
695 );
696 }
697
698 #[test]
699 fn test_low_s_enforcement() {
700 use k256::elliptic_curve::Curve;
702 let signer = EthereumSigner::generate().unwrap();
703 let order = k256::Secp256k1::ORDER;
704 let half_n = order.shr_vartime(1);
705
706 for i in 0u32..50 {
707 let msg = format!("test message {}", i);
708 let sig = signer.sign(msg.as_bytes()).unwrap();
709 let s = k256::U256::from_be_slice(&sig.s);
710 assert!(s <= half_n, "S value not low-S normalized");
711 }
712 }
713
714 #[test]
715 fn test_recovery_id() {
716 let signer = EthereumSigner::generate().unwrap();
717 let sig = signer.sign(b"test recovery").unwrap();
718 assert!(sig.v == 27 || sig.v == 28);
719 }
720
721 #[test]
722 fn test_address_derivation() {
723 let privkey =
725 hex::decode("4c0883a69102937d6231471b5dbb6204fe512961708279f3c6f2b54729a0f29e")
726 .unwrap();
727 let signer = EthereumSigner::from_bytes(&privkey).unwrap();
728 let addr = signer.address();
729 assert_eq!(
730 hex::encode(addr).to_lowercase(),
731 "0d77521fa96e4c41e4190cab2dbe0d613c4afa9d"
732 );
733 }
734
735 #[test]
736 fn test_known_vector_eth() {
737 let privkey =
739 hex::decode("4c0883a69102937d6231471b5dbb6204fe512961708279f3c6f2b54729a0f29e")
740 .unwrap();
741 let signer = EthereumSigner::from_bytes(&privkey).unwrap();
742 let sig = signer.sign(b"hello").unwrap();
743 let verifier = EthereumVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
744 assert!(verifier.verify(b"hello", &sig).unwrap());
745 assert!(sig.v == 27 || sig.v == 28);
747 }
748
749 #[test]
750 fn test_invalid_privkey_rejected() {
751 assert!(EthereumSigner::from_bytes(&[0u8; 32]).is_err());
753 assert!(EthereumSigner::from_bytes(&[1u8; 31]).is_err());
755 assert!(EthereumSigner::from_bytes(&[1u8; 33]).is_err());
757 }
758
759 #[test]
760 fn test_tampered_sig_fails() {
761 let signer = EthereumSigner::generate().unwrap();
762 let sig = signer.sign(b"test tamper").unwrap();
763 let verifier = EthereumVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
764
765 let mut tampered = sig.clone();
767 tampered.r[0] ^= 0xff;
768 let result = verifier.verify(b"test tamper", &tampered);
770 assert!(result.is_err() || !result.unwrap());
771 }
772
773 #[test]
774 fn test_wrong_pubkey_fails() {
775 let signer1 = EthereumSigner::generate().unwrap();
776 let signer2 = EthereumSigner::generate().unwrap();
777 let sig = signer1.sign(b"test wrong key").unwrap();
778 let verifier =
779 EthereumVerifier::from_public_key_bytes(&signer2.public_key_bytes()).unwrap();
780 let result = verifier.verify(b"test wrong key", &sig).unwrap();
781 assert!(!result);
782 }
783
784 #[test]
785 fn test_empty_message() {
786 let signer = EthereumSigner::generate().unwrap();
787 let sig = signer.sign(b"").unwrap();
788 let verifier = EthereumVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
789 assert!(verifier.verify(b"", &sig).unwrap());
790 }
791
792 #[test]
793 fn test_large_message() {
794 let signer = EthereumSigner::generate().unwrap();
795 let msg = vec![0xab_u8; 1_000_000]; let sig = signer.sign(&msg).unwrap();
797 let verifier = EthereumVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
798 assert!(verifier.verify(&msg, &sig).unwrap());
799 }
800
801 #[test]
802 fn test_sign_prehashed_roundtrip() {
803 let signer = EthereumSigner::generate().unwrap();
804 let msg = b"prehash test";
805 let digest = Keccak256::digest(msg);
806
807 let sig_raw = signer.sign(msg).unwrap();
808 let sig_pre = signer.sign_prehashed(&digest).unwrap();
809
810 let verifier = EthereumVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
812 assert!(verifier.verify(msg, &sig_raw).unwrap());
813 assert!(verifier.verify_prehashed(&digest, &sig_pre).unwrap());
814 }
815
816 #[test]
817 fn test_verify_prehashed() {
818 let signer = EthereumSigner::generate().unwrap();
819 let msg = b"verify prehash";
820 let digest = Keccak256::digest(msg);
821 let sig = signer.sign(msg).unwrap();
822 let verifier = EthereumVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
823 assert!(verifier.verify_prehashed(&digest, &sig).unwrap());
824 }
825
826 #[test]
827 fn test_zeroize_on_drop() {
828 let signer = EthereumSigner::generate().unwrap();
829 let key_bytes = signer.private_key_bytes();
830 let _: Zeroizing<Vec<u8>> = key_bytes;
832 drop(signer);
834 let fresh = EthereumSigner::generate().unwrap();
835 let _: Zeroizing<Vec<u8>> = fresh.private_key_bytes();
836 }
837
838 #[test]
839 fn test_signature_bytes_roundtrip() {
840 let signer = EthereumSigner::generate().unwrap();
841 let sig = signer.sign(b"roundtrip").unwrap();
842 let bytes = sig.to_bytes();
843 let restored = EthereumSignature::from_bytes(&bytes).unwrap();
844 assert_eq!(sig.r, restored.r);
845 assert_eq!(sig.s, restored.s);
846 assert_eq!(sig.v, restored.v);
847 }
848
849 #[test]
852 fn test_eip712_domain_type_hash() {
853 let type_hash = Eip712Domain::type_hash();
854 let expected = Keccak256::digest(
856 b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)",
857 );
858 assert_eq!(type_hash[..], expected[..]);
859 }
860
861 #[test]
862 fn test_eip712_domain_separator() {
863 let contract_addr: [u8; 20] = [0xCC; 20];
864 let domain = Eip712Domain {
865 name: "TestDapp",
866 version: "1",
867 chain_id: 1,
868 verifying_contract: &contract_addr,
869 };
870 let sep = domain.separator();
871 assert_eq!(sep.len(), 32);
873 let sep2 = domain.separator();
874 assert_eq!(sep, sep2);
875 }
876
877 #[test]
878 fn test_eip712_hash_prefix() {
879 let domain_sep = [0xAA_u8; 32];
880 let struct_hash = [0xBB_u8; 32];
881 let hash = eip712_hash(&domain_sep, &struct_hash);
882 assert_eq!(hash.len(), 32);
884 let plain_hash = Keccak256::digest(struct_hash);
885 assert_ne!(&hash[..], &plain_hash[..]);
886 }
887
888 #[test]
889 fn test_eip712_sign_verify_roundtrip() {
890 let signer = EthereumSigner::generate().unwrap();
891 let verifier = EthereumVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
892
893 let contract_addr: [u8; 20] = [0xCC; 20];
894 let domain = Eip712Domain {
895 name: "MyDapp",
896 version: "1",
897 chain_id: 1,
898 verifying_contract: &contract_addr,
899 };
900 let domain_sep = domain.separator();
901
902 let struct_hash: [u8; 32] = {
904 let mut h = [0u8; 32];
905 h.copy_from_slice(&Keccak256::digest(b"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"));
906 h
907 };
908
909 let sig = signer.sign_typed_data(&domain_sep, &struct_hash).unwrap();
910 assert!(sig.v == 27 || sig.v == 28);
911
912 assert!(verifier
914 .verify_typed_data(&domain_sep, &struct_hash, &sig)
915 .unwrap());
916 }
917
918 #[test]
919 fn test_eip712_wrong_domain_fails() {
920 let signer = EthereumSigner::generate().unwrap();
921 let verifier = EthereumVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
922
923 let struct_hash = [0xBB_u8; 32];
924 let domain_sep = [0xAA_u8; 32];
925 let sig = signer.sign_typed_data(&domain_sep, &struct_hash).unwrap();
926
927 let wrong_domain = [0xFF_u8; 32];
929 let result = verifier
930 .verify_typed_data(&wrong_domain, &struct_hash, &sig)
931 .unwrap();
932 assert!(!result);
933 }
934
935 #[test]
938 fn test_eip191_sign_verify_roundtrip() {
939 let signer = EthereumSigner::generate().unwrap();
940 let verifier = EthereumVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
941 let msg = b"Hello from chains-sdk!";
942 let sig = signer.personal_sign(msg).unwrap();
943 assert!(sig.v == 27 || sig.v == 28);
944 assert!(verifier.verify_personal_sign(msg, &sig).unwrap());
945 }
946
947 #[test]
948 fn test_eip191_hash_known_vector() {
949 let hash = eip191_hash(b"hello");
951 let expected = Keccak256::digest(b"\x19Ethereum Signed Message:\n5hello");
952 assert_eq!(&hash[..], &expected[..]);
953 }
954
955 #[test]
956 fn test_eip191_wrong_message_fails() {
957 let signer = EthereumSigner::generate().unwrap();
958 let verifier = EthereumVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
959 let sig = signer.personal_sign(b"correct message").unwrap();
960 let result = verifier.verify_personal_sign(b"wrong message", &sig);
961 assert!(result.is_err() || !result.unwrap());
962 }
963
964 #[test]
965 fn test_eip191_differs_from_raw_sign() {
966 let signer = EthereumSigner::generate().unwrap();
967 let verifier = EthereumVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
968 let msg = b"test";
969 let raw_sig = signer.sign(msg).unwrap();
970 let personal_sig = signer.personal_sign(msg).unwrap();
971 assert_ne!(raw_sig.r, personal_sig.r);
973 let result = verifier.verify(msg, &personal_sig).unwrap();
975 assert!(!result);
976 }
977}