├── .github └── workflows │ └── main_ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── payreq.d.ts ├── payreq.js └── test ├── fixtures.json ├── index.js └── list.md /.github/workflows/main_ci.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | coverage: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 17 | - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 18 | with: 19 | node-version: 16 20 | registry-url: https://registry.npmjs.org/ 21 | - run: npm ci 22 | - run: npm run coverage 23 | lint: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2 27 | - uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1 28 | with: 29 | node-version: 16 30 | registry-url: https://registry.npmjs.org/ 31 | - run: npm ci 32 | - run: npm run standard 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 bitcoinjs contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bolt11 2 | A library for encoding and decoding lightning network payment requests as defined in [BOLT #11](https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md). 3 | 4 | 5 | ## Installation 6 | ``` bash 7 | npm install bolt11 8 | ``` 9 | 10 | ## Setup 11 | ### Node.js 12 | ``` javascript 13 | var lightningPayReq = require('bolt11') 14 | ``` 15 | 16 | 17 | ## Examples 18 | ### Decoding 19 | ``` javascript 20 | // The tags array output can be parsed into an object using the tagsObject getter (see below) 21 | var decoded = lightningPayReq.decode('lnbc20u1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppqw508d6qejxtdg4y5r3zarvary0c5xw7kxqrrsssp5m6kmam774klwlh4dhmhaatd7al02m0h0m6kmam774klwlh4dhmhs9qypqqqcqpf3cwux5979a8j28d4ydwahx00saa68wq3az7v9jdgzkghtxnkf3z5t7q5suyq2dl9tqwsap8j0wptc82cpyvey9gf6zyylzrm60qtcqsq7egtsq') 22 | /* decoded == below 23 | { 24 | "complete": true, 25 | "millisatoshis": "2000000", 26 | "network": { 27 | "bech32": "bc", 28 | "pubKeyHash": 0, 29 | "scriptHash": 5, 30 | "validWitnessVersions": [0, 1] 31 | }, 32 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 33 | "paymentRequest": "lnbc20u1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppqw508d6qejxtdg4y5r3zarvary0c5xw7kxqrrsssp5m6kmam774klwlh4dhmhaatd7al02m0h0m6kmam774klwlh4dhmhs9qypqqqcqpf3cwux5979a8j28d4ydwahx00saa68wq3az7v9jdgzkghtxnkf3z5t7q5suyq2dl9tqwsap8j0wptc82cpyvey9gf6zyylzrm60qtcqsq7egtsq", 34 | "prefix": "lnbc20u", 35 | "recoveryFlag": 0, 36 | "satoshis": 2000, 37 | "signature": "8e1dc350be2f4f251db5235ddb99ef877ba3b811e8bcc2c9a81591759a764c4545f81487080537e5581d0e84f27b82bc1d580919921509d0884f887bd3c0bc02", 38 | "tags": [ 39 | { 40 | "tagName": "purpose_commit_hash", 41 | "data": "3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1" 42 | }, 43 | { 44 | "tagName": "payment_hash", 45 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 46 | }, 47 | { 48 | "tagName": "fallback_address", 49 | "data": { 50 | "code": 0, 51 | "address": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", 52 | "addressHash": "751e76e8199196d454941c45d1b3a323f1433bd6" 53 | } 54 | }, 55 | { 56 | "tagName": "expire_time", 57 | "data": 3600 58 | }, 59 | { 60 | "tagName": "payment_secret", 61 | "data": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" 62 | }, 63 | { 64 | "tagName": "feature_bits", 65 | "data": { 66 | "word_length": 4, 67 | "option_data_loss_protect": { 68 | "required": false, 69 | "supported": false 70 | }, 71 | "initial_routing_sync": { 72 | "required": false, 73 | "supported": false 74 | }, 75 | "option_upfront_shutdown_script": { 76 | "required": false, 77 | "supported": false 78 | }, 79 | "gossip_queries": { 80 | "required": false, 81 | "supported": false 82 | }, 83 | "var_onion_optin": { 84 | "required": false, 85 | "supported": false 86 | }, 87 | "gossip_queries_ex": { 88 | "required": false, 89 | "supported": false 90 | }, 91 | "option_static_remotekey": { 92 | "required": false, 93 | "supported": false 94 | }, 95 | "payment_secret": { 96 | "required": false, 97 | "supported": true 98 | }, 99 | "basic_mpp": { 100 | "required": false, 101 | "supported": false 102 | }, 103 | "option_support_large_channel": { 104 | "required": false, 105 | "supported": false 106 | }, 107 | "extra_bits": { 108 | "start_bit": 20, 109 | "bits": [], 110 | "has_required": false 111 | } 112 | } 113 | }, 114 | { 115 | "tagName": "min_final_cltv_expiry", 116 | "data": 9 117 | } 118 | ], 119 | "timeExpireDate": 1496318258, 120 | "timeExpireDateString": "2017-06-01T11:57:38.000Z", 121 | "timestamp": 1496314658, 122 | "timestampString": "2017-06-01T10:57:38.000Z", 123 | "wordsTemp": "temp1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppqw508d6qejxtdg4y5r3zarvary0c5xw7kxqrrsssp5m6kmam774klwlh4dhmhaatd7al02m0h0m6kmam774klwlh4dhmhs9qypqqqcqpf3cwux5979a8j28d4ydwahx00saa68wq3az7v9jdgzkghtxnkf3z5t7q5suyq2dl9tqwsap8j0wptc82cpyvey9gf6zyylzrm60qtcqsq5xx76e" 124 | } 125 | */ 126 | ``` 127 | 128 | ### Get tags as an object 129 | ```javascript 130 | // decoded is from above 131 | decoded.tagsObject 132 | /* 133 | { 134 | "purpose_commit_hash": "3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1", 135 | "payment_hash": "0001020304050607080900010203040506070809000102030405060708090102", 136 | "fallback_address": { 137 | "code": 0, 138 | "address": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", 139 | "addressHash": "751e76e8199196d454941c45d1b3a323f1433bd6" 140 | }, 141 | "expire_time": 3600, 142 | "payment_secret": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", 143 | "feature_bits": { 144 | "word_length": 4, 145 | "option_data_loss_protect": { 146 | "required": false, 147 | "supported": false 148 | }, 149 | "initial_routing_sync": { 150 | "required": false, 151 | "supported": false 152 | }, 153 | "option_upfront_shutdown_script": { 154 | "required": false, 155 | "supported": false 156 | }, 157 | "gossip_queries": { 158 | "required": false, 159 | "supported": false 160 | }, 161 | "var_onion_optin": { 162 | "required": false, 163 | "supported": false 164 | }, 165 | "gossip_queries_ex": { 166 | "required": false, 167 | "supported": false 168 | }, 169 | "option_static_remotekey": { 170 | "required": false, 171 | "supported": false 172 | }, 173 | "payment_secret": { 174 | "required": false, 175 | "supported": true 176 | }, 177 | "basic_mpp": { 178 | "required": false, 179 | "supported": false 180 | }, 181 | "option_support_large_channel": { 182 | "required": false, 183 | "supported": false 184 | }, 185 | "extra_bits": { 186 | "start_bit": 20, 187 | "bits": [], 188 | "has_required": false 189 | } 190 | }, 191 | "min_final_cltv_expiry": 9 192 | } 193 | */ 194 | ``` 195 | 196 | ### Warning 197 | The `"satoshis"` field will only be set if the invoice is for a whole number of satoshis. If it is in a fractional number of satoshis, the `"millisatoshis"` field must be used. 1000 millisatoshis is 1 satoshi. 198 | 199 | ### Encoding 200 | * MINIMUM NEED: `privateKey` and one `payment_hash` tag as well as one `description` 201 | * (`timestamp` defaults to current time, `description` defaults to empty string, 202 | and `network` defaults to bitcoin mainnet) 203 | * Alternatively: You can pass the result of decode into encode and it will use the 204 | signature and recoveryFlag attributes to reconstruct the payment request. In this 205 | case you will require also `network` and `timestamp` as well as all tags in the 206 | exact order of the original signed request. 207 | * It is also required to pass the `payeeNodeKey` attribute when encoding an 208 | already signed request, as decoders will recover the pubkey, any incorrect data 209 | would cause an incorrect pubkey to be generated and will cause an error on the 210 | decoding end when trying to send. 211 | * Note: tag order matters. The message is signed, so to maintain tag order it is 212 | an array type. 213 | 214 | ``` javascript 215 | var encoded = lightningPayReq.encode({ 216 | "network": { 217 | "bech32": "bc", 218 | "pubKeyHash": 0, 219 | "scriptHash": 5, 220 | "validWitnessVersions": [0, 1] 221 | }, 222 | "satoshis": 2000, 223 | "timestamp": 1496314658, 224 | "tags": [ 225 | { 226 | "tagName": "purpose_commit_hash", 227 | "data": "3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1" 228 | }, 229 | { 230 | "tagName": "payment_hash", 231 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 232 | }, 233 | { 234 | "tagName": "fallback_address", 235 | "data": { 236 | "address": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" 237 | } 238 | }, 239 | { 240 | "tagName": "expire_time", 241 | "data": 3600 242 | }, 243 | { 244 | "tagName": "payment_secret", 245 | "data": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" 246 | }, 247 | { 248 | "tagName": "feature_bits", 249 | "data": { 250 | "payment_secret": { 251 | "required": false, 252 | "supported": true 253 | } 254 | } 255 | } 256 | ] 257 | }) 258 | // sign takes the encoded object and the private key as arguments 259 | var privateKeyHex = 'e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734' 260 | var signed = lightningPayReq.sign(encoded, privateKeyHex) 261 | /* signed.paymentRequest == below 262 | lnbc20u1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppqw508d6qejxtdg4y5r3zarvary0c5xw7kxqrrsssp5m6kmam774klwlh4dhmhaatd7al02m0h0m6kmam774klwlh4dhmhs9qypqqqcqpf3cwux5979a8j28d4ydwahx00saa68wq3az7v9jdgzkghtxnkf3z5t7q5suyq2dl9tqwsap8j0wptc82cpyvey9gf6zyylzrm60qtcqsq7egtsq 263 | */ 264 | ``` 265 | 266 | ## Browser Use 267 | You can use this in the browser. First install browserify and uglify-es (uglifyjs for ES6+) globally. 268 | 269 | ``` bash 270 | npm install -g browserify uglify-es 271 | ``` 272 | 273 | Then run the command. 274 | 275 | ``` bash 276 | browserify -r bolt11 --standalone lightningPayReq | uglifyjs -c -m -o bolt11.min.js 277 | ``` 278 | 279 | Now load bolt11.min.js into an HTML page like so: 280 | 281 | ``` HTML 282 | 283 | ``` 284 | 285 | And now you can do all the examples above in a browser using the global 286 | `lightningPayReq` object. 287 | 288 | ## Contributing 289 | We are always accepting of pull requests, but we do adhere to specific standards in regards to coding style, test driven development and commit messages. 290 | 291 | Please make your best effort to adhere to these when contributing to save on trivial corrections. 292 | 293 | 294 | ### Running the test suite 295 | 296 | ``` bash 297 | npm test 298 | npm run-script coverage 299 | ``` 300 | 301 | 302 | ## LICENSE [MIT](LICENSE) 303 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bolt11", 3 | "version": "1.4.1", 4 | "description": "A library for encoding and decoding lightning network payment requests as defined in [BOLT #11](https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md).", 5 | "main": "payreq.js", 6 | "types": "payreq.d.ts", 7 | "files": [ 8 | "payreq.js", 9 | "payreq.d.ts" 10 | ], 11 | "scripts": { 12 | "coverage-report": "nyc report --reporter=lcov --reporter=html", 13 | "coverage": "nyc --check-coverage --statements 100 --branches 100 --functions 100 --lines 100 npm run unit", 14 | "standard": "standard", 15 | "test": "npm run standard && npm run coverage", 16 | "unit": "tape test/*.js" 17 | }, 18 | "devDependencies": { 19 | "nyc": "^15.0.0", 20 | "standard": "*", 21 | "tap-dot": "*", 22 | "tape": "^4.13.2" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/bitcoinjs/bolt11.git" 27 | }, 28 | "keywords": [ 29 | "invoice", 30 | "payment", 31 | "request", 32 | "lightning", 33 | "network", 34 | "bolt11", 35 | "bech32" 36 | ], 37 | "author": "Jonathan Underwood", 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/bitcoinjs/bolt11/issues" 41 | }, 42 | "homepage": "https://github.com/bitcoinjs/bolt11#readme", 43 | "dependencies": { 44 | "@types/bn.js": "^4.11.3", 45 | "bech32": "^1.1.2", 46 | "bitcoinjs-lib": "^6.0.0", 47 | "bn.js": "^4.11.8", 48 | "create-hash": "^1.2.0", 49 | "lodash": "^4.17.11", 50 | "safe-buffer": "^5.1.1", 51 | "secp256k1": "^4.0.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /payreq.d.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | 3 | type RoutingInfo = Array<{ 4 | pubkey: string; 5 | short_channel_id: string; 6 | fee_base_msat: number; 7 | fee_proportional_millionths: number; 8 | cltv_expiry_delta: number; 9 | }>; 10 | type FallbackAddress = { 11 | code: number; 12 | address: string; 13 | addressHash: string; 14 | }; 15 | type FeatureBits = { 16 | word_length: number; 17 | option_data_loss_protect?: Feature; 18 | initial_routing_sync?: Feature; 19 | option_upfront_shutdown_script?: Feature; 20 | gossip_queries?: Feature; 21 | var_onion_optin?: Feature; 22 | gossip_queries_ex?: Feature; 23 | option_static_remotekey?: Feature; 24 | payment_secret?: Feature; 25 | basic_mpp?: Feature; 26 | option_support_large_channel?: Feature; 27 | extra_bits?: { 28 | start_bit: number; 29 | bits: boolean[]; 30 | has_required?: boolean; 31 | }; 32 | } 33 | type Feature = { 34 | required?: boolean; 35 | supported?: boolean; 36 | }; 37 | type Network = { 38 | [index: string]: any; 39 | bech32: string; 40 | pubKeyHash: number; 41 | scriptHash: number; 42 | validWitnessVersions: number[]; 43 | }; 44 | type UnknownTag = { 45 | tagCode: number; 46 | words: string; 47 | }; 48 | 49 | // Start exports 50 | export declare type TagData = string | number | RoutingInfo | FallbackAddress | FeatureBits | UnknownTag; 51 | export declare type TagsObject = { 52 | payment_hash?: string; 53 | payment_secret?: string; 54 | description?: string; 55 | payee_node_key?: string; 56 | purpose_commit_hash?: string; 57 | expire_time?: number; 58 | min_final_cltv_expiry?: number; 59 | fallback_address?: FallbackAddress; 60 | routing_info?: RoutingInfo; 61 | feature_bits?: FeatureBits; 62 | unknownTags?: UnknownTag[]; 63 | }; 64 | export declare type PaymentRequestObject = { 65 | paymentRequest?: string; 66 | complete?: boolean; 67 | prefix?: string; 68 | wordsTemp?: string; 69 | network?: Network; 70 | satoshis?: number | null; 71 | millisatoshis?: string | null; 72 | timestamp?: number; 73 | timestampString?: string; 74 | timeExpireDate?: number; 75 | timeExpireDateString?: string; 76 | payeeNodeKey?: string; 77 | signature?: string; 78 | recoveryFlag?: number; 79 | tags: Array<{ 80 | tagName: string; 81 | data: TagData; 82 | }>; 83 | }; 84 | export declare function encode(inputData: PaymentRequestObject, addDefaults?: boolean): PaymentRequestObject; 85 | export declare function decode(paymentRequest: string, network?: Network): PaymentRequestObject & { tagsObject: TagsObject; }; 86 | export declare function sign(inputPayReqObj: PaymentRequestObject, inputPrivateKey: string | Buffer): PaymentRequestObject; 87 | export declare function satToHrp(satoshis: number | string): string; 88 | export declare function millisatToHrp(millisatoshis: number | string): string; 89 | export declare function hrpToSat(hrpString: string, outputString?: boolean): string | BN; 90 | export declare function hrpToMillisat(hrpString: string, outputString?: boolean): string | BN; 91 | -------------------------------------------------------------------------------- /payreq.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const createHash = require('create-hash') 4 | const bech32 = require('bech32') 5 | const secp256k1 = require('secp256k1') 6 | const Buffer = require('safe-buffer').Buffer 7 | const BN = require('bn.js') 8 | const bitcoinjsAddress = require('bitcoinjs-lib').address 9 | const cloneDeep = require('lodash/cloneDeep') 10 | 11 | // defaults for encode; default timestamp is current time at call 12 | const DEFAULTNETWORK = { 13 | // default network is bitcoin 14 | bech32: 'bc', 15 | pubKeyHash: 0x00, 16 | scriptHash: 0x05, 17 | validWitnessVersions: [0, 1] 18 | } 19 | const TESTNETWORK = { 20 | bech32: 'tb', 21 | pubKeyHash: 0x6f, 22 | scriptHash: 0xc4, 23 | validWitnessVersions: [0, 1] 24 | } 25 | const REGTESTNETWORK = { 26 | bech32: 'bcrt', 27 | pubKeyHash: 0x6f, 28 | scriptHash: 0xc4, 29 | validWitnessVersions: [0, 1] 30 | } 31 | const SIMNETWORK = { 32 | bech32: 'sb', 33 | pubKeyHash: 0x3f, 34 | scriptHash: 0x7b, 35 | validWitnessVersions: [0, 1] 36 | } 37 | const DEFAULTEXPIRETIME = 3600 38 | const DEFAULTCLTVEXPIRY = 9 39 | const DEFAULTDESCRIPTION = '' 40 | const DEFAULTFEATUREBITS = { 41 | word_length: 4, // last bit set default is 15 42 | var_onion_optin: { 43 | required: false, 44 | supported: true 45 | }, 46 | payment_secret: { 47 | required: false, 48 | supported: true 49 | } 50 | } 51 | 52 | const FEATUREBIT_ORDER = [ 53 | 'option_data_loss_protect', 54 | 'initial_routing_sync', 55 | 'option_upfront_shutdown_script', 56 | 'gossip_queries', 57 | 'var_onion_optin', 58 | 'gossip_queries_ex', 59 | 'option_static_remotekey', 60 | 'payment_secret', 61 | 'basic_mpp', 62 | 'option_support_large_channel' 63 | ] 64 | 65 | const DIVISORS = { 66 | m: new BN(1e3, 10), 67 | u: new BN(1e6, 10), 68 | n: new BN(1e9, 10), 69 | p: new BN(1e12, 10) 70 | } 71 | 72 | const MAX_MILLISATS = new BN('2100000000000000000', 10) 73 | 74 | const MILLISATS_PER_BTC = new BN(1e11, 10) 75 | const MILLISATS_PER_MILLIBTC = new BN(1e8, 10) 76 | const MILLISATS_PER_MICROBTC = new BN(1e5, 10) 77 | const MILLISATS_PER_NANOBTC = new BN(1e2, 10) 78 | const PICOBTC_PER_MILLISATS = new BN(10, 10) 79 | 80 | const TAGCODES = { 81 | payment_hash: 1, 82 | payment_secret: 16, 83 | description: 13, 84 | payee_node_key: 19, 85 | purpose_commit_hash: 23, // commit to longer descriptions (like a website) 86 | expire_time: 6, // default: 3600 (1 hour) 87 | min_final_cltv_expiry: 24, // default: 9 88 | fallback_address: 9, 89 | routing_info: 3, // for extra routing info (private etc.) 90 | feature_bits: 5 91 | } 92 | 93 | // reverse the keys and values of TAGCODES and insert into TAGNAMES 94 | const TAGNAMES = {} 95 | for (let i = 0, keys = Object.keys(TAGCODES); i < keys.length; i++) { 96 | const currentName = keys[i] 97 | const currentCode = TAGCODES[keys[i]].toString() 98 | TAGNAMES[currentCode] = currentName 99 | } 100 | 101 | const TAGENCODERS = { 102 | payment_hash: hexToWord, // 256 bits 103 | payment_secret: hexToWord, // 256 bits 104 | description: textToWord, // string variable length 105 | payee_node_key: hexToWord, // 264 bits 106 | purpose_commit_hash: purposeCommitEncoder, // 256 bits 107 | expire_time: intBEToWords, // default: 3600 (1 hour) 108 | min_final_cltv_expiry: intBEToWords, // default: 9 109 | fallback_address: fallbackAddressEncoder, 110 | routing_info: routingInfoEncoder, // for extra routing info (private etc.) 111 | feature_bits: featureBitsEncoder 112 | } 113 | 114 | const TAGPARSERS = { 115 | 1: (words) => wordsToBuffer(words, true).toString('hex'), // 256 bits 116 | 16: (words) => wordsToBuffer(words, true).toString('hex'), // 256 bits 117 | 13: (words) => wordsToBuffer(words, true).toString('utf8'), // string variable length 118 | 19: (words) => wordsToBuffer(words, true).toString('hex'), // 264 bits 119 | 23: (words) => wordsToBuffer(words, true).toString('hex'), // 256 bits 120 | 6: wordsToIntBE, // default: 3600 (1 hour) 121 | 24: wordsToIntBE, // default: 9 122 | 9: fallbackAddressParser, 123 | 3: routingInfoParser, // for extra routing info (private etc.) 124 | 5: featureBitsParser // keep feature bits as array of 5 bit words 125 | } 126 | 127 | const unknownTagName = 'unknownTag' 128 | 129 | function unknownEncoder (data) { 130 | data.words = bech32.decode(data.words, Number.MAX_SAFE_INTEGER).words 131 | return data 132 | } 133 | 134 | function getUnknownParser (tagCode) { 135 | return (words) => ({ 136 | tagCode: parseInt(tagCode), 137 | words: bech32.encode('unknown', words, Number.MAX_SAFE_INTEGER) 138 | }) 139 | } 140 | 141 | function wordsToIntBE (words) { 142 | return words.reverse().reduce((total, item, index) => { 143 | return total + item * Math.pow(32, index) 144 | }, 0) 145 | } 146 | 147 | function intBEToWords (intBE, bits) { 148 | const words = [] 149 | if (bits === undefined) bits = 5 150 | intBE = Math.floor(intBE) 151 | if (intBE === 0) return [0] 152 | while (intBE > 0) { 153 | words.push(intBE & (Math.pow(2, bits) - 1)) 154 | intBE = Math.floor(intBE / Math.pow(2, bits)) 155 | } 156 | return words.reverse() 157 | } 158 | 159 | function sha256 (data) { 160 | return createHash('sha256').update(data).digest() 161 | } 162 | 163 | function convert (data, inBits, outBits) { 164 | let value = 0 165 | let bits = 0 166 | const maxV = (1 << outBits) - 1 167 | 168 | const result = [] 169 | for (let i = 0; i < data.length; ++i) { 170 | value = (value << inBits) | data[i] 171 | bits += inBits 172 | 173 | while (bits >= outBits) { 174 | bits -= outBits 175 | result.push((value >> bits) & maxV) 176 | } 177 | } 178 | 179 | if (bits > 0) { 180 | result.push((value << (outBits - bits)) & maxV) 181 | } 182 | 183 | return result 184 | } 185 | 186 | function wordsToBuffer (words, trim) { 187 | let buffer = Buffer.from(convert(words, 5, 8, true)) 188 | if (trim && words.length * 5 % 8 !== 0) { 189 | buffer = buffer.slice(0, -1) 190 | } 191 | return buffer 192 | } 193 | 194 | function hexToBuffer (hex) { 195 | if (hex !== undefined && 196 | (typeof hex === 'string' || hex instanceof String) && 197 | hex.match(/^([a-zA-Z0-9]{2})*$/)) { 198 | return Buffer.from(hex, 'hex') 199 | } 200 | return hex 201 | } 202 | 203 | function textToBuffer (text) { 204 | return Buffer.from(text, 'utf8') 205 | } 206 | 207 | function hexToWord (hex) { 208 | const buffer = hexToBuffer(hex) 209 | return bech32.toWords(buffer) 210 | } 211 | 212 | function textToWord (text) { 213 | const buffer = textToBuffer(text) 214 | const words = bech32.toWords(buffer) 215 | return words 216 | } 217 | 218 | // see encoder for details 219 | function fallbackAddressParser (words, network) { 220 | const version = words[0] 221 | words = words.slice(1) 222 | 223 | const addressHash = wordsToBuffer(words, true) 224 | 225 | let address = null 226 | 227 | switch (version) { 228 | case 17: 229 | address = bitcoinjsAddress.toBase58Check(addressHash, network.pubKeyHash) 230 | break 231 | case 18: 232 | address = bitcoinjsAddress.toBase58Check(addressHash, network.scriptHash) 233 | break 234 | case 0: 235 | case 1: 236 | address = bitcoinjsAddress.toBech32(addressHash, version, network.bech32) 237 | break 238 | } 239 | 240 | return { 241 | code: version, 242 | address, 243 | addressHash: addressHash.toString('hex') 244 | } 245 | } 246 | 247 | // the code is the witness version OR 17 for P2PKH OR 18 for P2SH 248 | // anything besides code 17 or 18 should be bech32 or bech32m encoded address. 249 | // 1 word for the code, and right pad with 0 if necessary for the addressHash 250 | // (address parsing for encode is done in the encode function) 251 | function fallbackAddressEncoder (data, network) { 252 | return [data.code].concat(hexToWord(data.addressHash)) 253 | } 254 | 255 | // first convert from words to buffer, trimming padding where necessary 256 | // parse in 51 byte chunks. See encoder for details. 257 | function routingInfoParser (words) { 258 | const routes = [] 259 | let pubkey, shortChannelId, feeBaseMSats, feeProportionalMillionths, cltvExpiryDelta 260 | let routesBuffer = wordsToBuffer(words, true) 261 | while (routesBuffer.length > 0) { 262 | pubkey = routesBuffer.slice(0, 33).toString('hex') // 33 bytes 263 | shortChannelId = routesBuffer.slice(33, 41).toString('hex') // 8 bytes 264 | feeBaseMSats = parseInt(routesBuffer.slice(41, 45).toString('hex'), 16) // 4 bytes 265 | feeProportionalMillionths = parseInt(routesBuffer.slice(45, 49).toString('hex'), 16) // 4 bytes 266 | cltvExpiryDelta = parseInt(routesBuffer.slice(49, 51).toString('hex'), 16) // 2 bytes 267 | 268 | routesBuffer = routesBuffer.slice(51) 269 | 270 | routes.push({ 271 | pubkey, 272 | short_channel_id: shortChannelId, 273 | fee_base_msat: feeBaseMSats, 274 | fee_proportional_millionths: feeProportionalMillionths, 275 | cltv_expiry_delta: cltvExpiryDelta 276 | }) 277 | } 278 | return routes 279 | } 280 | 281 | function featureBitsParser (words) { 282 | const bools = words.slice().reverse().map(word => 283 | [ 284 | !!(word & 0b1), 285 | !!(word & 0b10), 286 | !!(word & 0b100), 287 | !!(word & 0b1000), 288 | !!(word & 0b10000) 289 | ] 290 | ).reduce((finalArr, itemArr) => finalArr.concat(itemArr), []) 291 | while (bools.length < FEATUREBIT_ORDER.length * 2) { 292 | bools.push(false) 293 | } 294 | const featureBits = { 295 | word_length: words.length 296 | } 297 | FEATUREBIT_ORDER.forEach((featureName, index) => { 298 | featureBits[featureName] = { 299 | required: bools[index * 2], 300 | supported: bools[index * 2 + 1] 301 | } 302 | }) 303 | if (bools.length > FEATUREBIT_ORDER.length * 2) { 304 | const extraBits = bools.slice(FEATUREBIT_ORDER.length * 2) 305 | featureBits.extra_bits = { 306 | start_bit: FEATUREBIT_ORDER.length * 2, 307 | bits: extraBits, 308 | has_required: extraBits.reduce( 309 | (result, bit, index) => 310 | index % 2 !== 0 311 | ? result || false 312 | : result || bit, 313 | false 314 | ) 315 | } 316 | } else { 317 | featureBits.extra_bits = { 318 | start_bit: FEATUREBIT_ORDER.length * 2, 319 | bits: [], 320 | has_required: false 321 | } 322 | } 323 | return featureBits 324 | } 325 | 326 | function featureBitsEncoder (featureBits) { 327 | let wordsLength = featureBits.word_length 328 | let bools = [] 329 | FEATUREBIT_ORDER.forEach(featureName => { 330 | bools.push(!!(featureBits[featureName] || {}).required) 331 | bools.push(!!(featureBits[featureName] || {}).supported) 332 | }) 333 | // Make sure that only minimal number of bits is encoded 334 | while (bools[bools.length - 1] === false) { 335 | bools.pop() 336 | } 337 | while (bools.length % 5 !== 0) { 338 | bools.push(false) 339 | } 340 | if ( 341 | featureBits.extra_bits && 342 | Array.isArray(featureBits.extra_bits.bits) && 343 | featureBits.extra_bits.bits.length > 0 344 | ) { 345 | while (bools.length < featureBits.extra_bits.start_bit) { 346 | bools.push(false) 347 | } 348 | bools = bools.concat(featureBits.extra_bits.bits) 349 | } 350 | if (wordsLength !== undefined && bools.length / 5 > wordsLength) { 351 | throw new Error('word_length is too small to contain all featureBits') 352 | } else if (wordsLength === undefined) { 353 | wordsLength = Math.ceil(bools.length / 5) 354 | } 355 | return new Array(wordsLength).fill(0).map((_, index) => 356 | bools[index * 5 + 4] << 4 | 357 | bools[index * 5 + 3] << 3 | 358 | bools[index * 5 + 2] << 2 | 359 | bools[index * 5 + 1] << 1 | 360 | bools[index * 5] << 0 361 | ).reverse() 362 | } 363 | 364 | // routing info is encoded first as a large buffer 365 | // 51 bytes for each channel 366 | // 33 byte pubkey, 8 byte short_channel_id, 4 byte millisatoshi base fee (left padded) 367 | // 4 byte fee proportional millionths and a 2 byte left padded CLTV expiry delta. 368 | // after encoding these 51 byte chunks and concatenating them 369 | // convert to words right padding 0 bits. 370 | function routingInfoEncoder (datas) { 371 | let buffer = Buffer.from([]) 372 | datas.forEach(data => { 373 | buffer = Buffer.concat([buffer, hexToBuffer(data.pubkey)]) 374 | buffer = Buffer.concat([buffer, hexToBuffer(data.short_channel_id)]) 375 | buffer = Buffer.concat([buffer, Buffer.from([0, 0, 0].concat(intBEToWords(data.fee_base_msat, 8)).slice(-4))]) 376 | buffer = Buffer.concat([buffer, Buffer.from([0, 0, 0].concat(intBEToWords(data.fee_proportional_millionths, 8)).slice(-4))]) 377 | buffer = Buffer.concat([buffer, Buffer.from([0].concat(intBEToWords(data.cltv_expiry_delta, 8)).slice(-2))]) 378 | }) 379 | return hexToWord(buffer) 380 | } 381 | 382 | // if text, return the sha256 hash of the text as words. 383 | // if hex, return the words representation of that data. 384 | function purposeCommitEncoder (data) { 385 | let buffer 386 | if (data !== undefined && (typeof data === 'string' || data instanceof String)) { 387 | if (data.match(/^([a-zA-Z0-9]{2})*$/)) { 388 | buffer = Buffer.from(data, 'hex') 389 | } else { 390 | buffer = sha256(Buffer.from(data, 'utf8')) 391 | } 392 | } else { 393 | throw new Error('purpose or purpose commit must be a string or hex string') 394 | } 395 | return bech32.toWords(buffer) 396 | } 397 | 398 | function tagsItems (tags, tagName) { 399 | const tag = tags.filter(item => item.tagName === tagName) 400 | const data = tag.length > 0 ? tag[0].data : null 401 | return data 402 | } 403 | 404 | function tagsContainItem (tags, tagName) { 405 | return tagsItems(tags, tagName) !== null 406 | } 407 | 408 | function orderKeys (unorderedObj, forDecode) { 409 | const orderedObj = {} 410 | Object.keys(unorderedObj).sort().forEach((key) => { 411 | orderedObj[key] = unorderedObj[key] 412 | }) 413 | if (forDecode === true) { 414 | const cacheName = '__tagsObject_cache' 415 | Object.defineProperty(orderedObj, 'tagsObject', { 416 | get () { 417 | if (!this[cacheName]) { 418 | Object.defineProperty(this, cacheName, { 419 | value: getTagsObject(this.tags) 420 | }) 421 | } 422 | return this[cacheName] 423 | } 424 | }) 425 | } 426 | return orderedObj 427 | } 428 | 429 | function satToHrp (satoshis) { 430 | if (!satoshis.toString().match(/^\d+$/)) { 431 | throw new Error('satoshis must be an integer') 432 | } 433 | const millisatoshisBN = new BN(satoshis, 10) 434 | return millisatToHrp(millisatoshisBN.mul(new BN(1000, 10))) 435 | } 436 | 437 | function millisatToHrp (millisatoshis) { 438 | if (!millisatoshis.toString().match(/^\d+$/)) { 439 | throw new Error('millisatoshis must be an integer') 440 | } 441 | const millisatoshisBN = new BN(millisatoshis, 10) 442 | const millisatoshisString = millisatoshisBN.toString(10) 443 | const millisatoshisLength = millisatoshisString.length 444 | let divisorString, valueString 445 | if (millisatoshisLength > 11 && /0{11}$/.test(millisatoshisString)) { 446 | divisorString = '' 447 | valueString = millisatoshisBN.div(MILLISATS_PER_BTC).toString(10) 448 | } else if (millisatoshisLength > 8 && /0{8}$/.test(millisatoshisString)) { 449 | divisorString = 'm' 450 | valueString = millisatoshisBN.div(MILLISATS_PER_MILLIBTC).toString(10) 451 | } else if (millisatoshisLength > 5 && /0{5}$/.test(millisatoshisString)) { 452 | divisorString = 'u' 453 | valueString = millisatoshisBN.div(MILLISATS_PER_MICROBTC).toString(10) 454 | } else if (millisatoshisLength > 2 && /0{2}$/.test(millisatoshisString)) { 455 | divisorString = 'n' 456 | valueString = millisatoshisBN.div(MILLISATS_PER_NANOBTC).toString(10) 457 | } else { 458 | divisorString = 'p' 459 | valueString = millisatoshisBN.mul(PICOBTC_PER_MILLISATS).toString(10) 460 | } 461 | return valueString + divisorString 462 | } 463 | 464 | function hrpToSat (hrpString, outputString) { 465 | const millisatoshisBN = hrpToMillisat(hrpString, false) 466 | if (!millisatoshisBN.mod(new BN(1000, 10)).eq(new BN(0, 10))) { 467 | throw new Error('Amount is outside of valid range') 468 | } 469 | const result = millisatoshisBN.div(new BN(1000, 10)) 470 | return outputString ? result.toString() : result 471 | } 472 | 473 | function hrpToMillisat (hrpString, outputString) { 474 | let divisor, value 475 | if (hrpString.slice(-1).match(/^[munp]$/)) { 476 | divisor = hrpString.slice(-1) 477 | value = hrpString.slice(0, -1) 478 | } else if (hrpString.slice(-1).match(/^[^munp0-9]$/)) { 479 | throw new Error('Not a valid multiplier for the amount') 480 | } else { 481 | value = hrpString 482 | } 483 | 484 | if (!value.match(/^\d+$/)) throw new Error('Not a valid human readable amount') 485 | 486 | const valueBN = new BN(value, 10) 487 | 488 | const millisatoshisBN = divisor 489 | ? valueBN.mul(MILLISATS_PER_BTC).div(DIVISORS[divisor]) 490 | : valueBN.mul(MILLISATS_PER_BTC) 491 | 492 | if (((divisor === 'p' && !valueBN.mod(new BN(10, 10)).eq(new BN(0, 10))) || 493 | millisatoshisBN.gt(MAX_MILLISATS))) { 494 | throw new Error('Amount is outside of valid range') 495 | } 496 | 497 | return outputString ? millisatoshisBN.toString() : millisatoshisBN 498 | } 499 | 500 | function sign (inputPayReqObj, inputPrivateKey) { 501 | const payReqObj = cloneDeep(inputPayReqObj) 502 | const privateKey = hexToBuffer(inputPrivateKey) 503 | if (payReqObj.complete && payReqObj.paymentRequest) return payReqObj 504 | 505 | if (privateKey === undefined || privateKey.length !== 32 || 506 | !secp256k1.privateKeyVerify(privateKey)) { 507 | throw new Error('privateKey must be a 32 byte Buffer and valid private key') 508 | } 509 | 510 | let nodePublicKey, tagNodePublicKey 511 | // If there is a payee_node_key tag convert to buffer 512 | if (tagsContainItem(payReqObj.tags, TAGNAMES['19'])) { 513 | tagNodePublicKey = hexToBuffer(tagsItems(payReqObj.tags, TAGNAMES['19'])) 514 | } 515 | // If there is payeeNodeKey attribute, convert to buffer 516 | if (payReqObj.payeeNodeKey) { 517 | nodePublicKey = hexToBuffer(payReqObj.payeeNodeKey) 518 | } 519 | // If they are not equal throw an error 520 | if (nodePublicKey && tagNodePublicKey && !tagNodePublicKey.equals(nodePublicKey)) { 521 | throw new Error('payee node key tag and payeeNodeKey attribute must match') 522 | } 523 | 524 | // make sure if either exist they are in nodePublicKey 525 | nodePublicKey = tagNodePublicKey || nodePublicKey 526 | 527 | const publicKey = Buffer.from(secp256k1.publicKeyCreate(privateKey)) 528 | 529 | // Check if pubkey matches for private key 530 | if (nodePublicKey && !publicKey.equals(nodePublicKey)) { 531 | throw new Error('The private key given is not the private key of the node public key given') 532 | } 533 | 534 | const words = bech32.decode(payReqObj.wordsTemp, Number.MAX_SAFE_INTEGER).words 535 | 536 | // the preimage for the signing data is the buffer of the prefix concatenated 537 | // with the buffer conversion of the data words excluding the signature 538 | // (right padded with 0 bits) 539 | const toSign = Buffer.concat([Buffer.from(payReqObj.prefix, 'utf8'), wordsToBuffer(words)]) 540 | // single SHA256 hash for the signature 541 | const payReqHash = sha256(toSign) 542 | 543 | // signature is 64 bytes (32 byte r value and 32 byte s value concatenated) 544 | // PLUS one extra byte appended to the right with the recoveryID in [0,1,2,3] 545 | // Then convert to 5 bit words with right padding 0 bits. 546 | const sigObj = secp256k1.ecdsaSign(payReqHash, privateKey) 547 | sigObj.signature = Buffer.from(sigObj.signature) 548 | const sigWords = hexToWord(sigObj.signature.toString('hex') + '0' + sigObj.recid) 549 | 550 | // append signature words to the words, mark as complete, and add the payreq 551 | payReqObj.payeeNodeKey = publicKey.toString('hex') 552 | payReqObj.signature = sigObj.signature.toString('hex') 553 | payReqObj.recoveryFlag = sigObj.recid 554 | payReqObj.wordsTemp = bech32.encode('temp', words.concat(sigWords), Number.MAX_SAFE_INTEGER) 555 | payReqObj.complete = true 556 | payReqObj.paymentRequest = bech32.encode(payReqObj.prefix, words.concat(sigWords), Number.MAX_SAFE_INTEGER) 557 | 558 | return orderKeys(payReqObj) 559 | } 560 | 561 | function encode (inputData, addDefaults) { 562 | // we don't want to affect the data being passed in, so we copy the object 563 | const data = cloneDeep(inputData) 564 | 565 | // by default we will add default values to description, expire time, and min cltv 566 | if (addDefaults === undefined) addDefaults = true 567 | 568 | const canReconstruct = !(data.signature === undefined || data.recoveryFlag === undefined) 569 | 570 | // if no cointype is defined, set to testnet 571 | let coinTypeObj 572 | if (data.network === undefined && !canReconstruct) { 573 | data.network = DEFAULTNETWORK 574 | coinTypeObj = DEFAULTNETWORK 575 | } else if (data.network === undefined && canReconstruct) { 576 | throw new Error('Need network for proper payment request reconstruction') 577 | } else { 578 | // if the coinType is not a valid name of a network in bitcoinjs-lib, fail 579 | if ( 580 | !data.network.bech32 || 581 | data.network.pubKeyHash === undefined || 582 | data.network.scriptHash === undefined || 583 | !Array.isArray(data.network.validWitnessVersions) 584 | ) throw new Error('Invalid network') 585 | coinTypeObj = data.network 586 | } 587 | 588 | // use current time as default timestamp (seconds) 589 | if (data.timestamp === undefined && !canReconstruct) { 590 | data.timestamp = Math.floor(new Date().getTime() / 1000) 591 | } else if (data.timestamp === undefined && canReconstruct) { 592 | throw new Error('Need timestamp for proper payment request reconstruction') 593 | } 594 | 595 | if (data.tags === undefined) throw new Error('Payment Requests need tags array') 596 | 597 | // If no payment hash, fail 598 | if (!tagsContainItem(data.tags, TAGNAMES['1'])) { 599 | throw new Error('Lightning Payment Request needs a payment hash') 600 | } 601 | // If no feature bits when payment secret is found, fail 602 | if (tagsContainItem(data.tags, TAGNAMES['16'])) { 603 | if (!tagsContainItem(data.tags, TAGNAMES['5'])) { 604 | if (addDefaults) { 605 | data.tags.push({ 606 | tagName: TAGNAMES['5'], 607 | data: DEFAULTFEATUREBITS 608 | }) 609 | } else { 610 | throw new Error('Payment request requires feature bits with at least payment secret support flagged if payment secret is included') 611 | } 612 | } else { 613 | const fB = tagsItems(data.tags, TAGNAMES['5']) 614 | if (!fB.payment_secret || (!fB.payment_secret.supported && !fB.payment_secret.required)) { 615 | throw new Error('Payment request requires feature bits with at least payment secret support flagged if payment secret is included') 616 | } 617 | } 618 | } 619 | // If no description or purpose commit hash/message, fail 620 | if (!tagsContainItem(data.tags, TAGNAMES['13']) && !tagsContainItem(data.tags, TAGNAMES['23'])) { 621 | if (addDefaults) { 622 | data.tags.push({ 623 | tagName: TAGNAMES['13'], 624 | data: DEFAULTDESCRIPTION 625 | }) 626 | } else { 627 | throw new Error('Payment request requires description or purpose commit hash') 628 | } 629 | } 630 | 631 | // If a description exists, check to make sure the buffer isn't greater than 632 | // 639 bytes long, since 639 * 8 / 5 = 1023 words (5 bit) when padded 633 | if (tagsContainItem(data.tags, TAGNAMES['13']) && 634 | Buffer.from(tagsItems(data.tags, TAGNAMES['13']), 'utf8').length > 639) { 635 | throw new Error('Description is too long: Max length 639 bytes') 636 | } 637 | 638 | // if there's no expire time, and it is not reconstructing (must have private key) 639 | // default to adding a 3600 second expire time (1 hour) 640 | if (!tagsContainItem(data.tags, TAGNAMES['6']) && !canReconstruct && addDefaults) { 641 | data.tags.push({ 642 | tagName: TAGNAMES['6'], 643 | data: DEFAULTEXPIRETIME 644 | }) 645 | } 646 | 647 | // if there's no minimum cltv time, and it is not reconstructing (must have private key) 648 | // default to adding a 9 block minimum cltv time (90 minutes for bitcoin) 649 | if (!tagsContainItem(data.tags, TAGNAMES['24']) && !canReconstruct && addDefaults) { 650 | data.tags.push({ 651 | tagName: TAGNAMES['24'], 652 | data: DEFAULTCLTVEXPIRY 653 | }) 654 | } 655 | 656 | let nodePublicKey, tagNodePublicKey 657 | // If there is a payee_node_key tag convert to buffer 658 | if (tagsContainItem(data.tags, TAGNAMES['19'])) tagNodePublicKey = hexToBuffer(tagsItems(data.tags, TAGNAMES['19'])) 659 | // If there is payeeNodeKey attribute, convert to buffer 660 | if (data.payeeNodeKey) nodePublicKey = hexToBuffer(data.payeeNodeKey) 661 | if (nodePublicKey && tagNodePublicKey && !tagNodePublicKey.equals(nodePublicKey)) { 662 | throw new Error('payeeNodeKey and tag payee node key do not match') 663 | } 664 | // in case we have one or the other, make sure it's in nodePublicKey 665 | nodePublicKey = nodePublicKey || tagNodePublicKey 666 | if (nodePublicKey) data.payeeNodeKey = nodePublicKey.toString('hex') 667 | 668 | let code, addressHash, address 669 | // If there is a fallback address tag we must check it is valid 670 | if (tagsContainItem(data.tags, TAGNAMES['9'])) { 671 | const addrData = tagsItems(data.tags, TAGNAMES['9']) 672 | // Most people will just provide address so Hash and code will be undefined here 673 | address = addrData.address 674 | addressHash = addrData.addressHash 675 | code = addrData.code 676 | 677 | if (addressHash === undefined || code === undefined) { 678 | let bech32addr, base58addr 679 | try { 680 | bech32addr = bitcoinjsAddress.fromBech32(address) 681 | addressHash = bech32addr.data 682 | code = bech32addr.version 683 | } catch (e) { 684 | try { 685 | base58addr = bitcoinjsAddress.fromBase58Check(address) 686 | if (base58addr.version === coinTypeObj.pubKeyHash) { 687 | code = 17 688 | } else if (base58addr.version === coinTypeObj.scriptHash) { 689 | code = 18 690 | } 691 | addressHash = base58addr.hash 692 | } catch (f) { 693 | throw new Error('Fallback address type is unknown') 694 | } 695 | } 696 | if (bech32addr && !(bech32addr.version in coinTypeObj.validWitnessVersions)) { 697 | throw new Error('Fallback address witness version is unknown') 698 | } 699 | if (bech32addr && bech32addr.prefix !== coinTypeObj.bech32) { 700 | throw new Error('Fallback address network type does not match payment request network type') 701 | } 702 | if (base58addr && base58addr.version !== coinTypeObj.pubKeyHash && 703 | base58addr.version !== coinTypeObj.scriptHash) { 704 | throw new Error('Fallback address version (base58) is unknown or the network type is incorrect') 705 | } 706 | 707 | // FIXME: If addressHash or code is missing, add them to the original Object 708 | // after parsing the address value... this changes the actual attributes of the data object. 709 | // Not very clean. 710 | // Without this, a person can not specify a fallback address tag with only the address key. 711 | addrData.addressHash = addressHash.toString('hex') 712 | addrData.code = code 713 | } 714 | } 715 | 716 | // If there is route info tag, check that each route has all 4 necessary info 717 | if (tagsContainItem(data.tags, TAGNAMES['3'])) { 718 | const routingInfo = tagsItems(data.tags, TAGNAMES['3']) 719 | routingInfo.forEach(route => { 720 | if (route.pubkey === undefined || 721 | route.short_channel_id === undefined || 722 | route.fee_base_msat === undefined || 723 | route.fee_proportional_millionths === undefined || 724 | route.cltv_expiry_delta === undefined) { 725 | throw new Error('Routing info is incomplete') 726 | } 727 | if (!secp256k1.publicKeyVerify(hexToBuffer(route.pubkey))) { 728 | throw new Error('Routing info pubkey is not a valid pubkey') 729 | } 730 | const shortId = hexToBuffer(route.short_channel_id) 731 | if (!(shortId instanceof Buffer) || shortId.length !== 8) { 732 | throw new Error('Routing info short channel id must be 8 bytes') 733 | } 734 | if (typeof route.fee_base_msat !== 'number' || 735 | Math.floor(route.fee_base_msat) !== route.fee_base_msat) { 736 | throw new Error('Routing info fee base msat is not an integer') 737 | } 738 | if (typeof route.fee_proportional_millionths !== 'number' || 739 | Math.floor(route.fee_proportional_millionths) !== route.fee_proportional_millionths) { 740 | throw new Error('Routing info fee proportional millionths is not an integer') 741 | } 742 | if (typeof route.cltv_expiry_delta !== 'number' || 743 | Math.floor(route.cltv_expiry_delta) !== route.cltv_expiry_delta) { 744 | throw new Error('Routing info cltv expiry delta is not an integer') 745 | } 746 | }) 747 | } 748 | 749 | let prefix = 'ln' 750 | prefix += coinTypeObj.bech32 751 | 752 | let hrpString 753 | // calculate the smallest possible integer (removing zeroes) and add the best 754 | // divisor (m = milli, u = micro, n = nano, p = pico) 755 | if (data.millisatoshis && data.satoshis) { 756 | hrpString = millisatToHrp(new BN(data.millisatoshis, 10)) 757 | const hrpStringSat = satToHrp(new BN(data.satoshis, 10)) 758 | if (hrpStringSat !== hrpString) { 759 | throw new Error('satoshis and millisatoshis do not match') 760 | } 761 | } else if (data.millisatoshis) { 762 | hrpString = millisatToHrp(new BN(data.millisatoshis, 10)) 763 | } else if (data.satoshis) { 764 | hrpString = satToHrp(new BN(data.satoshis, 10)) 765 | } else { 766 | hrpString = '' 767 | } 768 | 769 | // bech32 human readable part is lnbc2500m (ln + coinbech32 + satoshis (optional)) 770 | // lnbc or lntb would be valid as well. (no value specified) 771 | prefix += hrpString 772 | 773 | // timestamp converted to 5 bit number array (left padded with 0 bits, NOT right padded) 774 | const timestampWords = intBEToWords(data.timestamp) 775 | while (timestampWords.length < 7) { 776 | timestampWords.unshift(0) 777 | } 778 | 779 | const tags = data.tags 780 | let tagWords = [] 781 | tags.forEach(tag => { 782 | const possibleTagNames = Object.keys(TAGENCODERS) 783 | if (canReconstruct) possibleTagNames.push(unknownTagName) 784 | // check if the tagName exists in the encoders object, if not throw Error. 785 | if (possibleTagNames.indexOf(tag.tagName) === -1) { 786 | throw new Error('Unknown tag key: ' + tag.tagName) 787 | } 788 | 789 | let words 790 | if (tag.tagName !== unknownTagName) { 791 | // each tag starts with 1 word code for the tag 792 | tagWords.push(TAGCODES[tag.tagName]) 793 | 794 | const encoder = TAGENCODERS[tag.tagName] 795 | words = encoder(tag.data) 796 | } else { 797 | const result = unknownEncoder(tag.data) 798 | tagWords.push(result.tagCode) 799 | words = result.words 800 | } 801 | // after the tag code, 2 words are used to store the length (in 5 bit words) of the tag data 802 | // (also left padded, most integers are left padded while buffers are right padded) 803 | tagWords = tagWords.concat([0].concat(intBEToWords(words.length)).slice(-2)) 804 | // then append the tag data words 805 | tagWords = tagWords.concat(words) 806 | }) 807 | 808 | // the data part of the bech32 is TIMESTAMP || TAGS || SIGNATURE 809 | // currently dataWords = TIMESTAMP || TAGS 810 | let dataWords = timestampWords.concat(tagWords) 811 | 812 | // the preimage for the signing data is the buffer of the prefix concatenated 813 | // with the buffer conversion of the data words excluding the signature 814 | // (right padded with 0 bits) 815 | const toSign = Buffer.concat([Buffer.from(prefix, 'utf8'), Buffer.from(convert(dataWords, 5, 8))]) 816 | // single SHA256 hash for the signature 817 | const payReqHash = sha256(toSign) 818 | 819 | // signature is 64 bytes (32 byte r value and 32 byte s value concatenated) 820 | // PLUS one extra byte appended to the right with the recoveryID in [0,1,2,3] 821 | // Then convert to 5 bit words with right padding 0 bits. 822 | let sigWords 823 | if (canReconstruct) { 824 | /* Since BOLT11 does not require a payee_node_key tag in the specs, 825 | most parsers will have to recover the pubkey from the signature 826 | To ensure the tag data has been provided in the right order etc. 827 | we should check that the data we got and the node key given match when 828 | reconstructing a payment request from given signature and recoveryID. 829 | However, if a privatekey is given, the caller is the privkey owner. 830 | Earlier we check if the private key matches the payee node key IF they 831 | gave one. */ 832 | if (nodePublicKey) { 833 | const recoveredPubkey = Buffer.from(secp256k1.ecdsaRecover(Buffer.from(data.signature, 'hex'), data.recoveryFlag, payReqHash, true)) 834 | if (nodePublicKey && !nodePublicKey.equals(recoveredPubkey)) { 835 | throw new Error('Signature, message, and recoveryID did not produce the same pubkey as payeeNodeKey') 836 | } 837 | sigWords = hexToWord(data.signature + '0' + data.recoveryFlag) 838 | } else { 839 | throw new Error('Reconstruction with signature and recoveryID requires payeeNodeKey to verify correctness of input data.') 840 | } 841 | } 842 | 843 | if (sigWords) dataWords = dataWords.concat(sigWords) 844 | 845 | if (tagsContainItem(data.tags, TAGNAMES['6'])) { 846 | data.timeExpireDate = data.timestamp + tagsItems(data.tags, TAGNAMES['6']) 847 | data.timeExpireDateString = new Date(data.timeExpireDate * 1000).toISOString() 848 | } 849 | data.timestampString = new Date(data.timestamp * 1000).toISOString() 850 | data.complete = !!sigWords 851 | data.paymentRequest = data.complete ? bech32.encode(prefix, dataWords, Number.MAX_SAFE_INTEGER) : '' 852 | data.prefix = prefix 853 | data.wordsTemp = bech32.encode('temp', dataWords, Number.MAX_SAFE_INTEGER) 854 | 855 | // payment requests get pretty long. Nothing in the spec says anything about length. 856 | // Even though bech32 loses error correction power over 1023 characters. 857 | return orderKeys(data) 858 | } 859 | 860 | // decode will only have extra comments that aren't covered in encode comments. 861 | // also if anything is hard to read I'll comment. 862 | function decode (paymentRequest, network) { 863 | if (typeof paymentRequest !== 'string') throw new Error('Lightning Payment Request must be string') 864 | if (paymentRequest.slice(0, 2).toLowerCase() !== 'ln') throw new Error('Not a proper lightning payment request') 865 | const decoded = bech32.decode(paymentRequest, Number.MAX_SAFE_INTEGER) 866 | paymentRequest = paymentRequest.toLowerCase() 867 | const prefix = decoded.prefix 868 | let words = decoded.words 869 | 870 | // signature is always 104 words on the end 871 | // cutting off at the beginning helps since there's no way to tell 872 | // ahead of time how many tags there are. 873 | const sigWords = words.slice(-104) 874 | // grabbing a copy of the words for later, words will be sliced as we parse. 875 | const wordsNoSig = words.slice(0, -104) 876 | words = words.slice(0, -104) 877 | 878 | let sigBuffer = wordsToBuffer(sigWords, true) 879 | const recoveryFlag = sigBuffer.slice(-1)[0] 880 | sigBuffer = sigBuffer.slice(0, -1) 881 | 882 | if (!(recoveryFlag in [0, 1, 2, 3]) || sigBuffer.length !== 64) { 883 | throw new Error('Signature is missing or incorrect') 884 | } 885 | 886 | // Without reverse lookups, can't say that the multipier at the end must 887 | // have a number before it, so instead we parse, and if the second group 888 | // doesn't have anything, there's a good chance the last letter of the 889 | // coin type got captured by the third group, so just re-regex without 890 | // the number. 891 | let prefixMatches = prefix.match(/^ln(\S+?)(\d*)([a-zA-Z]?)$/) 892 | if (prefixMatches && !prefixMatches[2]) prefixMatches = prefix.match(/^ln(\S+)$/) 893 | if (!prefixMatches) { 894 | throw new Error('Not a proper lightning payment request') 895 | } 896 | 897 | const bech32Prefix = prefixMatches[1] 898 | let coinNetwork 899 | if (!network) { 900 | switch (bech32Prefix) { 901 | case DEFAULTNETWORK.bech32: 902 | coinNetwork = DEFAULTNETWORK 903 | break 904 | case TESTNETWORK.bech32: 905 | coinNetwork = TESTNETWORK 906 | break 907 | case REGTESTNETWORK.bech32: 908 | coinNetwork = REGTESTNETWORK 909 | break 910 | case SIMNETWORK.bech32: 911 | coinNetwork = SIMNETWORK 912 | break 913 | } 914 | } else { 915 | if ( 916 | network.bech32 === undefined || 917 | network.pubKeyHash === undefined || 918 | network.scriptHash === undefined || 919 | !Array.isArray(network.validWitnessVersions) 920 | ) throw new Error('Invalid network') 921 | coinNetwork = network 922 | } 923 | if (!coinNetwork || coinNetwork.bech32 !== bech32Prefix) { 924 | throw new Error('Unknown coin bech32 prefix') 925 | } 926 | 927 | const value = prefixMatches[2] 928 | let satoshis, millisatoshis, removeSatoshis 929 | if (value) { 930 | const divisor = prefixMatches[3] 931 | try { 932 | satoshis = parseInt(hrpToSat(value + divisor, true)) 933 | } catch (e) { 934 | satoshis = null 935 | removeSatoshis = true 936 | } 937 | millisatoshis = hrpToMillisat(value + divisor, true) 938 | } else { 939 | satoshis = null 940 | millisatoshis = null 941 | } 942 | 943 | // reminder: left padded 0 bits 944 | const timestamp = wordsToIntBE(words.slice(0, 7)) 945 | const timestampString = new Date(timestamp * 1000).toISOString() 946 | words = words.slice(7) // trim off the left 7 words 947 | 948 | const tags = [] 949 | let tagName, parser, tagLength, tagWords 950 | // we have no tag count to go on, so just keep hacking off words 951 | // until we have none. 952 | while (words.length > 0) { 953 | const tagCode = words[0].toString() 954 | tagName = TAGNAMES[tagCode] || unknownTagName 955 | parser = TAGPARSERS[tagCode] || getUnknownParser(tagCode) 956 | words = words.slice(1) 957 | 958 | tagLength = wordsToIntBE(words.slice(0, 2)) 959 | words = words.slice(2) 960 | 961 | tagWords = words.slice(0, tagLength) 962 | words = words.slice(tagLength) 963 | 964 | // See: parsers for more comments 965 | tags.push({ 966 | tagName, 967 | data: parser(tagWords, coinNetwork) // only fallback address needs coinNetwork 968 | }) 969 | } 970 | 971 | let timeExpireDate, timeExpireDateString 972 | // be kind and provide an absolute expiration date. 973 | // good for logs 974 | if (tagsContainItem(tags, TAGNAMES['6'])) { 975 | timeExpireDate = timestamp + tagsItems(tags, TAGNAMES['6']) 976 | timeExpireDateString = new Date(timeExpireDate * 1000).toISOString() 977 | } 978 | 979 | const toSign = Buffer.concat([Buffer.from(prefix, 'utf8'), Buffer.from(convert(wordsNoSig, 5, 8))]) 980 | const payReqHash = sha256(toSign) 981 | const sigPubkey = Buffer.from(secp256k1.ecdsaRecover(sigBuffer, recoveryFlag, payReqHash, true)) 982 | if (tagsContainItem(tags, TAGNAMES['19']) && tagsItems(tags, TAGNAMES['19']) !== sigPubkey.toString('hex')) { 983 | throw new Error('Lightning Payment Request signature pubkey does not match payee pubkey') 984 | } 985 | 986 | let finalResult = { 987 | paymentRequest, 988 | complete: true, 989 | prefix, 990 | wordsTemp: bech32.encode('temp', wordsNoSig.concat(sigWords), Number.MAX_SAFE_INTEGER), 991 | network: coinNetwork, 992 | satoshis, 993 | millisatoshis, 994 | timestamp, 995 | timestampString, 996 | payeeNodeKey: sigPubkey.toString('hex'), 997 | signature: sigBuffer.toString('hex'), 998 | recoveryFlag, 999 | tags 1000 | } 1001 | 1002 | if (removeSatoshis) { 1003 | delete finalResult.satoshis 1004 | } 1005 | 1006 | if (timeExpireDate) { 1007 | finalResult = Object.assign(finalResult, { timeExpireDate, timeExpireDateString }) 1008 | } 1009 | 1010 | return orderKeys(finalResult, true) 1011 | } 1012 | 1013 | function getTagsObject (tags) { 1014 | const result = {} 1015 | tags.forEach(tag => { 1016 | if (tag.tagName === unknownTagName) { 1017 | if (!result.unknownTags) { 1018 | result.unknownTags = [] 1019 | } 1020 | result.unknownTags.push(tag.data) 1021 | } else { 1022 | result[tag.tagName] = tag.data 1023 | } 1024 | }) 1025 | 1026 | return result 1027 | } 1028 | 1029 | module.exports = { 1030 | encode, 1031 | decode, 1032 | sign, 1033 | satToHrp, 1034 | millisatToHrp, 1035 | hrpToSat, 1036 | hrpToMillisat 1037 | } 1038 | -------------------------------------------------------------------------------- /test/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "satToHrp": { 3 | "valid": [ 4 | { 5 | "input": 1, 6 | "output": "10n" 7 | }, 8 | { 9 | "input": 10, 10 | "output": "100n" 11 | }, 12 | { 13 | "input": 100, 14 | "output": "1u" 15 | }, 16 | { 17 | "input": 100000, 18 | "output": "1m" 19 | }, 20 | { 21 | "input": 100000000, 22 | "output": "1" 23 | }, 24 | { 25 | "input": 123456789, 26 | "output": "1234567890n" 27 | }, 28 | { 29 | "input": 123450000, 30 | "output": "1234500u" 31 | }, 32 | { 33 | "input": 123400000, 34 | "output": "1234m" 35 | } 36 | ], 37 | "invalid": [ 38 | { 39 | "input": "10.2", 40 | "error": "satoshis must be an integer" 41 | }, 42 | { 43 | "input": 34.7, 44 | "error": "satoshis must be an integer" 45 | } 46 | ] 47 | }, 48 | "millisatToHrp": { 49 | "valid": [ 50 | { 51 | "input": 1, 52 | "output": "10p" 53 | } 54 | ], 55 | "invalid": [ 56 | { 57 | "input": "10.2", 58 | "error": "millisatoshis must be an integer" 59 | } 60 | ] 61 | }, 62 | "hrpToSat": { 63 | "valid": [ 64 | { 65 | "input": "10n", 66 | "output": "1" 67 | }, 68 | { 69 | "input": "1u", 70 | "output": "100" 71 | }, 72 | { 73 | "input": "1m", 74 | "output": "100000" 75 | }, 76 | { 77 | "input": "1", 78 | "output": "100000000" 79 | }, 80 | { 81 | "input": "1234567890n", 82 | "output": "123456789" 83 | }, 84 | { 85 | "input": "1234567u", 86 | "output": "123456700" 87 | }, 88 | { 89 | "input": "1234m", 90 | "output": "123400000" 91 | } 92 | ], 93 | "invalid": [ 94 | { 95 | "input": "10x", 96 | "error": "Not a valid multiplier for the amount" 97 | }, 98 | { 99 | "input": "1f0", 100 | "error": "Not a valid human readable amount" 101 | }, 102 | { 103 | "input": "9n", 104 | "error": "Amount is outside of valid range" 105 | }, 106 | { 107 | "input": "5670p", 108 | "error": "Amount is outside of valid range" 109 | }, 110 | { 111 | "input": "21000001", 112 | "error": "Amount is outside of valid range" 113 | } 114 | ] 115 | }, 116 | "hrpToMillisat": { 117 | "valid": [ 118 | { 119 | "input": "10p", 120 | "output": "1" 121 | } 122 | ], 123 | "invalid": [ 124 | { 125 | "input": "8p", 126 | "error": "Amount is outside of valid range" 127 | } 128 | ] 129 | }, 130 | "sign": { 131 | "invalid": [ 132 | { 133 | "data": { 134 | "network": { 135 | "bech32": "bc", 136 | "pubKeyHash": 0, 137 | "scriptHash": 5, 138 | "validWitnessVersions": [0, 1] 139 | }, 140 | "complete": false, 141 | "satoshis": null, 142 | "millisatoshis": null, 143 | "paymentRequest": "", 144 | "prefix": "lnbc", 145 | "tags": [ 146 | { 147 | "tagName": "payment_hash", 148 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 149 | }, 150 | { 151 | "tagName": "description", 152 | "data": "Please consider supporting this project" 153 | } 154 | ], 155 | "timestamp": 1513236122, 156 | "timestampString": "2017-12-14T07:22:02.000Z", 157 | "wordsTemp": "temp1pdryf56pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqfg6ad2" 158 | }, 159 | "privateKey": "e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca", 160 | "error": "privateKey must be a 32 byte Buffer and valid private key" 161 | }, 162 | { 163 | "data": { 164 | "network": { 165 | "bech32": "bc", 166 | "pubKeyHash": 0, 167 | "scriptHash": 5, 168 | "validWitnessVersions": [0, 1] 169 | }, 170 | "complete": false, 171 | "satoshis": null, 172 | "millisatoshis": null, 173 | "paymentRequest": "", 174 | "prefix": "lnbc", 175 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 176 | "tags": [ 177 | { 178 | "tagName": "payment_hash", 179 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 180 | }, 181 | { 182 | "tagName": "description", 183 | "data": "Please consider supporting this project" 184 | }, 185 | { 186 | "tagName": "payee_node_key", 187 | "data": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f93ffffff" 188 | } 189 | ], 190 | "timestamp": 1513236122, 191 | "timestampString": "2017-12-14T07:22:02.000Z", 192 | "wordsTemp": "temp1pdryf56pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqfg6ad2" 193 | }, 194 | "error": "payee node key tag and payeeNodeKey attribute must match" 195 | }, 196 | { 197 | "data": { 198 | "network": { 199 | "bech32": "bc", 200 | "pubKeyHash": 0, 201 | "scriptHash": 5, 202 | "validWitnessVersions": [0, 1] 203 | }, 204 | "complete": false, 205 | "satoshis": null, 206 | "millisatoshis": null, 207 | "paymentRequest": "", 208 | "prefix": "lnbc", 209 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dffff", 210 | "tags": [ 211 | { 212 | "tagName": "payment_hash", 213 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 214 | }, 215 | { 216 | "tagName": "description", 217 | "data": "Please consider supporting this project" 218 | } 219 | ], 220 | "timestamp": 1513236122, 221 | "timestampString": "2017-12-14T07:22:02.000Z", 222 | "wordsTemp": "temp1pdryf56pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqfg6ad2" 223 | }, 224 | "error": "The private key given is not the private key of the node public key given" 225 | } 226 | ] 227 | }, 228 | "encode": { 229 | "valid": [ 230 | { 231 | "data": { 232 | "satoshis": 12, 233 | "network": { 234 | "bech32": "tb", 235 | "pubKeyHash": 111, 236 | "scriptHash": 196, 237 | "validWitnessVersions": [0, 1] 238 | }, 239 | "tags": [ 240 | { 241 | "tagName": "payment_hash", 242 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 243 | }, 244 | { 245 | "tagName": "description", 246 | "data": "ナンセンス 1杯" 247 | } 248 | ] 249 | } 250 | }, 251 | { 252 | "data": { 253 | "satoshis": 12, 254 | "network": { 255 | "bech32": "bcrt", 256 | "pubKeyHash": 111, 257 | "scriptHash": 196, 258 | "validWitnessVersions": [0, 1] 259 | }, 260 | "tags": [ 261 | { 262 | "tagName": "payment_hash", 263 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 264 | }, 265 | { 266 | "tagName": "description", 267 | "data": "ナンセンス 1杯" 268 | } 269 | ] 270 | } 271 | }, 272 | { 273 | "data": { 274 | "network": { 275 | "bech32": "bc", 276 | "pubKeyHash": 0, 277 | "scriptHash": 5, 278 | "validWitnessVersions": [0, 1] 279 | }, 280 | "satoshis": 2500000, 281 | "timestamp": 1496314658, 282 | "tags": [ 283 | { 284 | "tagName": "payment_hash", 285 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 286 | }, 287 | { 288 | "tagName": "expire_time", 289 | "data": 3600 290 | }, 291 | { 292 | "tagName": "payment_secret", 293 | "data": "fffffffffffffffb74f54d269fe206be715000f94dac067d1c04a8ca3b2db734" 294 | }, 295 | { 296 | "tagName": "feature_bits", 297 | "data": { 298 | "option_data_loss_protect": { 299 | "required": false, 300 | "supported": false 301 | }, 302 | "initial_routing_sync": { 303 | "required": false, 304 | "supported": false 305 | }, 306 | "option_upfront_shutdown_script": { 307 | "required": false, 308 | "supported": false 309 | }, 310 | "gossip_queries": { 311 | "required": false, 312 | "supported": false 313 | }, 314 | "var_onion_optin": { 315 | "required": false, 316 | "supported": false 317 | }, 318 | "gossip_queries_ex": { 319 | "required": false, 320 | "supported": false 321 | }, 322 | "option_static_remotekey": { 323 | "required": false, 324 | "supported": false 325 | }, 326 | "payment_secret": { 327 | "required": true, 328 | "supported": true 329 | }, 330 | "basic_mpp": { 331 | "required": false, 332 | "supported": false 333 | }, 334 | "option_support_large_channel": { 335 | "required": false, 336 | "supported": false 337 | }, 338 | "extra_bits": { 339 | "start_bit": 24, 340 | "bits": [false, true], 341 | "has_required": false 342 | } 343 | } 344 | } 345 | ] 346 | } 347 | }, 348 | { 349 | "addDefaults": true, 350 | "data": { 351 | "network": { 352 | "bech32": "bc", 353 | "pubKeyHash": 0, 354 | "scriptHash": 5, 355 | "validWitnessVersions": [0, 1] 356 | }, 357 | "satoshis": 2500000, 358 | "timestamp": 1496314658, 359 | "tags": [ 360 | { 361 | "tagName": "payment_hash", 362 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 363 | }, 364 | { 365 | "tagName": "expire_time", 366 | "data": 3600 367 | }, 368 | { 369 | "tagName": "payment_secret", 370 | "data": "fffffffffffffffb74f54d269fe206be715000f94dac067d1c04a8ca3b2db734" 371 | } 372 | ] 373 | } 374 | } 375 | ], 376 | "invalid": [ 377 | { 378 | "data": { 379 | "network": { 380 | "boop": 9 381 | }, 382 | "tags": [ 383 | { 384 | "tagName": "payment_hash", 385 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 386 | }, 387 | { 388 | "tagName": "description", 389 | "data": "ナンセンス 1杯" 390 | } 391 | ] 392 | }, 393 | "error": "Invalid network" 394 | }, 395 | { 396 | "data": { 397 | "recoveryFlag": 1, 398 | "signature": "6249c3a38d2a7d78f54f5494cf79fa06949e11eaf26ede32158f8a8e796ba1b741e88c35bec6bf5ee8291a5d50a7d35549e7fc564f7d0a39d1db5f098a0d179a", 399 | "timestamp": 1513073744, 400 | "tags": [ 401 | { 402 | "tagName": "payment_hash", 403 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 404 | }, 405 | { 406 | "tagName": "description", 407 | "data": "ナンセンス 1杯" 408 | } 409 | ] 410 | }, 411 | "error": "Need network for proper payment request reconstruction" 412 | }, 413 | { 414 | "data": { 415 | "network": { 416 | "bech32": "bc", 417 | "pubKeyHash": 0, 418 | "scriptHash": 5, 419 | "validWitnessVersions": [0, 1] 420 | }, 421 | "recoveryFlag": 1, 422 | "signature": "6249c3a38d2a7d78f54f5494cf79fa06949e11eaf26ede32158f8a8e796ba1b741e88c35bec6bf5ee8291a5d50a7d35549e7fc564f7d0a39d1db5f098a0d179a", 423 | "tags": [ 424 | { 425 | "tagName": "payment_hash", 426 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 427 | }, 428 | { 429 | "tagName": "description", 430 | "data": "ナンセンス 1杯" 431 | } 432 | ] 433 | }, 434 | "error": "Need timestamp for proper payment request reconstruction" 435 | }, 436 | { 437 | "data": {}, 438 | "error": "Payment Requests need tags array" 439 | }, 440 | { 441 | "data": { 442 | "network": { 443 | "bech32": "bc", 444 | "pubKeyHash": 0, 445 | "scriptHash": 5, 446 | "validWitnessVersions": [0, 1] 447 | }, 448 | "tags": [ 449 | { 450 | "tagName": "description", 451 | "data": "ナンセンス 1杯" 452 | } 453 | ] 454 | }, 455 | "error": "Lightning Payment Request needs a payment hash" 456 | }, 457 | { 458 | "data": { 459 | "network": { 460 | "bech32": "bc", 461 | "pubKeyHash": 0, 462 | "scriptHash": 5, 463 | "validWitnessVersions": [0, 1] 464 | }, 465 | "tags": [ 466 | { 467 | "tagName": "payment_hash", 468 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 469 | } 470 | ] 471 | }, 472 | "error": "Payment request requires description or purpose commit hash", 473 | "addDefaults": false 474 | }, 475 | { 476 | "data": { 477 | "network": { 478 | "bech32": "bc", 479 | "pubKeyHash": 0, 480 | "scriptHash": 5, 481 | "validWitnessVersions": [0, 1] 482 | }, 483 | "payeeNodeKey": "03000102030405060708090001020304050607080900010203040506070809ffff", 484 | "tags": [ 485 | { 486 | "tagName": "payment_hash", 487 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 488 | }, 489 | { 490 | "tagName": "payee_node_key", 491 | "data": "030001020304050607080900010203040506070809000102030405060708090102" 492 | }, 493 | { 494 | "tagName": "description", 495 | "data": "ナンセンス 1杯" 496 | } 497 | ] 498 | }, 499 | "error": "payeeNodeKey and tag payee node key do not match" 500 | }, 501 | { 502 | "data": { 503 | "network": { 504 | "bech32": "tb", 505 | "pubKeyHash": 111, 506 | "scriptHash": 196, 507 | "validWitnessVersions": [0, 1] 508 | }, 509 | "tags": [ 510 | { 511 | "tagName": "payment_hash", 512 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 513 | }, 514 | { 515 | "tagName": "description", 516 | "data": "ナンセンス 1杯" 517 | }, 518 | { 519 | "tagName": "fallback_address", 520 | "data": { 521 | "address": "tb1zx93njcnp8qcnqde4vgckydpkv33rsdnpxccrwetp8qmn2vnrv4jnjdfs8q6rsdpksh0hkj" 522 | } 523 | } 524 | ] 525 | }, 526 | "error": "Fallback address witness version is unknown" 527 | }, 528 | { 529 | "data": { 530 | "network": { 531 | "bech32": "tb", 532 | "pubKeyHash": 111, 533 | "scriptHash": 196, 534 | "validWitnessVersions": [0, 1] 535 | }, 536 | "tags": [ 537 | { 538 | "tagName": "payment_hash", 539 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 540 | }, 541 | { 542 | "tagName": "description", 543 | "data": "ナンセンス 1杯" 544 | }, 545 | { 546 | "tagName": "fallback_address", 547 | "data": { 548 | "address": "bc1qqqqsyqcyq5rqwzqfpg9scrgwpuqsyqcy2f9nte" 549 | } 550 | } 551 | ] 552 | }, 553 | "error": "Fallback address network type does not match payment request network type" 554 | }, 555 | { 556 | "data": { 557 | "network": { 558 | "bech32": "tb", 559 | "pubKeyHash": 111, 560 | "scriptHash": 196, 561 | "validWitnessVersions": [0, 1] 562 | }, 563 | "tags": [ 564 | { 565 | "tagName": "payment_hash", 566 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 567 | }, 568 | { 569 | "tagName": "description", 570 | "data": "ナンセンス 1杯" 571 | }, 572 | { 573 | "tagName": "fallback_address", 574 | "data": { 575 | "address": "UmKe9X9WKo1vksgXkdhR45Rf1QKguAQwus" 576 | } 577 | } 578 | ] 579 | }, 580 | "error": "Fallback address version \\(base58\\) is unknown or the network type is incorrect" 581 | }, 582 | { 583 | "data": { 584 | "network": { 585 | "bech32": "tb", 586 | "pubKeyHash": 111, 587 | "scriptHash": 196, 588 | "validWitnessVersions": [0, 1] 589 | }, 590 | "tags": [ 591 | { 592 | "tagName": "payment_hash", 593 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 594 | }, 595 | { 596 | "tagName": "description", 597 | "data": "ナンセンス 1杯" 598 | }, 599 | { 600 | "tagName": "fallback_address", 601 | "data": { 602 | "address": "gd78fgGFGDGISFFgisdufudsfi7d7sfg" 603 | } 604 | } 605 | ] 606 | }, 607 | "error": "Fallback address type is unknown" 608 | }, 609 | { 610 | "data": { 611 | "network": { 612 | "bech32": "tb", 613 | "pubKeyHash": 111, 614 | "scriptHash": 196, 615 | "validWitnessVersions": [0, 1] 616 | }, 617 | "tags": [ 618 | { 619 | "tagName": "payment_hash", 620 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 621 | }, 622 | { 623 | "tagName": "description", 624 | "data": "ナンセンス 1杯" 625 | }, 626 | { 627 | "tagName": "routing_info", 628 | "data": [{}] 629 | } 630 | ] 631 | }, 632 | "error": "Routing info is incomplete" 633 | }, 634 | { 635 | "data": { 636 | "network": { 637 | "bech32": "tb", 638 | "pubKeyHash": 111, 639 | "scriptHash": 196, 640 | "validWitnessVersions": [0, 1] 641 | }, 642 | "tags": [ 643 | { 644 | "tagName": "payment_hash", 645 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 646 | }, 647 | { 648 | "tagName": "description", 649 | "data": "ナンセンス 1杯" 650 | }, 651 | { 652 | "tagName": "routing_info", 653 | "data": [ 654 | { 655 | "pubkey": "02ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 656 | "short_channel_id": "0102030405060708", 657 | "fee_base_msat": 1, 658 | "fee_proportional_millionths": 20, 659 | "cltv_expiry_delta": 3 660 | } 661 | ] 662 | } 663 | ] 664 | }, 665 | "error": "Routing info pubkey is not a valid pubkey" 666 | }, 667 | { 668 | "data": { 669 | "network": { 670 | "bech32": "tb", 671 | "pubKeyHash": 111, 672 | "scriptHash": 196, 673 | "validWitnessVersions": [0, 1] 674 | }, 675 | "tags": [ 676 | { 677 | "tagName": "payment_hash", 678 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 679 | }, 680 | { 681 | "tagName": "description", 682 | "data": "ナンセンス 1杯" 683 | }, 684 | { 685 | "tagName": "routing_info", 686 | "data": [ 687 | { 688 | "pubkey": "029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255", 689 | "short_channel_id": "02030405060708", 690 | "fee_base_msat": 1, 691 | "fee_proportional_millionths": 20, 692 | "cltv_expiry_delta": 3 693 | } 694 | ] 695 | } 696 | ] 697 | }, 698 | "error": "Routing info short channel id must be 8 bytes" 699 | }, 700 | { 701 | "data": { 702 | "network": { 703 | "bech32": "tb", 704 | "pubKeyHash": 111, 705 | "scriptHash": 196, 706 | "validWitnessVersions": [0, 1] 707 | }, 708 | "tags": [ 709 | { 710 | "tagName": "payment_hash", 711 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 712 | }, 713 | { 714 | "tagName": "description", 715 | "data": "ナンセンス 1杯" 716 | }, 717 | { 718 | "tagName": "routing_info", 719 | "data": [ 720 | { 721 | "pubkey": "029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255", 722 | "short_channel_id": "0102030405060708", 723 | "fee_base_msat": 1.345, 724 | "fee_proportional_millionths": 20, 725 | "cltv_expiry_delta": 3 726 | } 727 | ] 728 | } 729 | ] 730 | }, 731 | "error": "Routing info fee base msat is not an integer" 732 | }, 733 | { 734 | "data": { 735 | "network": { 736 | "bech32": "tb", 737 | "pubKeyHash": 111, 738 | "scriptHash": 196, 739 | "validWitnessVersions": [0, 1] 740 | }, 741 | "tags": [ 742 | { 743 | "tagName": "payment_hash", 744 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 745 | }, 746 | { 747 | "tagName": "description", 748 | "data": "ナンセンス 1杯" 749 | }, 750 | { 751 | "tagName": "routing_info", 752 | "data": [ 753 | { 754 | "pubkey": "029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255", 755 | "short_channel_id": "0102030405060708", 756 | "fee_base_msat": 1, 757 | "fee_proportional_millionths": 20.1, 758 | "cltv_expiry_delta": 3 759 | } 760 | ] 761 | } 762 | ] 763 | }, 764 | "error": "Routing info fee proportional millionths is not an integer" 765 | }, 766 | { 767 | "data": { 768 | "network": { 769 | "bech32": "tb", 770 | "pubKeyHash": 111, 771 | "scriptHash": 196, 772 | "validWitnessVersions": [0, 1] 773 | }, 774 | "tags": [ 775 | { 776 | "tagName": "payment_hash", 777 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 778 | }, 779 | { 780 | "tagName": "description", 781 | "data": "ナンセンス 1杯" 782 | }, 783 | { 784 | "tagName": "routing_info", 785 | "data": [ 786 | { 787 | "pubkey": "029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255", 788 | "short_channel_id": "0102030405060708", 789 | "fee_base_msat": 1, 790 | "fee_proportional_millionths": 20, 791 | "cltv_expiry_delta": 3.1 792 | } 793 | ] 794 | } 795 | ] 796 | }, 797 | "error": "Routing info cltv expiry delta is not an integer" 798 | }, 799 | { 800 | "data": { 801 | "network": { 802 | "bech32": "tb", 803 | "pubKeyHash": 111, 804 | "scriptHash": 196, 805 | "validWitnessVersions": [0, 1] 806 | }, 807 | "tags": [ 808 | { 809 | "tagName": "payment_hash", 810 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 811 | }, 812 | { 813 | "tagName": "description", 814 | "data": "ナンセンス 1杯" 815 | }, 816 | { 817 | "tagName": "aaaaaaaaaa", 818 | "data": {} 819 | } 820 | ] 821 | }, 822 | "error": "Unknown tag key" 823 | }, 824 | { 825 | "data": { 826 | "network": { 827 | "bech32": "tb", 828 | "pubKeyHash": 111, 829 | "scriptHash": 196, 830 | "validWitnessVersions": [0, 1] 831 | }, 832 | "tags": [ 833 | { 834 | "tagName": "payment_hash", 835 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 836 | }, 837 | { 838 | "tagName": "description", 839 | "data": "ナンセンス 1杯" 840 | }, 841 | { 842 | "tagName": "purpose_commit_hash", 843 | "data": 5675 844 | } 845 | ] 846 | }, 847 | "error": "purpose or purpose commit must be a string or hex string" 848 | }, 849 | { 850 | "data": { 851 | "network": { 852 | "bech32": "tb", 853 | "pubKeyHash": 111, 854 | "scriptHash": 196, 855 | "validWitnessVersions": [0, 1] 856 | }, 857 | "tags": [ 858 | { 859 | "tagName": "payment_hash", 860 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 861 | }, 862 | { 863 | "tagName": "description", 864 | "data": "0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz" 865 | } 866 | ] 867 | }, 868 | "error": "Description is too long: Max length 639 bytes" 869 | }, 870 | { 871 | "data": { 872 | "network": { 873 | "bech32": "bc", 874 | "pubKeyHash": 0, 875 | "scriptHash": 5, 876 | "validWitnessVersions": [0, 1] 877 | }, 878 | "satoshis": 23, 879 | "millisatoshis": "23000", 880 | "timestamp": 1496314658, 881 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 882 | "signature": "38ec6891345e204145be8a3a99de38e98a39d6a569434e1845c8af7205afcfcc7f425fcd1463e93c32881ead0d6e356d467ec8c02553f9aab15e5738b11f127f", 883 | "recoveryFlag": 0, 884 | "tags": [ 885 | { 886 | "tagName": "payment_hash", 887 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 888 | }, 889 | { 890 | "tagName": "fallback_address", 891 | "data": { 892 | "address": "31h38a54tFMrR8kzBnP2241Jj7qKMHmhoM" 893 | } 894 | } 895 | ] 896 | }, 897 | "error": "Signature, message, and recoveryID did not produce the same pubkey as payeeNodeKey" 898 | }, 899 | { 900 | "data": { 901 | "network": { 902 | "bech32": "tb", 903 | "pubKeyHash": 111, 904 | "scriptHash": 196, 905 | "validWitnessVersions": [0, 1] 906 | }, 907 | "satoshis": null, 908 | "millisatoshis": null, 909 | "timestamp": 1496314658, 910 | "signature": "38ec6891345e204145be8a3a99de38e98a39d6a569434e1845c8af7205afcfcc7f425fcd1463e93c32881ead0d6e356d467ec8c02553f9aab15e5738b11f127f", 911 | "recoveryFlag": 0, 912 | "tags": [ 913 | { 914 | "tagName": "payment_hash", 915 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 916 | }, 917 | { 918 | "tagName": "description", 919 | "data": "Please consider supporting this project" 920 | }, 921 | { 922 | "tagName": "fallback_address", 923 | "data": { 924 | "address": "mfWyW5fc9NUj75YAnFgoRLrhSb9JjaD4zp" 925 | } 926 | }, 927 | { 928 | "tagName": "purpose_commit_hash", 929 | "data": "This is some text to increase coverage" 930 | } 931 | ] 932 | }, 933 | "error": "Reconstruction with signature and recoveryID requires payeeNodeKey to verify correctness of input data." 934 | }, 935 | { 936 | "data": { 937 | "network": { 938 | "bech32": "tb", 939 | "pubKeyHash": 111, 940 | "scriptHash": 196, 941 | "validWitnessVersions": [0, 1] 942 | }, 943 | "satoshis": 100, 944 | "millisatoshis": "200000", 945 | "tags": [ 946 | { 947 | "tagName": "payment_hash", 948 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 949 | }, 950 | { 951 | "tagName": "description", 952 | "data": "ナンセンス 1杯" 953 | } 954 | ] 955 | }, 956 | "error": "satoshis and millisatoshis do not match" 957 | }, 958 | { 959 | "addDefaults": false, 960 | "data": { 961 | "network": { 962 | "bech32": "bc", 963 | "pubKeyHash": 0, 964 | "scriptHash": 5, 965 | "validWitnessVersions": [0, 1] 966 | }, 967 | "satoshis": 2500000, 968 | "timestamp": 1496314658, 969 | "tags": [ 970 | { 971 | "tagName": "payment_hash", 972 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 973 | }, 974 | { 975 | "tagName": "expire_time", 976 | "data": 3600 977 | }, 978 | { 979 | "tagName": "payment_secret", 980 | "data": "fffffffffffffffb74f54d269fe206be715000f94dac067d1c04a8ca3b2db734" 981 | } 982 | ] 983 | }, 984 | "error": "Payment request requires feature bits with at least payment secret support flagged if payment secret is included" 985 | }, 986 | { 987 | "data": { 988 | "network": { 989 | "bech32": "bc", 990 | "pubKeyHash": 0, 991 | "scriptHash": 5, 992 | "validWitnessVersions": [0, 1] 993 | }, 994 | "satoshis": 2500000, 995 | "timestamp": 1496314658, 996 | "tags": [ 997 | { 998 | "tagName": "payment_hash", 999 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1000 | }, 1001 | { 1002 | "tagName": "expire_time", 1003 | "data": 3600 1004 | }, 1005 | { 1006 | "tagName": "payment_secret", 1007 | "data": "fffffffffffffffb74f54d269fe206be715000f94dac067d1c04a8ca3b2db734" 1008 | }, 1009 | { 1010 | "tagName": "feature_bits", 1011 | "data": { 1012 | "word_length": 5, 1013 | "payment_secret": { 1014 | "required": false, 1015 | "supported": false 1016 | } 1017 | } 1018 | } 1019 | ] 1020 | }, 1021 | "error": "Payment request requires feature bits with at least payment secret support flagged if payment secret is included" 1022 | }, 1023 | { 1024 | "data": { 1025 | "network": { 1026 | "bech32": "bc", 1027 | "pubKeyHash": 0, 1028 | "scriptHash": 5, 1029 | "validWitnessVersions": [0, 1] 1030 | }, 1031 | "satoshis": 2500000, 1032 | "timestamp": 1496314658, 1033 | "tags": [ 1034 | { 1035 | "tagName": "payment_hash", 1036 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1037 | }, 1038 | { 1039 | "tagName": "expire_time", 1040 | "data": 3600 1041 | }, 1042 | { 1043 | "tagName": "payment_secret", 1044 | "data": "fffffffffffffffb74f54d269fe206be715000f94dac067d1c04a8ca3b2db734" 1045 | }, 1046 | { 1047 | "tagName": "feature_bits", 1048 | "data": { 1049 | "word_length": 1, 1050 | "payment_secret": { 1051 | "required": true, 1052 | "supported": true 1053 | } 1054 | } 1055 | } 1056 | ] 1057 | }, 1058 | "error": "word_length is too small to contain all featureBits" 1059 | } 1060 | ] 1061 | }, 1062 | "decode": { 1063 | "valid": [ 1064 | { 1065 | "paymentRequest": "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w", 1066 | "network": { 1067 | "bech32": "bc", 1068 | "pubKeyHash": 0, 1069 | "scriptHash": 5, 1070 | "validWitnessVersions": [0, 1] 1071 | }, 1072 | "prefix": "lnbc", 1073 | "complete": true, 1074 | "satoshis": null, 1075 | "millisatoshis": null, 1076 | "timestamp": 1496314658, 1077 | "timestampString": "2017-06-01T10:57:38.000Z", 1078 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1079 | "signature": "38ec6891345e204145be8a3a99de38e98a39d6a569434e1845c8af7205afcfcc7f425fcd1463e93c32881ead0d6e356d467ec8c02553f9aab15e5738b11f127f", 1080 | "recoveryFlag": 0, 1081 | "tags": [ 1082 | { 1083 | "tagName": "payment_hash", 1084 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1085 | }, 1086 | { 1087 | "tagName": "description", 1088 | "data": "Please consider supporting this project" 1089 | } 1090 | ], 1091 | "wordsTemp": "temp1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqe49hhl" 1092 | }, 1093 | { 1094 | "paymentRequest": "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp", 1095 | "network": { 1096 | "bech32": "bc", 1097 | "pubKeyHash": 0, 1098 | "scriptHash": 5, 1099 | "validWitnessVersions": [0, 1] 1100 | }, 1101 | "prefix": "lnbc2500u", 1102 | "complete": true, 1103 | "satoshis": 250000, 1104 | "millisatoshis": "250000000", 1105 | "timeExpireDate": 1496314718, 1106 | "timeExpireDateString": "2017-06-01T10:58:38.000Z", 1107 | "timestamp": 1496314658, 1108 | "timestampString": "2017-06-01T10:57:38.000Z", 1109 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1110 | "signature": "e89639ba6814e36689d4b91bf125f10351b55da057b00647a8dabaeb8a90c95f160f9d5a6e0f79d1fc2b964238b944e2fa4aa677c6f020d466472ab842bd750e", 1111 | "recoveryFlag": 1, 1112 | "tags": [ 1113 | { 1114 | "tagName": "payment_hash", 1115 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1116 | }, 1117 | { 1118 | "tagName": "description", 1119 | "data": "1 cup coffee" 1120 | }, 1121 | { 1122 | "tagName": "expire_time", 1123 | "data": 60 1124 | } 1125 | ], 1126 | "wordsTemp": "temp1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rsp6tg0jg" 1127 | }, 1128 | { 1129 | "complete": true, 1130 | "millisatoshis": "12000", 1131 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1132 | "paymentRequest": "lnbcrt120n1psd5ks5pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqrrsscqpfktzsxs25he76dx2d672r7j4sm7q4quts763pwemg8chgayn96p3sny6cqm0lfxa768cetu7rupzpf7ujmsnmnpsjaukgdah8nkecd4gp6h56uu", 1133 | "prefix": "lnbcrt120n", 1134 | "recoveryFlag": 1, 1135 | "satoshis": 12, 1136 | "signature": "b2c5034154be7da6994dd7943f4ab0df81507170f6a21767683e2e8e9265d06309935806dff49bbed1f195f3c3e04414fb92dc27b98612ef2c86f6e79db386d5", 1137 | "tags": [ 1138 | { 1139 | "tagName": "payment_hash", 1140 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1141 | }, 1142 | { 1143 | "tagName": "description", 1144 | "data": "ナンセンス 1杯" 1145 | }, 1146 | { 1147 | "tagName": "expire_time", 1148 | "data": 3600 1149 | }, 1150 | { 1151 | "tagName": "min_final_cltv_expiry", 1152 | "data": 9 1153 | } 1154 | ], 1155 | "timeExpireDate": 1624926244, 1156 | "timeExpireDateString": "2021-06-29T00:24:04.000Z", 1157 | "timestamp": 1624922644, 1158 | "timestampString": "2021-06-28T23:24:04.000Z", 1159 | "wordsTemp": "temp1psd5ks5pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqrrsscqpfktzsxs25he76dx2d672r7j4sm7q4quts763pwemg8chgayn96p3sny6cqm0lfxa768cetu7rupzpf7ujmsnmnpsjaukgdah8nkecd4gp9hjc0v" 1160 | }, 1161 | { 1162 | "paymentRequest": "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqscc6gd6ql3jrc5yzme8v4ntcewwz5cnw92tz0pc8qcuufvq7khhr8wpald05e92xw006sq94mg8v2ndf4sefvf9sygkshp5zfem29trqq2yxxz7", 1163 | "network": { 1164 | "bech32": "bc", 1165 | "pubKeyHash": 0, 1166 | "scriptHash": 5, 1167 | "validWitnessVersions": [0, 1] 1168 | }, 1169 | "prefix": "lnbc20m", 1170 | "complete": true, 1171 | "satoshis": 2000000, 1172 | "millisatoshis": "2000000000", 1173 | "timestamp": 1496314658, 1174 | "timestampString": "2017-06-01T10:57:38.000Z", 1175 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1176 | "signature": "c63486e81f8c878a105bc9d959af1973854c4dc552c4f0e0e0c7389603d6bdc67707bf6be992a8ce7bf50016bb41d8a9b5358652c4960445a170d049ced4558c", 1177 | "recoveryFlag": 0, 1178 | "tags": [ 1179 | { 1180 | "tagName": "payment_hash", 1181 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1182 | }, 1183 | { 1184 | "tagName": "purpose_commit_hash", 1185 | "data": "3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1" 1186 | } 1187 | ], 1188 | "wordsTemp": "temp1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqscc6gd6ql3jrc5yzme8v4ntcewwz5cnw92tz0pc8qcuufvq7khhr8wpald05e92xw006sq94mg8v2ndf4sefvf9sygkshp5zfem29trqq8y2zpz" 1189 | }, 1190 | { 1191 | "paymentRequest": "lntb20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfpp3x9et2e20v6pu37c5d9vax37wxq72un98kmzzhznpurw9sgl2v0nklu2g4d0keph5t7tj9tcqd8rexnd07ux4uv2cjvcqwaxgj7v4uwn5wmypjd5n69z2xm3xgksg28nwht7f6zspwp3f9t", 1192 | "network": { 1193 | "bech32": "tb", 1194 | "pubKeyHash": 111, 1195 | "scriptHash": 196, 1196 | "validWitnessVersions": [0, 1] 1197 | }, 1198 | "prefix": "lntb20m", 1199 | "complete": true, 1200 | "satoshis": 2000000, 1201 | "millisatoshis": "2000000000", 1202 | "timestamp": 1496314658, 1203 | "timestampString": "2017-06-01T10:57:38.000Z", 1204 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1205 | "signature": "b6c42b8a61e0dc5823ea63e76ff148ab5f6c86f45f9722af0069c7934daff70d5e315893300774c897995e3a7476c8193693d144a36e2645a0851e6ebafc9d0a", 1206 | "recoveryFlag": 1, 1207 | "tags": [ 1208 | { 1209 | "tagName": "purpose_commit_hash", 1210 | "data": "3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1" 1211 | }, 1212 | { 1213 | "tagName": "payment_hash", 1214 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1215 | }, 1216 | { 1217 | "tagName": "fallback_address", 1218 | "data": { 1219 | "code": 17, 1220 | "address": "mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP", 1221 | "addressHash": "3172b5654f6683c8fb146959d347ce303cae4ca7" 1222 | } 1223 | } 1224 | ], 1225 | "wordsTemp": "temp1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfpp3x9et2e20v6pu37c5d9vax37wxq72un98kmzzhznpurw9sgl2v0nklu2g4d0keph5t7tj9tcqd8rexnd07ux4uv2cjvcqwaxgj7v4uwn5wmypjd5n69z2xm3xgksg28nwht7f6zspeu4lw4" 1226 | }, 1227 | { 1228 | "paymentRequest": "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqj9n4evl6mr5aj9f58zp6fyjzup6ywn3x6sk8akg5v4tgn2q8g4fhx05wf6juaxu9760yp46454gpg5mtzgerlzezqcqvjnhjh8z3g2qqdhhwkj", 1229 | "network": { 1230 | "bech32": "bc", 1231 | "pubKeyHash": 0, 1232 | "scriptHash": 5, 1233 | "validWitnessVersions": [0, 1] 1234 | }, 1235 | "prefix": "lnbc20m", 1236 | "complete": true, 1237 | "satoshis": 2000000, 1238 | "millisatoshis": "2000000000", 1239 | "timestamp": 1496314658, 1240 | "timestampString": "2017-06-01T10:57:38.000Z", 1241 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1242 | "signature": "91675cb3fad8e9d915343883a49242e074474e26d42c7ed914655689a8074553733e8e4ea5ce9b85f69e40d755a55014536b12323f8b220600c94ef2b9c51428", 1243 | "recoveryFlag": 0, 1244 | "tags": [ 1245 | { 1246 | "tagName": "payment_hash", 1247 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1248 | }, 1249 | { 1250 | "tagName": "purpose_commit_hash", 1251 | "data": "3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1" 1252 | }, 1253 | { 1254 | "tagName": "fallback_address", 1255 | "data": { 1256 | "code": 17, 1257 | "address": "1RustyRX2oai4EYYDpQGWvEL62BBGqN9T", 1258 | "addressHash": "04b61f7dc1ea0dc99424464cc4064dc564d91e89" 1259 | } 1260 | }, 1261 | { 1262 | "tagName": "routing_info", 1263 | "data": [ 1264 | { 1265 | "pubkey": "029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255", 1266 | "short_channel_id": "0102030405060708", 1267 | "fee_base_msat": 1, 1268 | "fee_proportional_millionths": 20, 1269 | "cltv_expiry_delta": 3 1270 | }, 1271 | { 1272 | "pubkey": "039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255", 1273 | "short_channel_id": "030405060708090a", 1274 | "fee_base_msat": 2, 1275 | "fee_proportional_millionths": 30, 1276 | "cltv_expiry_delta": 4 1277 | } 1278 | ] 1279 | } 1280 | ], 1281 | "wordsTemp": "temp1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqj9n4evl6mr5aj9f58zp6fyjzup6ywn3x6sk8akg5v4tgn2q8g4fhx05wf6juaxu9760yp46454gpg5mtzgerlzezqcqvjnhjh8z3g2qqpqa4j6" 1282 | }, 1283 | { 1284 | "paymentRequest": "lnbc20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppj3a24vwu6r8ejrss3axul8rxldph2q7z9kmrgvr7xlaqm47apw3d48zm203kzcq357a4ls9al2ea73r8jcceyjtya6fu5wzzpe50zrge6ulk4nvjcpxlekvmxl6qcs9j3tz0469gq5g658y", 1285 | "network": { 1286 | "bech32": "bc", 1287 | "pubKeyHash": 0, 1288 | "scriptHash": 5, 1289 | "validWitnessVersions": [0, 1] 1290 | }, 1291 | "prefix": "lnbc20m", 1292 | "complete": true, 1293 | "satoshis": 2000000, 1294 | "millisatoshis": "2000000000", 1295 | "timestamp": 1496314658, 1296 | "timestampString": "2017-06-01T10:57:38.000Z", 1297 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1298 | "signature": "b6c6860fc6ff41bafba1745b538b6a7c6c2c0234f76bf817bf567be88cf2c632492c9dd279470841cd1e21a33ae7ed59b25809bf9b3366fe81881651589f5d15", 1299 | "recoveryFlag": 0, 1300 | "tags": [ 1301 | { 1302 | "tagName": "purpose_commit_hash", 1303 | "data": "3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1" 1304 | }, 1305 | { 1306 | "tagName": "payment_hash", 1307 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1308 | }, 1309 | { 1310 | "tagName": "fallback_address", 1311 | "data": { 1312 | "code": 18, 1313 | "address": "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX", 1314 | "addressHash": "8f55563b9a19f321c211e9b9f38cdf686ea07845" 1315 | } 1316 | } 1317 | ], 1318 | "wordsTemp": "temp1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppj3a24vwu6r8ejrss3axul8rxldph2q7z9kmrgvr7xlaqm47apw3d48zm203kzcq357a4ls9al2ea73r8jcceyjtya6fu5wzzpe50zrge6ulk4nvjcpxlekvmxl6qcs9j3tz0469gq5ffj0z" 1319 | }, 1320 | { 1321 | "paymentRequest": "lnbc20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppqw508d6qejxtdg4y5r3zarvary0c5xw7kepvrhrm9s57hejg0p662ur5j5cr03890fa7k2pypgttmh4897d3raaq85a293e9jpuqwl0rnfuwzam7yr8e690nd2ypcq9hlkdwdvycqa0qza8", 1322 | "network": { 1323 | "bech32": "bc", 1324 | "pubKeyHash": 0, 1325 | "scriptHash": 5, 1326 | "validWitnessVersions": [0, 1] 1327 | }, 1328 | "prefix": "lnbc20m", 1329 | "complete": true, 1330 | "satoshis": 2000000, 1331 | "millisatoshis": "2000000000", 1332 | "timestamp": 1496314658, 1333 | "timestampString": "2017-06-01T10:57:38.000Z", 1334 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1335 | "signature": "c8583b8f65853d7cc90f0eb4ae0e92a606f89caf4f7d65048142d7bbd4e5f3623ef407a75458e4b20f00efbc734f1c2eefc419f3a2be6d51038016ffb35cd613", 1336 | "recoveryFlag": 0, 1337 | "tags": [ 1338 | { 1339 | "tagName": "purpose_commit_hash", 1340 | "data": "3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1" 1341 | }, 1342 | { 1343 | "tagName": "payment_hash", 1344 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1345 | }, 1346 | { 1347 | "tagName": "fallback_address", 1348 | "data": { 1349 | "code": 0, 1350 | "address": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", 1351 | "addressHash": "751e76e8199196d454941c45d1b3a323f1433bd6" 1352 | } 1353 | } 1354 | ], 1355 | "wordsTemp": "temp1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppqw508d6qejxtdg4y5r3zarvary0c5xw7kepvrhrm9s57hejg0p662ur5j5cr03890fa7k2pypgttmh4897d3raaq85a293e9jpuqwl0rnfuwzam7yr8e690nd2ypcq9hlkdwdvycqawny4p" 1356 | }, 1357 | { 1358 | "paymentRequest": "lnbc20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfp4qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q28j0v3rwgy9pvjnd48ee2pl8xrpxysd5g44td63g6xcjcu003j3qe8878hluqlvl3km8rm92f5stamd3jw763n3hck0ct7p8wwj463cql26ava", 1359 | "network": { 1360 | "bech32": "bc", 1361 | "pubKeyHash": 0, 1362 | "scriptHash": 5, 1363 | "validWitnessVersions": [0, 1] 1364 | }, 1365 | "prefix": "lnbc20m", 1366 | "complete": true, 1367 | "satoshis": 2000000, 1368 | "millisatoshis": "2000000000", 1369 | "timestamp": 1496314658, 1370 | "timestampString": "2017-06-01T10:57:38.000Z", 1371 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1372 | "signature": "51e4f6446e410a164a6da9f39507e730c26241b4456ab6ea28d1b12c71ef8ca20c9cfe3dffc07d9f8db671ecaa4d20beedb193bda8ce37c59f85f82773a55d47", 1373 | "recoveryFlag": 0, 1374 | "tags": [ 1375 | { 1376 | "tagName": "purpose_commit_hash", 1377 | "data": "3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1" 1378 | }, 1379 | { 1380 | "tagName": "payment_hash", 1381 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1382 | }, 1383 | { 1384 | "tagName": "fallback_address", 1385 | "data": { 1386 | "code": 0, 1387 | "address": "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", 1388 | "addressHash": "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262" 1389 | } 1390 | } 1391 | ], 1392 | "wordsTemp": "temp1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfp4qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q28j0v3rwgy9pvjnd48ee2pl8xrpxysd5g44td63g6xcjcu003j3qe8878hluqlvl3km8rm92f5stamd3jw763n3hck0ct7p8wwj463cqxfgdr0" 1393 | }, 1394 | { 1395 | "paymentRequest": "lnbc20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfp4prp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q8playrmszfzefw6txnt50qlsytzavn3jyn75fv09wms65sr3n39rygdynm0tr645tgj8le5hu6lnvfgn7hmuf0reenpm605n3p668usqc3r7fq", 1396 | "network": { 1397 | "bech32": "bc", 1398 | "pubKeyHash": 0, 1399 | "scriptHash": 5, 1400 | "validWitnessVersions": [0, 1] 1401 | }, 1402 | "prefix": "lnbc20m", 1403 | "complete": true, 1404 | "satoshis": 2000000, 1405 | "millisatoshis": "2000000000", 1406 | "timestamp": 1496314658, 1407 | "timestampString": "2017-06-01T10:57:38.000Z", 1408 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1409 | "signature": "387fd20f70124594bb4b34d74783f022c5d64e3224fd44b1e576e1aa40719c4a3221a49edeb1eab45a247fe697e6bf362513f5f7c4bc79ccc3bd3e938875a3f2", 1410 | "recoveryFlag": 0, 1411 | "tags": [ 1412 | { 1413 | "tagName": "purpose_commit_hash", 1414 | "data": "3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1" 1415 | }, 1416 | { 1417 | "tagName": "payment_hash", 1418 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1419 | }, 1420 | { 1421 | "tagName": "fallback_address", 1422 | "data": { 1423 | "code": 1, 1424 | "address": "bc1prp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qj0fj5d", 1425 | "addressHash": "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262" 1426 | } 1427 | } 1428 | ], 1429 | "wordsTemp": "temp1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfp4prp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q8playrmszfzefw6txnt50qlsytzavn3jyn75fv09wms65sr3n39rygdynm0tr645tgj8le5hu6lnvfgn7hmuf0reenpm605n3p668usqpj3wxj" 1430 | }, 1431 | { 1432 | "paymentRequest": "lnbc1230p1pwpw4vhpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq8w3jhxaqxqrrsscqpfmmcvd29nucsnyapspmkpqqf65uedt4zvhqkstelrgyk4nfvwka38c3nlq06agjmazs9nr3uupxp6r0v0gzw4n26redc36urkqwxamqqqu7esys", 1433 | "network": { 1434 | "bech32": "bc", 1435 | "pubKeyHash": 0, 1436 | "scriptHash": 5, 1437 | "validWitnessVersions": [0, 1] 1438 | }, 1439 | "prefix": "lnbc1230p", 1440 | "complete": true, 1441 | "millisatoshis": "123", 1442 | "timestamp": 1545033111, 1443 | "timestampString": "2018-12-17T07:51:51.000Z", 1444 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1445 | "signature": "def0c6a8b3e6213274300eec10013aa732d5d44cb82d05e7e3412d59a58eb7627c467f03f5d44b7d140b31c79c0983a1bd8f409d59ab43cb711d7076038ddd80", 1446 | "recoveryFlag": 0, 1447 | "tags": [ 1448 | { 1449 | "tagName": "payment_hash", 1450 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1451 | }, 1452 | { 1453 | "tagName": "description", 1454 | "data": "test" 1455 | }, 1456 | { 1457 | "tagName": "expire_time", 1458 | "data": 3600 1459 | }, 1460 | { 1461 | "tagName": "min_final_cltv_expiry", 1462 | "data": 9 1463 | } 1464 | ], 1465 | "timeExpireDate": 1545036711, 1466 | "timeExpireDateString": "2018-12-17T08:51:51.000Z", 1467 | "wordsTemp": "temp1pwpw4vhpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq8w3jhxaqxqrrsscqpfmmcvd29nucsnyapspmkpqqf65uedt4zvhqkstelrgyk4nfvwka38c3nlq06agjmazs9nr3uupxp6r0v0gzw4n26redc36urkqwxamqqqumjsau" 1468 | }, 1469 | { 1470 | "complete": true, 1471 | "millisatoshis": null, 1472 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1473 | "paymentRequest": "lntb1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqxqrrsscqpftv2st2e87cvsqs3d77wdfz4rswz50s5kd87zyuvv9sawc6n5rew53lu8wsky9h8hw9jkfxca0fc44eyvwdufgykvqalltrqz5hfkurgppged2y", 1474 | "prefix": "lntb", 1475 | "recoveryFlag": 1, 1476 | "satoshis": null, 1477 | "signature": "5b1505ab27f61900422df79cd48aa3838547c29669fc22718c2c3aec6a741e5d48ff87742c42dcf77165649b1d7a715ae48c73789412cc077ff58c02a5d36e0d", 1478 | "tags": [ 1479 | { 1480 | "tagName": "payment_hash", 1481 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1482 | }, 1483 | { 1484 | "tagName": "description", 1485 | "data": "Please consider supporting this project" 1486 | }, 1487 | { 1488 | "tagName": "expire_time", 1489 | "data": 3600 1490 | }, 1491 | { 1492 | "tagName": "min_final_cltv_expiry", 1493 | "data": 9 1494 | } 1495 | ], 1496 | "timeExpireDate": 1496318258, 1497 | "timeExpireDateString": "2017-06-01T11:57:38.000Z", 1498 | "timestamp": 1496314658, 1499 | "timestampString": "2017-06-01T10:57:38.000Z", 1500 | "wordsTemp": "temp1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqxqrrsscqpftv2st2e87cvsqs3d77wdfz4rswz50s5kd87zyuvv9sawc6n5rew53lu8wsky9h8hw9jkfxca0fc44eyvwdufgykvqalltrqz5hfkurgppdgh5h" 1501 | }, 1502 | { 1503 | "complete": true, 1504 | "millisatoshis": null, 1505 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1506 | "paymentRequest": "lnsb1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqxqrrsscqpfq2hnv8x3a7nu9cgpky8vllqjytmgmg56tz26c8rg28nc8h53rddzra5en0er8zdc4rcwn47vnmshhqynqgqavyhmqwnz75ztg6puzlsqj75teg", 1507 | "prefix": "lnsb", 1508 | "recoveryFlag": 0, 1509 | "satoshis": null, 1510 | "signature": "02af361cd1efa7c2e101b10ecffc1222f68da29a5895ac1c6851e783de911b5a21f6999bf23389b8a8f0e9d7cc9ee17b80930201d612fb03a62f504b4683c17e", 1511 | "tags": [ 1512 | { 1513 | "tagName": "payment_hash", 1514 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1515 | }, 1516 | { 1517 | "tagName": "description", 1518 | "data": "Please consider supporting this project" 1519 | }, 1520 | { 1521 | "tagName": "expire_time", 1522 | "data": 3600 1523 | }, 1524 | { 1525 | "tagName": "min_final_cltv_expiry", 1526 | "data": 9 1527 | } 1528 | ], 1529 | "timeExpireDate": 1496318258, 1530 | "timeExpireDateString": "2017-06-01T11:57:38.000Z", 1531 | "timestamp": 1496314658, 1532 | "timestampString": "2017-06-01T10:57:38.000Z", 1533 | "wordsTemp": "temp1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqxqrrsscqpfq2hnv8x3a7nu9cgpky8vllqjytmgmg56tz26c8rg28nc8h53rddzra5en0er8zdc4rcwn47vnmshhqynqgqavyhmqwnz75ztg6puzlsqhz629u" 1534 | }, 1535 | { 1536 | "complete": true, 1537 | "millisatoshis": "2500000000", 1538 | "network": { 1539 | "bech32": "bc", 1540 | "pubKeyHash": 0, 1541 | "scriptHash": 5, 1542 | "validWitnessVersions": [0, 1] 1543 | }, 1544 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1545 | "paymentRequest": "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqxqrrsssp5llllllllllllka84f5nflcsxhec4qq8efkkqvlguqj5v5wedku6q9q9zpsqqdqqcqpf4kkf33geaaxs0nkyr6l2jqgdy0np6dtf5t73m8wzp4qqlzga37px45ekvs4pdxtlppekh05073elkfcwtrc5dtlkhzcsa3c035pa4mqpr5xjms", 1546 | "prefix": "lnbc25m", 1547 | "recoveryFlag": 1, 1548 | "satoshis": 2500000, 1549 | "signature": "adac98c519ef4d07cec41ebea9010d23e61d3569a2fd1d9dc20d400f891d8f826ad336642a16997f08736bbe8ff473fb270e58f146aff6b8b10ec70f8d03daec", 1550 | "tags": [ 1551 | { 1552 | "tagName": "payment_hash", 1553 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1554 | }, 1555 | { 1556 | "tagName": "expire_time", 1557 | "data": 3600 1558 | }, 1559 | { 1560 | "tagName": "payment_secret", 1561 | "data": "fffffffffffffffb74f54d269fe206be715000f94dac067d1c04a8ca3b2db734" 1562 | }, 1563 | { 1564 | "tagName": "feature_bits", 1565 | "data": { 1566 | "word_length": 5, 1567 | "option_data_loss_protect": { 1568 | "required": false, 1569 | "supported": false 1570 | }, 1571 | "initial_routing_sync": { 1572 | "required": false, 1573 | "supported": false 1574 | }, 1575 | "option_upfront_shutdown_script": { 1576 | "required": false, 1577 | "supported": false 1578 | }, 1579 | "gossip_queries": { 1580 | "required": false, 1581 | "supported": false 1582 | }, 1583 | "var_onion_optin": { 1584 | "required": false, 1585 | "supported": false 1586 | }, 1587 | "gossip_queries_ex": { 1588 | "required": false, 1589 | "supported": false 1590 | }, 1591 | "option_static_remotekey": { 1592 | "required": false, 1593 | "supported": false 1594 | }, 1595 | "payment_secret": { 1596 | "required": true, 1597 | "supported": true 1598 | }, 1599 | "basic_mpp": { 1600 | "required": false, 1601 | "supported": false 1602 | }, 1603 | "option_support_large_channel": { 1604 | "required": false, 1605 | "supported": false 1606 | }, 1607 | "extra_bits": { 1608 | "start_bit": 20, 1609 | "bits": [ 1610 | false, 1611 | true, 1612 | false, 1613 | false, 1614 | false 1615 | ], 1616 | "has_required": false 1617 | } 1618 | } 1619 | }, 1620 | { 1621 | "tagName": "description", 1622 | "data": "" 1623 | }, 1624 | { 1625 | "tagName": "min_final_cltv_expiry", 1626 | "data": 9 1627 | } 1628 | ], 1629 | "timeExpireDate": 1496318258, 1630 | "timeExpireDateString": "2017-06-01T11:57:38.000Z", 1631 | "timestamp": 1496314658, 1632 | "timestampString": "2017-06-01T10:57:38.000Z", 1633 | "wordsTemp": "temp1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqxqrrsssp5llllllllllllka84f5nflcsxhec4qq8efkkqvlguqj5v5wedku6q9q9zpsqqdqqcqpf4kkf33geaaxs0nkyr6l2jqgdy0np6dtf5t73m8wzp4qqlzga37px45ekvs4pdxtlppekh05073elkfcwtrc5dtlkhzcsa3c035pa4mqp0lfsur" 1634 | }, 1635 | { 1636 | "complete": true, 1637 | "millisatoshis": "2500000000", 1638 | "network": { 1639 | "bech32": "bc", 1640 | "pubKeyHash": 0, 1641 | "scriptHash": 5, 1642 | "validWitnessVersions": [0, 1] 1643 | }, 1644 | "payeeNodeKey": "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", 1645 | "paymentRequest": "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqxqrrss9qpzdqqcqpfpxgg7ymvwywwk4c5jmau65tt6vladd5z4cyvt5t48rkhc2e8v2txfmzhqdrzhet7rmfvcmkdycr5ecc0h8azkyn6hnnkg242f4wfcsqpj9a4zg", 1646 | "prefix": "lnbc25m", 1647 | "recoveryFlag": 1, 1648 | "satoshis": 2500000, 1649 | "signature": "09908f136c711ceb571496fbcd516bd33fd6b682ae08c5d17538ed7c2b27629664ec5703462be57e1ed2cc6ecd26074ce30fb9fa2b127abce7642aaa4d5c9c40", 1650 | "tags": [ 1651 | { 1652 | "tagName": "payment_hash", 1653 | "data": "0001020304050607080900010203040506070809000102030405060708090102" 1654 | }, 1655 | { 1656 | "tagName": "expire_time", 1657 | "data": 3600 1658 | }, 1659 | { 1660 | "tagName": "feature_bits", 1661 | "data": { 1662 | "word_length": 1, 1663 | "option_data_loss_protect": { 1664 | "required": false, 1665 | "supported": true 1666 | }, 1667 | "initial_routing_sync": { 1668 | "required": false, 1669 | "supported": false 1670 | }, 1671 | "option_upfront_shutdown_script": { 1672 | "required": false, 1673 | "supported": false 1674 | }, 1675 | "gossip_queries": { 1676 | "required": false, 1677 | "supported": false 1678 | }, 1679 | "var_onion_optin": { 1680 | "required": false, 1681 | "supported": false 1682 | }, 1683 | "gossip_queries_ex": { 1684 | "required": false, 1685 | "supported": false 1686 | }, 1687 | "option_static_remotekey": { 1688 | "required": false, 1689 | "supported": false 1690 | }, 1691 | "payment_secret": { 1692 | "required": false, 1693 | "supported": false 1694 | }, 1695 | "basic_mpp": { 1696 | "required": false, 1697 | "supported": false 1698 | }, 1699 | "option_support_large_channel": { 1700 | "required": false, 1701 | "supported": false 1702 | }, 1703 | "extra_bits": { 1704 | "start_bit": 20, 1705 | "bits": [], 1706 | "has_required": false 1707 | } 1708 | } 1709 | }, 1710 | { 1711 | "tagName": "description", 1712 | "data": "" 1713 | }, 1714 | { 1715 | "tagName": "min_final_cltv_expiry", 1716 | "data": 9 1717 | } 1718 | ], 1719 | "timeExpireDate": 1496318258, 1720 | "timeExpireDateString": "2017-06-01T11:57:38.000Z", 1721 | "timestamp": 1496314658, 1722 | "timestampString": "2017-06-01T10:57:38.000Z", 1723 | "wordsTemp": "temp1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqxqrrss9qpzdqqcqpfpxgg7ymvwywwk4c5jmau65tt6vladd5z4cyvt5t48rkhc2e8v2txfmzhqdrzhet7rmfvcmkdycr5ecc0h8azkyn6hnnkg242f4wfcsqptthaet" 1724 | } 1725 | ], 1726 | "invalid": [ 1727 | { 1728 | "paymentRequest": 4649, 1729 | "error": "Lightning Payment Request must be string" 1730 | }, 1731 | { 1732 | "paymentRequest": "m1jg3xv8", 1733 | "error": "Not a proper lightning payment request" 1734 | }, 1735 | { 1736 | "paymentRequest": "lnbc2500u1PvJlUeZpP5QqQsYqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp", 1737 | "error": "Mixed-case string lnbc2500u1PvJl" 1738 | }, 1739 | { 1740 | "paymentRequest": "lnbc500m1jg3xv8", 1741 | "error": "Signature is missing or incorrect" 1742 | }, 1743 | { 1744 | "paymentRequest": "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durllllll72gy6kphxuhh4a2ffwf9344ytfw98tyhvslsp9y5vt2uxdfhpucph83eqms28dyde9yxgu5ehln4zkwv04nvurxhst77vnng5s0ar9mqpm3cg0l", 1745 | "error": "Lightning Payment Request signature pubkey does not match payee pubkey" 1746 | }, 1747 | { 1748 | "paymentRequest": "ln1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq05xqmj", 1749 | "error": "Not a proper lightning payment request" 1750 | }, 1751 | { 1752 | "paymentRequest": "lnxy34m1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxt0p4s", 1753 | "error": "Unknown coin bech32 prefix" 1754 | } 1755 | ] 1756 | }, 1757 | "privateKey": "e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734" 1758 | } 1759 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const tape = require('tape') 3 | const fixtures = require('./fixtures') 4 | const lnpayreq = require('../') 5 | const BN = require('bn.js') 6 | 7 | fixtures.satToHrp.valid.forEach((f) => { 8 | tape('test valid satoshi to hrp string', (t) => { 9 | t.plan(1) 10 | t.same(f.output, lnpayreq.satToHrp(new BN(f.input, 10))) 11 | }) 12 | }) 13 | 14 | fixtures.millisatToHrp.valid.forEach((f) => { 15 | tape('test valid millisatoshi to hrp string', (t) => { 16 | t.plan(1) 17 | t.same(f.output, lnpayreq.millisatToHrp(new BN(f.input, 10))) 18 | }) 19 | }) 20 | 21 | fixtures.satToHrp.invalid.forEach((f) => { 22 | tape('test invalid satoshi to hrp string', (t) => { 23 | t.plan(1) 24 | t.throws(() => { 25 | lnpayreq.satToHrp(f.input) 26 | }, new RegExp(f.error)) 27 | }) 28 | }) 29 | 30 | fixtures.millisatToHrp.invalid.forEach((f) => { 31 | tape('test invalid millisatoshi to hrp string', (t) => { 32 | t.plan(1) 33 | t.throws(() => { 34 | lnpayreq.millisatToHrp(f.input) 35 | }, new RegExp(f.error)) 36 | }) 37 | }) 38 | 39 | fixtures.hrpToSat.valid.forEach((f) => { 40 | tape('test valid hrp string to satoshi', (t) => { 41 | t.plan(1) 42 | t.same(f.output, lnpayreq.hrpToSat(f.input).toString()) 43 | }) 44 | }) 45 | 46 | fixtures.hrpToMillisat.valid.forEach((f) => { 47 | tape('test valid hrp string to millisatoshi', (t) => { 48 | t.plan(1) 49 | t.same(f.output, lnpayreq.hrpToMillisat(f.input).toString()) 50 | }) 51 | }) 52 | 53 | fixtures.hrpToSat.invalid.forEach((f) => { 54 | tape('test invalid hrp string to satoshi', (t) => { 55 | t.plan(1) 56 | t.throws(() => { 57 | lnpayreq.hrpToSat(f.input) 58 | }, new RegExp(f.error)) 59 | }) 60 | }) 61 | 62 | fixtures.hrpToMillisat.invalid.forEach((f) => { 63 | tape('test invalid hrp string to millisatoshi', (t) => { 64 | t.plan(1) 65 | t.throws(() => { 66 | lnpayreq.hrpToMillisat(f.input) 67 | }, new RegExp(f.error)) 68 | }) 69 | }) 70 | 71 | fixtures.sign.invalid.forEach((f) => { 72 | tape('test invalid vectors for sign', (t) => { 73 | t.plan(1) 74 | 75 | const privateKey = f.privateKey 76 | ? Buffer.from(f.privateKey, 'hex') 77 | : Buffer.from(fixtures.privateKey, 'hex') 78 | 79 | t.throws(() => { 80 | lnpayreq.sign(f.data, privateKey) 81 | }, new RegExp(f.error)) 82 | }) 83 | }) 84 | 85 | fixtures.encode.valid.forEach((f) => { 86 | tape('test valid vectors for encode', (t) => { 87 | const encoded = lnpayreq.encode(f.data, f.addDefaults) 88 | 89 | const signedData = lnpayreq.sign(encoded, fixtures.privateKey) 90 | 91 | t.same(signedData.complete, true) 92 | 93 | let tagPayeeNodeKey = signedData.tags.filter(item => item.tagName === 'payee_node_key') 94 | if (tagPayeeNodeKey.length > 0) { 95 | tagPayeeNodeKey = tagPayeeNodeKey[0] 96 | t.same(tagPayeeNodeKey, signedData.payeeNodeKey) 97 | } 98 | 99 | t.end() 100 | }) 101 | }) 102 | 103 | fixtures.encode.invalid.forEach((f) => { 104 | tape('test invalid vectors for encode', (t) => { 105 | t.plan(1) 106 | 107 | t.throws(() => { 108 | lnpayreq.encode(f.data, f.addDefaults) 109 | }, new RegExp(f.error)) 110 | }) 111 | }) 112 | 113 | fixtures.decode.valid.forEach((f) => { 114 | tape('test valid vectors for decode', (t) => { 115 | const decoded = lnpayreq.decode(f.paymentRequest, f.network) 116 | 117 | if (f.network === undefined) f.network = decoded.network 118 | 119 | t.same(f, decoded) 120 | 121 | let tagPayeeNodeKey = decoded.tags.filter(item => item.tagName === 'payee_node_key') 122 | if (tagPayeeNodeKey.length > 0) { 123 | tagPayeeNodeKey = tagPayeeNodeKey[0] 124 | t.same(tagPayeeNodeKey, decoded.payeeNodeKey) 125 | } 126 | 127 | t.end() 128 | }) 129 | 130 | tape('test valid tagsObject for decode', (t) => { 131 | const decoded = lnpayreq.decode(f.paymentRequest, f.network) 132 | 133 | if (f.network === undefined) f.network = decoded.network 134 | 135 | t.same(f, decoded) 136 | t.assert(!!decoded.tagsObject) 137 | const keys = Object.keys(decoded.tagsObject) 138 | t.assert(keys.length > 0) 139 | 140 | keys.forEach(key => { 141 | const data = decoded.tagsObject[key] 142 | const tagsData = decoded.tags.filter(item => item.tagName === key) 143 | t.assert(tagsData.length === 1) 144 | t.same(data, tagsData[0].data) 145 | }) 146 | 147 | t.end() 148 | }) 149 | 150 | tape('test valid decode reverse encode without privateKey then with privateKey', (t) => { 151 | const decoded = lnpayreq.decode(f.paymentRequest, f.network) 152 | const encodedNoPriv = lnpayreq.encode(decoded) 153 | 154 | delete decoded.signature 155 | delete decoded.recoveryFlag 156 | 157 | const encodedWithPrivObj = lnpayreq.encode(decoded, false) 158 | 159 | delete encodedWithPrivObj.payeeNodeKey 160 | 161 | const signedData = lnpayreq.sign(encodedWithPrivObj, fixtures.privateKey) 162 | 163 | const encodedSignedData = lnpayreq.encode(signedData, false) 164 | 165 | encodedWithPrivObj.payeeNodeKey = signedData.payeeNodeKey 166 | 167 | const signedData2 = lnpayreq.sign(encodedWithPrivObj, fixtures.privateKey) 168 | 169 | const signedData3 = lnpayreq.sign(signedData2, fixtures.privateKey) 170 | 171 | t.same(f, encodedNoPriv) 172 | t.same(f, signedData) 173 | t.same(f, encodedSignedData) 174 | t.same(f, signedData2) 175 | t.same(f, signedData3) 176 | 177 | t.end() 178 | }) 179 | }) 180 | 181 | fixtures.decode.invalid.forEach((f) => { 182 | tape('test invalid vectors for decode', (t) => { 183 | t.plan(1) 184 | 185 | t.throws(() => { 186 | lnpayreq.decode(f.paymentRequest, f.network) 187 | }, new RegExp(f.error)) 188 | }) 189 | }) 190 | 191 | // edge cases 192 | 193 | function tagsItems (tags, tagName) { 194 | const tag = tags.filter(item => item.tagName === tagName) 195 | const data = tag.length > 0 ? tag[0].data : null 196 | return data 197 | } 198 | 199 | function tagsContainItem (tags, tagName) { 200 | return tagsItems(tags, tagName) !== null 201 | } 202 | 203 | tape('decode detects invalid network', (t) => { 204 | const f = fixtures.decode.valid[3] 205 | t.throws(() => { 206 | lnpayreq.decode(f.paymentRequest, { bech32: 'bc' }) 207 | }, new RegExp('Invalid network')) 208 | t.end() 209 | }) 210 | 211 | tape('encode adds defaults by default', (t) => { 212 | const encoded = lnpayreq.encode({ 213 | tags: [ 214 | { 215 | tagName: 'payment_hash', 216 | data: '100102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f' 217 | } 218 | ] 219 | }) 220 | 221 | t.ok(encoded.timestamp !== undefined) 222 | t.ok(encoded.network !== undefined) 223 | t.ok(tagsContainItem(encoded.tags, 'description')) 224 | t.ok(tagsContainItem(encoded.tags, 'expire_time')) 225 | t.ok(tagsContainItem(encoded.tags, 'min_final_cltv_expiry')) 226 | 227 | t.end() 228 | }) 229 | 230 | tape('can decode upper case payment request', (t) => { 231 | const decoded = lnpayreq.decode('LNBC2500U1PVJLUEZPP5QQQSYQCYQ5RQWZQFQQQSYQC' + 232 | 'YQ5RQWZQFQQQSYQCYQ5RQWZQFQYPQDQ5XYSXXATSYP3' + 233 | 'K7ENXV4JSXQZPUAZTRNWNGZN3KDZW5HYDLZF03QDGM2' + 234 | 'HDQ27CQV3AGM2AWHZ5SE903VRUATFHQ77W3LS4EVS3C' + 235 | 'H9ZW97J25EMUDUPQ63NYW24CG27H2RSPFJ9SRP') 236 | t.ok(decoded.complete === true) 237 | t.end() 238 | }) 239 | 240 | tape('can decode and encode payment request containing unknown tags', (t) => { 241 | const paymentRequest = 'lntb30m1pw2f2yspp5s59w4a0kjecw3zyexm7zur8l8n4scw674w' + 242 | '8sftjhwec33km882gsdpa2pshjmt9de6zqun9w96k2um5ypmkjar' + 243 | 'gypkh2mr5d9cxzun5ypeh2ursdae8gxqruyqvzddp68gup69uhnz' + 244 | 'wfj9cejuvf3xshrwde68qcrswf0d46kcarfwpshyaplw3skw0tdw' + 245 | '4k8g6tsv9e8glzddp68gup69uhnzwfj9cejuvf3xshrwde68qcrs' + 246 | 'wf0d46kcarfwpshyaplw3skw0tdw4k8g6tsv9e8gcqpfmy8keu46' + 247 | 'zsrgtz8sxdym7yedew6v2jyfswg9zeqetpj2yw3f52ny77c5xsrg' + 248 | '53q9273vvmwhc6p0gucz2av5gtk3esevk0cfhyvzgxgpgyyavt' 249 | 250 | const decoded = lnpayreq.decode(paymentRequest, { 251 | bech32: 'tb', 252 | pubKeyHash: 0x6f, 253 | scriptHash: 0xc4, 254 | validWitnessVersions: [0, 1] 255 | }) 256 | t.ok(decoded.complete === true) 257 | 258 | // Check tagsObject for the unknownTag 259 | t.assert(decoded.tagsObject.unknownTags.length === 2) 260 | t.same(decoded.tagsObject.unknownTags[0], decoded.tags[3].data) 261 | t.same(decoded.tagsObject.unknownTags[1], decoded.tags[4].data) 262 | 263 | const encoded = lnpayreq.encode(decoded) 264 | t.same(encoded.paymentRequest, paymentRequest) 265 | 266 | // make canReconstruct false 267 | // encoding unknown tags should fail if making a new request 268 | // if signature and recoveryFlag are present there are checks 269 | // to make sure that the data is what is signed 270 | // As long as it is impossible to create 271 | decoded.signature = undefined 272 | decoded.recoveryFlag = undefined 273 | 274 | t.throws(() => { 275 | lnpayreq.encode(decoded) 276 | }, new RegExp('Unknown tag key: unknownTag')) 277 | 278 | t.end() 279 | }) 280 | 281 | tape('can decode unknown network payment request', (t) => { 282 | const network = { 283 | bech32: 'sb', 284 | pubKeyHash: 0x6f, 285 | scriptHash: 0xc4, 286 | validWitnessVersions: [0, 1] 287 | } 288 | const decoded = lnpayreq.decode( 289 | 'lnsb1u1pwslkj8pp52u27w39645j24a0zfxnwytshxserjchdqt8nz8uwv9fp8wasxrhsdq' + 290 | 'l2pkxz7tfdenjqum0w4hxggrgv4kxj7qcqzpgnvqq8t63nxmgha5945s633fdd3p5x9k889' + 291 | 'g6p02qsghx4vrgqgr3xzz3hgld8r84ellwgz3teexvqzwlxj7lgkhl8xh2p7dstq0fgsspa' + 292 | '5ldq6', 293 | network 294 | ) 295 | t.ok(decoded.complete === true) 296 | t.end() 297 | }) 298 | 299 | tape('can encode and decode small timestamp', (t) => { 300 | const encoded = lnpayreq.encode({ 301 | satoshis: 12, 302 | timestamp: 1, 303 | network: { 304 | bech32: 'tb', 305 | pubKeyHash: 111, 306 | scriptHash: 196, 307 | validWitnessVersions: [0, 1] 308 | }, 309 | tags: [ 310 | { 311 | tagName: 'payment_hash', 312 | data: '0001020304050607080900010203040506070809000102030405060708090102' 313 | } 314 | ] 315 | }) 316 | 317 | const signedData = lnpayreq.sign(encoded, fixtures.privateKey) 318 | 319 | const decoded = lnpayreq.decode(signedData.paymentRequest) 320 | delete decoded.paymentRequest 321 | // This would fail because of corruption before fixing timestamp encoding 322 | const reEncoded = lnpayreq.encode(decoded) 323 | t.same(reEncoded.paymentRequest, signedData.paymentRequest) 324 | t.end() 325 | }) 326 | -------------------------------------------------------------------------------- /test/list.md: -------------------------------------------------------------------------------- 1 | # List of tests 2 | 3 | ## Encode 4 | 5 | * coinType 6 | * undefined coinType sets as testnet 7 | * ***DONE*** unknown coinType throws error (Unknown coin type) 8 | * payment hash 9 | * ***DONE*** missing payment hash throws error (Lightning Payment Request needs a payment hash) 10 | * payment hash is not correct size or data type throws error 11 | * description 12 | * missing description or purpose commit will set empty description tag 13 | * Allow empty string for description 14 | * payeeNodeKey 15 | * ***DONE*** if payeeNodeKey and tag payee node key do not match throws error (payeeNodeKey and tag payee node key do not match) 16 | * privateKey 17 | * ***DONE*** without private key or signature data throws error (Lightning Payment Request needs signature data OR privateKey buffer) 18 | * ***DONE*** private key wrong length or too large throws error (The private key given is not valid for SECP256K1) 19 | * ***DONE*** if private key doesn't match payee node key tag, throws error (The private key given is not the private key of the node public key given) 20 | * fallback address 21 | * ***DONE*** bech32 address with unknown witness version throws error (Fallback address witness version is unknown) 22 | * ***DONE*** bech32 prefix doesn't match network prefix throws error (Fallback address network type does not match payment request network type) 23 | * ***DONE*** if base58 address version doesn't match P2PKH or P2SH of network, throws error (Fallback address version (base58) is unknown or the network type is incorrect) 24 | * ***DONE*** if bech32 and base58 both fail, throws error (Fallback address type is unknown) 25 | * routing info 26 | * ***DONE*** if missing info throws error (Routing info is incomplete) 27 | * ***DONE*** if route pubkey is not valid pubkey throws error (Routing info pubkey is not a valid pubkey) 28 | * ***DONE*** if short channel id is not right length throws error (Routing info short channel id must be 8 bytes) 29 | * ***DONE*** mSats is not an integer throws error (Routing info fee is not an integer) 30 | * ***DONE*** cltvExpiryDelta is not an integer throws error (Routing info cltv expiry delta is not an integer) 31 | * tags 32 | * ***DONE*** unknown tag name throws error (Unknown tag key:) 33 | * signature 34 | * ***DONE*** payee node key tag doesn't match the recovered pulic key (data corruption) throws error (Signature, message, and recoveryID did not produce the same pubkey as payeeNodeKey) 35 | * ***DONE*** signature only encoding when you don't have a payeeNodekey OR a payee node key tag throws error (Reconstruction with signature and recoveryID requires payeeNodeKey to verify correctness of input data.) 36 | 37 | ## Decode 38 | 39 | * prefix 40 | * ***DONE*** Doesn't start with `ln` should throw error (Not a proper lightning payment request) 41 | * missing cointype throws error (Not a proper lightning payment request) 42 | * multiplier isn't in [munp], throws error (Unknown multiplier used in amount) 43 | * signature 44 | * recoveryID is not between 0-3 or signature is not 64 bytes throws error (Signature is missing or incorrect) 45 | * recovered pubkey does not match node key tag throws error (Lightning Payment Request signature pubkey does not match payee pubkey) 46 | --------------------------------------------------------------------------------