1use secp256k1::ecdsa::RecoverableSignature;
2use secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
12use sha2::{Digest as Sha256Digest, Sha256};
13use sha3::Keccak256;
14
15use crate::errors::O2Error;
16
17const SECP256K1_ORDER_HALF: [u8; 32] = [
19 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
20 0x5D, 0x57, 0x6E, 0x73, 0x57, 0xA4, 0x50, 0x1D, 0xDF, 0xE9, 0x2F, 0x46, 0x68, 0x1B, 0x20, 0xA0,
21];
22
23const SECP256K1_ORDER: [u8; 32] = [
25 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
26 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41,
27];
28
29#[derive(Debug, Clone)]
31pub struct Wallet {
32 pub private_key: [u8; 32],
33 pub public_key: [u8; 65],
34 pub b256_address: [u8; 32],
35}
36
37#[derive(Debug, Clone)]
39pub struct EvmWallet {
40 pub private_key: [u8; 32],
41 pub public_key: [u8; 65],
42 pub evm_address: [u8; 20],
43 pub b256_address: [u8; 32],
44}
45
46pub fn generate_keypair() -> Result<Wallet, O2Error> {
49 let secp = Secp256k1::new();
50 let mut rng = rand::thread_rng();
51 let (secret_key, public_key) = secp.generate_keypair(&mut rng);
52
53 let pubkey_bytes = public_key.serialize_uncompressed();
54 let address = Sha256::digest(&pubkey_bytes[1..65]);
55
56 Ok(Wallet {
57 private_key: secret_key.secret_bytes(),
58 public_key: pubkey_bytes,
59 b256_address: address.into(),
60 })
61}
62
63pub fn generate_evm_keypair() -> Result<EvmWallet, O2Error> {
67 let secp = Secp256k1::new();
68 let mut rng = rand::thread_rng();
69 let (secret_key, public_key) = secp.generate_keypair(&mut rng);
70
71 let pubkey_bytes = public_key.serialize_uncompressed();
72 let keccak_hash = Keccak256::digest(&pubkey_bytes[1..65]);
73
74 let mut evm_address = [0u8; 20];
75 evm_address.copy_from_slice(&keccak_hash[12..32]);
76
77 let mut b256_address = [0u8; 32];
78 b256_address[12..32].copy_from_slice(&evm_address);
79
80 Ok(EvmWallet {
81 private_key: secret_key.secret_bytes(),
82 public_key: pubkey_bytes,
83 evm_address,
84 b256_address,
85 })
86}
87
88pub fn load_wallet(private_key: &[u8; 32]) -> Result<Wallet, O2Error> {
90 let secp = Secp256k1::new();
91 let secret_key = SecretKey::from_slice(private_key)
92 .map_err(|e| O2Error::CryptoError(format!("Invalid private key: {e}")))?;
93 let public_key = PublicKey::from_secret_key(&secp, &secret_key);
94 let pubkey_bytes = public_key.serialize_uncompressed();
95 let address = Sha256::digest(&pubkey_bytes[1..65]);
96
97 Ok(Wallet {
98 private_key: *private_key,
99 public_key: pubkey_bytes,
100 b256_address: address.into(),
101 })
102}
103
104pub fn load_evm_wallet(private_key: &[u8; 32]) -> Result<EvmWallet, O2Error> {
106 let secp = Secp256k1::new();
107 let secret_key = SecretKey::from_slice(private_key)
108 .map_err(|e| O2Error::CryptoError(format!("Invalid private key: {e}")))?;
109 let public_key = PublicKey::from_secret_key(&secp, &secret_key);
110 let pubkey_bytes = public_key.serialize_uncompressed();
111 let keccak_hash = Keccak256::digest(&pubkey_bytes[1..65]);
112
113 let mut evm_address = [0u8; 20];
114 evm_address.copy_from_slice(&keccak_hash[12..32]);
115
116 let mut b256_address = [0u8; 32];
117 b256_address[12..32].copy_from_slice(&evm_address);
118
119 Ok(EvmWallet {
120 private_key: *private_key,
121 public_key: pubkey_bytes,
122 evm_address,
123 b256_address,
124 })
125}
126
127fn gt_be(a: &[u8; 32], b: &[u8; 32]) -> bool {
129 for i in 0..32 {
130 if a[i] > b[i] {
131 return true;
132 }
133 if a[i] < b[i] {
134 return false;
135 }
136 }
137 false
138}
139
140fn negate_s(s: &[u8; 32]) -> [u8; 32] {
143 let mut result = [0u8; 32];
144 let mut borrow: u16 = 0;
145 for i in (0..32).rev() {
146 let diff = SECP256K1_ORDER[i] as u16 - s[i] as u16 - borrow;
147 result[i] = diff as u8;
148 borrow = if diff > 255 { 1 } else { 0 };
149 }
150 result
151}
152
153pub fn fuel_compact_sign(private_key: &[u8; 32], digest: &[u8; 32]) -> Result<[u8; 64], O2Error> {
158 let secp = Secp256k1::new();
159 let secret_key = SecretKey::from_slice(private_key)
160 .map_err(|e| O2Error::CryptoError(format!("Invalid private key: {e}")))?;
161 let message = Message::from_digest(*digest);
162
163 let recoverable_sig: RecoverableSignature = secp.sign_ecdsa_recoverable(&message, &secret_key);
164 let (rec_id, compact) = recoverable_sig.serialize_compact();
165 let mut recovery_id = rec_id.to_i32() as u8;
166
167 let mut r = [0u8; 32];
168 let mut s = [0u8; 32];
169 r.copy_from_slice(&compact[0..32]);
170 s.copy_from_slice(&compact[32..64]);
171
172 if gt_be(&s, &SECP256K1_ORDER_HALF) {
174 s = negate_s(&s);
175 recovery_id ^= 1;
176 }
177
178 s[0] = (recovery_id << 7) | (s[0] & 0x7F);
180
181 let mut result = [0u8; 64];
182 result[0..32].copy_from_slice(&r);
183 result[32..64].copy_from_slice(&s);
184 Ok(result)
185}
186
187pub fn personal_sign(private_key: &[u8; 32], message: &[u8]) -> Result<[u8; 64], O2Error> {
191 let prefix = b"\x19Fuel Signed Message:\n";
192 let length_str = message.len().to_string();
193
194 let mut hasher = Sha256::new();
195 hasher.update(prefix);
196 hasher.update(length_str.as_bytes());
197 hasher.update(message);
198 let digest: [u8; 32] = hasher.finalize().into();
199
200 fuel_compact_sign(private_key, &digest)
201}
202
203pub fn raw_sign(private_key: &[u8; 32], message: &[u8]) -> Result<[u8; 64], O2Error> {
206 let digest: [u8; 32] = Sha256::digest(message).into();
207 fuel_compact_sign(private_key, &digest)
208}
209
210pub fn evm_personal_sign(private_key: &[u8; 32], message: &[u8]) -> Result<[u8; 64], O2Error> {
214 let prefix = format!("\x19Ethereum Signed Message:\n{}", message.len());
215
216 let mut hasher = Keccak256::new();
217 hasher.update(prefix.as_bytes());
218 hasher.update(message);
219 let digest: [u8; 32] = hasher.finalize().into();
220
221 fuel_compact_sign(private_key, &digest)
222}
223
224pub trait SignableWallet {
228 fn b256_address(&self) -> &[u8; 32];
230 fn personal_sign(&self, message: &[u8]) -> Result<[u8; 64], O2Error>;
235}
236
237impl SignableWallet for Wallet {
238 fn b256_address(&self) -> &[u8; 32] {
239 &self.b256_address
240 }
241 fn personal_sign(&self, message: &[u8]) -> Result<[u8; 64], O2Error> {
242 personal_sign(&self.private_key, message)
243 }
244}
245
246impl SignableWallet for EvmWallet {
247 fn b256_address(&self) -> &[u8; 32] {
248 &self.b256_address
249 }
250 fn personal_sign(&self, message: &[u8]) -> Result<[u8; 64], O2Error> {
251 evm_personal_sign(&self.private_key, message)
252 }
253}
254
255pub fn address_from_pubkey(public_key: &[u8; 65]) -> [u8; 32] {
257 Sha256::digest(&public_key[1..65]).into()
258}
259
260pub fn evm_address_from_pubkey(public_key: &[u8; 65]) -> [u8; 20] {
262 let hash = Keccak256::digest(&public_key[1..65]);
263 let mut addr = [0u8; 20];
264 addr.copy_from_slice(&hash[12..32]);
265 addr
266}
267
268pub fn to_hex_string(bytes: &[u8]) -> String {
270 format!("0x{}", hex::encode(bytes))
271}
272
273pub fn parse_hex_32(s: &str) -> Result<[u8; 32], O2Error> {
275 let s = s.strip_prefix("0x").unwrap_or(s);
276 let bytes = hex::decode(s).map_err(|e| O2Error::CryptoError(format!("Invalid hex: {e}")))?;
277 if bytes.len() != 32 {
278 return Err(O2Error::CryptoError(format!(
279 "Expected 32 bytes, got {}",
280 bytes.len()
281 )));
282 }
283 let mut result = [0u8; 32];
284 result.copy_from_slice(&bytes);
285 Ok(result)
286}