Skip to content

Commit

Permalink
Merge pull request #329 from SiaFoundation/nate/check-keys-during-ren…
Browse files Browse the repository at this point in the history
…ewal

Validate renewal unlock hash
  • Loading branch information
n8maninger authored Jun 13, 2024
2 parents b370a7b + 087a8db commit 85e9a3f
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 3 deletions.
6 changes: 3 additions & 3 deletions internal/test/rhp/v3/rhp.go
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,6 @@ func taxAdjustedPayout(target types.Currency) types.Currency {

func prepareContractRenewal(currentRevision types.FileContractRevision, renterAddress types.Address, renterKey types.PrivateKey, renterPayout, newCollateral types.Currency, hostKey types.PublicKey, hostAddr types.Address, host rhp3.HostPriceTable, endHeight uint64) (types.FileContract, types.Currency) {
hostValidPayout, hostMissedPayout, voidMissedPayout, basePrice := calculateRenewalPayouts(currentRevision.FileContract, newCollateral, host, endHeight)
renterPub := renterKey.PublicKey()
return types.FileContract{
Filesize: currentRevision.Filesize,
FileMerkleRoot: currentRevision.FileMerkleRoot,
Expand All @@ -764,9 +763,10 @@ func prepareContractRenewal(currentRevision types.FileContractRevision, renterAd
Payout: taxAdjustedPayout(renterPayout.Add(hostValidPayout)),
UnlockHash: types.Hash256(types.UnlockConditions{
PublicKeys: []types.UnlockKey{
{Algorithm: types.SpecifierEd25519, Key: renterPub[:]},
{Algorithm: types.SpecifierEd25519, Key: hostKey[:]},
renterKey.PublicKey().UnlockKey(),
hostKey.UnlockKey(),
},
SignaturesRequired: 2,
}.UnlockHash()),
RevisionNumber: 0,
ValidProofOutputs: []types.SiacoinOutput{
Expand Down
2 changes: 2 additions & 0 deletions rhp/v2/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ func validateContractRenewal(existing types.FileContractRevision, renewal types.
return types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency, errors.New("wrong address for missed host output")
case renewal.MissedProofOutputs[2].Address != types.VoidAddress:
return types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency, errors.New("wrong address for void output")
case renewal.UnlockHash != types.Hash256(contractUnlockConditions(hostKey, renterKey).UnlockHash()):
return types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency, errors.New("incorrect unlock hash")
}

expectedBurn := baseHostRevenue.Add(baseRiskedCollateral)
Expand Down
99 changes: 99 additions & 0 deletions rhp/v2/contracts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package rhp

import (
"math"
"testing"

rhp2 "go.sia.tech/core/rhp/v2"
"go.sia.tech/core/types"
"lukechampine.com/frand"
)

func TestValidateContractRenewal(t *testing.T) {
hostKey, renterKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey(), types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey()
hostAddress, renterAddress := types.StandardUnlockHash(hostKey), types.StandardUnlockHash(renterKey)
hostCollateral := types.NewCurrency64(frand.Uint64n(math.MaxUint64))
renterAllowance := types.NewCurrency64(frand.Uint64n(math.MaxUint64))

settings := rhp2.HostSettings{
MaxDuration: math.MaxUint64,
MaxCollateral: types.NewCurrency(math.MaxUint64, math.MaxUint64),
Address: hostAddress,
}

existing := types.FileContractRevision{
ParentID: types.FileContractID{1},
UnlockConditions: types.UnlockConditions{
PublicKeys: []types.UnlockKey{renterKey.UnlockKey(), hostKey.UnlockKey()},
SignaturesRequired: 2,
},
FileContract: types.FileContract{
RevisionNumber: frand.Uint64n(math.MaxUint64),
Filesize: frand.Uint64n(math.MaxUint64),
FileMerkleRoot: frand.Entropy256(),
WindowStart: 100,
WindowEnd: 300,
Payout: types.ZeroCurrency, // not validated here
UnlockHash: types.Hash256(types.UnlockConditions{
PublicKeys: []types.UnlockKey{renterKey.UnlockKey(), hostKey.UnlockKey()},
SignaturesRequired: 2,
}.UnlockHash()),
ValidProofOutputs: []types.SiacoinOutput{
{Address: renterAddress, Value: renterAllowance},
{Address: hostAddress, Value: hostCollateral},
},
MissedProofOutputs: []types.SiacoinOutput{
{Address: renterAddress, Value: renterAllowance},
{Address: hostAddress, Value: hostCollateral},
{Address: types.VoidAddress, Value: types.ZeroCurrency},
},
},
}

renewal := types.FileContract{
Filesize: existing.Filesize,
FileMerkleRoot: existing.FileMerkleRoot,
WindowStart: existing.WindowStart + 100,
WindowEnd: existing.WindowEnd + 100,
ValidProofOutputs: []types.SiacoinOutput{
{Address: renterAddress, Value: renterAllowance},
{Address: hostAddress, Value: hostCollateral},
},
MissedProofOutputs: []types.SiacoinOutput{
{Address: renterAddress, Value: renterAllowance},
{Address: hostAddress, Value: hostCollateral},
{Address: types.VoidAddress, Value: types.ZeroCurrency},
},
}

// bad renter key
badRenterKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey().UnlockKey()
renewal.UnlockHash = types.Hash256(contractUnlockConditions(hostKey.UnlockKey(), badRenterKey).UnlockHash())
_, _, _, err := validateContractRenewal(existing, renewal, hostKey.UnlockKey(), renterKey.UnlockKey(), types.ZeroCurrency, types.ZeroCurrency, 0, settings)
if err == nil || err.Error() != "incorrect unlock hash" {
t.Fatalf("expected unlock hash error, got %v", err)
}

// bad host key
badHostKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey().UnlockKey()
renewal.UnlockHash = types.Hash256(contractUnlockConditions(badHostKey, renterKey.UnlockKey()).UnlockHash())
_, _, _, err = validateContractRenewal(existing, renewal, hostKey.UnlockKey(), renterKey.UnlockKey(), types.ZeroCurrency, types.ZeroCurrency, 0, settings)
if err == nil || err.Error() != "incorrect unlock hash" {
t.Fatalf("expected unlock hash error, got %v", err)
}

// original keys
renewal.UnlockHash = types.Hash256(contractUnlockConditions(hostKey.UnlockKey(), renterKey.UnlockKey()).UnlockHash())
_, _, _, err = validateContractRenewal(existing, renewal, hostKey.UnlockKey(), renterKey.UnlockKey(), types.ZeroCurrency, types.ZeroCurrency, 0, settings)
if err != nil {
t.Fatal(err)
}

// different renter key, same host key
newRenterKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey().UnlockKey()
renewal.UnlockHash = types.Hash256(contractUnlockConditions(hostKey.UnlockKey(), newRenterKey).UnlockHash())
_, _, _, err = validateContractRenewal(existing, renewal, hostKey.UnlockKey(), newRenterKey, types.ZeroCurrency, types.ZeroCurrency, 0, settings)
if err != nil {
t.Fatal(err)
}
}
9 changes: 9 additions & 0 deletions rhp/v3/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ func hashFinalRevision(clearing types.FileContractRevision, renewal types.FileCo
return h.Sum()
}

func contractUnlockConditions(hostKey, renterKey types.UnlockKey) types.UnlockConditions {
return types.UnlockConditions{
PublicKeys: []types.UnlockKey{renterKey, hostKey},
SignaturesRequired: 2,
}
}

// validateContractRenewal verifies that the renewed contract is valid given the
// old contract. A renewal is valid if the contract fields match and the
// revision number is 0.
Expand Down Expand Up @@ -45,6 +52,8 @@ func validateContractRenewal(existing types.FileContractRevision, renewal types.
return types.ZeroCurrency, types.ZeroCurrency, errors.New("wrong address for missed host output")
case renewal.MissedProofOutputs[2].Address != types.VoidAddress:
return types.ZeroCurrency, types.ZeroCurrency, errors.New("wrong address for void output")
case renewal.UnlockHash != types.Hash256(contractUnlockConditions(hostKey, renterKey).UnlockHash()):
return types.ZeroCurrency, types.ZeroCurrency, errors.New("incorrect unlock hash")
}

expectedBurn := baseStorageRevenue.Add(baseRiskedCollateral)
Expand Down
98 changes: 98 additions & 0 deletions rhp/v3/contracts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package rhp

import (
"math"
"testing"

rhp3 "go.sia.tech/core/rhp/v3"
"go.sia.tech/core/types"
"lukechampine.com/frand"
)

func TestValidateContractRenewal(t *testing.T) {
hostKey, renterKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey(), types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey()
hostAddress, renterAddress := types.StandardUnlockHash(hostKey), types.StandardUnlockHash(renterKey)
hostCollateral := types.NewCurrency64(frand.Uint64n(math.MaxUint64))
renterAllowance := types.NewCurrency64(frand.Uint64n(math.MaxUint64))

pt := rhp3.HostPriceTable{
MaxDuration: math.MaxUint64,
MaxCollateral: types.NewCurrency(math.MaxUint64, math.MaxUint64),
}

existing := types.FileContractRevision{
ParentID: types.FileContractID{1},
UnlockConditions: types.UnlockConditions{
PublicKeys: []types.UnlockKey{renterKey.UnlockKey(), hostKey.UnlockKey()},
SignaturesRequired: 2,
},
FileContract: types.FileContract{
RevisionNumber: frand.Uint64n(math.MaxUint64),
Filesize: frand.Uint64n(math.MaxUint64),
FileMerkleRoot: frand.Entropy256(),
WindowStart: 100,
WindowEnd: 300,
Payout: types.ZeroCurrency, // not validated here
UnlockHash: types.Hash256(types.UnlockConditions{
PublicKeys: []types.UnlockKey{renterKey.UnlockKey(), hostKey.UnlockKey()},
SignaturesRequired: 2,
}.UnlockHash()),
ValidProofOutputs: []types.SiacoinOutput{
{Address: renterAddress, Value: renterAllowance},
{Address: hostAddress, Value: hostCollateral},
},
MissedProofOutputs: []types.SiacoinOutput{
{Address: renterAddress, Value: renterAllowance},
{Address: hostAddress, Value: hostCollateral},
{Address: types.VoidAddress, Value: types.ZeroCurrency},
},
},
}

renewal := types.FileContract{
Filesize: existing.Filesize,
FileMerkleRoot: existing.FileMerkleRoot,
WindowStart: existing.WindowStart + 100,
WindowEnd: existing.WindowEnd + 100,
ValidProofOutputs: []types.SiacoinOutput{
{Address: renterAddress, Value: renterAllowance},
{Address: hostAddress, Value: hostCollateral},
},
MissedProofOutputs: []types.SiacoinOutput{
{Address: renterAddress, Value: renterAllowance},
{Address: hostAddress, Value: hostCollateral},
{Address: types.VoidAddress, Value: types.ZeroCurrency},
},
}

// bad renter key
badRenterKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey().UnlockKey()
renewal.UnlockHash = types.Hash256(contractUnlockConditions(hostKey.UnlockKey(), badRenterKey).UnlockHash())
_, _, err := validateContractRenewal(existing, renewal, hostKey.UnlockKey(), renterKey.UnlockKey(), hostAddress, types.ZeroCurrency, types.ZeroCurrency, pt)
if err == nil || err.Error() != "incorrect unlock hash" {
t.Fatalf("expected unlock hash error, got %v", err)
}

// bad host key
badHostKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey().UnlockKey()
renewal.UnlockHash = types.Hash256(contractUnlockConditions(badHostKey, renterKey.UnlockKey()).UnlockHash())
_, _, err = validateContractRenewal(existing, renewal, hostKey.UnlockKey(), renterKey.UnlockKey(), hostAddress, types.ZeroCurrency, types.ZeroCurrency, pt)
if err == nil || err.Error() != "incorrect unlock hash" {
t.Fatalf("expected unlock hash error, got %v", err)
}

// original keys
renewal.UnlockHash = types.Hash256(contractUnlockConditions(hostKey.UnlockKey(), renterKey.UnlockKey()).UnlockHash())
_, _, err = validateContractRenewal(existing, renewal, hostKey.UnlockKey(), renterKey.UnlockKey(), hostAddress, types.ZeroCurrency, types.ZeroCurrency, pt)
if err != nil {
t.Fatal(err)
}

// different renter key, same host key
newRenterKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey().UnlockKey()
renewal.UnlockHash = types.Hash256(contractUnlockConditions(hostKey.UnlockKey(), newRenterKey).UnlockHash())
_, _, err = validateContractRenewal(existing, renewal, hostKey.UnlockKey(), newRenterKey, hostAddress, types.ZeroCurrency, types.ZeroCurrency, pt)
if err != nil {
t.Fatal(err)
}
}

0 comments on commit 85e9a3f

Please sign in to comment.