From 62432a6f9089095510eb43dd8c4f272bdaa8414f Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Mon, 9 Mar 2015 21:49:01 -0500 Subject: [PATCH] wire: Add func to get pkscript locs from a tx. This commit provides a new function named PkScriptLocs on the MsgTx type which can be used to efficiently retrieve a list of offsets for the public key scripts for the serialized form of the transaction. This is useful for certain applications which store fully serialized transactions and want to be able to quickly index into the serialized transaction to extract a give public key script directly thereby avoiding the need to deserialize the entire transaction. --- wire/msgblock.go | 5 ++-- wire/msgtx.go | 37 ++++++++++++++++++++++++++ wire/msgtx_test.go | 65 ++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 100 insertions(+), 7 deletions(-) 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}