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 encrypt_with_iv_sync(
236 &self,
237 plaintext: &[u8],
238 protocol: &Protocol,
239 key_id: &str,
240 counterparty: &Counterparty,
241 iv: &[u8; 32],
242 ) -> Result<Vec<u8>, WalletError> {
243 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
244 let sym_key = self
245 .key_deriver
246 .derive_symmetric_key(protocol, key_id, &effective)?;
247 Ok(sym_key.encrypt_with_iv(plaintext, iv)?)
248 }
249
250 pub fn decrypt_sync(
255 &self,
256 ciphertext: &[u8],
257 protocol: &Protocol,
258 key_id: &str,
259 counterparty: &Counterparty,
260 ) -> Result<Vec<u8>, WalletError> {
261 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
262 let sym_key = self
263 .key_deriver
264 .derive_symmetric_key(protocol, key_id, &effective)?;
265 Ok(sym_key.decrypt(ciphertext)?)
266 }
267
268 pub fn create_hmac_sync(
272 &self,
273 data: &[u8],
274 protocol: &Protocol,
275 key_id: &str,
276 counterparty: &Counterparty,
277 ) -> Result<Vec<u8>, WalletError> {
278 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
279 let sym_key = self
280 .key_deriver
281 .derive_symmetric_key(protocol, key_id, &effective)?;
282 let key_bytes = sym_key.to_bytes();
283 let hmac = sha256_hmac(&key_bytes, data);
284 Ok(hmac.to_vec())
285 }
286
287 pub fn verify_hmac_sync(
292 &self,
293 data: &[u8],
294 hmac_value: &[u8],
295 protocol: &Protocol,
296 key_id: &str,
297 counterparty: &Counterparty,
298 ) -> Result<bool, WalletError> {
299 let expected = self.create_hmac_sync(data, protocol, key_id, counterparty)?;
300 Ok(constant_time_eq(&expected, hmac_value))
302 }
303
304 pub fn reveal_counterparty_key_linkage_sync(
310 &self,
311 counterparty: &Counterparty,
312 verifier: &PublicKey,
313 ) -> Result<RevealCounterpartyResult, WalletError> {
314 let linkage_point = self.key_deriver.reveal_counterparty_secret(counterparty)?;
316 let linkage_bytes = linkage_point.to_der(); let prover = self.key_deriver.identity_key();
319
320 let revelation_time = current_utc_timestamp();
323
324 let verifier_counterparty = Counterparty {
325 counterparty_type: CounterpartyType::Other,
326 public_key: Some(verifier.clone()),
327 };
328
329 let linkage_protocol = Protocol {
330 security_level: 2,
331 protocol: "counterparty linkage revelation".to_string(),
332 };
333
334 let encrypted_linkage = self.encrypt_sync(
336 &linkage_bytes,
337 &linkage_protocol,
338 &revelation_time,
339 &verifier_counterparty,
340 )?;
341
342 let linkage_point = Point::from_der(&linkage_bytes)
345 .map_err(|e| WalletError::Internal(format!("invalid linkage point: {}", e)))?;
346 let counterparty_pub = match &counterparty.public_key {
347 Some(pk) => pk.clone(),
348 None => {
349 return Err(WalletError::InvalidParameter(
350 "counterparty public key required for linkage revelation".to_string(),
351 ))
352 }
353 };
354 let schnorr_proof = schnorr_generate_proof(
355 self.key_deriver.root_key(),
356 &self.key_deriver.identity_key(),
357 &counterparty_pub,
358 &linkage_point,
359 )?;
360 let mut proof_bin = Vec::with_capacity(33 + 33 + 32);
362 proof_bin.extend_from_slice(&schnorr_proof.r_point.to_der(true));
363 proof_bin.extend_from_slice(&schnorr_proof.s_prime.to_der(true));
364 proof_bin.extend_from_slice(&schnorr_proof.z.to_bytes());
365 let encrypted_proof = self.encrypt_sync(
366 &proof_bin,
367 &linkage_protocol,
368 &revelation_time,
369 &verifier_counterparty,
370 )?;
371
372 Ok(RevealCounterpartyResult {
373 prover,
374 counterparty: counterparty_pub,
375 verifier: verifier.clone(),
376 revelation_time,
377 encrypted_linkage,
378 encrypted_linkage_proof: encrypted_proof,
379 })
380 }
381
382 pub fn reveal_specific_key_linkage_sync(
387 &self,
388 counterparty: &Counterparty,
389 verifier: &PublicKey,
390 protocol: &Protocol,
391 key_id: &str,
392 ) -> Result<RevealSpecificResult, WalletError> {
393 let linkage = self
395 .key_deriver
396 .reveal_specific_secret(counterparty, protocol, key_id)?;
397
398 let prover = self.key_deriver.identity_key();
399
400 let verifier_counterparty = Counterparty {
401 counterparty_type: CounterpartyType::Other,
402 public_key: Some(verifier.clone()),
403 };
404
405 let encrypt_protocol = Protocol {
407 security_level: 2,
408 protocol: format!(
409 "specific linkage revelation {} {}",
410 protocol.security_level, protocol.protocol
411 ),
412 };
413
414 let encrypted_linkage =
416 self.encrypt_sync(&linkage, &encrypt_protocol, key_id, &verifier_counterparty)?;
417
418 let proof_bytes: [u8; 1] = [0];
420 let encrypted_proof = self.encrypt_sync(
421 &proof_bytes,
422 &encrypt_protocol,
423 key_id,
424 &verifier_counterparty,
425 )?;
426
427 let counterparty_pub = match &counterparty.public_key {
429 Some(pk) => pk.clone(),
430 None => {
431 return Err(WalletError::InvalidParameter(
432 "counterparty public key required for linkage revelation".to_string(),
433 ))
434 }
435 };
436
437 Ok(RevealSpecificResult {
438 encrypted_linkage,
439 encrypted_linkage_proof: encrypted_proof,
440 prover,
441 verifier: verifier.clone(),
442 counterparty: counterparty_pub,
443 protocol: protocol.clone(),
444 key_id: key_id.to_string(),
445 proof_type: 0,
446 })
447 }
448
449 fn default_counterparty(
451 &self,
452 counterparty: &Counterparty,
453 default_type: CounterpartyType,
454 ) -> Counterparty {
455 if counterparty.counterparty_type == CounterpartyType::Uninitialized {
456 Counterparty {
457 counterparty_type: default_type,
458 public_key: None,
459 }
460 } else {
461 counterparty.clone()
462 }
463 }
464}
465
466#[async_trait::async_trait]
474impl WalletInterface for ProtoWallet {
475 async fn create_action(
478 &self,
479 _args: CreateActionArgs,
480 _originator: Option<&str>,
481 ) -> Result<CreateActionResult, WalletError> {
482 Err(WalletError::NotImplemented("createAction".to_string()))
483 }
484
485 async fn sign_action(
486 &self,
487 _args: SignActionArgs,
488 _originator: Option<&str>,
489 ) -> Result<SignActionResult, WalletError> {
490 Err(WalletError::NotImplemented("signAction".to_string()))
491 }
492
493 async fn abort_action(
494 &self,
495 _args: AbortActionArgs,
496 _originator: Option<&str>,
497 ) -> Result<AbortActionResult, WalletError> {
498 Err(WalletError::NotImplemented("abortAction".to_string()))
499 }
500
501 async fn list_actions(
502 &self,
503 _args: ListActionsArgs,
504 _originator: Option<&str>,
505 ) -> Result<ListActionsResult, WalletError> {
506 Err(WalletError::NotImplemented("listActions".to_string()))
507 }
508
509 async fn internalize_action(
510 &self,
511 _args: InternalizeActionArgs,
512 _originator: Option<&str>,
513 ) -> Result<InternalizeActionResult, WalletError> {
514 Err(WalletError::NotImplemented("internalizeAction".to_string()))
515 }
516
517 async fn list_outputs(
520 &self,
521 _args: ListOutputsArgs,
522 _originator: Option<&str>,
523 ) -> Result<ListOutputsResult, WalletError> {
524 Err(WalletError::NotImplemented("listOutputs".to_string()))
525 }
526
527 async fn relinquish_output(
528 &self,
529 _args: RelinquishOutputArgs,
530 _originator: Option<&str>,
531 ) -> Result<RelinquishOutputResult, WalletError> {
532 Err(WalletError::NotImplemented("relinquishOutput".to_string()))
533 }
534
535 async fn get_public_key(
538 &self,
539 args: GetPublicKeyArgs,
540 _originator: Option<&str>,
541 ) -> Result<GetPublicKeyResult, WalletError> {
542 if args.privileged {
543 return Err(WalletError::NotImplemented(
544 "privileged key access not supported by ProtoWallet".to_string(),
545 ));
546 }
547 let protocol = args.protocol_id.unwrap_or(Protocol {
548 security_level: 0,
549 protocol: String::new(),
550 });
551 let key_id = args.key_id.unwrap_or_default();
552 let counterparty = args.counterparty.unwrap_or(Counterparty {
553 counterparty_type: CounterpartyType::Uninitialized,
554 public_key: None,
555 });
556 let for_self = args.for_self.unwrap_or(false);
557 let pk = self.get_public_key_sync(
558 &protocol,
559 &key_id,
560 &counterparty,
561 for_self,
562 args.identity_key,
563 )?;
564 Ok(GetPublicKeyResult { public_key: pk })
565 }
566
567 async fn reveal_counterparty_key_linkage(
568 &self,
569 args: RevealCounterpartyKeyLinkageArgs,
570 _originator: Option<&str>,
571 ) -> Result<RevealCounterpartyKeyLinkageResult, WalletError> {
572 let counterparty = Counterparty {
573 counterparty_type: CounterpartyType::Other,
574 public_key: Some(args.counterparty),
575 };
576 let result = self.reveal_counterparty_key_linkage_sync(&counterparty, &args.verifier)?;
577 Ok(RevealCounterpartyKeyLinkageResult {
578 prover: result.prover,
579 counterparty: result.counterparty,
580 verifier: result.verifier,
581 revelation_time: result.revelation_time,
582 encrypted_linkage: result.encrypted_linkage,
583 encrypted_linkage_proof: result.encrypted_linkage_proof,
584 })
585 }
586
587 async fn reveal_specific_key_linkage(
588 &self,
589 args: RevealSpecificKeyLinkageArgs,
590 _originator: Option<&str>,
591 ) -> Result<RevealSpecificKeyLinkageResult, WalletError> {
592 let result = self.reveal_specific_key_linkage_sync(
593 &args.counterparty,
594 &args.verifier,
595 &args.protocol_id,
596 &args.key_id,
597 )?;
598 Ok(RevealSpecificKeyLinkageResult {
599 encrypted_linkage: result.encrypted_linkage,
600 encrypted_linkage_proof: result.encrypted_linkage_proof,
601 prover: result.prover,
602 verifier: result.verifier,
603 counterparty: result.counterparty,
604 protocol_id: result.protocol.clone(),
605 key_id: result.key_id.clone(),
606 proof_type: result.proof_type,
607 })
608 }
609
610 async fn encrypt(
611 &self,
612 args: EncryptArgs,
613 _originator: Option<&str>,
614 ) -> Result<EncryptResult, WalletError> {
615 let ciphertext = self.encrypt_sync(
616 &args.plaintext,
617 &args.protocol_id,
618 &args.key_id,
619 &args.counterparty,
620 )?;
621 Ok(EncryptResult { ciphertext })
622 }
623
624 async fn decrypt(
625 &self,
626 args: DecryptArgs,
627 _originator: Option<&str>,
628 ) -> Result<DecryptResult, WalletError> {
629 let plaintext = self.decrypt_sync(
630 &args.ciphertext,
631 &args.protocol_id,
632 &args.key_id,
633 &args.counterparty,
634 )?;
635 Ok(DecryptResult { plaintext })
636 }
637
638 async fn create_hmac(
639 &self,
640 args: CreateHmacArgs,
641 _originator: Option<&str>,
642 ) -> Result<CreateHmacResult, WalletError> {
643 let hmac = self.create_hmac_sync(
644 &args.data,
645 &args.protocol_id,
646 &args.key_id,
647 &args.counterparty,
648 )?;
649 Ok(CreateHmacResult { hmac })
650 }
651
652 async fn verify_hmac(
653 &self,
654 args: VerifyHmacArgs,
655 _originator: Option<&str>,
656 ) -> Result<VerifyHmacResult, WalletError> {
657 let valid = self.verify_hmac_sync(
658 &args.data,
659 &args.hmac,
660 &args.protocol_id,
661 &args.key_id,
662 &args.counterparty,
663 )?;
664 if !valid {
666 return Err(WalletError::InvalidHmac);
667 }
668 Ok(VerifyHmacResult { valid: true })
669 }
670
671 async fn create_signature(
672 &self,
673 args: CreateSignatureArgs,
674 _originator: Option<&str>,
675 ) -> Result<CreateSignatureResult, WalletError> {
676 let signature = self.create_signature_sync(
677 args.data.as_deref(),
678 args.hash_to_directly_sign.as_deref(),
679 &args.protocol_id,
680 &args.key_id,
681 &args.counterparty,
682 )?;
683 Ok(CreateSignatureResult { signature })
684 }
685
686 async fn verify_signature(
687 &self,
688 args: VerifySignatureArgs,
689 _originator: Option<&str>,
690 ) -> Result<VerifySignatureResult, WalletError> {
691 let for_self = args.for_self.unwrap_or(false);
692 let valid = self.verify_signature_sync(
693 args.data.as_deref(),
694 args.hash_to_directly_verify.as_deref(),
695 &args.signature,
696 &args.protocol_id,
697 &args.key_id,
698 &args.counterparty,
699 for_self,
700 )?;
701 if !valid {
703 return Err(WalletError::InvalidSignature);
704 }
705 Ok(VerifySignatureResult { valid: true })
706 }
707
708 async fn acquire_certificate(
711 &self,
712 _args: AcquireCertificateArgs,
713 _originator: Option<&str>,
714 ) -> Result<Certificate, WalletError> {
715 Err(WalletError::NotImplemented(
716 "acquireCertificate".to_string(),
717 ))
718 }
719
720 async fn list_certificates(
721 &self,
722 _args: ListCertificatesArgs,
723 _originator: Option<&str>,
724 ) -> Result<ListCertificatesResult, WalletError> {
725 Err(WalletError::NotImplemented("listCertificates".to_string()))
726 }
727
728 async fn prove_certificate(
729 &self,
730 _args: ProveCertificateArgs,
731 _originator: Option<&str>,
732 ) -> Result<ProveCertificateResult, WalletError> {
733 Err(WalletError::NotImplemented("proveCertificate".to_string()))
734 }
735
736 async fn relinquish_certificate(
737 &self,
738 _args: RelinquishCertificateArgs,
739 _originator: Option<&str>,
740 ) -> Result<RelinquishCertificateResult, WalletError> {
741 Err(WalletError::NotImplemented(
742 "relinquishCertificate".to_string(),
743 ))
744 }
745
746 async fn discover_by_identity_key(
749 &self,
750 _args: DiscoverByIdentityKeyArgs,
751 _originator: Option<&str>,
752 ) -> Result<DiscoverCertificatesResult, WalletError> {
753 Err(WalletError::NotImplemented(
754 "discoverByIdentityKey".to_string(),
755 ))
756 }
757
758 async fn discover_by_attributes(
759 &self,
760 _args: DiscoverByAttributesArgs,
761 _originator: Option<&str>,
762 ) -> Result<DiscoverCertificatesResult, WalletError> {
763 Err(WalletError::NotImplemented(
764 "discoverByAttributes".to_string(),
765 ))
766 }
767
768 async fn is_authenticated(
771 &self,
772 _originator: Option<&str>,
773 ) -> Result<AuthenticatedResult, WalletError> {
774 Err(WalletError::NotImplemented("isAuthenticated".to_string()))
775 }
776
777 async fn wait_for_authentication(
778 &self,
779 _originator: Option<&str>,
780 ) -> Result<AuthenticatedResult, WalletError> {
781 Err(WalletError::NotImplemented(
782 "waitForAuthentication".to_string(),
783 ))
784 }
785
786 async fn get_height(&self, _originator: Option<&str>) -> Result<GetHeightResult, WalletError> {
787 Err(WalletError::NotImplemented("getHeight".to_string()))
788 }
789
790 async fn get_header_for_height(
791 &self,
792 _args: GetHeaderArgs,
793 _originator: Option<&str>,
794 ) -> Result<GetHeaderResult, WalletError> {
795 Err(WalletError::NotImplemented(
796 "getHeaderForHeight".to_string(),
797 ))
798 }
799
800 async fn get_network(
801 &self,
802 _originator: Option<&str>,
803 ) -> Result<GetNetworkResult, WalletError> {
804 Err(WalletError::NotImplemented("getNetwork".to_string()))
805 }
806
807 async fn get_version(
808 &self,
809 _originator: Option<&str>,
810 ) -> Result<GetVersionResult, WalletError> {
811 Err(WalletError::NotImplemented("getVersion".to_string()))
812 }
813}
814
815fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
817 if a.len() != b.len() {
818 return false;
819 }
820 let mut diff: u8 = 0;
821 for (x, y) in a.iter().zip(b.iter()) {
822 diff |= x ^ y;
823 }
824 diff == 0
825}
826
827fn current_utc_timestamp() -> String {
829 use std::time::{SystemTime, UNIX_EPOCH};
832 let now = SystemTime::now()
833 .duration_since(UNIX_EPOCH)
834 .unwrap_or_default();
835 format!("{}", now.as_secs())
836}
837
838#[cfg(test)]
839mod tests {
840 use super::*;
841 use crate::wallet::types::{BooleanDefaultFalse, BooleanDefaultTrue};
842
843 fn test_protocol() -> Protocol {
844 Protocol {
845 security_level: 2,
846 protocol: "test proto wallet".to_string(),
847 }
848 }
849
850 fn self_counterparty() -> Counterparty {
851 Counterparty {
852 counterparty_type: CounterpartyType::Self_,
853 public_key: None,
854 }
855 }
856
857 fn test_private_key() -> PrivateKey {
858 PrivateKey::from_hex("abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890")
859 .unwrap()
860 }
861
862 #[test]
863 fn test_new_creates_wallet_with_correct_identity_key() {
864 let pk = test_private_key();
865 let expected_pub = pk.to_public_key();
866 let wallet = ProtoWallet::new(pk);
867 let identity = wallet
868 .get_public_key_sync(&test_protocol(), "1", &self_counterparty(), false, true)
869 .unwrap();
870 assert_eq!(identity.to_der_hex(), expected_pub.to_der_hex());
871 }
872
873 #[test]
874 fn test_get_public_key_identity_key_true() {
875 let pk = test_private_key();
876 let expected = pk.to_public_key().to_der_hex();
877 let wallet = ProtoWallet::new(pk);
878 let result = wallet
879 .get_public_key_sync(&test_protocol(), "1", &self_counterparty(), false, true)
880 .unwrap();
881 assert_eq!(result.to_der_hex(), expected);
882 }
883
884 #[test]
885 fn test_get_public_key_derived() {
886 let wallet = ProtoWallet::new(test_private_key());
887 let protocol = test_protocol();
888 let pub1 = wallet
889 .get_public_key_sync(&protocol, "key1", &self_counterparty(), true, false)
890 .unwrap();
891 let pub2 = wallet
892 .get_public_key_sync(&protocol, "key2", &self_counterparty(), true, false)
893 .unwrap();
894 assert_ne!(pub1.to_der_hex(), pub2.to_der_hex());
896 }
897
898 #[test]
899 fn test_create_and_verify_signature_roundtrip() {
900 let wallet = ProtoWallet::new(test_private_key());
901 let protocol = test_protocol();
902 let counterparty = self_counterparty();
903 let data = b"hello world signature test";
904
905 let sig = wallet
906 .create_signature_sync(Some(data), None, &protocol, "sig1", &counterparty)
907 .unwrap();
908 assert!(!sig.is_empty());
909
910 let valid = wallet
911 .verify_signature_sync(
912 Some(data),
913 None,
914 &sig,
915 &protocol,
916 "sig1",
917 &counterparty,
918 true,
919 )
920 .unwrap();
921 assert!(valid, "signature should verify");
922 }
923
924 #[test]
925 fn test_verify_signature_rejects_wrong_data() {
926 let wallet = ProtoWallet::new(test_private_key());
927 let protocol = test_protocol();
928 let counterparty = self_counterparty();
929
930 let sig = wallet
931 .create_signature_sync(
932 Some(b"correct data"),
933 None,
934 &protocol,
935 "sig2",
936 &counterparty,
937 )
938 .unwrap();
939 let valid = wallet
940 .verify_signature_sync(
941 Some(b"wrong data"),
942 None,
943 &sig,
944 &protocol,
945 "sig2",
946 &counterparty,
947 true,
948 )
949 .unwrap();
950 assert!(!valid, "signature should not verify for wrong data");
951 }
952
953 #[test]
954 fn test_encrypt_decrypt_roundtrip() {
955 let wallet = ProtoWallet::new(test_private_key());
956 let protocol = test_protocol();
957 let counterparty = self_counterparty();
958 let plaintext = b"secret message for encryption";
959
960 let ciphertext = wallet
961 .encrypt_sync(plaintext, &protocol, "enc1", &counterparty)
962 .unwrap();
963 assert_ne!(ciphertext.as_slice(), plaintext);
964
965 let decrypted = wallet
966 .decrypt_sync(&ciphertext, &protocol, "enc1", &counterparty)
967 .unwrap();
968 assert_eq!(decrypted, plaintext);
969 }
970
971 #[test]
972 fn test_encrypt_decrypt_empty_plaintext() {
973 let wallet = ProtoWallet::new(test_private_key());
974 let protocol = test_protocol();
975 let counterparty = self_counterparty();
976
977 let ciphertext = wallet
978 .encrypt_sync(b"", &protocol, "enc2", &counterparty)
979 .unwrap();
980 let decrypted = wallet
981 .decrypt_sync(&ciphertext, &protocol, "enc2", &counterparty)
982 .unwrap();
983 assert!(decrypted.is_empty());
984 }
985
986 #[test]
987 fn test_create_and_verify_hmac_roundtrip() {
988 let wallet = ProtoWallet::new(test_private_key());
989 let protocol = test_protocol();
990 let counterparty = self_counterparty();
991 let data = b"hmac test data";
992
993 let hmac = wallet
994 .create_hmac_sync(data, &protocol, "hmac1", &counterparty)
995 .unwrap();
996 assert_eq!(hmac.len(), 32);
997
998 let valid = wallet
999 .verify_hmac_sync(data, &hmac, &protocol, "hmac1", &counterparty)
1000 .unwrap();
1001 assert!(valid, "HMAC should verify");
1002 }
1003
1004 #[test]
1005 fn test_verify_hmac_rejects_wrong_data() {
1006 let wallet = ProtoWallet::new(test_private_key());
1007 let protocol = test_protocol();
1008 let counterparty = self_counterparty();
1009
1010 let hmac = wallet
1011 .create_hmac_sync(b"correct", &protocol, "hmac2", &counterparty)
1012 .unwrap();
1013 let valid = wallet
1014 .verify_hmac_sync(b"wrong", &hmac, &protocol, "hmac2", &counterparty)
1015 .unwrap();
1016 assert!(!valid, "HMAC should not verify for wrong data");
1017 }
1018
1019 #[test]
1020 fn test_hmac_deterministic() {
1021 let wallet = ProtoWallet::new(test_private_key());
1022 let protocol = test_protocol();
1023 let counterparty = self_counterparty();
1024 let data = b"deterministic hmac";
1025
1026 let hmac1 = wallet
1027 .create_hmac_sync(data, &protocol, "hmac3", &counterparty)
1028 .unwrap();
1029 let hmac2 = wallet
1030 .create_hmac_sync(data, &protocol, "hmac3", &counterparty)
1031 .unwrap();
1032 assert_eq!(hmac1, hmac2);
1033 }
1034
1035 #[test]
1036 fn test_anyone_wallet_encrypt_decrypt() {
1037 let anyone = ProtoWallet::anyone();
1038 let other_key = test_private_key();
1039 let other_pub = other_key.to_public_key();
1040
1041 let counterparty = Counterparty {
1042 counterparty_type: CounterpartyType::Other,
1043 public_key: Some(other_pub),
1044 };
1045 let protocol = test_protocol();
1046 let plaintext = b"message from anyone";
1047
1048 let ciphertext = anyone
1049 .encrypt_sync(plaintext, &protocol, "anon1", &counterparty)
1050 .unwrap();
1051 let decrypted = anyone
1052 .decrypt_sync(&ciphertext, &protocol, "anon1", &counterparty)
1053 .unwrap();
1054 assert_eq!(decrypted, plaintext);
1055 }
1056
1057 #[test]
1058 fn test_uninitialized_counterparty_defaults_to_self_for_encrypt() {
1059 let wallet = ProtoWallet::new(test_private_key());
1060 let protocol = test_protocol();
1061 let uninit = Counterparty {
1062 counterparty_type: CounterpartyType::Uninitialized,
1063 public_key: None,
1064 };
1065 let self_cp = self_counterparty();
1066
1067 let ct_uninit = wallet
1068 .encrypt_sync(b"test", &protocol, "def1", &uninit)
1069 .unwrap();
1070 let decrypted = wallet
1072 .decrypt_sync(&ct_uninit, &protocol, "def1", &self_cp)
1073 .unwrap();
1074 assert_eq!(decrypted, b"test");
1075 }
1076
1077 #[test]
1078 fn test_reveal_specific_key_linkage() {
1079 let wallet_a = 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("bb").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),
1089 };
1090
1091 let protocol = test_protocol();
1092 let result = wallet_a
1093 .reveal_specific_key_linkage_sync(&counterparty, &verifier_pub, &protocol, "link1")
1094 .unwrap();
1095
1096 assert!(!result.encrypted_linkage.is_empty());
1097 assert!(!result.encrypted_linkage_proof.is_empty());
1098 assert_eq!(result.proof_type, 0);
1099 assert_eq!(result.key_id, "link1");
1100 }
1101
1102 #[test]
1103 fn test_reveal_counterparty_key_linkage() {
1104 let wallet = ProtoWallet::new(test_private_key());
1105 let verifier_key = PrivateKey::from_hex("ff").unwrap();
1106 let verifier_pub = verifier_key.to_public_key();
1107
1108 let counterparty_key = PrivateKey::from_hex("cc").unwrap();
1109 let counterparty_pub = counterparty_key.to_public_key();
1110
1111 let counterparty = Counterparty {
1112 counterparty_type: CounterpartyType::Other,
1113 public_key: Some(counterparty_pub.clone()),
1114 };
1115
1116 let result = wallet
1117 .reveal_counterparty_key_linkage_sync(&counterparty, &verifier_pub)
1118 .unwrap();
1119
1120 assert!(!result.encrypted_linkage.is_empty());
1121 assert!(!result.encrypted_linkage_proof.is_empty());
1122 assert_eq!(
1123 result.counterparty.to_der_hex(),
1124 counterparty_pub.to_der_hex()
1125 );
1126 assert_eq!(result.verifier.to_der_hex(), verifier_pub.to_der_hex());
1127 assert!(!result.revelation_time.is_empty());
1128 }
1129
1130 async fn get_pub_key_via_trait<W: WalletInterface + ?Sized>(
1136 w: &W,
1137 args: GetPublicKeyArgs,
1138 ) -> Result<GetPublicKeyResult, WalletError> {
1139 w.get_public_key(args, None).await
1140 }
1141
1142 #[tokio::test]
1143 async fn test_wallet_interface_get_public_key_identity() {
1144 let pk = test_private_key();
1145 let expected = pk.to_public_key().to_der_hex();
1146 let wallet = ProtoWallet::new(pk);
1147
1148 let result = get_pub_key_via_trait(
1149 &wallet,
1150 GetPublicKeyArgs {
1151 identity_key: true,
1152 protocol_id: None,
1153 key_id: None,
1154 counterparty: None,
1155 privileged: false,
1156 privileged_reason: None,
1157 for_self: None,
1158 seek_permission: None,
1159 },
1160 )
1161 .await
1162 .unwrap();
1163
1164 assert_eq!(result.public_key.to_der_hex(), expected);
1165 }
1166
1167 #[tokio::test]
1168 async fn test_wallet_interface_get_public_key_derived() {
1169 let wallet = ProtoWallet::new(test_private_key());
1170
1171 let result = get_pub_key_via_trait(
1172 &wallet,
1173 GetPublicKeyArgs {
1174 identity_key: false,
1175 protocol_id: Some(test_protocol()),
1176 key_id: Some("derived1".to_string()),
1177 counterparty: Some(self_counterparty()),
1178 privileged: false,
1179 privileged_reason: None,
1180 for_self: Some(true),
1181 seek_permission: None,
1182 },
1183 )
1184 .await
1185 .unwrap();
1186
1187 let direct = wallet
1189 .get_public_key_sync(
1190 &test_protocol(),
1191 "derived1",
1192 &self_counterparty(),
1193 true,
1194 false,
1195 )
1196 .unwrap();
1197 assert_eq!(result.public_key.to_der_hex(), direct.to_der_hex());
1198 }
1199
1200 #[tokio::test]
1201 async fn test_wallet_interface_privileged_rejected() {
1202 let wallet = ProtoWallet::new(test_private_key());
1203 let err = WalletInterface::get_public_key(
1204 &wallet,
1205 GetPublicKeyArgs {
1206 identity_key: true,
1207 protocol_id: None,
1208 key_id: None,
1209 counterparty: None,
1210 privileged: true,
1211 privileged_reason: Some("test".to_string()),
1212 for_self: None,
1213 seek_permission: None,
1214 },
1215 None,
1216 )
1217 .await;
1218
1219 assert!(err.is_err());
1220 let msg = format!("{}", err.unwrap_err());
1221 assert!(msg.contains("not implemented"), "got: {}", msg);
1222 }
1223
1224 #[tokio::test]
1225 async fn test_wallet_interface_create_verify_signature() {
1226 let wallet = ProtoWallet::new(test_private_key());
1227 let data = b"test data for wallet interface sig".to_vec();
1228
1229 let sig_result = WalletInterface::create_signature(
1230 &wallet,
1231 CreateSignatureArgs {
1232 protocol_id: test_protocol(),
1233 key_id: "wsig1".to_string(),
1234 counterparty: self_counterparty(),
1235 data: Some(data.clone()),
1236 hash_to_directly_sign: None,
1237 privileged: false,
1238 privileged_reason: None,
1239 seek_permission: None,
1240 },
1241 None,
1242 )
1243 .await
1244 .unwrap();
1245
1246 let verify_result = WalletInterface::verify_signature(
1247 &wallet,
1248 VerifySignatureArgs {
1249 protocol_id: test_protocol(),
1250 key_id: "wsig1".to_string(),
1251 counterparty: self_counterparty(),
1252 data: Some(data),
1253 hash_to_directly_verify: None,
1254 signature: sig_result.signature,
1255 for_self: Some(true),
1256 privileged: false,
1257 privileged_reason: None,
1258 seek_permission: None,
1259 },
1260 None,
1261 )
1262 .await
1263 .unwrap();
1264
1265 assert!(verify_result.valid);
1266 }
1267
1268 #[tokio::test]
1269 async fn test_wallet_interface_encrypt_decrypt() {
1270 let wallet = ProtoWallet::new(test_private_key());
1271 let plaintext = b"wallet interface encrypt test".to_vec();
1272
1273 let enc = WalletInterface::encrypt(
1274 &wallet,
1275 EncryptArgs {
1276 protocol_id: test_protocol(),
1277 key_id: "wenc1".to_string(),
1278 counterparty: self_counterparty(),
1279 plaintext: plaintext.clone(),
1280 privileged: false,
1281 privileged_reason: None,
1282 seek_permission: None,
1283 },
1284 None,
1285 )
1286 .await
1287 .unwrap();
1288
1289 let dec = WalletInterface::decrypt(
1290 &wallet,
1291 DecryptArgs {
1292 protocol_id: test_protocol(),
1293 key_id: "wenc1".to_string(),
1294 counterparty: self_counterparty(),
1295 ciphertext: enc.ciphertext,
1296 privileged: false,
1297 privileged_reason: None,
1298 seek_permission: None,
1299 },
1300 None,
1301 )
1302 .await
1303 .unwrap();
1304
1305 assert_eq!(dec.plaintext, plaintext);
1306 }
1307
1308 #[tokio::test]
1309 async fn test_wallet_interface_hmac_roundtrip() {
1310 let wallet = ProtoWallet::new(test_private_key());
1311 let data = b"wallet interface hmac test".to_vec();
1312
1313 let hmac_result = WalletInterface::create_hmac(
1314 &wallet,
1315 CreateHmacArgs {
1316 protocol_id: test_protocol(),
1317 key_id: "whmac1".to_string(),
1318 counterparty: self_counterparty(),
1319 data: data.clone(),
1320 privileged: false,
1321 privileged_reason: None,
1322 seek_permission: None,
1323 },
1324 None,
1325 )
1326 .await
1327 .unwrap();
1328
1329 assert_eq!(hmac_result.hmac.len(), 32);
1330
1331 let verify = WalletInterface::verify_hmac(
1332 &wallet,
1333 VerifyHmacArgs {
1334 protocol_id: test_protocol(),
1335 key_id: "whmac1".to_string(),
1336 counterparty: self_counterparty(),
1337 data,
1338 hmac: hmac_result.hmac,
1339 privileged: false,
1340 privileged_reason: None,
1341 seek_permission: None,
1342 },
1343 None,
1344 )
1345 .await
1346 .unwrap();
1347
1348 assert!(verify.valid);
1349 }
1350
1351 #[tokio::test]
1352 async fn test_wallet_interface_unsupported_methods_return_not_implemented() {
1353 use crate::wallet::interfaces::*;
1354 let wallet = ProtoWallet::new(test_private_key());
1355
1356 let err = WalletInterface::is_authenticated(&wallet, None).await;
1359 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1360
1361 let err = WalletInterface::wait_for_authentication(&wallet, None).await;
1362 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1363
1364 let err = WalletInterface::get_network(&wallet, None).await;
1365 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1366
1367 let err = WalletInterface::get_version(&wallet, None).await;
1368 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1369
1370 let err = WalletInterface::get_height(&wallet, None).await;
1371 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1372
1373 let err =
1374 WalletInterface::get_header_for_height(&wallet, GetHeaderArgs { height: 0 }, None)
1375 .await;
1376 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1377
1378 let err = WalletInterface::list_outputs(
1379 &wallet,
1380 ListOutputsArgs {
1381 basket: "test".to_string(),
1382 tags: vec![],
1383 tag_query_mode: None,
1384 include: None,
1385 include_custom_instructions: BooleanDefaultFalse(None),
1386 include_tags: BooleanDefaultFalse(None),
1387 include_labels: BooleanDefaultFalse(None),
1388 limit: Some(10),
1389 offset: None,
1390 seek_permission: BooleanDefaultTrue(None),
1391 },
1392 None,
1393 )
1394 .await;
1395 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1396 }
1397
1398 #[tokio::test]
1399 async fn test_wallet_interface_reveal_counterparty_key_linkage() {
1400 let wallet = ProtoWallet::new(test_private_key());
1401 let verifier_key = PrivateKey::from_hex("ff").unwrap();
1402 let counterparty_key = PrivateKey::from_hex("cc").unwrap();
1403
1404 let result = WalletInterface::reveal_counterparty_key_linkage(
1405 &wallet,
1406 RevealCounterpartyKeyLinkageArgs {
1407 counterparty: counterparty_key.to_public_key(),
1408 verifier: verifier_key.to_public_key(),
1409 privileged: None,
1410 privileged_reason: None,
1411 },
1412 None,
1413 )
1414 .await
1415 .unwrap();
1416
1417 assert!(!result.encrypted_linkage.is_empty());
1418 assert!(!result.encrypted_linkage_proof.is_empty());
1419 assert_eq!(
1420 result.counterparty.to_der_hex(),
1421 counterparty_key.to_public_key().to_der_hex()
1422 );
1423 assert!(!result.revelation_time.is_empty());
1424 }
1425
1426 #[tokio::test]
1427 async fn test_wallet_interface_reveal_specific_key_linkage() {
1428 let wallet = ProtoWallet::new(test_private_key());
1429 let verifier_key = PrivateKey::from_hex("ff").unwrap();
1430 let counterparty_key = PrivateKey::from_hex("bb").unwrap();
1431
1432 let result = WalletInterface::reveal_specific_key_linkage(
1433 &wallet,
1434 RevealSpecificKeyLinkageArgs {
1435 counterparty: Counterparty {
1436 counterparty_type: CounterpartyType::Other,
1437 public_key: Some(counterparty_key.to_public_key()),
1438 },
1439 verifier: verifier_key.to_public_key(),
1440 protocol_id: test_protocol(),
1441 key_id: "wlink1".to_string(),
1442 privileged: None,
1443 privileged_reason: None,
1444 },
1445 None,
1446 )
1447 .await
1448 .unwrap();
1449
1450 assert!(!result.encrypted_linkage.is_empty());
1451 assert_eq!(result.proof_type, 0);
1452 assert_eq!(result.key_id, "wlink1");
1453 }
1454
1455 #[test]
1468 fn test_counterparty_default_is_uninitialized() {
1469 let cp = Counterparty::default();
1473 assert_eq!(cp.counterparty_type, CounterpartyType::Uninitialized);
1474 assert!(cp.public_key.is_none());
1475 }
1476
1477 #[test]
1478 fn test_create_signature_defaults_uninitialized_to_anyone() {
1479 let wallet = ProtoWallet::new(test_private_key());
1489 let protocol = test_protocol();
1490 let data = b"cross-sdk-interop-canary";
1491
1492 let uninit = Counterparty::default();
1493 assert_eq!(uninit.counterparty_type, CounterpartyType::Uninitialized);
1494
1495 let sig = wallet
1496 .create_signature_sync(Some(data), None, &protocol, "sig-c1", &uninit)
1497 .unwrap();
1498
1499 let anyone = Counterparty {
1507 counterparty_type: CounterpartyType::Anyone,
1508 public_key: None,
1509 };
1510 let valid_anyone = wallet
1511 .verify_signature_sync(Some(data), None, &sig, &protocol, "sig-c1", &anyone, true)
1512 .unwrap();
1513 assert!(
1514 valid_anyone,
1515 "signature from Uninitialized counterparty must verify against 'anyone' derived key"
1516 );
1517
1518 let self_cp = self_counterparty();
1524 let valid_self = wallet
1525 .verify_signature_sync(Some(data), None, &sig, &protocol, "sig-c1", &self_cp, true)
1526 .unwrap_or(false);
1527 assert!(
1528 !valid_self,
1529 "signature from Uninitialized counterparty must NOT verify against 'self' derived key — \
1530 if this assertion fails, Counterparty::default() has regressed and createSignature \
1531 is no longer defaulting to 'anyone'"
1532 );
1533 }
1534
1535 #[test]
1536 fn test_encrypt_defaults_uninitialized_to_self() {
1537 let wallet = ProtoWallet::new(test_private_key());
1542 let protocol = test_protocol();
1543 let plaintext = b"dispatch-to-self canary";
1544
1545 let ciphertext_uninit = wallet
1546 .encrypt_sync(plaintext, &protocol, "enc-c1", &Counterparty::default())
1547 .unwrap();
1548 let ciphertext_self = wallet
1549 .encrypt_sync(plaintext, &protocol, "enc-c1", &self_counterparty())
1550 .unwrap();
1551
1552 let pt1 = wallet
1555 .decrypt_sync(
1556 &ciphertext_uninit,
1557 &protocol,
1558 "enc-c1",
1559 &self_counterparty(),
1560 )
1561 .unwrap();
1562 let pt2 = wallet
1563 .decrypt_sync(&ciphertext_self, &protocol, "enc-c1", &self_counterparty())
1564 .unwrap();
1565 assert_eq!(pt1, plaintext);
1566 assert_eq!(pt2, plaintext);
1567 }
1568
1569 #[test]
1570 fn test_encrypt_with_iv_sync_byte_locks_under_fixed_inputs() {
1571 let identity = PrivateKey::from_bytes(&[0x01u8; 32]).unwrap();
1574 let wallet = ProtoWallet::new(identity);
1575 let protocol = Protocol {
1576 security_level: 2,
1577 protocol: "mpcpresig".to_string(),
1578 };
1579 let key_id = "byte-lock-test-001";
1580 let counterparty = Counterparty {
1581 counterparty_type: CounterpartyType::Self_,
1582 public_key: None,
1583 };
1584 let plaintext = b"presig spare plaintext 32 bytesx";
1585 let iv = [0x0au8; 32];
1586
1587 let ct1 = wallet
1588 .encrypt_with_iv_sync(plaintext, &protocol, key_id, &counterparty, &iv)
1589 .unwrap();
1590 let ct2 = wallet
1591 .encrypt_with_iv_sync(plaintext, &protocol, key_id, &counterparty, &iv)
1592 .unwrap();
1593
1594 assert_eq!(ct1, ct2);
1596 assert_eq!(ct1.len(), 32 + plaintext.len() + 16);
1598 assert_eq!(&ct1[..32], &iv);
1600 let pt = wallet
1602 .decrypt_sync(&ct1, &protocol, key_id, &counterparty)
1603 .unwrap();
1604 assert_eq!(pt.as_slice(), plaintext.as_slice());
1605
1606 const EXPECTED_HEX: &str = "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a5ecbac1cba9e03ccd3599f6e862677d0e4b2bc96b6a58f9d82b04cee442dfe61bbaab30e7674c83b7139e447f7c30c94";
1612 assert_eq!(
1613 hex::encode(&ct1),
1614 EXPECTED_HEX,
1615 "byte-lock failed: key derivation or AES-GCM output has changed"
1616 );
1617 }
1618}