// validStorageProofsPre100e3 runs the code that was running before height // 100e3, which contains a hardforking bug, fixed at block 100e3. // // HARDFORK 100,000 // // Originally, it was impossible to provide a storage proof for data of length // zero. A hardfork was added triggering at block 100,000 to enable an // optimization where hosts could submit empty storage proofs for files of size // 0, saving space on the blockchain in conditions where the renter is content. func validStorageProofs100e3(tx *bolt.Tx, t types.Transaction) error { for _, sp := range t.StorageProofs { // Check that the storage proof itself is valid. segmentIndex, err := storageProofSegment(tx, sp.ParentID) if err != nil { return err } fc, err := getFileContract(tx, sp.ParentID) if err != nil { return err } leaves := crypto.CalculateLeaves(fc.FileSize) segmentLen := uint64(crypto.SegmentSize) if segmentIndex == leaves-1 { segmentLen = fc.FileSize % crypto.SegmentSize } // HARDFORK 21,000 // // Originally, the code used the entire segment to verify the // correctness of the storage proof. This made the code incompatible // with data sizes that did not fill an entire segment. // // This was patched with a hardfork in block 21,000. The new code made // it possible to perform successful storage proofs on the final // segment of a file if the final segment was not crypto.SegmentSize // bytes. // // Unfortunately, a new bug was introduced where storage proofs on the // final segment would fail if the final segment was selected and was // crypto.SegmentSize bytes, because the segmentLen would be set to 0 // instead of crypto.SegmentSize, due to an error with the modulus // math. This new error has been fixed with the block 100,000 hardfork. if (build.Release == "standard" && blockHeight(tx) < 21e3) || (build.Release == "testing" && blockHeight(tx) < 10) { segmentLen = uint64(crypto.SegmentSize) } verified := crypto.VerifySegment( sp.Segment[:segmentLen], sp.HashSet, leaves, segmentIndex, fc.FileMerkleRoot, ) if !verified { return errInvalidStorageProof } } return nil }
// storageProofSegment returns the index of the segment that needs to be proven // exists in a file contract. func (cs *ConsensusSet) storageProofSegment(fcid types.FileContractID) (index uint64, err error) { err = cs.db.View(func(tx *bolt.Tx) error { // Check that the parent file contract exists. fcBucket := tx.Bucket(FileContracts) fcBytes := fcBucket.Get(fcid[:]) if fcBytes == nil { return ErrUnrecognizedFileContractID } // Decode the file contract. var fc types.FileContract err := encoding.Unmarshal(fcBytes, &fc) if build.DEBUG && err != nil { panic(err) } // Get the trigger block id. blockPath := tx.Bucket(BlockPath) triggerHeight := fc.WindowStart - 1 if triggerHeight > types.BlockHeight(blockPath.Stats().KeyN) { return ErrUnfinishedFileContract } var triggerID types.BlockID copy(triggerID[:], blockPath.Get(encoding.EncUint64(uint64(triggerHeight)))) // Get the index by appending the file contract ID to the trigger block and // taking the hash, then converting the hash to a numerical value and // modding it against the number of segments in the file. The result is a // random number in range [0, numSegments]. The probability is very // slightly weighted towards the beginning of the file, but because the // size difference between the number of segments and the random number // being modded, the difference is too small to make any practical // difference. seed := crypto.HashAll(triggerID, fcid) numSegments := int64(crypto.CalculateLeaves(fc.FileSize)) seedInt := new(big.Int).SetBytes(seed[:]) index = seedInt.Mod(seedInt, big.NewInt(numSegments)).Uint64() return nil }) if err != nil { return 0, err } return index, nil }
// validStorageProofs checks that the storage proofs are valid in the context // of the consensus set. func validStorageProofs(tx *bolt.Tx, t types.Transaction) error { if (build.Release == "standard" && blockHeight(tx) < 100e3) || (build.Release == "testing" && blockHeight(tx) < 10) { return validStorageProofs100e3(tx, t) } for _, sp := range t.StorageProofs { // Check that the storage proof itself is valid. segmentIndex, err := storageProofSegment(tx, sp.ParentID) if err != nil { return err } fc, err := getFileContract(tx, sp.ParentID) if err != nil { return err } leaves := crypto.CalculateLeaves(fc.FileSize) segmentLen := uint64(crypto.SegmentSize) // If this segment chosen is the final segment, it should only be as // long as necessary to complete the filesize. if segmentIndex == leaves-1 { segmentLen = fc.FileSize % crypto.SegmentSize } if segmentLen == 0 { segmentLen = uint64(crypto.SegmentSize) } verified := crypto.VerifySegment( sp.Segment[:segmentLen], sp.HashSet, leaves, segmentIndex, fc.FileMerkleRoot, ) if !verified && fc.FileSize > 0 { return errInvalidStorageProof } } return nil }
// validTxStorageProofs checks that the storage proofs are valid in the context // of the consensus set. func (cs *ConsensusSet) validTxStorageProofs(tx *bolt.Tx, t types.Transaction) error { for _, sp := range t.StorageProofs { // Check that the storage proof itself is valid. segmentIndex, err := cs.txStorageProofSegment(tx, sp.ParentID) if err != nil { return err } fc, err := getFileContract(tx, sp.ParentID) if err != nil { return err } leaves := crypto.CalculateLeaves(fc.FileSize) segmentLen := uint64(crypto.SegmentSize) if segmentIndex == leaves-1 { segmentLen = fc.FileSize % crypto.SegmentSize } // COMPATv0.4.0 // // Fixing the padding situation resulted in a hardfork. The below code // will stop the hardfork from triggering before block 20,000. types.CurrentHeightLock.Lock() if (build.Release == "standard" && types.CurrentHeight < 21e3) || (build.Release == "testing" && types.CurrentHeight < 10) { segmentLen = uint64(crypto.SegmentSize) } types.CurrentHeightLock.Unlock() verified := crypto.VerifySegment( sp.Segment[:segmentLen], sp.HashSet, leaves, segmentIndex, fc.FileMerkleRoot, ) if !verified { return ErrInvalidStorageProof } } return nil }