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::key_deriver_api::KeyDeriverApi;
33use crate::wallet::types::{Counterparty, CounterpartyType, Protocol};
34use std::sync::Arc;
35
36pub struct RevealCounterpartyResult {
38 pub prover: PublicKey,
40 pub counterparty: PublicKey,
42 pub verifier: PublicKey,
44 pub revelation_time: String,
46 pub encrypted_linkage: Vec<u8>,
48 pub encrypted_linkage_proof: Vec<u8>,
50}
51
52pub struct RevealSpecificResult {
54 pub encrypted_linkage: Vec<u8>,
56 pub encrypted_linkage_proof: Vec<u8>,
58 pub prover: PublicKey,
60 pub verifier: PublicKey,
62 pub counterparty: PublicKey,
64 pub protocol: Protocol,
66 pub key_id: String,
68 pub proof_type: u8,
70}
71
72pub struct ProtoWallet {
79 key_deriver: Arc<dyn KeyDeriverApi>,
80}
81
82impl ProtoWallet {
83 pub fn new(private_key: PrivateKey) -> Self {
85 ProtoWallet {
86 key_deriver: Arc::new(KeyDeriver::new(private_key)),
87 }
88 }
89
90 pub fn from_key_deriver(kd: Arc<dyn KeyDeriverApi>) -> Self {
98 ProtoWallet { key_deriver: kd }
99 }
100
101 pub fn anyone() -> Self {
103 ProtoWallet {
104 key_deriver: Arc::new(KeyDeriver::new_anyone()),
105 }
106 }
107
108 pub fn get_public_key_sync(
115 &self,
116 protocol: &Protocol,
117 key_id: &str,
118 counterparty: &Counterparty,
119 for_self: bool,
120 identity_key: bool,
121 ) -> Result<PublicKey, WalletError> {
122 if identity_key {
123 return Ok(self.key_deriver.identity_key());
124 }
125
126 if protocol.protocol.is_empty() || key_id.is_empty() {
127 return Err(WalletError::InvalidParameter(
128 "protocolID and keyID are required if identityKey is false".to_string(),
129 ));
130 }
131
132 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
133 self.key_deriver
134 .derive_public_key(protocol, key_id, &effective, for_self)
135 }
136
137 pub fn create_signature_sync(
143 &self,
144 data: Option<&[u8]>,
145 hash_to_directly_sign: Option<&[u8]>,
146 protocol: &Protocol,
147 key_id: &str,
148 counterparty: &Counterparty,
149 ) -> Result<Vec<u8>, WalletError> {
150 let effective = self.default_counterparty(counterparty, CounterpartyType::Anyone);
151 let derived_key = self
152 .key_deriver
153 .derive_private_key(protocol, key_id, &effective)?;
154
155 let hash = if let Some(h) = hash_to_directly_sign {
156 if h.len() != 32 {
157 return Err(WalletError::InvalidParameter(
158 "hash_to_directly_sign must be exactly 32 bytes".to_string(),
159 ));
160 }
161 let mut arr = [0u8; 32];
162 arr.copy_from_slice(h);
163 arr
164 } else if let Some(d) = data {
165 sha256(d)
166 } else {
167 return Err(WalletError::InvalidParameter(
168 "either data or hash_to_directly_sign must be provided".to_string(),
169 ));
170 };
171 let sig = ecdsa_sign(&hash, derived_key.bn(), true)?;
172 Ok(sig.to_der())
173 }
174
175 #[allow(clippy::too_many_arguments)]
180 pub fn verify_signature_sync(
181 &self,
182 data: Option<&[u8]>,
183 hash_to_directly_verify: Option<&[u8]>,
184 signature: &[u8],
185 protocol: &Protocol,
186 key_id: &str,
187 counterparty: &Counterparty,
188 for_self: bool,
189 ) -> Result<bool, WalletError> {
190 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
191 let derived_pub = self
192 .key_deriver
193 .derive_public_key(protocol, key_id, &effective, for_self)?;
194
195 let sig = Signature::from_der(signature)?;
196 let hash = if let Some(h) = hash_to_directly_verify {
197 if h.len() != 32 {
198 return Err(WalletError::InvalidParameter(
199 "hash_to_directly_verify must be exactly 32 bytes".to_string(),
200 ));
201 }
202 let mut arr = [0u8; 32];
203 arr.copy_from_slice(h);
204 arr
205 } else if let Some(d) = data {
206 sha256(d)
207 } else {
208 return Err(WalletError::InvalidParameter(
209 "either data or hash_to_directly_verify must be provided".to_string(),
210 ));
211 };
212 Ok(ecdsa_verify(&hash, &sig, derived_pub.point()))
213 }
214
215 pub fn encrypt_sync(
220 &self,
221 plaintext: &[u8],
222 protocol: &Protocol,
223 key_id: &str,
224 counterparty: &Counterparty,
225 ) -> Result<Vec<u8>, WalletError> {
226 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
227 let sym_key = self
228 .key_deriver
229 .derive_symmetric_key(protocol, key_id, &effective)?;
230 Ok(sym_key.encrypt(plaintext)?)
231 }
232
233 pub fn encrypt_with_iv_sync(
244 &self,
245 plaintext: &[u8],
246 protocol: &Protocol,
247 key_id: &str,
248 counterparty: &Counterparty,
249 iv: &[u8; 32],
250 ) -> Result<Vec<u8>, WalletError> {
251 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
252 let sym_key = self
253 .key_deriver
254 .derive_symmetric_key(protocol, key_id, &effective)?;
255 Ok(sym_key.encrypt_with_iv(plaintext, iv)?)
256 }
257
258 pub fn decrypt_sync(
263 &self,
264 ciphertext: &[u8],
265 protocol: &Protocol,
266 key_id: &str,
267 counterparty: &Counterparty,
268 ) -> Result<Vec<u8>, WalletError> {
269 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
270 let sym_key = self
271 .key_deriver
272 .derive_symmetric_key(protocol, key_id, &effective)?;
273 Ok(sym_key.decrypt(ciphertext)?)
274 }
275
276 pub fn create_hmac_sync(
280 &self,
281 data: &[u8],
282 protocol: &Protocol,
283 key_id: &str,
284 counterparty: &Counterparty,
285 ) -> Result<Vec<u8>, WalletError> {
286 let effective = self.default_counterparty(counterparty, CounterpartyType::Self_);
287 let sym_key = self
288 .key_deriver
289 .derive_symmetric_key(protocol, key_id, &effective)?;
290 let key_bytes = sym_key.to_hmac_key_bytes();
294 let hmac = sha256_hmac(&key_bytes, data);
295 Ok(hmac.to_vec())
296 }
297
298 pub fn verify_hmac_sync(
303 &self,
304 data: &[u8],
305 hmac_value: &[u8],
306 protocol: &Protocol,
307 key_id: &str,
308 counterparty: &Counterparty,
309 ) -> Result<bool, WalletError> {
310 let expected = self.create_hmac_sync(data, protocol, key_id, counterparty)?;
311 Ok(constant_time_eq(&expected, hmac_value))
313 }
314
315 pub fn reveal_counterparty_key_linkage_sync(
321 &self,
322 counterparty: &Counterparty,
323 verifier: &PublicKey,
324 ) -> Result<RevealCounterpartyResult, WalletError> {
325 let linkage_point = self.key_deriver.reveal_counterparty_secret(counterparty)?;
327 let linkage_bytes = linkage_point.to_der(); let prover = self.key_deriver.identity_key();
330
331 let revelation_time = current_utc_timestamp();
334
335 let verifier_counterparty = Counterparty {
336 counterparty_type: CounterpartyType::Other,
337 public_key: Some(verifier.clone()),
338 };
339
340 let linkage_protocol = Protocol {
341 security_level: 2,
342 protocol: "counterparty linkage revelation".to_string(),
343 };
344
345 let encrypted_linkage = self.encrypt_sync(
347 &linkage_bytes,
348 &linkage_protocol,
349 &revelation_time,
350 &verifier_counterparty,
351 )?;
352
353 let linkage_point = Point::from_der(&linkage_bytes)
356 .map_err(|e| WalletError::Internal(format!("invalid linkage point: {e}")))?;
357 let counterparty_pub = match &counterparty.public_key {
358 Some(pk) => pk.clone(),
359 None => {
360 return Err(WalletError::InvalidParameter(
361 "counterparty public key required for linkage revelation".to_string(),
362 ))
363 }
364 };
365 let schnorr_proof = schnorr_generate_proof(
366 self.key_deriver.root_key(),
367 &self.key_deriver.identity_key(),
368 &counterparty_pub,
369 &linkage_point,
370 )?;
371 let mut proof_bin = Vec::with_capacity(33 + 33 + 32);
373 proof_bin.extend_from_slice(&schnorr_proof.r_point.to_der(true));
374 proof_bin.extend_from_slice(&schnorr_proof.s_prime.to_der(true));
375 proof_bin.extend_from_slice(&schnorr_proof.z.to_bytes());
376 let encrypted_proof = self.encrypt_sync(
377 &proof_bin,
378 &linkage_protocol,
379 &revelation_time,
380 &verifier_counterparty,
381 )?;
382
383 Ok(RevealCounterpartyResult {
384 prover,
385 counterparty: counterparty_pub,
386 verifier: verifier.clone(),
387 revelation_time,
388 encrypted_linkage,
389 encrypted_linkage_proof: encrypted_proof,
390 })
391 }
392
393 pub fn reveal_specific_key_linkage_sync(
398 &self,
399 counterparty: &Counterparty,
400 verifier: &PublicKey,
401 protocol: &Protocol,
402 key_id: &str,
403 ) -> Result<RevealSpecificResult, WalletError> {
404 let linkage = self
406 .key_deriver
407 .reveal_specific_secret(counterparty, protocol, key_id)?;
408
409 let prover = self.key_deriver.identity_key();
410
411 let verifier_counterparty = Counterparty {
412 counterparty_type: CounterpartyType::Other,
413 public_key: Some(verifier.clone()),
414 };
415
416 let encrypt_protocol = Protocol {
418 security_level: 2,
419 protocol: format!(
420 "specific linkage revelation {} {}",
421 protocol.security_level, protocol.protocol
422 ),
423 };
424
425 let encrypted_linkage =
427 self.encrypt_sync(&linkage, &encrypt_protocol, key_id, &verifier_counterparty)?;
428
429 let proof_bytes: [u8; 1] = [0];
431 let encrypted_proof = self.encrypt_sync(
432 &proof_bytes,
433 &encrypt_protocol,
434 key_id,
435 &verifier_counterparty,
436 )?;
437
438 let counterparty_pub = match &counterparty.public_key {
440 Some(pk) => pk.clone(),
441 None => {
442 return Err(WalletError::InvalidParameter(
443 "counterparty public key required for linkage revelation".to_string(),
444 ))
445 }
446 };
447
448 Ok(RevealSpecificResult {
449 encrypted_linkage,
450 encrypted_linkage_proof: encrypted_proof,
451 prover,
452 verifier: verifier.clone(),
453 counterparty: counterparty_pub,
454 protocol: protocol.clone(),
455 key_id: key_id.to_string(),
456 proof_type: 0,
457 })
458 }
459
460 fn default_counterparty(
462 &self,
463 counterparty: &Counterparty,
464 default_type: CounterpartyType,
465 ) -> Counterparty {
466 if counterparty.counterparty_type == CounterpartyType::Uninitialized {
467 Counterparty {
468 counterparty_type: default_type,
469 public_key: None,
470 }
471 } else {
472 counterparty.clone()
473 }
474 }
475}
476
477#[async_trait::async_trait]
485impl WalletInterface for ProtoWallet {
486 async fn create_action(
489 &self,
490 _args: CreateActionArgs,
491 _originator: Option<&str>,
492 ) -> Result<CreateActionResult, WalletError> {
493 Err(WalletError::NotImplemented("createAction".to_string()))
494 }
495
496 async fn sign_action(
497 &self,
498 _args: SignActionArgs,
499 _originator: Option<&str>,
500 ) -> Result<SignActionResult, WalletError> {
501 Err(WalletError::NotImplemented("signAction".to_string()))
502 }
503
504 async fn abort_action(
505 &self,
506 _args: AbortActionArgs,
507 _originator: Option<&str>,
508 ) -> Result<AbortActionResult, WalletError> {
509 Err(WalletError::NotImplemented("abortAction".to_string()))
510 }
511
512 async fn list_actions(
513 &self,
514 _args: ListActionsArgs,
515 _originator: Option<&str>,
516 ) -> Result<ListActionsResult, WalletError> {
517 Err(WalletError::NotImplemented("listActions".to_string()))
518 }
519
520 async fn internalize_action(
521 &self,
522 _args: InternalizeActionArgs,
523 _originator: Option<&str>,
524 ) -> Result<InternalizeActionResult, WalletError> {
525 Err(WalletError::NotImplemented("internalizeAction".to_string()))
526 }
527
528 async fn list_outputs(
531 &self,
532 _args: ListOutputsArgs,
533 _originator: Option<&str>,
534 ) -> Result<ListOutputsResult, WalletError> {
535 Err(WalletError::NotImplemented("listOutputs".to_string()))
536 }
537
538 async fn relinquish_output(
539 &self,
540 _args: RelinquishOutputArgs,
541 _originator: Option<&str>,
542 ) -> Result<RelinquishOutputResult, WalletError> {
543 Err(WalletError::NotImplemented("relinquishOutput".to_string()))
544 }
545
546 async fn get_public_key(
549 &self,
550 args: GetPublicKeyArgs,
551 _originator: Option<&str>,
552 ) -> Result<GetPublicKeyResult, WalletError> {
553 if args.privileged {
554 return Err(WalletError::NotImplemented(
555 "privileged key access not supported by ProtoWallet".to_string(),
556 ));
557 }
558 let protocol = args.protocol_id.unwrap_or(Protocol {
559 security_level: 0,
560 protocol: String::new(),
561 });
562 let key_id = args.key_id.unwrap_or_default();
563 let counterparty = args.counterparty.unwrap_or(Counterparty {
564 counterparty_type: CounterpartyType::Uninitialized,
565 public_key: None,
566 });
567 let for_self = args.for_self.unwrap_or(false);
568 let pk = self.get_public_key_sync(
569 &protocol,
570 &key_id,
571 &counterparty,
572 for_self,
573 args.identity_key,
574 )?;
575 Ok(GetPublicKeyResult { public_key: pk })
576 }
577
578 async fn reveal_counterparty_key_linkage(
579 &self,
580 args: RevealCounterpartyKeyLinkageArgs,
581 _originator: Option<&str>,
582 ) -> Result<RevealCounterpartyKeyLinkageResult, WalletError> {
583 let counterparty = Counterparty {
584 counterparty_type: CounterpartyType::Other,
585 public_key: Some(args.counterparty),
586 };
587 let result = self.reveal_counterparty_key_linkage_sync(&counterparty, &args.verifier)?;
588 Ok(RevealCounterpartyKeyLinkageResult {
589 prover: result.prover,
590 counterparty: result.counterparty,
591 verifier: result.verifier,
592 revelation_time: result.revelation_time,
593 encrypted_linkage: result.encrypted_linkage,
594 encrypted_linkage_proof: result.encrypted_linkage_proof,
595 })
596 }
597
598 async fn reveal_specific_key_linkage(
599 &self,
600 args: RevealSpecificKeyLinkageArgs,
601 _originator: Option<&str>,
602 ) -> Result<RevealSpecificKeyLinkageResult, WalletError> {
603 let result = self.reveal_specific_key_linkage_sync(
604 &args.counterparty,
605 &args.verifier,
606 &args.protocol_id,
607 &args.key_id,
608 )?;
609 Ok(RevealSpecificKeyLinkageResult {
610 encrypted_linkage: result.encrypted_linkage,
611 encrypted_linkage_proof: result.encrypted_linkage_proof,
612 prover: result.prover,
613 verifier: result.verifier,
614 counterparty: result.counterparty,
615 protocol_id: result.protocol.clone(),
616 key_id: result.key_id.clone(),
617 proof_type: result.proof_type,
618 })
619 }
620
621 async fn encrypt(
622 &self,
623 args: EncryptArgs,
624 _originator: Option<&str>,
625 ) -> Result<EncryptResult, WalletError> {
626 let ciphertext = self.encrypt_sync(
627 &args.plaintext,
628 &args.protocol_id,
629 &args.key_id,
630 &args.counterparty,
631 )?;
632 Ok(EncryptResult { ciphertext })
633 }
634
635 async fn decrypt(
636 &self,
637 args: DecryptArgs,
638 _originator: Option<&str>,
639 ) -> Result<DecryptResult, WalletError> {
640 let plaintext = self.decrypt_sync(
641 &args.ciphertext,
642 &args.protocol_id,
643 &args.key_id,
644 &args.counterparty,
645 )?;
646 Ok(DecryptResult { plaintext })
647 }
648
649 async fn create_hmac(
650 &self,
651 args: CreateHmacArgs,
652 _originator: Option<&str>,
653 ) -> Result<CreateHmacResult, WalletError> {
654 let hmac = self.create_hmac_sync(
655 &args.data,
656 &args.protocol_id,
657 &args.key_id,
658 &args.counterparty,
659 )?;
660 Ok(CreateHmacResult { hmac })
661 }
662
663 async fn verify_hmac(
664 &self,
665 args: VerifyHmacArgs,
666 _originator: Option<&str>,
667 ) -> Result<VerifyHmacResult, WalletError> {
668 let valid = self.verify_hmac_sync(
669 &args.data,
670 &args.hmac,
671 &args.protocol_id,
672 &args.key_id,
673 &args.counterparty,
674 )?;
675 if !valid {
677 return Err(WalletError::InvalidHmac);
678 }
679 Ok(VerifyHmacResult { valid: true })
680 }
681
682 async fn create_signature(
683 &self,
684 args: CreateSignatureArgs,
685 _originator: Option<&str>,
686 ) -> Result<CreateSignatureResult, WalletError> {
687 let signature = self.create_signature_sync(
688 args.data.as_deref(),
689 args.hash_to_directly_sign.as_deref(),
690 &args.protocol_id,
691 &args.key_id,
692 &args.counterparty,
693 )?;
694 Ok(CreateSignatureResult { signature })
695 }
696
697 async fn verify_signature(
698 &self,
699 args: VerifySignatureArgs,
700 _originator: Option<&str>,
701 ) -> Result<VerifySignatureResult, WalletError> {
702 let for_self = args.for_self.unwrap_or(false);
703 let valid = self.verify_signature_sync(
704 args.data.as_deref(),
705 args.hash_to_directly_verify.as_deref(),
706 &args.signature,
707 &args.protocol_id,
708 &args.key_id,
709 &args.counterparty,
710 for_self,
711 )?;
712 if !valid {
714 return Err(WalletError::InvalidSignature);
715 }
716 Ok(VerifySignatureResult { valid: true })
717 }
718
719 async fn acquire_certificate(
722 &self,
723 _args: AcquireCertificateArgs,
724 _originator: Option<&str>,
725 ) -> Result<Certificate, WalletError> {
726 Err(WalletError::NotImplemented(
727 "acquireCertificate".to_string(),
728 ))
729 }
730
731 async fn list_certificates(
732 &self,
733 _args: ListCertificatesArgs,
734 _originator: Option<&str>,
735 ) -> Result<ListCertificatesResult, WalletError> {
736 Err(WalletError::NotImplemented("listCertificates".to_string()))
737 }
738
739 async fn prove_certificate(
740 &self,
741 _args: ProveCertificateArgs,
742 _originator: Option<&str>,
743 ) -> Result<ProveCertificateResult, WalletError> {
744 Err(WalletError::NotImplemented("proveCertificate".to_string()))
745 }
746
747 async fn relinquish_certificate(
748 &self,
749 _args: RelinquishCertificateArgs,
750 _originator: Option<&str>,
751 ) -> Result<RelinquishCertificateResult, WalletError> {
752 Err(WalletError::NotImplemented(
753 "relinquishCertificate".to_string(),
754 ))
755 }
756
757 async fn discover_by_identity_key(
760 &self,
761 _args: DiscoverByIdentityKeyArgs,
762 _originator: Option<&str>,
763 ) -> Result<DiscoverCertificatesResult, WalletError> {
764 Err(WalletError::NotImplemented(
765 "discoverByIdentityKey".to_string(),
766 ))
767 }
768
769 async fn discover_by_attributes(
770 &self,
771 _args: DiscoverByAttributesArgs,
772 _originator: Option<&str>,
773 ) -> Result<DiscoverCertificatesResult, WalletError> {
774 Err(WalletError::NotImplemented(
775 "discoverByAttributes".to_string(),
776 ))
777 }
778
779 async fn is_authenticated(
782 &self,
783 _originator: Option<&str>,
784 ) -> Result<AuthenticatedResult, WalletError> {
785 Err(WalletError::NotImplemented("isAuthenticated".to_string()))
786 }
787
788 async fn wait_for_authentication(
789 &self,
790 _originator: Option<&str>,
791 ) -> Result<AuthenticatedResult, WalletError> {
792 Err(WalletError::NotImplemented(
793 "waitForAuthentication".to_string(),
794 ))
795 }
796
797 async fn get_height(&self, _originator: Option<&str>) -> Result<GetHeightResult, WalletError> {
798 Err(WalletError::NotImplemented("getHeight".to_string()))
799 }
800
801 async fn get_header_for_height(
802 &self,
803 _args: GetHeaderArgs,
804 _originator: Option<&str>,
805 ) -> Result<GetHeaderResult, WalletError> {
806 Err(WalletError::NotImplemented(
807 "getHeaderForHeight".to_string(),
808 ))
809 }
810
811 async fn get_network(
812 &self,
813 _originator: Option<&str>,
814 ) -> Result<GetNetworkResult, WalletError> {
815 Err(WalletError::NotImplemented("getNetwork".to_string()))
816 }
817
818 async fn get_version(
819 &self,
820 _originator: Option<&str>,
821 ) -> Result<GetVersionResult, WalletError> {
822 Err(WalletError::NotImplemented("getVersion".to_string()))
823 }
824}
825
826fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
828 if a.len() != b.len() {
829 return false;
830 }
831 let mut diff: u8 = 0;
832 for (x, y) in a.iter().zip(b.iter()) {
833 diff |= x ^ y;
834 }
835 diff == 0
836}
837
838fn current_utc_timestamp() -> String {
840 use std::time::{SystemTime, UNIX_EPOCH};
843 let now = SystemTime::now()
844 .duration_since(UNIX_EPOCH)
845 .unwrap_or_default();
846 format!("{}", now.as_secs())
847}
848
849#[cfg(test)]
850mod tests {
851 use super::*;
852 use crate::wallet::types::{BooleanDefaultFalse, BooleanDefaultTrue};
853
854 fn test_protocol() -> Protocol {
855 Protocol {
856 security_level: 2,
857 protocol: "test proto wallet".to_string(),
858 }
859 }
860
861 fn self_counterparty() -> Counterparty {
862 Counterparty {
863 counterparty_type: CounterpartyType::Self_,
864 public_key: None,
865 }
866 }
867
868 fn test_private_key() -> PrivateKey {
869 PrivateKey::from_hex("abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890")
870 .unwrap()
871 }
872
873 #[test]
874 fn test_new_creates_wallet_with_correct_identity_key() {
875 let pk = test_private_key();
876 let expected_pub = pk.to_public_key();
877 let wallet = ProtoWallet::new(pk);
878 let identity = wallet
879 .get_public_key_sync(&test_protocol(), "1", &self_counterparty(), false, true)
880 .unwrap();
881 assert_eq!(identity.to_der_hex(), expected_pub.to_der_hex());
882 }
883
884 #[test]
885 fn test_get_public_key_identity_key_true() {
886 let pk = test_private_key();
887 let expected = pk.to_public_key().to_der_hex();
888 let wallet = ProtoWallet::new(pk);
889 let result = wallet
890 .get_public_key_sync(&test_protocol(), "1", &self_counterparty(), false, true)
891 .unwrap();
892 assert_eq!(result.to_der_hex(), expected);
893 }
894
895 #[test]
896 fn test_get_public_key_derived() {
897 let wallet = ProtoWallet::new(test_private_key());
898 let protocol = test_protocol();
899 let pub1 = wallet
900 .get_public_key_sync(&protocol, "key1", &self_counterparty(), true, false)
901 .unwrap();
902 let pub2 = wallet
903 .get_public_key_sync(&protocol, "key2", &self_counterparty(), true, false)
904 .unwrap();
905 assert_ne!(pub1.to_der_hex(), pub2.to_der_hex());
907 }
908
909 #[test]
910 fn test_create_and_verify_signature_roundtrip() {
911 let wallet = ProtoWallet::new(test_private_key());
912 let protocol = test_protocol();
913 let counterparty = self_counterparty();
914 let data = b"hello world signature test";
915
916 let sig = wallet
917 .create_signature_sync(Some(data), None, &protocol, "sig1", &counterparty)
918 .unwrap();
919 assert!(!sig.is_empty());
920
921 let valid = wallet
922 .verify_signature_sync(
923 Some(data),
924 None,
925 &sig,
926 &protocol,
927 "sig1",
928 &counterparty,
929 true,
930 )
931 .unwrap();
932 assert!(valid, "signature should verify");
933 }
934
935 #[test]
936 fn test_verify_signature_rejects_wrong_data() {
937 let wallet = ProtoWallet::new(test_private_key());
938 let protocol = test_protocol();
939 let counterparty = self_counterparty();
940
941 let sig = wallet
942 .create_signature_sync(
943 Some(b"correct data"),
944 None,
945 &protocol,
946 "sig2",
947 &counterparty,
948 )
949 .unwrap();
950 let valid = wallet
951 .verify_signature_sync(
952 Some(b"wrong data"),
953 None,
954 &sig,
955 &protocol,
956 "sig2",
957 &counterparty,
958 true,
959 )
960 .unwrap();
961 assert!(!valid, "signature should not verify for wrong data");
962 }
963
964 #[test]
965 fn test_encrypt_decrypt_roundtrip() {
966 let wallet = ProtoWallet::new(test_private_key());
967 let protocol = test_protocol();
968 let counterparty = self_counterparty();
969 let plaintext = b"secret message for encryption";
970
971 let ciphertext = wallet
972 .encrypt_sync(plaintext, &protocol, "enc1", &counterparty)
973 .unwrap();
974 assert_ne!(ciphertext.as_slice(), plaintext);
975
976 let decrypted = wallet
977 .decrypt_sync(&ciphertext, &protocol, "enc1", &counterparty)
978 .unwrap();
979 assert_eq!(decrypted, plaintext);
980 }
981
982 #[test]
983 fn test_encrypt_decrypt_empty_plaintext() {
984 let wallet = ProtoWallet::new(test_private_key());
985 let protocol = test_protocol();
986 let counterparty = self_counterparty();
987
988 let ciphertext = wallet
989 .encrypt_sync(b"", &protocol, "enc2", &counterparty)
990 .unwrap();
991 let decrypted = wallet
992 .decrypt_sync(&ciphertext, &protocol, "enc2", &counterparty)
993 .unwrap();
994 assert!(decrypted.is_empty());
995 }
996
997 #[test]
998 fn test_create_and_verify_hmac_roundtrip() {
999 let wallet = ProtoWallet::new(test_private_key());
1000 let protocol = test_protocol();
1001 let counterparty = self_counterparty();
1002 let data = b"hmac test data";
1003
1004 let hmac = wallet
1005 .create_hmac_sync(data, &protocol, "hmac1", &counterparty)
1006 .unwrap();
1007 assert_eq!(hmac.len(), 32);
1008
1009 let valid = wallet
1010 .verify_hmac_sync(data, &hmac, &protocol, "hmac1", &counterparty)
1011 .unwrap();
1012 assert!(valid, "HMAC should verify");
1013 }
1014
1015 #[test]
1016 fn test_verify_hmac_rejects_wrong_data() {
1017 let wallet = ProtoWallet::new(test_private_key());
1018 let protocol = test_protocol();
1019 let counterparty = self_counterparty();
1020
1021 let hmac = wallet
1022 .create_hmac_sync(b"correct", &protocol, "hmac2", &counterparty)
1023 .unwrap();
1024 let valid = wallet
1025 .verify_hmac_sync(b"wrong", &hmac, &protocol, "hmac2", &counterparty)
1026 .unwrap();
1027 assert!(!valid, "HMAC should not verify for wrong data");
1028 }
1029
1030 #[test]
1031 fn test_hmac_deterministic() {
1032 let wallet = ProtoWallet::new(test_private_key());
1033 let protocol = test_protocol();
1034 let counterparty = self_counterparty();
1035 let data = b"deterministic hmac";
1036
1037 let hmac1 = wallet
1038 .create_hmac_sync(data, &protocol, "hmac3", &counterparty)
1039 .unwrap();
1040 let hmac2 = wallet
1041 .create_hmac_sync(data, &protocol, "hmac3", &counterparty)
1042 .unwrap();
1043 assert_eq!(hmac1, hmac2);
1044 }
1045
1046 #[test]
1047 fn test_anyone_wallet_encrypt_decrypt() {
1048 let anyone = ProtoWallet::anyone();
1049 let other_key = test_private_key();
1050 let other_pub = other_key.to_public_key();
1051
1052 let counterparty = Counterparty {
1053 counterparty_type: CounterpartyType::Other,
1054 public_key: Some(other_pub),
1055 };
1056 let protocol = test_protocol();
1057 let plaintext = b"message from anyone";
1058
1059 let ciphertext = anyone
1060 .encrypt_sync(plaintext, &protocol, "anon1", &counterparty)
1061 .unwrap();
1062 let decrypted = anyone
1063 .decrypt_sync(&ciphertext, &protocol, "anon1", &counterparty)
1064 .unwrap();
1065 assert_eq!(decrypted, plaintext);
1066 }
1067
1068 #[test]
1069 fn test_uninitialized_counterparty_defaults_to_self_for_encrypt() {
1070 let wallet = ProtoWallet::new(test_private_key());
1071 let protocol = test_protocol();
1072 let uninit = Counterparty {
1073 counterparty_type: CounterpartyType::Uninitialized,
1074 public_key: None,
1075 };
1076 let self_cp = self_counterparty();
1077
1078 let ct_uninit = wallet
1079 .encrypt_sync(b"test", &protocol, "def1", &uninit)
1080 .unwrap();
1081 let decrypted = wallet
1083 .decrypt_sync(&ct_uninit, &protocol, "def1", &self_cp)
1084 .unwrap();
1085 assert_eq!(decrypted, b"test");
1086 }
1087
1088 #[test]
1089 fn test_reveal_specific_key_linkage() {
1090 let wallet_a = ProtoWallet::new(test_private_key());
1091 let verifier_key = PrivateKey::from_hex("ff").unwrap();
1092 let verifier_pub = verifier_key.to_public_key();
1093
1094 let counterparty_key = PrivateKey::from_hex("bb").unwrap();
1095 let counterparty_pub = counterparty_key.to_public_key();
1096
1097 let counterparty = Counterparty {
1098 counterparty_type: CounterpartyType::Other,
1099 public_key: Some(counterparty_pub),
1100 };
1101
1102 let protocol = test_protocol();
1103 let result = wallet_a
1104 .reveal_specific_key_linkage_sync(&counterparty, &verifier_pub, &protocol, "link1")
1105 .unwrap();
1106
1107 assert!(!result.encrypted_linkage.is_empty());
1108 assert!(!result.encrypted_linkage_proof.is_empty());
1109 assert_eq!(result.proof_type, 0);
1110 assert_eq!(result.key_id, "link1");
1111 }
1112
1113 #[test]
1114 fn test_reveal_counterparty_key_linkage() {
1115 let wallet = ProtoWallet::new(test_private_key());
1116 let verifier_key = PrivateKey::from_hex("ff").unwrap();
1117 let verifier_pub = verifier_key.to_public_key();
1118
1119 let counterparty_key = PrivateKey::from_hex("cc").unwrap();
1120 let counterparty_pub = counterparty_key.to_public_key();
1121
1122 let counterparty = Counterparty {
1123 counterparty_type: CounterpartyType::Other,
1124 public_key: Some(counterparty_pub.clone()),
1125 };
1126
1127 let result = wallet
1128 .reveal_counterparty_key_linkage_sync(&counterparty, &verifier_pub)
1129 .unwrap();
1130
1131 assert!(!result.encrypted_linkage.is_empty());
1132 assert!(!result.encrypted_linkage_proof.is_empty());
1133 assert_eq!(
1134 result.counterparty.to_der_hex(),
1135 counterparty_pub.to_der_hex()
1136 );
1137 assert_eq!(result.verifier.to_der_hex(), verifier_pub.to_der_hex());
1138 assert!(!result.revelation_time.is_empty());
1139 }
1140
1141 async fn get_pub_key_via_trait<W: WalletInterface + ?Sized>(
1147 w: &W,
1148 args: GetPublicKeyArgs,
1149 ) -> Result<GetPublicKeyResult, WalletError> {
1150 w.get_public_key(args, None).await
1151 }
1152
1153 #[tokio::test]
1154 async fn test_wallet_interface_get_public_key_identity() {
1155 let pk = test_private_key();
1156 let expected = pk.to_public_key().to_der_hex();
1157 let wallet = ProtoWallet::new(pk);
1158
1159 let result = get_pub_key_via_trait(
1160 &wallet,
1161 GetPublicKeyArgs {
1162 identity_key: true,
1163 protocol_id: None,
1164 key_id: None,
1165 counterparty: None,
1166 privileged: false,
1167 privileged_reason: None,
1168 for_self: None,
1169 seek_permission: None,
1170 },
1171 )
1172 .await
1173 .unwrap();
1174
1175 assert_eq!(result.public_key.to_der_hex(), expected);
1176 }
1177
1178 #[tokio::test]
1179 async fn test_wallet_interface_get_public_key_derived() {
1180 let wallet = ProtoWallet::new(test_private_key());
1181
1182 let result = get_pub_key_via_trait(
1183 &wallet,
1184 GetPublicKeyArgs {
1185 identity_key: false,
1186 protocol_id: Some(test_protocol()),
1187 key_id: Some("derived1".to_string()),
1188 counterparty: Some(self_counterparty()),
1189 privileged: false,
1190 privileged_reason: None,
1191 for_self: Some(true),
1192 seek_permission: None,
1193 },
1194 )
1195 .await
1196 .unwrap();
1197
1198 let direct = wallet
1200 .get_public_key_sync(
1201 &test_protocol(),
1202 "derived1",
1203 &self_counterparty(),
1204 true,
1205 false,
1206 )
1207 .unwrap();
1208 assert_eq!(result.public_key.to_der_hex(), direct.to_der_hex());
1209 }
1210
1211 #[tokio::test]
1212 async fn test_wallet_interface_privileged_rejected() {
1213 let wallet = ProtoWallet::new(test_private_key());
1214 let err = WalletInterface::get_public_key(
1215 &wallet,
1216 GetPublicKeyArgs {
1217 identity_key: true,
1218 protocol_id: None,
1219 key_id: None,
1220 counterparty: None,
1221 privileged: true,
1222 privileged_reason: Some("test".to_string()),
1223 for_self: None,
1224 seek_permission: None,
1225 },
1226 None,
1227 )
1228 .await;
1229
1230 assert!(err.is_err());
1231 let msg = format!("{}", err.unwrap_err());
1232 assert!(msg.contains("not implemented"), "got: {msg}");
1233 }
1234
1235 #[tokio::test]
1236 async fn test_wallet_interface_create_verify_signature() {
1237 let wallet = ProtoWallet::new(test_private_key());
1238 let data = b"test data for wallet interface sig".to_vec();
1239
1240 let sig_result = WalletInterface::create_signature(
1241 &wallet,
1242 CreateSignatureArgs {
1243 protocol_id: test_protocol(),
1244 key_id: "wsig1".to_string(),
1245 counterparty: self_counterparty(),
1246 data: Some(data.clone()),
1247 hash_to_directly_sign: None,
1248 privileged: false,
1249 privileged_reason: None,
1250 seek_permission: None,
1251 },
1252 None,
1253 )
1254 .await
1255 .unwrap();
1256
1257 let verify_result = WalletInterface::verify_signature(
1258 &wallet,
1259 VerifySignatureArgs {
1260 protocol_id: test_protocol(),
1261 key_id: "wsig1".to_string(),
1262 counterparty: self_counterparty(),
1263 data: Some(data),
1264 hash_to_directly_verify: None,
1265 signature: sig_result.signature,
1266 for_self: Some(true),
1267 privileged: false,
1268 privileged_reason: None,
1269 seek_permission: None,
1270 },
1271 None,
1272 )
1273 .await
1274 .unwrap();
1275
1276 assert!(verify_result.valid);
1277 }
1278
1279 #[tokio::test]
1280 async fn test_wallet_interface_encrypt_decrypt() {
1281 let wallet = ProtoWallet::new(test_private_key());
1282 let plaintext = b"wallet interface encrypt test".to_vec();
1283
1284 let enc = WalletInterface::encrypt(
1285 &wallet,
1286 EncryptArgs {
1287 protocol_id: test_protocol(),
1288 key_id: "wenc1".to_string(),
1289 counterparty: self_counterparty(),
1290 plaintext: plaintext.clone(),
1291 privileged: false,
1292 privileged_reason: None,
1293 seek_permission: None,
1294 },
1295 None,
1296 )
1297 .await
1298 .unwrap();
1299
1300 let dec = WalletInterface::decrypt(
1301 &wallet,
1302 DecryptArgs {
1303 protocol_id: test_protocol(),
1304 key_id: "wenc1".to_string(),
1305 counterparty: self_counterparty(),
1306 ciphertext: enc.ciphertext,
1307 privileged: false,
1308 privileged_reason: None,
1309 seek_permission: None,
1310 },
1311 None,
1312 )
1313 .await
1314 .unwrap();
1315
1316 assert_eq!(dec.plaintext, plaintext);
1317 }
1318
1319 #[tokio::test]
1320 async fn test_wallet_interface_hmac_roundtrip() {
1321 let wallet = ProtoWallet::new(test_private_key());
1322 let data = b"wallet interface hmac test".to_vec();
1323
1324 let hmac_result = WalletInterface::create_hmac(
1325 &wallet,
1326 CreateHmacArgs {
1327 protocol_id: test_protocol(),
1328 key_id: "whmac1".to_string(),
1329 counterparty: self_counterparty(),
1330 data: data.clone(),
1331 privileged: false,
1332 privileged_reason: None,
1333 seek_permission: None,
1334 },
1335 None,
1336 )
1337 .await
1338 .unwrap();
1339
1340 assert_eq!(hmac_result.hmac.len(), 32);
1341
1342 let verify = WalletInterface::verify_hmac(
1343 &wallet,
1344 VerifyHmacArgs {
1345 protocol_id: test_protocol(),
1346 key_id: "whmac1".to_string(),
1347 counterparty: self_counterparty(),
1348 data,
1349 hmac: hmac_result.hmac,
1350 privileged: false,
1351 privileged_reason: None,
1352 seek_permission: None,
1353 },
1354 None,
1355 )
1356 .await
1357 .unwrap();
1358
1359 assert!(verify.valid);
1360 }
1361
1362 #[tokio::test]
1363 async fn test_wallet_interface_unsupported_methods_return_not_implemented() {
1364 use crate::wallet::interfaces::*;
1365 let wallet = ProtoWallet::new(test_private_key());
1366
1367 let err = WalletInterface::is_authenticated(&wallet, None).await;
1370 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1371
1372 let err = WalletInterface::wait_for_authentication(&wallet, None).await;
1373 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1374
1375 let err = WalletInterface::get_network(&wallet, None).await;
1376 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1377
1378 let err = WalletInterface::get_version(&wallet, None).await;
1379 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1380
1381 let err = WalletInterface::get_height(&wallet, None).await;
1382 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1383
1384 let err =
1385 WalletInterface::get_header_for_height(&wallet, GetHeaderArgs { height: 0 }, None)
1386 .await;
1387 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1388
1389 let err = WalletInterface::list_outputs(
1390 &wallet,
1391 ListOutputsArgs {
1392 basket: "test".to_string(),
1393 tags: vec![],
1394 tag_query_mode: None,
1395 include: None,
1396 include_custom_instructions: BooleanDefaultFalse(None),
1397 include_tags: BooleanDefaultFalse(None),
1398 include_labels: BooleanDefaultFalse(None),
1399 limit: Some(10),
1400 offset: None,
1401 seek_permission: BooleanDefaultTrue(None),
1402 },
1403 None,
1404 )
1405 .await;
1406 assert!(matches!(err, Err(WalletError::NotImplemented(_))));
1407 }
1408
1409 #[tokio::test]
1410 async fn test_wallet_interface_reveal_counterparty_key_linkage() {
1411 let wallet = ProtoWallet::new(test_private_key());
1412 let verifier_key = PrivateKey::from_hex("ff").unwrap();
1413 let counterparty_key = PrivateKey::from_hex("cc").unwrap();
1414
1415 let result = WalletInterface::reveal_counterparty_key_linkage(
1416 &wallet,
1417 RevealCounterpartyKeyLinkageArgs {
1418 counterparty: counterparty_key.to_public_key(),
1419 verifier: verifier_key.to_public_key(),
1420 privileged: None,
1421 privileged_reason: None,
1422 },
1423 None,
1424 )
1425 .await
1426 .unwrap();
1427
1428 assert!(!result.encrypted_linkage.is_empty());
1429 assert!(!result.encrypted_linkage_proof.is_empty());
1430 assert_eq!(
1431 result.counterparty.to_der_hex(),
1432 counterparty_key.to_public_key().to_der_hex()
1433 );
1434 assert!(!result.revelation_time.is_empty());
1435 }
1436
1437 #[tokio::test]
1438 async fn test_wallet_interface_reveal_specific_key_linkage() {
1439 let wallet = ProtoWallet::new(test_private_key());
1440 let verifier_key = PrivateKey::from_hex("ff").unwrap();
1441 let counterparty_key = PrivateKey::from_hex("bb").unwrap();
1442
1443 let result = WalletInterface::reveal_specific_key_linkage(
1444 &wallet,
1445 RevealSpecificKeyLinkageArgs {
1446 counterparty: Counterparty {
1447 counterparty_type: CounterpartyType::Other,
1448 public_key: Some(counterparty_key.to_public_key()),
1449 },
1450 verifier: verifier_key.to_public_key(),
1451 protocol_id: test_protocol(),
1452 key_id: "wlink1".to_string(),
1453 privileged: None,
1454 privileged_reason: None,
1455 },
1456 None,
1457 )
1458 .await
1459 .unwrap();
1460
1461 assert!(!result.encrypted_linkage.is_empty());
1462 assert_eq!(result.proof_type, 0);
1463 assert_eq!(result.key_id, "wlink1");
1464 }
1465
1466 #[test]
1479 fn test_counterparty_default_is_uninitialized() {
1480 let cp = Counterparty::default();
1484 assert_eq!(cp.counterparty_type, CounterpartyType::Uninitialized);
1485 assert!(cp.public_key.is_none());
1486 }
1487
1488 #[test]
1489 fn test_create_signature_defaults_uninitialized_to_anyone() {
1490 let wallet = ProtoWallet::new(test_private_key());
1500 let protocol = test_protocol();
1501 let data = b"cross-sdk-interop-canary";
1502
1503 let uninit = Counterparty::default();
1504 assert_eq!(uninit.counterparty_type, CounterpartyType::Uninitialized);
1505
1506 let sig = wallet
1507 .create_signature_sync(Some(data), None, &protocol, "sig-c1", &uninit)
1508 .unwrap();
1509
1510 let anyone = Counterparty {
1518 counterparty_type: CounterpartyType::Anyone,
1519 public_key: None,
1520 };
1521 let valid_anyone = wallet
1522 .verify_signature_sync(Some(data), None, &sig, &protocol, "sig-c1", &anyone, true)
1523 .unwrap();
1524 assert!(
1525 valid_anyone,
1526 "signature from Uninitialized counterparty must verify against 'anyone' derived key"
1527 );
1528
1529 let self_cp = self_counterparty();
1535 let valid_self = wallet
1536 .verify_signature_sync(Some(data), None, &sig, &protocol, "sig-c1", &self_cp, true)
1537 .unwrap_or(false);
1538 assert!(
1539 !valid_self,
1540 "signature from Uninitialized counterparty must NOT verify against 'self' derived key — \
1541 if this assertion fails, Counterparty::default() has regressed and createSignature \
1542 is no longer defaulting to 'anyone'"
1543 );
1544 }
1545
1546 #[test]
1547 fn test_encrypt_defaults_uninitialized_to_self() {
1548 let wallet = ProtoWallet::new(test_private_key());
1553 let protocol = test_protocol();
1554 let plaintext = b"dispatch-to-self canary";
1555
1556 let ciphertext_uninit = wallet
1557 .encrypt_sync(plaintext, &protocol, "enc-c1", &Counterparty::default())
1558 .unwrap();
1559 let ciphertext_self = wallet
1560 .encrypt_sync(plaintext, &protocol, "enc-c1", &self_counterparty())
1561 .unwrap();
1562
1563 let pt1 = wallet
1566 .decrypt_sync(
1567 &ciphertext_uninit,
1568 &protocol,
1569 "enc-c1",
1570 &self_counterparty(),
1571 )
1572 .unwrap();
1573 let pt2 = wallet
1574 .decrypt_sync(&ciphertext_self, &protocol, "enc-c1", &self_counterparty())
1575 .unwrap();
1576 assert_eq!(pt1, plaintext);
1577 assert_eq!(pt2, plaintext);
1578 }
1579
1580 #[test]
1581 fn test_encrypt_with_iv_sync_byte_locks_under_fixed_inputs() {
1582 let identity = PrivateKey::from_bytes(&[0x01u8; 32]).unwrap();
1585 let wallet = ProtoWallet::new(identity);
1586 let protocol = Protocol {
1587 security_level: 2,
1588 protocol: "mpcpresig".to_string(),
1589 };
1590 let key_id = "byte-lock-test-001";
1591 let counterparty = Counterparty {
1592 counterparty_type: CounterpartyType::Self_,
1593 public_key: None,
1594 };
1595 let plaintext = b"presig spare plaintext 32 bytesx";
1596 let iv = [0x0au8; 32];
1597
1598 let ct1 = wallet
1599 .encrypt_with_iv_sync(plaintext, &protocol, key_id, &counterparty, &iv)
1600 .unwrap();
1601 let ct2 = wallet
1602 .encrypt_with_iv_sync(plaintext, &protocol, key_id, &counterparty, &iv)
1603 .unwrap();
1604
1605 assert_eq!(ct1, ct2);
1607 assert_eq!(ct1.len(), 32 + plaintext.len() + 16);
1609 assert_eq!(&ct1[..32], &iv);
1611 let pt = wallet
1613 .decrypt_sync(&ct1, &protocol, key_id, &counterparty)
1614 .unwrap();
1615 assert_eq!(pt.as_slice(), plaintext.as_slice());
1616
1617 const EXPECTED_HEX: &str = "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a5ecbac1cba9e03ccd3599f6e862677d0e4b2bc96b6a58f9d82b04cee442dfe61bbaab30e7674c83b7139e447f7c30c94";
1623 assert_eq!(
1624 hex::encode(&ct1),
1625 EXPECTED_HEX,
1626 "byte-lock failed: key derivation or AES-GCM output has changed"
1627 );
1628 }
1629}