Merge branch 'uspvdev'

This commit is contained in:
Tadge Dryja 2016-02-26 13:23:00 -08:00
commit 2367300d71
13 changed files with 1309 additions and 344 deletions

3
.gitignore vendored

@ -33,6 +33,9 @@ cmd/cmd
cmd/lncli/lncli
cmd/lnshell/lnshell
sighash143/sighash143
sighash143/*.hex
test_wal/*
# vim

@ -86,7 +86,7 @@ func newLightningChannel(wallet *LightningWallet, events chainntnfs.ChainNotifie
return nil, err
}
_, multiSigIndex := findScriptOutputIndex(state.FundingTx, fundingPkScript)
lc.fundingTxIn = wire.NewTxIn(wire.NewOutPoint(&fundingTxId, multiSigIndex), nil)
lc.fundingTxIn = wire.NewTxIn(wire.NewOutPoint(&fundingTxId, multiSigIndex), nil, nil)
lc.fundingP2SH = fundingPkScript
return lc, nil

@ -529,7 +529,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
// Empty sig script, we'll actually sign if this reservation is
// queued up to be completed (the other side accepts).
outPoint := wire.NewOutPoint(coin.Hash(), coin.Index())
ourContribution.Inputs[i] = wire.NewTxIn(outPoint, nil)
ourContribution.Inputs[i] = wire.NewTxIn(outPoint, nil, nil)
}
l.coinSelectMtx.Unlock()
@ -794,7 +794,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// since the outputs are cannonically sorted.
fundingNTxid := fundingTx.TxSha() // NOTE: assumes testnet-L
_, multiSigIndex := findScriptOutputIndex(fundingTx, multiSigOut.PkScript)
fundingTxIn := wire.NewTxIn(wire.NewOutPoint(&fundingNTxid, multiSigIndex), nil)
fundingTxIn := wire.NewTxIn(wire.NewOutPoint(&fundingNTxid, multiSigIndex), nil, nil)
// With the funding tx complete, create both commitment transactions.
initialBalance := ourContribution.FundingAmount

@ -4,11 +4,12 @@ import (
"bytes"
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"io"
"io/ioutil"
)
var MAX_SLICE_LENGTH = 65535
@ -557,7 +558,7 @@ func readElement(r io.Reader, element interface{}) error {
var txins []*wire.TxIn
for i := uint8(0); i < numScripts; i++ {
outpoint := new(wire.OutPoint)
txin := wire.NewTxIn(outpoint, nil)
txin := wire.NewTxIn(outpoint, nil, nil)
err = readElement(r, &txin)
if err != nil {
return err

268
shell.go

@ -2,16 +2,13 @@ package main
import (
"bufio"
"bytes"
"fmt"
"log"
"os"
"sort"
"strconv"
"strings"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
@ -25,15 +22,15 @@ testing. It can send and receive coins.
const (
keyFileName = "testkey.hex"
headerFileName = "headers.bin"
dbFileName = "/dev/shm/utxo.db"
dbFileName = "utxo.db"
// this is my local testnet node, replace it with your own close by.
// Random internet testnet nodes usually work but sometimes don't, so
// maybe I should test against different versions out there.
SPVHostAdr = "127.0.0.1:18333"
SPVHostAdr = "127.0.0.1:28333"
)
var (
Params = &chaincfg.TestNet3Params
Params = &chaincfg.SegNetParams
SCon uspv.SPVCon // global here for now
)
@ -51,7 +48,7 @@ func shell() {
// setup spvCon
SCon, err = uspv.OpenSPV(
SPVHostAdr, headerFileName, dbFileName, &Store, false, false, Params)
SPVHostAdr, headerFileName, dbFileName, &Store, true, false, Params)
if err != nil {
log.Fatal(err)
}
@ -61,7 +58,7 @@ func shell() {
log.Fatal(err)
}
if tip == 0 { // DB has never been used, set to birthday
tip = 675000 // hardcoded; later base on keyfile date?
tip = 21900 // hardcoded; later base on keyfile date?
err = SCon.TS.SetDBSyncHeight(tip)
if err != nil {
log.Fatal(err)
@ -144,6 +141,20 @@ func Shellparse(cmdslice []string) error {
}
return nil
}
if cmd == "fan" {
err = Fan(args)
if err != nil {
fmt.Printf("fan error: %s\n", err)
}
return nil
}
if cmd == "sweep" {
err = Sweep(args)
if err != nil {
fmt.Printf("sweep error: %s\n", err)
}
return nil
}
if cmd == "txs" {
err = Txs(args)
if err != nil {
@ -199,39 +210,164 @@ func Bal(args []string) error {
return fmt.Errorf("Can't get balance, spv connection broken")
}
fmt.Printf(" ----- Account Balance ----- \n")
allUtxos, err := SCon.TS.GetAllUtxos()
rawUtxos, err := SCon.TS.GetAllUtxos()
if err != nil {
return err
}
var score int64
var allUtxos uspv.SortableUtxoSlice
for _, utxo := range rawUtxos {
allUtxos = append(allUtxos, *utxo)
}
// smallest and unconfirmed last (because it's reversed)
sort.Sort(sort.Reverse(allUtxos))
var score, confScore int64
for i, u := range allUtxos {
fmt.Printf("\tutxo %d height %d %s key: %d amt %d\n",
fmt.Printf("\tutxo %d height %d %s key:%d amt %d",
i, u.AtHeight, u.Op.String(), u.KeyIdx, u.Value)
if u.IsWit {
fmt.Printf(" WIT")
}
fmt.Printf("\n")
score += u.Value
if u.AtHeight != 0 {
confScore += u.Value
}
}
height, _ := SCon.TS.GetDBSyncHeight()
atx, err := SCon.TS.GetAllTxs()
stxos, err := SCon.TS.GetAllStxos()
for i, a := range SCon.TS.Adrs {
fmt.Printf("address %d %s\n", i, a.PkhAdr.String())
wa, err := btcutil.NewAddressWitnessPubKeyHash(
a.PkhAdr.ScriptAddress(), Params)
if err != nil {
return err
}
fmt.Printf("Total known utxos: %d\n", len(allUtxos))
fmt.Printf("Total spendable coin: %d\n", score)
fmt.Printf("address %d %s OR %s\n", i, a.PkhAdr.String(), wa.String())
}
fmt.Printf("Total known txs: %d\n", len(atx))
fmt.Printf("Known utxos: %d\tPreviously spent txos: %d\n",
len(allUtxos), len(stxos))
fmt.Printf("Total coin: %d confirmed: %d\n", score, confScore)
fmt.Printf("DB sync height: %d\n", height)
return nil
}
// Adr makes a new address.
func Adr(args []string) error {
// if there's an arg, make 10 adrs
if len(args) > 0 {
for i := 0; i < 10; i++ {
_, err := SCon.TS.NewAdr()
if err != nil {
return err
}
}
}
if len(args) > 1 {
for i := 0; i < 1000; i++ {
_, err := SCon.TS.NewAdr()
if err != nil {
return err
}
}
}
// always make one
a, err := SCon.TS.NewAdr()
if err != nil {
return err
}
fmt.Printf("made new address %s, %d addresses total\n",
a.String(), len(SCon.TS.Adrs))
fmt.Printf("made new address %s\n",
a.String())
return nil
}
// Sweep sends every confirmed uxto in your wallet to an address.
// it does them all individually to there are a lot of txs generated.
// syntax: sweep adr
func Sweep(args []string) error {
if len(args) < 2 {
return fmt.Errorf("sweep syntax: sweep adr")
}
adr, err := btcutil.DecodeAddress(args[0], SCon.TS.Param)
if err != nil {
fmt.Printf("error parsing %s as address\t", args[0])
return err
}
numTxs, err := strconv.ParseInt(args[1], 10, 64)
if err != nil {
return err
}
if numTxs < 1 {
return fmt.Errorf("can't send %d txs", numTxs)
}
rawUtxos, err := SCon.TS.GetAllUtxos()
if err != nil {
return err
}
var allUtxos uspv.SortableUtxoSlice
for _, utxo := range rawUtxos {
allUtxos = append(allUtxos, *utxo)
}
// smallest and unconfirmed last (because it's reversed)
sort.Sort(sort.Reverse(allUtxos))
for i, u := range allUtxos {
if u.AtHeight != 0 {
err = SCon.SendOne(allUtxos[i], adr)
if err != nil {
return err
}
numTxs--
if numTxs == 0 {
return nil
}
}
}
fmt.Printf("spent all confirmed utxos; not enough by %d\n", numTxs)
return nil
}
// Fan generates a bunch of fanout. Only for testing, can be expensive.
// syntax: fan adr numOutputs valOutputs witty
func Fan(args []string) error {
if len(args) < 3 {
return fmt.Errorf("fan syntax: fan adr numOutputs valOutputs")
}
adr, err := btcutil.DecodeAddress(args[0], SCon.TS.Param)
if err != nil {
fmt.Printf("error parsing %s as address\t", args[0])
return err
}
numOutputs, err := strconv.ParseInt(args[1], 10, 64)
if err != nil {
return err
}
valOutputs, err := strconv.ParseInt(args[2], 10, 64)
if err != nil {
return err
}
adrs := make([]btcutil.Address, numOutputs)
amts := make([]int64, numOutputs)
for i := int64(0); i < numOutputs; i++ {
adrs[i] = adr
amts[i] = valOutputs + i
}
return SCon.SendCoins(adrs, amts)
}
// Send sends coins.
func Send(args []string) error {
if SCon.RBytes == 0 {
@ -254,102 +390,30 @@ func Send(args []string) error {
}
// need args, fail
if len(args) < 2 {
return fmt.Errorf("need args: ssend amount(satoshis) address")
return fmt.Errorf("need args: ssend address amount(satoshis) wit?")
}
amt, err := strconv.ParseInt(args[0], 10, 64)
adr, err := btcutil.DecodeAddress(args[0], SCon.TS.Param)
if err != nil {
fmt.Printf("error parsing %s as address\t", args[0])
return err
}
amt, err := strconv.ParseInt(args[1], 10, 64)
if err != nil {
return err
}
if amt < 1000 {
return fmt.Errorf("can't send %d, too small", amt)
}
adr, err := btcutil.DecodeAddress(args[1], SCon.TS.Param)
if err != nil {
fmt.Printf("error parsing %s as address\t", args[1])
return err
}
fmt.Printf("send %d to address: %s \n",
amt, adr.String())
err = SendCoins(SCon, adr, amt)
if err != nil {
return err
}
return nil
}
// SendCoins does send coins, but it's very rudimentary
func SendCoins(s uspv.SPVCon, adr btcutil.Address, sendAmt int64) error {
var err error
var score int64
allUtxos, err := s.TS.GetAllUtxos()
if err != nil {
return err
}
var adrs []btcutil.Address
var amts []int64
for _, utxo := range allUtxos {
score += utxo.Value
}
// important rule in bitcoin, output total > input total is invalid.
if sendAmt > score {
return fmt.Errorf("trying to send %d but %d available.",
sendAmt, score)
}
tx := wire.NewMsgTx() // make new tx
// make address script 76a914...88ac
adrScript, err := txscript.PayToAddrScript(adr)
if err != nil {
return err
}
// make user specified txout and add to tx
txout := wire.NewTxOut(sendAmt, adrScript)
tx.AddTxOut(txout)
nokori := sendAmt // nokori is how much is needed on input side
for _, utxo := range allUtxos {
// generate pkscript to sign
prevPKscript, err := txscript.PayToAddrScript(
s.TS.Adrs[utxo.KeyIdx].PkhAdr)
if err != nil {
return err
}
// make new input from this utxo
thisInput := wire.NewTxIn(&utxo.Op, prevPKscript)
tx.AddTxIn(thisInput)
nokori -= utxo.Value
if nokori < -10000 { // minimum overage / fee is 1K now
break
}
}
// there's enough left to make a change output
if nokori < -200000 {
change, err := s.TS.NewAdr()
if err != nil {
return err
}
changeScript, err := txscript.PayToAddrScript(change)
if err != nil {
return err
}
changeOut := wire.NewTxOut((-100000)-nokori, changeScript)
tx.AddTxOut(changeOut)
}
// use txstore method to sign
err = s.TS.SignThis(tx)
if err != nil {
return err
}
fmt.Printf("tx: %s", uspv.TxToString(tx))
buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
tx.Serialize(buf)
fmt.Printf("tx: %x\n", buf.Bytes())
// send it out on the wire. hope it gets there.
// we should deal with rejects. Don't yet.
err = s.NewOutgoingTx(tx)
adrs = append(adrs, adr)
amts = append(amts, amt)
err = SCon.SendCoins(adrs, amts)
if err != nil {
return err
}

317
sighash143/sighash143.go Normal file

@ -0,0 +1,317 @@
package main
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"log"
"math"
"strings"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/uspv"
)
const (
inspk0 = "2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac"
inamt0 = int64(625000000)
inspk1 = "00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"
inamt1 = int64(600000000)
xpecthash = "c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670"
)
// calcWitnessSignatureHash is the witnessified version of calcSignatureHash
// put your pkscripts in the sigscript slot before handing the tx to this
// function. Also you're clearly supposed to cache the 3 sub-hashes generated
// here, because they apply to the tx, not a txin. But this doesn't yet.
func calcWitnessSignatureHash(sigscript []byte,
hashType txscript.SigHashType, tx *wire.MsgTx, idx int, amt int64) []byte {
// in the script.go calcSignatureHash(), idx is assumed safe, so I guess
// that's OK here too...? Nah I'm gonna check
if idx > len(tx.TxIn)-1 {
fmt.Printf("calcWitnessSignatureHash error: idx %d but %d txins",
idx, len(tx.TxIn))
return nil
}
// first get hashPrevOuts, hashSequence, and hashOutputs
hashPrevOuts := calcHashPrevOuts(tx, hashType)
hashSequence := calcHashSequence(tx, hashType)
hashOutputs := calcHashOutputs(tx, idx, hashType)
var buf4 [4]byte // buffer for 4-byte stuff
var buf8 [8]byte // buffer for 8-byte stuff
var pre []byte // the pre-image we're generating
binary.LittleEndian.PutUint32(buf4[:], uint32(tx.Version))
pre = append(pre, buf4[:]...)
pre = append(pre, hashPrevOuts.Bytes()...)
pre = append(pre, hashSequence.Bytes()...)
// outpoint being spent
pre = append(pre, tx.TxIn[idx].PreviousOutPoint.Hash.Bytes()...)
binary.LittleEndian.PutUint32(buf4[:], tx.TxIn[idx].PreviousOutPoint.Index)
pre = append(pre, buf4[:]...)
// scriptCode which is some new thing
// detect wpkh mode
fmt.Printf("txin %d sscript len %d\n", idx, len(tx.TxIn[idx].SignatureScript))
if len(sigscript) == 22 && sigscript[0] == 0x00 && sigscript[1] == 0x14 {
// wpkh mode .... recreate op_dup codes here
sCode := []byte{0x19, 0x76, 0xa9, 0x14}
sCode = append(sCode, sigscript[2:22]...)
sCode = append(sCode, []byte{0x88, 0xac}...)
pre = append(pre, sCode...)
} else if len(sigscript) == 34 &&
sigscript[0] == 0x00 && sigscript[1] == 0x20 {
// whs mode- need to remove codeseparators. this doesn't yet.
var buf bytes.Buffer
writeVarBytes(&buf, 0, sigscript)
pre = append(pre, buf.Bytes()...)
} else {
// ?? this is not witness tx! fail
fmt.Printf("Non witness error ")
return nil
}
// amount being signed off
binary.LittleEndian.PutUint64(buf8[:], uint64(amt))
pre = append(pre, buf8[:]...)
// nsequence of input
binary.LittleEndian.PutUint32(buf4[:], tx.TxIn[idx].Sequence)
pre = append(pre, buf4[:]...)
pre = append(pre, hashOutputs.Bytes()...)
// locktime
binary.LittleEndian.PutUint32(buf4[:], tx.LockTime)
pre = append(pre, buf4[:]...)
// hashType... in 4 bytes, instead of 1, because reasons.
binary.LittleEndian.PutUint32(buf4[:], uint32(hashType))
pre = append(pre, buf4[:]...)
fmt.Printf("calcWitnessSignatureHash pre: %x\n", pre)
hsh := wire.DoubleSha256SH(pre)
return hsh.Bytes()
}
// calcHashPrevOuts makes a single hash of all previous outputs in the tx
func calcHashPrevOuts(tx *wire.MsgTx, hType txscript.SigHashType) wire.ShaHash {
// skip this (0x00) for anyonecanpay
if hType == txscript.SigHashAnyOneCanPay {
var empty [32]byte
return empty
}
var pre []byte
for _, in := range tx.TxIn {
// first append 32 byte hash
pre = append(pre, in.PreviousOutPoint.Hash.Bytes()...)
// then make a buffer, put 4 byte index in lil' endian and append that
var buf [4]byte
binary.LittleEndian.PutUint32(buf[:], in.PreviousOutPoint.Index)
pre = append(pre, buf[:]...)
}
fmt.Printf("calcHashPrevOuts pre: %x\n", pre)
return wire.DoubleSha256SH(pre)
}
// calcHashSequence is hash of txins' seq numbers, lil' endian, stuck together
func calcHashSequence(tx *wire.MsgTx, hType txscript.SigHashType) wire.ShaHash {
// skip (0x00) for single, none, anyonecanpay
if hType == txscript.SigHashSingle || hType == txscript.SigHashNone ||
hType == txscript.SigHashAnyOneCanPay {
var empty [32]byte
return empty
}
var pre []byte
for _, in := range tx.TxIn {
var buf [4]byte
binary.LittleEndian.PutUint32(buf[:], in.Sequence)
pre = append(pre, buf[:]...)
}
fmt.Printf("calcHashSequence pre: %x\n", pre)
return wire.DoubleSha256SH(pre)
}
// calcHashOutputs also wants a input index, which it only uses for
// sighash single. If it's not sighash single, just put a 0 or whatever.
func calcHashOutputs(
tx *wire.MsgTx, inIndex int, hType txscript.SigHashType) wire.ShaHash {
if hType == txscript.SigHashNone ||
(hType == txscript.SigHashSingle && inIndex <= len(tx.TxOut)) {
var empty [32]byte
return empty
}
if hType == txscript.SigHashSingle {
var buf bytes.Buffer
writeTxOut(&buf, 0, 0, tx.TxOut[inIndex])
return wire.DoubleSha256SH(buf.Bytes())
}
var pre []byte
for _, out := range tx.TxOut {
var buf bytes.Buffer
writeTxOut(&buf, 0, 0, out)
pre = append(pre, buf.Bytes()...)
}
fmt.Printf("calcHashOutputs pre: %x\n", pre)
return wire.DoubleSha256SH(pre)
}
func main() {
fmt.Printf("sighash 143\n")
// get previous pkscripts for inputs 0 and 1 from hex
in0spk, err := hex.DecodeString(string(inspk0))
if err != nil {
log.Fatal(err)
}
in1spk, err := hex.DecodeString(string(inspk1))
if err != nil {
log.Fatal(err)
}
xpkt, err := hex.DecodeString(string(xpecthash))
if err != nil {
log.Fatal(err)
}
// load tx skeleton from local file
fmt.Printf("loading tx from file tx.hex\n")
txhex, err := ioutil.ReadFile("tx.hex")
if err != nil {
log.Fatal(err)
}
txhex = []byte(strings.TrimSpace(string(txhex)))
txbytes, err := hex.DecodeString(string(txhex))
if err != nil {
log.Fatal(err)
}
fmt.Printf("loaded %d byte tx %x\n", len(txbytes), txbytes)
// make tx
ttx := wire.NewMsgTx()
// deserialize into tx
buf := bytes.NewBuffer(txbytes)
ttx.Deserialize(buf)
fmt.Printf("=====tx locktime: %x\n", ttx.LockTime)
ttx.TxIn[0].SignatureScript = in0spk
// ttx.TxIn[1].SignatureScript = in1spk
fmt.Printf(uspv.TxToString(ttx))
priv, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
log.Fatal(err)
}
// assert flag before writing witness
ttx.Flags = 0x01
ttx.TxIn[1].Witness, err = txscript.WitnessScript(ttx, 1, inamt1, in1spk,
txscript.SigHashAll, priv, true)
// ttx.TxIn[1].SignatureScript = nil
hxh := calcWitnessSignatureHash(in1spk, txscript.SigHashAll, ttx, 1, inamt1)
fmt.Printf("got sigHash %x NON LIB\n", hxh)
fmt.Printf("expect hash %x\n ", xpkt)
fmt.Printf("\n%s\n", uspv.TxToString(ttx))
fmt.Printf("loading tx from file xtx.hex\n")
txhex, err = ioutil.ReadFile("xtx.hex")
if err != nil {
log.Fatal(err)
}
txhex = []byte(strings.TrimSpace(string(txhex)))
txbytes, err = hex.DecodeString(string(txhex))
if err != nil {
log.Fatal(err)
}
// make tx
xtx := wire.NewMsgTx()
// deserialize into tx
buf = bytes.NewBuffer(txbytes)
xtx.Deserialize(buf)
// ttx.Flags = 0x01
fmt.Printf("\n%s\n", uspv.TxToString(xtx))
}
// pver can be 0, doesn't do anything in these. Same for msg.Version
// writeVarInt serializes val to w using a variable number of bytes depending
// on its value.
func writeVarInt(w io.Writer, pver uint32, val uint64) error {
if val < 0xfd {
_, err := w.Write([]byte{uint8(val)})
return err
}
if val <= math.MaxUint16 {
var buf [3]byte
buf[0] = 0xfd
binary.LittleEndian.PutUint16(buf[1:], uint16(val))
_, err := w.Write(buf[:])
return err
}
if val <= math.MaxUint32 {
var buf [5]byte
buf[0] = 0xfe
binary.LittleEndian.PutUint32(buf[1:], uint32(val))
_, err := w.Write(buf[:])
return err
}
var buf [9]byte
buf[0] = 0xff
binary.LittleEndian.PutUint64(buf[1:], val)
_, err := w.Write(buf[:])
return err
}
// writeVarBytes serializes a variable length byte array to w as a varInt
// containing the number of bytes, followed by the bytes themselves.
func writeVarBytes(w io.Writer, pver uint32, bytes []byte) error {
slen := uint64(len(bytes))
err := writeVarInt(w, pver, slen)
if err != nil {
return err
}
_, err = w.Write(bytes)
if err != nil {
return err
}
return nil
}
// writeTxOut encodes to into the bitcoin protocol encoding for a transaction
// output (TxOut) to w.
func writeTxOut(w io.Writer, pver uint32, version int32, to *wire.TxOut) error {
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], uint64(to.Value))
_, err := w.Write(buf[:])
if err != nil {
return err
}
err = writeVarBytes(w, pver, to.PkScript)
if err != nil {
return err
}
return nil
}

@ -15,7 +15,8 @@ const (
headerFileName = "headers.bin"
// version hardcoded for now, probably ok...?
VERSION = 70011
// 70012 is for segnet... make this a init var?
VERSION = 70012
)
type SPVCon struct {
@ -115,7 +116,10 @@ func (s *SPVCon) IngestMerkleBlock(m *wire.MsgMerkleBlock) {
// into our SPV header file
newMerkBlockSha := m.Header.BlockSha()
if !hah.blockhash.IsEqual(&newMerkBlockSha) {
log.Printf("merkle block out of order error")
log.Printf("merkle block out of order got %s expect %s",
m.Header.BlockSha().String(), hah.blockhash.String())
log.Printf("has %d hashes %d txs flags: %x",
len(m.Hashes), m.Transactions, m.Flags)
return
}
@ -280,6 +284,40 @@ func (s *SPVCon) AskForHeaders() error {
return nil
}
// AskForOneBlock is for testing only, so you can ask for a specific block height
// and see what goes wrong
func (s *SPVCon) AskForOneBlock(h int32) error {
var hdr wire.BlockHeader
var err error
dbTip := int32(h)
s.headerMutex.Lock() // seek to header we need
_, err = s.headerFile.Seek(int64((dbTip)*80), os.SEEK_SET)
if err != nil {
return err
}
err = hdr.Deserialize(s.headerFile) // read header, done w/ file for now
s.headerMutex.Unlock() // unlock after reading 1 header
if err != nil {
log.Printf("header deserialize error!\n")
return err
}
bHash := hdr.BlockSha()
// create inventory we're asking for
iv1 := wire.NewInvVect(wire.InvTypeWitnessBlock, &bHash)
gdataMsg := wire.NewMsgGetData()
// add inventory
err = gdataMsg.AddInvVect(iv1)
if err != nil {
return err
}
hah := NewRootAndHeight(bHash, h)
s.outMsgQueue <- gdataMsg
s.blockQueue <- hah // push height and mroot of requested block on queue
return nil
}
// AskForMerkBlocks requests blocks from current to last
// right now this asks for 1 block per getData message.
// Maybe it's faster to ask for many in a each message?
@ -311,7 +349,7 @@ func (s *SPVCon) AskForBlocks() error {
return nil
}
fmt.Printf("will request merkleblocks %d to %d\n", dbTip, headerTip)
fmt.Printf("will request blocks %d to %d\n", dbTip+1, headerTip)
if !s.HardMode { // don't send this in hardmode! that's the whole point
// create initial filter
@ -324,11 +362,12 @@ func (s *SPVCon) AskForBlocks() error {
fmt.Printf("sent filter %x\n", filt.MsgFilterLoad().Filter)
}
// loop through all heights where we want merkleblocks.
for dbTip <= headerTip {
// load header from file
for dbTip < headerTip {
dbTip++ // we're requesting the next header
// load header from file
s.headerMutex.Lock() // seek to header we need
_, err = s.headerFile.Seek(int64((dbTip-1)*80), os.SEEK_SET)
_, err = s.headerFile.Seek(int64((dbTip)*80), os.SEEK_SET)
if err != nil {
return err
}
@ -344,9 +383,9 @@ func (s *SPVCon) AskForBlocks() error {
iv1 := new(wire.InvVect)
// if hardmode, ask for legit blocks, none of this ralphy stuff
if s.HardMode {
iv1 = wire.NewInvVect(wire.InvTypeBlock, &bHash)
iv1 = wire.NewInvVect(wire.InvTypeWitnessBlock, &bHash)
} else { // ah well
iv1 = wire.NewInvVect(wire.InvTypeFilteredBlock, &bHash)
iv1 = wire.NewInvVect(wire.InvTypeFilteredWitnessBlock, &bHash)
}
gdataMsg := wire.NewMsgGetData()
// add inventory
@ -354,14 +393,14 @@ func (s *SPVCon) AskForBlocks() error {
if err != nil {
return err
}
hah := NewRootAndHeight(hdr.BlockSha(), dbTip)
if dbTip == headerTip { // if this is the last block, indicate finality
hah.final = true
}
s.outMsgQueue <- gdataMsg
// waits here most of the time for the queue to empty out
s.blockQueue <- hah // push height and mroot of requested block on queue
dbTip++
s.outMsgQueue <- gdataMsg
}
return nil
}

@ -1,38 +1,156 @@
package uspv
import (
"bytes"
"fmt"
"log"
"os"
"sync"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/bloom"
)
// BlockRootOK checks that all the txs in the block match the merkle root.
// Only checks merkle root; it doesn't look at txs themselves.
func BlockRootOK(blk wire.MsgBlock) bool {
var shas []*wire.ShaHash
for _, tx := range blk.Transactions { // make slice of txids
nSha := tx.TxSha()
shas = append(shas, &nSha)
var (
WitMagicBytes = []byte{0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed}
)
// BlockRootOK checks for block self-consistency.
// If the block has no wintess txs, and no coinbase witness commitment,
// it only checks the tx merkle root. If either a witness commitment or
// any witnesses are detected, it also checks that as well.
// Returns false if anything goes wrong, true if everything is fine.
func BlockOK(blk wire.MsgBlock) bool {
var txids, wtxids []*wire.ShaHash // txids and wtxids
// witMode true if any tx has a wintess OR coinbase has wit commit
var witMode bool
for _, tx := range blk.Transactions { // make slice of (w)/txids
txid := tx.TxSha()
wtxid := tx.WTxSha()
if !witMode && !txid.IsEqual(&wtxid) {
witMode = true
}
neededLen := int(nextPowerOfTwo(uint32(len(shas)))) // kindof ugly
for len(shas) < neededLen {
shas = append(shas, nil) // pad out tx slice to get the full tree base
txids = append(txids, &txid)
wtxids = append(wtxids, &wtxid)
}
for len(shas) > 1 { // calculate merkle root. Terse, eh?
shas = append(shas[2:], MakeMerkleParent(shas[0], shas[1]))
} // auto recognizes coinbase-only blocks
return blk.Header.MerkleRoot.IsEqual(shas[0])
var commitBytes []byte
// try to extract coinbase witness commitment (even if !witMode)
cb := blk.Transactions[0] // get coinbase tx
for i := len(cb.TxOut) - 1; i >= 0; i-- { // start at the last txout
if bytes.HasPrefix(cb.TxOut[i].PkScript, WitMagicBytes) &&
len(cb.TxOut[i].PkScript) > 37 {
// 38 bytes or more, and starts with WitMagicBytes is a hit
commitBytes = cb.TxOut[i].PkScript[6:38]
witMode = true // it there is a wit commit it must be valid
}
}
if witMode { // witmode, so check witness tree
// first find ways witMode can be disqualified
if len(commitBytes) != 32 {
// witness in block but didn't find a wintess commitment; fail
log.Printf("block %s has witness but no witcommit",
blk.BlockSha().String())
return false
}
if len(cb.TxIn) != 1 {
log.Printf("block %s coinbase tx has %d txins (must be 1)",
blk.BlockSha().String(), len(cb.TxIn))
return false
}
if len(cb.TxIn[0].Witness) != 1 {
log.Printf("block %s coinbase has %d witnesses (must be 1)",
blk.BlockSha().String(), len(cb.TxIn[0].Witness))
return false
}
if len(cb.TxIn[0].Witness[0]) != 32 {
log.Printf("block %s coinbase has %d byte witness nonce (not 32)",
blk.BlockSha().String(), len(cb.TxIn[0].Witness[0]))
return false
}
// witness nonce is the cb's witness, subject to above constraints
witNonce, err := wire.NewShaHash(cb.TxIn[0].Witness[0])
if err != nil {
log.Printf("Witness nonce error: %s", err.Error())
return false // not sure why that'd happen but fail
}
var empty [32]byte
wtxids[0].SetBytes(empty[:]) // coinbase wtxid is 0x00...00
// witness root calculated from wtixds
witRoot := calcRoot(wtxids)
calcWitCommit := wire.DoubleSha256SH(
append(witRoot.Bytes(), witNonce.Bytes()...))
// witness root given in coinbase op_return
givenWitCommit, err := wire.NewShaHash(commitBytes)
if err != nil {
log.Printf("Witness root error: %s", err.Error())
return false // not sure why that'd happen but fail
}
// they should be the same. If not, fail.
if !calcWitCommit.IsEqual(givenWitCommit) {
log.Printf("Block %s witRoot error: calc %s given %s",
blk.BlockSha().String(),
calcWitCommit.String(), givenWitCommit.String())
return false
}
}
// got through witMode check so that should be OK;
// check regular txid merkleroot. Which is, like, trivial.
return blk.Header.MerkleRoot.IsEqual(calcRoot(txids))
}
// calcRoot calculates the merkle root of a slice of hashes.
func calcRoot(hashes []*wire.ShaHash) *wire.ShaHash {
for len(hashes) < int(nextPowerOfTwo(uint32(len(hashes)))) {
hashes = append(hashes, nil) // pad out hash slice to get the full base
}
for len(hashes) > 1 { // calculate merkle root. Terse, eh?
hashes = append(hashes[2:], MakeMerkleParent(hashes[0], hashes[1]))
}
return hashes[0]
}
func (ts *TxStore) Refilter() error {
allUtxos, err := ts.GetAllUtxos()
if err != nil {
return err
}
filterElements := uint32(len(allUtxos) + len(ts.Adrs))
ts.localFilter = bloom.NewFilter(filterElements, 0, 0, wire.BloomUpdateAll)
for _, u := range allUtxos {
ts.localFilter.AddOutPoint(&u.Op)
}
for _, a := range ts.Adrs {
ts.localFilter.Add(a.PkhAdr.ScriptAddress())
}
msg := ts.localFilter.MsgFilterLoad()
fmt.Printf("made %d element filter: %x\n", filterElements, msg.Filter)
return nil
}
// IngestBlock is like IngestMerkleBlock but aralphic
// different enough that it's better to have 2 separate functions
func (s *SPVCon) IngestBlock(m *wire.MsgBlock) {
var err error
ok := BlockRootOK(*m) // check block self-consistency
// var buf bytes.Buffer
// m.SerializeWitness(&buf)
// fmt.Printf("block hex %x\n", buf.Bytes())
// for _, tx := range m.Transactions {
// fmt.Printf("wtxid: %s\n", tx.WTxSha())
// fmt.Printf(" txid: %s\n", tx.TxSha())
// fmt.Printf("%d %s", i, TxToString(tx))
// }
ok := BlockOK(*m) // check block self-consistency
if !ok {
fmt.Printf("block %s not OK!!11\n", m.BlockSha().String())
return
@ -53,28 +171,39 @@ func (s *SPVCon) IngestBlock(m *wire.MsgBlock) {
return
}
// iterate through all txs in the block, looking for matches.
// this is slow and can be sped up by doing in-ram filters client side.
// kindof a pain to implement though and it's fast enough for now.
var wg sync.WaitGroup
wg.Add(len(m.Transactions))
for i, tx := range m.Transactions {
fPositive := 0 // local filter false positives
reFilter := 10 // after that many false positives, regenerate filter.
// 10? Making it up. False positives have disk i/o cost, and regenning
// the filter also has costs. With a large local filter, false positives
// should be rare.
go func() {
// iterate through all txs in the block, looking for matches.
// use a local bloom filter to ignore txs that don't affect us
for _, tx := range m.Transactions {
utilTx := btcutil.NewTx(tx)
if s.TS.localFilter.MatchTxAndUpdate(utilTx) {
hits, err := s.TS.Ingest(tx, hah.height)
if err != nil {
log.Printf("Incoming Tx error: %s\n", err.Error())
return
}
if hits > 0 {
log.Printf("block %d tx %d %s ingested and matches %d utxo/adrs.",
hah.height, i, tx.TxSha().String(), hits)
// log.Printf("block %d tx %d %s ingested and matches %d utxo/adrs.",
// hah.height, i, tx.TxSha().String(), hits)
} else {
fPositive++ // matched filter but no hits
}
}
wg.Done()
}()
}
wg.Wait()
if fPositive > reFilter {
fmt.Printf("%d filter false positives in this block\n", fPositive)
err = s.TS.Refilter()
if err != nil {
log.Printf("Refilter error: %s\n", err.Error())
return
}
}
// write to db that we've sync'd to the height indicated in the
// merkle block. This isn't QUITE true since we haven't actually gotten
// the txs yet but if there are problems with the txs we should backtrack.
@ -83,6 +212,7 @@ func (s *SPVCon) IngestBlock(m *wire.MsgBlock) {
log.Printf("full block sync error: %s\n", err.Error())
return
}
fmt.Printf("ingested full block %s height %d OK\n",
m.Header.BlockSha().String(), hah.height)
@ -99,35 +229,3 @@ func (s *SPVCon) IngestBlock(m *wire.MsgBlock) {
}
return
}
func (s *SPVCon) AskForOneBlock(h int32) error {
var hdr wire.BlockHeader
var err error
dbTip := int32(h)
s.headerMutex.Lock() // seek to header we need
_, err = s.headerFile.Seek(int64((dbTip)*80), os.SEEK_SET)
if err != nil {
return err
}
err = hdr.Deserialize(s.headerFile) // read header, done w/ file for now
s.headerMutex.Unlock() // unlock after reading 1 header
if err != nil {
log.Printf("header deserialize error!\n")
return err
}
bHash := hdr.BlockSha()
// create inventory we're asking for
iv1 := wire.NewInvVect(wire.InvTypeBlock, &bHash)
gdataMsg := wire.NewMsgGetData()
// add inventory
err = gdataMsg.AddInvVect(iv1)
if err != nil {
return err
}
s.outMsgQueue <- gdataMsg
return nil
}

@ -49,6 +49,8 @@ func OpenSPV(remoteNode string, hfn, dbfn string,
}
// must set this to enable SPV stuff
myMsgVer.AddService(wire.SFNodeBloom)
// set this to enable segWit
myMsgVer.AddService(wire.SFNodeWitness)
// this actually sends
n, err := wire.WriteMessageN(s.con, myMsgVer, s.localVersion, s.TS.Param.Net)
if err != nil {
@ -89,6 +91,13 @@ func OpenSPV(remoteNode string, hfn, dbfn string,
s.inWaitState = make(chan bool, 1)
go s.fPositiveHandler()
if hard {
err = s.TS.Refilter()
if err != nil {
return s, err
}
}
return s, nil
}

@ -5,6 +5,7 @@ import (
"log"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
func (s *SPVCon) incomingMessageHandler() {
@ -88,10 +89,18 @@ func (s *SPVCon) fPositiveHandler() {
// send filter
s.SendFilter(filt)
fmt.Printf("sent filter %x\n", filt.MsgFilterLoad().Filter)
// clear the channel
for len(s.fPositives) != 0 {
fpAccumulator += <-s.fPositives
finClear:
for {
select {
case x := <-s.fPositives:
fpAccumulator += x
default:
break finClear
}
}
fmt.Printf("reset %d false positives\n", fpAccumulator)
// reset accumulator
fpAccumulator = 0
@ -128,27 +137,29 @@ func (s *SPVCon) TxHandler(m *wire.MsgTx) {
height, ok := s.TS.OKTxids[m.TxSha()]
s.TS.OKMutex.Unlock()
if !ok {
log.Printf("Tx %s unknown, will not ingest\n")
log.Printf("Tx %s unknown, will not ingest\n", m.TxSha().String())
return
}
// check for double spends
allTxs, err := s.TS.GetAllTxs()
if err != nil {
log.Printf("Can't get txs from db: %s", err.Error())
return
}
dubs, err := CheckDoubleSpends(m, allTxs)
if err != nil {
log.Printf("CheckDoubleSpends error: %s", err.Error())
return
}
if len(dubs) > 0 {
for i, dub := range dubs {
fmt.Printf("dub %d known tx %s and new tx %s are exclusive!!!\n",
i, dub.String(), m.TxSha().String())
}
}
// allTxs, err := s.TS.GetAllTxs()
// if err != nil {
// log.Printf("Can't get txs from db: %s", err.Error())
// return
// }
// dubs, err := CheckDoubleSpends(m, allTxs)
// if err != nil {
// log.Printf("CheckDoubleSpends error: %s", err.Error())
// return
// }
// if len(dubs) > 0 {
// for i, dub := range dubs {
// fmt.Printf("dub %d known tx %s and new tx %s are exclusive!!!\n",
// i, dub.String(), m.TxSha().String())
// }
// }
utilTx := btcutil.NewTx(m)
if !s.HardMode || s.TS.localFilter.MatchTxAndUpdate(utilTx) {
hits, err := s.TS.Ingest(m, height)
if err != nil {
log.Printf("Incoming Tx error: %s\n", err.Error())
@ -162,6 +173,7 @@ func (s *SPVCon) TxHandler(m *wire.MsgTx) {
}
log.Printf("tx %s ingested and matches %d utxo/adrs.",
m.TxSha().String(), hits)
}
}
// GetDataHandler responds to requests for tx data, which happen after
@ -172,10 +184,10 @@ func (s *SPVCon) GetDataHandler(m *wire.MsgGetData) {
for i, thing := range m.InvList {
log.Printf("\t%d)%s : %s",
i, thing.Type.String(), thing.Hash.String())
if thing.Type != wire.InvTypeTx { // refuse non-tx reqs
log.Printf("We only respond to tx requests, ignoring")
continue
}
// separate wittx and tx. needed / combine?
// does the same thing right now
if thing.Type == wire.InvTypeWitnessTx {
tx, err := s.TS.GetTx(&thing.Hash)
if err != nil {
log.Printf("error getting tx %s: %s",
@ -183,6 +195,22 @@ func (s *SPVCon) GetDataHandler(m *wire.MsgGetData) {
}
s.outMsgQueue <- tx
sent++
continue
}
if thing.Type == wire.InvTypeTx {
tx, err := s.TS.GetTx(&thing.Hash)
if err != nil {
log.Printf("error getting tx %s: %s",
thing.Hash.String(), err.Error())
}
tx.Flags = 0x00 // dewitnessify
s.outMsgQueue <- tx
sent++
continue
}
// didn't match, so it's not something we're responding to
log.Printf("We only respond to tx requests, ignoring")
}
log.Printf("sent %d of %d requested items", sent, len(m.InvList))
}

@ -4,9 +4,11 @@ import (
"bytes"
"fmt"
"log"
"sort"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/bloom"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcutil/txsort"
@ -41,6 +43,48 @@ func (s *SPVCon) Rebroadcast() {
return
}
// make utxo slices sortable -- same as txsort
type utxoSlice []Utxo
// Sort utxos just like txins -- Len, Less, Swap
func (s utxoSlice) Len() int { return len(s) }
func (s utxoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// outpoint sort; First input hash (reversed / rpc-style), then index.
func (s utxoSlice) Less(i, j int) bool {
// Input hashes are the same, so compare the index.
ihash := s[i].Op.Hash
jhash := s[j].Op.Hash
if ihash == jhash {
return s[i].Op.Index < s[j].Op.Index
}
// At this point, the hashes are not equal, so reverse them to
// big-endian and return the result of the comparison.
const hashSize = wire.HashSize
for b := 0; b < hashSize/2; b++ {
ihash[b], ihash[hashSize-1-b] = ihash[hashSize-1-b], ihash[b]
jhash[b], jhash[hashSize-1-b] = jhash[hashSize-1-b], jhash[b]
}
return bytes.Compare(ihash[:], jhash[:]) == -1
}
type SortableUtxoSlice []Utxo
// utxoByAmts get sorted by utxo value. also put unconfirmed last
func (s SortableUtxoSlice) Len() int { return len(s) }
func (s SortableUtxoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// height 0 means your lesser
func (s SortableUtxoSlice) Less(i, j int) bool {
if s[i].AtHeight == 0 && s[j].AtHeight > 0 {
return true
}
if s[j].AtHeight == 0 && s[i].AtHeight > 0 {
return false
}
return s[i].Value < s[j].Value
}
func (s *SPVCon) NewOutgoingTx(tx *wire.MsgTx) error {
txid := tx.TxSha()
// assign height of zero for txs we create
@ -53,7 +97,7 @@ func (s *SPVCon) NewOutgoingTx(tx *wire.MsgTx) error {
return err
}
// make an inv message instead of a tx message to be polite
iv1 := wire.NewInvVect(wire.InvTypeTx, &txid)
iv1 := wire.NewInvVect(wire.InvTypeWitnessTx, &txid)
invMsg := wire.NewMsgInv()
err = invMsg.AddInvVect(iv1)
if err != nil {
@ -63,6 +107,313 @@ func (s *SPVCon) NewOutgoingTx(tx *wire.MsgTx) error {
return nil
}
func (s *SPVCon) SendOne(u Utxo, adr btcutil.Address) error {
// fixed fee
fee := int64(5000)
sendAmt := u.Value - fee
tx := wire.NewMsgTx() // make new tx
// add single output
outAdrScript, err := txscript.PayToAddrScript(adr)
if err != nil {
return err
}
// make user specified txout and add to tx
txout := wire.NewTxOut(sendAmt, outAdrScript)
tx.AddTxOut(txout)
var prevPKs []byte
if u.IsWit {
tx.Flags = 0x01
wa, err := btcutil.NewAddressWitnessPubKeyHash(
s.TS.Adrs[u.KeyIdx].PkhAdr.ScriptAddress(), s.TS.Param)
prevPKs, err = txscript.PayToAddrScript(wa)
if err != nil {
return err
}
} else { // otherwise generate directly
prevPKs, err = txscript.PayToAddrScript(
s.TS.Adrs[u.KeyIdx].PkhAdr)
if err != nil {
return err
}
}
tx.AddTxIn(wire.NewTxIn(&u.Op, prevPKs, nil))
var sig []byte
var wit [][]byte
hCache := txscript.CalcHashCache(tx, 0, txscript.SigHashAll)
child, err := s.TS.rootPrivKey.Child(u.KeyIdx + hdkeychain.HardenedKeyStart)
if err != nil {
return err
}
priv, err := child.ECPrivKey()
if err != nil {
return err
}
// This is where witness based sighash types need to happen
// sign into stash
if u.IsWit {
wit, err = txscript.WitnessScript(
tx, hCache, 0, u.Value, tx.TxIn[0].SignatureScript,
txscript.SigHashAll, priv, true)
if err != nil {
return err
}
} else {
sig, err = txscript.SignatureScript(
tx, 0, tx.TxIn[0].SignatureScript,
txscript.SigHashAll, priv, true)
if err != nil {
return err
}
}
// swap sigs into sigScripts in txins
if sig != nil {
tx.TxIn[0].SignatureScript = sig
}
if wit != nil {
tx.TxIn[0].Witness = wit
tx.TxIn[0].SignatureScript = nil
}
return s.NewOutgoingTx(tx)
}
// SendCoins does send coins, but it's very rudimentary
// wit makes it into p2wpkh. Which is not yet spendable.
func (s *SPVCon) SendCoins(adrs []btcutil.Address, sendAmts []int64) error {
if len(adrs) != len(sendAmts) {
return fmt.Errorf("%d addresses and %d amounts", len(adrs), len(sendAmts))
}
var err error
var score, totalSend, fee int64
dustCutoff := int64(20000) // below this amount, just give to miners
satPerByte := int64(80) // satoshis per byte fee; have as arg later
rawUtxos, err := s.TS.GetAllUtxos()
if err != nil {
return err
}
var allUtxos SortableUtxoSlice
// start with utxos sorted by value.
for _, utxo := range rawUtxos {
score += utxo.Value
allUtxos = append(allUtxos, *utxo)
}
// smallest and unconfirmed last (because it's reversed)
sort.Sort(sort.Reverse(allUtxos))
// sort.Reverse(allUtxos)
for _, amt := range sendAmts {
totalSend += amt
}
// important rule in bitcoin, output total > input total is invalid.
if totalSend > score {
return fmt.Errorf("trying to send %d but %d available.",
totalSend, score)
}
tx := wire.NewMsgTx() // make new tx
// add non-change (arg) outputs
for i, adr := range adrs {
// make address script 76a914...88ac or 0014...
outAdrScript, err := txscript.PayToAddrScript(adr)
if err != nil {
return err
}
// make user specified txout and add to tx
txout := wire.NewTxOut(sendAmts[i], outAdrScript)
tx.AddTxOut(txout)
}
// generate a utxo slice for your inputs
var ins utxoSlice
// add utxos until we've had enough
nokori := totalSend // nokori is how much is needed on input side
for _, utxo := range allUtxos {
// skip unconfirmed. Or de-prioritize?
// if utxo.AtHeight == 0 {
// continue
// }
// yeah, lets add this utxo!
ins = append(ins, utxo)
// as we add utxos, fill in sigscripts
// generate previous pkscripts (subscritpt?) for all utxos
// then make txins with the utxo and prevpk, and insert them into the tx
// these are all zeroed out during signing but it's an easy way to keep track
var prevPKs []byte
if utxo.IsWit {
tx.Flags = 0x01
wa, err := btcutil.NewAddressWitnessPubKeyHash(
s.TS.Adrs[utxo.KeyIdx].PkhAdr.ScriptAddress(), s.TS.Param)
prevPKs, err = txscript.PayToAddrScript(wa)
if err != nil {
return err
}
} else { // otherwise generate directly
prevPKs, err = txscript.PayToAddrScript(
s.TS.Adrs[utxo.KeyIdx].PkhAdr)
if err != nil {
return err
}
}
tx.AddTxIn(wire.NewTxIn(&utxo.Op, prevPKs, nil))
nokori -= utxo.Value
// if nokori is positive, don't bother checking fee yet.
if nokori < 0 {
fee = EstFee(tx, satPerByte)
if nokori < -fee { // done adding utxos: nokori below negative est. fee
break
}
}
}
// see if there's enough left to also add a change output
changeOld, err := s.TS.NewAdr() // change is witnessy
if err != nil {
return err
}
changeAdr, err := btcutil.NewAddressWitnessPubKeyHash(
changeOld.ScriptAddress(), s.TS.Param)
if err != nil {
return err
}
changeScript, err := txscript.PayToAddrScript(changeAdr)
if err != nil {
return err
}
changeOut := wire.NewTxOut(0, changeScript)
tx.AddTxOut(changeOut)
fee = EstFee(tx, satPerByte)
changeOut.Value = -(nokori + fee)
if changeOut.Value < dustCutoff {
// remove last output (change) : not worth it
tx.TxOut = tx.TxOut[:len(tx.TxOut)-1]
}
// sort utxos on the input side. use this instead of txsort
// because we want to remember which keys are associated with which inputs
sort.Sort(ins)
// sort tx -- this only will change txouts since inputs are already sorted
txsort.InPlaceSort(tx)
// tx is ready for signing,
sigStash := make([][]byte, len(ins))
witStash := make([][][]byte, len(ins))
// generate tx-wide hashCache for segwit stuff
// middle index number doesn't matter for sighashAll.
hCache := txscript.CalcHashCache(tx, 0, txscript.SigHashAll)
for i, txin := range tx.TxIn {
// pick key
child, err := s.TS.rootPrivKey.Child(
ins[i].KeyIdx + hdkeychain.HardenedKeyStart)
if err != nil {
return err
}
priv, err := child.ECPrivKey()
if err != nil {
return err
}
// This is where witness based sighash types need to happen
// sign into stash
if ins[i].IsWit {
witStash[i], err = txscript.WitnessScript(
tx, hCache, i, ins[i].Value, txin.SignatureScript,
txscript.SigHashAll, priv, true)
if err != nil {
return err
}
} else {
sigStash[i], err = txscript.SignatureScript(
tx, i, txin.SignatureScript,
txscript.SigHashAll, priv, true)
if err != nil {
return err
}
}
}
// swap sigs into sigScripts in txins
for i, txin := range tx.TxIn {
if sigStash[i] != nil {
txin.SignatureScript = sigStash[i]
}
if witStash[i] != nil {
txin.Witness = witStash[i]
txin.SignatureScript = nil
}
}
// fmt.Printf("tx: %s", TxToString(tx))
// buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
// send it out on the wire. hope it gets there.
// we should deal with rejects. Don't yet.
err = s.NewOutgoingTx(tx)
if err != nil {
return err
}
return nil
}
// EstFee gives a fee estimate based on a tx and a sat/Byte target.
// The TX should have all outputs, including the change address already
// populated (with potentially 0 amount. Also it should have all inputs
// populated, but inputs don't need to have sigscripts or witnesses
// (it'll guess the sizes of sigs/wits that arent' filled in).
func EstFee(otx *wire.MsgTx, spB int64) int64 {
mtsig := make([]byte, 72)
mtpub := make([]byte, 33)
tx := otx.Copy()
// iterate through txins, replacing subscript sigscripts with noise
// sigs or witnesses
for _, txin := range tx.TxIn {
// check wpkh
if len(txin.SignatureScript) == 22 &&
txin.SignatureScript[0] == 0x00 && txin.SignatureScript[1] == 0x14 {
txin.SignatureScript = nil
txin.Witness = make([][]byte, 2)
txin.Witness[0] = mtsig
txin.Witness[1] = mtpub
} else if len(txin.SignatureScript) == 34 &&
txin.SignatureScript[0] == 0x00 && txin.SignatureScript[1] == 0x20 {
// p2wsh -- sig lenght is a total guess!
txin.SignatureScript = nil
txin.Witness = make([][]byte, 3)
// 3 sigs? totally guessing here
txin.Witness[0] = mtsig
txin.Witness[1] = mtsig
txin.Witness[2] = mtsig
} else {
// assume everything else is p2pkh. Even though it's not
txin.Witness = nil
txin.SignatureScript = make([]byte, 105) // len of p2pkh sigscript
}
}
fmt.Printf(TxToString(tx))
size := int64(tx.VirtualSize())
fmt.Printf("%d spB, est vsize %d, fee %d\n", spB, size, size*spB)
return size * spB
}
// SignThis isn't used anymore...
func (t *TxStore) SignThis(tx *wire.MsgTx) error {
fmt.Printf("-= SignThis =-\n")

@ -23,6 +23,8 @@ type TxStore struct {
Adrs []MyAdr // endeavouring to acquire capital
StateDB *bolt.DB // place to write all this down
localFilter *bloom.Filter // local bloom filter for hard mode
// Params live here, not SCon
Param *chaincfg.Params // network parameters (testnet3, testnetL)
@ -39,6 +41,7 @@ type Utxo struct { // cash money.
Value int64 // higher is better
// IsCoinbase bool // can't spend for a while
IsWit bool // true if p2wpkh output
}
// Stxo is a utxo that has moved on.
@ -78,7 +81,7 @@ func (t *TxStore) AddTxid(txid *wire.ShaHash, height int32) error {
// ... or I'm gonna fade away
func (t *TxStore) GimmeFilter() (*bloom.Filter, error) {
if len(t.Adrs) == 0 {
return nil, fmt.Errorf("no addresses to filter for")
return nil, fmt.Errorf("no address to filter for")
}
// get all utxos to add outpoints to filter
@ -89,7 +92,10 @@ func (t *TxStore) GimmeFilter() (*bloom.Filter, error) {
elem := uint32(len(t.Adrs) + len(allUtxos))
f := bloom.NewFilter(elem, 0, 0.000001, wire.BloomUpdateAll)
for _, a := range t.Adrs {
// note there could be false positives since we're just looking
// for the 20 byte PKH without the opcodes.
for _, a := range t.Adrs { // add 20-byte pubkeyhash
f.Add(a.PkhAdr.ScriptAddress())
}
@ -133,14 +139,19 @@ func CheckDoubleSpends(
// TxToString prints out some info about a transaction. for testing / debugging
func TxToString(tx *wire.MsgTx) string {
str := fmt.Sprintf("\t - Tx %s\n", tx.TxSha().String())
str := fmt.Sprintf("size %d vsize %d wsize %d locktime %d flag %x txid %s\n",
tx.SerializeSize(), tx.VirtualSize(), tx.SerializeSizeWitness(),
tx.LockTime, tx.Flags, tx.TxSha().String())
for i, in := range tx.TxIn {
str += fmt.Sprintf("Input %d: %s\n", i, in.PreviousOutPoint.String())
str += fmt.Sprintf("SigScript for input %d: %x\n", i, in.SignatureScript)
str += fmt.Sprintf("Input %d spends %s\n", i, in.PreviousOutPoint.String())
str += fmt.Sprintf("\tSigScript: %x\n", in.SignatureScript)
for j, wit := range in.Witness {
str += fmt.Sprintf("\twitness %d: %x\n", j, wit)
}
}
for i, out := range tx.TxOut {
if out != nil {
str += fmt.Sprintf("\toutput %d script: %x amt: %d\n",
str += fmt.Sprintf("output %d script: %x amt: %d\n",
i, out.PkScript, out.Value)
} else {
str += fmt.Sprintf("output %d nil (WARNING)\n", i)
@ -175,6 +186,20 @@ func outPointToBytes(op *wire.OutPoint) ([]byte, error) {
return buf.Bytes(), nil
}
/*----- serialization for utxos ------- */
/* Utxos serialization:
byte length desc at offset
32 txid 0
4 idx 32
4 height 36
4 keyidx 40
8 amt 44
1 flag 52
end len 53
*/
// ToBytes turns a Utxo into some bytes.
// note that the txid is the first 36 bytes and in our use cases will be stripped
// off, but is left here for other applications
@ -205,6 +230,16 @@ func (u *Utxo) ToBytes() ([]byte, error) {
if err != nil {
return nil, err
}
// last byte indicates tx witness flags ( tx[5] from serialized tx)
// write a 1 at the end for p2wpkh (same as flags byte)
witByte := byte(0x00)
if u.IsWit {
witByte = 0x01
}
err = buf.WriteByte(witByte)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
@ -218,8 +253,8 @@ func UtxoFromBytes(b []byte) (Utxo, error) {
return u, fmt.Errorf("nil input slice")
}
buf := bytes.NewBuffer(b)
if buf.Len() < 52 { // utxos are 52 bytes
return u, fmt.Errorf("Got %d bytes for utxo, expect 52", buf.Len())
if buf.Len() < 53 { // utxos are 53 bytes
return u, fmt.Errorf("Got %d bytes for utxo, expect 53", buf.Len())
}
// read 32 byte txid
err := u.Op.Hash.SetBytes(buf.Next(32))
@ -246,38 +281,44 @@ func UtxoFromBytes(b []byte) (Utxo, error) {
if err != nil {
return u, err
}
// read 1 byte witness flags
witByte, err := buf.ReadByte()
if err != nil {
return u, err
}
if witByte != 0x00 {
u.IsWit = true
}
return u, nil
}
/*----- serialization for stxos ------- */
/* Stxo serialization:
byte length desc at offset
53 utxo 0
4 sheight 53
32 stxid 57
end len 89
*/
// ToBytes turns an Stxo into some bytes.
// outpoint txid, outpoint idx, height, key idx, amt, spendheight, spendtxid
// prevUtxo serialization, then spendheight [4], spendtxid [32]
func (s *Stxo) ToBytes() ([]byte, error) {
var buf bytes.Buffer
// write 32 byte txid of the utxo
_, err := buf.Write(s.Op.Hash.Bytes())
// first serialize the utxo part
uBytes, err := s.Utxo.ToBytes()
if err != nil {
return nil, err
}
// write 4 byte outpoint index within the tx to spend
err = binary.Write(&buf, binary.BigEndian, s.Op.Index)
if err != nil {
return nil, err
}
// write 4 byte height of utxo
err = binary.Write(&buf, binary.BigEndian, s.AtHeight)
if err != nil {
return nil, err
}
// write 4 byte key index of utxo
err = binary.Write(&buf, binary.BigEndian, s.KeyIdx)
if err != nil {
return nil, err
}
// write 8 byte amount of money at the utxo
err = binary.Write(&buf, binary.BigEndian, s.Value)
// write that into the buffer first
_, err = buf.Write(uBytes)
if err != nil {
return nil, err
}
// write 4 byte height where the txo was spent
err = binary.Write(&buf, binary.BigEndian, s.SpendHeight)
if err != nil {
@ -292,40 +333,21 @@ func (s *Stxo) ToBytes() ([]byte, error) {
}
// StxoFromBytes turns bytes into a Stxo.
// first take the first 53 bytes as a utxo, then the next 36 for how it's spent.
func StxoFromBytes(b []byte) (Stxo, error) {
var s Stxo
if b == nil {
return s, fmt.Errorf("nil input slice")
if len(b) < 89 {
return s, fmt.Errorf("Got %d bytes for stxo, expect 89", len(b))
}
buf := bytes.NewBuffer(b)
if buf.Len() < 88 { // stxos are 88 bytes
return s, fmt.Errorf("Got %d bytes for stxo, expect 88", buf.Len())
}
// read 32 byte txid
err := s.Op.Hash.SetBytes(buf.Next(32))
if err != nil {
return s, err
}
// read 4 byte outpoint index within the tx to spend
err = binary.Read(buf, binary.BigEndian, &s.Op.Index)
if err != nil {
return s, err
}
// read 4 byte height of utxo
err = binary.Read(buf, binary.BigEndian, &s.AtHeight)
if err != nil {
return s, err
}
// read 4 byte key index of utxo
err = binary.Read(buf, binary.BigEndian, &s.KeyIdx)
if err != nil {
return s, err
}
// read 8 byte amount of money at the utxo
err = binary.Read(buf, binary.BigEndian, &s.Value)
u, err := UtxoFromBytes(b[:53])
if err != nil {
return s, err
}
s.Utxo = u // assign the utxo
buf := bytes.NewBuffer(b[53:]) // make buffer for spend data
// read 4 byte spend height
err = binary.Read(buf, binary.BigEndian, &s.SpendHeight)
if err != nil {

@ -22,7 +22,7 @@ var (
BKTTxns = []byte("Txns") // all txs we care about, for replays
BKTState = []byte("MiscState") // last state of DB
// these are in the state bucket
KEYNumKeys = []byte("NumKeys") // number of keys used
KEYNumKeys = []byte("NumKeys") // number of p2pkh keys used
KEYTipHeight = []byte("TipHeight") // height synced to
)
@ -82,17 +82,22 @@ func (ts *TxStore) OpenDB(filename string) error {
// NewAdr creates a new, never before seen address, and increments the
// DB counter as well as putting it in the ram Adrs store, and returns it
func (ts *TxStore) NewAdr() (*btcutil.AddressPubKeyHash, error) {
func (ts *TxStore) NewAdr() (btcutil.Address, error) {
if ts.Param == nil {
return nil, fmt.Errorf("nil param")
return nil, fmt.Errorf("NewAdr error: nil param")
}
n := uint32(len(ts.Adrs))
priv, err := ts.rootPrivKey.Child(n + hdkeychain.HardenedKeyStart)
priv := new(hdkeychain.ExtendedKey)
var err error
var n uint32
var nAdr btcutil.Address
n = uint32(len(ts.Adrs))
priv, err = ts.rootPrivKey.Child(n + hdkeychain.HardenedKeyStart)
if err != nil {
return nil, err
}
newAdr, err := priv.Address(ts.Param)
nAdr, err = priv.Address(ts.Param)
if err != nil {
return nil, err
}
@ -107,18 +112,21 @@ func (ts *TxStore) NewAdr() (*btcutil.AddressPubKeyHash, error) {
// write to db file
err = ts.StateDB.Update(func(btx *bolt.Tx) error {
sta := btx.Bucket(BKTState)
return sta.Put(KEYNumKeys, buf.Bytes())
})
if err != nil {
return nil, err
}
// add in to ram.
var ma MyAdr
ma.PkhAdr = newAdr
ma.PkhAdr = nAdr
ma.KeyIdx = n
ts.Adrs = append(ts.Adrs, ma)
return newAdr, nil
ts.Adrs = append(ts.Adrs, ma)
ts.localFilter.Add(ma.PkhAdr.ScriptAddress())
return nAdr, nil
}
// SetDBSyncHeight sets sync height of the db, indicated the latest block
@ -345,7 +353,6 @@ func (ts *TxStore) PopulateAdrs(lastKey uint32) error {
func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) {
var hits uint32
var err error
var spentOPs [][]byte
var nUtxoBytes [][]byte
// tx has been OK'd by SPV; check tx sanity
@ -358,30 +365,57 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) {
// note that you can't check signatures; this is SPV.
// 0 conf SPV means pretty much nothing. Anyone can say anything.
spentOPs := make([][]byte, len(tx.TxIn))
// before entering into db, serialize all inputs of the ingested tx
for _, txin := range tx.TxIn {
nOP, err := outPointToBytes(&txin.PreviousOutPoint)
for i, txin := range tx.TxIn {
spentOPs[i], err = outPointToBytes(&txin.PreviousOutPoint)
if err != nil {
return hits, err
}
spentOPs = append(spentOPs, nOP)
}
// also generate PKscripts for all addresses (maybe keep storing these?)
for _, adr := range ts.Adrs {
// go through txouts, and then go through addresses to match
// generate PKscripts for all addresses
wPKscripts := make([][]byte, len(ts.Adrs))
aPKscripts := make([][]byte, len(ts.Adrs))
for i, _ := range ts.Adrs {
// iterate through all our addresses
aPKscript, err := txscript.PayToAddrScript(adr.PkhAdr)
// convert regular address to witness address. (split adrs later)
wa, err := btcutil.NewAddressWitnessPubKeyHash(
ts.Adrs[i].PkhAdr.ScriptAddress(), ts.Param)
if err != nil {
return hits, err
}
// iterate through all outputs of this tx
wPKscripts[i], err = txscript.PayToAddrScript(wa)
if err != nil {
return hits, err
}
aPKscripts[i], err = txscript.PayToAddrScript(ts.Adrs[i].PkhAdr)
if err != nil {
return hits, err
}
}
cachedSha := tx.TxSha()
// iterate through all outputs of this tx, see if we gain
for i, out := range tx.TxOut {
if bytes.Equal(out.PkScript, aPKscript) { // new utxo for us
var newu Utxo
for j, ascr := range aPKscripts {
// detect p2wpkh
witBool := false
if bytes.Equal(out.PkScript, wPKscripts[j]) {
witBool = true
}
if bytes.Equal(out.PkScript, ascr) || witBool { // new utxo found
var newu Utxo // create new utxo and copy into it
newu.AtHeight = height
newu.KeyIdx = adr.KeyIdx
newu.KeyIdx = ts.Adrs[j].KeyIdx
newu.Value = out.Value
newu.IsWit = witBool // copy witness version from pkscript
var newop wire.OutPoint
newop.Hash = tx.TxSha()
newop.Hash = cachedSha
newop.Index = uint32(i)
newu.Op = newop
b, err := newu.ToBytes()
@ -390,7 +424,7 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) {
}
nUtxoBytes = append(nUtxoBytes, b)
hits++
break // only one match
break // txos can match only 1 script
}
}
}
@ -405,53 +439,43 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) {
return fmt.Errorf("error: db not initialized")
}
// first see if we lose utxos
// iterate through duffel bag and look for matches
// this makes us lose money, which is regrettable, but we need to know.
for _, nOP := range spentOPs {
duf.ForEach(func(k, v []byte) error {
if bytes.Equal(k, nOP) { // matched, we lost utxo
v := duf.Get(nOP)
if v != nil {
hits++
// do all this just to figure out value we lost
x := make([]byte, len(k)+len(v))
copy(x, k)
copy(x[len(k):], v)
x := make([]byte, len(nOP)+len(v))
copy(x, nOP)
copy(x[len(nOP):], v)
lostTxo, err := UtxoFromBytes(x)
if err != nil {
return err
}
hits++
// then delete the utxo from duf, save to old
err = duf.Delete(k)
if err != nil {
return err
}
// after deletion, save stxo to old bucket
// after marking for deletion, save stxo to old bucket
var st Stxo // generate spent txo
st.Utxo = lostTxo // assign outpoint
st.SpendHeight = height // spent at height
st.SpendTxid = tx.TxSha() // spent by txid
st.SpendTxid = cachedSha // spent by txid
stxb, err := st.ToBytes() // serialize
if err != nil {
return err
}
err = old.Put(k, stxb) // write k:v outpoint:stxo bytes
if err != nil {
return err
}
// store this relevant tx
sha := tx.TxSha()
var buf bytes.Buffer
tx.Serialize(&buf)
err = txns.Put(sha.Bytes(), buf.Bytes())
err = old.Put(nOP, stxb) // write nOP:v outpoint:stxo bytes
if err != nil {
return err
}
return nil // matched utxo k, won't match another
err = duf.Delete(nOP)
if err != nil {
return err
}
return nil // no match
})
} // done losing utxos, next gain utxos
}
}
// done losing utxos, next gain utxos
// next add all new utxos to db, this is quick as the work is above
for _, ub := range nUtxoBytes {
err = duf.Put(ub[:36], ub[36:])
@ -459,6 +483,15 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) {
return err
}
}
// if hits is nonzero it's a relevant tx and we should store it
var buf bytes.Buffer
tx.SerializeWitness(&buf) // always store witness version
err = txns.Put(cachedSha.Bytes(), buf.Bytes())
if err != nil {
return err
}
return nil
})
return hits, err