From bf4dc8f7593571e8c47d975467b3ee12d983e38b Mon Sep 17 00:00:00 2001 From: mangoplane Date: Sat, 21 Sep 2024 20:04:12 +1000 Subject: [PATCH 1/9] fix: resolve loop variable 'i' captured by func literal in closure --- config/config_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/config/config_test.go b/config/config_test.go index 936622b06c..eac0df51d2 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -773,6 +773,7 @@ func TestLocal_ValidateP2PHybridConfig(t *testing.T) { for i, test := range tests { test := test + i := i t.Run(fmt.Sprintf("test=%d", i), func(t *testing.T) { t.Parallel() From 7b6b6c2c4c6302b6f13fd10e8150afe7e728f924 Mon Sep 17 00:00:00 2001 From: mangoplane Date: Sun, 22 Sep 2024 15:30:48 +1000 Subject: [PATCH 2/9] AVM: Adding bmodexp --- data/transactions/logic/assembler_test.go | 12 ++++- data/transactions/logic/doc.go | 5 +- data/transactions/logic/eval.go | 20 ++++++++ data/transactions/logic/eval_test.go | 50 ++++++++++++++++++++ data/transactions/logic/opcodes.go | 1 + data/transactions/logic/teal.tmLanguage.json | 2 +- 6 files changed, 85 insertions(+), 5 deletions(-) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 10d86f476a..6e0dce6484 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -439,6 +439,13 @@ dup; dup falcon_verify ` +const bmodexpNonsense = ` +pushbytes 0x0123456789abcd +pushbytes 0x0123456789abcd +pushbytes 0x0123456789abcd +bmodexp +` + const v8Nonsense = v7Nonsense + switchNonsense + frameNonsense + matchNonsense + boxNonsense const v9Nonsense = v8Nonsense @@ -450,7 +457,7 @@ const spliceNonsence = ` const v10Nonsense = v9Nonsense + pairingNonsense + spliceNonsence -const v11Nonsense = v10Nonsense + incentiveNonsense + stateProofNonsense +const v11Nonsense = v10Nonsense + incentiveNonsense + stateProofNonsense + bmodexpNonsense const v6Compiled = "2004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a" @@ -475,8 +482,9 @@ const v10Compiled = v9Compiled + pairingCompiled + spliceCompiled const incentiveCompiled = "757401" const stateProofCompiled = "80070123456789abcd86494985" +const bmodexpCompiled = "80070123456789abcd80070123456789abcd80070123456789abcde6" -const V11Compiled = v10Compiled + incentiveCompiled + stateProofCompiled +const V11Compiled = v10Compiled + incentiveCompiled + stateProofCompiled + bmodexpCompiled var nonsense = map[uint64]string{ 1: v1Nonsense, diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 3bab156561..3dfb2f0d7b 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -273,7 +273,8 @@ var opDescByName = map[string]OpDesc{ "b^": {"A bitwise-xor B. A and B are zero-left extended to the greater of their lengths", "", nil}, "b~": {"A with all bits inverted", "", nil}, - "bsqrt": {"The largest integer I such that I^2 <= A. A and I are interpreted as big-endian unsigned integers", "", nil}, + "bsqrt": {"The largest integer I such that I^2 <= A. A and I are interpreted as big-endian unsigned integers", "", nil}, + "bmodexp": {"A raised to the Bth power modulo C. A, B and C are interpreted as big-endian unsigned integers limited to 4096 bytes. Fail if C is zero.", "", nil}, "log": {"write A to log state of the current application", "`log` fails if called more than MaxLogCalls times in a program, or if the sum of logged bytes exceeds 1024 bytes.", nil}, "itxn_begin": {"begin preparation of a new inner transaction in a new transaction group", "`itxn_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the invoking transaction, and all other fields to zero or empty values.", nil}, @@ -352,7 +353,7 @@ func OpDocExtra(opName string) string { var OpGroups = map[string][]string{ "Arithmetic": {"+", "-", "/", "*", "<", ">", "<=", ">=", "&&", "||", "shl", "shr", "sqrt", "bitlen", "exp", "==", "!=", "!", "itob", "btoi", "%", "|", "&", "^", "~", "mulw", "addw", "divw", "divmodw", "expw"}, "Byte Array Manipulation": {"getbit", "setbit", "getbyte", "setbyte", "concat", "len", "substring", "substring3", "extract", "extract3", "extract_uint16", "extract_uint32", "extract_uint64", "replace2", "replace3", "base64_decode", "json_ref"}, - "Byte Array Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%", "bsqrt"}, + "Byte Array Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%", "bsqrt", "bmodexp"}, "Byte Array Logic": {"b|", "b&", "b^", "b~"}, "Cryptography": {"sha256", "keccak256", "sha512_256", "sha3_256", "sumhash512", "falcon_verify", "ed25519verify", "ed25519verify_bare", "ecdsa_verify", "ecdsa_pk_recover", "ecdsa_pk_decompress", "vrf_verify", "ec_add", "ec_scalar_mul", "ec_pairing_check", "ec_multi_scalar_mul", "ec_subgroup_check", "ec_map_to"}, "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "pushints", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "pushbytess", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "args", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gloadss", "gaid", "gaids"}, diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 4da436a1b6..9ee5a50a2f 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -2325,6 +2325,26 @@ func opBytesNeq(cx *EvalContext) error { return opNot(cx) } +func opBytesModExp(cx *EvalContext) error { + result := new(big.Int) + last := len(cx.Stack) - 1 // z + prev := last - 1 // y + pprev := last - 2 // x + + z := new(big.Int).SetBytes(cx.Stack[last].Bytes) + y := new(big.Int).SetBytes(cx.Stack[prev].Bytes) + x := new(big.Int).SetBytes(cx.Stack[pprev].Bytes) + if z.BitLen() == 0 { + return errors.New("modulo by zero") + } + + result.Exp(x, y, z) + + cx.Stack[pprev].Bytes = result.Bytes() + cx.Stack = cx.Stack[:prev] + return nil +} + func opBytesModulo(cx *EvalContext) error { result := new(big.Int) var inner error diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index c8f7a8bc5f..e0e41a37e4 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -5065,6 +5065,56 @@ func TestBitLen(t *testing.T) { } +func TestBytesModExp(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + type TestOutcome int + const ( + Accept TestOutcome = iota + Reject + Panic + ) + type ModexpTestVector struct { + Base string + Exponent string + Modulus string + Result string + TestOutcome TestOutcome + } + modexpTestVectors := []ModexpTestVector{ + {"0x01", "0x01", "0x", "0x00", Panic}, // Modulo of 0 should panic + {"0x01", "0x01", "0x0000", "0x00", Panic}, // Modulo of 0 should panic + {"0x00", "0x00", "0x01", "0x", Accept}, + {"0xfecd94e51b7227573693217cffee2069b16e86509aaeb141e3918b9522b48dfdb8cf6f842a5b25602d3a509addb561ddbee848b4a0d68473f142e0b895c99a7e5c1dd2a9d3aac4e65e87b61a7cb4689afeaf1a28fa642bd35a56e6fc123b60c1fd34c33fce472dc5a6bbcaa136a89af1df0b4c85aa99cf34f11b0d605709778f876295eee07828000ff37fa045b279fa12e0b0e99e525705acca77b54d4210fdcde48374152e97753fb9aae79d48e82abfb9e9afc102e78fe3d3b21218982ca78d044291b8d8ae715ead3b7690de00f67a91ec0eb85d4c0c2736215f14678a8ea239f8425151f7e31bb39000a6", "0xd27369499b4e38c037632ee1f1a963f1f4d9275f8a514a335ed46f64061dd354e7438f233a77ba2d1d372158c17078409e72699e909452a663497145d7f4d65932e8a49a9add52783531e5352d17085028ed0a7551439e4e4bf7c287ba0b9176532f300c2710d86d3380f64fced370fd61c8f3b52c4136aff1b8f7190ea220ee50d25182b37990e09f765c2aa65e4bb8930fc7d1e3d8466d988610a5ad1b7067851b95d7c88d5a57a2e710860193580c015fe067627dcbe157887c29b9709ed8e04dee40521fbf6f7472ef0e38cef45ea6b1b0dc3f9d8a0ca11376ac9f92512596d82ce1000e4ad0551f807060afc2", "0x27a889b93153f4a045dc7583c169fe4a86fa1909ccc1a4183fe577be60905c76e3b2990b525ab40afc36dd9009b6e5b89c7789f09f72ee36bf011501b797a7520f0f35b636f5f1457908e12746a13243b27e02814570d46ceb35bdb8bf5dbd45b1854f859ef5d1c6fc7c21450ead9002fb99e05879d7531463fb565bbe9fc0c336d11e686f2d7cd9f96423243bad8907a955966df2", "0x1159c95dfd2d6010f7df10078b3bca2681d8bc892596462d1bed9beb64b2d349c6b2422d12cd5c759c517e9d6861b3cd41a301b671c09c06fc8c463f958a82695f451b5b348361e8016105453d79b63cd6df2d0ab9dc37228dc71effeb7036b7daa971f94507b53d673df31ccad70295f26a91b648c962db1c278f97f7c6848c9b6eb14a66c350d86a541aafae7f22b1d64876369e", Accept}, + {"0xb83ede301f43216b751761b94103ca4c01352ddc0eec69abf69ecf70fdd9f7aacf9dda9518eba3862d138bd2e7336b0a8621e83b22e25b5347507336b061e38d48c083722b523c33509c1b9a0d30a3da8ca7f5b2fb8d0095ca48a7d4c5c9a34f222b43eaa3c66cd18c37b0ea4016356ebfb3a888db8a3c90d03ebab84d1b43a57d34cf781a78df7ae3f87102a12bb6412a42a642d649f1a349235b080322e585745c6ebaffd49919f1bed598414e8ea6edb191e17868e4d2353337509fd29c7053f67e27b261e478f37b5d59e4f667f4d51bb354ffd9a90e94bd8ec947564033ee1d578b1fe8d7", "0x183e792802e3e670be1d570a5658672500c287625582b7743de11e8e80361613a86419e464fdf57e0c504c155e6b7febdf1a5833f39b7fa5891386f8dfaa985dad5b1bf0001ada176bf05b5129c3d16fcad6be460feaed369cbc986929f94be839a82b5341c7a791f014f5ed0fdf0f3c6a1becbe23c4e0aa4ca9495a087a37464ff46745b6e7139c0e1fc5d2ec53d48495714bb1ccbadc0dca5275fc33424f0c9c057cd41b4d094c87fba00015ec5ed12ee115c3704898c4e702bd64de7879eeccee135957125dcf1c43b5a4ce424b", "0x379f241e6a9c0aed86705cf6ecbc9bfb6533cd757ed53facc7ec1f95c9139068560a3e185ae01401e0e05251eb2b036986cb17be92d8f46622800a64ae8d0b8d2d2b649a4d6be28e2a6fb705f349bd7513a055a33c92e140ad9643e7710459be1f1b4930f46a447bc0ec0552602d76f3616b2030613976afa00b961e7fa909ce2bf77a040a7ba72277c5406883ef2946e7571e192c3c0bfe5e9f66a786a9", "0x0fe90441ccf81a0a8227155936d90dada4", Reject}, + {"0x43f4df0787ae52b4433b7a5fe0a55993cb1cf79b0e9318f85a90b7ba7c0ced2603d464113b82ea7986ac5249e2266db648ebb23529c0a1d732ffdacdcba22fc88e184d", "0x0402678ddba1793fa049e81f30b2a825d97db413573f13d9ec9dd54523e5ee202308b9c0c20b26d4949c626ad62d3768e38316b1c7fc0a1ca3f84697f350408d10318bf39d8dffad2254cd5519de8a0defb8e17aad2a5f05cb5ac0f25c0f5274653406216212ad38c733959adbfb31ca531dddc9f8e5ce0478fe30b12d422732b401c103e96140cba3ec026511f0b3d2355d079910834f0cec1847bb9d9a2d040f095b2761e4bd26e79600ae7c575b80c54893131eb26302c4107e7f83acc5754840d8588e4803fe3bcdbd3c7a26a109a22407de5809cdbd525f8fd41fac561e301f08928638", "0xdd9224855029854c02a3c1b92a62a41e8d32ef64dbfd39858413df75631ce8b442d845fe256f6bc6b842f192fa27ba627a45bbcee0b9a606f761d3d753566cf8ed2a6cc3edffb011d190a2b7f31c15923e58da6b7aaaa775fa948cac4723c164bb3c084354bcd90b1dc19d338a4074a68d4ee1e81eca0fa2b1eeaee0254aa307a3adeabe06550996b59a4d3572021e3a5cb670d113030b155eed5be224d0800296d0b4181066dbdb36ceabb178b29527b2ecd5fc8443801e2e13422a4c9004013be315c6fbfe48e3d1ac858886426381aef76d8e0fd3a2ca9b1147f2da2cfef3988cec0d227666fe8b5919a67ed8", "0x0e2611da26bfb2a255a28659b32c4ad4031c9a519f0008280c315ebe0981428c717f70e293956abf2313286742bfa569e70ba883e337c33d0ba54f1eed68499f9be4e174451c22d940e4e50dd0c26578b1da11a3f174ba77f85988839c80c916de370274e30864de05ddb805713d2cad6d7c908db8ceec7e7e280eb65dd9e6f74bd031b1d6db72475697af7c8851a22b003c23b6139818576e11c8dbe667792e54cba5acce53b18747a09d43696042d3024bf04d23c3b2264527bd26d4163402389687dcbdacb1b75dfb1ed156b2faf5bb4f3284298b558ed4dd5ba754df0b7a5c3bdbf8f4619487ace3992e5921", Accept}, + {"0x43e3f4a2a30309f52331c8b23e9f2c2c5bfb5c3bef8b69a4b58ddd185b8b63b1f23b21ee7a0e85893433facfc135c7e21f39e06a4ad71bc26268acb480907a883685963bbc4c4ec52ab52c6ae681cd61245c3d8bd8f5bba2b5e4e123357dba12235c61ce1549f5c60f400afa453f5db65bf72b1d5d20b254b0c306a86a9e68ac2f0e226e", "0x4504b93f905be9f35e38e507d0fd5f666ecd212fde7579f23a47cc9910b54f9b420c2fcd54e8fbec2bb24ccd050fdc0300d3", "0x381ddee4f5f83bc09da93ede2d13022628f443f4aef73ef740ea50e6aaf377f67c3c21c1d858c46978076a5926e9d803999f911f2af2bcaa82ae02f0b3c48f4467ed545763d18c9b157376cc7a6aac2b0669abbc6894b5784f01bb786545933ad8bfd975a041ff03cc81b60677b7afc0aa059c4c47a41af3f30e33fe08743686cbc15049e26e13cfbf719515229281c59ee99b7a9a6aaa110df9a2d16b58394f704d4982ceed8e441a62d809c0a842f16bc85e8a9bb6ff3bf027cc0e73cca9ff285bc74e08d857629f9b5c0d051dcf88221ac9687245e7ee7e6d877072fde59222ff", "0x9312bfc6064fcbe01c7bf1ac1bba80a2b0607faaac2f0f102270129abf5a56dc50b3145c79aa5babda0dc85d7205f742da145d65764147fe9ca027d0e15affc9963ec7abaac1a025654213d91a22a11055f00621dd3c1aa5b7180e70de3a68ac7d3dde0088adf0006fe80950ee8d3c3e8bad87d68f6d9e45af1729ea2ffcd81b117f48be33e1fa1254a1e49bf67cf6def9df3d7030f88ae0d0cda5c999bcdb2c2f631508abc5a6ce3080c27a274d5b7d7d", Reject}, + {"0x2e6bebf6e587185ce2b74db841a5f7d681aedd53b7d0fa76a1e9dbeb3dcd60f021affa201c192257d89642dbf6afffd1a272c0f7b4e30b88", "0x4396069f7dd294400e725602c6c8ad4f3fe0301578c62537138c3bf98211c096a86a1448b2e57b0a005dec2eb631d8eb2aa9974ba3c3343a5170cac4094296937606fd318439f7f40dcb2c6b5a4f6059829e87b39eae61a40cc27ae22466f2c7b5cf78d7a2299aed6df7c5e69c80f450f979bfae39a5ed82343a02cc61e877079ee26b42b9afc01d1f60627b4bfbfe12a5", "0x676326af0980eeee7f6deeb6b77609df8bc847aa6e20c1f15bb070d8650c9427a4862b1e08fbed2f88ff18e1085c152ef80534b9dfd56a7c488c7068103e76ae730715997214d3c528515d572296941cf89250010b3bef7fbf175e55faeaf36f5746814ef94da60bdedfc8b8fd02434cd5122f56c5c36567a6a627e7cda8a36e90756e29cf8091465d140e5037bce8abc4f59b9160f580affb0845095071764754449fb485dd5ba1af", "0x2356ca087f69fe3fd129686b9406f448eb71bf04576cfb19e01731f541fb487409a7ac705b32de77511dc7952aa26980661cfc77d08a69e74eedeea60befe46af667b8c95b946c7535b3ac0c6210944a1f3804a7cf16c033adad01e6cddf29eee99db6245f3408db3dc77d32d83666535c60bbdcf79a251c5690fbb93830ec942607182fe0dd45739b9587fad72c98a8462f1fde205253b08b857d91a947192310df13a592f1417b78", Accept}, + {"0xefe0ed52aa48ac1954d289cddc48fe2fa9d2fd0a30ec80558d120df81dfd1bd5bdafc6db6c1be75408c8913667966a67eeae3d4660c51365cd126acbcff0896a242f998d6cdd12effe1519826eb869762835d33e30ecfa9fa1c3c26b8481d106e301cb9d7d53d5499fe7b38107e222de26cb2a5dd2cdba60dca3f8cfc35e512b5f4ba6429be53041a8fbcea34546b3562f20668d414d8025448b46fdcf2b6665ac6eab707642b5", "0x815178f225ffa7735b81058aeb7618db87d8e027b56d76dba76acfca305df3bf1039e86a2485d525894528", "0xe8cb702599322c16a07935ebaa175e1da2c090807209942f43df5c6db4732f08235da740218ce9ab2b654e53d8f102a0a4b0aca861270ca6788aebcd223d2ac3640ccdaa43aac14970751a6eafed7180a51a4d6de93b3e5e7e74f8a55bef5caf5a096e11354cae423d770fe1bc6682752b639e7510d7a2b77d44bf104c3333ce029d4f7e49255fd50e6ee2ba1002fc7c2c17b9f17053dc11135bc5b5e5f6e7cc075b3d0e658affc64b24819a3a8458a4467faa76112ffb6c061a96a199f2ef4f4e9a2b91687c7be85f009029c60495", "0x5437f7393e93182cc931add5c2ab61373c0a83a9c9713b91c0d880325cb6153a369feb6c2755c68f18444e1e67f0b16bcfba717c3b61833ae1c4a28b64b6860aaaf9fdebede0ee2e6aa88b18ac1e39ab5307678669de5d1046e800585adca7c560f923eb3511afccb3eb52c507a9e96a266aac75439b289a0756415f50658d07ff5ef236f3ffc6e5a57f4b5e27785737fbb77f4e0c780de0dbdafe2de14ec61344346ecd6f2db6cf6f0b12c95d7103f68fb837d022504a04f69a", Reject}, + {"0x5aaf78fa0137ed7a80a760cb1f00fe76f8dba1048bf35db85ed43d7081fff9c7fd61e71fe7a42ef861c081786d0b92053263a92d0491403f31bcddac325b7012d1078c19c17a16a22b57eed143f6d1aa3c7cb823479f8314a87db3142aaf50a8ec1eb8afec5c09bc5c1f97b5f617681ff9cafaf6b5bd6f186892e2016f58", "0xda431fbf1062b9ef1be8812d7d6870257f11aba4b9f27ce4f3db13200111a2e6862219042331d8e31c2438f9f6bc0dd77708200480d5c26632b9bac060daf4a4f74fd966fab0fcaea1853b3478eb6c3088b732a5ac20c0c13d5a3ae65fe23b", "0x57fb8a5ff255e23da812337e4145d4eaa11529de3894741886ae99a839bed9a10ae06719a0a820a34295a62e2534eaf0431b6be1b5187384dff144e828932622af0624976cb4b9660833385346cbc2d55379d0c72eda68a10202be3e8c92cc8d151864d2d465abc9ec6759f4f2e341cd4dcf4a4fac1ce4a480e065eaf859dff8799646bf9638535979cc8b616215b9c737d59bc0146520d415511aaef942a923afa58435d9d5", "0x05a08475905993424f0ce051bba9213855c74d601ed82089ec8f804b5e29275ec2b6b53ea813c1931a248418500a1a73e6c94a7a9f72df4cfd863218a928c999e8bb65106c606bc40e068b732e312b63b6ce5f6370402f9f576fe33d981ed9f06bcbc653aea90e3a2e7359560001e787119f8ee1d90695753582e52e077bd848c8c6eec421a2b79531fc15798c044ead4a81bc40b98964c9b21ff49c0a5da2d20752ca63a0d8", Accept}, + {"0x7afd6101ca03eee6db2ee5bd17c4df4645d3099c5b69d01efddd9656f14a5085534f1bbab0e9213e827715c5bd4abf8f9a7a1052cf1afb2b6332ec74c4b18e6a6ef976bfb70c2d", "0x33fe913d149938164862fe788a7dfaa901690376e3e193523fc92afc6e8e1ef4fb40ed01273231942a2b5b4d7d59b5e65b3397fa61c7453b1559b177b1b160fff8cf48867097e887c4be19a6c33ba156cae132090b4f8b7e54a501431a04f066b003ca2868e4cef065a1d1f5903ee581ac3c97003d71160fe3e643a42126c08e17c6b1675c8407bc6f3ad4ccb858535cbe132c8b48e9fbedcf2e222cec319a95a3c4f75df10463b11bd97e810c623c92ab8efe5d31f0d89ad85ac8", "0x757c4d7f4d108027363a6181eb0d950bc515afe7d7dd103cfadce2295030430840a45b4e541c4966e9f991ee219b26ace999b7a0d07d9e10414644daf591b09a04696705cc2555ec7c05dfc9e51d349ae25a96106f99a620d4962edae0b940c62276c6", "0x65fbf3319e294a7109a1f607c3379360f78df96c940b7a244c725a23cc632f4dd4c6283bddbe8dfb46c69de79dc337aea892708c5de1a27c6a422f41fce6ab5b19100de32081c2a066e51cacfbf5d8c76bed6a502ada2ff044619e3f5d196d401ba3a45cc8ebc8767750239ecf326b3eb658ef970baeefa40fd1e1dbcbdd7d6a7838d6a20595765021d5e3f8fe6a96c4a346f8a26990317de9c57a6a309a132a6b6dfad21c1d7f707e8607e497b7cca31b9d5bf804af4d46", Reject}, + } + + for _, modexpTestVector := range modexpTestVectors { + progText := fmt.Sprintf(`byte %s +byte %s +byte %s +bmodexp + +byte %s +==`, modexpTestVector.Base, modexpTestVector.Exponent, modexpTestVector.Modulus, modexpTestVector.Result) + switch modexpTestVector.TestOutcome { + case Accept: + testAccepts(t, progText, 11) + case Reject: + testRejects(t, progText, 11) + case Panic: + testPanics(t, progText, 11) + } + } + +} + func TestBytesMath(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index f3f8bfe37d..5f9639e048 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -798,6 +798,7 @@ var OpSpecs = []OpSpec{ costByField("g", &EcGroups, []int{ BN254g1: 630, BN254g2: 3_300, BLS12_381g1: 1_950, BLS12_381g2: 8_150})}, + {0xe6, "bmodexp", opBytesModExp, proto("bbb:b"), 11, costly(2000)}, // TODO: refine cost model for bmodexp } // OpcodesByVersion returns list of opcodes available in a specific version of TEAL diff --git a/data/transactions/logic/teal.tmLanguage.json b/data/transactions/logic/teal.tmLanguage.json index 53984e8dd2..118a250a76 100644 --- a/data/transactions/logic/teal.tmLanguage.json +++ b/data/transactions/logic/teal.tmLanguage.json @@ -76,7 +76,7 @@ }, { "name": "keyword.operator.teal", - "match": "^(\\!|\\!\\=|%|\u0026|\u0026\u0026|\\*|\\+|\\-|/|\\\u003c|\\\u003c\\=|\\=\\=|\\\u003e|\\\u003e\\=|\\^|addw|bitlen|btoi|divmodw|divw|exp|expw|itob|mulw|shl|shr|sqrt|\\||\\|\\||\\~|b\\!\\=|b%|b\\*|b\\+|b\\-|b/|b\\\u003c|b\\\u003c\\=|b\\=\\=|b\\\u003e|b\\\u003e\\=|bsqrt|b\u0026|b\\^|b\\||b\\~|base64_decode|concat|extract|extract3|extract_uint16|extract_uint32|extract_uint64|getbit|getbyte|json_ref|len|replace2|replace3|setbit|setbyte|substring|substring3|ec_add|ec_map_to|ec_multi_scalar_mul|ec_pairing_check|ec_scalar_mul|ec_subgroup_check|ecdsa_pk_decompress|ecdsa_pk_recover|ecdsa_verify|ed25519verify|ed25519verify_bare|falcon_verify|keccak256|sha256|sha3_256|sha512_256|sumhash512|vrf_verify|gitxn|gitxna|gitxnas|itxn|itxn_begin|itxn_field|itxn_next|itxn_submit|itxna|itxnas)\\b" + "match": "^(\\!|\\!\\=|%|\u0026|\u0026\u0026|\\*|\\+|\\-|/|\\\u003c|\\\u003c\\=|\\=\\=|\\\u003e|\\\u003e\\=|\\^|addw|bitlen|btoi|divmodw|divw|exp|expw|itob|mulw|shl|shr|sqrt|\\||\\|\\||\\~|b\\!\\=|b%|bmodexp|b\\*|b\\+|b\\-|b/|b\\\u003c|b\\\u003c\\=|b\\=\\=|b\\\u003e|b\\\u003e\\=|bsqrt|b\u0026|b\\^|b\\||b\\~|base64_decode|concat|extract|extract3|extract_uint16|extract_uint32|extract_uint64|getbit|getbyte|json_ref|len|replace2|replace3|setbit|setbyte|substring|substring3|ec_add|ec_map_to|ec_multi_scalar_mul|ec_pairing_check|ec_scalar_mul|ec_subgroup_check|ecdsa_pk_decompress|ecdsa_pk_recover|ecdsa_verify|ed25519verify|ed25519verify_bare|falcon_verify|keccak256|sha256|sha3_256|sha512_256|sumhash512|vrf_verify|gitxn|gitxna|gitxnas|itxn|itxn_begin|itxn_field|itxn_next|itxn_submit|itxna|itxnas)\\b" } ] }, From a72572d45bebd8dd7684293c4bb9d80a5112efb7 Mon Sep 17 00:00:00 2001 From: mangoplane Date: Tue, 15 Oct 2024 10:13:53 +1100 Subject: [PATCH 3/9] feat: add BenchmarkBytesModExp to benchmark bmodexp --- data/transactions/logic/eval_test.go | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index e0e41a37e4..b9e0f273a8 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -17,6 +17,7 @@ package logic import ( + "crypto/rand" "encoding/base64" "encoding/binary" "encoding/hex" @@ -5065,6 +5066,57 @@ func TestBitLen(t *testing.T) { } +func BenchmarkBytesModExp(b *testing.B) { + type ModexpTestVector struct { + Base string + Exponent string + Modulus string + Name string + } + + // Function to generate a random hex string of a specified length in bytes + generateRandomHexString := func(length int) string { + bytes := make([]byte, length) + rand.Read(bytes) + return fmt.Sprintf("0x%x", bytes) + } + + // Define the accepted test vectors using nested loops + modexpTestVectors := []ModexpTestVector{} + incr := 128 + maxDim := 1024 + for baseLen := incr; baseLen <= maxDim; baseLen += incr { + for expLen := incr; expLen <= maxDim; expLen += incr { + for modLen := incr; modLen <= maxDim; modLen += incr { + modexpTestVectors = append(modexpTestVectors, ModexpTestVector{ + Name: fmt.Sprintf(`TestVector_Dim(%d,%d,%d)`, baseLen, expLen, modLen), + Base: generateRandomHexString(baseLen), + Exponent: generateRandomHexString(expLen), + Modulus: generateRandomHexString(modLen), + }) + } + } + } + b.Run("bmod_cost", func(b *testing.B) { + b.ReportAllocs() + progText := fmt.Sprintf(`byte %s; byte %s;`, generateRandomHexString(64), generateRandomHexString(64)) + " b%; pop" + benchmarkOperation(b, "", progText, "int 1") + }) + b.Run("max_bmodexp_cost", func(b *testing.B) { + b.ReportAllocs() + progText := fmt.Sprintf(`byte %s; byte %s; byte %s; bmodexp; pop`, generateRandomHexString(4096), generateRandomHexString(4096), generateRandomHexString(4096)) + benchmarkOperation(b, "", progText, "int 1") + }) + // Iterate through the test vectors and benchmark the bmodexp computation + for _, tv := range modexpTestVectors { + b.Run(tv.Name, func(b *testing.B) { + b.ReportAllocs() + progText := fmt.Sprintf(`byte %s; byte %s; byte %s; bmodexp; pop`, tv.Base, tv.Exponent, tv.Modulus) + benchmarkOperation(b, "", progText, "int 1") + }) + } +} + func TestBytesModExp(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() From f33a9373016d6d670dd16d064fb6950fd4c8e8cd Mon Sep 17 00:00:00 2001 From: mangoplane <104189015+mangoplane@users.noreply.github.com> Date: Tue, 15 Oct 2024 10:40:03 +1100 Subject: [PATCH 4/9] feat: extended linearCost to allow for arbitrary opcode cost functions, accounting for cost computation and doc generation --- data/transactions/logic/eval_test.go | 35 ++++++++++------ data/transactions/logic/opcodes.go | 62 ++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 20 deletions(-) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index b9e0f273a8..0ff17ddfd8 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -5131,20 +5131,26 @@ func TestBytesModExp(t *testing.T) { Exponent string Modulus string Result string + LogicCost int TestOutcome TestOutcome } modexpTestVectors := []ModexpTestVector{ - {"0x01", "0x01", "0x", "0x00", Panic}, // Modulo of 0 should panic - {"0x01", "0x01", "0x0000", "0x00", Panic}, // Modulo of 0 should panic - {"0x00", "0x00", "0x01", "0x", Accept}, - {"0xfecd94e51b7227573693217cffee2069b16e86509aaeb141e3918b9522b48dfdb8cf6f842a5b25602d3a509addb561ddbee848b4a0d68473f142e0b895c99a7e5c1dd2a9d3aac4e65e87b61a7cb4689afeaf1a28fa642bd35a56e6fc123b60c1fd34c33fce472dc5a6bbcaa136a89af1df0b4c85aa99cf34f11b0d605709778f876295eee07828000ff37fa045b279fa12e0b0e99e525705acca77b54d4210fdcde48374152e97753fb9aae79d48e82abfb9e9afc102e78fe3d3b21218982ca78d044291b8d8ae715ead3b7690de00f67a91ec0eb85d4c0c2736215f14678a8ea239f8425151f7e31bb39000a6", "0xd27369499b4e38c037632ee1f1a963f1f4d9275f8a514a335ed46f64061dd354e7438f233a77ba2d1d372158c17078409e72699e909452a663497145d7f4d65932e8a49a9add52783531e5352d17085028ed0a7551439e4e4bf7c287ba0b9176532f300c2710d86d3380f64fced370fd61c8f3b52c4136aff1b8f7190ea220ee50d25182b37990e09f765c2aa65e4bb8930fc7d1e3d8466d988610a5ad1b7067851b95d7c88d5a57a2e710860193580c015fe067627dcbe157887c29b9709ed8e04dee40521fbf6f7472ef0e38cef45ea6b1b0dc3f9d8a0ca11376ac9f92512596d82ce1000e4ad0551f807060afc2", "0x27a889b93153f4a045dc7583c169fe4a86fa1909ccc1a4183fe577be60905c76e3b2990b525ab40afc36dd9009b6e5b89c7789f09f72ee36bf011501b797a7520f0f35b636f5f1457908e12746a13243b27e02814570d46ceb35bdb8bf5dbd45b1854f859ef5d1c6fc7c21450ead9002fb99e05879d7531463fb565bbe9fc0c336d11e686f2d7cd9f96423243bad8907a955966df2", "0x1159c95dfd2d6010f7df10078b3bca2681d8bc892596462d1bed9beb64b2d349c6b2422d12cd5c759c517e9d6861b3cd41a301b671c09c06fc8c463f958a82695f451b5b348361e8016105453d79b63cd6df2d0ab9dc37228dc71effeb7036b7daa971f94507b53d673df31ccad70295f26a91b648c962db1c278f97f7c6848c9b6eb14a66c350d86a541aafae7f22b1d64876369e", Accept}, - {"0xb83ede301f43216b751761b94103ca4c01352ddc0eec69abf69ecf70fdd9f7aacf9dda9518eba3862d138bd2e7336b0a8621e83b22e25b5347507336b061e38d48c083722b523c33509c1b9a0d30a3da8ca7f5b2fb8d0095ca48a7d4c5c9a34f222b43eaa3c66cd18c37b0ea4016356ebfb3a888db8a3c90d03ebab84d1b43a57d34cf781a78df7ae3f87102a12bb6412a42a642d649f1a349235b080322e585745c6ebaffd49919f1bed598414e8ea6edb191e17868e4d2353337509fd29c7053f67e27b261e478f37b5d59e4f667f4d51bb354ffd9a90e94bd8ec947564033ee1d578b1fe8d7", "0x183e792802e3e670be1d570a5658672500c287625582b7743de11e8e80361613a86419e464fdf57e0c504c155e6b7febdf1a5833f39b7fa5891386f8dfaa985dad5b1bf0001ada176bf05b5129c3d16fcad6be460feaed369cbc986929f94be839a82b5341c7a791f014f5ed0fdf0f3c6a1becbe23c4e0aa4ca9495a087a37464ff46745b6e7139c0e1fc5d2ec53d48495714bb1ccbadc0dca5275fc33424f0c9c057cd41b4d094c87fba00015ec5ed12ee115c3704898c4e702bd64de7879eeccee135957125dcf1c43b5a4ce424b", "0x379f241e6a9c0aed86705cf6ecbc9bfb6533cd757ed53facc7ec1f95c9139068560a3e185ae01401e0e05251eb2b036986cb17be92d8f46622800a64ae8d0b8d2d2b649a4d6be28e2a6fb705f349bd7513a055a33c92e140ad9643e7710459be1f1b4930f46a447bc0ec0552602d76f3616b2030613976afa00b961e7fa909ce2bf77a040a7ba72277c5406883ef2946e7571e192c3c0bfe5e9f66a786a9", "0x0fe90441ccf81a0a8227155936d90dada4", Reject}, - {"0x43f4df0787ae52b4433b7a5fe0a55993cb1cf79b0e9318f85a90b7ba7c0ced2603d464113b82ea7986ac5249e2266db648ebb23529c0a1d732ffdacdcba22fc88e184d", "0x0402678ddba1793fa049e81f30b2a825d97db413573f13d9ec9dd54523e5ee202308b9c0c20b26d4949c626ad62d3768e38316b1c7fc0a1ca3f84697f350408d10318bf39d8dffad2254cd5519de8a0defb8e17aad2a5f05cb5ac0f25c0f5274653406216212ad38c733959adbfb31ca531dddc9f8e5ce0478fe30b12d422732b401c103e96140cba3ec026511f0b3d2355d079910834f0cec1847bb9d9a2d040f095b2761e4bd26e79600ae7c575b80c54893131eb26302c4107e7f83acc5754840d8588e4803fe3bcdbd3c7a26a109a22407de5809cdbd525f8fd41fac561e301f08928638", "0xdd9224855029854c02a3c1b92a62a41e8d32ef64dbfd39858413df75631ce8b442d845fe256f6bc6b842f192fa27ba627a45bbcee0b9a606f761d3d753566cf8ed2a6cc3edffb011d190a2b7f31c15923e58da6b7aaaa775fa948cac4723c164bb3c084354bcd90b1dc19d338a4074a68d4ee1e81eca0fa2b1eeaee0254aa307a3adeabe06550996b59a4d3572021e3a5cb670d113030b155eed5be224d0800296d0b4181066dbdb36ceabb178b29527b2ecd5fc8443801e2e13422a4c9004013be315c6fbfe48e3d1ac858886426381aef76d8e0fd3a2ca9b1147f2da2cfef3988cec0d227666fe8b5919a67ed8", "0x0e2611da26bfb2a255a28659b32c4ad4031c9a519f0008280c315ebe0981428c717f70e293956abf2313286742bfa569e70ba883e337c33d0ba54f1eed68499f9be4e174451c22d940e4e50dd0c26578b1da11a3f174ba77f85988839c80c916de370274e30864de05ddb805713d2cad6d7c908db8ceec7e7e280eb65dd9e6f74bd031b1d6db72475697af7c8851a22b003c23b6139818576e11c8dbe667792e54cba5acce53b18747a09d43696042d3024bf04d23c3b2264527bd26d4163402389687dcbdacb1b75dfb1ed156b2faf5bb4f3284298b558ed4dd5ba754df0b7a5c3bdbf8f4619487ace3992e5921", Accept}, - {"0x43e3f4a2a30309f52331c8b23e9f2c2c5bfb5c3bef8b69a4b58ddd185b8b63b1f23b21ee7a0e85893433facfc135c7e21f39e06a4ad71bc26268acb480907a883685963bbc4c4ec52ab52c6ae681cd61245c3d8bd8f5bba2b5e4e123357dba12235c61ce1549f5c60f400afa453f5db65bf72b1d5d20b254b0c306a86a9e68ac2f0e226e", "0x4504b93f905be9f35e38e507d0fd5f666ecd212fde7579f23a47cc9910b54f9b420c2fcd54e8fbec2bb24ccd050fdc0300d3", "0x381ddee4f5f83bc09da93ede2d13022628f443f4aef73ef740ea50e6aaf377f67c3c21c1d858c46978076a5926e9d803999f911f2af2bcaa82ae02f0b3c48f4467ed545763d18c9b157376cc7a6aac2b0669abbc6894b5784f01bb786545933ad8bfd975a041ff03cc81b60677b7afc0aa059c4c47a41af3f30e33fe08743686cbc15049e26e13cfbf719515229281c59ee99b7a9a6aaa110df9a2d16b58394f704d4982ceed8e441a62d809c0a842f16bc85e8a9bb6ff3bf027cc0e73cca9ff285bc74e08d857629f9b5c0d051dcf88221ac9687245e7ee7e6d877072fde59222ff", "0x9312bfc6064fcbe01c7bf1ac1bba80a2b0607faaac2f0f102270129abf5a56dc50b3145c79aa5babda0dc85d7205f742da145d65764147fe9ca027d0e15affc9963ec7abaac1a025654213d91a22a11055f00621dd3c1aa5b7180e70de3a68ac7d3dde0088adf0006fe80950ee8d3c3e8bad87d68f6d9e45af1729ea2ffcd81b117f48be33e1fa1254a1e49bf67cf6def9df3d7030f88ae0d0cda5c999bcdb2c2f631508abc5a6ce3080c27a274d5b7d7d", Reject}, - {"0x2e6bebf6e587185ce2b74db841a5f7d681aedd53b7d0fa76a1e9dbeb3dcd60f021affa201c192257d89642dbf6afffd1a272c0f7b4e30b88", "0x4396069f7dd294400e725602c6c8ad4f3fe0301578c62537138c3bf98211c096a86a1448b2e57b0a005dec2eb631d8eb2aa9974ba3c3343a5170cac4094296937606fd318439f7f40dcb2c6b5a4f6059829e87b39eae61a40cc27ae22466f2c7b5cf78d7a2299aed6df7c5e69c80f450f979bfae39a5ed82343a02cc61e877079ee26b42b9afc01d1f60627b4bfbfe12a5", "0x676326af0980eeee7f6deeb6b77609df8bc847aa6e20c1f15bb070d8650c9427a4862b1e08fbed2f88ff18e1085c152ef80534b9dfd56a7c488c7068103e76ae730715997214d3c528515d572296941cf89250010b3bef7fbf175e55faeaf36f5746814ef94da60bdedfc8b8fd02434cd5122f56c5c36567a6a627e7cda8a36e90756e29cf8091465d140e5037bce8abc4f59b9160f580affb0845095071764754449fb485dd5ba1af", "0x2356ca087f69fe3fd129686b9406f448eb71bf04576cfb19e01731f541fb487409a7ac705b32de77511dc7952aa26980661cfc77d08a69e74eedeea60befe46af667b8c95b946c7535b3ac0c6210944a1f3804a7cf16c033adad01e6cddf29eee99db6245f3408db3dc77d32d83666535c60bbdcf79a251c5690fbb93830ec942607182fe0dd45739b9587fad72c98a8462f1fde205253b08b857d91a947192310df13a592f1417b78", Accept}, - {"0xefe0ed52aa48ac1954d289cddc48fe2fa9d2fd0a30ec80558d120df81dfd1bd5bdafc6db6c1be75408c8913667966a67eeae3d4660c51365cd126acbcff0896a242f998d6cdd12effe1519826eb869762835d33e30ecfa9fa1c3c26b8481d106e301cb9d7d53d5499fe7b38107e222de26cb2a5dd2cdba60dca3f8cfc35e512b5f4ba6429be53041a8fbcea34546b3562f20668d414d8025448b46fdcf2b6665ac6eab707642b5", "0x815178f225ffa7735b81058aeb7618db87d8e027b56d76dba76acfca305df3bf1039e86a2485d525894528", "0xe8cb702599322c16a07935ebaa175e1da2c090807209942f43df5c6db4732f08235da740218ce9ab2b654e53d8f102a0a4b0aca861270ca6788aebcd223d2ac3640ccdaa43aac14970751a6eafed7180a51a4d6de93b3e5e7e74f8a55bef5caf5a096e11354cae423d770fe1bc6682752b639e7510d7a2b77d44bf104c3333ce029d4f7e49255fd50e6ee2ba1002fc7c2c17b9f17053dc11135bc5b5e5f6e7cc075b3d0e658affc64b24819a3a8458a4467faa76112ffb6c061a96a199f2ef4f4e9a2b91687c7be85f009029c60495", "0x5437f7393e93182cc931add5c2ab61373c0a83a9c9713b91c0d880325cb6153a369feb6c2755c68f18444e1e67f0b16bcfba717c3b61833ae1c4a28b64b6860aaaf9fdebede0ee2e6aa88b18ac1e39ab5307678669de5d1046e800585adca7c560f923eb3511afccb3eb52c507a9e96a266aac75439b289a0756415f50658d07ff5ef236f3ffc6e5a57f4b5e27785737fbb77f4e0c780de0dbdafe2de14ec61344346ecd6f2db6cf6f0b12c95d7103f68fb837d022504a04f69a", Reject}, - {"0x5aaf78fa0137ed7a80a760cb1f00fe76f8dba1048bf35db85ed43d7081fff9c7fd61e71fe7a42ef861c081786d0b92053263a92d0491403f31bcddac325b7012d1078c19c17a16a22b57eed143f6d1aa3c7cb823479f8314a87db3142aaf50a8ec1eb8afec5c09bc5c1f97b5f617681ff9cafaf6b5bd6f186892e2016f58", "0xda431fbf1062b9ef1be8812d7d6870257f11aba4b9f27ce4f3db13200111a2e6862219042331d8e31c2438f9f6bc0dd77708200480d5c26632b9bac060daf4a4f74fd966fab0fcaea1853b3478eb6c3088b732a5ac20c0c13d5a3ae65fe23b", "0x57fb8a5ff255e23da812337e4145d4eaa11529de3894741886ae99a839bed9a10ae06719a0a820a34295a62e2534eaf0431b6be1b5187384dff144e828932622af0624976cb4b9660833385346cbc2d55379d0c72eda68a10202be3e8c92cc8d151864d2d465abc9ec6759f4f2e341cd4dcf4a4fac1ce4a480e065eaf859dff8799646bf9638535979cc8b616215b9c737d59bc0146520d415511aaef942a923afa58435d9d5", "0x05a08475905993424f0ce051bba9213855c74d601ed82089ec8f804b5e29275ec2b6b53ea813c1931a248418500a1a73e6c94a7a9f72df4cfd863218a928c999e8bb65106c606bc40e068b732e312b63b6ce5f6370402f9f576fe33d981ed9f06bcbc653aea90e3a2e7359560001e787119f8ee1d90695753582e52e077bd848c8c6eec421a2b79531fc15798c044ead4a81bc40b98964c9b21ff49c0a5da2d20752ca63a0d8", Accept}, - {"0x7afd6101ca03eee6db2ee5bd17c4df4645d3099c5b69d01efddd9656f14a5085534f1bbab0e9213e827715c5bd4abf8f9a7a1052cf1afb2b6332ec74c4b18e6a6ef976bfb70c2d", "0x33fe913d149938164862fe788a7dfaa901690376e3e193523fc92afc6e8e1ef4fb40ed01273231942a2b5b4d7d59b5e65b3397fa61c7453b1559b177b1b160fff8cf48867097e887c4be19a6c33ba156cae132090b4f8b7e54a501431a04f066b003ca2868e4cef065a1d1f5903ee581ac3c97003d71160fe3e643a42126c08e17c6b1675c8407bc6f3ad4ccb858535cbe132c8b48e9fbedcf2e222cec319a95a3c4f75df10463b11bd97e810c623c92ab8efe5d31f0d89ad85ac8", "0x757c4d7f4d108027363a6181eb0d950bc515afe7d7dd103cfadce2295030430840a45b4e541c4966e9f991ee219b26ace999b7a0d07d9e10414644daf591b09a04696705cc2555ec7c05dfc9e51d349ae25a96106f99a620d4962edae0b940c62276c6", "0x65fbf3319e294a7109a1f607c3379360f78df96c940b7a244c725a23cc632f4dd4c6283bddbe8dfb46c69de79dc337aea892708c5de1a27c6a422f41fce6ab5b19100de32081c2a066e51cacfbf5d8c76bed6a502ada2ff044619e3f5d196d401ba3a45cc8ebc8767750239ecf326b3eb658ef970baeefa40fd1e1dbcbdd7d6a7838d6a20595765021d5e3f8fe6a96c4a346f8a26990317de9c57a6a309a132a6b6dfad21c1d7f707e8607e497b7cca31b9d5bf804af4d46", Reject}, + {"0x01", "0x01", "0x", "0x00", 200, Panic}, // Modulo of 0 should panic + {"0x01", "0x01", "0x0000", "0x00", 200, Panic}, // Modulo of 0 should panic + {"0x54b7", "0x00", "0x01", "0x", 200, Accept}, + {"0x286e0b2a3fea08c786634bdf0a608fb22009c512e6f1f174", "0x9cebf0aae57f76408a", "0xcf5d2d1fdc2e3233adcc13c8b3fc2fb0a3d3c1032ee14288c9026968c59d6fd7f8c9ef82e63bea29304ebb91b150", "0x9e26c7578c46f09e26e67224526193f5af3512662276e54cb91944d9f80514b31fba2d4c6231c97309a79cfc09b0", 507, Accept}, + {"0xb04336dca137d1284edf958923d01c83f6a09e50bcfb1b509c2afe63bca4f64bf28a482f202cdf08e4fad627acde33c4a5206086641acf2ceab1669bf99b5d672dc71a5d2fc7ff99152f2ecb71e95543cb72be06151e3b75c12961773a0b20e59ceb18713ee7313cb3c146b10188a23de2dab3b733d2dbc4b30258e6e8cde85d1c394a76784a2038a0499feaf4851f22c48b30a7eedf02de934f8a31930d90426fd93241862614943e7a6e2e7f3ef9b08ce14030dcb8ca51d53743ac", "0x3bc794defa8e", "0xf418c1ba14622a93b40859b6fa5c8869ceeab204991a18bba8b414a03bab048c016a98c190ca7f4edb82745e8d91ce930b28c3e8c6f783ff6ea7cf4e092fe845d81189c8d77e4d6b2a3c967ed3d64a7310be13260589531e6485ddf9b065bed8142d7189fe22e213847bc0e10c5ff21e5f12c513f91357db5de6dc879f1e622dc386be6521f48cd476adb021050c09b913147ccb0c7e9ea2712f63b1c2273c4eb70267d366c8eb9548d3bcc19972dce8538767cd53d010e35a3bbab920afd498184d587f3f081fcc7018fd9ad448076a4a8ff231fc", "0x7d01fce371b80532a8ba65fc442e3adb4a5cae46d734258d342fbabaad7e83b14474fd21a5cee7e4a53f3de7e6f3c497b893f0cf23d9a743c4dfa736fb8080d54083a03b20f598ec1eed1d83714465914aa9171cdb1c3a56fb9c021e0c80f44a4d2b4b5c4e078fdc818474af5e0a334b25ac3f069d2dcc72dca335d05ac24fdbfabf07b17ce6e9fb996100509545bd9a0e5df48215112e04a68b2cc700b1a379e3a5df9d2913498cb8e15c92bec53a3c5775dd7fdfa9a5b515f738c88dc404b09cc2a4c389ee6334da58364d5c22482b905a1ec3fc", 2696, Accept}, + {"0x3e1c6d61105021cbd5388fbad1bd004929932619359415cbf63b2a5ba087a615ac387a710e19affd897f750581c815ab75c56ab0f7f43fe29ef0a2c10a582ee7cbf548a1e58d3be900f2edd57ecc3e3ce2543730a5b7241f640215", "0xa80794b876bc56a2031a0fe504ee9047dac05791fe78917dc82dc06bdeb519ce285713e9c3a97d4bbe065be9cdf6d7d845f0206bad7d23eace4856", "0x33f27976478080519dd19e89950d04b1e65f3e4bd5e684b234f68584c54415f2896440391e2b36a65bb3e3fafb10a9c6ffae6c5b8ce5223bf786fa0a7a3a6d5a54985f26936fdbc70b2b94790d712de3ed0ecf34332e805da31224f83041efa739e958634529d94f8bd4c64d39a8e3e3d2741623912d97c33751aa0d", "0x2d6f597bb2771b5e9eafeeebb220a157d9bc7a7acb3eb0fed18272da51d1abef322e4b85b02c7ac79bddae7ac8708cc7f01e774e8e8b87023374567b52c3f8e9df28063abfdcb19152d95e2b6b3077acfb687e45e32164c132b0931c587eaa3f34ac474ec0512c6b57bad2b98ef83dca85d23a7c9b114893170501f2", 10363, Accept}, + {"0x4de679ab106df6431f37cb20e60491eab2a00fda0017e3788856589e332db9596eda1ec03ea4e641ae4b22e7923662537ddd4ae130148ecd193b5e4578d7409e5371b50f45e92293d8c786b824eb26dd6c31419f8ad6383327772cd10b84b40d10a7438c1b3a92aedb718a0c97137e1064ad67484d7206487902ef8f8b7a34318474ba0f6113e9dc15c4e30e7711f641a82f6672ad2e039a09228db6db2287", "0xd021d1d6cc99274c090b16afe3a1c4314f48316dcd2a2cdebaaa2896c51c9f3d779bd7be01a2fdc17093c2c9a633", "0xb7a91bb84e0abaef90b4d4293d0c4968a2", "0x62c668fb320f44c7fec283e7827a6ce1ff", 12083, Accept}, + {"0x51ebd23d2a02f976d3d9aae2061e06a8c0452b4b556443606ade0c71ea57a8463bbad81a5d6312237f8ab6194e18feb808631d40318b608cb7fe876d3ded24f04bbddc053a3be4579f5c6cdadc3ed1192a5016c609ff80a76677ed214ed0e5e04bf70bc03b6c15b999c4d343466b0324fa5a0a42ad60885885ab43f928991f30783819caba87e247837b0948b1d8d050ed", "0x1fbe100e76befa9e13", "0x576df8720f308d6e342644063415492caa9d4a11c80030e25f7541e56e62869d15b08a8807d789870194b0c7a325cf9d13e49c654b08965c8eb3e144145fb7", "0x197bcf7eafab09fa4776dbaa9ebb8cc2ff6787d1a800819cd75f507a1372157085dc171f6e501edf2b44d46038d3fa49e0db8ebac4b41461bdce184585edd9", 2200, Accept}, + {"0x9c61f9e2209144eeabc02cb02e5db1484544e33478eb374a18be5baf71b16e91d9ab86882984ea9ba16fde77eb0ef161c497c1883e0cbdb8dca844ad7b8f270073ab640c385e4f9512", "0x648110c896453b6f1a3a0b234f5c0f8b7c4b4d958458280372a6232f9d98cd1420df6a5691fa1bf773f6bd", "0xe5e14e935f795ad814f54d95248b0102b2b0c351e8a5112541343c024d90dfa43c702eea820354a2670563425ee515c4dcc2c6bed73234b0e77384f3ba64a1b8b068149b2363566cdf9c80af", "0xa1d077b5157b4e829c814a0e8fb7e9b83e30e65ff46d3264dc619063a2de57fb171e050c5ec85715b73a8a8a7d2b155a6a77855edea62c9a6850c8dec1551bde227304b7df2811245405fdd5", 3535, Accept}, + {"0xe1b779ff6951aee456aeaf87a963678eaaae4ed61f387b68e4f196ff71440a5955b9a8c2144dbe4c00717b157564f21b54c8c3934bb43754af039068ba03d1ab7c53f9d5526842cb", "0x6bc12883963b0f0b6eb6c275bcc9", "0x072c23cab0e4dbc633b86e45a6b9bdfaf87076bf618c08b142d42b7ad8c3ea4795e873dad518ace0f9e84a4e265191972e303bfbbd6cff781de09a1ccb19f0dc5d874dfc3b89bfa666b391068c3dc9c183f04e4dcd1ec80ed92fd4c792e102904817a41ae1208ed39a9484d61e10491859924a0a04a9455e36458702dcd8a312176e9a05e0e45a14783ecaa7e93f410a8a51848ad706d014467d634fa7c0d6756f54e5f980", "0x06d4a5ffa4568bf3bc20ff29f74941b212ec9121d936d6becbfde46dca5034e0749fead5293e42331a922d1e6a64efc42bf165ca3853c2a80248d32fdf70a6c233ef32851b85ea1c1b51ca364dd2bcd9f25a6249c014dc36b123382099f5f060eb8c0b6d13e4facb932fa49ae140b917dad9e82076e71b407928405aa449fb66eacd97429296a8f8d3cafd8660d124f5d98e6fdb9f1a74ba10a76b724bb8ef4ec98c749b6b", 4041, Accept}, + {"0x4594ea63dd8b77f34701aaae1f430d4adee9811213bde681fd750cd4bff65322654553180248e580de54da02365dbcc61ad6039a61c0c5783872038cccf618ce10757b50d4f58529cc2d6d9ca30543e8ddedc481757a679101", "0xd32aab68fadf838e361d75da2ce241dd0b95dd38e3ceb860975e39d4eab04e84581269d22dc8880395c6c091b3859cd9fa031186af5bc0f23d6ada8fbfae9f7dcc307d862c", "0x7b646597ae005c1b0c2bc981917294e669a47fc12b27d08c1741caa5d31c68", "0x317c66d1e4a6e22d60a1299aaec61f9e8668e08bb94b11e59e32d1daad8e11", 7122, Accept}, + {"0x44dfe16a0cc499362a5a6b6b5d4167b9e45c3bbd1b98b494e99ff71010013a8c816816f112a69f9e70a320625c149555e1276bad70999da1b3c124e5c54cbeb02b534f845ffdfcede15b01fa8d0bd8f22b95ace6cf5d0aa97cb81f1688afecef51cc48fdf3155185090e8249795af2c26997ea1a915fd85b5a8bf9cce7c7dbbb6f268cd424e6b86331d32a6e4cf783957160", "0xc392b4bfe312f474a02d0860823a05a8a6d5846f1db0a9245a1f64cb354b5ab91590d24ebba8ef68a369d25932b1acfcb33b6af52a260313bcc0493c", "0x5492af9556e685fd639f80e42fae3e0cc4588233f4683017d376c5746b3eea", "0x4e13bec8183024e881e1688458437e0a3c49959c3099329c4540c0b5840a70", 13688, Accept}, + {"0x2ea1312db704ff29e0", "0x038302a78381a38adcb7581cbeb7a0797289d82d14a85cf4c36df72c5b5c3d464c4a280f930a85ef4aefb54ce935d01a18afd42d9a679140a360f2b185ac37fde9890d2808a6675e3d73bc696921babefa9cb1985b948e65734fae0515f0e6b7ef782bef9f1a4921c5df3e340e764bf6c347614c5649e645f3bdaaa2c7dbcc16b5107056", "0x71166d7a0b32f8cbd2f682474b61c5535e2867562bcf5dd5e43d2a4e036b78e871b18145e6da2ef327da994965ade4bb985f4f2402da936a6f90d0913512add104dc10741c06b948e911b8fbe9", "0x4540d8df3bc9cd82ff6e431440f65fd58165a43783dccabc315f5a33fa3a581068ca5ae3ede591e302fc863eff657b962d0e671235fc97456439921ec9023eff5b8256b056eb47eae5911f2e6b", 10658, Accept}, + {"0x1f206df741a36c542fb5e609c9299e62a96ee677ca7266d85d086d4ebd6ab9b52c56539c41b0a1a69a0a5dfc794cd6076360643660147c053f821992bf5c787a1fed53eab8f61e0d538aa3a352616774d419c7be55415e60a86f296d1baa199284ebd2ff12eb2b84a7dfedbe1d34efd3219265f302b91963416e42145bfbdf7d0132b1d32c98129521a61d92e2318f94b87f96f68eefe5263717999ab1780f9c15e895a5c188e47518b209f61c3a501e315c4ea0504de653d9b3f9d25658c1c30b99fa6b2a02ff99838d04b86bbeb13ca94d90fd96aca7eae17bc76cf13e33cf37769ad7bf98c6f151c3961d2157aa63ebd577f2f5dbb67805df9a649942843c", "0x37c463", "0x8eb38552534a9ca188412677f154eeba8f011cf6ae00472dcca54c068d57825ff7f703b1a8380d2fc9a7e1e142f8770a7da52e2d47638853aacbe450a80f2c35a9ce0e5feca7bfff871252ec2c5754cafdcde3cd20ce4767c23042570d3d9641e8517ea4c3f10d7f4ea927824d948aed87de2b856347faba08be786ad3d9f30cd1bc4b036dd4a0053c59d11fc2840aefde47222a0273323f45b08539313de7393d24ade84f8f57c719986db04a0f3f483375e5779c8b8ce913991a80ea6cb368bb3f1f2c3dc3d424d7c0ae607c6d052dc7b0ae170250e1ad10e6b327857cb8610904c526d51430c31931d4ba3d5ebab8d6321c48d6d482f5b129f69871f4405f", "0x1d6e8af1caa1098ce2429e32eb831598f6b28a65376e54fe863283b545949586e2f3b41285d6047fcd52d164be131325f80412d2ca8bb84dd945ae69b3e1bc4fa861905b1f3032ea7279d2ed3c03f78ece1c0d0f159e0a4776d1ee47516e4379105491c37d6bc86bc26420966076d114f5a4091e800259f59073fd5f7c0100fbcbc10a9f7cef6fcd03c04ae97b54994ea479e168bc00ae9ea84ad07497aa470d3d438ccfa669de5d99ccf36a2ab1773378101123f5bcdd9a6f5a1df889b8a0bbc071d692d68b69801cfa467bdcb8d00dc5f32be5ca907433667691527534c229701ec929ef836c7caef7a088205082f98a08860ed72d383e6ac256aa3680f3c6", 1884, Accept}, + {"0x7c391cf4e56c7c104d90177402b2e1a0f9179a06304f4357e4b146e116cd12e0f1c12bfc66171b8c8be104d09c304c340e125c4b6fda63b94315d74ad0e8b8178edac81b475da5dc7e825c309a4c0b5fb3c3e0bd7f94dc661cd8ac546940779e54edf58c6ace5589914541935bc66fff64442d8bc2e6dc8420257c8ab0a877729fe8", "0x74d69cdea330c38633c7bca9fb46d2e1e2050e5220c5fa3194584c62b4ebd3e85a70fd2f994d04681fc8aa32e580f87484b78ff8d3bab0412874e55772411288f4a6196f9da6db7aaebbf0d62e4e42275dfa475ac35802d912aacfb4f77e945f4e5e3c28610ddbd479280df848cd57829746fcc6452a5d4127b4f8b27a3149bc", "0x9000b0b587f64e78f51645a75d98b64d7fa1001d1636bcae53ea41f9f955f67f79c442adbca55d59c61642ed91364feef5e5147ed229cd5ff1d31b6c333a65f95e80c576f11ce4790c3162c351bd7df796c6e2184a387edea127c6ddf46a6eae6ade4066de609d655832b98b", "0x810f16edd6ffee0cad631b2f59ad6b3847f80974ce4376353ad1f8f487dae65e93ddb9552cc93b0725acb1ba3551132c138ef730568c3fde71918608edf3f78130170124d0a4d3d28fcd2cefb256465eb18e80ea0576fd1df44e76786a450285a0eef852b7df639925795293", 24014, Accept}, + {"0x9b7e403f0d0134635f90d344dbce30ac511e8e5e274a3436ccb75503d0ee72a3ba59c2a9b774ee74abe082e09702c65151186706c62200241d306d8cb18b40278c885222db5d001aecceff20e4be25ed83d4ff7d40c4c6e513a63238a5c07e45da3a24868caa67fae36047d955a648dd1c741284cdb8bc282c01b9d66d2c5b651268ff1d50356f1dc6be6d59814d7787e6", "0x30c54b", "0x093fd6b228d5d2268a36b0a1b8fb7dbcb4669c22e0cc2a5deaa3c3da890c5fa23dc0", "0x2a3d94206458cce1a0cee7ef45b3812de4f2ae4ee9b347acf55385eca217159f6b76b7c14774aa54e9667bb172d66b25d907682576a2ec7f2038c07e4f", 866, Panic}, } for _, modexpTestVector := range modexpTestVectors { @@ -5152,9 +5158,12 @@ func TestBytesModExp(t *testing.T) { byte %s byte %s bmodexp - byte %s -==`, modexpTestVector.Base, modexpTestVector.Exponent, modexpTestVector.Modulus, modexpTestVector.Result) +== +assert +global OpcodeBudget +int %d +==`, modexpTestVector.Base, modexpTestVector.Exponent, modexpTestVector.Modulus, modexpTestVector.Result, testLogicBudget-7-modexpTestVector.LogicCost) switch modexpTestVector.TestOutcome { case Accept: testAccepts(t, progText, 11) diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 5f9639e048..74906d60f7 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -19,6 +19,7 @@ package logic import ( "cmp" "fmt" + "math" "strconv" "strings" @@ -85,15 +86,41 @@ const spOpcodesVersion = 11 // falcon_verify, sumhash512 // Unlimited Global Storage opcodes const boxVersion = 8 // box_* +// CustomCost encapsulates a custom cost function and its documentation, applicable to opcodes like bmodexp where linearCost is inadequate by itself +type CustomCost struct { + compute func(stack []stackValue, depth int) int + docCost string +} + +// Custom cost definition for bmodexp +func bmodExpCostFunction(stack []stackValue, depth int) int { + last := len(stack) - depth - 1 // mod + prev := last - depth - 1 // exp + pprev := last - depth - 2 // base + + expLength := float64(len(stack[prev].Bytes)) + modLength := float64(len(stack[last].Bytes)) + baseLength := float64(len(stack[pprev].Bytes)) + cost := (math.Pow(math.Max(baseLength, modLength), 1.63) * expLength / 15) + 200 + + return int(cost) +} + +var bmodexpCustomCost = &CustomCost{ + compute: bmodExpCostFunction, + docCost: "((len(B) * max(len(A), len(C)) ^ 1.63) / 15) + 200", +} + type linearCost struct { - baseCost int - chunkCost int - chunkSize int - depth int + baseCost int + chunkCost int + chunkSize int + depth int + customCost *CustomCost } func (lc linearCost) check() linearCost { - if lc.baseCost < 1 || lc.chunkCost < 0 || lc.chunkSize < 0 || lc.chunkSize > maxStringSize || lc.depth < 0 { + if (lc.customCost == nil && lc.baseCost < 1) || lc.chunkCost < 0 || lc.chunkSize < 0 || lc.chunkSize > maxStringSize || lc.depth < 0 { panic(fmt.Sprintf("bad cost configuration %+v", lc)) } if lc.chunkCost > 0 && lc.chunkSize == 0 { @@ -102,10 +129,19 @@ func (lc linearCost) check() linearCost { if lc.chunkCost == 0 && lc.chunkSize > 0 { panic(fmt.Sprintf("no chunk cost with positive chunk size %+v", lc)) } + if lc.customCost != nil && lc.customCost.compute == nil { + panic(fmt.Sprintf("CustomCost exists without a non-nil compute function pointer value %+v", lc)) + } + if lc.customCost != nil && lc.customCost.docCost == "" { + panic(fmt.Sprintf("CustomCost exists without a value for docCost %+v", lc)) + } return lc } func (lc *linearCost) compute(stack []stackValue) int { + if lc.customCost != nil { + return lc.customCost.compute(stack, lc.depth) + } cost := lc.baseCost if lc.chunkCost != 0 && lc.chunkSize != 0 { // Uses basics.DivCeil rather than (count/chunkSize) to match how Ethereum discretizes hashing costs. @@ -116,6 +152,9 @@ func (lc *linearCost) compute(stack []stackValue) int { } func (lc *linearCost) docCost(argLen int) string { + if lc.customCost != nil { + return lc.customCost.docCost + } if *lc == (linearCost{}) { return "" } @@ -240,6 +279,15 @@ func (d OpDetails) costs(cost int) OpDetails { return d } +func defaultCustomCost(customCost *CustomCost) OpDetails { + return detDefault().customCost(customCost) +} + +func (d OpDetails) customCost(customCost *CustomCost) OpDetails { + d.FullCost = linearCost{customCost: customCost}.check() + return d +} + func only(m RunMode) OpDetails { d := detDefault() d.Modes = m @@ -334,7 +382,7 @@ func costByFieldAndLength(immediate string, group *FieldGroup, costs []linearCos func costByLength(initial, perChunk, chunkSize, depth int) OpDetails { d := detDefault() - d.FullCost = linearCost{initial, perChunk, chunkSize, depth}.check() + d.FullCost = linearCost{baseCost: initial, chunkCost: perChunk, chunkSize: chunkSize, depth: depth}.check() return d } @@ -798,7 +846,7 @@ var OpSpecs = []OpSpec{ costByField("g", &EcGroups, []int{ BN254g1: 630, BN254g2: 3_300, BLS12_381g1: 1_950, BLS12_381g2: 8_150})}, - {0xe6, "bmodexp", opBytesModExp, proto("bbb:b"), 11, costly(2000)}, // TODO: refine cost model for bmodexp + {0xe6, "bmodexp", opBytesModExp, proto("bbb:b"), 11, defaultCustomCost(bmodexpCustomCost)}, } // OpcodesByVersion returns list of opcodes available in a specific version of TEAL From 468210e44adbd29a6dca7acc2d342707eb8cb2b4 Mon Sep 17 00:00:00 2001 From: mangoplane <104189015+mangoplane@users.noreply.github.com> Date: Fri, 25 Oct 2024 21:43:55 +1100 Subject: [PATCH 5/9] refactor: Move bmodexp cost magic numbers to constants and improve comments --- data/transactions/logic/opcodes.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 74906d60f7..3fc4eb3b91 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -86,7 +86,8 @@ const spOpcodesVersion = 11 // falcon_verify, sumhash512 // Unlimited Global Storage opcodes const boxVersion = 8 // box_* -// CustomCost encapsulates a custom cost function and its documentation, applicable to opcodes like bmodexp where linearCost is inadequate by itself +// CustomCost encapsulates a custom cost function and its documentation, +// applicable to opcodes like bmodexp where linearCost is inadequate by itself type CustomCost struct { compute func(stack []stackValue, depth int) int docCost string @@ -98,10 +99,19 @@ func bmodExpCostFunction(stack []stackValue, depth int) int { prev := last - depth - 1 // exp pprev := last - depth - 2 // base + // Empirically estimated cost function constants + const ( + exponentFactor = 1.63 // Adjusts cost of base & mod multiplication in the modexp by squaring algorithm + scalingFactor = 15 // Normalization factor + baseCost = 200 // Minimum cost of bmodexp + ) + expLength := float64(len(stack[prev].Bytes)) modLength := float64(len(stack[last].Bytes)) baseLength := float64(len(stack[pprev].Bytes)) - cost := (math.Pow(math.Max(baseLength, modLength), 1.63) * expLength / 15) + 200 + + // Derived from the asymptotic time complexity of the exponentiation by squaring algorithm + cost := (math.Pow(math.Max(baseLength, modLength), exponentFactor) * expLength / scalingFactor) + baseCost return int(cost) } From 27ab7f588e2c17f5fb362f74b899916aba6a50a6 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 10 Jan 2025 16:10:14 -0500 Subject: [PATCH 6/9] Tests are back to passing --- data/transactions/logic/assembler_test.go | 2 +- data/transactions/logic/eval_test.go | 6 +++--- data/transactions/logic/opcodes.go | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index c78f122a51..e8700aa40b 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -492,7 +492,7 @@ const mimcCompiled = "802011223344556677889900aabbccddeeff11223344556677889900aa const v11Compiled = v10Compiled + incentiveCompiled + mimcCompiled const stateProofCompiled = "80070123456789abcd86494985" -const bmodexpCompiled = "80070123456789abcd80070123456789abcd80070123456789abcde6" +const bmodexpCompiled = "80070123456789abcd80070123456789abcd80070123456789abcd99" const v12Compiled = v11Compiled + stateProofCompiled + bmodexpCompiled diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 1cf2bfac7d..772c1364cf 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -5199,11 +5199,11 @@ int %d ==`, modexpTestVector.Base, modexpTestVector.Exponent, modexpTestVector.Modulus, modexpTestVector.Result, testLogicBudget-7-modexpTestVector.LogicCost) switch modexpTestVector.TestOutcome { case Accept: - testAccepts(t, progText, 11) + testAccepts(t, progText, 12) case Reject: - testRejects(t, progText, 11) + testRejects(t, progText, 12) case Panic: - testPanics(t, progText, 11) + testPanics(t, progText, 12) } } diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 9d43ee8d7d..f60caabad4 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -19,13 +19,13 @@ package logic import ( "cmp" "fmt" + "maps" "math" + "slices" "strconv" "strings" "github.com/algorand/go-algorand/data/basics" - "golang.org/toolchain/src/slices" - "golang.org/x/exp/maps" ) // LogicVersion defines default assembler and max eval versions @@ -739,6 +739,7 @@ var OpSpecs = []OpSpec{ /* Will end up following keccak256 - {0x98, "sha3_256", opSHA3_256, proto("b:b{32}"), ?, costByLength(...)},}, */ + {0x99, "bmodexp", opBytesModExp, proto("bbb:b"), 12, defaultCustomCost(bmodexpCustomCost)}, // Byteslice math. {0xa0, "b+", opBytesPlus, proto("II:b"), 4, costly(10).typed(typeByteMath(maxByteMathSize + 1))}, @@ -868,7 +869,6 @@ var OpSpecs = []OpSpec{ chunkCost: 550, chunkSize: 32, }})}, - {0xe7, "bmodexp", opBytesModExp, proto("bbb:b"), 11, defaultCustomCost(bmodexpCustomCost)}, } // OpcodesByVersion returns list of opcodes available in a specific version of TEAL From 929ee812bd640ff30bfa8e8ece081ced65ce5810 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Jan 2025 12:21:42 -0500 Subject: [PATCH 7/9] Merged, tests working --- data/transactions/logic/README.md | 7 +- data/transactions/logic/eval.go | 38 ++++- data/transactions/logic/eval_test.go | 117 +++++++++---- data/transactions/logic/opcodes.go | 166 ++++++++----------- data/transactions/logic/opcodes_test.go | 12 +- data/transactions/logic/pairing_test.go | 2 +- data/transactions/logic/teal.tmLanguage.json | 5 +- 7 files changed, 210 insertions(+), 137 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 8f5fbee163..e64e8f2ab5 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -445,6 +445,7 @@ bytes on outputs. | `b!=` | 0 if A is equal to B, else 1. A and B are interpreted as big-endian unsigned integers | | `b%` | A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero. | | `bsqrt` | The largest integer I such that I^2 <= A. A and I are interpreted as big-endian unsigned integers | +| `bmodexp` | A raised to the Bth power modulo C. A, B and C are interpreted as big-endian unsigned integers limited to 4096 bytes. Fail if C is zero. | These opcodes operate on the bits of byte-array values. The shorter input array is interpreted as though left padded with zeros until it is the @@ -467,6 +468,8 @@ these results may contain leading zero bytes. | `keccak256` | Keccak256 hash of value A, yields [32]byte | | `sha512_256` | SHA512_256 hash of value A, yields [32]byte | | `sha3_256` | SHA3_256 hash of value A, yields [32]byte | +| `sumhash512` | sumhash512 of value A, yields [64]byte | +| `falcon_verify` | for (data A, compressed-format signature B, pubkey C) verify the signature of data against the pubkey | | `ed25519verify` | for (data A, signature B, pubkey C) verify the signature of ("ProgData" \|\| program_hash \|\| data) against the pubkey => {0 or 1} | | `ed25519verify_bare` | for (data A, signature B, pubkey C) verify the signature of the data against the pubkey => {0 or 1} | | `ecdsa_verify v` | for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} | @@ -639,8 +642,8 @@ Global fields are fields that are common to all the transactions in the group. I | 18 | PayoutsEnabled | bool | v11 | Whether block proposal payouts are enabled. | | 19 | PayoutsGoOnlineFee | uint64 | v11 | The fee required in a keyreg transaction to make an account incentive eligible. | | 20 | PayoutsPercent | uint64 | v11 | The percentage of transaction fees in a block that can be paid to the block proposer. | -| 21 | PayoutsMinBalance | uint64 | v11 | The minimum balance an account must have in the agreement round to receive block payouts in the proposal round. | -| 22 | PayoutsMaxBalance | uint64 | v11 | The maximum balance an account can have in the agreement round to receive block payouts in the proposal round. | +| 21 | PayoutsMinBalance | uint64 | v11 | The minimum algo balance an account must have in the agreement round to receive block payouts in the proposal round. | +| 22 | PayoutsMaxBalance | uint64 | v11 | The maximum algo balance an account can have in the agreement round to receive block payouts in the proposal round. | **Asset Fields** diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index e151a7db5a..31466f7ccd 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -51,6 +51,10 @@ const maxStringSize = 4096 // maxByteMathSize is the limit of byte strings supplied as input to byte math opcodes const maxByteMathSize = 64 +// bigByteCmpVersion is the first version for which operators like `b==` and +// `b<` are unconstrained by maxByteMathSize +const bigByteCmpVersion = 12 + // maxLogSize is the limit of total log size from n log calls in a program const maxLogSize = config.MaxEvalDeltaTotalLogSize @@ -2275,8 +2279,10 @@ func opBytesLt(cx *EvalContext) error { last := len(cx.Stack) - 1 prev := last - 1 - if len(cx.Stack[last].Bytes) > maxByteMathSize || len(cx.Stack[prev].Bytes) > maxByteMathSize { - return errors.New("math attempted on large byte-array") + if cx.version < bigByteCmpVersion { + if len(cx.Stack[last].Bytes) > maxByteMathSize || len(cx.Stack[prev].Bytes) > maxByteMathSize { + return errors.New("math attempted on large byte-array") + } } rhs := nonzero(cx.Stack[last].Bytes) @@ -2320,8 +2326,10 @@ func opBytesEq(cx *EvalContext) error { last := len(cx.Stack) - 1 prev := last - 1 - if len(cx.Stack[last].Bytes) > maxByteMathSize || len(cx.Stack[prev].Bytes) > maxByteMathSize { - return errors.New("math attempted on large byte-array") + if cx.version < bigByteCmpVersion { + if len(cx.Stack[last].Bytes) > maxByteMathSize || len(cx.Stack[prev].Bytes) > maxByteMathSize { + return errors.New("math attempted on large byte-array") + } } rhs := nonzero(cx.Stack[last].Bytes) @@ -2360,6 +2368,28 @@ func opBytesModExp(cx *EvalContext) error { return nil } +func bmodExpCost(stack []stackValue, depth int) int { + last := len(stack) - depth - 1 // mod + prev := last - depth - 1 // exp + pprev := last - depth - 2 // base + + // Empirically estimated cost function constants + const ( + exponentFactor = 1.63 // Adjusts cost of base & mod multiplication in the modexp by squaring algorithm + scalingFactor = 15 // Normalization factor + baseCost = 200 // Minimum cost of bmodexp + ) + + expLength := float64(len(stack[prev].Bytes)) + modLength := len(stack[last].Bytes) + baseLength := len(stack[pprev].Bytes) + + // Derived from the asymptotic time complexity of the exponentiation by squaring algorithm + cost := (math.Pow(float64(max(baseLength, modLength)), exponentFactor) * expLength / scalingFactor) + baseCost + + return int(cost) +} + func opBytesModulo(cx *EvalContext) error { result := new(big.Int) var inner error diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 772c1364cf..1e28dd08f7 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -4618,22 +4618,18 @@ func notrack(program string) string { type evalTester func(t *testing.T, pass bool, err error) bool -func testEvaluation(t *testing.T, program string, introduced uint64, tester evalTester) error { +func testEvaluation(t *testing.T, program string, start uint64, stop uint64, tester evalTester) error { t.Helper() var outer error - for v := uint64(1); v <= AssemblerMaxVersion; v++ { + for v := start; v <= stop; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { t.Helper() - if v < introduced { - testProg(t, notrack(program), v, exp(0, "...was introduced...")) - return - } ops := testProg(t, program, v) // Programs created with a previous assembler // should still operate properly with future // EvalParams, so try all forward versions. - for lv := v; lv <= AssemblerMaxVersion; lv++ { + for lv := v; lv <= stop; lv++ { t.Run(fmt.Sprintf("lv=%d", lv), func(t *testing.T) { t.Helper() var txn transactions.SignedTxn @@ -4670,20 +4666,49 @@ func testEvaluation(t *testing.T, program string, introduced uint64, tester eval func testAccepts(t *testing.T, program string, introduced uint64) { t.Helper() - testEvaluation(t, program, introduced, func(t *testing.T, pass bool, err error) bool { + testPreexist(t, program, introduced) + testAcceptRange(t, program, introduced, AssemblerMaxVersion) +} + +func testAcceptRange(t *testing.T, program string, start uint64, stop uint64) { + t.Helper() + testEvaluation(t, program, start, stop, func(t *testing.T, pass bool, err error) bool { return pass && err == nil }) } + func testRejects(t *testing.T, program string, introduced uint64) { t.Helper() - testEvaluation(t, program, introduced, func(t *testing.T, pass bool, err error) bool { + testPreexist(t, program, introduced) + testRejectRange(t, program, introduced, AssemblerMaxVersion) +} + +func testRejectRange(t *testing.T, program string, start uint64, stop uint64) { + t.Helper() + testEvaluation(t, program, start, stop, func(t *testing.T, pass bool, err error) bool { // Returned False, but didn't panic return !pass && err == nil }) } + func testPanics(t *testing.T, program string, introduced uint64, pattern ...string) error { t.Helper() - return testEvaluation(t, program, introduced, func(t *testing.T, pass bool, err error) bool { + testPreexist(t, program, introduced) + return testPanicRange(t, program, introduced, AssemblerMaxVersion) +} + +func testPreexist(t *testing.T, program string, introduced uint64) { + for v := uint64(1); v < introduced; v++ { + t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { + t.Helper() + testProg(t, notrack(program), v, exp(0, "...was introduced...")) + }) + } +} + +func testPanicRange(t *testing.T, program string, start uint64, stop uint64, pattern ...string) error { + t.Helper() + return testEvaluation(t, program, start, stop, func(t *testing.T, pass bool, err error) bool { t.Helper() // TEAL panic! not just reject at exit if pass { @@ -5153,9 +5178,9 @@ func BenchmarkBytesModExp(b *testing.B) { func TestBytesModExp(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - type TestOutcome int + type testOutcome int const ( - Accept TestOutcome = iota + Accept testOutcome = iota Reject Panic ) @@ -5165,11 +5190,13 @@ func TestBytesModExp(t *testing.T) { Modulus string Result string LogicCost int - TestOutcome TestOutcome + TestOutcome testOutcome } - modexpTestVectors := []ModexpTestVector{ - {"0x01", "0x01", "0x", "0x00", 200, Panic}, // Modulo of 0 should panic - {"0x01", "0x01", "0x0000", "0x00", 200, Panic}, // Modulo of 0 should panic + cases := []ModexpTestVector{ + {"0x01", "0x01", "0x", "0x00", 0, Panic}, // Modulo of 0 should panic + {"0x01", "0x01", "0x0000", "0x00", 0, Panic}, // Modulo of 0 should panic + {"0x01", "0x01", "0x", "0x01", 0, Panic}, // Modulo of 0 should panic + {"0x01", "0x01", "0x0000", "0x01", 0, Panic}, // Modulo of 0 should panic {"0x54b7", "0x00", "0x01", "0x", 200, Accept}, {"0x286e0b2a3fea08c786634bdf0a608fb22009c512e6f1f174", "0x9cebf0aae57f76408a", "0xcf5d2d1fdc2e3233adcc13c8b3fc2fb0a3d3c1032ee14288c9026968c59d6fd7f8c9ef82e63bea29304ebb91b150", "0x9e26c7578c46f09e26e67224526193f5af3512662276e54cb91944d9f80514b31fba2d4c6231c97309a79cfc09b0", 507, Accept}, {"0xb04336dca137d1284edf958923d01c83f6a09e50bcfb1b509c2afe63bca4f64bf28a482f202cdf08e4fad627acde33c4a5206086641acf2ceab1669bf99b5d672dc71a5d2fc7ff99152f2ecb71e95543cb72be06151e3b75c12961773a0b20e59ceb18713ee7313cb3c146b10188a23de2dab3b733d2dbc4b30258e6e8cde85d1c394a76784a2038a0499feaf4851f22c48b30a7eedf02de934f8a31930d90426fd93241862614943e7a6e2e7f3ef9b08ce14030dcb8ca51d53743ac", "0x3bc794defa8e", "0xf418c1ba14622a93b40859b6fa5c8869ceeab204991a18bba8b414a03bab048c016a98c190ca7f4edb82745e8d91ce930b28c3e8c6f783ff6ea7cf4e092fe845d81189c8d77e4d6b2a3c967ed3d64a7310be13260589531e6485ddf9b065bed8142d7189fe22e213847bc0e10c5ff21e5f12c513f91357db5de6dc879f1e622dc386be6521f48cd476adb021050c09b913147ccb0c7e9ea2712f63b1c2273c4eb70267d366c8eb9548d3bcc19972dce8538767cd53d010e35a3bbab920afd498184d587f3f081fcc7018fd9ad448076a4a8ff231fc", "0x7d01fce371b80532a8ba65fc442e3adb4a5cae46d734258d342fbabaad7e83b14474fd21a5cee7e4a53f3de7e6f3c497b893f0cf23d9a743c4dfa736fb8080d54083a03b20f598ec1eed1d83714465914aa9171cdb1c3a56fb9c021e0c80f44a4d2b4b5c4e078fdc818474af5e0a334b25ac3f069d2dcc72dca335d05ac24fdbfabf07b17ce6e9fb996100509545bd9a0e5df48215112e04a68b2cc700b1a379e3a5df9d2913498cb8e15c92bec53a3c5775dd7fdfa9a5b515f738c88dc404b09cc2a4c389ee6334da58364d5c22482b905a1ec3fc", 2696, Accept}, @@ -5183,11 +5210,13 @@ func TestBytesModExp(t *testing.T) { {"0x2ea1312db704ff29e0", "0x038302a78381a38adcb7581cbeb7a0797289d82d14a85cf4c36df72c5b5c3d464c4a280f930a85ef4aefb54ce935d01a18afd42d9a679140a360f2b185ac37fde9890d2808a6675e3d73bc696921babefa9cb1985b948e65734fae0515f0e6b7ef782bef9f1a4921c5df3e340e764bf6c347614c5649e645f3bdaaa2c7dbcc16b5107056", "0x71166d7a0b32f8cbd2f682474b61c5535e2867562bcf5dd5e43d2a4e036b78e871b18145e6da2ef327da994965ade4bb985f4f2402da936a6f90d0913512add104dc10741c06b948e911b8fbe9", "0x4540d8df3bc9cd82ff6e431440f65fd58165a43783dccabc315f5a33fa3a581068ca5ae3ede591e302fc863eff657b962d0e671235fc97456439921ec9023eff5b8256b056eb47eae5911f2e6b", 10658, Accept}, {"0x1f206df741a36c542fb5e609c9299e62a96ee677ca7266d85d086d4ebd6ab9b52c56539c41b0a1a69a0a5dfc794cd6076360643660147c053f821992bf5c787a1fed53eab8f61e0d538aa3a352616774d419c7be55415e60a86f296d1baa199284ebd2ff12eb2b84a7dfedbe1d34efd3219265f302b91963416e42145bfbdf7d0132b1d32c98129521a61d92e2318f94b87f96f68eefe5263717999ab1780f9c15e895a5c188e47518b209f61c3a501e315c4ea0504de653d9b3f9d25658c1c30b99fa6b2a02ff99838d04b86bbeb13ca94d90fd96aca7eae17bc76cf13e33cf37769ad7bf98c6f151c3961d2157aa63ebd577f2f5dbb67805df9a649942843c", "0x37c463", "0x8eb38552534a9ca188412677f154eeba8f011cf6ae00472dcca54c068d57825ff7f703b1a8380d2fc9a7e1e142f8770a7da52e2d47638853aacbe450a80f2c35a9ce0e5feca7bfff871252ec2c5754cafdcde3cd20ce4767c23042570d3d9641e8517ea4c3f10d7f4ea927824d948aed87de2b856347faba08be786ad3d9f30cd1bc4b036dd4a0053c59d11fc2840aefde47222a0273323f45b08539313de7393d24ade84f8f57c719986db04a0f3f483375e5779c8b8ce913991a80ea6cb368bb3f1f2c3dc3d424d7c0ae607c6d052dc7b0ae170250e1ad10e6b327857cb8610904c526d51430c31931d4ba3d5ebab8d6321c48d6d482f5b129f69871f4405f", "0x1d6e8af1caa1098ce2429e32eb831598f6b28a65376e54fe863283b545949586e2f3b41285d6047fcd52d164be131325f80412d2ca8bb84dd945ae69b3e1bc4fa861905b1f3032ea7279d2ed3c03f78ece1c0d0f159e0a4776d1ee47516e4379105491c37d6bc86bc26420966076d114f5a4091e800259f59073fd5f7c0100fbcbc10a9f7cef6fcd03c04ae97b54994ea479e168bc00ae9ea84ad07497aa470d3d438ccfa669de5d99ccf36a2ab1773378101123f5bcdd9a6f5a1df889b8a0bbc071d692d68b69801cfa467bdcb8d00dc5f32be5ca907433667691527534c229701ec929ef836c7caef7a088205082f98a08860ed72d383e6ac256aa3680f3c6", 1884, Accept}, {"0x7c391cf4e56c7c104d90177402b2e1a0f9179a06304f4357e4b146e116cd12e0f1c12bfc66171b8c8be104d09c304c340e125c4b6fda63b94315d74ad0e8b8178edac81b475da5dc7e825c309a4c0b5fb3c3e0bd7f94dc661cd8ac546940779e54edf58c6ace5589914541935bc66fff64442d8bc2e6dc8420257c8ab0a877729fe8", "0x74d69cdea330c38633c7bca9fb46d2e1e2050e5220c5fa3194584c62b4ebd3e85a70fd2f994d04681fc8aa32e580f87484b78ff8d3bab0412874e55772411288f4a6196f9da6db7aaebbf0d62e4e42275dfa475ac35802d912aacfb4f77e945f4e5e3c28610ddbd479280df848cd57829746fcc6452a5d4127b4f8b27a3149bc", "0x9000b0b587f64e78f51645a75d98b64d7fa1001d1636bcae53ea41f9f955f67f79c442adbca55d59c61642ed91364feef5e5147ed229cd5ff1d31b6c333a65f95e80c576f11ce4790c3162c351bd7df796c6e2184a387edea127c6ddf46a6eae6ade4066de609d655832b98b", "0x810f16edd6ffee0cad631b2f59ad6b3847f80974ce4376353ad1f8f487dae65e93ddb9552cc93b0725acb1ba3551132c138ef730568c3fde71918608edf3f78130170124d0a4d3d28fcd2cefb256465eb18e80ea0576fd1df44e76786a450285a0eef852b7df639925795293", 24014, Accept}, - {"0x9b7e403f0d0134635f90d344dbce30ac511e8e5e274a3436ccb75503d0ee72a3ba59c2a9b774ee74abe082e09702c65151186706c62200241d306d8cb18b40278c885222db5d001aecceff20e4be25ed83d4ff7d40c4c6e513a63238a5c07e45da3a24868caa67fae36047d955a648dd1c741284cdb8bc282c01b9d66d2c5b651268ff1d50356f1dc6be6d59814d7787e6", "0x30c54b", "0x093fd6b228d5d2268a36b0a1b8fb7dbcb4669c22e0cc2a5deaa3c3da890c5fa23dc0", "0x2a3d94206458cce1a0cee7ef45b3812de4f2ae4ee9b347acf55385eca217159f6b76b7c14774aa54e9667bb172d66b25d907682576a2ec7f2038c07e4f", 866, Panic}, + {"0x9b7e403f0d0134635f90d344dbce30ac511e8e5e274a3436ccb75503d0ee72a3ba59c2a9b774ee74abe082e09702c65151186706c62200241d306d8cb18b40278c885222db5d001aecceff20e4be25ed83d4ff7d40c4c6e513a63238a5c07e45da3a24868caa67fae36047d955a648dd1c741284cdb8bc282c01b9d66d2c5b651268ff1d50356f1dc6be6d59814d7787e6", "0x30c54b", "0x093fd6b228d5d2268a36b0a1b8fb7dbcb4669c22e0cc2a5deaa3c3da890c5fa23dc0", "0x2a3d94206458cce1a0cee7ef45b3812de4f2ae4ee9b347acf55385eca217159f6b76b7c14774aa54e9667bb172d66b25d907682576a2ec7f2038c07e4f", 0, Panic}, } - for _, modexpTestVector := range modexpTestVectors { - progText := fmt.Sprintf(`byte %s + for i, tc := range cases { + // use subtests so that we can run all tests despite failures + t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + progText := fmt.Sprintf(`byte %s byte %s byte %s bmodexp @@ -5196,15 +5225,20 @@ byte %s assert global OpcodeBudget int %d -==`, modexpTestVector.Base, modexpTestVector.Exponent, modexpTestVector.Modulus, modexpTestVector.Result, testLogicBudget-7-modexpTestVector.LogicCost) - switch modexpTestVector.TestOutcome { - case Accept: - testAccepts(t, progText, 12) - case Reject: - testRejects(t, progText, 12) - case Panic: - testPanics(t, progText, 12) - } +==`, tc.Base, tc.Exponent, tc.Modulus, tc.Result, testLogicBudget-7-tc.LogicCost) + switch tc.TestOutcome { + case Accept: + testAccepts(t, progText, 12) + case Reject: + testRejects(t, progText, 12) + // ensure nobody thinks they are testing a cost here + require.Zero(t, tc.LogicCost) + case Panic: + testPanics(t, progText, 12) + // ensure nobody thinks they are testing a cost here + require.Zero(t, tc.LogicCost) + } + }) } } @@ -5260,9 +5294,17 @@ func TestBytesCompare(t *testing.T) { testAccepts(t, "byte 0x10; byte 0x10; b<; !", 4) testAccepts(t, "byte 0x10; byte 0x10; b<=", 4) - testPanics(t, "byte 0x10; int 65; bzero; b<=", 4) + + p := "byte 0x10; int 65; bzero; b<=" + testPreexist(t, p, 4) + testPanicRange(t, p, 4, 11) + testRejectRange(t, p, 12, AssemblerMaxVersion) + testAccepts(t, "byte 0x10; int 64; bzero; b>", 4) - testPanics(t, "byte 0x10; int 65; bzero; b>", 4) + p = "byte 0x10; int 65; bzero; b>" + testPreexist(t, p, 4) + testPanicRange(t, p, 4, 11) + testAcceptRange(t, p, 12, AssemblerMaxVersion) testAccepts(t, "byte 0x1010; byte 0x10; b<; !", 4) testAccepts(t, "byte 0x2000; byte 0x70; b<; !", 4) @@ -5283,18 +5325,27 @@ func TestBytesCompare(t *testing.T) { testAccepts(t, "byte 0x11; byte 0x10; b>=", 4) testAccepts(t, "byte 0x11; byte 0x0011; b>=", 4) - testPanics(t, "byte 0x10; int 65; bzero; b>=", 4) + p = "byte 0x10; int 65; bzero; b>=" + testPreexist(t, p, 4) + testPanicRange(t, p, 4, 11) + testAcceptRange(t, p, 12, AssemblerMaxVersion) testAccepts(t, "byte 0x11; byte 0x11; b==", 4) testAccepts(t, "byte 0x0011; byte 0x11; b==", 4) testAccepts(t, "byte 0x11; byte 0x00000000000011; b==", 4) testAccepts(t, "byte 0x00; int 64; bzero; b==", 4) - testPanics(t, "byte 0x00; int 65; bzero; b==", 4) + p = "byte 0x00; int 65; bzero; b==" + testPreexist(t, p, 4) + testPanicRange(t, p, 4, 11) + testAcceptRange(t, p, 12, AssemblerMaxVersion) testAccepts(t, "byte 0x11; byte 0x00; b!=", 4) testAccepts(t, "byte 0x0011; byte 0x1100; b!=", 4) testPanics(t, notrack("byte 0x11; int 17; b!="), 4) - testPanics(t, "byte 0x10; int 65; bzero; b!=", 4) + p = "byte 0x10; int 65; bzero; b!=" + testPreexist(t, p, 4) + testPanicRange(t, p, 4, 11) + testAcceptRange(t, p, 12, AssemblerMaxVersion) } func TestBytesBits(t *testing.T) { diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index f60caabad4..ebe3b76989 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -20,7 +20,6 @@ import ( "cmp" "fmt" "maps" - "math" "slices" "strconv" "strings" @@ -87,97 +86,73 @@ const spOpcodesVersion = 12 // falcon_verify, sumhash512 // Unlimited Global Storage opcodes const boxVersion = 8 // box_* -// CustomCost encapsulates a custom cost function and its documentation, -// applicable to opcodes like bmodexp where linearCost is inadequate by itself -type CustomCost struct { - compute func(stack []stackValue, depth int) int - docCost string +type costFn func(stack []stackValue, depth int) int +type costDescriptor struct { + baseCost int + chunkCost int + chunkSize int + depth int + // fn allows arbitrary cost functions. It _adds_ to costs from other fields + fn costFn + // doc replaces the generated documentation of the cost, for use with `compute` + doc string } -// Custom cost definition for bmodexp -func bmodExpCostFunction(stack []stackValue, depth int) int { - last := len(stack) - depth - 1 // mod - prev := last - depth - 1 // exp - pprev := last - depth - 2 // base - - // Empirically estimated cost function constants - const ( - exponentFactor = 1.63 // Adjusts cost of base & mod multiplication in the modexp by squaring algorithm - scalingFactor = 15 // Normalization factor - baseCost = 200 // Minimum cost of bmodexp - ) - - expLength := float64(len(stack[prev].Bytes)) - modLength := float64(len(stack[last].Bytes)) - baseLength := float64(len(stack[pprev].Bytes)) - - // Derived from the asymptotic time complexity of the exponentiation by squaring algorithm - cost := (math.Pow(math.Max(baseLength, modLength), exponentFactor) * expLength / scalingFactor) + baseCost - - return int(cost) -} - -var bmodexpCustomCost = &CustomCost{ - compute: bmodExpCostFunction, - docCost: "((len(B) * max(len(A), len(C)) ^ 1.63) / 15) + 200", -} - -type linearCost struct { - baseCost int - chunkCost int - chunkSize int - depth int - customCost *CustomCost +// Empty returns true if nothing is set in the costDescriptor. +func (cd costDescriptor) Empty() bool { + return cd.baseCost == 0 && + cd.chunkCost == 0 && + cd.chunkSize == 0 && + cd.depth == 0 && + cd.fn == nil && + cd.doc == "" } -func (lc linearCost) check() linearCost { - if (lc.customCost == nil && lc.baseCost < 1) || lc.chunkCost < 0 || lc.chunkSize < 0 || lc.chunkSize > maxStringSize || lc.depth < 0 { - panic(fmt.Sprintf("bad cost configuration %+v", lc)) - } - if lc.chunkCost > 0 && lc.chunkSize == 0 { - panic(fmt.Sprintf("chunk cost when chunk size is zero %+v", lc)) +func (cd costDescriptor) check() costDescriptor { + if (cd.fn == nil && cd.baseCost < 1) || cd.chunkCost < 0 || cd.chunkSize < 0 || cd.chunkSize > maxStringSize || cd.depth < 0 { + panic(fmt.Sprintf("bad cost configuration %+v", cd)) } - if lc.chunkCost == 0 && lc.chunkSize > 0 { - panic(fmt.Sprintf("no chunk cost with positive chunk size %+v", lc)) + if cd.chunkCost > 0 && cd.chunkSize == 0 { + panic(fmt.Sprintf("chunk cost when chunk size is zero %+v", cd)) } - if lc.customCost != nil && lc.customCost.compute == nil { - panic(fmt.Sprintf("CustomCost exists without a non-nil compute function pointer value %+v", lc)) + if cd.chunkCost == 0 && cd.chunkSize > 0 { + panic(fmt.Sprintf("no chunk cost with positive chunk size %+v", cd)) } - if lc.customCost != nil && lc.customCost.docCost == "" { - panic(fmt.Sprintf("CustomCost exists without a value for docCost %+v", lc)) + if cd.fn != nil && cd.doc == "" { + panic(fmt.Sprintf("Cost fn is used without adding doc %+v", cd)) } - return lc + return cd } -func (lc *linearCost) compute(stack []stackValue) int { - if lc.customCost != nil { - return lc.customCost.compute(stack, lc.depth) - } - cost := lc.baseCost - if lc.chunkCost != 0 && lc.chunkSize != 0 { +func (cd *costDescriptor) compute(stack []stackValue) int { + cost := cd.baseCost + if cd.chunkCost != 0 && cd.chunkSize != 0 { // Uses basics.DivCeil rather than (count/chunkSize) to match how Ethereum discretizes hashing costs. - count := len(stack[len(stack)-1-lc.depth].Bytes) - cost += lc.chunkCost * basics.DivCeil(count, lc.chunkSize) + count := len(stack[len(stack)-1-cd.depth].Bytes) + cost += cd.chunkCost * basics.DivCeil(count, cd.chunkSize) + } + if cd.fn != nil { + cost += cd.fn(stack, cd.depth) } return cost } -func (lc *linearCost) docCost(argLen int) string { - if lc.customCost != nil { - return lc.customCost.docCost +func (cd *costDescriptor) docCost(argLen int) string { + if cd.doc != "" { + return cd.doc } - if *lc == (linearCost{}) { + if cd.Empty() { return "" } - if lc.chunkCost == 0 { - return strconv.Itoa(lc.baseCost) + if cd.chunkCost == 0 { + return strconv.Itoa(cd.baseCost) } - idxFromStart := argLen - lc.depth - 1 + idxFromStart := argLen - cd.depth - 1 stackArg := rune(int('A') + idxFromStart) - if lc.chunkSize == 1 { - return fmt.Sprintf("%d + %d per byte of %c", lc.baseCost, lc.chunkCost, stackArg) + if cd.chunkSize == 1 { + return fmt.Sprintf("%d + %d per byte of %c", cd.baseCost, cd.chunkCost, stackArg) } - return fmt.Sprintf("%d + %d per %d bytes of %c", lc.baseCost, lc.chunkCost, lc.chunkSize, stackArg) + return fmt.Sprintf("%d + %d per %d bytes of %c", cd.baseCost, cd.chunkCost, cd.chunkSize, stackArg) } // OpDetails records details such as non-standard costs, immediate arguments, or @@ -191,16 +166,16 @@ type OpDetails struct { Modes RunMode // all modes that opcode can run in. i.e (cx.mode & Modes) != 0 allows - FullCost linearCost // if non-zero, the cost of the opcode, no immediates matter - Size int // if non-zero, the known size of opcode. if 0, check() determines. - Immediates []immediate // details of each immediate arg to opcode + FullCost costDescriptor // if non-zero, the cost of the opcode, no immediates matter + Size int // if non-zero, the known size of opcode. if 0, check() determines. + Immediates []immediate // details of each immediate arg to opcode trusted bool // if `trusted`, don't check stack effects. they are more complicated than simply checking the opcode prototype. } func (d *OpDetails) docCost(argLen int, version uint64) string { cost := d.FullCost.docCost(argLen) - if cost != "" { + if cost != "" { // costs must be based on the immediates return cost } found := false @@ -222,6 +197,9 @@ func (d *OpDetails) docCost(argLen int, version uint64) string { cost = strings.Join(fieldCostStrings, "; ") } } + if !found { + panic("top-level opcode had no cost, but found no field dependent costs") + } return cost } @@ -245,11 +223,11 @@ func (d *OpDetails) Cost(program []byte, pc int, stack []stackValue) int { } func detDefault() OpDetails { - return OpDetails{asmDefault, nil, nil, modeAny, linearCost{baseCost: 1}, 1, nil, false} + return OpDetails{asmDefault, nil, nil, modeAny, costDescriptor{baseCost: 1}, 1, nil, false} } func constants(asm asmFunc, checker checkFunc, name string, kind immKind) OpDetails { - return OpDetails{asm, checker, nil, modeAny, linearCost{baseCost: 1}, 0, []immediate{imm(name, kind)}, false} + return OpDetails{asm, checker, nil, modeAny, costDescriptor{baseCost: 1}, 0, []immediate{imm(name, kind)}, false} } func detBranch() OpDetails { @@ -286,16 +264,18 @@ func costly(cost int) OpDetails { } func (d OpDetails) costs(cost int) OpDetails { - d.FullCost = linearCost{baseCost: cost}.check() + d.FullCost = costDescriptor{baseCost: cost}.check() return d } -func defaultCustomCost(customCost *CustomCost) OpDetails { - return detDefault().customCost(customCost) +func costByFn(cost costFn, doc string) OpDetails { + d := detDefault() + d.FullCost = costDescriptor{fn: cost, doc: doc}.check() + return d } -func (d OpDetails) customCost(customCost *CustomCost) OpDetails { - d.FullCost = linearCost{customCost: customCost}.check() +func (d OpDetails) fnCost(cost costFn, doc string) OpDetails { + d.FullCost = costDescriptor{fn: cost, doc: doc}.check() return d } @@ -368,22 +348,22 @@ func costByField(immediate string, group *FieldGroup, costs []int) OpDetails { panic(fmt.Sprintf("While defining costs for %s in group %s: %d costs != %d names", immediate, group.Name, len(costs), len(group.Names))) } - fieldCosts := make([]linearCost, len(costs)) + fieldCosts := make([]costDescriptor, len(costs)) for i, cost := range costs { - fieldCosts[i] = linearCost{baseCost: cost} + fieldCosts[i] = costDescriptor{baseCost: cost} } return costByFieldAndLength(immediate, group, fieldCosts) } -func costByFieldAndLength(immediate string, group *FieldGroup, costs []linearCost) OpDetails { +func costByFieldAndLength(immediate string, group *FieldGroup, costs []costDescriptor) OpDetails { if len(costs) != len(group.Names) { panic(fmt.Sprintf("While defining costs for %s in group %s: %d costs != %d names", immediate, group.Name, len(costs), len(group.Names))) } opd := immediates(immediate) - opd.FullCost = linearCost{} // zero FullCost is what causes eval to look deeper + opd.FullCost = costDescriptor{} // zero FullCost is what causes eval to look deeper opd.Immediates[0].Group = group - full := make([]linearCost, 256) // ensure we have 256 entries for easy lookup + full := make([]costDescriptor, 256) // ensure we have 256 entries for easy lookup for i := range costs { full[i] = costs[i].check() } @@ -393,7 +373,7 @@ func costByFieldAndLength(immediate string, group *FieldGroup, costs []linearCos func costByLength(initial, perChunk, chunkSize, depth int) OpDetails { d := detDefault() - d.FullCost = linearCost{baseCost: initial, chunkCost: perChunk, chunkSize: chunkSize, depth: depth}.check() + d.FullCost = costDescriptor{baseCost: initial, chunkCost: perChunk, chunkSize: chunkSize, depth: depth}.check() return d } @@ -439,7 +419,7 @@ type immediate struct { Group *FieldGroup // If non-nil, always 256 long, so cost can be checked before eval - fieldCosts []linearCost + fieldCosts []costDescriptor } func imm(name string, kind immKind) immediate { @@ -739,7 +719,7 @@ var OpSpecs = []OpSpec{ /* Will end up following keccak256 - {0x98, "sha3_256", opSHA3_256, proto("b:b{32}"), ?, costByLength(...)},}, */ - {0x99, "bmodexp", opBytesModExp, proto("bbb:b"), 12, defaultCustomCost(bmodexpCustomCost)}, + {0x99, "bmodexp", opBytesModExp, proto("bbb:b"), 12, costByFn(bmodExpCost, "((len(B) * max(len(A), len(C)) ^ 1.63) / 15) + 200")}, // Byteslice math. {0xa0, "b+", opBytesPlus, proto("II:b"), 4, costly(10).typed(typeByteMath(maxByteMathSize + 1))}, @@ -805,7 +785,7 @@ var OpSpecs = []OpSpec{ BLS12_381g1: 2950, BLS12_381g2: 6530})}, {0xe2, "ec_pairing_check", opEcPairingCheck, proto("bb:T"), pairingVersion, - costByFieldAndLength("g", &EcGroups, []linearCost{ + costByFieldAndLength("g", &EcGroups, []costDescriptor{ BN254g1: { baseCost: 8000, chunkCost: 7_400, @@ -828,7 +808,7 @@ var OpSpecs = []OpSpec{ }})}, {0xe3, "ec_multi_scalar_mul", opEcMultiScalarMul, proto("bb:b"), pairingVersion, - costByFieldAndLength("g", &EcGroups, []linearCost{ + costByFieldAndLength("g", &EcGroups, []costDescriptor{ BN254g1: { baseCost: 3_600, chunkCost: 90, @@ -858,7 +838,7 @@ var OpSpecs = []OpSpec{ costByField("g", &EcGroups, []int{ BN254g1: 630, BN254g2: 3_300, BLS12_381g1: 1_950, BLS12_381g2: 8_150})}, - {0xe6, "mimc", opMimc, proto("b:b{32}"), mimcVersion, costByFieldAndLength("c", &MimcConfigs, []linearCost{ + {0xe6, "mimc", opMimc, proto("b:b{32}"), mimcVersion, costByFieldAndLength("c", &MimcConfigs, []costDescriptor{ BN254Mp110: { baseCost: 10, chunkCost: 550, diff --git a/data/transactions/logic/opcodes_test.go b/data/transactions/logic/opcodes_test.go index 86965d0887..bd08bee705 100644 --- a/data/transactions/logic/opcodes_test.go +++ b/data/transactions/logic/opcodes_test.go @@ -157,13 +157,23 @@ func TestOpcodesVersioningV2(t *testing.T) { require.Equal(t, cntv1, cntv0) require.Equal(t, 52, cntv1) + cdEqual := func(a, b costDescriptor) bool { + return a.baseCost == b.baseCost && + a.chunkCost == b.chunkCost && + a.chunkSize == b.chunkSize && + a.depth == b.depth && + reflect.ValueOf(a.fn).Pointer() == reflect.ValueOf(b.fn).Pointer() && + a.doc == b.doc + } + eqButVersion := func(a *OpSpec, b *OpSpec) (eq bool) { eq = a.Opcode == b.Opcode && a.Name == b.Name && reflect.ValueOf(a.op).Pointer() == reflect.ValueOf(b.op).Pointer() && reflect.ValueOf(a.asm).Pointer() == reflect.ValueOf(b.asm).Pointer() && reflect.DeepEqual(a.Arg, b.Arg) && reflect.DeepEqual(a.Return, b.Return) && a.Modes == b.Modes && - a.OpDetails.FullCost == b.OpDetails.FullCost && a.OpDetails.Size == b.OpDetails.Size && + cdEqual(a.FullCost, b.FullCost) && + a.OpDetails.Size == b.OpDetails.Size && reflect.ValueOf(a.OpDetails.check).Pointer() == reflect.ValueOf(b.OpDetails.check).Pointer() return } diff --git a/data/transactions/logic/pairing_test.go b/data/transactions/logic/pairing_test.go index c59a243ff4..6eb6d1627d 100644 --- a/data/transactions/logic/pairing_test.go +++ b/data/transactions/logic/pairing_test.go @@ -855,7 +855,7 @@ func TestLinearFieldCost(t *testing.T) { //nolint:paralleltest // manipulates op Name: "xxx", op: opPop, Proto: proto("a:"), - OpDetails: costByFieldAndLength("f", &EcGroups, []linearCost{{ + OpDetails: costByFieldAndLength("f", &EcGroups, []costDescriptor{{ baseCost: 1, chunkCost: 2, chunkSize: 2, diff --git a/data/transactions/logic/teal.tmLanguage.json b/data/transactions/logic/teal.tmLanguage.json index 4facb5cc88..4bd9a754a9 100644 --- a/data/transactions/logic/teal.tmLanguage.json +++ b/data/transactions/logic/teal.tmLanguage.json @@ -76,7 +76,7 @@ }, { "name": "keyword.operator.teal", - "match": "^(\\!|\\!\\=|%|\u0026|\u0026\u0026|\\*|\\+|\\-|/|\\\u003c|\\\u003c\\=|\\=\\=|\\\u003e|\\\u003e\\=|\\^|addw|bitlen|btoi|divmodw|divw|exp|expw|itob|mulw|shl|shr|sqrt|\\||\\|\\||\\~|b\\!\\=|b%|bmodexp|b\\*|b\\+|b\\-|b/|b\\\u003c|b\\\u003c\\=|b\\=\\=|b\\\u003e|b\\\u003e\\=|bsqrt|b\u0026|b\\^|b\\||b\\~|base64_decode|concat|extract|extract3|extract_uint16|extract_uint32|extract_uint64|getbit|getbyte|json_ref|len|replace2|replace3|setbit|setbyte|substring|substring3|ec_add|ec_map_to|ec_multi_scalar_mul|ec_pairing_check|ec_scalar_mul|ec_subgroup_check|ecdsa_pk_decompress|ecdsa_pk_recover|ecdsa_verify|ed25519verify|ed25519verify_bare|falcon_verify|keccak256|mimc|sha256|sha3_256|sha512_256|sumhash512|vrf_verify|gitxn|gitxna|gitxnas|itxn|itxn_begin|itxn_field|itxn_next|itxn_submit|itxna|itxnas)\\b" + "match": "^(\\!|\\!\\=|%|\u0026|\u0026\u0026|\\*|\\+|\\-|/|\\\u003c|\\\u003c\\=|\\=\\=|\\\u003e|\\\u003e\\=|\\^|addw|bitlen|btoi|divmodw|divw|exp|expw|itob|mulw|shl|shr|sqrt|\\||\\|\\||\\~|b\\!\\=|b%|b\\*|b\\+|b\\-|b/|b\\\u003c|b\\\u003c\\=|b\\=\\=|b\\\u003e|b\\\u003e\\=|bmodexp|bsqrt|b\u0026|b\\^|b\\||b\\~|base64_decode|concat|extract|extract3|extract_uint16|extract_uint32|extract_uint64|getbit|getbyte|json_ref|len|replace2|replace3|setbit|setbyte|substring|substring3|ec_add|ec_map_to|ec_multi_scalar_mul|ec_pairing_check|ec_scalar_mul|ec_subgroup_check|ecdsa_pk_decompress|ecdsa_pk_recover|ecdsa_verify|ed25519verify|ed25519verify_bare|falcon_verify|keccak256|sha256|sha3_256|sha512_256|sumhash512|vrf_verify|gitxn|gitxna|gitxnas|itxn|itxn_begin|itxn_field|itxn_next|itxn_submit|itxna|itxnas)\\b" } ] }, @@ -111,8 +111,7 @@ "match": "\\b(?\u003c=byte\\s+)(0x[0-9]+)\\b" }, { - "name": "variable.parameter.teal", - "match": "\\b(unknown|pay|keyreg|acfg|axfer|afrz|appl|NoOp|OptIn|CloseOut|ClearState|UpdateApplication|DeleteApplication|Secp256k1|Secp256r1|Sender|Fee|FirstValid|FirstValidTime|LastValid|Note|Lease|Receiver|Amount|CloseRemainderTo|VotePK|SelectionPK|VoteFirst|VoteLast|VoteKeyDilution|Type|TypeEnum|XferAsset|AssetAmount|AssetSender|AssetReceiver|AssetCloseTo|GroupIndex|TxID|ApplicationID|OnCompletion|NumAppArgs|NumAccounts|ApprovalProgram|ClearStateProgram|RekeyTo|ConfigAsset|ConfigAssetTotal|ConfigAssetDecimals|ConfigAssetDefaultFrozen|ConfigAssetUnitName|ConfigAssetName|ConfigAssetURL|ConfigAssetMetadataHash|ConfigAssetManager|ConfigAssetReserve|ConfigAssetFreeze|ConfigAssetClawback|FreezeAsset|FreezeAssetAccount|FreezeAssetFrozen|NumAssets|NumApplications|GlobalNumUint|GlobalNumByteSlice|LocalNumUint|LocalNumByteSlice|ExtraProgramPages|Nonparticipation|NumLogs|CreatedAssetID|CreatedApplicationID|LastLog|StateProofPK|NumApprovalProgramPages|NumClearStateProgramPages|MinTxnFee|MinBalance|MaxTxnLife|ZeroAddress|GroupSize|LogicSigVersion|Round|LatestTimestamp|CurrentApplicationID|CreatorAddress|CurrentApplicationAddress|GroupID|OpcodeBudget|CallerApplicationID|CallerApplicationAddress|AssetCreateMinBalance|AssetOptInMinBalance|GenesisHash|PayoutsEnabled|PayoutsGoOnlineFee|PayoutsPercent|PayoutsMinBalance|PayoutsMaxBalance|ApplicationArgs|Accounts|Assets|Applications|Logs|ApprovalProgramPages|ClearStateProgramPages|URLEncoding|StdEncoding|JSONString|JSONUint64|JSONObject|AssetBalance|AssetFrozen|AssetTotal|AssetDecimals|AssetDefaultFrozen|AssetUnitName|AssetName|AssetURL|AssetMetadataHash|AssetManager|AssetReserve|AssetFreeze|AssetClawback|AssetCreator|AppApprovalProgram|AppClearStateProgram|AppGlobalNumUint|AppGlobalNumByteSlice|AppLocalNumUint|AppLocalNumByteSlice|AppExtraProgramPages|AppCreator|AppAddress|AcctBalance|AcctMinBalance|AcctAuthAddr|AcctTotalNumUint|AcctTotalNumByteSlice|AcctTotalExtraAppPages|AcctTotalAppsCreated|AcctTotalAppsOptedIn|AcctTotalAssetsCreated|AcctTotalAssets|AcctTotalBoxes|AcctTotalBoxBytes|AcctIncentiveEligible|AcctLastProposed|AcctLastHeartbeat|VoterBalance|VoterIncentiveEligible|VrfAlgorand|BlkSeed|BlkTimestamp|BlkProposer|BlkFeesCollected|BlkBonus|BlkBranch|BlkFeeSink|BlkProtocol|BlkTxnCounter|BlkProposerPayout|BN254g1|BN254g2|BLS12_381g1|BLS12_381g2|BN254Mp110|BLS12_381Mp111)\\b" + "name": "variable.parameter.teal" } ] }, From 23d7b26875d2258166ef901cd3e73381bcda2ba2 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 5 Feb 2025 14:13:40 -0500 Subject: [PATCH 8/9] all bytes math is big, need costs --- data/transactions/logic/README.md | 7 +- data/transactions/logic/eval.go | 78 +++++-- data/transactions/logic/eval_test.go | 219 ++++++++++++------- data/transactions/logic/opcodes.go | 22 +- data/transactions/logic/teal.tmLanguage.json | 5 +- 5 files changed, 225 insertions(+), 106 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index e64e8f2ab5..8f5fbee163 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -445,7 +445,6 @@ bytes on outputs. | `b!=` | 0 if A is equal to B, else 1. A and B are interpreted as big-endian unsigned integers | | `b%` | A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero. | | `bsqrt` | The largest integer I such that I^2 <= A. A and I are interpreted as big-endian unsigned integers | -| `bmodexp` | A raised to the Bth power modulo C. A, B and C are interpreted as big-endian unsigned integers limited to 4096 bytes. Fail if C is zero. | These opcodes operate on the bits of byte-array values. The shorter input array is interpreted as though left padded with zeros until it is the @@ -468,8 +467,6 @@ these results may contain leading zero bytes. | `keccak256` | Keccak256 hash of value A, yields [32]byte | | `sha512_256` | SHA512_256 hash of value A, yields [32]byte | | `sha3_256` | SHA3_256 hash of value A, yields [32]byte | -| `sumhash512` | sumhash512 of value A, yields [64]byte | -| `falcon_verify` | for (data A, compressed-format signature B, pubkey C) verify the signature of data against the pubkey | | `ed25519verify` | for (data A, signature B, pubkey C) verify the signature of ("ProgData" \|\| program_hash \|\| data) against the pubkey => {0 or 1} | | `ed25519verify_bare` | for (data A, signature B, pubkey C) verify the signature of the data against the pubkey => {0 or 1} | | `ecdsa_verify v` | for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} | @@ -642,8 +639,8 @@ Global fields are fields that are common to all the transactions in the group. I | 18 | PayoutsEnabled | bool | v11 | Whether block proposal payouts are enabled. | | 19 | PayoutsGoOnlineFee | uint64 | v11 | The fee required in a keyreg transaction to make an account incentive eligible. | | 20 | PayoutsPercent | uint64 | v11 | The percentage of transaction fees in a block that can be paid to the block proposer. | -| 21 | PayoutsMinBalance | uint64 | v11 | The minimum algo balance an account must have in the agreement round to receive block payouts in the proposal round. | -| 22 | PayoutsMaxBalance | uint64 | v11 | The maximum algo balance an account can have in the agreement round to receive block payouts in the proposal round. | +| 21 | PayoutsMinBalance | uint64 | v11 | The minimum balance an account must have in the agreement round to receive block payouts in the proposal round. | +| 22 | PayoutsMaxBalance | uint64 | v11 | The maximum balance an account can have in the agreement round to receive block payouts in the proposal round. | **Asset Fields** diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 31466f7ccd..3cd4cfce73 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -51,9 +51,9 @@ const maxStringSize = 4096 // maxByteMathSize is the limit of byte strings supplied as input to byte math opcodes const maxByteMathSize = 64 -// bigByteCmpVersion is the first version for which operators like `b==` and +// fullByteMathVersion is the first version for which operators like `b==` and // `b<` are unconstrained by maxByteMathSize -const bigByteCmpVersion = 12 +const fullByteMathVersion = 12 // maxLogSize is the limit of total log size from n log calls in a program const maxLogSize = config.MaxEvalDeltaTotalLogSize @@ -2221,13 +2221,47 @@ func opBytesBinOp(cx *EvalContext, result *big.Int, op func(x, y *big.Int) *big. return nil } +// opBytesFullBinOp is like opBytesBinOp but it allows any size inputs (and +// therefore must check the size of the output). +func opBytesFullBinOp(cx *EvalContext, result *big.Int, op func(x, y *big.Int) *big.Int) error { + last := len(cx.Stack) - 1 + prev := last - 1 + + rhs := new(big.Int).SetBytes(cx.Stack[last].Bytes) + lhs := new(big.Int).SetBytes(cx.Stack[prev].Bytes) + op(lhs, rhs) // op's receiver has already been bound to result + if result.Sign() < 0 { + return errors.New("byte math would have negative result") + } + cx.Stack[prev].Bytes = result.Bytes() + /* + if len(cx.Stack[last].Bytes) > maxStringSize { + return fmt.Errorf("byte math results would exceed %d bytes", maxStringSize) + } + */ + cx.Stack = cx.Stack[:last] + return nil +} + func opBytesPlus(cx *EvalContext) error { result := new(big.Int) + if cx.version >= fullByteMathVersion { + return opBytesFullBinOp(cx, result, result.Add) + } return opBytesBinOp(cx, result, result.Add) } +func bplusCost(stack []stackValue) int { + last := len(stack) - 1 + prev := last - 1 + return 8 + max(len(stack[last].Bytes), len(stack[prev].Bytes)) +} + func opBytesMinus(cx *EvalContext) error { result := new(big.Int) + if cx.version >= fullByteMathVersion { + return opBytesFullBinOp(cx, result, result.Sub) + } return opBytesBinOp(cx, result, result.Sub) } @@ -2241,7 +2275,12 @@ func opBytesDiv(cx *EvalContext) error { } return result.Div(x, y) } - err := opBytesBinOp(cx, result, checkDiv) + var err error + if cx.version >= fullByteMathVersion { + err = opBytesFullBinOp(cx, result, checkDiv) + } else { + err = opBytesBinOp(cx, result, checkDiv) + } if err != nil { return err } @@ -2250,14 +2289,19 @@ func opBytesDiv(cx *EvalContext) error { func opBytesMul(cx *EvalContext) error { result := new(big.Int) - return opBytesBinOp(cx, result, result.Mul) + if cx.version < fullByteMathVersion { + return opBytesBinOp(cx, result, result.Mul) + } + return opBytesFullBinOp(cx, result, result.Mul) } func opBytesSqrt(cx *EvalContext) error { last := len(cx.Stack) - 1 - if len(cx.Stack[last].Bytes) > maxByteMathSize { - return errors.New("math attempted on large byte-array") + if cx.version < fullByteMathVersion { + if len(cx.Stack[last].Bytes) > maxByteMathSize { + return errors.New("math attempted on large byte-array") + } } val := new(big.Int).SetBytes(cx.Stack[last].Bytes) @@ -2279,7 +2323,7 @@ func opBytesLt(cx *EvalContext) error { last := len(cx.Stack) - 1 prev := last - 1 - if cx.version < bigByteCmpVersion { + if cx.version < fullByteMathVersion { if len(cx.Stack[last].Bytes) > maxByteMathSize || len(cx.Stack[prev].Bytes) > maxByteMathSize { return errors.New("math attempted on large byte-array") } @@ -2326,7 +2370,7 @@ func opBytesEq(cx *EvalContext) error { last := len(cx.Stack) - 1 prev := last - 1 - if cx.version < bigByteCmpVersion { + if cx.version < fullByteMathVersion { if len(cx.Stack[last].Bytes) > maxByteMathSize || len(cx.Stack[prev].Bytes) > maxByteMathSize { return errors.New("math attempted on large byte-array") } @@ -2368,10 +2412,10 @@ func opBytesModExp(cx *EvalContext) error { return nil } -func bmodExpCost(stack []stackValue, depth int) int { - last := len(stack) - depth - 1 // mod - prev := last - depth - 1 // exp - pprev := last - depth - 2 // base +func bmodexpCost(stack []stackValue) int { + last := len(stack) - 1 // mod + prev := last - 1 // exp + pprev := last - 2 // base // Empirically estimated cost function constants const ( @@ -2400,7 +2444,12 @@ func opBytesModulo(cx *EvalContext) error { } return result.Mod(x, y) } - err := opBytesBinOp(cx, result, checkMod) + var err error + if cx.version >= fullByteMathVersion { + err = opBytesFullBinOp(cx, result, checkMod) + } else { + err = opBytesBinOp(cx, result, checkMod) + } if err != nil { return err } @@ -5275,6 +5324,9 @@ func (cx *EvalContext) availableAsset(aid basics.AssetIndex) bool { return true } } + if slices.Contains(cx.txn.Txn.ForeignAssets, aid) { + return true + } // or was created in group if cx.version >= createdResourcesVersion { if _, ok := cx.available.createdAsas[aid]; ok { diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 1e28dd08f7..b4168c4667 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -3980,80 +3980,136 @@ main: } } -func BenchmarkByteLogic(b *testing.B) { - e64 := "byte 0x8090a0b0c0d0e0f0;" - o64 := "byte 0x1020304050607080;" - hex128e := "90a0b0c0d0e0f0001020304050607080" - hex128o := "102030405060708090a0b0c0d0e0f000" - e128 := "byte 0x" + strings.Repeat(hex128e, 1) + ";" - o128 := "byte 0x" + strings.Repeat(hex128o, 1) + ";" - e256 := "byte 0x" + strings.Repeat(hex128e, 2) + ";" - o256 := "byte 0x" + strings.Repeat(hex128o, 2) + ";" - e512 := "byte 0x" + strings.Repeat(hex128e, 4) + ";" - o512 := "byte 0x" + strings.Repeat(hex128o, 4) + ";" +// bigint multiply ought to have a weird cost function because Karatsuba kicks at 40 Words (320 bytes) +func BenchmarkBytesMul(b *testing.B) { + for i := range 32 { + size := i * 64 + b.Run(fmt.Sprintf("b* %d", size), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", "byte "+randBytes(size)+"; byte "+randBytes(size)+";b*;pop", "int 1") + }) + } +} - benches := [][]string{ - {"b& 8", "", e64 + o64 + "b&; pop", "int 1"}, - {"b| 8", "", e64 + o64 + "b|; pop", "int 1"}, - {"b^ 8", "", e64 + o64 + "b^; pop", "int 1"}, - {"b~ 8", e64, "b~", "pop; int 1"}, - - {"b& 16", "", e128 + o128 + "b&; pop", "int 1"}, - {"b| 16", "", e128 + o128 + "b|; pop", "int 1"}, - {"b^ 16", "", e128 + o128 + "b^; pop", "int 1"}, - {"b~ 16", e128, "b~", "pop; int 1"}, - - {"b& 32", "", e256 + o256 + "b&; pop", "int 1"}, - {"b| 32", "", e256 + o256 + "b|; pop", "int 1"}, - {"b^ 32", "", e256 + o256 + "b^; pop", "int 1"}, - {"b~ 32", e256, "b~", "pop; int 1"}, - - {"b& 64", "", e512 + o512 + "b&; pop", "int 1"}, - {"b| 64", "", e512 + o512 + "b|; pop", "int 1"}, - {"b^ 64", "", e512 + o512 + "b^; pop", "int 1"}, - {"b~ 64", e512, "b~", "pop; int 1"}, +func BenchmarkBytesSqrt(b *testing.B) { + for i := range 64 { + size := i * 64 + b.Run(fmt.Sprintf("bqsrt %d", size), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", "byte "+randBytes(size)+";bsqrt;pop", "int 1") + }) } - for _, bench := range benches { - b.Run(bench[0], func(b *testing.B) { +} + +func BenchmarkBytesMulDifferingSizes(b *testing.B) { + for awords := 1; awords <= 40; awords++ { + product := 32 * 32 + bwords := product / awords + if bwords > 40 { + continue + } + b.Run(fmt.Sprintf("b* %d-%d", awords, bwords), func(b *testing.B) { b.ReportAllocs() - benchmarkOperation(b, bench[1], bench[2], bench[3]) + benchmarkOperation(b, "", "byte "+randBytes(8*awords)+"; byte "+randBytes(8*bwords)+";b*;pop", "int 1") }) } } -func BenchmarkByteMath(b *testing.B) { - u64 := "byte 0x8090a0b0c0d0e0f0;" - hex128 := "102030405060708090a0b0c0d0e0f000" - u128 := "byte 0x" + strings.Repeat(hex128, 1) + ";" - u256 := "byte 0x" + strings.Repeat(hex128, 2) + ";" - u512 := "byte 0x" + strings.Repeat(hex128, 4) + ";" +func BenchmarkFindWorstBytesDiv(b *testing.B) { + for divisor := 4096; divisor > 0; divisor -= 128 { + b.Run(fmt.Sprintf("b/ %d", divisor), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", "byte "+randBytes(4096)+"; byte "+randBytes(divisor)+";b/;pop", "int 1") + }) + } +} +func BenchmarkFindWorstBytesMod(b *testing.B) { + for _, big := range []int{128, 512, 2096} { + for divisor := big; divisor > 0; divisor -= (big / 10) { + b.Run(fmt.Sprintf("b%% %d %d", big, divisor), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", "byte "+randBytes(big)+"; byte "+randBytes(divisor)+";b%;pop", "int 1") + }) + } + } +} + +func BenchmarkBinaryByteLogic(b *testing.B) { + for i := range 16 { + size := i * 64 + for _, op := range []string{"b&", "b|", "b^"} { + b.Run(fmt.Sprintf("%s %d", op, size), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", "byte "+randBytes(size)+"; byte "+randBytes(size)+";"+op+";pop", "int 1") + }) + } + } +} + +func BenchmarkByteNot(b *testing.B) { + for i := range 16 { + size := i * 64 + b.Run(fmt.Sprintf("b~ %d", size), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "byte "+randBytes(size), "b~", "pop; int 1") + }) + } +} + +// randBytes generates a random byte constant of length `length` +func randBytes(length int) string { + bytes := make([]byte, length) + rand.Read(bytes) + return fmt.Sprintf("0x%x", bytes) +} + +// chunk emits one `byte` statement creating a byte string with `count` (64 bit) words +func chunk(count int) string { + buf := make([]byte, 8*count) + rand.Read(buf) + word := "7090a0b0c0d0e0f0" // enough hex for one 64bit word, small enough to add without overflow + return "byte 0x" + strings.Repeat(word, count) + ";" +} + +func BenchmarkByteMath(b *testing.B) { benches := [][]string{ - {"bytec", u128 + "pop"}, + {"bytec", chunk(2) + "pop"}, - {"b+ 128", u128 + u128 + "b+; pop"}, - {"b- 128", u128 + u128 + "b-; pop"}, - {"b* 128", u128 + u128 + "b*; pop"}, + {"b+ 1w", chunk(1) + chunk(1) + "b+; pop"}, + {"b- 1w", chunk(1) + chunk(1) + "b-; pop"}, + {"b* 1w", chunk(1) + chunk(1) + "b*; pop"}, // half sized divisor seems pessimal for / and % - {"b/ 128", u128 + u64 + "b/; pop"}, - {"b% 128", u128 + u64 + "b%; pop"}, - {"bsqrt 128", u128 + "bsqrt; pop"}, - - {"b+ 256", u256 + u256 + "b+; pop"}, - {"b- 256", u256 + u256 + "b-; pop"}, - {"b* 256", u256 + u256 + "b*; pop"}, - {"b/ 256", u256 + u128 + "b/; pop"}, - {"b% 256", u256 + u128 + "b%; pop"}, - {"bsqrt 256", u256 + "bsqrt; pop"}, - - {"b+ 512", u512 + u512 + "b+; pop"}, - {"b- 512", u512 + u512 + "b-; pop"}, - {"b* 512", u512 + u512 + "b*; pop"}, - {"b/ 512", u512 + u256 + "b/; pop"}, - {"b% 512", u512 + u256 + "b%; pop"}, - {"bsqrt 512", u512 + "bsqrt; pop"}, - - {"bytec recheck", u128 + "pop"}, + {"b/ 1w", chunk(1) + "byte 0x8090a0b0;" + "b/; pop"}, + {"b% 1w", chunk(1) + "byte 0x8090a0b0;" + "b%; pop"}, + {"bsqrt 1w", chunk(1) + "bsqrt; pop"}, + + // maximum sizes, pre fullByteMathVersion + {"b+ 8w", chunk(8) + chunk(8) + "b+; pop"}, + {"b- 8w", chunk(8) + chunk(8) + "b-; pop"}, + {"b+ 8w4w", chunk(8) + chunk(4) + "b+; pop"}, + {"b- 8w4w", chunk(8) + chunk(4) + "b-; pop"}, + {"b* 8w", chunk(8) + chunk(8) + "b*; pop"}, + {"b/ 8w", chunk(8) + chunk(4) + "b/; pop"}, + {"b% 8w", chunk(8) + chunk(4) + "b%; pop"}, + {"bsqrt 8w", chunk(8) + "bsqrt; pop"}, + + {"b+ 16w", chunk(16) + chunk(16) + "b+; pop"}, + {"b- 16w", chunk(16) + chunk(16) + "b-; pop"}, + {"b+ 16w8w", chunk(16) + chunk(8) + "b+; pop"}, + {"b- 18w8w", chunk(16) + chunk(8) + "b-; pop"}, + + {"b+ 64w", chunk(64) + chunk(64) + "b+; pop"}, + {"b- 64w", chunk(64) + chunk(64) + "b-; pop"}, + {"b+ 64w32w", chunk(64) + chunk(32) + "b+; pop"}, + {"b- 64w32w", chunk(64) + chunk(32) + "b-; pop"}, + + {"b+ 512w", chunk(512) + chunk(512) + "b+; pop"}, + {"b- 512w", chunk(512) + chunk(512) + "b-; pop"}, + {"b+ 512w1w", chunk(512) + chunk(1) + "b+; pop"}, + {"b+ 1w512w", chunk(1) + chunk(512) + "b+; pop"}, + {"b- 512w1w", chunk(512) + chunk(1) + "b-; pop"}, + {"bytec recheck", chunk(2) + "pop"}, } for _, bench := range benches { b.Run(bench[0], func(b *testing.B) { @@ -4069,7 +4125,7 @@ func BenchmarkByteCompare(b *testing.B) { u128 := "byte 0x" + strings.Repeat(hex128, 1) + ";" u256 := "byte 0x" + strings.Repeat(hex128, 2) + ";" u512 := "byte 0x" + strings.Repeat(hex128, 4) + ";" - //u4k := "byte 0x" + strings.Repeat(hex128, 256) + ";" + u4k := "byte 0x" + strings.Repeat(hex128, 256) + ";" benches := [][]string{ {"b== 64", u64 + u64 + "b==; pop"}, @@ -4087,9 +4143,9 @@ func BenchmarkByteCompare(b *testing.B) { // These can only be run with the maxByteMathSize check removed. They // show that we can remove that check in a later AVM version, as there // is no appreciable cost to even a 4k compare. - // {"b== 4k", u4k + u4k + "b==; pop"}, - // {"b< 4k", u4k + u4k + "b<; pop"}, - // {"b<= 4k", u4k + u4k + "b<=; pop"}, + {"b== 4k", u4k + u4k + "b==; pop"}, + {"b< 4k", u4k + u4k + "b<; pop"}, + {"b<= 4k", u4k + u4k + "b<=; pop"}, } for _, bench := range benches { b.Run(bench[0], func(b *testing.B) { @@ -5132,13 +5188,6 @@ func BenchmarkBytesModExp(b *testing.B) { Name string } - // Function to generate a random hex string of a specified length in bytes - generateRandomHexString := func(length int) string { - bytes := make([]byte, length) - rand.Read(bytes) - return fmt.Sprintf("0x%x", bytes) - } - // Define the accepted test vectors using nested loops modexpTestVectors := []ModexpTestVector{} incr := 128 @@ -5148,21 +5197,21 @@ func BenchmarkBytesModExp(b *testing.B) { for modLen := incr; modLen <= maxDim; modLen += incr { modexpTestVectors = append(modexpTestVectors, ModexpTestVector{ Name: fmt.Sprintf(`TestVector_Dim(%d,%d,%d)`, baseLen, expLen, modLen), - Base: generateRandomHexString(baseLen), - Exponent: generateRandomHexString(expLen), - Modulus: generateRandomHexString(modLen), + Base: randBytes(baseLen), + Exponent: randBytes(expLen), + Modulus: randBytes(modLen), }) } } } b.Run("bmod_cost", func(b *testing.B) { b.ReportAllocs() - progText := fmt.Sprintf(`byte %s; byte %s;`, generateRandomHexString(64), generateRandomHexString(64)) + " b%; pop" + progText := fmt.Sprintf(`byte %s; byte %s;`, randBytes(64), randBytes(64)) + " b%; pop" benchmarkOperation(b, "", progText, "int 1") }) b.Run("max_bmodexp_cost", func(b *testing.B) { b.ReportAllocs() - progText := fmt.Sprintf(`byte %s; byte %s; byte %s; bmodexp; pop`, generateRandomHexString(4096), generateRandomHexString(4096), generateRandomHexString(4096)) + progText := fmt.Sprintf(`byte %s; byte %s; byte %s; bmodexp; pop`, randBytes(4096), randBytes(4096), randBytes(4096)) benchmarkOperation(b, "", progText, "int 1") }) // Iterate through the test vectors and benchmark the bmodexp computation @@ -5253,8 +5302,12 @@ func TestBytesMath(t *testing.T) { effs := strings.Repeat("ff", 64) // 64 byte long inputs are accepted, even if they produce longer outputs testAccepts(t, fmt.Sprintf("byte 0x%s; byte 0x10; b+; len; int 65; ==", effs), 4) - // 65 byte inputs are not ok. - testPanics(t, NoTrack(fmt.Sprintf("byte 0x%s00; byte 0x10; b-; len; int 65; ==", effs)), 4) + // 4096 byte inputs that sum to 4097 byte outputs panic, no matter the version. + testPanics(t, notrack(fmt.Sprintf("byte 0x%s; byte 0x10; b+; len; int 4097; ==", strings.Repeat("ff", 4096))), 4) + // 65 byte inputs are not ok until v12. + p := fmt.Sprintf("byte 0x%s00; byte 0x10; b-; len; int 65; ==", effs) + testPanicRange(t, notrack(p), 4, 11) + testAcceptRange(t, p, 12, AssemblerMaxVersion) testAccepts(t, `byte 0x01; byte 0x01; b-; byte ""; ==`, 4) testAccepts(t, "byte 0x0200; byte 0x01; b-; byte 0x01FF; ==", 4) @@ -5279,7 +5332,7 @@ func TestBytesMath(t *testing.T) { testAccepts(t, "byte 0x10; bsqrt; byte 0x04; ==; return", 6) testAccepts(t, "byte 0x11; bsqrt; byte 0x04; ==; return", 6) testAccepts(t, "byte 0xffffff; bsqrt; len; int 2; ==; return", 6) - // 64 byte long inputs are accepted, even if they produce longer outputs + // 64 byte long inputs are accepted testAccepts(t, fmt.Sprintf("byte 0x%s; bsqrt; len; int 32; ==", effs), 6) // 65 byte inputs are not ok (no track allows assembly) testPanics(t, notrack(fmt.Sprintf("byte 0x%s00; bsqrt; pop; int 1", effs)), 6) @@ -5375,7 +5428,7 @@ func TestBytesBits(t *testing.T) { testAccepts(t, "int 33; bzero; byte 0x000000000000000000000000000000000000000000000000000000000000000000; ==", 4) testAccepts(t, "int 4096; bzero; len; int 4096; ==", 4) - testPanics(t, NoTrack("int 4097; bzero; len; int 4097; =="), 4) + testPanics(t, notrack("int 4097; bzero; len; int 4097; =="), 4) } func TestBytesConversions(t *testing.T) { diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index ebe3b76989..e8fc2f6b4a 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -86,7 +86,7 @@ const spOpcodesVersion = 12 // falcon_verify, sumhash512 // Unlimited Global Storage opcodes const boxVersion = 8 // box_* -type costFn func(stack []stackValue, depth int) int +type costFn func(stack []stackValue) int type costDescriptor struct { baseCost int chunkCost int @@ -132,7 +132,7 @@ func (cd *costDescriptor) compute(stack []stackValue) int { cost += cd.chunkCost * basics.DivCeil(count, cd.chunkSize) } if cd.fn != nil { - cost += cd.fn(stack, cd.depth) + cost += cd.fn(stack) } return cost } @@ -714,29 +714,45 @@ var OpSpecs = []OpSpec{ {0x94, "exp", opExp, proto("ii:i"), 4, detDefault()}, {0x95, "expw", opExpw, proto("ii:ii"), 4, costly(10)}, {0x96, "bsqrt", opBytesSqrt, proto("I:I"), 6, costly(40)}, + {0x96, "bsqrt", opBytesSqrt, proto("b:b"), fullByteMathVersion, costly(222)}, {0x97, "divw", opDivw, proto("iii:i"), 6, detDefault()}, {0x98, "sha3_256", opSHA3_256, proto("b:b{32}"), 7, costly(130)}, /* Will end up following keccak256 - {0x98, "sha3_256", opSHA3_256, proto("b:b{32}"), ?, costByLength(...)},}, */ - {0x99, "bmodexp", opBytesModExp, proto("bbb:b"), 12, costByFn(bmodExpCost, "((len(B) * max(len(A), len(C)) ^ 1.63) / 15) + 200")}, + {0x99, "bmodexp", opBytesModExp, proto("bbb:b"), 12, costByFn(bmodexpCost, "((len(B) * max(len(A), len(C)) ^ 1.63) / 15) + 200")}, // Byteslice math. {0xa0, "b+", opBytesPlus, proto("II:b"), 4, costly(10).typed(typeByteMath(maxByteMathSize + 1))}, + {0xa0, "b+", opBytesPlus, proto("bb:b"), fullByteMathVersion, costByFn(bplusCost, "8 + max(len(A), len(B))//16")}, {0xa1, "b-", opBytesMinus, proto("II:I"), 4, costly(10)}, + {0xa1, "b-", opBytesMinus, proto("bb:b"), fullByteMathVersion, costByLength(8, 1, 16, 1)}, {0xa2, "b/", opBytesDiv, proto("II:I"), 4, costly(20)}, + {0xa2, "b/", opBytesDiv, proto("bb:b"), fullByteMathVersion, costly(222)}, {0xa3, "b*", opBytesMul, proto("II:b"), 4, costly(20).typed(typeByteMath(maxByteMathSize * 2))}, + {0xa3, "b*", opBytesMul, proto("bb:b"), fullByteMathVersion, costly(222)}, {0xa4, "b<", opBytesLt, proto("II:T"), 4, detDefault()}, + {0xa4, "b<", opBytesLt, proto("bb:T"), fullByteMathVersion, detDefault()}, {0xa5, "b>", opBytesGt, proto("II:T"), 4, detDefault()}, + {0xa5, "b>", opBytesGt, proto("bb:T"), fullByteMathVersion, detDefault()}, {0xa6, "b<=", opBytesLe, proto("II:T"), 4, detDefault()}, + {0xa6, "b<=", opBytesLe, proto("bb:T"), fullByteMathVersion, detDefault()}, {0xa7, "b>=", opBytesGe, proto("II:T"), 4, detDefault()}, + {0xa7, "b>=", opBytesGe, proto("bb:T"), fullByteMathVersion, detDefault()}, {0xa8, "b==", opBytesEq, proto("II:T"), 4, detDefault()}, + {0xa8, "b==", opBytesEq, proto("bb:T"), fullByteMathVersion, detDefault()}, {0xa9, "b!=", opBytesNeq, proto("II:T"), 4, detDefault()}, + {0xa9, "b!=", opBytesNeq, proto("bb:T"), fullByteMathVersion, detDefault()}, {0xaa, "b%", opBytesModulo, proto("II:I"), 4, costly(20)}, + {0xaa, "b%", opBytesModulo, proto("bb:b"), fullByteMathVersion, costly(222)}, {0xab, "b|", opBytesBitOr, proto("bb:b"), 4, costly(6)}, + {0xab, "b|", opBytesBitOr, proto("bb:b"), fullByteMathVersion, costByLength(1, 1, 32, 0)}, {0xac, "b&", opBytesBitAnd, proto("bb:b"), 4, costly(6)}, + {0xac, "b&", opBytesBitAnd, proto("bb:b"), fullByteMathVersion, costByLength(1, 1, 32, 0)}, {0xad, "b^", opBytesBitXor, proto("bb:b"), 4, costly(6)}, + {0xad, "b^", opBytesBitXor, proto("bb:b"), fullByteMathVersion, costByLength(1, 1, 32, 0)}, {0xae, "b~", opBytesBitNot, proto("b:b"), 4, costly(4)}, + {0xae, "b~", opBytesBitNot, proto("b:b"), fullByteMathVersion, costByLength(1, 1, 32, 0)}, {0xaf, "bzero", opBytesZero, proto("i:b"), 4, detDefault().typed(typeBzero)}, // AVM "effects" diff --git a/data/transactions/logic/teal.tmLanguage.json b/data/transactions/logic/teal.tmLanguage.json index 4bd9a754a9..5ad52903b6 100644 --- a/data/transactions/logic/teal.tmLanguage.json +++ b/data/transactions/logic/teal.tmLanguage.json @@ -76,7 +76,7 @@ }, { "name": "keyword.operator.teal", - "match": "^(\\!|\\!\\=|%|\u0026|\u0026\u0026|\\*|\\+|\\-|/|\\\u003c|\\\u003c\\=|\\=\\=|\\\u003e|\\\u003e\\=|\\^|addw|bitlen|btoi|divmodw|divw|exp|expw|itob|mulw|shl|shr|sqrt|\\||\\|\\||\\~|b\\!\\=|b%|b\\*|b\\+|b\\-|b/|b\\\u003c|b\\\u003c\\=|b\\=\\=|b\\\u003e|b\\\u003e\\=|bmodexp|bsqrt|b\u0026|b\\^|b\\||b\\~|base64_decode|concat|extract|extract3|extract_uint16|extract_uint32|extract_uint64|getbit|getbyte|json_ref|len|replace2|replace3|setbit|setbyte|substring|substring3|ec_add|ec_map_to|ec_multi_scalar_mul|ec_pairing_check|ec_scalar_mul|ec_subgroup_check|ecdsa_pk_decompress|ecdsa_pk_recover|ecdsa_verify|ed25519verify|ed25519verify_bare|falcon_verify|keccak256|sha256|sha3_256|sha512_256|sumhash512|vrf_verify|gitxn|gitxna|gitxnas|itxn|itxn_begin|itxn_field|itxn_next|itxn_submit|itxna|itxnas)\\b" + "match": "^(\\!|\\!\\=|%|\u0026|\u0026\u0026|\\*|\\+|\\-|/|\\\u003c|\\\u003c\\=|\\=\\=|\\\u003e|\\\u003e\\=|\\^|addw|bitlen|btoi|divmodw|divw|exp|expw|itob|mulw|shl|shr|sqrt|\\||\\|\\||\\~|b\\!\\=|b%|b\\*|b\\+|b\\-|b/|b\\\u003c|b\\\u003c\\=|b\\=\\=|b\\\u003e|b\\\u003e\\=|bmodexp|bsqrt|b\u0026|b\\^|b\\||b\\~|base64_decode|concat|extract|extract3|extract_uint16|extract_uint32|extract_uint64|getbit|getbyte|json_ref|len|replace2|replace3|setbit|setbyte|substring|substring3|ec_add|ec_map_to|ec_multi_scalar_mul|ec_pairing_check|ec_scalar_mul|ec_subgroup_check|ecdsa_pk_decompress|ecdsa_pk_recover|ecdsa_verify|ed25519verify|ed25519verify_bare|falcon_verify|keccak256|mimc|sha256|sha3_256|sha512_256|sumhash512|vrf_verify|gitxn|gitxna|gitxnas|itxn|itxn_begin|itxn_field|itxn_next|itxn_submit|itxna|itxnas)\\b" } ] }, @@ -111,7 +111,8 @@ "match": "\\b(?\u003c=byte\\s+)(0x[0-9]+)\\b" }, { - "name": "variable.parameter.teal" + "name": "variable.parameter.teal", + "match": "\\b(unknown|pay|keyreg|acfg|axfer|afrz|appl|NoOp|OptIn|CloseOut|ClearState|UpdateApplication|DeleteApplication|Secp256k1|Secp256r1|Sender|Fee|FirstValid|FirstValidTime|LastValid|Note|Lease|Receiver|Amount|CloseRemainderTo|VotePK|SelectionPK|VoteFirst|VoteLast|VoteKeyDilution|Type|TypeEnum|XferAsset|AssetAmount|AssetSender|AssetReceiver|AssetCloseTo|GroupIndex|TxID|ApplicationID|OnCompletion|NumAppArgs|NumAccounts|ApprovalProgram|ClearStateProgram|RekeyTo|ConfigAsset|ConfigAssetTotal|ConfigAssetDecimals|ConfigAssetDefaultFrozen|ConfigAssetUnitName|ConfigAssetName|ConfigAssetURL|ConfigAssetMetadataHash|ConfigAssetManager|ConfigAssetReserve|ConfigAssetFreeze|ConfigAssetClawback|FreezeAsset|FreezeAssetAccount|FreezeAssetFrozen|NumAssets|NumApplications|GlobalNumUint|GlobalNumByteSlice|LocalNumUint|LocalNumByteSlice|ExtraProgramPages|Nonparticipation|NumLogs|CreatedAssetID|CreatedApplicationID|LastLog|StateProofPK|NumApprovalProgramPages|NumClearStateProgramPages|MinTxnFee|MinBalance|MaxTxnLife|ZeroAddress|GroupSize|LogicSigVersion|Round|LatestTimestamp|CurrentApplicationID|CreatorAddress|CurrentApplicationAddress|GroupID|OpcodeBudget|CallerApplicationID|CallerApplicationAddress|AssetCreateMinBalance|AssetOptInMinBalance|GenesisHash|PayoutsEnabled|PayoutsGoOnlineFee|PayoutsPercent|PayoutsMinBalance|PayoutsMaxBalance|ApplicationArgs|Accounts|Assets|Applications|Logs|ApprovalProgramPages|ClearStateProgramPages|URLEncoding|StdEncoding|JSONString|JSONUint64|JSONObject|AssetBalance|AssetFrozen|AssetTotal|AssetDecimals|AssetDefaultFrozen|AssetUnitName|AssetName|AssetURL|AssetMetadataHash|AssetManager|AssetReserve|AssetFreeze|AssetClawback|AssetCreator|AppApprovalProgram|AppClearStateProgram|AppGlobalNumUint|AppGlobalNumByteSlice|AppLocalNumUint|AppLocalNumByteSlice|AppExtraProgramPages|AppCreator|AppAddress|AcctBalance|AcctMinBalance|AcctAuthAddr|AcctTotalNumUint|AcctTotalNumByteSlice|AcctTotalExtraAppPages|AcctTotalAppsCreated|AcctTotalAppsOptedIn|AcctTotalAssetsCreated|AcctTotalAssets|AcctTotalBoxes|AcctTotalBoxBytes|AcctIncentiveEligible|AcctLastProposed|AcctLastHeartbeat|VoterBalance|VoterIncentiveEligible|VrfAlgorand|BlkSeed|BlkTimestamp|BlkProposer|BlkFeesCollected|BlkBonus|BlkBranch|BlkFeeSink|BlkProtocol|BlkTxnCounter|BlkProposerPayout|BN254g1|BN254g2|BLS12_381g1|BLS12_381g2|BN254Mp110|BLS12_381Mp111)\\b" } ] }, From 2a6c4d0f0f5612622c8874b638e4ba612d4cbf20 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 10 Mar 2025 10:21:32 -0400 Subject: [PATCH 9/9] bigint optimizations, larger allowed inputs, custom costs --- data/transactions/logic/eval.go | 333 +++++++++++------ data/transactions/logic/evalStateful_test.go | 2 +- data/transactions/logic/eval_test.go | 370 ++++++++++++++----- data/transactions/logic/opcodes.go | 19 +- 4 files changed, 495 insertions(+), 229 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 3cd4cfce73..475f8e09e3 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -31,6 +31,7 @@ import ( "slices" "strconv" "strings" + "sync" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" @@ -1566,8 +1567,8 @@ func (cx *EvalContext) step() error { } if opcost > cx.remainingBudget() { - return fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s: local program cost was %d", - cx.pc, spec.Name, cx.cost) + return fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s (%d): local program cost was %d", + cx.pc, spec.Name, opcost, cx.cost) } cx.cost += opcost @@ -1691,7 +1692,7 @@ func (cx *EvalContext) checkStep() (int, error) { } opcost := deets.Cost(cx.program, cx.pc, blankStack) if opcost <= 0 { - return 0, fmt.Errorf("%s reported non-positive cost", spec.Name) + return 0, fmt.Errorf("%s reported non-positive cost %d", spec.Name, opcost) } prevpc := cx.pc if deets.check != nil { @@ -1792,22 +1793,80 @@ func opAddw(cx *EvalContext) error { return nil } +// We always use big.Ints for a short time (one opcode) and then return them to +// the pool. It is a fool's errand to try to get big.Ints to be allocated on the +// stack because even if you manage it, their internal slice of Words must have +// a backing store that ends up allocated on the heap. +var bigIntPool = sync.Pool{ + New: func() interface{} { + return new(big.Int) + }, +} + +func getBig() *big.Int { + return bigIntPool.Get().(*big.Int) +} + +func putBig(b *big.Int) { + bigIntPool.Put(b) +} + +// uint128 returns a big.Int constructed from the two uint64s supplied. func uint128(hi uint64, lo uint64) *big.Int { - whole := new(big.Int).SetUint64(hi) + // directly manipulate the words of the big.Int if they are uint64. big.Word + // is a `uint`, not `uint64`, so it might only be 32 bits. The words are + // LITTLE endian (lowest order bits are in words[0]) + if bits.UintSize == 64 { + // this saves about 17% of the time in divmodw + b := getBig() + // reuse the existing Bits() slice to avoid allocation (it usually + // exists because this big.Int came from the pool) + words := append(b.Bits()[:0], big.Word(lo), big.Word(hi)) + return b.SetBits(words) + } + // slowpath for non 64-bit systems + whole := getBig().SetUint64(hi) whole.Lsh(whole, 64) - whole.Add(whole, new(big.Int).SetUint64(lo)) + low := getBig().SetUint64(lo) + whole.Add(whole, low) + putBig(low) return whole } +// uint64s returns the high and low words of a big.Int. The big.Int MUST be no +// larger than 128 bits. +func uint64s(bi *big.Int) (hi uint64, lo uint64) { + // this is barely worth it. saves the cost of 1 opcode, roughly. But, since + // `uint128` has already peeked behind the big.Int curtain, why not? + if bits.UintSize == 64 { + words := bi.Bits() + if l := len(words); l > 0 { + lo = uint64(words[0]) + if l > 1 { + hi = uint64(words[1]) + } + } + return + } + // slowpath for non 64-bit systems + lo = bi.Uint64() // take lo first, so bi can be modified - avoid an allocation + hi = bi.Rsh(bi, 64).Uint64() + return +} + func opDivModwImpl(hiNum, loNum, hiDen, loDen uint64) (hiQuo uint64, loQuo uint64, hiRem uint64, loRem uint64) { dividend := uint128(hiNum, loNum) divisor := uint128(hiDen, loDen) - quo, rem := new(big.Int).QuoRem(dividend, divisor, new(big.Int)) - return new(big.Int).Rsh(quo, 64).Uint64(), - quo.Uint64(), - new(big.Int).Rsh(rem, 64).Uint64(), - rem.Uint64() + quo, rem := getBig().QuoRem(dividend, divisor, getBig()) + putBig(divisor) + putBig(dividend) + + hiQuo, loQuo = uint64s(quo) + hiRem, loRem = uint64s(rem) + putBig(quo) + putBig(rem) + return } func opDivModw(cx *EvalContext) error { @@ -2092,23 +2151,26 @@ func opSqrt(cx *EvalContext) error { return nil } +// bitlen returns the 1 based index of the highest set bit in the byte slice, +// when thinking of the byteslize as a big-endian integer. (If the slice is +// all zeros, it returns 0.) +func bitlen(bytes []byte) int { + for i, b := range bytes { + if b != 0 { + return bits.Len8(b) + (8 * (len(bytes) - i - 1)) + } + } + return 0 +} + func opBitLen(cx *EvalContext) error { last := len(cx.Stack) - 1 if cx.Stack[last].avmType() == avmUint64 { cx.Stack[last].Uint = uint64(bits.Len64(cx.Stack[last].Uint)) return nil } - length := len(cx.Stack[last].Bytes) - idx := 0 - for i, b := range cx.Stack[last].Bytes { - if b != 0 { - idx = bits.Len8(b) + (8 * (length - i - 1)) - break - } - - } + cx.Stack[last].Uint = uint64(bitlen(cx.Stack[last].Bytes)) cx.Stack[last].Bytes = nil - cx.Stack[last].Uint = uint64(idx) return nil } @@ -2159,28 +2221,31 @@ func opExpwImpl(base uint64, exp uint64) (*big.Int, error) { // These checks are slightly repetive but the clarity of // avoiding nested checks seems worth it. if exp == 0 && base == 0 { - return &big.Int{}, errors.New("0^0 is undefined") + return nil, errors.New("0^0 is undefined") } if base == 0 { - return &big.Int{}, nil + return getBig().SetUint64(0), nil } if exp == 0 || base == 1 { - return new(big.Int).SetUint64(1), nil + return getBig().SetUint64(1), nil } // base is now at least 2, so exp can not be 128 if exp >= 128 { - return &big.Int{}, fmt.Errorf("%d^%d overflow", base, exp) + return nil, fmt.Errorf("%d^%d overflow", base, exp) } - answer := new(big.Int).SetUint64(base) - bigbase := new(big.Int).SetUint64(base) + answer := getBig().SetUint64(base) + bigbase := getBig().SetUint64(base) // safe to cast exp, because it is known to fit in int (it's < 128) for i := 1; i < int(exp); i++ { answer.Mul(answer, bigbase) if answer.BitLen() > 128 { - return &big.Int{}, fmt.Errorf("%d^%d overflow", base, exp) + putBig(answer) + putBig(bigbase) + return nil, fmt.Errorf("%d^%d overflow", base, exp) } } + putBig(bigbase) return answer, nil } @@ -2194,105 +2259,117 @@ func opExpw(cx *EvalContext) error { if err != nil { return err } - hi := new(big.Int).Rsh(val, 64).Uint64() - lo := val.Uint64() + hi, lo := uint64s(val) + putBig(val) cx.Stack[prev].Uint = hi cx.Stack[last].Uint = lo return nil } -func opBytesBinOp(cx *EvalContext, result *big.Int, op func(x, y *big.Int) *big.Int) error { +func byteMathOperands(cx *EvalContext) (*big.Int, *big.Int, error) { last := len(cx.Stack) - 1 prev := last - 1 - if len(cx.Stack[last].Bytes) > maxByteMathSize || len(cx.Stack[prev].Bytes) > maxByteMathSize { - return errors.New("math attempted on large byte-array") + if cx.version < fullByteMathVersion { + if len(cx.Stack[last].Bytes) > maxByteMathSize || len(cx.Stack[prev].Bytes) > maxByteMathSize { + return nil, nil, errors.New("math attempted on large byte-array") + } } - rhs := new(big.Int).SetBytes(cx.Stack[last].Bytes) - lhs := new(big.Int).SetBytes(cx.Stack[prev].Bytes) - op(lhs, rhs) // op's receiver has already been bound to result - if result.Sign() < 0 { - return errors.New("byte math would have negative result") - } - cx.Stack[prev].Bytes = result.Bytes() - cx.Stack = cx.Stack[:last] - return nil + rhs := getBig().SetBytes(cx.Stack[last].Bytes) + lhs := getBig().SetBytes(cx.Stack[prev].Bytes) + return lhs, rhs, nil } -// opBytesFullBinOp is like opBytesBinOp but it allows any size inputs (and -// therefore must check the size of the output). -func opBytesFullBinOp(cx *EvalContext, result *big.Int, op func(x, y *big.Int) *big.Int) error { +func byteMathResult(cx *EvalContext, result *big.Int) error { last := len(cx.Stack) - 1 prev := last - 1 - - rhs := new(big.Int).SetBytes(cx.Stack[last].Bytes) - lhs := new(big.Int).SetBytes(cx.Stack[prev].Bytes) - op(lhs, rhs) // op's receiver has already been bound to result - if result.Sign() < 0 { - return errors.New("byte math would have negative result") - } cx.Stack[prev].Bytes = result.Bytes() - /* - if len(cx.Stack[last].Bytes) > maxStringSize { - return fmt.Errorf("byte math results would exceed %d bytes", maxStringSize) - } - */ + putBig(result) cx.Stack = cx.Stack[:last] return nil } func opBytesPlus(cx *EvalContext) error { - result := new(big.Int) - if cx.version >= fullByteMathVersion { - return opBytesFullBinOp(cx, result, result.Add) + lhs, rhs, err := byteMathOperands(cx) + if err != nil { + return err } - return opBytesBinOp(cx, result, result.Add) + lhs.Add(lhs, rhs) + putBig(rhs) + return byteMathResult(cx, lhs) } func bplusCost(stack []stackValue) int { last := len(stack) - 1 prev := last - 1 - return 8 + max(len(stack[last].Bytes), len(stack[prev].Bytes)) + return 1 + basics.DivCeil(max(len(stack[last].Bytes), len(stack[prev].Bytes)), 16) } func opBytesMinus(cx *EvalContext) error { - result := new(big.Int) - if cx.version >= fullByteMathVersion { - return opBytesFullBinOp(cx, result, result.Sub) + lhs, rhs, err := byteMathOperands(cx) + if err != nil { + return err } - return opBytesBinOp(cx, result, result.Sub) + lhs.Sub(lhs, rhs) + putBig(rhs) + if lhs.Sign() < 0 { + putBig(lhs) + return errors.New("byte math would have negative result") + } + return byteMathResult(cx, lhs) } func opBytesDiv(cx *EvalContext) error { - result := new(big.Int) - var inner error - checkDiv := func(x, y *big.Int) *big.Int { - if y.BitLen() == 0 { - inner = errors.New("division by zero") - return new(big.Int) - } - return result.Div(x, y) + lhs, rhs, err := byteMathOperands(cx) + if err != nil { + return err } - var err error - if cx.version >= fullByteMathVersion { - err = opBytesFullBinOp(cx, result, checkDiv) - } else { - err = opBytesBinOp(cx, result, checkDiv) + if rhs.BitLen() == 0 { + putBig(rhs) + putBig(lhs) + return errors.New("division by zero") } + lhs.Div(lhs, rhs) + putBig(rhs) + return byteMathResult(cx, lhs) +} + +func bdivCost(stack []stackValue) int { + last := len(stack) - 1 + prev := last - 1 + l := len(stack[prev].Bytes) + chunks := l / 96 + return 10 + chunks*chunks + l/10 +} + +func opBytesMul(cx *EvalContext) error { + lhs, rhs, err := byteMathOperands(cx) if err != nil { return err } - return inner + result := getBig().Mul(lhs, rhs) // lhs.Mul(lhs, rhs) would cause internal allocation + putBig(rhs) + putBig(lhs) + return byteMathResult(cx, result) } -func opBytesMul(cx *EvalContext) error { - result := new(big.Int) - if cx.version < fullByteMathVersion { - return opBytesBinOp(cx, result, result.Mul) +func bmulCost(stack []stackValue) int { + last := len(stack) - 1 + prev := last - 1 + return mulComplexity(len(stack[last].Bytes), len(stack[prev].Bytes)) +} + +func mulComplexity(la, lb int) int { + const chunk = 16 + linear := 0 + factor := 12 + if min(la, lb) >= 512 { + factor = 24 + linear = 2 * max(la, lb) / chunk } - return opBytesFullBinOp(cx, result, result.Mul) + return linear + (8+la/chunk)*(8+lb/chunk)/factor } func opBytesSqrt(cx *EvalContext) error { @@ -2304,12 +2381,22 @@ func opBytesSqrt(cx *EvalContext) error { } } - val := new(big.Int).SetBytes(cx.Stack[last].Bytes) - val.Sqrt(val) - cx.Stack[last].Bytes = val.Bytes() + val := getBig().SetBytes(cx.Stack[last].Bytes) + result := getBig().Sqrt(val) // val.Sqrt(val) would cause internal allocation + putBig(val) + cx.Stack[last].Bytes = result.Bytes() + putBig(result) return nil } +func bsqrtCost(stack []stackValue) int { + last := len(stack) - 1 + bytes := stack[last].Bytes + l := len(bytes) + chunks := l / 96 + return 5 + chunks*chunks*bits.Len(uint(l)) + l +} + func nonzero(b []byte) []byte { for i := range b { if b[i] != 0 { @@ -2393,67 +2480,81 @@ func opBytesNeq(cx *EvalContext) error { } func opBytesModExp(cx *EvalContext) error { - result := new(big.Int) last := len(cx.Stack) - 1 // z prev := last - 1 // y pprev := last - 2 // x - z := new(big.Int).SetBytes(cx.Stack[last].Bytes) - y := new(big.Int).SetBytes(cx.Stack[prev].Bytes) - x := new(big.Int).SetBytes(cx.Stack[pprev].Bytes) + z := getBig().SetBytes(cx.Stack[last].Bytes) if z.BitLen() == 0 { + putBig(z) return errors.New("modulo by zero") } - - result.Exp(x, y, z) - + y := getBig().SetBytes(cx.Stack[prev].Bytes) + x := getBig().SetBytes(cx.Stack[pprev].Bytes) + result := getBig().Exp(x, y, z) + putBig(x) + putBig(y) + putBig(z) cx.Stack[pprev].Bytes = result.Bytes() + putBig(result) + cx.Stack = cx.Stack[:prev] return nil } -func bmodexpCost(stack []stackValue) int { +func bmodexpCostFloat(stack []stackValue) int { last := len(stack) - 1 // mod prev := last - 1 // exp pprev := last - 2 // base // Empirically estimated cost function constants const ( - exponentFactor = 1.63 // Adjusts cost of base & mod multiplication in the modexp by squaring algorithm - scalingFactor = 15 // Normalization factor - baseCost = 200 // Minimum cost of bmodexp + exponentFactor = 1.63 // Adjusts cost of base & mod multiplication in the modexp by squaring algorithm + scalingFactor = 15 * 8 // Normalization factor + baseCost = 200 // Minimum cost of bmodexp ) - expLength := float64(len(stack[prev].Bytes)) + expLength := float64(bitlen(stack[prev].Bytes)) modLength := len(stack[last].Bytes) baseLength := len(stack[pprev].Bytes) // Derived from the asymptotic time complexity of the exponentiation by squaring algorithm - cost := (math.Pow(float64(max(baseLength, modLength)), exponentFactor) * expLength / scalingFactor) + baseCost + cost := baseCost + (math.Pow(float64(max(baseLength, modLength)), exponentFactor) * expLength / scalingFactor) return int(cost) } +// bmodexpCost approximates the cost of bmodexp, which works by repeated +// squaring. There are bitlen(`exp`) steps, and at each step there is a +// squaring, possibly an addition, and the result can be reduced modulo `mod`. +func bmodexpCost(stack []stackValue) int { + mod := len(stack) - 1 + exp := mod - 1 + base := mod - 2 + + // EIP-198 uses max(base,mod) here, but I don't really understand why. If + // each step reduces module `mod`, it seems as though we should just use + // len(mod). If len(base) is bigger than len(mod), bmodexp ought to be + // reducing ahead of each step, so it _never_ multiplies numbers wider than + // `exp`. + m := max(len(stack[base].Bytes), len(stack[mod].Bytes)) + + return bitlen(stack[exp].Bytes) * mulComplexity(m, m) +} + func opBytesModulo(cx *EvalContext) error { - result := new(big.Int) - var inner error - checkMod := func(x, y *big.Int) *big.Int { - if y.BitLen() == 0 { - inner = errors.New("modulo by zero") - return new(big.Int) - } - return result.Mod(x, y) - } - var err error - if cx.version >= fullByteMathVersion { - err = opBytesFullBinOp(cx, result, checkMod) - } else { - err = opBytesBinOp(cx, result, checkMod) - } + lhs, rhs, err := byteMathOperands(cx) if err != nil { return err } - return inner + if rhs.BitLen() == 0 { + putBig(rhs) + putBig(lhs) + return errors.New("modulo by zero") + } + lhs.Mod(lhs, rhs) + putBig(rhs) + return byteMathResult(cx, lhs) } func zpad(smaller []byte, size int) []byte { diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 7b50c3283d..5eb434c827 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -3694,7 +3694,7 @@ func TestPooledAppCallsVerifyOp(t *testing.T) { call := transactions.SignedTxn{Txn: transactions.Transaction{Type: protocol.ApplicationCallTx}} // Simulate test with 2 grouped txn testApps(t, []string{source, ""}, []transactions.SignedTxn{call, call}, nil, ledger, - exp(0, "pc=107 dynamic cost budget exceeded, executing ed25519verify: local program cost was 5")) + exp(0, "pc=107 dynamic cost budget exceeded, executing ed25519verify (1900): local program cost was 5")) // Simulate test with 3 grouped txn testApps(t, []string{source, "", ""}, []transactions.SignedTxn{call, call, call}, nil, ledger) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index b4168c4667..7bc65053c0 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -450,6 +450,8 @@ func TestSimpleMath(t *testing.T) { testAccepts(t, "int 21; int 7; / ; int 3; ==", 1) testPanics(t, "int 1; int 2; - ; int 0; ==", 1) + + testCost(t, "int 1; int 2; + ; int 3; ==", 5, 6) } // TestRapidMath uses rapid.Check to be a bit more exhaustive @@ -3980,23 +3982,54 @@ main: } } +func BenchmarkBytesDiv(b *testing.B) { + for i := range 128 { + size := (i + 1) * 32 + b.Run(fmt.Sprintf("b/ %d", size), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", randBytes(size)+randBytes(size*3/8)+"b/; pop", "int 1") + }) + } +} + // bigint multiply ought to have a weird cost function because Karatsuba kicks at 40 Words (320 bytes) -func BenchmarkBytesMul(b *testing.B) { +func BenchmarkBytesMulEqualSize(b *testing.B) { for i := range 32 { size := i * 64 b.Run(fmt.Sprintf("b* %d", size), func(b *testing.B) { b.ReportAllocs() - benchmarkOperation(b, "", "byte "+randBytes(size)+"; byte "+randBytes(size)+";b*;pop", "int 1") + benchmarkOperation(b, "", randBytes(size)+randBytes(size)+"b*; pop", "int 1") }) } } -func BenchmarkBytesSqrt(b *testing.B) { +func BenchmarkBytesMulBySmall(b *testing.B) { for i := range 64 { size := i * 64 + b.Run(fmt.Sprintf("b* %d by 8", size), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", randBytes(size)+randBytes(8)+"b*; pop", "int 1") + }) + } +} + +func BenchmarkBytesMulByMed(b *testing.B) { + for i := range 64 { + size := i * 48 + other := size / 3 + b.Run(fmt.Sprintf("b* %d by %d", size, other), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", randBytes(size)+randBytes(other)+"b*; pop", "int 1") + }) + } +} + +func BenchmarkBytesSqrt(b *testing.B) { + for i := range 64 { + size := i * 16 b.Run(fmt.Sprintf("bqsrt %d", size), func(b *testing.B) { b.ReportAllocs() - benchmarkOperation(b, "", "byte "+randBytes(size)+";bsqrt;pop", "int 1") + benchmarkOperation(b, "", randBytes(size)+"bsqrt; pop", "int 1") }) } } @@ -4010,38 +4043,29 @@ func BenchmarkBytesMulDifferingSizes(b *testing.B) { } b.Run(fmt.Sprintf("b* %d-%d", awords, bwords), func(b *testing.B) { b.ReportAllocs() - benchmarkOperation(b, "", "byte "+randBytes(8*awords)+"; byte "+randBytes(8*bwords)+";b*;pop", "int 1") + benchmarkOperation(b, "", randBytes(8*awords)+randBytes(8*bwords)+"b*; pop", "int 1") }) } } +// BenchmarkFindWorstBytesDiv determines the relatiuve byte size of the dividend +// and divisor for worst case speed testing. About 3/8. func BenchmarkFindWorstBytesDiv(b *testing.B) { - for divisor := 4096; divisor > 0; divisor -= 128 { + for divisor := 1600; divisor > 1500; divisor -= 10 { b.Run(fmt.Sprintf("b/ %d", divisor), func(b *testing.B) { b.ReportAllocs() - benchmarkOperation(b, "", "byte "+randBytes(4096)+"; byte "+randBytes(divisor)+";b/;pop", "int 1") + benchmarkOperation(b, "", randBytes(4096)+randBytes(divisor)+"b/; pop", "int 1") }) } } -func BenchmarkFindWorstBytesMod(b *testing.B) { - for _, big := range []int{128, 512, 2096} { - for divisor := big; divisor > 0; divisor -= (big / 10) { - b.Run(fmt.Sprintf("b%% %d %d", big, divisor), func(b *testing.B) { - b.ReportAllocs() - benchmarkOperation(b, "", "byte "+randBytes(big)+"; byte "+randBytes(divisor)+";b%;pop", "int 1") - }) - } - } -} - func BenchmarkBinaryByteLogic(b *testing.B) { for i := range 16 { size := i * 64 for _, op := range []string{"b&", "b|", "b^"} { b.Run(fmt.Sprintf("%s %d", op, size), func(b *testing.B) { b.ReportAllocs() - benchmarkOperation(b, "", "byte "+randBytes(size)+"; byte "+randBytes(size)+";"+op+";pop", "int 1") + benchmarkOperation(b, "", randBytes(size)+randBytes(size)+op+";pop", "int 1") }) } } @@ -4052,64 +4076,38 @@ func BenchmarkByteNot(b *testing.B) { size := i * 64 b.Run(fmt.Sprintf("b~ %d", size), func(b *testing.B) { b.ReportAllocs() - benchmarkOperation(b, "byte "+randBytes(size), "b~", "pop; int 1") + benchmarkOperation(b, randBytes(size), "b~", "pop; int 1") }) } } -// randBytes generates a random byte constant of length `length` -func randBytes(length int) string { +// randBytes generates a random `byte` constant of length `length`, replacing +// the leading bytes with optional `prefix` bytes. +func randBytes(length int, prefix ...byte) string { bytes := make([]byte, length) rand.Read(bytes) - return fmt.Sprintf("0x%x", bytes) + copy(bytes, prefix) + return fmt.Sprintf("byte 0x%x;", bytes) } -// chunk emits one `byte` statement creating a byte string with `count` (64 bit) words -func chunk(count int) string { - buf := make([]byte, 8*count) - rand.Read(buf) - word := "7090a0b0c0d0e0f0" // enough hex for one 64bit word, small enough to add without overflow - return "byte 0x" + strings.Repeat(word, count) + ";" -} - -func BenchmarkByteMath(b *testing.B) { +func BenchmarkByteAddSub(b *testing.B) { benches := [][]string{ - {"bytec", chunk(2) + "pop"}, + {"bytec", randBytes(16) + "pop"}, - {"b+ 1w", chunk(1) + chunk(1) + "b+; pop"}, - {"b- 1w", chunk(1) + chunk(1) + "b-; pop"}, - {"b* 1w", chunk(1) + chunk(1) + "b*; pop"}, - // half sized divisor seems pessimal for / and % - {"b/ 1w", chunk(1) + "byte 0x8090a0b0;" + "b/; pop"}, - {"b% 1w", chunk(1) + "byte 0x8090a0b0;" + "b%; pop"}, - {"bsqrt 1w", chunk(1) + "bsqrt; pop"}, + {"b+ 8", randBytes(8) + randBytes(8) + "b+; pop"}, + {"b- 8", randBytes(8, 0xff) + randBytes(8, 0x11) + "b-; pop"}, // maximum sizes, pre fullByteMathVersion - {"b+ 8w", chunk(8) + chunk(8) + "b+; pop"}, - {"b- 8w", chunk(8) + chunk(8) + "b-; pop"}, - {"b+ 8w4w", chunk(8) + chunk(4) + "b+; pop"}, - {"b- 8w4w", chunk(8) + chunk(4) + "b-; pop"}, - {"b* 8w", chunk(8) + chunk(8) + "b*; pop"}, - {"b/ 8w", chunk(8) + chunk(4) + "b/; pop"}, - {"b% 8w", chunk(8) + chunk(4) + "b%; pop"}, - {"bsqrt 8w", chunk(8) + "bsqrt; pop"}, - - {"b+ 16w", chunk(16) + chunk(16) + "b+; pop"}, - {"b- 16w", chunk(16) + chunk(16) + "b-; pop"}, - {"b+ 16w8w", chunk(16) + chunk(8) + "b+; pop"}, - {"b- 18w8w", chunk(16) + chunk(8) + "b-; pop"}, - - {"b+ 64w", chunk(64) + chunk(64) + "b+; pop"}, - {"b- 64w", chunk(64) + chunk(64) + "b-; pop"}, - {"b+ 64w32w", chunk(64) + chunk(32) + "b+; pop"}, - {"b- 64w32w", chunk(64) + chunk(32) + "b-; pop"}, - - {"b+ 512w", chunk(512) + chunk(512) + "b+; pop"}, - {"b- 512w", chunk(512) + chunk(512) + "b-; pop"}, - {"b+ 512w1w", chunk(512) + chunk(1) + "b+; pop"}, - {"b+ 1w512w", chunk(1) + chunk(512) + "b+; pop"}, - {"b- 512w1w", chunk(512) + chunk(1) + "b-; pop"}, - {"bytec recheck", chunk(2) + "pop"}, + {"b+ 64", randBytes(64) + randBytes(64) + "b+; pop"}, + {"b- 64", randBytes(64, 0xff) + randBytes(64, 0x11) + "b-; pop"}, + + {"b+ 512", randBytes(512) + randBytes(512) + "b+; pop"}, + {"b- 512", randBytes(512, 0xff) + randBytes(512) + "b-; pop"}, + + {"b+ 4096", randBytes(4096, 0x11) + randBytes(4096, 0x22) + "b+; pop"}, + {"b- 4096", randBytes(4096, 0xff) + randBytes(4096, 0x11) + "b-; pop"}, + + {"bytec recheck", randBytes(16) + "pop"}, } for _, bench := range benches { b.Run(bench[0], func(b *testing.B) { @@ -4720,6 +4718,20 @@ func testEvaluation(t *testing.T, program string, start uint64, stop uint64, tes return outer } +// testCost ensures that the program accepts and that the cost as described +func testCost(t *testing.T, program string, cost uint64, ver uint64) { + t.Helper() + if ver < 6 { + panic("testCost doesn't work until ver 6") + } + // compare the OpCodeBudget remaining to the cost supplied, but add 2 to + // account for the access instructionms. use pushint to avoid influencing + // the way `int` might be compiled in the test program. leave the stack as + // it was found. + program += fmt.Sprintf("; pushint %d; global OpcodeBudget; -; pushint 2; -; pushint %d; ==; assert", testLogicBudget, cost) + testAcceptRange(t, program, ver, ver) +} + func testAccepts(t *testing.T, program string, introduced uint64) { t.Helper() testPreexist(t, program, introduced) @@ -5180,6 +5192,100 @@ func TestBitLen(t *testing.T) { } +// BenchmarkBytesModExpRealistic is used to benchmark bmodexp for more realistic +// values, things that will take less than 500k ticks. (320k is the most you can get today. (16 * 20k) +func BenchmarkBytesModExpRealistic(b *testing.B) { + base := 32 + for i := range 32 { + exp := i * 16 + if i == 0 { + i = 1 + } + b.Run(fmt.Sprintf("bmodexp-%d-%d", base, exp), func(b *testing.B) { + b.ReportAllocs() + // 0xaa ensures top bit is set, plus "some" + benchmarkOperation(b, "", randBytes(base)+randBytes(exp, 0xaa)+randBytes(base)+"bmodexp; pop", "int 1") + }) + } + base = 64 + for i := range 16 { + exp := i * 32 + if i == 0 { + i = 1 + } + b.Run(fmt.Sprintf("bmodexp-%d-%d", base, exp), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", randBytes(base)+randBytes(exp, 0xaa)+randBytes(base)+"bmodexp; pop", "int 1") + }) + } + base = 96 + for i := range 16 { + exp := i * 32 + if i == 0 { + i = 1 + } + b.Run(fmt.Sprintf("bmodexp-%d-%d", base, exp), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", randBytes(base)+randBytes(exp, 0xaa)+randBytes(base)+"bmodexp; pop", "int 1") + }) + } + base = 512 // Typical RSA max key exp (4096 bits) + for i := range 16 { + exp := 1 + i*2 // RSA typically uses 65537 for verify, which has a *byte* width of just 3 (bit width = 17) + b.Run(fmt.Sprintf("bmodexp-%d-%d", base, exp), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", randBytes(base)+randBytes(exp, 0xaa)+randBytes(base)+"bmodexp; pop", "int 1") + }) + } + base = 1024 // Barely ever used (8192 bits) + for i := range 16 { + exp := 1 + i // RSA typically uses 65537 for verify, which has a *byte* width of just 3 (bit width = 17) + b.Run(fmt.Sprintf("bmodexp-%d-%d", base, exp), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", randBytes(base)+randBytes(exp, 0xaa)+randBytes(base)+"bmodexp; pop", "int 1") + }) + } +} + +// BenchmarkBytesModExpLinearExponent is used to show that bmodexp takes linear time in the width of the exponent +func BenchmarkBytesModExpLinearExponent(b *testing.B) { + for i := range 32 { + exp := (i + 1) * 128 + b.Run(fmt.Sprintf("bmodexp64-%d", exp), func(b *testing.B) { + b.ReportAllocs() + // 0xaa ensures top bit is set, plus "some" + benchmarkOperation(b, "", randBytes(64)+randBytes(exp, 0xaa)+randBytes(64)+"bmodexp; pop", "int 1") + }) + } + for i := range 32 { + exp := (i + 1) * 128 + b.Run(fmt.Sprintf("bmodexp512-%d", exp), func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", randBytes(512)+randBytes(exp, 0xaa)+randBytes(512)+"bmodexp; pop", "int 1") + }) + } +} + +// BenchmarkBytesModBitlen shows why we use bitlen in the cost +// function. Exponent of 0x0f is roughly half as costly as exponent of 0xf0, +// even though both are 1 byte. (And RSA uses 3 as an exponent) +func BenchmarkBytesModExpBitlen(b *testing.B) { + for i := range 32 { + size := (i + 1) * 128 + b.Run("bmodexp0x0f", func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", randBytes(size)+"byte 0x0f;"+randBytes(size)+"bmodexp; pop", "int 1") + }) + } + for i := range 32 { + size := (i + 1) * 128 + b.Run("bmodexp0xf0", func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", randBytes(size)+"byte 0xf0;"+randBytes(size)+"bmodexp; pop", "int 1") + }) + } +} + func BenchmarkBytesModExp(b *testing.B) { type ModexpTestVector struct { Base string @@ -5206,19 +5312,19 @@ func BenchmarkBytesModExp(b *testing.B) { } b.Run("bmod_cost", func(b *testing.B) { b.ReportAllocs() - progText := fmt.Sprintf(`byte %s; byte %s;`, randBytes(64), randBytes(64)) + " b%; pop" + progText := randBytes(64) + randBytes(64) + " b%; pop" benchmarkOperation(b, "", progText, "int 1") }) b.Run("max_bmodexp_cost", func(b *testing.B) { b.ReportAllocs() - progText := fmt.Sprintf(`byte %s; byte %s; byte %s; bmodexp; pop`, randBytes(4096), randBytes(4096), randBytes(4096)) + progText := randBytes(4096) + randBytes(4096) + randBytes(4096) + "bmodexp; pop" benchmarkOperation(b, "", progText, "int 1") }) // Iterate through the test vectors and benchmark the bmodexp computation for _, tv := range modexpTestVectors { b.Run(tv.Name, func(b *testing.B) { b.ReportAllocs() - progText := fmt.Sprintf(`byte %s; byte %s; byte %s; bmodexp; pop`, tv.Base, tv.Exponent, tv.Modulus) + progText := tv.Base + tv.Exponent + tv.Modulus + "bmodexp; pop" benchmarkOperation(b, "", progText, "int 1") }) } @@ -5238,7 +5344,7 @@ func TestBytesModExp(t *testing.T) { Exponent string Modulus string Result string - LogicCost int + LogicCost uint64 // The cost of the bmodexp instruction TestOutcome testOutcome } cases := []ModexpTestVector{ @@ -5248,36 +5354,29 @@ func TestBytesModExp(t *testing.T) { {"0x01", "0x01", "0x0000", "0x01", 0, Panic}, // Modulo of 0 should panic {"0x54b7", "0x00", "0x01", "0x", 200, Accept}, {"0x286e0b2a3fea08c786634bdf0a608fb22009c512e6f1f174", "0x9cebf0aae57f76408a", "0xcf5d2d1fdc2e3233adcc13c8b3fc2fb0a3d3c1032ee14288c9026968c59d6fd7f8c9ef82e63bea29304ebb91b150", "0x9e26c7578c46f09e26e67224526193f5af3512662276e54cb91944d9f80514b31fba2d4c6231c97309a79cfc09b0", 507, Accept}, - {"0xb04336dca137d1284edf958923d01c83f6a09e50bcfb1b509c2afe63bca4f64bf28a482f202cdf08e4fad627acde33c4a5206086641acf2ceab1669bf99b5d672dc71a5d2fc7ff99152f2ecb71e95543cb72be06151e3b75c12961773a0b20e59ceb18713ee7313cb3c146b10188a23de2dab3b733d2dbc4b30258e6e8cde85d1c394a76784a2038a0499feaf4851f22c48b30a7eedf02de934f8a31930d90426fd93241862614943e7a6e2e7f3ef9b08ce14030dcb8ca51d53743ac", "0x3bc794defa8e", "0xf418c1ba14622a93b40859b6fa5c8869ceeab204991a18bba8b414a03bab048c016a98c190ca7f4edb82745e8d91ce930b28c3e8c6f783ff6ea7cf4e092fe845d81189c8d77e4d6b2a3c967ed3d64a7310be13260589531e6485ddf9b065bed8142d7189fe22e213847bc0e10c5ff21e5f12c513f91357db5de6dc879f1e622dc386be6521f48cd476adb021050c09b913147ccb0c7e9ea2712f63b1c2273c4eb70267d366c8eb9548d3bcc19972dce8538767cd53d010e35a3bbab920afd498184d587f3f081fcc7018fd9ad448076a4a8ff231fc", "0x7d01fce371b80532a8ba65fc442e3adb4a5cae46d734258d342fbabaad7e83b14474fd21a5cee7e4a53f3de7e6f3c497b893f0cf23d9a743c4dfa736fb8080d54083a03b20f598ec1eed1d83714465914aa9171cdb1c3a56fb9c021e0c80f44a4d2b4b5c4e078fdc818474af5e0a334b25ac3f069d2dcc72dca335d05ac24fdbfabf07b17ce6e9fb996100509545bd9a0e5df48215112e04a68b2cc700b1a379e3a5df9d2913498cb8e15c92bec53a3c5775dd7fdfa9a5b515f738c88dc404b09cc2a4c389ee6334da58364d5c22482b905a1ec3fc", 2696, Accept}, + {"0xb04336dca137d1284edf958923d01c83f6a09e50bcfb1b509c2afe63bca4f64bf28a482f202cdf08e4fad627acde33c4a5206086641acf2ceab1669bf99b5d672dc71a5d2fc7ff99152f2ecb71e95543cb72be06151e3b75c12961773a0b20e59ceb18713ee7313cb3c146b10188a23de2dab3b733d2dbc4b30258e6e8cde85d1c394a76784a2038a0499feaf4851f22c48b30a7eedf02de934f8a31930d90426fd93241862614943e7a6e2e7f3ef9b08ce14030dcb8ca51d53743ac", "0x3bc794defa8e", "0xf418c1ba14622a93b40859b6fa5c8869ceeab204991a18bba8b414a03bab048c016a98c190ca7f4edb82745e8d91ce930b28c3e8c6f783ff6ea7cf4e092fe845d81189c8d77e4d6b2a3c967ed3d64a7310be13260589531e6485ddf9b065bed8142d7189fe22e213847bc0e10c5ff21e5f12c513f91357db5de6dc879f1e622dc386be6521f48cd476adb021050c09b913147ccb0c7e9ea2712f63b1c2273c4eb70267d366c8eb9548d3bcc19972dce8538767cd53d010e35a3bbab920afd498184d587f3f081fcc7018fd9ad448076a4a8ff231fc", "0x7d01fce371b80532a8ba65fc442e3adb4a5cae46d734258d342fbabaad7e83b14474fd21a5cee7e4a53f3de7e6f3c497b893f0cf23d9a743c4dfa736fb8080d54083a03b20f598ec1eed1d83714465914aa9171cdb1c3a56fb9c021e0c80f44a4d2b4b5c4e078fdc818474af5e0a334b25ac3f069d2dcc72dca335d05ac24fdbfabf07b17ce6e9fb996100509545bd9a0e5df48215112e04a68b2cc700b1a379e3a5df9d2913498cb8e15c92bec53a3c5775dd7fdfa9a5b515f738c88dc404b09cc2a4c389ee6334da58364d5c22482b905a1ec3fc", 2592, Accept}, {"0x3e1c6d61105021cbd5388fbad1bd004929932619359415cbf63b2a5ba087a615ac387a710e19affd897f750581c815ab75c56ab0f7f43fe29ef0a2c10a582ee7cbf548a1e58d3be900f2edd57ecc3e3ce2543730a5b7241f640215", "0xa80794b876bc56a2031a0fe504ee9047dac05791fe78917dc82dc06bdeb519ce285713e9c3a97d4bbe065be9cdf6d7d845f0206bad7d23eace4856", "0x33f27976478080519dd19e89950d04b1e65f3e4bd5e684b234f68584c54415f2896440391e2b36a65bb3e3fafb10a9c6ffae6c5b8ce5223bf786fa0a7a3a6d5a54985f26936fdbc70b2b94790d712de3ed0ecf34332e805da31224f83041efa739e958634529d94f8bd4c64d39a8e3e3d2741623912d97c33751aa0d", "0x2d6f597bb2771b5e9eafeeebb220a157d9bc7a7acb3eb0fed18272da51d1abef322e4b85b02c7ac79bddae7ac8708cc7f01e774e8e8b87023374567b52c3f8e9df28063abfdcb19152d95e2b6b3077acfb687e45e32164c132b0931c587eaa3f34ac474ec0512c6b57bad2b98ef83dca85d23a7c9b114893170501f2", 10363, Accept}, {"0x4de679ab106df6431f37cb20e60491eab2a00fda0017e3788856589e332db9596eda1ec03ea4e641ae4b22e7923662537ddd4ae130148ecd193b5e4578d7409e5371b50f45e92293d8c786b824eb26dd6c31419f8ad6383327772cd10b84b40d10a7438c1b3a92aedb718a0c97137e1064ad67484d7206487902ef8f8b7a34318474ba0f6113e9dc15c4e30e7711f641a82f6672ad2e039a09228db6db2287", "0xd021d1d6cc99274c090b16afe3a1c4314f48316dcd2a2cdebaaa2896c51c9f3d779bd7be01a2fdc17093c2c9a633", "0xb7a91bb84e0abaef90b4d4293d0c4968a2", "0x62c668fb320f44c7fec283e7827a6ce1ff", 12083, Accept}, - {"0x51ebd23d2a02f976d3d9aae2061e06a8c0452b4b556443606ade0c71ea57a8463bbad81a5d6312237f8ab6194e18feb808631d40318b608cb7fe876d3ded24f04bbddc053a3be4579f5c6cdadc3ed1192a5016c609ff80a76677ed214ed0e5e04bf70bc03b6c15b999c4d343466b0324fa5a0a42ad60885885ab43f928991f30783819caba87e247837b0948b1d8d050ed", "0x1fbe100e76befa9e13", "0x576df8720f308d6e342644063415492caa9d4a11c80030e25f7541e56e62869d15b08a8807d789870194b0c7a325cf9d13e49c654b08965c8eb3e144145fb7", "0x197bcf7eafab09fa4776dbaa9ebb8cc2ff6787d1a800819cd75f507a1372157085dc171f6e501edf2b44d46038d3fa49e0db8ebac4b41461bdce184585edd9", 2200, Accept}, - {"0x9c61f9e2209144eeabc02cb02e5db1484544e33478eb374a18be5baf71b16e91d9ab86882984ea9ba16fde77eb0ef161c497c1883e0cbdb8dca844ad7b8f270073ab640c385e4f9512", "0x648110c896453b6f1a3a0b234f5c0f8b7c4b4d958458280372a6232f9d98cd1420df6a5691fa1bf773f6bd", "0xe5e14e935f795ad814f54d95248b0102b2b0c351e8a5112541343c024d90dfa43c702eea820354a2670563425ee515c4dcc2c6bed73234b0e77384f3ba64a1b8b068149b2363566cdf9c80af", "0xa1d077b5157b4e829c814a0e8fb7e9b83e30e65ff46d3264dc619063a2de57fb171e050c5ec85715b73a8a8a7d2b155a6a77855edea62c9a6850c8dec1551bde227304b7df2811245405fdd5", 3535, Accept}, - {"0xe1b779ff6951aee456aeaf87a963678eaaae4ed61f387b68e4f196ff71440a5955b9a8c2144dbe4c00717b157564f21b54c8c3934bb43754af039068ba03d1ab7c53f9d5526842cb", "0x6bc12883963b0f0b6eb6c275bcc9", "0x072c23cab0e4dbc633b86e45a6b9bdfaf87076bf618c08b142d42b7ad8c3ea4795e873dad518ace0f9e84a4e265191972e303bfbbd6cff781de09a1ccb19f0dc5d874dfc3b89bfa666b391068c3dc9c183f04e4dcd1ec80ed92fd4c792e102904817a41ae1208ed39a9484d61e10491859924a0a04a9455e36458702dcd8a312176e9a05e0e45a14783ecaa7e93f410a8a51848ad706d014467d634fa7c0d6756f54e5f980", "0x06d4a5ffa4568bf3bc20ff29f74941b212ec9121d936d6becbfde46dca5034e0749fead5293e42331a922d1e6a64efc42bf165ca3853c2a80248d32fdf70a6c233ef32851b85ea1c1b51ca364dd2bcd9f25a6249c014dc36b123382099f5f060eb8c0b6d13e4facb932fa49ae140b917dad9e82076e71b407928405aa449fb66eacd97429296a8f8d3cafd8660d124f5d98e6fdb9f1a74ba10a76b724bb8ef4ec98c749b6b", 4041, Accept}, + {"0x51ebd23d2a02f976d3d9aae2061e06a8c0452b4b556443606ade0c71ea57a8463bbad81a5d6312237f8ab6194e18feb808631d40318b608cb7fe876d3ded24f04bbddc053a3be4579f5c6cdadc3ed1192a5016c609ff80a76677ed214ed0e5e04bf70bc03b6c15b999c4d343466b0324fa5a0a42ad60885885ab43f928991f30783819caba87e247837b0948b1d8d050ed", "0x1fbe100e76befa9e13", "0x576df8720f308d6e342644063415492caa9d4a11c80030e25f7541e56e62869d15b08a8807d789870194b0c7a325cf9d13e49c654b08965c8eb3e144145fb7", "0x197bcf7eafab09fa4776dbaa9ebb8cc2ff6787d1a800819cd75f507a1372157085dc171f6e501edf2b44d46038d3fa49e0db8ebac4b41461bdce184585edd9", 2117, Accept}, + {"0x9c61f9e2209144eeabc02cb02e5db1484544e33478eb374a18be5baf71b16e91d9ab86882984ea9ba16fde77eb0ef161c497c1883e0cbdb8dca844ad7b8f270073ab640c385e4f9512", "0x648110c896453b6f1a3a0b234f5c0f8b7c4b4d958458280372a6232f9d98cd1420df6a5691fa1bf773f6bd", "0xe5e14e935f795ad814f54d95248b0102b2b0c351e8a5112541343c024d90dfa43c702eea820354a2670563425ee515c4dcc2c6bed73234b0e77384f3ba64a1b8b068149b2363566cdf9c80af", "0xa1d077b5157b4e829c814a0e8fb7e9b83e30e65ff46d3264dc619063a2de57fb171e050c5ec85715b73a8a8a7d2b155a6a77855edea62c9a6850c8dec1551bde227304b7df2811245405fdd5", 3525, Accept}, + {"0xe1b779ff6951aee456aeaf87a963678eaaae4ed61f387b68e4f196ff71440a5955b9a8c2144dbe4c00717b157564f21b54c8c3934bb43754af039068ba03d1ab7c53f9d5526842cb", "0x6bc12883963b0f0b6eb6c275bcc9", "0x072c23cab0e4dbc633b86e45a6b9bdfaf87076bf618c08b142d42b7ad8c3ea4795e873dad518ace0f9e84a4e265191972e303bfbbd6cff781de09a1ccb19f0dc5d874dfc3b89bfa666b391068c3dc9c183f04e4dcd1ec80ed92fd4c792e102904817a41ae1208ed39a9484d61e10491859924a0a04a9455e36458702dcd8a312176e9a05e0e45a14783ecaa7e93f410a8a51848ad706d014467d634fa7c0d6756f54e5f980", "0x06d4a5ffa4568bf3bc20ff29f74941b212ec9121d936d6becbfde46dca5034e0749fead5293e42331a922d1e6a64efc42bf165ca3853c2a80248d32fdf70a6c233ef32851b85ea1c1b51ca364dd2bcd9f25a6249c014dc36b123382099f5f060eb8c0b6d13e4facb932fa49ae140b917dad9e82076e71b407928405aa449fb66eacd97429296a8f8d3cafd8660d124f5d98e6fdb9f1a74ba10a76b724bb8ef4ec98c749b6b", 4007, Accept}, {"0x4594ea63dd8b77f34701aaae1f430d4adee9811213bde681fd750cd4bff65322654553180248e580de54da02365dbcc61ad6039a61c0c5783872038cccf618ce10757b50d4f58529cc2d6d9ca30543e8ddedc481757a679101", "0xd32aab68fadf838e361d75da2ce241dd0b95dd38e3ceb860975e39d4eab04e84581269d22dc8880395c6c091b3859cd9fa031186af5bc0f23d6ada8fbfae9f7dcc307d862c", "0x7b646597ae005c1b0c2bc981917294e669a47fc12b27d08c1741caa5d31c68", "0x317c66d1e4a6e22d60a1299aaec61f9e8668e08bb94b11e59e32d1daad8e11", 7122, Accept}, {"0x44dfe16a0cc499362a5a6b6b5d4167b9e45c3bbd1b98b494e99ff71010013a8c816816f112a69f9e70a320625c149555e1276bad70999da1b3c124e5c54cbeb02b534f845ffdfcede15b01fa8d0bd8f22b95ace6cf5d0aa97cb81f1688afecef51cc48fdf3155185090e8249795af2c26997ea1a915fd85b5a8bf9cce7c7dbbb6f268cd424e6b86331d32a6e4cf783957160", "0xc392b4bfe312f474a02d0860823a05a8a6d5846f1db0a9245a1f64cb354b5ab91590d24ebba8ef68a369d25932b1acfcb33b6af52a260313bcc0493c", "0x5492af9556e685fd639f80e42fae3e0cc4588233f4683017d376c5746b3eea", "0x4e13bec8183024e881e1688458437e0a3c49959c3099329c4540c0b5840a70", 13688, Accept}, - {"0x2ea1312db704ff29e0", "0x038302a78381a38adcb7581cbeb7a0797289d82d14a85cf4c36df72c5b5c3d464c4a280f930a85ef4aefb54ce935d01a18afd42d9a679140a360f2b185ac37fde9890d2808a6675e3d73bc696921babefa9cb1985b948e65734fae0515f0e6b7ef782bef9f1a4921c5df3e340e764bf6c347614c5649e645f3bdaaa2c7dbcc16b5107056", "0x71166d7a0b32f8cbd2f682474b61c5535e2867562bcf5dd5e43d2a4e036b78e871b18145e6da2ef327da994965ade4bb985f4f2402da936a6f90d0913512add104dc10741c06b948e911b8fbe9", "0x4540d8df3bc9cd82ff6e431440f65fd58165a43783dccabc315f5a33fa3a581068ca5ae3ede591e302fc863eff657b962d0e671235fc97456439921ec9023eff5b8256b056eb47eae5911f2e6b", 10658, Accept}, - {"0x1f206df741a36c542fb5e609c9299e62a96ee677ca7266d85d086d4ebd6ab9b52c56539c41b0a1a69a0a5dfc794cd6076360643660147c053f821992bf5c787a1fed53eab8f61e0d538aa3a352616774d419c7be55415e60a86f296d1baa199284ebd2ff12eb2b84a7dfedbe1d34efd3219265f302b91963416e42145bfbdf7d0132b1d32c98129521a61d92e2318f94b87f96f68eefe5263717999ab1780f9c15e895a5c188e47518b209f61c3a501e315c4ea0504de653d9b3f9d25658c1c30b99fa6b2a02ff99838d04b86bbeb13ca94d90fd96aca7eae17bc76cf13e33cf37769ad7bf98c6f151c3961d2157aa63ebd577f2f5dbb67805df9a649942843c", "0x37c463", "0x8eb38552534a9ca188412677f154eeba8f011cf6ae00472dcca54c068d57825ff7f703b1a8380d2fc9a7e1e142f8770a7da52e2d47638853aacbe450a80f2c35a9ce0e5feca7bfff871252ec2c5754cafdcde3cd20ce4767c23042570d3d9641e8517ea4c3f10d7f4ea927824d948aed87de2b856347faba08be786ad3d9f30cd1bc4b036dd4a0053c59d11fc2840aefde47222a0273323f45b08539313de7393d24ade84f8f57c719986db04a0f3f483375e5779c8b8ce913991a80ea6cb368bb3f1f2c3dc3d424d7c0ae607c6d052dc7b0ae170250e1ad10e6b327857cb8610904c526d51430c31931d4ba3d5ebab8d6321c48d6d482f5b129f69871f4405f", "0x1d6e8af1caa1098ce2429e32eb831598f6b28a65376e54fe863283b545949586e2f3b41285d6047fcd52d164be131325f80412d2ca8bb84dd945ae69b3e1bc4fa861905b1f3032ea7279d2ed3c03f78ece1c0d0f159e0a4776d1ee47516e4379105491c37d6bc86bc26420966076d114f5a4091e800259f59073fd5f7c0100fbcbc10a9f7cef6fcd03c04ae97b54994ea479e168bc00ae9ea84ad07497aa470d3d438ccfa669de5d99ccf36a2ab1773378101123f5bcdd9a6f5a1df889b8a0bbc071d692d68b69801cfa467bdcb8d00dc5f32be5ca907433667691527534c229701ec929ef836c7caef7a088205082f98a08860ed72d383e6ac256aa3680f3c6", 1884, Accept}, - {"0x7c391cf4e56c7c104d90177402b2e1a0f9179a06304f4357e4b146e116cd12e0f1c12bfc66171b8c8be104d09c304c340e125c4b6fda63b94315d74ad0e8b8178edac81b475da5dc7e825c309a4c0b5fb3c3e0bd7f94dc661cd8ac546940779e54edf58c6ace5589914541935bc66fff64442d8bc2e6dc8420257c8ab0a877729fe8", "0x74d69cdea330c38633c7bca9fb46d2e1e2050e5220c5fa3194584c62b4ebd3e85a70fd2f994d04681fc8aa32e580f87484b78ff8d3bab0412874e55772411288f4a6196f9da6db7aaebbf0d62e4e42275dfa475ac35802d912aacfb4f77e945f4e5e3c28610ddbd479280df848cd57829746fcc6452a5d4127b4f8b27a3149bc", "0x9000b0b587f64e78f51645a75d98b64d7fa1001d1636bcae53ea41f9f955f67f79c442adbca55d59c61642ed91364feef5e5147ed229cd5ff1d31b6c333a65f95e80c576f11ce4790c3162c351bd7df796c6e2184a387edea127c6ddf46a6eae6ade4066de609d655832b98b", "0x810f16edd6ffee0cad631b2f59ad6b3847f80974ce4376353ad1f8f487dae65e93ddb9552cc93b0725acb1ba3551132c138ef730568c3fde71918608edf3f78130170124d0a4d3d28fcd2cefb256465eb18e80ea0576fd1df44e76786a450285a0eef852b7df639925795293", 24014, Accept}, - {"0x9b7e403f0d0134635f90d344dbce30ac511e8e5e274a3436ccb75503d0ee72a3ba59c2a9b774ee74abe082e09702c65151186706c62200241d306d8cb18b40278c885222db5d001aecceff20e4be25ed83d4ff7d40c4c6e513a63238a5c07e45da3a24868caa67fae36047d955a648dd1c741284cdb8bc282c01b9d66d2c5b651268ff1d50356f1dc6be6d59814d7787e6", "0x30c54b", "0x093fd6b228d5d2268a36b0a1b8fb7dbcb4669c22e0cc2a5deaa3c3da890c5fa23dc0", "0x2a3d94206458cce1a0cee7ef45b3812de4f2ae4ee9b347acf55385eca217159f6b76b7c14774aa54e9667bb172d66b25d907682576a2ec7f2038c07e4f", 0, Panic}, + {"0x2ea1312db704ff29e0", "0x038302a78381a38adcb7581cbeb7a0797289d82d14a85cf4c36df72c5b5c3d464c4a280f930a85ef4aefb54ce935d01a18afd42d9a679140a360f2b185ac37fde9890d2808a6675e3d73bc696921babefa9cb1985b948e65734fae0515f0e6b7ef782bef9f1a4921c5df3e340e764bf6c347614c5649e645f3bdaaa2c7dbcc16b5107056", "0x71166d7a0b32f8cbd2f682474b61c5535e2867562bcf5dd5e43d2a4e036b78e871b18145e6da2ef327da994965ade4bb985f4f2402da936a6f90d0913512add104dc10741c06b948e911b8fbe9", "0x4540d8df3bc9cd82ff6e431440f65fd58165a43783dccabc315f5a33fa3a581068ca5ae3ede591e302fc863eff657b962d0e671235fc97456439921ec9023eff5b8256b056eb47eae5911f2e6b", 10598, Accept}, + {"0x1f206df741a36c542fb5e609c9299e62a96ee677ca7266d85d086d4ebd6ab9b52c56539c41b0a1a69a0a5dfc794cd6076360643660147c053f821992bf5c787a1fed53eab8f61e0d538aa3a352616774d419c7be55415e60a86f296d1baa199284ebd2ff12eb2b84a7dfedbe1d34efd3219265f302b91963416e42145bfbdf7d0132b1d32c98129521a61d92e2318f94b87f96f68eefe5263717999ab1780f9c15e895a5c188e47518b209f61c3a501e315c4ea0504de653d9b3f9d25658c1c30b99fa6b2a02ff99838d04b86bbeb13ca94d90fd96aca7eae17bc76cf13e33cf37769ad7bf98c6f151c3961d2157aa63ebd577f2f5dbb67805df9a649942843c", "0x37c463", "0x8eb38552534a9ca188412677f154eeba8f011cf6ae00472dcca54c068d57825ff7f703b1a8380d2fc9a7e1e142f8770a7da52e2d47638853aacbe450a80f2c35a9ce0e5feca7bfff871252ec2c5754cafdcde3cd20ce4767c23042570d3d9641e8517ea4c3f10d7f4ea927824d948aed87de2b856347faba08be786ad3d9f30cd1bc4b036dd4a0053c59d11fc2840aefde47222a0273323f45b08539313de7393d24ade84f8f57c719986db04a0f3f483375e5779c8b8ce913991a80ea6cb368bb3f1f2c3dc3d424d7c0ae607c6d052dc7b0ae170250e1ad10e6b327857cb8610904c526d51430c31931d4ba3d5ebab8d6321c48d6d482f5b129f69871f4405f", "0x1d6e8af1caa1098ce2429e32eb831598f6b28a65376e54fe863283b545949586e2f3b41285d6047fcd52d164be131325f80412d2ca8bb84dd945ae69b3e1bc4fa861905b1f3032ea7279d2ed3c03f78ece1c0d0f159e0a4776d1ee47516e4379105491c37d6bc86bc26420966076d114f5a4091e800259f59073fd5f7c0100fbcbc10a9f7cef6fcd03c04ae97b54994ea479e168bc00ae9ea84ad07497aa470d3d438ccfa669de5d99ccf36a2ab1773378101123f5bcdd9a6f5a1df889b8a0bbc071d692d68b69801cfa467bdcb8d00dc5f32be5ca907433667691527534c229701ec929ef836c7caef7a088205082f98a08860ed72d383e6ac256aa3680f3c6", 1744, Accept}, + {"0x7c391cf4e56c7c104d90177402b2e1a0f9179a06304f4357e4b146e116cd12e0f1c12bfc66171b8c8be104d09c304c340e125c4b6fda63b94315d74ad0e8b8178edac81b475da5dc7e825c309a4c0b5fb3c3e0bd7f94dc661cd8ac546940779e54edf58c6ace5589914541935bc66fff64442d8bc2e6dc8420257c8ab0a877729fe8", "0x74d69cdea330c38633c7bca9fb46d2e1e2050e5220c5fa3194584c62b4ebd3e85a70fd2f994d04681fc8aa32e580f87484b78ff8d3bab0412874e55772411288f4a6196f9da6db7aaebbf0d62e4e42275dfa475ac35802d912aacfb4f77e945f4e5e3c28610ddbd479280df848cd57829746fcc6452a5d4127b4f8b27a3149bc", "0x9000b0b587f64e78f51645a75d98b64d7fa1001d1636bcae53ea41f9f955f67f79c442adbca55d59c61642ed91364feef5e5147ed229cd5ff1d31b6c333a65f95e80c576f11ce4790c3162c351bd7df796c6e2184a387edea127c6ddf46a6eae6ade4066de609d655832b98b", "0x810f16edd6ffee0cad631b2f59ad6b3847f80974ce4376353ad1f8f487dae65e93ddb9552cc93b0725acb1ba3551132c138ef730568c3fde71918608edf3f78130170124d0a4d3d28fcd2cefb256465eb18e80ea0576fd1df44e76786a450285a0eef852b7df639925795293", 23991, Accept}, + {"0x9b7e403f0d0134635f90d344dbce30ac511e8e5e274a3436ccb75503d0ee72a3ba59c2a9b774ee74abe082e09702c65151186706c62200241d306d8cb18b40278c885222db5d001aecceff20e4be25ed83d4ff7d40c4c6e513a63238a5c07e45da3a24868caa67fae36047d955a648dd1c741284cdb8bc282c01b9d66d2c5b651268ff1d50356f1dc6be6d59814d7787e6", "0x30c54b", "0x093fd6b228d5d2268a36b0a1b8fb7dbcb4669c22e0cc2a5deaa3c3da890c5fa23dc0", "0x2a3d94206458cce1a0cee7ef45b3812de4f2ae4ee9b347acf55385eca217159f6b76b7c14774aa54e9667bb172d66b25d907682576a2ec7f2038c07e4f", 0, Reject}, } for i, tc := range cases { // use subtests so that we can run all tests despite failures t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { - progText := fmt.Sprintf(`byte %s -byte %s -byte %s -bmodexp -byte %s -== -assert -global OpcodeBudget -int %d -==`, tc.Base, tc.Exponent, tc.Modulus, tc.Result, testLogicBudget-7-tc.LogicCost) + progText := fmt.Sprintf("pushbytes %s; pushbytes %s; pushbytes %s; bmodexp; pushbytes %s; ==", + tc.Base, tc.Exponent, tc.Modulus, tc.Result) switch tc.TestOutcome { case Accept: testAccepts(t, progText, 12) + testCost(t, progText, tc.LogicCost+5, 12) // 4 pushbytes, and == case Reject: testRejects(t, progText, 12) // ensure nobody thinks they are testing a cost here @@ -5294,9 +5393,11 @@ int %d func TestBytesMath(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + testAccepts(t, "byte 0x01; byte 0x01; b+; byte 0x02; ==", 4) + testCost(t, "byte 0x01; byte 0x02; b+; byte 0x03; ==", 2+10+2, 6) + testCost(t, "byte 0x01; byte 0x02; b+; byte 0x03; ==", 2+2+2, 12) testAccepts(t, "byte 0x01FF; byte 0x01; b+; byte 0x0200; ==", 4) effs := strings.Repeat("ff", 64) @@ -5304,10 +5405,11 @@ func TestBytesMath(t *testing.T) { testAccepts(t, fmt.Sprintf("byte 0x%s; byte 0x10; b+; len; int 65; ==", effs), 4) // 4096 byte inputs that sum to 4097 byte outputs panic, no matter the version. testPanics(t, notrack(fmt.Sprintf("byte 0x%s; byte 0x10; b+; len; int 4097; ==", strings.Repeat("ff", 4096))), 4) - // 65 byte inputs are not ok until v12. - p := fmt.Sprintf("byte 0x%s00; byte 0x10; b-; len; int 65; ==", effs) + // 65 byte inputs are not ok for b+ until v12. + p := fmt.Sprintf("byte 0x%s00; byte 0x10; b+; len; int 65; ==", effs) testPanicRange(t, notrack(p), 4, 11) testAcceptRange(t, p, 12, AssemblerMaxVersion) + testCost(t, p, 2+6+3, 12) testAccepts(t, `byte 0x01; byte 0x01; b-; byte ""; ==`, 4) testAccepts(t, "byte 0x0200; byte 0x01; b-; byte 0x01FF; ==", 4) @@ -5315,35 +5417,103 @@ func TestBytesMath(t *testing.T) { testAccepts(t, "byte 0x0100; byte 0x01; b-; byte 0xFF; ==", 4) testPanics(t, "byte 0x01; byte 0x02; b-; int 1; return", 4) + // 65 byte inputs are not ok for b- until v12. + p = fmt.Sprintf("byte 0x%s00; byte 0x10; b-; len; int 65; ==", effs) + testPanicRange(t, notrack(p), 4, 11) + testAcceptRange(t, p, 12, AssemblerMaxVersion) + testCost(t, p, 2+6+3, 12) + + testAccepts(t, "byte 0x10; byte 0x10; b*; byte 0x0100; ==", 4) + testAccepts(t, "byte 0x100000000000; byte 0x00; b*; byte b64(); ==", 4) + testCost(t, "byte 0x100000000000; byte 0x00; b*; byte b64(); ==", 20+4, 6) + testCost(t, "byte 0x100000000000; byte 0x2000; b*; byte 0x0200000000000000; ==", 5+4, 12) + testAccepts(t, "byte 0x01; byte 0x01; b/; byte 0x01; ==", 4) testPanics(t, "byte 0x0200; byte b64(); b/; int 1; return", 4) testPanics(t, "byte 0x01; byte 0x00; b/; int 1; return", 4) - testPanics(t, "int 65; bzero; byte 0x01; b/; int 1; return", 4) + p = "int 65; bzero; byte 0x01; b/; byte 0x; ==" + testPanicRange(t, p, 4, 11) + testAcceptRange(t, p, 12, AssemblerMaxVersion) testAccepts(t, "byte 0x10; byte 0x07; b%; byte 0x02; ==; return", 4) testPanics(t, "byte 0x01; byte 0x00; b%; int 1; return", 4) - testPanics(t, "int 65; bzero; byte 0x10; b%", 4) + p = "byte 0x01; int 64; bzero; concat; byte 0x10; b%; byte 0x00; b==" + testPanicRange(t, p, 4, 11) + testAcceptRange(t, p, 12, AssemblerMaxVersion) // Even 128 byte outputs are ok - testAccepts(t, fmt.Sprintf("byte 0x%s; byte 0x%s; b*; len; int 128; ==", effs, effs), 4) + p = fmt.Sprintf("byte 0x%s; byte 0x%s; b*; len; int 128; ==", effs, effs) + testAccepts(t, p, 4) + testCost(t, p, 26, 6) // 20 + 6 (5 obvious instructions plus bytecblock) + assert.Equal(t, 12, mulComplexity(64, 64)) + testCost(t, p, 18, 12) // 12 + 6 + // But not 65 byte inputs (until v12) + p = fmt.Sprintf("byte 0x%s; byte 0xff; concat; byte 0xff; b*; len; int 66; ==", effs) + testPanicRange(t, p, 4, 11) + testAcceptRange(t, p, 12, AssemblerMaxVersion) + assert.Equal(t, 8, mulComplexity(65, 1)) + testCost(t, p, 16, 12) // 8 + 8 (7 obvious instructions, plus bytecblock) - testAccepts(t, "byte 0x00; bsqrt; byte 0x; ==; return", 6) testAccepts(t, "byte 0x01; bsqrt; byte 0x01; ==; return", 6) + testAccepts(t, "byte 0x00; bsqrt; byte 0x; ==; return", 6) testAccepts(t, "byte 0x10; bsqrt; byte 0x04; ==; return", 6) testAccepts(t, "byte 0x11; bsqrt; byte 0x04; ==; return", 6) testAccepts(t, "byte 0xffffff; bsqrt; len; int 2; ==; return", 6) // 64 byte long inputs are accepted - testAccepts(t, fmt.Sprintf("byte 0x%s; bsqrt; len; int 32; ==", effs), 6) - // 65 byte inputs are not ok (no track allows assembly) - testPanics(t, notrack(fmt.Sprintf("byte 0x%s00; bsqrt; pop; int 1", effs)), 6) + p = fmt.Sprintf("byte 0x%s; bsqrt; len; int 32; ==", effs) + testAccepts(t, p, 6) + testCost(t, p, 44, 6) // 40 + 4 + testCost(t, p, 73, 12) // 69 + 4 + // 65 byte inputs are not ok until v12 (no track allows assembly) + p = notrack(fmt.Sprintf("byte 0x%s00; bsqrt; len; int 33; ==", effs)) + testPanicRange(t, p, 6, 11) + testAcceptRange(t, p, 12, AssemblerMaxVersion) + testCost(t, p, 74, 12) // 70 + 4 + + // 128, mostly for cost test + p = notrack(fmt.Sprintf("byte 0x%s%s; bsqrt; len; int 64; ==", effs, effs)) + testPanicRange(t, p, 6, 11) + testAcceptRange(t, p, 12, AssemblerMaxVersion) + testCost(t, p, 145, 12) // 141 + 4 } -func TestBytesCompare(t *testing.T) { +func TestMulComplexity(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() + + a := assert.New(t) + // test with sizes that are roughly equal + a.Equal(5, mulComplexity(0, 0)) + a.Equal(5, mulComplexity(6, 7)) + a.Equal(6, mulComplexity(16, 7)) + a.Equal(6, mulComplexity(16, 16)) + a.Equal(7, mulComplexity(32, 16)) + a.Equal(21, mulComplexity(128, 128)) + // demonstrate nice continuity near the changeover at 512 + a.Equal(126, mulComplexity(511, 511)) + a.Equal(130, mulComplexity(512, 512)) + a.Equal(130, mulComplexity(513, 513)) + a.Equal(130, mulComplexity(513, 511)) + + // test for bad discontinuities at 512 + for la := range 4096 { + below := mulComplexity(la, 511) + above := mulComplexity(la, 512) + a.GreaterOrEqual(above, below) // bigger input has bigger cost + a.GreaterOrEqual(1+la/25, above-below, la) // the cost goes up a "reasonable" amount, compared to length + } + + // silly exhaustive test. only takes a few seconds + for la := range 4096 { + for lb := range 4096 { + a.Equal(mulComplexity(la, lb), mulComplexity(lb, la)) + } + } +} +func TestBytesCompare(t *testing.T) { + partitiontest.PartitionTest(t) t.Parallel() - testAccepts(t, "byte 0x10; byte 0x10; b*; byte 0x0100; ==", 4) - testAccepts(t, "byte 0x100000000000; byte 0x00; b*; byte b64(); ==", 4) testAccepts(t, "byte 0x10; byte 0x10; b<; !", 4) testAccepts(t, "byte 0x10; byte 0x10; b<=", 4) diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index e8fc2f6b4a..b0f78a07da 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -274,11 +274,6 @@ func costByFn(cost costFn, doc string) OpDetails { return d } -func (d OpDetails) fnCost(cost costFn, doc string) OpDetails { - d.FullCost = costDescriptor{fn: cost, doc: doc}.check() - return d -} - func only(m RunMode) OpDetails { d := detDefault() d.Modes = m @@ -714,23 +709,23 @@ var OpSpecs = []OpSpec{ {0x94, "exp", opExp, proto("ii:i"), 4, detDefault()}, {0x95, "expw", opExpw, proto("ii:ii"), 4, costly(10)}, {0x96, "bsqrt", opBytesSqrt, proto("I:I"), 6, costly(40)}, - {0x96, "bsqrt", opBytesSqrt, proto("b:b"), fullByteMathVersion, costly(222)}, + {0x96, "bsqrt", opBytesSqrt, proto("b:b"), fullByteMathVersion, costByFn(bsqrtCost, "5 + (len(A) // 96)^2*bitlen(len(A)) + len(A)")}, {0x97, "divw", opDivw, proto("iii:i"), 6, detDefault()}, {0x98, "sha3_256", opSHA3_256, proto("b:b{32}"), 7, costly(130)}, /* Will end up following keccak256 - {0x98, "sha3_256", opSHA3_256, proto("b:b{32}"), ?, costByLength(...)},}, */ - {0x99, "bmodexp", opBytesModExp, proto("bbb:b"), 12, costByFn(bmodexpCost, "((len(B) * max(len(A), len(C)) ^ 1.63) / 15) + 200")}, + {0x99, "bmodexp", opBytesModExp, proto("bbb:b"), 12, costByFn(bmodexpCost, "((bitlen(B) * max(len(A), len(C)) ^ 1.63) / 120) + 200")}, // Byteslice math. {0xa0, "b+", opBytesPlus, proto("II:b"), 4, costly(10).typed(typeByteMath(maxByteMathSize + 1))}, - {0xa0, "b+", opBytesPlus, proto("bb:b"), fullByteMathVersion, costByFn(bplusCost, "8 + max(len(A), len(B))//16")}, + {0xa0, "b+", opBytesPlus, proto("bb:b"), fullByteMathVersion, costByFn(bplusCost, "1 + max(len(A), len(B))//16")}, {0xa1, "b-", opBytesMinus, proto("II:I"), 4, costly(10)}, - {0xa1, "b-", opBytesMinus, proto("bb:b"), fullByteMathVersion, costByLength(8, 1, 16, 1)}, + {0xa1, "b-", opBytesMinus, proto("bb:b"), fullByteMathVersion, costByLength(1, 1, 16, 1)}, {0xa2, "b/", opBytesDiv, proto("II:I"), 4, costly(20)}, - {0xa2, "b/", opBytesDiv, proto("bb:b"), fullByteMathVersion, costly(222)}, + {0xa2, "b/", opBytesDiv, proto("bb:b"), fullByteMathVersion, costByFn(bdivCost, "hmm")}, {0xa3, "b*", opBytesMul, proto("II:b"), 4, costly(20).typed(typeByteMath(maxByteMathSize * 2))}, - {0xa3, "b*", opBytesMul, proto("bb:b"), fullByteMathVersion, costly(222)}, + {0xa3, "b*", opBytesMul, proto("bb:b"), fullByteMathVersion, costByFn(bmulCost, "hmm")}, {0xa4, "b<", opBytesLt, proto("II:T"), 4, detDefault()}, {0xa4, "b<", opBytesLt, proto("bb:T"), fullByteMathVersion, detDefault()}, {0xa5, "b>", opBytesGt, proto("II:T"), 4, detDefault()}, @@ -744,7 +739,7 @@ var OpSpecs = []OpSpec{ {0xa9, "b!=", opBytesNeq, proto("II:T"), 4, detDefault()}, {0xa9, "b!=", opBytesNeq, proto("bb:T"), fullByteMathVersion, detDefault()}, {0xaa, "b%", opBytesModulo, proto("II:I"), 4, costly(20)}, - {0xaa, "b%", opBytesModulo, proto("bb:b"), fullByteMathVersion, costly(222)}, + {0xaa, "b%", opBytesModulo, proto("bb:b"), fullByteMathVersion, costByFn(bdivCost, "hmm")}, {0xab, "b|", opBytesBitOr, proto("bb:b"), 4, costly(6)}, {0xab, "b|", opBytesBitOr, proto("bb:b"), fullByteMathVersion, costByLength(1, 1, 32, 0)}, {0xac, "b&", opBytesBitAnd, proto("bb:b"), 4, costly(6)},