go-blockchain/wire/transaction.go
Snider 76488e0beb
Some checks are pending
Security Scan / security (push) Waiting to run
Test / Test (push) Waiting to run
feat(wire): add alias entry readers + AX usage-example comments
Add readExtraAliasEntryOld (tag 20) and readExtraAliasEntry (tag 33)
wire readers so the node can deserialise blocks containing alias
registrations. Without these readers, mainnet sync would fail on any
block with an alias transaction. Three round-trip tests validate the
new readers.

Also apply AX-2 (comments as usage examples) across 12 files: add
concrete usage-example comments to exported functions in config/,
types/, wire/, chain/, difficulty/, and consensus/. Fix stale doc
in consensus/doc.go that incorrectly referenced *config.ChainConfig.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-05 08:46:54 +01:00

1264 lines
34 KiB
Go

// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// SPDX-License-Identifier: EUPL-1.2
package wire
import (
"fmt"
coreerr "dappco.re/go/core/log"
"dappco.re/go/core/blockchain/types"
)
// EncodeTransactionPrefix serialises the transaction prefix (without
// signatures, attachment, or proofs) in the consensus wire format.
//
// The version field determines the field ordering:
//
// v0/v1: version, vin, vout, extra
// v2+: version, vin, extra, vout, [hardfork_id]
func EncodeTransactionPrefix(enc *Encoder, tx *types.Transaction) {
enc.WriteVarint(tx.Version)
if tx.Version <= types.VersionPreHF4 {
encodePrefixV1(enc, tx)
} else {
encodePrefixV2(enc, tx)
}
}
// EncodeTransaction serialises a full transaction including suffix fields.
func EncodeTransaction(enc *Encoder, tx *types.Transaction) {
EncodeTransactionPrefix(enc, tx)
if tx.Version <= types.VersionPreHF4 {
encodeSuffixV1(enc, tx)
} else {
encodeSuffixV2(enc, tx)
}
}
// DecodeTransactionPrefix deserialises a transaction prefix.
func DecodeTransactionPrefix(dec *Decoder) types.Transaction {
var tx types.Transaction
tx.Version = dec.ReadVarint()
if dec.Err() != nil {
return tx
}
if tx.Version <= types.VersionPreHF4 {
decodePrefixV1(dec, &tx)
} else {
decodePrefixV2(dec, &tx)
}
return tx
}
// DecodeTransaction deserialises a full transaction.
func DecodeTransaction(dec *Decoder) types.Transaction {
tx := DecodeTransactionPrefix(dec)
if dec.Err() != nil {
return tx
}
if tx.Version <= types.VersionPreHF4 {
decodeSuffixV1(dec, &tx)
} else {
decodeSuffixV2(dec, &tx)
}
return tx
}
// --- v0/v1 prefix ---
func encodePrefixV1(enc *Encoder, tx *types.Transaction) {
encodeInputs(enc, tx.Vin)
encodeOutputsV1(enc, tx.Vout)
enc.WriteBytes(tx.Extra) // raw wire bytes including varint count prefix
}
func decodePrefixV1(dec *Decoder, tx *types.Transaction) {
tx.Vin = decodeInputs(dec)
tx.Vout = decodeOutputsV1(dec)
tx.Extra = decodeRawVariantVector(dec)
}
// --- v2+ prefix ---
func encodePrefixV2(enc *Encoder, tx *types.Transaction) {
encodeInputs(enc, tx.Vin)
enc.WriteBytes(tx.Extra)
encodeOutputsV2(enc, tx.Vout)
if tx.Version >= types.VersionPostHF5 {
enc.WriteUint8(tx.HardforkID)
}
}
func decodePrefixV2(dec *Decoder, tx *types.Transaction) {
tx.Vin = decodeInputs(dec)
tx.Extra = decodeRawVariantVector(dec)
tx.Vout = decodeOutputsV2(dec)
if tx.Version >= types.VersionPostHF5 {
tx.HardforkID = dec.ReadUint8()
}
}
// --- v0/v1 suffix (signatures + attachment) ---
func encodeSuffixV1(enc *Encoder, tx *types.Transaction) {
enc.WriteVarint(uint64(len(tx.Signatures)))
for _, ring := range tx.Signatures {
enc.WriteVarint(uint64(len(ring)))
for i := range ring {
enc.WriteBlob64((*[64]byte)(&ring[i]))
}
}
enc.WriteBytes(tx.Attachment)
}
func decodeSuffixV1(dec *Decoder, tx *types.Transaction) {
sigCount := dec.ReadVarint()
if sigCount > 0 && dec.Err() == nil {
tx.Signatures = make([][]types.Signature, sigCount)
for i := uint64(0); i < sigCount; i++ {
ringSize := dec.ReadVarint()
if ringSize > 0 && dec.Err() == nil {
tx.Signatures[i] = make([]types.Signature, ringSize)
for j := uint64(0); j < ringSize; j++ {
dec.ReadBlob64((*[64]byte)(&tx.Signatures[i][j]))
}
}
}
}
tx.Attachment = decodeRawVariantVector(dec)
}
// --- v2+ suffix (attachment + signatures + proofs) ---
func encodeSuffixV2(enc *Encoder, tx *types.Transaction) {
enc.WriteBytes(tx.Attachment)
enc.WriteBytes(tx.SignaturesRaw)
enc.WriteBytes(tx.Proofs)
}
func decodeSuffixV2(dec *Decoder, tx *types.Transaction) {
tx.Attachment = decodeRawVariantVector(dec)
tx.SignaturesRaw = decodeRawVariantVector(dec)
tx.Proofs = decodeRawVariantVector(dec)
}
// --- inputs ---
func encodeInputs(enc *Encoder, vin []types.TxInput) {
enc.WriteVarint(uint64(len(vin)))
for _, in := range vin {
enc.WriteVariantTag(in.InputType())
switch v := in.(type) {
case types.TxInputGenesis:
enc.WriteVarint(v.Height)
case types.TxInputToKey:
enc.WriteVarint(v.Amount)
encodeKeyOffsets(enc, v.KeyOffsets)
enc.WriteBlob32((*[32]byte)(&v.KeyImage))
enc.WriteBytes(v.EtcDetails)
case types.TxInputHTLC:
// Wire order: HTLCOrigin (string) is serialised before parent fields.
encodeStringField(enc, v.HTLCOrigin)
enc.WriteVarint(v.Amount)
encodeKeyOffsets(enc, v.KeyOffsets)
enc.WriteBlob32((*[32]byte)(&v.KeyImage))
enc.WriteBytes(v.EtcDetails)
case types.TxInputMultisig:
enc.WriteVarint(v.Amount)
enc.WriteBlob32((*[32]byte)(&v.MultisigOutID))
enc.WriteVarint(v.SigsCount)
enc.WriteBytes(v.EtcDetails)
case types.TxInputZC:
encodeKeyOffsets(enc, v.KeyOffsets)
enc.WriteBlob32((*[32]byte)(&v.KeyImage))
enc.WriteBytes(v.EtcDetails)
default:
enc.err = coreerr.E("encodeInputs", fmt.Sprintf("wire: unsupported input type %T", in), nil)
return
}
}
}
func decodeInputs(dec *Decoder) []types.TxInput {
n := dec.ReadVarint()
if n == 0 || dec.Err() != nil {
return nil
}
vin := make([]types.TxInput, 0, n)
for i := uint64(0); i < n; i++ {
tag := dec.ReadVariantTag()
if dec.Err() != nil {
return vin
}
switch tag {
case types.InputTypeGenesis:
vin = append(vin, types.TxInputGenesis{Height: dec.ReadVarint()})
case types.InputTypeToKey:
var in types.TxInputToKey
in.Amount = dec.ReadVarint()
in.KeyOffsets = decodeKeyOffsets(dec)
dec.ReadBlob32((*[32]byte)(&in.KeyImage))
in.EtcDetails = decodeRawVariantVector(dec)
vin = append(vin, in)
case types.InputTypeHTLC:
var in types.TxInputHTLC
in.HTLCOrigin = decodeStringField(dec)
in.Amount = dec.ReadVarint()
in.KeyOffsets = decodeKeyOffsets(dec)
dec.ReadBlob32((*[32]byte)(&in.KeyImage))
in.EtcDetails = decodeRawVariantVector(dec)
vin = append(vin, in)
case types.InputTypeMultisig:
var in types.TxInputMultisig
in.Amount = dec.ReadVarint()
dec.ReadBlob32((*[32]byte)(&in.MultisigOutID))
in.SigsCount = dec.ReadVarint()
in.EtcDetails = decodeRawVariantVector(dec)
vin = append(vin, in)
case types.InputTypeZC:
var in types.TxInputZC
in.KeyOffsets = decodeKeyOffsets(dec)
dec.ReadBlob32((*[32]byte)(&in.KeyImage))
in.EtcDetails = decodeRawVariantVector(dec)
vin = append(vin, in)
default:
dec.err = coreerr.E("decodeInputs", fmt.Sprintf("wire: unsupported input tag 0x%02x", tag), nil)
return vin
}
}
return vin
}
// --- key offsets (txout_ref_v variant vector) ---
func encodeKeyOffsets(enc *Encoder, refs []types.TxOutRef) {
enc.WriteVarint(uint64(len(refs)))
for _, ref := range refs {
enc.WriteVariantTag(ref.Tag)
switch ref.Tag {
case types.RefTypeGlobalIndex:
enc.WriteUint64LE(ref.GlobalIndex)
case types.RefTypeByID:
enc.WriteBlob32((*[32]byte)(&ref.TxID))
enc.WriteVarint(ref.N)
}
}
}
func decodeKeyOffsets(dec *Decoder) []types.TxOutRef {
n := dec.ReadVarint()
if n == 0 || dec.Err() != nil {
return nil
}
refs := make([]types.TxOutRef, n)
for i := uint64(0); i < n; i++ {
refs[i].Tag = dec.ReadVariantTag()
switch refs[i].Tag {
case types.RefTypeGlobalIndex:
refs[i].GlobalIndex = dec.ReadUint64LE()
case types.RefTypeByID:
dec.ReadBlob32((*[32]byte)(&refs[i].TxID))
refs[i].N = dec.ReadVarint()
default:
dec.err = coreerr.E("decodeKeyOffsets", fmt.Sprintf("wire: unsupported ref tag 0x%02x", refs[i].Tag), nil)
return refs
}
}
return refs
}
func encodeTxOutTarget(enc *Encoder, target types.TxOutTarget, context string) bool {
switch t := target.(type) {
case types.TxOutToKey:
enc.WriteVariantTag(types.TargetTypeToKey)
enc.WriteBlob32((*[32]byte)(&t.Key))
enc.WriteUint8(t.MixAttr)
case types.TxOutMultisig:
enc.WriteVariantTag(types.TargetTypeMultisig)
enc.WriteVarint(t.MinimumSigs)
enc.WriteVarint(uint64(len(t.Keys)))
for i := range t.Keys {
enc.WriteBlob32((*[32]byte)(&t.Keys[i]))
}
case types.TxOutHTLC:
enc.WriteVariantTag(types.TargetTypeHTLC)
enc.WriteBlob32((*[32]byte)(&t.HTLCHash))
enc.WriteUint8(t.Flags)
enc.WriteVarint(t.Expiration)
enc.WriteBlob32((*[32]byte)(&t.PKRedeem))
enc.WriteBlob32((*[32]byte)(&t.PKRefund))
default:
enc.err = coreerr.E(context, fmt.Sprintf("wire: unsupported output target type %T", target), nil)
return false
}
return true
}
func decodeTxOutTarget(dec *Decoder, tag uint8, context string) types.TxOutTarget {
switch tag {
case types.TargetTypeToKey:
var t types.TxOutToKey
dec.ReadBlob32((*[32]byte)(&t.Key))
t.MixAttr = dec.ReadUint8()
return t
case types.TargetTypeMultisig:
var t types.TxOutMultisig
t.MinimumSigs = dec.ReadVarint()
keyCount := dec.ReadVarint()
t.Keys = make([]types.PublicKey, keyCount)
for i := uint64(0); i < keyCount; i++ {
dec.ReadBlob32((*[32]byte)(&t.Keys[i]))
}
return t
case types.TargetTypeHTLC:
var t types.TxOutHTLC
dec.ReadBlob32((*[32]byte)(&t.HTLCHash))
t.Flags = dec.ReadUint8()
t.Expiration = dec.ReadVarint()
dec.ReadBlob32((*[32]byte)(&t.PKRedeem))
dec.ReadBlob32((*[32]byte)(&t.PKRefund))
return t
default:
dec.err = coreerr.E(context, fmt.Sprintf("wire: unsupported target tag 0x%02x", tag), nil)
return nil
}
}
// --- outputs ---
// encodeOutputsV1 serialises v0/v1 outputs. In v0/v1, outputs are tx_out_bare
// directly without an outer tx_out_v variant tag.
func encodeOutputsV1(enc *Encoder, vout []types.TxOutput) {
enc.WriteVarint(uint64(len(vout)))
for _, out := range vout {
switch v := out.(type) {
case types.TxOutputBare:
enc.WriteVarint(v.Amount)
// Target is a variant (txout_target_v)
if !encodeTxOutTarget(enc, v.Target, "encodeOutputsV1") {
return
}
default:
enc.err = coreerr.E("encodeOutputsV1", fmt.Sprintf("wire: unsupported output type %T", out), nil)
return
}
}
}
func decodeOutputsV1(dec *Decoder) []types.TxOutput {
n := dec.ReadVarint()
if n == 0 || dec.Err() != nil {
return nil
}
vout := make([]types.TxOutput, 0, n)
for i := uint64(0); i < n; i++ {
var out types.TxOutputBare
out.Amount = dec.ReadVarint()
tag := dec.ReadVariantTag()
if dec.Err() != nil {
return vout
}
out.Target = decodeTxOutTarget(dec, tag, "decodeOutputsV1")
if dec.Err() != nil {
return vout
}
vout = append(vout, out)
}
return vout
}
// encodeOutputsV2 serialises v2+ outputs with outer tx_out_v variant tags.
func encodeOutputsV2(enc *Encoder, vout []types.TxOutput) {
enc.WriteVarint(uint64(len(vout)))
for _, out := range vout {
enc.WriteVariantTag(out.OutputType())
switch v := out.(type) {
case types.TxOutputBare:
enc.WriteVarint(v.Amount)
if !encodeTxOutTarget(enc, v.Target, "encodeOutputsV2") {
return
}
case types.TxOutputZarcanum:
enc.WriteBlob32((*[32]byte)(&v.StealthAddress))
enc.WriteBlob32((*[32]byte)(&v.ConcealingPoint))
enc.WriteBlob32((*[32]byte)(&v.AmountCommitment))
enc.WriteBlob32((*[32]byte)(&v.BlindedAssetID))
enc.WriteUint64LE(v.EncryptedAmount)
enc.WriteUint8(v.MixAttr)
default:
enc.err = coreerr.E("encodeOutputsV2", fmt.Sprintf("wire: unsupported output type %T", out), nil)
return
}
}
}
func decodeOutputsV2(dec *Decoder) []types.TxOutput {
n := dec.ReadVarint()
if n == 0 || dec.Err() != nil {
return nil
}
vout := make([]types.TxOutput, 0, n)
for i := uint64(0); i < n; i++ {
tag := dec.ReadVariantTag()
if dec.Err() != nil {
return vout
}
switch tag {
case types.OutputTypeBare:
var out types.TxOutputBare
out.Amount = dec.ReadVarint()
targetTag := dec.ReadVariantTag()
out.Target = decodeTxOutTarget(dec, targetTag, "decodeOutputsV2")
if dec.Err() != nil {
return vout
}
vout = append(vout, out)
case types.OutputTypeZarcanum:
var out types.TxOutputZarcanum
dec.ReadBlob32((*[32]byte)(&out.StealthAddress))
dec.ReadBlob32((*[32]byte)(&out.ConcealingPoint))
dec.ReadBlob32((*[32]byte)(&out.AmountCommitment))
dec.ReadBlob32((*[32]byte)(&out.BlindedAssetID))
out.EncryptedAmount = dec.ReadUint64LE()
out.MixAttr = dec.ReadUint8()
vout = append(vout, out)
default:
dec.err = coreerr.E("decodeOutputsV2", fmt.Sprintf("wire: unsupported output tag 0x%02x", tag), nil)
return vout
}
}
return vout
}
// --- raw variant vector encoding ---
// These helpers handle variant vectors stored as opaque raw bytes.
// The raw bytes include the varint count prefix and all tagged elements.
// decodeRawVariantVector reads a variant vector from the decoder and returns
// the raw wire bytes (including the varint count prefix). This enables
// bit-identical round-tripping of extra, attachment, etc_details, and proofs
// without needing to understand every variant type.
//
// For each element, the tag byte determines how to find the element boundary.
// Known fixed-size tags are skipped by size; unknown tags cause an error.
func decodeRawVariantVector(dec *Decoder) []byte {
if dec.err != nil {
return nil
}
// Read the count and capture the varint bytes.
count := dec.ReadVarint()
if dec.err != nil {
return nil
}
if count == 0 {
return EncodeVarint(0) // just the count prefix
}
// Build the raw bytes: start with the count varint.
raw := EncodeVarint(count)
for i := uint64(0); i < count; i++ {
tag := dec.ReadUint8()
if dec.err != nil {
return nil
}
raw = append(raw, tag)
data := readVariantElementData(dec, tag)
if dec.err != nil {
return nil
}
raw = append(raw, data...)
}
return raw
}
// Variant element tags from SET_VARIANT_TAGS (currency_basic.h:1249-1322).
// These are used by readVariantElementData to determine element boundaries
// when reading raw variant vectors (extra, attachment, etc_details).
const (
tagTxComment = 7 // tx_comment — string
tagTxPayerOld = 8 // tx_payer_old — 2 public keys
tagString = 9 // std::string — string
tagTxCryptoChecksum = 10 // tx_crypto_checksum — two uint32 LE
tagTxDerivationHint = 11 // tx_derivation_hint — string
tagTxServiceAttachment = 12 // tx_service_attachment — 3 strings + vector<key> + uint8
tagUnlockTime = 14 // etc_tx_details_unlock_time — varint
tagExpirationTime = 15 // etc_tx_details_expiration_time — varint
tagTxDetailsFlags = 16 // etc_tx_details_flags — varint
tagSignedParts = 17 // signed_parts — two varints (n_outs, n_extras)
tagExtraAttachmentInfo = 18 // extra_attachment_info — string + hash + varint
tagExtraUserData = 19 // extra_user_data — string
tagExtraAliasEntryOld = 20 // extra_alias_entry_old — complex
tagExtraPadding = 21 // extra_padding — vector<uint8>
tagPublicKey = 22 // crypto::public_key — 32 bytes
tagEtcTxFlags16 = 23 // etc_tx_flags16_t — uint16 LE
tagUint16 = 24 // uint16_t — uint16 LE
tagUint64 = 26 // uint64_t — varint
tagEtcTxTime = 27 // etc_tx_time — varint
tagUint32 = 28 // uint32_t — uint32 LE
tagTxReceiverOld = 29 // tx_receiver_old — 2 public keys
tagUnlockTime2 = 30 // etc_tx_details_unlock_time2 — vector of entries
tagTxPayer = 31 // tx_payer — 2 keys + optional flag
tagTxReceiver = 32 // tx_receiver — 2 keys + optional flag
tagExtraAliasEntry = 33 // extra_alias_entry — complex
tagZarcanumTxDataV1 = 39 // zarcanum_tx_data_v1 — varint (fee)
// Signature variant tags (signature_v).
tagNLSAGSig = 42 // NLSAG_sig — vector<signature>
tagZCSig = 43 // ZC_sig — 2 public_keys + CLSAG_GGX
tagVoidSig = 44 // void_sig — empty
tagZarcanumSig = 45 // zarcanum_sig — complex
// Asset operation tags (HF5 confidential assets).
tagAssetDescriptorOperation = types.AssetDescriptorOperationTag // asset_descriptor_operation
tagAssetOperationProof = 49 // asset_operation_proof
tagAssetOperationOwnershipProof = 50 // asset_operation_ownership_proof
tagAssetOperationOwnershipProofETH = 51 // asset_operation_ownership_proof_eth
// Proof variant tags (proof_v).
tagZCAssetSurjectionProof = 46 // vector<BGE_proof_s>
tagZCOutsRangeProof = 47 // bpp_serialized + aggregation_proof
tagZCBalanceProof = 48 // generic_double_schnorr_sig_s (96 bytes)
)
// readVariantElementData reads the data portion of a variant element (after the
// tag byte) and returns the raw bytes. The tag determines the expected size.
func readVariantElementData(dec *Decoder, tag uint8) []byte {
switch tag {
// 32-byte fixed blob
case tagPublicKey:
return dec.ReadBytes(32)
// String fields (varint length + bytes)
case tagTxComment, tagString, tagTxDerivationHint, tagExtraUserData:
return readStringBlob(dec)
// Varint fields (structs with VARINT_FIELD)
case tagUnlockTime, tagExpirationTime, tagTxDetailsFlags, tagEtcTxTime:
v := dec.ReadVarint()
if dec.err != nil {
return nil
}
return EncodeVarint(v)
// Fixed-size integer fields
case tagUint64: // raw uint64_t — do_serialize → serialize_int → 8-byte LE
return dec.ReadBytes(8)
case tagTxCryptoChecksum: // two uint32 LE
return dec.ReadBytes(8)
case tagUint32: // uint32 LE
return dec.ReadBytes(4)
case tagSignedParts: // two varints: n_outs + n_extras
return readSignedParts(dec)
case tagEtcTxFlags16, tagUint16: // uint16 LE
return dec.ReadBytes(2)
// Vector of uint8 (varint count + bytes)
case tagExtraPadding:
return readVariantVectorFixed(dec, 1)
// Struct types: 2 public keys (64 bytes)
case tagTxPayerOld, tagTxReceiverOld:
return dec.ReadBytes(64)
// Struct types: 2 public keys + optional flag
case tagTxPayer, tagTxReceiver:
return readTxPayer(dec)
// Alias types
case tagExtraAliasEntryOld:
return readExtraAliasEntryOld(dec)
case tagExtraAliasEntry:
return readExtraAliasEntry(dec)
// Composite types
case tagExtraAttachmentInfo:
return readExtraAttachmentInfo(dec)
case tagUnlockTime2:
return readUnlockTime2(dec)
case tagTxServiceAttachment:
return readTxServiceAttachment(dec)
// Zarcanum extra variant
case tagZarcanumTxDataV1: // fee — FIELD(fee) → serialize_int → 8-byte LE
return dec.ReadBytes(8)
// Signature variants
case tagNLSAGSig: // vector<signature> (64 bytes each)
return readVariantVectorFixed(dec, 64)
case tagZCSig: // 2 public_keys + CLSAG_GGX_serialized
return readZCSig(dec)
case tagVoidSig: // empty struct
return []byte{}
case tagZarcanumSig: // complex: 10 scalars + bppe + public_key + CLSAG_GGXXG
return readZarcanumSig(dec)
// Asset operation variants (HF5)
case tagAssetDescriptorOperation:
return readAssetDescriptorOperation(dec)
case tagAssetOperationProof:
return readAssetOperationProof(dec)
case tagAssetOperationOwnershipProof:
return readAssetOperationOwnershipProof(dec)
case tagAssetOperationOwnershipProofETH:
return readAssetOperationOwnershipProofETH(dec)
// Proof variants
case tagZCAssetSurjectionProof: // vector<BGE_proof_s>
return readZCAssetSurjectionProof(dec)
case tagZCOutsRangeProof: // bpp_serialized + aggregation_proof
return readZCOutsRangeProof(dec)
case tagZCBalanceProof: // generic_double_schnorr_sig_s (3 scalars = 96 bytes)
return dec.ReadBytes(96)
default:
dec.err = coreerr.E("readVariantElementData", fmt.Sprintf("wire: unsupported variant tag 0x%02x (%d)", tag, tag), nil)
return nil
}
}
// encodeStringField writes a string as a varint length prefix followed by
// the UTF-8 bytes.
func encodeStringField(enc *Encoder, s string) {
enc.WriteVarint(uint64(len(s)))
if len(s) > 0 {
enc.WriteBytes([]byte(s))
}
}
// decodeStringField reads a varint-prefixed string and returns the Go string.
func decodeStringField(dec *Decoder) string {
length := dec.ReadVarint()
if dec.err != nil || length == 0 {
return ""
}
data := dec.ReadBytes(int(length))
if dec.err != nil {
return ""
}
return string(data)
}
// readStringBlob reads a varint-prefixed string and returns the raw bytes
// including the length varint.
func readStringBlob(dec *Decoder) []byte {
length := dec.ReadVarint()
if dec.err != nil {
return nil
}
raw := EncodeVarint(length)
if length > 0 {
data := dec.ReadBytes(int(length))
if dec.err != nil {
return nil
}
raw = append(raw, data...)
}
return raw
}
// readVariantVectorFixed reads a vector of fixed-size elements and returns
// the raw bytes including the count varint.
func readVariantVectorFixed(dec *Decoder, elemSize int) []byte {
count := dec.ReadVarint()
if dec.err != nil {
return nil
}
raw := EncodeVarint(count)
if count > 0 {
data := dec.ReadBytes(int(count) * elemSize)
if dec.err != nil {
return nil
}
raw = append(raw, data...)
}
return raw
}
// readExtraAttachmentInfo reads the extra_attachment_info variant (tag 18).
// Structure: cnt_type (string) + hash (32 bytes) + sz (varint).
func readExtraAttachmentInfo(dec *Decoder) []byte {
var raw []byte
// cnt_type: string
str := readStringBlob(dec)
if dec.err != nil {
return nil
}
raw = append(raw, str...)
// hash: 32 bytes
h := dec.ReadBytes(32)
if dec.err != nil {
return nil
}
raw = append(raw, h...)
// sz: varint
v := dec.ReadVarint()
if dec.err != nil {
return nil
}
raw = append(raw, EncodeVarint(v)...)
return raw
}
// readUnlockTime2 reads etc_tx_details_unlock_time2 (tag 30).
// Structure: vector of {varint unlock_time, varint output_index}.
func readUnlockTime2(dec *Decoder) []byte {
count := dec.ReadVarint()
if dec.err != nil {
return nil
}
raw := EncodeVarint(count)
for i := uint64(0); i < count; i++ {
v1 := dec.ReadVarint()
if dec.err != nil {
return nil
}
raw = append(raw, EncodeVarint(v1)...)
v2 := dec.ReadVarint()
if dec.err != nil {
return nil
}
raw = append(raw, EncodeVarint(v2)...)
}
return raw
}
// readTxPayer reads tx_payer / tx_receiver (tags 31 / 32).
// Structure: spend_public_key (32 bytes) + view_public_key (32 bytes)
// + optional_field marker. In the binary_archive, the optional is
// serialised as uint8(1)+data or uint8(0) for empty.
func readTxPayer(dec *Decoder) []byte {
var raw []byte
// Two public keys
keys := dec.ReadBytes(64)
if dec.err != nil {
return nil
}
raw = append(raw, keys...)
// is_auditable flag (optional_field serialised as uint8 presence + data)
marker := dec.ReadUint8()
if dec.err != nil {
return nil
}
raw = append(raw, marker)
if marker != 0 {
b := dec.ReadUint8()
if dec.err != nil {
return nil
}
raw = append(raw, b)
}
return raw
}
// readTxServiceAttachment reads tx_service_attachment (tag 12).
// Structure: service_id (string) + instruction (string) + body (string)
// + security (vector<public_key>) + flags (uint8).
func readTxServiceAttachment(dec *Decoder) []byte {
var raw []byte
// Three string fields
for range 3 {
s := readStringBlob(dec)
if dec.err != nil {
return nil
}
raw = append(raw, s...)
}
// security: vector<crypto::public_key> (varint count + 32*N bytes)
v := readVariantVectorFixed(dec, 32)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
// flags: uint8
b := dec.ReadUint8()
if dec.err != nil {
return nil
}
raw = append(raw, b)
return raw
}
// readExtraAliasEntryOld reads extra_alias_entry_old (tag 20).
// Structure: alias(string) + address(spend_key(32) + view_key(32)) +
// text_comment(string) + sign(vector of generic_schnorr_sig_s, each 64 bytes).
func readExtraAliasEntryOld(dec *Decoder) []byte {
var raw []byte
// m_alias: string
alias := readStringBlob(dec)
if dec.err != nil {
return nil
}
raw = append(raw, alias...)
// m_address: spend_public_key(32) + view_public_key(32) = 64 bytes
addr := dec.ReadBytes(64)
if dec.err != nil {
return nil
}
raw = append(raw, addr...)
// m_text_comment: string
comment := readStringBlob(dec)
if dec.err != nil {
return nil
}
raw = append(raw, comment...)
// m_sign: vector<generic_schnorr_sig_s> (each is 2 scalars = 64 bytes)
v := readVariantVectorFixed(dec, 64)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
return raw
}
// readExtraAliasEntry reads extra_alias_entry (tag 33).
// Structure: alias(string) + address(spend_key(32) + view_key(32) + optional flag) +
// text_comment(string) + sign(vector of generic_schnorr_sig_s, each 64 bytes) +
// view_key(optional secret_key, 32 bytes).
func readExtraAliasEntry(dec *Decoder) []byte {
var raw []byte
// m_alias: string
alias := readStringBlob(dec)
if dec.err != nil {
return nil
}
raw = append(raw, alias...)
// m_address: account_public_address with optional is_auditable flag
// Same wire format as tx_payer (tag 31): spend_key(32) + view_key(32) + optional
addr := readTxPayer(dec)
if dec.err != nil {
return nil
}
raw = append(raw, addr...)
// m_text_comment: string
comment := readStringBlob(dec)
if dec.err != nil {
return nil
}
raw = append(raw, comment...)
// m_sign: vector<generic_schnorr_sig_s> (each is 2 scalars = 64 bytes)
v := readVariantVectorFixed(dec, 64)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
// m_view_key: optional<crypto::secret_key> — uint8 marker + 32 bytes if present
marker := dec.ReadUint8()
if dec.err != nil {
return nil
}
raw = append(raw, marker)
if marker != 0 {
key := dec.ReadBytes(32)
if dec.err != nil {
return nil
}
raw = append(raw, key...)
}
return raw
}
// readSignedParts reads signed_parts (tag 17).
// Structure: n_outs (varint) + n_extras (varint).
func readSignedParts(dec *Decoder) []byte {
v1 := dec.ReadVarint()
if dec.err != nil {
return nil
}
raw := EncodeVarint(v1)
v2 := dec.ReadVarint()
if dec.err != nil {
return nil
}
raw = append(raw, EncodeVarint(v2)...)
return raw
}
// --- crypto blob readers ---
// These read variable-length serialised crypto structures and return raw bytes.
// All vectors are varint(count) + 32*count bytes (scalars or points).
// readVectorOfPoints reads a vector of 32-byte points/scalars.
// Returns raw bytes including the varint count prefix.
func readVectorOfPoints(dec *Decoder) []byte {
return readVariantVectorFixed(dec, 32)
}
// readBPPSerialized reads a bpp_signature_serialized.
// Wire: vec(L) + vec(R) + A0(32) + A(32) + B(32) + r(32) + s(32) + delta(32).
func readBPPSerialized(dec *Decoder) []byte {
var raw []byte
// L: vector of points
v := readVectorOfPoints(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
// R: vector of points
v = readVectorOfPoints(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
// 6 fixed scalars: A0, A, B, r, s, delta
b := dec.ReadBytes(6 * 32)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
return raw
}
// readBPPESerialized reads a bppe_signature_serialized.
// Wire: vec(L) + vec(R) + A0(32) + A(32) + B(32) + r(32) + s(32) + delta_1(32) + delta_2(32).
func readBPPESerialized(dec *Decoder) []byte {
var raw []byte
v := readVectorOfPoints(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
v = readVectorOfPoints(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
// 7 fixed scalars: A0, A, B, r, s, delta_1, delta_2
b := dec.ReadBytes(7 * 32)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
return raw
}
// readBGEProof reads a BGE_proof_s.
// Wire: A(32) + B(32) + vec(Pk) + vec(f) + y(32) + z(32).
func readBGEProof(dec *Decoder) []byte {
var raw []byte
// A + B: 2 fixed points
b := dec.ReadBytes(64)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// Pk: vector of points
v := readVectorOfPoints(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
// f: vector of scalars
v = readVectorOfPoints(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
// y + z: 2 fixed scalars
b = dec.ReadBytes(64)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
return raw
}
// readAggregationProof reads a vector_UG_aggregation_proof_serialized.
// Wire: vec(commitments) + vec(y0s) + vec(y1s) + c(32).
func readAggregationProof(dec *Decoder) []byte {
var raw []byte
// 3 vectors of points/scalars
for range 3 {
v := readVectorOfPoints(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
}
// c: 1 fixed scalar
b := dec.ReadBytes(32)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
return raw
}
// readCLSAG_GGX reads a CLSAG_GGX_signature_serialized.
// Wire: c(32) + vec(r_g) + vec(r_x) + K1(32) + K2(32).
func readCLSAG_GGX(dec *Decoder) []byte {
var raw []byte
// c: 1 fixed scalar
b := dec.ReadBytes(32)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// r_g, r_x: 2 vectors
for range 2 {
v := readVectorOfPoints(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
}
// K1 + K2: 2 fixed points
b = dec.ReadBytes(64)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
return raw
}
// readCLSAG_GGXXG reads a CLSAG_GGXXG_signature_serialized.
// Wire: c(32) + vec(r_g) + vec(r_x) + K1(32) + K2(32) + K3(32) + K4(32).
func readCLSAG_GGXXG(dec *Decoder) []byte {
var raw []byte
// c: 1 fixed scalar
b := dec.ReadBytes(32)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// r_g, r_x: 2 vectors
for range 2 {
v := readVectorOfPoints(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
}
// K1 + K2 + K3 + K4: 4 fixed points
b = dec.ReadBytes(128)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
return raw
}
// --- signature variant readers ---
// readZCSig reads ZC_sig (tag 43).
// Wire: pseudo_out_amount_commitment(32) + pseudo_out_blinded_asset_id(32) + CLSAG_GGX.
func readZCSig(dec *Decoder) []byte {
var raw []byte
// 2 public keys
b := dec.ReadBytes(64)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// CLSAG_GGX_serialized
v := readCLSAG_GGX(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
return raw
}
// readZarcanumSig reads zarcanum_sig (tag 45).
// Wire: d(32) + C(32) + C'(32) + E(32) + c(32) + y0(32) + y1(32) + y2(32) + y3(32) + y4(32)
//
// - bppe_serialized + pseudo_out_amount_commitment(32) + CLSAG_GGXXG.
func readZarcanumSig(dec *Decoder) []byte {
var raw []byte
// 10 fixed scalars/points
b := dec.ReadBytes(10 * 32)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// E_range_proof: bppe_signature_serialized
v := readBPPESerialized(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
// pseudo_out_amount_commitment: 1 public key
b = dec.ReadBytes(32)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// clsag_ggxxg: CLSAG_GGXXG_signature_serialized
v = readCLSAG_GGXXG(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
return raw
}
// --- proof variant readers ---
// --- HF5 asset operation readers ---
// readAssetDescriptorOperation reads asset_descriptor_operation (tag 40).
// Wire: version(uint8) + operation_type(uint8) + opt_asset_id(uint8 marker
// + 32 bytes if present) + opt_descriptor(uint8 marker + descriptor if
// present) + amount_to_emit(uint64 LE) + amount_to_burn(uint64 LE) +
// etc(vector<uint8>).
//
// Descriptor (AssetDescriptorBase): ticker(string) + full_name(string) +
// total_max_supply(uint64 LE) + current_supply(uint64 LE) +
// decimal_point(uint8) + meta_info(string) + owner_key(32 bytes) +
// etc(vector<uint8>).
func readAssetDescriptorOperation(dec *Decoder) []byte {
raw, _ := parseAssetDescriptorOperation(dec)
return raw
}
// readAssetOperationProof reads asset_operation_proof (tag 49).
// Wire: version(uint8) + generic_schnorr_sig_s(64 bytes) + asset_id(32 bytes)
// + etc(vector<uint8>).
func readAssetOperationProof(dec *Decoder) []byte {
var raw []byte
// ver: uint8
ver := dec.ReadUint8()
if dec.err != nil {
return nil
}
raw = append(raw, ver)
// gss: generic_schnorr_sig_s — 2 scalars (s, c) = 64 bytes
b := dec.ReadBytes(64)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// asset_id: 32-byte hash
b = dec.ReadBytes(32)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// etc: vector<uint8>
v := readVariantVectorFixed(dec, 1)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
return raw
}
// readAssetOperationOwnershipProof reads asset_operation_ownership_proof (tag 50).
// Wire: version(uint8) + generic_schnorr_sig_s(64 bytes) + etc(vector<uint8>).
func readAssetOperationOwnershipProof(dec *Decoder) []byte {
var raw []byte
// ver: uint8
ver := dec.ReadUint8()
if dec.err != nil {
return nil
}
raw = append(raw, ver)
// gss: generic_schnorr_sig_s — 2 scalars (s, c) = 64 bytes
b := dec.ReadBytes(64)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// etc: vector<uint8>
v := readVariantVectorFixed(dec, 1)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
return raw
}
// readAssetOperationOwnershipProofETH reads asset_operation_ownership_proof_eth
// (tag 51). Wire: version(uint8) + eth_sig(65 bytes) + etc(vector<uint8>).
func readAssetOperationOwnershipProofETH(dec *Decoder) []byte {
var raw []byte
// ver: uint8
ver := dec.ReadUint8()
if dec.err != nil {
return nil
}
raw = append(raw, ver)
// eth_sig: 65 bytes (r=32 + s=32 + v=1)
b := dec.ReadBytes(65)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// etc: vector<uint8>
v := readVariantVectorFixed(dec, 1)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
return raw
}
// --- proof variant readers ---
// readZCAssetSurjectionProof reads zc_asset_surjection_proof (tag 46).
// Wire: varint(count) + count * BGE_proof_s.
func readZCAssetSurjectionProof(dec *Decoder) []byte {
count := dec.ReadVarint()
if dec.err != nil {
return nil
}
raw := EncodeVarint(count)
for i := uint64(0); i < count; i++ {
b := readBGEProof(dec)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
}
return raw
}
// readZCOutsRangeProof reads zc_outs_range_proof (tag 47).
// Wire: bpp_signature_serialized + vector_UG_aggregation_proof_serialized.
func readZCOutsRangeProof(dec *Decoder) []byte {
var raw []byte
// bpp
v := readBPPSerialized(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
// aggregation_proof
v = readAggregationProof(dec)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
return raw
}