A third-party Taiwan citizen digital identity wallet for iOS. Holds the national ID, government credentials from the official TWDIW wallet ecosystem, MyData files, and official electronic documents. Presents and verifies them online, offline over Bluetooth, and with zero-knowledge proofs.
中文說明在下方 · Field reports · Getting started · Status
Taiwan is rolling out a national digital credential wallet (數位憑證皮夾, TWDIW), a mobile citizen certificate (行動自然人憑證, TW FidO), a personal data portability platform (MyData), and electronic official-document delivery. Each is designed as a service the government runs. Bonds asks a different question:
If a citizen wrote the wallet instead of the state, what would the ideal one look like, and where does the public infrastructure fall short?
The answer is built, not argued. Bonds speaks the official protocols end to end against the real production and demo endpoints, on real devices, and publishes what it finds as a report series. Every gap, caveat, and policy recommendation in those reports traces back to code in this repository.
The project name 有備而來 means "to arrive prepared". The short English name is
Bonds; the working codename backupTW survives in the Xcode target and bundle
identifier tw.bonds.backupTW.
| Capability | Standards and counterparties | Where |
|---|---|---|
| Collect government credentials from the official TWDIW ecosystem (driver licence, telecom card, partner cards) | OpenID4VCI pre-authorized code flow, SD-JWT credentials, two trust gates before the issuer is contacted: the 數位發展部 trust list and an on-chain anchor on Arbitrum | backupTW/TWDIW/ |
| Present credentials with selective disclosure to any TWDIW-compatible verifier | OpenID4VP by reference, request object as JWS verified against the verifier's did:key, holder picks claims, signed vp_token |
backupTW/TWDIW/OID4VP*, Presentation/SelectiveDisclosure.swift |
| Convenience-store pickup barcode from a telecom credential | Official offline verifier list, short-lived till-readable barcode; used for a real 7-ELEVEN pickup | TWDIW/ConvenienceStorePickup.swift |
| Self-issued national ID credential from a MyData download | The ID document is parsed into a vc+moica credential signed by a per-card key; the plaintext is destroyed |
Model/, Storage/MyDataScratch.swift |
| MyData vault for eight other personal documents | Originals kept in Application Support with Data Protection, excluded from backup, SHA-256 fingerprinted, previewable, individually deletable | Storage/MyDataVaultArchive.swift |
| Official-document inbox (電子公文個人接收站) | 檔案管理局 EN/DI/ESW XML triple, SHA-256 integrity, public G2B2C address book; physical 自然人憑證 card consent signing via PKCS#11 | Model/OfficialDocument*, tools/ |
| Zero-knowledge age proof ("over 18" without revealing the birth date) | OpenAC jwt_2k + show circuits from ethereum/zkID (Spartan2/Hyrax, P-256) over an ES256 SD-JWT, verifier-first nonce, linked Prepare/Show proofs; verifiable on-device, on a second device over Bluetooth, or on the web |
Native/OpenACAge/, backupTW/Model/AgePredicate* |
| Zero-knowledge proof of certificate holding over the holder's 自然人憑證 X.509 chain | Groth16 cert_chain_rs4096 + user_sig_rs2048 circuits, sparse-Merkle-tree revocation snapshot, SHA-256-pinned assets, every proof carries explicit caveats |
backupTW/ZK/ |
| Offline verification between two Bonds devices | W3C VC/VP over ES256 JWS, SD-JWT disclosures, chunked QR or BLE transport, cached trust list and revocation snapshot, caveats shown on the verdict | Presentation/OfflineVerifier.swift, BluetoothLink.swift |
| Mobile citizen certificate signing (TW FidO) | 行動自然人憑證 SP API v2.9 (getSpTicket, getAthOrSignResult, push), AES-256-GCM sp_checksum, app-to-app via mobilemoica:// |
backupTW/TWFidO/ |
| Release signing boundary | Debug builds may use local SP credentials; Release builds can only sign through an App Attest-gated broker with three fixed intents. CI fails if a Release binary contains any local-credential marker | TWFidO/AppAttestSigningBroker.swift, .github/workflows/ci.yml |
| Device self-check | Runs the assertions only a real device can answer: Keychain erase, Secure Enclave backing, file-protection class, with plain-language verdicts | Diagnostics/SelfCheck.swift |
Design principles that shape every screen are written down in
docs/design-system.md: honesty is layout, offline is
the default, verdicts must be readable at a counter, and "partial" must never
look like "pass".
Bonds is a working research prototype, not a shipped product. Read the "completed" claims in the reports with that in mind.
- Real-device evidence exists for: OID4VCI collection and OID4VP presentation against the official demo, one real convenience-store pickup with a telecom credential, MyData vault with a real account, offline cross-device verification, web ZK age verification, physical-card consent signing.
- Release builds cannot sign yet. The App Attest signing broker
(
bonds-signing-broker) is deployed to UAT with signing disabled; TestFlight and App Store distribution wait on it. Most feature completeness so far is Debug-build completeness. - The ZK certificate-holding proof carries a protocol-level caveat: TW FidO SIGN does not sign a verifier challenge, so the signing material is replayable. The app says so on screen rather than hiding it.
- To obtain a pre-authorized code from the official issuer, the app must send
the official wallet's
client_id(moda_dw); the token endpoint rejects any other value. This is documented in code and is one of the items queued for upstream disclosure. - Draft findings against upstream systems live in
docs/upstream-reports.md. They have not been sent; coordinated disclosure is a pending decision, not a done one. - Open work is tracked in
GitHub issues and
docs/roadmap-2026-08-27.md.
- Keys. One long-lived P-256 install key (Secure Enclave when available)
and one signing key per stored card so two verifiers cannot correlate two
cards. All Keychain items are
WhenUnlockedThisDeviceOnly, never synced. - Files. Every store lives in Application Support, never
Documents, withcompleteUnlessOpenprotection andisExcludedFromBackup. One code path erases everything, including the ZK working directory. - Network. Trust lists, revocation snapshots, and circuit assets are fetched and pinned; verifying-key hashes are compiled in and no remote source can override them. Proofs are only sent to an allow-listed verifier host.
- No telemetry. Verification timings recorded for the test matrix contain no name, DID, credential identifier, or disclosed value.
- Secrets. TW FidO SP credentials exist only inside
#if DEBUG, are read from scheme environment variables, and are blocked from the tree by.gitignoreand from the Release binary by CI.
See SECURITY.md for how to report a problem.
Requirements: macOS with Xcode 16 or later, an iOS 16+ simulator or device, Node.js 20+ for the helper scripts, Rust for the physical-card tool.
git clone https://github.com/bonds-tw/Bonds-iOS.git
cd Bonds-iOS
xcodebuild -resolvePackageDependencies -project backupTW.xcodeproj
open backupTW.xcodeprojRun the unit tests the same way CI does (pick any available iPhone simulator):
xcodebuild test -project backupTW.xcodeproj -scheme backupTW \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-only-testing:backupTWTests \
CODE_SIGN_IDENTITY="-" CODE_SIGN_STYLE=Manual DEVELOPMENT_TEAM="" PROVISIONING_PROFILE_SPECIFIER=""Notes for a first build:
- The project file carries the maintainers'
DEVELOPMENT_TEAM. Set your own team in Signing & Capabilities to run on a device. - Keychain, Secure Enclave, App Attest, Bluetooth, and the camera behave
differently on a simulator. Anything that touches them should be verified on
a real device;
Diagnosticsin the app tells you which checks it could not run. - TW FidO signing in Debug needs
TWFIDO_SP_SERVICE_IDandTWFIDO_SP_AES_KEYas scheme environment variables from your own SP registration. Without them the signing entry points fail closed. Never put them in a source file. - ZK circuit assets are downloaded on first use and pinned by SHA-256. The certificate-holding circuits need roughly 1 GB on disk after decompression.
backupTWUITestsare not run in CI. Run them locally when a change touches navigation or the screenshot tour.- Strings live in
backupTW/Localizable.xcstringswithenas source andzh-Hantas the shipped localization;LocalizationCoverageTestsfails on a missing translation.
backupTW/ The app (UIKit, Swift 5)
Crypto/ Install key, per-card holder keys, did:key
TWDIW/ OpenID4VCI / OpenID4VP, trust list, on-chain anchor, pickup
TWFidO/ Mobile citizen certificate SP API, App Attest signing broker client
Presentation/ QR/BLE transport, selective disclosure, offline verifier, ZK packages
ZK/ Groth16 certificate-holding proof: assets, verifying keys, caveats
Model/ Storage/ Credentials, MyData vault, official-document inbox, erasure
Wall/ Lennon Wall signing client (written and tested, not wired to UI)
ViewController/ Every screen
BondsDesign.swift Design tokens; spec in docs/design-system.md
backupTWTests/ Unit tests (Swift Testing + XCTest), the bundle CI runs
backupTWUITests/ UI tests and the screenshot tour, run locally
Native/OpenACAge/ Reproducible overlay on ethereum/zkID for the age proof (Rust, Mopro)
Native/OpenACAgePackage/ SwiftPM wrapper around the checksummed XCFramework
tools/ Rust PKCS#11 signer for the physical 自然人憑證 card (Debug only)
scripts/ TW FidO signing helper, PDF signature check, timing collection, icons
docs/ Design docs, plans, phase reports, audits (index in docs/README.md)
The report series 有備而來:理想的數位皮夾開發報告 on pro.mashbean.net documents each stage with real-device measurements and policy analysis. The reports are in Traditional Chinese.
| Date | Report | Topic |
|---|---|---|
| 2026-09-06 | 斷網時,讓數位皮夾互相離線驗證 | Offline cross-device verification of SD-JWT-VC and ZK proofs over Bluetooth |
| 2026-09-05 | 整合 MyData:用數位皮夾實踐資料保險箱 | MyData vault design, real-account results, verifier recognition policy |
| 2026-09-05 | 讓數位皮夾實現零知識證明,同時免費部署服務到 Cloudflare | ZK age proof from phone to a Cloudflare-fronted verifier, timings and costs |
| 2026-09-03 | 請出示皮夾:把數位皮夾查驗做成一鍵部署的開源服務 | The companion one-click verifier and its governance boundary |
| 2026-09-02 | 用「有備而來」完整重現數位皮夾超商取貨 | Telecom credential to store POS pickup on a real device |
| 2026-09-01 | 如果用自然人憑證接收電子公文? | Official-document inbox prototype, legal delivery, procurement gaps |
More on the same site: the digital identity topic and the civic proof series that frames why a citizen-built wallet matters.
Inside this repository, docs/README.md indexes the design
documents, integration plans, phase reports, and audits.
| Project | Role |
|---|---|
| bonds-tw/bonds-signing-broker | App Attest-gated TW FidO signing broker that Release builds use (private while in UAT) |
| mashbean/twdiw-vp-verifier-lite | One-click Cloudflare verifier for TWDIW presentations and Bonds ZK proofs; demo at verifier.mashbean.net |
| mashbean/twdiw-vc-issuer-lite | One-click Cloudflare test-card issuer for TWDIW-dialect credentials, its sister project; demo at issuer.mashbean.net. Bonds Debug builds pin its did:key as a sandbox exception; Release builds do not. |
| bonds-tw/bonds-tw.github.io | Deep-link landing, TWDIW field notes, credential type metadata |
| moda-gov-tw/TWDIW-official-app | The official wallet this project interoperates with |
| ethereum/zkID, privacy-ethereum/openac-rsa-x509-swift | Circuits and bindings for both zero-knowledge proofs |
| denkeni: TWDIW, The Missing Manual | Independent documentation the integration plan builds on |
Issues and pull requests are welcome. Read CONTRIBUTING.md first: it covers the branch model, what must be verified on a real device, the localization rule, and the hard line on real personal data in fixtures. This project follows the Contributor Covenant.
Apache License 2.0. See LICENSE and NOTICE for third-party components. To cite the project, see CITATION.cff.
有備而來(Bonds)是一個由公民自行開發的台灣數位身分皮夾 iOS App。 它能收存國民身分證、數位憑證皮夾(TWDIW)生態系的政府卡片、MyData 文件與電子 公文,並以線上、離線藍牙與零知識證明三種方式出示或查驗。
數位憑證皮夾、行動自然人憑證、MyData 與電子公文送達都由政府設計並營運。本專案 從另一個方向提問:如果皮夾由公民來寫,理想的皮夾應該長什麼樣,公共基礎設施還 缺哪些零件。答案用程式碼回答。App 對正式與示範環境跑完整協定,在真機上量測, 再把發現寫成報告。報告裡每一項缺口、但書與政策建議,都能對回本 repo 的程式碼。
- 以 OpenID4VCI 向官方發卡端收卡,收卡前同時核對數位發展部信任清單與 Arbitrum 鏈上紀錄。
- 以 OpenID4VP 對任何相容查驗端做選擇性揭露出示。
- 用電信卡產生超商取貨條碼,已在真實門市完成一次取貨。
- 讀取 MyData 下載的身分證明,轉為每卡獨立金鑰簽章的自發證件後銷毀明文。
- MyData 資料保險箱收存其他八種文件,原始檔受 Data Protection 保護、不進備份、 可重驗指紋、預覽與單項刪除。
- 電子公文個人接收站,含實體自然人憑證卡簽署同意的桌面工具。
- 零知識年齡證明:只證明「已滿 N 歲」,不揭露生日,可在本機、第二台裝置或網頁 查驗。
- 零知識持證證明:對自然人憑證 X.509 鏈做 Groth16 證明,並附撤銷快照,每份證明 都帶明示但書。
- 兩台裝置間的離線查驗,走 QR 或藍牙,信任清單與撤銷快照事先快取。
- 行動自然人憑證簽章,Debug 版用本機 SP 憑證,Release 版只能透過 App Attest 把關的簽章代理,CI 會檢查 Release 二進位不含任何本機憑證痕跡。
本專案是可運作的研究原型,尚未上架。Release 版目前無法簽章,簽章代理仍在 UAT
且簽章功能關閉。零知識持證證明受協定限制,簽章材料可重放,App 會在畫面上說明。
向官方發卡端取碼時必須送出官方皮夾的 client_id,程式碼中已註明,並列入待回
報上游的項目。對上游系統的問題草稿在
docs/upstream-reports.md,尚未送出。
需要 Xcode 16 以上、iOS 16 以上的模擬器或裝置。
git clone https://github.com/bonds-tw/Bonds-iOS.git
cd Bonds-iOS
xcodebuild -resolvePackageDependencies -project backupTW.xcodeproj
open backupTW.xcodeproj裝機請改成自己的開發團隊。Keychain、Secure Enclave、App Attest、藍牙與相機在
模擬器上行為不同,相關修改請在真機驗證。Debug 版的行動自然人憑證簽章需要自
己申請的 SP 帳號,以 scheme 環境變數 TWFIDO_SP_SERVICE_ID 與
TWFIDO_SP_AES_KEY 提供,不得寫進原始碼。
開發報告系列「有備而來:理想的數位皮夾開發報告」發佈於
pro.mashbean.net,
六篇報告連結見上方英文表格。Repo 內的設計文件、整合計畫、階段報告與稽核索引
在 docs/README.md。
Apache License 2.0,第三方元件見 NOTICE。參與方式見 CONTRIBUTING.md,安全回報見 SECURITY.md。 在 issue 或 PR 中不得出現任何真實個人的證件、身分證字號或憑證。