279 lines
6.3 KiB
Go
279 lines
6.3 KiB
Go
// Copyright (c) 2019-2020 The Zcash developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
|
|
|
// Package bytestring provides a cryptobyte-inspired API specialized to the
|
|
// needs of parsing Zcash transactions.
|
|
package bytestring
|
|
|
|
import (
|
|
"io"
|
|
)
|
|
|
|
const maxCompactSize uint64 = 0x02000000
|
|
|
|
const (
|
|
op0 uint8 = 0x00
|
|
op1Negate uint8 = 0x4f
|
|
op1 uint8 = 0x51
|
|
op16 uint8 = 0x60
|
|
)
|
|
|
|
// String represents a string of bytes and provides methods for parsing values
|
|
// from it.
|
|
type String []byte
|
|
|
|
// read advances the string by n bytes and returns them. If fewer than n bytes
|
|
// remain, it returns nil.
|
|
func (s *String) read(n int) []byte {
|
|
if len(*s) < n {
|
|
return nil
|
|
}
|
|
|
|
out := (*s)[:n]
|
|
(*s) = (*s)[n:]
|
|
return out
|
|
}
|
|
|
|
// Read reads the next len(p) bytes from the string, or the remainder of the
|
|
// string if len(*s) < len(p). It returns the number of bytes read as n. If the
|
|
// string is empty it returns an io.EOF error, or a nil error if len(p) == 0.
|
|
// Read satisfies io.Reader.
|
|
func (s *String) Read(p []byte) (n int, err error) {
|
|
if s.Empty() {
|
|
if len(p) == 0 {
|
|
return 0, nil
|
|
}
|
|
return 0, io.EOF
|
|
}
|
|
|
|
n = copy(p, *s)
|
|
s.Skip(n)
|
|
return n, nil
|
|
}
|
|
|
|
// Empty reports whether or not the string is empty.
|
|
func (s *String) Empty() bool {
|
|
return len(*s) == 0
|
|
}
|
|
|
|
// Skip advances the string by n bytes and reports whether it was successful.
|
|
func (s *String) Skip(n int) bool {
|
|
if len(*s) < n {
|
|
return false
|
|
}
|
|
(*s) = (*s)[n:]
|
|
return true
|
|
}
|
|
|
|
// ReadByte reads a single byte into out and advances over it. It reports if
|
|
// the read was successful.
|
|
func (s *String) ReadByte(out *byte) bool {
|
|
v := s.read(1)
|
|
if v == nil {
|
|
return false
|
|
}
|
|
*out = v[0]
|
|
return true
|
|
}
|
|
|
|
// ReadBytes reads n bytes into out and advances over them. It reports if the
|
|
// read was successful.
|
|
func (s *String) ReadBytes(out *[]byte, n int) bool {
|
|
v := s.read(n)
|
|
if v == nil {
|
|
return false
|
|
}
|
|
*out = v
|
|
return true
|
|
}
|
|
|
|
// ReadCompactSize reads and interprets a Bitcoin-custom compact integer
|
|
// encoding used for length-prefixing and count values. If the values fall
|
|
// outside the expected canonical ranges, it returns false.
|
|
func (s *String) ReadCompactSize(size *int) bool {
|
|
*size = 0
|
|
lenBytes := s.read(1)
|
|
if lenBytes == nil {
|
|
return false
|
|
}
|
|
lenByte := lenBytes[0]
|
|
|
|
var lenLen int
|
|
var length, minSize uint64
|
|
|
|
switch {
|
|
case lenByte < 253:
|
|
length = uint64(lenByte)
|
|
case lenByte == 253:
|
|
lenLen = 2
|
|
minSize = 253
|
|
case lenByte == 254:
|
|
lenLen = 4
|
|
minSize = 0x10000
|
|
case lenByte == 255:
|
|
// this case is not currently usable, beyond maxCompactSize;
|
|
// also, this is not possible if sizeof(int) is 4 bytes
|
|
// lenLen = 8; minSize = 0x100000000
|
|
return false
|
|
}
|
|
|
|
if lenLen > 0 {
|
|
// expect little endian uint of varying size
|
|
lenBytes := s.read(lenLen)
|
|
if len(lenBytes) < lenLen {
|
|
return false
|
|
}
|
|
for i := lenLen - 1; i >= 0; i-- {
|
|
length <<= 8
|
|
length = length | uint64(lenBytes[i])
|
|
}
|
|
}
|
|
|
|
if length > maxCompactSize || length < minSize {
|
|
return false
|
|
}
|
|
*size = int(length)
|
|
return true
|
|
}
|
|
|
|
// ReadCompactLengthPrefixed reads data prefixed by a CompactSize-encoded
|
|
// length field into out. It reports whether the read was successful.
|
|
func (s *String) ReadCompactLengthPrefixed(out *String) bool {
|
|
var length int
|
|
if !s.ReadCompactSize(&length) {
|
|
return false
|
|
}
|
|
|
|
v := s.read(length)
|
|
if v == nil {
|
|
return false
|
|
}
|
|
|
|
*out = v
|
|
return true
|
|
}
|
|
|
|
// SkipCompactLengthPrefixed skips a CompactSize-encoded
|
|
// length field.
|
|
func (s *String) SkipCompactLengthPrefixed() bool {
|
|
var length int
|
|
if !s.ReadCompactSize(&length) {
|
|
return false
|
|
}
|
|
return s.Skip(length)
|
|
}
|
|
|
|
// ReadInt32 decodes a little-endian 32-bit value into out, treating it as
|
|
// signed, and advances over it. It reports whether the read was successful.
|
|
func (s *String) ReadInt32(out *int32) bool {
|
|
var tmp uint32
|
|
if !s.ReadUint32(&tmp) {
|
|
return false
|
|
}
|
|
|
|
*out = int32(tmp)
|
|
return true
|
|
}
|
|
|
|
// ReadInt64 decodes a little-endian 64-bit value into out, treating it as
|
|
// signed, and advances over it. It reports whether the read was successful.
|
|
func (s *String) ReadInt64(out *int64) bool {
|
|
var tmp uint64
|
|
if !s.ReadUint64(&tmp) {
|
|
return false
|
|
}
|
|
|
|
*out = int64(tmp)
|
|
return true
|
|
}
|
|
|
|
// ReadUint16 decodes a little-endian, 16-bit value into out and advances over
|
|
// it. It reports whether the read was successful.
|
|
func (s *String) ReadUint16(out *uint16) bool {
|
|
v := s.read(2)
|
|
if v == nil {
|
|
return false
|
|
}
|
|
*out = 0
|
|
for i := 1; i >= 0; i-- {
|
|
*out <<= 8
|
|
*out |= uint16(v[i])
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ReadUint32 decodes a little-endian, 32-bit value into out and advances over
|
|
// it. It reports whether the read was successful.
|
|
func (s *String) ReadUint32(out *uint32) bool {
|
|
v := s.read(4)
|
|
if v == nil {
|
|
return false
|
|
}
|
|
*out = 0
|
|
for i := 3; i >= 0; i-- {
|
|
*out <<= 8
|
|
*out |= uint32(v[i])
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ReadUint64 decodes a little-endian, 64-bit value into out and advances over
|
|
// it. It reports whether the read was successful.
|
|
func (s *String) ReadUint64(out *uint64) bool {
|
|
v := s.read(8)
|
|
if v == nil {
|
|
return false
|
|
}
|
|
*out = 0
|
|
for i := 7; i >= 0; i-- {
|
|
*out <<= 8
|
|
*out |= uint64(v[i])
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ReadScriptInt64 reads and interprets a Bitcoin-custom compact integer
|
|
// encoding used for int64 numbers in scripts.
|
|
//
|
|
// Serializer in zcashd:
|
|
// https://github.com/zcash/zcash/blob/4df60f4b334dd9aee5df3a481aee63f40b52654b/src/script/script.h#L363-L378
|
|
//
|
|
// Partial parser in zcashd:
|
|
// https://github.com/zcash/zcash/blob/4df60f4b334dd9aee5df3a481aee63f40b52654b/src/script/interpreter.cpp#L308-L335
|
|
func (s *String) ReadScriptInt64(num *int64) bool {
|
|
// First byte is either an integer opcode, or the number of bytes in the
|
|
// number.
|
|
*num = 0
|
|
firstBytes := s.read(1)
|
|
if firstBytes == nil {
|
|
return false
|
|
}
|
|
firstByte := firstBytes[0]
|
|
|
|
var number uint64
|
|
|
|
if firstByte == op1Negate {
|
|
*num = -1
|
|
return true
|
|
} else if firstByte == op0 {
|
|
number = 0
|
|
} else if firstByte >= op1 && firstByte <= op16 {
|
|
number = uint64(firstByte) - uint64(op1-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
|
|
}
|