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() diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index ca17f4d4af..f69441f863 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..0ff17ddfd8 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,116 @@ 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() + type TestOutcome int + const ( + Accept TestOutcome = iota + Reject + Panic + ) + type ModexpTestVector struct { + Base string + Exponent string + Modulus string + Result string + LogicCost int + 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 + {"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 { + progText := fmt.Sprintf(`byte %s +byte %s +byte %s +bmodexp +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, 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..3fc4eb3b91 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,51 @@ 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 + + // 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 + 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 +139,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 +162,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 +289,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 +392,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,6 +856,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, defaultCustomCost(bmodexpCustomCost)}, } // 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" } ] },