diff --git a/.gitignore b/.gitignore index 69b774b..43f5114 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ boltcard createboltcard/createboltcard wipeboltcard/wipeboltcard -cli/cli # Test binary, built with `go test -c` *.test diff --git a/cli/main.go b/cli/main.go deleted file mode 100644 index b11bc1d..0000000 --- a/cli/main.go +++ /dev/null @@ -1,198 +0,0 @@ -package main - -import ( - "bytes" - "crypto/aes" - "encoding/hex" - "fmt" - "github.com/aead/cmac" - "github.com/boltcard/boltcard/crypto" - "os" -) - -// inspired by parse_request() in lnurlw_request.go - -func aes_cmac(key_sdm_file_read_mac []byte, sv2 []byte, ba_c []byte) (bool, error) { - - c2, err := aes.NewCipher(key_sdm_file_read_mac) - if err != nil { - return false, err - } - - ks, err := cmac.Sum(sv2, c2, 16) - if err != nil { - return false, err - } - - fmt.Println("ks = ", ks) - - c3, err := aes.NewCipher(ks) - if err != nil { - return false, err - } - - cm, err := cmac.Sum([]byte{}, c3, 16) - if err != nil { - return false, err - } - - fmt.Println("cm = ", cm) - - ct := make([]byte, 8) - ct[0] = cm[1] - ct[1] = cm[3] - ct[2] = cm[5] - ct[3] = cm[7] - ct[4] = cm[9] - ct[5] = cm[11] - ct[6] = cm[13] - ct[7] = cm[15] - - fmt.Println("ct = ", ct) - - res_cmac := bytes.Compare(ct, ba_c) - if res_cmac != 0 { - return false, nil - } - - return true, nil -} - -func check_cmac(uid []byte, ctr []byte, k2_cmac_key []byte, cmac []byte) (bool, error) { - - sv2 := make([]byte, 16) - sv2[0] = 0x3c - sv2[1] = 0xc3 - sv2[2] = 0x00 - sv2[3] = 0x01 - sv2[4] = 0x00 - sv2[5] = 0x80 - sv2[6] = uid[0] - sv2[7] = uid[1] - sv2[8] = uid[2] - sv2[9] = uid[3] - sv2[10] = uid[4] - sv2[11] = uid[5] - sv2[12] = uid[6] - sv2[13] = ctr[2] - sv2[14] = ctr[1] - sv2[15] = ctr[0] - - fmt.Println("sv2 = ", sv2) - - cmac_verified, err := aes_cmac(k2_cmac_key, sv2, cmac) - - if err != nil { - return false, err - } - - return cmac_verified, nil -} - -func main() { - - fmt.Println("-- bolt card crypto test vectors --") - fmt.Println() - - args := os.Args[1:] - - if len(args) != 4 { - fmt.Println("error: should have arguments for: p c aes_decrypt_key aes_cmac_key") - os.Exit(1) - } - - // get from args - p_hex := args[0] - c_hex := args[1] - aes_decrypt_key_hex := args[2] - aes_cmac_key_hex := args[3] - - fmt.Println("p = ", p_hex) - fmt.Println("c = ", c_hex) - fmt.Println("aes_decrypt_key = ", aes_decrypt_key_hex) - fmt.Println("aes_cmac_key = ", aes_cmac_key_hex) - fmt.Println() - - p, err := hex.DecodeString(p_hex) - - if err != nil { - fmt.Println("ERROR: p not valid hex", err) - os.Exit(1) - } - - c, err := hex.DecodeString(c_hex) - - if err != nil { - fmt.Println("ERROR: c not valid hex", err) - os.Exit(1) - } - - if len(p) != 16 { - fmt.Println("ERROR: p length not valid") - os.Exit(1) - } - - if len(c) != 8 { - fmt.Println("ERROR: c length not valid") - os.Exit(1) - } - - // decrypt p with aes_decrypt_key - - aes_decrypt_key, err := hex.DecodeString(aes_decrypt_key_hex) - - if err != nil { - fmt.Println("ERROR: DecodeString() returned an error", err) - os.Exit(1) - } - - dec_p, err := crypto.Aes_decrypt(aes_decrypt_key, p) - - if err != nil { - fmt.Println("ERROR: Aes_decrypt() returned an error", err) - os.Exit(1) - } - - if dec_p[0] != 0xC7 { - fmt.Println("ERROR: decrypted data does not start with 0xC7 so is invalid") - os.Exit(1) - } - - uid := dec_p[1:8] - - ctr := make([]byte, 3) - ctr[0] = dec_p[10] - ctr[1] = dec_p[9] - ctr[2] = dec_p[8] - - // set up uid & ctr for card record if needed - - uid_str := hex.EncodeToString(uid) - ctr_str := hex.EncodeToString(ctr) - - fmt.Println("decrypted card data : uid", uid_str, ", ctr", ctr_str) - - // check cmac - - aes_cmac_key, err := hex.DecodeString(aes_cmac_key_hex) - - if err != nil { - fmt.Println("ERROR: aes_cmac_key is not valid hex", err) - os.Exit(1) - } - - cmac_valid, err := check_cmac(uid, ctr, aes_cmac_key, c) - - if err != nil { - fmt.Println("ERROR: check_cmac() returned an error", err) - os.Exit(1) - } - - if cmac_valid == false { - fmt.Println("ERROR: cmac incorrect") - os.Exit(1) - } - - fmt.Println("cmac validates ok") - os.Exit(0) -} diff --git a/docker_init.sh b/docker_init.sh index 708070c..926ed5b 100755 --- a/docker_init.sh +++ b/docker_init.sh @@ -32,4 +32,3 @@ sed -i "s/[(]'FEE_LIMIT_PERCENT'[^)]*[)]/(\'FEE_LIMIT_PERCENT\', \'0.5\')/" sql/ sed -i "s/[(]'FUNCTION_LNURLW'[^)]*[)]/(\'FUNCTION_LNURLW\', \'ENABLE\')/" sql/settings.sql sed -i "s/[(]'FUNCTION_LNURLP'[^)]*[)]/(\'FUNCTION_LNURLP\', \'DISABLE\')/" sql/settings.sql sed -i "s/[(]'FUNCTION_EMAIL'[^)]*[)]/(\'FUNCTION_EMAIL\', \'DISABLE\')/" sql/settings.sql -sed -i "s/[(]'LN_INVOICE_EXPIRY_SEC'[^)]*[)]/(\'LN_INVOICE_EXPIRY_SEC\', \'3600\')/" sql/settings.sql diff --git a/docs/CARD_MANUAL.md b/docs/CARD_MANUAL.md index 6688650..d7613be 100644 --- a/docs/CARD_MANUAL.md +++ b/docs/CARD_MANUAL.md @@ -46,9 +46,7 @@ lnurlw://card.yourdomain.com/ln ``` lnurlw://card.yourdomain.com/ln?p=00000000000000000000000000000000&c=0000000000000000 ``` - - click after `p=` and note the p_position (41 in this case) -![find the p_position](images/posn-p.webp) - click after `c=` and note the c_position (76 in this case) - select `Write To Tag` @@ -103,7 +101,7 @@ lnurlw://card.yourdomain.com/ln?p=00000000000000000000000000000000&c=00000000000 - set up the values in the order shown -![file and SDM options with field entry order](images/fs-add-2.webp) +![file and SDM options with field entry order](images/fs-add.webp) - select `Change File Settings` diff --git a/docs/DEEPLINK.md b/docs/DEEPLINK.md deleted file mode 100644 index 4ac2b1a..0000000 --- a/docs/DEEPLINK.md +++ /dev/null @@ -1,113 +0,0 @@ -## Abstract - -Boltcard NFC Programmer App is a native app on iOS and Android able to program or reset NTag424 into a Boltcard, the typical steps in the setup a Boltcard are: - -1. The `Boltcard Service` generates the keys, and format them into a QR Code -2. The user opens the Boltcard NFC Programmer, go to `Create Bolt Card`, scans the QR code -3. The user then taps the card - -The QR code contains all the keys necessary for the app to create the Boltcard. - -Here are the shortcomings of this process that we aim to address in this specification: - -1. If the QR code get displayed on the mobile device itself, it is difficult to scan it -2. The `Boltcard Service`, not knowing the UID when the keys are requested, isn't able to assign a specific pair of keys for the NTag424 being setup (for example, the [deterministic key generation](./DETERMINISTIC.md) needs the UID before generating the keys) - -## Boltcard deeplinks - -The solution is for the `Boltcard Service` to generate deep links with the following format: `boltcard://[program|reset]?url=[keys-request-url]`. - -When clicked, `Boltcard NFC Programmer` would open and either allow the user to program their NTag424 or reset it after asking for the NTags keys to the `keys-request-url`. - -The `Boltcard NFC Programmer` should send an HTTP POST request with `Content-Type: application/json` in the following format: - -```json -{ - "UID": "[UID]" -} -``` - -Or - -```json -{ - "LNURLW": "lnurlw://..." -} -``` - -In `curl`: - -```bash -curl -X POST "[keys-request-url]" -H "Content-Type: application/json" -d '{"UID": "[UID]"}' -``` - -* `UID` needs to be 7 bytes. (Program action) -* `LNURLW` needs to be read from the Boltcard's NDEF and can be sent in place of `UID`. It must contains the `p=` and `c=` arguments of the Boltcard. (Reset action) - -The response will be similar to the format of the QR code: - -```json -{ - "LNURLW": "lnurlw://...", - "K0":"[Key0]", - "K1":"[Key1]", - "K2":"[Key2]", - "K3":"[Key3]", - "K4":"[Key4]" -} -``` - -## The Program action - -If `program` is specified in the Boltcard link, the `Boltcard NFC Programmer` must: - -1. Check if the lnurlw `NDEF` can be read. - * If the record can be read, then the card isn't blank, an error should be displayed to the user to first reset the Boltcard. - * If the record can't be read, assume `K0` is `00000000000000000000000000000000` authenticate and call `GetUID` on the card again. (Since `GetUID` is called after authentication, the real `UID` will be returned even if `Random UID` has been activated) -2. Send a request to the `keys-request-url` using the UID as explained above to get the NTag424 app keys -3. Program the Boltcard with the app keys - -## The Reset action - -If `reset` is specified in the Boltcard link, the `Boltcard NFC Programmer` must: -1. Check if the lnurlw `NDEF` can be read. - * If the record can't be read, then the card is already reset, show an error message to the user. - * If the record can be read, continue to step 2. -2. Send a request to the `keys-request-url` using the lnurlw as explained above to get the NTag424 app keys -3. Reset the Boltcard to factory state with the app keys - -## Handling setup/reset cycles for Boltcard Services - -When a NTag424 is reset, its counter is reset too. -This means that if the user: - -* Setup a Boltcard -* Make `5` payments -* Reset the Boltcard -* Setup the Boltcard on same `keys-request-url` - -With a naive implementation, the server will expect the next counter to be above `5`, but the next payment will have a counter of `0`. - -More precisely, the user will need to tap the card `5` times before being able to use the Boltcard for a payment successfully again. - -To avoid this issue the `Boltcard Service`, if using [Deterministic key generation](./DETERMINISTIC.md), should ensure it updates the key version during a `program` action. - -This can be done easily by the `Boltcard Service` by adding a parameter in the `keys-request-url` which specifies that the version need to be updated. - -When the `Boltcard NFC Programmer` queries the URL with the UID of the card, the `Boltcard Service` will detect this parameter, and update the version. - -## Test vectors - -Here is an example of two links for respectively program the Boltcard and Reset it. - -```html -

