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/README.md b/README.md index b8e55c5..6201301 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ The simplest way to understand and set up your own system is to read the main do | --- | --- | | [System](docs/SYSTEM.md) | Bolt card system overview | | [Specification](docs/SPEC.md) | Bolt card specifications | -| [Privacy](docs/CARD_PRIVACY.md) | Bolt card privacy | | [Docker Service Install](docs/DOCKER_INSTALL.md) | Bolt card service docker installation | | [Automatic Card Creation](docs/CARD_ANDROID.md) | Bolt card creation using the Bolt Card app| @@ -27,8 +26,6 @@ The simplest way to understand and set up your own system is to read the main do | [Manual Card Creation](docs/CARD_MANUAL.md) | Bolt card creation using NXP TagXplorer software | | [LndHub Payments](docs/LNDHUB.md) | How to use LndHub | | [FAQ](docs/FAQ.md) | Frequently asked questions | -| [Datasheet](docs/NT4H2421Gx.pdf) | NXP NTAG424DNA datasheet | -| [Application Note](docs/NT4H2421Gx.pdf) | NXP NTAG424DNA features and hints | ## Telegram group 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/db/db.go b/db/db.go index f2fb515..fb5e8ca 100644 --- a/db/db.go +++ b/db/db.go @@ -29,9 +29,6 @@ type Card struct { One_time_code string Card_name string Allow_negative_balance string - Pin_enable string - Pin_number string - Pin_limit_sats int } type Payment struct { @@ -353,8 +350,7 @@ func Get_card_from_card_id(card_id int) (*Card, error) { `last_counter_value, lnurlw_request_timeout_sec, ` + `lnurlw_enable, tx_limit_sats, day_limit_sats, ` + `email_enable, email_address, card_name, ` + - `allow_negative_balance, pin_enable, pin_number, ` + - `pin_limit_sats FROM cards WHERE card_id=$1;` + `allow_negative_balance FROM cards WHERE card_id=$1;` row := db.QueryRow(sqlStatement, card_id) err = row.Scan( &c.Card_id, @@ -368,10 +364,7 @@ func Get_card_from_card_id(card_id int) (*Card, error) { &c.Email_enable, &c.Email_address, &c.Card_name, - &c.Allow_negative_balance, - &c.Pin_enable, - &c.Pin_number, - &c.Pin_limit_sats) + &c.Allow_negative_balance) if err != nil { return &c, err } @@ -392,7 +385,7 @@ func Get_card_from_card_name(card_name string) (*Card, error) { sqlStatement := `SELECT card_id, k2_cmac_key, uid,` + ` last_counter_value, lnurlw_request_timeout_sec,` + - ` lnurlw_enable, tx_limit_sats, day_limit_sats, pin_enable, pin_limit_sats` + + ` lnurlw_enable, tx_limit_sats, day_limit_sats` + ` FROM cards WHERE card_name=$1 AND wiped = 'N';` row := db.QueryRow(sqlStatement, card_name) err = row.Scan( @@ -403,9 +396,7 @@ func Get_card_from_card_name(card_name string) (*Card, error) { &c.Lnurlw_request_timeout_sec, &c.Lnurlw_enable, &c.Tx_limit_sats, - &c.Day_limit_sats, - &c.Pin_enable, - &c.Pin_limit_sats) + &c.Day_limit_sats) if err != nil { return &c, err } @@ -839,71 +830,6 @@ func Insert_card(one_time_code string, k0_auth_key string, k2_cmac_key string, k return nil } -func Insert_card_with_pin(one_time_code string, k0_auth_key string, k2_cmac_key string, k3 string, k4 string, - tx_limit_sats int, day_limit_sats int, lnurlw_enable bool, card_name string, uid_privacy bool, - allow_neg_bal_ptr bool, pin_enable bool, pin_number string, pin_limit_sats int) error { - - lnurlw_enable_yn := "N" - if lnurlw_enable { - lnurlw_enable_yn = "Y" - } - - uid_privacy_yn := "N" - if uid_privacy { - uid_privacy_yn = "Y" - } - - allow_neg_bal_yn := "N" - if allow_neg_bal_ptr { - allow_neg_bal_yn = "Y" - } - - pin_enable_yn := "N" - if pin_enable { - pin_enable_yn = "Y" - } - - db, err := open() - if err != nil { - return err - } - defer db.Close() - - // ensure any cards with the same card_name are wiped - - sqlStatement := `UPDATE cards SET` + - ` lnurlw_enable = 'N', lnurlp_enable = 'N', email_enable = 'N', wiped = 'Y'` + - ` WHERE card_name = $1;` - res, err := db.Exec(sqlStatement, card_name) - if err != nil { - return err - } - - // insert a new record into cards - - sqlStatement = `INSERT INTO cards` + - ` (one_time_code, k0_auth_key, k2_cmac_key, k3, k4, uid, last_counter_value,` + - ` lnurlw_request_timeout_sec, tx_limit_sats, day_limit_sats, lnurlw_enable,` + - ` one_time_code_used, card_name, uid_privacy, allow_negative_balance,` + - ` pin_enable, pin_number, pin_limit_sats)` + - ` VALUES ($1, $2, $3, $4, $5, '', 0, 60, $6, $7, $8, 'N', $9, $10, $11, $12, $13, $14);` - res, err = db.Exec(sqlStatement, one_time_code, k0_auth_key, k2_cmac_key, k3, k4, - tx_limit_sats, day_limit_sats, lnurlw_enable_yn, card_name, uid_privacy_yn, - allow_neg_bal_yn, pin_enable_yn, pin_number, pin_limit_sats) - if err != nil { - return err - } - count, err := res.RowsAffected() - if err != nil { - return err - } - if count != 1 { - return errors.New("not one card record inserted") - } - - return nil -} - func Wipe_card(card_name string) (*Card_wipe_info, error) { card_wipe_info := Card_wipe_info{} @@ -945,8 +871,7 @@ func Wipe_card(card_name string) (*Card_wipe_info, error) { return &card_wipe_info, nil } -func Update_card(card_name string, lnurlw_enable bool, tx_limit_sats int, - day_limit_sats int) error { +func Update_card(card_name string, lnurlw_enable bool, tx_limit_sats int, day_limit_sats int) error { lnurlw_enable_yn := "N" if lnurlw_enable { @@ -982,91 +907,3 @@ func Update_card(card_name string, lnurlw_enable bool, tx_limit_sats int, return nil } - -func Update_card_with_pin(card_name string, lnurlw_enable bool, tx_limit_sats int, day_limit_sats int, - pin_enable bool, pin_number string, pin_limit_sats int) error { - - lnurlw_enable_yn := "N" - if lnurlw_enable { - lnurlw_enable_yn = "Y" - } - - pin_enable_yn := "N" - if pin_enable { - pin_enable_yn = "Y" - } - - db, err := open() - - if err != nil { - return err - } - - defer db.Close() - - sqlStatement := `UPDATE cards SET lnurlw_enable = $2, tx_limit_sats = $3, day_limit_sats = $4, ` + - `pin_enable = $5, pin_number = $6, pin_limit_sats = $7 WHERE card_name = $1 AND wiped = 'N';` - - res, err := db.Exec(sqlStatement, card_name, lnurlw_enable_yn, tx_limit_sats, day_limit_sats, - pin_enable_yn, pin_number, pin_limit_sats) - - if err != nil { - return err - } - - count, err := res.RowsAffected() - - if err != nil { - return err - } - - if count != 1 { - return errors.New("not one card record updated") - } - - return nil -} - -func Update_card_with_part_pin(card_name string, lnurlw_enable bool, tx_limit_sats int, day_limit_sats int, - pin_enable bool, pin_limit_sats int) error { - - lnurlw_enable_yn := "N" - if lnurlw_enable { - lnurlw_enable_yn = "Y" - } - - pin_enable_yn := "N" - if pin_enable { - pin_enable_yn = "Y" - } - - db, err := open() - - if err != nil { - return err - } - - defer db.Close() - - sqlStatement := `UPDATE cards SET lnurlw_enable = $2, tx_limit_sats = $3, day_limit_sats = $4, ` + - `pin_enable = $5, pin_limit_sats = $6 WHERE card_name = $1 AND wiped = 'N';` - - res, err := db.Exec(sqlStatement, card_name, lnurlw_enable_yn, tx_limit_sats, day_limit_sats, - pin_enable_yn, pin_limit_sats) - - if err != nil { - return err - } - - count, err := res.RowsAffected() - - if err != nil { - return err - } - - if count != 1 { - return errors.New("not one card record updated") - } - - return nil -} 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/AN12196.pdf b/docs/AN12196.pdf deleted file mode 100644 index c1ecbc3..0000000 Binary files a/docs/AN12196.pdf and /dev/null differ 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) - - 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 - + - select `Change File Settings` diff --git a/docs/CARD_PRIVACY.md b/docs/CARD_PRIVACY.md deleted file mode 100644 index c164df4..0000000 --- a/docs/CARD_PRIVACY.md +++ /dev/null @@ -1,64 +0,0 @@ -# Card Privacy - -## Payment tracking - -This document describes the different levels of privacy possible with bolt card implementations. - -## Card NDEF - -The URI that is programmed into the card and returned as the NDEF consists of three parts. -1. The static part -2. The encrypted part -3. The authentication part - -``` -lnurlw://card.yourdomain.com/ln?p=A2EF40F6D46F1BB36E6EBF0114D4A464&c=F509EEA788E37E32 URI example - -lnurlw://card.yourdomain.com/ln?p= &c= static - - A2EF40F6D46F1BB36E6EBF0114D4A464 encrypted - - F509EEA788E37E32 authentication -``` - -| part | use | -|------|-----| -| static | specfying the protocol and service location as a URI | -| encrypted | unique id and counter values encrypted by the card | -| authentication | a value to authenticate that the entire URI is as generated by the card | - -## Card privacy levels - -In order for the system to work, the card must provide the point-of-sale with a URL for the backend server. -For maximum privacy, it should not be possible for the point-of-sale to identify the card any further than this. - -Unfortunately, early implementations do not have this fully built out. - -You can check your card/s by reading the NDEF value (e.g. with the NXP TagInfo app) to check for a static identifier or a static UID value. This will enable you to find the level of privacy that has been implemented on creating the card. - -| Privacy level | Static id | UID plaintext| -| ------------- | --------- | ------------ | -| minimal | yes | yes | -| good | no | yes | -| best | no | no | - -### Minimal privacy (aka tracker) - -An identifier is included in the static part of the lnurlw. -This is used on the server side to look up the decryption key and the authentication key per card. -This is how early systems were implemented and allows the point-of-sale devices to track the use of the card. - -### Good privacy - -There is no identifier included in the static part of the lnurlw. -This is made possible by holding the decryption key at database level. -The authentication key is still recorded per card. - -This protects against leaking of point-of-sale databases and log files, however, a untrustworthy point-of-sale could still obtain the card UID using proprietary NXP commands. - -### Best privacy - -There is no identifier included in the static part of the lnurlw. -In addition, the UID field is made inaccessible by NXP proprietary commands by using the Random ID feature. - -This protects against individual card tracking by trustworthy and untrustworthy point-of-sale systems. 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/FAQ.md b/docs/FAQ.md index 350ef87..ef30e43 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -25,12 +25,3 @@ It can be useful to test paying invoices directly from your lightning node. # Can I use the same lightning node for the customer (bolt card) and the merchant (POS) ? When tested with LND in Nov 2022, the paying (customer, bolt card) lightning node must be a separate instance to the invoicing (merchant, POS) lightning node. - -# I get a 6982 error when trying to program a blank card - -A 6982 error is is known to happen after trying to use a 'blank' card which has been wiped with the CoinCorner customer app (July 2023) and happens because the card settings have not been cleared down correctly. It can also happen where a card is removed partway through programming (which can take a few seconds) or where the mobile device does not complete programming due to being in a low battery situation. -The card settings can be fixed by using the 'Bolt Card NFC Card Creator' app. The card will then be blank and usable again. -- Reset Keys -- Enter all '0's in Key 0 until the field is full and copy to Keys 1-4 -- Reset Card Now -- present the card diff --git a/docs/INSTALL.md b/docs/INSTALL.md index ea4bed2..faca21e 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -106,7 +106,6 @@ add email notifications for payments and fund receipt `AWS_SES_ID=..."` (settings table) `AWS_SES_SECRET=..."` (settings table) `AWS_SES_EMAIL_FROM=..."` (settings table) -`AWS_REGION=...` (settings table) `FUNCTION_EMAIL=ENABLE"` (settings table) `cards.email_address='card.notifications@yourdomain.com'` `cards.email_enable='Y'` diff --git a/docs/NT4H2421Gx.pdf b/docs/NT4H2421Gx.pdf deleted file mode 100644 index 6612e8b..0000000 Binary files a/docs/NT4H2421Gx.pdf and /dev/null differ diff --git a/docs/SETTINGS.md b/docs/SETTINGS.md index 1c52afa..e42df0b 100644 --- a/docs/SETTINGS.md +++ b/docs/SETTINGS.md @@ -23,15 +23,10 @@ Here are the descriptions of values available to use in the `settings` table: | FUNCTION_LNURLW | ENABLE | system level switch for LNURLw (bolt card) services | | FUNCTION_LNURLP | DISABLE | system level switch for LNURLp (lightning address) services | | FUNCTION_EMAIL | DISABLE | system level switch for email updates on credits & debits | -| DEFAULT_DESCRIPTION | 'bolt card service' | default description of payment | | AWS_SES_ID | | Amazon Web Services - Simple Email Service - access id | | AWS_SES_SECRET | | Amazon Web Services - Simple Email Service - access secret | | AWS_SES_EMAIL_FROM | | Amazon Web Services - Simple Email Service - email from field | -| AWS_REGION | us-east-1 | Amazon Web Services - Account region | | EMAIL_MAX_TXS | | maximum number of transactions to include in the email body | | FUNCTION_LNDHUB | DISABLE | system level switch for using LNDHUB in place of LND | | LNDHUB_URL | | URL for the LNDHUB service | | 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 deleted file mode 100644 index 9c1d248..0000000 --- a/docs/TECHNOLOGY.md +++ /dev/null @@ -1,12 +0,0 @@ -## Bolt Card technology - -| Document | Description | -| --- | --- | -| [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 | -| [FAQ](FAQ.md) | Bolt card FAQ | 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/email/email.go b/email/email.go index f0335c4..8f78ddd 100644 --- a/email/email.go +++ b/email/email.go @@ -1,18 +1,15 @@ package email import ( - "strconv" - "strings" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ses" "github.com/boltcard/boltcard/db" - "github.com/sendgrid/sendgrid-go" - "github.com/sendgrid/sendgrid-go/helpers/mail" log "github.com/sirupsen/logrus" + "strconv" + "strings" ) func Send_balance_email(recipient_email string, card_id int) { @@ -89,91 +86,69 @@ func Send_balance_email(recipient_email string, card_id int) { } // https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/ses-example-send-email.html -// https://github.com/sendgrid/sendgrid-go func Send_email(recipient string, subject string, htmlBody string, textBody string) { - send_grid_api_key := db.Get_setting("SENDGRID_API_KEY") - send_grid_email_sender := db.Get_setting("SENDGRID_EMAIL_SENDER") - if send_grid_api_key != "" && send_grid_email_sender != "" { - from := mail.NewEmail("", send_grid_email_sender) - subject := subject - to := mail.NewEmail("", recipient) - plainTextContent := textBody - htmlContent := htmlBody - message := mail.NewSingleEmail(from, subject, to, plainTextContent, htmlContent) - client := sendgrid.NewSendClient(send_grid_api_key) - response, err := client.Send(message) - if err != nil { - log.Warn(err.Error()) - } else { - log.WithFields(log.Fields{"result": response}).Info("email sent") - } + aws_ses_id := db.Get_setting("AWS_SES_ID") + aws_ses_secret := db.Get_setting("AWS_SES_SECRET") + sender := db.Get_setting("AWS_SES_EMAIL_FROM") - } else { + sess, err := session.NewSession(&aws.Config{ + Region: aws.String("us-east-1"), + Credentials: credentials.NewStaticCredentials(aws_ses_id, aws_ses_secret, ""), + }) - aws_ses_id := db.Get_setting("AWS_SES_ID") - aws_ses_secret := db.Get_setting("AWS_SES_SECRET") - sender := db.Get_setting("AWS_SES_EMAIL_FROM") - region := db.Get_setting("AWS_REGION") + svc := ses.New(sess) - sess, err := session.NewSession(&aws.Config{ - Region: aws.String(region), - Credentials: credentials.NewStaticCredentials(aws_ses_id, aws_ses_secret, ""), - }) + charSet := "UTF-8" - svc := ses.New(sess) - - charSet := "UTF-8" - - input := &ses.SendEmailInput{ - Destination: &ses.Destination{ - CcAddresses: []*string{}, - ToAddresses: []*string{ - aws.String(recipient), - }, + input := &ses.SendEmailInput{ + Destination: &ses.Destination{ + CcAddresses: []*string{}, + ToAddresses: []*string{ + aws.String(recipient), }, - Message: &ses.Message{ - Body: &ses.Body{ - Html: &ses.Content{ - Charset: aws.String(charSet), - Data: aws.String(htmlBody), - }, - Text: &ses.Content{ - Charset: aws.String(charSet), - Data: aws.String(textBody), - }, - }, - Subject: &ses.Content{ + }, + Message: &ses.Message{ + Body: &ses.Body{ + Html: &ses.Content{ Charset: aws.String(charSet), - Data: aws.String(subject), + Data: aws.String(htmlBody), + }, + Text: &ses.Content{ + Charset: aws.String(charSet), + Data: aws.String(textBody), }, }, - Source: aws.String(sender), - //ConfigurationSetName: aws.String(ConfigurationSet), - } - - result, err := svc.SendEmail(input) - - if err != nil { - if aerr, ok := err.(awserr.Error); ok { - switch aerr.Code() { - case ses.ErrCodeMessageRejected: - log.Warn(ses.ErrCodeMessageRejected, aerr.Error()) - case ses.ErrCodeMailFromDomainNotVerifiedException: - log.Warn(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error()) - case ses.ErrCodeConfigurationSetDoesNotExistException: - log.Warn(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error()) - default: - log.Warn(aerr.Error()) - } - } else { - log.Warn(err.Error()) - } - - return - } - - log.WithFields(log.Fields{"result": result}).Info("email sent") + Subject: &ses.Content{ + Charset: aws.String(charSet), + Data: aws.String(subject), + }, + }, + Source: aws.String(sender), + //ConfigurationSetName: aws.String(ConfigurationSet), } + + result, err := svc.SendEmail(input) + + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case ses.ErrCodeMessageRejected: + log.Warn(ses.ErrCodeMessageRejected, aerr.Error()) + case ses.ErrCodeMailFromDomainNotVerifiedException: + log.Warn(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error()) + case ses.ErrCodeConfigurationSetDoesNotExistException: + log.Warn(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error()) + default: + log.Warn(aerr.Error()) + } + } else { + log.Warn(err.Error()) + } + + return + } + + log.WithFields(log.Fields{"result": result}).Info("email sent") } diff --git a/go.mod b/go.mod index 5223a0d..abc8029 100644 --- a/go.mod +++ b/go.mod @@ -97,8 +97,6 @@ require ( github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/rogpeppe/fastuuid v1.2.0 // indirect - github.com/sendgrid/rest v2.6.9+incompatible // indirect - github.com/sendgrid/sendgrid-go v3.12.0+incompatible // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect diff --git a/go.sum b/go.sum index b9afa5f..52e950e 100644 --- a/go.sum +++ b/go.sum @@ -607,10 +607,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0= -github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= -github.com/sendgrid/sendgrid-go v3.12.0+incompatible h1:/N2vx18Fg1KmQOh6zESc5FJB8pYwt5QFBDflYPh1KVg= -github.com/sendgrid/sendgrid-go v3.12.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= diff --git a/internalapi/createboltcard.go b/internalapi/createboltcard.go index a8aa8f3..ec4834a 100644 --- a/internalapi/createboltcard.go +++ b/internalapi/createboltcard.go @@ -1,16 +1,26 @@ package internalapi import ( - "net/http" - "strconv" - "strings" - + "crypto/rand" + "encoding/hex" "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 +func random_hex() string { + b := make([]byte, 16) + _, err := rand.Read(b) + if err != nil { + log.Warn(err.Error()) + return "" + } + + return hex.EncodeToString(b) +} func Createboltcard(w http.ResponseWriter, r *http.Request) { if db.Get_setting("FUNCTION_INTERNAL_API") != "ENABLE" { @@ -101,16 +111,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 deleted file mode 100644 index a7e8cd8..0000000 --- a/internalapi/createboltcardwithpin.go +++ /dev/null @@ -1,159 +0,0 @@ -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" -) - -func random_hex() string { - b := make([]byte, 16) - _, err := rand.Read(b) - if err != nil { - log.Warn(err.Error()) - return "" - } - - return hex.EncodeToString(b) -} - -func Createboltcardwithpin(w http.ResponseWriter, r *http.Request) { - if db.Get_setting("FUNCTION_INTERNAL_API") != "ENABLE" { - msg := "createboltcardwithpin: internal API function is not enabled" - log.Debug(msg) - resp_err.Write_message(w, msg) - return - } - - tx_max_str := r.URL.Query().Get("tx_max") - tx_max, err := strconv.Atoi(tx_max_str) - if err != nil { - msg := "createboltcardwithpin: tx_max is not a valid integer" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - day_max_str := r.URL.Query().Get("day_max") - day_max, err := strconv.Atoi(day_max_str) - if err != nil { - msg := "createboltcardwithpin: day_max is not a valid integer" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - enable_flag_str := r.URL.Query().Get("enable") - enable_flag, err := strconv.ParseBool(enable_flag_str) - if err != nil { - msg := "createboltcardwithpin: enable is not a valid boolean" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - card_name := r.URL.Query().Get("card_name") - if card_name == "" { - msg := "createboltcardwithpin: the card name must be set" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - uid_privacy_flag_str := r.URL.Query().Get("uid_privacy") - uid_privacy_flag, err := strconv.ParseBool(uid_privacy_flag_str) - if err != nil { - msg := "createboltcardwithpin: uid_privacy is not a valid boolean" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - allow_neg_bal_flag_str := r.URL.Query().Get("allow_neg_bal") - allow_neg_bal_flag, err := strconv.ParseBool(allow_neg_bal_flag_str) - if err != nil { - msg := "createboltcardwithpin: allow_neg_bal is not a valid boolean" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - pin_enable_flag_str := r.URL.Query().Get("enable_pin") - pin_enable_flag, err := strconv.ParseBool(pin_enable_flag_str) - if err != nil { - msg := "createboltcardwithpin: enable_pin is not a valid boolean" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - pin_number := r.URL.Query().Get("pin_number") - - pin_limit_sats_str := r.URL.Query().Get("pin_limit_sats") - pin_limit_sats, err := strconv.Atoi(pin_limit_sats_str) - if err != nil { - msg := "createboltcardwithpin: pin_limit_sats is not a valid integer" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - // log the request - - log.WithFields(log.Fields{ - "card_name": card_name, "tx_max": tx_max, "day_max": day_max, - "enable": enable_flag, "uid_privacy": uid_privacy_flag, - "allow_neg_bal": allow_neg_bal_flag, "enable_pin": pin_enable_flag, - "pin_number": pin_number, "pin_limit_sats": pin_limit_sats}).Info("createboltcardwithpin API request") - - // create the keys - - one_time_code := random_hex() - k0_auth_key := random_hex() - k2_cmac_key := random_hex() - k3 := random_hex() - k4 := random_hex() - - // create the new card record - - err = db.Insert_card_with_pin(one_time_code, k0_auth_key, k2_cmac_key, k3, k4, - tx_max, day_max, enable_flag, card_name, - uid_privacy_flag, allow_neg_bal_flag, pin_enable_flag, pin_number, pin_limit_sats) - if err != nil { - log.Warn(err.Error()) - return - } - - // 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 - } else { - url = "https://" + hostdomain + hostdomainsuffix + "/new?a=" + one_time_code - } - - // log the response - - log.WithFields(log.Fields{ - "card_name": card_name, "url": url}).Info("createboltcard API response") - - jsonData := []byte(`{"status":"OK",` + - `"url":"` + url + `"}`) - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write(jsonData) -} diff --git a/internalapi/getboltcard.go b/internalapi/getboltcard.go index 0103752..aef2847 100644 --- a/internalapi/getboltcard.go +++ b/internalapi/getboltcard.go @@ -37,9 +37,7 @@ func Getboltcard(w http.ResponseWriter, r *http.Request) { `"uid": "` + c.Db_uid + `",` + `"lnurlw_enable": "` + c.Lnurlw_enable + `",` + `"tx_limit_sats": "` + strconv.Itoa(c.Tx_limit_sats) + `",` + - `"day_limit_sats": "` + strconv.Itoa(c.Day_limit_sats) + `", ` + - `"pin_enable": "` + c.Pin_enable + `", ` + - `"pin_limit_sats": "` + strconv.Itoa(c.Pin_limit_sats) + `"}`) + `"day_limit_sats": "` + strconv.Itoa(c.Day_limit_sats) + `"}`) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) diff --git a/internalapi/updateboltcardwithpin.go b/internalapi/updateboltcardwithpin.go deleted file mode 100644 index 26ab45a..0000000 --- a/internalapi/updateboltcardwithpin.go +++ /dev/null @@ -1,116 +0,0 @@ -package internalapi - -import ( - "github.com/boltcard/boltcard/db" - "github.com/boltcard/boltcard/resp_err" - log "github.com/sirupsen/logrus" - "net/http" - "strconv" -) - -func Updateboltcardwithpin(w http.ResponseWriter, r *http.Request) { - if db.Get_setting("FUNCTION_INTERNAL_API") != "ENABLE" { - msg := "updateboltcardwithpin: internal API function is not enabled" - log.Debug(msg) - resp_err.Write_message(w, msg) - return - } - - enable_flag_str := r.URL.Query().Get("enable") - enable_flag, err := strconv.ParseBool(enable_flag_str) - if err != nil { - msg := "updateboltcardwithpin: enable is not a valid boolean" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - tx_max_str := r.URL.Query().Get("tx_max") - tx_max, err := strconv.Atoi(tx_max_str) - if err != nil { - msg := "updateboltcardwithpin: tx_max is not a valid integer" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - day_max_str := r.URL.Query().Get("day_max") - day_max, err := strconv.Atoi(day_max_str) - if err != nil { - msg := "updateboltcardwithpin: day_max is not a valid integer" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - pin_enable_flag_str := r.URL.Query().Get("enable_pin") - pin_enable_flag, err := strconv.ParseBool(pin_enable_flag_str) - if err != nil { - msg := "updateboltcardwithpin: enable_pin is not a valid boolean" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - pin_number := r.URL.Query().Get("pin_number") - - pin_limit_sats_str := r.URL.Query().Get("pin_limit_sats") - pin_limit_sats, err := strconv.Atoi(pin_limit_sats_str) - if err != nil { - msg := "updateboltcardwithpin: pin_limit_sats is not a valid integer" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - card_name := r.URL.Query().Get("card_name") - - // check if card_name exists - - card_count, err := db.Get_card_name_count(card_name) - if err != nil { - log.Warn(err.Error()) - return - } - - if card_count == 0 { - msg := "updateboltcardwithpin: the card name does not exist in the database" - log.Warn(msg) - resp_err.Write_message(w, msg) - return - } - - // log the request - - log.WithFields(log.Fields{ - "card_name": card_name, "tx_max": tx_max, "day_max": day_max, - "enable": enable_flag, "enable_pin": pin_enable_flag, - "pin_number": pin_number, "pin_limit_sats": pin_limit_sats}).Info("updateboltcardwithpin API request") - - // update the card record - - if pin_number == "" { - err = db.Update_card_with_part_pin(card_name, enable_flag, tx_max, day_max, - pin_enable_flag, pin_limit_sats) - if err != nil { - log.Warn(err.Error()) - return - } - } - - if pin_number != "" { - err = db.Update_card_with_pin(card_name, enable_flag, tx_max, day_max, - pin_enable_flag, pin_number, pin_limit_sats) - if err != nil { - log.Warn(err.Error()) - return - } - } - - // send a response - - jsonData := []byte(`{"status":"OK"}`) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write(jsonData) -} 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_callback.go b/lnurlw/lnurlw_callback.go index de8c74f..b5d5a61 100644 --- a/lnurlw/lnurlw_callback.go +++ b/lnurlw/lnurlw_callback.go @@ -56,13 +56,13 @@ func lndhub_payment(w http.ResponseWriter, p *db.Payment, bolt11 decodepay.Bolt1 card_name_parts := strings.Split(c.Card_name, ":") if len(card_name_parts) != 2 { - log.WithFields(log.Fields{"card_payment_id": p.Card_payment_id}).Warn("login:password not found") + log.WithFields(log.Fields{"card_payment_id": p.Card_payment_id}).Warn(err) resp_err.Write(w) return } if len(card_name_parts[0]) != 20 || len(card_name_parts[1]) != 20 { - log.WithFields(log.Fields{"card_payment_id": p.Card_payment_id}).Warn("login:password badly formed") + log.WithFields(log.Fields{"card_payment_id": p.Card_payment_id}).Warn(err) resp_err.Write(w) return } @@ -72,6 +72,7 @@ func lndhub_payment(w http.ResponseWriter, p *db.Payment, bolt11 decodepay.Bolt1 lhAuthRequest.Password = card_name_parts[1] authReq, err := json.Marshal(lhAuthRequest) + log.Info(string(authReq)) req_auth, err := http.NewRequest("POST", lndhub_url+"/auth", bytes.NewBuffer(authReq)) if err != nil { @@ -100,9 +101,6 @@ func lndhub_payment(w http.ResponseWriter, p *db.Payment, bolt11 decodepay.Bolt1 return } - log.WithFields(log.Fields{"card_payment_id": p.Card_payment_id, - "resp_auth_bytes": string(resp_auth_bytes)}).Info("issue 62") - var auth_keys LndhubAuthResponse err = json.Unmarshal([]byte(resp_auth_bytes), &auth_keys) @@ -227,15 +225,17 @@ func Callback(w http.ResponseWriter, req *http.Request) { url := req.URL.RequestURI() log.WithFields(log.Fields{"url": url}).Debug("cb request") - // get k1 value - param_k1 := req.URL.Query().Get("k1") + // check k1 value + params_k1, ok := req.URL.Query()["k1"] - if param_k1 == "" { + if !ok || len(params_k1[0]) < 1 { log.WithFields(log.Fields{"url": url}).Debug("k1 not found") resp_err.Write(w) return } + param_k1 := params_k1[0] + p, err := db.Get_payment_k1(param_k1) if err != nil { log.WithFields(log.Fields{"url": url, "k1": param_k1}).Warn(err) @@ -263,14 +263,14 @@ func Callback(w http.ResponseWriter, req *http.Request) { return } - // get the payment request - param_pr := req.URL.Query().Get("pr") - if param_pr == "" { + params_pr, ok := req.URL.Query()["pr"] + if !ok || len(params_pr[0]) < 1 { log.WithFields(log.Fields{"card_payment_id": p.Card_payment_id}).Warn("pr field not found") resp_err.Write(w) return } + param_pr := params_pr[0] bolt11, _ := decodepay.Decodepay(param_pr) // record the lightning invoice @@ -283,23 +283,6 @@ func Callback(w http.ResponseWriter, req *http.Request) { log.WithFields(log.Fields{"card_payment_id": p.Card_payment_id}).Debug("checking payment rules") - // get the pin if it has been passed in - param_pin := req.URL.Query().Get("pin") - - c, err := db.Get_card_from_card_id(p.Card_id) - if err != nil { - log.WithFields(log.Fields{"card_payment_id": p.Card_payment_id}).Warn(err) - resp_err.Write(w) - return - } - - // check the pin if needed - if c.Pin_enable == "Y" && int(bolt11.MSatoshi/1000) >= c.Pin_limit_sats && c.Pin_number != param_pin { - log.WithFields(log.Fields{"card_payment_id": p.Card_payment_id}).Warn("incorrect pin provided") - resp_err.Write(w) - return - } - // check if we are only sending funds to a defined test node testnode := db.Get_setting("LN_TESTNODE") if testnode != "" && bolt11.Payee != testnode { diff --git a/lnurlw/lnurlw_request.go b/lnurlw/lnurlw_request.go index ad392a9..fe1a93d 100644 --- a/lnurlw/lnurlw_request.go +++ b/lnurlw/lnurlw_request.go @@ -4,17 +4,25 @@ 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" ) +type ResponseData struct { + Tag string `json:"tag"` + Callback string `json:"callback"` + LnurlwK1 string `json:"k1"` + DefaultDescription string `json:"defaultDescription"` + MinWithdrawable int `json:"minWithdrawable"` + MaxWithdrawable int `json:"maxWithdrawable"` +} + func get_p_c(req *http.Request, p_name string, c_name string) (p string, c string) { params_p, ok := req.URL.Query()[p_name] @@ -247,12 +255,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") resp_err.Write(w) @@ -286,10 +288,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") @@ -310,29 +312,13 @@ func Response(w http.ResponseWriter, req *http.Request) { return } - // get pin_enable & pin_limit_sats - - c, err := db.Get_card_from_card_id(card_id) - if err != nil { - log.WithFields(log.Fields{"card_id": card_id}).Warn(err) - resp_err.Write(w) - return - } - - default_description := db.Get_setting("DEFAULT_DESCRIPTION") - - response := make(map[string]interface{}) - - response["tag"] = "withdrawRequest" - response["callback"] = lnurlw_cb_url - response["k1"] = lnurlw_k1 - response["defaultDescription"] = default_description - response["minWithdrawable"] = min_withdraw_sats * 1000 // milliSats - response["maxWithdrawable"] = max_withdraw_sats * 1000 // milliSats - - if c.Pin_enable == "Y" { - response["pinLimit"] = c.Pin_limit_sats * 1000 // milliSats - } + response := ResponseData{} + response.Tag = "withdrawRequest" + response.Callback = lnurlw_cb_url + response.LnurlwK1 = lnurlw_k1 + response.DefaultDescription = "WWT withdrawal" + response.MinWithdrawable = min_withdraw_sats * 1000 // milliSats + response.MaxWithdrawable = max_withdraw_sats * 1000 // milliSats jsonData, err := json.Marshal(response) diff --git a/main.go b/main.go index d4e7148..4390985 100644 --- a/main.go +++ b/main.go @@ -53,9 +53,7 @@ func main() { internal_router.Path("/ping").Methods("GET").HandlerFunc(internalapi.Internal_ping) internal_router.Path("/createboltcard").Methods("GET").HandlerFunc(internalapi.Createboltcard) - internal_router.Path("/createboltcardwithpin").Methods("GET").HandlerFunc(internalapi.Createboltcardwithpin) internal_router.Path("/updateboltcard").Methods("GET").HandlerFunc(internalapi.Updateboltcard) - internal_router.Path("/updateboltcardwithpin").Methods("GET").HandlerFunc(internalapi.Updateboltcardwithpin) internal_router.Path("/wipeboltcard").Methods("GET").HandlerFunc(internalapi.Wipeboltcard) internal_router.Path("/getboltcard").Methods("GET").HandlerFunc(internalapi.Getboltcard) 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/script/s_create_db b/script/s_create_db index aecb7b6..1e0b7d8 100755 --- a/script/s_create_db +++ b/script/s_create_db @@ -15,7 +15,7 @@ if [ "$x" = "y" ]; then psql postgres -f sql/create_db_init.sql psql postgres -f sql/create_db.sql psql postgres -f sql/create_db_user.sql - psql postgres -f sql/settings.sql.secret + psql postgres -f sql/settings.sql echo Database created else echo No action diff --git a/script/s_setup_test_data b/script/s_setup_test_data deleted file mode 100755 index 7426df8..0000000 --- a/script/s_setup_test_data +++ /dev/null @@ -1,6 +0,0 @@ -# to close any database connections -sudo systemctl stop postgresql -sudo systemctl start postgresql - -psql postgres -f sql/data.test.sql -echo Test data added diff --git a/sql/create_db.sql b/sql/create_db.sql index 9cd6135..03c80f7 100644 --- a/sql/create_db.sql +++ b/sql/create_db.sql @@ -28,9 +28,6 @@ CREATE TABLE cards ( one_time_code_expiry TIMESTAMPTZ DEFAULT NOW() + INTERVAL '1 DAY', one_time_code_used CHAR(1) NOT NULL DEFAULT 'Y', allow_negative_balance CHAR(1) NOT NULL DEFAULT 'N', - pin_enable CHAR(1) NOT NULL DEFAULT 'N', - pin_number CHAR(4) NOT NULL DEFAULT '0000', - pin_limit_sats INT NOT NULL DEFAULT 0, wiped CHAR(1) NOT NULL DEFAULT 'N', PRIMARY KEY(card_id) ); diff --git a/sql/data.test.sql b/sql/data.test.sql deleted file mode 100644 index cae568a..0000000 --- a/sql/data.test.sql +++ /dev/null @@ -1,24 +0,0 @@ --- connect to card_db -\c card_db; - --- clear out table data -DELETE FROM settings; -DELETE FROM card_payments; -DELETE FROM card_receipts; -DELETE FROM cards; - --- set up test data -INSERT INTO settings (name, value) VALUES ('LOG_LEVEL', 'DEBUG'); -INSERT INTO settings (name, value) VALUES ('AES_DECRYPT_KEY', '994de7f8156609a0effafbdb049337b1'); -INSERT INTO settings (name, value) VALUES ('HOST_DOMAIN', 'localhost:9000'); -INSERT INTO settings (name, value) VALUES ('FUNCTION_INTERNAL_API', 'ENABLE'); -INSERT INTO settings (name, value) VALUES ('MIN_WITHDRAW_SATS', '1'); -INSERT INTO settings (name, value) VALUES ('MAX_WITHDRAW_SATS', '1000'); - - -INSERT INTO cards - (k0_auth_key, k2_cmac_key, k3, k4, lnurlw_enable, last_counter_value, lnurlw_request_timeout_sec, - tx_limit_sats, day_limit_sats, card_name, pin_enable, pin_number, pin_limit_sats) - VALUES - ('', 'd3dffa1e12d2477e443a6ee9fcfeab18', '', '', 'Y', 0, 10, - 0, 0, 'test_card', 'Y', '1234', 1000); diff --git a/sql/settings.sql b/sql/settings.sql index 810a2bb..75b7dd1 100644 --- a/sql/settings.sql +++ b/sql/settings.sql @@ -20,15 +20,10 @@ INSERT INTO settings (name, value) VALUES ('LN_TESTNODE', ''); INSERT INTO settings (name, value) VALUES ('FUNCTION_LNURLW', ''); INSERT INTO settings (name, value) VALUES ('FUNCTION_LNURLP', ''); INSERT INTO settings (name, value) VALUES ('FUNCTION_EMAIL', ''); -INSERT INTO settings (name, value) VALUES ('DEFAULT_DESCRIPTION', 'bolt card service'); INSERT INTO settings (name, value) VALUES ('AWS_SES_ID', ''); INSERT INTO settings (name, value) VALUES ('AWS_SES_SECRET', ''); INSERT INTO settings (name, value) VALUES ('AWS_SES_EMAIL_FROM', ''); -INSERT INTO settings (name, value) VALUES ('AWS_REGION', 'us-east-1'); INSERT INTO settings (name, value) VALUES ('EMAIL_MAX_TXS', ''); INSERT INTO settings (name, value) VALUES ('FUNCTION_LNDHUB', ''); 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');