From 66c62b87e464f0567e5c4c281b1eb371501c39f7 Mon Sep 17 00:00:00 2001 From: Mark Smith Date: Mon, 10 May 2021 12:03:53 +0100 Subject: [PATCH 1/8] encapsulating some of the fields in tx and input that can cause issue if accessed directly by someone that doesn'tknow what they're doing. Added validation for adding PrevTXId and removed the bytes suffix. Adding some helpers for getting Outputs and Inputs as well as getting by index. --- input.go | 25 ++++++-- localsigner.go | 2 +- localsigner_test.go | 4 +- signaturehash.go | 20 +++--- signaturehash_test.go | 8 +-- tx.go | 83 +++++++++++++++++++----- tx_test.go | 144 ++++++++++++++++++++++++++++++++++++++++-- txchange.go | 12 ++-- txchange_test.go | 18 +++--- txinput.go | 25 ++++---- txinput_test.go | 4 +- txoutput.go | 6 +- txoutput_test.go | 12 ++-- txsign.go | 6 +- 14 files changed, 283 insertions(+), 86 deletions(-) diff --git a/input.go b/input.go index d749fc3a..91e9a6c8 100644 --- a/input.go +++ b/input.go @@ -27,7 +27,7 @@ const DefaultSequenceNumber uint32 = 0xFFFFFFFF // DO NOT CHANGE ORDER - Optimized for memory via maligned // type Input struct { - PreviousTxIDBytes []byte + previousTxID []byte PreviousTxSatoshis uint64 PreviousTxScript *bscript.Script UnlockingScript *bscript.Script @@ -35,11 +35,28 @@ type Input struct { SequenceNumber uint32 } +// PreviousTxIDAdd will add the supplied txID bytes to the Input, +// if it isn't a valid transaction id an ErrInvalidTxID error will be returned. +func (i *Input) PreviousTxIDAdd(txID []byte) error { + if !IsValidTxID(txID) { + return ErrInvalidTxID + } + i.previousTxID = txID + return nil +} + +// PreviousTxID will return the PreviousTxID if set. +func (i *Input) PreviousTxID() []byte { + return i.previousTxID +} + // PreviousTxIDStr returns the Previous TxID as a hex string. func (i *Input) PreviousTxIDStr() string { - return hex.EncodeToString(i.PreviousTxIDBytes) + return hex.EncodeToString(i.previousTxID) } +// String implements the Stringer interface and returns a string +// representation of a transaction input. func (i *Input) String() string { return fmt.Sprintf( `prevTxHash: %s @@ -48,7 +65,7 @@ scriptLen: %d script: %x sequence: %x `, - hex.EncodeToString(i.PreviousTxIDBytes), + hex.EncodeToString(i.previousTxID), i.PreviousTxOutIndex, len(*i.UnlockingScript), i.UnlockingScript, @@ -60,7 +77,7 @@ sequence: %x func (i *Input) ToBytes(clear bool) []byte { h := make([]byte, 0) - h = append(h, ReverseBytes(i.PreviousTxIDBytes)...) + h = append(h, ReverseBytes(i.previousTxID)...) h = append(h, LittleEndianBytes(i.PreviousTxOutIndex, 4)...) if clear { h = append(h, 0x00) diff --git a/localsigner.go b/localsigner.go index caaf8306..fa3f3264 100644 --- a/localsigner.go +++ b/localsigner.go @@ -6,7 +6,7 @@ import ( ) // LocalSigner implements the Signer interface. It is used to sign a Tx locally -// using a bsvec PrivateKey. +// using a bkec PrivateKey. type LocalSigner struct { PrivateKey *bsvec.PrivateKey } diff --git a/localsigner_test.go b/localsigner_test.go index f06f0957..2af26289 100644 --- a/localsigner_test.go +++ b/localsigner_test.go @@ -19,8 +19,8 @@ func TestInternalSigner_SignAuto(t *testing.T) { assert.NotNil(t, tx) // Add the UTXO amount and script. - tx.Inputs[0].PreviousTxSatoshis = 100000000 - tx.Inputs[0].PreviousTxScript, err = bscript.NewFromHexString("76a914c0a3c167a28cabb9fbb495affa0761e6e74ac60d88ac") + tx.Inputs()[0].PreviousTxSatoshis = 100000000 + tx.Inputs()[0].PreviousTxScript, err = bscript.NewFromHexString("76a914c0a3c167a28cabb9fbb495affa0761e6e74ac60d88ac") assert.NoError(t, err) // Our private key diff --git a/signaturehash.go b/signaturehash.go index 170c5be7..70e00a26 100644 --- a/signaturehash.go +++ b/signaturehash.go @@ -28,12 +28,12 @@ func (tx *Tx) CalcInputSignatureHash(inputNumber uint32, sigHashFlag sighash.Fla // see https://github.com/bitcoin-sv/bitcoin-sv/blob/master/doc/abc/replay-protected-sighash.md#digest-algorithm func (tx *Tx) CalcInputPreimage(inputNumber uint32, sigHashFlag sighash.Flag) ([]byte, error) { - if tx.Inputs[inputNumber] == nil { + if tx.Inputs()[inputNumber] == nil { return nil, errors.New("specified input does not exist") } - in := tx.Inputs[inputNumber] + in := tx.Inputs()[inputNumber] - if len(in.PreviousTxIDBytes) == 0 { + if len(in.PreviousTxID()) == 0 { return nil, errors.New("'PreviousTxID' not supplied") } if in.PreviousTxScript == nil { @@ -59,7 +59,7 @@ func (tx *Tx) CalcInputPreimage(inputNumber uint32, sigHashFlag sighash.Flag) ([ if (sigHashFlag&31) != sighash.Single && (sigHashFlag&31) != sighash.None { // This will be executed in the usual BSV case (where sigHashType = SighashAllForkID) hashOutputs = tx.getOutputsHash(-1) - } else if (sigHashFlag&31) == sighash.Single && inputNumber < uint32(len(tx.Outputs)) { + } else if (sigHashFlag&31) == sighash.Single && inputNumber < uint32(tx.OutputCount()) { // This will *not* be executed in the usual BSV case (where sigHashType = SighashAllForkID) hashOutputs = tx.getOutputsHash(int32(inputNumber)) } @@ -76,7 +76,7 @@ func (tx *Tx) CalcInputPreimage(inputNumber uint32, sigHashFlag sighash.Flag) ([ buf = append(buf, hashSequence...) // outpoint (32-byte hash + 4-byte little endian) - buf = append(buf, ReverseBytes(in.PreviousTxIDBytes)...) + buf = append(buf, ReverseBytes(in.PreviousTxID())...) oi := make([]byte, 4) binary.LittleEndian.PutUint32(oi, in.PreviousTxOutIndex) buf = append(buf, oi...) @@ -115,8 +115,8 @@ func (tx *Tx) CalcInputPreimage(inputNumber uint32, sigHashFlag sighash.Flag) ([ func (tx *Tx) getPreviousOutHash() []byte { buf := make([]byte, 0) - for _, in := range tx.Inputs { - buf = append(buf, ReverseBytes(in.PreviousTxIDBytes)...) + for _, in := range tx.Inputs() { + buf = append(buf, ReverseBytes(in.PreviousTxID())...) oi := make([]byte, 4) binary.LittleEndian.PutUint32(oi, in.PreviousTxOutIndex) buf = append(buf, oi...) @@ -128,7 +128,7 @@ func (tx *Tx) getPreviousOutHash() []byte { func (tx *Tx) getSequenceHash() []byte { buf := make([]byte, 0) - for _, in := range tx.Inputs { + for _, in := range tx.Inputs() { oi := make([]byte, 4) binary.LittleEndian.PutUint32(oi, in.SequenceNumber) buf = append(buf, oi...) @@ -141,11 +141,11 @@ func (tx *Tx) getOutputsHash(n int32) []byte { buf := make([]byte, 0) if n == -1 { - for _, out := range tx.Outputs { + for _, out := range tx.Outputs() { buf = append(buf, out.BytesForSigHash()...) } } else { - buf = append(buf, tx.Outputs[n].BytesForSigHash()...) + buf = append(buf, tx.Outputs()[n].BytesForSigHash()...) } return crypto.Sha256d(buf) diff --git a/signaturehash_test.go b/signaturehash_test.go index aceec0e8..21f260ec 100644 --- a/signaturehash_test.go +++ b/signaturehash_test.go @@ -61,8 +61,8 @@ func TestTx_CalcInputPreimage(t *testing.T) { assert.NotNil(t, tx) // Add the UTXO amount and script (PreviousTxScript already in unsiged tx) - tx.Inputs[test.index].PreviousTxSatoshis = test.previousTxSatoshis - tx.Inputs[test.index].PreviousTxScript, err = bscript.NewFromHexString(test.previousTxScript) + tx.Inputs()[test.index].PreviousTxSatoshis = test.previousTxSatoshis + tx.Inputs()[test.index].PreviousTxScript, err = bscript.NewFromHexString(test.previousTxScript) assert.NoError(t, err) var actualSigHash []byte @@ -124,8 +124,8 @@ func TestTx_CalcInputSignatureHash(t *testing.T) { assert.NotNil(t, tx) // Add the UTXO amount and script (PreviousTxScript already in unsiged tx) - tx.Inputs[test.index].PreviousTxSatoshis = test.previousTxSatoshis - tx.Inputs[test.index].PreviousTxScript, err = bscript.NewFromHexString(test.previousTxScript) + tx.Inputs()[test.index].PreviousTxSatoshis = test.previousTxSatoshis + tx.Inputs()[test.index].PreviousTxScript, err = bscript.NewFromHexString(test.previousTxScript) assert.NoError(t, err) var actualSigHash []byte diff --git a/tx.go b/tx.go index 8e864d69..cf96fda4 100644 --- a/tx.go +++ b/tx.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "encoding/hex" + "errors" "fmt" "github.com/libsv/go-bt/crypto" @@ -31,13 +32,17 @@ lock_time if non-zero and sequence numbers are < 0xFFFFFFFF: block height -------------------------------------------------------- */ +var ( + ErrInvalidTxID = errors.New("invalid TxID") +) + // Tx wraps a bitcoin transaction // // DO NOT CHANGE ORDER - Optimized memory via malign // type Tx struct { - Inputs []*Input - Outputs []*Output + inputs []*Input + outputs []*Output Version uint32 LockTime uint32 } @@ -50,18 +55,18 @@ func NewTx() *Tx { // NewTxFromString takes a toBytesHelper string representation of a bitcoin transaction // and returns a Tx object. func NewTxFromString(str string) (*Tx, error) { - bytes, err := hex.DecodeString(str) + bb, err := hex.DecodeString(str) if err != nil { return nil, err } - return NewTxFromBytes(bytes) + return NewTxFromBytes(bb) } // NewTxFromBytes takes an array of bytes, constructs a Tx and returns it. // This function assumes that the byte slice contains exactly 1 transaction. func NewTxFromBytes(b []byte) (*Tx, error) { - tx, used, err := NewTxFromStream((b)) + tx, used, err := NewTxFromStream(b) if err != nil { return nil, err } @@ -73,7 +78,7 @@ func NewTxFromBytes(b []byte) (*Tx, error) { return tx, nil } -// NewTxFromStream takes an array of bytes and contructs a Tx from it, returning the Tx and the bytes used. +// NewTxFromStream takes an array of bytes and constructs a Tx from it, returning the Tx and the bytes used. // Despite the name, this is not actually reading a stream in the true sense: it is a byte slice that contains // many transactions one after another. func NewTxFromStream(b []byte) (*Tx, int, error) { @@ -101,8 +106,7 @@ func NewTxFromStream(b []byte) (*Tx, int, error) { return nil, 0, err } offset += size - - t.Inputs = append(t.Inputs, input) + t.addInput(input) } // create outputs @@ -116,7 +120,7 @@ func NewTxFromStream(b []byte) (*Tx, int, error) { return nil, 0, err } offset += size - t.Outputs = append(t.Outputs, output) + t.AddOutput(output) } t.LockTime = binary.LittleEndian.Uint32(b[offset:]) @@ -128,7 +132,7 @@ func NewTxFromStream(b []byte) (*Tx, int, error) { // HasDataOutputs returns true if the transaction has // at least one data (OP_RETURN) output in it. func (tx *Tx) HasDataOutputs() bool { - for _, out := range tx.Outputs { + for _, out := range tx.Outputs() { if out.LockingScript.IsData() { return true } @@ -136,20 +140,52 @@ func (tx *Tx) HasDataOutputs() bool { return false } +// Inputs returns the inputs for the transaction. +func (tx *Tx) Inputs() []*Input { + return tx.inputs +} + +// InputIdx will return the input at the specified index. +// +// This will consume an overflow error and simply return nil if the input +// isn't found at the index. +func (tx *Tx) InputIdx(i int) *Input { + if i > tx.InputCount()-1 { + return nil + } + return tx.inputs[i] +} + +// Outputs returns the outputs for the transaction. +func (tx *Tx) Outputs() []*Output { + return tx.outputs +} + +// OutputIdx will return the output at the specified index. +// +// This will consume an overflow error and simply return nil if the output +// isn't found at the index. +func (tx *Tx) OutputIdx(i int) *Output { + if i > tx.OutputCount()-1 { + return nil + } + return tx.outputs[i] +} + // IsCoinbase determines if this transaction is a coinbase by // checking if the tx input is a standard coinbase input. func (tx *Tx) IsCoinbase() bool { - if len(tx.Inputs) != 1 { + if len(tx.inputs) != 1 { return false } cbi := make([]byte, 32) - if !bytes.Equal(tx.Inputs[0].PreviousTxIDBytes, cbi) { + if !bytes.Equal(tx.inputs[0].PreviousTxID(), cbi) { return false } - if tx.Inputs[0].PreviousTxOutIndex == DefaultSequenceNumber || tx.Inputs[0].SequenceNumber == DefaultSequenceNumber { + if tx.inputs[0].PreviousTxOutIndex == DefaultSequenceNumber || tx.inputs[0].SequenceNumber == DefaultSequenceNumber { return true } @@ -168,6 +204,19 @@ func (tx *Tx) TxID() string { return hex.EncodeToString(ReverseBytes(crypto.Sha256d(tx.ToBytes()))) } +// IsValidTxID will check that the txid bytes are valid. +// +// A txid should be in hexadecimal and be of 64 bytes length. +func IsValidTxID(txid []byte) bool { + if len(txid) != 64 { + return false + } + if _, err := hex.DecodeString(string(txid)); err != nil { + return false + } + return true +} + // ToString encodes the transaction into a hex string. func (tx *Tx) ToString() string { return hex.EncodeToString(tx.ToBytes()) @@ -190,9 +239,9 @@ func (tx *Tx) toBytesHelper(index int, lockingScript []byte) []byte { h = append(h, LittleEndianBytes(tx.Version, 4)...) - h = append(h, VarInt(uint64(len(tx.Inputs)))...) + h = append(h, VarInt(uint64(len(tx.inputs)))...) - for i, in := range tx.Inputs { + for i, in := range tx.inputs { s := in.ToBytes(lockingScript != nil) if i == index && lockingScript != nil { h = append(h, VarInt(uint64(len(lockingScript)))...) @@ -202,8 +251,8 @@ func (tx *Tx) toBytesHelper(index int, lockingScript []byte) []byte { } } - h = append(h, VarInt(uint64(len(tx.Outputs)))...) - for _, out := range tx.Outputs { + h = append(h, VarInt(uint64(len(tx.outputs)))...) + for _, out := range tx.outputs { h = append(h, out.ToBytes()...) } diff --git a/tx_test.go b/tx_test.go index cbeecc83..d300f386 100644 --- a/tx_test.go +++ b/tx_test.go @@ -57,24 +57,24 @@ func TestNewTxFromString(t *testing.T) { // Check version, locktime, inputs assert.Equal(t, uint32(2), tx.Version) assert.Equal(t, uint32(0), tx.LockTime) - assert.Equal(t, 1, len(tx.Inputs)) + assert.Equal(t, 1, len(tx.Inputs())) // Create a new unlocking script ptid, _ := hex.DecodeString("9c5b1428aaad5e9b0196c89be8628b366f33c7b22933da0489b921d487a7cb1c") i := bt.Input{ - PreviousTxIDBytes: ptid, PreviousTxOutIndex: 0, SequenceNumber: bt.DefaultSequenceNumber, } + assert.NotEmpty(t, i.PreviousTxIDAdd(ptid)) i.UnlockingScript, err = bscript.NewFromHexString("47304402205cc711985ce2a6d61eece4f9b6edd6337bad3b7eca3aa3ce59bc15620d8de2a80220410c92c48a226ba7d5a9a01105524097f673f31320d46c3b61d2378e6f05320041") assert.NoError(t, err) assert.NotNil(t, i.UnlockingScript) // Check input type - assert.Equal(t, true, reflect.DeepEqual(*tx.Inputs[0], i)) + assert.Equal(t, true, reflect.DeepEqual(*tx.Inputs()[0], i)) // Check output - assert.Equal(t, 1, len(tx.Outputs)) + assert.Equal(t, 1, len(tx.Outputs())) // New output var ls *bscript.Script @@ -84,7 +84,7 @@ func TestNewTxFromString(t *testing.T) { // Check the type o := bt.Output{Satoshis: 4999000000, LockingScript: ls} - assert.Equal(t, true, reflect.DeepEqual(*tx.Outputs[0], o)) + assert.Equal(t, true, reflect.DeepEqual(*tx.Outputs()[0], o)) }) } @@ -270,3 +270,137 @@ func TestTx_HasDataOutputs(t *testing.T) { assert.Equal(t, false, tx.HasDataOutputs()) }) } + +func TestTx_OutputIdx(t *testing.T) { + t.Parallel() + tests := map[string]struct { + tx *bt.Tx + idx int + expOutput *bt.Output + }{ + "tx with 3 outputs and output idx 0 requested should return output": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.PayTo("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) + assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + return tx + }(), + idx: 0, + expOutput: &bt.Output{ + Satoshis: 1000, + LockingScript: func() *bscript.Script { + s, err := bscript.NewP2PKHFromAddress("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh") + assert.NoError(t, err) + return s + }(), + }, + }, "tx with 3 outputs and output idx 2 requested should return output": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.PayTo("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) + assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.PayTo("mywmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + return tx + }(), + idx: 2, + expOutput: &bt.Output{ + Satoshis: 1000, + LockingScript: func() *bscript.Script { + s, err := bscript.NewP2PKHFromAddress("mywmGVP89x3DsLNqk3NvctfQy9m9pvt7mz") + assert.NoError(t, err) + return s + }(), + }, + }, "tx with 3 outputs and output idx 5 requested should return nil": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.PayTo("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) + assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.PayTo("mywmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + return tx + }(), + idx: 5, + expOutput: nil, + }, "tx with 0 outputs and output idx 5 requested should return nil": { + tx: func() *bt.Tx { + return bt.NewTx() + }(), + idx: 5, + expOutput: nil, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + o := test.tx.OutputIdx(test.idx) + assert.Equal(t, test.expOutput, o) + }) + } +} + +func TestTx_InputIdx(t *testing.T) { + t.Parallel() + tests := map[string]struct { + tx *bt.Tx + idx int + expOutput *bt.Output + }{ + "tx with 3 inputs and input idx 0 requested should return correct input": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.PayTo("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) + assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + return tx + }(), + idx: 0, + expOutput: &bt.Output{ + Satoshis: 1000, + LockingScript: func() *bscript.Script { + s, err := bscript.NewP2PKHFromAddress("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh") + assert.NoError(t, err) + return s + }(), + }, + }, "tx with 3 outputs and output idx 2 requested should return output": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.PayTo("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) + assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.PayTo("mywmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + return tx + }(), + idx: 2, + expOutput: &bt.Output{ + Satoshis: 1000, + LockingScript: func() *bscript.Script { + s, err := bscript.NewP2PKHFromAddress("mywmGVP89x3DsLNqk3NvctfQy9m9pvt7mz") + assert.NoError(t, err) + return s + }(), + }, + }, "tx with 3 outputs and output idx 5 requested should return nil": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.PayTo("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) + assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.PayTo("mywmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + return tx + }(), + idx: 5, + expOutput: nil, + }, "tx with 0 outputs and output idx 5 requested should return nil": { + tx: func() *bt.Tx { + return bt.NewTx() + }(), + idx: 5, + expOutput: nil, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + o := test.tx.OutputIdx(test.idx) + assert.Equal(t, test.expOutput, o) + }) + } +} diff --git a/txchange.go b/txchange.go index 2051b39e..17feafda 100644 --- a/txchange.go +++ b/txchange.go @@ -26,7 +26,7 @@ func (tx *Tx) Change(s *bscript.Script, f []*Fee) error { } if hasChange { // add rest of available sats to the change output - tx.Outputs[len(tx.Outputs)-1].Satoshis = available + tx.Outputs()[tx.OutputCount()-1].Satoshis = available } return nil } @@ -34,15 +34,15 @@ func (tx *Tx) Change(s *bscript.Script, f []*Fee) error { // ChangeToOutput will calculate fees and add them to an output at the index specified (0 based). // If an invalid index is supplied and error is returned. func (tx *Tx) ChangeToOutput(index uint, f []*Fee) error { - if int(index) > len(tx.Outputs)-1 { + if int(index) > tx.OutputCount()-1 { return errors.New("index is greater than number of inputs in transaction") } - available, hasChange, err := tx.change(tx.Outputs[index].LockingScript, f, false) + available, hasChange, err := tx.change(tx.Outputs()[index].LockingScript, f, false) if err != nil { return err } if hasChange { - tx.Outputs[index].Satoshis += available + tx.Outputs()[index].Satoshis += available } return nil } @@ -143,7 +143,7 @@ func (tx *Tx) getExpectedUnlockingScriptFees(f []*Fee) (uint64, error) { var expectedBytes int - for _, in := range tx.Inputs { + for _, in := range tx.Inputs() { if !in.PreviousTxScript.IsP2PKH() { return 0, errors.New("non-P2PKH input used in the tx - unsupported") } @@ -155,7 +155,7 @@ func (tx *Tx) getExpectedUnlockingScriptFees(f []*Fee) (uint64, error) { func (tx *Tx) getStandardAndDataBytes() (standardBytes, dataBytes int) { // Subtract the value of each output as well as keeping track of data outputs - for _, out := range tx.Outputs { + for _, out := range tx.Outputs() { if out.LockingScript.IsData() && len(*out.LockingScript) > 0 { dataBytes += len(*out.LockingScript) } diff --git a/txchange_test.go b/txchange_test.go index af77e55a..335ef503 100644 --- a/txchange_test.go +++ b/txchange_test.go @@ -52,7 +52,7 @@ func TestTx_ChangeToAddress(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, tx.OutputCount()) - assert.Equal(t, "76a914a7a1a7fd7d279b57b84e596cbbf82608efdb441a88ac", tx.Outputs[0].LockingScript.ToString()) + assert.Equal(t, "76a914a7a1a7fd7d279b57b84e596cbbf82608efdb441a88ac", tx.Outputs()[0].LockingScript.ToString()) }) } @@ -112,12 +112,12 @@ func TestTx_Change(t *testing.T) { assert.NoError(t, err) // Correct fee for the tx - assert.Equal(t, uint64(3999904), tx.Outputs[0].Satoshis) + assert.Equal(t, uint64(3999904), tx.Outputs()[0].Satoshis) // Correct script hex string assert.Equal(t, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - tx.Outputs[0].LockingScriptHexString(), + tx.Outputs()[0].LockingScriptHexString(), ) }) @@ -192,7 +192,7 @@ func TestTx_Change(t *testing.T) { assert.Equal(t, "01000000010b94a1ef0fb352aa2adc54207ce47ba55d5a1c1609afda58fe9520e472299107000000006a473044022049ee0c0f26c00e6a6b3af5990fc8296c66eab3e3e42ab075069b89b1be6fefec02206079e49dd8c9e1117ef06fbe99714d822620b1f0f5d19f32a1128f5d29b7c3c4412102c8803fdd437d902f08e3c2344cb33065c99d7c99982018ff9f7219c3dd352ff0ffffffff01a0083d00000000001976a914af2590a45ae401651fdbdf59a76ad43d1862534088ac00000000", tx.ToString()) - assert.Equal(t, uint64(3999904), tx.Outputs[0].Satoshis) + assert.Equal(t, uint64(3999904), tx.Outputs()[0].Satoshis) }) t.Run("spend entire utxo - multi payouts - expected fee", func(t *testing.T) { @@ -225,8 +225,8 @@ func TestTx_Change(t *testing.T) { assert.Equal(t, "01000000010b94a1ef0fb352aa2adc54207ce47ba55d5a1c1609afda58fe9520e472299107000000006a47304402206bbb4b23349bdf86e6fbc9067226e9a7b15c977fa530999b39cd0a6d9c83360d02202dd8ffdc610e58b3fc92b44400d99e38c78866765f31acb40d98007a52e7a826412102c8803fdd437d902f08e3c2344cb33065c99d7c99982018ff9f7219c3dd352ff0ffffffff0240420f00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88acc0c62d00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88ac00000000", tx.ToString()) - assert.Equal(t, uint64(1000000), tx.Outputs[0].Satoshis) - assert.Equal(t, uint64(3000000), tx.Outputs[1].Satoshis) + assert.Equal(t, uint64(1000000), tx.Outputs()[0].Satoshis) + assert.Equal(t, uint64(3000000), tx.Outputs()[1].Satoshis) }) t.Run("spend entire utxo - multi payouts - incorrect fee", func(t *testing.T) { @@ -261,8 +261,8 @@ func TestTx_Change(t *testing.T) { // todo: expected the pay-to inputs to change based on the fee :P - assert.Equal(t, uint64(999995), tx.Outputs[0].Satoshis) - assert.Equal(t, uint64(3000000), tx.Outputs[1].Satoshis) + assert.Equal(t, uint64(999995), tx.Outputs()[0].Satoshis) + assert.Equal(t, uint64(3000000), tx.Outputs()[1].Satoshis) }) t.Run("multiple inputs, spend all", func(t *testing.T) { @@ -386,7 +386,7 @@ func TestTx_ChangeToOutput(t *testing.T) { return } assert.Equal(t, test.expOutputTotal, test.tx.TotalOutputSatoshis()) - assert.Equal(t, test.expChangeOutput, test.tx.Outputs[test.index].Satoshis) + assert.Equal(t, test.expChangeOutput, test.tx.Outputs()[test.index].Satoshis) }) } } diff --git a/txinput.go b/txinput.go index 80e409cb..d7447517 100644 --- a/txinput.go +++ b/txinput.go @@ -3,7 +3,6 @@ package bt import ( "bytes" "encoding/binary" - "encoding/hex" "fmt" "github.com/libsv/go-bt/bscript" @@ -27,7 +26,7 @@ func NewInputFromBytes(bytes []byte) (*Input, int, error) { } return &Input{ - PreviousTxIDBytes: ReverseBytes(bytes[0:32]), + previousTxID: ReverseBytes(bytes[0:32]), PreviousTxOutIndex: binary.LittleEndian.Uint32(bytes[32:36]), SequenceNumber: binary.LittleEndian.Uint32(bytes[offset+int(l):]), UnlockingScript: bscript.NewFromBytes(bytes[offset : offset+int(l)]), @@ -36,21 +35,21 @@ func NewInputFromBytes(bytes []byte) (*Input, int, error) { // TotalInputSatoshis returns the total Satoshis inputted to the transaction. func (tx *Tx) TotalInputSatoshis() (total uint64) { - for _, in := range tx.Inputs { + for _, in := range tx.inputs { total += in.PreviousTxSatoshis } return } func (tx *Tx) addInput(input *Input) { - tx.Inputs = append(tx.Inputs, input) + tx.inputs = append(tx.inputs, input) } // AddInputFromTx will add all outputs of given previous transaction // that match a specific public key to your transaction. func (tx *Tx) AddInputFromTx(pvsTx *Tx, matchPK []byte) error { - for i, utxo := range pvsTx.Outputs { + for i, utxo := range pvsTx.outputs { utxoPkHASH160, err := utxo.LockingScript.PublicKeyHash() if err != nil { return err @@ -76,23 +75,21 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat return err } - ptxid, err := hex.DecodeString(prevTxID) - if err != nil { - return err - } - - tx.addInput(&Input{ - PreviousTxIDBytes: ptxid, + i := &Input{ PreviousTxOutIndex: vout, PreviousTxSatoshis: satoshis, PreviousTxScript: pts, SequenceNumber: DefaultSequenceNumber, // use default finalized sequence number - }) + } + if err := i.PreviousTxIDAdd([]byte(prevTxID)); err != nil { + return err + } + tx.addInput(i) return nil } // InputCount returns the number of transaction inputs. func (tx *Tx) InputCount() int { - return len(tx.Inputs) + return len(tx.inputs) } diff --git a/txinput_test.go b/txinput_test.go index d112a13c..25c224f6 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -70,9 +70,9 @@ func TestTx_From(t *testing.T) { 4000000) assert.NoError(t, err) - inputs := tx.Inputs + inputs := tx.Inputs() assert.Equal(t, 1, len(inputs)) - assert.Equal(t, "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", hex.EncodeToString(inputs[0].PreviousTxIDBytes)) + assert.Equal(t, "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", hex.EncodeToString(inputs[0].PreviousTxID())) assert.Equal(t, uint32(0), inputs[0].PreviousTxOutIndex) assert.Equal(t, uint64(4000000), inputs[0].PreviousTxSatoshis) assert.Equal(t, bt.DefaultSequenceNumber, inputs[0].SequenceNumber) diff --git a/txoutput.go b/txoutput.go index 5cd46092..7b618f51 100644 --- a/txoutput.go +++ b/txoutput.go @@ -35,7 +35,7 @@ func NewOutputFromBytes(bytes []byte) (*Output, int, error) { // TotalOutputSatoshis returns the total Satoshis outputted from the transaction. func (tx *Tx) TotalOutputSatoshis() (total uint64) { - for _, o := range tx.Outputs { + for _, o := range tx.outputs { total += o.Satoshis } return @@ -169,12 +169,12 @@ func createOpReturnOutput(data [][]byte) (*Output, error) { // OutputCount returns the number of transaction inputs. func (tx *Tx) OutputCount() int { - return len(tx.Outputs) + return len(tx.outputs) } // AddOutput adds a new output to the transaction. func (tx *Tx) AddOutput(output *Output) { - tx.Outputs = append(tx.Outputs, output) + tx.outputs = append(tx.outputs, output) } // PayTo creates a new P2PKH output from a BitCoin address (base58) diff --git a/txoutput_test.go b/txoutput_test.go index 587dcd58..76d1f288 100644 --- a/txoutput_test.go +++ b/txoutput_test.go @@ -22,7 +22,7 @@ func TestNewP2PKHOutputFromPubKeyHashStr(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "76a91488ac", - tx.Outputs[0].LockingScriptHexString(), + tx.Outputs()[0].LockingScriptHexString(), ) }) @@ -45,7 +45,7 @@ func TestNewP2PKHOutputFromPubKeyHashStr(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "76a9148fe80c75c9560e8b56ed64ea3c26e18d2c52211b88ac", - tx.Outputs[0].LockingScriptHexString(), + tx.Outputs()[0].LockingScriptHexString(), ) }) } @@ -66,7 +66,7 @@ func TestNewHashPuzzleOutput(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "a914b472a266d0bd89c13706a4132ccfb16f7c3b9fcb8876a90088ac", - tx.Outputs[0].LockingScriptHexString(), + tx.Outputs()[0].LockingScriptHexString(), ) }) @@ -81,7 +81,7 @@ func TestNewHashPuzzleOutput(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "a914d3f9e3d971764be5838307b175ee4e08ba427b908876a914c28f832c3d539933e0c719297340b34eee0f4c3488ac", - tx.Outputs[0].LockingScriptHexString(), + tx.Outputs()[0].LockingScriptHexString(), ) }) } @@ -98,7 +98,7 @@ func TestNewOpReturnOutput(t *testing.T) { err := tx.AddOpReturnOutput(dataBytes) assert.NoError(t, err) - script := tx.Outputs[0].LockingScriptHexString() + script := tx.Outputs()[0].LockingScriptHexString() dataLength := bt.VarInt(uint64(len(dataBytes))) assert.Equal(t, "006a4d2201"+hex.EncodeToString(dataBytes), script) @@ -113,7 +113,7 @@ func TestNewOpReturnPartsOutput(t *testing.T) { err := tx.AddOpReturnPartsOutput(dataBytes) assert.NoError(t, err) - assert.Equal(t, "006a02686903686f770361726503796f75", tx.Outputs[0].LockingScriptHexString()) + assert.Equal(t, "006a02686903686f770361726503796f75", tx.Outputs()[0].LockingScriptHexString()) } func TestTx_TotalOutputSatoshis(t *testing.T) { diff --git a/txsign.go b/txsign.go index 6d116169..d9f9116d 100644 --- a/txsign.go +++ b/txsign.go @@ -67,8 +67,8 @@ func (tx *Tx) ApplyP2PKHUnlockingScript(index uint32, pubKey []byte, sig []byte, // ApplyUnlockingScript applies a script to the transaction at a specific index in // unlocking script field. func (tx *Tx) ApplyUnlockingScript(index uint32, s *bscript.Script) error { - if tx.Inputs[index] != nil { - tx.Inputs[index].UnlockingScript = s + if tx.inputs[index] != nil { + tx.inputs[index].UnlockingScript = s return nil } @@ -84,7 +84,7 @@ func (tx *Tx) SignAuto(s AutoSigner) (inputsSigned []int, err error) { shf := sighash.AllForkID // use SIGHASHALLFORFORKID to sign automatically - for i, in := range tx.Inputs { + for i, in := range tx.inputs { pubKeyHash, _ := in.PreviousTxScript.PublicKeyHash() // doesn't matter if returns error (not p2pkh) pubKeyHashStr := hex.EncodeToString(pubKeyHash) From a261a708df58858bcf9a224ee518ef3290d7641b Mon Sep 17 00:00:00 2001 From: Mark Smith Date: Mon, 10 May 2021 17:00:06 +0100 Subject: [PATCH 2/8] adding InputIdx tests --- tx_test.go | 116 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 82 insertions(+), 34 deletions(-) diff --git a/tx_test.go b/tx_test.go index 1a8d8099..c8692b53 100644 --- a/tx_test.go +++ b/tx_test.go @@ -342,66 +342,114 @@ func TestTx_OutputIdx(t *testing.T) { func TestTx_InputIdx(t *testing.T) { t.Parallel() tests := map[string]struct { - tx *bt.Tx - idx int - expOutput *bt.Output + tx *bt.Tx + idx int + expInput *bt.Input }{ "tx with 3 inputs and input idx 0 requested should return correct input": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.PayTo("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) - assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) - assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + 0, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 1000)) + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + 0, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 2000000)) + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + 0, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 2000000)) return tx }(), idx: 0, - expOutput: &bt.Output{ - Satoshis: 1000, - LockingScript: func() *bscript.Script { - s, err := bscript.NewP2PKHFromAddress("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh") - assert.NoError(t, err) - return s - }(), - }, + expInput: func() *bt.Input { + in := &bt.Input{ + PreviousTxSatoshis: 1000, + PreviousTxScript: func() *bscript.Script { + b, err := bscript.NewFromHexString("76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac") + assert.NoError(t, err) + return b + }(), + PreviousTxOutIndex: 0, + SequenceNumber: bt.DefaultSequenceNumber, + } + _ = in.PreviousTxIDAddStr("3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5") + return in + }(), }, "tx with 3 outputs and output idx 2 requested should return output": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.PayTo("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) - assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) - assert.NoError(t, tx.PayTo("mywmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + 0, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 1000)) + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + 0, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 2000000)) + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdac4", + 0, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 999)) return tx }(), idx: 2, - expOutput: &bt.Output{ - Satoshis: 1000, - LockingScript: func() *bscript.Script { - s, err := bscript.NewP2PKHFromAddress("mywmGVP89x3DsLNqk3NvctfQy9m9pvt7mz") - assert.NoError(t, err) - return s - }(), - }, + expInput: func() *bt.Input { + in := &bt.Input{ + PreviousTxSatoshis: 999, + PreviousTxScript: func() *bscript.Script { + b, err := bscript.NewFromHexString("76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac") + assert.NoError(t, err) + return b + }(), + PreviousTxOutIndex: 0, + SequenceNumber: bt.DefaultSequenceNumber, + } + _ = in.PreviousTxIDAddStr("3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdac4") + return in + }(), }, "tx with 3 outputs and output idx 5 requested should return nil": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.PayTo("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) - assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) - assert.NoError(t, tx.PayTo("mywmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + 0, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 1000)) + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + 0, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 2000000)) + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + 0, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 999)) return tx }(), - idx: 5, - expOutput: nil, + idx: 5, + expInput: nil, }, "tx with 0 outputs and output idx 5 requested should return nil": { tx: func() *bt.Tx { return bt.NewTx() }(), - idx: 5, - expOutput: nil, + idx: 5, + expInput: nil, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - o := test.tx.OutputIdx(test.idx) - assert.Equal(t, test.expOutput, o) + o := test.tx.InputIdx(test.idx) + assert.Equal(t, test.expInput, o) }) } } From 99d7fd73a8e02c71f49d01489fb8d9707f20b112 Mon Sep 17 00:00:00 2001 From: Mark Smith Date: Mon, 10 May 2021 17:30:55 +0100 Subject: [PATCH 3/8] using idx helpers --- localsigner_test.go | 8 ++++---- signaturehash.go | 4 ++-- signaturehash_test.go | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/localsigner_test.go b/localsigner_test.go index 9b097495..3372a466 100644 --- a/localsigner_test.go +++ b/localsigner_test.go @@ -5,9 +5,9 @@ import ( "encoding/hex" "testing" + . "github.com/libsv/go-bk/wif" "github.com/libsv/go-bt" "github.com/libsv/go-bt/bscript" - . "github.com/libsv/go-bk/wif" "github.com/stretchr/testify/assert" ) @@ -20,8 +20,8 @@ func TestInternalSigner_SignAuto(t *testing.T) { assert.NotNil(t, tx) // Add the UTXO amount and script. - tx.Inputs()[0].PreviousTxSatoshis = 100000000 - tx.Inputs()[0].PreviousTxScript, err = bscript.NewFromHexString("76a914c0a3c167a28cabb9fbb495affa0761e6e74ac60d88ac") + tx.InputIdx(0).PreviousTxSatoshis = 100000000 + tx.InputIdx(0).PreviousTxScript, err = bscript.NewFromHexString("76a914c0a3c167a28cabb9fbb495affa0761e6e74ac60d88ac") assert.NoError(t, err) // Our private key @@ -30,7 +30,7 @@ func TestInternalSigner_SignAuto(t *testing.T) { assert.NoError(t, err) signer := bt.LocalSigner{PrivateKey: wif.PrivKey} - _, err = tx.SignAuto(context.Background(),&signer) + _, err = tx.SignAuto(context.Background(), &signer) assert.NoError(t, err) expectedSignedTx := "010000000193a35408b6068499e0d5abd799d3e827d9bfe70c9b75ebe209c91d2507232651000000006b483045022100c1d77036dc6cd1f3fa1214b0688391ab7f7a16cd31ea4e5a1f7a415ef167df820220751aced6d24649fa235132f1e6969e163b9400f80043a72879237dab4a1190ad412103b8b40a84123121d260f5c109bc5a46ec819c2e4002e5ba08638783bfb4e01435ffffffff02404b4c00000000001976a91404ff367be719efa79d76e4416ffb072cd53b208888acde94a905000000001976a91404d03f746652cfcb6cb55119ab473a045137d26588ac00000000" diff --git a/signaturehash.go b/signaturehash.go index 788226e3..706a633e 100644 --- a/signaturehash.go +++ b/signaturehash.go @@ -28,10 +28,10 @@ func (tx *Tx) CalcInputSignatureHash(inputNumber uint32, sigHashFlag sighash.Fla // see https://github.com/bitcoin-sv/bitcoin-sv/blob/master/doc/abc/replay-protected-sighash.md#digest-algorithm func (tx *Tx) CalcInputPreimage(inputNumber uint32, sigHashFlag sighash.Flag) ([]byte, error) { - if tx.Inputs()[inputNumber] == nil { + if tx.InputIdx(int(inputNumber)) == nil { return nil, errors.New("specified input does not exist") } - in := tx.Inputs()[inputNumber] + in := tx.InputIdx(int(inputNumber)) if len(in.PreviousTxID()) == 0 { return nil, errors.New("'PreviousTxID' not supplied") diff --git a/signaturehash_test.go b/signaturehash_test.go index 21f260ec..8258a026 100644 --- a/signaturehash_test.go +++ b/signaturehash_test.go @@ -16,7 +16,7 @@ func TestTx_CalcInputPreimage(t *testing.T) { var testVector = []struct { name string unsignedTx string - index uint32 + index int previousTxSatoshis uint64 previousTxScript string sigHashType sighash.Flag @@ -61,12 +61,12 @@ func TestTx_CalcInputPreimage(t *testing.T) { assert.NotNil(t, tx) // Add the UTXO amount and script (PreviousTxScript already in unsiged tx) - tx.Inputs()[test.index].PreviousTxSatoshis = test.previousTxSatoshis - tx.Inputs()[test.index].PreviousTxScript, err = bscript.NewFromHexString(test.previousTxScript) + tx.InputIdx(test.index).PreviousTxSatoshis = test.previousTxSatoshis + tx.InputIdx(test.index).PreviousTxScript, err = bscript.NewFromHexString(test.previousTxScript) assert.NoError(t, err) var actualSigHash []byte - actualSigHash, err = tx.CalcInputPreimage(test.index, sighash.All|sighash.ForkID) + actualSigHash, err = tx.CalcInputPreimage(uint32(test.index), sighash.All|sighash.ForkID) assert.NoError(t, err) assert.Equal(t, test.expectedPreimage, hex.EncodeToString(actualSigHash)) }) From d381d55106ca95ce882a805f49d8e9c499d0c693 Mon Sep 17 00:00:00 2001 From: Mark Smith Date: Mon, 26 Jul 2021 15:11:56 +0100 Subject: [PATCH 4/8] adding BIP276 struct to tidy up bip276 encoding and decoding --- bscript/addressvalidation.go | 4 +- bscript/bip276.go | 57 ++++++++++++-------- bscript/bip276_test.go | 101 ++++++++++++++++++----------------- 3 files changed, 86 insertions(+), 76 deletions(-) diff --git a/bscript/addressvalidation.go b/bscript/addressvalidation.go index fe3e085f..7ad28118 100644 --- a/bscript/addressvalidation.go +++ b/bscript/addressvalidation.go @@ -53,9 +53,7 @@ func (a *a25) set58(s []byte) error { // Checks both mainnet and testnet. func ValidateAddress(address string) (bool, error) { if strings.HasPrefix(address, "bitcoin-script:") { - _, _, _, _, err := DecodeBIP276(address) - - if err != nil { + if _, err := DecodeBIP276(address); err != nil { return false, fmt.Errorf("bitcoin-script invalid [%w]", err) } return true, nil diff --git a/bscript/bip276.go b/bscript/bip276.go index 9a75726b..5e773ab5 100644 --- a/bscript/bip276.go +++ b/bscript/bip276.go @@ -10,6 +10,15 @@ import ( "github.com/libsv/go-bk/crypto" ) +// BIP276 proposes a scheme for encoding typed bitcoin related data in a user friendly way +// see https://github.com/moneybutton/bips/blob/master/bip-0276.mediawiki +type BIP276 struct { + Prefix string + Version int + Network int + Data []byte +} + // PrefixScript is the prefix in the BIP276 standard which // specifies if it is a script or template. const PrefixScript = "bitcoin-script" @@ -35,52 +44,54 @@ var validBIP276 = regexp.MustCompile(`^(.+?):(\d{2})(\d{2})([0-9A-Fa-f]+)([0-9A- // EncodeBIP276 is used to encode specific (non-standard) scripts in BIP276 format. // See https://github.com/moneybutton/bips/blob/master/bip-0276.mediawiki -func EncodeBIP276(prefix string, network, version int, data []byte) string { - if version == 0 || version > 255 || network == 0 || network > 255 { +func EncodeBIP276(script BIP276) string { + if script.Version == 0 || script.Version > 255 || script.Network == 0 || script.Network > 255 { return "ERROR" } - p, c := createBIP276(prefix, network, version, data) + p, c := createBIP276(script) return p + c } -func createBIP276(prefix string, network, version int, data []byte) (string, string) { - payload := fmt.Sprintf("%s:%.2x%.2x%x", prefix, network, version, data) +func createBIP276(script BIP276) (string, string) { + payload := fmt.Sprintf("%s:%.2x%.2x%x", script.Prefix, script.Network, script.Version, script.Data) return payload, hex.EncodeToString(crypto.Sha256d([]byte(payload))[:4]) } // DecodeBIP276 is used to decode BIP276 formatted data into specific (non-standard) scripts. // See https://github.com/moneybutton/bips/blob/master/bip-0276.mediawiki -func DecodeBIP276(text string) (prefix string, version, network int, data []byte, err error) { +func DecodeBIP276(text string) (*BIP276, error) { // Determine if regex match res := validBIP276.FindStringSubmatch(text) // Check if we got a result from the regex match first if len(res) == 0 { - err = fmt.Errorf("text did not match the BIP276 format") - return + return nil, fmt.Errorf("text did not match the BIP276 format") } - - // Set the prefix - prefix = res[1] - - if version, err = strconv.Atoi(res[2]); err != nil { - return + s := BIP276{ + Prefix: res[1], } - if network, err = strconv.Atoi(res[3]); err != nil { - return + version, err := strconv.Atoi(res[2]) + if err != nil { + return nil, err } - - if data, err = hex.DecodeString(res[4]); err != nil { - return + s.Version = version + network, err := strconv.Atoi(res[3]) + if err != nil { + return nil, err } - - if _, checkSum := createBIP276(prefix, network, version, data); res[5] != checkSum { - err = errors.New("invalid checksum") + s.Network = network + data, err := hex.DecodeString(res[4]) + if err != nil { + return nil, err + } + s.Data = data + if _, checkSum := createBIP276(s); res[5] != checkSum { + return nil, errors.New("invalid checksum") } - return + return &s, nil } diff --git a/bscript/bip276_test.go b/bscript/bip276_test.go index 0afc9e86..bf7e0a5e 100644 --- a/bscript/bip276_test.go +++ b/bscript/bip276_test.go @@ -13,10 +13,12 @@ func TestEncodeBIP276(t *testing.T) { t.Run("valid encode (mainnet)", func(t *testing.T) { s := bscript.EncodeBIP276( - bscript.PrefixScript, - bscript.NetworkMainnet, - bscript.CurrentVersion, - []byte("fake script"), + bscript.BIP276{ + Prefix: bscript.PrefixScript, + Version: bscript.CurrentVersion, + Network: bscript.NetworkMainnet, + Data: []byte("fake script"), + }, ) assert.Equal(t, "bitcoin-script:010166616b65207363726970746f0cd86a", s) @@ -24,10 +26,12 @@ func TestEncodeBIP276(t *testing.T) { t.Run("valid encode (testnet)", func(t *testing.T) { s := bscript.EncodeBIP276( - bscript.PrefixScript, - bscript.NetworkTestnet, - bscript.CurrentVersion, - []byte("fake script"), + bscript.BIP276{ + Prefix: bscript.PrefixScript, + Version: bscript.CurrentVersion, + Network: bscript.NetworkTestnet, + Data: []byte("fake script"), + }, ) assert.Equal(t, "bitcoin-script:020166616b65207363726970742577a444", s) @@ -35,10 +39,12 @@ func TestEncodeBIP276(t *testing.T) { t.Run("invalid version = 0", func(t *testing.T) { s := bscript.EncodeBIP276( - bscript.PrefixScript, - bscript.NetworkMainnet, - 0, - []byte("fake script"), + bscript.BIP276{ + Prefix: bscript.PrefixScript, + Version: 0, + Network: bscript.NetworkMainnet, + Data: []byte("fake script"), + }, ) assert.Equal(t, "ERROR", s) @@ -46,10 +52,12 @@ func TestEncodeBIP276(t *testing.T) { t.Run("invalid version > 255", func(t *testing.T) { s := bscript.EncodeBIP276( - bscript.PrefixScript, - bscript.NetworkMainnet, - 256, - []byte("fake script"), + bscript.BIP276{ + Prefix: bscript.PrefixScript, + Version: 256, + Network: bscript.NetworkMainnet, + Data: []byte("fake script"), + }, ) assert.Equal(t, "ERROR", s) @@ -57,21 +65,12 @@ func TestEncodeBIP276(t *testing.T) { t.Run("invalid network = 0", func(t *testing.T) { s := bscript.EncodeBIP276( - bscript.PrefixScript, - 0, - bscript.CurrentVersion, - []byte("fake script"), - ) - - assert.Equal(t, "ERROR", s) - }) - - t.Run("invalid version > 255", func(t *testing.T) { - s := bscript.EncodeBIP276( - bscript.PrefixScript, - 256, - bscript.CurrentVersion, - []byte("fake script"), + bscript.BIP276{ + Prefix: bscript.PrefixScript, + Version: bscript.CurrentVersion, + Network: 0, + Data: []byte("fake script"), + }, ) assert.Equal(t, "ERROR", s) @@ -79,10 +78,12 @@ func TestEncodeBIP276(t *testing.T) { t.Run("different prefix", func(t *testing.T) { s := bscript.EncodeBIP276( - "different-prefix", - bscript.NetworkMainnet, - bscript.CurrentVersion, - []byte("fake script"), + bscript.BIP276{ + Prefix: "different-prefix", + Version: bscript.CurrentVersion, + Network: bscript.NetworkMainnet, + Data: []byte("fake script"), + }, ) assert.Equal(t, "different-prefix:010166616b6520736372697074effdb090", s) @@ -90,10 +91,12 @@ func TestEncodeBIP276(t *testing.T) { t.Run("template prefix", func(t *testing.T) { s := bscript.EncodeBIP276( - bscript.PrefixTemplate, - bscript.NetworkMainnet, - bscript.CurrentVersion, - []byte("fake script"), + bscript.BIP276{ + Prefix: bscript.PrefixTemplate, + Version: bscript.CurrentVersion, + Network: bscript.NetworkMainnet, + Data: []byte("fake script"), + }, ) assert.Equal(t, "bitcoin-template:010166616b65207363726970749e31aa72", s) @@ -104,25 +107,23 @@ func TestDecodeBIP276(t *testing.T) { t.Parallel() t.Run("valid decode", func(t *testing.T) { - prefix, network, version, data, err := bscript.DecodeBIP276("bitcoin-script:010166616b65207363726970746f0cd86a") + script, err := bscript.DecodeBIP276("bitcoin-script:010166616b65207363726970746f0cd86a") assert.NoError(t, err) - assert.Equal(t, `"bitcoin-script"`, fmt.Sprintf("%q", prefix)) - assert.Equal(t, 1, network) - assert.Equal(t, 1, version) - assert.Equal(t, "fake script", string(data)) + assert.Equal(t, `"bitcoin-script"`, fmt.Sprintf("%q", script.Prefix)) + assert.Equal(t, 1, script.Network) + assert.Equal(t, 1, script.Version) + assert.Equal(t, "fake script", string(script.Data)) }) t.Run("invalid decode", func(t *testing.T) { - _, _, _, _, err := bscript.DecodeBIP276("bitcoin-script:01") + script, err := bscript.DecodeBIP276("bitcoin-script:01") assert.Error(t, err) + assert.Nil(t, script) }) t.Run("valid format, bad checksum", func(t *testing.T) { - prefix, network, version, data, err := bscript.DecodeBIP276("bitcoin-script:010166616b65207363726970746f0cd8") + script, err := bscript.DecodeBIP276("bitcoin-script:010166616b65207363726970746f0cd8") assert.Error(t, err) - assert.Equal(t, `"bitcoin-script"`, fmt.Sprintf("%q", prefix)) - assert.Equal(t, 1, network) - assert.Equal(t, 1, version) - assert.Equal(t, "fake scrip", string(data)) + assert.Nil(t, script) }) } From 3114ed39c20c44099ce7883d0cb4cffe19a6714f Mon Sep 17 00:00:00 2001 From: Mark Smith Date: Mon, 26 Jul 2021 16:48:43 +0100 Subject: [PATCH 5/8] adding output from script helper, tidying up some other areas and improving consistency with String and Bytes helpers --- bscript/script.go | 15 +++--- bscript/unlockingscript.go | 5 +- examples/create_tx/create_tx.go | 2 +- .../create_tx_with_opreturn.go | 2 +- localsigner.go | 7 ++- localsigner_test.go | 2 +- output.go | 4 +- tx.go | 22 ++++---- tx_test.go | 30 +++++------ txchange.go | 2 +- txchange_test.go | 36 ++++++------- txoutput.go | 23 +++++++- txoutput_test.go | 52 +++++++++++++++++-- 13 files changed, 130 insertions(+), 72 deletions(-) diff --git a/bscript/script.go b/bscript/script.go index 69e3fe6a..f8174dc5 100644 --- a/bscript/script.go +++ b/bscript/script.go @@ -47,11 +47,9 @@ func NewFromBytes(b []byte) *Script { // NewFromASM creates a new script from a BitCoin ASM formatted string. func NewFromASM(str string) (*Script, error) { - sections := strings.Split(str, " ") + s := Script{} - s := &Script{} - - for _, section := range sections { + for _, section := range strings.Split(str, " ") { if val, ok := opCodeStrings[section]; ok { s.AppendOpCode(val) } else { @@ -61,14 +59,13 @@ func NewFromASM(str string) (*Script, error) { } } - return s, nil + return &s, nil } // NewP2PKHFromPubKeyEC takes a public key hex string (in // compressed format) and creates a P2PKH script from it. func NewP2PKHFromPubKeyEC(pubKey *bec.PublicKey) (*Script, error) { - pubKeyBytes := pubKey.SerialiseCompressed() - return NewP2PKHFromPubKeyBytes(pubKeyBytes) + return NewP2PKHFromPubKeyBytes(pubKey.SerialiseCompressed()) } // NewP2PKHFromPubKeyStr takes a public key hex string (in @@ -200,8 +197,8 @@ func (s *Script) AppendOpCode(o uint8) *Script { return s } -// ToString returns hex string of script. -func (s *Script) String() string { // TODO: change to HexString? +// String implements the stringer interface and returns the hex string of script. +func (s *Script) String() string { return hex.EncodeToString(*s) } diff --git a/bscript/unlockingscript.go b/bscript/unlockingscript.go index f5c61e6a..0a3c0db4 100644 --- a/bscript/unlockingscript.go +++ b/bscript/unlockingscript.go @@ -5,11 +5,10 @@ import "github.com/libsv/go-bt/sighash" // NewP2PKHUnlockingScript creates a new unlocking script which spends // a P2PKH locking script from a public key, a signature, and // a SIGHASH flag. -func NewP2PKHUnlockingScript(pubKey []byte, sig []byte, - sigHashFlag sighash.Flag) (*Script, error) { +func NewP2PKHUnlockingScript(pubKey []byte, sig []byte, sigHashFlag sighash.Flag) (*Script, error) { // append SIGHASH to DER sig - sigBuf := []byte{} + sigBuf := make([]byte, 0) sigBuf = append(sigBuf, sig...) sigBuf = append(sigBuf, uint8(sigHashFlag)) diff --git a/examples/create_tx/create_tx.go b/examples/create_tx/create_tx.go index 709f6680..4c7303f3 100644 --- a/examples/create_tx/create_tx.go +++ b/examples/create_tx/create_tx.go @@ -17,7 +17,7 @@ func main() { "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac6a0568656c6c6f", 1500) - _ = tx.PayTo("1NRoySJ9Lvby6DuE2UQYnyT67AASwNZxGb", 1000) + _ = tx.PayToAddress("1NRoySJ9Lvby6DuE2UQYnyT67AASwNZxGb", 1000) wif, _ := wif.DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") diff --git a/examples/create_tx_with_opreturn/create_tx_with_opreturn.go b/examples/create_tx_with_opreturn/create_tx_with_opreturn.go index 14f33bce..0aecb9c4 100644 --- a/examples/create_tx_with_opreturn/create_tx_with_opreturn.go +++ b/examples/create_tx_with_opreturn/create_tx_with_opreturn.go @@ -17,7 +17,7 @@ func main() { "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac", 1000) - _ = tx.PayTo("1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL", 900) + _ = tx.PayToAddress("1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL", 900) _ = tx.AddOpReturnOutput([]byte("You are using go-bt!")) diff --git a/localsigner.go b/localsigner.go index 66faf93d..5ffc8c03 100644 --- a/localsigner.go +++ b/localsigner.go @@ -7,8 +7,8 @@ import ( "github.com/libsv/go-bt/sighash" ) -// LocalSigner implements the Signer interface. It is used to sign a Tx locally -// using a bkec PrivateKey. +// LocalSigner implements the Signer interface. It is used to sign Tx inputs locally +// using a bkec PrivateKey, any input found that can be signed by the key will be signed. type LocalSigner struct { PrivateKey *bec.PrivateKey } @@ -32,8 +32,7 @@ func (is *LocalSigner) Sign(ctx context.Context, unsignedTx *Tx, index uint32, // SignHash a transaction at a given a hash digest using the PrivateKey passed in through the // InternalSigner struct. -func (is *LocalSigner) SignHash(ctx context.Context, hash []byte) (publicKey []byte, signature []byte, err error) { - +func (is *LocalSigner) SignHash(ctx context.Context, hash []byte) (publicKey, signature []byte, err error) { sig, err := is.PrivateKey.Sign(hash) if err != nil { return diff --git a/localsigner_test.go b/localsigner_test.go index 3372a466..f677fa9b 100644 --- a/localsigner_test.go +++ b/localsigner_test.go @@ -34,7 +34,7 @@ func TestInternalSigner_SignAuto(t *testing.T) { assert.NoError(t, err) expectedSignedTx := "010000000193a35408b6068499e0d5abd799d3e827d9bfe70c9b75ebe209c91d2507232651000000006b483045022100c1d77036dc6cd1f3fa1214b0688391ab7f7a16cd31ea4e5a1f7a415ef167df820220751aced6d24649fa235132f1e6969e163b9400f80043a72879237dab4a1190ad412103b8b40a84123121d260f5c109bc5a46ec819c2e4002e5ba08638783bfb4e01435ffffffff02404b4c00000000001976a91404ff367be719efa79d76e4416ffb072cd53b208888acde94a905000000001976a91404d03f746652cfcb6cb55119ab473a045137d26588ac00000000" - assert.Equal(t, expectedSignedTx, hex.EncodeToString(tx.ToBytes())) + assert.Equal(t, expectedSignedTx, hex.EncodeToString(tx.Bytes())) // TODO: what is this for? // if unsignedTx == expectedSignedTx { diff --git a/output.go b/output.go index 2c4d6262..487d5013 100644 --- a/output.go +++ b/output.go @@ -113,8 +113,8 @@ script: %s `, o.Satoshis, len(*o.LockingScript), o.LockingScript) } -// ToBytes encodes the Output into a byte array. -func (o *Output) ToBytes() []byte { +// Bytes encodes the Output into a byte array. +func (o *Output) Bytes() []byte { b := make([]byte, 8) binary.LittleEndian.PutUint64(b, o.Satoshis) diff --git a/tx.go b/tx.go index 7fa59854..fd6c83e7 100644 --- a/tx.go +++ b/tx.go @@ -75,7 +75,7 @@ func (tx *Tx) MarshalJSON() ([]byte, error) { Outputs: tx.outputs, TxID: tx.TxID(), Hash: tx.TxID(), - Size: len(tx.ToBytes()), + Size: len(tx.Bytes()), Hex: tx.String(), } return json.Marshal(txj) @@ -249,21 +249,21 @@ func (tx *Tx) IsCoinbase() bool { return false } -// TxIDAsBytes returns the transaction ID of the transaction as bytes +// TxIDBytes returns the transaction ID of the transaction as bytes // (which is also the transaction hash). -func (tx *Tx) TxIDAsBytes() []byte { - return ReverseBytes(crypto.Sha256d(tx.ToBytes())) +func (tx *Tx) TxIDBytes() []byte { + return ReverseBytes(crypto.Sha256d(tx.Bytes())) } // TxID returns the transaction ID of the transaction // (which is also the transaction hash). func (tx *Tx) TxID() string { - return hex.EncodeToString(ReverseBytes(crypto.Sha256d(tx.ToBytes()))) + return hex.EncodeToString(ReverseBytes(crypto.Sha256d(tx.Bytes()))) } // String encodes the transaction into a hex string. func (tx *Tx) String() string { - return hex.EncodeToString(tx.ToBytes()) + return hex.EncodeToString(tx.Bytes()) } // IsValidTxID will check that the txid bytes are valid. @@ -279,15 +279,15 @@ func IsValidTxID(txid []byte) bool { return true } -// ToBytes encodes the transaction into a byte array. +// Bytes encodes the transaction into a byte array. // See https://chainquery.com/bitcoin-cli/decoderawtransaction -func (tx *Tx) ToBytes() []byte { +func (tx *Tx) Bytes() []byte { return tx.toBytesHelper(0, nil) } -// ToBytesWithClearedInputs encodes the transaction into a byte array but clears its inputs first. +// BytesWithClearedInputs encodes the transaction into a byte array but clears its inputs first. // This is used when signing transactions. -func (tx *Tx) ToBytesWithClearedInputs(index int, lockingScript []byte) []byte { +func (tx *Tx) BytesWithClearedInputs(index int, lockingScript []byte) []byte { return tx.toBytesHelper(index, lockingScript) } @@ -310,7 +310,7 @@ func (tx *Tx) toBytesHelper(index int, lockingScript []byte) []byte { h = append(h, VarInt(uint64(len(tx.outputs)))...) for _, out := range tx.outputs { - h = append(h, out.ToBytes()...) + h = append(h, out.Bytes()...) } lt := make([]byte, 4) diff --git a/tx_test.go b/tx_test.go index 80f567ec..e0c5429a 100644 --- a/tx_test.go +++ b/tx_test.go @@ -202,7 +202,7 @@ func TestTx_CreateTx(t *testing.T) { 2000000) assert.NoError(t, err) - err = tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) + err = tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) assert.NoError(t, err) var wif *WIF @@ -228,7 +228,7 @@ func TestTx_HasDataOutputs(t *testing.T) { 2000000) assert.NoError(t, err) - err = tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) + err = tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) assert.NoError(t, err) // Add op return data @@ -260,7 +260,7 @@ func TestTx_HasDataOutputs(t *testing.T) { 2000000) assert.NoError(t, err) - err = tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) + err = tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) assert.NoError(t, err) var wif *WIF @@ -296,7 +296,7 @@ func TestTx_JSON(t *testing.T) { 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 2000000)) - assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) + assert.NoError(t, tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) var wif *WIF wif, err := DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") assert.NoError(t, err) @@ -314,7 +314,7 @@ func TestTx_JSON(t *testing.T) { 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 2000000)) - assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) + assert.NoError(t, tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) var wif *WIF wif, err := DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") assert.NoError(t, err) @@ -415,7 +415,7 @@ func TestTx_MarshallJSON(t *testing.T) { 114, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 10000)) - assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) + assert.NoError(t, tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) var w *wif.WIF w, err := wif.DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") assert.NoError(t, err) @@ -658,9 +658,9 @@ func TestTx_OutputIdx(t *testing.T) { "tx with 3 outputs and output idx 0 requested should return output": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.PayTo("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) - assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) - assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.PayToAddress("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) + assert.NoError(t, tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) return tx }(), idx: 0, @@ -675,9 +675,9 @@ func TestTx_OutputIdx(t *testing.T) { }, "tx with 3 outputs and output idx 2 requested should return output": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.PayTo("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) - assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) - assert.NoError(t, tx.PayTo("mywmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.PayToAddress("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) + assert.NoError(t, tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.PayToAddress("mywmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) return tx }(), idx: 2, @@ -692,9 +692,9 @@ func TestTx_OutputIdx(t *testing.T) { }, "tx with 3 outputs and output idx 5 requested should return nil": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.PayTo("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) - assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) - assert.NoError(t, tx.PayTo("mywmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.PayToAddress("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) + assert.NoError(t, tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) + assert.NoError(t, tx.PayToAddress("mywmGVP89x3DsLNqk3NvctfQy9m9pvt7mz", 1000)) return tx }(), idx: 5, diff --git a/txchange.go b/txchange.go index cd8fe559..a877b612 100644 --- a/txchange.go +++ b/txchange.go @@ -162,6 +162,6 @@ func (tx *Tx) getStandardAndDataBytes() (standardBytes, dataBytes int) { } } - standardBytes = len(tx.ToBytes()) - dataBytes + standardBytes = len(tx.Bytes()) - dataBytes return } diff --git a/txchange_test.go b/txchange_test.go index e10b835c..09888aa3 100644 --- a/txchange_test.go +++ b/txchange_test.go @@ -136,7 +136,7 @@ func TestTx_Change(t *testing.T) { assert.NoError(t, err) // pay to - err = tx.PayTo("1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL", 500) + err = tx.PayToAddress("1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL", 500) assert.NoError(t, err) // add some op return @@ -162,7 +162,7 @@ func TestTx_Change(t *testing.T) { feePaid := tx.TotalInputSatoshis() - tx.TotalOutputSatoshis() assert.Equal(t, uint64(122), feePaid) - txSize := len(tx.ToBytes()) + txSize := len(tx.Bytes()) assert.Equal(t, 251, txSize) feeRate := float64(feePaid) / float64(txSize) @@ -207,10 +207,10 @@ func TestTx_Change(t *testing.T) { 4000000) assert.NoError(t, err) - err = tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 1000000) + err = tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 1000000) assert.NoError(t, err) - err = tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 3000000) + err = tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 3000000) assert.NoError(t, err) err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.DefaultFees()) @@ -241,10 +241,10 @@ func TestTx_Change(t *testing.T) { 4000000) assert.NoError(t, err) - err = tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 999995) + err = tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 999995) assert.NoError(t, err) - err = tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 3000000) + err = tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 3000000) assert.NoError(t, err) err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.DefaultFees()) @@ -319,7 +319,7 @@ func TestTx_ChangeToOutput(t *testing.T) { 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 1000)) + assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 1000)) return tx }(), index: 0, @@ -335,7 +335,7 @@ func TestTx_ChangeToOutput(t *testing.T) { 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) return tx }(), index: 0, @@ -351,10 +351,10 @@ func TestTx_ChangeToOutput(t *testing.T) { 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 2500)) - assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) - assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) - assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) - assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) return tx }(), index: 3, @@ -370,7 +370,7 @@ func TestTx_ChangeToOutput(t *testing.T) { 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) return tx }(), index: 1, @@ -407,7 +407,7 @@ func TestTx_CalculateChange(t *testing.T) { 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) return tx }(), fees: bt.DefaultFees(), @@ -420,10 +420,10 @@ func TestTx_CalculateChange(t *testing.T) { 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 2500)) - assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) - assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) - assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) - assert.NoError(t, tx.PayTo("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) + assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) return tx }(), fees: bt.DefaultFees(), diff --git a/txoutput.go b/txoutput.go index 56d8e295..adac4cfb 100644 --- a/txoutput.go +++ b/txoutput.go @@ -3,10 +3,11 @@ package bt import ( "encoding/binary" "encoding/hex" + "errors" "fmt" - "github.com/libsv/go-bt/bscript" "github.com/libsv/go-bk/crypto" + "github.com/libsv/go-bt/bscript" ) // NewOutputFromBytes returns a transaction Output from the bytes provided @@ -97,6 +98,18 @@ func (tx *Tx) AddP2PKHOutputFromAddress(addr string, satoshis uint64) error { return nil } +// AddP2PKHOutputFromScript makes an output to a P2PKH script paid to the provided locking script with a value. +func (tx *Tx) AddP2PKHOutputFromScript(script *bscript.Script, satoshis uint64) error { + if !script.IsP2PKH() { + return errors.New("script is not a valid P2PKH script") + } + tx.AddOutput(&Output{ + Satoshis: satoshis, + LockingScript: script, + }) + return nil +} + // AddHashPuzzleOutput makes an output to a hash puzzle + PKH with a value. func (tx *Tx) AddHashPuzzleOutput(secret, publicKeyHash string, satoshis uint64) error { publicKeyHashBytes, err := hex.DecodeString(publicKeyHash) @@ -177,6 +190,12 @@ func (tx *Tx) AddOutput(output *Output) { // PayTo creates a new P2PKH output from a BitCoin address (base58) // and the satoshis amount and adds that to the transaction. -func (tx *Tx) PayTo(addr string, satoshis uint64) error { +func (tx *Tx) PayTo(script *bscript.Script, satoshis uint64) error { + return tx.AddP2PKHOutputFromScript(script, satoshis) +} + +// PayToAddress creates a new P2PKH output from a BitCoin address (base58) +// and the satoshis amount and adds that to the transaction. +func (tx *Tx) PayToAddress(addr string, satoshis uint64) error { return tx.AddP2PKHOutputFromAddress(addr, satoshis) } diff --git a/txoutput_test.go b/txoutput_test.go index 9e41bd87..157f334f 100644 --- a/txoutput_test.go +++ b/txoutput_test.go @@ -2,6 +2,7 @@ package bt_test import ( "encoding/hex" + "errors" "fmt" "testing" @@ -134,7 +135,7 @@ func TestTx_TotalOutputSatoshis(t *testing.T) { }) } -func TestTx_PayTo(t *testing.T) { +func TestTx_PayToAddress(t *testing.T) { t.Run("missing pay to address", func(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) @@ -145,7 +146,7 @@ func TestTx_PayTo(t *testing.T) { 4000000) assert.NoError(t, err) - err = tx.PayTo("", 100) + err = tx.PayToAddress("", 100) assert.Error(t, err) }) @@ -159,7 +160,7 @@ func TestTx_PayTo(t *testing.T) { 4000000) assert.NoError(t, err) - err = tx.PayTo("1234567", 100) + err = tx.PayToAddress("1234567", 100) assert.Error(t, err) }) @@ -173,8 +174,51 @@ func TestTx_PayTo(t *testing.T) { 4000000) assert.NoError(t, err) - err = tx.PayTo("1GHMW7ABrFma2NSwiVe9b9bZxkMB7tuPZi", 100) + err = tx.PayToAddress("1GHMW7ABrFma2NSwiVe9b9bZxkMB7tuPZi", 100) assert.NoError(t, err) assert.Equal(t, 1, tx.OutputCount()) }) } + +func TestTx_PayTo(t *testing.T) { + t.Parallel() + tests := map[string]struct { + script *bscript.Script + err error + }{ + "valid p2pkh script should create valid output": { + script: func() *bscript.Script { + s, err := bscript.NewP2PKHFromAddress("1GHMW7ABrFma2NSwiVe9b9bZxkMB7tuPZi") + assert.NoError(t, err) + return s + }(), + err: nil, + }, "empty p2pkh script should return error": { + script: &bscript.Script{}, + err: errors.New("script is not a valid P2PKH script"), + }, "non p2pkh script should return error": { + script: bscript.NewFromBytes([]byte("test")), + err: errors.New("script is not a valid P2PKH script"), + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + tx := bt.NewTx() + assert.NotNil(t, tx) + err := tx.From( + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) + assert.NoError(t, err) + err = tx.PayTo(test.script, 100) + if test.err == nil { + assert.NoError(t, err) + assert.Equal(t, 1, tx.OutputCount()) + return + } + assert.EqualError(t, err, test.err.Error()) + assert.Equal(t, 0, tx.OutputCount()) + }) + } +} From c8e23ab004070ea866966a15c2bec92394fd962d Mon Sep 17 00:00:00 2001 From: Mark Smith Date: Mon, 26 Jul 2021 16:52:56 +0100 Subject: [PATCH 6/8] rename input ToBytes to just Bytes --- input.go | 4 ++-- tx.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/input.go b/input.go index bdb1ff13..b4bd93ab 100644 --- a/input.go +++ b/input.go @@ -152,8 +152,8 @@ sequence: %x ) } -// ToBytes encodes the Input into a hex byte array. -func (i *Input) ToBytes(clear bool) []byte { +// Bytes encodes the Input into a hex byte array. +func (i *Input) Bytes(clear bool) []byte { h := make([]byte, 0) h = append(h, ReverseBytes(i.previousTxID)...) diff --git a/tx.go b/tx.go index fd6c83e7..08f8bff9 100644 --- a/tx.go +++ b/tx.go @@ -299,7 +299,7 @@ func (tx *Tx) toBytesHelper(index int, lockingScript []byte) []byte { h = append(h, VarInt(uint64(len(tx.inputs)))...) for i, in := range tx.inputs { - s := in.ToBytes(lockingScript != nil) + s := in.Bytes(lockingScript != nil) if i == index && lockingScript != nil { h = append(h, VarInt(uint64(len(lockingScript)))...) h = append(h, lockingScript...) From e43069859a2bb6618f718253ba72fc5fdc2855c8 Mon Sep 17 00:00:00 2001 From: Mark Smith Date: Mon, 26 Jul 2021 16:54:28 +0100 Subject: [PATCH 7/8] changing test matric to 1.15 and 1.16 --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5665465a..90207913 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -17,7 +17,7 @@ jobs: test: strategy: matrix: - go-version: [ 1.14.x, 1.15.x ] + go-version: [ 1.15.x,1.16.x, ] os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} steps: From 25d921a9c750ce91bbe618b41896f519e1a6cdcb Mon Sep 17 00:00:00 2001 From: Mark Smith Date: Tue, 27 Jul 2021 15:37:36 +0100 Subject: [PATCH 8/8] adding changes suggested in PR. Making Inputs and Outputs public rather than private. --- input.go | 6 +--- localsigner.go | 4 +-- signaturehash.go | 8 +++--- signaturehash_test.go | 4 +-- signer.go | 4 +-- tx.go | 64 ++++++++++++++++--------------------------- tx_test.go | 34 +++++++++++------------ txchange.go | 16 +++++------ txchange_test.go | 24 ++++++++-------- txinput.go | 14 +++++----- txinput_test.go | 4 +-- txoutput.go | 8 +++--- txoutput_test.go | 14 +++++----- txsign.go | 10 +++---- 14 files changed, 97 insertions(+), 117 deletions(-) diff --git a/input.go b/input.go index b4bd93ab..d938ff8a 100644 --- a/input.go +++ b/input.go @@ -117,11 +117,7 @@ func (i *Input) PreviousTxIDAddStr(txID string) error { if err != nil { return err } - if !IsValidTxID(bb) { - return ErrInvalidTxID - } - i.previousTxID = bb - return nil + return i.PreviousTxIDAdd(bb) } // PreviousTxID will return the PreviousTxID if set. diff --git a/localsigner.go b/localsigner.go index 5ffc8c03..65f314d0 100644 --- a/localsigner.go +++ b/localsigner.go @@ -7,8 +7,8 @@ import ( "github.com/libsv/go-bt/sighash" ) -// LocalSigner implements the Signer interface. It is used to sign Tx inputs locally -// using a bkec PrivateKey, any input found that can be signed by the key will be signed. +// LocalSigner implements the Signer interface. It is used to sign Tx Inputs locally +// using a bkec PrivateKey. type LocalSigner struct { PrivateKey *bec.PrivateKey } diff --git a/signaturehash.go b/signaturehash.go index 706a633e..52ef8d44 100644 --- a/signaturehash.go +++ b/signaturehash.go @@ -115,7 +115,7 @@ func (tx *Tx) CalcInputPreimage(inputNumber uint32, sigHashFlag sighash.Flag) ([ func (tx *Tx) getPreviousOutHash() []byte { buf := make([]byte, 0) - for _, in := range tx.Inputs() { + for _, in := range tx.Inputs { buf = append(buf, ReverseBytes(in.PreviousTxID())...) oi := make([]byte, 4) binary.LittleEndian.PutUint32(oi, in.PreviousTxOutIndex) @@ -128,7 +128,7 @@ func (tx *Tx) getPreviousOutHash() []byte { func (tx *Tx) getSequenceHash() []byte { buf := make([]byte, 0) - for _, in := range tx.Inputs() { + for _, in := range tx.Inputs { oi := make([]byte, 4) binary.LittleEndian.PutUint32(oi, in.SequenceNumber) buf = append(buf, oi...) @@ -141,11 +141,11 @@ func (tx *Tx) getOutputsHash(n int32) []byte { buf := make([]byte, 0) if n == -1 { - for _, out := range tx.Outputs() { + for _, out := range tx.Outputs { buf = append(buf, out.BytesForSigHash()...) } } else { - buf = append(buf, tx.Outputs()[n].BytesForSigHash()...) + buf = append(buf, tx.Outputs[n].BytesForSigHash()...) } return crypto.Sha256d(buf) diff --git a/signaturehash_test.go b/signaturehash_test.go index 8258a026..f3d1bbdf 100644 --- a/signaturehash_test.go +++ b/signaturehash_test.go @@ -124,8 +124,8 @@ func TestTx_CalcInputSignatureHash(t *testing.T) { assert.NotNil(t, tx) // Add the UTXO amount and script (PreviousTxScript already in unsiged tx) - tx.Inputs()[test.index].PreviousTxSatoshis = test.previousTxSatoshis - tx.Inputs()[test.index].PreviousTxScript, err = bscript.NewFromHexString(test.previousTxScript) + tx.Inputs[test.index].PreviousTxSatoshis = test.previousTxSatoshis + tx.Inputs[test.index].PreviousTxScript, err = bscript.NewFromHexString(test.previousTxScript) assert.NoError(t, err) var actualSigHash []byte diff --git a/signer.go b/signer.go index 6b433bd5..d8942070 100644 --- a/signer.go +++ b/signer.go @@ -25,8 +25,8 @@ type Signer interface { // canonical in accordance with RFC6979 and BIP0062. // // To automatically sign, the PublicKey() method must also be implemented in order to -// use the public key to check which inputs can be signed for before signing. +// use the public key to check which Inputs can be signed for before signing. type AutoSigner interface { Signer - PublicKey(ctx context.Context ) (publicKey []byte) + PublicKey(ctx context.Context) (publicKey []byte) } diff --git a/tx.go b/tx.go index 08f8bff9..50a39aae 100644 --- a/tx.go +++ b/tx.go @@ -20,12 +20,12 @@ Version no currently 1 In-counter positive integer VI = VarInt 1 - 9 bytes -list of inputs the first input of the first transaction is also called "coinbase" -many inputs +list of Inputs the first input of the first transaction is also called "coinbase" -many Inputs (its content was ignored in earlier versions) Out-counter positive integer VI = VarInt 1 - 9 bytes -list of outputs the outputs of the first transaction spend the mined -many outputs +list of Outputs the Outputs of the first transaction spend the mined -many Outputs bitcoins for the block lock_time if non-zero and sequence numbers are < 0xFFFFFFFF: block height or 4 bytes @@ -43,8 +43,8 @@ var ( // DO NOT CHANGE ORDER - Optimised memory via malign // type Tx struct { - inputs []*Input - outputs []*Output + Inputs []*Input + Outputs []*Output Version uint32 LockTime uint32 } @@ -65,14 +65,14 @@ func (tx *Tx) MarshalJSON() ([]byte, error) { if tx == nil { return nil, errors.New("tx is nil so cannot be marshalled") } - for i, o := range tx.outputs { + for i, o := range tx.Outputs { o.index = i } txj := txJSON{ Version: tx.Version, LockTime: tx.LockTime, - Inputs: tx.inputs, - Outputs: tx.outputs, + Inputs: tx.Inputs, + Outputs: tx.Outputs, TxID: tx.TxID(), Hash: tx.TxID(), Size: len(tx.Bytes()), @@ -96,8 +96,8 @@ func (tx *Tx) UnmarshalJSON(b []byte) error { *tx = *t return nil } - tx.inputs = txj.Inputs - tx.outputs = txj.Outputs + tx.Inputs = txj.Inputs + tx.Outputs = txj.Outputs tx.LockTime = txj.LockTime tx.Version = txj.Version return nil @@ -152,7 +152,7 @@ func NewTxFromStream(b []byte) (*Tx, int, error) { inputCount, size := DecodeVarInt(b[offset:]) offset += size - // create inputs + // create Inputs var i uint64 var err error var input *Input @@ -165,7 +165,7 @@ func NewTxFromStream(b []byte) (*Tx, int, error) { t.addInput(input) } - // create outputs + // create Outputs var outputCount uint64 var output *Output outputCount, size = DecodeVarInt(b[offset:]) @@ -189,7 +189,7 @@ func NewTxFromStream(b []byte) (*Tx, int, error) { // HasDataOutputs returns true if the transaction has // at least one data (OP_RETURN) output in it. func (tx *Tx) HasDataOutputs() bool { - for _, out := range tx.Outputs() { + for _, out := range tx.Outputs { if out.LockingScript.IsData() { return true } @@ -197,11 +197,6 @@ func (tx *Tx) HasDataOutputs() bool { return false } -// Inputs returns the inputs for the transaction. -func (tx *Tx) Inputs() []*Input { - return tx.inputs -} - // InputIdx will return the input at the specified index. // // This will consume an overflow error and simply return nil if the input @@ -210,12 +205,7 @@ func (tx *Tx) InputIdx(i int) *Input { if i > tx.InputCount()-1 { return nil } - return tx.inputs[i] -} - -// Outputs returns the outputs for the transaction. -func (tx *Tx) Outputs() []*Output { - return tx.outputs + return tx.Inputs[i] } // OutputIdx will return the output at the specified index. @@ -226,23 +216,23 @@ func (tx *Tx) OutputIdx(i int) *Output { if i > tx.OutputCount()-1 { return nil } - return tx.outputs[i] + return tx.Outputs[i] } // IsCoinbase determines if this transaction is a coinbase by // checking if the tx input is a standard coinbase input. func (tx *Tx) IsCoinbase() bool { - if len(tx.inputs) != 1 { + if len(tx.Inputs) != 1 { return false } cbi := make([]byte, 32) - if !bytes.Equal(tx.inputs[0].PreviousTxID(), cbi) { + if !bytes.Equal(tx.Inputs[0].PreviousTxID(), cbi) { return false } - if tx.inputs[0].PreviousTxOutIndex == DefaultSequenceNumber || tx.inputs[0].SequenceNumber == DefaultSequenceNumber { + if tx.Inputs[0].PreviousTxOutIndex == DefaultSequenceNumber || tx.Inputs[0].SequenceNumber == DefaultSequenceNumber { return true } @@ -268,15 +258,9 @@ func (tx *Tx) String() string { // IsValidTxID will check that the txid bytes are valid. // -// A txid should be in hexadecimal and be of 32 bytes length. +// A txid should be of 32 bytes length. func IsValidTxID(txid []byte) bool { - if len(txid) != 32 { - return false - } - if s := hex.EncodeToString(txid); s == "" { - return false - } - return true + return len(txid) == 32 } // Bytes encodes the transaction into a byte array. @@ -285,7 +269,7 @@ func (tx *Tx) Bytes() []byte { return tx.toBytesHelper(0, nil) } -// BytesWithClearedInputs encodes the transaction into a byte array but clears its inputs first. +// BytesWithClearedInputs encodes the transaction into a byte array but clears its Inputs first. // This is used when signing transactions. func (tx *Tx) BytesWithClearedInputs(index int, lockingScript []byte) []byte { return tx.toBytesHelper(index, lockingScript) @@ -296,9 +280,9 @@ func (tx *Tx) toBytesHelper(index int, lockingScript []byte) []byte { h = append(h, LittleEndianBytes(tx.Version, 4)...) - h = append(h, VarInt(uint64(len(tx.inputs)))...) + h = append(h, VarInt(uint64(len(tx.Inputs)))...) - for i, in := range tx.inputs { + for i, in := range tx.Inputs { s := in.Bytes(lockingScript != nil) if i == index && lockingScript != nil { h = append(h, VarInt(uint64(len(lockingScript)))...) @@ -308,8 +292,8 @@ func (tx *Tx) toBytesHelper(index int, lockingScript []byte) []byte { } } - h = append(h, VarInt(uint64(len(tx.outputs)))...) - for _, out := range tx.outputs { + h = append(h, VarInt(uint64(len(tx.Outputs)))...) + for _, out := range tx.Outputs { h = append(h, out.Bytes()...) } diff --git a/tx_test.go b/tx_test.go index e0c5429a..948946e9 100644 --- a/tx_test.go +++ b/tx_test.go @@ -34,7 +34,7 @@ func TestNewTx(t *testing.T) { func TestNewTxFromString(t *testing.T) { t.Parallel() - t.Run("valid tx no inputs", func(t *testing.T) { + t.Run("valid tx no Inputs", func(t *testing.T) { tx, err := bt.NewTxFromString("01000000000100000000000000001a006a07707265666978310c6578616d706c65206461746102133700000000") assert.NoError(t, err) assert.NotNil(t, tx) @@ -58,10 +58,10 @@ func TestNewTxFromString(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, tx) - // Check version, locktime, inputs + // Check version, locktime, Inputs assert.Equal(t, uint32(2), tx.Version) assert.Equal(t, uint32(0), tx.LockTime) - assert.Equal(t, 1, len(tx.Inputs())) + assert.Equal(t, 1, len(tx.Inputs)) // Create a new unlocking script //ptid, _ := hex.DecodeString("9c5b1428aaad5e9b0196c89be8628b366f33c7b22933da0489b921d487a7cb1c") @@ -78,7 +78,7 @@ func TestNewTxFromString(t *testing.T) { assert.Equal(t, tx.InputIdx(0), i) // Check output - assert.Equal(t, 1, len(tx.Outputs())) + assert.Equal(t, 1, len(tx.Outputs)) // New output var ls *bscript.Script @@ -88,7 +88,7 @@ func TestNewTxFromString(t *testing.T) { // Check the type o := bt.Output{Satoshis: 4999000000, LockingScript: ls} - assert.Equal(t, true, reflect.DeepEqual(*tx.Outputs()[0], o)) + assert.Equal(t, true, reflect.DeepEqual(*tx.Outputs[0], o)) }) } @@ -148,7 +148,7 @@ func TestVersion(t *testing.T) { func TestTx_IsCoinbase(t *testing.T) { t.Parallel() - t.Run("invalid number of inputs", func(t *testing.T) { + t.Run("invalid number of Inputs", func(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) assert.Equal(t, false, tx.IsCoinbase()) @@ -217,7 +217,7 @@ func TestTx_CreateTx(t *testing.T) { func TestTx_HasDataOutputs(t *testing.T) { t.Parallel() - t.Run("has data outputs", func(t *testing.T) { + t.Run("has data Outputs", func(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) @@ -249,7 +249,7 @@ func TestTx_HasDataOutputs(t *testing.T) { assert.Equal(t, true, tx.HasDataOutputs()) }) - t.Run("no data outputs", func(t *testing.T) { + t.Run("no data Outputs", func(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) @@ -397,7 +397,7 @@ func TestTx_MarshallJSON(t *testing.T) { } ] }`, - }, "transaction with multiple inputs": { + }, "transaction with multiple Inputs": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( @@ -655,7 +655,7 @@ func TestTx_OutputIdx(t *testing.T) { idx int expOutput *bt.Output }{ - "tx with 3 outputs and output idx 0 requested should return output": { + "tx with 3 Outputs and output idx 0 requested should return output": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.PayToAddress("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) @@ -672,7 +672,7 @@ func TestTx_OutputIdx(t *testing.T) { return s }(), }, - }, "tx with 3 outputs and output idx 2 requested should return output": { + }, "tx with 3 Outputs and output idx 2 requested should return output": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.PayToAddress("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) @@ -689,7 +689,7 @@ func TestTx_OutputIdx(t *testing.T) { return s }(), }, - }, "tx with 3 outputs and output idx 5 requested should return nil": { + }, "tx with 3 Outputs and output idx 5 requested should return nil": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.PayToAddress("myUmQeCYxQECGHXbupe539n41u6BTBz1Eh", 1000)) @@ -699,7 +699,7 @@ func TestTx_OutputIdx(t *testing.T) { }(), idx: 5, expOutput: nil, - }, "tx with 0 outputs and output idx 5 requested should return nil": { + }, "tx with 0 Outputs and output idx 5 requested should return nil": { tx: func() *bt.Tx { return bt.NewTx() }(), @@ -722,7 +722,7 @@ func TestTx_InputIdx(t *testing.T) { idx int expInput *bt.Input }{ - "tx with 3 inputs and input idx 0 requested should return correct input": { + "tx with 3 Inputs and input idx 0 requested should return correct input": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( @@ -757,7 +757,7 @@ func TestTx_InputIdx(t *testing.T) { _ = in.PreviousTxIDAddStr("3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5") return in }(), - }, "tx with 3 outputs and output idx 2 requested should return output": { + }, "tx with 3 Outputs and output idx 2 requested should return output": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( @@ -792,7 +792,7 @@ func TestTx_InputIdx(t *testing.T) { _ = in.PreviousTxIDAddStr("3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdac4") return in }(), - }, "tx with 3 outputs and output idx 5 requested should return nil": { + }, "tx with 3 Outputs and output idx 5 requested should return nil": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( @@ -814,7 +814,7 @@ func TestTx_InputIdx(t *testing.T) { }(), idx: 5, expInput: nil, - }, "tx with 0 outputs and output idx 5 requested should return nil": { + }, "tx with 0 Outputs and output idx 5 requested should return nil": { tx: func() *bt.Tx { return bt.NewTx() }(), diff --git a/txchange.go b/txchange.go index a877b612..5f89bf7b 100644 --- a/txchange.go +++ b/txchange.go @@ -26,7 +26,7 @@ func (tx *Tx) Change(s *bscript.Script, f []*Fee) error { } if hasChange { // add rest of available sats to the change output - tx.Outputs()[tx.OutputCount()-1].Satoshis = available + tx.Outputs[tx.OutputCount()-1].Satoshis = available } return nil } @@ -35,14 +35,14 @@ func (tx *Tx) Change(s *bscript.Script, f []*Fee) error { // If an invalid index is supplied and error is returned. func (tx *Tx) ChangeToExistingOutput(index uint, f []*Fee) error { if int(index) > tx.OutputCount()-1 { - return errors.New("index is greater than number of inputs in transaction") + return errors.New("index is greater than number of Inputs in transaction") } - available, hasChange, err := tx.change(tx.Outputs()[index].LockingScript, f, false) + available, hasChange, err := tx.change(tx.Outputs[index].LockingScript, f, false) if err != nil { return err } if hasChange { - tx.Outputs()[index].Satoshis += available + tx.Outputs[index].Satoshis += available } return nil } @@ -101,7 +101,7 @@ func (tx *Tx) canAddChange(available uint64, standardFees *Fee) bool { varIntUpper := VarIntUpperLimitInc(uint64(tx.OutputCount())) if varIntUpper == -1 { - return false // upper limit of outputs in one tx reached + return false // upper limit of Outputs in one tx reached } changeOutputFee := uint64(varIntUpper) @@ -144,7 +144,7 @@ func (tx *Tx) getExpectedUnlockingScriptFees(f []*Fee) (uint64, error) { var expectedBytes int - for _, in := range tx.Inputs() { + for _, in := range tx.Inputs { if !in.PreviousTxScript.IsP2PKH() { return 0, errors.New("non-P2PKH input used in the tx - unsupported") } @@ -155,8 +155,8 @@ func (tx *Tx) getExpectedUnlockingScriptFees(f []*Fee) (uint64, error) { } func (tx *Tx) getStandardAndDataBytes() (standardBytes, dataBytes int) { - // Subtract the value of each output as well as keeping track of data outputs - for _, out := range tx.Outputs() { + // Subtract the value of each output as well as keeping track of data Outputs + for _, out := range tx.Outputs { if out.LockingScript.IsData() && len(*out.LockingScript) > 0 { dataBytes += len(*out.LockingScript) } diff --git a/txchange_test.go b/txchange_test.go index 09888aa3..d24b9a05 100644 --- a/txchange_test.go +++ b/txchange_test.go @@ -53,7 +53,7 @@ func TestTx_ChangeToAddress(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, tx.OutputCount()) - assert.Equal(t, "76a914a7a1a7fd7d279b57b84e596cbbf82608efdb441a88ac", tx.Outputs()[0].LockingScript.String()) + assert.Equal(t, "76a914a7a1a7fd7d279b57b84e596cbbf82608efdb441a88ac", tx.Outputs[0].LockingScript.String()) }) } @@ -113,7 +113,7 @@ func TestTx_Change(t *testing.T) { assert.NoError(t, err) // Correct fee for the tx - assert.Equal(t, uint64(3999904), tx.Outputs()[0].Satoshis) + assert.Equal(t, uint64(3999904), tx.Outputs[0].Satoshis) // Correct script hex string assert.Equal(t, @@ -193,7 +193,7 @@ func TestTx_Change(t *testing.T) { assert.Equal(t, "01000000010b94a1ef0fb352aa2adc54207ce47ba55d5a1c1609afda58fe9520e472299107000000006a473044022049ee0c0f26c00e6a6b3af5990fc8296c66eab3e3e42ab075069b89b1be6fefec02206079e49dd8c9e1117ef06fbe99714d822620b1f0f5d19f32a1128f5d29b7c3c4412102c8803fdd437d902f08e3c2344cb33065c99d7c99982018ff9f7219c3dd352ff0ffffffff01a0083d00000000001976a914af2590a45ae401651fdbdf59a76ad43d1862534088ac00000000", tx.String()) - assert.Equal(t, uint64(3999904), tx.Outputs()[0].Satoshis) + assert.Equal(t, uint64(3999904), tx.Outputs[0].Satoshis) }) t.Run("spend entire utxo - multi payouts - expected fee", func(t *testing.T) { @@ -226,8 +226,8 @@ func TestTx_Change(t *testing.T) { assert.Equal(t, "01000000010b94a1ef0fb352aa2adc54207ce47ba55d5a1c1609afda58fe9520e472299107000000006a47304402206bbb4b23349bdf86e6fbc9067226e9a7b15c977fa530999b39cd0a6d9c83360d02202dd8ffdc610e58b3fc92b44400d99e38c78866765f31acb40d98007a52e7a826412102c8803fdd437d902f08e3c2344cb33065c99d7c99982018ff9f7219c3dd352ff0ffffffff0240420f00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88acc0c62d00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88ac00000000", tx.String()) - assert.Equal(t, uint64(1000000), tx.Outputs()[0].Satoshis) - assert.Equal(t, uint64(3000000), tx.Outputs()[1].Satoshis) + assert.Equal(t, uint64(1000000), tx.Outputs[0].Satoshis) + assert.Equal(t, uint64(3000000), tx.Outputs[1].Satoshis) }) t.Run("spend entire utxo - multi payouts - incorrect fee", func(t *testing.T) { @@ -260,13 +260,13 @@ func TestTx_Change(t *testing.T) { assert.Equal(t, "01000000010b94a1ef0fb352aa2adc54207ce47ba55d5a1c1609afda58fe9520e472299107000000006b483045022100fd07316603e9abf393e695192e8ce1e7f808d2735cc57039109a2210ad32d9a7022000e301e2a988b23ab3872b041df8b6eb0315238e0918944cbaf8b6abdde75cac412102c8803fdd437d902f08e3c2344cb33065c99d7c99982018ff9f7219c3dd352ff0ffffffff023b420f00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88acc0c62d00000000001976a914b6aa34534d2b11e66b438c7525f819aee01e397c88ac00000000", tx.String()) - // todo: expected the pay-to inputs to change based on the fee :P + // todo: expected the pay-to Inputs to change based on the fee :P - assert.Equal(t, uint64(999995), tx.Outputs()[0].Satoshis) - assert.Equal(t, uint64(3000000), tx.Outputs()[1].Satoshis) + assert.Equal(t, uint64(999995), tx.Outputs[0].Satoshis) + assert.Equal(t, uint64(3000000), tx.Outputs[1].Satoshis) }) - t.Run("multiple inputs, spend all", func(t *testing.T) { + t.Run("multiple Inputs, spend all", func(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) @@ -375,7 +375,7 @@ func TestTx_ChangeToOutput(t *testing.T) { }(), index: 1, fees: bt.DefaultFees(), - err: errors.New("index is greater than number of inputs in transaction"), + err: errors.New("index is greater than number of Inputs in transaction"), }, } for name, test := range tests { @@ -387,7 +387,7 @@ func TestTx_ChangeToOutput(t *testing.T) { return } assert.Equal(t, test.expOutputTotal, test.tx.TotalOutputSatoshis()) - assert.Equal(t, test.expChangeOutput, test.tx.Outputs()[test.index].Satoshis) + assert.Equal(t, test.expChangeOutput, test.tx.Outputs[test.index].Satoshis) }) } } @@ -412,7 +412,7 @@ func TestTx_CalculateChange(t *testing.T) { }(), fees: bt.DefaultFees(), expFees: 96, - }, "Transaction with one input 4 outputs should return 147": { + }, "Transaction with one input 4 Outputs should return 147": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( diff --git a/txinput.go b/txinput.go index 04eec3cf..db3e89f0 100644 --- a/txinput.go +++ b/txinput.go @@ -36,20 +36,20 @@ func NewInputFromBytes(bytes []byte) (*Input, int, error) { // TotalInputSatoshis returns the total Satoshis inputted to the transaction. func (tx *Tx) TotalInputSatoshis() (total uint64) { - for _, in := range tx.inputs { + for _, in := range tx.Inputs { total += in.PreviousTxSatoshis } return } func (tx *Tx) addInput(input *Input) { - tx.inputs = append(tx.inputs, input) + tx.Inputs = append(tx.Inputs, input) } -// AddInputFromTx will add all outputs of given previous transaction +// AddP2PKHInputsFromTx will add all Outputs of given previous transaction // that match a specific public key to your transaction. -func (tx *Tx) AddInputFromTx(pvsTx *Tx, matchPK []byte) error { - for i, utxo := range pvsTx.outputs { +func (tx *Tx) AddP2PKHInputsFromTx(pvsTx *Tx, matchPK []byte) error { + for i, utxo := range pvsTx.Outputs { utxoPkHASH160, err := utxo.LockingScript.PublicKeyHash() if err != nil { return err @@ -88,7 +88,7 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat return nil } -// InputCount returns the number of transaction inputs. +// InputCount returns the number of transaction Inputs. func (tx *Tx) InputCount() int { - return len(tx.inputs) + return len(tx.Inputs) } diff --git a/txinput_test.go b/txinput_test.go index 383b794d..ee3f64fd 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -21,7 +21,7 @@ func TestAddInputFromTx(t *testing.T) { assert.NoError(t, err) newTx := bt.NewTx() - err = newTx.AddInputFromTx(prvTx, pubkey1) + err = newTx.AddP2PKHInputsFromTx(prvTx, pubkey1) assert.NoError(t, err) assert.Equal(t, newTx.InputCount(), 2) // only 2 utxos added assert.Equal(t, newTx.TotalInputSatoshis(), uint64(200000)) @@ -70,7 +70,7 @@ func TestTx_From(t *testing.T) { 4000000) assert.NoError(t, err) - inputs := tx.Inputs() + inputs := tx.Inputs assert.Equal(t, 1, len(inputs)) assert.Equal(t, "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", hex.EncodeToString(inputs[0].PreviousTxID())) assert.Equal(t, uint32(0), inputs[0].PreviousTxOutIndex) diff --git a/txoutput.go b/txoutput.go index adac4cfb..479952d0 100644 --- a/txoutput.go +++ b/txoutput.go @@ -36,7 +36,7 @@ func NewOutputFromBytes(bytes []byte) (*Output, int, error) { // TotalOutputSatoshis returns the total Satoshis outputted from the transaction. func (tx *Tx) TotalOutputSatoshis() (total uint64) { - for _, o := range tx.outputs { + for _, o := range tx.Outputs { total += o.Satoshis } return @@ -178,14 +178,14 @@ func createOpReturnOutput(data [][]byte) (*Output, error) { return &Output{LockingScript: s}, nil } -// OutputCount returns the number of transaction inputs. +// OutputCount returns the number of transaction Inputs. func (tx *Tx) OutputCount() int { - return len(tx.outputs) + return len(tx.Outputs) } // AddOutput adds a new output to the transaction. func (tx *Tx) AddOutput(output *Output) { - tx.outputs = append(tx.outputs, output) + tx.Outputs = append(tx.Outputs, output) } // PayTo creates a new P2PKH output from a BitCoin address (base58) diff --git a/txoutput_test.go b/txoutput_test.go index 157f334f..fd406168 100644 --- a/txoutput_test.go +++ b/txoutput_test.go @@ -24,7 +24,7 @@ func TestNewP2PKHOutputFromPubKeyHashStr(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "76a91488ac", - tx.Outputs()[0].LockingScriptHexString(), + tx.Outputs[0].LockingScriptHexString(), ) }) @@ -47,7 +47,7 @@ func TestNewP2PKHOutputFromPubKeyHashStr(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "76a9148fe80c75c9560e8b56ed64ea3c26e18d2c52211b88ac", - tx.Outputs()[0].LockingScriptHexString(), + tx.Outputs[0].LockingScriptHexString(), ) }) } @@ -68,7 +68,7 @@ func TestNewHashPuzzleOutput(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "a914b472a266d0bd89c13706a4132ccfb16f7c3b9fcb8876a90088ac", - tx.Outputs()[0].LockingScriptHexString(), + tx.Outputs[0].LockingScriptHexString(), ) }) @@ -83,7 +83,7 @@ func TestNewHashPuzzleOutput(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "a914d3f9e3d971764be5838307b175ee4e08ba427b908876a914c28f832c3d539933e0c719297340b34eee0f4c3488ac", - tx.Outputs()[0].LockingScriptHexString(), + tx.Outputs[0].LockingScriptHexString(), ) }) } @@ -100,7 +100,7 @@ func TestNewOpReturnOutput(t *testing.T) { err := tx.AddOpReturnOutput(dataBytes) assert.NoError(t, err) - script := tx.Outputs()[0].LockingScriptHexString() + script := tx.Outputs[0].LockingScriptHexString() dataLength := bt.VarInt(uint64(len(dataBytes))) assert.Equal(t, "006a4d2201"+hex.EncodeToString(dataBytes), script) @@ -115,7 +115,7 @@ func TestNewOpReturnPartsOutput(t *testing.T) { err := tx.AddOpReturnPartsOutput(dataBytes) assert.NoError(t, err) - assert.Equal(t, "006a02686903686f770361726503796f75", tx.Outputs()[0].LockingScriptHexString()) + assert.Equal(t, "006a02686903686f770361726503796f75", tx.Outputs[0].LockingScriptHexString()) } func TestTx_TotalOutputSatoshis(t *testing.T) { @@ -128,7 +128,7 @@ func TestTx_TotalOutputSatoshis(t *testing.T) { assert.Equal(t, uint64((29.89999582+20.00)*1e8), tx.TotalOutputSatoshis()) }) - t.Run("zero outputs", func(t *testing.T) { + t.Run("zero Outputs", func(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) assert.Equal(t, uint64(0), tx.TotalOutputSatoshis()) diff --git a/txsign.go b/txsign.go index 22c086fe..34afcda1 100644 --- a/txsign.go +++ b/txsign.go @@ -5,8 +5,8 @@ import ( "encoding/hex" "fmt" - "github.com/libsv/go-bt/bscript" "github.com/libsv/go-bk/crypto" + "github.com/libsv/go-bt/bscript" "github.com/libsv/go-bt/sighash" ) @@ -66,15 +66,15 @@ func (tx *Tx) ApplyP2PKHUnlockingScript(index uint32, pubKey []byte, sig []byte, // ApplyUnlockingScript applies a script to the transaction at a specific index in // unlocking script field. func (tx *Tx) ApplyUnlockingScript(index uint32, s *bscript.Script) error { - if tx.inputs[index] != nil { - tx.inputs[index].UnlockingScript = s + if tx.Inputs[index] != nil { + tx.Inputs[index].UnlockingScript = s return nil } return fmt.Errorf("no input at index %d", index) } -// SignAuto is used to automatically check which P2PKH inputs are +// SignAuto is used to automatically check which P2PKH Inputs are // able to be signed (match the public key) and then sign them. // It takes a Signed interface as a parameter so that different // signing implementations can be used to sign the transaction - @@ -82,7 +82,7 @@ func (tx *Tx) ApplyUnlockingScript(index uint32, s *bscript.Script) error { func (tx *Tx) SignAuto(ctx context.Context, s AutoSigner) (inputsSigned []int, err error) { shf := sighash.AllForkID // use SIGHASHALLFORFORKID to sign automatically - for i, in := range tx.inputs { + for i, in := range tx.Inputs { pubKeyHash, _ := in.PreviousTxScript.PublicKeyHash() // doesn't matter if returns error (not p2pkh) pubKeyHashStr := hex.EncodeToString(pubKeyHash)