//! let secp_ctx = Secp256k1::new();
//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?);
//! let pubkey = PublicKey::from(keys);
-//! let wpubkey_hash = bitcoin::util::key::PublicKey::new(pubkey).wpubkey_hash().unwrap();
+//! let wpubkey_hash = bitcoin::key::PublicKey::new(pubkey).wpubkey_hash().unwrap();
//! let mut buffer = Vec::new();
//!
//! // Invoice for the "offer to be paid" flow.
//! # let secp_ctx = Secp256k1::new();
//! # let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?);
//! # let pubkey = PublicKey::from(keys);
-//! # let wpubkey_hash = bitcoin::util::key::PublicKey::new(pubkey).wpubkey_hash().unwrap();
+//! # let wpubkey_hash = bitcoin::key::PublicKey::new(pubkey).wpubkey_hash().unwrap();
//! # let mut buffer = Vec::new();
//!
//! // Invoice for the "offer for money" flow.
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
use bitcoin::secp256k1::schnorr::Signature;
-use bitcoin::util::address::{Address, Payload, WitnessVersion};
-use bitcoin::util::schnorr::TweakedPublicKey;
+use bitcoin::address::{Address, Payload, WitnessProgram, WitnessVersion};
+use bitcoin::key::TweakedPublicKey;
use core::convert::{AsRef, Infallible, TryFrom};
use core::time::Duration;
use crate::io;
use crate::blinded_path::BlindedPath;
use crate::ln::PaymentHash;
+use crate::ln::channelmanager::PaymentId;
use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::DecodeError;
#[cfg(feature = "std")]
use std::time::SystemTime;
-const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
+pub(crate) const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
/// Tag for the hash function used when signing a [`Bolt12Invoice`]'s merkle root.
pub const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
created_at: Duration, payment_hash: PaymentHash
) -> Result<Self, Bolt12SemanticError> {
- let amount_msats = Self::check_amount_msats(invoice_request)?;
+ let amount_msats = Self::amount_msats(invoice_request)?;
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
let contents = InvoiceContents::ForOffer {
invoice_request: invoice_request.contents.clone(),
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
) -> Result<Self, Bolt12SemanticError> {
- let amount_msats = Self::check_amount_msats(invoice_request)?;
+ let amount_msats = Self::amount_msats(invoice_request)?;
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
let contents = InvoiceContents::ForOffer {
invoice_request: invoice_request.contents.clone(),
}
impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
- fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result<u64, Bolt12SemanticError> {
+ pub(crate) fn amount_msats(
+ invoice_request: &InvoiceRequest
+ ) -> Result<u64, Bolt12SemanticError> {
match invoice_request.amount_msats() {
Some(amount_msats) => Ok(amount_msats),
None => match invoice_request.contents.inner.offer.amount() {
pub fn fallback_v0_p2wsh(mut self, script_hash: &WScriptHash) -> Self {
let address = FallbackAddress {
version: WitnessVersion::V0.to_num(),
- program: Vec::from(&script_hash.into_inner()[..]),
+ program: Vec::from(script_hash.to_byte_array()),
};
self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
self
pub fn fallback_v0_p2wpkh(mut self, pubkey_hash: &WPubkeyHash) -> Self {
let address = FallbackAddress {
version: WitnessVersion::V0.to_num(),
- program: Vec::from(&pubkey_hash.into_inner()[..]),
+ program: Vec::from(pubkey_hash.to_byte_array()),
};
self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
self
}
}
+ #[cfg(not(feature = "std"))] {
+ if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) {
+ return Err(Bolt12SemanticError::AlreadyExpired);
+ }
+ }
+
let InvoiceBuilder { invreq_bytes, invoice, .. } = self;
Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice))
}
}
}
+ #[cfg(not(feature = "std"))] {
+ if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) {
+ return Err(Bolt12SemanticError::AlreadyExpired);
+ }
+ }
+
let InvoiceBuilder {
invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys)
} = self;
bytes: self.bytes,
contents: self.contents,
signature,
+ tagged_hash: self.tagged_hash,
})
}
}
bytes: Vec<u8>,
contents: InvoiceContents,
signature: Signature,
+ tagged_hash: TaggedHash,
}
/// The contents of an [`Bolt12Invoice`] for responding to either an [`Offer`] or a [`Refund`].
/// Hash that was used for signing the invoice.
pub fn signable_hash(&self) -> [u8; 32] {
- merkle::message_digest(SIGNATURE_TAG, &self.bytes).as_ref().clone()
+ self.tagged_hash.as_digest().as_ref().clone()
}
- /// Verifies that the invoice was for a request or refund created using the given key.
+ /// Verifies that the invoice was for a request or refund created using the given key. Returns
+ /// the associated [`PaymentId`] to use when sending the payment.
pub fn verify<T: secp256k1::Signing>(
&self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> bool {
+ ) -> Result<PaymentId, ()> {
self.contents.verify(TlvStream::new(&self.bytes), key, secp_ctx)
}
- #[cfg(test)]
- pub(super) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
+ pub(crate) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) =
self.contents.as_tlv_stream();
let signature_tlv_stream = SignatureTlvStreamRef {
}
}
+ #[cfg(not(feature = "std"))]
+ fn is_offer_or_refund_expired_no_std(&self, duration_since_epoch: Duration) -> bool {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } =>
+ invoice_request.inner.offer.is_expired_no_std(duration_since_epoch),
+ InvoiceContents::ForRefund { refund, .. } =>
+ refund.is_expired_no_std(duration_since_epoch),
+ }
+ }
+
fn offer_chains(&self) -> Option<Vec<ChainHash>> {
match self {
InvoiceContents::ForOffer { invoice_request, .. } =>
};
let program = &address.program;
- if program.len() < 2 || program.len() > 40 {
- return None;
- }
-
- let address = Address {
- payload: Payload::WitnessProgram {
- version,
- program: program.clone(),
- },
- network,
+ let witness_program = match WitnessProgram::new(version, program.clone()) {
+ Ok(witness_program) => witness_program,
+ Err(_) => return None,
};
-
- if !address.is_standard() && version == WitnessVersion::V0 {
- return None;
- }
-
- Some(address)
+ Some(Address::new(network, Payload::WitnessProgram(witness_program)))
};
self.fields().fallbacks
fn verify<T: secp256k1::Signing>(
&self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> bool {
+ ) -> Result<PaymentId, ()> {
let offer_records = tlv_stream.clone().range(OFFER_TYPES);
let invreq_records = tlv_stream.range(INVOICE_REQUEST_TYPES).filter(|record| {
match record.r#type {
},
};
- match signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) {
- Ok(_) => true,
- Err(()) => false,
- }
+ signer::verify_payer_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx)
}
fn derives_keys(&self) -> bool {
type FullInvoiceTlvStream =
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream);
-#[cfg(test)]
type FullInvoiceTlvStreamRef<'a> = (
PayerTlvStreamRef<'a>,
OfferTlvStreamRef<'a>,
None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
Some(signature) => signature,
};
- let message = TaggedHash::new(SIGNATURE_TAG, &bytes);
+ let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
let pubkey = contents.fields().signing_pubkey;
- merkle::verify_signature(&signature, message, pubkey)?;
+ merkle::verify_signature(&signature, &tagged_hash, pubkey)?;
- Ok(Bolt12Invoice { bytes, contents, signature })
+ Ok(Bolt12Invoice { bytes, contents, signature, tagged_hash })
}
}
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
use bitcoin::blockdata::constants::ChainHash;
- use bitcoin::blockdata::script::Script;
+ use bitcoin::blockdata::script::ScriptBuf;
use bitcoin::hashes::Hash;
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{Message, Secp256k1, XOnlyPublicKey, self};
- use bitcoin::util::address::{Address, Payload, WitnessVersion};
- use bitcoin::util::schnorr::TweakedPublicKey;
+ use bitcoin::address::{Address, Payload, WitnessProgram, WitnessVersion};
+ use bitcoin::key::TweakedPublicKey;
use core::convert::TryFrom;
use core::time::Duration;
use crate::blinded_path::{BlindedHop, BlindedPath};
assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
let message = TaggedHash::new(SIGNATURE_TAG, &invoice.bytes);
- assert!(merkle::verify_signature(&invoice.signature, message, recipient_pubkey()).is_ok());
+ assert!(merkle::verify_signature(&invoice.signature, &message, recipient_pubkey()).is_ok());
let digest = Message::from_slice(&invoice.signable_hash()).unwrap();
let pubkey = recipient_pubkey().into();
assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
let message = TaggedHash::new(SIGNATURE_TAG, &invoice.bytes);
- assert!(merkle::verify_signature(&invoice.signature, message, recipient_pubkey()).is_ok());
+ assert!(merkle::verify_signature(&invoice.signature, &message, recipient_pubkey()).is_ok());
assert_eq!(
invoice.as_tlv_stream(),
.build().unwrap()
.sign(payer_sign).unwrap();
- if let Err(e) = invoice_request
- .verify_and_respond_using_derived_keys_no_std(
- payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
- )
- .unwrap()
+ if let Err(e) = invoice_request.clone()
+ .verify(&expanded_key, &secp_ctx).unwrap()
+ .respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build_and_sign(&secp_ctx)
{
panic!("error building invoice: {:?}", e);
}
let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32]));
- match invoice_request.verify_and_respond_using_derived_keys_no_std(
- payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
- ) {
- Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
- }
+ assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
let desc = "foo".to_string();
let offer = OfferBuilder
::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
.amount_msats(1000)
+ // Omit the path so that node_id is used for the signing pubkey instead of deriving
.build().unwrap();
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
- match invoice_request.verify_and_respond_using_derived_keys_no_std(
- payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
- ) {
+ match invoice_request
+ .verify(&expanded_key, &secp_ctx).unwrap()
+ .respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now())
+ {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
}
#[test]
fn builds_invoice_with_fallback_address() {
- let script = Script::new();
- let pubkey = bitcoin::util::key::PublicKey::new(recipient_pubkey());
+ let script = ScriptBuf::new();
+ let pubkey = bitcoin::key::PublicKey::new(recipient_pubkey());
let x_only_pubkey = XOnlyPublicKey::from_keypair(&recipient_keys()).0;
let tweaked_pubkey = TweakedPublicKey::dangerous_assume_tweaked(x_only_pubkey);
Some(&vec![
FallbackAddress {
version: WitnessVersion::V0.to_num(),
- program: Vec::from(&script.wscript_hash().into_inner()[..]),
+ program: Vec::from(script.wscript_hash().to_byte_array()),
},
FallbackAddress {
version: WitnessVersion::V0.to_num(),
- program: Vec::from(&pubkey.wpubkey_hash().unwrap().into_inner()[..]),
+ program: Vec::from(pubkey.wpubkey_hash().unwrap().to_byte_array()),
},
FallbackAddress {
version: WitnessVersion::V1.to_num(),
#[test]
fn parses_invoice_with_fallback_address() {
- let script = Script::new();
- let pubkey = bitcoin::util::key::PublicKey::new(recipient_pubkey());
+ let script = ScriptBuf::new();
+ let pubkey = bitcoin::key::PublicKey::new(recipient_pubkey());
let x_only_pubkey = XOnlyPublicKey::from_keypair(&recipient_keys()).0;
let tweaked_pubkey = TweakedPublicKey::dangerous_assume_tweaked(x_only_pubkey);
match Bolt12Invoice::try_from(buffer) {
Ok(invoice) => {
+ let v1_witness_program = WitnessProgram::new(WitnessVersion::V1, vec![0u8; 33]).unwrap();
+ let v2_witness_program = WitnessProgram::new(WitnessVersion::V2, vec![0u8; 40]).unwrap();
assert_eq!(
invoice.fallbacks(),
vec![
Address::p2wsh(&script, Network::Bitcoin),
Address::p2wpkh(&pubkey, Network::Bitcoin).unwrap(),
Address::p2tr_tweaked(tweaked_pubkey, Network::Bitcoin),
- Address {
- payload: Payload::WitnessProgram {
- version: WitnessVersion::V1,
- program: vec![0u8; 33],
- },
- network: Network::Bitcoin,
- },
- Address {
- payload: Payload::WitnessProgram {
- version: WitnessVersion::V2,
- program: vec![0u8; 40],
- },
- network: Network::Bitcoin,
- },
+ Address::new(Network::Bitcoin, Payload::WitnessProgram(v1_witness_program)),
+ Address::new(Network::Bitcoin, Payload::WitnessProgram(v2_witness_program)),
],
);
},