diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index c6fd5d05649..5bd5a95d8e6 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -439,6 +439,7 @@ impl UnsignedBolt12Invoice { bytes: self.bytes, contents: self.contents, signature, + tagged_hash: self.tagged_hash, }) } } @@ -463,6 +464,7 @@ pub struct Bolt12Invoice { bytes: Vec, contents: InvoiceContents, signature: Signature, + tagged_hash: TaggedHash, } /// The contents of an [`Bolt12Invoice`] for responding to either an [`Offer`] or a [`Refund`]. @@ -707,7 +709,7 @@ 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() + self.tagged_hash.as_digest().as_ref().clone() } /// Verifies that the invoice was for a request or refund created using the given key. Returns @@ -1212,11 +1214,11 @@ impl TryFrom> for Bolt12Invoice { 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 }) } } @@ -1431,7 +1433,7 @@ mod tests { 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(); @@ -1528,7 +1530,7 @@ mod tests { 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(), diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index bd6d58371a9..9ddd741a1d5 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -876,7 +876,7 @@ impl TryFrom> for InvoiceRequest { Some(signature) => signature, }; let message = TaggedHash::new(SIGNATURE_TAG, &bytes); - merkle::verify_signature(&signature, message, contents.payer_id)?; + merkle::verify_signature(&signature, &message, contents.payer_id)?; Ok(InvoiceRequest { bytes, contents, signature }) } @@ -1013,7 +1013,7 @@ mod tests { assert_eq!(invoice_request.payer_note(), None); let message = TaggedHash::new(SIGNATURE_TAG, &invoice_request.bytes); - assert!(merkle::verify_signature(&invoice_request.signature, message, payer_pubkey()).is_ok()); + assert!(merkle::verify_signature(&invoice_request.signature, &message, payer_pubkey()).is_ok()); assert_eq!( invoice_request.as_tlv_stream(), diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index 7390b58fef8..a86f0b4e6fb 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -30,20 +30,41 @@ 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); +#[derive(Clone, Debug, PartialEq)] +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 tag_hash = sha256::Hash::hash(tag.as_bytes()); + let merkle_root = root_hash(tlv_stream); + let digest = Message::from_slice(&tagged_hash(tag_hash, merkle_root)).unwrap(); + Self { + tag: tag.to_owned(), + merkle_root, + digest, + } } /// 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 } } @@ -91,7 +112,7 @@ where /// Verifies the signature with a pubkey over the given message using a tagged hash as the message /// digest. pub(super) fn verify_signature( - signature: &Signature, message: TaggedHash, pubkey: PublicKey, + signature: &Signature, message: &TaggedHash, pubkey: PublicKey, ) -> Result<(), secp256k1::Error> { let digest = message.as_digest(); let pubkey = pubkey.into(); @@ -99,12 +120,6 @@ pub(super) fn verify_signature( secp_ctx.verify_schnorr(signature, digest, &pubkey) } -pub(super) fn message_digest(tag: &str, bytes: &[u8]) -> 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 { @@ -258,12 +273,13 @@ mod tests { use super::{SIGNATURE_TYPES, TlvStream, WithoutSignatures}; use bitcoin::hashes::{Hash, sha256}; - use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey}; + use bitcoin::secp256k1::{KeyPair, Message, Secp256k1, SecretKey}; use bitcoin::secp256k1::schnorr::Signature; use core::convert::Infallible; use crate::offers::offer::{Amount, OfferBuilder}; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::parse::Bech32Encode; + use crate::offers::test_utils::{payer_pubkey, recipient_pubkey}; use crate::util::ser::Writeable; #[test] @@ -322,6 +338,25 @@ mod tests { ); } + #[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 to succesfully compute a tagged hash. + let tagged_hash = unsigned_invoice_request.as_ref(); + let expected_digest = unsigned_invoice_request.as_ref().as_digest(); + let tag = sha256::Hash::hash(tagged_hash.tag().as_bytes()); + let actual_digest = Message::from_slice(&super::tagged_hash(tag, tagged_hash.merkle_root())) + .unwrap(); + assert_eq!(*expected_digest, actual_digest); + } + #[test] fn skips_encoding_signature_tlv_records() { let secp_ctx = Secp256k1::new();