X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Fmerkle.rs;h=57e7fe6833c2fd7eac8f4ec6125a0e0b2bc58039;hb=45eb0f3186c277d935b870870e1115a10bcbb599;hp=c7aafb0cfef2516fc8a4cd5965c155652d2594aa;hpb=a7adc7602a7865b677095ef2cdb80d290dff1170;p=rust-lightning diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index c7aafb0c..57e7fe68 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -10,6 +10,8 @@ //! Tagged hashes for use in signature calculation and verification. 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}; @@ -18,6 +20,58 @@ use crate::prelude::*; /// Valid type range for signature TLV records. const SIGNATURE_TYPES: core::ops::RangeInclusive = 240..=1000; +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. +/// +/// Panics if `bytes` is not a well-formed TLV stream containing at least one TLV record. +pub(super) fn verify_signature( + signature: &Signature, tag: &str, bytes: &[u8], pubkey: PublicKey, +) -> Result<(), secp256k1::Error> { + 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 { @@ -144,6 +198,11 @@ impl<'a> Iterator for TlvStream<'a> { #[cfg(test)] mod tests { 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; #[test] fn calculates_merkle_root_hash() { @@ -164,4 +223,50 @@ 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(), + ); + } + + 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) + } + } }