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