@@ -369,8 +369,11 @@ impl<D: tb::Bool, H: tb::Bool> InvoiceBuilder<D, H> {
369
369
}
370
370
371
371
/// Adds a private route.
372
- pub fn route ( mut self , route : Route ) -> Self {
373
- self . tagged_fields . push ( TaggedField :: Route ( route) ) ;
372
+ pub fn route ( mut self , route : Vec < RouteHop > ) -> Self {
373
+ match Route :: new ( route) {
374
+ Ok ( r) => self . tagged_fields . push ( TaggedField :: Route ( r) ) ,
375
+ Err ( e) => self . error = Some ( e) ,
376
+ }
374
377
self
375
378
}
376
379
@@ -698,6 +701,10 @@ impl RawInvoice {
698
701
}
699
702
700
703
impl Invoice {
704
+ fn into_signed_raw ( self ) -> SignedRawInvoice {
705
+ self . signed_invoice
706
+ }
707
+
701
708
/// Check that all mandatory fields are present
702
709
fn check_field_counts ( & self ) -> Result < ( ) , SemanticError > {
703
710
// "A writer MUST include exactly one p field […]."
@@ -764,6 +771,10 @@ impl Invoice {
764
771
Ok ( invoice)
765
772
}
766
773
774
+ pub fn timestamp ( & self ) -> u64 {
775
+ self . signed_invoice . raw_invoice . data . timestamp
776
+ }
777
+
767
778
/// Returns an iterator over all tagged fields of this Invoice.
768
779
pub fn tagged_fields ( & self )
769
780
-> FilterMap < Iter < RawTaggedField > , fn ( & RawTaggedField ) -> Option < & TaggedField > > {
@@ -1075,9 +1086,11 @@ mod test {
1075
1086
fn test_builder_amount ( ) {
1076
1087
use :: * ;
1077
1088
1078
- let invoice = InvoiceBuilder :: new ( Currency :: Bitcoin )
1089
+ let builder = InvoiceBuilder :: new ( Currency :: Bitcoin )
1079
1090
. description ( "Test" . into ( ) )
1080
- . payment_hash ( [ 0 ; 32 ] )
1091
+ . payment_hash ( [ 0 ; 32 ] ) ;
1092
+
1093
+ let invoice = builder. clone ( )
1081
1094
. amount_pico_btc ( 15000 )
1082
1095
. build_raw ( )
1083
1096
. unwrap ( ) ;
@@ -1086,14 +1099,147 @@ mod test {
1086
1099
assert_eq ! ( invoice. hrp. raw_amount, Some ( 15 ) ) ;
1087
1100
1088
1101
1089
- let invoice = InvoiceBuilder :: new ( Currency :: Bitcoin )
1090
- . description ( "Test" . into ( ) )
1091
- . payment_hash ( [ 0 ; 32 ] )
1102
+ let invoice = builder. clone ( )
1092
1103
. amount_pico_btc ( 1500 )
1093
1104
. build_raw ( )
1094
1105
. unwrap ( ) ;
1095
1106
1096
1107
assert_eq ! ( invoice. hrp. si_prefix, Some ( SiPrefix :: Pico ) ) ;
1097
1108
assert_eq ! ( invoice. hrp. raw_amount, Some ( 1500 ) ) ;
1098
1109
}
1110
+
1111
+ #[ test]
1112
+ fn test_builder_fail ( ) {
1113
+ use :: * ;
1114
+ use std:: iter:: FromIterator ;
1115
+ use secp256k1:: key:: PublicKey ;
1116
+ use secp256k1:: Secp256k1 ;
1117
+
1118
+ let builder = InvoiceBuilder :: new ( Currency :: Bitcoin )
1119
+ . payment_hash ( [ 0 ; 32 ] ) ;
1120
+
1121
+ let too_long_string = String :: from_iter (
1122
+ ( 0 ..1024 ) . map ( |_| '?' )
1123
+ ) ;
1124
+
1125
+ let long_desc_res = builder. clone ( )
1126
+ . description ( too_long_string)
1127
+ . build_raw ( ) ;
1128
+ assert_eq ! ( long_desc_res, Err ( CreationError :: DescriptionTooLong ) ) ;
1129
+
1130
+ let route_hop = RouteHop {
1131
+ pubkey : PublicKey :: from_slice (
1132
+ & Secp256k1 :: without_caps ( ) ,
1133
+ & [
1134
+ 0x03 , 0x9e , 0x03 , 0xa9 , 0x01 , 0xb8 , 0x55 , 0x34 , 0xff , 0x1e , 0x92 , 0xc4 ,
1135
+ 0x3c , 0x74 , 0x43 , 0x1f , 0x7c , 0xe7 , 0x20 , 0x46 , 0x06 , 0x0f , 0xcf , 0x7a ,
1136
+ 0x95 , 0xc3 , 0x7e , 0x14 , 0x8f , 0x78 , 0xc7 , 0x72 , 0x55
1137
+ ] [ ..]
1138
+ ) . unwrap ( ) ,
1139
+ short_channel_id : [ 0 ; 8 ] ,
1140
+ fee_base_msat : 0 ,
1141
+ fee_proportional_millionths : 0 ,
1142
+ cltv_expiry_delta : 0 ,
1143
+ } ;
1144
+ let too_long_route = vec ! [ route_hop; 13 ] ;
1145
+ let long_route_res = builder. clone ( )
1146
+ . description ( "Test" . into ( ) )
1147
+ . route ( too_long_route)
1148
+ . build_raw ( ) ;
1149
+ assert_eq ! ( long_route_res, Err ( CreationError :: RouteTooLong ) ) ;
1150
+
1151
+ let sign_error_res = builder. clone ( )
1152
+ . description ( "Test" . into ( ) )
1153
+ . try_build_signed ( |_| {
1154
+ Err ( "ImaginaryError" )
1155
+ } ) ;
1156
+ assert_eq ! ( sign_error_res, Err ( SignOrCreationError :: SignError ( "ImaginaryError" ) ) ) ;
1157
+ }
1158
+
1159
+ #[ test]
1160
+ fn test_builder_ok ( ) {
1161
+ use :: * ;
1162
+ use secp256k1:: Secp256k1 ;
1163
+ use secp256k1:: key:: { SecretKey , PublicKey } ;
1164
+
1165
+ let secp_ctx = Secp256k1 :: new ( ) ;
1166
+
1167
+ let private_key = SecretKey :: from_slice (
1168
+ & secp_ctx,
1169
+ & [
1170
+ 0xe1 , 0x26 , 0xf6 , 0x8f , 0x7e , 0xaf , 0xcc , 0x8b , 0x74 , 0xf5 , 0x4d , 0x26 , 0x9f , 0xe2 ,
1171
+ 0x06 , 0xbe , 0x71 , 0x50 , 0x00 , 0xf9 , 0x4d , 0xac , 0x06 , 0x7d , 0x1c , 0x04 , 0xa8 , 0xca ,
1172
+ 0x3b , 0x2d , 0xb7 , 0x34
1173
+ ] [ ..]
1174
+ ) . unwrap ( ) ;
1175
+ let public_key = PublicKey :: from_secret_key ( & secp_ctx, & private_key) ;
1176
+
1177
+ let route_1 = vec ! [
1178
+ RouteHop {
1179
+ pubkey: public_key. clone( ) ,
1180
+ short_channel_id: [ 123 ; 8 ] ,
1181
+ fee_base_msat: 2 ,
1182
+ fee_proportional_millionths: 1 ,
1183
+ cltv_expiry_delta: 145 ,
1184
+ } ,
1185
+ RouteHop {
1186
+ pubkey: public_key. clone( ) ,
1187
+ short_channel_id: [ 42 ; 8 ] ,
1188
+ fee_base_msat: 3 ,
1189
+ fee_proportional_millionths: 2 ,
1190
+ cltv_expiry_delta: 146 ,
1191
+ }
1192
+ ] ;
1193
+
1194
+ let route_2 = vec ! [
1195
+ RouteHop {
1196
+ pubkey: public_key. clone( ) ,
1197
+ short_channel_id: [ 0 ; 8 ] ,
1198
+ fee_base_msat: 4 ,
1199
+ fee_proportional_millionths: 3 ,
1200
+ cltv_expiry_delta: 147 ,
1201
+ } ,
1202
+ RouteHop {
1203
+ pubkey: public_key. clone( ) ,
1204
+ short_channel_id: [ 1 ; 8 ] ,
1205
+ fee_base_msat: 5 ,
1206
+ fee_proportional_millionths: 4 ,
1207
+ cltv_expiry_delta: 148 ,
1208
+ }
1209
+ ] ;
1210
+
1211
+ let builder = InvoiceBuilder :: new ( Currency :: BitcoinTestnet )
1212
+ . amount_pico_btc ( 123 )
1213
+ . timestamp ( 1234567 )
1214
+ . payee_pub_key ( public_key. clone ( ) )
1215
+ . expiry_time_seconds ( 54321 )
1216
+ . min_final_cltv_expiry ( 144 )
1217
+ . min_final_cltv_expiry ( 143 )
1218
+ . fallback ( Fallback :: PubKeyHash ( [ 0 ; 20 ] ) )
1219
+ . route ( route_1. clone ( ) )
1220
+ . route ( route_2. clone ( ) )
1221
+ . description_hash ( [ 3 ; 32 ] )
1222
+ . payment_hash ( [ 21 ; 32 ] ) ;
1223
+
1224
+ let invoice = builder. clone ( ) . build_signed ( |hash| {
1225
+ secp_ctx. sign_recoverable ( hash, & private_key)
1226
+ } ) . unwrap ( ) ;
1227
+
1228
+ assert ! ( invoice. check_signature( ) . is_ok( ) ) ;
1229
+ assert_eq ! ( invoice. tagged_fields( ) . count( ) , 9 ) ;
1230
+
1231
+ assert_eq ! ( invoice. amount_pico_btc( ) , Some ( 123 ) ) ;
1232
+ assert_eq ! ( invoice. currency( ) , Currency :: BitcoinTestnet ) ;
1233
+ assert_eq ! ( invoice. timestamp( ) , 1234567 ) ;
1234
+ assert_eq ! ( invoice. payee_pub_key( ) , Some ( & PayeePubKey ( public_key) ) ) ;
1235
+ assert_eq ! ( invoice. expiry_time( ) , Some ( & ExpiryTime { seconds: 54321 } ) ) ;
1236
+ assert_eq ! ( invoice. min_final_cltv_expiry( ) , Some ( & MinFinalCltvExpiry ( 144 ) ) ) ;
1237
+ assert_eq ! ( invoice. fallbacks( ) , vec![ & Fallback :: PubKeyHash ( [ 0 ; 20 ] ) ] ) ;
1238
+ assert_eq ! ( invoice. routes( ) , vec![ & Route ( route_1) , & Route ( route_2) ] ) ;
1239
+ assert_eq ! ( invoice. description( ) , InvoiceDescription :: Hash ( & Sha256 ( [ 3 ; 32 ] ) ) ) ;
1240
+ assert_eq ! ( invoice. payment_hash( ) , & Sha256 ( [ 21 ; 32 ] ) ) ;
1241
+
1242
+ let raw_invoice = builder. build_raw ( ) . unwrap ( ) ;
1243
+ assert_eq ! ( raw_invoice, * invoice. into_signed_raw( ) . raw_invoice( ) )
1244
+ }
1099
1245
}
0 commit comments