// 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 + 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 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 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 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 (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 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) + 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 (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 (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 (each is 2 scalars = 64 bytes) v := readVariantVectorFixed(dec, 64) if dec.err != nil { return nil } raw = append(raw, v...) // m_view_key: optional — 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). // // 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). 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). 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 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). 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 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). 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 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 }