zcashblob

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 6 Imported by: 0

README

go-zcashblob

CI Security Go Reference

go-zcashblob is a dependency-free Go library for parsing, serializing, and hashing Zcash version 5 raw transactions.

A blob is an opaque sequence of binary bytes. A Zcash transaction blob is the exact byte representation accepted and returned by node RPC interfaces. This package turns those bytes into typed transparent, Sapling, and Orchard fields and can rebuild the original bytes without loss.

Features

  • ZIP-225 version 5 transaction parsing and serialization
  • transparent inputs and outputs
  • Sapling spends, outputs, proofs, and authorizing data
  • Orchard actions, aggregated proofs, and authorizing data
  • ZIP-244 non-malleable transaction IDs
  • ZIP-244 authorizing-data commitments
  • canonical CompactSize encoding
  • byte-for-byte parse/serialize round trips
  • bounded allocations and complete writer-error propagation
  • no external dependencies

Installation

go get github.com/ruzcash/go-zcashblob

Example

package main

import (
	"encoding/hex"
	"fmt"
	"log"

	"github.com/ruzcash/go-zcashblob"
)

func main() {
	const rawHex = "050000800a27a726b4d0d6c2c2eb518f68984d02010000000000000000000000000000000000000000000000000000000000000000ffffffff060468984d0200ffffffff00000000"

	blob, err := hex.DecodeString(rawHex)
	if err != nil {
		log.Fatal(err)
	}
	tx, err := zcashblob.Parse(blob)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("transparent inputs: %d\n", len(tx.TransparentInputs))
	fmt.Printf("Sapling outputs: %d\n", len(tx.Sapling.Outputs))
	fmt.Printf("Orchard actions: %d\n", len(tx.Orchard.Actions))
	fmt.Printf("transaction ID: %s\n", tx.TxIDString())
	fmt.Printf("authorization digest: %x\n", tx.AuthDigest())

	rebuilt, err := zcashblob.Serialize(tx)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("round trip preserved %d bytes\n", len(rebuilt))
}

TxID returns the 32 digest bytes in hash-function order. TxIDString returns the byte-reversed, lowercase hexadecimal form conventionally shown by RPCs and block explorers.

For programmatic construction, NewTransactionV5 initializes the required v5 header and version group ID for a supplied consensus branch ID. Call Transaction.Validate before hashing or serialization when fields were populated manually. Validation checks whether the structure can be encoded by this package; it does not establish consensus validity.

Scope and safety

The parser validates the v5 wire structure. It rejects non-canonical lengths, reserved Orchard flag bits, oversized input, truncated fields, inconsistent authorization-vector lengths during serialization, and trailing bytes.

This package is not a consensus validator. It does not execute transparent scripts or validate monetary ranges, expiry rules, curve points, proofs, or signatures. Only a consensus node can determine whether a transaction is valid for a particular network and chain state.

Versions 1 through 4 are intentionally rejected because they use different pool layouts and transaction-ID rules. MaxTransactionSize, MaxScriptSize, MaxProofSize, and MaxElements are defensive library policies.

The Orchard proof length is preserved exactly instead of being forced to the original ZIP-225 formula. This maintains compatibility across consensus branches that apply different historical proof-shape rules.

Development

Go 1.22 is the minimum supported version for library consumers. Contributors should use the current stable Go release for the complete local preflight:

make ci

See CONTRIBUTING.md for the development workflow, focused test and fuzz commands, platform-independent equivalents, and pull request expectations.

Verification

The repository carries the complete ten-case native-order ZIP-244 corpus as a pinned, checksum-verified fixture. Every vector is checked through both parser entry points for the expected transaction ID and authorization digest, then serialized back to the exact original bytes. The fixture's source revision, checksum, and license are recorded in testdata/README.md.

