bitcoin-payment-instructions 0.7.0

A parser for arbitrary bitcoin payment instructions.
Documentation
use dnssec_prover::rr::{Name, RR};
use dnssec_prover::ser::parse_rr_stream;
use dnssec_prover::validation::verify_rr_stream;

use std::time::SystemTime;

use crate::hrn_resolution::HrnResolution;

pub fn resolve_proof(dns_name: &Name, proof: Vec<u8>) -> Result<HrnResolution, &'static str> {
	let rrs = parse_rr_stream(&proof)
		.map_err(|()| "DNS Proof Builder somehow generated an invalid proof")?;
	let verified_rrs = verify_rr_stream(&rrs).map_err(|_| "DNSSEC signatures were invalid")?;

	let clock_err =
		"DNSSEC validation relies on having a correct system clock. It is currently set before 1970.";
	let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).map_err(|_| clock_err)?;

	if now.as_secs() < verified_rrs.valid_from {
		return Err("Some DNSSEC records are not yet valid. Check your system clock.");
	}
	if now.as_secs() > verified_rrs.expires {
		return Err("Some DNSSEC records are expired. Check your system clock.");
	}

	let resolved_rrs = verified_rrs.resolve_name(dns_name);

	let mut result = None;
	for rr in resolved_rrs {
		if let RR::Txt(txt) = rr {
			let txt = txt.data.as_vec();
			if has_bitcoin_prefix(&txt) {
				if result.is_some() {
					return Err("Multiple TXT records existed for the HRN, which is invalid");
				}
				result = Some(txt);
			}
		}
	}
	let res = result.ok_or("No validated TXT record found")?;
	let result = String::from_utf8(res).map_err(|_| "TXT record contained an invalid string")?;
	Ok(HrnResolution::DNSSEC { proof: Some(proof), result })
}

fn has_bitcoin_prefix(text: &[u8]) -> bool {
	const URI_PREFIX: &[u8] = b"bitcoin:";
	text.len() >= URI_PREFIX.len() && text[..URI_PREFIX.len()].eq_ignore_ascii_case(URI_PREFIX)
}

#[cfg(test)]
mod tests {
	use super::has_bitcoin_prefix;

	#[test]
	fn detects_expected_prefix() {
		assert!(has_bitcoin_prefix(b"bitcoin:bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty"));
		assert!(has_bitcoin_prefix(b"BiTcOiN:pay?amount=1000"));
		assert!(!has_bitcoin_prefix(b"lightning:lnurl"));
		assert!(!has_bitcoin_prefix(b"bitco"));
	}
}