Gavinok
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Trusted Verifier Design Document **Issue**: [#225 Spike: verifier identification](https://github.com/openwallet-foundation/acapy-vc-authn-oidc/issues/225) ## Overview This document outlines the design for implementing **Trusted Verifier Authentication** in VC-AuthN OIDC and Bifold Wallet. This feature enables wallets to verify that a verifier (VC-AuthN instance) is authorized to request sensitive credential information before the wallet responds to proof requests. ## Key Objectives 1. **Mutual Authentication**: Enable bidirectional proof exchange where wallets can challenge verifiers 2. **Protected Claims**: Allow credential schemas to designate sensitive claims requiring verifier authorization 3. **Seamless UX**: Implement authorization checks without disrupting existing proof request flows 4. **Reusable Connections**: Leverage established connections for bidirectional proof exchanges ## Scope **In Scope:** - VC-AuthN holder/prover capabilities for responding to proof requests - Bifold wallet capability to send reverse proof requests to verifiers - Configuration for protected claims and trusted verifier credentials - Bidirectional proof exchange over existing connections **Out of Scope:** - Governance framework for issuing trusted verifier credentials - Credential revocation handling (Phase 1) - Connectionless verifier authorization ## Problem Statement ### Business Context When VC-AuthN requests credentials containing sensitive information (PII, unique identifiers, health records, etc.), holders have no mechanism to verify that the verifier is legitimate and authorized to receive this information. This creates: - **Security Risk**: Malicious verifiers can request sensitive data without accountability - **Privacy Concern**: Users cannot validate who is collecting their personal information - **Compliance Gap**: Regulated industries may require verifier authentication for sensitive data ### User Story **As a** wallet holder **I want to** verify that a verifier is authorized to request my sensitive credentials **So that** I can trust that my personal information is being collected by legitimate, authorized entities ### Technical Challenge Currently: ``` Wallet <- proof-request <- VC-AuthN (Holder) -> presentation -> (Verifier) ``` **One-way verification**: Only VC-AuthN verifies the wallet; the wallet cannot verify VC-AuthN. Required: ``` Wallet <- proof-request <- VC-AuthN (Verifier -> proof-request -> (Verifier +Holder) <- presentation <- +Holder) -> presentation -> ``` **Bidirectional verification**: Both parties authenticate each other. --- ## Current State Analysis | Component | Current Capabilities | Missing for Trusted Verifier | |-----------|---------------------|------------------------------| | **VC-AuthN** | - Create/send presentation requests<br>- Receive presentations<br>- Connection management | - Incoming proof request handler<br>- Credential storage/management<br>- Presentation response logic<br>- Role-based routing | | **Bifold** | - Send proof requests (verifier)<br>- Receive proof requests (holder)<br>- Detect sensitive claims<br>- Connection management | - Bidirectional proof exchange<br>- Verify verifier credentials<br>- Conditional response logic | ## Proposed Solution ### High-Level Architecture The solution implements bidirectional proof exchange: 0. **VC-AuthN is offered and stores a dedicated credential** 1. **VC-AuthN sends proof request** to wallet (existing flow) 2. **Wallet detects protected claims** and pauses 3. **Wallet sends reverse proof request** to VC-AuthN asking for authorization 4. **VC-AuthN responds with trusted verifier credential** 5. **Wallet validates authorization** and decides to proceed or decline 6. **Wallet responds to original proof request** (if authorized) ### Key Design Decisions | Decision | Choice | Rationale | Critical Notes | |----------|--------|-----------|----------------| | **Connection Type** | Ephemeral connections | Works with current implementation | Authorization MUST complete before original exchange for proper cleanup | | **Connection Reuse** | Same connection for both exchanges | Reduces latency, simpler flow | Each exchange has unique `pres_ex_id`, shares `connection_id` | | **Response Mode** | Automatic (Phase 1) | Faster, simpler implementation | Future: Add manual approval option | | **Protected Claims** | Schema-level metadata | Centralized policy, OCA compatible | Bifold checks `containsPI` flag | | **Authorization Timeout** | 30 seconds | Balance UX and network latency | Auto-decline on timeout | | **Cleanup Logic** | Role-aware (verifier only) | Prevents premature connection deletion | Prover-role exchanges don't trigger cleanup | ### Storing Connections --- ## Technical Architecture This section provides detailed architecture diagrams showing the system components, data models, and interaction patterns. The diagrams use PlantUML notation and show both the current single-direction verification and the required bidirectional approach. ### Current vs Required Architecture #### Current Architecture (Verifier Only) ```plantuml @startuml !theme plain participant "Wallet\n(Holder)" as Wallet participant "VC-AuthN\n(Verifier)" as VCAuthN VCAuthN -> Wallet : Proof Request activate Wallet Wallet -> VCAuthN : Presentation deactivate Wallet activate VCAuthN VCAuthN -> VCAuthN : Verify Presentation deactivate VCAuthN note right of VCAuthN One-way verification: Only VC-AuthN verifies the wallet Wallet cannot verify VC-AuthN end note @enduml ``` #### Required Architecture (Bidirectional) ```plantuml @startuml !theme plain participant "Wallet\n(Verifier + Holder)" as Wallet participant "VC-AuthN\n(Verifier + Holder)" as VCAuthN VCAuthN -> Wallet : Proof Request activate Wallet Wallet -> VCAuthN : Reverse Proof Request\n(Prove Authorization) activate VCAuthN VCAuthN -> Wallet : Presentation\n(Trusted Verifier Credential) deactivate VCAuthN Wallet -> Wallet : Validate Verifier alt Verifier Authorized Wallet -> VCAuthN : Presentation activate VCAuthN VCAuthN -> VCAuthN : Verify Presentation deactivate VCAuthN else Verifier Not Authorized Wallet -> VCAuthN : Decline (Problem Report) end deactivate Wallet note right of VCAuthN Bidirectional verification: Both parties authenticate each other Wallet validates verifier authorization before sharing credentials end note @enduml ``` ### Sequence Diagram ```plantuml @startuml autonumber participant "BC Wallet" as Wallet participant "Frontend" participant "VC-AuthN OIDC Controller" as Verifier participant "ACA-Py Agent" as ACA ACA -> Wallet : Send proof request with protected claims == Mutual Authentication: Wallet Verifies Verifier == Wallet -> Wallet : Detect protected claims in proof request note left: Check if requested credentials/claims\nrequire verifier authorization Wallet -> ACA : POST /present-proof-2.0/send-request\n(connection_id)\n[Request: "trusted verifier" credential] note left: Wallet sends reverse proof request\nto verify verifier's authorization ACA -> ACA : Auto-respond with matching credential\n(--auto-respond-presentation-request) ACA -> Wallet : Send verifier authorization proof ACA -> Verifier : Webhook: present_proof_v2_0 (request-received, done) note right: Controller logs for observability Wallet -> Wallet : Validate verifier credential\n- Check schema/cred_def\n- Verify issuer\n- Check claims match requirements note left: Validation determines if\nverifier is authorized for\nthe requested protected claims alt Verifier Authorization Valid Wallet -> Wallet : Authorization successful\nProceed with original proof request Wallet -> ACA : POST /present-proof-2.0/records/{pres_ex_id}/send-presentation\n[Original proof request response] note left: Wallet presents requested credentials\nafter successful authorization ACA -> Verifier : Webhook: present_proof_v2_0 (presentation-received) ACA -> Verifier : Webhook: present_proof_v2_0 (done) Verifier -> Verifier : Update AuthSession (VERIFIED) Verifier --> Frontend : WebSocket notification: verified Verifier --> Frontend : Return OIDC token with vc_presented_attributes else Verifier Authorization Failed or Timeout Wallet -> Wallet : Authorization failed/timeout\nDecline original proof request note left: Timeout if verifier doesn't respond\nto authorization request Wallet -> ACA : POST /present-proof-2.0/records/{pres_ex_id}/problem-report Note left: Wallet declines original proof request\ndue to failed authorization ACA -> Verifier : Webhook: present_proof_v2_0 (abandoned) Verifier -> Verifier : Update AuthSession (FAILED) Verifier --> Frontend : WebSocket notification: failed Verifier --> Frontend : Return error: Unauthorized verifier end @enduml ``` ### Data Model #### AuthSession Extension (VC-AuthN) ```plantuml @startuml !theme plain class AuthSession { ' Existing fields + id: str + pres_exch_id: str + connection_id: str | None ' NEW: Track reverse proof exchange + verifier_challenge_exch_id: str | None + verifier_challenge_state: str | None + verifier_challenge_timestamp: datetime | None } note right of AuthSession::pres_exch_id Original proof request (VC-AuthN as verifier) end note note right of AuthSession::verifier_challenge_exch_id Reverse request (VC-AuthN as prover) Both exchanges share the same connection_id end note @enduml ``` #### Presentation Exchange Correlation ```plantuml @startuml !theme plain object "Exchange 1\n(Original)" as E1 { pres_ex_id = "abc123" role = "verifier" direction = "VC-AuthN -> Wallet" connection_id = "conn-xyz" } object "Exchange 2\n(Reverse/Challenge)" as E2 { pres_ex_id = "def456" role = "prover" direction = "Wallet -> VC-AuthN" connection_id = "conn-xyz" } object "Connection\nconn-xyz" as Conn { connection_id = "conn-xyz" state = "active" } E1 --> Conn : uses E2 --> Conn : uses (same connection!) note bottom of Conn **Problem:** Two different pres_ex_ids **Solution:** Lookup by connection_id This allows correlating both exchanges to the same AuthSession end note @enduml ``` **Solution**: - Lookup AuthSession by `connection_id` (not `pres_ex_id`) when handling reverse proof requests - Store reverse exchange ID in `verifier_challenge_exch_id` field - Both exchanges correlate via shared `connection_id` ### Configuration Schema **ACA-Py Settings** (`charts/vc-authn-oidc/values.yaml` ->`acapy.argfile.yml`): - `auto-respond-presentation-request: true` - Auto-respond to proof requests with matching credentials **Bifold Configuration** (`packages/core/src/types/config.ts`): - `verifierAuthorization.enabled` - Feature flag - `verifierAuthorization.protectedClaims` - Claim names requiring authorization (array) - `verifierAuthorization.protectedSchemas` - Schema IDs requiring authorization (array) - `verifierAuthorization.verifierCredentialSchema` - Expected verifier credential schema - `verifierAuthorization.timeoutMs` - Timeout in milliseconds (default: 30000) **Helm Chart** (`charts/vc-authn-oidc/values.yaml`): - `acapy.argfile.yml.auto-respond-presentation-request` - Enable auto-response (default: false) --- ## Implementation Requirements This section outlines the implementation work broken into 4 phases: (1) ACA-Py agent setup, (2) VC-AuthN controller code, (3) Bifold wallet changes, and (4) integration testing. Each phase includes specific tasks, implementation details, and acceptance criteria. ### Phase 1: VC-AuthN - ACA-Py Agent Setup #### Tasks 1. **Enable Auto-Respond in Helm Chart** - Add `auto-respond-presentation-request: true` to `acapy.argfile.yml` in values.yaml - ACA-Py will automatically respond to proof requests with matching credentials 2. **Verify ACA-Py Capabilities** - Confirm holder/prover features enabled - Validate webhook delivery for `request-received` state with `role: "prover"` 3. **Issue Trusted Verifier Credential** Choose approach based on environment: - **Dev/Test**: Self-issuance via bootstrap script (agent issues to itself) - **Production**: External trusted issuer (governance authority) Bootstrap script should: - Create schema/cred_def if not exists - Issue credential to VC-AuthN's agent - Be idempotent (safe to re-run) 4. **Test Auto-Respond Flow** - Manually send proof request to VC-AuthN - Verify ACA-Py auto-responds with credential - Validate presentation verification #### Acceptance Criteria **Helm Chart**: - [ ] `auto-respond-presentation-request: true` added to values.yaml **ACA-Py Capabilities**: - [ ] ACA-Py agent successfully receives proof requests - [ ] Webhook fires with `state: "request-received"` and `role: "prover"` - [ ] ACA-Py auto-responds with matching credential - [ ] Trusted verifier credential issued via issue-credential protocol --- ### Phase 2: VC-AuthN - Controller Implementation Since ACA-Py handles auto-response via `--auto-respond-presentation-request`, the controller only needs logging for observability. #### Task 2.1: Add Prover-Role Webhook Logging **What to Build**: - Check `role` field in `present_proof_v2_0` webhook - Log prover-role events for observability: - `request-received`: "Received verifier authorization challenge" - `presentation-sent`: "Sent trusted verifier credential" - `done`: "Verifier authorization exchange complete" - Include: `connection_id`, `pres_ex_id`, timestamp - **CRITICAL**: Don't trigger connection cleanup from prover-role exchanges **Acceptance Criteria**: - [ ] Webhook routes based on `role` field - [ ] Prover-role events logged with relevant IDs - [ ] No cleanup from prover-role handler --- #### Task 2.2: Extend AuthSession Data Model (Optional) For correlation/observability, track the reverse exchange: - Add `verifier_challenge_exch_id` field to AuthSession model - Use existing `get_by_connection_id()` method to lookup session **Acceptance Criteria**: - [ ] Challenge exchange ID stored for correlation --- ### Phase 3: Bifold Wallet Implementation Add wallet capability to challenge verifiers before responding to proof requests with protected claims. #### Task 3.1: Create VerifierAuthorizationService **What to Build**: - `requiresAuthorization()` - Check if proof request needs verifier auth (based on protected claims/schemas) - `requestVerifierCredential()` - Send reverse proof request to verifier - `verifyVerifierCredential()` - Validate verifier's credential response - `isAuthorizedForClaims()` - Check if verifier authorized for specific claims #### Task 3.2: Create React Hook **File**: `packages/core/src/hooks/verifier-authorization.ts` **What to Build**: - State management: `isAuthorized`, `isChecking`, `error` - `checkAuthorization()` method with 30s timeout - Integration with VerifierAuthorizationService #### Task 3.3: Modify ProofRequest Screen **What to Build**: - Pre-authorization check before `handleAcceptPress` - Loading state during authorization - Auto-decline if authorization fails - Skip authorization for non-protected claims #### Task 3.4: Create Authorization Modal **What to Build**: - Show "Checking verifier..." loading state - Display verifier credential details - Show success/failure states with appropriate messaging #### Task 3.5: Add Configuration **What to Build**: - `verifierAuthorization.enabled` - Feature flag - `verifierAuthorization.protectedClaims` - Claim names requiring auth - `verifierAuthorization.protectedSchemas` - Schema IDs requiring auth - `verifierAuthorization.verifierCredentialSchema` - Expected verifier cred schema - `verifierAuthorization.timeoutMs` - Timeout (default: 30000) --- ### Phase 4: Integration Testing #### Test Scenarios | Scenario | Expected Outcome | |----------|------------------| | **Success Path** | Wallet validates verifier credential, proceeds with original proof request | | **Missing Credential** | VC-AuthN can't provide credential, wallet declines | | **Invalid Credential** | Wallet validation fails, declines proof request | | **Timeout** | 30s timeout expires, wallet auto-declines | | **Non-Protected Claims** | Wallet skips authorization, proceeds normally | #### Testing Approach 1. **Local Testing**: Docker environment (`./manage start`) + Bifold dev build 2. **Network Testing**: Deploy to BCovrin Test ledger for end-to-end validation #### Success Criteria - [ ] All scenarios pass with proper error handling - [ ] Authorization completes in <5 seconds - [ ] No connection/exchange leaks - [ ] Clear user error messages - [ ] Complete audit logging --- ## Flow Diagrams This section provides additional visual representations to understand the behavior and state management of the trusted verifier feature. ### AuthSession State Transitions The following diagram shows how authentication sessions progress through different states during the bidirectional proof exchange. Protected claims trigger an authorization challenge before verification proceeds. ### State Diagram ```plantuml @startuml !theme plain title AuthSession State Transitions [*] --> INITIALIZED INITIALIZED --> CONNECTED : Connection\nestablished CONNECTED --> REQUEST_SENT : Proof request\nsent REQUEST_SENT --> CHALLENGE_SENT : Protected claims\ndetected REQUEST_SENT --> PRESENTATION_RECEIVED : Non-protected claims\n(bypass authorization) CHALLENGE_SENT --> CHALLENGE_RECEIVED : Verifier responds\nwith credential CHALLENGE_RECEIVED --> VERIFIED : Credential valid CHALLENGE_RECEIVED --> FAILED : Credential invalid\nor timeout PRESENTATION_RECEIVED --> VERIFIED : Verification\nsuccessful PRESENTATION_RECEIVED --> FAILED : Verification\nfailed VERIFIED --> [*] FAILED --> [*] note right of REQUEST_SENT Branching point: - Protected claims -> Challenge verifier - Non-protected -> Direct verification end note note right of CHALLENGE_RECEIVED Validation checks: - Schema matches expected - Issuer is trusted - Claims authorize request end note @enduml ``` ### Webhook Handler Routing This diagram illustrates how the webhook handler distinguishes between VC-AuthN acting as a verifier (normal flow) versus acting as a prover (responding to wallet challenges). The key difference is using `pres_ex_id` lookup for verifier-role webhooks and `connection_id` lookup for prover-role webhooks. ### Webhook Routing Logic ```plantuml @startuml !theme plain title Webhook Handler Routing (present_proof_v2_0) start :Webhook Received; :Parse body\nGet "role" field; if (role == "verifier"?) then (yes) partition "EXISTING FLOW\n(VC-AuthN sent request)" { :Lookup AuthSession\nby pres_ex_id; if (state?) then (presentation-received) :Process presentation; elseif (done) :Finalize verification; elseif (abandoned) :Mark as failed; endif } else (no, role == "prover") partition "NEW FLOW\n(Wallet sent request)" { :Log event for observability; note right: ACA-Py auto-responds\nvia --auto-respond-presentation-request :Lookup AuthSession\nby connection_id (optional); :Store challenge exchange ID\nfor correlation; } endif stop note right **Key Difference:** - Verifier role: pres_ex_id lookup - Prover role: connection_id lookup This solves the correlation problem for bidirectional exchanges. end note @enduml ``` --- ## Success Criteria ### Functional Requirements - [ ] **FR-1**: VC-AuthN can receive and respond to proof requests from wallets - [ ] **FR-2**: VC-AuthN automatically responds with trusted verifier credential when configured - [ ] **FR-3**: Bifold wallet detects protected claims in proof requests - [ ] **FR-4**: Bifold wallet sends reverse proof request to verify verifier authorization - [ ] **FR-5**: Bifold wallet validates verifier credential before responding - [ ] **FR-6**: Bidirectional proof exchanges use the same connection - [ ] **FR-7**: Authorization failures result in declined proof requests - [ ] **FR-8**: Configuration controls authorization behavior ### Non-Functional Requirements - [ ] **NFR-1**: Authorization check completes within 5 seconds (95th percentile) - [ ] **NFR-2**: No additional connections created for authorization - [ ] **NFR-3**: Authorization timeout prevents indefinite waiting (30s default) - [ ] **NFR-4**: Error messages clearly indicate authorization failure reasons - [ ] **NFR-5**: Audit logs capture all authorization attempts - [ ] **NFR-6**: Feature can be disabled via configuration - [ ] **NFR-7**: No breaking changes to existing proof request flows ### User Experience - [ ] **UX-1**: Users see clear indication that verifier is being checked - [ ] **UX-2**: Users see verifier credential details before proceeding - [ ] **UX-3**: Timeout failures show helpful error messages - [ ] **UX-4**: Non-protected claims bypass authorization (seamless) --- ## Risks and Mitigation ### Security Risks | Risk | Impact | Likelihood | Mitigation | |------|--------|------------|------------| | **Malicious Credential Offers** | High | Low | Credential Offers Should Be Manually Accepted or Rate Limited | | **Malicious Authorization Requests** | High | Low | Validate proof request format, rate limit requests | | **Credential Theft** | High | Low | Only respond to valid proof request formats | | **Man-in-the-Middle** | High | Low | Use DIDComm encryption, validate DIDs | | **Replay Attacks** | Medium | Low | Use presentation exchange nonces | ### Operational Risks | Risk | Impact | Likelihood | Mitigation | |------|--------|------------|------------| | **Configuration Errors** | Medium | High | Validation on startup, clear documentation | | **Performance Degradation** | Medium | Low | Load testing, resource monitoring | | **Breaking Changes** | Low | Low | Comprehensive testing, feature flags | --- ## Appendix ### B. References - [Issue #225: Spike - Verifier Identification](https://github.com/openwallet-foundation/acapy-vc-authn-oidc/issues/225) - [Bifold Wallet Architecture](https://github.com/openwallet-foundation/bifold-wallet) ### D. Configuration Examples **Helm Chart** (`values.yaml`): ```yaml acapy: argfile.yml: auto-respond-presentation-request: true ``` **Bifold Configuration:** - `verifierAuthorization.enabled=true` - `verifierAuthorization.protectedClaims=["ssn", "license_number", ...]` - `verifierAuthorization.verifierCredentialSchema=<schema-id>` - `verifierAuthorization.timeoutMs=30000`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully