Skip to content

Instantly share code, notes, and snippets.

@mkaczanowski
Last active January 25, 2021 10:36
Show Gist options
  • Save mkaczanowski/1db9a92b1dc99c172358c6276ca63806 to your computer and use it in GitHub Desktop.
Save mkaczanowski/1db9a92b1dc99c172358c6276ca63806 to your computer and use it in GitHub Desktop.

NOTE: The PR slightly changed, so this data might be slightly out of date (but overall, it's still indicative)

Benchmark 0 (hdevalence/ed25519consensus)

$ GOMAXPROCS=1 go test  -cpu 1 -bench=BenchmarkPrecompiledEd25519Verify -benchmem
goos: linux
goarch: amd64
pkg: github.com/ethereum/go-ethereum/core/vm
BenchmarkPrecompiledEd25519Verify/valid_input-Gas=1584             17599             67400 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/valid_input-Gas=1608                  17914             67768 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/valid_input_4_word_msg-Gas=1632                  17775             68614 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/valid_input_with_empty_message-Gas=1584          16480             68342 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/invalid_input_malformed_public_key-Gas=1584             164198              7205 ns/op            1384 B/op         16 allocs/op
BenchmarkPrecompiledEd25519Verify/invalid_input_malformed_signature-Gas=1584               17793             69061 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/invalid_input_msg_too_short-Gas=1584                     17588             68460 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/invalid_input_empty-Gas=1560                            387652              2887 ns/op            1368 B/op         15 allocs/op
PASS
ok      github.com/ethereum/go-ethereum/core/vm 13.934s

In summary:

1 word = 67400 ns/op
2 word = 67768 ns/op (368 ns/op slower than 1 word)
4 word =  68614 ns/op (846 ns/op slower than 1 word)

Benchmark 1 (stdlib crypto/ed25519)

$ GOMAXPROCS=1 go test  -cpu 1 -bench=BenchmarkPrecompiledEd25519Verify -benchmem
goos: linux
goarch: amd64
pkg: github.com/ethereum/go-ethereum/core/vm
BenchmarkPrecompiledEd25519Verify/valid_input-Gas=1584                              9183            133197 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/valid_input-Gas=1608                   7893            134578 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/valid_input_4_word_msg-Gas=1632                   9027            134921 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/valid_input_with_empty_message-Gas=1584           9013            134856 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/invalid_input_malformed_public_key-Gas=1584             103746             11533 ns/op            1368 B/op         15 allocs/op
BenchmarkPrecompiledEd25519Verify/invalid_input_malformed_signature-Gas=1584                9060            135270 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/invalid_input_msg_too_short-Gas=1584                      8984            139509 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/invalid_input_empty-Gas=1560                            430832              2867 ns/op            1368 B/op         15 allocs/op
PASS
ok      github.com/ethereum/go-ethereum/core/vm 10.036s

In summary:

1 word = 133197 ns/op
2 word = 134578 ns/op (1381ns slower than 1 word)
4 word = 134921 ns/op (343ns slower than 2 word)

Benchmark 2 (github.com/oasisprotocol/ed25519)

[admin@laptop vm]$ GOMAXPROCS=1 go test  -cpu 1 -bench=BenchmarkPrecompiledEd25519Verify -benchmem
goos: linux
goarch: amd64
pkg: github.com/ethereum/go-ethereum/core/vm
BenchmarkPrecompiledEd25519Verify/valid_input-Gas=1584             12343             95035 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/valid_input-Gas=1608                  12684             95417 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/valid_input_4_word_msg-Gas=1632                  12546             95767 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/valid_input_with_empty_message-Gas=1584          12489             95941 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/invalid_input_malformed_public_key-Gas=1584             134200              8228 ns/op            1432 B/op         16 allocs/op
BenchmarkPrecompiledEd25519Verify/invalid_input_malformed_signature-Gas=1584               12519             95889 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/invalid_input_msg_too_short-Gas=1584                     12375             97944 ns/op            1656 B/op         17 allocs/op
BenchmarkPrecompiledEd25519Verify/invalid_input_empty-Gas=1560                            426584              2935 ns/op            1368 B/op         15 allocs/op
PASS
ok      github.com/ethereum/go-ethereum/core/vm 15.679s

In summary:

1 word = 95035 ns/op
2 word = 95417 ns/op (382ns slower than 1 word)
4 word = 95767 ns/op (350ns slower than 2 word)

Benchmark 1 (stdlib) vs Benchmark 2 (oasis)

1 word = 133197 ns/op vs 95035 ns/op (oasis's 40% faster)
2 word = 134578 ns/op vs 95417 ns/op (oasis's 41% faster)
4 word = 134921 ns/op vs 95767 ns/op (oasis's 40% faster)

Benchmark 0 (hdevalence) vs Benchmark 2 (oasis)

1 word = 67400 ns/op vs 95035 ns/op (hdevalence's 41% faster)
2 word = 67768 ns/op vs 95417 ns/op (hdvalence's 40% faster)
4 word =  68614 ns/op vs 95767 ns/op (hdvalence's 39% faster)

Comparison benchmark: Ecrecover

$ GOMAXPROCS=1 go test  -cpu 1 -bench=BenchmarkPrecompiledEcrecover -benchmem
goos: linux
goarch: amd64
pkg: github.com/ethereum/go-ethereum/core/vm
BenchmarkPrecompiledEcrecover/-Gas=3000                     8944            145887 ns/op            2632 B/op         22 allocs/op
PASS
ok      github.com/ethereum/go-ethereum/core/vm 1.494s

Summary

Based on the presented benchmark, I conclude that:

  • the bigger the message the slower (by ns/op) execution time. However the difference between 1,2 or 4 word (where the word is 64byte) is not linear,
  • the gas price is amortized by the sha512 per-word cost which seems to be a fair calculation (to be adjusted if needed)
  • 1-word test in benchmark 0 (hdvalence) takes 67400 ns/op + 1584 gas where comparison function (ecrecover) runs in 145887 ns/op + 3000 gas. So, ed25519 is ~116% faster, also the memory usage is ~37% lower (1656 vs 2632 B/op)

Taking the benchmark results into consideration we think the hdvalence is the best choice performance-wise.

Solidity

Example contract

pragma solidity ^0.5.12;


contract Console {
    event LogBytes32(string, bytes32);
    function log(string memory s , bytes32 x) public {
        emit LogBytes32(s, x);
    }
    
    event LogBytes(string, bytes);
    function log(string memory s , bytes memory x) public {
        emit LogBytes(s, x);
    }
}

contract Demo is Console {
    function callEd25519Verify(bytes memory message, bytes32 publicKey, bytes32[2] memory signature) public returns (bytes32 result) {
        bytes memory data = new bytes(publicKey.length + 2 * 32);
        uint i;
        uint idx = 0;
    
        for( i = 0; i < 32; i++ ) {
            data[idx++] = publicKey[i];
        }
        
    
        for( i = 0; i < 2; i++ ) {
            uint j = 0;
            
            for( j = 0; j < 32; j++ ) {
                data[idx++] = signature[i][j];
            }
        }
        
        bytes memory all = abi.encodePacked(data, message);
        
        assembly {
            let len := mload(all)
            
            let success := call(gas, 0x0a, 0, add(all, 0x20), len, result, 0x20)
            switch success
            case 0 {
                revert(0,0)
            } default {
               result := mload(result)
            }
        }
        
        log("input", all);
        log("result", result);

        return result;
    }
}

Valid Input:

"0x74657374206d657373616765","0x304b6bb12f4dcffd4b147881bdeebfc63b7fb61412a3b696a530df076dde0546",["0xb3624c07c18b1abdb8f4808f0115e6a33d7323ac821976479cfae8426e86c178","0xbd32dacce8f0d52456da8dfaf88cb42f352679674f9d4980635c9c686c6c560d"]

where this is concatenated version passed to precompiled code:

0x304b6bb12f4dcffd4b147881bdeebfc63b7fb61412a3b696a530df076dde0546b3624c07c18b1abdb8f4808f0115e6a33d7323ac821976479cfae8426e86c178bd32dacce8f0d52456da8dfaf88cb42f352679674f9d4980635c9c686c6c560d74657374206d657373616765

Example contract output

Transaction logs with valid data:

[
	{
		"from": "0xcC10Fc73910D244c4458020BaF43dF6DD43132c0",
		"topic": "0xe8407a0209fa99ec3a7228aff140c3d3e68bd279391739c7e0b65cd406cc93b5",
		"event": "LogBytes",
		"args": {
			"0": "input",
			"1": "0x304b6bb12f4dcffd4b147881bdeebfc63b7fb61412a3b696a530df076dde0546b3624c07c18b1abdb8f4808f0115e6a33d7323ac821976479cfae8426e86c178bd32dacce8f0d52456da8dfaf88cb42f352679674f9d4980635c9c686c6c560d74657374206d657373616765"
		}
	},
	{
		"from": "0xcC10Fc73910D244c4458020BaF43dF6DD43132c0",
		"topic": "0x02d93529bba9d141e5e06733c52c7e6fbcb1149586adb5c24064b522ab26f1d7",
		"event": "LogBytes32",
		"args": {
			"0": "result",
			"1": "0x0000000000000000000000000000000000000000000000000000000000000000"
		}
	}
]

Transaction logs with invalid data:

[
	{
		"from": "0xcC10Fc73910D244c4458020BaF43dF6DD43132c0",
		"topic": "0xe8407a0209fa99ec3a7228aff140c3d3e68bd279391739c7e0b65cd406cc93b5",
		"event": "LogBytes",
		"args": {
			"0": "input",
			"1": "0x304b6bb12f4dcffd4b147881bdeebfc63b7fb61412a3b696a530df076dde0546b3624c07c18b1abdb8f4808f0115e6a33d7323ac821976479cfae8426e86c178bd32dacce8f0d52456da8dfaf88cb42f352679674f9d4980635c9c686c6c560d64257374206d657373616765"
		}
	},
	{
		"from": "0xcC10Fc73910D244c4458020BaF43dF6DD43132c0",
		"topic": "0x02d93529bba9d141e5e06733c52c7e6fbcb1149586adb5c24064b522ab26f1d7",
		"event": "LogBytes32",
		"args": {
			"0": "result",
			"1": "0x0000000000000000000000000000000000000000000000000000000000000001"
		}
	}
]

cURL test

valid case

curl localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 -d '{
  "jsonrpc": "2.0",
  "method": "eth_call",
  "params": [
    {
      "to": "0x000000000000000000000000000000000000000a",
      "data": "0x304b6bb12f4dcffd4b147881bdeebfc63b7fb61412a3b696a530df076dde0546b3624c07c18b1abdb8f4808f0115e6a33d7323ac821976479cfae8426e86c178bd32dacce8f0d52456da8dfaf88cb42f352679674f9d4980635c9c686c6c560d74657374206d657373616765"
    },
    "latest"
  ],
  "id": 1
 }'
 
 {"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000000"}

invalid case: malformed public key

curl localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 -d '{
  "jsonrpc": "2.0",
  "method": "eth_call",
  "params": [
    {
      "to": "0x000000000000000000000000000000000000000a",
      "data": "0x204b6bb12f4dcffd4b147881bdeebfc63b7fb61412a3b696a530df076dde0546b3624c07c18b1abdb8f4808f0115e6a33d7323ac821976479cfae8426e86c178bd32dacce8f0d52456da8dfaf88cb42f352679674f9d4980635c9c686c6c560d74657374206d657373616765"
    },
    "latest"
  ],
  "id": 1
 }'
 
 {"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000001"}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment