A next generation NKN wallet with built-in age encryption and NKN OpenAPI support.
nkn-wallet is a library that implements nkn-sdk-go with a new wallet functionality utilizing age encryption and using the NKN OpenAPI for querying the NKN blockchain.
Upcoming releases will include the possibility for encryption by SSH public key ("ssh-ed25519 AAAA...", "ssh-rsa AAAA...") stored on disk or by fetching the keys from a Github user profile (github.com/[user].keys).
While nkn-wallet has its own wallet implementation the underlying SDK functionality remains the same and is compatible with nkn-sdk-go. The move to age as an encryption toolkit results in overall less loc since there is no need to implement scrypt functionality manually. This wallet implementation uses age's internal scrypt feature which is identical to the original NKN wallet implementation performance-wise and slightly enhanced security-wise through the use of an additional layer, that is, the derived scrypt key being encrypted again with a ChaCha20-Poly1305 AEAD.
Features:
- Manage all your different accounts from a central place
- State-of-the-art encryption with Age
- Manage multiple accounts in a single wallet file (multiwallet capability)
- List all your accounts
- Set an alias for your account
- Show the balance of your account
- Show transactions of your account
- Move funds between accounts in the wallet
- Transfer funds to another NKN address
- Restore your accounts
- Change password of your account
- Change or set an alias for your account
- NKN OpenAPI support
import nknwallet "github.com/omani/nkn-wallet"
package main
import (
"fmt"
"log"
"github.com/nknorg/nkn-sdk-go"
nknwallet "github.com/omani/nkn-wallet"
)
func main() {
passwd := []byte("secretpassword")
store := nknwallet.NewStore("mywallet.json")
walletcfg := &nkn.WalletConfig{}
wallet, err := store.NewWallet(passwd, "my-new-shiny-account", walletcfg)
if err != nil {
log.Fatal(err)
}
// save new wallet to store (otherwise it is in-memory only)
store.SaveWallet(wallet)
// list wallets in store
store.ListWallets()
// get balance of newly created account
balance, err := wallet.OpenAPI().GetBalance()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Address: %s - Balance: %s NKN\n", wallet.Address(), balance)
// fetch a wallet from store
wallet, err = store.GetWalletByAlias("my-new-shiny-account", passwd)
if err != nil {
log.Fatal(err)
}
// print account address
fmt.Printf("Address of account: %s\n", wallet.Address())
// get transactions associated with account
txn, err := wallet.OpenAPI().GetTransactions()
if err != nil {
log.Fatal(err)
}
if len(txn.Data) == 0 {
fmt.Println("Account has no transactions.")
return
}
for _, tx := range txn.Data {
fmt.Printf("Sent %s NKN to address %s", tx.Payload.Amount, tx.Payload.RecipientWallet)
}
}
package main
import (
"crypto/rand"
"encoding/hex"
"fmt"
"log"
"time"
"github.com/nknorg/nkn-sdk-go"
nknwallet "github.com/omani/nkn-wallet"
)
func main() {
store := nknwallet.NewStore("mywallet.json")
walletcfg := &nkn.WalletConfig{}
wallet, err := store.NewWallet([]byte("secretpassword"), "my-new-shiny-account", walletcfg)
if err != nil {
log.Fatal(err)
}
// continue to use nkn-sdk-go as usual
err = func() error {
// get the underlying nkn.Account
account := wallet.Account()
fromIdentifier := make([]byte, 8)
_, err = rand.Read(fromIdentifier)
if err != nil {
return err
}
toIdentifier := make([]byte, 8)
_, err = rand.Read(toIdentifier)
if err != nil {
return err
}
fromClient, err := nkn.NewMultiClient(account, hex.EncodeToString(fromIdentifier), 4, false, nil)
if err != nil {
return err
}
defer fromClient.Close()
<-fromClient.OnConnect.C
toClient, err := nkn.NewMultiClient(account, hex.EncodeToString(toIdentifier), 4, false, nil)
if err != nil {
return err
}
defer toClient.Close()
<-toClient.OnConnect.C
time.Sleep(time.Second)
timeSent := time.Now().UnixNano() / int64(time.Millisecond)
var timeReceived int64
go func() {
msg := <-toClient.OnMessage.C
timeReceived = time.Now().UnixNano() / int64(time.Millisecond)
isEncryptedStr := "unencrypted"
if msg.Encrypted {
isEncryptedStr = "encrypted"
}
log.Println("Receive", isEncryptedStr, "message", "\""+string(msg.Data)+"\"", "from", msg.Src, "after", timeReceived-timeSent, "ms")
// []byte("World") can be replaced with "World" for text payload type
msg.Reply([]byte("World"))
}()
log.Println("Send message from", fromClient.Address(), "to", toClient.Address())
// []byte("Hello") can be replaced with "Hello" for text payload type
onReply, err := fromClient.Send(nkn.NewStringArray(toClient.Address()), []byte("Hello"), nil)
if err != nil {
return err
}
reply := <-onReply.C
isEncryptedStr := "unencrypted"
if reply.Encrypted {
isEncryptedStr = "encrypted"
}
timeResponse := time.Now().UnixNano() / int64(time.Millisecond)
log.Println("Got", isEncryptedStr, "reply", "\""+string(reply.Data)+"\"", "from", reply.Src, "after", timeResponse-timeReceived, "ms")
// wait to send receipt
time.Sleep(time.Second)
return nil
}()
if err != nil {
fmt.Println(err)
}
}
This package comes with a CLI app in cmd/
go install github.com/omani/nkn-wallet/cmd/nkn-wallet@latest
$ nkn-wallet
nkn-wallet v1.0
---------------------------------------------------------------------------
nkn-wallet is a library that implements nkn-sdk-go with a new wallet
functionality utilizing age encryption and using the NKN OpenAPI for
querying the NKN blockchain.
Upcoming releases will include the possibility for encryption by
SSH public key ("ssh-ed25519 AAAA...", "ssh-rsa AAAA...") stored on disk
or by fetching the keys from a Github user profile (github.com/[user].keys).
URL: https://github.com/omani/nkn-wallet
MIT license. Copyright (c) 2023 HAH! Sun
[This message will be removed with the next version]
---------------------------------------------------------------------------
Usage:
nkn-wallet [command]
Available Commands:
change Change various information of an account in the wallet
create Create an account in the wallet
delete Delete an account from the wallet
help Help about any command
list List all accounts of the wallet
move Move funds between accounts in the wallet
restore Restore an account from a seed
show Show various information to an account in the wallet
transfer Transfer funds to another NKN address
Flags:
-h, --help help for nkn-wallet
--ip string DNS/IP of NKN remote node (default "mainnet-seed-0001.org")
-p, --path string path to wallet file (default "./nkn-wallet.json")
-v, --version version for nkn-wallet
Use "nkn-wallet [command] --help" for more information about a command.
$ nkn-wallet --path usethiswallet.json create --alias mining-wallet-srv01 -s
Password:
Re-enter Password:
Account information:
Seed: c2017a52472f4e616b1ee028297c8609d09fdec90db8522b978df85df9a14d10
Address: NKNT43Q5z863qL2wdheRqEmSgPtNDnbiLTw3
Alias: mining-wallet-srv01
Account saved successfully.
Note: Use the -s
flag to save the newly created account to the wallet!
$ nkn-wallet --path usethiswallet.json create --alias github-donations -s
Password:
Re-enter Password:
Account information:
Seed: de392b438d5020b46123f461d0c1db50c2eef244fb6e213c20bfaa3a4b2ae237
Address: NKND1ejSRWBExoq8t4DWpPHaK5BMBe4yifTN
Alias: github-donations
Account saved successfully.
Note: Use the -s
flag to save the newly created account to the wallet!
$ nkn-wallet --path usethiswallet.json list
βββββΌββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ
ID β ALIAS β ADDRESS
βββββΌββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ
1 β mining-wallet-srv01 β NKNT43Q5z863qL2wdheRqEmSgPtNDnbiLTw3
2 β github-donations β NKND1ejSRWBExoq8t4DWpPHaK5BMBe4yifTN
$ nkn-wallet --path usethiswallet.json show info -i 1
Password:
ID β ALIAS β ADDRESS β PUBKEY β SEED
βββββΌββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1 β mining-wallet-srv01 β NKNT43Q5z863qL2wdheRqEmSgPtNDnbiLTw3 β d9390cfde27fb118c18e70367afb2fc4ffb17d2386c43f5b157b13c34a5b3120 β c2017a52472f4e616b1ee028297c8609d09fdec90db8522b978df85df9a14d10
$ nkn-wallet --path usethiswallet.json show balance -i 1
ID β ALIAS β ADDRESS β BALANCE
βββββΌββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββΌβββββββββ
1 β mining-wallet-srv01 β NKNT43Q5z863qL2wdheRqEmSgPtNDnbiLTw3 β 0
For example purposes the wallet shown in this example is a wallet with already several transactions, not the newly created one in the examples above.
$ nkn-wallet --path usethiswallet.json show transactions -i 1
Password:
CREATED AT β BLOCK HEIGHT β TXN HASH β SENDER β RECIPIENT β AMOUNT
ββββββββββββββββββββββΌβββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββΌββββββββββββββ
2023-08-05 01:58:55 β 5666741 β 26d7c4e968a4b40a2773e74c9429b73243210981102471cae7973b3c81558510 β NKNKQ34u5DeSxfi5VN1HDtG9iuQdhmsxAx7m β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β 0
2023-08-05 01:49:44 β 5666719 β b770f948117078ea9e6f07f876e8c359732a8e8fa0fa5e2619b6d5604057f8dc β NKNKQ34u5DeSxfi5VN1HDtG9iuQdhmsxAx7m β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β 1
2023-08-05 01:34:33 β 5666683 β 2854bf89c665caaf393a2fa70e01f3abd861a13f16fc4ff828a14a71524667dd β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β NKNKQ34u5DeSxfi5VN1HDtG9iuQdhmsxAx7m β 1
2023-08-02 21:32:56 β 5658789 β d6005aeb904038b45041ce4680ca11229178053c02c7a585b998b54f6a9b1d59 β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β 1
2023-07-30 19:19:03 β 5647512 β 43a465187b71e80864d72c4c40140f9e22b587628915be384d36989839d05f04 β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β 1
2023-03-31 14:48:06 β 5205199 β a095963dc703e3a67829b00c8a3c258b937d826ff653d2469f0cd49c88534847 β NKNJ8PDyiaJBbJJagiWEZLfpDyeBcxvgCgTu β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β 35.87375384
2023-03-24 15:12:15 β 5179448 β d54bc49c200602a5c5b1a7dc58fc7e1bdf5391ebfbf0ef0ad4aada31a6cf2a18 β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β NKNJebQsifkREcuvGVteiZwcTL8WTxEXdLtb β 300
2023-03-24 15:07:01 β 5179436 β c4123b1a842563520e826f6c594cd5b7dbd9b237c043a18cb91b01d73bde29dd β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β NKNSns2iiMibVUfwmXKC2oLFfeWZoTENiMvk β 300
2021-12-24 01:41:40 β 3469174 β 497862b0b28dac959fabc760c3c81ef9077ce0a46b266bf598feb35edad1253c β NKNRBnhQq4dG68C3XVmEsos2QPaHtN4JiC6V β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β 281
2021-12-21 07:52:54 β 3458748 β 8e703cdd54696df94cc11c91163ebcb3c203e85bad8708764ae05132eb0da808 β NKNToMMXL3W4EQxAZ7ooqgkyzPuHDb52fQJy β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β 100
2021-12-05 03:33:45 β 3397448 β 5ee5a1c763d7a1bdb00486fcf176363ac6c1b111b98fdea6277278f113df9a9b β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β NKNPDgusz7HSnXGcvCqPQsLiqNaGspQoCzbz β 10
2021-12-05 03:33:00 β 3397446 β 3719cfee8b7c4c0c34df799120f717b65cff035843e221428ec3021481def1b3 β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β NKNGuNrjgcLTsRvGpYKfE68Fht4Anbh52aR4 β 10
2021-12-05 02:22:24 β 3397261 β e64cf77e46833a1d9e23ca0988b9624b4d7b29f0660fde85277b51937149f955 β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β NKNJ8PDyiaJBbJJagiWEZLfpDyeBcxvgCgTu β 10
2021-12-05 02:05:20 β 3397216 β b0c260b27903df84d7719f1ef602f02be5a457dfc53c2d2d02bf24ee7a60928c β NKNHb4UMs8whWosffseg7aPpztVNBt5NDrCR β NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm β 284.96270000
Total balance of account: 71.83645384 NKN
For example purposes the wallet shown in this example is a wallet with a positive balance, not the newly created one in the examples above.
$ nkn-wallet transfer -i 10 --amount 1 --to NKNCSjyCsuDbJcSrRW4kG2atu4SW6sTJkx4o
Password:
Successfully sent 1 KNN from NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm to NKNCSjyCsuDbJcSrRW4kG2atu4SW6sTJkx4o. txHash: ba2313cdffa1060a4f28474a01ef19dc09ea5e042398eb56593870542e664cbb
For example purposes the wallet shown in this example is a wallet with a positive balance, not the newly created one in the examples above.
Use the special keyword "all" to move all funds.
$ nkn-wallet move --from-id 11 --to-id 10 --amount all
Password:
Successfully sent 1 NKN from NKNCSjyCsuDbJcSrRW4kG2atu4SW6sTJkx4o to NKNVmZQZcDrgdMJKdgRfz2gn5ZdTAyro5uHm. txHash: 61c8035b27b8232faad2adf641922dea09d4e925e4e5230d73c9a55ae8917549
Refer to https://github.com/FiloSottile/age to see how to install age.
~ $ cd /tmp/
/tmp $ age-keygen > ident1
age-keygen: warning: writing secret key to a world-readable file
Public key: age1f2fcrl2l5swmd4nquukwhh7kd8qz579usp74ee4t7gm068wqpgjqwnuldf
/tmp $ age-keygen > ident2
age-keygen: warning: writing secret key to a world-readable file
Public key: age1pklphvwtf6sfedu4seth44vepy597tesc4e82zsh536lj58ggp4szg25p3
/tmp $
$ go run main.go create -r age1f2fcrl2l5swmd4nquukwhh7kd8qz579usp74ee4t7gm068wqpgjqwnuldf -s
Account information:
ID: 1
Address: NKNEtSx32P6AnhSjdaWujE98sKZHFm2iHPSv
Seed: 947c34c358c9157aa01e9f46d1af613ac9aed0912644fe55e9391184802d9063
Account saved successfully.
$ go run main.go create -r age1pklphvwtf6sfedu4seth44vepy597tesc4e82zsh536lj58ggp4szg25p3 -s
Account information:
ID: 2
Address: NKNYzaG8M6VcBcbSyUPgak46TQRCav8LzGfQ
Seed: 405ef86b621ebb7a3bab1de127a203778366b5abee445aaf5d40cbc7322b1c05
Account saved successfully.
Now we have to accounts in the wallet. Each encrypted to the recipients above
$ go run main.go list
ID β ALIAS β ADDRESS
βββββΌββββββββΌββββββββββββββββββββββββββββββββββββββ
1 β β NKNEtSx32P6AnhSjdaWujE98sKZHFm2iHPSv
2 β β NKNYzaG8M6VcBcbSyUPgak46TQRCav8LzGfQ
$ go run main.go show info --index 1
Error: Wallet is not an scrypt type. Use an identity file to decrypt it.
exit status 1
$ go run main.go show info --index 1 -i /tmp/ident2
Error: no identity matched any of the recipients
exit status 1
$ go run main.go show info --index 1 -i /tmp/ident1
ID β ALIAS β ADDRESS β PUBKEY β SEED
βββββΌββββββββΌβββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1 β β NKNEtSx32P6AnhSjdaWujE98sKZHFm2iHPSv β 8a16c717629980ed7fcf93f4430300b65f1645a83020d97d9615539b5a08a499 β 947c34c358c9157aa01e9f46d1af613ac9aed0912644fe55e9391184802d9063
go run main.go create -R ~/.ssh/id_ed25519.pub -s
Account information:
ID: 1
Address: NKNTNxHR3NRFHT3RGbjjwkKQzWVYF3cfraSR
Seed: bb54ec61a42a9b1dd213a0a2e613576c17590c429c79621c6bccfc0e97d93092
Account saved successfully.
go run main.go show info --index 1
Error: Wallet is not an scrypt type. Use an identity file to decrypt it.
exit status 1
go run main.go show info --index 1 -i ~/.ssh/id_ed25519
1
ID β ALIAS β ADDRESS β PUBKEY β SEED
βββββΌββββββββΌβββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1 β β NKNTNxHR3NRFHT3RGbjjwkKQzWVYF3cfraSR β 101905457c9e798d5e04fb350779d79999c4f3a9297dcdcf803d182d11ddaf17 β bb54ec61a42a9b1dd213a0a2e613576c17590c429c79621c6bccfc0e97d93092
For information refer to the help menu with nkn-wallet help
or nkn-wallet [cmd] help
.
The file format of the wallet looks like this:
...
{
"id": 5,
"address": "NKNRdA5G8U5kSQ68Q5beeAuzGfvp2vEiRzEr",
"armor": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCBqWjFpT01iV2g0VzZ6aW9L\nM21tajZnIDE4Cm1BZFNnNjhzdWR4NGQzUUR6TzBKV2xSOVJFZXl4ZW1oVDZJZWdh\nSzhtRWMKLS0tIHRBSlp0ZkdKaWNHamd4b1Vab250WFF3TTFxVThXUFFyUVQxRDRy\nWGNqQTgK7M8ZKYVVvQGP0LEKwiEO9lN0mTK3MUjnO4X98nEerJOh+kJZnziJPkxL\nxrKvpsGOCurMSkuTLbU10NlQfuc6Bv1oHSYP1+GYcmwrvJgI/6B00atlHGY/jmeb\nMRo5D1mJBxhhgdWe3udvjMB264k46R/oIGKkxeCC5nA/eLToqvplb0qDv6FMapDe\nScrKSg50eNuaoe3yWxZlP8bS1RsPDJtIgJ+cTE0B66xt6G8rLQX80B4xp72AzHnh\nXn182U0+2i5PjYIpFZsoKdIKTD4eXXDF0aH8BfpYm0bHHM32vRS2rW3pCYz0mF35\nBzBHCel5A3kXhksSCgze7w==\n-----END AGE ENCRYPTED FILE-----\n",
"alias": "personal"
},
{
"id": 7,
"address": "NKNHyoTeS3XEWFfEMuBXGKi16RP6rSqjr3bU",
"armor": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCA2YkZDbmlrN3J6QkpIK0xE\naXNEcDlnIDE4Cnp0KzFOeWNQRmM5MjYzajI2L3NzK3RaeFY2WWZNd2YyTzNLRWJH\na0o1dTgKLS0tIHFNdTFTc0pXUVMrdVVLSjlsREU1RkdJamMrWit2cXVoZjVvdzNI\naDZQVlUKVzxvmCI4A9+tVmEzKnTADJ1T2yAVoWQ8pUJ7yuPXOR3Kf1VPc+iwGMsF\n5Tdoom46Ii/VtBG8DPpFVNflehqdS5zAqjvB3UQO9qtUR1ZRL2goG20UtVbrS0Np\nWHEgi/gr+QZho6rYd9wr1M691rbDrOSgzQUNEdALczrwe/SBFDXGMPGRlGP5SAyP\nX/ZhQaYO6QTwdjnpbEzYsZ+OoijyAT9052RDhQwtCYKuZ4p4NxITmFM6TAdgfo3P\nR5XHKHVzGY/JLsqFQcp1nMGPy5XNghnDvxk+v/TqA6BEe4qn4YoloZ5cFjvmf4Xp\n1Mem0tXgEHxIt8rO4enQNg==\n-----END AGE ENCRYPTED FILE-----\n",
"alias": "github-donations"
},
{
"id": 8,
"address": "NKNKQ34u5DeSxfi5VN1HDtG9iuQdhmsxAx7m",
"armor": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCBjbTRUK3Z2N2xLWCtnQk1W\nVjlaVEdRIDE4CmNkYm9DVThDRFNVdE53elhSK0l3b2F0SFpqdUxNbVY0OEFDdmd4\nZ0FlSHMKLS0tIHFIaVRmZVBCUDhOWUJVZjlJUE9KU2F5clpvejMrM2tuakVJUTJQ\nRENjU0kK1ov/aEbZzd+WUV+i47MpGR0LdhNWBXewUjwSrqIPH8xGsm8hwmvs/j5v\nGTSO9ndzzFdizU93zhvsXoQIZmzkm5OvTRXCj6IUj/B0UlCtcUHkcvUFK9KJQ7Uc\nuUVpxazelIDCg5Oi3+tGrT45KqKYiwnYwPKbk5zj7nVNvOVEST15m2MjPM4cWq9W\ny17jdKrHekeIOvXGiLau3ZGEUh7Z9XtJh9BAI20lN9upMrpQYZzn9vuKQZByznI5\n5ge3CbAs9F8ldD4TocSbg8vabQ3iQwPanxZQZiXrn6u4+fQJnXaw/LXUpJITg0Az\n7gX1fz/Ea6FVyk+ZggB3Ig==\n-----END AGE ENCRYPTED FILE-----\n",
"alias": "office"
},
...
The age encryption is stored as an armor in the armor
field. the associated NKN address, id and alias are cleartext.
The base64 decoded armor looks like this:
$ echo "YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCBqWjFpT01iV2g0VzZ6aW9L
M21tajZnIDE4Cm1BZFNnNjhzdWR4NGQzUUR6TzBKV2xSOVJFZXl4ZW1oVDZJZWdh
SzhtRWMKLS0tIHRBSlp0ZkdKaWNHamd4b1Vab250WFF3TTFxVThXUFFyUVQxRDRy
WGNqQTgK7M8ZKYVVvQGP0LEKwiEO9lN0mTK3MUjnO4X98nEerJOh+kJZnziJPkxL
xrKvpsGOCurMSkuTLbU10NlQfuc6Bv1oHSYP1+GYcmwrvJgI/6B00atlHGY/jmeb
MRo5D1mJBxhhgdWe3udvjMB264k46R/oIGKkxeCC5nA/eLToqvplb0qDv6FMapDe
ScrKSg50eNuaoe3yWxZlP8bS1RsPDJtIgJ+cTE0B66xt6G8rLQX80B4xp72AzHnh
Xn182U0+2i5PjYIpFZsoKdIKTD4eXXDF0aH8BfpYm0bHHM32vRS2rW3pCYz0mF35
BzBHCel5A3kXhksSCgze7w==" | base64 -d
age-encryption.org/v1
-> scrypt jZ1iOMbWh4W6zioK3mmj6g 18
mAdSg68sudx4d3QDzO0JWlR9REeyxemhT6IegaK8mEc
--- tAJZtfGJicGjgxoUZontXQwM1qU8WPQrQT1D4rXcjA8
)UΠ±
!St21H;qBY8>LKΖ²
JK-5P~:h&rltΡ«ef?g19YaΥov8 bp?xeoJLjIJtxΫ[e?
LMmo+-1y^}|M>.O)()
L>]pXFm ]0G yyK
- You can fork this, extend it and contribute back.
- You can contribute with pull requests.
I accept payments and donations in BTC:
bc1qgezvfp4s0xme8pdv6aaqu9ayfgnv4mejdlv3tx
MIT License