Skip to content

Commit 4064cf1

Browse files
committed
Add offer signature verification
1 parent c7de9ba commit 4064cf1

File tree

2 files changed

+162
-11
lines changed

2 files changed

+162
-11
lines changed

lightning/src/offers/merkle.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
//! Tagged hashes for use in signature calculation and verification.
11+
12+
use bitcoin::hashes::{Hash, HashEngine, sha256};
13+
use util::ser::{BigSize, Readable};
14+
15+
const SIGNATURE_TYPES: core::ops::RangeInclusive<u64> = 240..=1000;
16+
17+
pub(super) fn tagged_root_hash(tag: sha256::Hash, data: &[u8]) -> sha256::Hash {
18+
let mut engine = sha256::Hash::engine();
19+
engine.input("LnAll".as_bytes());
20+
for record in TlvStream::new(&data[..]) {
21+
if !SIGNATURE_TYPES.contains(&record.r#type.0) {
22+
engine.input(record.as_ref());
23+
}
24+
}
25+
let nonce_tag = sha256::Hash::from_engine(engine);
26+
let leaf_tag = sha256::Hash::hash("LnLeaf".as_bytes());
27+
let branch_tag = sha256::Hash::hash("LnBranch".as_bytes());
28+
29+
let mut merkle_leaves = Vec::new();
30+
for record in TlvStream::new(&data[..]) {
31+
if !SIGNATURE_TYPES.contains(&record.r#type.0) {
32+
merkle_leaves.push(tagged_hash(leaf_tag, &record));
33+
merkle_leaves.push(tagged_hash(nonce_tag, &record));
34+
}
35+
}
36+
37+
while merkle_leaves.len() != 1 {
38+
let mut parents = Vec::with_capacity(merkle_leaves.len() / 2 + merkle_leaves.len() % 2);
39+
let mut iter = merkle_leaves.chunks_exact(2);
40+
for leaves in iter.by_ref() {
41+
parents.push(tagged_branch_hash(branch_tag, leaves[0], leaves[1]));
42+
}
43+
for leaf in iter.remainder() {
44+
parents.push(*leaf);
45+
}
46+
core::mem::swap(&mut parents, &mut merkle_leaves);
47+
}
48+
49+
tagged_hash(tag, merkle_leaves.first().unwrap())
50+
}
51+
52+
fn tagged_branch_hash(tag: sha256::Hash, leaf1: sha256::Hash, leaf2: sha256::Hash) -> sha256::Hash {
53+
let mut engine = sha256::Hash::engine();
54+
engine.input(tag.as_ref());
55+
engine.input(tag.as_ref());
56+
if leaf1 < leaf2 {
57+
engine.input(leaf1.as_ref());
58+
engine.input(leaf2.as_ref());
59+
} else {
60+
engine.input(leaf2.as_ref());
61+
engine.input(leaf1.as_ref());
62+
};
63+
sha256::Hash::from_engine(engine)
64+
}
65+
66+
fn tagged_hash<T: AsRef<[u8]>>(tag: sha256::Hash, msg: T) -> sha256::Hash {
67+
let mut engine = sha256::Hash::engine();
68+
engine.input(tag.as_ref());
69+
engine.input(tag.as_ref());
70+
engine.input(msg.as_ref());
71+
sha256::Hash::from_engine(engine)
72+
}
73+
74+
struct TlvStream<'a> {
75+
data: ::io::Cursor<&'a [u8]>,
76+
}
77+
78+
impl<'a> TlvStream<'a> {
79+
fn new(data: &'a [u8]) -> Self {
80+
Self {
81+
data: ::io::Cursor::new(data),
82+
}
83+
}
84+
}
85+
86+
struct TlvRecord<'a> {
87+
r#type: BigSize,
88+
length: BigSize,
89+
value: &'a [u8],
90+
data: &'a [u8],
91+
}
92+
93+
impl AsRef<[u8]> for TlvRecord<'_> {
94+
fn as_ref(&self) -> &[u8] { &self.data }
95+
}
96+
97+
impl<'a> Iterator for TlvStream<'a> {
98+
type Item = TlvRecord<'a>;
99+
100+
fn next(&mut self) -> Option<Self::Item> {
101+
if self.data.position() < self.data.get_ref().len() as u64 {
102+
let start = self.data.position();
103+
104+
let r#type: BigSize = Readable::read(&mut self.data).unwrap();
105+
let length: BigSize = Readable::read(&mut self.data).unwrap();
106+
107+
let offset = self.data.position();
108+
let end = offset + length.0;
109+
110+
let value = &self.data.get_ref()[offset as usize..end as usize];
111+
let data = &self.data.get_ref()[start as usize..end as usize];
112+
113+
self.data.set_position(end);
114+
115+
Some(TlvRecord { r#type, length, value, data })
116+
} else {
117+
None
118+
}
119+
}
120+
}

lightning/src/offers/mod.rs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ use bitcoin::bech32;
1414
use bitcoin::bech32::FromBase32;
1515
use bitcoin::blockdata::constants::genesis_block;
1616
use bitcoin::hash_types::BlockHash;
17+
use bitcoin::hashes::{Hash, sha256};
1718
use bitcoin::network::constants::Network;
18-
use bitcoin::secp256k1::{PublicKey, XOnlyPublicKey};
19+
use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, XOnlyPublicKey, self};
1920
use bitcoin::secp256k1::schnorr::Signature;
2021
use core::convert::TryFrom;
2122
use core::str::FromStr;
@@ -30,6 +31,8 @@ use prelude::*;
3031
#[cfg(feature = "std")]
3132
use std::time::SystemTime;
3233

34+
mod merkle;
35+
3336
///
3437
#[derive(Clone, Debug)]
3538
pub struct Offer {
@@ -184,6 +187,15 @@ pub enum Destination {
184187
Paths(Vec<BlindedPath>),
185188
}
186189

190+
impl Destination {
191+
fn node_id(&self) -> XOnlyPublicKey {
192+
match self {
193+
Destination::NodeId(node_id) => *node_id,
194+
Destination::Paths(paths) => unimplemented!(),
195+
}
196+
}
197+
}
198+
187199
///
188200
#[derive(Clone, Debug)]
189201
pub struct SendInvoice {
@@ -255,6 +267,10 @@ struct Recurrence {
255267

256268
impl_writeable!(Recurrence, { time_unit, period });
257269

270+
/// An offer parsed from a bech32-encoded string as a TLV stream and the corresponding bytes. The
271+
/// latter is used for signature verification.
272+
struct ParsedOffer(OfferTlvStream, Vec<u8>);
273+
258274
/// Error when parsing a bech32 encoded message using [`str::parse`].
259275
#[derive(Debug, PartialEq)]
260276
pub enum ParseError {
@@ -293,6 +309,8 @@ pub enum SemanticError {
293309
InvalidQuantity,
294310
///
295311
UnexpectedRefund,
312+
///
313+
InvalidSignature(secp256k1::Error),
296314
}
297315

298316
impl From<bech32::Error> for ParseError {
@@ -313,23 +331,28 @@ impl From<SemanticError> for ParseError {
313331
}
314332
}
315333

334+
impl From<secp256k1::Error> for SemanticError {
335+
fn from(error: secp256k1::Error) -> Self {
336+
Self::InvalidSignature(error)
337+
}
338+
}
339+
316340
impl FromStr for Offer {
317341
type Err = ParseError;
318342

319343
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
320-
let tlv_stream = OfferTlvStream::from_str(s)?;
321-
Ok(Offer::try_from(tlv_stream)?)
344+
Ok(Offer::try_from(ParsedOffer::from_str(s)?)?)
322345
}
323346
}
324347

325-
impl TryFrom<OfferTlvStream> for Offer {
348+
impl TryFrom<ParsedOffer> for Offer {
326349
type Error = SemanticError;
327350

328-
fn try_from(tlv_stream: OfferTlvStream) -> Result<Self, Self::Error> {
329-
let OfferTlvStream {
351+
fn try_from(offer: ParsedOffer) -> Result<Self, Self::Error> {
352+
let ParsedOffer(OfferTlvStream {
330353
chains, currency, amount, description, features, absolute_expiry, paths, issuer,
331354
quantity_min, quantity_max, recurrence, node_id, send_invoice, refund_for, signature,
332-
} = tlv_stream;
355+
}, data) = offer;
333356

334357
let supported_chains = [
335358
genesis_block(Network::Bitcoin).block_hash(),
@@ -394,6 +417,14 @@ impl TryFrom<OfferTlvStream> for Offer {
394417
(Some(_), _) => Some(SendInvoice { refund_for }),
395418
};
396419

420+
421+
if let Some(signature) = &signature {
422+
let tag = sha256::Hash::hash(concat!("lightning", "offer", "signature").as_bytes());
423+
let message = Message::from_slice(&merkle::tagged_root_hash(tag, &data)).unwrap();
424+
let secp_ctx = Secp256k1::verification_only();
425+
secp_ctx.verify_schnorr(signature, &message, &destination.node_id())?;
426+
}
427+
397428
Ok(Offer {
398429
chains,
399430
amount,
@@ -414,7 +445,7 @@ impl TryFrom<OfferTlvStream> for Offer {
414445

415446
const OFFER_BECH32_HRP: &str = "lno";
416447

417-
impl FromStr for OfferTlvStream {
448+
impl FromStr for ParsedOffer {
418449
type Err = ParseError;
419450

420451
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
@@ -434,7 +465,7 @@ impl FromStr for OfferTlvStream {
434465
}
435466

436467
let data = Vec::<u8>::from_base32(&data)?;
437-
Ok(Readable::read(&mut &data[..])?)
468+
Ok(ParsedOffer(Readable::read(&mut &data[..])?, data))
438469
}
439470
}
440471

@@ -460,7 +491,7 @@ impl core::fmt::Display for OfferTlvStream {
460491

461492
#[cfg(test)]
462493
mod tests {
463-
use super::{Offer, OfferTlvStream, ParseError};
494+
use super::{Offer, ParseError, ParsedOffer};
464495
use bitcoin::bech32;
465496
use ln::msgs::DecodeError;
466497

@@ -487,7 +518,7 @@ mod tests {
487518
];
488519
for encoded_offer in &offers {
489520
// TODO: Use Offer once Destination semantics are finalized.
490-
if let Err(e) = encoded_offer.parse::<OfferTlvStream>() {
521+
if let Err(e) = encoded_offer.parse::<ParsedOffer>() {
491522
panic!("Invalid offer ({:?}): {}", e, encoded_offer);
492523
}
493524
}

0 commit comments

Comments
 (0)