1pub mod transaction;
4pub mod witness;
5
6use crate::crypto;
7use crate::error::SignerError;
8use crate::traits;
9use p256::ecdsa::signature::hazmat::PrehashSigner;
10use p256::ecdsa::signature::hazmat::PrehashVerifier;
11use p256::ecdsa::{Signature as P256Signature, SigningKey, VerifyingKey};
12use zeroize::Zeroizing;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[must_use]
18pub struct NeoSignature {
19 #[cfg_attr(feature = "serde", serde(with = "crate::hex_bytes"))]
21 pub bytes: [u8; 64],
22}
23
24impl core::fmt::Display for NeoSignature {
25 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
26 write!(f, "0x")?;
27 for byte in &self.bytes {
28 write!(f, "{byte:02x}")?;
29 }
30 Ok(())
31 }
32}
33
34impl NeoSignature {
35 pub fn to_bytes(&self) -> [u8; 64] {
37 self.bytes
38 }
39
40 pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignerError> {
42 if bytes.len() != 64 {
43 return Err(SignerError::InvalidSignature(format!(
44 "expected 64 bytes, got {}",
45 bytes.len()
46 )));
47 }
48 let mut out = [0u8; 64];
49 out.copy_from_slice(bytes);
50 Ok(Self { bytes: out })
51 }
52}
53
54pub struct NeoSigner {
56 signing_key: SigningKey,
57}
58
59impl NeoSigner {
60 pub fn script_hash(&self) -> [u8; 20] {
65 let pubkey = self.signing_key.verifying_key().to_encoded_point(true);
66 let mut script = Vec::with_capacity(35);
67 script.push(0x21); script.extend_from_slice(pubkey.as_bytes());
69 script.push(0xAC); crypto::hash160(&script)
72 }
73
74 pub fn address(&self) -> String {
78 let hash = self.script_hash();
79 let mut payload = vec![0x17u8]; payload.extend_from_slice(&hash);
81 let checksum = crypto::double_sha256(&payload);
82 payload.extend_from_slice(&checksum[..4]);
83 bs58::encode(payload).into_string()
84 }
85}
86
87pub fn validate_address(address: &str) -> bool {
91 if !address.starts_with('A') {
92 return false;
93 }
94 let decoded = match bs58::decode(address).into_vec() {
95 Ok(d) => d,
96 Err(_) => return false,
97 };
98 if decoded.len() != 25 || decoded[0] != 0x17 {
99 return false;
100 }
101 use subtle::ConstantTimeEq;
102 let checksum = crypto::double_sha256(&decoded[..21]);
103 checksum[..4].ct_eq(&decoded[21..25]).unwrap_u8() == 1
104}
105
106impl NeoSigner {
107 pub fn to_wif(&self) -> Zeroizing<String> {
115 let pk_bytes = self.signing_key.to_bytes();
116 let mut payload = Vec::with_capacity(38);
117 payload.push(0x80); payload.extend_from_slice(&pk_bytes);
119 payload.push(0x01); let checksum = crypto::double_sha256(&payload);
122 payload.extend_from_slice(&checksum[..4]);
123 Zeroizing::new(bs58::encode(payload).into_string())
124 }
125
126 pub fn from_wif(wif: &str) -> Result<Self, SignerError> {
130 let decoded = bs58::decode(wif)
131 .into_vec()
132 .map_err(|_| SignerError::InvalidPrivateKey("invalid Base58".into()))?;
133
134 if decoded.len() != 38 {
135 return Err(SignerError::InvalidPrivateKey(format!(
136 "WIF: expected 38 bytes, got {}",
137 decoded.len()
138 )));
139 }
140
141 if decoded[0] != 0x80 {
142 return Err(SignerError::InvalidPrivateKey(format!(
143 "WIF: version 0x{:02x} != 0x80",
144 decoded[0]
145 )));
146 }
147
148 if decoded[33] != 0x01 {
149 return Err(SignerError::InvalidPrivateKey(
150 "WIF: missing compression flag".into(),
151 ));
152 }
153
154 let checksum = crypto::double_sha256(&decoded[..34]);
156 use subtle::ConstantTimeEq;
157 if decoded[34..38].ct_eq(&checksum[..4]).unwrap_u8() != 1 {
158 return Err(SignerError::InvalidPrivateKey("WIF: bad checksum".into()));
159 }
160
161 use crate::traits::KeyPair;
162 Self::from_bytes(&decoded[1..33])
163 }
164}
165
166impl Drop for NeoSigner {
167 fn drop(&mut self) {
168 }
170}
171
172impl NeoSigner {
173 pub(crate) fn sign_digest(&self, digest: &[u8; 32]) -> Result<NeoSignature, SignerError> {
174 let sig: P256Signature = self
175 .signing_key
176 .sign_prehash(digest)
177 .map_err(|e| SignerError::SigningFailed(e.to_string()))?;
178 let mut bytes = [0u8; 64];
179 bytes.copy_from_slice(&sig.to_bytes());
180 Ok(NeoSignature { bytes })
181 }
182}
183
184impl traits::Signer for NeoSigner {
185 type Signature = NeoSignature;
186 type Error = SignerError;
187
188 fn sign(&self, message: &[u8]) -> Result<NeoSignature, SignerError> {
189 let hash = crypto::sha256(message);
190 self.sign_digest(&hash)
191 }
192
193 fn sign_prehashed(&self, digest: &[u8]) -> Result<NeoSignature, SignerError> {
194 if digest.len() != 32 {
195 return Err(SignerError::InvalidHashLength {
196 expected: 32,
197 got: digest.len(),
198 });
199 }
200 let mut hash = [0u8; 32];
201 hash.copy_from_slice(digest);
202 self.sign_digest(&hash)
203 }
204
205 fn public_key_bytes(&self) -> Vec<u8> {
206 self.signing_key
207 .verifying_key()
208 .to_encoded_point(true)
209 .as_bytes()
210 .to_vec()
211 }
212
213 fn public_key_bytes_uncompressed(&self) -> Vec<u8> {
214 self.signing_key
215 .verifying_key()
216 .to_encoded_point(false)
217 .as_bytes()
218 .to_vec()
219 }
220}
221
222impl traits::KeyPair for NeoSigner {
223 fn generate() -> Result<Self, SignerError> {
224 let mut key_bytes = zeroize::Zeroizing::new([0u8; 32]);
225 crate::security::secure_random(&mut *key_bytes)?;
226 let signing_key = SigningKey::from_bytes((&*key_bytes).into())
227 .map_err(|e| SignerError::InvalidPrivateKey(e.to_string()))?;
228 Ok(Self { signing_key })
229 }
230
231 fn from_bytes(private_key: &[u8]) -> Result<Self, SignerError> {
232 if private_key.len() != 32 {
233 return Err(SignerError::InvalidPrivateKey(format!(
234 "expected 32 bytes, got {}",
235 private_key.len()
236 )));
237 }
238 let signing_key = SigningKey::from_bytes(private_key.into())
239 .map_err(|e| SignerError::InvalidPrivateKey(e.to_string()))?;
240 Ok(Self { signing_key })
241 }
242
243 fn private_key_bytes(&self) -> Zeroizing<Vec<u8>> {
244 Zeroizing::new(self.signing_key.to_bytes().to_vec())
245 }
246}
247
248pub struct NeoVerifier {
250 verifying_key: VerifyingKey,
251}
252
253impl NeoVerifier {
254 pub fn from_public_key_bytes(bytes: &[u8]) -> Result<Self, SignerError> {
256 let verifying_key = VerifyingKey::from_sec1_bytes(bytes)
257 .map_err(|e| SignerError::InvalidPublicKey(e.to_string()))?;
258 Ok(Self { verifying_key })
259 }
260
261 fn verify_digest(
262 &self,
263 digest: &[u8; 32],
264 signature: &NeoSignature,
265 ) -> Result<bool, SignerError> {
266 let sig = P256Signature::from_bytes((&signature.bytes).into())
267 .map_err(|e| SignerError::InvalidSignature(e.to_string()))?;
268 match self.verifying_key.verify_prehash(digest, &sig) {
269 Ok(()) => Ok(true),
270 Err(_) => Ok(false),
271 }
272 }
273}
274
275impl traits::Verifier for NeoVerifier {
276 type Signature = NeoSignature;
277 type Error = SignerError;
278
279 fn verify(&self, message: &[u8], signature: &NeoSignature) -> Result<bool, SignerError> {
280 let hash = crypto::sha256(message);
281 self.verify_digest(&hash, signature)
282 }
283
284 fn verify_prehashed(
285 &self,
286 digest: &[u8],
287 signature: &NeoSignature,
288 ) -> Result<bool, SignerError> {
289 if digest.len() != 32 {
290 return Err(SignerError::InvalidHashLength {
291 expected: 32,
292 got: digest.len(),
293 });
294 }
295 let mut hash = [0u8; 32];
296 hash.copy_from_slice(digest);
297 self.verify_digest(&hash, signature)
298 }
299}
300
301#[cfg(test)]
302#[allow(clippy::unwrap_used, clippy::expect_used)]
303mod tests {
304 use super::*;
305 use crate::traits::{KeyPair, Signer, Verifier};
306
307 #[test]
308 fn test_generate_keypair() {
309 let signer = NeoSigner::generate().unwrap();
310 let pubkey = signer.public_key_bytes();
311 assert_eq!(pubkey.len(), 33); }
313
314 #[test]
315 fn test_from_bytes_roundtrip() {
316 let signer = NeoSigner::generate().unwrap();
317 let restored = NeoSigner::from_bytes(&signer.private_key_bytes()).unwrap();
318 assert_eq!(signer.public_key_bytes(), restored.public_key_bytes());
319 }
320
321 #[test]
322 fn test_sign_verify_roundtrip() {
323 let signer = NeoSigner::generate().unwrap();
324 let sig = signer.sign(b"hello neo").unwrap();
325 let verifier = NeoVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
326 assert!(verifier.verify(b"hello neo", &sig).unwrap());
327 }
328
329 #[test]
330 fn test_p256_not_k256() {
331 let privkey =
333 hex::decode("708309a7449e156b0db70e5b52e606c7e094ed676ce8953bf6c14757c826f590")
334 .unwrap();
335 let neo_signer = NeoSigner::from_bytes(&privkey).unwrap();
336 let neo_pubkey = neo_signer.public_key_bytes();
337 assert_eq!(neo_pubkey.len(), 33);
339 }
340
341 #[test]
342 fn test_neo_serialization() {
343 let signer = NeoSigner::generate().unwrap();
344 let sig = signer.sign(b"serialization test").unwrap();
345 assert_eq!(sig.bytes.len(), 64); }
347
348 #[test]
350 fn test_known_vector_p256_fips() {
351 let privkey =
352 hex::decode("708309a7449e156b0db70e5b52e606c7e094ed676ce8953bf6c14757c826f590")
353 .unwrap();
354 let signer = NeoSigner::from_bytes(&privkey).unwrap();
355 let sig = signer.sign(b"NIST P-256 test").unwrap();
357 let verifier = NeoVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
358 assert!(verifier.verify(b"NIST P-256 test", &sig).unwrap());
359 }
360
361 #[test]
362 fn test_invalid_privkey_rejected() {
363 assert!(NeoSigner::from_bytes(&[0u8; 32]).is_err());
364 assert!(NeoSigner::from_bytes(&[1u8; 31]).is_err());
365 }
366
367 #[test]
368 fn test_tampered_sig_fails() {
369 let signer = NeoSigner::generate().unwrap();
370 let sig = signer.sign(b"tamper").unwrap();
371 let verifier = NeoVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
372 let mut tampered = sig.clone();
373 tampered.bytes[0] ^= 0xff;
374 let result = verifier.verify(b"tamper", &tampered);
375 assert!(result.is_err() || !result.unwrap());
376 }
377
378 #[test]
379 fn test_sign_prehashed_roundtrip() {
380 let signer = NeoSigner::generate().unwrap();
381 let msg = b"prehash neo";
382 let digest = crate::crypto::sha256(msg);
383 let sig = signer.sign_prehashed(&digest).unwrap();
384 let verifier = NeoVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
385 assert!(verifier.verify_prehashed(&digest, &sig).unwrap());
386 }
387
388 #[test]
389 fn test_zeroize_on_drop() {
390 let signer = NeoSigner::generate().unwrap();
391 let _: Zeroizing<Vec<u8>> = signer.private_key_bytes();
392 drop(signer);
393 }
394
395 #[test]
396 fn test_empty_message() {
397 let signer = NeoSigner::generate().unwrap();
398 let sig = signer.sign(b"").unwrap();
399 let verifier = NeoVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
400 assert!(verifier.verify(b"", &sig).unwrap());
401 }
402
403 #[test]
406 fn test_neo_address_format() {
407 let signer = NeoSigner::generate().unwrap();
408 let addr = signer.address();
409 assert!(addr.starts_with('A'), "NEO address must start with 'A'");
410 assert_eq!(addr.len(), 34); assert!(validate_address(&addr));
412 }
413
414 #[test]
415 fn test_neo_script_hash_length() {
416 let signer = NeoSigner::generate().unwrap();
417 let hash = signer.script_hash();
418 assert_eq!(hash.len(), 20);
419 }
420
421 #[test]
422 fn test_neo_address_validation_edges() {
423 assert!(!validate_address(""));
424 assert!(!validate_address("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH")); assert!(!validate_address("AINVALID"));
426 }
427
428 #[test]
429 fn test_neo_address_deterministic() {
430 let signer = NeoSigner::generate().unwrap();
431 let addr1 = signer.address();
432 let addr2 = signer.address();
433 assert_eq!(addr1, addr2);
434 }
435
436 #[test]
439 fn test_neo_wif_roundtrip() {
440 let signer = NeoSigner::generate().unwrap();
441 let wif = signer.to_wif();
442 let restored = NeoSigner::from_wif(&wif).unwrap();
443 assert_eq!(signer.public_key_bytes(), restored.public_key_bytes());
444 }
445
446 #[test]
447 fn test_neo_wif_format() {
448 let signer = NeoSigner::generate().unwrap();
449 let wif = signer.to_wif();
450 assert!(
452 wif.starts_with('K') || wif.starts_with('L'),
453 "WIF should start with K or L, got: {}",
454 &wif[..1]
455 );
456 }
457
458 #[test]
459 fn test_neo_wif_invalid_checksum() {
460 let signer = NeoSigner::generate().unwrap();
461 let wif = signer.to_wif();
462 let mut bad = wif.chars().collect::<Vec<_>>();
464 let last = bad.len() - 1;
465 bad[last] = if bad[last] == 'A' { 'B' } else { 'A' };
466 let bad_wif: String = bad.into_iter().collect();
467 assert!(NeoSigner::from_wif(&bad_wif).is_err());
468 }
469
470 #[test]
471 fn test_neo_wif_invalid_base58() {
472 assert!(NeoSigner::from_wif("0OIl").is_err()); }
474
475 #[test]
481 fn test_neo_wif_known_vector() {
482 let privkey =
483 hex::decode("c7134d6fd8e73d819e82755c64c93788d8db0961929e025a53363c4cc02a6962")
484 .unwrap();
485 let signer = NeoSigner::from_bytes(&privkey).unwrap();
486 let wif = signer.to_wif();
487 assert_eq!(
488 wif.as_str(),
489 "L3tgppXLgdaeqSGSFw1Go3skBiy8vQAM7YMXvTHsKQtE16PBncSU",
490 "WIF must match neo.org official test vector"
491 );
492
493 let restored =
495 NeoSigner::from_wif("L3tgppXLgdaeqSGSFw1Go3skBiy8vQAM7YMXvTHsKQtE16PBncSU").unwrap();
496 assert_eq!(signer.public_key_bytes(), restored.public_key_bytes());
497 }
498}