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

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

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
38 changes: 22 additions & 16 deletions data/transactions/logic/assembler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,11 @@ dup; dup
falcon_verify
`

const mimcNonsense = `
pushbytes 0x11223344556677889900aabbccddeeff11223344556677889900aabbccddeeff
mimc BLS12_381g1
`

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

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

const v10Nonsense = v9Nonsense + pairingNonsense + spliceNonsence

const v11Nonsense = v10Nonsense + stateProofNonsense
const v11Nonsense = v10Nonsense + stateProofNonsense + mimcNonsense

const v6Compiled = "2004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a"

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

const stateProofCompiled = "80070123456789abcd86494985"
const mimcCompiled = "802011223344556677889900aabbccddeeff11223344556677889900aabbccddeeffe602"

const V11Compiled = v10Compiled + stateProofCompiled
const V11Compiled = v10Compiled + stateProofCompiled + mimcCompiled

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

Expand Down Expand Up @@ -3212,19 +3218,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 @@ -3280,7 +3286,7 @@ add:
extract_uint32
stores

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

int 255
Expand All @@ -3304,11 +3310,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 @@ -3337,15 +3343,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
44 changes: 44 additions & 0 deletions data/transactions/logic/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,59 @@
"crypto/sha512"
"errors"
"fmt"
"hash"
"math/big"

"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/secp256k1"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-sumhash"
"golang.org/x/crypto/sha3"

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

// mimc is implemented for compatibility with zk circuits,
// matching the implementation in circuits generated by gnark
func opMimc(cx *EvalContext) error {
group := EcGroup(cx.program[cx.pc+1])
fs, ok := ecGroupSpecByField(group)
if !ok { // no version check yet, all groups appeared at once
return fmt.Errorf("invalid mimc group %s", group)

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

View check run for this annotation

Codecov / codecov/patch

data/transactions/logic/crypto.go#L45

Added line #L45 was not covered by tests
}

last := len(cx.Stack) - 1
data := cx.Stack[last].Bytes
if len(data) == 0 {
return fmt.Errorf("the input data cannot be empty")
}
if len(data)%32 != 0 {
return fmt.Errorf("the input data must be a multiple of 32 bytes")
}

var mimc hash.Hash

switch fs.field {
case BN254g1, BN254g2:
mimc = bn254mimc.NewMiMC()
case BLS12_381g1, BLS12_381g2:
mimc = bls12_381mimc.NewMiMC()
default:
return fmt.Errorf("invalid mimc group %s", group)

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

View check run for this annotation

Codecov / codecov/patch

data/transactions/logic/crypto.go#L64-L65

Added lines #L64 - L65 were not covered by tests
}

// unlike most hash.Hash objects, a mimc hasher has strict requirements. The
// input must be a multiple of the curve's encoded element size, and no
// element may exceed the corve modulus.
if _, err := mimc.Write(cx.Stack[last].Bytes); err != nil {
return fmt.Errorf("invalid mimc input %w", err)
}

cx.Stack[last].Bytes = mimc.Sum(nil)
return nil
}

func opSHA256(cx *EvalContext) error {
last := len(cx.Stack) - 1
hash := sha256.Sum256(cx.Stack[last].Bytes)
Expand Down
71 changes: 70 additions & 1 deletion data/transactions/logic/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,72 @@ byte 0x98D2C31612EA500279B6753E5F6E780CA63EBA8274049664DAD66A2565ED1D2A
testAccepts(t, progText, 1)
}

func TestMimc(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

// Test vectors from https://github.com/giuliop/test-mimc-opcodes/blob/main/testcases/circuit.go
// generated by instantiating a zk-circuit that processes MiMC hash of the preimages.
// We test success for 32-byte and 96-byte preimages, and failure for preimage input size of 0,
// input size not multiple of 32 bytes, and chunks representing values greater than the modulus.

preImageTestVectors := []string{
// FAIL: zero-length
"0x",
// SUCCEED: 32 bytes, less than modulus
giuliop marked this conversation as resolved.
Show resolved Hide resolved
"0x23a950068dd3d1e21cee48e7919be7ae32cdef70311fc486336ea9d4b5042535",
// FAIL: 32 bytes, more than modulus
"0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000002",
// FAIL: less than 32 byte
"0xdeadf00d",
// SUCCEED: 32 bytes, less than modulus | 32 bytes, less than modulus | 32 bytes, less than modulus
"0x183de351a72141d79c51a27d10405549c98302cb2536c5968deeb3cba635121723a950068dd3d1e21cee48e7919be7ae32cdef70311fc486336ea9d4b504253530644e72e131a029b85045b68181585d2833e84879b9709143e1f593ef676981",
// FAIL: 32 bytes, less than modulus | 32 bytes, less than modulus | 32 bytes, more than modulus
"0x183de351a72141d79c51a27d10405549c98302cb2536c5968deeb3cba635121723a950068dd3d1e21cee48e7919be7ae32cdef70311fc486336ea9d4b504253573eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000002",
// FAIL: 32 bytes, less than modulus | 32 bytes, less than modulus | 32 bytes, more than modulus
"0x183de351a72141d79c51a27d10405549c98302cb2536c5968deeb3cba635121723a950068dd3d1e21cee48e7919be7ae32cdef70311fc486336ea9d4b5042535abba",
}
circuitHashTestVectors := map[string][]string{
"BN254": {
"20104241803663641422577121134203490505137011783614913652735802145961801733870",
"12886436712380113721405259596386800092738845035233065858332878701083870690753",
"19565877911319815535452130675266047290072088868113536892077808700068649624391",
"1037254799353855871006189384309576393135431139055333626960622147300727796413",
"6040222623731283351958201178122781676432899642144860863024149088913741383362",
"21691351735381703396517600859480938764038501053226864452091917666642352837076",
"10501393540371963307040960561318023073151272109639330842515119353134949995409",
},
"BLS12_381": {
"17991912493598890696181760734961918471863781118188078948205844982816313445306",
"8791766422525455185980675814845076441443662947059416063736889106252015893524",
"35137972692771717943992759113612269767581262500164574105059686144346651628747",
"15039173432183897369859775531867817848264266283034981501223857291379142522368",
"12964111614552580241101202600014316932811348627866250816177200046290462797607",
"21773894974440411325489312534417904228129169539217646609523079291104496302656",
"9873666029497961930790892458408217321483390383568592297687427911011295910871",
},
}
shouldSucceed := []bool{false, true, false, false, true, false, false}

for _, curve := range []string{"BN254", "BLS12_381"} {
for i, preImage := range preImageTestVectors {
var n big.Int
n.SetString(circuitHashTestVectors[curve][i], 10)
circuitHash := n.Bytes()
progText := fmt.Sprintf(`byte %s
mimc %sg1

