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(
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(
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(
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(
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(
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(
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(
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(data, protocol, key_id, counterparty)?;
242 Ok(constant_time_eq(&expected, hmac_value))
244 }
245
246 pub fn reveal_counterparty_key_linkage(
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(
278 &linkage_bytes,
279 &linkage_protocol,
280 &revelation_time,
281 &verifier_counterparty,
282 )?;
283
284 let proof = self.create_hmac(
286 &linkage_bytes,
287 &linkage_protocol,
288 &revelation_time,
289 &verifier_counterparty,
290 )?;
291 let encrypted_proof = self.encrypt(
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(
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(&linkage, &encrypt_protocol, key_id, &verifier_counterparty)?;
353
354 let proof_bytes: [u8; 1] = [0];
356 let encrypted_proof = self.encrypt(
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(
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(&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(
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(
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(
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(
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(
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(
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(
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
768 fn test_protocol() -> Protocol {
769 Protocol {
770 security_level: 2,
771 protocol: "test proto wallet".to_string(),
772 }
773 }
774
775 fn self_counterparty() -> Counterparty {
776 Counterparty {
777 counterparty_type: CounterpartyType::Self_,
778 public_key: None,
779 }
780 }
781
782 fn test_private_key() -> PrivateKey {
783 PrivateKey::from_hex("abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890")
784 .unwrap()
785 }
786
787 #[test]
788 fn test_new_creates_wallet_with_correct_identity_key() {
789 let pk = test_private_key();
790 let expected_pub = pk.to_public_key();
791 let wallet = ProtoWallet::new(pk);
792 let identity = wallet
793 .get_public_key(&test_protocol(), "1", &self_counterparty(), false, true)
794 .unwrap();
795 assert_eq!(identity.to_der_hex(), expected_pub.to_der_hex());
796 }
797
798 #[test]
799 fn test_get_public_key_identity_key_true() {
800 let pk = test_private_key();
801 let expected = pk.to_public_key().to_der_hex();
802 let wallet = ProtoWallet::new(pk);
803 let result = wallet
804 .get_public_key(&test_protocol(), "1", &self_counterparty(), false, true)
805 .unwrap();
806 assert_eq!(result.to_der_hex(), expected);
807 }
808
809 #[test]
810 fn test_get_public_key_derived() {
811 let wallet = ProtoWallet::new(test_private_key());
812 let protocol = test_protocol();
813 let pub1 = wallet
814 .get_public_key(&protocol, "key1", &self_counterparty(), true, false)
815 .unwrap();
816 let pub2 = wallet
817 .get_public_key(&protocol, "key2", &self_counterparty(), true, false)
818 .unwrap();
819 assert_ne!(pub1.to_der_hex(), pub2.to_der_hex());
821 }
822
823 #[test]
824 fn test_create_and_verify_signature_roundtrip() {
825 let wallet = ProtoWallet::new(test_private_key());
826 let protocol = test_protocol();
827 let counterparty = self_counterparty();
828 let data = b"hello world signature test";
829
830 let sig = wallet
831 .create_signature(data, &protocol, "sig1", &counterparty)
832 .unwrap();
833 assert!(!sig.is_empty());
834
835 let valid = wallet
836 .verify_signature(data, &sig, &protocol, "sig1", &counterparty, true)
837 .unwrap();
838 assert!(valid, "signature should verify");
839 }
840
841 #[test]
842 fn test_verify_signature_rejects_wrong_data() {
843 let wallet = ProtoWallet::new(test_private_key());
844 let protocol = test_protocol();
845 let counterparty = self_counterparty();
846
847 let sig = wallet
848 .create_signature(b"correct data", &protocol, "sig2", &counterparty)
849 .unwrap();
850 let valid = wallet
851 .verify_signature(b"wrong data", &sig, &protocol, "sig2", &counterparty, true)
852 .unwrap();
853 assert!(!valid, "signature should not verify for wrong data");
854 }
855
856 #[test]
857 fn test_encrypt_decrypt_roundtrip() {
858 let wallet = ProtoWallet::new(test_private_key());
859 let protocol = test_protocol();
860 let counterparty = self_counterparty();
861 let plaintext = b"secret message for encryption";
862
863 let ciphertext = wallet
864 .encrypt(plaintext, &protocol, "enc1", &counterparty)
865 .unwrap();
866 assert_ne!(ciphertext.as_slice(), plaintext);
867
868 let decrypted = wallet
869 .decrypt(&ciphertext, &protocol, "enc1", &counterparty)
870 .unwrap();
871 assert_eq!(decrypted, plaintext);
872 }
873
874 #[test]
875 fn test_encrypt_decrypt_empty_plaintext() {
876 let wallet = ProtoWallet::new(test_private_key());
877 let protocol = test_protocol();
878 let counterparty = self_counterparty();
879
880 let ciphertext = wallet
881 .encrypt(b"", &protocol, "enc2", &counterparty)
882 .unwrap();
883 let decrypted = wallet
884 .decrypt(&ciphertext, &protocol, "enc2", &counterparty)
885 .unwrap();
886 assert!(decrypted.is_empty());
887 }
888
889 #[test]
890 fn test_create_and_verify_hmac_roundtrip() {
891 let wallet = ProtoWallet::new(test_private_key());
892 let protocol = test_protocol();
893 let counterparty = self_counterparty();
894 let data = b"hmac test data";
895
896 let hmac = wallet
897 .create_hmac(data, &protocol, "hmac1", &counterparty)
898 .unwrap();
899 assert_eq!(hmac.len(), 32);
900
901 let valid = wallet
902 .verify_hmac(data, &hmac, &protocol, "hmac1", &counterparty)
903 .unwrap();
904 assert!(valid, "HMAC should verify");
905 }
906
907 #[test]
908 fn test_verify_hmac_rejects_wrong_data() {
909 let wallet = ProtoWallet::new(test_private_key());
910 let protocol = test_protocol();
911 let counterparty = self_counterparty();
912
913 let hmac = wallet
914 .create_hmac(b"correct", &protocol, "hmac2", &counterparty)
915 .unwrap();
916 let valid = wallet
917 .verify_hmac(b"wrong", &hmac, &protocol, "hmac2", &counterparty)
918 .unwrap();
919 assert!(!valid, "HMAC should not verify for wrong data");
920 }
921
922 #[test]
923 fn test_hmac_deterministic() {
924 let wallet = ProtoWallet::new(test_private_key());
925 let protocol = test_protocol();
926 let counterparty = self_counterparty();
927 let data = b"deterministic hmac";
928
929 let hmac1 = wallet
930 .create_hmac(data, &protocol, "hmac3", &counterparty)
931 .unwrap();
932 let hmac2 = wallet
933 .create_hmac(data, &protocol, "hmac3", &counterparty)
934 .unwrap();
935 assert_eq!(hmac1, hmac2);
936 }
937
938 #[test]
939 fn test_anyone_wallet_encrypt_decrypt() {
940 let anyone = ProtoWallet::anyone();
941 let other_key = test_private_key();
942 let other_pub = other_key.to_public_key();
943
944 let counterparty = Counterparty {
945 counterparty_type: CounterpartyType::Other,
946 public_key: Some(other_pub),
947 };
948 let protocol = test_protocol();
949 let plaintext = b"message from anyone";
950
951 let ciphertext = anyone
952 .encrypt(plaintext, &protocol, "anon1", &counterparty)
953 .unwrap();
954 let decrypted = anyone
955 .decrypt(&ciphertext, &protocol, "anon1", &counterparty)
956 .unwrap();
957 assert_eq!(decrypted, plaintext);
958 }
959
960 #[test]
961 fn test_uninitialized_counterparty_defaults_to_self_for_encrypt() {
962 let wallet = ProtoWallet::new(test_private_key());
963 let protocol = test_protocol();
964 let uninit = Counterparty {
965 counterparty_type: CounterpartyType::Uninitialized,
966 public_key: None,
967 };
968 let self_cp = self_counterparty();
969
970 let ct_uninit = wallet.encrypt(b"test", &protocol, "def1", &uninit).unwrap();
971 let decrypted = wallet
973 .decrypt(&ct_uninit, &protocol, "def1", &self_cp)
974 .unwrap();
975 assert_eq!(decrypted, b"test");
976 }
977
978 #[test]
979 fn test_reveal_specific_key_linkage() {
980 let wallet_a = ProtoWallet::new(test_private_key());
981 let verifier_key = PrivateKey::from_hex("ff").unwrap();
982 let verifier_pub = verifier_key.to_public_key();
983
984 let counterparty_key = PrivateKey::from_hex("bb").unwrap();
985 let counterparty_pub = counterparty_key.to_public_key();
986
987 let counterparty = Counterparty {
988 counterparty_type: CounterpartyType::Other,
989 public_key: Some(counterparty_pub),
990 };
991
992 let protocol = test_protocol();
993 let result = wallet_a
994 .reveal_specific_key_linkage(&counterparty, &verifier_pub, &protocol, "link1")
995 .unwrap();
996
997 assert!(!result.encrypted_linkage.is_empty());
998 assert!(!result.encrypted_linkage_proof.is_empty());
999 assert_eq!(result.proof_type, 0);
1000 assert_eq!(result.key_id, "link1");
1001 }
1002
1003 #[test]
1004 fn test_reveal_counterparty_key_linkage() {
1005 let wallet = ProtoWallet::new(test_private_key());
1006 let verifier_key = PrivateKey::from_hex("ff").unwrap();
1007 let verifier_pub = verifier_key.to_public_key();
1008
1009 let counterparty_key = PrivateKey::from_hex("cc").unwrap();
1010 let counterparty_pub = counterparty_key.to_public_key();
1011
1012 let counterparty = Counterparty {
1013 counterparty_type: CounterpartyType::Other,
1014 public_key: Some(counterparty_pub.clone()),
1015 };
1016
1017 let result = wallet
1018 .reveal_counterparty_key_linkage(&counterparty, &verifier_pub)
1019 .unwrap();
1020
1021 assert!(!result.encrypted_linkage.is_empty());
1022 assert!(!result.encrypted_linkage_proof.is_empty());
1023 assert_eq!(
1024 result.counterparty.to_der_hex(),
1025 counterparty_pub.to_der_hex()
1026 );
1027 assert_eq!(result.verifier.to_der_hex(), verifier_pub.to_der_hex());
1028 assert!(!result.revelation_time.is_empty());
1029 }
1030
1031 async fn get_pub_key_via_trait<W: WalletInterface>(
1037 w: &W,
1038 args: GetPublicKeyArgs,
1039 ) -> Result<GetPublicKeyResult, WalletError> {
1040 w.get_public_key(args, None).await
1041 }
1042
1043 #[tokio::test]
1044 async fn test_wallet_interface_get_public_key_identity() {
1045 let pk = test_private_key();
1046 let expected = pk.to_public_key().to_der_hex();
1047 let wallet = ProtoWallet::new(pk);
1048
1049 let result = get_pub_key_via_trait(
1050 &wallet,
1051 GetPublicKeyArgs {
1052 identity_key: true,
1053 protocol_id: None,
1054 key_id: None,
1055 counterparty: None,
1056 privileged: false,
1057 privileged_reason: None,
1058 for_self: None,
1059 seek_permission: None,
1060 },
1061 )
1062 .await
1063 .unwrap();
1064
1065 assert_eq!(result.public_key.to_der_hex(), expected);
1066 }
1067
1068 #[tokio::test]
1069 async fn test_wallet_interface_get_public_key_derived() {
1070 let wallet = ProtoWallet::new(test_private_key());
1071
1072 let result = get_pub_key_via_trait(
1073 &wallet,
1074 GetPublicKeyArgs {
1075 identity_key: false,
1076 protocol_id: Some(test_protocol()),
1077 key_id: Some("derived1".to_string()),
1078 counterparty: Some(self_counterparty()),
1079 privileged: false,
1080 privileged_reason: None,
1081 for_self: Some(true),
1082 seek_permission: None,
1083 },
1084 )
1085 .await
1086 .unwrap();
1087
1088 let direct = wallet
1090 .get_public_key(
1091 &test_protocol(),
1092 "derived1",
1093 &self_counterparty(),
1094 true,
1095 false,
1096 )
1097 .unwrap();
1098 assert_eq!(result.public_key.to_der_hex(), direct.to_der_hex());
1099 }
1100
1101 #[tokio::test]
1102 async fn test_wallet_interface_privileged_rejected() {
1103 let wallet = ProtoWallet::new(test_private_key());
1104 let err = WalletInterface::get_public_key(
1105 &wallet,
1106 GetPublicKeyArgs {
1107 identity_key: true,
1108 protocol_id: None,
1109 key_id: None,
1110 counterparty: None,
1111 privileged: true,
1112 privileged_reason: Some("test".to_string()),
1113 for_self: None,
1114 seek_permission: None,
1115 },
1116 None,
1117 )
1118 .await;
1119
1120 assert!(err.is_err());
1121 let msg = format!("{}", err.unwrap_err());
1122 assert!(msg.contains("not implemented"), "got: {}", msg);
1123 }
1124
1125 #[tokio::test]
1126 async fn test_wallet_interface_create_verify_signature() {
1127 let wallet = ProtoWallet::new(test_private_key());
1128 let data = b"test data for wallet interface sig".to_vec();
1129
1130 let sig_result = WalletInterface::create_signature(
1131 &wallet,
1132 CreateSignatureArgs {
1133 protocol_id: test_protocol(),
1134 key_id: "wsig1".to_string(),
1135 counterparty: self_counterparty(),
1136 data: data.clone(),
1137 privileged: false,
1138 privileged_reason: None,
1139 seek_permission: None,
1140 },
1141 None,
1142 )
1143 .await
1144 .unwrap();
1145
1146 let verify_result = WalletInterface::verify_signature(
1147 &wallet,
1148 VerifySignatureArgs {
1149 protocol_id: test_protocol(),
1150 key_id: "wsig1".to_string(),
1151 counterparty: self_counterparty(),
1152 data,
1153 signature: sig_result.signature,
1154 for_self: Some(true),
1155 privileged: false,
1156 privileged_reason: None,
1157 seek_permission: None,
1158 },
1159 None,
1160 )
1161 .await
1162 .unwrap();
1163
1164 assert!(verify_result.valid);
1165 }
1166
1167 #[tokio::test]
1168 async fn test_wallet_interface_encrypt_decrypt() {
1169 let wallet = ProtoWallet::new(test_private_key());
1170 let plaintext = b"wallet interface encrypt test".to_vec();
1171
1172 let enc = WalletInterface::encrypt(
1173 &wallet,
1174 EncryptArgs {
1175 protocol_id: test_protocol(),
1176 key_id: "wenc1".to_string(),
1177 counterparty: self_counterparty(),
1178 plaintext: plaintext.clone(),
1179 privileged: false,
1180 privileged_reason: None,
1181 seek_permission: None,
1182 },
1183 None,
1184 )
1185 .await
1186 .unwrap();
1187
1188 let dec = WalletInterface::decrypt(
1189 &wallet,
1190 DecryptArgs {
1191 protocol_id: test_protocol(),
1192 key_id: "wenc1".to_string(),
1193 counterparty: self_counterparty(),
1194 ciphertext: enc.ciphertext,
1195 privileged: false,
1196 privileged_reason: None,
1197 seek_permission: None,
1198 },
1199 None,
1200 )
1201 .await
1202 .unwrap();
1203
1204 assert_eq!(dec.plaintext, plaintext);
1205 }
1206
1207 #[tokio::test]
1208 async fn test_wallet_interface_hmac_roundtrip() {
1209 let wallet = ProtoWallet::new(test_private_key());
1210 let data = b"wallet interface hmac test".to_vec();
1211
1212 let hmac_result = WalletInterface::create_hmac(
1213 &wallet,
1214 CreateHmacArgs {
1215 protocol_id: test_protocol(),
1216 key_id: "whmac1".to_string(),
1217 counterparty: self_counterparty(),
1218 data: data.clone(),
1219 privileged: false,
1220 privileged_reason: None,
1221 seek_permission: None,
1222 },
1223 None,
1224 )
1225 .await
1226 .unwrap();
1227
1228 assert_eq!(hmac_result.hmac.len(), 32);
1229
1230 let verify = WalletInterface::verify_hmac(
1231 &wallet,
1232 VerifyHmacArgs {
1233 protocol_id: test_protocol(),
1234 key_id: "whmac1".to_string(),
1235 counterparty: self_counterparty(),
1236 data,
1237 hmac: hmac_result.hmac,
1238 privileged: false,
1239 privileged_reason: None,
1240 seek_permission: None,
1241 },
1242 None,
1243 )
1244 .await
1245 .unwrap();
1246
1247 assert!(verify.valid);
1248 }
1249
1250 #[tokio::test]
1251 async fn test_wallet_interface_unsupported_methods_return_not_implemented() {
1252 use crate::wallet::interfaces::*;
1253 let wallet = ProtoWallet::new(test_private_key());
1254
1255 let err = WalletInterface::is_authenticated(&wallet, None).await;
1258 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1259
1260 let err = WalletInterface::wait_for_authentication(&wallet, None).await;
1261 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1262
1263 let err = WalletInterface::get_network(&wallet, None).await;
1264 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1265
1266 let err = WalletInterface::get_version(&wallet, None).await;
1267 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1268
1269 let err = WalletInterface::get_height(&wallet, None).await;
1270 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1271
1272 let err =
1273 WalletInterface::get_header_for_height(&wallet, GetHeaderArgs { height: 0 }, None)
1274 .await;
1275 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1276
1277 let err = WalletInterface::list_outputs(
1278 &wallet,
1279 ListOutputsArgs {
1280 basket: "test".to_string(),
1281 tags: vec![],
1282 tag_query_mode: None,
1283 include: None,
1284 include_custom_instructions: None,
1285 include_tags: None,
1286 include_labels: None,
1287 limit: Some(10),
1288 offset: None,
1289 seek_permission: None,
1290 },
1291 None,
1292 )
1293 .await;
1294 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1295 }
1296
1297 #[tokio::test]
1298 async fn test_wallet_interface_reveal_counterparty_key_linkage() {
1299 let wallet = ProtoWallet::new(test_private_key());
1300 let verifier_key = PrivateKey::from_hex("ff").unwrap();
1301 let counterparty_key = PrivateKey::from_hex("cc").unwrap();
1302
1303 let result = WalletInterface::reveal_counterparty_key_linkage(
1304 &wallet,
1305 RevealCounterpartyKeyLinkageArgs {
1306 counterparty: counterparty_key.to_public_key(),
1307 verifier: verifier_key.to_public_key(),
1308 privileged: None,
1309 privileged_reason: None,
1310 },
1311 None,
1312 )
1313 .await
1314 .unwrap();
1315
1316 assert!(!result.encrypted_linkage.is_empty());
1317 assert!(!result.encrypted_linkage_proof.is_empty());
1318 assert_eq!(
1319 result.counterparty.to_der_hex(),
1320 counterparty_key.to_public_key().to_der_hex()
1321 );
1322 assert!(!result.revelation_time.is_empty());
1323 }
1324
1325 #[tokio::test]
1326 async fn test_wallet_interface_reveal_specific_key_linkage() {
1327 let wallet = ProtoWallet::new(test_private_key());
1328 let verifier_key = PrivateKey::from_hex("ff").unwrap();
1329 let counterparty_key = PrivateKey::from_hex("bb").unwrap();
1330
1331 let result = WalletInterface::reveal_specific_key_linkage(
1332 &wallet,
1333 RevealSpecificKeyLinkageArgs {
1334 counterparty: Counterparty {
1335 counterparty_type: CounterpartyType::Other,
1336 public_key: Some(counterparty_key.to_public_key()),
1337 },
1338 verifier: verifier_key.to_public_key(),
1339 protocol_id: test_protocol(),
1340 key_id: "wlink1".to_string(),
1341 privileged: None,
1342 privileged_reason: None,
1343 },
1344 None,
1345 )
1346 .await
1347 .unwrap();
1348
1349 assert!(!result.encrypted_linkage.is_empty());
1350 assert_eq!(result.proof_type, 0);
1351 assert_eq!(result.key_id, "wlink1");
1352 }
1353}