breez_sdk_spark/signer/
rtsync.rs1use anyhow::anyhow;
2use bitcoin::bip32::DerivationPath;
3use breez_sdk_common::sync::SyncSigner;
4use std::sync::Arc;
5
6use crate::{
7 Network,
8 signer::{BreezSigner, EciesSigner},
9};
10
11const SIGNING_DERIVATION_PATH: &str = "m/1220588449'/0'/0'/0/0";
12const SIGNING_DERIVATION_PATH_TEST: &str = "m/1220588449'/1'/0'/0/0";
13const SIGNING_DERIVATION_PATH_SIGNET: &str = "m/1220588449'/1'/1'/0/0";
14const ENCRYPTION_DERIVATION_PATH: &str = "m/1782705014'/0'/0'/0/0";
15const ENCRYPTION_DERIVATION_PATH_TEST: &str = "m/1782705014'/1'/0'/0/0";
16const ENCRYPTION_DERIVATION_PATH_SIGNET: &str = "m/1782705014'/1'/1'/0/0";
17
18pub struct RTSyncSigner {
19 signer: Arc<dyn BreezSigner>,
20 ecies: Arc<dyn EciesSigner>,
21 signing_path: DerivationPath,
22 encryption_path: DerivationPath,
23}
24
25impl RTSyncSigner {
26 pub fn new(
27 signer: Arc<dyn BreezSigner>,
28 ecies: Arc<dyn EciesSigner>,
29 network: Network,
30 ) -> Result<Self, bitcoin::bip32::Error> {
31 let signing_path: DerivationPath = match network {
32 Network::Mainnet => SIGNING_DERIVATION_PATH,
33 Network::Regtest => SIGNING_DERIVATION_PATH_TEST,
34 Network::Signet => SIGNING_DERIVATION_PATH_SIGNET,
35 }
36 .parse()?;
37 let encryption_path: DerivationPath = match network {
38 Network::Mainnet => ENCRYPTION_DERIVATION_PATH,
39 Network::Regtest => ENCRYPTION_DERIVATION_PATH_TEST,
40 Network::Signet => ENCRYPTION_DERIVATION_PATH_SIGNET,
41 }
42 .parse()?;
43
44 Ok(Self {
45 signer,
46 ecies,
47 signing_path,
48 encryption_path,
49 })
50 }
51}
52
53#[macros::async_trait]
54impl SyncSigner for RTSyncSigner {
55 async fn sign_ecdsa_recoverable(&self, data: &[u8]) -> anyhow::Result<Vec<u8>> {
56 use bitcoin::hashes::{Hash, sha256};
57 use bitcoin::secp256k1::Message;
58
59 let hash = sha256::Hash::hash(sha256::Hash::hash(data).as_ref());
61 let message = Message::from_digest(hash.to_byte_array());
62 let sig = self
63 .signer
64 .sign_ecdsa_recoverable(message, &self.signing_path)
65 .await
66 .map_err(|e| anyhow!(e.to_string()))?;
67
68 let (recovery_id, sig_bytes) = sig.serialize_compact();
70 let mut complete_signature = vec![31u8.saturating_add(
71 u8::try_from(recovery_id.to_i32()).map_err(|e| anyhow!(e.to_string()))?,
72 )];
73 complete_signature.extend_from_slice(&sig_bytes);
74 Ok(complete_signature)
75 }
76
77 async fn encrypt_ecies(&self, msg: Vec<u8>) -> anyhow::Result<Vec<u8>> {
78 self.ecies
79 .encrypt_ecies(&msg, &self.encryption_path)
80 .await
81 .map_err(|e| anyhow!(e.to_string()))
82 }
83
84 async fn decrypt_ecies(&self, msg: Vec<u8>) -> anyhow::Result<Vec<u8>> {
85 self.ecies
86 .decrypt_ecies(&msg, &self.encryption_path)
87 .await
88 .map_err(|e| anyhow!(e.to_string()))
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::signer::breez::BreezSignerImpl;
96 use bitcoin::bip32::Xpriv;
97
98 #[test]
99 fn sync_derivation_paths() {
100 let master = Xpriv::new_master(bitcoin::Network::Signet, &[7; 32]).unwrap();
101 let signer = Arc::new(BreezSignerImpl::new(master));
102 for (network, signing, encryption) in [
103 (
104 Network::Mainnet,
105 "m/1220588449'/0'/0'/0/0",
106 "m/1782705014'/0'/0'/0/0",
107 ),
108 (
109 Network::Regtest,
110 "m/1220588449'/1'/0'/0/0",
111 "m/1782705014'/1'/0'/0/0",
112 ),
113 (
114 Network::Signet,
115 "m/1220588449'/1'/1'/0/0",
116 "m/1782705014'/1'/1'/0/0",
117 ),
118 ] {
119 let sync = RTSyncSigner::new(signer.clone(), signer.clone(), network).unwrap();
120 assert_eq!(sync.signing_path, signing.parse().unwrap());
121 assert_eq!(sync.encryption_path, encryption.parse().unwrap());
122 }
123 }
124
125 #[macros::async_test_all]
126 async fn signet_sync_is_isolated_from_regtest_with_the_same_signer() {
127 let master = Xpriv::new_master(bitcoin::Network::Signet, &[7; 32]).unwrap();
128 let shared_signer = Arc::new(BreezSignerImpl::new(master));
129 let signet = RTSyncSigner::new(
130 shared_signer.clone(),
131 shared_signer.clone(),
132 Network::Signet,
133 )
134 .unwrap();
135 let regtest =
136 RTSyncSigner::new(shared_signer.clone(), shared_signer, Network::Regtest).unwrap();
137 let message = b"sync data".to_vec();
138 assert_ne!(
139 signet.sign_ecdsa_recoverable(&message).await.unwrap(),
140 regtest.sign_ecdsa_recoverable(&message).await.unwrap()
141 );
142 for (source, other) in [(&signet, ®test), (®test, &signet)] {
143 let encrypted = source.encrypt_ecies(message.clone()).await.unwrap();
144 assert_eq!(
145 source.decrypt_ecies(encrypted.clone()).await.unwrap(),
146 message
147 );
148 assert!(other.decrypt_ecies(encrypted).await.is_err());
149 }
150 }
151}