zanolib

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2026 License: MIT Imports: 15 Imported by: 0

README

GoDoc Go Report Card Tests

zanolib

Go library for Zano cryptocurrency operations, including address parsing, offline transaction signing, and zero-knowledge proof generation.

Features

  • Address handling — parse, create, and manipulate Zano addresses (standard, integrated, auditable)
  • Offline transaction signing — sign transactions generated by a view-only simplewallet without exposing the spend key to the network
  • Deposit scanning — detect incoming outputs for a wallet directly from a Zano daemon's JSON-RPC, decode amounts/assets, and recover integrated-address payment IDs (view key only)
  • Sending — build, sign and broadcast transactions from scanned deposits (decoy ring selection, multi-asset, fee handling) entirely from the library
  • Cryptographic primitives — CLSAG-GGX ring signatures, Bulletproof+ range proofs, BGE asset surjection proofs, balance proofs
  • Serialization — full EPEE binary serialization compatible with Zano's C++ implementation

Install

go get github.com/KarpelesLab/zanolib

Offline Signatures

Compatible Zano version: 2.1.0.3822.1.19.477 (the ZC→ZC transaction format is unchanged across this range)

This library allows loading unsigned transactions produced by a view-only simplewallet and signing them offline. There are a few caveats:

  • The unsigned transaction is a binary format not meant to be portable — it only works between specific versions of Zano. This library is tested against the versions above and may not work with newer versions. Blob files aren't versioned so structure changes cannot be detected automatically.
  • For now this library only supports ZC→ZC transactions.
Usage
import (
	"crypto/rand"
	"os"

	"github.com/KarpelesLab/zanolib"
)

// Initialize a wallet from a securely stored spend secret.
// Set flags to 1 for auditable wallets.
wallet, err := zanolib.LoadSpendSecret(secret, 0)
if err != nil {
	// handle error
}

// Parse the unsigned transaction produced by simplewallet.
ftp, err := wallet.ParseFTP(unsignedTxBlob)
if err != nil {
	// handle error
}

// Inspect ftp to verify this is the transaction you want to sign.
// ...

// Sign the transaction.
finalized, err := wallet.Sign(rand.Reader, ftp, nil)
if err != nil {
	// handle error
}

// Encrypt and write to disk for broadcast via the view-only wallet.
signed, err := wallet.Encrypt(finalized)
if err != nil {
	// handle error
}
os.WriteFile("zano_tx_signed", signed, 0600)

Deposit Scanning

zanolib can scan the chain for outputs paid to a wallet without running a local daemon — it fetches raw transactions over JSON-RPC from any Zano node and detects/decodes received outputs locally using only the view secret key. The caller stores each watched address together with the last block height it scanned, then resumes from there.

Wallet.ScanTx is the pure, offline core (it takes a parsed transaction and returns the owned outputs plus the decrypted payment ID). The zanorpc package adds a JSON-RPC client and a Scanner driver that walks confirmed block ranges.

import (
	"context"

	"github.com/KarpelesLab/zanolib"
	"github.com/KarpelesLab/zanolib/zanorpc"
)

// A view-only wallet is enough to detect deposits.
wallet, _ := zanolib.LoadSpendSecret(secret, 0)

scanner := zanorpc.NewScanner(wallet, "") // "" = public modchain gateway; or pass a daemon base URL like "http://127.0.0.1:11211"

// Resume from the last height you persisted, up to the current confirmed tip.
last, err := scanner.ScanRange(context.Background(), fromHeight, toHeight, func(d zanorpc.Deposit) error {
	// d.Out.Amount, d.Out.AssetId, d.Out.IsNative — what was received
	// d.PaymentId — integrated-address payment id (attributes the deposit to a user)
	// d.TxId, d.Height, d.GlobalIndex — where it landed (GlobalIndex lets you spend it later)
	creditUser(d)
	return nil
})
// persist `last` as the new watermark; on restart re-scan a small tail to absorb reorgs

