Skip to content

Commit e2f81e3

Browse files
Move invoice signing behind KeysInterface
1 parent 1dc23a4 commit e2f81e3

File tree

6 files changed

+67
-11
lines changed

6 files changed

+67
-11
lines changed

fuzz/src/chanmon_consistency.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ use utils::test_logger;
5656
use utils::test_persister::TestPersister;
5757

5858
use bitcoin::secp256k1::key::{PublicKey,SecretKey};
59+
use bitcoin::secp256k1::recovery::RecoverableSignature;
5960
use bitcoin::secp256k1::Secp256k1;
6061

6162
use std::mem;
@@ -206,6 +207,10 @@ impl KeysInterface for KeyProvider {
206207
disable_revocation_policy_check: false,
207208
})
208209
}
210+
211+
fn sign_invoice(&self, _invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, DecodeError> {
212+
unreachable!()
213+
}
209214
}
210215

211216
impl KeyProvider {

fuzz/src/full_stack.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use utils::test_logger;
4848
use utils::test_persister::TestPersister;
4949

5050
use bitcoin::secp256k1::key::{PublicKey, SecretKey};
51+
use bitcoin::secp256k1::recovery::RecoverableSignature;
5152
use bitcoin::secp256k1::Secp256k1;
5253

5354
use std::cell::RefCell;
@@ -460,6 +461,10 @@ impl KeysInterface for KeyProvider {
460461
fn read_chan_signer(&self, data: &[u8]) -> Result<EnforcingSigner, DecodeError> {
461462
EnforcingSigner::read(&mut std::io::Cursor::new(data))
462463
}
464+
465+
fn sign_invoice(&self, _invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, DecodeError> {
466+
unreachable!()
467+
}
463468
}
464469

465470
#[inline]

lightning-invoice/src/lib.rs

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,9 +1290,10 @@ impl<S> Display for SignOrCreationError<S> {
12901290
}
12911291
}
12921292

1293-
/// Convenient utilities to create an invoice.
1293+
/// Convenient utilities to create an invoice.
12941294
pub mod utils {
1295-
use {CreationError, Currency, Invoice, InvoiceBuilder};
1295+
use {Currency, Invoice, InvoiceBuilder, SemanticError, SignedRawInvoice};
1296+
use bech32::{FromBase32, ToBase32, u5};
12961297
use bitcoin_hashes::Hash;
12971298
use lightning::chain;
12981299
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
@@ -1302,15 +1303,14 @@ pub mod utils {
13021303
use lightning::routing::network_graph::RoutingFees;
13031304
use lightning::routing::router::RouteHintHop;
13041305
use lightning::util::logger::Logger;
1305-
use secp256k1::Secp256k1;
13061306
use std::ops::Deref;
13071307

13081308
/// Utility to construct an invoice.
13091309
#[allow(dead_code)]
13101310
pub fn from_channelmanager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>(
13111311
channelmanager: &ChannelManager<Signer, M, T, K, F, L>, amt_msat: Option<u64>, description: String,
1312-
network: Currency,
1313-
) -> Result<Invoice, CreationError>
1312+
network: Currency, keys_manager: &dyn KeysInterface<Signer = Signer>
1313+
) -> Result<Invoice, SemanticError>
13141314
where
13151315
M::Target: chain::Watch<Signer>,
13161316
T::Target: BroadcasterInterface,
@@ -1348,7 +1348,6 @@ pub mod utils {
13481348
7200, // default invoice expiry is 2 hours
13491349
0,
13501350
);
1351-
let secp_ctx = Secp256k1::new();
13521351
let our_node_pubkey = channelmanager.get_our_node_id();
13531352
let mut invoice = InvoiceBuilder::new(network)
13541353
.description(description)
@@ -1365,9 +1364,32 @@ pub mod utils {
13651364
invoice = invoice.route(hint);
13661365
}
13671366

1368-
invoice.build_signed(|msg_hash| {
1369-
secp_ctx.sign_recoverable(msg_hash, &channelmanager.get_our_node_secret())
1370-
})
1367+
let raw_invoice = invoice.build_raw().unwrap();
1368+
let hrp_str = raw_invoice.hrp.to_string();
1369+
let hrp_bytes = hrp_str.as_bytes();
1370+
let data_without_signature = raw_invoice.data.to_base32();
1371+
let mut invoice_preimage = Vec::<u8>::from(hrp_bytes);
1372+
1373+
let mut data_part = Vec::from(data_without_signature);
1374+
let overhang = (data_part.len() * 5) % 8;
1375+
if overhang > 0 {
1376+
// add padding if data does not end at a byte boundary
1377+
data_part.push(u5::try_from_u8(0).unwrap());
1378+
1379+
// if overhang is in (1..3) we need to add u5(0) padding two times
1380+
if overhang < 3 {
1381+
data_part.push(u5::try_from_u8(0).unwrap());
1382+
}
1383+
}
1384+
1385+
invoice_preimage.extend_from_slice(&Vec::<u8>::from_base32(&data_part)
1386+
.expect("No padding error may occur due to appended zero above."));
1387+
let signature = match keys_manager.sign_invoice(invoice_preimage) {
1388+
Ok(sig) => sig,
1389+
Err(_) => return Err(SemanticError::InvalidSignature)
1390+
};
1391+
let signed_raw_invoice: Result<SignedRawInvoice, SemanticError> = raw_invoice.sign(|_| Ok(signature));
1392+
Invoice::from_signed(signed_raw_invoice.unwrap())
13711393
}
13721394

13731395
}
@@ -1697,7 +1719,7 @@ mod test {
16971719
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
16981720
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
16991721
let _chan = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
1700-
let invoice = ::utils::from_channelmanager(&nodes[1].node, Some(10_000), "test".to_string(), Currency::BitcoinTestnet).unwrap();
1722+
let invoice = ::utils::from_channelmanager(&nodes[1].node, Some(10_000), "test".to_string(), Currency::BitcoinTestnet, nodes[1].keys_manager).unwrap();
17011723
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
17021724
assert_eq!(invoice.min_final_cltv_expiry(), Some(&9));
17031725
assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));

