Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

AVM: new teal opcodes for the MiMC hash function to support Zero Knowledge Proofs #5978

Merged
merged 21 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 27 additions & 16 deletions data/transactions/logic/assembler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,15 @@ dup; dup
falcon_verify
`

const mimcBN254Nonsense = `
pushbytes 0x11223344556677889900aabbccddeeff11223344556677889900aabbccddeeff
mimc_BN254
`
const mimcBLS12381Nonsense = `
pushbytes 0x11223344556677889900aabbccddeeff11223344556677889900aabbccddeeff
mimc_BLS12_381
`

const v8Nonsense = v7Nonsense + switchNonsense + frameNonsense + matchNonsense + boxNonsense

const v9Nonsense = v8Nonsense
Expand All @@ -445,7 +454,7 @@ const spliceNonsence = `

const v10Nonsense = v9Nonsense + pairingNonsense + spliceNonsence

const v11Nonsense = v10Nonsense + stateProofNonsense
const v11Nonsense = v10Nonsense + stateProofNonsense + mimcBN254Nonsense + mimcBLS12381Nonsense

const v6Compiled = "2004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a"

Expand All @@ -468,8 +477,10 @@ const spliceCompiled = "d2d3"
const v10Compiled = v9Compiled + pairingCompiled + spliceCompiled

const stateProofCompiled = "80070123456789abcd86494985"
const mimcBN254Compiled = "802011223344556677889900aabbccddeeff11223344556677889900aabbccddeefff0"
const mimcBLS12381Compiled = "802011223344556677889900aabbccddeeff11223344556677889900aabbccddeefff1"

const V11Compiled = v10Compiled + stateProofCompiled
const V11Compiled = v10Compiled + stateProofCompiled + mimcBN254Compiled + mimcBLS12381Compiled