Notes:

  • Attribution: give each user a distinct integrated address; d.PaymentId maps a deposit back to the user. Per-wallet addresses work too (then payment IDs are simply absent).
  • Assets: native ZANO and confidential assets are both decoded; d.Out.AssetId / d.Out.IsNative identify which.
  • Confirmations / reorgs: scan only finalized heights and re-scan a small tail on restart. Mempool (0-conf) scanning and spent/key-image tracking are not included.

Sending

With a full wallet (spend secret), the library can build, sign and broadcast a transaction that spends scanned deposits — including decoy ring selection (via getrandom_outs3.bin), multi-asset transfers, and fee handling. zanorpc.Client.SweepTo sends a set of deposits to a recipient, deducting the fee from the native amount:

import (
	"context"

	"github.com/KarpelesLab/zanolib"
	"github.com/KarpelesLab/zanolib/zanorpc"
)

wallet, _ := zanolib.LoadSpendSecret(secret, 0) // full wallet (can sign)
client := zanorpc.New("") // "" = modchain gateway; or a daemon base URL

// deposits: a []*zanorpc.Deposit gathered from scanning (see Deposit Scanning)
signed, raw, status, err := client.SweepTo(
	context.Background(), wallet, deposits,
	"Zx...recipient...", // destination address
	10_000_000_000,      // fee in native atomic units (0.01 ZANO)
	true,                // broadcast
)
// status == "OK" once accepted into the mempool

For finer control, Wallet.BuildTransfer assembles and signs an arbitrary set of TransferInputs and TransferDests (you supply the decoy rings), returning a ready-to-serialize transaction. Native ZANO and confidential assets can be mixed in a single transaction; the fee is the native surplus and every non-native asset must balance.

License

See LICENSE file.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	CRYPTO_HDS_OUT_AMOUNT_BLINDING_MASK = []byte("ZANO_HDS_OUT_AMOUNT_BLIND_MASK_\x00")
)

Functions

func NativeCoinAssetId

func NativeCoinAssetId() zanobase.Value256

NativeCoinAssetId returns the native coin asset id (Value256), useful when constructing native-coin TransferInputs/TransferDests.

Types

type Address

type Address struct {
	Type      AddressType
	Flags     uint8
	SpendKey  []byte
	ViewKey   []byte
	PaymentId []byte
}

func ParseAddress

func ParseAddress(addr string) (*Address, error)

ParseAddress will parse a zano address and return and Address object containing the address

func (*Address) Debug

func (addr *Address) Debug() string

func (*Address) SetPaymentId

func (addr *Address) SetPaymentId(paymentId []byte) error

SetPaymentId sets the payment ID for the given address, and updates the address type accordingly.

func (*Address) String

func (addr *Address) String() string

String returns the address encoded as a standard Zano address

type AddressType

type AddressType uint64

AddressType represents the type of a Zano address, encoded as a varint prefix in the base58-encoded address string.

const (
	PublicAddress           AddressType = 0xc5   // Zx — standard public address
	PublicIntegAddress      AddressType = 0x3678 // iZ — integrated address with payment ID
	PublicIntegAddressV2    AddressType = 0x36f8 // iZ — integrated address V2 (with flags)
	PublicAuditAddress      AddressType = 0x98c8 // aZx — auditable public address
	PublicAuditIntegAddress AddressType = 0x8a49 // aiZX — auditable integrated address
)

func (AddressType) Auditable

func (a AddressType) Auditable() bool

Auditable returns true if this address type is an auditable address.

func (AddressType) HasFlags

func (a AddressType) HasFlags() bool

HasFlags returns true if this address type includes a flags byte in its encoding.

func (AddressType) String

func (a AddressType) String() string

String returns a human-readable representation of the address type.

type FinalizeTxParam

type FinalizeTxParam struct {
	UnlockTime           uint64
	Extra                []*zanobase.Variant         // currency::extra_v
	Attachments          []*zanobase.Variant         // currency::attachment_v
	CryptAddress         *zanobase.AccountPublicAddr // currency::account_public_address
	TxOutsAttr           uint8
	Shuffle              bool
	Flags                uint8
	MultisigId           zanobase.Value256 // crypto::hash
	Sources              []*TxSource       // currency::tx_source_entry
	SelectedTransfers    []zanobase.Varint // not sure why, but this is encoded as "01 00" in the bytestream
	PreparedDestinations []*TxDest         // currency::tx_destination_entry
	ExpirationTime       uint64
	SpendPubKey          *zanobase.Point // only for validations
	TxVersion            uint64
	TxHardforkId         uint64
	ModeSeparateFee      uint64
}

FinalizeTxParam contains the parameters needed to finalize and sign a Zano transaction. It is typically received encrypted from the network and parsed via ParseFTP or Wallet.ParseFTP.

func ParseFTP

func ParseFTP(buf, viewSecretKey []byte) (*FinalizeTxParam, error)

ParseFTP decrypts buf using the provided view secret key and deserializes it into a FinalizeTxParam. Returns an error if decryption or deserialization fails, or if there is trailing data.

type FinalizedTx

type FinalizedTx struct {
	Tx             *zanobase.Transaction     `json:"tx"`
	TxId           zanobase.Value256         `json:"txid"`         // might be zeroes?
	OneTimeKey     *zanobase.Scalar          `json:"one_time_key"` // crypto::secret_key
	FTP            *FinalizeTxParam          `json:"ftp"`
	HtlcOrigin     string                    `json:"htlc_origin"`
	OutsKeyImages  []*zanobase.KeyImageIndex `json:"outs_key_images,omitempty"` // pairs (out_index, key_image) for each change output
	Derivation     zanobase.Value256         `json:"derivation"`                // crypto::key_derivation, a ec_point
	WasNotPrepared bool                      `json:"was_not_prepared"`          // true if tx was not prepared/created for some good reason (e.g. not enough outs for UTXO defragmentation tx). Because we decided not to throw exceptions for non-error cases. -- sowle
}

FinalizedTx represents a fully constructed and signed Zano transaction along with its metadata and the original finalization parameters.

func ParseFinalized

func ParseFinalized(buf, viewSecretKey []byte) (*FinalizedTx, error)

ParseFinalized decrypts buf using the provided view secret key and deserializes it into a FinalizedTx. Returns an error if decryption or deserialization fails, or if there is trailing data.

type GenContext

type GenContext struct {
}

type ReceivedOutput

type ReceivedOutput struct {
	OutputIndex         int               `json:"output_index"`
	Amount              uint64            `json:"amount"`
	AssetId             zanobase.Value256 `json:"asset_id"`
	IsNative            bool              `json:"is_native"`
	StealthAddress      zanobase.Value256 `json:"stealth_address"`
	AmountBlindingMask  *zanobase.Scalar  `json:"amount_blinding_mask"`
	AssetIdBlindingMask *zanobase.Scalar  `json:"asset_id_blinding_mask"`
}

ReceivedOutput is a transaction output detected as belonging to the wallet. AmountBlindingMask and AssetIdBlindingMask are retained so the output can later be turned into a spendable tx_source_entry.

type RingMember

type RingMember struct {
	GlobalIndex      uint64
	StealthAddress   zanobase.Value256
	ConcealingPoint  zanobase.Value256
	AmountCommitment zanobase.Value256
	BlindedAssetId   zanobase.Value256
}

RingMember is one output in an input's decoy ring (the real output plus decoys), identified by its chain-wide global index. The point fields are the on-chain (1/8-premultiplied) values, exactly as returned by the daemon.

type ScanResult

type ScanResult struct {
	TxPubKey  zanobase.Value256 `json:"tx_pub_key"`
	Outputs   []*ReceivedOutput `json:"outputs"`
	PaymentId []byte            `json:"payment_id,omitempty"`
}

ScanResult holds the outputs of a transaction that belong to the wallet, along with the recovered integrated-address payment ID (if any).

func (*ScanResult) Found

func (r *ScanResult) Found() bool

Found reports whether any output of the scanned transaction belongs to the wallet.

type TransferDest

type TransferDest struct {
	Address *Address
	AssetId zanobase.Value256
	Amount  uint64
}

TransferDest is an output to create: send Amount of AssetId to Address.

type TransferInput

type TransferInput struct {
	Amount              uint64
	AssetId             zanobase.Value256 // unblinded asset id (native = native coin asset id)
	AmountBlindingMask  *zanobase.Scalar
	AssetIdBlindingMask *zanobase.Scalar
	RealOutTxKey        zanobase.Value256 // tx public key of the depositing tx
	RealOutIndex        uint64            // output index within the depositing tx
	RealGlobalIndex     uint64            // chain-wide global index of the real output
	Ring                []RingMember      // decoys + the real output
}

TransferInput describes one output to spend: the data recovered by ScanTx (amount, asset id, blinding masks, the depositing tx public key and output index) plus a ring of decoys (which must include the real output, located by RealGlobalIndex).

type TxDest

type TxDest struct {
	Amount          uint64
	Addr            []*zanobase.AccountPublicAddr // account_public_address; destination address, in case of 1 address - txout_to_key, in case of more - txout_multisig
	MinimumSigs     uint64                        // if txout_multisig: minimum signatures that are required to spend this output (minimum_sigs <= addr.size())  IF txout_to_key - not used
	AmountToProvide uint64                        // amount money that provided by initial creator of tx, used with partially created transactions
	UnlockTime      uint64                        //
	HtlcOptions     *TxDestHtlcOut                // destination_option_htlc_out
	AssetId         *zanobase.Point               // not blinded, not premultiplied
	Flags           uint64                        // set of flags (see tx_destination_entry_flags)
}

TxDest represents a transaction output destination including the recipient address, amount, asset ID, and optional HTLC parameters.

func (*TxDest) AmountCommitment

func (dst *TxDest) AmountCommitment(scalar *edwards25519.Scalar, ogc *zanobase.GenContext, i int) *edwards25519.Point

AmountCommitment computes the Pedersen commitment to the output amount: amount * blindedAssetId + blindingMask * G, pre-multiplied by 1/8.

func (*TxDest) BlindedAssetId

func (dst *TxDest) BlindedAssetId(scalar *edwards25519.Scalar, ogc *zanobase.GenContext, i int) *edwards25519.Point

BlindedAssetId computes the blinded asset ID T = assetId + blindingMask * X, pre-multiplied by 1/8. It also stores the blinding mask in ogc.

func (*TxDest) ConcealingPoint

func (dst *TxDest) ConcealingPoint(scalar *edwards25519.Scalar, ogc *zanobase.GenContext, i int) *edwards25519.Point

ConcealingPoint computes the concealing point Q for this output, derived from the recipient's view public key and the derivation scalar.

func (*TxDest) StealthAddress

func (dst *TxDest) StealthAddress(scalar *edwards25519.Scalar, ogc *zanobase.GenContext, i int) *edwards25519.Point

StealthAddress computes the one-time stealth address for this destination using the derivation scalar and the recipient's spend public key.

type TxDestHtlcOut

type TxDestHtlcOut struct {
	Expiration uint64
	HtlcHash   zanobase.Value256 // crypto::hash
}

TxDestHtlcOut contains HTLC (Hash Time-Locked Contract) options for a transaction destination.

type TxSource

