Skip to content

Commit 207ae51

Browse files
authored
eip 7951 - P256VERIFY precompile hook (#219)
* draft p256verify hook * run the entire test-vectors.json for p256verify * fix build errors
1 parent ff0cca9 commit 207ae51

9 files changed

Lines changed: 5700 additions & 18 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ CPPFLAGS += --std=c++20 -fPIC -O3 $(INCLUDES)
141141
plugin-c/%.o: plugin-c/%.cpp $(PREFIX)/libcryptopp/lib/libcryptopp.a $(PREFIX)/libff/lib/libff.a $(PREFIX)/c-kzg-4844/lib/libckzg.a $(PREFIX)/c-kzg-4844/lib/libblst.a
142142
$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) -o $@ $<
143143

144-
$(PREFIX)/plugin/lib/plugin.a: plugin-c/crypto.o plugin-c/hash_ext.o plugin-c/kzg.o plugin-c/json.o plugin-c/k.o plugin-c/plugin_util.o
144+
$(PREFIX)/plugin/lib/plugin.a: plugin-c/crypto.o plugin-c/hash_ext.o plugin-c/kzg.o plugin-c/json.o plugin-c/k.o plugin-c/plugin_util.o plugin-c/p256verify.o
145145
mkdir -p $(dir $@)
146146
ar r $@ $^
147147

krypto/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ test-unit: poetry-install
3333
$(POETRY_RUN) pytest src/tests/unit --maxfail=1 --verbose $(TEST_ARGS)
3434

3535
test-integration: poetry-install
36-
$(POETRY_RUN) pytest src/tests/integration --maxfail=1 --verbose --durations=0 --numprocesses=4 --dist=worksteal $(TEST_ARGS)
36+
$(POETRY_RUN) pytest src/tests/integration --maxfail=1 --verbose --durations=0 --numprocesses=8 --dist=worksteal $(TEST_ARGS)
3737

3838

3939
# Coverage

krypto/src/tests/integration/conftest.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,18 @@ def _krypto_kompile(**kwargs: Any) -> Path:
4747
return kompile(**args)
4848

4949
return _krypto_kompile
50+
51+
52+
@pytest.fixture(scope='session')
53+
def definition_dir(krypto_kompile: Callable[..., Path]) -> Path:
54+
definition = """
55+
requires "plugin/krypto.md"
56+
57+
module TEST
58+
imports BOOL
59+
imports KRYPTO
60+
syntax Pgm ::= Bool | Bytes | String | G1Point | G2Point
61+
configuration <k> $PGM:Pgm </k>
62+
endmodule
63+
"""
64+
return krypto_kompile(definition=definition, main_module='TEST', syntax_module='TEST')

krypto/src/tests/integration/test-data/test-vectors.json

Lines changed: 5469 additions & 0 deletions
Large diffs are not rendered by default.

krypto/src/tests/integration/test_hooks.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,13 @@
88
from .utils import hex2bytes, run
99

1010
if TYPE_CHECKING:
11-
from collections.abc import Callable
1211
from pathlib import Path
1312
from typing import Final
1413

1514

1615
x01_32B: Final = hex2bytes(31 * '00' + '01') # noqa: N816
1716

1817

19-
@pytest.fixture(scope='session')
20-
def definition_dir(krypto_kompile: Callable[..., Path]) -> Path:
21-
definition = """
22-
requires "plugin/krypto.md"
23-
24-
module TEST
25-
imports BOOL
26-
imports KRYPTO
27-
syntax Pgm ::= Bool | Bytes | String | G1Point | G2Point
28-
configuration <k> $PGM:Pgm </k>
29-
endmodule
30-
"""
31-
return krypto_kompile(definition=definition, main_module='TEST', syntax_module='TEST')
32-
33-
3418
HOOK_TEST_DATA: Final = (
3519
(
3620
'Keccak256',
@@ -169,6 +153,11 @@ def definition_dir(krypto_kompile: Callable[..., Path]) -> Path:
169153
'isValidPoint((0x0, 0x0))',
170154
'true',
171155
),
156+
(
157+
'p256verify',
158+
f'P256Verify({hex2bytes("bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca6050232ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e184cd60b855d442f5b3c7b11eb6c4e0ae7525fe710fab9aa7c77a67f79e6fadd762927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e")})',
159+
'b"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01"',
160+
),
172161
)
173162

