Skip to content

Instantly share code, notes, and snippets.

@bkase
Last active June 7, 2021 23:20
Show Gist options
  • Save bkase/c437ba23b3218c5a481376f6474fcafe to your computer and use it in GitHub Desktop.
Save bkase/c437ba23b3218c5a481376f6474fcafe to your computer and use it in GitHub Desktop.
Generating an offline transaction for mina-generate-keypair keys (v2 -- no docker dependency)

Generating an offline transaction v2

Before there is an official tool to directly generate an offline transaction with the standard mina-generate-keypair generated private key, we can instead convert the key to the client-sdk compatible format and then use the client-sdk to generate the transaction.

A while ago I shared this idea with @nholland94, who in turn made a script that I then tweaked to use Docker and created this one. Thanks @nholland94 !

@nholland94's script minimizes the time that the private key is unencrypted by immediately creating the transaction as soon as the key is dumped.

Version 2 of this script replaces the key loading functionality from relying on the mina daemon via docker to doing everything through NodeJS -- thanks to Auro wallet ( https://github.com/bitcat365/auro-wallet-browser-extension/blob/0888a71dc6abde41ad72889c1826a722c3954553/src/background/accountService.js#L58 ) for the routine of loading the libsodium-encrypted wallet files.

How to use

  1. Install NodeJS, libsodium, and yarn on your system

On MacOS in a Terminal session (Terminal.app):

brew install node libsodium

Note: This process was tested as working with libsodium-1.0.18

npm install -g yarn
  1. Install docker on your system

  2. Download this gist as a zip file and unzip and enter this directory in your Terminal.app

  3. Pull the latest version of the dependencies

yarn install
  1. Copy in your private key

Put a keys/ folder containing your (password protected) private key with chmod 700 permissions (this should already be set properly if you generated your keys using the minaprotocol.com docs) in this directory.

  1. Edit the make-transaction.js to point to your key

Change the line in main() to set the privateKeyPath to your key.

  1. Edit the make-transaction.js to form your transaction

Change these fields as appropriate for your transaction

{
    to: 'B62qoozSJ5Zjz8m3LtRnmoejs425H38LfU6kYTrsoPPVy9hzrLc5HBM', // the receiver of the payment
    amount: 10**9, // 1.0 mina -- in nanonmina (1 billion = 1.0 mina)
    fee: 1 * 10**7, // 0.01 mina -- in nanonmina (1 billion = 1.0 mina)
    nonce: 0 // the number of transactions you've made on this account already. you can find this in minaexplorer.com
}
  1. Open a new terminal session

On MacOS, this is the Terminal.app program (or your favorite)

  1. Export your private key password as an environment variable

Change mypassword below with your password

export CODA_PRIVKEY_PASS=mypassword
  1. Run the script generating the transaction in that Terminal.app session
node make-transaction.js

This will make the transaction in a file called my-txn.json in this directory. You can view it with cat:

cat my-txn.json
  1. Broadcast using your favorite tool

For example, https://minaexplorer.com/broadcast-tx

const MinaSDK = require('@o1labs/client-sdk');
const fs = require('fs');
const process = require('process');
const child_process = require('child_process');
const bs58check = require('bs58check');
const sodium = require('libsodium-wrappers');
function run(cmd, env) {
return new Promise((resolve, reject) => {
child_process.exec(cmd, {env}, (error, stdout, stderr) => {
if(error) {
reject(error);
} else {
resolve(stdout.split("\n").filter((l) => l.length > 0));
}
});
});
}
// tweaked from Auro wallet https://github.com/bitcat365/auro-wallet-browser-extension/blob/0888a71dc6abde41ad72889c1826a722c3954553/src/background/accountService.js#L58
async function importWalletByKeystore(keyfile, keyfilePassword) {
try {
await sodium.ready;
let key = await sodium.crypto_pwhash(
32,
keyfilePassword,
bs58check.decode(keyfile.pwsalt).slice(1),
keyfile.pwdiff[1],
keyfile.pwdiff[0],
sodium.crypto_pwhash_ALG_ARGON2I13
)
const ciphertext = bs58check.decode(keyfile.ciphertext).slice(1)
const nonce = bs58check.decode(keyfile.nonce).slice(1)
const privateKeyHex = '5a' + sodium.crypto_secretbox_open_easy(ciphertext, nonce, key, 'hex')
const privateKeyBuffer = Buffer.from(privateKeyHex, 'hex')
const privateKey = bs58check.encode(privateKeyBuffer)
const publicKey = MinaSDK.derivePublicKey(privateKey)
return {
privateKey,
publicKey
}
}
catch (e) {
return {error: 'keystoreError' , type:"local", err: e}
}
}
async function makePayment(outputFile, {privateKeyPath, to, fee, amount, nonce}) {
const keyfile = JSON.parse(fs.readFileSync(privateKeyPath, 'utf-8'));
const keypair = await importWalletByKeystore(keyfile, process.env.CODA_PRIVKEY_PASS);
const from = keypair.publicKey;
const signedPayment = MinaSDK.signPayment({
from,
to,
fee,
amount,
// memo,
nonce
}, keypair);
fs.writeFileSync(outputFile, JSON.stringify(signedPayment));
}
async function main() {
const privateKeyPath = 'keys/my-wallet-private-key'
await makePayment('my-txn.json', {
privateKeyPath,
to: 'B62qoozSJ5Zjz8m3LtRnmoejs425H38LfU6kYTrsoPPVy9hzrLc5HBM', // the receiver of the payment
amount: 10**9, // 1.0 mina -- in nanonmina (1 billion = 1.0 mina)
fee: 1 * 10**7, // 0.01 mina -- in nanonmina (1 billion = 1.0 mina)
nonce: 0 // the number of transactions you've made on this account already. you can find this in minaexplorer.com
});
}
main();
{
"dependencies": {
"@o1labs/client-sdk": "^1.0.1",
"bs58check": "^2.1.2",
"libsodium-wrappers": "~0.7"
}
}
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@o1labs/client-sdk@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@o1labs/client-sdk/-/client-sdk-1.0.1.tgz#5b2824a52fd0e839873791e4a5949aa5197a2ed1"
integrity sha512-fHAmY31WIKuXIgMohyf5kJkjNGNqco965YwE2Td4k+IJRswO2ymaIK2Uo4CSp8UqivGzF02i7oJJhzyWWViN5Q==
dependencies:
"@types/node" "^13.7.0"
"@types/node@^13.7.0":
version "13.13.52"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.52.tgz#03c13be70b9031baaed79481c0c0cfb0045e53f7"
integrity sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==
base-x@^3.0.2:
version "3.0.8"
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d"
integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==
dependencies:
safe-buffer "^5.0.1"
bs58@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo=
dependencies:
base-x "^3.0.2"
bs58check@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc"
integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==
dependencies:
bs58 "^4.0.0"
create-hash "^1.1.0"
safe-buffer "^5.1.2"
cipher-base@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==
dependencies:
inherits "^2.0.1"
safe-buffer "^5.0.1"
create-hash@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
dependencies:
cipher-base "^1.0.1"
inherits "^2.0.1"
md5.js "^1.3.4"
ripemd160 "^2.0.1"
sha.js "^2.4.0"
hash-base@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"
integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==
dependencies:
inherits "^2.0.4"
readable-stream "^3.6.0"
safe-buffer "^5.2.0"
inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
libsodium-wrappers@~0.7:
version "0.7.9"
resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.9.tgz#4ffc2b69b8f7c7c7c5594a93a4803f80f6d0f346"
integrity sha512-9HaAeBGk1nKTRFRHkt7nzxqCvnkWTjn1pdjKgcUnZxj0FyOP4CnhgFhMdrFfgNsukijBGyBLpP2m2uKT1vuWhQ==
dependencies:
libsodium "^0.7.0"
libsodium@^0.7.0:
version "0.7.9"
resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.9.tgz#4bb7bcbf662ddd920d8795c227ae25bbbfa3821b"
integrity sha512-gfeADtR4D/CM0oRUviKBViMGXZDgnFdMKMzHsvBdqLBHd9ySi6EtYnmuhHVDDYgYpAO8eU8hEY+F8vIUAPh08A==
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==
dependencies:
hash-base "^3.0.0"
inherits "^2.0.1"
safe-buffer "^5.1.2"
readable-stream@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
ripemd160@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==
dependencies:
hash-base "^3.0.0"
inherits "^2.0.1"
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
sha.js@^2.4.0:
version "2.4.11"
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==
dependencies:
inherits "^2.0.1"
safe-buffer "^5.0.1"
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies:
safe-buffer "~5.2.0"
util-deprecate@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment