Skip to content

Commit

Permalink
asset: limit max value of issuance leaf to math.MaxUint32
Browse files Browse the repository at this point in the history
This is a step towards fixing:
#544.

In this commit, we limit the max amount of an asset that can be issued
at one time to math.MaxUint32. The existing 32-bit sum is left in place.
This gives us enough buffer room, as we'd need 4 billion UTXOs of the
same assets to overflow the root sum. In the future, we can lift this
limit with future asset versions.

An asset can have more than 4 billion ish units if they issue with
distinct UTXOs. This value can be viewed as the max amt that can be
issued in a single tranche.
  • Loading branch information
Roasbeef committed Oct 3, 2023
1 parent 1c50eba commit 9da3366
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 3 deletions.
2 changes: 1 addition & 1 deletion address/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func randAddress(t *testing.T, net *ChainParams, v Version, groupPubKey,
}

if amt == nil && assetType == asset.Normal {
amount = test.RandInt[uint64]()
amount = uint64(test.RandInt[uint32]())
}

var tapscriptSibling *commitment.TapscriptPreimage
Expand Down
2 changes: 1 addition & 1 deletion address/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func RandAddr(t testing.TB, params *ChainParams,
internalKey := test.RandPrivKey(t)

genesis := asset.RandGenesis(t, asset.Type(test.RandInt31n(2)))
amount := test.RandInt[uint64]()
amount := uint64(test.RandInt[uint32]())
if genesis.Type == asset.Collectible {
amount = 1
}
Expand Down
17 changes: 17 additions & 0 deletions asset/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"io"
"math"
"reflect"
"strings"
"unicode"
Expand All @@ -30,6 +31,16 @@ const (
// This byte length is equivalent to character count for single-byte
// UTF-8 characters.
MaxAssetNameLength = 64

// MaxIssuanceUnits is the maximum number of units that can be issued
// in a single asset UTXO for v0 and v1 assets.
MaxIssuanceUnits = math.MaxUint32
)

var (
// ErrMaxIssuanceUnits is returned when an asset is being issued with
// more units that permitted.
ErrMaxIssuanceUnits = errors.New("asset: max issuance units exceeded")
)

// SerializedKey is a type for representing a public key, serialized in the
Expand Down Expand Up @@ -883,6 +894,12 @@ func New(genesis Genesis, amount, locktime, relativeLocktime uint64,
genesis.Type)
}

// An asset can't be created that exceeds the max issuance value for
// this asset version.
if amount > MaxIssuanceUnits {
return nil, fmt.Errorf("%w: %d", ErrMaxIssuanceUnits, amount)
}

// Valid genesis asset witness.
genesisWitness := Witness{
PrevID: &PrevID{},
Expand Down
62 changes: 62 additions & 0 deletions asset/asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"crypto/sha256"
"encoding/hex"
"math"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -38,8 +39,11 @@ var (

generatedTestVectorName = "asset_tlv_encoding_generated.json"

amtTestVectorName = "asset_tlv_amounts.json"

allTestVectorFiles = []string{
generatedTestVectorName,
amtTestVectorName,
"asset_tlv_encoding_error_cases.json",
}

Expand Down Expand Up @@ -757,3 +761,61 @@ func runBIPTestVector(t *testing.T, testVectors *TestVectors) {
})
}
}

// TestAssetMaxAmount tests that the maximum amount of an asset is correctly
// enforced for v0 and v1 assets.
func TestAssetMaxAmount(t *testing.T) {
t.Parallel()

testVectors := &TestVectors{}

testGen := splitGen
testGen.Type = Normal

t.Run("max_uint32_plus_1", func(t *testing.T) {
_, err := New(
testGen, math.MaxUint32+1, 0, 0, NewScriptKey(pubKey),
nil,
)
require.ErrorIs(t, err, ErrMaxIssuanceUnits)

asset0Error, err := New(
testGen, math.MaxUint32, 0, 0, NewScriptKey(pubKey),
nil,
)
require.NoError(t, err)

asset0Error.Amount = math.MaxUint32 + 1

testVectors.ErrorTestCases = append(
testVectors.ErrorTestCases, &ErrorTestCase{
Asset: NewTestFromAsset(t, asset0Error),
Error: ErrMaxIssuanceUnits.Error(),
Comment: "invalid asset value > max uint32",
},
)
})

t.Run("max_uint32", func(t *testing.T) {
asset1, err := New(
testGen, math.MaxUint32, 0, 0, NewScriptKey(pubKey),
nil,
)
require.NoError(t, err)

var buf bytes.Buffer
require.NoError(t, asset1.Encode(&buf))

testVectors.ValidTestCases = append(
testVectors.ValidTestCases, &ValidTestCase{
Asset: NewTestFromAsset(t, asset1),
Expected: hex.EncodeToString(buf.Bytes()),
Comment: "max uint32 valid asset value",
},
)
})

// Write test vectors to file. This is a no-op if the
// "gen_test_vectors" build tag is not set.
test.WriteTestVectors(t, amtTestVectorName, testVectors)
}
5 changes: 4 additions & 1 deletion asset/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,10 @@ func RandAssetWithValues(t testing.TB, genesis Genesis, groupKey *GroupKey,

t.Helper()

units := test.RandInt[uint64]() + 1
units := uint64(test.RandInt[uint32]())
if units == 0 {
units = 1
}

switch genesis.Type {
case Normal:
Expand Down

0 comments on commit 9da3366

Please sign in to comment.