go-blockchain/wire/hash.go
Claude 8335b11a85
feat(wire): v2+ transaction serialisation with real testnet verification
Fix three bugs in the v2+ wire format and add complete variant tag handlers
for Zarcanum proof and signature structures. Verified byte-identical
round-trip against a real post-HF4 coinbase transaction from testnet block
101 (1323 bytes, tx hash 543bc3c2...3b61e0).

Bugs fixed:
- V2 suffix order was attachment+proofs, corrected to attachment+signatures+proofs
- TransactionHash was hashing full blob, corrected to prefix-only (matching C++)
- tagSignedParts was reading 4 fixed bytes, corrected to two varints

New: TxInputZC type, SignaturesRaw field, tagZarcanumTxDataV1 handler,
proof tags 46-48, signature tags 42-45, crypto blob readers for BPP/BPPE/
BGE/CLSAG GGX/GGXXG/aggregation proof/double Schnorr structures.

Co-Authored-By: Charon <charon@lethean.io>
2026-02-21 19:09:34 +00:00

73 lines
2.3 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 (
"bytes"
"forge.lthn.ai/core/go-blockchain/types"
)
// BlockHashingBlob builds the blob used to compute a block's hash.
//
// The format (from currency_format_utils_blocks.cpp) is:
//
// serialised_block_header || tree_root_hash || varint(tx_count)
//
// where tx_count = 1 (miner_tx) + len(tx_hashes).
func BlockHashingBlob(b *types.Block) []byte {
var buf bytes.Buffer
enc := NewEncoder(&buf)
EncodeBlockHeader(enc, &b.BlockHeader)
// Compute tree hash over all transaction hashes.
txCount := 1 + len(b.TxHashes)
hashes := make([][32]byte, txCount)
hashes[0] = [32]byte(TransactionPrefixHash(&b.MinerTx))
for i, h := range b.TxHashes {
hashes[i+1] = [32]byte(h)
}
treeRoot := TreeHash(hashes)
buf.Write(treeRoot[:])
buf.Write(EncodeVarint(uint64(txCount)))
return buf.Bytes()
}
// BlockHash computes the block ID (Keccak-256 of the block hashing blob).
//
// The C++ code calls get_object_hash(blobdata) which serialises the string
// through binary_archive before hashing. For std::string this prepends a
// varint length prefix, so the actual hash input is:
//
// varint(len(blob)) || blob
func BlockHash(b *types.Block) types.Hash {
blob := BlockHashingBlob(b)
var prefixed []byte
prefixed = append(prefixed, EncodeVarint(uint64(len(blob)))...)
prefixed = append(prefixed, blob...)
return types.Hash(Keccak256(prefixed))
}
// TransactionHash computes the transaction hash (tx_id).
//
// In the C++ daemon, get_transaction_hash delegates to
// get_transaction_prefix_hash for all versions. The tx_id is always
// Keccak-256 of the serialised prefix (version + inputs + outputs + extra,
// in version-dependent field order).
func TransactionHash(tx *types.Transaction) types.Hash {
return TransactionPrefixHash(tx)
}
// TransactionPrefixHash computes the hash of a transaction prefix.
// This is Keccak-256 of the serialised transaction prefix (version + vin +
// vout + extra, in version-dependent order).
func TransactionPrefixHash(tx *types.Transaction) types.Hash {
var buf bytes.Buffer
enc := NewEncoder(&buf)
EncodeTransactionPrefix(enc, tx)
return types.Hash(Keccak256(buf.Bytes()))
}