External Management

TOP

For larger, more robust systems, external private key management is recommended. In this setup, the node operator generates and stores private keys in an external database and only queries the rai_node to:

  1. Find pending blocks for an account
  2. Sign transactions given a private key. More advanced systems may choose to implement signing themselves.
  3. Broadcast the signed transaction to the network.

WALLET_IDs are not used for External Private Key Management since private keys are not stored in the rai_node. Much of this section builds off of the Universal Blocks documentation.


Expanding Private Keys

A Nano private key is a 256-bit piece of data produced from a cryptographically secure random number generator. Generating private keys from an insecure source may result in loss of funds. Be sure to backup any generated private key; if lost the funds in the account will become inaccessible.

The bash command below generates a valid private key from a cryptographically secure random number generator:

cat /dev/urandom | tr -dc '0-9A-F' | head -c${1:-64}
# D56143E7561D71C1AF4D563C6AF79EECE93E82479818AD8ED88BED1AAE8BE4E5

From the private key, a public key can be derived, and the public key can be translated into a Nano Address.

Request Format
curl -d '{ "action": "key_expand", "key": "<PRIVATE_KEY>"}' http://127.0.0.1:7076
Request
curl -d '{
  "action": "key_expand",
  "key": "781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3"
}' http://127.0.0.1:7076

Success Response

{
  "private": "781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3",
  "public": "3068BB1CA04525BB0E416C485FE6A67FD52540227D267CC8B6E8DA958A7FA039",
  "account": "xrb_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx"
}

Generating Transactions

Using external keys, transactions are generated in two steps: creation and broadcast. This section will be more heavy on example rather than precise specifications.

Send Transaction

To send funds to an account, first call the account_info RPC command to gather necessary account information to craft your transaction. Setting "representative": "true" makes the rai_node also return the account's representative address, a necessary piece of data for creating a transaction.

Request Format
curl -d '{"action": "account_info", "representative": "true", "account": "<YOUR_ACCOUNT>" }' http://127.0.0.1:7076
Request
curl -d '{"action": "account_info", "representative": "true", "account": "xrb_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx"}' http://127.0.0.1:7076
Success Response
{
  "frontier": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D",
  "open_block": "B292BFFAAE9013BE630B31144EF15205E986940080687C0441CCFE6EAB67FE53",
  "representative_block": "B292BFFAAE9013BE630B31144EF15205E986940080687C0441CCFE6EAB67FE53",
  "balance": "4618869000000000000000000000000",
  "modified_timestamp": "1524626644",
  "block_count": "4",
  "representative": "xrb_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou"
}

Using this information, we can copy over some of the fields of the block_create command. All block_create calls have "type": "state". Note: at this point the transaction information is incomplete.

{
  "action": "block_create",
  "type": "state",
  "previous": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D",
  "account": "xrb_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx",
  "representative": "xrb_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou",
  "balance": "",
  "link": "",
  "key": "",
}

The destination address is entered into the "link" field. The account balance in rawraw, after the transaction is complete, is entered in the "balance" field. Since only the resulting balance is recorded, the transaction amount is interpreted as the difference in balance from the previous block on the account-chain and the newly created block. For this reason, it is crucial that you obtain the current account balance and headblock in the same atomic RPC call via the account_info command.

To complete this example, we will send 1 nanonano (1030raw10^{30} raw) to address xrb_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p. Enter the account's private keys in the "key" field.

curl -d '{
  "action": "block_create",
  "type": "state",
  "previous": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D",
  "account": "xrb_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx",
  "representative": "xrb_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou",
  "balance": "3618869000000000000000000000000",
  "link": "xrb_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p",
  "key": "781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3"
}' http://127.0.0.1:7076

The rai_node creating and signing this transaction has no concept of what the transaction amount is nor network state; all the rai_node knows is that it is creating a block whose previous block on the account chain has hash 92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D results in the account having a balance of 3618869000000000000000000000000.

If the account's balance at block hash 92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D was actually 5618869000000000000000000000000, then 2 nanonano would have been sent to xrb_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p.


This may raise the question:

  • "What if I receive funds on my account and then broadcast the above crafted send? Would this result in me sending excess funds to the recipient?"

  • If you followed this guide, then the answer is "no". When you issued the account_info command, you received the account's balance at a specific blockhash on its account-chain. In your crafted transaction, you specify that hash in the "previous" field. If funds were signed into your account, the headblock on your account-chain would change. Since your send no longer refers to the headblock on your account-chain when broadcasted, the Nano Network would reject your transaction.