The independent tests also include:

  • a non-zero, all-fields semantic round trip with a fixed wire-format oracle;
  • field-by-field ZIP-244 commitment checks, including ciphertext split points;
  • a one-bit mutation check across every byte, proving that each still-parseable change affects TxID, AuthDigest, or both;
  • hostile count and length declarations that must fail before large allocations;
  • complete writer fault injection and validation-before-write guarantees;
  • every-byte truncation, CompactSize canonicality, and hash block boundaries;
  • raw parser and CompactSize fuzz targets.

CI tests the minimum supported Go version, oldstable, and stable Go on Linux, plus stable Go on Windows and macOS. Separate jobs run the race detector, go vet, deterministic fuzz smoke tests, and an atomic statement coverage gate of 98%. Extended fuzzing and reachable-vulnerability scanning run on a weekly schedule as well as on demand.

go test -count=1 ./...
go test -race -count=1 ./...
go vet ./...
go test -run='^$' -fuzz='^FuzzParse$' -fuzztime=30s .
go test -run='^$' -fuzz='^FuzzCompactSize$' -fuzztime=30s .

API stability

This project follows Semantic Versioning. While the major version is zero, minor releases may contain documented breaking API changes. Patch releases in the same minor series are intended to remain source compatible, although correctness or security fixes may reject previously accepted malformed data.

Defensive limits exported by the package are library policies rather than Zcash consensus constants. Changes to those policies are called out in the changelog.

Specifications

Project documentation

Licensed under the MIT License.

Documentation

Overview

Package zcashblob parses, serializes, and hashes Zcash ZIP-225 version 5 transaction blobs.

Parse and ParseFromReader accept one complete wire encoding and reject trailing bytes, non-canonical CompactSize integers, unsupported transaction versions, and inputs outside the package safety limits. Serialize and SerializeToWriter perform the inverse operation after structural validation.

NewTransactionV5 initializes the mandatory v5 header fields for callers that construct a transaction. Validate checks that a Transaction can be encoded without discarding fields and under this package's structural and resource limits. It is not a Zcash consensus validator: scripts, amounts, expiry rules, keys, commitments, proofs, and signatures are not cryptographically validated.

TxID and AuthDigest implement the personalized BLAKE2b-256 digest trees from ZIP-244. A Transaction should pass Validate before either digest is computed.

Index

Examples

Constants

View Source
const (
	// OverwinterFlag is bit 31 of a post-Overwinter transaction header. ZIP-225
	// requires this bit to be set for version 5 transactions.
	OverwinterFlag uint32 = 1 << 31
	// Version5 is the transaction version introduced by NU5 and specified by
	// ZIP-225.
	Version5 uint32 = 5
	// VersionGroupIDV5 identifies the ZIP-225 version 5 transaction layout.
	VersionGroupIDV5 uint32 = 0x26A7270A
	// MaxElements is the package safety limit for every attacker-controlled
	// transaction vector.
	MaxElements = (1 << 16) - 1
	// MaxScriptSize is the package safety limit for one transparent script.
	MaxScriptSize = 10 << 20
	// MaxProofSize is the package safety limit for the encoded Orchard proof.
	MaxProofSize = 10 << 20
	// MaxTransactionSize is the package safety limit for input accepted by
	// Parse and ParseFromReader and output accepted by Validate.
	MaxTransactionSize = 16 << 20
)

Variables

View Source
var (
	// ErrNonCanonical indicates a CompactSize value used a longer encoding.
	ErrNonCanonical = errors.New("non-canonical compactSize")
	// ErrTooLarge indicates input data or a transaction field exceeded a safety
	// limit.
	ErrTooLarge = errors.New("declared size exceeds limit")
	// ErrTrailingData indicates bytes remained after a complete transaction.
	ErrTrailingData = errors.New("trailing data after transaction")
	// ErrUnsupportedVersion indicates a non-v5 or non-Overwintered header.
	ErrUnsupportedVersion = errors.New("only overwintered Zcash v5 transactions are supported")
	// ErrInvalidStructure indicates inconsistent transaction fields.
	ErrInvalidStructure = errors.New("invalid transaction structure")
	// ErrNilReader indicates a nil reader was passed to ParseFromReader.
	ErrNilReader = errors.New("nil reader")
	// ErrNilWriter indicates a nil writer was passed to SerializeToWriter.
	ErrNilWriter = errors.New("nil writer")
)

Functions

func Serialize

func Serialize(tx *Transaction) ([]byte, error)

Serialize encodes tx in the ZIP-225 version 5 wire format. It returns the same validation errors as Transaction.Validate.

func SerializeToWriter

func SerializeToWriter(tx *Transaction, w io.Writer) error

SerializeToWriter writes tx in the ZIP-225 version 5 wire format. It calls Transaction.Validate before writing the first byte.

Example
package main

import (
	"bytes"
	"fmt"
	"log"

	"github.com/ruzcash/go-zcashblob"
)

func main() {
	tx := zcashblob.NewTransactionV5(0xc2d6d0b4)
	tx.TransparentOutputs = []zcashblob.TxOut{{Value: 1, ScriptPubKey: []byte{0x51}}}

	var wire bytes.Buffer
	if err := zcashblob.SerializeToWriter(tx, &wire); err != nil {
		log.Fatal(err)
	}
	parsed, err := zcashblob.ParseFromReader(&wire)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("outputs=%d value=%d\n",
		len(parsed.TransparentOutputs), parsed.TransparentOutputs[0].Value)

}
Output:
outputs=1 value=1

Types

type OrchardAction

type OrchardAction struct {
	// CV is the value commitment to the input note's value minus the output
	// note's value.
	CV [32]byte
	// Nullifier identifies the input note without revealing it.
	Nullifier [32]byte
	// RK is the randomized validating key for the corresponding spend
	// authorization signature.
	RK [32]byte
	// CMX is the x-coordinate of the output note commitment.
	CMX [32]byte
	// EphemeralKey is an encoded ephemeral Pallas public key.
	EphemeralKey [32]byte
	// EncCiphertext contains the encrypted note plaintext.
	EncCiphertext [580]byte
	// OutCiphertext encrypts the outgoing recovery data.
	OutCiphertext [80]byte
}

OrchardAction contains the effecting data of a v5 Orchard action.

type OrchardBundle

type OrchardBundle struct {
	// Actions contains Orchard action descriptions.
	Actions []OrchardAction
	// Flags contains enableSpendsOrchard in bit 0 and enableOutputsOrchard in
	// bit 1. All other bits are reserved and rejected by Parse and Validate.
	Flags byte
	// ValueBalance is the net value of Orchard spends minus outputs in
	// zatoshis.
	ValueBalance int64
	// Anchor is a root of the Orchard note commitment tree.
	Anchor [32]byte
	// Proofs is the encoded aggregated zk-SNARK proof for Actions, without its
	// CompactSize length prefix. Its exact length is preserved.
	Proofs []byte
	// SpendAuthSigs contains one authorization signature per element of Actions.
	SpendAuthSigs [][64]byte
	// BindingSig is the Orchard binding signature.
	BindingSig [64]byte
}

OrchardBundle contains the Orchard effecting and authorizing data of a v5 transaction. All fields after Actions are encoded only when Actions is non-empty.

type OutPoint

type OutPoint struct {
	// Hash is the previous transaction identifier in ZIP-225 wire byte order.
	Hash [32]byte
	// Index selects an output of the previous transaction.
	Index uint32
}

OutPoint identifies a transparent output of an earlier transaction.

type SaplingBundle

type SaplingBundle struct {
	// Spends contains the effecting portion of each Sapling spend.
	Spends []SaplingSpend
	// Outputs contains the effecting portion of each Sapling output.
	Outputs []SaplingOutput
	// ValueBalance is the net value of Sapling spends minus outputs in
	// zatoshis. It is encoded only when the bundle has a spend or output.
	ValueBalance int64
	// Anchor is a root of the Sapling note commitment tree. It is shared by
	// every spend and encoded only when Spends is non-empty.
	Anchor [32]byte
	// SpendProofs contains one zk-SNARK proof per element of Spends.
	SpendProofs [][192]byte
	// SpendAuthSigs contains one authorization signature per element of Spends.
	SpendAuthSigs [][64]byte
	// OutputProofs contains one zk-SNARK proof per element of Outputs.
	OutputProofs [][192]byte
	// BindingSig is the Sapling binding signature. It is encoded only when the
	// bundle has a spend or output.
	BindingSig [64]byte
}

