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) }