When not following this guide closely, the following inappropriate sequence of events could lead to erroneous amounts sent to a recipient.

  1. An account's balance, say 5 nanonano, was obtained using the account_balance RPC command (never use this command for transaction related operations). This balance is valid as of hypothetical BLOCK_A.
  2. By another process you control, a receive (BLOCK_B) was signed and broadcasted into your account-chain (race-condition).

    • Lets say this receive increased the funds on the account chain by 10 nanonano, resulting in a final balance 15 nanonano.
  3. The account's frontier block is obtained by the accounts_frontiers RPC command, returning the hash of BLOCK_B. Other transaction metadata is obtained by other RPC commands.
  4. With the collected data, if a send transaction was created for 3 nanonano, the final balance would be computed as 535 - 3, or 2 nanonano.
  5. When this is broadcasted, since it is referring to the current head block on the account, BLOCK_B, the network would accept it. But, because the balance as of BLOCK_B was actually 15 nanonano, this would result in 12 nanonano being sent to the recipient.

For this reason, only populate transaction data source from a single account_info RPC call.


As a result of the command above, the rai_node will return a signed, but not yet broadcasted transaction. Broadcasting of the signed transaction is covered in the Broadcasting a Transaction section.

{
  "hash": "8DB5C07E0E62E9DFE8558CB9BD654A115B02245B38CD369753CECE36DAD13C05",
  "block": "{\n    \"type\": \"state\",\n    \"account\": \"xrb_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx\",\n    \"previous\": \"92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D\",\n    \"representative\": \"xrb_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou\",\n    \"balance\": \"3618869000000000000000000000000\",\n    \"link\": \"5C2FBB148E006A8E8BA7A75DD86C9FE00C83F5FFDBFD76EAA09531071436B6AF\",\n    \"link_as_account\": \"xrb_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p\",\n    \"signature\": \"79240D56231EF1885F354473733AF158DC6DA50E53836179565A20C0BE89D473ED3FF8CD11545FF0ED162A0B2C4626FD6BF84518568F8BB965A4884C7C32C205\",\n    \"work\": \"fbffed7c73b61367\"\n}\n"
}

block_create commands generally take longer than other RPC commands because the rai_node has to generate the proof-of-work for the transaction. The response block data is already properly escaped for the process RPC command.

The newlines ("\n") are for display purposes only and are ignored.

Always ensure that every quotation mark is properly escaped.

Receive Transaction

Receiving funds is very similar to sending funds outlined in the previous section. The differences are as follows:


  1. "link" field will refer to the block hash of its paired Send transaction.

  2. Account balance must increase by the amount indicated by the Send transaction.


The remaining portion of this section will run through the complete process.

curl -d '{"action": "account_info", "representative": "true", "account": "xrb_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx"}' http://127.0.0.1:7076
{
  "frontier": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D",
  "open_block": "B292BFFAAE9013BE630B31144EF15205E986940080687C0441CCFE6EAB67FE53",
  "representative_block": "B292BFFAAE9013BE630B31144EF15205E986940080687C0441CCFE6EAB67FE53",
  "balance": "4618869000000000000000000000000",
  "modified_timestamp": "1524626644",
  "block_count": "4",
  "representative": "xrb_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou"
}

Using this information, we can fill in some of the fields of the block_create command.


All block_create requests should include a "type" of "state". This indicates that the block type is a Universal Block.

At this point the transaction information is still incomplete.


{
  "action": "block_create",
  "type": "state",
  "previous": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D",
  "account": "xrb_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx",
  "representative": "xrb_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou",
  "balance": "",
  "link": "",
  "key": "",
}

In this example, we will assume that the pairing send was for 7nano7 nano and has a hash of CBC911F57B6827649423C92C88C0C56637A4274FF019E77E24D61D12B5338783.

The command to create this transaction would be:

curl -d '{
  "action": "block_create",
  "type": "state",
  "previous": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D",
  "account": "xrb_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx",
  "representative": "xrb_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou",
  "balance": "11618869000000000000000000000000",
  "link": "CBC911F57B6827649423C92C88C0C56637A4274FF019E77E24D61D12B5338783",
  "key": "781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3"
}' http://127.0.0.1:7076

Here the follow scenario occurs:

  • Previous balance was 4618869000000000000000000000000 rawraw
  • Increased our balance by 7000000000000000000000000000000 rawraw
  • Final balance becomes 11618869000000000000000000000000 rawraw
{
  "hash": "350D145570578A36D3D5ADE58DC7465F4CAAF257DD55BD93055FF826057E2CDD",
  "block": "{\n    \"type\": \"state\",\n    \"account\": \"xrb_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx\",\n    \"previous\": \"92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D\",\n    \"representative\": \"xrb_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou\",\n    \"balance\": \"11618869000000000000000000000000\",\n    \"link\": \"CBC911F57B6827649423C92C88C0C56637A4274FF019E77E24D61D12B5338783\",\n    \"link_as_account\": \"xrb_3kyb49tqpt39ekc49kbej51ecsjqnimnzw1swxz4boix4ctm93w517umuiw8\",\n    \"signature\": \"EEFFE1EFCCC8F2F6F2F1B79B80ABE855939DD9D6341323186494ADEE775DAADB3B6A6A07A85511F2185F6E739C4A54F1454436E22255A542ED879FD04FEED001\",\n    \"work\": \"c5cf86de24b24419\"\n}\n"
}

The block data is now ready to be broadcasted via the process RPC command.

Manually Receive First Block

The very first transaction on an account-chain, which is always a receive, is a bit special since it doesn't have a "previous" block. The process however, is very similar to a conventional receive transaction.

Field Description
previous Value is 0 (32 0's)
account Same as normal receive.
representative Choose a reliable, trustworthy representative.
balance Same as normal receive. This will be the transaction amount of the pairing send.
link Same as normal receive.
key Same as normal receive.

Broadcasting a Transaction

Common to all of these transactions is the need to broadcast the completed block to the network. This is achieved by the process RPC command. A successful broadcast will return the broadcasted block's hash.

Request Format
curl -d '{"action": "process", "block": "<STRINGIFIED_JSON_DATA>" }' http://127.0.0.1:7076
Request
curl -d '{
  "action": "process",
  "block": "{
      \"type\": \"state\",
      \"account\": \"xrb_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx\",
      \"previous\": \"92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D\",
      \"representative\": \"xrb_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou\",
      \"balance\": \"11618869000000000000000000000000\",
      \"link\": \"CBC911F57B6827649423C92C88C0C56637A4274FF019E77E24D61D12B5338783\",
      \"signature\": \"EEFFE1EFCCC8F2F6F2F1B79B80ABE855939DD9D6341323186494ADEE775DAADB3B6A6A07A85511F2185F6E739C4A54F1454436E22255A542ED879FD04FEED001\",
      \"work\": \"c5cf86de24b24419\"
    }"
  }' http://127.0.0.1:7076

Success Response

{ "hash": "42A723D2B60462BF7C9A003FE9A70057D3A6355CA5F1D0A57581000000000000" }

It is best practice to run an additional, auxilary rai_node to confirm that the transaction was successfully broadcasted to the Nano network. On the auxilary rai_node, you can check if the transaction is in this secondary nodes local ledger by utilizing the block RPC command.

curl -d '{
  "action": "block",
  "hash": "48006BF3146C18CAD3A53A957BF64EF7C57820B21FCCE373FA637559DA260358"
}' http://127.0.0.1:7076
{
  "contents": "{
    \"type\": \"state\",
    \"account\": \"xrb_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx\",
    \"previous\": \"92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D\",
    \"representative\": \"xrb_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou\",
    \"balance\": \"11618869000000000000000000000000\",
    \"link\": \"CBC911F57B6827649423C92C88C0C56637A4274FF019E77E24D61D12B5338783\",
    \"link_as_account\": \"xrb_3kyb49tqpt39ekc49kbej51ecsjqnimnzw1swxz4boix4ctm93w517umuiw8\",
    \"signature\": \"EEFFE1EFCCC8F2F6F2F1B79B80ABE855939DD9D6341323186494ADEE775DAADB3B6A6A07A85511F2185F6E739C4A54F1454436E22255A542ED879FD04FEED001\",
    \"work\": \"c5cf86de24b24419\"
  }"
}

Below are a few helpful pieces of information to consider:

  • It may take a few seconds for the transaction to appear. If the transaction fails to appear, you may call the same process command with the same block data with no risk. Account-chains must be continuous and unbroken.

  • If for some reason a transaction fails to properly broadcast, subsequent transactions on the account-chain after that transaction will not be accepted by the network since the "previous" field in the transaction data refers to a non-existant block.

  • Rebroadcasting the missing transaction(s) will make the subsequent blocks valid in the network's ledger.


The following command rebroadcasts all hashes on an account-chain starting at block with hash ${BLOCK_HASH}:

Request Format
curl -d '{"action": "republish", "hash": "<BLOCK_HASH>"}' http://127.0.0.1:7076