1use crate::error::BitcoinError;
40use bitcoin::{
41 Address, Network, OutPoint, Transaction,
42 bip32::Xpub,
43 hashes::{Hash, sha256},
44 secp256k1::{PublicKey, Secp256k1, SecretKey, ecdh},
45};
46use serde::{Deserialize, Serialize};
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50pub enum PaymentCodeVersion {
51 V1,
53 V2,
55 V3,
57}
58
59impl PaymentCodeVersion {
60 pub fn as_byte(&self) -> u8 {
62 match self {
63 Self::V1 => 0x01,
64 Self::V2 => 0x02,
65 Self::V3 => 0x03,
66 }
67 }
68
69 pub fn from_byte(byte: u8) -> Result<Self, BitcoinError> {
71 match byte {
72 0x01 => Ok(Self::V1),
73 0x02 => Ok(Self::V2),
74 0x03 => Ok(Self::V3),
75 _ => Err(BitcoinError::InvalidAddress(format!(
76 "Invalid payment code version: {}",
77 byte
78 ))),
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub struct PaymentCode {
89 pub version: PaymentCodeVersion,
91 pub features: u8,
93 pub public_key: PublicKey,
95 pub chain_code: [u8; 32],
97 pub network: Network,
99}
100
101impl PaymentCode {
102 pub fn new(
104 version: PaymentCodeVersion,
105 public_key: PublicKey,
106 chain_code: [u8; 32],
107 network: Network,
108 ) -> Self {
109 Self {
110 version,
111 features: 0x00, public_key,
113 chain_code,
114 network,
115 }
116 }
117
118 pub fn to_base58(&self) -> String {
122 let mut payload = Vec::new();
123 payload.push(self.version.as_byte());
124 payload.push(self.features);
125 payload.extend_from_slice(&self.public_key.serialize());
126 payload.extend_from_slice(&self.chain_code);
127
128 let version_byte = match self.network {
130 Network::Bitcoin => 0x47, _ => 0x4b, };
133
134 base58_encode_check(&[version_byte], &payload)
135 }
136
137 pub fn from_base58(s: &str) -> Result<Self, BitcoinError> {
139 let (version_byte, payload) = base58_decode_check(s)?;
140
141 let network = match version_byte {
142 0x47 => Network::Bitcoin,
143 0x4b => Network::Testnet,
144 _ => {
145 return Err(BitcoinError::InvalidAddress(
146 "Invalid payment code network byte".into(),
147 ));
148 }
149 };
150
151 if payload.len() != 67 {
152 return Err(BitcoinError::InvalidAddress(
153 "Invalid payment code length".into(),
154 ));
155 }
156
157 let version = PaymentCodeVersion::from_byte(payload[0])?;
158 let features = payload[1];
159 let public_key = PublicKey::from_slice(&payload[2..35])
160 .map_err(|e| BitcoinError::InvalidAddress(format!("Invalid public key: {}", e)))?;
161 let mut chain_code = [0u8; 32];
162 chain_code.copy_from_slice(&payload[35..67]);
163
164 Ok(Self {
165 version,
166 features,
167 public_key,
168 chain_code,
169 network,
170 })
171 }
172
173 pub fn is_valid(&self) -> bool {
175 true }
178}
179
180pub struct PaymentCodeManager {
182 network: Network,
183 #[allow(dead_code)]
184 secp: Secp256k1<bitcoin::secp256k1::All>,
185}
186
187impl PaymentCodeManager {
188 pub fn new(network: Network) -> Self {
190 Self {
191 network,
192 secp: Secp256k1::new(),
193 }
194 }
195
196 pub fn generate_payment_code(&self) -> Result<PaymentCode, BitcoinError> {
198 Err(BitcoinError::InvalidInput(
201 "Payment code generation requires private key access".into(),
202 ))
203 }
204
205 pub fn from_xpub(&self, xpub: &Xpub) -> Result<PaymentCode, BitcoinError> {
207 let public_key = xpub.public_key;
209 let chain_code = xpub.chain_code.to_bytes();
210
211 Ok(PaymentCode::new(
212 PaymentCodeVersion::V3, public_key,
214 chain_code,
215 self.network,
216 ))
217 }
218
219 #[allow(dead_code)]
224 fn derive_shared_secret(
225 &self,
226 my_private_key: &SecretKey,
227 their_public_key: &PublicKey,
228 ) -> [u8; 32] {
229 let shared_point = ecdh::shared_secret_point(their_public_key, my_private_key);
230 let mut secret = [0u8; 32];
231 secret.copy_from_slice(&shared_point[..32]);
232 secret
233 }
234
235 pub fn derive_receive_address(
242 &self,
243 _their_code: &PaymentCode,
244 _index: u32,
245 ) -> Result<Address, BitcoinError> {
246 Err(BitcoinError::InvalidInput(
249 "Address derivation requires private key access".into(),
250 ))
251 }
252
253 pub fn derive_send_address(
260 &self,
261 _their_code: &PaymentCode,
262 _index: u32,
263 ) -> Result<Address, BitcoinError> {
264 Err(BitcoinError::InvalidInput(
267 "Address derivation requires private key access".into(),
268 ))
269 }
270
271 pub fn create_notification_transaction(
277 &self,
278 _their_code: &PaymentCode,
279 _input: OutPoint,
280 _change_address: Address,
281 ) -> Result<NotificationTransaction, BitcoinError> {
282 Err(BitcoinError::InvalidInput(
285 "Notification transaction creation not yet implemented".into(),
286 ))
287 }
288
289 pub fn extract_notification(&self, tx: &Transaction) -> Result<PaymentCode, BitcoinError> {
291 for output in &tx.output {
293 if output.script_pubkey.is_op_return() {
294 return Err(BitcoinError::InvalidInput(
297 "Notification extraction not yet implemented".into(),
298 ));
299 }
300 }
301
302 Err(BitcoinError::InvalidAddress(
303 "No notification found in transaction".into(),
304 ))
305 }
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct NotificationTransaction {
311 pub transaction: Transaction,
313 pub notification_index: usize,
315 pub sender_code: PaymentCode,
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct PaymentChannel {
322 pub our_code: PaymentCode,
324 pub their_code: PaymentCode,
326 pub receive_index: u32,
328 pub send_index: u32,
330 pub notification_sent: bool,
332 pub notification_received: bool,
334}
335
336impl PaymentChannel {
337 pub fn new(our_code: PaymentCode, their_code: PaymentCode) -> Self {
339 Self {
340 our_code,
341 their_code,
342 receive_index: 0,
343 send_index: 0,
344 notification_sent: false,
345 notification_received: false,
346 }
347 }
348
349 pub fn is_active(&self) -> bool {
351 self.notification_sent && self.notification_received
352 }
353
354 pub fn next_receive_index(&mut self) -> u32 {
356 let index = self.receive_index;
357 self.receive_index += 1;
358 index
359 }
360
361 pub fn next_send_index(&mut self) -> u32 {
363 let index = self.send_index;
364 self.send_index += 1;
365 index
366 }
367}
368
369fn base58_encode_check(version: &[u8], payload: &[u8]) -> String {
371 let mut data = Vec::new();
372 data.extend_from_slice(version);
373 data.extend_from_slice(payload);
374
375 let hash = sha256::Hash::hash(&data);
377 let hash2 = sha256::Hash::hash(hash.as_byte_array());
378 let checksum = &hash2.as_byte_array()[..4];
379
380 data.extend_from_slice(checksum);
381 bs58::encode(data).into_string()
382}
383
384fn base58_decode_check(s: &str) -> Result<(u8, Vec<u8>), BitcoinError> {
386 let decoded = bs58::decode(s)
387 .into_vec()
388 .map_err(|e| BitcoinError::InvalidAddress(format!("Base58 decode error: {}", e)))?;
389
390 if decoded.len() < 5 {
391 return Err(BitcoinError::InvalidAddress("Invalid base58 length".into()));
392 }
393
394 let checksum_index = decoded.len() - 4;
395 let (data, checksum) = decoded.split_at(checksum_index);
396
397 let hash = sha256::Hash::hash(data);
399 let hash2 = sha256::Hash::hash(hash.as_byte_array());
400 if &hash2.as_byte_array()[..4] != checksum {
401 return Err(BitcoinError::InvalidAddress("Invalid checksum".into()));
402 }
403
404 Ok((data[0], data[1..].to_vec()))
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410 use bitcoin::secp256k1::rand::thread_rng;
411
412 #[test]
413 fn test_payment_code_version() {
414 assert_eq!(PaymentCodeVersion::V1.as_byte(), 0x01);
415 assert_eq!(PaymentCodeVersion::V2.as_byte(), 0x02);
416 assert_eq!(PaymentCodeVersion::V3.as_byte(), 0x03);
417
418 assert_eq!(
419 PaymentCodeVersion::from_byte(0x01).unwrap(),
420 PaymentCodeVersion::V1
421 );
422 assert_eq!(
423 PaymentCodeVersion::from_byte(0x02).unwrap(),
424 PaymentCodeVersion::V2
425 );
426 assert_eq!(
427 PaymentCodeVersion::from_byte(0x03).unwrap(),
428 PaymentCodeVersion::V3
429 );
430 }
431
432 #[test]
433 fn test_payment_code_manager_creation() {
434 let manager = PaymentCodeManager::new(Network::Bitcoin);
435 assert_eq!(manager.network, Network::Bitcoin);
436
437 let manager_testnet = PaymentCodeManager::new(Network::Testnet);
438 assert_eq!(manager_testnet.network, Network::Testnet);
439 }
440
441 #[test]
442 fn test_payment_channel_creation() {
443 let secp = Secp256k1::new();
444 let (_, public_key1) = secp.generate_keypair(&mut thread_rng());
445 let (_, public_key2) = secp.generate_keypair(&mut thread_rng());
446
447 let code1 = PaymentCode::new(
448 PaymentCodeVersion::V3,
449 public_key1,
450 [0u8; 32],
451 Network::Bitcoin,
452 );
453 let code2 = PaymentCode::new(
454 PaymentCodeVersion::V3,
455 public_key2,
456 [0u8; 32],
457 Network::Bitcoin,
458 );
459
460 let mut channel = PaymentChannel::new(code1.clone(), code2.clone());
461 assert_eq!(channel.receive_index, 0);
462 assert_eq!(channel.send_index, 0);
463 assert!(!channel.is_active());
464
465 let index = channel.next_receive_index();
466 assert_eq!(index, 0);
467 assert_eq!(channel.receive_index, 1);
468
469 let index = channel.next_send_index();
470 assert_eq!(index, 0);
471 assert_eq!(channel.send_index, 1);
472 }
473
474 #[test]
475 fn test_payment_channel_activation() {
476 let secp = Secp256k1::new();
477 let (_, public_key1) = secp.generate_keypair(&mut thread_rng());
478 let (_, public_key2) = secp.generate_keypair(&mut thread_rng());
479
480 let code1 = PaymentCode::new(
481 PaymentCodeVersion::V3,
482 public_key1,
483 [0u8; 32],
484 Network::Bitcoin,
485 );
486 let code2 = PaymentCode::new(
487 PaymentCodeVersion::V3,
488 public_key2,
489 [0u8; 32],
490 Network::Bitcoin,
491 );
492
493 let mut channel = PaymentChannel::new(code1, code2);
494 assert!(!channel.is_active());
495
496 channel.notification_sent = true;
497 assert!(!channel.is_active());
498
499 channel.notification_received = true;
500 assert!(channel.is_active());
501 }
502
503 #[test]
504 fn test_payment_code_validation() {
505 let secp = Secp256k1::new();
506 let (_, public_key) = secp.generate_keypair(&mut thread_rng());
507
508 let code = PaymentCode::new(
509 PaymentCodeVersion::V3,
510 public_key,
511 [0u8; 32],
512 Network::Bitcoin,
513 );
514
515 assert!(code.is_valid());
516 }
517}