---
description: Basic data objects used by Nightfall
---

# Nightfall objects

Nightfall has a number of data abstractions that it uses internally. It's possible to use Nightfall without understanding these internal objects but it is always better to have some understanding of what an application that you use is doing.

### The object hierarchy

Below, we will talk about commitments, nullifiers, transactions and blocks. A block contains transactions and a transaction contains commitments and/or nullifiers, at least in some sense.

### Commitments

This is one of the most basic object types. It is somewhat analogous to a token in a conventional blockchain, although one should not stretch the analogy too far.  Mathematically, it is a hash with the preimage comprising a number of items relevant to the layer 1 token it was created from.  Specifically

$$
c = H(ercAddress, tokenId,value,zkpPublicKey,salt)
$$

Where:

* `ercAddress` is the address of the contract of the Layer 1 token.
* `tokenId` is the token ID in the case of an ERC1155 or ERC721 token (`0x00` for an ERC20).
* `value` is the value in the case of an ERC20 or ERC1155 token (0 in the case of an ERC721)
* `zkpPublicKey` is the described below.
* `salt` is a random field value to ensure that each commitment is unique and that the preimage cannot be brute forced.
* &#x20;`H` is a hash function (Nightfall\_3 uses Poseidon).

The commitment is used to prove that you own a certain value of tokens. For example, I can create a ZKP that the preimage of c contains my public key, and that it is my public key because I know the corresponding private key. I could at the same time prove that it has the value `value`. This is done in zero knowledge so that the only data on chain is the hash value `c`. An observer learns nothing about who owns the commitment nor its value. See the [page](how-nightfall-works) about how Nightfall works for more details.

Note that equation 1 is a slight simplification.  The `tokenId` is a 256 bit number and therefore won't fit into a bn254 field. This is a problem if we're using a field-based hash. To get around this, we move the top 4 bytes into the `ercAddress` variable before hashing, which has room because it's only 20 bytes long.

### Nullifier

The nullifier object is used to spend a commitment or, rather, the absence of a nullifier in the blockchain record indicates that a commitment is available to spend. It's purpose, therefore, is to prevent double spending. It needs to be provably related to one and only one commitment and it must only be possible for the commitment owner to create it, but the relationships must not be obvious from the value.  We define it thusly:

$$
n = H(c, nullifierKey)
$$

The `nullifierKey` is a secret key derived from the `zkpPublicKey` used in the commitment. Only the commitment owner will know that. This prevents someone else spending your money. See the section on [in-band secret distribution](broken-reference) for details on key derivation.

### Transaction

The transaction object is both a Node class/object and also a Solidity struct. It contains all the information associated with a transaction:

```solidity
 struct Transaction {
        uint256 packedInfo;
        uint256[] historicRootBlockNumberL2;
        bytes32 tokenId;
        bytes32 ercAddress;
        bytes32 recipientAddress;
        bytes32[] commitments;
        bytes32[] nullifiers;
        bytes32[2] compressedSecrets;
        uint256[4] proof;
    }
```

where:

* `packedInfo` is a word that contains small values compressed into a single EVM word (value, fee, circuitHash, tokenType), where `value` is the value of the token `fee` is the amount to be paid to a Proposer for processing the transaction, `circuitHash` is a hash of the ZKP circuit that the transaction will interact with (it's used to select the correct verifier key), and `tokenType` is one of ERC20, ERC721 or ERC1155.
* historicRootBlockNumberL2 is an array containing the blocks whose roots are used as the root of the Merkle proof that the commitments being nullified actually exist on chain and haven't just been made up.
* `tokenId` is the ERC721 or ERC1155 `tokenId`. It is set to `0x00`  for an ERC20 token.
* `recipientAddress` is the Ethereum address that should receive the token resulting from the finalisation of a Withdraw transaction. It is only populated in the case of a withdraw.
* `commitments` the values of the output commitments that this transaction creates
* `nullifiers` the values of the nullifiers that nullify the input commitments to this transaction.
* `compressedSecrets` these bytes contain the encryption of the compressed `ercAddress`, `salt`, `value` and `tokenId` . In the case of a transfer transaction, the recipient will be able to decrypt these values and take ownership of the commitment transferred to them. Details of how the (de)encryption works are in the section about [in-band secret distribution](in-band-secret-distribution).
* `proof` compressed curve points representing the Groth16 zero-knowledge proof that nightfall 3 users. The compressed curve points are represented as the affine y coordinate, with the first bit of the word representing the parity of the x coordinate, so that the point can be unambiguously reconstructed using the curve equation.

The transaction object is the smallest 'unit of exchange' in Nightfall. It is created by a user and sent to a Proposer for inclusion in a block, either directly or by posting on chain (posting on-chain is required for a deposit but otherwise tends to be a large waste of gas and risks compromising privacy by Gas-tracking).

Note that transaction structs only ever exist as call-data.

### Blocks

Layer 2 blocks represent the roll-up of Nightfall transactions and are submitted on-chain by a Proposer (see [Nightfall's actors](nightfalls-actors)). Like transactions, they are also represented by a Node class and a Solidity struct:

```solidity
struct Block {
        uint256 packedInfo;
        bytes32 root;
        bytes32 previousBlockHash;
        bytes32 frontierHash;
        bytes32 transactionHashesRoot; // This variable needs to be the last one in order proposeBlock to work
}
```

where:

* `packedInfo` contains: the `leafcount`, which is the number of leaves present in the commitment Merkle tree _before_ the commitments in this block are added to the Merkle tree; the address of the Proposer that submitted the block; and the block number (note this is the Layer 2 block number and nothing to do with the Ethereum block numbers).
* `root` the value of the commitment tree root, after the new commitments included in this block have been added.
* `previousBlockHash` the hash of the block prior to this one (ensures that blocks are submitted in the correct order).
* `frontierHash` the hash of the current commitment Merkle tree frontier. Knowing this, greatly reduces the cost of challenging an incorrect commitment tree root.
* `transactionHashesRoot` The transactions associated with this block have their hashes incorporated in a small Merkle tree and the root of the tree is included in the block.  This means that a Challenger can prove (if needed) that a particular block contains a particular transaction by providing a Merkle proof to that effect.