byte 0x%x
==`, preImage, curve, circuitHash)
if shouldSucceed[i] {
testAccepts(t, progText, 11)
} else {
testPanics(t, progText, 11)
}
}
}
}

// This is patterned off vrf_test.go, but we don't create proofs here, we only
// check that the output is correct, given the proof.
func testVrfApp(pubkey, proof, data string, output string) string {
Expand Down Expand Up @@ -713,8 +779,11 @@ 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 BN254g1", "mimc BLS12_381g1"} {
for _, size := range []int{0, 32, 128, 512, 1024, 4096} {
if size == 0 && (hash == "mimc BN254g1" || hash == "mimc BLS12_381g1") {
continue
}
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
6 changes: 5 additions & 1 deletion data/transactions/logic/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ 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": {"MIMC hash of value A in the curve field G, yields [32]byte",
"A is split into 32-byte chunks and written to the hasher to finally compute the MiMC hash. Fail if A's length is not a multiple of 32 or any chunk encodes a value greater than the curve modulus", []string{"curve index"},
},

"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 +356,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"},
"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
3 changes: 3 additions & 0 deletions data/transactions/logic/evalStateful_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3200,6 +3200,9 @@ 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

// mimc requires an input size multiple of 32 bytes.
"mimc": ": byte 0x0000000000000000000000000000000000000000000000000000000000000001; mimc BN254g1",
}

/* Make sure the specialCmd tests the opcode in question */
Expand Down
22 changes: 22 additions & 0 deletions data/transactions/logic/opcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ const spliceVersion = 10 // box splicing/resizing
const incentiveVersion = 11 // block fields, heartbeat

const spOpcodesVersion = 11 // falcon_verify, sumhash512
const mimcVersion = 11

// Unlimited Global Storage opcodes
const boxVersion = 8 // box_*
Expand Down Expand Up @@ -796,6 +797,27 @@ 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("g", &EcGroups, []linearCost{
BN254g1: {
baseCost: 10,
chunkCost: 650,
chunkSize: 32,
},
BN254g2: {
baseCost: 10,
chunkCost: 650,
chunkSize: 32,
},
BLS12_381g1: {
baseCost: 10,
chunkCost: 650,
chunkSize: 32,
},
BLS12_381g2: {
baseCost: 10,
chunkCost: 650,
chunkSize: 32,
}})},
}

// 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|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
Loading