lightning/src/chain/keysinterface.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ use bitcoin::hashes::sha256d::Hash as Sha256dHash;
2525
use bitcoin::hash_types::WPubkeyHash;
2626

2727
use bitcoin::secp256k1::key::{SecretKey, PublicKey};
28-
use bitcoin::secp256k1::{Secp256k1, Signature, Signing};
28+
use bitcoin::secp256k1::{Message, Secp256k1, Signature, Signing};
29+
use bitcoin::secp256k1::recovery::RecoverableSignature;
2930
use bitcoin::secp256k1;
3031

3132
use util::{byte_utils, transaction_utils};
@@ -391,6 +392,12 @@ pub trait KeysInterface: Send + Sync {
391392
/// contain no versioning scheme. You may wish to include your own version prefix and ensure
392393
/// you've read all of the provided bytes to ensure no corruption occurred.
393394
fn read_chan_signer(&self, reader: &[u8]) -> Result<Self::Signer, DecodeError>;
395+
396+
/// Sign an invoice's preimage (note that this is the preimage of the invoice, not the HTLC's
397+
/// preimage). By parameterizing by the preimage instead of the hash, we allow implementors of
398+
/// this trait to parse the invoice and make sure they're signing what they expect, rather than
399+
/// blindly signing the hash.
400+
fn sign_invoice(&self, invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, DecodeError>;
394401
}
395402

396403
#[derive(Clone)]
@@ -1047,6 +1054,15 @@ impl KeysInterface for KeysManager {
10471054
fn read_chan_signer(&self, reader: &[u8]) -> Result<Self::Signer, DecodeError> {
10481055
InMemorySigner::read(&mut std::io::Cursor::new(reader))
10491056
}
1057+
1058+
fn sign_invoice(&self, invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, DecodeError> {
1059+
let secp_ctx = Secp256k1::new();
1060+
let mut raw_hash: [u8; 32] = Default::default();
1061+
raw_hash.copy_from_slice(&Sha256::hash(&invoice_preimage)[..]);
1062+
let msg_hash = Message::from_slice(&raw_hash[..])
1063+
.expect("Hash is 32 bytes long, same as MESSAGE_SIZE");
1064+
Ok(secp_ctx.sign_recoverable(&msg_hash, &self.get_node_secret()))
1065+
}
10501066
}
10511067

10521068
// Ensure that BaseSign can have a vtable

lightning/src/ln/channel.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4843,6 +4843,7 @@ mod tests {
48434843
use bitcoin::secp256k1::{Secp256k1, Message, Signature, All};
48444844
use bitcoin::secp256k1::ffi::Signature as FFISignature;
48454845
use bitcoin::secp256k1::key::{SecretKey,PublicKey};
4846+
use bitcoin::secp256k1::recovery::RecoverableSignature;
48464847
use bitcoin::hashes::sha256::Hash as Sha256;
48474848
use bitcoin::hashes::Hash;
48484849
use bitcoin::hash_types::{Txid, WPubkeyHash};
@@ -4888,6 +4889,7 @@ mod tests {
48884889
}
48894890
fn get_secure_random_bytes(&self) -> [u8; 32] { [0; 32] }
48904891
fn read_chan_signer(&self, _data: &[u8]) -> Result<Self::Signer, DecodeError> { panic!(); }
4892+
fn sign_invoice(&self, _invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, DecodeError> { panic!(); }
48914893
}
48924894

48934895
fn public_from_secret_hex(secp_ctx: &Secp256k1<All>, hex: &str) -> PublicKey {

lightning/src/util/test_utils.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use bitcoin::network::constants::Network;
3232
use bitcoin::hash_types::{BlockHash, Txid};
3333

3434
use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1, Signature};
35+
use bitcoin::secp256k1::recovery::RecoverableSignature;
3536

3637
use regex;
3738

@@ -75,6 +76,7 @@ impl keysinterface::KeysInterface for OnlyReadsKeysInterface {
7576
fn read_chan_signer(&self, reader: &[u8]) -> Result<Self::Signer, msgs::DecodeError> {
7677
EnforcingSigner::read(&mut std::io::Cursor::new(reader))
7778
}
79+
fn sign_invoice(&self, _invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, msgs::DecodeError> { unreachable!(); }
7880
}
7981

8082
pub struct TestChainMonitor<'a> {
@@ -483,6 +485,10 @@ impl keysinterface::KeysInterface for TestKeysInterface {
483485
disable_revocation_policy_check: self.disable_revocation_policy_check,
484486
})
485487
}
488+
489+
fn sign_invoice(&self, invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, msgs::DecodeError> {
490+
self.backing.sign_invoice(invoice_preimage)
491+
}
486492
}
487493

488494

0 commit comments

Comments
 (0)