This site says that I have to use flags to extract out the transaction amounts [...] Also, what are the end markers I could use to extract that amount?
(my emphasis)
My advice is don't search the hex data for magic flag or marker values, parse the data completely:
Here's how I would parse a raw transaction in network format and start to calculate the transaction value:
/* Example basic parsing blockchain transaction data, Redgrittybrick 2023 */
package main
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"os"
)
const txhex string =
"0200000000010179aaafbe7c9d3b0812a489facaf77508c08c190ec7dfd82f12" +
"9aeb995aca23ab0000000000fdffffff020bd2190000000000160014d2caa7b0" +
"8db89cd62c9af34da53332d30e53bb1598151b00000000001600143d4427468c" +
"be7ae396427a1aa9128fa05b18c7db024730440220573fd27574cfdde4843476" +
"21e1f48f85ae975cb8c2265a04496ded038896822302204a5e04a3a2d160c315" +
"8caa39b58bfc91ac64c484078ec0225a7d4d2d4454661f012103d96e3819b522" +
"45e42c76f869c9a875f6ea5344cf1aee2e6b3ab03adcfef0d80ede3b0b00"
func main() {
txbin, err := hex.DecodeString(txhex)
check(err)
buf := bytes.NewBuffer(txbin)
// VERSION
version := buf.Next(4)
fmt.Printf("Version = %x\n", version)
// OPTIONAL SEGWIT MARKER AND FLAG
// OR INPUT COUNT
var segwitMarker, segwitFlag byte // each defaults to 0
inputCount := NextCInt(buf)
if inputCount == 0 { // It's a Segwit marker, not an input count
segwitFlag, err = buf.ReadByte() // expect 1
check(err)
fmt.Printf("Segwit Marker = %x, Flag = %x\n" segwitMarker, segwitFlag)
inputCount = NextCInt(buf)
}
fmt.Printf("Input Count = %d\n", inputCount)
// INPUTS
for i := 0; i < int(inputCount); i++ {
txid := buf.Next(32)
vout := binary.LittleEndian.Uint32(buf.Next(4))
scriptLen := NextCInt(buf)
fmt.Printf("Input %d: tx = %x vout = %d\n", i+1, txid, vout)
fmt.Printf("\tUnlocking Script length = %d\n", scriptLen)
if scriptLen > 0 {
script := buf.Next(int(scriptLen))
fmt.Printf("\tScript = %x\n", script)
}
}
// SEQUENCE
sequence := buf.Next(4)
fmt.Printf("Sequence = %x\n", sequence)
// OUTPUTS
outCount := NextCInt(buf)
fmt.Printf("Output count = %d\n", outCount)
var txValue uint64
for o := 0; o < int(outCount); o++ {
amount := binary.LittleEndian.Uint64(buf.Next(8))
scriptLen := NextCInt(buf)
script := buf.Next(int(scriptLen))
fmt.Printf("Output %d: value = %d script = %x\n", o+1, amount, script)
txValue += amount
}
//WITNESSES
if segwitFlag != 0 {
for i:=0; i<int(inputCount); i++ {
compCount := NextCInt(buf)
for c:=0; c<int(compCount); c++ {
l := NextCInt(buf)
if l > 0 {
component := buf.Next(int(l))
fmt.Printf("Witness %d component %d = %x\n",
i+1, c+1, component)
}
}
}
}
// LOCKTIME
lockTime := buf.Next(4)
fmt.Printf("Locktime = %x\n", lockTime)
// DERIVED DATA
fmt.Printf("\n(Tx value (less fee) = %d)\n", txValue)
}
// Extracts a Compact Int
func NextCInt(buf *bytes.Buffer) (uint64) {
var result uint64
first, err := buf.ReadByte()
check(err)
switch first {
case 0xFF :
result = binary.LittleEndian.Uint64(buf.Next(8))
case 0xFE :
result = uint64(binary.LittleEndian.Uint32(buf.Next(4)))
case 0xFD :
result = uint64(binary.LittleEndian.Uint16(buf.Next(2)))
default :
result = uint64(first)
}
return result
}
// Rudimentary error handling
func check(err error) {
if err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
}
Output
Version = 02000000
Segwit Marker = 0, Flag = 1
Input Count = 1
Input 1: tx = 79aaafbe7c9d3b0812a489facaf77508c08c190ec7dfd82f129aeb995aca23ab vout = 0
Unlocking Script length = 0
Sequence = fdffffff
Output count = 2
Output 1: value = 1692171 script = 0014d2caa7b08db89cd62c9af34da53332d30e53bb15
Output 2: value = 1775000 script = 00143d4427468cbe7ae396427a1aa9128fa05b18c7db
Witness 1 component 1 = 30440220573fd27574cfdde484347621e1f48f85ae975cb8c2265a04496ded038896822302204a5e04a3a2d160c3158caa39b58bfc91ac64c484078ec0225a7d4d2d4454661f01
Witness 1 component 2 = 03d96e3819b52245e42c76f869c9a875f6ea5344cf1aee2e6b3ab03adcfef0d80e
Locktime = de3b0b00
(Tx value (less fee) = 3467171)
The code is quick and dirty. Production code needs more defensive error checking in case of corrupt, invalid or truncated data. For example it should use buf.Read()
instead of buf.Next()
and check for errors thrown. Normally I'd populate an object (go data struct) instead of just printing stuff out. To calculate the fee you need to extract vout value from referenced prior transactions, fee=sum(ins)-sum(outs).
Note that the code above has no external dependencies - the imports are just standard go libraries that come with the compiler. It doesn't rely on any Bitcoin library (which also means it is unproven code!)
The go compiler is a free download at https://go.dev/ it compiles to a single native execatable (and can cross-compile to other platform), A typical way to use the above might be
- install go compiler
- create a project folder somewhere convenient
- cd to folder
- copy above go code into file named
main.go
(or anything.go)
go mod init
go mod tidy
go build
- run new program
The go mod ...
commands are project initialisation and don't need to be repeated when recompiling.
It is certainly more than possible this code has other flaws (comments welcome) but I hope it acts as a potential starting point or at least suggests a possible approach that can be implemented in your preferred programming language.