d8e62f6898
all adrs can be converted on the fly and displayed both ways. in the actual UI this shouldn't happen though. Also utxos are sorted and signed properly. utxo selection is still pretty low tech.
273 lines
6.7 KiB
Go
273 lines
6.7 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
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|