// FileContractTerminationPayoutID returns the ID of a file contract // termination payout, given the index of the payout in the termination. The // ID is calculated by hashing the concatenation of the // FileContractTerminationPayout Specifier, the ID of the file contract being // terminated, and the payout index. func (fcid FileContractID) FileContractTerminationPayoutID(i int) SiacoinOutputID { return SiacoinOutputID(crypto.HashAll( SpecifierFileContractTerminationPayout, fcid, i, )) }
// StorageProofOutputID returns the ID of an output created by a file // contract, given the status of the storage proof. The ID is calculating by // hashing the concatenation of the StorageProofOutput Specifier, the ID of // the file contract that the proof is for, a boolean indicating whether the // proof was valid (true) or missed (false), and the index of the output // within the file contract. func (fcid FileContractID) StorageProofOutputID(proofStatus ProofStatus, i uint64) SiacoinOutputID { return SiacoinOutputID(crypto.HashAll( SpecifierStorageProofOutput, fcid, proofStatus, i, )) }
// generateSpendableKey creates the keys and unlock conditions a given index of a // seed. func generateSpendableKey(seed modules.Seed, index uint64) spendableKey { // Generate the keys and unlock conditions. entropy := crypto.HashAll(seed, index) sk, pk := crypto.GenerateKeyPairDeterministic(entropy) return spendableKey{ UnlockConditions: generateUnlockConditions(pk), SecretKeys: []crypto.SecretKey{sk}, } }
// ID returns the id of a transaction, which is taken by marshalling all of the // fields except for the signatures and taking the hash of the result. func (t Transaction) ID() crypto.Hash { return crypto.HashAll( t.SiacoinInputs, t.SiacoinOutputs, t.FileContracts, t.FileContractRevisions, t.StorageProofs, t.SiafundInputs, t.SiafundOutputs, t.MinerFees, t.ArbitraryData, ) }
// SiafundOutputID returns the ID of a SiafundOutput at the given index, which // is calculated by hashing the concatenation of the SiafundOutput Specifier, // all of the fields in the transaction (except the signatures), and output // index. func (t Transaction) SiafundOutputID(i int) SiafundOutputID { return SiafundOutputID(crypto.HashAll( SpecifierSiafundOutput, t.SiacoinInputs, t.SiacoinOutputs, t.FileContracts, t.FileContractRevisions, t.StorageProofs, t.SiafundInputs, t.SiafundOutputs, t.MinerFees, t.ArbitraryData, i, )) }
// removeFileContractRevisions removes all of the file contract revisions of a // transaction from the unconfirmed consensus set. func (tp *TransactionPool) removeFileContractRevisions(t types.Transaction) { for _, fcr := range t.FileContractRevisions { // Sanity check - the corresponding file contract should be in the // reference set. referenceID := crypto.HashAll(fcr.ParentID, fcr.NewRevisionNumber) if build.DEBUG { _, exists := tp.referenceFileContractRevisions[referenceID] if !exists { panic("cannot locate file contract to delete storage proof transaction") } } tp.fileContracts[fcr.ParentID] = tp.referenceFileContractRevisions[referenceID] delete(tp.referenceFileContractRevisions, referenceID) } }
// 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 }
// TestBlockHeader checks that BlockHeader returns the correct value, and that // the hash is consistent with the old method for obtaining the hash. func TestBlockHeader(t *testing.T) { var b Block b.ParentID[1] = 1 b.Nonce[2] = 2 b.Timestamp = 3 b.MinerPayouts = []SiacoinOutput{{Value: NewCurrency64(4)}} b.Transactions = []Transaction{{ArbitraryData: [][]byte{[]byte{'5'}}}} id1 := b.ID() id2 := BlockID(crypto.HashBytes(encoding.Marshal(b.Header()))) id3 := BlockID(crypto.HashAll( b.ParentID, b.Nonce, b.Timestamp, b.MerkleRoot(), )) if id1 != id2 || id2 != id3 || id3 != id1 { t.Error("Methods for getting block id don't return the same results") } }
// uidEncryptionKey creates an encryption key that is used to decrypt a // specific key file. func uidEncryptionKey(masterKey crypto.TwofishKey, uid UniqueID) crypto.TwofishKey { return crypto.TwofishKey(crypto.HashAll(masterKey, uid)) }
// deriveKey derives the key used to encrypt and decrypt a specific file piece. func deriveKey(masterKey crypto.TwofishKey, chunkIndex, pieceIndex uint64) crypto.TwofishKey { return crypto.TwofishKey(crypto.HashAll(masterKey, chunkIndex, pieceIndex)) }
// MinerPayoutID returns the ID of the miner payout at the given index, which // is calculated by hashing the concatenation of the BlockID and the payout // index. func (b Block) MinerPayoutID(i uint64) SiacoinOutputID { return SiacoinOutputID(crypto.HashAll( b.ID(), i, )) }
// CalculateWalletTransactionID is a helper function for determining the id of // a wallet transaction. func CalculateWalletTransactionID(tid types.TransactionID, oid types.OutputID) WalletTransactionID { return WalletTransactionID(crypto.HashAll(tid, oid)) }
// negotiateContract establishes a connection to a host and negotiates an // initial file contract according to the terms of the host. func (hu *hostUploader) negotiateContract(filesize uint64, duration types.BlockHeight, renterAddress types.UnlockHash) error { conn, err := net.DialTimeout("tcp", string(hu.settings.IPAddress), 15*time.Second) if err != nil { return err } defer conn.Close() conn.SetDeadline(time.Now().Add(30 * time.Second)) // inital calculations before connecting to host lockID := hu.renter.mu.RLock() height := hu.renter.blockHeight hu.renter.mu.RUnlock(lockID) renterCost := hu.settings.Price.Mul(types.NewCurrency64(filesize)).Mul(types.NewCurrency64(uint64(duration))) renterCost = renterCost.MulFloat(1.05) // extra buffer to guarantee we won't run out of money during revision payout := renterCost // no collateral // write rpcID if err := encoding.WriteObject(conn, modules.RPCUpload); err != nil { return errors.New("couldn't initiate RPC: " + err.Error()) } // read host key // TODO: need to save this? var hostPublicKey types.SiaPublicKey if err := encoding.ReadObject(conn, &hostPublicKey, 256); err != nil { return errors.New("couldn't read host's public key: " + err.Error()) } // create our own key by combining the renter entropy with the host key entropy := crypto.HashAll(hu.renter.entropy, hostPublicKey) ourSK, ourPK := crypto.StdKeyGen.GenerateDeterministic(entropy) ourPublicKey := types.SiaPublicKey{ Algorithm: types.SignatureEd25519, Key: ourPK[:], } hu.secretKey = ourSK // used to sign future revisions // send our public key if err := encoding.WriteObject(conn, ourPublicKey); err != nil { return errors.New("couldn't send our public key: " + err.Error()) } // create unlock conditions hu.unlockConditions = types.UnlockConditions{ PublicKeys: []types.SiaPublicKey{ourPublicKey, hostPublicKey}, SignaturesRequired: 2, } // create file contract fc := types.FileContract{ FileSize: 0, FileMerkleRoot: crypto.Hash{}, // no proof possible without data WindowStart: height + duration, WindowEnd: height + duration + hu.settings.WindowSize, Payout: payout, UnlockHash: hu.unlockConditions.UnlockHash(), RevisionNumber: 0, } // outputs need account for tax fc.ValidProofOutputs = []types.SiacoinOutput{ {Value: renterCost.Sub(types.Tax(hu.renter.blockHeight, fc.Payout)), UnlockHash: renterAddress}, {Value: types.ZeroCurrency, UnlockHash: hu.settings.UnlockHash}, // no collateral } fc.MissedProofOutputs = []types.SiacoinOutput{ // same as above fc.ValidProofOutputs[0], // goes to the void, not the renter {Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}}, } // build transaction containing fc txnBuilder := hu.renter.wallet.StartTransaction() err = txnBuilder.FundSiacoins(fc.Payout) if err != nil { return err } txnBuilder.AddFileContract(fc) txn, parents := txnBuilder.View() txnSet := append(parents, txn) // calculate contract ID fcid := txn.FileContractID(0) // TODO: is it actually 0? // send txn if err := encoding.WriteObject(conn, txnSet); err != nil { txnBuilder.Drop() return errors.New("couldn't send our proposed contract: " + err.Error()) } // read back acceptance var response string if err := encoding.ReadObject(conn, &response, 128); err != nil { txnBuilder.Drop() return errors.New("couldn't read the host's response to our proposed contract: " + err.Error()) } if response != modules.AcceptResponse { txnBuilder.Drop() return errors.New("host rejected proposed contract: " + response) } // read back txn with host collateral. var hostTxnSet []types.Transaction if err := encoding.ReadObject(conn, &hostTxnSet, types.BlockSizeLimit); err != nil { txnBuilder.Drop() return errors.New("couldn't read the host's updated contract: " + err.Error()) } // check that txn is okay. For now, no collateral will be added, so the // transaction sets should be identical. if len(hostTxnSet) != len(txnSet) { txnBuilder.Drop() return errors.New("host sent bad collateral transaction") } for i := range hostTxnSet { if hostTxnSet[i].ID() != txnSet[i].ID() { txnBuilder.Drop() return errors.New("host sent bad collateral transaction") } } // sign the txn and resend // NOTE: for now, we are assuming that the transaction has not changed // since we sent it. Otherwise, the txnBuilder would have to be updated // with whatever fields were added by the host. signedTxnSet, err := txnBuilder.Sign(true) if err != nil { txnBuilder.Drop() return err } if err := encoding.WriteObject(conn, signedTxnSet); err != nil { txnBuilder.Drop() return errors.New("couldn't send the contract signed by us: " + err.Error()) } // read signed txn from host var signedHostTxnSet []types.Transaction if err := encoding.ReadObject(conn, &signedHostTxnSet, types.BlockSizeLimit); err != nil { txnBuilder.Drop() return errors.New("couldn't read the contract signed by the host: " + err.Error()) } // submit to blockchain err = hu.renter.tpool.AcceptTransactionSet(signedHostTxnSet) if err == modules.ErrDuplicateTransactionSet { // this can happen if the renter is uploading to itself err = nil } if err != nil { txnBuilder.Drop() return err } // create initial fileContract object hu.contract = fileContract{ ID: fcid, IP: hu.settings.IPAddress, WindowStart: fc.WindowStart, } lockID = hu.renter.mu.Lock() hu.renter.contracts[fcid] = fc hu.renter.mu.Unlock(lockID) return nil }
// unlockKey creates a wallet unlocking key from the input master key. func unlockKey(masterKey crypto.TwofishKey) crypto.TwofishKey { return crypto.TwofishKey(crypto.HashAll(masterKey, unlockModifier)) }
// sectorID returns the id that should be used when referring to a sector. // There are lots of sectors, and to minimize their footprint a reduced size // hash is used. Hashes are typically 256 bits to provide collision resistance // against an attacker that is able to peform an obscene number of trials per // second on each of an obscene number of machines. Potential collisions for // sectors are limited because hosts have secret data that the attacker does // not know which is used to salt the transformation of a sector hash to a // sectorID. As a result, an attacker is limited in the number of times they // can try to cause a collision - one random shot every time they upload a // sector, and the attacker has limited ability to learn of the success of the // attempt. Uploads are very slow; even on fast machines there will be less // than 1000 per second. It is therefore safe to reduce the security from // 256 bits to 96 bits, which has a collision resistance of 2^48. A reasonable // upper bound for the number of sectors on a host is 2^32, corresponding with // 16 PB of data. // // 12 bytes can be represented as a filepath using 16 base64 characters. This // keeps the filesize small and therefore limits the amount of load placed on // the filesystem when trying to manage hundreds of thousands or even tens of // millions of sectors in a single folder. func (sm *StorageManager) sectorID(sectorRootBytes []byte) []byte { saltedRoot := crypto.HashAll(sectorRootBytes, sm.sectorSalt) id := make([]byte, base64.RawURLEncoding.EncodedLen(12)) base64.RawURLEncoding.Encode(id, saltedRoot[:12]) return id }
// seedFileEncryptionKey creates an encryption key that is used to decrypt a // specific key file. func seedFileEncryptionKey(masterKey crypto.TwofishKey, sfuid SeedFileUID) crypto.TwofishKey { return crypto.TwofishKey(crypto.HashAll(masterKey, seedModifier, sfuid)) }