]> git.codecow.com Git - libnemo.git/commitdiff
Update description and README.
authorChris Duncan <chris@zoso.dev>
Tue, 9 Sep 2025 04:34:45 +0000 (21:34 -0700)
committerChris Duncan <chris@zoso.dev>
Tue, 9 Sep 2025 04:34:45 +0000 (21:34 -0700)
README.md
package.json

index 8b90593b638fbb4617834e3556d326974f97953a..3ac08f335a65feadcddcfc5678b2069d0a51b607 100644 (file)
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
 into its own distinct package. It is used for client-side implementations of
 Nano cryptocurrency wallets and enables building web-based applications that can
 work even while offline. `libnemo` supports managing wallets, deriving accounts,
- signing blocks, and more.
+signing blocks, and more.
 
 It utilizes the Web Crypto API which is native to all modern browsers. Private
 keys are encrypted in storage with a user password as soon as they are derived,
@@ -19,19 +19,19 @@ hardware wallet support.
 
 ## Features
 
-* Generate new BIP-32 hierarchial deterministic (HD) wallets with a BIP-39
-mnemonic phrase and the Nano path registered with BIP-44. Used by Ledger
-hardware wallet.
-* Generate new BLAKE2b wallets with a BIP-39 mnemonic phrases. Original method
-described by nano spec.
-* Import wallets with a mnemonic phrase or a seed.
-* Derive indexed accounts with a Nano address and a public-private keypair.
-* Create, sign, and verify send, receive, and change blocks.
-* Get account info and process blocks on the network while online.
-* Manage known addresses with a rolodex.
-* Sign and verify arbitrary strings with relevant keys.
-* Validate seeds, mnemonic phrases, and Nano addresses.
-* Convert Nano unit denominations.
+- Generate new BIP-32 hierarchial deterministic (HD) wallets with a BIP-39
+  mnemonic phrase and the Nano path registered with BIP-44. Used by Ledger
+  hardware wallet.
+- Generate new BLAKE2b wallets with a BIP-39 mnemonic phrases. Original method
+  described by nano spec.
+- Import wallets with a mnemonic phrase or a seed.
+- Derive indexed accounts with a Nano address and a public-private keypair.
+- Create, sign, and verify send, receive, and change blocks.
+- Get account info and process blocks on the network while online.
+- Manage known addresses with a rolodex.
+- Sign and verify arbitrary strings with relevant keys.
+- Validate seeds, mnemonic phrases, and Nano addresses.
+- Convert Nano unit denominations.
 
 ## Installation
 
@@ -53,10 +53,10 @@ in a wallet starts at index 0.
 
 For clarity, the following terms are used throughout the library:
 
