Skip to content

Commit d97fd1a

Browse files
committed
add optional InternalPk to Beneficiary::WitnessVout
1 parent 688a6ab commit d97fd1a

File tree

2 files changed

+101
-20
lines changed

2 files changed

+101
-20
lines changed

invoice/src/invoice.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use std::ops::Deref;
2323
use std::str::FromStr;
2424

2525
use amplify::{ByteArray, Bytes32};
26-
use bp::{InvalidPubkey, OutputPk, PubkeyHash, ScriptHash, WPubkeyHash, WScriptHash};
26+
use bp::{InternalPk, InvalidPubkey, OutputPk, PubkeyHash, ScriptHash, WPubkeyHash, WScriptHash};
2727
use indexmap::IndexMap;
2828
use invoice::{AddressNetwork, AddressPayload, Network};
2929
use rgb::{AttachId, ChainNet, ContractId, Layer1, SchemaId, SecretSeal, StateType};
@@ -216,8 +216,7 @@ impl TryFrom<[u8; 33]> for Pay2Vout {
216216
pub enum Beneficiary {
217217
#[from]
218218
BlindedSeal(SecretSeal),
219-
#[from]
220-
WitnessVout(Pay2Vout),
219+
WitnessVout(Pay2Vout, Option<InternalPk>),
221220
}
222221

223222
#[derive(Clone, Eq, PartialEq, Debug)]

invoice/src/parse.rs

Lines changed: 99 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use std::num::ParseIntError;
2525
use std::str::FromStr;
2626

2727
use baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str};
28+
use bp::InternalPk;
2829
use fluent_uri::enc::EStr;
2930
use fluent_uri::Uri;
3031
use indexmap::IndexMap;
@@ -107,10 +108,6 @@ pub enum InvoiceParseError {
107108
/// invalid query parameter {0}.
108109
InvalidQueryParam(String),
109110

110-
#[from]
111-
#[display(inner)]
112-
Id(baid64::Baid64ParseError),
113-
114111
/// can't recognize beneficiary "{0}": it should be either a bitcoin address
115112
/// or a blinded UTXO seal.
116113
Beneficiary(String),
@@ -203,7 +200,14 @@ impl Display for XChainNet<Beneficiary> {
203200
write!(f, "{}:", self.chain_network().prefix())?;
204201
match self.into_inner() {
205202
Beneficiary::BlindedSeal(seal) => Display::fmt(&seal, f),
206-
Beneficiary::WitnessVout(payload) => payload.fmt_baid64(f),
203+
Beneficiary::WitnessVout(pay2vout, internal_pk) => {
204+
write!(
205+
f,
206+
"{}{}",
207+
pay2vout.to_baid64_string(),
208+
if let Some(ipk) = internal_pk { format!("+{ipk}") } else { s!("") }
209+
)
210+
}
207211
}
208212
}
209213
}
@@ -251,16 +255,36 @@ impl FromStr for XChainNet<Beneficiary> {
251255

252256
fn from_str(s: &str) -> Result<Self, Self::Err> {
253257
let Some((cn, beneficiary)) = s.split_once(':') else {
254-
return Err(InvoiceParseError::Beneficiary(s.to_owned()));
258+
return Err(InvoiceParseError::Beneficiary(s!("missing beneficiary HRI")));
255259
};
256260
let cn =
257-
ChainNet::from_str(cn).map_err(|_| InvoiceParseError::Beneficiary(s.to_owned()))?;
261+
ChainNet::from_str(cn).map_err(|e| InvoiceParseError::Beneficiary(e.to_string()))?;
258262
if let Ok(seal) = SecretSeal::from_str(beneficiary) {
259263
return Ok(XChainNet::with(cn, Beneficiary::BlindedSeal(seal)));
260264
}
261265

262-
let payload = Pay2Vout::from_str(beneficiary)?;
263-
Ok(XChainNet::with(cn, Beneficiary::WitnessVout(payload)))
266+
let (pay2vout, internal_pk) = beneficiary
267+
.split_once("+")
268+
.map(|(p, i)| (p, Some(i)))
269+
.unwrap_or((beneficiary, None));
270+
271+
let pay2vout = Pay2Vout::from_str(pay2vout)
272+
.map_err(|e| InvoiceParseError::Beneficiary(e.to_string()))?;
273+
274+
let internal_pk = match internal_pk {
275+
None => None,
276+
Some(i) => {
277+
if i.is_empty() {
278+
return Err(InvoiceParseError::Beneficiary(s!("missing internal pk")));
279+
}
280+
Some(
281+
InternalPk::from_str(i)
282+
.map_err(|_| InvoiceParseError::Beneficiary(s!("invalid internal pk")))?,
283+
)
284+
}
285+
};
286+
287+
Ok(XChainNet::with(cn, Beneficiary::WitnessVout(pay2vout, internal_pk)))
264288
}
265289
}
266290

