1use crate::primitives::ecdsa::{ecdsa_sign, ecdsa_verify};
10use crate::primitives::hash::{sha256, sha256_hmac};
11use crate::primitives::private_key::PrivateKey;
12use crate::primitives::public_key::PublicKey;
13use crate::primitives::signature::Signature;
14use crate::wallet::error::WalletError;
15use crate::wallet::interfaces::{
16 AbortActionArgs, AbortActionResult, AcquireCertificateArgs, AuthenticatedResult, Certificate,
17 CreateActionArgs, CreateActionResult, CreateHmacArgs, CreateHmacResult, CreateSignatureArgs,
18 CreateSignatureResult, DecryptArgs, DecryptResult, DiscoverByAttributesArgs,
19 DiscoverByIdentityKeyArgs, DiscoverCertificatesResult, EncryptArgs, EncryptResult,
20 GetHeaderArgs, GetHeaderResult, GetHeightResult, GetNetworkResult, GetPublicKeyArgs,
21 GetPublicKeyResult, GetVersionResult, InternalizeActionArgs, InternalizeActionResult,
22 ListActionsArgs, ListActionsResult, ListCertificatesArgs, ListCertificatesResult,
23 ListOutputsArgs, ListOutputsResult, ProveCertificateArgs, ProveCertificateResult,
24 RelinquishCertificateArgs, RelinquishCertificateResult, RelinquishOutputArgs,
25 RelinquishOutputResult, RevealCounterpartyKeyLinkageArgs, RevealCounterpartyKeyLinkageResult,
26 RevealSpecificKeyLinkageArgs, RevealSpecificKeyLinkageResult, SignActionArgs, SignActionResult,
27 VerifyHmacArgs, VerifyHmacResult, VerifySignatureArgs, VerifySignatureResult, WalletInterface,
28};
29use crate::wallet::key_deriver::KeyDeriver;
30use crate::wallet::types::{Counterparty, CounterpartyType, Protocol};
31
32pub struct RevealCounterpartyResult {
34 pub prover: PublicKey,
36 pub counterparty: PublicKey,
38 pub verifier: PublicKey,
40 pub revelation_time: String,
42 pub encrypted_linkage: Vec<u8>,
44 pub encrypted_linkage_proof: Vec<u8>,
46}
47
48pub struct RevealSpecificResult {
50 pub encrypted_linkage: Vec<u8>,
52 pub encrypted_linkage_proof: Vec<u8>,
54 pub prover: PublicKey,
56 pub verifier: PublicKey,
58 pub counterparty: PublicKey,
60 pub protocol: Protocol,
62 pub key_id: String,
64 pub proof_type: u8,
66}
67
68pub struct ProtoWallet {
75 key_deriver: KeyDeriver,
76}
77
78impl ProtoWallet {
79 pub fn new(private_key: PrivateKey) -> Self {
81 ProtoWallet {
82 key_deriver: KeyDeriver::new(private_key),
83 }
84 }
85
86 pub fn from_key_deriver(kd: KeyDeriver) -> Self {
88 ProtoWallet { key_deriver: kd }
89 }
90
91 pub fn anyone() -> Self {
93 ProtoWallet {
94 key_deriver: KeyDeriver::new_anyone(),
95 }
96 }
97
98 pub fn get_public_key_sync(
105 &self,
106 protocol: &Protocol,
107 key_id: &str,
108 counterparty: &Counterparty,
109 for_self: bool,
110 identity_key: bool,
111 ) -> Result<PublicKey, WalletError> {
112 if identity_key {
113 return Ok(self.key_deriver.identity_key());
114 }
115
116 if protocol.protocol.is_empty() || key_id.is_empty() {
117 return Err(WalletError::InvalidParameter(
118 "protocolID and keyID are required if identityKey is false".to_string(),
119 ));
120 }
121
122 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
123 self.key_deriver
124 .derive_public_key(protocol, key_id, &effective, for_self)
125 }
126
127 pub fn create_signature_sync(
132 &self,
133 data: &[u8],
134 protocol: &Protocol,
135 key_id: &str,
136 counterparty: &Counterparty,
137 ) -> Result<Vec<u8>, WalletError> {
138 let effective = self.default_counterparty(counterparty, CounterpartyType::Anyone);
139 let derived_key = self
140 .key_deriver
141 .derive_private_key(protocol, key_id, &effective)?;
142
143 let data_hash = sha256(data);
144 let sig = ecdsa_sign(&data_hash, derived_key.bn(), true)?;
147 Ok(sig.to_der())
148 }
149
150 pub fn verify_signature_sync(
155 &self,
156 data: &[u8],
157 signature: &[u8],
158 protocol: &Protocol,
159 key_id: &str,
160 counterparty: &Counterparty,
161 for_self: bool,
162 ) -> Result<bool, WalletError> {
163 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
164 let derived_pub = self
165 .key_deriver
166 .derive_public_key(protocol, key_id, &effective, for_self)?;
167
168 let sig = Signature::from_der(signature)?;
169 let data_hash = sha256(data);
170 Ok(ecdsa_verify(&data_hash, &sig, derived_pub.point()))
172 }
173
174 pub fn encrypt_sync(
179 &self,
180 plaintext: &[u8],
181 protocol: &Protocol,
182 key_id: &str,
183 counterparty: &Counterparty,
184 ) -> Result<Vec<u8>, WalletError> {
185 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
186 let sym_key = self
187 .key_deriver
188 .derive_symmetric_key(protocol, key_id, &effective)?;
189 Ok(sym_key.encrypt(plaintext)?)
190 }
191
192 pub fn decrypt_sync(
197 &self,
198 ciphertext: &[u8],
199 protocol: &Protocol,
200 key_id: &str,
201 counterparty: &Counterparty,
202 ) -> Result<Vec<u8>, WalletError> {
203 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
204 let sym_key = self
205 .key_deriver
206 .derive_symmetric_key(protocol, key_id, &effective)?;
207 Ok(sym_key.decrypt(ciphertext)?)
208 }
209
210 pub fn create_hmac_sync(
214 &self,
215 data: &[u8],
216 protocol: &Protocol,
217 key_id: &str,
218 counterparty: &Counterparty,
219 ) -> Result<Vec<u8>, WalletError> {
220 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
221 let sym_key = self
222 .key_deriver
223 .derive_symmetric_key(protocol, key_id, &effective)?;
224 let key_bytes = sym_key.to_bytes();
225 let hmac = sha256_hmac(&key_bytes, data);
226 Ok(hmac.to_vec())
227 }
228
229 pub fn verify_hmac_sync(
234 &self,
235 data: &[u8],
236 hmac_value: &[u8],
237 protocol: &Protocol,
238 key_id: &str,
239 counterparty: &Counterparty,
240 ) -> Result<bool, WalletError> {
241 let expected = self.create_hmac_sync(data, protocol, key_id, counterparty)?;
242 Ok(constant_time_eq(&expected, hmac_value))
244 }
245
246 pub fn reveal_counterparty_key_linkage_sync(
252 &self,
253 counterparty: &Counterparty,
254 verifier: &PublicKey,
255 ) -> Result<RevealCounterpartyResult, WalletError> {
256 let linkage_point = self.key_deriver.reveal_counterparty_secret(counterparty)?;
258 let linkage_bytes = linkage_point.to_der(); let prover = self.key_deriver.identity_key();
261
262 let revelation_time = current_utc_timestamp();
265
266 let verifier_counterparty = Counterparty {
267 counterparty_type: CounterpartyType::Other,
268 public_key: Some(verifier.clone()),
269 };
270
271 let linkage_protocol = Protocol {
272 security_level: 2,
273 protocol: "counterparty linkage revelation".to_string(),
274 };
275
276 let encrypted_linkage = self.encrypt_sync(
278 &linkage_bytes,
279 &linkage_protocol,
280 &revelation_time,
281 &verifier_counterparty,
282 )?;
283
284 let proof = self.create_hmac_sync(
286 &linkage_bytes,
287 &linkage_protocol,
288 &revelation_time,
289 &verifier_counterparty,
290 )?;
291 let encrypted_proof = self.encrypt_sync(
292 &proof,
293 &linkage_protocol,
294 &revelation_time,
295 &verifier_counterparty,
296 )?;
297
298 let counterparty_pub = match &counterparty.public_key {
300 Some(pk) => pk.clone(),
301 None => {
302 return Err(WalletError::InvalidParameter(
303 "counterparty public key required for linkage revelation".to_string(),
304 ))
305 }
306 };
307
308 Ok(RevealCounterpartyResult {
309 prover,
310 counterparty: counterparty_pub,
311 verifier: verifier.clone(),
312 revelation_time,
313 encrypted_linkage,
314 encrypted_linkage_proof: encrypted_proof,
315 })
316 }
317
318 pub fn reveal_specific_key_linkage_sync(
323 &self,
324 counterparty: &Counterparty,
325 verifier: &PublicKey,
326 protocol: &Protocol,
327 key_id: &str,
328 ) -> Result<RevealSpecificResult, WalletError> {
329 let linkage = self
331 .key_deriver
332 .reveal_specific_secret(counterparty, protocol, key_id)?;
333
334 let prover = self.key_deriver.identity_key();
335
336 let verifier_counterparty = Counterparty {
337 counterparty_type: CounterpartyType::Other,
338 public_key: Some(verifier.clone()),
339 };
340
341 let encrypt_protocol = Protocol {
343 security_level: 2,
344 protocol: format!(
345 "specific linkage revelation {} {}",
346 protocol.security_level, protocol.protocol
347 ),
348 };
349
350 let encrypted_linkage =
352 self.encrypt_sync(&linkage, &encrypt_protocol, key_id, &verifier_counterparty)?;
353
354 let proof_bytes: [u8; 1] = [0];
356 let encrypted_proof = self.encrypt_sync(
357 &proof_bytes,
358 &encrypt_protocol,
359 key_id,
360 &verifier_counterparty,
361 )?;
362
363 let counterparty_pub = match &counterparty.public_key {
365 Some(pk) => pk.clone(),
366 None => {
367 return Err(WalletError::InvalidParameter(
368 "counterparty public key required for linkage revelation".to_string(),
369 ))
370 }
371 };
372
373 Ok(RevealSpecificResult {
374 encrypted_linkage,
375 encrypted_linkage_proof: encrypted_proof,
376 prover,
377 verifier: verifier.clone(),
378 counterparty: counterparty_pub,
379 protocol: protocol.clone(),
380 key_id: key_id.to_string(),
381 proof_type: 0,
382 })
383 }
384
385 fn default_counterparty(
387 &self,
388 counterparty: &Counterparty,
389 default_type: CounterpartyType,
390 ) -> Counterparty {
391 if counterparty.counterparty_type == CounterpartyType::Uninitialized {
392 Counterparty {
393 counterparty_type: default_type,
394 public_key: None,
395 }
396 } else {
397 counterparty.clone()
398 }
399 }
400}
401
402#[async_trait::async_trait]
410impl WalletInterface for ProtoWallet {
411 async fn create_action(
414 &self,
415 _args: CreateActionArgs,
416 _originator: Option<&str>,
417 ) -> Result<CreateActionResult, WalletError> {
418 Err(WalletError::NotImplemented("createAction".to_string()))
419 }
420
421 async fn sign_action(
422 &self,
423 _args: SignActionArgs,
424 _originator: Option<&str>,
425 ) -> Result<SignActionResult, WalletError> {
426 Err(WalletError::NotImplemented("signAction".to_string()))
427 }
428
429 async fn abort_action(
430 &self,
431 _args: AbortActionArgs,
432 _originator: Option<&str>,
433 ) -> Result<AbortActionResult, WalletError> {
434 Err(WalletError::NotImplemented("abortAction".to_string()))
435 }
436
437 async fn list_actions(
438 &self,
439 _args: ListActionsArgs,
440 _originator: Option<&str>,
441 ) -> Result<ListActionsResult, WalletError> {
442 Err(WalletError::NotImplemented("listActions".to_string()))
443 }
444
445 async fn internalize_action(
446 &self,
447 _args: InternalizeActionArgs,
448 _originator: Option<&str>,
449 ) -> Result<InternalizeActionResult, WalletError> {
450 Err(WalletError::NotImplemented("internalizeAction".to_string()))
451 }
452
453 async fn list_outputs(
456 &self,
457 _args: ListOutputsArgs,
458 _originator: Option<&str>,
459 ) -> Result<ListOutputsResult, WalletError> {
460 Err(WalletError::NotImplemented("listOutputs".to_string()))
461 }
462
463 async fn relinquish_output(
464 &self,
465 _args: RelinquishOutputArgs,
466 _originator: Option<&str>,
467 ) -> Result<RelinquishOutputResult, WalletError> {
468 Err(WalletError::NotImplemented("relinquishOutput".to_string()))
469 }
470
471 async fn get_public_key(
474 &self,
475 args: GetPublicKeyArgs,
476 _originator: Option<&str>,
477 ) -> Result<GetPublicKeyResult, WalletError> {
478 if args.privileged {
479 return Err(WalletError::NotImplemented(
480 "privileged key access not supported by ProtoWallet".to_string(),
481 ));
482 }
483 let protocol = args.protocol_id.unwrap_or(Protocol {
484 security_level: 0,
485 protocol: String::new(),
486 });
487 let key_id = args.key_id.unwrap_or_default();
488 let counterparty = args.counterparty.unwrap_or(Counterparty {
489 counterparty_type: CounterpartyType::Uninitialized,
490 public_key: None,
491 });
492 let for_self = args.for_self.unwrap_or(false);
493 let pk = self.get_public_key_sync(
494 &protocol,
495 &key_id,
496 &counterparty,
497 for_self,
498 args.identity_key,
499 )?;
500 Ok(GetPublicKeyResult { public_key: pk })
501 }
502
503 async fn reveal_counterparty_key_linkage(
504 &self,
505 args: RevealCounterpartyKeyLinkageArgs,
506 _originator: Option<&str>,
507 ) -> Result<RevealCounterpartyKeyLinkageResult, WalletError> {
508 let counterparty = Counterparty {
509 counterparty_type: CounterpartyType::Other,
510 public_key: Some(args.counterparty),
511 };
512 let result = self.reveal_counterparty_key_linkage_sync(&counterparty, &args.verifier)?;
513 Ok(RevealCounterpartyKeyLinkageResult {
514 prover: result.prover,
515 counterparty: result.counterparty,
516 verifier: result.verifier,
517 revelation_time: result.revelation_time,
518 encrypted_linkage: result.encrypted_linkage,
519 encrypted_linkage_proof: result.encrypted_linkage_proof,
520 })
521 }
522
523 async fn reveal_specific_key_linkage(
524 &self,
525 args: RevealSpecificKeyLinkageArgs,
526 _originator: Option<&str>,
527 ) -> Result<RevealSpecificKeyLinkageResult, WalletError> {
528 let result = self.reveal_specific_key_linkage_sync(
529 &args.counterparty,
530 &args.verifier,
531 &args.protocol_id,
532 &args.key_id,
533 )?;
534 Ok(RevealSpecificKeyLinkageResult {
535 encrypted_linkage: result.encrypted_linkage,
536 encrypted_linkage_proof: result.encrypted_linkage_proof,
537 prover: result.prover,
538 verifier: result.verifier,
539 counterparty: result.counterparty,
540 protocol_id: result.protocol.clone(),
541 key_id: result.key_id.clone(),
542 proof_type: result.proof_type,
543 })
544 }
545
546 async fn encrypt(
547 &self,
548 args: EncryptArgs,
549 _originator: Option<&str>,
550 ) -> Result<EncryptResult, WalletError> {
551 let ciphertext = self.encrypt_sync(
552 &args.plaintext,
553 &args.protocol_id,
554 &args.key_id,
555 &args.counterparty,
556 )?;
557 Ok(EncryptResult { ciphertext })
558 }
559
560 async fn decrypt(
561 &self,
562 args: DecryptArgs,
563 _originator: Option<&str>,
564 ) -> Result<DecryptResult, WalletError> {
565 let plaintext = self.decrypt_sync(
566 &args.ciphertext,
567 &args.protocol_id,
568 &args.key_id,
569 &args.counterparty,
570 )?;
571 Ok(DecryptResult { plaintext })
572 }
573
574 async fn create_hmac(
575 &self,
576 args: CreateHmacArgs,
577 _originator: Option<&str>,
578 ) -> Result<CreateHmacResult, WalletError> {
579 let hmac = self.create_hmac_sync(
580 &args.data,
581 &args.protocol_id,
582 &args.key_id,
583 &args.counterparty,
584 )?;
585 Ok(CreateHmacResult { hmac })
586 }
587
588 async fn verify_hmac(
589 &self,
590 args: VerifyHmacArgs,
591 _originator: Option<&str>,
592 ) -> Result<VerifyHmacResult, WalletError> {
593 let valid = self.verify_hmac_sync(
594 &args.data,
595 &args.hmac,
596 &args.protocol_id,
597 &args.key_id,
598 &args.counterparty,
599 )?;
600 Ok(VerifyHmacResult { valid })
601 }
602
603 async fn create_signature(
604 &self,
605 args: CreateSignatureArgs,
606 _originator: Option<&str>,
607 ) -> Result<CreateSignatureResult, WalletError> {
608 let signature = self.create_signature_sync(
609 &args.data,
610 &args.protocol_id,
611 &args.key_id,
612 &args.counterparty,
613 )?;
614 Ok(CreateSignatureResult { signature })
615 }
616
617 async fn verify_signature(
618 &self,
619 args: VerifySignatureArgs,
620 _originator: Option<&str>,
621 ) -> Result<VerifySignatureResult, WalletError> {
622 let for_self = args.for_self.unwrap_or(false);
623 let valid = self.verify_signature_sync(
624 &args.data,
625 &args.signature,
626 &args.protocol_id,
627 &args.key_id,
628 &args.counterparty,
629 for_self,
630 )?;
631 Ok(VerifySignatureResult { valid })
632 }
633
634 async fn acquire_certificate(
637 &self,
638 _args: AcquireCertificateArgs,
639 _originator: Option<&str>,
640 ) -> Result<Certificate, WalletError> {
641 Err(WalletError::NotImplemented(
642 "acquireCertificate".to_string(),
643 ))
644 }
645
646 async fn list_certificates(
647 &self,
648 _args: ListCertificatesArgs,
649 _originator: Option<&str>,
650 ) -> Result<ListCertificatesResult, WalletError> {
651 Err(WalletError::NotImplemented("listCertificates".to_string()))
652 }
653
654 async fn prove_certificate(
655 &self,
656 _args: ProveCertificateArgs,
657 _originator: Option<&str>,
658 ) -> Result<ProveCertificateResult, WalletError> {
659 Err(WalletError::NotImplemented("proveCertificate".to_string()))
660 }
661
662 async fn relinquish_certificate(
663 &self,
664 _args: RelinquishCertificateArgs,
665 _originator: Option<&str>,
666 ) -> Result<RelinquishCertificateResult, WalletError> {
667 Err(WalletError::NotImplemented(
668 "relinquishCertificate".to_string(),
669 ))
670 }
671
672 async fn discover_by_identity_key(
675 &self,
676 _args: DiscoverByIdentityKeyArgs,
677 _originator: Option<&str>,
678 ) -> Result<DiscoverCertificatesResult, WalletError> {
679 Err(WalletError::NotImplemented(
680 "discoverByIdentityKey".to_string(),
681 ))
682 }
683
684 async fn discover_by_attributes(
685 &self,
686 _args: DiscoverByAttributesArgs,
687 _originator: Option<&str>,
688 ) -> Result<DiscoverCertificatesResult, WalletError> {
689 Err(WalletError::NotImplemented(
690 "discoverByAttributes".to_string(),
691 ))
692 }
693
694 async fn is_authenticated(
697 &self,
698 _originator: Option<&str>,
699 ) -> Result<AuthenticatedResult, WalletError> {
700 Err(WalletError::NotImplemented("isAuthenticated".to_string()))
701 }
702
703 async fn wait_for_authentication(
704 &self,
705 _originator: Option<&str>,
706 ) -> Result<AuthenticatedResult, WalletError> {
707 Err(WalletError::NotImplemented(
708 "waitForAuthentication".to_string(),
709 ))
710 }
711
712 async fn get_height(&self, _originator: Option<&str>) -> Result<GetHeightResult, WalletError> {
713 Err(WalletError::NotImplemented("getHeight".to_string()))
714 }
715
716 async fn get_header_for_height(
717 &self,
718 _args: GetHeaderArgs,
719 _originator: Option<&str>,
720 ) -> Result<GetHeaderResult, WalletError> {
721 Err(WalletError::NotImplemented(
722 "getHeaderForHeight".to_string(),
723 ))
724 }
725
726 async fn get_network(
727 &self,
728 _originator: Option<&str>,
729 ) -> Result<GetNetworkResult, WalletError> {
730 Err(WalletError::NotImplemented("getNetwork".to_string()))
731 }
732
733 async fn get_version(
734 &self,
735 _originator: Option<&str>,
736 ) -> Result<GetVersionResult, WalletError> {
737 Err(WalletError::NotImplemented("getVersion".to_string()))
738 }
739}
740
741fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
743 if a.len() != b.len() {
744 return false;
745 }
746 let mut diff: u8 = 0;
747 for (x, y) in a.iter().zip(b.iter()) {
748 diff |= x ^ y;
749 }
750 diff == 0
751}
752
753fn current_utc_timestamp() -> String {
755 use std::time::{SystemTime, UNIX_EPOCH};
758 let now = SystemTime::now()
759 .duration_since(UNIX_EPOCH)
760 .unwrap_or_default();
761 format!("{}", now.as_secs())
762}
763
764#[cfg(test)]
765mod tests {
766 use super::*;
767 use crate::wallet::types::{BooleanDefaultFalse, BooleanDefaultTrue};
768
769 fn test_protocol() -> Protocol {
770 Protocol {
771 security_level: 2,
772 protocol: "test proto wallet".to_string(),
773 }
774 }
775
776 fn self_counterparty() -> Counterparty {
777 Counterparty {
778 counterparty_type: CounterpartyType::Self_,
779 public_key: None,
780 }
781 }
782
783 fn test_private_key() -> PrivateKey {
784 PrivateKey::from_hex("abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890")
785 .unwrap()
786 }
787
788 #[test]
789 fn test_new_creates_wallet_with_correct_identity_key() {
790 let pk = test_private_key();
791 let expected_pub = pk.to_public_key();
792 let wallet = ProtoWallet::new(pk);
793 let identity = wallet
794 .get_public_key_sync(&test_protocol(), "1", &self_counterparty(), false, true)
795 .unwrap();
796 assert_eq!(identity.to_der_hex(), expected_pub.to_der_hex());
797 }
798
799 #[test]
800 fn test_get_public_key_identity_key_true() {
801 let pk = test_private_key();
802 let expected = pk.to_public_key().to_der_hex();
803 let wallet = ProtoWallet::new(pk);
804 let result = wallet
805 .get_public_key_sync(&test_protocol(), "1", &self_counterparty(), false, true)
806 .unwrap();
807 assert_eq!(result.to_der_hex(), expected);
808 }
809
810 #[test]
811 fn test_get_public_key_derived() {
812 let wallet = ProtoWallet::new(test_private_key());
813 let protocol = test_protocol();
814 let pub1 = wallet
815 .get_public_key_sync(&protocol, "key1", &self_counterparty(), true, false)
816 .unwrap();
817 let pub2 = wallet
818 .get_public_key_sync(&protocol, "key2", &self_counterparty(), true, false)
819 .unwrap();
820 assert_ne!(pub1.to_der_hex(), pub2.to_der_hex());
822 }
823
824 #[test]
825 fn test_create_and_verify_signature_roundtrip() {
826 let wallet = ProtoWallet::new(test_private_key());
827 let protocol = test_protocol();
828 let counterparty = self_counterparty();
829 let data = b"hello world signature test";
830
831 let sig = wallet
832 .create_signature_sync(data, &protocol, "sig1", &counterparty)
833 .unwrap();
834 assert!(!sig.is_empty());
835
836 let valid = wallet
837 .verify_signature_sync(data, &sig, &protocol, "sig1", &counterparty, true)
838 .unwrap();
839 assert!(valid, "signature should verify");
840 }
841
842 #[test]
843 fn test_verify_signature_rejects_wrong_data() {
844 let wallet = ProtoWallet::new(test_private_key());
845 let protocol = test_protocol();
846 let counterparty = self_counterparty();
847
848 let sig = wallet
849 .create_signature_sync(b"correct data", &protocol, "sig2", &counterparty)
850 .unwrap();
851 let valid = wallet
852 .verify_signature_sync(b"wrong data", &sig, &protocol, "sig2", &counterparty, true)
853 .unwrap();
854 assert!(!valid, "signature should not verify for wrong data");
855 }
856
857 #[test]
858 fn test_encrypt_decrypt_roundtrip() {
859 let wallet = ProtoWallet::new(test_private_key());
860 let protocol = test_protocol();
861 let counterparty = self_counterparty();
862 let plaintext = b"secret message for encryption";
863
864 let ciphertext = wallet
865 .encrypt_sync(plaintext, &protocol, "enc1", &counterparty)
866 .unwrap();
867 assert_ne!(ciphertext.as_slice(), plaintext);
868
869 let decrypted = wallet
870 .decrypt_sync(&ciphertext, &protocol, "enc1", &counterparty)
871 .unwrap();
872 assert_eq!(decrypted, plaintext);
873 }
874
875 #[test]
876 fn test_encrypt_decrypt_empty_plaintext() {
877 let wallet = ProtoWallet::new(test_private_key());
878 let protocol = test_protocol();
879 let counterparty = self_counterparty();
880
881 let ciphertext = wallet
882 .encrypt_sync(b"", &protocol, "enc2", &counterparty)
883 .unwrap();
884 let decrypted = wallet
885 .decrypt_sync(&ciphertext, &protocol, "enc2", &counterparty)
886 .unwrap();
887 assert!(decrypted.is_empty());
888 }
889
890 #[test]
891 fn test_create_and_verify_hmac_roundtrip() {
892 let wallet = ProtoWallet::new(test_private_key());
893 let protocol = test_protocol();
894 let counterparty = self_counterparty();
895 let data = b"hmac test data";
896
897 let hmac = wallet
898 .create_hmac_sync(data, &protocol, "hmac1", &counterparty)
899 .unwrap();
900 assert_eq!(hmac.len(), 32);
901
902 let valid = wallet
903 .verify_hmac_sync(data, &hmac, &protocol, "hmac1", &counterparty)
904 .unwrap();
905 assert!(valid, "HMAC should verify");
906 }
907
908 #[test]
909 fn test_verify_hmac_rejects_wrong_data() {
910 let wallet = ProtoWallet::new(test_private_key());
911 let protocol = test_protocol();
912 let counterparty = self_counterparty();
913
914 let hmac = wallet
915 .create_hmac_sync(b"correct", &protocol, "hmac2", &counterparty)
916 .unwrap();
917 let valid = wallet
918 .verify_hmac_sync(b"wrong", &hmac, &protocol, "hmac2", &counterparty)
919 .unwrap();
920 assert!(!valid, "HMAC should not verify for wrong data");
921 }
922
923 #[test]
924 fn test_hmac_deterministic() {
925 let wallet = ProtoWallet::new(test_private_key());
926 let protocol = test_protocol();
927 let counterparty = self_counterparty();
928 let data = b"deterministic hmac";
929
930 let hmac1 = wallet
931 .create_hmac_sync(data, &protocol, "hmac3", &counterparty)
932 .unwrap();
933 let hmac2 = wallet
934 .create_hmac_sync(data, &protocol, "hmac3", &counterparty)
935 .unwrap();
936 assert_eq!(hmac1, hmac2);
937 }
938
939 #[test]
940 fn test_anyone_wallet_encrypt_decrypt() {
941 let anyone = ProtoWallet::anyone();
942 let other_key = test_private_key();
943 let other_pub = other_key.to_public_key();
944
945 let counterparty = Counterparty {
946 counterparty_type: CounterpartyType::Other,
947 public_key: Some(other_pub),
948 };
949 let protocol = test_protocol();
950 let plaintext = b"message from anyone";
951
952 let ciphertext = anyone
953 .encrypt_sync(plaintext, &protocol, "anon1", &counterparty)
954 .unwrap();
955 let decrypted = anyone
956 .decrypt_sync(&ciphertext, &protocol, "anon1", &counterparty)
957 .unwrap();
958 assert_eq!(decrypted, plaintext);
959 }
960
961 #[test]
962 fn test_uninitialized_counterparty_defaults_to_self_for_encrypt() {
963 let wallet = ProtoWallet::new(test_private_key());
964 let protocol = test_protocol();
965 let uninit = Counterparty {
966 counterparty_type: CounterpartyType::Uninitialized,
967 public_key: None,
968 };
969 let self_cp = self_counterparty();
970
971 let ct_uninit = wallet.encrypt_sync(b"test", &protocol, "def1", &uninit).unwrap();
972 let decrypted = wallet
974 .decrypt_sync(&ct_uninit, &protocol, "def1", &self_cp)
975 .unwrap();
976 assert_eq!(decrypted, b"test");
977 }
978
979 #[test]
980 fn test_reveal_specific_key_linkage() {
981 let wallet_a = ProtoWallet::new(test_private_key());
982 let verifier_key = PrivateKey::from_hex("ff").unwrap();
983 let verifier_pub = verifier_key.to_public_key();
984
985 let counterparty_key = PrivateKey::from_hex("bb").unwrap();
986 let counterparty_pub = counterparty_key.to_public_key();
987
988 let counterparty = Counterparty {
989 counterparty_type: CounterpartyType::Other,
990 public_key: Some(counterparty_pub),
991 };
992
993 let protocol = test_protocol();
994 let result = wallet_a
995 .reveal_specific_key_linkage_sync(&counterparty, &verifier_pub, &protocol, "link1")
996 .unwrap();
997
998 assert!(!result.encrypted_linkage.is_empty());
999 assert!(!result.encrypted_linkage_proof.is_empty());
1000 assert_eq!(result.proof_type, 0);
1001 assert_eq!(result.key_id, "link1");
1002 }
1003
1004 #[test]
1005 fn test_reveal_counterparty_key_linkage() {
1006 let wallet = ProtoWallet::new(test_private_key());
1007 let verifier_key = PrivateKey::from_hex("ff").unwrap();
1008 let verifier_pub = verifier_key.to_public_key();
1009
1010 let counterparty_key = PrivateKey::from_hex("cc").unwrap();
1011 let counterparty_pub = counterparty_key.to_public_key();
1012
1013 let counterparty = Counterparty {
1014 counterparty_type: CounterpartyType::Other,
1015 public_key: Some(counterparty_pub.clone()),
1016 };
1017
1018 let result = wallet
1019 .reveal_counterparty_key_linkage_sync(&counterparty, &verifier_pub)
1020 .unwrap();
1021
1022 assert!(!result.encrypted_linkage.is_empty());
1023 assert!(!result.encrypted_linkage_proof.is_empty());
1024 assert_eq!(
1025 result.counterparty.to_der_hex(),
1026 counterparty_pub.to_der_hex()
1027 );
1028 assert_eq!(result.verifier.to_der_hex(), verifier_pub.to_der_hex());
1029 assert!(!result.revelation_time.is_empty());
1030 }
1031
1032 async fn get_pub_key_via_trait<W: WalletInterface + ?Sized>(
1038 w: &W,
1039 args: GetPublicKeyArgs,
1040 ) -> Result<GetPublicKeyResult, WalletError> {
1041 w.get_public_key(args, None).await
1042 }
1043
1044 #[tokio::test]
1045 async fn test_wallet_interface_get_public_key_identity() {
1046 let pk = test_private_key();
1047 let expected = pk.to_public_key().to_der_hex();
1048 let wallet = ProtoWallet::new(pk);
1049
1050 let result = get_pub_key_via_trait(
1051 &wallet,
1052 GetPublicKeyArgs {
1053 identity_key: true,
1054 protocol_id: None,
1055 key_id: None,
1056 counterparty: None,
1057 privileged: false,
1058 privileged_reason: None,
1059 for_self: None,
1060 seek_permission: None,
1061 },
1062 )
1063 .await
1064 .unwrap();
1065
1066 assert_eq!(result.public_key.to_der_hex(), expected);
1067 }
1068
1069 #[tokio::test]
1070 async fn test_wallet_interface_get_public_key_derived() {
1071 let wallet = ProtoWallet::new(test_private_key());
1072
1073 let result = get_pub_key_via_trait(
1074 &wallet,
1075 GetPublicKeyArgs {
1076 identity_key: false,
1077 protocol_id: Some(test_protocol()),
1078 key_id: Some("derived1".to_string()),
1079 counterparty: Some(self_counterparty()),
1080 privileged: false,
1081 privileged_reason: None,
1082 for_self: Some(true),
1083 seek_permission: None,
1084 },
1085 )
1086 .await
1087 .unwrap();
1088
1089 let direct = wallet
1091 .get_public_key_sync(
1092 &test_protocol(),
1093 "derived1",
1094 &self_counterparty(),
1095 true,
1096 false,
1097 )
1098 .unwrap();
1099 assert_eq!(result.public_key.to_der_hex(), direct.to_der_hex());
1100 }
1101
1102 #[tokio::test]
1103 async fn test_wallet_interface_privileged_rejected() {
1104 let wallet = ProtoWallet::new(test_private_key());
1105 let err = WalletInterface::get_public_key(
1106 &wallet,
1107 GetPublicKeyArgs {
1108 identity_key: true,
1109 protocol_id: None,
1110 key_id: None,
1111 counterparty: None,
1112 privileged: true,
1113 privileged_reason: Some("test".to_string()),
1114 for_self: None,
1115 seek_permission: None,
1116 },
1117 None,
1118 )
1119 .await;
1120
1121 assert!(err.is_err());
1122 let msg = format!("{}", err.unwrap_err());
1123 assert!(msg.contains("not implemented"), "got: {}", msg);
1124 }
1125
1126 #[tokio::test]
1127 async fn test_wallet_interface_create_verify_signature() {
1128 let wallet = ProtoWallet::new(test_private_key());
1129 let data = b"test data for wallet interface sig".to_vec();
1130
1131 let sig_result = WalletInterface::create_signature(
1132 &wallet,
1133 CreateSignatureArgs {
1134 protocol_id: test_protocol(),
1135 key_id: "wsig1".to_string(),
1136 counterparty: self_counterparty(),
1137 data: data.clone(),
1138 privileged: false,
1139 privileged_reason: None,
1140 seek_permission: None,
1141 },
1142 None,
1143 )
1144 .await
1145 .unwrap();
1146
1147 let verify_result = WalletInterface::verify_signature(
1148 &wallet,
1149 VerifySignatureArgs {
1150 protocol_id: test_protocol(),
1151 key_id: "wsig1".to_string(),
1152 counterparty: self_counterparty(),
1153 data,
1154 signature: sig_result.signature,
1155 for_self: Some(true),
1156 privileged: false,
1157 privileged_reason: None,
1158 seek_permission: None,
1159 },
1160 None,
1161 )
1162 .await
1163 .unwrap();
1164
1165 assert!(verify_result.valid);
1166 }
1167
1168 #[tokio::test]
1169 async fn test_wallet_interface_encrypt_decrypt() {
1170 let wallet = ProtoWallet::new(test_private_key());
1171 let plaintext = b"wallet interface encrypt test".to_vec();
1172
1173 let enc = WalletInterface::encrypt(
1174 &wallet,
1175 EncryptArgs {
1176 protocol_id: test_protocol(),
1177 key_id: "wenc1".to_string(),
1178 counterparty: self_counterparty(),
1179 plaintext: plaintext.clone(),
1180 privileged: false,
1181 privileged_reason: None,
1182 seek_permission: None,
1183 },
1184 None,
1185 )
1186 .await
1187 .unwrap();
1188
1189 let dec = WalletInterface::decrypt(
1190 &wallet,
1191 DecryptArgs {
1192 protocol_id: test_protocol(),
1193 key_id: "wenc1".to_string(),
1194 counterparty: self_counterparty(),
1195 ciphertext: enc.ciphertext,
1196 privileged: false,
1197 privileged_reason: None,
1198 seek_permission: None,
1199 },
1200 None,
1201 )
1202 .await
1203 .unwrap();
1204
1205 assert_eq!(dec.plaintext, plaintext);
1206 }
1207
1208 #[tokio::test]
1209 async fn test_wallet_interface_hmac_roundtrip() {
1210 let wallet = ProtoWallet::new(test_private_key());
1211 let data = b"wallet interface hmac test".to_vec();
1212
1213 let hmac_result = WalletInterface::create_hmac(
1214 &wallet,
1215 CreateHmacArgs {
1216 protocol_id: test_protocol(),
1217 key_id: "whmac1".to_string(),
1218 counterparty: self_counterparty(),
1219 data: data.clone(),
1220 privileged: false,
1221 privileged_reason: None,
1222 seek_permission: None,
1223 },
1224 None,
1225 )
1226 .await
1227 .unwrap();
1228
1229 assert_eq!(hmac_result.hmac.len(), 32);
1230
1231 let verify = WalletInterface::verify_hmac(
1232 &wallet,
1233 VerifyHmacArgs {
1234 protocol_id: test_protocol(),
1235 key_id: "whmac1".to_string(),
1236 counterparty: self_counterparty(),
1237 data,
1238 hmac: hmac_result.hmac,
1239 privileged: false,
1240 privileged_reason: None,
1241 seek_permission: None,
1242 },
1243 None,
1244 )
1245 .await
1246 .unwrap();
1247
1248 assert!(verify.valid);
1249 }
1250
1251 #[tokio::test]
1252 async fn test_wallet_interface_unsupported_methods_return_not_implemented() {
1253 use crate::wallet::interfaces::*;
1254 let wallet = ProtoWallet::new(test_private_key());
1255
1256 let err = WalletInterface::is_authenticated(&wallet, None).await;
1259 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1260
1261 let err = WalletInterface::wait_for_authentication(&wallet, None).await;
1262 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1263
1264 let err = WalletInterface::get_network(&wallet, None).await;
1265 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1266
1267 let err = WalletInterface::get_version(&wallet, None).await;
1268 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1269
1270 let err = WalletInterface::get_height(&wallet, None).await;
1271 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1272
1273 let err =
1274 WalletInterface::get_header_for_height(&wallet, GetHeaderArgs { height: 0 }, None)
1275 .await;
1276 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1277
1278 let err = WalletInterface::list_outputs(
1279 &wallet,
1280 ListOutputsArgs {
1281 basket: "test".to_string(),
1282 tags: vec![],
1283 tag_query_mode: None,
1284 include: None,
1285 include_custom_instructions: BooleanDefaultFalse(None),
1286 include_tags: BooleanDefaultFalse(None),
1287 include_labels: BooleanDefaultFalse(None),
1288 limit: Some(10),
1289 offset: None,
1290 seek_permission: BooleanDefaultTrue(None),
1291 },
1292 None,
1293 )
1294 .await;
1295 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1296 }
1297
1298 #[tokio::test]
1299 async fn test_wallet_interface_reveal_counterparty_key_linkage() {
1300 let wallet = ProtoWallet::new(test_private_key());
1301 let verifier_key = PrivateKey::from_hex("ff").unwrap();
1302 let counterparty_key = PrivateKey::from_hex("cc").unwrap();
1303
1304 let result = WalletInterface::reveal_counterparty_key_linkage(
1305 &wallet,
1306 RevealCounterpartyKeyLinkageArgs {
1307 counterparty: counterparty_key.to_public_key(),
1308 verifier: verifier_key.to_public_key(),
1309 privileged: None,
1310 privileged_reason: None,
1311 },
1312 None,
1313 )
1314 .await
1315 .unwrap();
1316
1317 assert!(!result.encrypted_linkage.is_empty());
1318 assert!(!result.encrypted_linkage_proof.is_empty());
1319 assert_eq!(
1320 result.counterparty.to_der_hex(),
1321 counterparty_key.to_public_key().to_der_hex()
1322 );
1323 assert!(!result.revelation_time.is_empty());
1324 }
1325
1326 #[tokio::test]
1327 async fn test_wallet_interface_reveal_specific_key_linkage() {
1328 let wallet = ProtoWallet::new(test_private_key());
1329 let verifier_key = PrivateKey::from_hex("ff").unwrap();
1330 let counterparty_key = PrivateKey::from_hex("bb").unwrap();
1331
1332 let result = WalletInterface::reveal_specific_key_linkage(
1333 &wallet,
1334 RevealSpecificKeyLinkageArgs {
1335 counterparty: Counterparty {
1336 counterparty_type: CounterpartyType::Other,
1337 public_key: Some(counterparty_key.to_public_key()),
1338 },
1339 verifier: verifier_key.to_public_key(),
1340 protocol_id: test_protocol(),
1341 key_id: "wlink1".to_string(),
1342 privileged: None,
1343 privileged_reason: None,
1344 },
1345 None,
1346 )
1347 .await
1348 .unwrap();
1349
1350 assert!(!result.encrypted_linkage.is_empty());
1351 assert_eq!(result.proof_type, 0);
1352 assert_eq!(result.key_id, "wlink1");
1353 }
1354}