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