Skip to content

Commit 2673d90

Browse files
committed
Add /slides presentation page for Clear Signing
12 slides covering: - The problem: smart contracts are too smart (with wojak) - The solution: Verity intent DSL, scriptable ERC-7730 with code example, ZK circuit compiler for hardware verification - How it works: 7 steps from spec lookup to hardware verification - Demo slide Keyboard navigation (arrows/space), click to advance, slide dots. Syntax-highlighted Lean DSL code block on the DSL slide.
1 parent 21bccb0 commit 2673d90

5 files changed

Lines changed: 289 additions & 1 deletion

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,4 @@ next-env.d.ts
4444
# screenshots
4545
*.png
4646
verity/
47+
trustlessinference/

public/slides/wojak.jpg

23.1 KB
Loading

src/app/slides/layout.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default function SlidesLayout({
2+
children,
3+
}: {
4+
children: React.ReactNode;
5+
}) {
6+
return (
7+
<div className="h-screen w-screen overflow-hidden bg-white text-foreground">
8+
{children}
9+
</div>
10+
);
11+
}

src/app/slides/page.tsx

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
"use client";
2+
3+
import React, { useCallback, useEffect, useState } from "react";
4+
5+
// ─── Slide Data ─────────────────────────────────────────────────────────────
6+
7+
type Slide = {
8+
label: string;
9+
title: string;
10+
bullets?: string[];
11+
footnote?: string;
12+
image?: string;
13+
code?: string;
14+
variant?: "title" | "demo" | "default";
15+
};
16+
17+
const slides: Slide[] = [
18+
// ── Act 1: The Problem ──
19+
{
20+
label: "The Problem",
21+
title: "Smart contracts are too smart",
22+
image: "/slides/wojak.jpg",
23+
variant: "title",
24+
},
25+
26+
// ── Act 2: The Solution ──
27+
{
28+
label: "The Solution",
29+
title: "Formally defined transaction interpretation",
30+
bullets: [
31+
"We extended the Verity smart contract language with an intent DSL",
32+
"Each contract function gets a formal spec that maps calldata to natural language",
33+
"The spec is written in Lean 4, so the mapping itself is provable",
34+
],
35+
},
36+
{
37+
label: "The Solution",
38+
title: "A scriptable, provable ERC-7730",
39+
bullets: [
40+
"The DSL supports conditions, loops, predicates",
41+
"But evaluating this DSL is too heavy for an edge device like a Ledger",
42+
],
43+
code: `intent approve(spender : address, amount : uint256) where
44+
when amount == maxUint256 =>
45+
emit "Approve {spender} to spend unlimited USDC"
46+
otherwise =>
47+
emit "Approve {spender} to spend {amount:fixed decimals} USDC"`,
48+
},
49+
{
50+
label: "The Solution",
51+
title: "So we built a ZK circuit compiler",
52+
bullets: [
53+
"The Verity compiler generates a Groth16 circuit from the same Lean spec",
54+
"The circuit proves: this natural language string is the correct interpretation of this calldata",
55+
"A hardware wallet just verifies a proof, 4 pairings on BLS12-381",
56+
],
57+
},
58+
59+
// ── Act 3: How It Works ──
60+
{
61+
label: "How It Works",
62+
title: "Step 1: Spec lookup",
63+
bullets: [
64+
"The contract address is matched against the veryclear.eth ENS registry",
65+
"Each entry maps a contract to a compiled spec (e.g. ERC20, UniswapV2)",
66+
"The registry is on-chain, verifiable by anyone",
67+
],
68+
},
69+
{
70+
label: "How It Works",
71+
title: "Step 2: Function identification",
72+
bullets: [
73+
"The 4-byte selector identifies which function is being called",
74+
"0xa9059cbb = transfer, 0x095ea7b3 = approve",
75+
"The spec binding maps each selector to an intent function",
76+
],
77+
},
78+
{
79+
label: "How It Works",
80+
title: "Step 3: Calldata decoding",
81+
bullets: [
82+
"Raw bytes are decoded into typed parameters using the ABI",
83+
"uint256 values split into 128-bit limbs to fit the ZK field",
84+
"Addresses, booleans, arrays each have their own decoding logic",
85+
],
86+
},
87+
{
88+
label: "How It Works",
89+
title: "Step 4: Intent evaluation",
90+
bullets: [
91+
"The DSL program selects a template based on parameter values",
92+
'amount == MAX_UINT256? "Approve unlimited" : "Approve {amount}"',
93+
"Template index matches the circuit output for proof consistency",
94+
],
95+
},
96+
{
97+
label: "How It Works",
98+
title: "Step 5: Address resolution",
99+
bullets: [
100+
"Raw addresses in the template are resolved against the spec registry",
101+
"0x7a250d... becomes UniswapV2Router",
102+
"Unknown addresses stay truncated (0xAbCd...1234)",
103+
],
104+
},
105+
{
106+
label: "How It Works",
107+
title: "Step 6: Proof generation",
108+
bullets: [
109+
"The browser generates a Groth16 proof over BLS12-381",
110+
"Circuit computes Poseidon(selector, params) and Poseidon(templateId, holes) internally",
111+
"Proof size: ~400 bytes, generation: ~500ms in browser",
112+
],
113+
},
114+
{
115+
label: "How It Works",
116+
title: "Step 7: Hardware verification",
117+
bullets: [
118+
"The proof + verification key + intent text are sent to the Ledger Nano S+",
119+
"The Ledger displays the intent, user confirms with physical buttons",
120+
"On approve: 4-pairing Groth16 check on ARM Cortex-M35P secure element",
121+
],
122+
footnote: "The browser generates, the hardware verifies. Zero trust required.",
123+
},
124+
125+
// ── Closing ──
126+
{
127+
label: "Demo",
128+
title: "explain.md/clear-signing",
129+
variant: "demo",
130+
},
131+
];
132+
133+
// ─── Slide Component ────────────────────────────────────────────────────────
134+
135+
function highlightCode(line: string): React.ReactNode {
136+
if (line.trim() === "") return "\u00A0";
137+
// Simple Lean-like syntax highlighting
138+
return line
139+
.replace(
140+
/\b(intent|when|otherwise|emit|where)\b/g,
141+
'<kw>$1</kw>'
142+
)
143+
.replace(
144+
/\b(address|uint256|bool)\b/g,
145+
'<ty>$1</ty>'
146+
)
147+
.replace(
148+
/"([^"]*)"/g,
149+
'<str>"$1"</str>'
150+
)
151+
.replace(
152+
/\b(maxUint256|decimals)\b/g,
153+
'<id>$1</id>'
154+
)
155+
.split(/(<kw>.*?<\/kw>|<ty>.*?<\/ty>|<str>.*?<\/str>|<id>.*?<\/id>)/)
156+
.map((part, i) => {
157+
if (part.startsWith('<kw>')) return <span key={i} className="text-[#907aa9]">{part.slice(4, -5)}</span>;
158+
if (part.startsWith('<ty>')) return <span key={i} className="text-[#ea9d34]">{part.slice(4, -5)}</span>;
159+
if (part.startsWith('<str>')) return <span key={i} className="text-[#56949f]">{part.slice(5, -6)}</span>;
160+
if (part.startsWith('<id>')) return <span key={i} className="text-[#286983]">{part.slice(4, -5)}</span>;
161+
return part;
162+
});
163+
}
164+
165+
function SlideView({ slide }: { slide: Slide }) {
166+
if (slide.variant === "demo") {
167+
return (
168+
<>
169+
<p className="mb-6 text-sm uppercase tracking-[0.3em] opacity-40">
170+
{slide.label}
171+
</p>
172+
<h1 className="font-serif text-7xl italic">{slide.title}</h1>
173+
</>
174+
);
175+
}
176+
177+
return (
178+
<>
179+
<p className="mb-6 text-sm uppercase tracking-[0.3em] opacity-40">
180+
{slide.label}
181+
</p>
182+
<h1
183+
className={`max-w-3xl text-center font-serif leading-tight ${
184+
slide.variant === "title" ? "text-5xl" : "text-4xl"
185+
}`}
186+
>
187+
{slide.title}
188+
</h1>
189+
{slide.image && (
190+
<img
191+
src={slide.image}
192+
alt=""
193+
className="mt-8 max-h-[40vh] rounded-lg"
194+
/>
195+
)}
196+
{slide.code && (
197+
<pre className="mt-8 bg-[#faf4ed] rounded-lg px-6 py-5 text-[15px] leading-[1.7] font-mono max-w-2xl overflow-x-auto">
198+
{slide.code.split("\n").map((line, i) => (
199+
<div key={i}>
200+
{highlightCode(line)}
201+
</div>
202+
))}
203+
</pre>
204+
)}
205+
{slide.bullets && (
206+
<ul className="mt-10 max-w-2xl space-y-5 text-center font-serif text-xl leading-relaxed opacity-70">
207+
{slide.bullets.map((b) => (
208+
<li key={b}>{b}</li>
209+
))}
210+
</ul>
211+
)}
212+
{slide.footnote && (
213+
<p className="mt-12 max-w-xl text-center text-sm italic opacity-40">
214+
{slide.footnote}
215+
</p>
216+
)}
217+
</>
218+
);
219+
}
220+
221+
// ─── Presentation ───────────────────────────────────────────────────────────
222+
223+
export default function SlidesPage() {
224+
const [current, setCurrent] = useState(0);
225+
226+
const next = useCallback(() => {
227+
setCurrent((c) => Math.min(c + 1, slides.length - 1));
228+
}, []);
229+
230+
const prev = useCallback(() => {
231+
setCurrent((c) => Math.max(c - 1, 0));
232+
}, []);
233+
234+
useEffect(() => {
235+
function onKey(e: KeyboardEvent) {
236+
if (e.key === "ArrowRight" || e.key === " ") {
237+
e.preventDefault();
238+
next();
239+
}
240+
if (e.key === "ArrowLeft") {
241+
e.preventDefault();
242+
prev();
243+
}
244+
}
245+
window.addEventListener("keydown", onKey);
246+
return () => window.removeEventListener("keydown", onKey);
247+
}, [next, prev]);
248+
249+
const slide = slides[current];
250+
251+
return (
252+
<div
253+
className="flex h-screen w-screen select-none flex-col items-center justify-center px-12 cursor-pointer"
254+
onClick={next}
255+
>
256+
<SlideView slide={slide} />
257+
258+
{/* Slide indicator */}
259+
<div className="fixed bottom-8 flex gap-2">
260+
{slides.map((_, i) => (
261+
<div
262+
key={i}
263+
className={`h-1.5 w-1.5 rounded-full transition-opacity ${
264+
i === current ? "bg-foreground opacity-60" : "bg-foreground opacity-15"
265+
}`}
266+
/>
267+
))}
268+
</div>
269+
270+
{/* Slide number */}
271+
<div className="fixed bottom-8 right-8 font-mono text-xs opacity-30">
272+
{current + 1} / {slides.length}
273+
</div>
274+
</div>
275+
);
276+
}

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@
3030
".next/dev/types/**/*.ts",
3131
"**/*.mts"
3232
],
33-
"exclude": ["node_modules", "verity"]
33+
"exclude": ["node_modules", "verity", "trustlessinference"]
3434
}

0 commit comments

Comments
 (0)