174163

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from pathlib import Path
5+
from typing import Final
6+
7+
import pytest
8+
9+
from .utils import hex2bytes, run
10+
11+
# EIP-7951 test vectors URL
12+
# wget 'https://raw.githubusercontent.com/ethereum/EIPs/d386b29b5a31bd5cfd8d21bbf4e8a0c87734085e/assets/eip-7951/test-vectors.json'
13+
TEST_VECTORS_FILE: Final = Path(__file__).parent / 'test-data' / 'test-vectors.json'
14+
P256VERIFY_SUCCESS: Final = (
15+
'b"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01"'
16+
)
17+
P256VERIFY_FAILURE: Final = 'b""'
18+
19+
20+
def load_test_vectors() -> list[tuple[str, str, str]]:
21+
"""Load and parse EIP-7951 test vectors.
22+
23+
Returns:
24+
List of (test_name, input_hex, expected_hex) tuples
25+
"""
26+
with TEST_VECTORS_FILE.open('r') as f:
27+
test_data = json.load(f)
28+
29+
test_vectors = []
30+
for test in test_data:
31+
test_name = test['Name']
32+
input_hex = test['Input']
33+
expected_hex = test['Expected']
34+
35+
expected_output = P256VERIFY_SUCCESS if expected_hex else P256VERIFY_FAILURE
36+
37+
test_vectors.append((test_name, input_hex, expected_output))
38+
39+
return test_vectors
40+
41+
42+
P256VERIFY_TEST_DATA = load_test_vectors()
43+
44+
45+
@pytest.mark.parametrize(
46+
'test_id,input_hex,expected_output',
47+
P256VERIFY_TEST_DATA,
48+
ids=[test_id for test_id, *_ in P256VERIFY_TEST_DATA],
49+
)
50+
def test_p256verify_hook(definition_dir: Path, test_id: str, input_hex: str, expected_output: str) -> None:
51+
# Given
52+
pgm = f'P256Verify({hex2bytes(input_hex)})'
53+
expected = f'<k>\n {expected_output} ~> .K\n</k>'
54+
55+
# When
56+
actual = run(definition_dir, pgm)
57+
58+
# Then
59+
assert expected == actual

