X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Fmerkle.rs;h=9782dc7d1e84131f1d6e659dce2d28ac7e9de6f0;hb=825ea9d062886a369bda9d30ad6890fada7f2ed8;hp=d34a2a073f345c1b6c375b0775d378b363897eb3;hpb=13ba7cc5233bf7ed692adebefee0b352d4800ab3;p=rust-lightning diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index d34a2a07..9782dc7d 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -13,7 +13,7 @@ use bitcoin::hashes::{Hash, HashEngine, sha256}; use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self}; use bitcoin::secp256k1::schnorr::Signature; use crate::io; -use crate::util::ser::{BigSize, Readable}; +use crate::util::ser::{BigSize, Readable, Writeable, Writer}; use crate::prelude::*; @@ -25,7 +25,7 @@ tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, SIGNATURE_TYPES, { }); /// Error when signing messages. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum SignError { /// User-defined error when signing the message. Signing(E), @@ -75,22 +75,21 @@ fn message_digest(tag: &str, bytes: &[u8]) -> Message { /// 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 { - let mut tlv_stream = TlvStream::new(&data[..]).peekable(); 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(); engine.input("LnNonce".as_bytes()); - engine.input(tlv_stream.peek().unwrap().record_bytes); + engine.input(first_tlv_record.record_bytes); engine })); let leaf_tag = tagged_hash_engine(sha256::Hash::hash("LnLeaf".as_bytes())); let branch_tag = tagged_hash_engine(sha256::Hash::hash("LnBranch".as_bytes())); let mut leaves = Vec::new(); - for record in tlv_stream { - if !SIGNATURE_TYPES.contains(&record.r#type) { - leaves.push(tagged_hash_from_engine(leaf_tag.clone(), &record)); - leaves.push(tagged_hash_from_engine(nonce_tag.clone(), &record.type_bytes)); - } + let tlv_stream = TlvStream::new(&data[..]); + for record in tlv_stream.skip_signatures() { + leaves.push(tagged_hash_from_engine(leaf_tag.clone(), &record.record_bytes)); + leaves.push(tagged_hash_from_engine(nonce_tag.clone(), &record.type_bytes)); } // Calculate the merkle root hash in place. @@ -154,6 +153,10 @@ impl<'a> TlvStream<'a> { data: io::Cursor::new(data), } } + + fn skip_signatures(self) -> core::iter::Filter, fn(&TlvRecord) -> bool> { + self.filter(|record| !SIGNATURE_TYPES.contains(&record.r#type)) + } } /// A slice into a [`TlvStream`] for a record. @@ -164,10 +167,6 @@ struct TlvRecord<'a> { record_bytes: &'a [u8], } -impl AsRef<[u8]> for TlvRecord<'_> { - fn as_ref(&self) -> &[u8] { &self.record_bytes } -} - impl<'a> Iterator for TlvStream<'a> { type Item = TlvRecord<'a>; @@ -195,9 +194,33 @@ impl<'a> Iterator for TlvStream<'a> { } } +/// Encoding for a pre-serialized TLV stream that excludes any signature TLV records. +/// +/// Panics if the wrapped bytes are not a well-formed TLV stream. +pub(super) struct WithoutSignatures<'a>(pub &'a Vec); + +impl<'a> Writeable for WithoutSignatures<'a> { + #[inline] + fn write(&self, writer: &mut W) -> Result<(), io::Error> { + let tlv_stream = TlvStream::new(&self.0[..]); + for record in tlv_stream.skip_signatures() { + writer.write_all(record.record_bytes)?; + } + Ok(()) + } +} + #[cfg(test)] mod tests { + use super::{TlvStream, WithoutSignatures}; + use bitcoin::hashes::{Hash, sha256}; + use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey}; + use core::convert::Infallible; + use crate::offers::offer::{Amount, OfferBuilder}; + use crate::offers::invoice_request::InvoiceRequest; + use crate::offers::parse::Bech32Encode; + use crate::util::ser::Writeable; #[test] fn calculates_merkle_root_hash() { @@ -218,4 +241,80 @@ mod tests { sha256::Hash::from_slice(&hex::decode("ab2e79b1283b0b31e0b035258de23782df6b89a38cfa7237bde69aed1a658c5d").unwrap()).unwrap(), ); } + + #[test] + fn calculates_merkle_root_hash_from_invoice_request() { + let secp_ctx = Secp256k1::new(); + let recipient_pubkey = { + let secret_key = SecretKey::from_slice(&hex::decode("4141414141414141414141414141414141414141414141414141414141414141").unwrap()).unwrap(); + KeyPair::from_secret_key(&secp_ctx, &secret_key).public_key() + }; + let payer_keys = { + let secret_key = SecretKey::from_slice(&hex::decode("4242424242424242424242424242424242424242424242424242424242424242").unwrap()).unwrap(); + KeyPair::from_secret_key(&secp_ctx, &secret_key) + }; + + // BOLT 12 test vectors + let invoice_request = OfferBuilder::new("A Mathematical Treatise".into(), recipient_pubkey) + .amount(Amount::Currency { iso4217_code: *b"USD", amount: 100 }) + .build_unchecked() + .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() + .build_unchecked() + .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys))) + .unwrap(); + assert_eq!( + invoice_request.to_string(), + "lnr1qqyqqqqqqqqqqqqqqcp4256ypqqkgzshgysy6ct5dpjk6ct5d93kzmpq23ex2ct5d9ek293pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpjkppqvjx204vgdzgsqpvcp4mldl3plscny0rt707gvpdh6ndydfacz43euzqhrurageg3n7kafgsek6gz3e9w52parv8gs2hlxzk95tzeswywffxlkeyhml0hh46kndmwf4m6xma3tkq2lu04qz3slje2rfthc89vss", + ); + assert_eq!( + super::root_hash(&invoice_request.bytes[..]), + sha256::Hash::from_slice(&hex::decode("608407c18ad9a94d9ea2bcdbe170b6c20c462a7833a197621c916f78cf18e624").unwrap()).unwrap(), + ); + } + + #[test] + fn skips_encoding_signature_tlv_records() { + let secp_ctx = Secp256k1::new(); + let recipient_pubkey = { + let secret_key = SecretKey::from_slice(&[41; 32]).unwrap(); + KeyPair::from_secret_key(&secp_ctx, &secret_key).public_key() + }; + let payer_keys = { + let secret_key = SecretKey::from_slice(&[42; 32]).unwrap(); + KeyPair::from_secret_key(&secp_ctx, &secret_key) + }; + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey) + .amount_msats(100) + .build_unchecked() + .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() + .build_unchecked() + .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys))) + .unwrap(); + + let mut bytes_without_signature = Vec::new(); + WithoutSignatures(&invoice_request.bytes).write(&mut bytes_without_signature).unwrap(); + + assert_ne!(bytes_without_signature, invoice_request.bytes); + assert_eq!( + TlvStream::new(&bytes_without_signature).count(), + TlvStream::new(&invoice_request.bytes).count() - 1, + ); + } + + impl AsRef<[u8]> for InvoiceRequest { + fn as_ref(&self) -> &[u8] { + &self.bytes + } + } + + impl Bech32Encode for InvoiceRequest { + const BECH32_HRP: &'static str = "lnr"; + } + + impl core::fmt::Display for InvoiceRequest { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + self.fmt_bech32_str(f) + } + } }