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