1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use bitcoin::hashes::Hash;
use bitcoin::{bech32::u5, util::address::Payload, Address, Network};
use byteorder::{BigEndian, ByteOrder};
use chrono::{offset::Local, DateTime, Duration};
use lightning_invoice::{Currency, Fallback, Invoice, InvoiceDescription, RouteHop};
use serde::{Deserialize, Serialize};

const WRONG_CID: &'static str = "incorrect short channel ID HRF format";

/// Parse a short channel is in the form of `${blockheight)x$(txindex}x${outputindex}`.
pub fn parse_short_channel_id(cid: &str) -> u64 {
	let mut split = cid.split("x");
	let blocknum: u64 = split.next().expect(WRONG_CID).parse().expect(WRONG_CID);
	if blocknum & 0xFFFFFF != blocknum {
		panic!(WRONG_CID);
	}
	let txnum: u64 = split.next().expect(WRONG_CID).parse().expect(WRONG_CID);
	if txnum & 0xFFFFFF != txnum {
		panic!(WRONG_CID);
	}
	let outnum: u64 = split.next().expect(WRONG_CID).parse().expect(WRONG_CID);
	if outnum & 0xFFFF != outnum {
		panic!(WRONG_CID);
	}
	blocknum << 40 | txnum << 16 | outnum
}

/// Parse a short channel is in the form of `${blockheight)x$(txindex}x${outputindex}`.
pub fn fmt_short_channel_id(cid: u64) -> String {
	let blocknum = cid >> 40;
	let txnum = cid >> 16 & 0x00FFFFFF;
	let outnum = cid & 0xFFFF;
	format!("{}x{}x{}", blocknum, txnum, outnum)
}

#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct RouteHopInfo {
	pub pubkey: ::HexBytes,
	pub short_channel_id: u64,
	pub short_channel_id_hex: ::HexBytes,
	pub short_channel_id_hrf: String,
	pub fee_base_msat: u32,
	pub fee_proportional_millionths: u32,
	pub cltv_expiry_delta: u16,
}

impl ::GetInfo<RouteHopInfo> for RouteHop {
	fn get_info(&self, _network: Network) -> RouteHopInfo {
		let ssid_hex = &self.short_channel_id[..];
		let ssid = BigEndian::read_u64(&ssid_hex);
		RouteHopInfo {
			pubkey: self.pubkey.serialize()[..].into(),
			short_channel_id: ssid,
			short_channel_id_hex: ssid_hex.into(),
			short_channel_id_hrf: fmt_short_channel_id(ssid),
			fee_base_msat: self.fee_base_msat,
			fee_proportional_millionths: self.fee_proportional_millionths,
			cltv_expiry_delta: self.cltv_expiry_delta,
		}
	}
}

#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct InvoiceInfo {
	pub timestamp: DateTime<Local>,
	pub payment_hash: String, //TODO(stevenroose) use bitcoin_hashes
	pub description: String,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub payee_pub_key: Option<::HexBytes>,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub expiry_time: Option<DateTime<Local>>,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub min_final_cltv_expiry: Option<u64>,
	#[serde(skip_serializing_if = "Vec::is_empty")]
	pub fallback_addresses: Vec<Address>,
	#[serde(skip_serializing_if = "Vec::is_empty")]
	pub routes: Vec<Vec<RouteHopInfo>>,
	pub currency: String,
	#[serde(skip_serializing_if = "Option::is_none")]
	pub amount_pico_btc: Option<u64>,

	// For signed invoices.
	pub signature: ::HexBytes,
	pub signature_recover_id: i32,
	pub payee_pubkey: Option<::HexBytes>,
}

impl ::GetInfo<InvoiceInfo> for Invoice {
	fn get_info(&self, network: Network) -> InvoiceInfo {
		let signed_raw = self.clone().into_signed_raw();
		let (sig_rec, sig) = signed_raw.signature().0.serialize_compact();

		InvoiceInfo {
			timestamp: self.timestamp().clone().into(),
			//TODO(stevenroose) see https://github.com/rust-bitcoin/rust-lightning-invoice/issues/23
			payment_hash: format!("{:?}", self.payment_hash()),
			description: match self.description() {
				InvoiceDescription::Direct(s) => s.clone().into_inner(),
				//TODO(stevenroose) see https://github.com/rust-bitcoin/rust-lightning-invoice/issues/23
				InvoiceDescription::Hash(h) => format!("{:?}", h),
			},
			payee_pub_key: self.payee_pub_key().map(|pk| pk.serialize()[..].into()),
			expiry_time: self.expiry_time().map(|e| {
				let duration = Duration::from_std(*e.as_duration()).expect("invalid expiry");
				Local::now() + duration
			}),
			min_final_cltv_expiry: self.min_final_cltv_expiry().map(|e| e.0),
			fallback_addresses: self
				.fallbacks()
				.iter()
				.map(|f| {
					//TODO(stevenroose) see https://github.com/rust-bitcoin/rust-lightning-invoice/issues/24
					Address {
						payload: match f {
							Fallback::PubKeyHash(pkh) => {
								Payload::PubkeyHash(Hash::from_slice(&pkh[..]).expect("wrong hash"))
							}
							Fallback::ScriptHash(sh) => {
								Payload::ScriptHash(Hash::from_slice(&sh[..]).expect("wrong hash"))
							}
							Fallback::SegWitProgram {
								version: v,
								program: p,
							} => Payload::WitnessProgram {
								//TODO(stevenroose) remove after https://github.com/rust-bitcoin/rust-bech32-bitcoin/issues/21
								version: u5::try_from_u8(v.to_u8())
									.expect("invalid segwit version"),
								program: p.to_vec(),
							},
						},
						network: network,
					}
				})
				.collect(),
			routes: self
				.routes()
				.iter()
				.map(|r| r.iter().map(|h| ::GetInfo::get_info(h, network)).collect())
				.collect(),
			currency: match self.currency() {
				Currency::Bitcoin => "bitcoin".to_owned(),
				Currency::BitcoinTestnet => "bitcoin-testnet".to_owned(),
			},
			amount_pico_btc: self.amount_pico_btc(),
			signature: sig.as_ref().into(),
			signature_recover_id: sig_rec.to_i32(),
			payee_pubkey: signed_raw
				.recover_payee_pub_key()
				.ok()
				.map(|s| s.0.serialize().as_ref().into()),
		}
	}
}