284 lines
7.0 KiB
Go
284 lines
7.0 KiB
Go
package uspv
|
|
|
|
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"
|
|
)
|
|
|
|
func (s *SPVCon) PongBack(nonce uint64) {
|
|
mpong := wire.NewMsgPong(nonce)
|
|
|
|
s.outMsgQueue <- mpong
|
|
return
|
|
}
|
|
|
|
func (s *SPVCon) SendFilter(f *bloom.Filter) {
|
|
s.outMsgQueue <- f.MsgFilterLoad()
|
|
|
|
return
|
|
}
|
|
|
|
// Rebroadcast sends an inv message of all the unconfirmed txs the db is
|
|
// aware of. This is called after every sync. Only txids so hopefully not
|
|
// too annoying for nodes.
|
|
func (s *SPVCon) Rebroadcast() {
|
|
// get all unconfirmed txs
|
|
invMsg, err := s.TS.GetPendingInv()
|
|
if err != nil {
|
|
log.Printf("Rebroadcast error: %s", err.Error())
|
|
}
|
|
if len(invMsg.InvList) == 0 { // nothing to broadcast, so don't
|
|
return
|
|
}
|
|
s.outMsgQueue <- invMsg
|
|
return
|
|
}
|
|
|
|
// make utxo slices sortable
|
|
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
|
|
}
|
|
|
|
func (s *SPVCon) NewOutgoingTx(tx *wire.MsgTx) error {
|
|
txid := tx.TxSha()
|
|
// assign height of zero for txs we create
|
|
err := s.TS.AddTxid(&txid, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = s.TS.Ingest(tx, 0) // our own tx; don't keep track of false positives
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// make an inv message instead of a tx message to be polite
|
|
iv1 := wire.NewInvVect(wire.InvTypeWitnessTx, &txid)
|
|
invMsg := wire.NewMsgInv()
|
|
err = invMsg.AddInvVect(iv1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.outMsgQueue <- invMsg
|
|
return nil
|
|
}
|
|
|
|
// SendCoins does send coins, but it's very rudimentary
|
|
// wit makes it into p2wpkh. Which is not yet spendable.
|
|
func (s *SPVCon) SendCoins(adr btcutil.Address, sendAmt int64) error {
|
|
|
|
var err error
|
|
var score int64
|
|
allUtxos, err := s.TS.GetAllUtxos()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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 or 0014...
|
|
outAdrScript, err := txscript.PayToAddrScript(adr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
////////////////////////////
|
|
|
|
// generate a utxo slice for your inputs
|
|
var ins utxoSlice
|
|
|
|
// add utxos until we've had enough
|
|
nokori := sendAmt // nokori is how much is needed on input side
|
|
for _, utxo := range allUtxos {
|
|
// yeah, lets add this utxo!
|
|
ins = append(ins, *utxo)
|
|
nokori -= utxo.Value
|
|
if nokori < -10000 { // minimum overage / fee is 10K now
|
|
break
|
|
}
|
|
}
|
|
|
|
// sort utxos on the input side
|
|
sort.Sort(ins)
|
|
|
|
// make user specified txout and add to tx
|
|
txout := wire.NewTxOut(sendAmt, outAdrScript)
|
|
tx.AddTxOut(txout)
|
|
// see if there's enough left to also add a change output
|
|
if nokori < -200000 {
|
|
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((-100000)-nokori, changeScript)
|
|
tx.AddTxOut(changeOut)
|
|
}
|
|
|
|
// generate previous pkscripts for all the (now sorted) utxos
|
|
// then make txins with the utxo and prevpk, and insert them into the tx
|
|
for _, in := range ins {
|
|
var prevPKs []byte
|
|
|
|
// if wit utxo, convert address to generate pkscript
|
|
if in.IsWit {
|
|
wa, err := btcutil.NewAddressWitnessPubKeyHash(
|
|
s.TS.Adrs[in.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[in.KeyIdx].PkhAdr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
tx.AddTxIn(wire.NewTxIn(&in.Op, prevPKs, nil))
|
|
}
|
|
// 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))
|
|
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 {
|
|
sigStash[i], err = txscript.WitnessSignatureScript(
|
|
tx, 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 {
|
|
txin.SignatureScript = sigStash[i]
|
|
}
|
|
|
|
fmt.Printf("tx: %s", 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)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SignThis isn't used anymore...
|
|
func (t *TxStore) SignThis(tx *wire.MsgTx) error {
|
|
fmt.Printf("-= SignThis =-\n")
|
|
|
|
// sort tx before signing.
|
|
txsort.InPlaceSort(tx)
|
|
|
|
sigs := make([][]byte, len(tx.TxIn))
|
|
// first iterate over each input
|
|
for j, in := range tx.TxIn {
|
|
for k := uint32(0); k < uint32(len(t.Adrs)); k++ {
|
|
child, err := t.rootPrivKey.Child(k + hdkeychain.HardenedKeyStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
myadr, err := child.Address(t.Param)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
adrScript, err := txscript.PayToAddrScript(myadr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bytes.Equal(adrScript, in.SignatureScript) {
|
|
fmt.Printf("Hit; key %d matches input %d. Signing.\n", k, j)
|
|
priv, err := child.ECPrivKey()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sigs[j], err = txscript.SignatureScript(
|
|
tx, j, in.SignatureScript, txscript.SigHashAll, priv, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
for i, s := range sigs {
|
|
if s != nil {
|
|
tx.TxIn[i].SignatureScript = s
|
|
}
|
|
}
|
|
return nil
|
|
}
|