Technical background
In order to discuss and compare different existing approaches with the one we present in the following, we provide a common understanding about relevant cryptographic basics. We start with hash functions, which are an essential component of most cryptographic systems (Schneier 2017). A hash function H is a deterministic function that maps an input x of arbitrary length to an output y of fixed, relatively short, length, aiming to fulfill two essential properties. First, hash functions should be one-way functions, meaning that it is very difficult—nearly impossible—to determine the input x given an output \(y=H(x)\), we call this pre-image resistance. Second, they should be collision resistant, which means that it is very difficult to find two inputs \(x_1\) and \(x_2\) sharing the same output y, i.e., \(H(x_1)=H(x_2)\) cannot be solved efficiently when \(x_1\ne x_2\).
Another essential building block of most blockchain systems are Merkle trees (Merkle 1988). They combine the hierarchical data structure of a tree with the utility of hash functions with the purpose of representing multiple data points efficiently under a single hash. A tree, in general, is a data structure consisting of multiple data points called nodes. Starting from the first node, called the root, every (parent) node has child nodes, which themselves have child nodes, and so on, until the lowest level of nodes is reached. We call these nodes without own child leaves. Each leaf of a Merkle tree represents a hash of one of the data points. Each parent node consists of the hash of the concatenation of all its child nodes. Thereby, each parent node represents its child nodes, and the Merkle root represents all data points (leaves) of the tree. When we speak about Merkle trees, we mean by default fully perfect binary trees, this type of tree consists of nodes having either two or no child nodes and leaves having the same number of (in)direct parent nodes. Besides the properties of hash functions such as pre-image and collision resistance, Merkle trees provide so-called Merkle proofs, which offer the possibility to prove that a data point is part of the tree, represented by the Merkle root, without having to include other data points directly in this proof. In order to do so, the prover just requires the “path” of the data point inside the tree, which are all nodes of the tree with which the leaf of the data point is directly or indirectly hashed. Thereby, pre-image resistance prevents from reconstructing any other data point and collision resistance from creating Merkle proofs for data points which are not part of the tree. The required storage for Merkle trees grows constantly with the number of data points it represents, but the size of a Merkle proof only grows logarithmically.
The third cryptographic building block we use are Zero-Knowledge Proofs (ZKPs). The fundamental concept of a ZKP is to generate a proof that a statement is true, without revealing more useful information than the statement itself (Goldwasser et al. 1989). Examples are proving that a computation for a known output was done correctly without redoing the whole computation process or verifiably showing that one has a certain piece of information that leads to a given hash without presenting it. Like most other cryptographic concepts, ZKPs are based on the hardness of cryptographic problems, meaning that it is “practically infeasable” to generate a valid proof from a false statement. One of the most popular zero-knowledge proving systems for relatively general computer programs are Succinct Non-interactive Arguments of Knowledge (SNARKs) (Ben-Sasson et al. 2013). They consist of two main steps: First, representing a computer program by a polynomial, which is possible for all finite programs, and second proving that the polynomial has specific roots, which could only be obtained if the program was executed correctly, without revealing the full polynomial. For the conversion, so called “circuits” are defined, which represent a computer program as a closed system. Each circuit has a fixed number of constraints that corresponds to the degree of the polynomial required for the proof and depends on the complexity of the underlying computer program. In practice, these circuits need to be optimized to have acceptable computational costs. Specifically, hash functions like SHA256 (Appel 2015) involve many constraints, which is why hash functions specialized for ZKPs, such as Poseidon (Grassi et al. 2021), often find application. The second step, proving the knowledge of a polynomial with specific roots, is based on basic polynomial properties, especially the factorization of polynomials and the fact that two non-identical polynomials of degree d cannot have more than d points of intersection. For SNARKs, this is combined with other cryptographic primitives such as partially homomorphic encryption and pairings of elliptic curves. On the other hand, there are also ZKPs that only require secure hash functions for their construction, called Scalable Transparent Arguments of Knowledge (STARKs) (Ben-Sasson et al. 2019).
Implementation of privacy-preserving NFTs in detail
After giving an overview of different approaches and preliminary work, we will use the following section to present our implementation of privacy-preserving, fractionalizable NFTs. We start by introducing related, existing implementations that fulfill some of the requirements of our system. Subsequently, we explain our implementation in detail. As discussed earlier, an implementation of a documentation and trading system for carbon emissions needs to satisfy at least the following requirements: Verifiability, distinguishability (non-fungibility), traceability, support of fractional ownership, and privacy, including GDPR-compliance. As, for example, the Bitcoin blockchain satisfies verifiability of transactions and NFT standards on Ethereum smart contracts moreover provide distinguishable and traceable digital assets, fractionality and privacy have not yet become a widely known practice. To the best of our knowledge, there are no current implementations for privacy-preserving (shielded) NFTs with fractional ownership, especially not in the application of emission tracing. As we build on and extend existing approaches, we want to present and discuss them briefly.
The implementation of NFTs facilitates non-fungibility in blockchain-based systems. The ERC-721 token standard is the first standardized implementation of NFTs on the Ethereum blockchain. The subsequent ERC-1155 standard combines fungible (ERC-20) and non-fungible (ERC-721) tokens and additionally enables splitting them for fractional ownership. On the other hand, these standards do not provides strong privacy guarantees because—while user addresses are pseudonymous—transactions are linkable (Biryukov et al. 2014; Pfitzmann and Hansen 2010). Zcash on the other hand, which was proposed by Sasson et al. (2014) and is one of the first blockchain-based solutions for anonymous “electronic cash”, offers different privacy and transparency options, depending on the type of transaction, but does not provide distinguishability. It uses Merkle trees to store the different transactions and ZKPs to verify them without revealing information to the public. Yet, Zcash is limited to simple payments with fungible tokens. The cryptocurrency Tezos with its Sapling integration (Nomadic Labs 2021) adopts the concept (and partially even the implementation) of Zcash but applies it inside smart contracts. Thereby, it enables private transactions for other tokens than just the blockchain native token, but the privacy is still limited to fungible tokens. EYs nightfall implementation (EYBlockchain 2020), which is usable on Ethereum and other EVM-compatible blockchains, further develops Tezos’ sapling implementation and adds the possibility to transact non-fungible tokens. With this, Nightfall provides private transactions for NFTs, making it the implementation that fulfills the most of our requirements, but still misses the fractionalizability of the shielded NFTs.
For our prototype, we took the Zcash approach introduced in Sasson et al. (2014) as a basis. This approach builds on top of the Unspent Transaction Outputs (UTXO) model, first introduced by Bitcoin, and combines it with ZKPs constructed with SNARKs to achieve full privacy in its native token transfers. A transfer involves consuming some of the sender’s UTXOs and creating new UTXOs. The sender issues a new UTXOs, which comprises a public key and an amount, to the receiver address. Since UTXOs can only be spent in their entirety, in most cases the sender issues a second UTXO to its own address again. Its value results of the old coins minus the value of the new coins, i.e., this UTXO in some sense represents the change of the transaction. Everyone can verify that a UTXO has not been spent before and that the total input and output values match. As the sender can issue every new UTXO to a new public key (address), it seems privacy-oriented at first. However, the direct linkage of spent and newly created UTXOs in fact allows to de-anonymize most transactions using more sophisticated tools (Biryukov et al. 2014). Consequently, to enhance privacy and to hide sensitive information on the public blockchain ledger, Zcash does not verify transactions based on a publicly linkable transaction history. Using hash functions and adding some randomness as entropy (“salt”), one can easily hide all transaction details, including transaction addresses, spent coins, received coins, and change, which are usually visible to all network participants. We call these salted hashes of the context of each UTXO commitment. A private (“shielded”) Zcash transaction hence has to involve a proof of knowledge of all pre-images of a all involved commitments to ensure that the transaction actually corresponds to a commitment in history and that all new commitments are valid. This involves, for example, to prove the ownership of a commitment and to ensure constant supply, i.e., to prevent the creation of new tokens from thin air. To make sure that one cannot spend coins multiple times, the commitment needs to be “nullified”, i.e., tagged as “spent”. However, to ensure the unlinkability required for anonymous transactions, there must be no direct pointer to the UTXOs consumed and created in a transfer. Hence, a Merkle tree of initially fixed (large) size bundles all commitments, where each leaf represents one commitment. When conducting a new transaction that creates two new commitments, the blockchain nodes add the commitments to the Merkle tree and post the Merkle root of that new resulting tree on the blockchain. This makes it possible for the owner of a commitment to verify its existence by a proof of membership in the Merkle tree. This proof of membership validates a Merkle proof for a certain commitment through a ZKP and publishes only the corresponding Merkle root, which has to exist on the ledger at any point of time. Additionally, the sender creates a nullifier for the commitment, which will also be appended to the blockchain. Knowing the pre-image of all involved UTXOs’ commitments, the sender’s ZKP also proves—without revealing the commitment or the associated transfer amount and addresses involved—that: (1) the new commitments create no new coins, i.e., the total amount in the spent UTXOs equals the total amount in the newly created UTXOs, (2) the spending commitment exists (proof of membership), (3) the sender owns the spending commitments, i.e., knows the associated private key, (4) the sender correctly derives the nullifier from the pre-image of the spending commitment. To process a transfer, consisting of a ZKP, two commitments, a nullifer, and a Merkle root, the network checks the statements (1)–(4) and that the revealed nullifier has not been published before. If all this is true, the network adds the commitments to the tree (and updates the Merkle root accordingly) and appends the nullifer to the corresponding list.
To account for the intended distinguishability, i.e., non-fungibility of CO\(_{2}\) certificates represented by shielded UTXOs, we adapt the approach of Zcash and add further information to the commitments’ pre-images. Hereby, we essentially make them represent shielded (fractional) NFT transfers. The pre-image of each commitment in our approach contains the following attributes:
-
Hash: The hash of the object the NFT represents (e.g., emission certificate).
-
Quantity: The share of the object represented by this UTXO.
-
Public Key: The NFT owner’s public key.
-
Consumed: Marks the NFT as consumed when the represented object no longer exists.
-
Salt: Ensures that each commitment is unique through adding entropy.
The CO\(_{2}\) emissions tracing implementation we present here can be managed by blockchain technology, just like Zcash. However, as a large-scale and fine-granular carbon market would arguably have higher requirements on throughput in the future and as we do not focus on performance improvements in this paper, we assume instead that a central institution manages the associated ledger, which we call ledger authority. Due to the privacy-by-design of our approach, businesses and end-users must trust this institution only with regard to integrity and non-censorship, but not with their sensitive information.
Although we assume that a central instance can manage the ledger without restricting the privacy of the users, the initial creation of the NFTs (“minting”) should rely on a central institution alone, since this again requires access to partially sensitive data and creates several challenges regarding scalability when aiming for a fine-granular tracing system. This is why the verification of CO\(_{2}\) emission data and the subsequent minting of certificates should be carried out as decentralized as possible. We aim for a system in which each emission-relevant plant is capable of minting its NFTs on its own. For this purpose, we assume that trust-creating parties embed trustworthy sensors in each of these plants. Each sensor has a unique and non-transferable public-private key pair, on behalf of which it is capable of minting new NFTs. In this digital identity approach, the trust-creating parties report all trustworthy public keys to the ledger authority, which writes them on a safelist holding all public keys authorized for minting. To avoid that every minter has to authenticate with their public key every time they create a NFTs, the safelist is represented by another Merkle tree holding one public key in each leaf. Hereby a minter can utilize a proof of membership on the safelist tree to avoid showing its public key. To ensure private transaction creation, we require that both the commitment and safelist Merkle tree have public read access. However, if the respective use case desires a decentralized management of the ledger, our approach can operate without further concerns regarding the privacy of transactions, etc., within a blockchain infrastructure.
Figure 1 features the ZKP’s main statements for a minting transaction. In our nomenclature, INPUTS are private (the prover does not reveal them) yet some of the private inputs may be exchanged between sender (prover) and receiver [for instance, the receiver’s Public Key (PK)], OUTPUTS are public (they need to be disclosed for verification of the ZKP). To mint a new NFT, the minter creates and sends a valid minting-ZKP, the Merkle root of the current safelist, and the new commitment to the ledger authority. Subsequently, the authority verifies the ZKP, checks if the safelist’s root matches to the one stored on the ledger, and finally adds the commitment to the ledger. This transaction does not disclose the miner’s identity or any details about the NFT.
As in Zcash, a nullifier must invalidate a commitment; therefore, there are two properties that it should fulfill. First, it must be bound to the pre-image of the commitment (without being linkable to the value of the commitment) and second, it should only be possible for the owner of the commitment to create it. Therefore, we chose an implementation where the pre-image of the nullifier includes the signature of the commitment itself using the Secret Key (SK) corresponding to the commitments PK: \(\text {nullifier} = \text {Hash}(\text {Signature}(\text {SK}, \text {commitment}))\). Previous to the proof generation, the receiver sends its PK to the sender. Due to the fact that the commitment Merkle tree has public access rights, senders can generate a proof of membership for their commitment by their own. Consequently, senders have all information for creating the ZKP, which Fig. 2 describes. Since the nullifier consists of a signature of the commitment and its pre-image contains its owner’s PK, only the holder of the matching SK (owner of the NFT) is capable of generating the nullifier and therefore spending the commitment. Thus, the sender submits the Merkle root, the nullifier, and the two new commitments along with the transfer-ZKP, which confirms the correct generation of all these hashes (commitments and nullifier) to the ledger authority. The authority validates the ZKP and checks whether the Merkle root existed at some point in history. If these conditions are met, the ledger authority adds the receiver and change commitment to the tree, updates the new Merkle root, and appends the nullifier to the list. At this point, the ledger authority already settled the transaction by storing it on the ledger, but transaction recipients cannot realize or even utilize this yet. Consequently, the sender has to send the transaction receiver the pre-image of the newly added receiver commitment. Finally, the recipient can verify the pre-image of the transaction by comparing its hash to the commitments on the ledger. As the sender issued the receiver commitment to the receiver’s PK the receiver is now capable of proving the ownership or spending the NFT on behalf of its SK.