Proofs
Overview
The Filecoin protocol uses cryptographic proofs to ensure the two following guarantees:
- Storage Based Consensus: Miners’ power in the consensus is proportional to their amount of storage. Miner increase their power by proving that they are dedicating unique storage to the network.
- Verifiable Storage Market: Miners must be proving that they are dedicating unique physical space for each copy of the clients data through a period of time.
We briefly describe the three main Filecoin proofs:
- Proof-of-Replication (PoRep) proves that a Storage Miner is dedicating unique dedicated storage for each sector. Filecoin Storage Miners collect new clients’ data in a sector, run a slow encoding process (called
Seal
) and generate a proof (SealProof
) that the encoding was generated correctly.
In Filecoin, PoRep provides two guarantees: (1) space-hardness: Storage Miners cannot lie about the amount of space they are dedicating to Filecoin in order to gain more power in the consensus; (2) replication: Storage Miners are dedicating unique storage for each copy of their clients data.
Proof of Spacetime proves that an arbitrary number of sealed sectors existed over a specified period of time in their own dedicated storage — as opposed to being generated on-the-fly at proof generation time.
Piece-Inclusion-Proof proves that a given piece is contained within a specified sealed sector.
Glossary
Throughout this document, the following definitions are used:
- sector: a fixed-size block of data of
SECTOR_SIZE
bytes which generally contains clients’ data. - piece: a block of data of at most
SECTOR_SIZE
bytes which is generally is a client’s file or part of. - original data: the concatenation of a sector’s constitutent pieces, all piece padding, and any terminal padding.
- unsealed sector: a concrete representation (on disk or in memory) of a sector’s original data.
- sealed sector: a concrete representation (on disk or in memory) of the unique replica generated by
Seal
from an unsealed sector. - piece padding: a block of zero or more ‘zero bytes’ inserted between pieces to ensure they are positioned within the containing sector in a way compatible with the Piece Inclusion Proof.
- terminal padding: a block of zero or more ‘zero bytes’ inserted after a sector’s final piece, ensuring that the length of the original data is
SECTOR_BYTES
. - preprocessing: a transformation applied to an unsealed sector as the first stage of sealing and which may increase the size of the data.
- preprocessed data: the result of preprocessing the original data.
- preprocessed sector: a concrete representation (on disk or in memory) of a sector’s preprocessed data.
- SNARK proof: a block of bytes which proves that the creator is in possession of a satisfying assignment to a quadratic arithmetic program (circuit).
- Multi-SNARK proof: a block of one or more SNARK proofs, each proving partition of the total set of challenges to be proved.
- Merkle inclusion proof: a proof (whose format is unspecified here) that a given leaf block is contained within a merkle tree whose root is the commitment associated with the proof.
- commitment: an opaque block of data to which a prover ‘commits’, enabling subsequent proofs which cannot be validly constructed unless the commitment itself was validly constructed. For example: the output of a suitable pseudorandom collision-resistant hash function may serve as a commitment to the data which is the preimage of that hash. Publication of the commitment proves that the creator was in possession of the preimage at the time the commitment was generated.
- prover: the party who generates a proof, in Filecoin it’s always the Storage Miner.
- verifier: the party who verifies a proof generated by a prover, in Filecoin it’s a full node.
The SectorID
defined by the chain is au64
, which gets encoded as a 31-byte array for the purpose of proofs. This transform consists of encoding the number to its little-endian byte representation and then zero-pad to 31-bytes.
Example:
uint64(1025) -> [1 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
Proof-of-Replication Algorithms
Filecoin Proof of Replication generates a unique copy (sealed sector) of a sector’s original data, a Multi-SNARK proof, and a set of commitments identifying the sealed sector and linking it to the corresponding unsealed sector.
Seal
Seal
has the side effect of generating a sealed sector from an unsealed sector, and returns identifying commitments and a Multi-SNARK proof. The proof returned is a Multi-SNARK proof.
The commitments are used to verify that the correct original data was sealed, and that the correct sealed data is the subject of later Proofs of Spacetime, proving that this data is being stored continuously.
Seal
operates by performing a slow encoding of the unsealed sector — such that it is infeasible for a dishonest prover to computationally regenerate the sealed sector quickly enough to satisfy subsequent required Proofs of Spacetime — thus ensuring that the sealed sector remains manifest as a unique, concrete representation of the original data.
Seal
(
// request represents a request to seal a sector.
partitions uint64, // influences the size of the output proof; using less partitions requires more hardware but produces shorter proofs
sectorSize uint64, // the number of bytes in the sealed sector
unsealedPath string, // path of unsealed sector (regular file, ramdisk, etc.) from which a unique replica will be created
sealedPath string, // path to which sealed sector will be written
proverID [31]byte, // uniquely identifies miner
ticket [32]byte, // ticket to which miner commits when sealing begins
sectorID [31]byte, // uniquely identifies sector
) err Error | (
// response contains the commitments resulting from a successful Seal().
commD [32]byte, // data commitment: merkle root of original data
commR [32]byte, // replica commitment: merkle root of replicated data [will be removed in future iteration]
commRStar [32]byte, // a hash of intermediate layers
proof []byte,
)
VerifySeal
VerifySeal
is the functional counterpart to Seal
’s proof component. It takes all of Seal's
outputs, along with those of Seal’s inputs which are required to uniquely identify the created sealed sector. This allows a verifier to determine whether a given proof is valid. All inputs are required because verification requires sufficient context to determine not only that a proof is valid but also that the proof indeed corresponds to what it purports to prove.
VerifySeal
(
// request represents a request to verify the output of a Seal() operation.
commD [32]byte, // returned from Seal
commR [32]byte, // returned from Seal [will be removed in future iteration]
commRStar [32]byte, // returned from Seal
proof []byte, // returned from Seal
proverID [31]byte, // uniquely identifies miner
ticket [32]byte, // ticket to which miner committed when sealing began
sectorID [31]byte, // uniquely identifies sector
) err Error |
IsValid bool // true iff the provided proof-of-replication os valid
Unseal
Unseal
is the counterpart to Seal
’s encoding side-effect. It reverses the ‘slow encoding’ and creates an unsealed sector from a sealed sector as a special case of its more general function. In general, it allows extraction of a range of bytes (specified in terms of the layout of the original data).
Unseal
(
// request represents a request to unseal a sector.
sectorSize uint64, // the number of bytes in the sealed sector
sealedPath string, // path from which sealed bytes will be read
outputPath string, // path to which unsealed bytes will be written (regular file, ramdisk, etc.)
proverID [31]byte, // uniquely identifies miner
sectorID [31]byte, // uniquely identifies sector
ticket [32]byte, // ticket to which miner committed when sealing began
startOffset uint64, // zero-based byte offset in original, unsealed sector-file
numBytes uint64, // number of bytes to unseal (corresponds to contents of unsealed sector-file)
) err Error |
NumBytesWritten uint64 // the number of bytes unsealed (and written) by Unseal()
Security Notes
Guaranteeing sector uniqueness
Every sealed sector is unique, even if the unsealed data is identical. This prevents a malicious miner from storing the same sector twice without dedicating twice the amount of storage, or two malicious miners pretending to store the same sector, but only storing one copy. Sector uniqueness is guaranteed by having unique proverId
and sectorId
. Each miner has a unique proverID
, and each sector has a unique sectorID
within that miner’s sectors. Taken together, proverID
and sectorID
are globally unique . Both the proverId
and the sectorId
are used to encode the sealed data.
The Filecoin node verifies that the correct proverId
and sectorId
is used when verifying the proof.
Piece Inclusion Proof
A PieceInclusionProof
contains a potentially complex Merkle inclusion proof that all leaves included in commP
(the piece commitment) are also included in commD
(the sector data commitment).
struct PieceInclusionProof {
Position uint,
ProofElements [32]byte
}
GeneratePieceInclusionProofs
GeneratePieceInclusionProofs
takes a merkle tree and a slice of piece start positions and lengths (in nodes), and returns
a vector of PieceInclusionProofs
corresponding to the pieces. For this method to work, the piece data used to validate pieces will need to be padded as necessary, and pieces will need to be aligned (to 128-byte chunks, after padding, due to details of preprocessing) when written. This assumes that pieces have been packed and padded according to the assumptions of the algorithm. For this reason, practical implementations should also provide a function to assist in correct packing of pieces. All pieces will be zero-padded such that the total length of the piece is a multiple of 127 bytes before preprocessing and a multiple of 128 bytes after pre-processing.
GeneratePieceInclusionProofs
(
Tree MerkleTree,
PieceStarts []uint
PieceLengths []uint,
) []PieceInclusionProof
GeneratePieceInclusionProofs
takes a merkle tree, an array of the index positions of the first nodes of the pieces, and an array of the corresponding piece lengths. It returns an array of PieceInclusionProof
s corresponding to the supplied start/length pairs — each of which specifies a piece as a sequence of leaves ([start..start+length]
) of Tree
.
GeneratePieceInclusionProof
(
tree MerkleTree,
firstNode uint,
pieceLength uint,
) err Error | proof PieceInclusionProof
The structure of a PieceInclusionProof
is determined by the start position and length of the piece to be proved. (Note that if start + length
is greater than the number of leaves in Tree
, and no PieceInclusionProof
can be generated since these parameters do not specify a valid piece.)
The form of a PieceInclusionProof
is as follows:
- The piece’s position within the tree must be specified. This, combined with the length provided during verification completely determines the shape of the proof.
- The remainder of the proof is a series of ProofElements
, whose order is interpreted by the proof algorithm and is not (yet: TODO) specified here. The significance of the provided ProofElements
is described by their role in the verification algorithm below.
VerifyPieceInclusionProof
takes a sector data commitment (commD
), piece commitment (commP
), sector size, and piece size.
Iff it returns true, then PieceInclusionProof
indeed proves that all of piece’s bytes were included in the merkle tree corresponding
to commD
of a sector of sectorSize
. The size inputs are necessary to prevent malicious provers from claiming to store the entire
piece but actually storing only the piece commitment.
VerifyPieceInclusionProof
(
proof PieceInclusionProof,
commD [32]byte,
commP [32]byte,
sectorSize uint,
pieceSize uint,
) err Error | IsValid bool // true iff the provided PieceInclusionProof is valid.
The abstract verification algorithm is described below. Only the algorithm for an aligned PieceInclusionProof
is fully specified here. [TODO: provide details fully specifying the ordering of ProofElements
in the general case.]
A PieceInclusionProof
includes a start position and a sequence of ProofElements
. Verification of a PieceInclusionProof
is with respect to a given commP
, commD
, sector size, and piece size.
Piece size and start position are used, as in proof generation, to determine the shape of the proof. This shape fully determines the inputs to and order of applications of RepCompress
which constitute the proof.
Proof verification is as follows:
- A ProofElement
is a 32-byte value which will be supplied as either the left or right input to RepCompress
(along with an appropriate height) to combine with a complementary (right or left) ProofElement
already known to the verifier.
- Each time RepCompress
is called on a pair of known ProofElements
, a new ProofElement
is considered to be known to the verifier.
- Initially, the verifier knows only of two ProofElements
, commP
(the Piece Commitment) and commD
(the Data Commitment).
- The proof proceeds in two stages:
1. Zero or more candidate ProofElements
are proved to hash to commP
through subsequent applications of RepCompress
. Only after commP
has been constructed from a set of candidate ProofElements
do those ProofElements
become eligible for use in the second phase of the proof. (Because RepCompress
takes height as an input, only ProofElements
from the same height in either the piece or data tree’s can be combined. The output of RepCompress
is always a ProofElement
with height one greater than that of its two inputs.)
- commP
itself is by definition always eligible for use in the second proof phase.
- An aligned PieceInclusionProof
is one whose start
index is a power of 2, and for which either its piece’s length is a power of 2 or the piece was zero-padded with Piece Padding when packed in a sector. In these cases, commP
exists as a node in the data tree, and a minimal-size PieceInclusionProof
can be generated.
- In the case of an aligned PieceInclusionProof
, zero candidate ProofElements
are required. (This means that commP
is the only eligible ProofElement
.)
2. Provided ProofElements
are added to the set of eligible ProofElements
by successive application of RepCompress
, as in the first phase.
- When commD
is produced by application of an eligible ProofElement
and a ProofElement
provided by the proof, the proof is considered complete.
- If all eligible ProofElement
s have been used as inputs to RepCompress
and are dependencies of the final construction of commD
, then the proof is considered to be valid.
NOTE: in the case of an aligned PieceInclusionProof
the ProofElements
take the form of a standard Merkle inclusion proof proving that commP
is contained in a sub-tree of the data tree whose root is commD
. Because commP
’s position within the data tree is fully specified by the tree’s height and the piece’s start position and length, the verifier can deterministically combine each successive ProofElement
with the result of the previous RepCompress
operation as either a right or left input to the next RepCompress
operation. In this sense, an aligned PieceInclusionProof
is simply a Merkle inclusion proof that commP
is a constituent of commD
’s merkle tree and that it is located at the appropriate height for it to also be the root of a (piece) tree with length
leaves.
In the non-aligned case, the principle is similar. However, in this case, commP
does not occur as a node in commD
’s Merkle tree. This is because the original piece has been packed out-of-order to minimize alignment padding in the sector (at the cost of a larger PieceInclusionProof
). Because commP
does not exist as the root of a data sub-tree, it is necessary first to prove that the root of every sub-tree into which the original piece has been decomposed (when reordering) is indeed present in the data tree. Once each candidate ProofElement
has been proved to be an actual constituent of commP
, it must also be shown that this eligible ProofElement
is also a constituent of commD
.