type TxSource struct {
	Outputs                    []*TxSourceOutputEntry
	RealOutput                 uint64
	RealOutTxKey               *zanobase.Point  // crypto::public_key
	RealOutAmountBlindingMask  *zanobase.Scalar // crypto::scalar_t
	RealOutAssetIdBlindingMask *zanobase.Scalar // crypto::scalar_t
	RealOutInTxIndex           uint64           // size_t, index in transaction outputs vector
	Amount                     uint64
	TransferIndex              uint64
	MultisigId                 zanobase.Value256 // crypto::hash if txin_multisig: multisig output id
	MsSigsCount                uint64            // size_t
	MsKeysCount                uint64            // size_t
	SeparatelySignedTxComplete bool
	HtlcOrigin                 string // for htlc, specify origin. len = 1, content = "\x00" ?
	// contains filtered or unexported fields
}

TxSource represents a transaction input source with ring members, the real output index, blinding masks, and amount information.

func (*TxSource) IsZC

func (src *TxSource) IsZC() bool

IsZC returns true if this source is a zero-confidential (ZC) input. A source is ZC if explicitly flagged (IsZCInput, set when built from a Zarcanum output) or, for sources parsed from an unsigned-tx blob, if its asset ID blinding mask is non-zero.

type TxSourceOutputEntry

type TxSourceOutputEntry struct {
	OutReference     *zanobase.Variant // TxOutRef // either global output index or ref_by_id
	StealthAddress   *zanobase.Point   // crypto::public_key, a.k.a output's one-time public key
	ConcealingPoint  *zanobase.Point   // only for ZC outputs
	AmountCommitment *zanobase.Point   // only for ZC outputs
	BlindedAssetID   *zanobase.Point   // only for ZC outputs
}

TxSourceOutputEntry represents a single output reference within a transaction source, including its stealth address and commitment data.

type ViewWalletData

type ViewWalletData struct {
	Address        string `json:"address,omitempty"`          // standard Zano address (alternative to the explicit keys)
	SpendPublicKey string `json:"spend_public_key,omitempty"` // hex, 32 bytes
	ViewPublicKey  string `json:"view_public_key,omitempty"`  // hex, 32 bytes (optional)
	ViewSecretKey  string `json:"view_secret_key"`            // hex, 32 bytes
	Flags          uint8  `json:"flags,omitempty"`            // 1 = auditable
	StartHeight    uint64 `json:"start_height"`               // block height to resume scanning from
}

ViewWalletData is a JSON-marshalable description of a view-only wallet plus the block height from which scanning should resume. It carries exactly what the scanner needs: the view secret key, the spend public key, and a start height. Keys are lowercase hex. Either Address or SpendPublicKey must be set; ViewPublicKey is optional and validated against ViewSecretKey when present.

func (*ViewWalletData) LoadViewWallet

func (d *ViewWalletData) LoadViewWallet() (*Wallet, error)

LoadViewWallet builds a view-only Wallet from a ViewWalletData. The StartHeight field is metadata for the scanner and is not part of the wallet; read it from the same struct when calling the scanner.

type Wallet

type Wallet struct {
	SpendPrivKey *edwards25519.Scalar
	SpendPubKey  *edwards25519.Point
	ViewPrivKey  *edwards25519.Scalar
	ViewPubKey   *edwards25519.Point
	Flags        uint8 // flag 1 = auditable
}

Wallet holds the key material for a Zano wallet, including both spend and view key pairs. It is typically created via LoadSpendSecret.

func GenerateWallet

func GenerateWallet(rnd io.Reader, flags uint8) (*Wallet, error)

GenerateWallet creates a brand new wallet with a random spend secret read from rnd (use crypto/rand.Reader). The view key is derived from the spend secret exactly as Zano does. Set flags to 1 for an auditable wallet.

This is handy for receiving test deposits: generate a wallet, fund its Address(), then scan for the incoming output with a view-only copy.

func LoadSpendSecret

func LoadSpendSecret(pk []byte, flags uint8) (*Wallet, error)

LoadSpendSecret initializesd a Wallet based on a spend secret as found in zano if you run spendkey. It will automatically derive the view key using the appropriate method for Zano.

