问题: 如何证明托管主机确实存储了某个文件。
回答:
1. 先从交易(Transaction)开始说起,一个交易包含了多个关键信息。 其中文件合同,文件合同更新和存储证明这三者使云存储成为可能。
以下是一个Transaction所用到的数据结构定义:
TransactionID crypto.Hash // 定义了一个交易的标识符,用来区分不同的交易
FileContractID crypto.Hash // 交易所用到的文件合同的ID
Transactionstruct{
SiacoinInputs []SiacoinInput
SiacoinOutputs []SiacoinOutput
FileContracts []FileContract // 文件合同
FileContractRevisions []FileContractRevision // 文件合同更新
StorageProofs []StorageProof // 存储证明
SiafundInputs []SiafundInput
SiafundOutputs []SiafundOutput
MinerFees []Currency
ArbitraryData [][]byte
TransactionSignatures []TransactionSignature
}
2. 文件合同(FileContracts)储存了租用方和托管主机间的协议,租用方同意给代存文件的托管主机付费,而托管主机在一定的时间段里为租用方代存文件。在一开始租用和托管双方均在合同里放siacoin。租用方的币用来在托管方完成合约任务后支付托管方。托管方的钱是押金,一旦托管方不能执行合约中的义务,押金将被没收。文件合同记录在区块链中,这个区块链相当于文件合同的第三方代管者。文件合同的核心是使用了文件的Merkle root hash。文件会分成大小一样的分段(segment)然后散列到一个Merkle树上。
以下是文件合同数据结构定义:
FileContract struct {
FileSize uint64
FileMerkleRoot crypto.Hash // 文件合同的核心
WindowStart BlockHeight
WindowEnd BlockHeight
Payout Currency
ValidProofOutputs []SiacoinOutput
MissedProofOutputs []SiacoinOutput
UnlockHash UnlockHash
RevisionNumber uint64
}
文件合同要求托管主机必须在” WindowStart”和”WindowEnd”期间,向区块链上提交存储证明(StorageProof),以证明自己还存储着文件。如果提交了存储证明,有效证明输出(ValidProofOutputs)被创建,托管主机获得存储的奖励;如果丢失存储证明,丢失证明输出(MissedProofOutputs)被创建,托管主机不仅无法获得奖励,而且还会失去先前交的押金。这个机制保证了托管主机有动力去持续的保存文件。
3. 存储证明(StorageProof)实际上是实现了文件合同,如果没有存储证明的提交,仅仅是合同创建了,也是相当于没有任何监管,并且没有存储证明,也无法证明托管主机确实存储了某个文件。存储证明提交到区块链上,对所有人都是公开的。 一个存储证明包含存储文件的一个分段,文件Merkle树上的一组散列。 这两个条件组合起来,就能够证明这个分段就是所存储文件的一部分,也就证明了这个托管主机确实存储了文件。 为了保证严谨性,文件分段是随机选择的。
以下是存储证明数据结构的定义:
StorageProofstruct{
ParentIDFileContractID // 文件合同的ID
Segment [crypto.SegmentSize]byte // 文件分段
HashSet []crypto.Hash // 散列值
}
目前系统定义的segmentSize的大小是64byte,这个值是权衡了区块链空间和网络带宽后定义的。
4. 验证存储证明是否合法相关代码实现介绍
func validStorageProofs(tx*bolt.Tx, t types.Transaction) error {
if(build.Release == "standard" && blockHeight(tx) < 100e3)|| (build.Release == "testing" && blockHeight(tx) < 10) {
returnvalidStorageProofs100e3(tx, t)
}
for _, sp :=range t.StorageProofs {
//验证存储证明本身是否合法。 返回值是文件分段的index
segmentIndex,err := storageProofSegment(tx, sp.ParentID)
iferr != nil {
returnerr
}
// 从数据库中取一个文件合同
fc,err := getFileContract(tx, sp.ParentID)
iferr != nil {
returnerr
}
// 根据文件size大小,计算叶子数
leaves:= crypto.CalculateLeaves(fc.FileSize)
segmentLen:= uint64(crypto.SegmentSize)
ifsegmentIndex == leaves-1 {
segmentLen= fc.FileSize % crypto.SegmentSize
}
ifsegmentLen == 0 {
segmentLen= uint64(crypto.SegmentSize)
}
// 验证分段是否是Merkle root的一部分
verified:= crypto.VerifySegment(
sp.Segment[:segmentLen],
sp.HashSet,
leaves,
segmentIndex,
fc.FileMerkleRoot,
)
if!verified && fc.FileSize > 0 {
returnerrInvalidStorageProof
}
}
return nil
}
// 验证分段是否是Merkleroot的一部分
func VerifySegment(base []byte, hashSet []Hash,numSegments, proofIndex uint64, root Hash) bool {
proofSet :=make([][]byte, len(hashSet)+1)
proofSet[0] =base
for i := rangehashSet {
proofSet[i+1]= hashSet[i][:]
}
returnmerkletree.VerifyProof(NewHash(), root[:], proofSet, proofIndex, numSegments)
}
// 根据存储证明里定义的值,去计算Merkleroot并和实际的对比,看存储证明是否合法
func VerifyProof(h hash.Hash, merkleRoot []byte,proofSet [][]byte, proofIndex uint64, numLeaves uint64) bool {
if merkleRoot== nil {
returnfalse
}
if proofIndex>= numLeaves {
returnfalse
}
height := 0
iflen(proofSet) <= height {
returnfalse
}
sum :=leafSum(h, proofSet[height])
height++
// 当前子树计算完成,使用完全子树算法决定下一个兄弟子树的位置。
// "stableEnd"表示最后一个完全子树的结束标识,
stableEnd :=proofIndex
for {
// 决定子树是否完成.
subTreeStartIndex:= (proofIndex / (1 << uint(height))) * (1 << uint(height))
// 减掉1是因为开始的index是包含在内的
subTreeEndIndex:= subTreeStartIndex + (1 << (uint(height))) - 1
ifsubTreeEndIndex >= numLeaves {
//If the Merkle tree does not have a leaf at index
//'subTreeEndIndex', then the subtree of the current height is not
//a complete subtree.
break
}
stableEnd= subTreeEndIndex
iflen(proofSet) <= height {
returnfalse
}
ifproofIndex-subTreeStartIndex < 1<<uint(height-1) {
sum= nodeSum(h, sum, proofSet[height])
}else {
sum= nodeSum(h, proofSet[height], sum)
}
height++
}
if stableEnd!= numLeaves-1 {
iflen(proofSet) <= height {
returnfalse
}
sum= nodeSum(h, sum, proofSet[height])
height++
}
for height< len(proofSet) {
sum= nodeSum(h, proofSet[height], sum)
height++
}
// 根据我们传入的参数计算出来的Merkle root和实际的对比,一致则返回true.
if bytes.Compare(sum,merkleRoot) == 0 {
returntrue
}
return false
}
5. 总结:托管主机通过存储证明来证明它确实存储了文件。通过存储证明数据结构中的{ ParentID,Segment,HashSet }去获取相应的文件合同,分段Index,叶子数等,进而去计算Merkle root,并和实际的Merkle root值对比。看两者是否一致。从而判断存储证明是否合法。