From a2366e6e0b3baa9d4d41e671d36475e92ce48f24 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Tue, 16 Mar 2021 02:47:13 -0500 Subject: [PATCH] stdaddr: Accept vote and revoke limits separately. This modifies the RewardCommitmentScript method of the StakeAddress interface to accept the vote and revoke fee limits separately instead of expecting the caller to have already encoded it properly. This simplifies the creation of the reward commitment script since there is not currently any other method available anywhere to independently encode the limits properly and it is not a particularly trivial encoding. Finally, the tests and example are updated accordingly. --- txscript/stdaddr/address.go | 11 ++++++-- txscript/stdaddr/address_test.go | 21 ++++++++------- txscript/stdaddr/addressv0.go | 45 +++++++++++++++++++++++++++++--- txscript/stdaddr/example_test.go | 5 ++-- 4 files changed, 64 insertions(+), 18 deletions(-) diff --git a/txscript/stdaddr/address.go b/txscript/stdaddr/address.go index 17a32e9d11..0fef64c64e 100644 --- a/txscript/stdaddr/address.go +++ b/txscript/stdaddr/address.go @@ -55,8 +55,15 @@ type StakeAddress interface { // RewardCommitmentScript returns the script version associated with the // address along with a script that commits the original funds locked to // purchase a ticket plus the reward to the address along with limits to - // impose on any fees. - RewardCommitmentScript(amount int64, limits uint16) (uint16, []byte) + // impose on any fees (in atoms). + // + // Note that fee limits are encoded in the commitment script in terms of the + // closest base 2 exponent that results in a limit that is >= the provided + // limit. In other words, the limits are rounded up to the next power of 2 + // when they are not already an exact power of 2. For example, a revocation + // limit of 2^23 + 1 will result in allowing a revocation fee of up to 2^24 + // atoms. + RewardCommitmentScript(amount, voteFeeLimit, revocationFeeLimit int64) (uint16, []byte) // StakeChangeScript returns the script version associated with the address // along with a script to pay change to the address. It is only valid when diff --git a/txscript/stdaddr/address_test.go b/txscript/stdaddr/address_test.go index b7a1e5e269..27c19d9b14 100644 --- a/txscript/stdaddr/address_test.go +++ b/txscript/stdaddr/address_test.go @@ -147,7 +147,8 @@ func TestAddresses(t *testing.T) { payScript string // hex-encoded expected payment script voteScript string // hex-encoded expected voting rights script rewardAmount int64 // reward commitment amount - feeLimits uint16 // reward fee limits commitment + voteFeeLimit int64 // reward commitment vote fee limit + revFeeLimit int64 // reward commitment revoke fee limit rewardScript string // hex-encoded expected reward commitment script changeScript string // hex-encoded expected stake change script commitScript string // hex-encoded expected vote commitment script @@ -711,7 +712,7 @@ func TestAddresses(t *testing.T) { payScript: "76a9142789d58cfa0957d206f025c2af056fc8a77cebb088ac", voteScript: "ba76a9142789d58cfa0957d206f025c2af056fc8a77cebb088ac", rewardAmount: 1e8, - feeLimits: 0x5800, + revFeeLimit: 16777216, rewardScript: "6a1e2789d58cfa0957d206f025c2af056fc8a77cebb000e1f505000000000058", changeScript: "bd76a9142789d58cfa0957d206f025c2af056fc8a77cebb088ac", commitScript: "bb76a9142789d58cfa0957d206f025c2af056fc8a77cebb088ac", @@ -731,7 +732,7 @@ func TestAddresses(t *testing.T) { payScript: "76a914229ebac30efd6a69eec9c1a48e048b7c975c25f288ac", voteScript: "ba76a914229ebac30efd6a69eec9c1a48e048b7c975c25f288ac", rewardAmount: 9556193632, - feeLimits: 0x5900, + revFeeLimit: 33554432, rewardScript: "6a1e229ebac30efd6a69eec9c1a48e048b7c975c25f260f19739020000000059", changeScript: "bd76a914229ebac30efd6a69eec9c1a48e048b7c975c25f288ac", commitScript: "bb76a914229ebac30efd6a69eec9c1a48e048b7c975c25f288ac", @@ -751,7 +752,7 @@ func TestAddresses(t *testing.T) { payScript: "76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac", voteScript: "ba76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac", rewardAmount: 2428220961, - feeLimits: 0x5800, + revFeeLimit: 16777216, rewardScript: "6a1ef15da1cb8d1bcb162c6ab446c95757a6e791c91621b6bb90000000000058", changeScript: "bd76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac", commitScript: "bb76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac", @@ -771,7 +772,7 @@ func TestAddresses(t *testing.T) { payScript: "76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac", voteScript: "ba76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac", rewardAmount: 2428220961, - feeLimits: 0x5800, + revFeeLimit: 16777216, rewardScript: "6a1ef15da1cb8d1bcb162c6ab446c95757a6e791c91621b6bb90000000000058", changeScript: "bd76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac", commitScript: "bb76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac", @@ -964,7 +965,7 @@ func TestAddresses(t *testing.T) { version: 0, payScript: "a914f0b4e85100aee1a996f22915eb3c3f764d53779a87", rewardAmount: 1e8, - feeLimits: 0x5800, + revFeeLimit: 16777216, rewardScript: "6a1ef0b4e85100aee1a996f22915eb3c3f764d53779a00e1f505000000800058", voteScript: "baa914f0b4e85100aee1a996f22915eb3c3f764d53779a87", changeScript: "bda914f0b4e85100aee1a996f22915eb3c3f764d53779a87", @@ -985,7 +986,7 @@ func TestAddresses(t *testing.T) { payScript: "a914c7da5095683436f4435fc4e7163dcafda1a2d00787", voteScript: "baa914c7da5095683436f4435fc4e7163dcafda1a2d00787", rewardAmount: 9556193632, - feeLimits: 0x5900, + revFeeLimit: 33554432, rewardScript: "6a1ec7da5095683436f4435fc4e7163dcafda1a2d00760f19739020000800059", changeScript: "bda914c7da5095683436f4435fc4e7163dcafda1a2d00787", commitScript: "bba914c7da5095683436f4435fc4e7163dcafda1a2d00787", @@ -1005,7 +1006,7 @@ func TestAddresses(t *testing.T) { payScript: "a91436c1ca10a8a6a4b5d4204ac970853979903aa28487", voteScript: "baa91436c1ca10a8a6a4b5d4204ac970853979903aa28487", rewardAmount: 2428220961, - feeLimits: 0x5800, + revFeeLimit: 16777216, rewardScript: "6a1e36c1ca10a8a6a4b5d4204ac970853979903aa28421b6bb90000000800058", changeScript: "bda91436c1ca10a8a6a4b5d4204ac970853979903aa28487", commitScript: "bba91436c1ca10a8a6a4b5d4204ac970853979903aa28487", @@ -1025,7 +1026,7 @@ func TestAddresses(t *testing.T) { payScript: "a91436c1ca10a8a6a4b5d4204ac970853979903aa28487", voteScript: "baa91436c1ca10a8a6a4b5d4204ac970853979903aa28487", rewardAmount: 2428220961, - feeLimits: 0x5800, + revFeeLimit: 16777216, rewardScript: "6a1e36c1ca10a8a6a4b5d4204ac970853979903aa28421b6bb90000000800058", changeScript: "bda91436c1ca10a8a6a4b5d4204ac970853979903aa28487", commitScript: "bba91436c1ca10a8a6a4b5d4204ac970853979903aa28487", @@ -1116,7 +1117,7 @@ func TestAddresses(t *testing.T) { continue } gotScriptVer, gotScript = stakeAddr.RewardCommitmentScript( - test.rewardAmount, test.feeLimits) + test.rewardAmount, test.voteFeeLimit, test.revFeeLimit) if gotScriptVer != test.version { t.Errorf("%s: mismatched reward cmt script version -- got %d, "+ "want %d", test.name, gotScriptVer, test.version) diff --git a/txscript/stdaddr/addressv0.go b/txscript/stdaddr/addressv0.go index dbae5b3841..d8f504a847 100644 --- a/txscript/stdaddr/addressv0.go +++ b/txscript/stdaddr/addressv0.go @@ -8,6 +8,7 @@ import ( "encoding/binary" "errors" "fmt" + "math" "github.com/decred/base58" "github.com/decred/dcrd/crypto/blake256" @@ -648,13 +649,40 @@ func (addr *AddressPubKeyHashEcdsaSecp256k1V0) VotingRightsScript() (uint16, []b return 0, script[:] } +// calcRewardCommitScriptLimits calculates the encoded limits to impose on fees +// applied to votes and revocations via the reward commitment script of a ticket +// purchase. +func calcRewardCommitScriptLimits(voteFeeLimit, revocationFeeLimit int64) uint16 { + // The limits are defined in terms of the closest base 2 exponent and + // a bit that must be set to specify the limit is to be applied. The + // vote fee exponent is in the bottom 8 bits, while the revocation fee + // exponent is in the upper 8 bits. + limits := uint16(0) + if voteFeeLimit != 0 { + exp := uint16(math.Ceil(math.Log2(float64(voteFeeLimit)))) + limits |= (exp | 0x40) + } + if revocationFeeLimit != 0 { + exp := uint16(math.Ceil(math.Log2(float64(revocationFeeLimit)))) + limits |= ((exp | 0x40) << 8) + } + return limits +} + // RewardCommitmentScript returns the script version associated with the address // along with a script that commits the original funds locked to purchase a // ticket plus the reward to the address along with limits to impose on any -// fees. +// fees (in atoms). +// +// Note that fee limits are encoded in the commitment script in terms of the +// closest base 2 exponent that results in a limit that is >= the provided +// limit. In other words, the limits are rounded up to the next power of 2 +// when they are not already an exact power of 2. For example, a revocation +// limit of 2^23 + 1 will result in allowing a revocation fee of up to 2^24 +// atoms. // // This is part of the StakeAddress interface implementation. -func (addr *AddressPubKeyHashEcdsaSecp256k1V0) RewardCommitmentScript(amount int64, limits uint16) (uint16, []byte) { +func (addr *AddressPubKeyHashEcdsaSecp256k1V0) RewardCommitmentScript(amount, voteFeeLimit, revocationFeeLimit int64) (uint16, []byte) { // The reward commitment output of a ticket purchase is a provably pruneable // script of the form: // RETURN <20-byte hash || 8-byte amount || 2-byte fee limits> @@ -663,6 +691,7 @@ func (addr *AddressPubKeyHashEcdsaSecp256k1V0) RewardCommitmentScript(amount int // is a public key hash that represents a pay-to-pubkey-hash-ecdsa-secp256k1 // script or a script hash that represents a pay-to-script-hash script. It // is NOT set for a public key hash. + limits := calcRewardCommitScriptLimits(voteFeeLimit, revocationFeeLimit) var script [32]byte script[0] = opReturn script[1] = opData30 @@ -1048,10 +1077,17 @@ func (addr *AddressScriptHashV0) VotingRightsScript() (uint16, []byte) { // RewardCommitmentScript returns the script version associated with the address // along with a script that commits the original funds locked to purchase a // ticket plus the reward to the address along with limits to impose on any -// fees. +// fees (in atoms). +// +// Note that fee limits are encoded in the commitment script in terms of the +// closest base 2 exponent that results in a limit that is >= the provided +// limit. In other words, the limits are rounded up to the next power of 2 +// when they are not already an exact power of 2. For example, a revocation +// limit of 2^23 + 1 will result in allowing a revocation fee of up to 2^24 +// atoms. // // This is part of the StakeAddress interface implementation. -func (addr *AddressScriptHashV0) RewardCommitmentScript(amount int64, limits uint16) (uint16, []byte) { +func (addr *AddressScriptHashV0) RewardCommitmentScript(amount, voteFeeLimit, revocationFeeLimit int64) (uint16, []byte) { // The reward commitment output of a ticket purchase is a provably pruneable // script of the form: // RETURN <20-byte hash || 8-byte amount || 2-byte fee limits> @@ -1060,6 +1096,7 @@ func (addr *AddressScriptHashV0) RewardCommitmentScript(amount int64, limits uin // is a public key hash that represents a pay-to-pubkey-hash-ecdsa-secp256k1 // script or a script hash that represents a pay-to-script-hash script. It // is set for a script hash. + limits := calcRewardCommitScriptLimits(voteFeeLimit, revocationFeeLimit) var script [32]byte script[0] = opReturn script[1] = opData30 diff --git a/txscript/stdaddr/example_test.go b/txscript/stdaddr/example_test.go index d773e3d690..395fbb3034 100644 --- a/txscript/stdaddr/example_test.go +++ b/txscript/stdaddr/example_test.go @@ -90,9 +90,10 @@ func ExampleDecodeAddress() { // calculated correctly, but they are hard coded here for the // purposes of this example. const rewardAmount = 1e8 - const feeLimit = 0x5800 + const voteFeeLimit = 0 + const revokeFeeLimit = 16777216 rewardScriptVer, rewardScript := stakeAddr.RewardCommitmentScript( - rewardAmount, feeLimit) + rewardAmount, voteFeeLimit, revokeFeeLimit) fmt.Printf(" reward script version: %d\n", rewardScriptVer) fmt.Printf(" reward script: %x\n", rewardScript) }