X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Fmerkle.rs;h=9782dc7d1e84131f1d6e659dce2d28ac7e9de6f0;hb=825ea9d062886a369bda9d30ad6890fada7f2ed8;hp=95183bea20d2a50c29dc62f2890c0e0381cb402d;hpb=59a7bd29fe02f10f6fcdc192c7994629675c7a30;p=rust-lightning diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index 95183bea..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::*; @@ -24,6 +24,35 @@ tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, SIGNATURE_TYPES, { (240, signature: Signature), }); +/// Error when signing messages. +#[derive(Debug, PartialEq)] +pub enum SignError { + /// User-defined error when signing the message. + Signing(E), + /// Error when verifying the produced signature using the given pubkey. + Verification(secp256k1::Error), +} + +/// Signs a message digest consisting of a tagged hash of the given bytes, checking if it can be +/// verified with the supplied pubkey. +/// +/// Panics if `bytes` is not a well-formed TLV stream containing at least one TLV record. +pub(super) fn sign_message( + sign: F, tag: &str, bytes: &[u8], pubkey: PublicKey, +) -> Result> +where + F: FnOnce(&Message) -> Result +{ + let digest = message_digest(tag, bytes); + let signature = sign(&digest).map_err(|e| SignError::Signing(e))?; + + let pubkey = pubkey.into(); + let secp_ctx = Secp256k1::verification_only(); + secp_ctx.verify_schnorr(&signature, &digest, &pubkey).map_err(|e| SignError::Verification(e))?; + + Ok(signature) +} + /// Verifies the signature with a pubkey over the given bytes using a tagged hash as the message /// digest. /// @@ -31,33 +60,36 @@ tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, SIGNATURE_TYPES, { pub(super) fn verify_signature( signature: &Signature, tag: &str, bytes: &[u8], pubkey: PublicKey, ) -> Result<(), secp256k1::Error> { - let tag = sha256::Hash::hash(tag.as_bytes()); - let merkle_root = root_hash(bytes); - let digest = Message::from_slice(&tagged_hash(tag, merkle_root)).unwrap(); + let digest = message_digest(tag, bytes); let pubkey = pubkey.into(); let secp_ctx = Secp256k1::verification_only(); secp_ctx.verify_schnorr(signature, &digest, &pubkey) } +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 { - 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. @@ -121,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. @@ -131,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>; @@ -162,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() { @@ -185,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) + } + } }