SaplingBundle contains the Sapling effecting and authorizing data of a v5 transaction. Proof and signature slices have one-to-one, index-preserving correspondence with Spends or Outputs; Validate enforces those lengths.

type SaplingOutput

type SaplingOutput struct {
	// CV is the value commitment to the output note's value.
	CV [32]byte
	// CMU is the u-coordinate of the output note commitment.
	CMU [32]byte
	// EphemeralKey is an encoded ephemeral Jubjub public key.
	EphemeralKey [32]byte
	// EncCiphertext contains the encrypted note plaintext.
	EncCiphertext [580]byte
	// OutCiphertext encrypts the outgoing recovery data.
	OutCiphertext [80]byte
}

SaplingOutput contains the effecting data of a v5 Sapling output description.

type SaplingSpend

type SaplingSpend struct {
	// CV is the value commitment to the input note's value.
	CV [32]byte
	// Nullifier identifies the input note without revealing it.
	Nullifier [32]byte
	// RK is the randomized validating key for the corresponding spend
	// authorization signature.
	RK [32]byte
}

SaplingSpend contains the effecting data of a v5 Sapling spend description. Its proof and authorization signature occupy the corresponding indices in SaplingBundle.SpendProofs and SaplingBundle.SpendAuthSigs.

type Transaction

type Transaction struct {
	// Header contains the Overwinter flag in bit 31 and the transaction version
	// in bits 30 through 0.
	Header uint32
	// VersionGroupID identifies the transaction layout. Version 5 uses
	// VersionGroupIDV5.
	VersionGroupID uint32
	// ConsensusBranchID identifies the target consensus branch and domain
	// separates ZIP-244 top-level digests.
	ConsensusBranchID uint32
	// LockTime is a Unix time or block height encoded as in Bitcoin.
	LockTime uint32
	// ExpiryHeight is the height after which the transaction expires. ZIP-225
	// permits 1 through 499999999, or zero to disable expiry; Validate does not
	// enforce this consensus range.
	ExpiryHeight uint32
	// TransparentInputs contains the transparent inputs in wire order.
	TransparentInputs []TxIn
	// TransparentOutputs contains the transparent outputs in wire order.
	TransparentOutputs []TxOut
	// Sapling contains the Sapling bundle.
	Sapling SaplingBundle
	// Orchard contains the Orchard bundle.
	Orchard OrchardBundle
}

Transaction is a ZIP-225 version 5 transaction. The zero value is not a valid v5 header; use NewTransactionV5 when constructing a transaction.

func NewTransactionV5

func NewTransactionV5(consensusBranchID uint32) *Transaction

NewTransactionV5 returns an empty structurally valid ZIP-225 transaction for consensusBranchID. LockTime and ExpiryHeight are zero and all pool bundles are empty. The caller is responsible for choosing a branch ID suitable for the target network and epoch.

Example
package main

import (
	"fmt"

	"github.com/ruzcash/go-zcashblob"
)

func main() {
	tx := zcashblob.NewTransactionV5(0xc2d6d0b4)
	fmt.Printf("version=%d group=%08x valid=%t\n",
		tx.Version(), tx.VersionGroupID, tx.Validate() == nil)

}
Output:
version=5 group=26a7270a valid=true

func Parse

func Parse(data []byte) (*Transaction, error)

Parse decodes exactly one ZIP-225 version 5 transaction. It rejects trailing bytes, non-canonical CompactSize values, and inputs above the safety limits.

Example
package main

import (
	"encoding/hex"
	"fmt"
	"log"

	"github.com/ruzcash/go-zcashblob"
)

