Skip to content

Commit 16e3f32

Browse files
authored
fix: fixes for release 1.0.3 (#11)
* created proto-gen github actions workflow * fix proto-gen github action * fix TestParams with new ed25519 precompile * fix ed25519 gas calculation and costs * fix TestConvertEvmCoinFrom18Decimals * fix proto-gen github workflow add readme for ed25519 precompile
2 parents 79ed002 + 8e01bec commit 16e3f32

6 files changed

Lines changed: 245 additions & 16 deletions

File tree

.github/workflows/proto-gen.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Generate Protobuf
2+
3+
on:
4+
workflow_dispatch:
5+
6+
permissions:
7+
contents: write
8+
9+
jobs:
10+
generate-proto:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout repository
14+
uses: actions/checkout@v4
15+
with:
16+
ref: ${{ github.ref_name }}
17+
fetch-depth: 0
18+
19+
- name: Generate Protobuf files
20+
run: make proto-gen
21+
22+
- name: Check for changes
23+
id: check_changes
24+
run: |
25+
if [[ -n $(git status --porcelain) ]]; then
26+
echo "changes=true" >> $GITHUB_OUTPUT
27+
echo "Proto files have changed:"
28+
git status --porcelain
29+
else
30+
echo "changes=false" >> $GITHUB_OUTPUT
31+
echo "No changes detected in proto files"
32+
fi
33+
34+
- name: Commit and push changes
35+
if: steps.check_changes.outputs.changes == 'true'
36+
run: |
37+
git config user.name "github-actions[bot]"
38+
git config user.email "github-actions[bot]@users.noreply.github.com"
39+
git add .
40+
git commit -m "chore: regenerate protobuf files"
41+
git push origin ${{ github.ref_name }}
42+
43+
- name: No changes summary
44+
if: steps.check_changes.outputs.changes == 'false'
45+
run: |
46+
echo "::notice::No protobuf changes detected. Nothing to push."

precompiles/ed25519/README.md

Lines changed: 173 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,173 @@
1-
https://hackmd.io/@046EkvuRTzieElv1kovjww/r1b1kD3Gge#
1+
# ED25519 Signature Verification Precompile
2+
3+
## Overview
4+
5+
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.
6+
7+
## Contract Address
8+
9+
The ED25519 precompile is deployed at a fixed address:
10+
11+
```
12+
0x00000000000000000000000000000000000008f3
13+
```
14+
15+
## Interface
16+
17+
### Methods
18+
19+
#### `ed25519Verify`
20+
21+
Verifies an ED25519 signature against a public key and message.
22+
23+
```solidity
24+
function ed25519Verify(
25+
bytes32 publicKey,
26+
bytes32[2] signature,
27+
bytes message
28+
) returns (bool isValid)
29+
```
30+
31+
**Parameters:**
32+
- `publicKey` (bytes32): The ED25519 public key (32 bytes)
33+
- `signature` (bytes32[2]): The ED25519 signature split into two 32-byte parts:
34+
- `signature[0]`: R component (first 32 bytes)
35+
- `signature[1]`: S component (last 32 bytes)
36+
- `message` (bytes): The message that was signed (variable length)
37+
38+
**Returns:**
39+
- `isValid` (bool): `true` if the signature is valid, `false` otherwise
40+
41+
## Gas Costs
42+
43+
The gas cost for the `ed25519Verify` function is calculated dynamically based on the message length:
44+
45+
```
46+
gas = ED25519_VERIFY_BASE_GAS + SHA512_BASE_GAS + SHA512_PER_WORD_GAS * ((msgLen + 31) / 32)
47+
```
48+
49+
Where:
50+
- `ED25519_VERIFY_BASE_GAS = 2000`: Base cost for ED25519 signature verification
51+
- `SHA512_BASE_GAS = 60`: Base cost for SHA512 hashing
52+
- `SHA512_PER_WORD_GAS = 12`: Cost per 32-byte word for SHA512 hashing
53+
- `msgLen`: Length of the message (excluding the 36 bytes for method selector and signature.S)
54+
55+
56+
## Usage Example
57+
58+
### Solidity
59+
60+
```solidity
61+
// SPDX-License-Identifier: MIT
62+
pragma solidity ^0.8.0;
63+
64+
interface IED25519 {
65+
function ed25519Verify(
66+
bytes32 publicKey,
67+
bytes32[2] calldata signature,
68+
bytes calldata message
69+
) external returns (bool isValid);
70+
}
71+
72+
contract ED25519Example {
73+
IED25519 constant ED25519_PRECOMPILE = IED25519(0x00000000000000000000000000000000000008f3);
74+
75+
function verifySignature(
76+
bytes32 publicKey,
77+
bytes32[2] calldata signature,
78+
bytes calldata message
79+
) public returns (bool) {
80+
return ED25519_PRECOMPILE.ed25519Verify(publicKey, signature, message);
81+
}
82+
}
83+
```
84+
85+
### JavaScript/TypeScript (ethers.js)
86+
87+
```typescript
88+
import { ethers } from 'ethers';
89+
90+
const ED25519_ADDRESS = '0x00000000000000000000000000000000000008f3';
91+
92+
const ed25519ABI = [
93+
{
94+
"inputs": [
95+
{ "internalType": "bytes32", "name": "publicKey", "type": "bytes32" },
96+
{ "internalType": "bytes32[2]", "name": "signature", "type": "bytes32[2]" },
97+
{ "internalType": "bytes", "name": "message", "type": "bytes" }
98+
],
99+
"name": "ed25519Verify",
100+
"outputs": [
101+
{ "internalType": "bool", "name": "isValid", "type": "bool" }
102+
],
103+
"stateMutability": "nonpayable",
104+
"type": "function"
105+
}
106+
];
107+
108+
async function verifyED25519Signature(
109+
provider: ethers.Provider,
110+
publicKey: string,
111+
signature: [string, string],
112+
message: string
113+
): Promise<boolean> {
114+
const ed25519Contract = new ethers.Contract(ED25519_ADDRESS, ed25519ABI, provider);
115+
const isValid = await ed25519Contract.ed25519Verify(publicKey, signature, message);
116+
return isValid;
117+
}
118+
```
119+
120+
## Technical Details
121+
122+
### ED25519 Algorithm
123+
124+
ED25519 is a public-key signature system that uses:
125+
- Curve25519 elliptic curve
126+
- SHA-512 hash function
127+
- Schnorr signature scheme
128+
129+
### Signature Format
130+
131+
The ED25519 signature is 64 bytes long and is split into two components:
132+
- **R component** (32 bytes): The first half of the signature
133+
- **S component** (32 bytes): The second half of the signature
134+
135+
### Implementation
136+
137+
The precompile uses Go's standard `crypto/ed25519` package for signature verification, ensuring compatibility with standard ED25519 implementations.
138+
139+
## Security Considerations
140+
141+
1. **Public Key Validation**: The precompile expects a valid 32-byte ED25519 public key. Invalid keys will result in verification failure.
142+
143+
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.
144+
145+
3. **Message Integrity**: The message should be provided exactly as it was when the signature was created. Any modification will cause verification to fail.
146+
147+
4. **Gas Limits**: When verifying large messages, ensure sufficient gas is provided based on the dynamic gas calculation formula.
148+
149+
## Use Cases
150+
151+
- **Cross-chain Communication**: Verify signatures from chains that use ED25519 (e.g., Cosmos SDK chains, Solana)
152+
- **Identity Verification**: Validate ED25519-based digital identities
153+
- **Secure Messaging**: Verify signed messages in decentralized applications
154+
- **Multi-signature Wallets**: Implement multi-sig wallets that support ED25519
155+
- **Oracle Data Verification**: Verify signed data from oracles using ED25519 keys
156+
157+
## Testing
158+
159+
The precompile includes comprehensive test coverage. Run tests with:
160+
161+
```bash
162+
go test ./precompiles/ed25519/...
163+
```
164+
165+
## References
166+
167+
- [ED25519 Specification (RFC 8032)](https://datatracker.ietf.org/doc/html/rfc8032)
168+
- [Go crypto/ed25519 Package](https://pkg.go.dev/crypto/ed25519)
169+
- [Curve25519 and ED25519](https://ed25519.cr.yp.to/)
170+
171+
## License
172+
173+
This precompile is part of the Cosmos EVM project and is licensed under the project's license terms.

precompiles/ed25519/ed25519.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ import (
1818
//go:embed abi.json
1919
var f embed.FS
2020

21-
const ED25519_VERIFY_BASE_GAS = 1500
21+
const ED25519_VERIFY_BASE_GAS = 2000
2222
const SHA512_BASE_GAS = 60
23-
const SHA512_PER_WORD_GAS = 8
23+
const SHA512_PER_WORD_GAS = 12
2424

2525
const ED25519VerifyMethod = "ed25519Verify"
2626

@@ -46,7 +46,8 @@ func (Precompile) Address() common.Address {
4646
func (p Precompile) RequiredGas(input []byte) uint64 {
4747
// Challenge for ed25519 uses sha512 of sig.R, pubkey, msg
4848
// So exclude 32 bytes of sig.Z from the length
49-
msgLen := max(len(input)-32, 0)
49+
// Also exclute 4 bytes of method selector
50+
msgLen := max(len(input)-36, 0)
5051
return ED25519_VERIFY_BASE_GAS + SHA512_BASE_GAS + SHA512_PER_WORD_GAS*((uint64(msgLen)+31)/32)
5152
}
5253

precompiles/ed25519/ed25519_test.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ func (s *PrecompileTestSuite) TestAddress() {
5050
}
5151

5252
func (s *PrecompileTestSuite) TestRequiredGas() {
53+
// Formula: msgLen = max(len(input) - 36, 0)
54+
// gas = ED25519_VERIFY_BASE_GAS + SHA512_BASE_GAS + SHA512_PER_WORD_GAS * ((msgLen + 31) / 32)
55+
// 36 = 4 (method selector) + 32 (sig.Z excluded from sha512 calculation)
5356
testCases := []struct {
5457
name string
5558
input []byte
@@ -58,27 +61,32 @@ func (s *PrecompileTestSuite) TestRequiredGas() {
5861
{
5962
"empty input",
6063
[]byte{},
64+
// msgLen = 0, words = 0
6165
edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS,
6266
},
6367
{
6468
"minimal input (96 bytes)",
6569
make([]byte, 96),
66-
edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS,
70+
// msgLen = 96-36 = 60, words = (60+31)/32 = 2
71+
edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS*2,
6772
},
6873
{
69-
"input with 32 byte message",
70-
make([]byte, 96+32),
71-
edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS,
74+
"input with 32 byte message (128 bytes total)",
75+
make([]byte, 128),
76+
// msgLen = 128-36 = 92, words = (92+31)/32 = 3
77+
edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS*3,
7278
},
7379
{
74-
"input with 64 byte message",
75-
make([]byte, 96+64),
76-
edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS*2,
80+
"input with 64 byte message (160 bytes total)",
81+
make([]byte, 160),
82+
// msgLen = 160-36 = 124, words = (124+31)/32 = 4
83+
edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS*4,
7784
},
7885
{
79-
"input with 100 byte message",
80-
make([]byte, 96+100),
81-
edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS*4,
86+
"input with 100 byte message (196 bytes total)",
87+
make([]byte, 196),
88+
// msgLen = 196-36 = 160, words = (160+31)/32 = 5
89+
edprecompile.ED25519_VERIFY_BASE_GAS + edprecompile.SHA512_BASE_GAS + edprecompile.SHA512_PER_WORD_GAS*5,
8290
},
8391
}
8492

x/vm/types/precompiles.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ const LiquidStakePrecompileAddress = "0x000000000000000000000000000000000000
2323
//
2424
// NOTE: To be explicit, this list does not include the dynamically registered EVM extensions
2525
// like the ERC-20 extensions.
26+
// NOTE: This list MUST be sorted lexicographically by address to match the expected order
27+
// after params are set (SetParams sorts the precompiles).
2628
var AvailableStaticPrecompiles = []string{
2729
P256PrecompileAddress,
2830
Bech32PrecompileAddress,
29-
Ed25519PrecompileAddress,
3031
StakingPrecompileAddress,
3132
DistributionPrecompileAddress,
3233
ICS20PrecompileAddress,
@@ -35,5 +36,6 @@ var AvailableStaticPrecompiles = []string{
3536
GovPrecompileAddress,
3637
SlashingPrecompileAddress,
3738
EvidencePrecompileAddress,
39+
Ed25519PrecompileAddress,
3840
LiquidStakePrecompileAddress,
3941
}

x/vm/types/scaling_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ func TestConvertEvmCoinFrom18Decimals(t *testing.T) {
153153

154154
if !tc.expErr {
155155
require.NoError(t, err)
156-
require.Equal(t, tc.expCoin, coinConverted, "expected a different coin")
156+
require.True(t, tc.expCoin.IsEqual(coinConverted), "expected a different coin: got %s, want %s", coinConverted, tc.expCoin)
157157
} else {
158158
require.Error(t, err)
159159
}

0 commit comments

Comments
 (0)