Skip to content

Commit 1ccaf61

Browse files
committed
Initial commit
0 parents  commit 1ccaf61

File tree

5 files changed

+171
-0
lines changed

5 files changed

+171
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.vscode
2+
node_modules

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2017 Dominik Schöni
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# bip39-tx-signer
2+
While there are [some tools](https://github.com/iancoleman/bip39) to easily generate your own HD-wallet using BIP39, signing a transaction on your cold storage hardware is still a hassle. I wrote this small CLI tool to make my life, and maybe yours, a bit easier.
3+
4+
## How-To
5+
6+
1. Clone this repository
7+
2. `npm install`
8+
3. `node index.js` for help
9+
10+
## Examples
11+
To sign a transaction:
12+
13+
```
14+
node index.js createEthTx --from "0xc2E87a289041fd0f04a954da6044ff8bb60927a4" --to "0xAAec6b3e1A7D9841255fAd5db80c027372496B2E" --nonce 0 --value 1 --gasPrice 21000000000 --gasLimit 21000
15+
```
16+
17+
This will create a transaction for 1 ETH from `0xc2E87a289041fd0f04a954da6044ff8bb60927a4` to `0xAAec6b3e1A7D9841255fAd5db80c027372496B2E`. You will then be asked to supply your mnemonic phrase using a prompt.
18+
19+
**Caution** : If you use `--mnemonic` to supply your phrase, be advised that your phrase can be recovered from your bash/shell history.

index.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/usr/bin/env node
2+
3+
const bip39 = require('bip39')
4+
const EthereumTx = require('ethereumjs-tx')
5+
const ethereumUtil = require('ethereumjs-util')
6+
const ethereumUnits = require('ethereumjs-units')
7+
const bitcoinjs = require('bitcoinjs-lib')
8+
const prompt = require('prompt')
9+
const yargs = require('yargs')
10+
11+
var decimalToHex = function (number) {
12+
if (typeof number === 'string') {
13+
number = parseInt(number)
14+
}
15+
16+
if (number < 0) {
17+
return process.exit(1)
18+
}
19+
20+
return ethereumUtil.addHexPrefix(number.toString(16))
21+
}
22+
23+
var buildTx = function (mnemonic, argv) {
24+
mnemonic = mnemonic.trim()
25+
26+
if (!bip39.validateMnemonic(mnemonic)) {
27+
console.warn('your mnenomic phrase could not been validated.')
28+
process.exit(1)
29+
}
30+
31+
const bip32RootKey = bitcoinjs.HDNode.fromSeedBuffer(bip39.mnemonicToSeed(mnemonic))
32+
33+
const child = bip32RootKey.derivePath(argv.path)
34+
35+
const privateKey = child.keyPair.d.toBuffer()
36+
const senderAddress = ethereumUtil.toChecksumAddress(ethereumUtil.privateToAddress(privateKey).toString('hex'))
37+
38+
if (senderAddress !== ethereumUtil.toChecksumAddress(argv.from)) {
39+
console.warn('your derivation path does not give the same address as you have specified.')
40+
console.warn('Your Address:', argv.from)
41+
console.warn('Derived Address:', senderAddress)
42+
process.exit(1)
43+
}
44+
45+
console.log('')
46+
console.log('')
47+
console.log('Sender: ', senderAddress)
48+
console.log('Path: ' + argv.path)
49+
console.log('')
50+
51+
const tx = new EthereumTx({
52+
value: decimalToHex(ethereumUnits.convert(argv.value, 'ether', 'wei')),
53+
nonce: decimalToHex(argv.nonce),
54+
gasPrice: decimalToHex(argv.gasPrice),
55+
gasLimit: decimalToHex(argv.gasLimit),
56+
data: argv.data.toString(16),
57+
to: argv.to
58+
}, 1)
59+
60+
const fee = tx.getUpfrontCost()
61+
console.log('Minimal Account Balance:', ethereumUnits.convert(fee.toString(), 'wei', 'eth'), 'ETH')
62+
63+
tx.sign(privateKey)
64+
65+
console.log('')
66+
console.log(senderAddress, '==>', ethereumUnits.convert(parseInt(tx.value.toString('hex'), 16), 'wei', 'eth'), 'ETH ==>', ethereumUtil.toChecksumAddress(tx.to.toString('hex')))
67+
console.log('')
68+
console.log('')
69+
console.log('--- Signed Transaction ----')
70+
console.log(tx.serialize().toString('hex'))
71+
}
72+
73+
var signTx = function (argv) {
74+
if (!argv.mnemonic) {
75+
prompt.start()
76+
prompt.get(['mnemonic'], (error, result) => {
77+
if (error) {
78+
console.warn(error)
79+
process.exit(1)
80+
}
81+
82+
buildTx(result.mnemonic, argv)
83+
})
84+
} else {
85+
buildTx(argv.mnemonic, argv)
86+
}
87+
}
88+
89+
yargs.usage('$0 <cmd> [args]')
90+
.command('createEthTx [from] [to] [value] [nonce] [gasPrice] [gasLimit] [data]', 'sign a transaction using your derivation path and mnenomic phrase', {
91+
path: { type: 'string', describe: 'BIP32/BIP44 compatible derivation path', default: "m/44'/60'/0'/0/0" },
92+
to: { type: 'string', describe: 'ETH Address to receive your funds' },
93+
from: { type: 'string', describe: 'ETH Address you\'re sending from' },
94+
value: { type: 'string', describe: 'Value to send in ETH' },
95+
nonce: { type: 'string' },
96+
gasPrice: { type: 'string' },
97+
gasLimit: { type: 'string' },
98+
data: { type: 'string', default: '0x' }
99+
}, signTx)
100+
.demandOption(['path'], 'A BIP32 or BIP44 compatible derivation path is necessary')
101+
.demandOption(['from'], 'ETH Address you\'re sending from. We need this to validate your path is correct')
102+
.demandOption(['to'], 'ETH Address that should receive your funds')
103+
.demandOption(['value'], 'How much ETH would you like to send?')
104+
.demandOption(['nonce'], 'The nonce is mandatory')
105+
.demandOption(['gasPrice'], 'Please provide the Gas Price in WEI')
106+
.demandOption(['gasLimit'], 'Please provide the Gas Limit in WEI')
107+
.help()
108+
.argv

package.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "bip39-tx-signer",
3+
"version": "1.0.0",
4+
"description": "A small CLI that allows you to sign your transactions using your bip39 mnenomic phrase and the correct derivation path",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": "Dominik Schöni",
10+
"license": "MIT",
11+
"dependencies": {
12+
"bignumber.js": "^4.0.2",
13+
"bip39": "^2.3.1",
14+
"bitcoinjs-lib": "^3.1.0",
15+
"ethereumjs-tx": "^1.3.1",
16+
"ethereumjs-units": "^0.2.0",
17+
"ethereumjs-util": "^5.1.2",
18+
"prompt": "^1.0.0",
19+
"yargs": "^8.0.2"
20+
}
21+
}

0 commit comments

Comments
 (0)