Skip to content

Commit e6756c8

Browse files
committed
enhance Invoice Deserialization to Return Detailed Information
This commit improves the deserialize_invoice functionality across all implementations: - Change return type from bool to string to provide rich invoice content - Implement consistent format for invoice data across all implementations: HASH=<payment_hash>; AMOUNT=<milli_sats>;DESCRIPTION=<desc>;RECIPIENT=<pubkey>; EXPIRY=<seconds>;TIMESTAMP=<unix_time>; ROUTING_HINTS=<count>;MIN_CLTV=<value> - Ensure consistent memory management for returned strings in all modules These changes enable consistent testing across different Lightning implementations, allowing for detailed comparison of parsed invoice fields rather than just success/failure validation.
1 parent 3dfe663 commit e6756c8

File tree

15 files changed

+259
-50
lines changed

15 files changed

+259
-50
lines changed

Diff for: driver.cpp

+5-3
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,14 @@ namespace bitcoinfuzz
107107
{
108108
FuzzedDataProvider provider(buffer.data(), buffer.size());
109109
std::string invoice{provider.ConsumeRemainingBytesAsString()};
110-
std::optional<bool> last_response{std::nullopt};
110+
std::optional<std::string> last_response{std::nullopt};
111111
for (auto &module : modules)
112112
{
113-
std::optional<bool> res{module.second->deserialize_invoice(invoice)};
113+
std::optional<std::string> res{module.second->deserialize_invoice(invoice)};
114114
if (!res.has_value()) continue;
115-
if (last_response.has_value()) assert(*res == *last_response);
115+
if (last_response.has_value()) {
116+
assert(*res == *last_response);
117+
}
116118

117119
last_response = res.value();
118120
}

Diff for: include/bitcoinfuzz/basemodule.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ namespace bitcoinfuzz
3838
return std::nullopt;
3939
}
4040

41-
std::optional<bool> BaseModule::deserialize_invoice(std::string str) const
41+
std::optional<std::string> BaseModule::deserialize_invoice(std::string str) const
4242
{
4343
return std::nullopt;
4444
}

Diff for: include/bitcoinfuzz/basemodule.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ namespace bitcoinfuzz
2323
virtual std::optional<bool> descriptor_parse(std::string str) const;
2424
virtual std::optional<bool> miniscript_parse(std::string str) const;
2525
virtual std::optional<std::string> script_asm(std::span<const uint8_t> buffer) const;
26-
virtual std::optional<bool> deserialize_invoice(std::string str) const;
26+
virtual std::optional<std::string> deserialize_invoice(std::string str) const;
2727
virtual std::optional<std::string> address_parse(std::string str) const;
2828

2929
virtual ~BaseModule() noexcept;

Diff for: modules/ldk/ldk_lib/ldk_lib.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
#include <cstdint>
22

3-
extern "C" bool ldk_des_invoice(const char* input);
3+
extern "C" char* ldk_des_invoice(const char* input);
4+
5+
extern "C" void ldk_free_string(const char* ptr);

Diff for: modules/ldk/ldk_lib/src/lib.rs

+87-15
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,104 @@
1-
use lightning_invoice::Currency;
1+
use lightning_invoice::{
2+
Bolt11InvoiceDescriptionRef, Bolt11SemanticError, Currency, ParseOrSemanticError,
3+
};
4+
use std::ffi::CString;
5+
use std::os::raw::c_char;
26
use std::{ffi::CStr, str::FromStr};
37

8+
unsafe fn str_to_c_string(input: &str) -> *mut c_char {
9+
CString::new(input).unwrap().into_raw()
10+
}
11+
412
#[no_mangle]
5-
pub unsafe extern "C" fn ldk_des_invoice(input: *const std::os::raw::c_char) -> bool {
13+
pub unsafe extern "C" fn ldk_des_invoice(input: *const std::os::raw::c_char) -> *mut c_char {
614
if input.is_null() {
7-
return false;
15+
return str_to_c_string("");
816
}
917

1018
// Convert C string to Rust string
1119
let c_str = match CStr::from_ptr(input).to_str() {
1220
Ok(s) => s,
13-
Err(_) => return false,
21+
Err(_) => return str_to_c_string(""),
1422
};
1523

16-
match lightning_invoice::SignedRawBolt11Invoice::from_str(c_str) {
24+
match lightning_invoice::Bolt11Invoice::from_str(c_str) {
1725
Ok(invoice) => {
18-
// If the destination pubkey was provided as a tagged field, use that
19-
// to verify the signature, otherwise recover it from the signature
20-
let is_signature_valid = if let Some(_) = invoice.payee_pub_key() {
21-
invoice.check_signature()
22-
} else {
23-
invoice.recover_payee_pub_key().is_ok()
26+
if invoice.currency() != Currency::Bitcoin {
27+
return str_to_c_string("");
28+
}
29+
let mut result = String::new();
30+
31+
result.push_str("HASH=");
32+
result.push_str(&invoice.payment_hash().to_string());
33+
34+
result.push_str(";AMOUNT=");
35+
if let Some(amount) = invoice.amount_milli_satoshis() {
36+
result.push_str(&amount.to_string());
37+
}
38+
39+
result.push_str(";DESCRIPTION=");
40+
if let Bolt11InvoiceDescriptionRef::Direct(direct_description) = invoice.description() {
41+
result.push_str(&direct_description.to_string());
42+
}
43+
44+
let invoice_payee_pub_key = match invoice.payee_pub_key() {
45+
Some(payee) => payee.clone(),
46+
None => invoice.recover_payee_pub_key(),
2447
};
25-
let is_currency_bitcoin = invoice.currency() == Currency::Bitcoin;
26-
let has_payment_hash = invoice.payment_hash().is_some();
2748

28-
has_payment_hash && is_currency_bitcoin && is_signature_valid
49+
result.push_str(";RECIPIENT=");
50+
result.push_str(&invoice_payee_pub_key.to_string());
51+
52+
result.push_str(";EXPIRY=");
53+
result.push_str(&invoice.expiry_time().as_secs().to_string());
54+
55+
result.push_str(";TIMESTAMP=");
56+
result.push_str(
57+
&invoice
58+
.clone()
59+
.into_signed_raw()
60+
.raw_invoice()
61+
.data
62+
.timestamp
63+
.as_unix_timestamp()
64+
.to_string(),
65+
);
66+
67+
result.push_str(";ROUTING_HINTS=");
68+
result.push_str(&invoice.private_routes().len().to_string());
69+
70+
result.push_str(";MIN_CLTV=");
71+
result.push_str(&invoice.min_final_cltv_expiry_delta().to_string());
72+
73+
str_to_c_string(&result)
74+
}
75+
// Handle invoices without payment secrets by returning null
76+
// This is needed because some Lightning implementations don't require payment secrets,
77+
// and we need to maintain compatibility with these implementations
78+
Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::NoPaymentSecret)) => {
79+
std::ptr::null_mut()
80+
}
81+
// Handle invoices with multiple payment hashes by returning null
82+
// This is needed because some Lightning implementations don't require payment to have only one hash,
83+
// and we need to maintain compatibility with these implementations
84+
Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::MultiplePaymentHashes)) => {
85+
std::ptr::null_mut()
86+
}
87+
// Handle invoices with multiple descriptions hashes by returning null
88+
// This is needed because some Lightning implementations don't require to have only one description,
89+
// and we need to maintain compatibility with these implementations
90+
Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::MultipleDescriptions)) => {
91+
std::ptr::null_mut()
2992
}
30-
Err(_) => false,
93+
Err(_) => str_to_c_string(""),
3194
}
3295
}
96+
97+
#[no_mangle]
98+
pub extern "C" fn ldk_free_string(ptr: *mut c_char) {
99+
if !ptr.is_null() {
100+
unsafe {
101+
let _ = CString::from_raw(ptr);
102+
}
103+
}
104+
}

Diff for: modules/ldk/module.cpp

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
#include <span>
22

33
#include "module.h"
4-
5-
extern "C" bool ldk_des_invoice(const char* input);
4+
#include "ldk_lib/ldk_lib.h"
65

76
namespace bitcoinfuzz
87
{
98
namespace module
109
{
1110
Ldk::Ldk(void) : BaseModule("Ldk") {}
1211

13-
std::optional<bool> Ldk::deserialize_invoice(std::string str) const
12+
std::optional<std::string> Ldk::deserialize_invoice(std::string str) const
1413
{
15-
bool result = ldk_des_invoice(str.c_str());
16-
return result;
14+
auto result = ldk_des_invoice(str.c_str());
15+
if (result == nullptr) {
16+
return std::nullopt;
17+
}
18+
std::string result_str(result);
19+
ldk_free_string(result);
20+
return result_str;
1721
}
1822

1923
}

Diff for: modules/ldk/module.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace bitcoinfuzz
1313
{
1414
public:
1515
Ldk(void);
16-
std::optional<bool> deserialize_invoice(std::string str) const override;
16+
std::optional<std::string> deserialize_invoice(std::string str) const override;
1717
~Ldk() noexcept override = default;
1818
};
1919

Diff for: modules/lnd/lnd_wrapper/liblnd_wrapper.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
8080
extern "C" {
8181
#endif
8282

83-
extern int LndDeserializeInvoice(char* cInvoiceStr);
83+
extern char* LndDeserializeInvoice(char* cInvoiceStr);
8484

8585
#ifdef __cplusplus
8686
}

Diff for: modules/lnd/lnd_wrapper/libscript.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ extern "C"
9696
{
9797
#endif
9898

99-
extern int LndDeserializeInvoice(const char *input);
99+
extern char* LndDeserializeInvoice(const char *input);
100100

101101
#ifdef __cplusplus
102102
}

Diff for: modules/lnd/lnd_wrapper/wrapper.go

+43-6
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@ package main
77
import "C"
88

99
import (
10+
"fmt"
1011
"runtime"
12+
"strings"
1113

1214
"github.com/btcsuite/btcd/chaincfg"
1315
"github.com/lightningnetwork/lnd/zpay32"
1416
)
1517

1618
//export LndDeserializeInvoice
17-
func LndDeserializeInvoice(cInvoiceStr *C.char) C.int {
19+
func LndDeserializeInvoice(cInvoiceStr *C.char) *C.char {
1820
if cInvoiceStr == nil {
19-
return 0
21+
return C.CString("")
2022
}
2123

2224
runtime.GC()
@@ -26,14 +28,49 @@ func LndDeserializeInvoice(cInvoiceStr *C.char) C.int {
2628

2729
network := &chaincfg.MainNetParams
2830

29-
_, err := zpay32.Decode(invoiceStr, network)
31+
invoice, err := zpay32.Decode(invoiceStr, network)
3032
if err != nil {
31-
return 0
33+
return C.CString("")
3234
}
3335

34-
runtime.GC()
36+
var sb strings.Builder
37+
38+
sb.WriteString("HASH=")
39+
if invoice.PaymentHash != nil {
40+
sb.WriteString(fmt.Sprintf("%x", *invoice.PaymentHash))
41+
}
42+
43+
sb.WriteString(";AMOUNT=")
44+
if invoice.MilliSat != nil {
45+
sb.WriteString(fmt.Sprintf("%d", *invoice.MilliSat))
46+
}
47+
48+
sb.WriteString(";DESCRIPTION=")
49+
if invoice.Description != nil {
50+
sb.WriteString(*invoice.Description)
51+
}
52+
53+
sb.WriteString(";RECIPIENT=")
54+
if invoice.Destination != nil {
55+
sb.WriteString(fmt.Sprintf("%x", invoice.Destination.SerializeCompressed()))
56+
}
57+
58+
sb.WriteString(";EXPIRY=")
59+
if invoice.Expiry() > 0 {
60+
sb.WriteString(fmt.Sprintf("%d", int64(invoice.Expiry().Seconds())))
61+
}
62+
63+
sb.WriteString(";TIMESTAMP=")
64+
sb.WriteString(fmt.Sprintf("%d", invoice.Timestamp.Unix()))
65+
66+
sb.WriteString(fmt.Sprintf(";ROUTING_HINTS=%d", len(invoice.RouteHints)))
67+
68+
sb.WriteString(";MIN_CLTV=")
69+
if invoice.MinFinalCLTVExpiry() > 0 {
70+
sb.WriteString(fmt.Sprintf("%d", invoice.MinFinalCLTVExpiry()))
71+
}
3572

36-
return 1
73+
return C.CString(sb.String())
3774
}
3875

3976
func main() {}

Diff for: modules/lnd/module.cpp

+5-4
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ namespace bitcoinfuzz
99
{
1010
Lnd::Lnd(void) : BaseModule("Lnd") {}
1111

12-
std::optional<bool> Lnd::deserialize_invoice(std::string str) const
12+
std::optional<std::string> Lnd::deserialize_invoice(std::string str) const
1313
{
14-
bool result = LndDeserializeInvoice(str.c_str());
15-
return result;
14+
auto result = LndDeserializeInvoice(str.c_str());
15+
std::string result_str(result);
16+
free(result);
17+
return result_str;
1618
}
17-
1819
}
1920
}

Diff for: modules/lnd/module.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace bitcoinfuzz
1313
{
1414
public:
1515
Lnd(void);
16-
std::optional<bool> deserialize_invoice(std::string str) const override;
16+
std::optional<std::string> deserialize_invoice(std::string str) const override;
1717
~Lnd() noexcept override = default;
1818
};
1919

Diff for: modules/nlightning/module.cpp

+36-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ namespace bitcoinfuzz
3232
{
3333
namespace module
3434
{
35+
FreeStringFunc NLightning::freeString = nullptr;
3536
DecodeInvoiceFunc NLightning::decodeInvoice = nullptr;
37+
CleanupResources NLightning::cleanupResources = nullptr;
3638

3739
NLightning::NLightning(void) : BaseModule("NLightning")
3840
{
@@ -69,11 +71,43 @@ namespace bitcoinfuzz
6971
CLOSE_LIBRARY(libHandle);
7072
return;
7173
}
74+
75+
if (freeString == nullptr)
76+
freeString = (FreeStringFunc)GET_PROC_ADDRESS(libHandle, "FreeString");
77+
78+
if (!freeString)
79+
{
80+
std::cerr << "Failed to find FreeString symbol" << std::endl;
81+
CLOSE_LIBRARY(libHandle);
82+
return;
83+
}
84+
85+
if (cleanupResources == nullptr)
86+
cleanupResources = (CleanupResources)GET_PROC_ADDRESS(libHandle, "CleanupResources");
87+
88+
if (!cleanupResources)
89+
{
90+
std::cerr << "Failed to find CleanupResources symbol" << std::endl;
91+
CLOSE_LIBRARY(libHandle);
92+
return;
93+
}
7294
}
7395

74-
std::optional<bool> NLightning::deserialize_invoice(std::string str) const
96+
std::optional<std::string> NLightning::deserialize_invoice(std::string str) const
7597
{
76-
return decodeInvoice(str.c_str());
98+
char* result = decodeInvoice(str.c_str());
99+
100+
if (result == nullptr) {
101+
cleanupResources();
102+
return std::nullopt;
103+
}
104+
105+
std::string resultStr(result);
106+
freeString(result);
107+
cleanupResources();
108+
109+
110+
return resultStr;
77111
}
78112
}
79113
}

0 commit comments

Comments
 (0)