Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

asset: limit max value of issuance leaf to math.MaxUint32 #545

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ gen-deterministic-test-vectors:
make unit gen-test-vectors=true pkg=address case=TestAddressEncoding
make unit gen-test-vectors=true pkg=asset case=TestAssetEncoding
make unit gen-test-vectors=true pkg=asset case=TestDeriveBurnKey
make unit gen-test-vectors=true pkg=asset case=TestAssetMaxAmount
make unit gen-test-vectors=true pkg=mssmt case=TestProofEncoding
make unit gen-test-vectors=true pkg=mssmt case=TestInsertionOverflow
make unit gen-test-vectors=true pkg=mssmt case=TestReplaceWithEmptyBranch
Expand Down
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
16 changes: 8 additions & 8 deletions address/testdata/address_tlv_encoding_generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
"script_key": "02a0afeb165f0ec36880b68e0baabd9ad9c62fd1a69aa998bc30e9a346202e078f",
"internal_key": "02a0afeb165f0ec36880b68e0baabd9ad9c62fd1a69aa998bc30e9a346202e078f",
"tapscript_sibling": "",
"amount": 5577006791947779410,
"amount": 134020434,
"proof_courier_addr": "hashmail://rand.hashmail.proof.courier:443"
},
"expected": "taprt1qqqsqqspqqzzq73cz93shve4q0r9xmp6yg7netktj0l9ta9ngw2j3m0j0vgd8r5nqcss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pqss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pgyl7nt9sgss0l8a2gxz56rpwd5x6ctfdsaz7tmjv9hxgtngv9eksmtpd9kzuurjdahkvtnrda6hy6t9wgargdpn5zn5dz",
"expected": "taprt1qqqsqqspqqzzq73cz93shve4q0r9xmp6yg7netktj0l9ta9ngw2j3m0j0vgd8r5nqcss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pqss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pgzluplul4fqc2ngv9eksmtpd9kr5te0wfskuepwdpshx6rdv95kctnswfhk7e3wvdhh2unfv4er5dp5xvsnxtg0",
"comment": "valid regtest address"
},
{
Expand All @@ -26,10 +26,10 @@
"script_key": "02a0afeb165f0ec36880b68e0baabd9ad9c62fd1a69aa998bc30e9a346202e078f",
"internal_key": "02a0afeb165f0ec36880b68e0baabd9ad9c62fd1a69aa998bc30e9a346202e078f",
"tapscript_sibling": "",
"amount": 3510942875414458836,
"amount": 2210689492,
"proof_courier_addr": "hashmail://rand.hashmail.proof.courier:443"
},
"expected": "tapsb1qqqsqqspqqzzpzkt292zv9p9m4snlksemmsq550ft35jrhu27sy9h3rgwp4egmlvqcss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pqss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pgyl7v9etlcc83r36sxz56rpwd5x6ctfdsaz7tmjv9hxgtngv9eksmtpd9kzuurjdahkvtnrda6hy6t9wgargdpnrzxc4w",
"expected": "tapsb1qqqsqqspqqzzpzkt292zv9p9m4snlksemmsq550ft35jrhu27sy9h3rgwp4egmlvqcss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pqss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pgzlaq7yw82qc2ngv9eksmtpd9kr5te0wfskuepwdpshx6rdv95kctnswfhk7e3wvdhh2unfv4er5dp5xv6f3kgz",
"comment": "valid simnet address"
},
{
Expand All @@ -42,10 +42,10 @@
"script_key": "02a0afeb165f0ec36880b68e0baabd9ad9c62fd1a69aa998bc30e9a346202e078f",
"internal_key": "02a0afeb165f0ec36880b68e0baabd9ad9c62fd1a69aa998bc30e9a346202e078f",
"tapscript_sibling": "",
"amount": 2740103009342231109,
"amount": 1473417797,
"proof_courier_addr": "hashmail://rand.hashmail.proof.courier:443"
},
"expected": "taptb1qqqsqqspqqzzqcz9nja56nn20zuvtp297tlqy6kjz7lsltlw6hl4uwen08r93h0nqcss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pqss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pgyl7fsxe544055jg5xz56rpwd5x6ctfdsaz7tmjv9hxgtngv9eksmtpd9kzuurjdahkvtnrda6hy6t9wgargdpny8e5zg",
"expected": "taptb1qqqsqqspqqzzqcz9nja56nn20zuvtp297tlqy6kjz7lsltlw6hl4uwen08r93h0nqcss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pqss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pgzlu47jjfzsc2ngv9eksmtpd9kr5te0wfskuepwdpshx6rdv95kctnswfhk7e3wvdhh2unfv4er5dp5xvxehu4k",
"comment": "valid testnet address"
},
{
Expand All @@ -58,10 +58,10 @@
"script_key": "02a0afeb165f0ec36880b68e0baabd9ad9c62fd1a69aa998bc30e9a346202e078f",
"internal_key": "02a0afeb165f0ec36880b68e0baabd9ad9c62fd1a69aa998bc30e9a346202e078f",
"tapscript_sibling": "",
"amount": 545291762129038907,
"amount": 4104515131,
"proof_courier_addr": "hashmail://rand.hashmail.proof.courier:443"
},
"expected": "tapbc1qqqsqqspqqzzpuk8pcfxrdmp7zvynxgc4nmveuvd0kw00nttury5qcc0qep6hqevqcss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pqss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pgyl7pu3g0mlff0w8vxz56rpwd5x6ctfdsaz7tmjv9hxgtngv9eksmtpd9kzuurjdahkvtnrda6hy6t9wgargdpnlhg0u8",
"expected": "tapbc1qqqsqqspqqzzpuk8pcfxrdmp7zvynxgc4nmveuvd0kw00nttury5qcc0qep6hqevqcss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pqss9g90avt97rkrdzqtdrst427e4kwx9lg6dx4fnz7rp6drgcszupu0pgzlaa99acasc2ngv9eksmtpd9kr5te0wfskuepwdpshx6rdv95kctnswfhk7e3wvdhh2unfv4er5dp5xv0h7upk",
"comment": "valid mainnet address"
},
{
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
66 changes: 66 additions & 0 deletions asset/testdata/asset_tlv_amounts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"valid_test_cases": [
{
"asset": {
"version": 0,
"genesis_first_prev_out": "0101010101010101010101010101010101010101010101010101010101010101:1",
"genesis_tag": "asset",
"genesis_meta_hash": "0102030000000000000000000000000000000000000000000000000000000000",
"genesis_output_index": 1,
"genesis_type": 0,
"amount": 4294967295,
"lock_time": 0,
"relative_lock_time": 0,
"prev_witnesses": [
{
"prev_id": {
"out_point": "0000000000000000000000000000000000000000000000000000000000000000:0",
"asset_id": "0000000000000000000000000000000000000000000000000000000000000000",
"script_key": "000000000000000000000000000000000000000000000000000000000000000000"
},
"tx_witness": null,
"split_commitment": null
}
],
"split_commitment_root": null,
"script_version": 0,
"script_key": "02a0afeb165f0ec36880b68e0baabd9ad9c62fd1a69aa998bc30e9a346202e078f",
"group_key": null
},
"expected": "000100014f010101010101010101010101010101010101010101010101010101010101010100000001056173736574010203000000000000000000000000000000000000000000000000000000000000000001000201000305feffffffff066901670065000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008020000092102a0afeb165f0ec36880b68e0baabd9ad9c62fd1a69aa998bc30e9a346202e078f",
"comment": "max uint32 valid asset value"
}
],
"error_test_cases": [
{
"asset": {
"version": 0,
"genesis_first_prev_out": "0101010101010101010101010101010101010101010101010101010101010101:1",
"genesis_tag": "asset",
"genesis_meta_hash": "0102030000000000000000000000000000000000000000000000000000000000",
"genesis_output_index": 1,
"genesis_type": 0,
"amount": 4294967296,
"lock_time": 0,
"relative_lock_time": 0,
"prev_witnesses": [
{
"prev_id": {
"out_point": "0000000000000000000000000000000000000000000000000000000000000000:0",
"asset_id": "0000000000000000000000000000000000000000000000000000000000000000",
"script_key": "000000000000000000000000000000000000000000000000000000000000000000"
},
"tx_witness": null,
"split_commitment": null
}
],
"split_commitment_root": null,
"script_version": 0,
"script_key": "02a0afeb165f0ec36880b68e0baabd9ad9c62fd1a69aa998bc30e9a346202e078f",
"group_key": null
},
"error": "asset: max issuance units exceeded",
"comment": "invalid asset value \u003e max uint32"
}
]
}
2 changes: 1 addition & 1 deletion proof/testdata/ownership-proof.hex
Original file line number Diff line number Diff line change
@@ -1 +1 @@
54415050000400000000012461ee8f0acfbfd5f6493a5caeb2d8394560384b2b0ec17d4e0990406cc6e3e75b0000000102500000402092a9256d4b85021548627deb2c43f36af17910072d708295b778f5ad3f5bb87dd83ea06c46664e4315f7de9ee8d3506c71d82b40200cfc2541e68b86c4146a67c21c1b65ffff7f200600000003fd018c0200000000010204478bc55a872d647f4b68a48f6d8d2240d18d03b9370a7f38743f4827cd99430000000000ffffffff61ee8f0acfbfd5f6493a5caeb2d8394560384b2b0ec17d4e0990406cc6e3e75b01000000000000000003e80300000000000022512028fb02ae7f93d46f673d798597985b2e26f3caeba668be40eb215e7e3b4583c7e80300000000000022512032b6eeb107b144586f1134b285d9f0dcb432233188851829e131aa4478264ef325d0f50500000000225120dec8af6483c79fa7ea39bea834b92024acfe5c1b6ccdd66889048c2ca0a206600247304402207820ce964c23db5429446ea89d666b45d1b3c4fae30aec6894d34214ad0ed089022044d895c3ba0b30babbe5f927795ab186e5384bb25596e2623caeb94029ee64af0121032cbea8ed49a89ee5c69276349fb9fa4a98613ced896cacc4019c02c97314e77001403aff7a5dc8b6501541c36ad1500c5dd84f59f0c3d732e0417fe7bc28371704a5981b0a8bdfd57c592efaad8e0913b29b43ff97a0822d7ee6b65d3d4fdb1d3f9f000000000422015aa0323cf2cc7f34962f63618b77c9bb44c26c7d69fb7b25c9774e36b914f1890005fd02b40001000159000edf94014e8e647be631dc8f23a1a2ac762babf95e2110a04cc6d11c9039ff000000000f66697273742d69746573746275787872055b46e57b82693def0aab2e58eb4b78068238f3d62b84d89050b92fc2a6e500000000000201000303fd025806fd022301fd021f0065000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002fd01b44a0001e829447f0dd17d26880ba56a672caa4de2859a5d732799f39077d1ee48595d7b0000000000000258ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdffd01660001000159000edf94014e8e647be631dc8f23a1a2ac762babf95e2110a04cc6d11c9039ff000000000f66697273742d69746573746275787872055b46e57b82693def0aab2e58eb4b78068238f3d62b84d89050b92fc2a6e500000000000201000303fd025806ad01ab006561ee8f0acfbfd5f6493a5caeb2d8394560384b2b0ec17d4e0990406cc6e3e75b00000001e304927a992c3a1d81f50586301c1f055f59923c236b93704097d02fdcd9eb7802aee2739664013a7de5c6c324f520af0dda900e53fdde79d97caae61f53b8634b01420140cc16977679b45a50b64c03b363b95ca3389590495ca742018735f531d5ce3fe6b88dfda0637e06e6eba86e1cdd9f2df4e4cb96ff7c36effd1b50a73334674bd607281c6319db0fc74f357d435e6efd4406c54d6473fa35face18d29b47ecdf63a8c200000000000004b00802000009210294aab4886e65569873d588d86dbbb9a8785576b251261170a378a30b2af2d3d208020000092102995eefdf358055573f2f70106f09c208f86277e7ab7038edfe52a7e2003c06d9069f000400000001012103cdff2a9995874b27e2dc83ae878b7753c925d996339455982250d6c237adecba027400490001000120e304927a992c3a1d81f50586301c1f055f59923c236b93704097d02fdcd9eb7802220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff012700010001220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff07f802c700040000000001210260037c3b9987798745c48397b4651f2ee73d433f27aa3bb33f96744a6607b9f5029c00710001000120e304927a992c3a1d81f50586301c1f055f59923c236b93704097d02fdcd9eb78024a000111bf752ef14c9095b14932062e5be15755cecbcd8cd5f26473dbb0a6db1373630000000000000258ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f012700010001220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2e000400000002012102ff7b62b0ada51df1a354f19f71dd957868b062515f7c1ae5f6e63a4643f4170b0303020101089f00040000000001210260037c3b9987798745c48397b4651f2ee73d433f27aa3bb33f96744a6607b9f5027400490001000120e304927a992c3a1d81f50586301c1f055f59923c236b93704097d02fdcd9eb7802220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff012700010001220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0b4201400e5788a6d685c0aae69e29cc1178411d42e86d846505eec1b945c4ad24b88707b1fff685d69243efb70054852398881a4775c3fcd6178cda5347a60e84d3bd760c04000001bc
544150500004000000000124eaf8ec89bad03b90a79a24ee1cc4c3af55c20912fffc2dec07ee71e991fab9d800000001025000004020d22d1789eb232f3cc213218f39f557461f453f1db7e0bd09df9374de793acf15088d6d4b7ddc614cae647dfaee3a5f615a080f86b6f64ca3bee2bcd28ad7fa150ca41c65ffff7f200100000003fd018d020000000001020036456dfd7eb807df0c46f96b1e587c32381069e67a55bec3f23b50e716f52f0000000000ffffffffeaf8ec89bad03b90a79a24ee1cc4c3af55c20912fffc2dec07ee71e991fab9d801000000000000000003e8030000000000002251203f63e658b1c01cc2384da523096d9438bf5a5e4d8267a17fbe8c328a213f2fb9e803000000000000225120881cca40c419fbda018f184c5243cb82f6e9583c2e7db208d851ddae32e07b2125d0f505000000002251203149d1bb57e3b1dc82c27919e5c001a1abedc51e05ad4eb78b543ad95c6ee51002483045022100e30857517723a9ab15f4da5316ab4eecacd84bf4923175a1b742b853687d3fd3022039a636f100f906643af8743c6eb63302833505ae7290bdfecb5b53a019a7aed5012103e5fea4849b0b71d6b3d13a74a9337418ad94068b6ef7a8644fa18e1aa19d82540140b44fa77fd1af1f8e6c649b5cf04906e398ccc7299e70df5098d8f864a52a250362cbdbcc20a50f7a020aab1189cfd24cd9d9651ecf69129e977d43df17717f9000000000042201915cd53ecb227938a380dcdf4e16895f134adc2c0ffeed6a5c44aaf15e09e1180005fd02b400010001590333326f7cf17486ae7e620e9518aa10fd41fb2a4a3422ed6867b8e620022365000000000f66697273742d69746573746275787872055b46e57b82693def0aab2e58eb4b78068238f3d62b84d89050b92fc2a6e500000000000201000303fd025806fd022301fd021f0065000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002fd01b44a0001f1566bdb1ff5bd1c8d0420e58aa60f2f9d405c9a6b3274af88204833deb306360000000000000258fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fd016600010001590333326f7cf17486ae7e620e9518aa10fd41fb2a4a3422ed6867b8e620022365000000000f66697273742d69746573746275787872055b46e57b82693def0aab2e58eb4b78068238f3d62b84d89050b92fc2a6e500000000000201000303fd025806ad01ab0065eaf8ec89bad03b90a79a24ee1cc4c3af55c20912fffc2dec07ee71e991fab9d80000000129eece06a3a13a9118214778147d90df8eaa14c5cfb479576ae71b7e909ab79a02ac50b7d6174a181b30913b334f97b1455a27d3ef3007f25a87213f28aad8eca001420140bc47393295de6e214373cef938808503f4c6c10c8f593f0e56bd37e0edb15c0b525aba647203d2655937bd691279c19edf63477b87f84c25e1ddf7b9dff3b2100728f38e0f2e69a6d8cfed71c52d240e190a0565995ab0f9d7fd55523c7c0f06d71d00000000000004b008020000092102c25abafacdb5af8d9ac927b5d653d2711a18782b4a51ea6838caab7dd5e139da0802000009210215cbb4fbf456cb4a8679e00056a9cda7be7c3831448ae59e8bc67e2c7aca6e0c069f0004000000010121028dcb3b0975c3d69da111bd24a3d0d9f2b0ed3e4740a4918c2fdf33a19653e89b02740049000100012029eece06a3a13a9118214778147d90df8eaa14c5cfb479576ae71b7e909ab79a02220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff012700010001220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff07f802c7000400000000012102876dc937d95d4acf842fc35bdeeb442abaad71866bd720f63401525e7d6a3b74029c0071000100012029eece06a3a13a9118214778147d90df8eaa14c5cfb479576ae71b7e909ab79a024a000135f721afee6153460d91ac6b2b63069ed03989cff42b9c400e228e25857630810000000000000258ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f012700010001220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2e0004000000020121029ffcb5f37d8c83fa6d7210e8a84e66bad801b70ee00a89d5882255b15caa884e0303020101089f000400000000012102876dc937d95d4acf842fc35bdeeb442abaad71866bd720f63401525e7d6a3b7402740049000100012029eece06a3a13a9118214778147d90df8eaa14c5cfb479576ae71b7e909ab79a02220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff012700010001220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0b420140b695494e4e731311d1182bb1adbb3a22480674d2926957c26f4d5738e7d94d9c2ebb81cccb4903c8d518042ffb727dddd3135e211d0758e5b3cc4cd702688b4e0c04000001bc
Loading