Chain-bound signatures
A ZooBC signature is valid on one chain only. Before signing a transaction, a wallet computes a digest that includes the chain's genesis hash, so a payment signed on testnet does not verify on mainnet or devnet, even though the same key gives the same address on every chain. The same binding protects the connections between nodes and the messages signed off the chain.
The digest
The signer never signs the raw transaction bytes. It signs this digest:
digest = SHA3-256("ZBC-TX" ‖ genesis hash ‖ unsigned transaction bytes)
transaction hash = SHA3-256(unsigned transaction bytes ‖ signature)
transaction id = the first 8 bytes of the transaction hash, little-endian
The tag "ZBC-TX" marks the digest as a transaction's; other signed actions use other tags (see below). The genesis hash ties the signature to one chain. Only this chain-bound digest is accepted: a signature over the bare bytes, with no chain bound in, is refused on every chain. See signing a transaction for the full signing procedure.
The genesis hash is the chain's identity
The genesis hash is the hash of a chain's very first block. It is derived from the chain's genesis configuration, so changing any committed value of that configuration gives a new hash, which is a different chain (see genesis block). There is no separate network-id byte anywhere in ZooBC: the genesis hash is the chain's only identity. Devnet, testnet and mainnet each have their own (see devnet, testnet and mainnet), and a relaunched test network gets a new one.
A node reports its chain's genesis_hash and its signing_version on its information route, /api/v1/node/info (see node API), so a wallet or tool can tell which chain it is signing for.
Replay across chains
Suppose a payment is signed on testnet and someone copies it to mainnet. The mainnet node computes the digest with mainnet's genesis hash, gets a different value, and the signature does not verify: the transaction is refused as "not signed for this chain". The amount plays no part, and nothing needs to be added by the wallet.
The same key gives the same address on every ZooBC chain, and an address funded on one chain simply shows a zero balance on another. That is exactly why the binding matters: without it, a payment made on one chain could be copied onto another and move coins there.
Every account type, every signer
ZooBC accounts can be controlled by many kinds of keys: ZooBC's own Ed25519 keys, Bitcoin and Ethereum keys and others (see keys and account types). Every account type signs the same chain-bound digest; some re-hash it first, as their home chain's signature scheme requires (Ethereum-style keys sign its Keccak-256 hash, for example). The participants of a multisignature account sign the chain-bound digest of the inner transaction.
Other signed actions use digests of their own, also bound to the chain: the consents that link or control account groups ("ZBC-GROUP-LINK", "ZBC-GROUP-CONTROL"), the co-signature of a multi-party escrow, and the request that cancels a pending transaction in a node's pool ("ZBC-CANCEL").
Nodes and messages
Chain binding does not stop at transactions:
- The peer handshake. When two nodes connect, the handshake is signed by the node key and carries the genesis hash. A peer of another chain is refused with "Genesis hash mismatch", so a node of one chain never peers with a node of another.
- Signed messages. Messages signed off the chain with the tools use the digest SHA3-256("ZBC-MSG-v2" ‖ genesis hash ‖ message), so a message signed on devnet never verifies on testnet or mainnet (see signed and encrypted messages).
Receipts are the exception inside the protocol: a receipt names no chain, so it carries no chain binding of its own. It does name a reference block by height and hash, which validators check.