func main() {
	const rawHex = "050000800a27a726b4d0d6c2c2eb518f68984d02010000000000000000000000000000000000000000000000000000000000000000ffffffff060468984d0200ffffffff00000000"
	blob, err := hex.DecodeString(rawHex)
	if err != nil {
		log.Fatal(err)
	}
	tx, err := zcashblob.Parse(blob)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("version=%d inputs=%d outputs=%d\n", tx.Version(), len(tx.TransparentInputs), len(tx.TransparentOutputs))

}
Output:
version=5 inputs=1 outputs=0

func ParseFromReader

func ParseFromReader(r io.Reader) (*Transaction, error)

ParseFromReader reads one bounded transaction from r and passes it to Parse. It buffers the transaction so it can guarantee that no trailing bytes exist.

func (*Transaction) AuthDigest

func (tx *Transaction) AuthDigest() [32]byte

AuthDigest computes the ZIP-244 commitment to transaction authorizing data. Together, TxID and AuthDigest commit to every byte of a v5 transaction. AuthDigest assumes that tx passes Validate; it does not repeat structural validation and does not perform consensus validation.

func (*Transaction) Hash

func (tx *Transaction) Hash() [32]byte

Hash is an alias for TxID and has the same Validate precondition.

func (*Transaction) TxID

func (tx *Transaction) TxID() [32]byte

TxID computes the non-malleable ZIP-244 transaction identifier. The returned bytes are in digest order; RPCs and explorers conventionally display them in reverse order. TxID assumes that tx passes Validate; it does not repeat structural validation and does not perform consensus validation.

func (*Transaction) TxIDString

func (tx *Transaction) TxIDString() string

TxIDString returns the conventional lowercase hexadecimal transaction ID used by Zcash RPCs and block explorers. It reverses the digest-order bytes returned by TxID before encoding them.

TxIDString has the same precondition as TxID: tx should pass Validate.

Example
package main

import (
	"fmt"

	"github.com/ruzcash/go-zcashblob"
)

func main() {
	tx := zcashblob.NewTransactionV5(0xc2d6d0b4)
	fmt.Println(tx.TxIDString())

}
Output:
8e6b6d721fc653ef162daa85b32bff85144b9245add517cf710d5155cf5876df

func (*Transaction) Validate

func (tx *Transaction) Validate() error

Validate reports whether tx has a supported v5 header, internally consistent authorization-vector lengths, conditional-field presence, allowed Orchard flags, and an encoded size within the package safety limits.

Validate only checks the structure needed for safe, unambiguous ZIP-225 serialization. It does not validate Zcash consensus rules, monetary ranges, scripts, expiry, encoded curve points, note commitments, proofs, or signatures. A nil receiver is reported as ErrInvalidStructure.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/ruzcash/go-zcashblob"
)

func main() {
	tx := zcashblob.NewTransactionV5(0xc2d6d0b4)
	tx.Orchard.Actions = make([]zcashblob.OrchardAction, 1)

	err := tx.Validate()
	fmt.Println(errors.Is(err, zcashblob.ErrInvalidStructure))

}
Output:
true

func (*Transaction) Version

func (tx *Transaction) Version() uint32

Version returns the transaction version with the Overwinter flag removed.

type TxIn

type TxIn struct {
	// PreviousOutput identifies the transparent output being spent.
	PreviousOutput OutPoint
	// ScriptSig is the unlocking script, without its CompactSize length prefix.
	ScriptSig []byte
	// Sequence is the input's nSequence value.
	Sequence uint32
}

TxIn is a transparent input, encoded as in Bitcoin by ZIP-225.

type TxOut

type TxOut struct {
	// Value is the output amount in zatoshis. Validate does not check monetary
	// range or consensus rules.
	Value int64
	// ScriptPubKey is the locking script, without its CompactSize length prefix.
	ScriptPubKey []byte
}

TxOut is a transparent output, encoded as in Bitcoin by ZIP-225.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL