diff --git a/wire/msgblock.go b/wire/msgblock.go index 812e7bf7..b97ae19c 100644 --- a/wire/msgblock.go +++ b/wire/msgblock.go @@ -108,8 +108,9 @@ func (msg *MsgBlock) Deserialize(r io.Reader) error { } // DeserializeTxLoc decodes r in the same manner Deserialize does, but it takes -// a byte buffer instead of a generic reader and returns a slice containing the start and length of -// each transaction within the raw data that is being deserialized. +// a byte buffer instead of a generic reader and returns a slice containing the +// start and length of each transaction within the raw data that is being +// deserialized. func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, error) { fullLen := r.Len() diff --git a/wire/msgtx.go b/wire/msgtx.go index 19a0bc3e..fa262e33 100644 --- a/wire/msgtx.go +++ b/wire/msgtx.go @@ -430,6 +430,43 @@ func (msg *MsgTx) MaxPayloadLength(pver uint32) uint32 { return MaxBlockPayload } +// PkScriptLocs returns a slice containing the start of each public key script +// within the raw serialized transaction. The caller can easily obtain the +// length of each script by using len on the script available via the +// appropriate transaction output entry. +func (msg *MsgTx) PkScriptLocs() []int { + numTxOut := len(msg.TxOut) + if numTxOut == 0 { + return nil + } + + // The starting offset in the serialized transaction of the first + // transaction output is: + // + // Version 4 bytes + serialized varint size for the number of + // transaction inputs and outputs + serialized size of each transaction + // input. + n := 4 + VarIntSerializeSize(uint64(len(msg.TxIn))) + + VarIntSerializeSize(uint64(numTxOut)) + for _, txIn := range msg.TxIn { + n += txIn.SerializeSize() + } + + // Calculate and set the appropriate offset for each public key script. + pkScriptLocs := make([]int, numTxOut) + for i, txOut := range msg.TxOut { + // The offset of the script in the transaction output is: + // + // Value 8 bytes + serialized varint size for the length of + // PkScript. + n += 8 + VarIntSerializeSize(uint64(len(txOut.PkScript))) + pkScriptLocs[i] = n + n += len(txOut.PkScript) + } + + return pkScriptLocs +} + // NewMsgTx returns a new bitcoin tx message that conforms to the Message // interface. The return instance has a default version of TxVersion and there // are no transaction inputs or outputs. Also, the lock time is set to zero diff --git a/wire/msgtx_test.go b/wire/msgtx_test.go index d165d03d..e1b64d33 100644 --- a/wire/msgtx_test.go +++ b/wire/msgtx_test.go @@ -389,15 +389,17 @@ func TestTxSerialize(t *testing.T) { } tests := []struct { - in *wire.MsgTx // Message to encode - out *wire.MsgTx // Expected decoded message - buf []byte // Serialized data + in *wire.MsgTx // Message to encode + out *wire.MsgTx // Expected decoded message + buf []byte // Serialized data + pkScriptLocs []int // Expected output script locations }{ // No transactions. { noTx, noTx, noTxEncoded, + nil, }, // Multiple transactions. @@ -405,6 +407,7 @@ func TestTxSerialize(t *testing.T) { multiTx, multiTx, multiTxEncoded, + multiTxPkScriptLocs, }, } @@ -436,6 +439,25 @@ func TestTxSerialize(t *testing.T) { spew.Sdump(&tx), spew.Sdump(test.out)) continue } + + // Ensure the public key script locations are accurate. + pkScriptLocs := test.in.PkScriptLocs() + if !reflect.DeepEqual(pkScriptLocs, test.pkScriptLocs) { + t.Errorf("PkScriptLocs #%d\n got: %s want: %s", i, + spew.Sdump(pkScriptLocs), + spew.Sdump(test.pkScriptLocs)) + continue + } + for j, loc := range pkScriptLocs { + wantPkScript := test.in.TxOut[j].PkScript + gotPkScript := test.buf[loc : loc+len(wantPkScript)] + if !bytes.Equal(gotPkScript, wantPkScript) { + t.Errorf("PkScriptLocs #%d:%d\n unexpected "+ + "script got: %s want: %s", i, j, + spew.Sdump(gotPkScript), + spew.Sdump(wantPkScript)) + } + } } } @@ -611,7 +633,7 @@ func TestTxSerializeSize(t *testing.T) { {noTx, 10}, // Transcaction with an input and an output. - {multiTx, 134}, + {multiTx, 210}, } t.Logf("Running %d tests", len(tests)) @@ -657,6 +679,22 @@ var multiTx = &wire.MsgTx{ 0xac, // OP_CHECKSIG }, }, + { + Value: 0x5f5e100, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + }, }, LockTime: 0, } @@ -674,7 +712,7 @@ var multiTxEncoded = []byte{ 0x07, // Varint for length of signature script 0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62, // Signature script 0xff, 0xff, 0xff, 0xff, // Sequence - 0x01, // Varint for number of output transactions + 0x02, // Varint for number of output transactions 0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount 0x43, // Varint for length of pk script 0x41, // OP_DATA_65 @@ -686,7 +724,24 @@ var multiTxEncoded = []byte{ 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + 0x00, 0xe1, 0xf5, 0x05, 0x00, 0x00, 0x00, 0x00, // Transaction amount + 0x43, // Varint for length of pk script + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, 0xa6, // 65-byte signature 0xac, // OP_CHECKSIG 0x00, 0x00, 0x00, 0x00, // Lock time } + +// multiTxPkScriptLocs is the location information for the public key scripts +// located in multiTx. +var multiTxPkScriptLocs = []int{63, 139}