var nonsense = map[uint64]string{
1: v1Nonsense,
Expand Down Expand Up @@ -3168,7 +3179,7 @@ func TestMacros(t *testing.T) {
#define ==? ==; bnz
pushint 1; pushint 2; ==? label1
err
label1:
label1:
pushint 1`,
)

Expand Down Expand Up @@ -3206,19 +3217,19 @@ func TestMacros(t *testing.T) {
pushbytes 0xddf2554d
txna ApplicationArgs 0
==
bnz kickstart
pushbytes 0x903f4535
bnz kickstart
pushbytes 0x903f4535
txna ApplicationArgs 0
==
bnz portal_transfer
bnz portal_transfer
kickstart:
pushint 1
portal_transfer:
pushint 1
`, `
#define abi-route txna ApplicationArgs 0; ==; bnz
method "kickstart(account)void"; abi-route kickstart
method "portal_transfer(byte[])byte[]"; abi-route portal_transfer
#define abi-route txna ApplicationArgs 0; ==; bnz
method "kickstart(account)void"; abi-route kickstart
method "portal_transfer(byte[])byte[]"; abi-route portal_transfer
kickstart:
pushint 1
portal_transfer:
Expand Down Expand Up @@ -3274,7 +3285,7 @@ add:
extract_uint32
stores

load 1; load 2; +
load 1; load 2; +
store 255

int 255
Expand All @@ -3298,11 +3309,11 @@ add:
#define abi-decode-uint32 ;int 0; extract_uint32;
#define abi-encode-uint32 ;itob;extract 4 0;

#define abi-encode-bytes ;dup; len; abi-encode-uint16; swap; concat;
#define abi-encode-bytes ;dup; len; abi-encode-uint16; swap; concat;
#define abi-decode-bytes ;extract 2 0;

// abi method handling
#define abi-route ;txna ApplicationArgs 0; ==; bnz
// abi method handling
#define abi-route ;txna ApplicationArgs 0; ==; bnz
#define abi-return ;pushbytes 0x151f7c75; swap; concat; log; int 1; return;

// stanza: "set $var from-{type}"
Expand Down Expand Up @@ -3331,15 +3342,15 @@ echo:


// add handler
method "add(uint32,uint32)uint32"; abi-route add
method "add(uint32,uint32)uint32"; abi-route add
add:
#define x 1
parse x from-uint32
parse x from-uint32

#define y 2
parse y from-uint32

#define sum 255
#define sum 255
load x; load y; +; store sum

returns sum as-uint32
Expand Down
61 changes: 61 additions & 0 deletions data/transactions/logic/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,69 @@
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-sumhash"
"golang.org/x/crypto/sha3"

bls12_381fr "github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
bls12_381mimc "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/mimc"
bn254fr "github.com/consensys/gnark-crypto/ecc/bn254/fr"
bn254mimc "github.com/consensys/gnark-crypto/ecc/bn254/fr/mimc"
)

// mimc on curve BLS12-381 is implemented for compatibility with zk circuits,
// matching the implementation in circuits generated by gnark
func opMimcBLS12381(cx *EvalContext) error {
last := len(cx.Stack) - 1
data := cx.Stack[last].Bytes
if len(data)%32 != 0 {
return fmt.Errorf("the input data must be a multiple of 32 bytes")

Check warning on line 46 in data/transactions/logic/crypto.go

View check run for this annotation

Codecov / codecov/patch

data/transactions/logic/crypto.go#L46

Added line #L46 was not covered by tests
}
hasher := bls12_381mimc.NewMiMC()
blockSize := hasher.BlockSize()
for i := 0; i < len(data); i += 32 {
d := data[i : i+32]
// ensure the input to hasher.Write is smaller than the curve modulus,
// otherwise hasher.Write will fail
n := new(big.Int).SetBytes(d)
n.Mod(n, bls12_381fr.Modulus())
d = n.Bytes()
if len(d) < blockSize {
d = n.FillBytes(make([]byte, blockSize))
}
hasher.Write(d)
}
hv := make([]byte, 0, hasher.Size())
hv = hasher.Sum(hv)
cx.Stack[last].Bytes = hv
return nil
giuliop marked this conversation as resolved.
Show resolved Hide resolved
}

// mimc on curve BN254 is implemented for compatibility with zk circuits,
// matching the implementation in circuits generated by gnark
func opMimcBN254(cx *EvalContext) error {
last := len(cx.Stack) - 1
data := cx.Stack[last].Bytes
if len(data)%32 != 0 {
return fmt.Errorf("the input data must be a multiple of 32 bytes")

Check warning on line 74 in data/transactions/logic/crypto.go

View check run for this annotation

Codecov / codecov/patch

data/transactions/logic/crypto.go#L74

Added line #L74 was not covered by tests
}
hasher := bn254mimc.NewMiMC()
blockSize := hasher.BlockSize()
for i := 0; i < len(data); i += 32 {
d := data[i : i+32]
// ensure the input to hasher.Write is smaller than the curve modulus,
// otherwise hasher.Write will fail
n := new(big.Int).SetBytes(d)
n.Mod(n, bn254fr.Modulus())
d = n.Bytes()
if len(d) < blockSize {
d = n.FillBytes(make([]byte, blockSize))
}
hasher.Write(d)
}
hv := make([]byte, 0, hasher.Size())
hv = hasher.Sum(hv)
cx.Stack[last].Bytes = hv
return nil
}

func opSHA256(cx *EvalContext) error {
last := len(cx.Stack) - 1
hash := sha256.Sum256(cx.Stack[last].Bytes)
Expand Down
2 changes: 1 addition & 1 deletion data/transactions/logic/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ int ` + fmt.Sprintf("%d", testLogicBudget-2500-8) + `
}

func BenchmarkHashes(b *testing.B) {
for _, hash := range []string{"sha256", "keccak256" /* skip, same as keccak "sha3_256", */, "sha512_256", "sumhash512"} {
for _, hash := range []string{"sha256", "keccak256" /* skip, same as keccak "sha3_256", */, "sha512_256", "sumhash512", "mimc_BN254", "mimc_BLS12_381"} {
for _, size := range []int{0, 32, 128, 512, 1024, 4096} {
b.Run(hash+"-"+strconv.Itoa(size), func(b *testing.B) {
benchmarkOperation(b, "", fmt.Sprintf("int %d; bzero; %s; pop", size, hash), "int 1")
Expand Down
9 changes: 8 additions & 1 deletion data/transactions/logic/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ var opDescByName = map[string]OpDesc{
"sumhash512": {"sumhash512 of value A, yields [64]byte", "", nil},
"falcon_verify": {"for (data A, compressed-format signature B, pubkey C) verify the signature of data against the pubkey", "", nil},

"mimc_BN254": {"MIMC hash of value A in the BN254 curve field, yields [32]byte",
"A is split into 32-byte chunks, each reduced by the curve modulus, and written to the hasher to finally compute the MiMC hash. Fail if A's length is not a multiple of 32", nil,
},
"mimc_BLS12_381": {"MIMC hash of value A in the BLS12-381 curve field, yields [32]byte",
"A is split into 32-byte chunks, each reduced by the curve modulus, and written to the hasher to finally compute the MiMC hash. Fail if A's length is not a multiple of 32", nil,
},

"ed25519verify": {"for (data A, signature B, pubkey C) verify the signature of (\"ProgData\" || program_hash || data) against the pubkey => {0 or 1}", "The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack.", nil},
"ed25519verify_bare": {"for (data A, signature B, pubkey C) verify the signature of the data against the pubkey => {0 or 1}", "", nil},
"ecdsa_verify": {"for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1}", "The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. All values are big-endian encoded. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted.", []string{"curve index"}},
Expand Down Expand Up @@ -352,7 +359,7 @@ var OpGroups = map[string][]string{
"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 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"},
"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", "mimc_BN254", "mimc_BLS12_381"},
"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"},
"Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "popn", "dup", "dup2", "dupn", "dig", "bury", "cover", "uncover", "frame_dig", "frame_bury", "swap", "select", "assert", "callsub", "proto", "retsub", "switch", "match"},
"State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "acct_params_get", "log", "block"},
Expand Down
4 changes: 4 additions & 0 deletions data/transactions/logic/evalStateful_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3200,6 +3200,10 @@ func TestReturnTypes(t *testing.T) {

"box_create": "int 9; +; box_create", // make the size match the 10 in CreateBox
"box_put": "byte 0x010203040506; concat; box_put", // make the 4 byte arg into a 10

// the mimc_BN254 and mimc_BLS12_381 opcodes require an input size multiple of 32 bytes
"mimc_BN254": ": byte 0x0000000000000000000000000000000000000000000000000000000000000001; mimc_BN254",
"mimc_BLS12_381": ": byte 0x0000000000000000000000000000000000000000000000000000000000000001; mimc_BLS12_381",
}

/* Make sure the specialCmd tests the opcode in question */
Expand Down
3 changes: 3 additions & 0 deletions data/transactions/logic/opcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,9 @@ var OpSpecs = []OpSpec{
costByField("g", &EcGroups, []int{
BN254g1: 630, BN254g2: 3_300,
BLS12_381g1: 1_950, BLS12_381g2: 8_150})},

{0xf0, "mimc_BN254", opMimcBN254, proto("b:b{32}"), 11, costByLength(1, 620, 32, 0)},
{0xf1, "mimc_BLS12_381", opMimcBLS12381, proto("b:b{32}"), 11, costByLength(1, 620, 32, 0)},
}

// OpcodesByVersion returns list of opcodes available in a specific version of TEAL
Expand Down
2 changes: 1 addition & 1 deletion data/transactions/logic/teal.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -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%|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_BLS12_381|mimc_BN254|sha256|sha3_256|sha512_256|sumhash512|vrf_verify|gitxn|gitxna|gitxnas|itxn|itxn_begin|itxn_field|itxn_next|itxn_submit|itxna|itxnas)\\b"
}
]
},
Expand Down