@@ -25,6 +25,7 @@ use std::num::ParseIntError;
2525use std:: str:: FromStr ;
2626
2727use baid64:: { Baid64ParseError , DisplayBaid64 , FromBaid64Str } ;
28+ use bp:: InternalPk ;
2829use fluent_uri:: enc:: EStr ;
2930use fluent_uri:: Uri ;
3031use 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