fix tags and add dedicated page

* fix fetch articles

* add dedicate page for tags

* remove project category filters

* fix labels and add metadata to tags page

* fix labels and add metadata

* fix tag labels
This commit is contained in:
Kalidou Diagne
2025-06-13 01:23:23 +08:00
committed by GitHub
parent fd0d52b6ec
commit 6538ca8fb8
13 changed files with 283 additions and 132 deletions

View File

@@ -63,7 +63,7 @@ export default function BlogArticle({ params }: any) {
const isNewsletter =
post?.title?.toLowerCase().includes("newsletter") ||
post?.tags?.includes("newsletter") ||
post?.tags?.some((tag) => tag.name.toLowerCase().includes("newsletter")) ||
post?.tldr?.toLowerCase()?.includes("newsletter")
return (
@@ -142,12 +142,12 @@ export default function BlogArticle({ params }: any) {
</span>
<div className="flex flex-wrap gap-2">
{post?.tags?.map((tag) => (
<Link key={tag} href={`/blog?tag=${tag}`}>
<Link key={tag.id} href={`/blog/tags/${tag.id}`}>
<Button
size="xs"
variant={imageAsCover ? "secondary" : "default"}
>
{tag}
{tag.name}
</Button>
</Link>
))}

View File

@@ -10,21 +10,22 @@ import {
} from "@tanstack/react-query"
import ArticlesList from "@/components/blog/ArticlesList"
import { Skeleton } from "@/components/skeleton"
import { getArticles } from "@/lib/blog"
export const dynamic = "force-dynamic"
export const revalidate = 60 // Revalidate every 60 seconds
export const metadata: Metadata = {
title: "Blog",
description: "",
title: LABELS.BLOG_PAGE.TITLE,
description: LABELS.BLOG_PAGE.SUBTITLE,
}
interface BlogPageProps {
searchParams?: { [key: string]: string | string[] | undefined }
}
const LoadingSkeleton = () => {
export const BlogLoadingSkeleton = () => {
return (
<>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 lg:gap-6 items-stretch">
@@ -67,19 +68,8 @@ const BlogPage = async ({ searchParams }: BlogPageProps) => {
queryKey: ["articles", tag],
queryFn: async () => {
try {
const params = new URLSearchParams()
if (tag) params.append("tag", tag)
const response = await fetch(`/api/articles?${params.toString()}`, {
next: { revalidate },
})
if (!response.ok) {
throw new Error(`Failed to fetch articles: ${response.status}`)
}
const data = await response.json()
return data.articles || []
const articles = getArticles({ tag })
return articles
} catch (error) {
console.error("Error fetching articles:", error)
return []
@@ -104,9 +94,9 @@ const BlogPage = async ({ searchParams }: BlogPageProps) => {
</div>
<AppContent className="flex flex-col gap-10 lg:gap-16 pb-10 lg:py-10 lg:max-w-[978px]">
<Suspense fallback={<LoadingSkeleton />}>
<Suspense fallback={<BlogLoadingSkeleton />}>
<HydrationBoundary state={dehydrate(queryClient)}>
<ArticlesList tag={tag} fallback={<LoadingSkeleton />} />
<ArticlesList tag={tag} />
</HydrationBoundary>
</Suspense>
</AppContent>

View File

@@ -0,0 +1,101 @@
import { LABELS } from "@/app/labels"
import { ArticleListCard } from "@/components/blog/article-list-card"
import { AppContent } from "@/components/ui/app-content"
import { Label } from "@/components/ui/label"
import { getArticles, Article, getArticleTags } from "@/lib/blog"
import { interpolate } from "@/lib/utils"
import {
HydrationBoundary,
QueryClient,
dehydrate,
} from "@tanstack/react-query"
import { Suspense } from "react"
import { BlogLoadingSkeleton } from "../../page"
import Link from "next/link"
import { Icons } from "@/components/icons"
import { Metadata } from "next"
interface BlogTagPageProps {
params: { tag: string }
}
export async function generateMetadata({
params,
}: BlogTagPageProps): Promise<Metadata> {
const { tag } = params
return {
title: interpolate(LABELS.BLOG_TAGS_PAGE.TAG_TITLE, { tag }),
description: LABELS.BLOG_TAGS_PAGE.SUBTITLE,
}
}
export const generateStaticParams = async () => {
const tags = await getArticleTags()
return tags.map((tag) => ({ tag }))
}
const BlogTagPage = async ({ params }: BlogTagPageProps) => {
const { tag } = params
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ["get-articles-by-tag", tag],
queryFn: async () => {
try {
const articles = getArticles({ tag })
return articles
} catch (error) {
console.error("Error fetching articles:", error)
return []
}
},
})
const articles = queryClient.getQueryData([
"get-articles-by-tag",
tag,
]) as Article[]
return (
<div className="flex flex-col">
<div className="w-full bg-page-header-gradient">
<AppContent className="flex flex-col gap-4 py-10 w-full">
<Link
className="flex items-center gap-2 text-tuatara-950/80 hover:text-tuatara-950 mr-auto"
href="/blog"
>
<Icons.arrowLeft />
<span className="font-sans text-base">
{LABELS.BLOG_TAGS_PAGE.BACK_TO_ARTICLES}
</span>
</Link>
<Label.PageTitle
label={interpolate(LABELS.BLOG_TAGS_PAGE.TAG_TITLE, {
tag: tag,
})}
/>
</AppContent>
</div>
<AppContent className="flex flex-col gap-10 lg:gap-16 pb-10 lg:py-10 lg:max-w-[978px]">
<Suspense fallback={<BlogLoadingSkeleton />}>
<HydrationBoundary state={dehydrate(queryClient)}>
<div className="flex flex-col gap-5 lg:gap-14">
{articles?.map((article: Article) => (
<ArticleListCard key={article.id} article={article} />
))}
{articles?.length === 0 && (
<p className="text-center text-gray-500 py-10">
No articles found for tag &quot;{tag}&quot;
</p>
)}
</div>
</HydrationBoundary>
</Suspense>
</AppContent>
</div>
)
}
export default BlogTagPage

View File

@@ -0,0 +1,70 @@
import { LABELS } from "@/app/labels"
import { Icons } from "@/components/icons"
import { AppContent } from "@/components/ui/app-content"
import { Label } from "@/components/ui/label"
import { getArticleTags } from "@/lib/blog"
import { HydrationBoundary, QueryClient } from "@tanstack/react-query"
import Link from "next/link"
import { Suspense } from "react"
import { Metadata } from "next"
export const metadata: Metadata = {
title: LABELS.BLOG_TAGS_PAGE.TITLE,
description: LABELS.BLOG_TAGS_PAGE.SUBTITLE,
}
const BlogTagsPage = async () => {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ["get-articles-tags"],
queryFn: async () => {
try {
const articles = getArticleTags()
return articles
} catch (error) {
console.error("Error fetching articles:", error)
return []
}
},
})
const tags = queryClient.getQueryData(["get-articles-tags"]) as string[]
return (
<div className="flex flex-col pb-10v">
<div className="w-full bg-page-header-gradient">
<AppContent className="flex flex-col gap-6 py-10 w-full">
<Link
className="flex items-center gap-2 text-tuatara-950/80 hover:text-tuatara-950 mr-auto"
href="/blog"
>
<Icons.arrowLeft />
<span className="font-sans text-base">
{LABELS.BLOG_TAGS_PAGE.BACK_TO_ARTICLES}
</span>
</Link>
<Label.PageTitle label={LABELS.BLOG_TAGS_PAGE.TITLE} />
</AppContent>
</div>
<AppContent className="grid grid-cols-3 gap-2 lg:gap-4 lg:py-10 lg:max-w-[1200px]">
<Suspense fallback={null}>
<HydrationBoundary>
{tags?.map((tag) => (
<Link
href={`/blog/tags/${tag}`}
key={tag}
className="text-neutral-950 border-b-[2px] border-b-anakiwa-500 text-sm font-medium w-fit hover:text-anakiwa-500 duration-200"
>
{tag}
</Link>
))}
</HydrationBoundary>
</Suspense>
</AppContent>
</div>
)
}
export default BlogTagsPage

View File

@@ -13,7 +13,7 @@ export const LABELS = {
{
TITLE: "01. Cryptography for people",
DESCRIPTION: [
'Cryptography is everywhere: every time you connect to a secure site, log in with a password or unlock your phone, you\'re seeing cryptography in action. With "programmable" cryptography (like zero knowledge proofs, multi-party computation or homomorphic encryption) we can make verifiable claims about secret information without revealing the information itself.',
"Cryptography is everywhere: every time you connect to a secure site, log in with a password or unlock your phone, you're seeing cryptography in action. With 'programmable' cryptography (like zero knowledge proofs, multi-party computation or homomorphic encryption) we can make verifiable claims about secret information without revealing the information itself.",
" This can be applied to identity management, collusion resistance, anonymous communication and so much more. We're building a library of dev tools, research papers, and prototypes that are open source and free for everyone to use. We hope our resources inspire people to innovate the technologies that their communities need.",
],
},
@@ -48,6 +48,13 @@ export const LABELS = {
SEE_MORE: "See more",
READ_MORE: "Read more",
},
BLOG_TAGS_PAGE: {
TITLE: "Blog tags",
TAG_TITLE: "Blog posts tagged with: '{{tag}}'",
SUBTITLE:
"Read our latest articles and stay updated on the latest news in the world of cryptography.",
BACK_TO_ARTICLES: "Back to articles",
},
COMMON: {
SITE_TITLE: "Privacy & Scaling Explorations",
SITE_DESCRIPTION:

View File

@@ -6,8 +6,6 @@ tldr: "Quantum computers will shatter todays elliptic-curve schemes (and the
date: "2025-05-20"
---
# Are elliptic curves going to survive the quantum apocalypse?
Elliptic curves offer unmatched cryptographic power, especially with [pairings](https://en.wikipedia.org/wiki/Pairing-based_cryptography) that enable advanced protocols like identity-based encryption and short signatures. But quantum computers threaten this entire ecosystem by breaking the discrete logarithm problem—on which these schemes depend.
However, elliptic curves might retain their place in cryptography after all—through entirely different assumptions.
@@ -16,7 +14,6 @@ However, elliptic curves might retain their place in cryptography after all—th
Elliptic curves are wonderful—no other area of mathematics combines cryptographic versatility with computational efficiency so effectively. Just think of pairings—you can do all kinds of things with pairings. You can construct identity-based encryption, build short signatures, enable non-interactive zero-knowledge proofs, or design functional encryption schemes. It's a beautiful intersection of theory and practice, where abstract concepts translate directly into cutting-edge protocols.
The fact that pairings can be defined on elliptic curves—which were already a highly attractive setting for cryptography even before pairings came into use—is, as one of the pioneers of pairing-based cryptography, Ben Lynn, [put it](https://static1.squarespace.com/static/5fdbb09f31d71c1227082339/t/5ff394720493bd28278889c6/1609798774687/PairingsForBeginners.pdf), a “happy coincidence”.
Pairings are special bilinear maps defined on groups derived from elliptic curves or higher-dimensional abelian varieties. A typical pairing has the form:
@@ -26,6 +23,7 @@ e: G_1 \times G_2 \to G_T
$$
where $G_1$, $G_2$, and $G_T$ are groups of the same prime order $p$. Theres one crucial property of pairings, called bilinearity:
$$
e(aP, bQ) = e(P, Q)^{ab}.
$$
@@ -33,6 +31,7 @@ $$
Pairings allow one to perform certain operations on hidden exponents without solving the discrete logarithm.
Suppose you have:
$$
A = aP \in G_1
$$
@@ -42,16 +41,16 @@ B = bQ \in G_2
$$
You cannot recover $a$ or $b$ ([discrete logatihm](https://en.wikipedia.org/wiki/Discrete_logarithm) is hard), but using a bilinear pairing, you can compute:
$$
e(A, B) = e(aP, bQ) = e(P, Q)^{ab}
$$
This means that although $a$ and $b$ are hidden, you can compute something that depends on their product—without knowing either value individually.
## Will quantum computers kill the magic of elliptic curves?
So, is all this magic set to *die* with the advent of quantum computers?
So, is all this magic set to _die_ with the advent of quantum computers?
Given two points $P$ and $R$ on an elliptic curve, a quantum computer can efficiently compute an integer $\ell$ such that $\ell P = R$, if such an $\ell$ exists. This task—known as the discrete logarithm problem—is believed to be hard for classical computers, and the security of virtually all elliptic curve cryptography depends on that hardness.
@@ -61,7 +60,7 @@ Will pairings be lost as well?
Unfortunately, yes. Pairing-based cryptography relies on the discrete logarithm problem being hard (in all three groups: $G_1$, $G_2$, and $G_T$).
So yes, my friend. *You should be sad that elliptic curve cryptography is going to be buried.*
So yes, my friend. _You should be sad that elliptic curve cryptography is going to be buried._
## Unless...
@@ -71,7 +70,6 @@ Isogeny-based cryptography is a post-quantum alternative that still relies on el
![genus-1](https://hackmd.io/_uploads/S1Chdpybee.png)
This means that, given two elliptic curves, computing an explicit isogeny between them is believed to be computationally hard—even for a quantum computer.
Yeah, you might mention [Castryck-Decru attack](https://eprint.iacr.org/2022/975), but as I wrote [last time](https://pse.dev/en/blog/code-optimizations-in-the-landscape-of-post-quantum-cryptography), this applies only to certain special isogeny-based schemes.
@@ -99,10 +97,13 @@ To evaluate the image of a point under the isogeny—that is, to determine where
The theta model significantly accelerates isogeny-based cryptography in higher dimensions. But whats even more intriguing is the possibility that abelian varieties—and the theta machinery—could one day enable [trilinear maps](https://hal.science/tel-03498268/document). And you know, trilinear maps are bilinear maps on steroids.
A trilinear map is a function
$$
e : G_1 \times G_2 \times G_3 \to G_T
$$
where all groups have the same prime order $p$, satisfying the following property:
$$
e(aP, bQ, cR) = e(P, Q, R)^{abc}
$$

View File

@@ -4,16 +4,22 @@ title: "Efficient Client-Side Proving for zkID"
image: "/articles/efficient-client-side-proving-for-zkid/cover.webp" # Image used as cover, Keep in mind the image size, where possible use .webp format, possibly images less then 200/300kb
tldr: "Binius and Ligero emerge as the top candidates for mobile SHA-256 ZK-proving under transparent, post-quantum constraints." #Short summary
date: "2025-06-11" # Publication date in ISO format
tags: ["client-side proving", "zkp", "zero-knowledge proofs", "zkid", "post-quantum", "benchmarks"] # (Optional) Add relevant tags as an array of strings to categorize the article
tags: [
"client-side proving",
"zkp",
"zero-knowledge proofs",
"zkid",
"post-quantum",
"benchmarks",
] # (Optional) Add relevant tags as an array of strings to categorize the article
projects: ["client-side-proving", "zk-id"]
---
# Efficient Client-Side Proving for zkID
**TL;DR: Binius and Ligero emerge as the top candidates for mobile SHA-256 ZK-proving under transparent, post-quantum constraints.**
This work is helpful for wallet developers, standards authors, and ZK library maintainers in the digital ID space who require a post-quantum-secure proof scheme with a transparent setup that fits within tight mobile CPU, RAM, and bandwidth budgets. We compare seven schemes on a SHA-256 circuit with 2 kB input, measuring prover time, RAM usage, proof size, and preprocessing overhead. Our results show that:
* **Binius** offers ≈5 s proving, sub-50 MB RAM, and small proofs.
* **Ligero** is a runner-up with ≈30-95 s proving and a modest RAM/VRAM footprint.
- **Binius** offers ≈5 s proving, sub-50 MB RAM, and small proofs.
- **Ligero** is a runner-up with ≈30-95 s proving and a modest RAM/VRAM footprint.
No single scheme is perfect: Binius excels at bitwise operations, while Ligero achieves performance by utilizing WebGPU. Developers should choose based on their specific mobile-context trade-offs. Our future work will build upon these benchmarks, so feel free to reach out to the Client-Side Proving team if you have a particular use case or would like to collaborate.
@@ -30,10 +36,9 @@ The summary of our requirements is given in the table below.
| Requirement | Rationale |
| --------------------------------------------------- | ---------------------------------------------------------- |
| No trusted setup / long structured reference string | Fewer security assumptions and less mobile bandwidth usage |
| Small proof size | Mobile device limitations |
| Small proof size | Mobile device limitations |
| Fast proving | Mobile device limitations |
| Post-quantum soundness | Future-proofing |
| Post-quantum soundness | Future-proofing |
## Baseline Mobile Hardware
@@ -41,15 +46,14 @@ Before choosing a proof scheme, it is essential to understand the limitations of
Comprehensive market reports from research firms such as IDC and Counterpoint are prohibitively expensive (e.g., [$7500](https://my.idc.com/getdoc.jsp?containerId=US52082024)), making them inaccessible for this analysis. Therefore, we analyzed the data published by various mobile SDK developers in different industries (analytics, gaming, ads). Our findings were the following:
* Android is dominating the market, while the iPhone's share is around 27%;
* An average iPhone has 6-core CPU @ 3.33 GHz and 5.7 GB RAM;
* An average Android phone has 7-core CPU @ 2.16 GHz and 3.82 GB RAM.
- Android is dominating the market, while the iPhone's share is around 27%;
- An average iPhone has 6-core CPU @ 3.33 GHz and 5.7 GB RAM;
- An average Android phone has 7-core CPU @ 2.16 GHz and 3.82 GB RAM.
You can find the data and learn more about our methodology here: https://hackmd.io/@clientsideproving/AvgMobileHardware.
An interesting observation arising from our results is that, in case the developer needs to compile the prover to WebAssembly (WASM), WASM's 4 GB RAM limit does not impose a significant additional constraint. Given that an average Android device has approximately 3.82 GB of total RAM, fully utilizing the 4 GB limit is hard in practice anyway.
## Candidate ZKP Schemes
The zero-knowledge proof schemes listed in the table below meet all the requirements we described earlier, including performance, setup, and post-quantum security.
@@ -73,48 +77,51 @@ We initially ran benchmarks on an Apple M2 Air laptop (8-core M2 CPU, 24 GB RAM)
Note that not all available implementations we tested include the zero-knowledge property by default. Adding the zero-knowledge may result in additional prover overhead, but it is generally lightweight, so the benchmarks remain representative.
### Laptop Benchmarks
### Laptop Benchmarks
We ran the benchmarks on an Apple M2 Air laptop (8-core M2 CPU, 24 GB RAM). The Binius circuit performs 33 SHA-256 compressions instead of full hashing; therefore, an actual full implementation would incur additional overhead. We benchmarked the Polyhedra Expander circuit with a 1 kB input due to a prover bug that prevented it from running with a 2 kB input.
| Circuit (GitHub link) | Proving Time | Verification Time | Proof Size | Preprocessing Size | Preprocessing RAM | Prover RAM | Is ZK? |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -------------------- | ---------- | -------------------------------------------------- | ----------------- | --------------------------- | --- |
| [Binius (no-lookup)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/binius) | 1.8545 s | 244.48 ms | 475.6 KB | 321.8 KB | ~10.44 MB | ~26.94 MB | No |
| [Binius (lookup)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/binius) | 11.025 s | 572.73 ms | 1.8 MB | 716.86 KB | ~5.02 MB | ~66.14 MB | No |
| [Plonky2 (no-lookup)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/plonky2) | 20.138 s | 5.3135 ms | 175.6 KB | 2.28 GB (prover-only data) + 1.06 KB (common data) | ~2.74 GB | ~2.40 GB | Yes |
| [Plonky3 (SP1 w/precompile)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/plonky3-sp1) | 12.596 s | 184.11 ms | 1.72 MB | 156.34 MB (proving key) + 90.76 KB (ELF) | ~1 GB | ~5 GB | No |
| [Plonky3 (powdr, no precompile)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/plonky3-powdr) | 20.741 s | 256.11 ms | 1.93 MB | 3.1 GB (proving key) + 321 MB (constants) | ~3.87 GB | ~0.32 GB | No |
| [STWO (Cairo)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/stwo) | 21.1 s | N/A (verifier error) | 39.5 MB | 12.6 MB (trace) + 3.4 Mb (memory) | ~600 MB | ~10GB | No |
| [Ligero (Ligetron, *uses WebGPU*)](https://platform.ligetron.com/marketplace/project?id=78180426-2a09-4c36-ac68-52f1ab4ffbe6&version=1.0) | 12.06 s | 9.16 s | 10.29 MB | 33KB (prover data) | N/A | ~500 MB VRAM + ~30 MB RAM | Yes |
| [Polyhedra Expander (Orion + GF2), 1kB input](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/polyhedra-expander) | 70 s | 26 s | 30.75 MB | 6 GB (circuit) | N/A | 15.55 GB | No |
| Circuit (GitHub link) | Proving Time | Verification Time | Proof Size | Preprocessing Size | Preprocessing RAM | Prover RAM | Is ZK? |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -------------------- | ---------- | -------------------------------------------------- | ----------------- | ------------------------- | ------ |
| [Binius (no-lookup)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/binius) | 1.8545 s | 244.48 ms | 475.6 KB | 321.8 KB | ~10.44 MB | ~26.94 MB | No |
| [Binius (lookup)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/binius) | 11.025 s | 572.73 ms | 1.8 MB | 716.86 KB | ~5.02 MB | ~66.14 MB | No |
| [Plonky2 (no-lookup)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/plonky2) | 20.138 s | 5.3135 ms | 175.6 KB | 2.28 GB (prover-only data) + 1.06 KB (common data) | ~2.74 GB | ~2.40 GB | Yes |
| [Plonky3 (SP1 w/precompile)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/plonky3-sp1) | 12.596 s | 184.11 ms | 1.72 MB | 156.34 MB (proving key) + 90.76 KB (ELF) | ~1 GB | ~5 GB | No |
| [Plonky3 (powdr, no precompile)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/plonky3-powdr) | 20.741 s | 256.11 ms | 1.93 MB | 3.1 GB (proving key) + 321 MB (constants) | ~3.87 GB | ~0.32 GB | No |
| [STWO (Cairo)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/stwo) | 21.1 s | N/A (verifier error) | 39.5 MB | 12.6 MB (trace) + 3.4 Mb (memory) | ~600 MB | ~10GB | No |
| [Ligero (Ligetron, _uses WebGPU_)](https://platform.ligetron.com/marketplace/project?id=78180426-2a09-4c36-ac68-52f1ab4ffbe6&version=1.0) | 12.06 s | 9.16 s | 10.29 MB | 33KB (prover data) | N/A | ~500 MB VRAM + ~30 MB RAM | Yes |
| [Polyhedra Expander (Orion + GF2), 1kB input](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/polyhedra-expander) | 70 s | 26 s | 30.75 MB | 6 GB (circuit) | N/A | 15.55 GB | No |
### Mobile Benchmarks
We used the following devices for mobile benchmarks:
* iPhone 13 Pro: Hexa-core CPU (2x3.23 GHz Avalanche + 4x1.82 GHz Blizzard), 5-core GPU, 6 GB RAM
* Pixel 6: Octa-core CPU (2x2.80 GHz Cortex-X1 & 2x2.25 GHz Cortex-A76 & 4x1.80 GHz Cortex-A55), Mali-G78 MP20 GPU, 8 GB RAM.
- iPhone 13 Pro: Hexa-core CPU (2x3.23 GHz Avalanche + 4x1.82 GHz Blizzard), 5-core GPU, 6 GB RAM
- Pixel 6: Octa-core CPU (2x2.80 GHz Cortex-X1 & 2x2.25 GHz Cortex-A76 & 4x1.80 GHz Cortex-A55), Mali-G78 MP20 GPU, 8 GB RAM.
The results are in the table below.
| Circuit | Platform | Proving Time | Peak RAM |
| Circuit | Platform | Proving Time | Peak RAM |
| ------- | ------------- | ----------------------- | -------- |
| [Binius (no-lookup)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/mobile/binius/android) | Pixel 6 | 5.1023 s | 45 MB |
| [Binius (no-lookup)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/mobile/binius/ios) | iPhone 13 Pro | 5.0124 s | 22 MB |
| [Ligero](https://platform.ligetron.com/marketplace/project?id=78180426-2a09-4c36-ac68-52f1ab4ffbe6&version=1.0) | Pixel 6 | 93.59 s | N/A |
| [Ligero](https://platform.ligetron.com/marketplace/project?id=78180426-2a09-4c36-ac68-52f1ab4ffbe6&version=1.0) | iPhone 13 Pro | 29.77 s | N/A |
| [Plonky2](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/mobile/plonky2/android) | Pixel 6 | Crashed (out of memory) | - |
| [Plonky2](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/mobile/plonky2/ios) | iPhone 13 Pro | Crashed (out of memory) | - |
| [Binius (no-lookup)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/mobile/binius/android) | Pixel 6 | 5.1023 s | 45 MB |
| [Binius (no-lookup)](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/mobile/binius/ios) | iPhone 13 Pro | 5.0124 s | 22 MB |
| [Ligero](https://platform.ligetron.com/marketplace/project?id=78180426-2a09-4c36-ac68-52f1ab4ffbe6&version=1.0) | Pixel 6 | 93.59 s | N/A |
| [Ligero](https://platform.ligetron.com/marketplace/project?id=78180426-2a09-4c36-ac68-52f1ab4ffbe6&version=1.0) | iPhone 13 Pro | 29.77 s | N/A |
| [Plonky2](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/mobile/plonky2/android) | Pixel 6 | Crashed (out of memory) | - |
| [Plonky2](https://github.com/privacy-scaling-explorations/zkid-benchmarks/tree/main/mobile/plonky2/ios) | iPhone 13 Pro | Crashed (out of memory) | - |
## Conclusion
Our results reveal that no single scheme perfectly balances mobile resource constraints and proof efficiency. Here is the summary of our findings:
* Binius stands out for SHA-256 circuits thanks to its use of towers of binary fields. Other schemes struggle with bitwise operations, such as XOR and shifts, when working over larger fields.
* Binius no-lookup circuit outperforms its lookup variant because the underlying proof scheme is already optimized for binary operations; adding lookups only incurs extra overhead.
* Ligero exhibits significantly slower proving times on mobile, particularly on Android, despite leveraging WebGPU acceleration and demonstrating good performance on laptops.
* Plonky2 experienced out-of-memory failures, making it unsuitable for the SD-JWT hash signature proving scenario on mobile devices.
* STARK-based solutions (Plonky2, SP1 and powdr) demonstrate expected behavior by demanding multi-gigabyte memory, which places them out of reach for most phones.
* Polyhedra Expander circuit underperforms because it doesn't leverage the layered GKR approach; its long proving time and massive RAM usage reflect a missed optimization opportunity.
- Binius stands out for SHA-256 circuits thanks to its use of towers of binary fields. Other schemes struggle with bitwise operations, such as XOR and shifts, when working over larger fields.
- Binius no-lookup circuit outperforms its lookup variant because the underlying proof scheme is already optimized for binary operations; adding lookups only incurs extra overhead.
- Ligero exhibits significantly slower proving times on mobile, particularly on Android, despite leveraging WebGPU acceleration and demonstrating good performance on laptops.
- Plonky2 experienced out-of-memory failures, making it unsuitable for the SD-JWT hash signature proving scenario on mobile devices.
- STARK-based solutions (Plonky2, SP1 and powdr) demonstrate expected behavior by demanding multi-gigabyte memory, which places them out of reach for most phones.
- Polyhedra Expander circuit underperforms because it doesn't leverage the layered GKR approach; its long proving time and massive RAM usage reflect a missed optimization opportunity.
Overall, the strongest candidates for SHA-256 proving on mobile are Binius and Ligero, yet neither is universally optimal. Developers should select the scheme that best matches their constraints by weighing all the trade-offs.
[^1]: https://github.com/eu-digital-identity-wallet
[^2]: https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-19.html#section-4.1.1
[^2]: https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-19.html#section-4.1.1

View File

@@ -10,8 +10,6 @@ tags:
projects: ["machina-io"]
---
# Hello, World: the first signs of practical iO
04.28.2025 | [Sora Suegami](https://x.com/SoraSue77), [Enrico Bottazzi](https://x.com/backaes), [Pia Park](https://x.com/0xpiapark)
Today, were excited to announce that **we have successfully implemented [Diamond iO](https://eprint.iacr.org/2025/236)**, a straightforward construction of indistinguishability obfuscation (iO) that we published in February, and **completed its end-to-end workflow**. You can explore the codebase [here](https://github.com/MachinaIO/diamond-io).

View File

@@ -159,12 +159,10 @@ async function fetchArticles(tag?: string) {
interface ArticlesListProps {
tag?: string
fallback?: React.ReactNode
}
const ArticlesList: React.FC<ArticlesListProps> = ({
tag,
fallback = null,
}: ArticlesListProps) => {
const {
data: articles = [],
@@ -176,7 +174,7 @@ const ArticlesList: React.FC<ArticlesListProps> = ({
})
if (isLoading || articles.length === 0) {
return <>{fallback}</>
return null
}
if (isError) {

View File

@@ -55,6 +55,11 @@ export const ArticleListCard = ({
<div className="hidden lg:block">
<Markdown
components={{
a: ({ children }) => (
<span className="font-sans text-sm text-tuatara-600 group-hover:text-tuatara-950 duration-200">
{children}
</span>
),
h1: ({ children }) => (
<h1 className="font-sans text-sm text-tuatara-600 group-hover:text-tuatara-950 duration-200">
{children}

View File

@@ -81,15 +81,8 @@ export default function ProjectFiltersBar() {
const [searchQuery, setSearchQuery] = useState("")
const [filterCount, setFilterCount] = useState(0)
const {
filters,
toggleFilter,
queryString,
activeFilters,
onFilterProject,
currentCategory,
setCurrentCategory,
} = useProjectFiltersState((state) => state)
const { filters, toggleFilter, queryString, activeFilters, onFilterProject } =
useProjectFiltersState((state) => state)
useEffect(() => {
if (!queryString) return
@@ -256,38 +249,6 @@ export default function ProjectFiltersBar() {
</div>
</Modal>
<div className="flex flex-col gap-4">
<nav className="container px-4 mx-auto">
<ul className="flex space-x-6">
<div
className={cn(
"relative block px-2 py-1 text-sm font-medium uppercase transition-colors cursor-pointer hover:text-primary",
currentCategory == null
? "text-sky-400 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:bg-sky-400"
: ""
)}
onClick={() => setCurrentCategory(null)}
>
All
</div>
{ProjectCategories.map((key) => {
if (key === ProjectCategory.RESEARCH) return null // Research category has now it's own page
return (
<div
key={key}
className={cn(
"relative block px-2 py-1 text-sm font-medium uppercase transition-colors cursor-pointer hover:text-primary",
currentCategory === key
? "text-sky-400 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:bg-sky-400"
: ""
)}
onClick={() => setCurrentCategory(key as ProjectCategory)}
>
{key}
</div>
)
})}
</ul>
</nav>
<div className="flex flex-col gap-6">
<div className="grid items-center justify-between grid-cols-1 gap-3 md:grid-cols-5 md:gap-12">
<div className="col-span-1 grid grid-cols-[1fr_auto] gap-2 md:col-span-3 md:gap-3">

View File

@@ -15,7 +15,7 @@ export interface Article {
publicKey?: string
hash?: string
canonical?: string
tags?: string[]
tags?: { id: string; name: string }[]
projects?: string[]
}
@@ -71,7 +71,13 @@ export function getArticles(options?: {
return {
id,
...matterResult.data,
tags: tags,
tags: tags.map((tag) => ({
id: tag
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^a-z0-9-]/g, ""),
name: tag,
})),
content: matterResult.content,
}
} catch (error) {
@@ -92,7 +98,7 @@ export function getArticles(options?: {
// Filter by tag if provided
if (tag) {
filteredArticles = filteredArticles.filter((article) =>
article.tags?.includes(tag)
article.tags?.some((t) => t.id === tag)
)
}
@@ -117,6 +123,28 @@ export function getArticles(options?: {
.filter((article) => article.id !== "_article-template")
}
export const getArticleTags = () => {
const articles = getArticles()
const allTags =
articles
.map((article) => article.tags?.map((t) => t.name))
.flat()
.filter(Boolean) ?? []
return Array.from(new Set(allTags)) as string[]
}
export const getArticleTagsWithIds = () => {
const tags = getArticleTags()
return tags.map((tag) => ({
id: tag
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^a-z0-9-]/g, ""),
name: tag,
}))
}
export function getArticleById(slug?: string) {
// Note: This might need adjustment if you expect getArticleById to also have tags
// Currently relies on the base getArticles() which fetches all tags

View File

@@ -51,7 +51,6 @@ interface ProjectStateProps {
activeFilters: Partial<FiltersProps>
queryString: string
searchQuery: string
currentCategory: ProjectCategory | null
}
interface SearchMatchByParamsProps {
@@ -72,7 +71,6 @@ interface ProjectActionsProps {
onFilterProject: (searchPattern: string) => void
onSelectTheme: (theme: string, searchPattern?: string) => void
sortProjectBy: (sortBy: ProjectSortBy) => void
setCurrentCategory: (section: ProjectCategory | null) => void
}
const createURLQueryString = (params: Partial<FiltersProps>): string => {
@@ -236,7 +234,6 @@ const sortProjectByFn = ({
export const useProjectFiltersState = create<
ProjectStateProps & ProjectActionsProps
>()((set) => ({
currentCategory: null,
sortBy: DEFAULT_PROJECT_SORT_BY,
projects: sortProjectByFn({
projects,
@@ -351,16 +348,4 @@ export const useProjectFiltersState = create<
}
})
},
setCurrentCategory(category: ProjectCategory | null) {
set((state: any) => {
return {
...state,
projects: projects.filter((project) => {
if (category == null) return true // return all projects
return project?.category === category
}),
currentCategory: category,
}
})
},
}))