- - Setup Boltcard - -  |  - - Reset Boltcard - -

-``` diff --git a/docs/DETERMINISTIC.md b/docs/DETERMINISTIC.md deleted file mode 100644 index eec8171..0000000 --- a/docs/DETERMINISTIC.md +++ /dev/null @@ -1,216 +0,0 @@ -## Abstract - -The NXP NTAG424DNA allows applications to configure five application keys, named `K0`, `K1`, `K2`, `K3`, and `K4`. In the BoltCard configuration: - -* `K0` is the `App Master Key`, it is the only key permitted to change the application keys. -* `K1` serves as the `encryption key` for the `PICCData`, represented by the `p=` parameter. -* `K2` is the `authentication key` used for calculating the SUN MAC of the `PICCData`, represented by the `c=` parameter. -* `K3` and `K4` are not used but should be configured as recommended in the [NTag424 application notes](https://www.nxp.com/docs/en/application-note/AN12196.pdf). - -A simple approach to issuing BoltCards would involve randomly generating the five different keys and storing them in a database. - -When a validation request is made, the verifier would attempt to decrypt the `p=` parameter using all existing encryption keys until finding a match. Once decrypted, the `p=` parameter would reveal the card's uid, which can then be used to retrieve the remaining keys. - -The primary drawback of this method is its lack of scalability. If many cards have been issued, identifying the correct encryption key could become a computationally intensive task. - -In this document, we propose a solution to this issue. - -## Keys generation - -First, the `LNUrl Withdraw Service` generates a `IssuerKey` that it will use to generate the keys for every NTag424. - -Then, configure a BoltCard as follows: - -* `CardKey = PRF(IssuerKey, '2d003f75' || UID || Version)` -* `K0 = PRF(CardKey, '2d003f76')` -* `K1 = PRF(IssuerKey, '2d003f77')` -* `K2 = PRF(CardKey, '2d003f78')` -* `K3 = PRF(CardKey, '2d003f79')` -* `K4 = PRF(CardKey, '2d003f7a')` -* `ID = PRF(IssuerKey, '2d003f7b' || UID)` - -With the following parameters: -* `IssuerKey`: This 16-bytes key is used by an `LNUrl Withdraw Service` to setup all its BoltCards. -* `UID`: This is the 7-byte ID of the card. You can retrieve it from the NTag424 using the `GetCardUID` function after identification with K1, or by decrypting the `p=` parameter, also known as `PICCData`. -* `Version`: A 4-bytes little endian version number. This must be incremented every time the user re-programs (reset/setup) the same BoltCard on the same `LNUrl Withdraw Service`. - -The Pseudo Random Function `PRF(key, message)` applied during the key generation is the CMAC algorithm described in NIST Special Publication 800-38B. [See implementation notes](#notes) - -## How to setup a new BoltCard - -1. Execute `ReadData` or `ISOReaDBinary` on the BoltCard to ensure the card is blank. -2. Execute `AuthenticateEV2First` with the application key `00000000000000000000000000000000` -3. Fetch the `UID` with `GetCardUID`. -4. Calculate `ID` -5. Fetch the `State` and `Version` of the BoltCard with the specified `ID` from the database. -6. Ensure either: - * If no BoltCard is found, insert an entry in the database with `Version=0` and its state set to `Configured`. - * If a BoltCard is found and its state is `Reset` then increment `Version` by `1`, and change its state to `Configured`. -7. Generate `CardKey` with `UID` and `Version`. -8. Calculate `K0`, `K1`, `K2`, `K3`, `K4`. -9. [Setup the BoltCard](./CARD_MANUAL.md). - -## How to implement a Reset feature - -If a `LNUrl Withdraw Service` offers a factory reset feature for a user's BoltCard, here is the recommended procedure: - -1. Read the NDEF lnurlw URL, extract `p=` and `c=`. -2. Derive `Encryption Key (K1)`, decrypt `p=` to obtain the `PICCData`. -3. Check `PICCData[0] == 0xc7`. -4. Calculate `ID` with the `UID` from the `PICCData`. -5. Fetch the BoltCard's `Version` with `ID` from the database. -6. Ensure the BoltCard's state is `Configured`. -7. Generate `CardKey` with `UID` and `Version`. -8. Derive `K0`, `K2`, `K3`, `K4` with `CardKey` and the `UID`. -9. Verify that the SUN MAC in `c=` matches the one calculated using `Authentication Key (K2)`. -10. Execute `AuthenticateEV2First` with `K0` -11. Erase the NDEF data file using `WriteData` or `ISOUpdateBinary` -12. Restore the NDEF file settings to default values with `ChangeFileSettings`. -13. Use `ChangeKey` with the recovered application keys to reset `K4` through `K0` to `00000000000000000000000000000000`. -14. Update the BoltCard's state to `Reset` in the database. - -Rational: Attempting to call `AuthenticateEV2First` without validating the `p=` and `c=` parameters could render the NTag inoperable after a few attempts. - -## How to implement a verification - -If a `LNUrl Withdraw Service` needs to verify a payment request, follow these steps: - -1. Read the NDEF lnurlw URL, extract `p=` and `c=`. -2. Derive `Encryption Key (K1)`, decrypts `p=` to get the `PICCData`. -3. Check `PICCData[0] == 0xc7`. -4. Calculate `ID` with the `UID` from the `PICCData`. -5. Fetch the BoltCard's `Version` with `ID` from the database. -6. Ensure the BoltCard's state in the database is not `Reset`. -7. Generate `CardKey` with `UID` and `Version`. -8. Derive `Authentication Key (K2)` with `CardKey` and the `UID`. -9. Verify that the SUN MAC in `c=` matches the one calculated using `Authentication Key (K2)`. -10. Confirm that the last-seen counter for `ID` is lower than what is stored in `counter=PICCData[8..11]`. (Little Endian) -11. Update the last-seen counter. - -Rationale: The `ID` is calculated to prevent the exposure of the `UID` in the `LNUrl Withdraw Service` database. This approach provides both privacy and security. Specifically, because the `UID` is used to derive keys, it is preferable not to store it outside the NTag. - -## Multiple IssuerKeys - -A single `LNUrl Withdraw Service` can own multiple `IssuerKeys`. In such cases, it will need to attempt them all to decrypt `p=`, and pick the first one which satisfies `PICCData[0] == 0xc7` and verifies the `c=` checksum. - -Using multiple `IssuerKeys` can decrease the impact of a compromised `Encryption Key (K1)` at the cost of performance. - -## Security consideration - -### K1 security - -Since `K1` is shared among multiple BoltCards, the security of this scheme is based on the following assumptions: - -* `K1` cannot be extracted from a legitimate NTag424. -* BoltCard setup occurs in a trusted environment. - -While NXP gives assurance keys can't be extracted, a non genuine NTag424 could potentially expose these keys. - -Furthermore, because blank NTag424 uses the well-known initial application keys `00000000000000000000000000000000`, communication between the PCD and the PICC could be intercepted. If the BoltCard setup does not occur in a trusted environment, `K1` could be exposed during the calls to `ChangeKey`. - -However, if `K1` is compromised, the attacker still cannot produce a valid checksum and can only recover the `UID` for tracking purposes. - -Note that verifying the signature returned by `Read_Sig` can only prove NXP issued a card with a specific `UID`. It cannot prove that the current communication channel is established with an authentic NTag424. This is because the signature returned by `Read_Sig` covers only the `UID` and can therefore be replayed by a non-genuine NTag424. - -### Issuer database security - -If the issuer's database is compromised, revealing both the IssuerKey and CardKeys, it would still be infeasible for an attacker to derive `K2` and thus to forge signatures for an arbitrary card. - -This is because the database only stores `ID` and not the `UID` itself. - -## Implementation notes {#notes} - -Here is a C# implementation of the CMAC algorithm described in NIST Special Publication 800-38B. - -```csharp -public byte[] CMac(byte[] data) -{ - var key = _bytes; - // SubKey generation - // step 1, AES-128 with key K is applied to an all-zero input block. - byte[] L = AesEncrypt(key, new byte[16], new byte[16]); - - // step 2, K1 is derived through the following operation: - byte[] - FirstSubkey = - RotateLeft(L); //If the most significant bit of L is equal to 0, K1 is the left-shift of L by 1 bit. - if ((L[0] & 0x80) == 0x80) - FirstSubkey[15] ^= - 0x87; // Otherwise, K1 is the exclusive-OR of const_Rb and the left-shift of L by 1 bit. - - // step 3, K2 is derived through the following operation: - byte[] - SecondSubkey = - RotateLeft(FirstSubkey); // If the most significant bit of K1 is equal to 0, K2 is the left-shift of K1 by 1 bit. - if ((FirstSubkey[0] & 0x80) == 0x80) - SecondSubkey[15] ^= - 0x87; // Otherwise, K2 is the exclusive-OR of const_Rb and the left-shift of K1 by 1 bit. - - // MAC computing - if (((data.Length != 0) && (data.Length % 16 == 0)) == true) - { - // If the size of the input message block is equal to a positive multiple of the block size (namely, 128 bits), - // the last block shall be exclusive-OR'ed with K1 before processing - for (int j = 0; j < FirstSubkey.Length; j++) - data[data.Length - 16 + j] ^= FirstSubkey[j]; - } - else - { - // Otherwise, the last block shall be padded with 10^i - byte[] padding = new byte[16 - data.Length % 16]; - padding[0] = 0x80; - - data = data.Concat(padding.AsEnumerable()).ToArray(); - - // and exclusive-OR'ed with K2 - for (int j = 0; j < SecondSubkey.Length; j++) - data[data.Length - 16 + j] ^= SecondSubkey[j]; - } - - // The result of the previous process will be the input of the last encryption. - byte[] encResult = AesEncrypt(key, new byte[16], data); - - byte[] HashValue = new byte[16]; - Array.Copy(encResult, encResult.Length - HashValue.Length, HashValue, 0, HashValue.Length); - - return HashValue; -} -static byte[] RotateLeft(byte[] b) -{ - byte[] r = new byte[b.Length]; - byte carry = 0; - - for (int i = b.Length - 1; i >= 0; i--) - { - ushort u = (ushort)(b[i] << 1); - r[i] = (byte)((u & 0xff) + carry); - carry = (byte)((u & 0xff00) >> 8); - } - - return r; -} -``` - -## Implementation - -* [BTCPayServer.BoltCardTools](https://github.com/btcpayserver/BTCPayServer.BoltCardTools), a BoltCard/NTag424 library in C#. - -## Test vectors - -Input: -``` -UID: 04a39493cc8680 -Issuer Key: 00000000000000000000000000000001 -Version: 1 -``` - -Expected: -``` -K0: a29119fcb48e737d1591d3489557e49b -K1: 55da174c9608993dc27bb3f30a4a7314 -K2: f4b404be700ab285e333e32348fa3d3b -K3: 73610ba4afe45b55319691cb9489142f -K4: addd03e52964369be7f2967736b7bdb5 -ID: e07ce1279d980ecb892a81924b67bf18 -CardKey: ebff5a4e6da5ee14cbfe720ae06fbed9 -``` \ No newline at end of file diff --git a/docs/SETTINGS.md b/docs/SETTINGS.md index 1c52afa..371b4fc 100644 --- a/docs/SETTINGS.md +++ b/docs/SETTINGS.md @@ -34,4 +34,3 @@ Here are the descriptions of values available to use in the `settings` table: | FUNCTION_INTERNAL_API | DISABLE | system level switch for activating the internal API | | SENDGRID_API_KEY | | User API Key from SendGrid.com | | SENDGRID_EMAIL_SENDER | | Single Sender email address verified by SendGrid | -| LN_INVOICE_EXPIRY_SEC | 3600 | LN invoice's expiry time in seconds | diff --git a/docs/SPEC.md b/docs/SPEC.md index a5f9db4..2712de6 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -3,21 +3,11 @@ The bolt card system is built on the technologies listed below. - [LUD-03: withdrawRequest base spec.](https://github.com/fiatjaf/lnurl-rfc/blob/luds/03.md) - - with the exception of maxWithdrawable which must be returned as higher than the actual maximum amount - [LUD-17: Protocol schemes and raw (non bech32-encoded) URLs.](https://github.com/fiatjaf/lnurl-rfc/blob/luds/17.md) - NFC Data Exchange Format (NDEF) - Replay protection - NXP Secure Unique NFC Message (SUN) technology as implemented in the NXP NTAG 424 DNA card -Bolt card systems should implement the best possible privacy. - -- [Privacy levels](https://github.com/boltcard/boltcard/blob/main/docs/CARD_PRIVACY.md) - -Bolt card systems may optionally support these technogies. - -- [LUD-19: Pay link discoverable from withdraw link.](https://github.com/lnurl/luds/blob/luds/19.md) -- [LUD-21: pinLimit for withdrawRequest](https://github.com/bitcoin-ring/luds/blob/withdraw-pin/21.md) - ## Bolt card and POS interaction the point-of-sale (POS) will read a NDEF message from the card, which changes with each use, for example diff --git a/docs/TECHNOLOGY.md b/docs/TECHNOLOGY.md index 9c1d248..9dfc777 100644 --- a/docs/TECHNOLOGY.md +++ b/docs/TECHNOLOGY.md @@ -4,8 +4,6 @@ | --- | --- | | [System](SYSTEM.md) | Bolt card system overview | | [Specification](SPEC.md) | Bolt card specifications | -| [Deterministic Keys (DRAFT FOR COMMENT)](DETERMINISTIC.md) | Consideration about key generation | -| [Boltcard Setup via Deeplink](DEEPLINK.md) | Deeplink for Boltcard creator apps | | [Privacy](CARD_PRIVACY.md) | Bolt card privacy | | [NXP 424 Datasheet](NT4H2421Gx.pdf) | NXP NTAG424DNA datasheet | | [NXP 424 Application Note](NT4H2421Gx.pdf) | NXP NTAG424DNA features and hints | diff --git a/docs/TEST_VECTORS.md b/docs/TEST_VECTORS.md deleted file mode 100644 index 646922a..0000000 --- a/docs/TEST_VECTORS.md +++ /dev/null @@ -1,54 +0,0 @@ -# test vectors - -some test vectors to help with developing code to AES decode and validate lnurlw:// requests - -these have been created by using an actual card and with [a small command line utility](https://github.com/boltcard/boltcard/blob/main/cli/main.go) - -``` --- bolt card crypto test vectors -- - -p = 4E2E289D945A66BB13377A728884E867 -c = E19CCB1FED8892CE -aes_decrypt_key = 0c3b25d92b38ae443229dd59ad34b85d -aes_cmac_key = b45775776cb224c75bcde7ca3704e933 - -decrypted card data : uid 04996c6a926980 , ctr 000003 -sv2 = [60 195 0 1 0 128 4 153 108 106 146 105 128 3 0 0] -ks = [242 92 75 92 230 171 63 244 5 242 135 175 172 78 77 26] -cm = [118 225 233 156 238 203 64 31 163 237 110 136 112 146 124 206] -ct = [225 156 203 31 237 136 146 206] -cmac validates ok - - - --- bolt card crypto test vectors -- - -p = 00F48C4F8E386DED06BCDC78FA92E2FE -c = 66B4826EA4C155B4 -aes_decrypt_key = 0c3b25d92b38ae443229dd59ad34b85d -aes_cmac_key = b45775776cb224c75bcde7ca3704e933 - -decrypted card data : uid 04996c6a926980 , ctr 000005 -sv2 = [60 195 0 1 0 128 4 153 108 106 146 105 128 5 0 0] -ks = [73 70 39 105 116 24 126 152 96 101 139 189 130 16 200 190] -cm = [94 102 243 180 93 130 2 110 198 164 241 193 67 85 112 180] -ct = [102 180 130 110 164 193 85 180] -cmac validates ok - - - --- bolt card crypto test vectors -- - -p = 0DBF3C59B59B0638D60B5842A997D4D1 -c = CC61660C020B4D96 -aes_decrypt_key = 0c3b25d92b38ae443229dd59ad34b85d -aes_cmac_key = b45775776cb224c75bcde7ca3704e933 - -decrypted card data : uid 04996c6a926980 , ctr 000007 -sv2 = [60 195 0 1 0 128 4 153 108 106 146 105 128 7 0 0] -ks = [97 189 177 81 15 79 217 5 102 95 162 58 192 199 38 97] -cm = [40 204 202 97 87 102 6 12 101 2 250 11 199 77 73 150] -ct = [204 97 102 12 2 11 77 150] -cmac validates ok - -``` diff --git a/docs/images/fs-add-2.webp b/docs/images/fs-add-2.webp deleted file mode 100644 index d395a7b..0000000 Binary files a/docs/images/fs-add-2.webp and /dev/null differ diff --git a/docs/images/fs-add.webp b/docs/images/fs-add.webp index d395a7b..6d15cec 100644 Binary files a/docs/images/fs-add.webp and b/docs/images/fs-add.webp differ diff --git a/docs/images/posn-p.webp b/docs/images/posn-p.webp deleted file mode 100644 index 0f9810a..0000000 Binary files a/docs/images/posn-p.webp and /dev/null differ diff --git a/docs/images/posn.webp b/docs/images/posn.webp deleted file mode 100644 index d395a7b..0000000 Binary files a/docs/images/posn.webp and /dev/null differ diff --git a/internalapi/createboltcard.go b/internalapi/createboltcard.go index a8aa8f3..e0e1eb3 100644 --- a/internalapi/createboltcard.go +++ b/internalapi/createboltcard.go @@ -1,13 +1,12 @@ package internalapi import ( - "net/http" - "strconv" - "strings" - "github.com/boltcard/boltcard/db" "github.com/boltcard/boltcard/resp_err" log "github.com/sirupsen/logrus" + "net/http" + "strconv" + "strings" ) // random_hex() from Createboltcardwithpin used here @@ -101,16 +100,11 @@ func Createboltcard(w http.ResponseWriter, r *http.Request) { // return the URI + one_time_code hostdomain := db.Get_setting("HOST_DOMAIN") - hostdomainPort := db.Get_setting("HOST_DOMAIN_PORT") - hostdomainsuffix := "" - if hostdomainPort != "" { - hostdomainsuffix = ":" + hostdomainPort - } url := "" if strings.HasSuffix(hostdomain, ".onion") { - url = "http://" + hostdomain + hostdomainsuffix + "/new?a=" + one_time_code + url = "http://" + hostdomain + "/new?a=" + one_time_code } else { - url = "https://" + hostdomain + hostdomainsuffix + "/new?a=" + one_time_code + url = "https://" + hostdomain + "/new?a=" + one_time_code } // log the response diff --git a/internalapi/createboltcardwithpin.go b/internalapi/createboltcardwithpin.go index a7e8cd8..64d9efd 100644 --- a/internalapi/createboltcardwithpin.go +++ b/internalapi/createboltcardwithpin.go @@ -3,13 +3,12 @@ package internalapi import ( "crypto/rand" "encoding/hex" - "net/http" - "strconv" - "strings" - "github.com/boltcard/boltcard/db" "github.com/boltcard/boltcard/resp_err" log "github.com/sirupsen/logrus" + "net/http" + "strconv" + "strings" ) func random_hex() string { @@ -133,16 +132,11 @@ func Createboltcardwithpin(w http.ResponseWriter, r *http.Request) { // return the URI + one_time_code hostdomain := db.Get_setting("HOST_DOMAIN") - hostdomainPort := db.Get_setting("HOST_DOMAIN_PORT") - hostdomainsuffix := "" - if hostdomainPort != "" { - hostdomainsuffix = ":" + hostdomainPort - } url := "" if strings.HasSuffix(hostdomain, ".onion") { - url = "http://" + hostdomain + hostdomainsuffix + "/new?a=" + one_time_code + url = "http://" + hostdomain + "/new?a=" + one_time_code } else { - url = "https://" + hostdomain + hostdomainsuffix + "/new?a=" + one_time_code + url = "https://" + hostdomain + "/new?a=" + one_time_code } // log the response diff --git a/lnd/lnd.go b/lnd/lnd.go index f86548c..c5e0c1a 100644 --- a/lnd/lnd.go +++ b/lnd/lnd.go @@ -81,10 +81,6 @@ func Add_invoice(amount_sat int64, metadata string) (payment_request string, r_h if err != nil { return "", nil, err } - ln_invoice_expiry, err := strconv.ParseInt(db.Get_setting("LN_INVOICE_EXPIRY_SEC"), 10, 64) - if err != nil { - return "", nil, err - } dh := sha256.Sum256([]byte(metadata)) @@ -102,7 +98,6 @@ func Add_invoice(amount_sat int64, metadata string) (payment_request string, r_h result, err := l_client.AddInvoice(ctx, &lnrpc.Invoice{ Value: amount_sat, DescriptionHash: dh[:], - Expiry: ln_invoice_expiry, }) if err != nil { @@ -125,11 +120,6 @@ func Monitor_invoice_state(r_hash []byte) { log.Warn(err) return } - ln_invoice_expiry, err := strconv.Atoi(db.Get_setting("LN_INVOICE_EXPIRY_SEC")) - if err != nil { - log.Warn(err) - return - } connection := getGrpcConn( db.Get_setting("LN_HOST"), @@ -139,7 +129,7 @@ func Monitor_invoice_state(r_hash []byte) { i_client := invoicesrpc.NewInvoicesClient(connection) - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(ln_invoice_expiry)*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() stream, err := i_client.SubscribeSingleInvoice(ctx, &invoicesrpc.SubscribeSingleInvoiceRequest{ @@ -238,11 +228,10 @@ func PayInvoice(card_payment_id int, invoice string) { bolt11, _ := decodepay.Decodepay(invoice) invoice_msats := bolt11.MSatoshi - invoice_expiry := bolt11.Expiry fee_limit_product := int64((fee_limit_percent / 100) * (float64(invoice_msats) / 1000)) - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(invoice_expiry)*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() stream, err := r_client.SendPaymentV2(ctx, &routerrpc.SendPaymentRequest{ diff --git a/lnurlp/lnurlp_callback.go b/lnurlp/lnurlp_callback.go index 7d02eaf..1984e8c 100644 --- a/lnurlp/lnurlp_callback.go +++ b/lnurlp/lnurlp_callback.go @@ -2,14 +2,13 @@ package lnurlp import ( "encoding/hex" - "net/http" - "strconv" - "github.com/boltcard/boltcard/db" "github.com/boltcard/boltcard/lnd" "github.com/boltcard/boltcard/resp_err" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" + "net/http" + "strconv" ) func Callback(w http.ResponseWriter, r *http.Request) { @@ -38,11 +37,6 @@ func Callback(w http.ResponseWriter, r *http.Request) { }).Info("lnurlp_callback") domain := db.Get_setting("HOST_DOMAIN") - hostdomainPort := db.Get_setting("HOST_DOMAIN_PORT") - hostdomainsuffix := "" - if hostdomainPort != "" { - hostdomainsuffix = ":" + hostdomainPort - } if r.Host != domain { log.Warn("wrong host domain") resp_err.Write(w) @@ -58,7 +52,7 @@ func Callback(w http.ResponseWriter, r *http.Request) { amount_sat := amount_msat / 1000 - metadata := "[[\"text/identifier\",\"" + name + "@" + domain + hostdomainsuffix + "\"],[\"text/plain\",\"bolt card deposit\"]]" + metadata := "[[\"text/identifier\",\"" + name + "@" + domain + "\"],[\"text/plain\",\"bolt card deposit\"]]" pr, r_hash, err := lnd.Add_invoice(amount_sat, metadata) if err != nil { log.Warn("could not add_invoice") diff --git a/lnurlp/lnurlp_request.go b/lnurlp/lnurlp_request.go index 853614d..6483388 100644 --- a/lnurlp/lnurlp_request.go +++ b/lnurlp/lnurlp_request.go @@ -1,12 +1,11 @@ package lnurlp import ( - "net/http" - "github.com/boltcard/boltcard/db" "github.com/boltcard/boltcard/resp_err" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" + "net/http" ) func Response(w http.ResponseWriter, r *http.Request) { @@ -27,11 +26,6 @@ func Response(w http.ResponseWriter, r *http.Request) { // look up domain setting (HOST_DOMAIN) domain := db.Get_setting("HOST_DOMAIN") - hostdomainPort := db.Get_setting("HOST_DOMAIN_PORT") - hostdomainsuffix := "" - if hostdomainPort != "" { - hostdomainsuffix = ":" + hostdomainPort - } if r.Host != domain { log.Warn("wrong host domain") resp_err.Write(w) @@ -53,10 +47,10 @@ func Response(w http.ResponseWriter, r *http.Request) { return } - metadata := "[[\\\"text/identifier\\\",\\\"" + name + "@" + domain + hostdomainsuffix + "\\\"],[\\\"text/plain\\\",\\\"bolt card deposit\\\"]]" + metadata := "[[\\\"text/identifier\\\",\\\"" + name + "@" + domain + "\\\"],[\\\"text/plain\\\",\\\"bolt card deposit\\\"]]" jsonData := []byte(`{"status":"OK",` + - `"callback":"https://` + domain + hostdomainsuffix + `/lnurlp/` + name + `",` + + `"callback":"https://` + domain + `/lnurlp/` + name + `",` + `"tag":"payRequest",` + `"maxSendable":1000000000,` + `"minSendable":1000,` + diff --git a/lnurlw/lnurlw_request.go b/lnurlw/lnurlw_request.go index ad392a9..eb0b15f 100644 --- a/lnurlw/lnurlw_request.go +++ b/lnurlw/lnurlw_request.go @@ -4,15 +4,14 @@ import ( "encoding/hex" "encoding/json" "errors" - "net/http" - "os" - "strconv" - "strings" - "github.com/boltcard/boltcard/crypto" "github.com/boltcard/boltcard/db" "github.com/boltcard/boltcard/resp_err" log "github.com/sirupsen/logrus" + "net/http" + "os" + "strconv" + "strings" ) func get_p_c(req *http.Request, p_name string, c_name string) (p string, c string) { @@ -247,11 +246,6 @@ func parse_request(req *http.Request) (int, error) { func Response(w http.ResponseWriter, req *http.Request) { env_host_domain := db.Get_setting("HOST_DOMAIN") - hostdomainPort := db.Get_setting("HOST_DOMAIN_PORT") - hostdomainsuffix := "" - if hostdomainPort != "" { - hostdomainsuffix = ":" + hostdomainPort - } if req.Host != env_host_domain { log.Warn("wrong host domain") @@ -286,10 +280,10 @@ func Response(w http.ResponseWriter, req *http.Request) { } lnurlw_cb_url := "" - if strings.HasSuffix(env_host_domain, ".onion") { - lnurlw_cb_url = "http://" + env_host_domain + hostdomainsuffix + "/cb" + if strings.HasSuffix(req.Host, ".onion") { + lnurlw_cb_url = "http://" + req.Host + "/cb" } else { - lnurlw_cb_url = "https://" + env_host_domain + hostdomainsuffix + "/cb" + lnurlw_cb_url = "https://" + req.Host + "/cb" } min_withdraw_sats_str := db.Get_setting("MIN_WITHDRAW_SATS") diff --git a/new_card_request.go b/new_card_request.go index 1512e4f..c35bd43 100644 --- a/new_card_request.go +++ b/new_card_request.go @@ -3,11 +3,10 @@ package main import ( "database/sql" "encoding/json" - "net/http" - "github.com/boltcard/boltcard/db" "github.com/boltcard/boltcard/resp_err" log "github.com/sirupsen/logrus" + "net/http" ) /** @@ -56,12 +55,7 @@ func new_card_request(w http.ResponseWriter, req *http.Request) { a := params_a[0] - hostdomainPort := db.Get_setting("HOST_DOMAIN_PORT") - hostdomainsuffix := "" - if hostdomainPort != "" { - hostdomainsuffix = ":" + hostdomainPort - } - lnurlw_base := "lnurlw://" + db.Get_setting("HOST_DOMAIN") + hostdomainsuffix + "/ln" + lnurlw_base := "lnurlw://" + db.Get_setting("HOST_DOMAIN") + "/ln" c, err := db.Get_new_card(a) diff --git a/sql/settings.sql b/sql/settings.sql index 810a2bb..1f6a4e4 100644 --- a/sql/settings.sql +++ b/sql/settings.sql @@ -31,4 +31,3 @@ INSERT INTO settings (name, value) VALUES ('LNDHUB_URL', ''); INSERT INTO settings (name, value) VALUES ('FUNCTION_INTERNAL_API', ''); INSERT INTO settings (name, value) VALUES ('SENDGRID_API_KEY', ''); INSERT INTO settings (name, value) VALUES ('SENDGRID_EMAIL_SENDER', ''); -INSERT INTO settings (name, value) VALUES ('LN_INVOICE_EXPIRY_SEC', '3600');