Skip to content

Commit ff79cec

Browse files
authored
SSZ fixes (#93)
2 parents ad76ea7 + 8cdfcaf commit ff79cec

File tree

14 files changed

+1110
-219
lines changed

14 files changed

+1110
-219
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@ jobs:
128128
set -euo pipefail
129129
echo "Running benchmark/benchmark.py for cross-language SSZ compatibility (lifetimes 2^8 and 2^32)"
130130
python3 benchmark/benchmark.py --lifetime "2^8,2^32"
131+
132+
- name: Test pre-generated keys (SSZ)
133+
shell: bash
134+
run: |
135+
set -euo pipefail
136+
echo "Running inspect_pregenerated_keys.py to verify pre-generated key compatibility"
137+
python3 benchmark/inspect_pregenerated_keys.py
131138
132139
cross-platform-build:
133140
name: Cross-Platform Build (${{ matrix.os }})

benchmark/benchmark.py

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,17 @@ def parse_args() -> argparse.Namespace:
126126
def build_scenarios(lifetimes: list[str], seed_hex: str) -> list[ScenarioConfig]:
127127
scenarios: list[ScenarioConfig] = []
128128
for lifetime in lifetimes:
129-
# Use 1024 active epochs for 2^32 lifetime, 256 for others
130-
num_active_epochs = 1024 if lifetime == "2^32" else 256
129+
# Use appropriate active epochs for each lifetime
130+
# Note: Zig/Rust multiply by 128 internally, so actual = input * 128
131+
# 2^8: max 256 signatures, so max input = 256/128 = 2
132+
# 2^18: max 262,144 signatures, so we can use 256 (256*128=32,768)
133+
# 2^32: max 4B+ signatures, so we can use 1024 (1024*128=131,072)
134+
if lifetime == "2^32":
135+
num_active_epochs = 1024
136+
elif lifetime == "2^18":
137+
num_active_epochs = 256
138+
else: # 2^8
139+
num_active_epochs = 2
131140
scenarios.append(
132141
ScenarioConfig(
133142
lifetime=lifetime,
@@ -262,6 +271,8 @@ def run_rust_sign(cfg: ScenarioConfig, paths: Dict[str, Path]) -> OperationResul
262271
tmp_dir.mkdir(exist_ok=True)
263272

264273
# Save active epochs to file for the tool to read
274+
# Note: Rust leansig multiplies by 128 internally, Zig now does the same
275+
# So we pass the same value to both
265276
(tmp_dir / "rust_active_epochs.txt").write_text(str(cfg.num_active_epochs))
266277

267278
# Generate keypair first
@@ -416,6 +427,68 @@ def run_rust_verify(
416427
return OperationResult(success, duration, result.stdout, result.stderr)
417428

418429

430+
def compare_file_sizes(cfg: ScenarioConfig, paths: Dict[str, Path]) -> bool:
431+
"""Compare file sizes between Rust and Zig generated files"""
432+
print(f"\n-- Comparing file sizes --")
433+
434+
discrepancies = []
435+
436+
# Compare public keys
437+
rust_pk_size = paths["rust_pk"].stat().st_size if paths["rust_pk"].exists() else 0
438+
zig_pk_size = paths["zig_pk"].stat().st_size if paths["zig_pk"].exists() else 0
439+
440+
print(f"Public key sizes:")
441+
print(f" Rust: {rust_pk_size} bytes")
442+
print(f" Zig: {zig_pk_size} bytes")
443+
444+
if rust_pk_size != zig_pk_size:
445+
discrepancies.append(f"Public key size mismatch: Rust={rust_pk_size} vs Zig={zig_pk_size}")
446+
print(f" ⚠️ WARNING: Public key sizes differ!")
447+
else:
448+
print(f" ✅ Public key sizes match")
449+
450+
# Compare signatures
451+
rust_sig_size = paths["rust_sig"].stat().st_size if paths["rust_sig"].exists() else 0
452+
zig_sig_size = paths["zig_sig"].stat().st_size if paths["zig_sig"].exists() else 0
453+
454+
print(f"Signature sizes:")
455+
print(f" Rust: {rust_sig_size} bytes")
456+
print(f" Zig: {zig_sig_size} bytes")
457+
458+
if rust_sig_size != zig_sig_size:
459+
discrepancies.append(f"Signature size mismatch: Rust={rust_sig_size} vs Zig={zig_sig_size}")
460+
print(f" ⚠️ WARNING: Signature sizes differ!")
461+
else:
462+
print(f" ✅ Signature sizes match")
463+
464+
# Compare secret keys (check both project tmp and rust_benchmark tmp)
465+
rust_sk_path = REPO_ROOT / "benchmark" / "rust_benchmark" / "tmp" / "rust_sk.ssz"
466+
zig_sk_path = REPO_ROOT / "tmp" / "zig_sk.ssz"
467+
468+
if rust_sk_path.exists() and zig_sk_path.exists():
469+
rust_sk_size = rust_sk_path.stat().st_size
470+
zig_sk_size = zig_sk_path.stat().st_size
471+
472+
print(f"Secret key sizes:")
473+
print(f" Rust: {rust_sk_size:,} bytes")
474+
print(f" Zig: {zig_sk_size:,} bytes")
475+
476+
if rust_sk_size != zig_sk_size:
477+
discrepancies.append(f"Secret key size mismatch: Rust={rust_sk_size:,} vs Zig={zig_sk_size:,}")
478+
print(f" ⚠️ WARNING: Secret key sizes differ!")
479+
print(f" This indicates incompatible SSZ serialization formats!")
480+
print(f" Rust includes full trees, Zig may only save metadata.")
481+
else:
482+
print(f" ✅ Secret key sizes match")
483+
484+
if discrepancies:
485+
print(f"\n⚠️ {len(discrepancies)} size discrepanc{'y' if len(discrepancies) == 1 else 'ies'} detected:")
486+
for disc in discrepancies:
487+
print(f" - {disc}")
488+
return False
489+
490+
return True
491+
419492
def run_scenario(cfg: ScenarioConfig, timeout_2_32: int) -> tuple[Dict[str, OperationResult], Dict[str, Path]]:
420493
print(f"\n=== Scenario: {cfg.label} ===")
421494
paths = scenario_paths(cfg)
@@ -438,6 +511,11 @@ def run_scenario(cfg: ScenarioConfig, timeout_2_32: int) -> tuple[Dict[str, Oper
438511
# signature must be accepted by the Rust verifier.
439512
results["zig_to_rust"] = run_rust_verify(cfg, paths["zig_pk"], paths["zig_sig"], "Zig sign → Rust verify")
440513

514+
# Compare file sizes to detect serialization format mismatches
515+
sizes_match = compare_file_sizes(cfg, paths)
516+
if not sizes_match:
517+
print("\n⚠️ WARNING: File size mismatches detected. This may indicate serialization format issues.")
518+
441519
return results, paths
442520

443521

0 commit comments

Comments
 (0)