Set flags to zero for normal keys, or 1 for auditable keys.

func LoadViewOnly

func LoadViewOnly(viewSecretKey, spendPublicKey []byte, flags uint8) (*Wallet, error)

LoadViewOnly builds a view-only wallet from a view secret key and a spend public key (the data exported by a Zano watch-only wallet). The resulting wallet can detect and decode incoming outputs (ScanTx) but cannot sign, since the spend secret is absent.

func (*Wallet) Address

func (w *Wallet) Address() *Address

Address returns this wallet's address.

func (*Wallet) BuildTransfer

func (w *Wallet) BuildTransfer(rnd io.Reader, inputs []*TransferInput, dests []*TransferDest, version, hardforkID uint64) (*FinalizedTx, error)

BuildTransfer assembles a FinalizeTxParam from spendable inputs and destinations and signs it, producing a ready-to-broadcast transaction. The fee is implicit: it is the native-asset surplus (sum of native inputs minus native outputs), so the caller controls it by choosing destination amounts. Every non-native asset must balance (inputs == outputs).

func (*Wallet) Encrypt

func (w *Wallet) Encrypt(data any) ([]byte, error)

Encrypt will serialize and encrypt whatever data is passed (can be a FTP or a finalized transaction) so it can be read again.

func (*Wallet) ExportView

func (w *Wallet) ExportView(startHeight uint64) *ViewWalletData

ExportView produces the JSON-marshalable view-only description of this wallet, suitable for handing to a scanner. startHeight is the block height from which scanning should begin (e.g. the wallet's creation height or last-scanned height).

func (*Wallet) IsViewOnly

func (w *Wallet) IsViewOnly() bool

IsViewOnly reports whether the wallet lacks a spend secret key (and so can scan for deposits but cannot sign transactions).

func (*Wallet) ParseFTP

func (w *Wallet) ParseFTP(buf []byte) (*FinalizeTxParam, error)

ParseFTP decrypts and deserializes a finalize transaction parameter blob using this wallet's view private key.

func (*Wallet) ParseFinalized

func (w *Wallet) ParseFinalized(buf []byte) (*FinalizedTx, error)

ParseFinalized decrypts and deserializes a finalized transaction blob using this wallet's view private key.

func (*Wallet) ScanTx

func (w *Wallet) ScanTx(tx *zanobase.Transaction) (*ScanResult, error)

ScanTx inspects a transaction and returns the outputs that belong to this wallet (decoding amount and asset id for each), plus the decrypted payment ID if the transaction carries one. It is the receive-side inverse of Sign and mirrors currency::lookup_acc_outs / is_out_to_acc / decode_output_amount_and_asset_id.

Only the wallet's view secret key is required, so this works for view-only wallets. A transaction with no tx public key, or none of whose outputs belong to the wallet, yields a ScanResult with an empty Outputs slice (not an error).

func (*Wallet) Sign

func (w *Wallet) Sign(rnd io.Reader, ftp *FinalizeTxParam, oneTimeKey *edwards25519.Scalar) (*FinalizedTx, error)

Sign constructs and signs a Zano transaction from the given finalization parameters. If oneTimeKey is nil, a random key is generated. The method builds the transaction structure, generates CLSAG-GGX ring signatures for each input, and produces balance, range, and asset surjection proofs.

Directories

Path Synopsis
Package epee implements a minimal encoder/decoder for epee's "portable storage" binary format — the serialization used by Zano's daemon .bin RPC endpoints (and by wallet key blobs).
Package epee implements a minimal encoder/decoder for epee's "portable storage" binary format — the serialization used by Zano's daemon .bin RPC endpoints (and by wallet key blobs).
Package zanorpc is a minimal JSON-RPC client for a Zano daemon and a deposit scanner built on top of it.
Package zanorpc is a minimal JSON-RPC client for a Zano daemon and a deposit scanner built on top of it.

Jump to

Keyboard shortcuts

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