-
Notifications
You must be signed in to change notification settings - Fork 155
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add offline wallet guide and movefunds utility (#252)
A guide explaining how to spend from a cold wallet while offline was added along with a utility, movefunds, enabling easier signing of offline transactions.
- Loading branch information
Showing
6 changed files
with
435 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"txfee": 10000000, | ||
"sendtoaddress": "Ssgymtv6sjYPVEE6mwkbYJz1HzPicKpLCwc", | ||
"network": "simnet", | ||
"dcrctlargs": "--wallet" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/* | ||
* Copyright (c) 2016 The Decred developers | ||
* | ||
* Permission to use, copy, modify, and distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | ||
* copyright notice and this permission notice appear in all copies. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*/ | ||
|
||
package main | ||
|
||
import ( | ||
"github.com/decred/dcrd/chaincfg" | ||
"github.com/decred/dcrd/txscript" | ||
"github.com/decred/dcrd/wire" | ||
"github.com/decred/dcrutil" | ||
) | ||
|
||
// makeTx generates a transaction spending outputs to a single address. | ||
func makeTx(params *chaincfg.Params, | ||
inputs []*extendedOutPoint, | ||
addr dcrutil.Address, | ||
txFee int64) (*wire.MsgTx, error) { | ||
mtx := wire.NewMsgTx() | ||
|
||
allInAmts := int64(0) | ||
for _, input := range inputs { | ||
txIn := wire.NewTxIn(input.op, []byte{}) | ||
mtx.AddTxIn(txIn) | ||
allInAmts += input.amt | ||
} | ||
|
||
pkScript, err := txscript.PayToAddrScript(addr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
txOut := wire.NewTxOut(allInAmts-txFee, pkScript) | ||
txOut.Version = txscript.DefaultScriptVersion | ||
mtx.AddTxOut(txOut) | ||
|
||
return mtx, nil | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* | ||
* Copyright (c) 2016 The Decred developers | ||
* | ||
* Permission to use, copy, modify, and distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | ||
* copyright notice and this permission notice appear in all copies. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*/ | ||
|
||
package main | ||
|
||
const ( | ||
// All transactions have 4 bytes for version, 4 bytes of locktime, | ||
// 4 bytes of expiry, and 2 varints for the number of inputs and | ||
// outputs, and 1 varint for the witnesses. | ||
txOverheadEstimate = 4 + 4 + 4 + 1 + 1 + 1 | ||
|
||
// A worst case signature script to redeem a P2PKH output for a | ||
// compressed pubkey has 73 bytes of the possible DER signature | ||
// (with no leading 0 bytes for R and S), 65 bytes of serialized pubkey, | ||
// and data push opcodes for both, plus one byte for the hash type flag | ||
// appended to the end of the signature. | ||
sigScriptEstimate = 1 + 73 + 1 + 65 + 1 | ||
|
||
// A best case tx input serialization cost is 32 bytes of sha, 4 bytes | ||
// of output index, 1 byte for tree, 4 bytes of sequence, 12 bytes for | ||
// fraud proof, one byte for both the txin signature size (0) and the | ||
// witness signature script size, and the estimated signature script | ||
// size. | ||
txInEstimate = 32 + 4 + 1 + 12 + 4 + 1 + 1 + sigScriptEstimate | ||
|
||
// A P2PKH pkScript contains the following bytes: | ||
// - OP_DUP | ||
// - OP_HASH160 | ||
// - OP_DATA_20 + 20 bytes of pubkey hash | ||
// - OP_EQUALVERIFY | ||
// - OP_CHECKSIG | ||
pkScriptEstimate = 1 + 1 + 1 + 20 + 1 + 1 | ||
|
||
// txOutEstimate is a best case tx output serialization cost is 8 bytes | ||
// of value, two bytes of version, one byte of varint, and the pkScript | ||
// size. | ||
txOutEstimate = 8 + 2 + 1 + pkScriptEstimate | ||
) | ||
|
||
var ( | ||
// maxTxSize is the maximum size of a transaction we can | ||
// build with the wallet. | ||
maxTxSize int | ||
) | ||
|
||
func estimateTxSize(numInputs, numOutputs int) int { | ||
return txOverheadEstimate + txInEstimate*numInputs + txOutEstimate*numOutputs | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
/* | ||
* Copyright (c) 2016 The Decred developers | ||
* | ||
* Permission to use, copy, modify, and distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | ||
* copyright notice and this permission notice appear in all copies. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*/ | ||
|
||
package main | ||
|
||
import ( | ||
"bytes" | ||
"encoding/hex" | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"math" | ||
"os" | ||
"sort" | ||
|
||
"github.com/decred/dcrd/chaincfg" | ||
"github.com/decred/dcrd/chaincfg/chainhash" | ||
"github.com/decred/dcrd/dcrjson" | ||
"github.com/decred/dcrd/wire" | ||
"github.com/decred/dcrutil" | ||
) | ||
|
||
// params is the global representing the chain parameters. It is assigned | ||
// in main. | ||
var params *chaincfg.Params | ||
|
||
// configJSON is a configuration file used for transaction generation. | ||
type configJSON struct { | ||
TxFee int64 `json:"txfee"` | ||
SendToAddress string `json:"sendtoaddress"` | ||
Network string `json:"network"` | ||
DcrctlArgs string `json:"dcrctlargs"` | ||
} | ||
|
||
// extendedOutPoint is a UTXO with an amount. | ||
type extendedOutPoint struct { | ||
op *wire.OutPoint | ||
amt int64 | ||
pkScript []byte | ||
} | ||
|
||
// extendedOutPoints is an extendedOutPoint used for sorting by UTXO amount. | ||
type extendedOutPoints struct { | ||
eops []*extendedOutPoint | ||
} | ||
|
||
func (e extendedOutPoints) Len() int { return len(e.eops) } | ||
func (e extendedOutPoints) Less(i, j int) bool { | ||
return e.eops[i].amt < e.eops[j].amt | ||
} | ||
func (e extendedOutPoints) Swap(i, j int) { | ||
e.eops[i], e.eops[j] = e.eops[j], e.eops[i] | ||
} | ||
|
||
// convertJSONUnspentToOutPoints converts a JSON raw dump from listunspent to | ||
// a set of UTXOs. | ||
func convertJSONUnspentToOutPoints( | ||
utxos []dcrjson.ListUnspentResult) []*extendedOutPoint { | ||
var eops []*extendedOutPoint | ||
for _, utxo := range utxos { | ||
if utxo.TxType == 1 && utxo.Vout == 0 { | ||
continue | ||
} | ||
|
||
op := new(wire.OutPoint) | ||
hash, _ := chainhash.NewHashFromStr(utxo.TxID) | ||
op.Hash = *hash | ||
op.Index = uint32(utxo.Vout) | ||
op.Tree = int8(utxo.Tree) | ||
|
||
pks, err := hex.DecodeString(utxo.ScriptPubKey) | ||
if err != nil { | ||
fmt.Println("failure decoding pkscript from unspent list") | ||
os.Exit(1) | ||
} | ||
|
||
eop := new(extendedOutPoint) | ||
eop.op = op | ||
amtCast, _ := dcrutil.NewAmount(utxo.Amount) | ||
eop.amt = int64(amtCast) | ||
eop.pkScript = pks | ||
|
||
eops = append(eops, eop) | ||
} | ||
|
||
return eops | ||
} | ||
|
||
func main() { | ||
// 1. Load the UTXOs ---------------------------------------------------------- | ||
unspentFile, err := os.Open("unspent.json") | ||
if err != nil { | ||
fmt.Println("error opening unspent file unspent.json", err.Error()) | ||
} | ||
|
||
var utxos []dcrjson.ListUnspentResult | ||
|
||
jsonParser := json.NewDecoder(unspentFile) | ||
if err = jsonParser.Decode(&utxos); err != nil { | ||
fmt.Println("error parsing unspent file", err.Error()) | ||
} | ||
|
||
// Sort the inputs so that the largest one is first. | ||
inputs := extendedOutPoints{convertJSONUnspentToOutPoints(utxos)} | ||
sort.Sort(sort.Reverse(inputs)) | ||
|
||
// 2. Load the config --------------------------------------------------------- | ||
configFile, err := os.Open("config.json") | ||
if err != nil { | ||
fmt.Println("error opening config file config.json", err.Error()) | ||
} | ||
|
||
cfg := new(configJSON) | ||
|
||
jsonParser = json.NewDecoder(configFile) | ||
if err = jsonParser.Decode(cfg); err != nil { | ||
fmt.Println("error parsing config file", err.Error()) | ||
} | ||
|
||
// 3. Check the config and parse ---------------------------------------------- | ||
switch cfg.Network { | ||
case "testnet": | ||
params = &chaincfg.TestNetParams | ||
case "mainnet": | ||
params = &chaincfg.MainNetParams | ||
case "simnet": | ||
params = &chaincfg.SimNetParams | ||
default: | ||
fmt.Println("Failed to parse a correct network") | ||
return | ||
} | ||
|
||
maxTxSize = params.MaximumBlockSize - 75000 | ||
|
||
sendToAddress, err := dcrutil.DecodeAddress(cfg.SendToAddress, params) | ||
if err != nil { | ||
fmt.Println("Failed to parse tx address: ", err.Error()) | ||
} | ||
|
||
// 4. Create the transaction -------------------------------------------------- | ||
// First get how much we're sending. | ||
allInAmts := int64(0) | ||
var utxosToUse []*extendedOutPoint | ||
for _, utxo := range inputs.eops { | ||
utxosToUse = append(utxosToUse, utxo) | ||
allInAmts += utxo.amt | ||
} | ||
|
||
// Convert to KB. | ||
sz := float64(estimateTxSize(len(utxosToUse), 1)) / 1000 | ||
feeEst := int64(math.Ceil(sz * float64(cfg.TxFee))) | ||
|
||
tx, err := makeTx(params, utxosToUse, sendToAddress, feeEst) | ||
if err != nil { | ||
fmt.Println("Couldn't produce tx: ", err.Error()) | ||
return | ||
} | ||
|
||
if tx.SerializeSize() > maxTxSize { | ||
fmt.Printf("tx too big: got %v, max %v", tx.SerializeSize(), | ||
maxTxSize) | ||
return | ||
} | ||
|
||
// 5. Write the transactions to files in raw form with the proper command | ||
// required to sign them. | ||
txB, err := tx.Bytes() | ||
if err != nil { | ||
fmt.Println("Failed to serialize tx: ", err.Error()) | ||
return | ||
} | ||
|
||
// The command to sign the transaction. | ||
var buf bytes.Buffer | ||
buf.WriteString("dcrctl ") | ||
buf.WriteString(cfg.DcrctlArgs) | ||
buf.WriteString(" signrawtransaction ") | ||
buf.WriteString(hex.EncodeToString(txB)) | ||
buf.WriteString(" '[") | ||
last := len(utxosToUse) - 1 | ||
for i, utxo := range utxosToUse { | ||
buf.WriteString("{\"txid\":\"") | ||
buf.WriteString(utxo.op.Hash.String()) | ||
buf.WriteString("\",\"vout\":") | ||
buf.WriteString(fmt.Sprintf("%v", utxo.op.Index)) | ||
buf.WriteString(",\"tree\":") | ||
buf.WriteString(fmt.Sprintf("%v", utxo.op.Tree)) | ||
buf.WriteString(",\"scriptpubkey\":\"") | ||
buf.WriteString(hex.EncodeToString(utxo.pkScript)) | ||
buf.WriteString("\",\"redeemscript\":\"\"}") | ||
if i != last { | ||
buf.WriteString(",") | ||
} | ||
} | ||
buf.WriteString("]' ") | ||
buf.WriteString("| jq -r .hex") | ||
err = ioutil.WriteFile("sign.sh", []byte(buf.String()), 0755) | ||
if err != nil { | ||
fmt.Println("Failed to write signing script: ", err.Error()) | ||
return | ||
} | ||
|
||
fmt.Println("Successfully wrote transaction to sign script.") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
### Guides | ||
|
||
[Rebuilding all transaction history with forced rescans](https://github.com/decred/dcrwallet/tree/master/docs/force_rescans.md) | ||
|
||
[Spending funds offline using cold wallets](https://github.com/decred/dcrwallet/tree/master/docs/offline_wallets.md) |
Oops, something went wrong.