diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index c6fd5d05649..ca746d3b79c 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -115,7 +115,7 @@ use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequ use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::DecodeError; use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef}; -use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self}; +use crate::offers::merkle::{root_hash, SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self}; use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity}; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef}; @@ -707,7 +707,8 @@ impl Bolt12Invoice { /// 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() + let merkle_root_hash = root_hash(&self.bytes); + merkle::message_digest(SIGNATURE_TAG, merkle_root_hash).as_ref().clone() } /// Verifies that the invoice was for a request or refund created using the given key. Returns diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index bd6d58371a9..db1b34dfaa4 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -930,8 +930,9 @@ mod tests { use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG, UnsignedInvoiceRequest}; use bitcoin::blockdata::constants::ChainHash; + use bitcoin::hashes::{sha256, Hash}; use bitcoin::network::constants::Network; - use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey, self}; + use bitcoin::secp256k1::{KeyPair, Message, Secp256k1, SecretKey, self}; use core::convert::{Infallible, TryFrom}; use core::num::NonZeroU64; #[cfg(feature = "std")] @@ -942,7 +943,7 @@ mod tests { use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; use crate::offers::invoice::{Bolt12Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG}; - use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self}; + use crate::offers::merkle::{tagged_hash, SignError, SignatureTlvStreamRef, TaggedHash, self}; use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity}; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use crate::offers::payer::PayerTlvStreamRef; @@ -1545,6 +1546,23 @@ mod tests { assert_eq!(tlv_stream.payer_note, Some(&String::from("baz"))); } + #[test] + fn compute_tagged_hash() { + let unsigned_invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .payer_note("bar".into()) + .build().unwrap(); + + // Simply test that we can grab the tag and merkle root exposed by the accessor + // functions, then use them tosuccesfully compute a tagged hash. + let taggedhash = unsigned_invoice_request.as_ref(); + let tag = sha256::Hash::hash(taggedhash.tag().as_bytes()); + let _ = Message::from_slice(&tagged_hash(tag, taggedhash.merkle_root())) + .unwrap(); + } + #[test] fn fails_signing_invoice_request() { match OfferBuilder::new("foo".into(), recipient_pubkey()) diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index 7390b58fef8..0c4b29d52cc 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -31,19 +31,38 @@ tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, SIGNATURE_TYPES, { /// [BIP 340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki /// [BOLT 12]: https://github.com/rustyrussell/lightning-rfc/blob/guilt/offers/12-offer-encoding.md#signature-calculation #[derive(Debug, PartialEq)] -pub struct TaggedHash(Message); +pub struct TaggedHash { + tag: String, + merkle_root: sha256::Hash, + digest: Message, +} impl TaggedHash { /// Creates a tagged hash with the given parameters. /// /// Panics if `tlv_stream` is not a well-formed TLV stream containing at least one TLV record. pub(super) fn new(tag: &str, tlv_stream: &[u8]) -> Self { - Self(message_digest(tag, tlv_stream)) + let merkle_root = root_hash(tlv_stream); + Self { + tag: tag.to_owned(), + merkle_root, + digest: message_digest(tag, merkle_root), + } } /// Returns the digest to sign. pub fn as_digest(&self) -> &Message { - &self.0 + &self.digest + } + + /// Returns the tag used in the tagged hash. + pub fn tag(&self) -> &str { + &self.tag + } + + /// Returns the merkle root used in the tagged hash. + pub fn merkle_root(&self) -> sha256::Hash { + self.merkle_root } } @@ -99,15 +118,14 @@ pub(super) fn verify_signature( secp_ctx.verify_schnorr(signature, digest, &pubkey) } -pub(super) fn message_digest(tag: &str, bytes: &[u8]) -> Message { +pub(super) fn message_digest(tag: &str, merkle_root: sha256::Hash) -> Message { let tag = sha256::Hash::hash(tag.as_bytes()); - let merkle_root = root_hash(bytes); Message::from_slice(&tagged_hash(tag, merkle_root)).unwrap() } /// Computes a merkle root hash for the given data, which must be a well-formed TLV stream /// containing at least one TLV record. -fn root_hash(data: &[u8]) -> sha256::Hash { +pub(crate) fn root_hash(data: &[u8]) -> sha256::Hash { let nonce_tag = tagged_hash_engine(sha256::Hash::from_engine({ let first_tlv_record = TlvStream::new(&data[..]).next().unwrap(); let mut engine = sha256::Hash::engine(); @@ -144,7 +162,7 @@ fn root_hash(data: &[u8]) -> sha256::Hash { *leaves.first().unwrap() } -fn tagged_hash>(tag: sha256::Hash, msg: T) -> sha256::Hash { +pub(crate) fn tagged_hash>(tag: sha256::Hash, msg: T) -> sha256::Hash { let engine = tagged_hash_engine(tag); tagged_hash_from_engine(engine, msg) }