From c51e74f244dcd42dda5ef2161381fa983fee8686 Mon Sep 17 00:00:00 2001 From: Aleksei Kokinos Date: Thu, 5 Feb 2026 19:20:35 +0700 Subject: [PATCH 1/6] created proto-gen github actions workflow --- .github/workflows/proto-gen-pr.yml | 66 ++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/proto-gen-pr.yml diff --git a/.github/workflows/proto-gen-pr.yml b/.github/workflows/proto-gen-pr.yml new file mode 100644 index 000000000..05ba35228 --- /dev/null +++ b/.github/workflows/proto-gen-pr.yml @@ -0,0 +1,66 @@ +name: Generate Protobuf and Create PR + +on: + workflow_dispatch: + inputs: + target_branch: + description: 'Branch to create PR against' + required: true + default: 'enter target branch here' + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + generate-proto: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.target_branch }} + fetch-depth: 0 + + - name: Generate Protobuf files + run: make proto-gen + + - name: Check for changes + id: check_changes + run: | + if [[ -n $(git status --porcelain) ]]; then + echo "changes=true" >> $GITHUB_OUTPUT + echo "Proto files have changed:" + git status --porcelain + else + echo "changes=false" >> $GITHUB_OUTPUT + echo "No changes detected in proto files" + fi + + - name: Create Pull Request + if: steps.check_changes.outputs.changes == 'true' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore: regenerate protobuf files" + title: "chore: regenerate protobuf files for ${{ github.event.inputs.target_branch }}" + body: | + This PR was automatically generated by the `proto-gen-pr` workflow. + ## Changes + - Regenerated protobuf Go files from `.proto` definitions + ## Target Branch + `${{ github.event.inputs.target_branch }}` + ## Triggered by + @${{ github.actor }} + branch: proto-gen/${{ github.event.inputs.target_branch }}-${{ github.run_number }} + base: ${{ github.event.inputs.target_branch }} + delete-branch: true + labels: | + automated + protobuf + + - name: No changes summary + if: steps.check_changes.outputs.changes == 'false' + run: | + echo "::notice::No protobuf changes detected. PR was not created." From 99e4891dd9a488faaee80ec1a308bca263dc033c Mon Sep 17 00:00:00 2001 From: Aleksei Kokinos Date: Thu, 5 Feb 2026 23:02:16 +0700 Subject: [PATCH 2/6] fix proto-gen github action --- .github/workflows/proto-gen-pr.yml | 66 ------------------------------ .github/workflows/proto-gen.yml | 52 +++++++++++++++++++++++ 2 files changed, 52 insertions(+), 66 deletions(-) delete mode 100644 .github/workflows/proto-gen-pr.yml create mode 100644 .github/workflows/proto-gen.yml diff --git a/.github/workflows/proto-gen-pr.yml b/.github/workflows/proto-gen-pr.yml deleted file mode 100644 index 05ba35228..000000000 --- a/.github/workflows/proto-gen-pr.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Generate Protobuf and Create PR - -on: - workflow_dispatch: - inputs: - target_branch: - description: 'Branch to create PR against' - required: true - default: 'enter target branch here' - type: string - -permissions: - contents: write - pull-requests: write - -jobs: - generate-proto: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: ${{ github.event.inputs.target_branch }} - fetch-depth: 0 - - - name: Generate Protobuf files - run: make proto-gen - - - name: Check for changes - id: check_changes - run: | - if [[ -n $(git status --porcelain) ]]; then - echo "changes=true" >> $GITHUB_OUTPUT - echo "Proto files have changed:" - git status --porcelain - else - echo "changes=false" >> $GITHUB_OUTPUT - echo "No changes detected in proto files" - fi - - - name: Create Pull Request - if: steps.check_changes.outputs.changes == 'true' - uses: peter-evans/create-pull-request@v6 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: "chore: regenerate protobuf files" - title: "chore: regenerate protobuf files for ${{ github.event.inputs.target_branch }}" - body: | - This PR was automatically generated by the `proto-gen-pr` workflow. - ## Changes - - Regenerated protobuf Go files from `.proto` definitions - ## Target Branch - `${{ github.event.inputs.target_branch }}` - ## Triggered by - @${{ github.actor }} - branch: proto-gen/${{ github.event.inputs.target_branch }}-${{ github.run_number }} - base: ${{ github.event.inputs.target_branch }} - delete-branch: true - labels: | - automated - protobuf - - - name: No changes summary - if: steps.check_changes.outputs.changes == 'false' - run: | - echo "::notice::No protobuf changes detected. PR was not created." diff --git a/.github/workflows/proto-gen.yml b/.github/workflows/proto-gen.yml new file mode 100644 index 000000000..257f19713 --- /dev/null +++ b/.github/workflows/proto-gen.yml @@ -0,0 +1,52 @@ +name: Generate Protobuf + +on: + workflow_dispatch: + inputs: + target_branch: + description: 'Branch to generate proto for' + required: true + default: 'enter target branch here' + type: string + +permissions: + contents: write + +jobs: + generate-proto: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.target_branch }} + fetch-depth: 0 + + - name: Generate Protobuf files + run: make proto-gen + + - name: Check for changes + id: check_changes + run: | + if [[ -n $(git status --porcelain) ]]; then + echo "changes=true" >> $GITHUB_OUTPUT + echo "Proto files have changed:" + git status --porcelain + else + echo "changes=false" >> $GITHUB_OUTPUT + echo "No changes detected in proto files" + fi + + - name: Commit and push changes + if: steps.check_changes.outputs.changes == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add . + git commit -m "chore: regenerate protobuf files" + git push origin ${{ github.event.inputs.target_branch }} + + - name: No changes summary + if: steps.check_changes.outputs.changes == 'false' + run: | + echo "::notice::No protobuf changes detected. Nothing to push." From 239cbc012e534bc7de55c935bb09932cfce53aba Mon Sep 17 00:00:00 2001 From: Aleksei Kokinos Date: Fri, 6 Feb 2026 00:22:22 +0700 Subject: [PATCH 3/6] fix TestParams with new ed25519 precompile --- x/vm/types/precompiles.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x/vm/types/precompiles.go b/x/vm/types/precompiles.go index ce687740e..c0e621089 100644 --- a/x/vm/types/precompiles.go +++ b/x/vm/types/precompiles.go @@ -23,10 +23,11 @@ const LiquidStakePrecompileAddress = "0x000000000000000000000000000000000000 // // NOTE: To be explicit, this list does not include the dynamically registered EVM extensions // like the ERC-20 extensions. +// NOTE: This list MUST be sorted lexicographically by address to match the expected order +// after params are set (SetParams sorts the precompiles). var AvailableStaticPrecompiles = []string{ P256PrecompileAddress, Bech32PrecompileAddress, - Ed25519PrecompileAddress, StakingPrecompileAddress, DistributionPrecompileAddress, ICS20PrecompileAddress, @@ -35,5 +36,6 @@ var AvailableStaticPrecompiles = []string{ GovPrecompileAddress, SlashingPrecompileAddress, EvidencePrecompileAddress, + Ed25519PrecompileAddress, LiquidStakePrecompileAddress, } From 239ecf126ea0cd3ca6feb033849f22f89a77aa91 Mon Sep 17 00:00:00 2001 From: Aleksei Kokinos Date: Fri, 6 Feb 2026 18:25:25 +0700 Subject: [PATCH 4/6] fix ed25519 gas calculation and costs --- precompiles/ed25519/ed25519.go | 7 ++++--- precompiles/ed25519/ed25519_test.go | 28 ++++++++++++++++++---------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/precompiles/ed25519/ed25519.go b/precompiles/ed25519/ed25519.go index 20362a1b5..3908c833a 100644 --- a/precompiles/ed25519/ed25519.go +++ b/precompiles/ed25519/ed25519.go @@ -18,9 +18,9 @@ import ( //go:embed abi.json var f embed.FS -const ED25519_VERIFY_BASE_GAS = 1500 +const ED25519_VERIFY_BASE_GAS = 2000 const SHA512_BASE_GAS = 60 -const SHA512_PER_WORD_GAS = 8 +const SHA512_PER_WORD_GAS = 12 const ED25519VerifyMethod = "ed25519Verify" @@ -46,7 +46,8 @@ func (Precompile) Address() common.Address { func (p Precompile) RequiredGas(input []byte) uint64 { // Challenge for ed25519 uses sha512 of sig.R, pubkey, msg // So exclude 32 bytes of sig.Z from the length - msgLen := max(len(input)-32, 0) + // Also exclute 4 bytes of method selector + msgLen := max(len(input)-36, 0) return ED25519_VERIFY_BASE_GAS + SHA512_BASE_GAS + SHA512_PER_WORD_GAS*((uint64(msgLen)+31)/32) } diff --git a/precompiles/ed25519/ed25519_test.go b/precompiles/ed25519/ed25519_test.go index d83a8635b..77060892a 100644 --- a/precompiles/ed25519/ed25519_test.go +++ b/precompiles/ed25519/ed25519_test.go @@ -50,6 +50,9 @@ func (s *PrecompileTestSuite) TestAddress() { } func (s *PrecompileTestSuite) TestRequiredGas() { + // Formula: msgLen = max(len(input) - 36, 0) + // gas = ED25519_VERIFY_BASE_GAS + SHA512_BASE_GAS + SHA512_PER_WORD_GAS * ((msgLen + 31) / 32) + // 36 = 4 (method selector) + 32 (sig.Z excluded from sha512 calculation) testCases := []struct { name string input []byte @@ -58,27 +61,32 @@ func (s *PrecompileTestSuite) TestRequiredGas() { { "empty input", []byte{}, + // msgLen = 0, words = 0 edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS, }, { "minimal input (96 bytes)", make([]byte, 96), - edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS, + // msgLen = 96-36 = 60, words = (60+31)/32 = 2 + edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS*2, }, { - "input with 32 byte message", - make([]byte, 96+32), - edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS, + "input with 32 byte message (128 bytes total)", + make([]byte, 128), + // msgLen = 128-36 = 92, words = (92+31)/32 = 3 + edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS*3, }, { - "input with 64 byte message", - make([]byte, 96+64), - edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS*2, + "input with 64 byte message (160 bytes total)", + make([]byte, 160), + // msgLen = 160-36 = 124, words = (124+31)/32 = 4 + edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS*4, }, { - "input with 100 byte message", - make([]byte, 96+100), - edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS*4, + "input with 100 byte message (196 bytes total)", + make([]byte, 196), + // msgLen = 196-36 = 160, words = (160+31)/32 = 5 + edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS*5, }, } From 3c546604c1d2ce77c562729a0fa0544390237ead Mon Sep 17 00:00:00 2001 From: Aleksei Kokinos Date: Fri, 6 Feb 2026 18:25:38 +0700 Subject: [PATCH 5/6] fix TestConvertEvmCoinFrom18Decimals --- x/vm/types/scaling_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/vm/types/scaling_test.go b/x/vm/types/scaling_test.go index 354290c08..8e80d9d5f 100644 --- a/x/vm/types/scaling_test.go +++ b/x/vm/types/scaling_test.go @@ -153,7 +153,7 @@ func TestConvertEvmCoinFrom18Decimals(t *testing.T) { if !tc.expErr { require.NoError(t, err) - require.Equal(t, tc.expCoin, coinConverted, "expected a different coin") + require.True(t, tc.expCoin.IsEqual(coinConverted), "expected a different coin: got %s, want %s", coinConverted, tc.expCoin) } else { require.Error(t, err) } From 8e01bec6500c12869a516df899f2e2c670265587 Mon Sep 17 00:00:00 2001 From: Aleksei Kokinos Date: Fri, 6 Feb 2026 19:46:57 +0700 Subject: [PATCH 6/6] fix proto-gen github workflow add readme for ed25519 precompile --- .github/workflows/proto-gen.yml | 10 +- precompiles/ed25519/README.md | 174 +++++++++++++++++++++++++++++++- 2 files changed, 175 insertions(+), 9 deletions(-) diff --git a/.github/workflows/proto-gen.yml b/.github/workflows/proto-gen.yml index 257f19713..3bdd1a600 100644 --- a/.github/workflows/proto-gen.yml +++ b/.github/workflows/proto-gen.yml @@ -2,12 +2,6 @@ name: Generate Protobuf on: workflow_dispatch: - inputs: - target_branch: - description: 'Branch to generate proto for' - required: true - default: 'enter target branch here' - type: string permissions: contents: write @@ -19,7 +13,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.target_branch }} + ref: ${{ github.ref_name }} fetch-depth: 0 - name: Generate Protobuf files @@ -44,7 +38,7 @@ jobs: git config user.email "github-actions[bot]@users.noreply.github.com" git add . git commit -m "chore: regenerate protobuf files" - git push origin ${{ github.event.inputs.target_branch }} + git push origin ${{ github.ref_name }} - name: No changes summary if: steps.check_changes.outputs.changes == 'false' diff --git a/precompiles/ed25519/README.md b/precompiles/ed25519/README.md index 28032c926..d6354c9f7 100644 --- a/precompiles/ed25519/README.md +++ b/precompiles/ed25519/README.md @@ -1 +1,173 @@ -https://hackmd.io/@046EkvuRTzieElv1kovjww/r1b1kD3Gge# \ No newline at end of file +# ED25519 Signature Verification Precompile + +## Overview + +The ED25519 precompile provides native support for verifying ED25519 digital signatures within the EVM. This precompile enables efficient cryptographic signature verification using the ED25519 elliptic curve, which is widely used in blockchain and cryptographic applications. + +## Contract Address + +The ED25519 precompile is deployed at a fixed address: + +``` +0x00000000000000000000000000000000000008f3 +``` + +## Interface + +### Methods + +#### `ed25519Verify` + +Verifies an ED25519 signature against a public key and message. + +```solidity +function ed25519Verify( + bytes32 publicKey, + bytes32[2] signature, + bytes message +) returns (bool isValid) +``` + +**Parameters:** +- `publicKey` (bytes32): The ED25519 public key (32 bytes) +- `signature` (bytes32[2]): The ED25519 signature split into two 32-byte parts: + - `signature[0]`: R component (first 32 bytes) + - `signature[1]`: S component (last 32 bytes) +- `message` (bytes): The message that was signed (variable length) + +**Returns:** +- `isValid` (bool): `true` if the signature is valid, `false` otherwise + +## Gas Costs + +The gas cost for the `ed25519Verify` function is calculated dynamically based on the message length: + +``` +gas = ED25519_VERIFY_BASE_GAS + SHA512_BASE_GAS + SHA512_PER_WORD_GAS * ((msgLen + 31) / 32) +``` + +Where: +- `ED25519_VERIFY_BASE_GAS = 2000`: Base cost for ED25519 signature verification +- `SHA512_BASE_GAS = 60`: Base cost for SHA512 hashing +- `SHA512_PER_WORD_GAS = 12`: Cost per 32-byte word for SHA512 hashing +- `msgLen`: Length of the message (excluding the 36 bytes for method selector and signature.S) + + +## Usage Example + +### Solidity + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IED25519 { + function ed25519Verify( + bytes32 publicKey, + bytes32[2] calldata signature, + bytes calldata message + ) external returns (bool isValid); +} + +contract ED25519Example { + IED25519 constant ED25519_PRECOMPILE = IED25519(0x00000000000000000000000000000000000008f3); + + function verifySignature( + bytes32 publicKey, + bytes32[2] calldata signature, + bytes calldata message + ) public returns (bool) { + return ED25519_PRECOMPILE.ed25519Verify(publicKey, signature, message); + } +} +``` + +### JavaScript/TypeScript (ethers.js) + +```typescript +import { ethers } from 'ethers'; + +const ED25519_ADDRESS = '0x00000000000000000000000000000000000008f3'; + +const ed25519ABI = [ + { + "inputs": [ + { "internalType": "bytes32", "name": "publicKey", "type": "bytes32" }, + { "internalType": "bytes32[2]", "name": "signature", "type": "bytes32[2]" }, + { "internalType": "bytes", "name": "message", "type": "bytes" } + ], + "name": "ed25519Verify", + "outputs": [ + { "internalType": "bool", "name": "isValid", "type": "bool" } + ], + "stateMutability": "nonpayable", + "type": "function" + } +]; + +async function verifyED25519Signature( + provider: ethers.Provider, + publicKey: string, + signature: [string, string], + message: string +): Promise { + const ed25519Contract = new ethers.Contract(ED25519_ADDRESS, ed25519ABI, provider); + const isValid = await ed25519Contract.ed25519Verify(publicKey, signature, message); + return isValid; +} +``` + +## Technical Details + +### ED25519 Algorithm + +ED25519 is a public-key signature system that uses: +- Curve25519 elliptic curve +- SHA-512 hash function +- Schnorr signature scheme + +### Signature Format + +The ED25519 signature is 64 bytes long and is split into two components: +- **R component** (32 bytes): The first half of the signature +- **S component** (32 bytes): The second half of the signature + +### Implementation + +The precompile uses Go's standard `crypto/ed25519` package for signature verification, ensuring compatibility with standard ED25519 implementations. + +## Security Considerations + +1. **Public Key Validation**: The precompile expects a valid 32-byte ED25519 public key. Invalid keys will result in verification failure. + +2. **Signature Validation**: The signature must be exactly 64 bytes (provided as two 32-byte arrays). Invalid signature lengths or formats will result in an error. + +3. **Message Integrity**: The message should be provided exactly as it was when the signature was created. Any modification will cause verification to fail. + +4. **Gas Limits**: When verifying large messages, ensure sufficient gas is provided based on the dynamic gas calculation formula. + +## Use Cases + +- **Cross-chain Communication**: Verify signatures from chains that use ED25519 (e.g., Cosmos SDK chains, Solana) +- **Identity Verification**: Validate ED25519-based digital identities +- **Secure Messaging**: Verify signed messages in decentralized applications +- **Multi-signature Wallets**: Implement multi-sig wallets that support ED25519 +- **Oracle Data Verification**: Verify signed data from oracles using ED25519 keys + +## Testing + +The precompile includes comprehensive test coverage. Run tests with: + +```bash +go test ./precompiles/ed25519/... +``` + +## References + +- [ED25519 Specification (RFC 8032)](https://datatracker.ietf.org/doc/html/rfc8032) +- [Go crypto/ed25519 Package](https://pkg.go.dev/crypto/ed25519) +- [Curve25519 and ED25519](https://ed25519.cr.yp.to/) + +## License + +This precompile is part of the Cosmos EVM project and is licensed under the project's license terms.