@@ -364,16 +388,15 @@ impl FromStr for RgbInvoice {
364388
let (amount, beneficiary) = assignment
365389
.as_str()
366390
.split_once('+')
367-
.map(|(a, b)| (Some(a), Some(b)))
368-
.unwrap_or((Some(assignment.as_str()), None));
391+
.map(|(a, b)| (Some(a), b))
392+
.unwrap_or((None, assignment.as_str()));
369393
// TODO: support other state types
370-
let (beneficiary_str, value) = match (beneficiary, amount) {
371-
(Some(b), Some(a)) => (
372-
b,
394+
let (value, beneficiary_str) = match (amount, beneficiary) {
395+
(Some(a), b) => (
373396
InvoiceState::from_str(a).map_err(|_| InvoiceParseError::Data(a.to_string()))?,
397+
b,
374398
),
375-
(None, Some(b)) => (b, InvoiceState::Void),
376-
_ => unreachable!(),
399+
(None, b) => (InvoiceState::Void, b),
377400
};
378401

379402
let beneficiary = XChainNet::<Beneficiary>::from_str(beneficiary_str)?;
@@ -461,6 +484,21 @@ mod test {
461484
assert_eq!(invoice.to_string(), invoice_str);
462485
assert_eq!(format!("{invoice:#}"), invoice_str.replace('-', ""));
463486

487+
// witness vout without internal pk
488+
let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/\
489+
Il7xqc$4yuca6X0oy0kN27e13ieIAxAv43uVfpG$gYE/BF+bc:wvout:\
490+
BYWmQlmL-$i5Co3j-LtTvxSr-53!Brv7-fc7ZntC-ha988ci-jqKOj4Q";
491+
let invoice = RgbInvoice::from_str(invoice_str).unwrap();
492+
assert_eq!(invoice.to_string(), invoice_str);
493+
494+
// witness vout with internal pk
495+
let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/\
496+
Il7xqc$4yuca6X0oy0kN27e13ieIAxAv43uVfpG$gYE/BF+bc:wvout:\
497+
BYWmQlmL-$i5Co3j-LtTvxSr-53!Brv7-fc7ZntC-ha988ci-jqKOj4Q\
498+
+750f58bcca0fdb11891e7979d829b8c56e0963dba08c44f54a256cf7dbc09caf";
499+
let invoice = RgbInvoice::from_str(invoice_str).unwrap();
500+
assert_eq!(invoice.to_string(), invoice_str);
501+
464502
// no amount
465503
let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/\
466504
Il7xqc$4yuca6X0oy0kN27e13ieIAxAv43uVfpG$gYE/bc:utxob:\
@@ -684,7 +722,15 @@ mod test {
684722
assert_eq!(invoice.transports, transports);
685723
assert_eq!(invoice.to_string(), invoice_str);
686724

687-
// TODO: rgb+storm variant
725+
// rgb+storm variant
726+
let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/\
727+
Il7xqc$4yuca6X0oy0kN27e13ieIAxAv43uVfpG$gYE/BF+bc:utxob:\
728+
zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=storm:\
729+
//_/";
730+
let invoice = RgbInvoice::from_str(invoice_str).unwrap();
731+
let transports = vec![RgbTransport::Storm {}];
732+
assert_eq!(invoice.transports, transports);
733+
assert_eq!(invoice.to_string(), invoice_str);
688734

689735
// multiple transports
690736
let invoice_str = "rgb:\
@@ -728,6 +774,42 @@ mod test {
728774
// rgb-rpc variant with invalid separator parse error
729775
let result = RgbTransport::from_str("rpc/host.example.com");
730776
assert!(matches!(result, Err(TransportParseError::InvalidTransport(_))));
777+
778+
// invalid witness vout: invalid length of identifier wvout
779+
let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/\
780+
Il7xqc$4yuca6X0oy0kN27e13ieIAxAv43uVfpG$gYE/BF+bc:wvout:\
781+
+750f58bcca0fdb11891e7979d829b8c56e0963dba08c44f54a256cf7dbc09caf";
782+
let result = RgbInvoice::from_str(invoice_str);
783+
assert!(matches!(result, Err(InvoiceParseError::Beneficiary(_))));
784+
785+
// invalid witness vout: missing beneficiary HRI
786+
let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/\
787+
Il7xqc$4yuca6X0oy0kN27e13ieIAxAv43uVfpG$gYE/\
788+
BF+750f58bcca0fdb11891e7979d829b8c56e0963dba08c44f54a256cf7dbc09caf";
789+
let result = RgbInvoice::from_str(invoice_str);
790+
assert!(matches!(result, Err(InvoiceParseError::Beneficiary(_))));
791+
792+
// invalid witness vout: invalid chain-network pair
793+
let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/\
794+
Il7xqc$4yuca6X0oy0kN27e13ieIAxAv43uVfpG$gYE/BF+:\
795+
+750f58bcca0fdb11891e7979d829b8c56e0963dba08c44f54a256cf7dbc09caf";
796+
let result = RgbInvoice::from_str(invoice_str);
797+
assert!(matches!(result, Err(InvoiceParseError::Beneficiary(_))));
798+
799+
// invalid witness vout: invalid internal pk
800+
let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/\
801+
Il7xqc$4yuca6X0oy0kN27e13ieIAxAv43uVfpG$gYE/BF+bc:wvout:\
802+
BYWmQlmL-$i5Co3j-LtTvxSr-53!\
803+
Brv7-fc7ZntC-ha988ci-jqKOj4Q+750f58bcca0fdb11891e7979";
804+
let result = RgbInvoice::from_str(invoice_str);
805+
assert!(matches!(result, Err(InvoiceParseError::Beneficiary(_))));
806+
807+
// invalid witness vout: missing internal pk
808+
let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/\
809+
Il7xqc$4yuca6X0oy0kN27e13ieIAxAv43uVfpG$gYE/BF+bc:wvout:\
810+
BYWmQlmL-$i5Co3j-LtTvxSr-53!Brv7-fc7ZntC-ha988ci-jqKOj4Q+";
811+
let result = RgbInvoice::from_str(invoice_str);
812+
assert!(matches!(result, Err(InvoiceParseError::Beneficiary(_))));
731813
}
732814

733815
#[test]

0 commit comments

Comments
 (0)