From 5acfb9af34562c54fbd37283301508fcee061a32 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 4 May 2019 11:37:00 +0100 Subject: [PATCH] parser: Correctly parse heights from script-encoded int64 values --- parser/block.go | 14 +++----- parser/internal/bytestring/bytestring.go | 44 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/parser/block.go b/parser/block.go index 17c0799..c12baa3 100644 --- a/parser/block.go +++ b/parser/block.go @@ -62,21 +62,15 @@ func (b *block) GetHeight() int { return b.height } coinbaseScript := bytestring.String(b.vtx[0].transparentInputs[0].ScriptSig) - var heightByte byte - if ok := coinbaseScript.ReadByte(&heightByte); !ok { + var heightNum int64 + if ok := coinbaseScript.ReadScriptInt64(&heightNum); !ok { return -1 } - heightLen := int(heightByte) - var heightBytes = make([]byte, heightLen) - if ok := coinbaseScript.ReadBytes(&heightBytes, heightLen); !ok { + if heightNum < 0 { return -1 } // uint32 should last us a while (Nov 2018) - var blockHeight uint32 - for i := heightLen - 1; i >= 0; i-- { - blockHeight <<= 8 - blockHeight = blockHeight | uint32(heightBytes[i]) - } + blockHeight := uint32(heightNum) if blockHeight == genesisTargetDifficulty { blockHeight = 0 diff --git a/parser/internal/bytestring/bytestring.go b/parser/internal/bytestring/bytestring.go index 5bcf5bc..f3e71da 100644 --- a/parser/internal/bytestring/bytestring.go +++ b/parser/internal/bytestring/bytestring.go @@ -9,6 +9,11 @@ import ( const MAX_COMPACT_SIZE uint64 = 0x02000000 +const OP_0 uint8 = 0x00 +const OP_1NEGATE uint8 = 0x4f +const OP_1 uint8 = 0x51 +const OP_16 uint8 = 0x60 + // String represents a string of bytes and provides methods for parsing values // from it. type String []byte @@ -194,3 +199,42 @@ func (s *String) ReadUint64(out *uint64) bool { uint64(v[4])<<32 | uint64(v[5])<<40 | uint64(v[6])<<48 | uint64(v[7])<<56 return true } + +// ReadScriptInt64 reads and interprets a Bitcoin-custom compact integer +// encoding used for int64 numbers in scripts. +// +// See https://github.com/zcash/zcash/blob/4df60f4b334dd9aee5df3a481aee63f40b52654b/src/script/script.h#L363-L378 +func (s *String) ReadScriptInt64(num *int64) bool { + // First byte is either an integer opcode, or the number of bytes in the + // number. + firstBytes := s.read(1) + if firstBytes == nil { + return false + } + firstByte := firstBytes[0] + + var number uint64 + + if firstByte == OP_1NEGATE { + *num = -1 + return true + } else if firstByte == OP_0 { + number = 0 + } else if firstByte >= OP_1 && firstByte <= OP_16 { + number = uint64(firstByte) - uint64(OP_1 - 1) + } else { + numLen := int(firstByte) + // expect little endian int of varying size + numBytes := s.read(numLen) + if numBytes == nil { + return false + } + for i := numLen - 1; i >= 0; i-- { + number <<= 8 + number = number | uint64(numBytes[i]) + } + } + + *num = int64(number) + return true +}