Skip to content

Commit e57bfaf

Browse files
authored
feat: add leanSig timing chart (#55)
* feat: add leanSig timing chart * fix: linting
1 parent 7239ad0 commit e57bfaf

File tree

7 files changed

+148
-89
lines changed

7 files changed

+148
-89
lines changed

components/benchmarks/Benchmarks.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ function getPercentageColor(subValue: string, label: string): string {
1515
// Use the first (or only) percentage for color determination
1616
const percent = parseInt(matches[0], 10);
1717

18-
// For size metrics, lower is better (being under target is good)
19-
const lowerIsBetter = label.toLowerCase().includes('size');
18+
// For size and time metrics, lower is better (being under target is good)
19+
const lowerIsBetter =
20+
label.toLowerCase().includes('size') || label.toLowerCase().includes('time');
2021

2122
if (lowerIsBetter) {
2223
if (percent <= 100) return 'text-emerald-600';

components/benchmarks/LeanSigTimingChart.tsx

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
11
'use client';
22

33
import {
4-
LineChart,
5-
Line,
4+
BarChart,
5+
Bar,
66
XAxis,
77
YAxis,
88
CartesianGrid,
99
Tooltip,
1010
Legend,
11+
ReferenceLine,
1112
ResponsiveContainer,
1213
} from 'recharts';
13-
import { leanSigTimingData, leanSigSeriesConfig } from '@/data/leansig-timing';
14+
import { leanSigTimingData, LEANSIG_TIMING_TARGET } from '@/data/benchmarks/leansig-timing';
15+
16+
const seriesConfig = {
17+
signing: {
18+
label: 'Signing',
19+
color: '#8b5cf6',
20+
},
21+
verification: {
22+
label: 'Verification',
23+
color: '#06b6d4',
24+
},
25+
};
1426

1527
export function LeanSigTimingChart() {
1628
const hasData = leanSigTimingData.length > 0;
@@ -22,19 +34,20 @@ export function LeanSigTimingChart() {
2234
</h4>
2335
<ResponsiveContainer width="100%" height={240}>
2436
{hasData ? (
25-
<LineChart data={leanSigTimingData} margin={{ top: 0, right: 40, left: 0, bottom: 0 }}>
37+
<BarChart data={leanSigTimingData} margin={{ top: 0, right: 40, left: 0, bottom: 0 }}>
2638
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
2739
<XAxis
28-
dataKey="date"
40+
dataKey="config"
2941
tick={{ fontSize: 11, fill: '#64748b' }}
3042
tickLine={{ stroke: '#cbd5e1' }}
3143
axisLine={{ stroke: '#cbd5e1' }}
44+
padding={{ left: 20, right: 60 }}
3245
/>
3346
<YAxis
3447
tick={{ fontSize: 11, fill: '#64748b' }}
3548
tickLine={{ stroke: '#cbd5e1' }}
3649
axisLine={{ stroke: '#cbd5e1' }}
37-
unit="s"
50+
unit=" µs"
3851
/>
3952
<Tooltip
4053
contentStyle={{
@@ -44,34 +57,62 @@ export function LeanSigTimingChart() {
4457
fontSize: '12px',
4558
}}
4659
formatter={(value: number, name: string) => {
47-
const config = leanSigSeriesConfig[name as keyof typeof leanSigSeriesConfig];
48-
return [`${value}s`, config?.label || name];
60+
const config = seriesConfig[name as keyof typeof seriesConfig];
61+
return [`${value} µs`, config?.label || name];
4962
}}
5063
/>
5164
<Legend
5265
wrapperStyle={{ fontSize: '11px', paddingTop: '10px' }}
5366
formatter={(value: string) => {
54-
const config = leanSigSeriesConfig[value as keyof typeof leanSigSeriesConfig];
67+
const config = seriesConfig[value as keyof typeof seriesConfig];
5568
return config?.label || value;
5669
}}
5770
/>
58-
<Line
59-
type="monotone"
60-
dataKey="signing"
61-
stroke={leanSigSeriesConfig.signing.color}
71+
<ReferenceLine
72+
y={LEANSIG_TIMING_TARGET}
73+
stroke="#555555"
74+
strokeDasharray="7 3"
6275
strokeWidth={2}
63-
dot={{ fill: leanSigSeriesConfig.signing.color, strokeWidth: 0, r: 4 }}
64-
connectNulls
76+
label={({ viewBox }) => (
77+
<text
78+
x={viewBox.width + viewBox.x - 5}
79+
y={viewBox.y - 20}
80+
textAnchor="end"
81+
fontSize={11}
82+
fill="#555555"
83+
>
84+
<tspan x={viewBox.width + viewBox.x - 5} dy={0}>
85+
Target
86+
</tspan>
87+
<tspan x={viewBox.width + viewBox.x - 5} dy={13}>
88+
({LEANSIG_TIMING_TARGET} µs)
89+
</tspan>
90+
</text>
91+
)}
6592
/>
66-
<Line
67-
type="monotone"
93+
<Bar
94+
dataKey="signing"
95+
fill={seriesConfig.signing.color}
96+
radius={[4, 4, 0, 0]}
97+
label={{
98+
position: 'top',
99+
fontSize: 9,
100+
fill: '#94a3b8',
101+
formatter: (v: number) => `${v}µs`,
102+
}}
103+
/>
104+
<Bar
68105
dataKey="verification"
69-
stroke={leanSigSeriesConfig.verification.color}
70-
strokeWidth={2}
71-
dot={{ fill: leanSigSeriesConfig.verification.color, strokeWidth: 0, r: 4 }}
72-
connectNulls
106+
fill={seriesConfig.verification.color}
107+
radius={[4, 4, 0, 0]}
108+
label={{
109+
position: 'top',
110+
fontSize: 9,
111+
fill: '#94a3b8',
112+
formatter: (v: number) => `${v}µs`,
113+
}}
73114
/>
74-
</LineChart>
115+
</BarChart>
75116
) : (
76117
<div className="flex items-center justify-center h-full">
77118
<p className="text-sm text-slate-400 italic">Chart coming soon</p>

components/benchmarks/XmssAggregationChart.tsx

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,25 @@ import {
1111
ReferenceLine,
1212
ResponsiveContainer,
1313
} from 'recharts';
14-
import { xmssAggregationData, xmssSeriesConfig, XMSS_TARGET } from '@/data/xmss-aggregation';
14+
import {
15+
xmssAggregationData,
16+
XMSS_AGGREGATE_TIMING_TARGET,
17+
} from '@/data/benchmarks/xmss-aggregation';
18+
19+
const seriesConfig = {
20+
i9: {
21+
label: 'i9-12900H',
22+
color: '#2e86ab',
23+
},
24+
m4: {
25+
label: 'M4 Max (efficient)',
26+
color: '#ef4444',
27+
},
28+
m4vm: {
29+
label: 'M4 Max (lean-vm-simple)',
30+
color: '#22c55e',
31+
},
32+
};
1533

1634
const formatDate = (date: string): string => {
1735
return new Date(date).toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
@@ -48,24 +66,24 @@ export function XmssAggregationChart() {
4866
fontSize: '12px',
4967
}}
5068
formatter={(value: number, name: string) => {
51-
const config = xmssSeriesConfig[name as keyof typeof xmssSeriesConfig];
69+
const config = seriesConfig[name as keyof typeof seriesConfig];
5270
return [value, config?.label || name];
5371
}}
5472
/>
5573
<Legend
5674
wrapperStyle={{ fontSize: '11px', paddingTop: '10px' }}
5775
formatter={(value: string) => {
58-
const config = xmssSeriesConfig[value as keyof typeof xmssSeriesConfig];
76+
const config = seriesConfig[value as keyof typeof seriesConfig];
5977
return config?.label || value;
6078
}}
6179
/>
6280
<ReferenceLine
63-
y={XMSS_TARGET}
81+
y={XMSS_AGGREGATE_TIMING_TARGET}
6482
stroke="#555555"
6583
strokeDasharray="7 3"
6684
strokeWidth={2}
6785
label={{
68-
value: `Target (${XMSS_TARGET}/s)`,
86+
value: `Target (${XMSS_AGGREGATE_TIMING_TARGET}/s)`,
6987
position: 'insideTopLeft',
7088
fontSize: 11,
7189
fill: '#555555',
@@ -74,25 +92,25 @@ export function XmssAggregationChart() {
7492
<Line
7593
type="monotone"
7694
dataKey="i9"
77-
stroke={xmssSeriesConfig.i9.color}
95+
stroke={seriesConfig.i9.color}
7896
strokeWidth={2}
79-
dot={{ fill: xmssSeriesConfig.i9.color, strokeWidth: 0, r: 4 }}
97+
dot={{ fill: seriesConfig.i9.color, strokeWidth: 0, r: 4 }}
8098
connectNulls
8199
/>
82100
<Line
83101
type="monotone"
84102
dataKey="m4"
85-
stroke={xmssSeriesConfig.m4.color}
103+
stroke={seriesConfig.m4.color}
86104
strokeWidth={2}
87-
dot={{ fill: xmssSeriesConfig.m4.color, strokeWidth: 0, r: 4 }}
105+
dot={{ fill: seriesConfig.m4.color, strokeWidth: 0, r: 4 }}
88106
connectNulls
89107
/>
90108
<Line
91109
type="monotone"
92110
dataKey="m4vm"
93-
stroke={xmssSeriesConfig.m4vm.color}
111+
stroke={seriesConfig.m4vm.color}
94112
strokeWidth={2}
95-
dot={{ fill: xmssSeriesConfig.m4vm.color, strokeWidth: 0, r: 4 }}
113+
dot={{ fill: seriesConfig.m4vm.color, strokeWidth: 0, r: 4 }}
96114
connectNulls
97115
/>
98116
</LineChart>

data/benchmarks.ts

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { xmssLatest, XMSS_TARGET, AGGREGATE_SIZE_TARGET, aggregateSize } from './xmss-aggregation';
1+
import {
2+
xmssLatest,
3+
XMSS_AGGREGATE_TIMING_TARGET,
4+
AGGREGATE_SIZE_TARGET,
5+
aggregateSize,
6+
} from './benchmarks/xmss-aggregation';
7+
import { leanSigTimingData, LEANSIG_TIMING_TARGET } from './benchmarks/leansig-timing';
28

39
export interface BenchmarkCategory {
410
id: string;
@@ -34,23 +40,39 @@ export const benchmarksData: BenchmarkCategory[] = [
3440
value: '3',
3541
unit: 'KiB',
3642
},
43+
{
44+
label: 'Key generation time',
45+
note: '10-core MacBook Pro M1; 8 years key lifetime',
46+
value: '3.5',
47+
unit: 'hours',
48+
},
49+
{
50+
label: 'Signing time (Target)',
51+
note: 'Performance goal',
52+
value: String(LEANSIG_TIMING_TARGET),
53+
unit: 'μs',
54+
highlight: true,
55+
},
3756
{
3857
label: 'Signing time',
39-
note: 'Single core performance',
40-
value: '~500',
58+
note: 'Single core performance, MacBook Pro M1',
59+
value: `~${leanSigTimingData[0].signing}`,
4160
unit: 'μs',
61+
subValue: `${Math.round((leanSigTimingData[0].signing / LEANSIG_TIMING_TARGET) * 100)}% of target${leanSigTimingData[0].signing <= LEANSIG_TIMING_TARGET ? ' 🎉' : ''}`,
4262
},
4363
{
44-
label: 'Verification time',
45-
note: 'Single core performance',
46-
value: '~300',
64+
label: 'Verification time (Target)',
65+
note: 'Performance goal',
66+
value: String(LEANSIG_TIMING_TARGET),
4767
unit: 'μs',
68+
highlight: true,
4869
},
4970
{
50-
label: 'Key generation time',
51-
note: '10-core MacBook Pro; 8 years key lifetime',
52-
value: '3.5',
53-
unit: 'hours',
71+
label: 'Verification time',
72+
note: 'Single core performance, MacBook Pro M1',
73+
value: `~${leanSigTimingData[0].verification}`,
74+
unit: 'μs',
75+
subValue: `${Math.round((leanSigTimingData[0].verification / LEANSIG_TIMING_TARGET) * 100)}% of target${leanSigTimingData[0].verification <= LEANSIG_TIMING_TARGET ? ' 🎉' : ''}`,
5476
},
5577
],
5678
},
@@ -62,7 +84,7 @@ export const benchmarksData: BenchmarkCategory[] = [
6284
{
6385
label: 'XMSS aggregated (Target)',
6486
note: 'Performance goal',
65-
value: String(XMSS_TARGET),
87+
value: String(XMSS_AGGREGATE_TIMING_TARGET),
6688
unit: 'XMSS/s',
6789
highlight: true,
6890
},
@@ -71,21 +93,21 @@ export const benchmarksData: BenchmarkCategory[] = [
7193
note: 'main branch; optimized for prover efficiency',
7294
value: `~${xmssLatest.m4}`,
7395
unit: '/sec',
74-
subValue: `${Math.round((xmssLatest.m4! / XMSS_TARGET) * 100)}% of target`,
96+
subValue: `${Math.round((xmssLatest.m4! / XMSS_AGGREGATE_TIMING_TARGET) * 100)}% of target${xmssLatest.m4! >= XMSS_AGGREGATE_TIMING_TARGET ? ' 🎉' : ''}`,
7597
},
7698
{
7799
label: 'XMSS aggregated (M4 Max - Simple)',
78100
note: 'lean-vm-simple branch; optimized for simplicity',
79101
value: `~${xmssLatest.m4vm}`,
80102
unit: '/sec',
81-
subValue: `${Math.round((xmssLatest.m4vm! / XMSS_TARGET) * 100)}% of target`,
103+
subValue: `${Math.round((xmssLatest.m4vm! / XMSS_AGGREGATE_TIMING_TARGET) * 100)}% of target${xmssLatest.m4vm! >= XMSS_AGGREGATE_TIMING_TARGET ? ' 🎉' : ''}`,
82104
},
83105
{
84106
label: 'XMSS aggregated (i9-12900H)',
85107
note: 'Baseline Intel performance',
86108
value: `~${xmssLatest.i9}`,
87109
unit: '/sec',
88-
subValue: `${Math.round((xmssLatest.i9! / XMSS_TARGET) * 100)}% of target`,
110+
subValue: `${Math.round((xmssLatest.i9! / XMSS_AGGREGATE_TIMING_TARGET) * 100)}% of target${xmssLatest.i9! >= XMSS_AGGREGATE_TIMING_TARGET ? ' 🎉' : ''}`,
89111
},
90112
{
91113
label: 'Aggregate size (Target)',
@@ -99,14 +121,14 @@ export const benchmarksData: BenchmarkCategory[] = [
99121
note: 'main branch; optimized for prover efficiency',
100122
value: `~${aggregateSize.efficient.min} - ${aggregateSize.efficient.max}`,
101123
unit: 'KiB',
102-
subValue: `${Math.round((aggregateSize.efficient.min / AGGREGATE_SIZE_TARGET) * 100)} - ${Math.round((aggregateSize.efficient.max / AGGREGATE_SIZE_TARGET) * 100)}% of target`,
124+
subValue: `${Math.round((aggregateSize.efficient.min / AGGREGATE_SIZE_TARGET) * 100)} - ${Math.round((aggregateSize.efficient.max / AGGREGATE_SIZE_TARGET) * 100)}% of target${aggregateSize.efficient.max <= AGGREGATE_SIZE_TARGET ? ' 🎉' : ''}`,
103125
},
104126
{
105127
label: 'Aggregate size (Simple)',
106128
note: 'lean-vm-simple branch; optimized for simplicity',
107129
value: `~${aggregateSize.simple}`,
108130
unit: 'KiB',
109-
subValue: `${Math.round((aggregateSize.simple / AGGREGATE_SIZE_TARGET) * 100)}% of target`,
131+
subValue: `${Math.round((aggregateSize.simple / AGGREGATE_SIZE_TARGET) * 100)}% of target${aggregateSize.simple <= AGGREGATE_SIZE_TARGET ? ' 🎉' : ''}`,
110132
},
111133
],
112134
},

data/benchmarks/leansig-timing.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export interface LeanSigDataPoint {
2+
config: string;
3+
signing: number;
4+
verification: number;
5+
}
6+
7+
// Time in microseconds (µs)
8+
export const LEANSIG_TIMING_TARGET = 500;
9+
10+
export const leanSigTimingData: LeanSigDataPoint[] = [
11+
{ config: 'Hashing Optimized', signing: 535.2, verification: 193.42 },
12+
{ config: 'Trade-off', signing: 685.19, verification: 270.98 },
13+
{ config: 'Size Optimized', signing: 1213.4, verification: 392.67 },
14+
];
Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const xmssAggregationData: XmssDataPoint[] = [
2626
{ date: '2025-12-05', m4: 970, m4vm: 815 },
2727
];
2828

29-
export const XMSS_TARGET = 1000;
29+
export const XMSS_AGGREGATE_TIMING_TARGET = 1000;
3030

3131
// Aggregate size in KiB
3232
export const AGGREGATE_SIZE_TARGET = 128;
@@ -51,18 +51,3 @@ export const xmssLatest = {
5151
m4: getLatestValue('m4'),
5252
m4vm: getLatestValue('m4vm'),
5353
};
54-
55-
export const xmssSeriesConfig = {
56-
i9: {
57-
label: 'i9-12900H',
58-
color: '#2e86ab',
59-
},
60-
m4: {
61-
label: 'M4 Max (efficient)',
62-
color: '#ef4444',
63-
},
64-
m4vm: {
65-
label: 'M4 Max (lean-vm-simple)',
66-
color: '#22c55e',
67-
},
68-
};

0 commit comments

Comments
 (0)