Release v0.3.0

This commit is contained in:
Manu Herrera
2020-11-09 10:05:29 -03:00
parent 4e9aa7a3c5
commit 8107c4478b
1265 changed files with 440488 additions and 107809 deletions

View File

@@ -0,0 +1,16 @@
ISC License
Copyright (c) 2013-2017 The btcsuite developers
Copyright (c) 2015-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.

View File

@@ -0,0 +1,367 @@
// Copyright (c) 2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
// Package txauthor provides transaction creation code for wallets.
package txauthor
import (
"errors"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/wallet/txrules"
"github.com/btcsuite/btcwallet/wallet/txsizes"
)
// SumOutputValues sums up the list of TxOuts and returns an Amount.
func SumOutputValues(outputs []*wire.TxOut) (totalOutput btcutil.Amount) {
for _, txOut := range outputs {
totalOutput += btcutil.Amount(txOut.Value)
}
return totalOutput
}
// InputSource provides transaction inputs referencing spendable outputs to
// construct a transaction outputting some target amount. If the target amount
// can not be satisified, this can be signaled by returning a total amount less
// than the target or by returning a more detailed error implementing
// InputSourceError.
type InputSource func(target btcutil.Amount) (total btcutil.Amount, inputs []*wire.TxIn,
inputValues []btcutil.Amount, scripts [][]byte, err error)
// InputSourceError describes the failure to provide enough input value from
// unspent transaction outputs to meet a target amount. A typed error is used
// so input sources can provide their own implementations describing the reason
// for the error, for example, due to spendable policies or locked coins rather
// than the wallet not having enough available input value.
type InputSourceError interface {
error
InputSourceError()
}
// Default implementation of InputSourceError.
type insufficientFundsError struct{}
func (insufficientFundsError) InputSourceError() {}
func (insufficientFundsError) Error() string {
return "insufficient funds available to construct transaction"
}
// AuthoredTx holds the state of a newly-created transaction and the change
// output (if one was added).
type AuthoredTx struct {
Tx *wire.MsgTx
PrevScripts [][]byte
PrevInputValues []btcutil.Amount
TotalInput btcutil.Amount
ChangeIndex int // negative if no change
}
// ChangeSource provides P2PKH change output scripts for transaction creation.
type ChangeSource func() ([]byte, error)
// NewUnsignedTransaction creates an unsigned transaction paying to one or more
// non-change outputs. An appropriate transaction fee is included based on the
// transaction size.
//
// Transaction inputs are chosen from repeated calls to fetchInputs with
// increasing targets amounts.
//
// If any remaining output value can be returned to the wallet via a change
// output without violating mempool dust rules, a P2WPKH change output is
// appended to the transaction outputs. Since the change output may not be
// necessary, fetchChange is called zero or one times to generate this script.
// This function must return a P2WPKH script or smaller, otherwise fee estimation
// will be incorrect.
//
// If successful, the transaction, total input value spent, and all previous
// output scripts are returned. If the input source was unable to provide
// enough input value to pay for every output any any necessary fees, an
// InputSourceError is returned.
//
// BUGS: Fee estimation may be off when redeeming non-compressed P2PKH outputs.
func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb btcutil.Amount,
fetchInputs InputSource, fetchChange ChangeSource) (*AuthoredTx, error) {
targetAmount := SumOutputValues(outputs)
estimatedSize := txsizes.EstimateVirtualSize(0, 1, 0, outputs, true)
targetFee := txrules.FeeForSerializeSize(relayFeePerKb, estimatedSize)
for {
inputAmount, inputs, inputValues, scripts, err := fetchInputs(targetAmount + targetFee)
if err != nil {
return nil, err
}
if inputAmount < targetAmount+targetFee {
return nil, insufficientFundsError{}
}
// We count the types of inputs, which we'll use to estimate
// the vsize of the transaction.
var nested, p2wpkh, p2pkh int
for _, pkScript := range scripts {
switch {
// If this is a p2sh output, we assume this is a
// nested P2WKH.
case txscript.IsPayToScriptHash(pkScript):
nested++
case txscript.IsPayToWitnessPubKeyHash(pkScript):
p2wpkh++
default:
p2pkh++
}
}
maxSignedSize := txsizes.EstimateVirtualSize(p2pkh, p2wpkh,
nested, outputs, true)
maxRequiredFee := txrules.FeeForSerializeSize(relayFeePerKb, maxSignedSize)
remainingAmount := inputAmount - targetAmount
if remainingAmount < maxRequiredFee {
targetFee = maxRequiredFee
continue
}
unsignedTransaction := &wire.MsgTx{
Version: wire.TxVersion,
TxIn: inputs,
TxOut: outputs,
LockTime: 0,
}
changeIndex := -1
changeAmount := inputAmount - targetAmount - maxRequiredFee
if changeAmount != 0 && !txrules.IsDustAmount(changeAmount,
txsizes.P2WPKHPkScriptSize, relayFeePerKb) {
changeScript, err := fetchChange()
if err != nil {
return nil, err
}
if len(changeScript) > txsizes.P2WPKHPkScriptSize {
return nil, errors.New("fee estimation requires change " +
"scripts no larger than P2WPKH output scripts")
}
change := wire.NewTxOut(int64(changeAmount), changeScript)
l := len(outputs)
unsignedTransaction.TxOut = append(outputs[:l:l], change)
changeIndex = l
}
return &AuthoredTx{
Tx: unsignedTransaction,
PrevScripts: scripts,
PrevInputValues: inputValues,
TotalInput: inputAmount,
ChangeIndex: changeIndex,
}, nil
}
}
// RandomizeOutputPosition randomizes the position of a transaction's output by
// swapping it with a random output. The new index is returned. This should be
// done before signing.
func RandomizeOutputPosition(outputs []*wire.TxOut, index int) int {
r := cprng.Int31n(int32(len(outputs)))
outputs[r], outputs[index] = outputs[index], outputs[r]
return int(r)
}
// RandomizeChangePosition randomizes the position of an authored transaction's
// change output. This should be done before signing.
func (tx *AuthoredTx) RandomizeChangePosition() {
tx.ChangeIndex = RandomizeOutputPosition(tx.Tx.TxOut, tx.ChangeIndex)
}
// SecretsSource provides private keys and redeem scripts necessary for
// constructing transaction input signatures. Secrets are looked up by the
// corresponding Address for the previous output script. Addresses for lookup
// are created using the source's blockchain parameters and means a single
// SecretsSource can only manage secrets for a single chain.
//
// TODO: Rewrite this interface to look up private keys and redeem scripts for
// pubkeys, pubkey hashes, script hashes, etc. as separate interface methods.
// This would remove the ChainParams requirement of the interface and could
// avoid unnecessary conversions from previous output scripts to Addresses.
// This can not be done without modifications to the txscript package.
type SecretsSource interface {
txscript.KeyDB
txscript.ScriptDB
ChainParams() *chaincfg.Params
}
// AddAllInputScripts modifies transaction a transaction by adding inputs
// scripts for each input. Previous output scripts being redeemed by each input
// are passed in prevPkScripts and the slice length must match the number of
// inputs. Private keys and redeem scripts are looked up using a SecretsSource
// based on the previous output script.
func AddAllInputScripts(tx *wire.MsgTx, prevPkScripts [][]byte, inputValues []btcutil.Amount,
secrets SecretsSource) error {
inputs := tx.TxIn
hashCache := txscript.NewTxSigHashes(tx)
chainParams := secrets.ChainParams()
if len(inputs) != len(prevPkScripts) {
return errors.New("tx.TxIn and prevPkScripts slices must " +
"have equal length")
}
for i := range inputs {
pkScript := prevPkScripts[i]
switch {
// If this is a p2sh output, who's script hash pre-image is a
// witness program, then we'll need to use a modified signing
// function which generates both the sigScript, and the witness
// script.
case txscript.IsPayToScriptHash(pkScript):
err := spendNestedWitnessPubKeyHash(inputs[i], pkScript,
int64(inputValues[i]), chainParams, secrets,
tx, hashCache, i)
if err != nil {
return err
}
case txscript.IsPayToWitnessPubKeyHash(pkScript):
err := spendWitnessKeyHash(inputs[i], pkScript,
int64(inputValues[i]), chainParams, secrets,
tx, hashCache, i)
if err != nil {
return err
}
default:
sigScript := inputs[i].SignatureScript
script, err := txscript.SignTxOutput(chainParams, tx, i,
pkScript, txscript.SigHashAll, secrets, secrets,
sigScript)
if err != nil {
return err
}
inputs[i].SignatureScript = script
}
}
return nil
}
// spendWitnessKeyHash generates, and sets a valid witness for spending the
// passed pkScript with the specified input amount. The input amount *must*
// correspond to the output value of the previous pkScript, or else verification
// will fail since the new sighash digest algorithm defined in BIP0143 includes
// the input value in the sighash.
func spendWitnessKeyHash(txIn *wire.TxIn, pkScript []byte,
inputValue int64, chainParams *chaincfg.Params, secrets SecretsSource,
tx *wire.MsgTx, hashCache *txscript.TxSigHashes, idx int) error {
// First obtain the key pair associated with this p2wkh address.
_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript,
chainParams)
if err != nil {
return err
}
privKey, compressed, err := secrets.GetKey(addrs[0])
if err != nil {
return err
}
pubKey := privKey.PubKey()
// Once we have the key pair, generate a p2wkh address type, respecting
// the compression type of the generated key.
var pubKeyHash []byte
if compressed {
pubKeyHash = btcutil.Hash160(pubKey.SerializeCompressed())
} else {
pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed())
}
p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, chainParams)
if err != nil {
return err
}
// With the concrete address type, we can now generate the
// corresponding witness program to be used to generate a valid witness
// which will allow us to spend this output.
witnessProgram, err := txscript.PayToAddrScript(p2wkhAddr)
if err != nil {
return err
}
witnessScript, err := txscript.WitnessSignature(tx, hashCache, idx,
inputValue, witnessProgram, txscript.SigHashAll, privKey, true)
if err != nil {
return err
}
txIn.Witness = witnessScript
return nil
}
// spendNestedWitnessPubKey generates both a sigScript, and valid witness for
// spending the passed pkScript with the specified input amount. The generated
// sigScript is the version 0 p2wkh witness program corresponding to the queried
// key. The witness stack is identical to that of one which spends a regular
// p2wkh output. The input amount *must* correspond to the output value of the
// previous pkScript, or else verification will fail since the new sighash
// digest algorithm defined in BIP0143 includes the input value in the sighash.
func spendNestedWitnessPubKeyHash(txIn *wire.TxIn, pkScript []byte,
inputValue int64, chainParams *chaincfg.Params, secrets SecretsSource,
tx *wire.MsgTx, hashCache *txscript.TxSigHashes, idx int) error {
// First we need to obtain the key pair related to this p2sh output.
_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript,
chainParams)
if err != nil {
return err
}
privKey, compressed, err := secrets.GetKey(addrs[0])
if err != nil {
return err
}
pubKey := privKey.PubKey()
var pubKeyHash []byte
if compressed {
pubKeyHash = btcutil.Hash160(pubKey.SerializeCompressed())
} else {
pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed())
}
// Next, we'll generate a valid sigScript that'll allow us to spend
// the p2sh output. The sigScript will contain only a single push of
// the p2wkh witness program corresponding to the matching public key
// of this address.
p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, chainParams)
if err != nil {
return err
}
witnessProgram, err := txscript.PayToAddrScript(p2wkhAddr)
if err != nil {
return err
}
bldr := txscript.NewScriptBuilder()
bldr.AddData(witnessProgram)
sigScript, err := bldr.Script()
if err != nil {
return err
}
txIn.SignatureScript = sigScript
// With the sigScript in place, we'll next generate the proper witness
// that'll allow us to spend the p2wkh output.
witnessScript, err := txscript.WitnessSignature(tx, hashCache, idx,
inputValue, witnessProgram, txscript.SigHashAll, privKey, compressed)
if err != nil {
return err
}
txIn.Witness = witnessScript
return nil
}
// AddAllInputScripts modifies an authored transaction by adding inputs scripts
// for each input of an authored transaction. Private keys and redeem scripts
// are looked up using a SecretsSource based on the previous output script.
func (tx *AuthoredTx) AddAllInputScripts(secrets SecretsSource) error {
return AddAllInputScripts(tx.Tx, tx.PrevScripts, tx.PrevInputValues, secrets)
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package txauthor
import (
"crypto/rand"
"encoding/binary"
mrand "math/rand"
"sync"
)
// cprng is a cryptographically random-seeded math/rand prng. It is seeded
// during package init. Any initialization errors result in panics. It is safe
// for concurrent access.
var cprng = cprngType{}
type cprngType struct {
r *mrand.Rand
mu sync.Mutex
}
func init() {
buf := make([]byte, 8)
_, err := rand.Read(buf)
if err != nil {
panic("Failed to seed prng: " + err.Error())
}
seed := int64(binary.LittleEndian.Uint64(buf))
cprng.r = mrand.New(mrand.NewSource(seed))
}
func (c *cprngType) Int31n(n int32) int32 {
defer c.mu.Unlock() // Int31n may panic
c.mu.Lock()
return c.r.Int31n(n)
}

View File

@@ -0,0 +1,14 @@
module github.com/btcsuite/btcwallet/wallet/txauthor
go 1.12
require (
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0
)
replace github.com/btcsuite/btcwallet/wallet/txrules => ../txrules
replace github.com/btcsuite/btcwallet/wallet/txsizes => ../txsizes

View File

@@ -0,0 +1,66 @@
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3 h1:A/EVblehb75cUgXA5njHPn0kLAsykn6mJGz7rnmW5W0=
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4=
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJGQE=
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44 h1:9lP3x0pW80sDI6t1UMSLA4to18W7R7imwAI/sWS9S8Q=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006 h1:bfLnR+k0tq5Lqt6dflRLcZiz6UaXCMt3vhYJ1l4FQ80=
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=