Inspiration

People paste their bank statements into chatbots to ask one question: can I afford this? That hands a cloud company every merchant, amount and account they own.

We noticed something simple. Personal finance questions are just equations. "Can I buy this jacket?" "How long would my savings last if I quit?" "How much do I need to save per month?" Each one is a formula over a few numbers: savings, income, spending, a goal, a date.

A model doesn't need your numbers to write the equation. Only the solver needs them. So we split the job: the cloud writes the equation, your Mac solves it.

What it does

Blindfold is a finance agent you text over iMessage. It thinks blindfolded: the cloud sees symbols, your Mac sees the numbers.

  • Can I afford this? It checks a purchase against your goal, your pace and your real monthly net, then suggests options: a cheaper price that keeps you on pace, or skipping a few delivery dinners.
  • Goals and what-ifs. Ask "How long would I last if my income stops Jan 1?" and it shows the full breakdown.
  • Who paid? Rules decide whose charge it is on a shared card. Apple Pay on her card is hers. A work-to-home ride is yours. When it's unsure, it asks, and you reply 1, 2 or 3.
  • Nudges. "You've spent $450 on commute rides this month. Take the train a few days."
  • Memory you control. Text "remember…", "memories" or "forget 2". Everything is stored on your Mac.
  • A live ledger. A local dashboard shows income, spending and the exact bytes every cloud call sent.

How we built it

Every message runs through one pipeline.

  1. Symbolize. Plain code on the Mac swaps every merchant, amount, account, person and address for a symbol: MERCHANT_3, Q_AMT_1, PERSON_1.
  2. Classify. Laya, a small PII classifier running on the Mac, flags anything sensitive the rules missed.
  3. Understand and word. Luna, a small, fast model, reads the symbolized message and drafts the reply with placeholders only.
  4. Reason. Sol, the big model, gets a table of named variables with units and descriptions, never their values. It returns a plan of facts, equations and a reply template.
  5. Scan. A leak canary compares the exact outgoing bytes with every real value in the database. One match blocks the call.
  6. Solve. The Mac evaluates the equations on the real numbers, fills the placeholders and texts back.

For "if I quit Jan 1, how long would I last?", Sol writes:

$$\text{cash}_{\text{Jan 1}} = S + C + Q_1 + \bar{N}\cdot m$$

$$R = \frac{\text{cash}_{\text{Jan 1}}}{\bar{E}}$$

Here $S$ is savings, $C$ is checking, $Q_1$ is the pay still coming, $\bar{N}$ is average monthly net, $m$ is the months until the quit date and $\bar{E}$ is average monthly spending. Sol never learns what $S$ is. The Mac computes $R$.

Affordability is the same idea. With projected savings $S(D) = S_0 + \bar{N}\,\Delta t$ at the deadline $D$, goal $G$ and price $P$:

$$\text{verdict}(P)=\begin{cases}\text{yes} & S(D)-P \ge G\ \text{tight} & S(D)-P \ge 0.9\,G\ \text{hold off} & \text{otherwise}\end{cases}$$

The canary rule is strict:

$$\text{send} \iff \forall v \in V_{\text{db}}:\ \text{norm}(v) \not\subseteq \text{norm}(\text{bytes})$$

Stack: Bun, TypeScript, SQLite, iMessage on macOS, IMAP for receipts, and OpenAI for Luna and Sol. The math runs in a safe expression evaluator with unit checks and no eval.

Challenges we ran into

  • Pattern matching failed. Our first version used rules to decide which math to run. Real messages broke it every time: "come Jan 1st I wanna quit", "6k in checking", "end of October". We rebuilt the core so the model reasons and the Mac computes.
  • Models invent numbers. We banned digits, number words and $ in reply templates. Every number must come from a placeholder the Mac fills.
  • Our own canary caught us. It blocked our prompt because "capitals" normalized to a merchant word. It blocked max_output_tokens: 3000 because \$30.00 was a real charge. We made it skip numbers only at exact protocol fields.
  • Names leak. "Send my friend Priya the rent" went out in clear once. We added cue-word rules and a capitalized-name check, plus Laya as a backstop.
  • Latency. Two privacy layers and two models add up. We cut the prompt and the variable table, and fall back to local answers on timeout.
  • Real data is messy. Statements don't reconcile, Zelle memos hide names, and promo emails look like receipts.

Accomplishments that we're proud of

  • The cloud never sees a real value unless you approve that one value.
  • An adversarial red team ran 81 attacks against the exact request bytes. It found 34 leaks over six rounds. We fixed all 34, and every attack stays in the test suite.
  • 1,100+ tests.
  • Real data. It runs on real bank statements and hundreds of email receipts, with every statement reconciled.
  • Every call is auditable. Each one lands in a local ledger with its exact payload and canary counts.

What we learned

  • Privacy is a systems problem, not a model problem. Plain code you can read beats a model you have to trust.
  • Equations are the right interface. A model reasons fine over $S$, $\bar{N}$ and $G$ without their values.
  • Small models handle the talking. The big model handles the thinking.
  • Test the bytes, not the intent. "It loads" is not "the guards hold."

What's next for Blindfold

  • Run the same vault in a confidential VM with attestation, for people without a Mac.
  • Live bank connections through Plaid.
  • Push Laya and Luna fully on-device as Apple's models improve.
  • Shared households: split goals and shared cards, with each person's data private from the other.

Built With

Share this project:

Updates

Submission history