Signing a transaction
A signature proves that the holder of an account's key approved a transaction, byte for byte. On ZooBC the signer never signs the bare transaction: it signs a digest that also covers the chain's identity, so the signature is valid on one chain only. Accounts of many key types can sign, each with its home chain's scheme, which lets a Bitcoin, Ethereum or Solana key control a ZooBC account directly.
What is signed
The signer takes the unsigned envelope, every field except the signature, and signs this digest:
digest = SHA3-256( "ZBC-TX" ‖ genesis block hash ‖ unsigned bytes )
"ZBC-TX" is an ASCII tag, and ‖ means "followed by". Only this chain-bound digest is accepted. In a version-3 transaction the 4 expiry bytes are part of the unsigned bytes, so they are signed like every other field.
The genesis block hash is the chain's identity. ZooBC has no network-id field: devnet, testnet and mainnet each have their own genesis hash, so a signature made for mainnet does not verify on devnet or testnet, even when the same key holds funds on all three. Nodes also compare genesis hashes before they connect to each other. This closes the cross-chain replay, in which someone copies your transaction from one chain to another; see chain-bound signatures.
The hash and the id
Once signed, a transaction is identified by its hash and its id:
hash = SHA3-256( unsigned bytes ‖ signature )
id = the first 8 bytes of the hash, read as a little-endian signed 64-bit integer
A node refuses an id that is already confirmed, which stops a replay on the same chain. The id of an IssueToken transaction is also the id of the token it creates (see issuing a token).
Key types that can sign
An address starts with a type number, and the type decides how its signature is checked:
| Key type (number) | Signature scheme |
|---|---|
| ZooBC (0), Solana (11) | Ed25519 |
| Cardano (13), Tezos (16) | Ed25519, with the public key carried in a 98-byte signature field |
| Bitcoin legacy (1), P2PKH (5), P2SH-wrapped P2WPKH (6), P2WPKH (7), Ripple (14) | secp256k1 ECDSA over a double SHA-256 of the digest, with the public key carried |
| Ethereum (4), Tron (15) | secp256k1 over Keccak-256 of the digest; the key is recovered from a 65-byte r‖s‖v signature |
| Bitcoin Taproot (9) | BIP-340 Schnorr |
| Polkadot (12) | sr25519 |
ECDSA signatures must use the low-s form. Whatever the scheme, every key signs the same chain-bound digest.
Some address types can never sign: Empty (2), Estonia eID (3, which has no verifier), Bitcoin P2WSH scripts (8) and datasets (10). A P2WSH address stands for a script rather than a single key, and a dataset address names data, not a signer. Keyless accounts, such as the system pools, account groups and multisignature accounts, have no key at all and spend only through their own rules.
Signing with a foreign wallet
A foreign wallet can also sign a transaction of its own chain and have it carried, as the message, inside the matching ZooBC transaction:
- Ethereum: a legacy, EIP-2930 or EIP-1559 transaction with an empty access list;
- Solana: a System transfer;
- Bitcoin: one input, one or two outputs, and a fee of zero.
The node checks that the sender, the recipient and the amount agree with the ZooBC transaction, and a given foreign payload can be spent only once. Such a message may hold up to 4,096 bytes. See MetaMask and Ethereum-style access for Ethereum-style access.
Other signed digests
The same chain-bound pattern, each time with its own tag, covers other consents:
- linking an address to a group and handing over a group's control ("ZBC-GROUP-LINK" and "ZBC-GROUP-CONTROL");
- asking a node to cancel a pending transaction in its pool ("ZBC-CANCEL");
- the co-signature of a multi-party escrow.
Off-chain messages, which are not transactions, are signed as SHA3-256("ZBC-MSG-v2" ‖ genesis hash ‖ message) with an Ed25519 key; see signed and encrypted messages.