Fork Notice: This project is a fork of banana_split originally developed by Parity Technologies. Original work © 2019–2020 Parity Technologies. This fork © 2026 Evgeny Mezin. Licensed under GPLv3.
Banana Split uses Shamir's Secret Sharing to split secrets into QR-code shards. Any majority of shards can reconstruct the secret — fewer reveal nothing.
Any 3 of 5 will know everything; any 2 of 5 will know nothing.
| Platform | Format | Notes |
|---|---|---|
| Android | APK / AAB | Direct install or Google Play upload |
| Windows | ZIP | Extract and run (includes VC++ Runtime check) |
| Web | Single HTML file | Deploy to S3, any web server, or open locally |
All artifacts are available on the Releases page.
- Enter your secret (e.g., a seed phrase, private key, password).
- Choose how many shards to create and how many are required to reconstruct (default: majority).
- Use the auto-generated passphrase or enter your own custom passphrase (min 8 characters).
- Banana Split encrypts the secret with the passphrase, then splits the ciphertext into N QR codes using Shamir's scheme.
- Print or save the QR codes. Write the passphrase by hand on every sheet — this protects against printer interception.
- Scan a majority of QR code shards (e.g., 3 of 5) using camera or gallery import.
- Enter the passphrase.
- Your secret is restored.
A single paper backup is vulnerable: anyone who sees it can copy it without you knowing. Splitting it in half means losing one piece loses everything.
With Banana Split, you split into 5 pieces and distribute them. Losing 2 pieces is fine — any 3 can reconstruct. And 2 colluding holders learn nothing about your secret.
This repo contains two implementations sharing the same cryptographic protocol:
Vue 2 + TypeScript single-page app. Builds to a single self-contained HTML file with all JS/CSS inlined — no server, no dependencies at runtime.
Crypto pipeline: scrypt key derivation → NaCl secretbox (XSalsa20-Poly1305) encryption → Shamir split over GF(256).
Native app for Android and Windows (also builds for macOS/Linux). Pure Dart implementation of the same crypto pipeline using pinenacl and a custom Shamir port.
Additional features:
- Save shards as PNGs or PDF with full Unicode font support (Roboto + Noto Sans Georgian)
- Files tab for browsing, sharing, and deleting saved exports
- Language selector with 7 locales (EN, RU, TR, BE, KA, UK, PL) persisted across sessions
- Camera and gallery QR scanning with two-stage decode
- Custom Banana Split app icon
Shared features (both apps):
- Custom passphrase or auto-generated passphrase
- User-selectable quorum (how many shards required to reconstruct)
- Full shard format interoperability (v0, v1, v2)
Requires Node.js (see .nvmrc) and Yarn.
yarn install # Install dependencies
yarn serve # Dev server with hot reload
yarn build # Production build → dist/index.html
yarn lint # ESLint
yarn test:unit # Jest unit tests
yarn test:e2e # Playwright E2E tests (auto-starts dev server)Requires Flutter SDK (see FLUTTER_VERSION in CI workflow).
cd banana_split_flutter
flutter pub get # Install dependencies
flutter run # Run on connected device
flutter test # Run all tests
flutter test test/<file>_test.dart # Run single test file
flutter analyze # Static analysis| Workflow | Trigger | What it does |
|---|---|---|
| Web App CI | Push/PR to master | Lint, unit tests, E2E tests, CodeQL, Trivy scan |
| Flutter CI | Push/PR to master (Flutter paths) | Analyze, test, on-demand debug APK/Windows builds |
| Release | Tag v*.*.* or manual dispatch |
Builds all platforms (Android APK+AAB, Windows ZIP, Web HTML), creates GitHub Release with checksums |
The web app is deployed to https://nfcarchiver.com/banana/ by the
Deploy web app workflow (.github/workflows/deploy-webapp.yml). It is
manual only — it never runs on push or tag.
Actions → Deploy web app → Run workflow. Pick a ref, then optionally tick either input:
| Input | Effect |
|---|---|
dry_run |
Print the upload plan and stop. Uploads nothing. |
force_fail_verify |
Deploy for real, then force verification to fail, exercising the rollback path. Takes ~62 s at the verify step — that is the healthcheck's real backoff schedule. |
The ref must have a reachable tag: the workflow stamps the build with
git describe --long --tags and refuses to deploy if that fails, rather than
falling back to a short SHA that could collide with unrelated hex in the bundle.
The workflow builds and verifies the bundle in a job with no AWS access, then uploads, invalidates CloudFront, and checks that the live URL serves the exact build it produced. If that check fails it restores the previous version automatically and fails the run.
Repository → Settings → Secrets and variables → Actions
| Kind | Name | Value |
|---|---|---|
| Secret | AWS_DEPLOY_ROLE_ARN |
the deploy role ARN (secret, so the account ID is masked in logs) |
| Variable | AWS_REGION |
the bucket's region |
| Variable | S3_BUCKET |
nfcarchiver.com |
| Variable | S3_PREFIX |
banana/ — the workflow refuses to run for any other value, since the deploy role can also write to the sibling app's app/ prefix in the same bucket. Changing the deploy target requires editing the workflow, not just this variable. |
| Variable | CLOUDFRONT_DISTRIBUTION_ID |
EPIRQ7CFJKRDQ |
| Variable | SITE_BASE_URL |
https://nfcarchiver.com/banana/ |
Repository → Settings → Environments → New environment production
Required reviewers: none. Deployment branches: selected branches → master.
AWS. The OIDC provider and the deploy role already exist, shared with the
nfcarchiver repository. Both of the role's policies need updating — the exact
JSON is in
docs/superpowers/specs/2026-08-01-webapp-s3-deploy-design.md,
section AWS setup. In short: the trust policy's sub condition gains
repo:mezinster/banana_split:environment:production, and the permission policy
gains the banana/ prefix for objects and for ListBucket.
Two things live outside the role's policy and will produce a successful-looking deploy that serves a broken page. Check both — see the spec's pre-flight checks for the exact commands:
- Does CloudFront's Origin Access Control grant cover
banana/, or was it scoped toapp/*? If scoped, every visitor gets a 403. - Does
/banana/resolve to/banana/index.htmlat the edge? Depends on whether the origin is an S3 website endpoint or REST + OAC.
Run once with dry_run ticked before the first real deploy.
Re-run the workflow from the last good tag or commit. One click, and it is the same path automatic rollback uses.
To rehearse automatic rollback without an incident, dispatch once with
force_fail_verify ticked, after a successful deploy so there is a previous
version to restore. Nothing needs to be reset afterwards. Do not rehearse it
by repointing SITE_BASE_URL at another application — the workflow now asserts
that SITE_BASE_URL is an https URL ending in /banana/ and refuses otherwise,
because a forgotten reset would silently roll back every later deploy.
| Format | Encoding | Written by | Read by |
|---|---|---|---|
| v0 | hex nonce, hex data | legacy web app | both |
| v1 | base64 nonce, base64 data | current web app | both |
| v2 | base64 nonce, base64 data | Flutter app | both |
v1 and v2 use identical encoding — the version field is only a provenance marker indicating which app created the shard. All formats are fully interoperable: shards created in either app can be reconstructed in either app.