- * BIP-32 - Defines how hierarchical determinstic (HD) wallets are generated
- * BIP-39 - Defines how mnemonic phrases are generated
- * BIP-44 - Expands on BIP-32 to define how an enhanced derivation path can
- allow a single wallet to store multiple currencies
+- BIP-32 - Defines how hierarchical determinstic (HD) wallets are generated
+- BIP-39 - Defines how mnemonic phrases are generated
+- BIP-44 - Expands on BIP-32 to define how an enhanced derivation path can
 allow a single wallet to store multiple currencies
 
 `libnemo` is able to generate and import HD and BLAKE2b wallets, and it can
 derive accounts for both. An HD wallet seed is 64 bytes (128 hexadecimal
@@ -86,20 +86,20 @@ const wallet = await Wallet.load('BLAKE2b', password, mnemonic)
 
 ```javascript
 try {
-       await wallet.unlock(password)
+  await wallet.unlock(password);
 } catch (err) {
-       console.error(err)
+  console.error(err);
 }
 
-const firstAccount = await wallet.account()
-const secondAccount = await wallet.account(1)
-const multipleAccounts = await wallet.accounts(2, 3)
-const thirdAccount = multipleAccounts[2]
-const { address, publicKey, index } = firstAccount
+const firstAccount = await wallet.account();
+const secondAccount = await wallet.account(1);
+const multipleAccounts = await wallet.accounts(2, 3);
+const thirdAccount = multipleAccounts[2];
+const { address, publicKey, index } = firstAccount;
 
-const nodeUrl = 'https://nano-node.example.com/'
-await firstAccount.refresh(nodeUrl) // online
-const { frontier, balance, representative } = firstAccount
+const nodeUrl = "https://nano-node.example.com/";
+await firstAccount.refresh(nodeUrl); // online
+const { frontier, balance, representative } = firstAccount;
 ```
 
 ### Blocks
@@ -115,11 +115,11 @@ All blocks are 'state' types, but they are interpreted as one of three different
 subtypes based on the data they contain: send, receive, or change
 representative. `libnemo` implements three methods to handle them appropriately:
 
-* `send(recipient, amount)`: the Nano balance of the account decreases
-* `receive(hash, amount)`: the Nano balance of the account increases and
-requires a matching send block hash
-* `change(representative)`: the representative for the account changes while the
-Nano balance does not
+- `send(recipient, amount)`: the Nano balance of the account decreases
+- `receive(hash, amount)`: the Nano balance of the account increases and
+  requires a matching send block hash
+- `change(representative)`: the representative for the account changes while the
+  Nano balance does not
 
 _Nano protocol allows changing the representative at the same time as a balance
 change. `libnemo` does not implement this for purposes of clarity; all change
@@ -129,7 +129,8 @@ Always fetch the most up to date information for the account from the network
 using the
 [account_info RPC command](https://docs.nano.org/commands/rpc-protocol/#account_info)
 which can then be used to populate the block parameters. This can be done on a
-per-account basis with the `account.refresh()` method.
+per-account basis with the `account.refresh()` method or for a range of accounts
+with the `wallet.refresh()` method.
 
 Blocks require a small proof-of-work that must be calculated for the block to be
 accepted by the network. This can be provided when creating the block, generated
@@ -138,7 +139,7 @@ allows the
 [work_generate RPC command](https://docs.nano.org/commands/rpc-protocol/#work_generate).
 
 Finally, the block must be signed with the private key of the account. `libnemo`
-wallets, accounts, and blocks can all create signatures, event offline if
+wallets, accounts, and blocks can all create signatures, even offline if
 desired. After being signed, the block can be published to the network with the
 `block.process()` method or by separately calling out to the
 [process RPC command](https://docs.nano.org/commands/rpc-protocol/#process).
@@ -146,75 +147,75 @@ desired. After being signed, the block can be published to the network with the
 #### Creating blocks
 
 ```javascript
-import { Block } from 'libnemo'
+import { Block } from "libnemo";
 
 const sendBlock = new Block(
-       'nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d', // sender
-       '5618869000000000000000000000000',                                   // current balance
-       '92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D',  // hash of previous block
-       'nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou'  // representative
-)
+  "nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d", // sender
+  "5618869000000000000000000000000", // current balance
+  "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D", // hash of previous block
+  "nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou" // representative
+);
 sendBlock.send(
-       'nano_3phqgrqbso99xojkb1bijmfryo7dy1k38ep1o3k3yrhb7rqu1h1k47yu78gz', // recipient
-       '2000000000000000000000000000000'                                    // amount to send
-)
+  "nano_3phqgrqbso99xojkb1bijmfryo7dy1k38ep1o3k3yrhb7rqu1h1k47yu78gz", // recipient
+  "2000000000000000000000000000000" // amount to send
+);
 await sendBlock.pow(
-       'fbffed7c73b61367'                                                   // PoW nonce (argument is optional)
-)
-await sendBlock.sign(wallet, accountIndex)                             // signature added to block
+  "fbffed7c73b61367" // PoW nonce (argument is optional)
+);
+await sendBlock.sign(wallet, accountIndex); // signature added to block
 await sendBlock.process(
-       'https://nano-node.example.com'                                      // must be online
-)
+  "https://nano-node.example.com" // must be online
+);
 
 const receiveBlock = await new Block(
-       'nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d', // recipient
-       '18618869000000000000000000000000',                                  // current balance
-       '92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D',  // hash of previous block
-       'nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou'  // representative
+  "nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d", // recipient
+  "18618869000000000000000000000000", // current balance
+  "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D", // hash of previous block
+  "nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou" // representative
 )
-.receive(                                                              // methods can be chained
-       'CBC911F57B6827649423C92C88C0C56637A4274FF019E77E24D61D12B5338783',  // origin (hash of matching send block)
-       '7000000000000000000000000000000',                                   // amount that was sent
-)
-.pow(
-       'c5cf86de24b24419'                                                   // PoW nonce (synchronous if value provided)
-)
-.sign(wallet, accountIndex, frontier)                                  // frontier may be necessary when using Ledger devices
+  .receive(
+    // methods can be chained
+    "CBC911F57B6827649423C92C88C0C56637A4274FF019E77E24D61D12B5338783", // origin (hash of matching send block)
+    "7000000000000000000000000000000" // amount that was sent
+  )
+  .pow(
+    "c5cf86de24b24419" // PoW nonce (synchronous if value provided)
+  )
+  .sign(wallet, accountIndex, frontier); // frontier may be necessary when using Ledger devices
 
 const changeBlock = await new Block(
-       'nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d', // account redelegating vote weight
-       '3000000000000000000000000000000',                                   // current balance
-       '128106287002E595F479ACD615C818117FCB3860EC112670557A2467386249D4'   // hash of previous block
-)
-.change(
-       'nano_1anrzcuwe64rwxzcco8dkhpyxpi8kd7zsjc1oeimpc3ppca4mrjtwnqposrs'  // new representative
-)
+  "nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d", // account redelegating vote weight
+  "3000000000000000000000000000000", // current balance
+  "128106287002E595F479ACD615C818117FCB3860EC112670557A2467386249D4" // hash of previous block
+).change(
+  "nano_1anrzcuwe64rwxzcco8dkhpyxpi8kd7zsjc1oeimpc3ppca4mrjtwnqposrs" // new representative
+);
 sign(
-       '1495F2D49159CC2EAAAA97EBB42346418E1268AFF16D7FCA90E6BAD6D0965520'   // sign blocks with literal private keys too
-)
-.pow()                                                                 // async if calculating locally
+  "1495F2D49159CC2EAAAA97EBB42346418E1268AFF16D7FCA90E6BAD6D0965520" // sign blocks with literal private keys too
+).pow(); // async if calculating locally
 ```
 
 #### Signing a block with a wallet
 
 ```javascript
-const wallet = await Wallet.create('BIP-44', 'password123')
-await wallet.unlock('password123')
+const wallet = await Wallet.create("BIP-44", "password123");
+await wallet.unlock("password123");
 try {
-       await wallet.sign(0, block)
+  await wallet.sign(0, block);
 } catch (err) {
-       console.error(err)
+  console.error(err);
 }
 ```
 
 #### Signing a block with a private key
 
 ```javascript
-const privateKey = '3BE4FC2EF3F3B7374E6FC4FB6E7BB153F8A2998B3B3DAB50853EABE128024143'
+const privateKey =
+  "3BE4FC2EF3F3B7374E6FC4FB6E7BB153F8A2998B3B3DAB50853EABE128024143";
 try {
-       await block.sign(privateKey)
+  await block.sign(privateKey);
 } catch (err) {
-       console.error(err)
+  console.error(err);
 }
 ```
 
@@ -222,31 +223,31 @@ try {
 
 ```javascript
 try {
-       await block.pow()
+  await block.pow();
 } catch (err) {
-       console.error(err)
+  console.error(err);
 }
 ```
 
 #### Requesting proof-of-work from an online service
 
 ```javascript
-const node = new Rpc('https://nano-node.example.com/')
+const node = new Rpc("https://nano-node.example.com/");
 try {
-       await block.pow('https://nano-node.example.com/')
+  await block.pow("https://nano-node.example.com/");
 } catch (err) {
-       console.error(err)
+  console.error(err);
 }
 ```
 
 #### Processing a block on the network
 
 ```javascript
-const node = new Rpc('https://nano-node.example.com', 'nodes-api-key')
+const node = new Rpc("https://nano-node.example.com", "nodes-api-key");
 try {
-       const hash = await block.process('https://nano-node.example.com/')
+  const hash = await block.process("https://nano-node.example.com/");
 } catch (err) {
-       console.error(err)
+  console.error(err);
 }
 ```
 
@@ -258,8 +259,8 @@ Raw values are the native unit of exchange throughout libnemo and are
 represented by the primitive `bigint` data type. Other supported denominations
 are as follows:
 
-| Unit  | Raw |
-|-------|-----|
+| Unit  | Raw                 |
+| ----- | ------------------- |
 | RAI   | 10<sup>24</sup> raw |
 | NYANO | 10<sup>24</sup> raw |
 | KRAI  | 10<sup>27</sup> raw |
@@ -270,31 +271,53 @@ are as follows:
 | MNANO | 10<sup>36</sup> raw |
 
 ```javascript
-import { Tools } from 'libnemo'
+import { Tools } from "libnemo";
 // Denominations are case-insensitive
-const oneNanoToRaw = Tools.convert('1', 'NANO', 'RAW') // 1000000000000000000000000000000
-const oneNonillionRawToNano = Tools.convert('1000000000000000000000000000000', 'RAW', 'NANO') // 1
-const oneThousandNyanoToPico = Tools.convert('1000', 'nYaNo', 'pico') //1
-const oneThousandPicoToNano = Tools.convert('1000', 'pico', 'NANO') // 1
+const oneNanoToRaw = Tools.convert("1", "NANO", "RAW"); // 1000000000000000000000000000000
+const oneNonillionRawToNano = Tools.convert(
+  "1000000000000000000000000000000",
+  "RAW",
+  "NANO"
+); // 1
+const oneThousandNyanoToPico = Tools.convert("1000", "nYaNo", "pico"); //1
+const oneThousandPicoToNano = Tools.convert("1000", "pico", "NANO"); // 1
 ```
 
 #### Verifying signatures and signing anything with the private key
 
-Since cryptocurrencies like Nano uses asymmetric keys to sign and verify blocks
+Since cryptocurrencies like Nano use asymmetric keys to sign and verify blocks
 and transactions, a Nano account itself can be used to sign arbitrary data
 with its private key and verify signatures from other accounts with their public
-keys.
+keys. For compatibility with other similar tools, `libnemo` will first hash the
+data to a 32-byte value using BLAKE2b and then sign the resulting digest.
 
 For example, a client-side login can be implemented by challenging an account
-owner to sign their email address using their private key:
+owner to sign their email address:
 
 ```javascript
-import { Tools } from 'libnemo'
+// sign an arbitrary string
+const wallet = await Wallet.load("BIP-44", "some_password", seedToImport);
+await wallet.unlock("some_password");
+const account = await wallet.account(0);
 
-const privateKey = '3BE4FC2EF3F3B7374E6FC4FB6E7BB153F8A2998B3B3DAB50853EABE128024143'
-const publicKey = '5B65B0E8173EE0802C2C3E6C9080D1A16B06DE1176C938A924F58670904E82C4'
-const signature = await Tools.sign(privateKey, 'johndoe@example.com')
-const isValid = await Tools.verify(publicKey, signature, 'johndoe@example.com')
+const data = "johndoe@example.com";
+const signature = await wallet.sign(account.index, data);
+
+const isValid = await Tools.verify(account.publicKey, signature, data);
+console.log(isValid);
+```
+
+If the user has their private key, they can use it directly to sign too:
+
+```javascript
+import { Tools } from "libnemo";
+
+const privateKey =
+  "3BE4FC2EF3F3B7374E6FC4FB6E7BB153F8A2998B3B3DAB50853EABE128024143";
+const publicKey =
+  "5B65B0E8173EE0802C2C3E6C9080D1A16B06DE1176C938A924F58670904E82C4";
+const signature = await Tools.sign(privateKey, "johndoe@example.com");
+const isValid = await Tools.verify(publicKey, signature, "johndoe@example.com");
 ```
 
 Ownership of a Nano address can also be proven by challenging the account owner
@@ -302,32 +325,36 @@ to sign an arbitrary string and then validating the signature with the Nano
 account address.
 
 ```javascript
-import { Tools } from 'libnemo'
+import { Tools } from "libnemo";
 
-const address = 'nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d'
-const privateKey = '3BE4FC2EF3F3B7374E6FC4FB6E7BB153F8A2998B3B3DAB50853EABE128024143'
-const randomData = crypto.getRandomValues(new Uint8Array(32))
+const address =
+  "nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d";
+const privateKey =
+  "3BE4FC2EF3F3B7374E6FC4FB6E7BB153F8A2998B3B3DAB50853EABE128024143";
+const randomData = crypto.getRandomValues(new Uint8Array(32));
 
-const signature = await Tools.sign(privateKey, ...randomData)
-const publicKey = new Account(address).publicKey
-const isValid = await Tools.verify(publicKey, signature, ...randomData)
+const signature = await Tools.sign(privateKey, ...randomData);
+const publicKey = new Account(address).publicKey;
+const isValid = await Tools.verify(publicKey, signature, ...randomData);
 ```
 
 #### Validate a Nano account address
 
 ```javascript
-import { Tools } from 'libnemo'
+import { Tools } from "libnemo";
 
-const valid = Account.validate('nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d')
+const valid = Account.validate(
+  "nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d"
+);
 ```
 
 ## Tests
 
 Test vectors were retrieved from the following publicly-available locations:
 
- * Nano (BIP-44): https://docs.nano.org/integration-guides/key-management/#test-vectors
- * Trezor (BIP-39): https://github.com/trezor/python-mnemonic/blob/master/vectors.json
- * BIP-32: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#user-content-Test_Vectors
+- Nano (BIP-44): https://docs.nano.org/integration-guides/key-management/#test-vectors
+- Trezor (BIP-39): https://github.com/trezor/python-mnemonic/blob/master/vectors.json
+- BIP-32: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#user-content-Test_Vectors
 
 Another set of test vectors were created for libnemo based on the Trezor set.
 These extra test vectors were generated purely to test uncommon yet valid
@@ -337,12 +364,12 @@ mnemonic phrase lengths like 15 or 18 words.
 
 ## Building
 
-* `npm run build`: compile and build
-* `npm run test`: all of the above, run tests, and print results to the console
-* `npm run test:coverage`: all of the above, calculate code coverage, and print
-code coverage to the console
-* `npm run test:coverage:report`: all of the above, and open an HTML code
-coverage report in the browser (requires lcov and xdg-open)
+- `npm run build`: compile and build
+- `npm run test`: all of the above, run tests, and print results to the console
+- `npm run test:coverage`: all of the above, calculate code coverage, and print
+  code coverage to the console
+- `npm run test:coverage:report`: all of the above, and open an HTML code
+  coverage report in the browser (requires lcov and xdg-open)
 
 ## Donations
 
index e8e76d3f7a43f791f504c66e776fb40d8abd3024..c32c7e639dc1b69a28cb519087055167ee0c8758 100644 (file)
@@ -1,7 +1,7 @@
 {
        "name": "libnemo",
        "version": "0.6.0",
-       "description": "Asynchronous, non-blocking Nano cryptocurrency integration toolkit.",
+       "description": "Nano cryptocurrency wallet library.",
        "keywords": [
                "nemo",
                "nano",