@@ -402,7 +402,7 @@ macro_rules! offer_builder_methods { (
402402 pub fn build( $( $self_mut) * $self: $self_type) -> Result <Offer , Bolt12SemanticError > {
403403 match $self. offer. amount {
404404 Some ( Amount :: Bitcoin { amount_msats } ) => {
405- if amount_msats > MAX_VALUE_MSAT {
405+ if amount_msats == 0 || amount_msats > MAX_VALUE_MSAT {
406406 return Err ( Bolt12SemanticError :: InvalidAmount ) ;
407407 }
408408 } ,
@@ -1306,11 +1306,12 @@ impl TryFrom<FullOfferTlvStream> for OfferContents {
13061306
13071307 let amount = match ( currency, amount) {
13081308 ( None , None ) => None ,
1309- ( None , Some ( amount_msats) ) if amount_msats > MAX_VALUE_MSAT => {
1309+ ( None , Some ( amount_msats) ) if amount_msats == 0 || amount_msats > MAX_VALUE_MSAT => {
13101310 return Err ( Bolt12SemanticError :: InvalidAmount ) ;
13111311 } ,
13121312 ( None , Some ( amount_msats) ) => Some ( Amount :: Bitcoin { amount_msats } ) ,
13131313 ( Some ( _) , None ) => return Err ( Bolt12SemanticError :: MissingAmount ) ,
1314+ ( Some ( _) , Some ( 0 ) ) => return Err ( Bolt12SemanticError :: InvalidAmount ) ,
13141315 ( Some ( currency_bytes) , Some ( amount) ) => {
13151316 let iso4217_code = CurrencyCode :: new ( currency_bytes)
13161317 . map_err ( |_| Bolt12SemanticError :: InvalidCurrencyCode ) ?;
@@ -1702,6 +1703,12 @@ mod tests {
17021703 Ok ( _) => panic ! ( "expected error" ) ,
17031704 Err ( e) => assert_eq ! ( e, Bolt12SemanticError :: InvalidAmount ) ,
17041705 }
1706+
1707+ // An amount of 0 must be rejected per BOLT 12.
1708+ match OfferBuilder :: new ( pubkey ( 42 ) ) . amount_msats ( 0 ) . build ( ) {
1709+ Ok ( _) => panic ! ( "expected error" ) ,
1710+ Err ( e) => assert_eq ! ( e, Bolt12SemanticError :: InvalidAmount ) ,
1711+ }
17051712 }
17061713
17071714 #[ test]
@@ -1974,6 +1981,59 @@ mod tests {
19741981 Bolt12ParseError :: InvalidSemantics ( Bolt12SemanticError :: InvalidCurrencyCode )
19751982 ) ,
19761983 }
1984+
1985+ // An offer with amount=0 must be rejected per BOLT 12.
1986+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
1987+ tlv_stream. 0 . amount = Some ( 0 ) ;
1988+ tlv_stream. 0 . currency = None ;
1989+
1990+ let mut encoded_offer = Vec :: new ( ) ;
1991+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
1992+
1993+ match Offer :: try_from ( encoded_offer) {
1994+ Ok ( _) => panic ! ( "expected error" ) ,
1995+ Err ( e) => assert_eq ! (
1996+ e,
1997+ Bolt12ParseError :: InvalidSemantics ( Bolt12SemanticError :: InvalidAmount )
1998+ ) ,
1999+ }
2000+
2001+ // An offer with amount=0 and a currency must also be rejected.
2002+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
2003+ tlv_stream. 0 . amount = Some ( 0 ) ;
2004+ tlv_stream. 0 . currency = Some ( b"USD" ) ;
2005+
2006+ let mut encoded_offer = Vec :: new ( ) ;
2007+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
2008+
2009+ match Offer :: try_from ( encoded_offer) {
2010+ Ok ( _) => panic ! ( "expected error" ) ,
2011+ Err ( e) => assert_eq ! (
2012+ e,
2013+ Bolt12ParseError :: InvalidSemantics ( Bolt12SemanticError :: InvalidAmount )
2014+ ) ,
2015+ }
2016+
2017+ // BOLT 12 test vectors: verify rejection of offers with amount=0 from their
2018+ // bech32 encoding (see bolt12/offers-test.json).
2019+ match "lno1pqqq5qqkyyp4he0fg7pqje62jmnq78cr0ashv4q06qql58tyd9rhp3t2wuyugtq" . parse :: < Offer > ( )
2020+ {
2021+ Ok ( _) => panic ! ( "expected error" ) ,
2022+ Err ( e) => assert_eq ! (
2023+ e,
2024+ Bolt12ParseError :: InvalidSemantics ( Bolt12SemanticError :: InvalidAmount )
2025+ ) ,
2026+ }
2027+
2028+ match "lno1qcp4256ypqqq5qqkyyp4he0fg7pqje62jmnq78cr0ashv4q06qql58tyd9rhp3t2wuyugtq"
2029+ . parse :: < Offer > ( )
2030+ {
2031+ Ok ( _) => panic ! ( "expected error" ) ,
2032+ Err ( e) => assert_eq ! (
2033+ e,
2034+ Bolt12ParseError :: InvalidSemantics ( Bolt12SemanticError :: InvalidAmount )
2035+ ) ,
2036+ }
19772037 }
19782038
19792039 #[ test]
0 commit comments