Skip to content

Commit 85e9a3f

Browse files
authored
Merge pull request #329 from SiaFoundation/nate/check-keys-during-renewal
Validate renewal unlock hash
2 parents b370a7b + 087a8db commit 85e9a3f

File tree

5 files changed

+211
-3
lines changed

5 files changed

+211
-3
lines changed

internal/test/rhp/v3/rhp.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,6 @@ func taxAdjustedPayout(target types.Currency) types.Currency {
755755

756756
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) {
757757
hostValidPayout, hostMissedPayout, voidMissedPayout, basePrice := calculateRenewalPayouts(currentRevision.FileContract, newCollateral, host, endHeight)
758-
renterPub := renterKey.PublicKey()
759758
return types.FileContract{
760759
Filesize: currentRevision.Filesize,
761760
FileMerkleRoot: currentRevision.FileMerkleRoot,
@@ -764,9 +763,10 @@ func prepareContractRenewal(currentRevision types.FileContractRevision, renterAd
764763
Payout: taxAdjustedPayout(renterPayout.Add(hostValidPayout)),
765764
UnlockHash: types.Hash256(types.UnlockConditions{
766765
PublicKeys: []types.UnlockKey{
767-
{Algorithm: types.SpecifierEd25519, Key: renterPub[:]},
768-
{Algorithm: types.SpecifierEd25519, Key: hostKey[:]},
766+
renterKey.PublicKey().UnlockKey(),
767+
hostKey.UnlockKey(),
769768
},
769+
SignaturesRequired: 2,
770770
}.UnlockHash()),
771771
RevisionNumber: 0,
772772
ValidProofOutputs: []types.SiacoinOutput{

rhp/v2/contracts.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ func validateContractRenewal(existing types.FileContractRevision, renewal types.
9999
return types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency, errors.New("wrong address for missed host output")
100100
case renewal.MissedProofOutputs[2].Address != types.VoidAddress:
101101
return types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency, errors.New("wrong address for void output")
102+
case renewal.UnlockHash != types.Hash256(contractUnlockConditions(hostKey, renterKey).UnlockHash()):
103+
return types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency, errors.New("incorrect unlock hash")
102104
}
103105

104106
expectedBurn := baseHostRevenue.Add(baseRiskedCollateral)

rhp/v2/contracts_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package rhp
2+
3+
import (
4+
"math"
5+
"testing"
6+
7+
rhp2 "go.sia.tech/core/rhp/v2"
8+
"go.sia.tech/core/types"
9+
"lukechampine.com/frand"
10+
)
11+
12+
func TestValidateContractRenewal(t *testing.T) {
13+
hostKey, renterKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey(), types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey()
14+
hostAddress, renterAddress := types.StandardUnlockHash(hostKey), types.StandardUnlockHash(renterKey)
15+
hostCollateral := types.NewCurrency64(frand.Uint64n(math.MaxUint64))
16+
renterAllowance := types.NewCurrency64(frand.Uint64n(math.MaxUint64))
17+
18+
settings := rhp2.HostSettings{
19+
MaxDuration: math.MaxUint64,
20+
MaxCollateral: types.NewCurrency(math.MaxUint64, math.MaxUint64),
21+
Address: hostAddress,
22+
}
23+
24+
existing := types.FileContractRevision{
25+
ParentID: types.FileContractID{1},
26+
UnlockConditions: types.UnlockConditions{
27+
PublicKeys: []types.UnlockKey{renterKey.UnlockKey(), hostKey.UnlockKey()},
28+
SignaturesRequired: 2,
29+
},
30+
FileContract: types.FileContract{
31+
RevisionNumber: frand.Uint64n(math.MaxUint64),
32+
Filesize: frand.Uint64n(math.MaxUint64),
33+
FileMerkleRoot: frand.Entropy256(),
34+
WindowStart: 100,
35+
WindowEnd: 300,
36+
Payout: types.ZeroCurrency, // not validated here
37+
UnlockHash: types.Hash256(types.UnlockConditions{
38+
PublicKeys: []types.UnlockKey{renterKey.UnlockKey(), hostKey.UnlockKey()},
39+
SignaturesRequired: 2,
40+
}.UnlockHash()),
41+
ValidProofOutputs: []types.SiacoinOutput{
42+
{Address: renterAddress, Value: renterAllowance},
43+
{Address: hostAddress, Value: hostCollateral},
44+
},
45+
MissedProofOutputs: []types.SiacoinOutput{
46+
{Address: renterAddress, Value: renterAllowance},
47+
{Address: hostAddress, Value: hostCollateral},
48+
{Address: types.VoidAddress, Value: types.ZeroCurrency},
49+
},
50+
},
51+
}
52+
53+
renewal := types.FileContract{
54+
Filesize: existing.Filesize,
55+
FileMerkleRoot: existing.FileMerkleRoot,
56+
WindowStart: existing.WindowStart + 100,
57+
WindowEnd: existing.WindowEnd + 100,
58+
ValidProofOutputs: []types.SiacoinOutput{
59+
{Address: renterAddress, Value: renterAllowance},
60+
{Address: hostAddress, Value: hostCollateral},
61+
},
62+
MissedProofOutputs: []types.SiacoinOutput{
63+
{Address: renterAddress, Value: renterAllowance},
64+
{Address: hostAddress, Value: hostCollateral},
65+
{Address: types.VoidAddress, Value: types.ZeroCurrency},
66+
},
67+
}
68+
69+
// bad renter key
70+
badRenterKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey().UnlockKey()
71+
renewal.UnlockHash = types.Hash256(contractUnlockConditions(hostKey.UnlockKey(), badRenterKey).UnlockHash())
72+
_, _, _, err := validateContractRenewal(existing, renewal, hostKey.UnlockKey(), renterKey.UnlockKey(), types.ZeroCurrency, types.ZeroCurrency, 0, settings)
73+
if err == nil || err.Error() != "incorrect unlock hash" {
74+
t.Fatalf("expected unlock hash error, got %v", err)
75+
}
76+
77+
// bad host key
78+
badHostKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey().UnlockKey()
79+
renewal.UnlockHash = types.Hash256(contractUnlockConditions(badHostKey, renterKey.UnlockKey()).UnlockHash())
80+
_, _, _, err = validateContractRenewal(existing, renewal, hostKey.UnlockKey(), renterKey.UnlockKey(), types.ZeroCurrency, types.ZeroCurrency, 0, settings)
81+
if err == nil || err.Error() != "incorrect unlock hash" {
82+
t.Fatalf("expected unlock hash error, got %v", err)
83+
}
84+
85+
// original keys
86+
renewal.UnlockHash = types.Hash256(contractUnlockConditions(hostKey.UnlockKey(), renterKey.UnlockKey()).UnlockHash())
87+
_, _, _, err = validateContractRenewal(existing, renewal, hostKey.UnlockKey(), renterKey.UnlockKey(), types.ZeroCurrency, types.ZeroCurrency, 0, settings)
88+
if err != nil {
89+
t.Fatal(err)
90+
}
91+
92+
// different renter key, same host key
93+
newRenterKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey().UnlockKey()
94+
renewal.UnlockHash = types.Hash256(contractUnlockConditions(hostKey.UnlockKey(), newRenterKey).UnlockHash())
95+
_, _, _, err = validateContractRenewal(existing, renewal, hostKey.UnlockKey(), newRenterKey, types.ZeroCurrency, types.ZeroCurrency, 0, settings)
96+
if err != nil {
97+
t.Fatal(err)
98+
}
99+
}

rhp/v3/contracts.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ func hashFinalRevision(clearing types.FileContractRevision, renewal types.FileCo
1616
return h.Sum()
1717
}
1818

19+
func contractUnlockConditions(hostKey, renterKey types.UnlockKey) types.UnlockConditions {
20+
return types.UnlockConditions{
21+
PublicKeys: []types.UnlockKey{renterKey, hostKey},
22+
SignaturesRequired: 2,
23+
}
24+
}
25+
1926
// validateContractRenewal verifies that the renewed contract is valid given the
2027
// old contract. A renewal is valid if the contract fields match and the
2128
// revision number is 0.
@@ -45,6 +52,8 @@ func validateContractRenewal(existing types.FileContractRevision, renewal types.
4552
return types.ZeroCurrency, types.ZeroCurrency, errors.New("wrong address for missed host output")
4653
case renewal.MissedProofOutputs[2].Address != types.VoidAddress:
4754
return types.ZeroCurrency, types.ZeroCurrency, errors.New("wrong address for void output")
55+
case renewal.UnlockHash != types.Hash256(contractUnlockConditions(hostKey, renterKey).UnlockHash()):
56+
return types.ZeroCurrency, types.ZeroCurrency, errors.New("incorrect unlock hash")
4857
}
4958

5059
expectedBurn := baseStorageRevenue.Add(baseRiskedCollateral)

rhp/v3/contracts_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package rhp
2+
3+
import (
4+
"math"
5+
"testing"
6+
7+
rhp3 "go.sia.tech/core/rhp/v3"
8+
"go.sia.tech/core/types"
9+
"lukechampine.com/frand"
10+
)
11+
12+
func TestValidateContractRenewal(t *testing.T) {
13+
hostKey, renterKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey(), types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey()
14+
hostAddress, renterAddress := types.StandardUnlockHash(hostKey), types.StandardUnlockHash(renterKey)
15+
hostCollateral := types.NewCurrency64(frand.Uint64n(math.MaxUint64))
16+
renterAllowance := types.NewCurrency64(frand.Uint64n(math.MaxUint64))
17+
18+
pt := rhp3.HostPriceTable{
19+
MaxDuration: math.MaxUint64,
20+
MaxCollateral: types.NewCurrency(math.MaxUint64, math.MaxUint64),
21+
}
22+
23+
existing := types.FileContractRevision{
24+
ParentID: types.FileContractID{1},
25+
UnlockConditions: types.UnlockConditions{
26+
PublicKeys: []types.UnlockKey{renterKey.UnlockKey(), hostKey.UnlockKey()},
27+
SignaturesRequired: 2,
28+
},
29+
FileContract: types.FileContract{
30+
RevisionNumber: frand.Uint64n(math.MaxUint64),
31+
Filesize: frand.Uint64n(math.MaxUint64),
32+
FileMerkleRoot: frand.Entropy256(),
33+
WindowStart: 100,
34+
WindowEnd: 300,
35+
Payout: types.ZeroCurrency, // not validated here
36+
UnlockHash: types.Hash256(types.UnlockConditions{
37+
PublicKeys: []types.UnlockKey{renterKey.UnlockKey(), hostKey.UnlockKey()},
38+
SignaturesRequired: 2,
39+
}.UnlockHash()),
40+
ValidProofOutputs: []types.SiacoinOutput{
41+
{Address: renterAddress, Value: renterAllowance},
42+
{Address: hostAddress, Value: hostCollateral},
43+
},
44+
MissedProofOutputs: []types.SiacoinOutput{
45+
{Address: renterAddress, Value: renterAllowance},
46+
{Address: hostAddress, Value: hostCollateral},
47+
{Address: types.VoidAddress, Value: types.ZeroCurrency},
48+
},
49+
},
50+
}
51+
52+
renewal := types.FileContract{
53+
Filesize: existing.Filesize,
54+
FileMerkleRoot: existing.FileMerkleRoot,
55+
WindowStart: existing.WindowStart + 100,
56+
WindowEnd: existing.WindowEnd + 100,
57+
ValidProofOutputs: []types.SiacoinOutput{
58+
{Address: renterAddress, Value: renterAllowance},
59+
{Address: hostAddress, Value: hostCollateral},
60+
},
61+
MissedProofOutputs: []types.SiacoinOutput{
62+
{Address: renterAddress, Value: renterAllowance},
63+
{Address: hostAddress, Value: hostCollateral},
64+
{Address: types.VoidAddress, Value: types.ZeroCurrency},
65+
},
66+
}
67+
68+
// bad renter key
69+
badRenterKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey().UnlockKey()
70+
renewal.UnlockHash = types.Hash256(contractUnlockConditions(hostKey.UnlockKey(), badRenterKey).UnlockHash())
71+
_, _, err := validateContractRenewal(existing, renewal, hostKey.UnlockKey(), renterKey.UnlockKey(), hostAddress, types.ZeroCurrency, types.ZeroCurrency, pt)
72+
if err == nil || err.Error() != "incorrect unlock hash" {
73+
t.Fatalf("expected unlock hash error, got %v", err)
74+
}
75+
76+
// bad host key
77+
badHostKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey().UnlockKey()
78+
renewal.UnlockHash = types.Hash256(contractUnlockConditions(badHostKey, renterKey.UnlockKey()).UnlockHash())
79+
_, _, err = validateContractRenewal(existing, renewal, hostKey.UnlockKey(), renterKey.UnlockKey(), hostAddress, types.ZeroCurrency, types.ZeroCurrency, pt)
80+
if err == nil || err.Error() != "incorrect unlock hash" {
81+
t.Fatalf("expected unlock hash error, got %v", err)
82+
}
83+
84+
// original keys
85+
renewal.UnlockHash = types.Hash256(contractUnlockConditions(hostKey.UnlockKey(), renterKey.UnlockKey()).UnlockHash())
86+
_, _, err = validateContractRenewal(existing, renewal, hostKey.UnlockKey(), renterKey.UnlockKey(), hostAddress, types.ZeroCurrency, types.ZeroCurrency, pt)
87+
if err != nil {
88+
t.Fatal(err)
89+
}
90+
91+
// different renter key, same host key
92+
newRenterKey := types.NewPrivateKeyFromSeed(frand.Bytes(32)).PublicKey().UnlockKey()
93+
renewal.UnlockHash = types.Hash256(contractUnlockConditions(hostKey.UnlockKey(), newRenterKey).UnlockHash())
94+
_, _, err = validateContractRenewal(existing, renewal, hostKey.UnlockKey(), newRenterKey, hostAddress, types.ZeroCurrency, types.ZeroCurrency, pt)
95+
if err != nil {
96+
t.Fatal(err)
97+
}
98+
}

0 commit comments

Comments
 (0)