plugin-c/p256verify.cpp

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#include "p256verify.h"
2+
#include "plugin_util.h"
3+
4+
extern "C" {
5+
6+
// Helper: Create public key EVP_PKEY object
7+
static EVP_PKEY *p256_pubkey_from_coords(const unsigned char *qx, const unsigned char *qy);
8+
// Helper: Build signature from raw (r, s) components
9+
static unsigned char *p256_sig_to_der(const unsigned char *r_bytes, const unsigned char *s_bytes, int *out_len);
10+
// Helper: Perform ECDSA verification with pre-hashed data
11+
static int p256_verify_prehash(EVP_PKEY *pkey, const unsigned char *sig_der, int sig_len, const unsigned char *hash, size_t hash_len);
12+
13+
struct string *hook_KRYPTO_p256verify(struct string *input) {
14+
// The precompile expects exactly 160 bytes: h || r || s || qx || qy
15+
if (len(input) != 160) {
16+
return allocString(0);
17+
}
18+
19+
// 32-bytes slices
20+
const unsigned char *data = (unsigned char *)input->data;
21+
const unsigned char *h = data;
22+
const unsigned char *r_bytes = data + 32;
23+
const unsigned char *s_bytes = data + 64;
24+
const unsigned char *qx = data + 96;
25+
const unsigned char *qy = data + 128;
26+
27+
EVP_PKEY *pkey = p256_pubkey_from_coords(qx, qy);
28+
if (!pkey) {
29+
return allocString(0);
30+
}
31+
32+
int sig_der_len = 0;
33+
unsigned char *sig_der = p256_sig_to_der(r_bytes, s_bytes, &sig_der_len);
34+
if (!sig_der) {
35+
EVP_PKEY_free(pkey);
36+
return allocString(0);
37+
}
38+
39+
int valid = p256_verify_prehash(pkey, sig_der, sig_der_len, h, 32);
40+
41+
OPENSSL_free(sig_der);
42+
EVP_PKEY_free(pkey);
43+
44+
if (valid) {
45+
unsigned char result[32] = {0};
46+
result[31] = 1;
47+
return raw(result, 32);
48+
}
49+
return allocString(0);
50+
}
51+
52+
static EVP_PKEY *p256_pubkey_from_coords(const unsigned char *qx, const unsigned char *qy) {
53+
// Build SEC1 uncompressed format: 0x04 || x || y
54+
unsigned char pubkey_uncompressed[65];
55+
pubkey_uncompressed[0] = 0x04;
56+
memcpy(pubkey_uncompressed + 1, qx, 32);
57+
memcpy(pubkey_uncompressed + 33, qy, 32);
58+
59+
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
60+
if (!pctx) {
61+
return NULL;
62+
}
63+
64+
if (EVP_PKEY_fromdata_init(pctx) <= 0) {
65+
EVP_PKEY_CTX_free(pctx);
66+
return NULL;
67+
}
68+
69+
OSSL_PARAM params[] = {
70+
OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, (char *)"prime256v1", 0),
71+
OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, pubkey_uncompressed, 65),
72+
OSSL_PARAM_construct_end()
73+
};
74+
75+
EVP_PKEY *pkey = NULL;
76+
if (EVP_PKEY_fromdata(pctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) {
77+
EVP_PKEY_CTX_free(pctx);
78+
return NULL;
79+
}
80+
81+
EVP_PKEY_CTX_free(pctx);
82+
return pkey;
83+
}
84+
85+
static unsigned char *p256_sig_to_der(const unsigned char *r_bytes, const unsigned char *s_bytes, int *out_len) {
86+
ECDSA_SIG *sig = ECDSA_SIG_new();
87+
if (!sig) {
88+
return NULL;
89+
}
90+
91+
BIGNUM *bn_r = BN_bin2bn(r_bytes, 32, NULL);
92+
BIGNUM *bn_s = BN_bin2bn(s_bytes, 32, NULL);
93+
94+
if (!bn_r || !bn_s || !ECDSA_SIG_set0(sig, bn_r, bn_s)) {
95+
// Note: ECDSA_SIG_set0 takes ownership on success, so only free on failure
96+
if (bn_r) BN_free(bn_r);
97+
if (bn_s) BN_free(bn_s);
98+
ECDSA_SIG_free(sig);
99+
return NULL;
100+
}
101+
102+
unsigned char *sig_der = NULL;
103+
*out_len = i2d_ECDSA_SIG(sig, &sig_der);
104+
ECDSA_SIG_free(sig);
105+
106+
if (*out_len <= 0) {
107+
return NULL;
108+
}
109+
110+
return sig_der;
111+
}
112+
113+
static int p256_verify_prehash(EVP_PKEY *pkey, const unsigned char *sig_der, int sig_len, const unsigned char *hash, size_t hash_len) {
114+
EVP_PKEY_CTX *vctx = EVP_PKEY_CTX_new(pkey, NULL);
115+
if (!vctx) {
116+
return 0;
117+
}
118+
119+
int result = 0;
120+
if (EVP_PKEY_verify_init(vctx) > 0) {
121+
result = (EVP_PKEY_verify(vctx, sig_der, sig_len, hash, hash_len) == 1);
122+
}
123+
124+
EVP_PKEY_CTX_free(vctx);
125+
return result;
126+
}
127+
128+
}

plugin-c/p256verify.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#ifndef P256VERIFY_H
2+
#define P256VERIFY_H
3+
4+
#include <openssl/evp.h>
5+
#include <openssl/ec.h>
6+
#include <openssl/ecdsa.h>
7+
#include <openssl/obj_mac.h>
8+
#include <openssl/param_build.h>
9+
#include <openssl/core_names.h>
10+
11+
#include "plugin_util.h"
12+
13+
extern "C" {
14+
struct string *hook_KRYPTO_p256verify(struct string *input);
15+
}
16+
17+
#endif

plugin/krypto.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,5 +160,10 @@ functions evaluate to `#False`.
160160
syntax Int ::= "BLS12_FIELD_MODULUS" [alias]
161161
rule BLS12_FIELD_MODULUS => 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787
162162
// -----------------------------------------------------------------------------------------------------
163+
```
164+
165+
```k
166+
syntax Bytes ::= P256Verify ( Bytes ) [function, hook(KRYPTO.p256verify)]
167+
// -------------------------------------------------------------------------
163168
endmodule
164169
```

0 commit comments

Comments
 (0)