Correctly parses early coinbase tx's
This commit is contained in:
parent
eb6b16f450
commit
30996ab018
304
transaction.go
304
transaction.go
|
@ -1,10 +1,29 @@
|
|||
package moneroutil
|
||||
|
||||
type Hash [32]byte
|
||||
type PubKey [32]byte
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
txInGenMarker = 0xff
|
||||
txInToScriptMarker = 0
|
||||
txInToScriptHashMarker = 1
|
||||
txInToKeyMarker = 2
|
||||
txOutToScriptMarker = 0
|
||||
txOutToScriptHashMarker = 1
|
||||
txOutToKeyMarker = 2
|
||||
|
||||
HashLength = 32
|
||||
PubKeyLength = 32
|
||||
)
|
||||
|
||||
type Hash [HashLength]byte
|
||||
type PubKey [PubKeyLength]byte
|
||||
type Signature struct {
|
||||
c []byte
|
||||
r []byte
|
||||
c [PubKeyLength]byte
|
||||
r [PubKeyLength]byte
|
||||
}
|
||||
|
||||
type txOutToScript struct {
|
||||
|
@ -17,11 +36,12 @@ type txOutToScriptHash struct {
|
|||
}
|
||||
|
||||
type txOutToKey struct {
|
||||
pubkey PubKey
|
||||
key PubKey
|
||||
}
|
||||
|
||||
type TxOutTargeter interface {
|
||||
TxOutTarget() []byte
|
||||
String() string
|
||||
}
|
||||
|
||||
type txInGen struct {
|
||||
|
@ -30,15 +50,15 @@ type txInGen struct {
|
|||
|
||||
type txInToScript struct {
|
||||
prev []byte
|
||||
prevout uint64
|
||||
prevOut uint64
|
||||
sigSet []byte
|
||||
}
|
||||
|
||||
type txInToScriptHash struct {
|
||||
prev Hash
|
||||
prevout uint64
|
||||
txOutToScript []byte
|
||||
sigSet []byte
|
||||
prev Hash
|
||||
prevOut uint64
|
||||
script []byte
|
||||
sigSet []byte
|
||||
}
|
||||
|
||||
type txInToKey struct {
|
||||
|
@ -52,15 +72,15 @@ type TxInMaker interface {
|
|||
}
|
||||
|
||||
type TxOut struct {
|
||||
amount uint64
|
||||
targets TxOutTargeter
|
||||
amount uint64
|
||||
target TxOutTargeter
|
||||
}
|
||||
|
||||
type TransactionPrefix struct {
|
||||
version uint32
|
||||
unlockTime uint64
|
||||
vin []TxInMaker
|
||||
vout TxOut
|
||||
vout []*TxOut
|
||||
extra []byte
|
||||
}
|
||||
|
||||
|
@ -71,3 +91,261 @@ type Transaction struct {
|
|||
hash []byte
|
||||
blobSize uint32
|
||||
}
|
||||
|
||||
func (h *Hash) Serialize() (result []byte) {
|
||||
result = h[:]
|
||||
return
|
||||
}
|
||||
|
||||
func (p *PubKey) Serialize() (result []byte) {
|
||||
result = p[:]
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Signature) Serialize() (result []byte) {
|
||||
result = make([]byte, 64)
|
||||
copy(result, s.c[:])
|
||||
copy(result[32:64], s.r[:])
|
||||
return
|
||||
}
|
||||
|
||||
func (t *txOutToScript) TxOutTarget() (result []byte) {
|
||||
result = []byte{txInToScriptMarker}
|
||||
for i, pubkey := range t.pubKeys {
|
||||
if i != 0 {
|
||||
result = append(result, byte(txOutToScriptMarker))
|
||||
}
|
||||
result = append(result, pubkey.Serialize()...)
|
||||
}
|
||||
result = append(result, t.script...)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *txOutToScript) String() (result string) {
|
||||
result = fmt.Sprintf("script: %x", t.script)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *txOutToScriptHash) TxOutTarget() (result []byte) {
|
||||
result = append([]byte{txInToScriptHashMarker}, t.hash.Serialize()...)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *txOutToScriptHash) String() (result string) {
|
||||
result = fmt.Sprintf("hash: %x", t.hash)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *txOutToKey) TxOutTarget() (result []byte) {
|
||||
result = append([]byte{txInToKeyMarker}, t.key.Serialize()...)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *txOutToKey) String() (result string) {
|
||||
result = fmt.Sprintf("key: %x", t.key)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *txInGen) TxIn() (result []byte) {
|
||||
result = append([]byte{txInGenMarker}, Uint64ToBytes(t.height)...)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *txInToScript) TxIn() (result []byte) {
|
||||
result = append([]byte{txInToScriptMarker}, t.prev...)
|
||||
result = append(result, Uint64ToBytes(t.prevOut)...)
|
||||
result = append(result, t.sigSet...)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *txInToScriptHash) TxIn() (result []byte) {
|
||||
result = append([]byte{txInToScriptHashMarker}, t.prev.Serialize()...)
|
||||
result = append(result, Uint64ToBytes(t.prevOut)...)
|
||||
result = append(result, t.script...)
|
||||
result = append(result, t.sigSet...)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *txInToKey) TxIn() (result []byte) {
|
||||
result = append([]byte{txInToKeyMarker}, Uint64ToBytes(t.amount)...)
|
||||
for _, keyOffset := range t.keyOffsets {
|
||||
result = append(result, Uint64ToBytes(keyOffset)...)
|
||||
}
|
||||
result = append(result, t.keyImage[:]...)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TxOut) Serialize() (result []byte) {
|
||||
result = append(Uint64ToBytes(t.amount), t.target.TxOutTarget()...)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TransactionPrefix) Serialize() (result []byte) {
|
||||
result = append(Uint64ToBytes(uint64(t.version)), Uint64ToBytes(t.unlockTime)...)
|
||||
result = append(result, Uint64ToBytes(uint64(len(t.vin)))...)
|
||||
for _, txIn := range t.vin {
|
||||
result = append(result, txIn.TxIn()...)
|
||||
}
|
||||
result = append(result, Uint64ToBytes(uint64(len(t.vout)))...)
|
||||
for _, txOut := range t.vout {
|
||||
result = append(result, txOut.Serialize()...)
|
||||
}
|
||||
result = append(result, Uint64ToBytes(uint64(len(t.extra)))...)
|
||||
result = append(result, t.extra...)
|
||||
return
|
||||
}
|
||||
|
||||
func ParseTxInGen(buf *bytes.Buffer) (txIn *txInGen, err error) {
|
||||
t := new(txInGen)
|
||||
t.height, err = ReadVarInt(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
txIn = t
|
||||
return
|
||||
}
|
||||
|
||||
func ParseTxInToScript(buf *bytes.Buffer) (txIn *txInToScript, err error) {
|
||||
err = errors.New("Unimplemented")
|
||||
return
|
||||
}
|
||||
|
||||
func ParseTxInToScriptHash(buf *bytes.Buffer) (txIn *txInToScriptHash, err error) {
|
||||
err = errors.New("Unimplemented")
|
||||
return
|
||||
}
|
||||
|
||||
func ParseTxInToKey(buf *bytes.Buffer) (txIn *txInToKey, err error) {
|
||||
err = errors.New("Unimplemented")
|
||||
return
|
||||
}
|
||||
|
||||
func ParseTxIn(buf *bytes.Buffer) (txIn TxInMaker, err error) {
|
||||
marker, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case marker == txInGenMarker:
|
||||
txIn, err = ParseTxInGen(buf)
|
||||
case marker == txInToScriptMarker:
|
||||
txIn, err = ParseTxInToScript(buf)
|
||||
case marker == txInToScriptHashMarker:
|
||||
txIn, err = ParseTxInToScriptHash(buf)
|
||||
case marker == txInToKeyMarker:
|
||||
txIn, err = ParseTxInToKey(buf)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ParseTxOutToScript(buf *bytes.Buffer) (txOutTarget *txOutToScript, err error) {
|
||||
err = errors.New("Unimplemented")
|
||||
return
|
||||
}
|
||||
|
||||
func ParseTxOutToScriptHash(buf *bytes.Buffer) (txOutTarget *txOutToScriptHash, err error) {
|
||||
err = errors.New("Unimplemented")
|
||||
return
|
||||
}
|
||||
|
||||
func ParseTxOutToKey(buf *bytes.Buffer) (txOutTarget *txOutToKey, err error) {
|
||||
t := new(txOutToKey)
|
||||
pubKey := buf.Next(PubKeyLength)
|
||||
if len(pubKey) != PubKeyLength {
|
||||
err = errors.New("Buffer not long enough for public key")
|
||||
return
|
||||
}
|
||||
copy(t.key[:], pubKey)
|
||||
txOutTarget = t
|
||||
return
|
||||
}
|
||||
|
||||
func ParseTxOut(buf *bytes.Buffer) (txOut *TxOut, err error) {
|
||||
t := new(TxOut)
|
||||
t.amount, err = ReadVarInt(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
marker, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case marker == txOutToScriptMarker:
|
||||
t.target, err = ParseTxOutToScript(buf)
|
||||
case marker == txOutToScriptHashMarker:
|
||||
t.target, err = ParseTxOutToScriptHash(buf)
|
||||
case marker == txOutToKeyMarker:
|
||||
t.target, err = ParseTxOutToKey(buf)
|
||||
default:
|
||||
err = errors.New("Bad Marker")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
txOut = t
|
||||
return
|
||||
}
|
||||
|
||||
func ParseExtra(buf *bytes.Buffer) (extra []byte, err error) {
|
||||
length, err := ReadVarInt(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
e := buf.Next(int(length))
|
||||
if len(e) != int(length) {
|
||||
err = errors.New("Not enough bytes for extra")
|
||||
return
|
||||
}
|
||||
extra = e
|
||||
return
|
||||
}
|
||||
|
||||
func ParseTransaction(buf *bytes.Buffer) (transaction *TransactionPrefix, err error) {
|
||||
t := new(TransactionPrefix)
|
||||
version, err := ReadVarInt(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.version = uint32(version)
|
||||
t.unlockTime, err = ReadVarInt(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
numInputs, err := ReadVarInt(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.vin = make([]TxInMaker, int(numInputs), int(numInputs))
|
||||
for i := 0; i < int(numInputs); i++ {
|
||||
t.vin[i], err = ParseTxIn(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
numOutputs, err := ReadVarInt(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.vout = make([]*TxOut, int(numOutputs), int(numOutputs))
|
||||
for i := 0; i < int(numOutputs); i++ {
|
||||
t.vout[i], err = ParseTxOut(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.extra, err = ParseExtra(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
transaction = t
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TransactionPrefix) OutputSum() (sum uint64) {
|
||||
for _, output := range t.vout {
|
||||
sum += output.amount
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
64
transaction_test.go
Normal file
64
transaction_test.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package moneroutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTransactionParse(t *testing.T) {
|
||||
|
||||
serializedTxHex := "014b01ff0f098fd61702a3ef0df2a51b14891676b39f18d6c0b8c52bfc3f6ba5f63f792e993f73b900df8092f40102204e4d5992876b76cc8a30760c8fd82dbaf948cb2d858278a08f4d8db326682a8087a70e027a2328c2d86a1f84335010d183d0eef067195da4a57169ac9d1c8c1f84ec74a580d293ad03023c6411e620104487402583db89dd15245448092f339a30a4982e076c7ba0a7578094ebdc0302861696af6e92cea973fa217f3b4df3e8c43a2a1cb888b515e23d2d5d3d41ba688088aca3cf0202d3087348abe2dffa189c79f82e032f14cbd12cfd6ece0a5192ec235a4cf2c96e8090cad2c60e02feb619b90856473e906295252cc9a87abbb7f5db409579d3169be5ee23b35ab680e08d84ddcb010275cd0dbe02a00558669ef424c2d5f8cbefc8c4af711f1afde0404628c401b13580c0caf384a30202b1bb2e13bf7c1d88885b7ebf2be56e5064af24d69f3c92d9264550122eed1a0b2101bbac13803d9b7941444cc817292b91a9634fa6cee88ced917571df2e0c87ad79"
|
||||
expectedHashHex := "e3a799da24d9f41aac231ba2efb853ae649283feaf5e1ba46b5fc2c194414c5d"
|
||||
serializedTx, _ := hex.DecodeString(serializedTxHex)
|
||||
expectedHash, _ := hex.DecodeString(expectedHashHex)
|
||||
hash := Keccak256(serializedTx)
|
||||
if bytes.Compare(expectedHash, hash) != 0 {
|
||||
t.Fatalf("want %x, got %x", expectedHash, hash)
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer.Write(serializedTx)
|
||||
transaction, err := ParseTransaction(buffer)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing tx: %s", err)
|
||||
}
|
||||
wantVersion := uint32(1)
|
||||
if wantVersion != transaction.version {
|
||||
t.Fatalf("version: want %d, got %d", wantVersion, transaction.version)
|
||||
}
|
||||
wantUnlock := uint64(75)
|
||||
if wantUnlock != transaction.unlockTime {
|
||||
t.Fatalf("unlock: want %d, got %d", wantUnlock, transaction.unlockTime)
|
||||
}
|
||||
wantSum := uint64(17591934387855)
|
||||
gotSum := transaction.OutputSum()
|
||||
if wantSum != gotSum {
|
||||
t.Fatalf("sum: want %d, got %d", wantSum, gotSum)
|
||||
}
|
||||
wantLen := 1
|
||||
gotLen := len(transaction.vin)
|
||||
if wantLen != gotLen {
|
||||
t.Fatalf("input len: want %d, got %d", wantLen, gotLen)
|
||||
}
|
||||
wantIn := []byte{0xff, 0x0f}
|
||||
gotIn := transaction.vin[0].TxIn()
|
||||
if bytes.Compare(wantIn, gotIn) != 0 {
|
||||
t.Fatalf("input 1: want %x, got %x", wantIn, gotIn)
|
||||
}
|
||||
wantLen = 9
|
||||
gotLen = len(transaction.vout)
|
||||
if wantLen != gotLen {
|
||||
t.Fatalf("output len: want %d, got %d", wantLen, gotLen)
|
||||
}
|
||||
wantOutHex := "02a3ef0df2a51b14891676b39f18d6c0b8c52bfc3f6ba5f63f792e993f73b900df"
|
||||
wantOut, _ := hex.DecodeString(wantOutHex)
|
||||
gotOut := transaction.vout[0].target.TxOutTarget()
|
||||
if bytes.Compare(wantOut, gotOut) != 0 {
|
||||
t.Fatalf("input 1: want %x, got %x", wantOut, gotOut)
|
||||
}
|
||||
gotSerialized := transaction.Serialize()
|
||||
if bytes.Compare(serializedTx, gotSerialized) != 0 {
|
||||
t.Fatalf("serialized: want %x, got %x", serializedTx, gotSerialized)
|
||||
}
|
||||
|
||||
}
|
19
varint.go
19
varint.go
|
@ -1,16 +1,25 @@
|
|||
package moneroutil
|
||||
|
||||
func ReadVarInt(varInt []byte) (result int64) {
|
||||
for i, b := range varInt {
|
||||
result += (int64(b) & 0x7f) << uint(i*7)
|
||||
if int64(b)&0x80 == 0 {
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
func ReadVarInt(buf *bytes.Buffer) (result uint64, err error) {
|
||||
var b byte
|
||||
for i := 0; ; i++ {
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
result += (uint64(b) & 0x7f) << uint(i*7)
|
||||
if uint64(b)&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func WriteVarInt(num int64) (result []byte) {
|
||||
func Uint64ToBytes(num uint64) (result []byte) {
|
||||
for ; num >= 0x80; num >>= 7 {
|
||||
result = append(result, byte((num&0x7f)|0x80))
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ func TestVarInt(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
varInt []byte
|
||||
want int64
|
||||
want uint64
|
||||
}{
|
||||
{
|
||||
name: "1 byte",
|
||||
|
@ -32,15 +32,23 @@ func TestVarInt(t *testing.T) {
|
|||
want: 10000000000000,
|
||||
},
|
||||
}
|
||||
var got int64
|
||||
var got uint64
|
||||
var err error
|
||||
var gotVarInt []byte
|
||||
buf := new(bytes.Buffer)
|
||||
for _, test := range tests {
|
||||
gotVarInt = WriteVarInt(test.want)
|
||||
gotVarInt = Uint64ToBytes(test.want)
|
||||
if bytes.Compare(gotVarInt, test.varInt) != 0 {
|
||||
t.Errorf("%s: varint want %x, got %x", test.name, test.varInt, gotVarInt)
|
||||
continue
|
||||
}
|
||||
got = ReadVarInt(test.varInt)
|
||||
buf.Reset()
|
||||
buf.Write(test.varInt)
|
||||
got, err = ReadVarInt(buf)
|
||||
if err != nil {
|
||||
t.Errorf("%s: %s", test.name, err)
|
||||
continue
|
||||
}
|
||||
if test.want != got {
|
||||
t.Errorf("%s: want %d, got %d", test.name, test.want, got)
|
||||
continue
|
||||
|
|
Reference in a new issue