Add consensus-critical binary serialisation for blocks and transactions, verified by computing the testnet genesis block hash and matching the C++ daemon output (cb9d5455...4963). Fixes Phase 0 type mismatches (variant tags, field widths, missing fields) and adds encoder/decoder, tree hash, and block/transaction hashing. Key discovery: CryptoNote's get_object_hash(blobdata) prepends varint(length) before hashing, so BlockHash = Keccak256(varint(len) || blob). Co-Authored-By: Charon <charon@lethean.io> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
88 lines
2 KiB
Go
88 lines
2 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 (
|
|
"encoding/binary"
|
|
"io"
|
|
)
|
|
|
|
// Encoder writes consensus-critical binary data to an io.Writer.
|
|
// It uses a sticky error pattern: after the first write error, all
|
|
// subsequent writes become no-ops. Call Err() after a complete
|
|
// encoding sequence to check for failures.
|
|
type Encoder struct {
|
|
w io.Writer
|
|
err error
|
|
buf [10]byte // scratch for LE integers and varints
|
|
}
|
|
|
|
// NewEncoder creates a new Encoder writing to w.
|
|
func NewEncoder(w io.Writer) *Encoder {
|
|
return &Encoder{w: w}
|
|
}
|
|
|
|
// Err returns the first error encountered during encoding.
|
|
func (e *Encoder) Err() error { return e.err }
|
|
|
|
// WriteUint8 writes a single byte.
|
|
func (e *Encoder) WriteUint8(v uint8) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
e.buf[0] = v
|
|
_, e.err = e.w.Write(e.buf[:1])
|
|
}
|
|
|
|
// WriteUint64LE writes a uint64 in little-endian byte order.
|
|
func (e *Encoder) WriteUint64LE(v uint64) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
binary.LittleEndian.PutUint64(e.buf[:8], v)
|
|
_, e.err = e.w.Write(e.buf[:8])
|
|
}
|
|
|
|
// WriteVarint writes a uint64 as a CryptoNote varint (LEB128).
|
|
func (e *Encoder) WriteVarint(v uint64) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
b := EncodeVarint(v)
|
|
_, e.err = e.w.Write(b)
|
|
}
|
|
|
|
// WriteBytes writes raw bytes with no length prefix.
|
|
func (e *Encoder) WriteBytes(b []byte) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
if len(b) == 0 {
|
|
return
|
|
}
|
|
_, e.err = e.w.Write(b)
|
|
}
|
|
|
|
// WriteBlob32 writes a 32-byte fixed-size blob (hash, public key, key image).
|
|
func (e *Encoder) WriteBlob32(b *[32]byte) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
_, e.err = e.w.Write(b[:])
|
|
}
|
|
|
|
// WriteBlob64 writes a 64-byte fixed-size blob (signature).
|
|
func (e *Encoder) WriteBlob64(b *[64]byte) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
_, e.err = e.w.Write(b[:])
|
|
}
|
|
|
|
// WriteVariantTag writes a single-byte variant discriminator.
|
|
func (e *Encoder) WriteVariantTag(tag uint8) {
|
|
e.WriteUint8(tag)
|
|
}
|