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 if decoded[34..38] != checksum[..4] {
157 return Err(SignerError::InvalidPrivateKey("WIF: bad checksum".into()));
158 }
159
160 use crate::traits::KeyPair;
161 Self::from_bytes(&decoded[1..33])
162 }
163}
164
165impl Drop for NeoSigner {
166 fn drop(&mut self) {
167 }
169}
170
171impl NeoSigner {
172 pub(crate) fn sign_digest(&self, digest: &[u8; 32]) -> Result<NeoSignature, SignerError> {
173 let sig: P256Signature = self
174 .signing_key
175 .sign_prehash(digest)
176 .map_err(|e| SignerError::SigningFailed(e.to_string()))?;
177 let mut bytes = [0u8; 64];
178 bytes.copy_from_slice(&sig.to_bytes());
179 Ok(NeoSignature { bytes })
180 }
181}
182
183impl traits::Signer for NeoSigner {
184 type Signature = NeoSignature;
185 type Error = SignerError;
186
187 fn sign(&self, message: &[u8]) -> Result<NeoSignature, SignerError> {
188 let hash = crypto::sha256(message);
189 self.sign_digest(&hash)
190 }
191
192 fn sign_prehashed(&self, digest: &[u8]) -> Result<NeoSignature, SignerError> {
193 if digest.len() != 32 {
194 return Err(SignerError::InvalidHashLength {
195 expected: 32,
196 got: digest.len(),
197 });
198 }
199 let mut hash = [0u8; 32];
200 hash.copy_from_slice(digest);
201 self.sign_digest(&hash)
202 }
203
204 fn public_key_bytes(&self) -> Vec<u8> {
205 self.signing_key
206 .verifying_key()
207 .to_encoded_point(true)
208 .as_bytes()
209 .to_vec()
210 }
211
212 fn public_key_bytes_uncompressed(&self) -> Vec<u8> {
213 self.signing_key
214 .verifying_key()
215 .to_encoded_point(false)
216 .as_bytes()
217 .to_vec()
218 }
219}
220
221impl traits::KeyPair for NeoSigner {
222 fn generate() -> Result<Self, SignerError> {
223 let mut key_bytes = zeroize::Zeroizing::new([0u8; 32]);
224 crate::security::secure_random(&mut *key_bytes)?;
225 let signing_key = SigningKey::from_bytes((&*key_bytes).into())
226 .map_err(|e| SignerError::InvalidPrivateKey(e.to_string()))?;
227 Ok(Self { signing_key })
228 }
229
230 fn from_bytes(private_key: &[u8]) -> Result<Self, SignerError> {
231 if private_key.len() != 32 {
232 return Err(SignerError::InvalidPrivateKey(format!(
233 "expected 32 bytes, got {}",
234 private_key.len()
235 )));
236 }
237 let signing_key = SigningKey::from_bytes(private_key.into())
238 .map_err(|e| SignerError::InvalidPrivateKey(e.to_string()))?;
239 Ok(Self { signing_key })
240 }
241
242 fn private_key_bytes(&self) -> Zeroizing<Vec<u8>> {
243 Zeroizing::new(self.signing_key.to_bytes().to_vec())
244 }
245}
246
247pub struct NeoVerifier {
249 verifying_key: VerifyingKey,
250}
251
252impl NeoVerifier {
253 pub fn from_public_key_bytes(bytes: &[u8]) -> Result<Self, SignerError> {
255 let verifying_key = VerifyingKey::from_sec1_bytes(bytes)
256 .map_err(|e| SignerError::InvalidPublicKey(e.to_string()))?;
257 Ok(Self { verifying_key })
258 }
259
260 fn verify_digest(
261 &self,
262 digest: &[u8; 32],
263 signature: &NeoSignature,
264 ) -> Result<bool, SignerError> {
265 let sig = P256Signature::from_bytes((&signature.bytes).into())
266 .map_err(|e| SignerError::InvalidSignature(e.to_string()))?;
267 match self.verifying_key.verify_prehash(digest, &sig) {
268 Ok(()) => Ok(true),
269 Err(_) => Ok(false),
270 }
271 }
272}
273
274impl traits::Verifier for NeoVerifier {
275 type Signature = NeoSignature;
276 type Error = SignerError;
277
278 fn verify(&self, message: &[u8], signature: &NeoSignature) -> Result<bool, SignerError> {
279 let hash = crypto::sha256(message);
280 self.verify_digest(&hash, signature)
281 }
282
283 fn verify_prehashed(
284 &self,
285 digest: &[u8],
286 signature: &NeoSignature,
287 ) -> Result<bool, SignerError> {
288 if digest.len() != 32 {
289 return Err(SignerError::InvalidHashLength {
290 expected: 32,
291 got: digest.len(),
292 });
293 }
294 let mut hash = [0u8; 32];
295 hash.copy_from_slice(digest);
296 self.verify_digest(&hash, signature)
297 }
298}
299
300#[cfg(test)]
301#[allow(clippy::unwrap_used, clippy::expect_used)]
302mod tests {
303 use super::*;
304 use crate::traits::{KeyPair, Signer, Verifier};
305
306 #[test]
307 fn test_generate_keypair() {
308 let signer = NeoSigner::generate().unwrap();
309 let pubkey = signer.public_key_bytes();
310 assert_eq!(pubkey.len(), 33); }
312
313 #[test]
314 fn test_from_bytes_roundtrip() {
315 let signer = NeoSigner::generate().unwrap();
316 let restored = NeoSigner::from_bytes(&signer.private_key_bytes()).unwrap();
317 assert_eq!(signer.public_key_bytes(), restored.public_key_bytes());
318 }
319
320 #[test]
321 fn test_sign_verify_roundtrip() {
322 let signer = NeoSigner::generate().unwrap();
323 let sig = signer.sign(b"hello neo").unwrap();
324 let verifier = NeoVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
325 assert!(verifier.verify(b"hello neo", &sig).unwrap());
326 }
327
328 #[test]
329 fn test_p256_not_k256() {
330 let privkey =
332 hex::decode("708309a7449e156b0db70e5b52e606c7e094ed676ce8953bf6c14757c826f590")
333 .unwrap();
334 let neo_signer = NeoSigner::from_bytes(&privkey).unwrap();
335 let neo_pubkey = neo_signer.public_key_bytes();
336 assert_eq!(neo_pubkey.len(), 33);
338 }
339
340 #[test]
341 fn test_neo_serialization() {
342 let signer = NeoSigner::generate().unwrap();
343 let sig = signer.sign(b"serialization test").unwrap();
344 assert_eq!(sig.bytes.len(), 64); }
346
347 #[test]
349 fn test_known_vector_p256_fips() {
350 let privkey =
351 hex::decode("708309a7449e156b0db70e5b52e606c7e094ed676ce8953bf6c14757c826f590")
352 .unwrap();
353 let signer = NeoSigner::from_bytes(&privkey).unwrap();
354 let sig = signer.sign(b"NIST P-256 test").unwrap();
356 let verifier = NeoVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
357 assert!(verifier.verify(b"NIST P-256 test", &sig).unwrap());
358 }
359
360 #[test]
361 fn test_invalid_privkey_rejected() {
362 assert!(NeoSigner::from_bytes(&[0u8; 32]).is_err());
363 assert!(NeoSigner::from_bytes(&[1u8; 31]).is_err());
364 }
365
366 #[test]
367 fn test_tampered_sig_fails() {
368 let signer = NeoSigner::generate().unwrap();
369 let sig = signer.sign(b"tamper").unwrap();
370 let verifier = NeoVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
371 let mut tampered = sig.clone();
372 tampered.bytes[0] ^= 0xff;
373 let result = verifier.verify(b"tamper", &tampered);
374 assert!(result.is_err() || !result.unwrap());
375 }
376
377 #[test]
378 fn test_sign_prehashed_roundtrip() {
379 let signer = NeoSigner::generate().unwrap();
380 let msg = b"prehash neo";
381 let digest = crate::crypto::sha256(msg);
382 let sig = signer.sign_prehashed(&digest).unwrap();
383 let verifier = NeoVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
384 assert!(verifier.verify_prehashed(&digest, &sig).unwrap());
385 }
386
387 #[test]
388 fn test_zeroize_on_drop() {
389 let signer = NeoSigner::generate().unwrap();
390 let _: Zeroizing<Vec<u8>> = signer.private_key_bytes();
391 drop(signer);
392 }
393
394 #[test]
395 fn test_empty_message() {
396 let signer = NeoSigner::generate().unwrap();
397 let sig = signer.sign(b"").unwrap();
398 let verifier = NeoVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
399 assert!(verifier.verify(b"", &sig).unwrap());
400 }
401
402 #[test]
405 fn test_neo_address_format() {
406 let signer = NeoSigner::generate().unwrap();
407 let addr = signer.address();
408 assert!(addr.starts_with('A'), "NEO address must start with 'A'");
409 assert_eq!(addr.len(), 34); assert!(validate_address(&addr));
411 }
412
413 #[test]
414 fn test_neo_script_hash_length() {
415 let signer = NeoSigner::generate().unwrap();
416 let hash = signer.script_hash();
417 assert_eq!(hash.len(), 20);
418 }
419
420 #[test]
421 fn test_neo_address_validation_edges() {
422 assert!(!validate_address(""));
423 assert!(!validate_address("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH")); assert!(!validate_address("AINVALID"));
425 }
426
427 #[test]
428 fn test_neo_address_deterministic() {
429 let signer = NeoSigner::generate().unwrap();
430 let addr1 = signer.address();
431 let addr2 = signer.address();
432 assert_eq!(addr1, addr2);
433 }
434
435 #[test]
438 fn test_neo_wif_roundtrip() {
439 let signer = NeoSigner::generate().unwrap();
440 let wif = signer.to_wif();
441 let restored = NeoSigner::from_wif(&wif).unwrap();
442 assert_eq!(signer.public_key_bytes(), restored.public_key_bytes());
443 }
444
445 #[test]
446 fn test_neo_wif_format() {
447 let signer = NeoSigner::generate().unwrap();
448 let wif = signer.to_wif();
449 assert!(
451 wif.starts_with('K') || wif.starts_with('L'),
452 "WIF should start with K or L, got: {}",
453 &wif[..1]
454 );
455 }
456
457 #[test]
458 fn test_neo_wif_invalid_checksum() {
459 let signer = NeoSigner::generate().unwrap();
460 let wif = signer.to_wif();
461 let mut bad = wif.chars().collect::<Vec<_>>();
463 let last = bad.len() - 1;
464 bad[last] = if bad[last] == 'A' { 'B' } else { 'A' };
465 let bad_wif: String = bad.into_iter().collect();
466 assert!(NeoSigner::from_wif(&bad_wif).is_err());
467 }
468
469 #[test]
470 fn test_neo_wif_invalid_base58() {
471 assert!(NeoSigner::from_wif("0OIl").is_err()); }
473
474 #[test]
480 fn test_neo_wif_known_vector() {
481 let privkey =
482 hex::decode("c7134d6fd8e73d819e82755c64c93788d8db0961929e025a53363c4cc02a6962")
483 .unwrap();
484 let signer = NeoSigner::from_bytes(&privkey).unwrap();
485 let wif = signer.to_wif();
486 assert_eq!(
487 wif.as_str(),
488 "L3tgppXLgdaeqSGSFw1Go3skBiy8vQAM7YMXvTHsKQtE16PBncSU",
489 "WIF must match neo.org official test vector"
490 );
491
492 let restored =
494 NeoSigner::from_wif("L3tgppXLgdaeqSGSFw1Go3skBiy8vQAM7YMXvTHsKQtE16PBncSU").unwrap();
495 assert_eq!(signer.public_key_bytes(), restored.public_key_bytes());
496 }
497}