Justin Zen
    • 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 New
    • Engagement control
    • Make a copy
    • 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 Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy 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
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Stealth Address AA Plugin [Stealth Address AA Plugin](https://github.com/moonchute/stealth-address-aa-plugin/tree/develop) is a smart account plugin built on top of modular smart account provider including [ZeroDev Kernel](https://zerodev.app/) and [Biconomy](https://www.biconomy.io/). > 💡 If you're well-versed in Account Abstraction and stealth addresses, please skip to [Section III](#III-Why-Stealth-Address-AA-Plugin). ## I. What is Account Abstraction (AA) Plugin ### a. Account Abstraction Account Abstraction ([ERC-4337](https://eips.ethereum.org/EIPS/eip-4337)) changes how you interact with your crypto wallet. To better understand this, let's use a real-world analogy: think of your crypto wallet as your house, a secure place where you store all your digital assets. In the pre-AA era, entering this 'house' to manage your assets required a singular, specific 'key'—your private key. You'd need this one and only key to both access your assets and perform transactions. However, Account Abstraction significantly expands your options. Now, instead of relying solely on that traditional private key, you can also gain entry through various other means of identification like fingerprint scans, voice recognition, or any other form of proof that establishes your identity. Beyond that, you can add custom rules and logic to your smart account. For instance, you could implement a daily transaction limit for added security, or automate a small contribution to the developers behind Account Abstraction each time you access your wallet, as a way to express gratitude for their groundbreaking work. The wallet with account abstraction is called smart wallet or smart account compared to external owned account (EOA). ### b. Modular Smart Contract Account and Plugin As smart accounts evolve, gaining new capabilities for enhanced security and user experience, the question arises: What if we want to adapt our smart accounts to different scenarios or upgrade them just like we update software on smartphones? The traditional method of asset migration between smart accounts designed for different use cases has been cumbersome, to say the least. Enter [ERC-6900: Modular Smart Contract Account and Plugin](https://eips.ethereum.org/EIPS/eip-6900), which addresses these challenges head-on. Extending our 'house' analogy, with ERC-6900, you no longer have to build your digital house from scratch. You can now start with a robust foundational structure and customize it with your choice of 'furniture,' such as specific gates, modules, or plugins, to fit your unique needs. Platforms like [ZeroDev Kernel](https://github.com/zerodevapp/kernel) and [Biconomy](https://github.com/bcnmy/scw-contracts) have already implemented this modular approach and make it remarkably straightforward to create these modular plugins or modules for your smart account. For those interested in diving deeper into the realms of Account Abstraction and its modular extension, we recommend checking out our previous article [Account Abstraction Lego](https://moonchute.xyz/blog/account-abstraction-lego). Additional comprehensive guides can be found [here](https://blog.getclave.io/p/ultimate-account-abstraction-guide), [here](https://www.alchemy.com/blog/account-abstraction) and [here](https://blog.rhinestone.wtf/part-1-modular-account-abstraction-for-everyone-else-84567422bc46). ## II. What is Stealth Address While the transparency of blockchain technology brings numerous benefits, it also has its downsides—particularly when it comes to privacy. For instance, if you're paying your employee's salary or buying a cup of coffee, the transactions are publicly visible. This means anyone can trace the addresses involved and discern financial details like salary amounts or a café's monthly earnings. A workaround might be to create a new address each time you receive tokens. However, this strategy makes managing assets across multiple addresses a complicated and cumbersome task. To address this privacy concern, the concept of stealth addresses was [introduced](https://bitcointalk.org/index.php?topic=5965.0) for Bitcoin back in 2014, and has since been refined ([improved](https://bytecoin.org/old/whitepaper.pdf), [dual-key](https://github.com/shadowproject)). It's now available for Ethereum as [ERC-5564](https://eips.ethereum.org/EIPS/eip-5564). <figure style="text-align: center;"> <img src="https://hackmd.io/_uploads/rky3M_Fya.png" alt="stealth-flow" style="margin: 0 auto;"> <figcaption style="text-align: center;">Workflow from Vitalik's Post</figcaption> </figure> With a stealth address, you don't send tokens directly to the receiver's public address. Instead, a stealth address is generated using an ephemeral key combined with the receiver's public address. Only the sender and receiver are privy to who actually owns this stealth address. Furthermore, only the receiver has the ability to withdraw tokens sent to the stealth address, ensuring both privacy and security. For those eager to delve into the finer details of stealth addresses, we highly recommend reading [An incomplete guide to stealth addresses](https://vitalik.ca/general/2023/01/20/stealth.html) post by Vitalik, as well as this insightful [post](https://hackernoon.com/blockchain-privacy-enhancing-technology-series-stealth-address-i-c8a3eb4e4e43) that provides an excellent introduction to the subject. ## III. Why Stealth Address AA Plugin In this section, we will delve into the dual motivations behind our Stealth Address Account Abstraction (AA) Plugin, looking at it through the lenses of both Account Abstraction and stealth addresses: 1. Account Abstraction: privacy-preserving plugin 2. Stealth address: transition from EOA to smart contract account ### a. Account Abstraction: privacy-preserving plugin We believe that privacy should be an inherent property of blockchain technology, not just an optional add-on. While Account Abstraction (AA) has been instrumental in providing flexible verification logic beyond the traditional ECDSA signature, there remains the need to confirm your identity and prove ownership of the wallet. Whether it's a private key, an enclave-based key, or even biometric data, some form of identification is essential. At MoonChute, we've created [Unified Smart Account Managers](https://app.moonchute.xyz/) that associates an individual's EOA address with their respective smart accounts. From the feedback we've gathered, this utility has been instrumental in simplifying the management of multiple smart accounts. However, we believe that users should also have the freedom to choose how much privacy they want to maintain. <figure style="text-align: center;"> <img src="https://hackmd.io/_uploads/SJ0F-YKya.png" alt="smart-account-manager" style="margin: 0 auto;"> <figcaption style="text-align: center;">Unified Smart Account Manager</figcaption> </figure> To strike a balance between the utility of Account Abstraction and the need for privacy, stealth addresses serve as an ideal solution. They fulfill our aim of enhancing user privacy without sacrificing the flexibility and user experience that smart accounts offer. ### b. Stealth address: transition from EOA to SCA If we are going to [allow existing EOAs to upgrade to contract ](https://www.youtube.com/watch?v=iLf8qpOmxQc&t=2488s), why not make stealth address a contract account? ## IV. How Stealth Address AA Plugin works ### a. ECDSA validator plugin Within the ZeroDev Kernel's [ECDSA Validator](https://github.com/zerodevapp/kernel/blob/main/src/validator/ECDSAValidator.sol), a user operation (userop) is validated based on whether the signature comes from the owner address specified in the validator contract. As the validator facilitates a seamless transition from to smart accounts, there's an opportunity to boost privacy. Specifically, we can break the direct link between the smart account and its owner. ```solidity struct ECDSAValidatorStorage { address owner; } function validateUserOp(UserOperation calldata _userOp, bytes32 _userOpHash, uint256) external payable override returns (ValidationData validationData) { address owner = ecdsaValidatorStorage[_userOp.sender].owner; bytes32 hash = ECDSA.toEthSignedMessageHash(_userOpHash); if (owner == ECDSA.recover(hash, _userOp.signature)) { return ValidationData.wrap(0); } if (owner != ECDSA.recover(_userOpHash, _userOp.signature)) { return SIG_VALIDATION_FAILED; } } ``` ### b. Stealth address For simplicity, let's focus on the basic version of stealth addresses. The objective here is for a sender to transfer tokens to a receiver in a way that masks the identity of the receiver, while also ensuring that only the receiver can access and spend the tokens. First, the sender obtains the public key of the receiver and randomly generates an ephemeral key pair. The sender then publishes the ephemeral public key. Both parties can now compute a shared secret via the [Diffie-Hellman key exchange](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange), which is established between the receiver's public key and the ephemeral key, followed by hashing. The stealth address is formulated by combining the receiver's public key and the public key of the shared secret. The corresponding stealth private key can only be calculated by the receiver, as it combines the shared secret and the receiver's private key. <figure style="text-align: center;"> <img src="https://hackmd.io/_uploads/SkV5cp9k6.png" alt="smart-account-manager" style="margin: 0 auto;"> </figure> ### c. Stealth address validator plugin The Stealth Address Validator Plugin are employed to mask the identity of smart account owners. This results in the decoupling of the visible link between smart account owners and their respective smart accounts, thereby ensuring a higher degree of privacy. ```solidity struct StealthValidatorStorage { address stealthAddress; } function validateUserOp(UserOperation calldata _userOp, bytes32 _userOpHash, uint256) external payable override returns (ValidationData validationData) { address stealthAddress = stealthValidatorStorage[_userOp.sender].stealthAddress; bytes32 hash = ECDSA.toEthSignedMessageHash(_userOpHash); if (stealthAddress == ECDSA.recover(hash, _userOp.signature)) { return ValidationData.wrap(0); } if (stealthAddress != ECDSA.recover(_userOpHash, _userOp.signature)) { return SIG_VALIDATION_FAILED; } } ``` ## V. Validation with Stealth address plugin ### a. Signature signed by stealth address In the simplest scenario, we verify a signature generated by the stealth address's private key. However, the practical limitations of generating shared secrets and corresponding private keys within existing wallet UIs present a challenge. To overcome this, we propose using aggregate signatures that can be verified by the contract. ### b. Aggregate signature #### 1. Shared secret The shared secret, denoted by $s$, can be generated using the ephemeral public key and the user's private key, or vice versa: $$priv_{shared}=Hash(bR)=Hash(rB)$$ In standard stealth address implementations, this usually requires manual management of private keys by users. However, ephemeral private keys could be stored when creating the smart account, simplifying shared secret retrieval within existing wallet UIs. #### 2. Private key of stealth address The private key of the stealth address, denoted as $priv_{stealth}$, is calculated as: $$priv_{stealth}=priv_{shared}+b$$ Where $s$ is the shared secret and $b$ is the private key of the owner. The challenge we face is that we're unable to alter the private key within the wallet's user interface. Nevertheless, we do have the latitude to make subtle adjustments in the way signatures are generated and verified, which could provide us with a viable path forward. Here's original [ECDSA signing](https://cryptobook.nakov.com/digital-signatures/ecdsa-sign-verify-messages) #### ECDSA Signing Given a private key: $privKey$, and a message: $m$ 1. Calculate the message hash: $h=hash(m)$ 2. Generate a random number $k$ 3. Calculate $R=k*G$ and take its x-coordinate $r=R.x$ 4. Calculate $s=k^{-1}*(h+r*privKey)\mod n$ 5. The signature is $(r,s)$ #### ECDSA Verifying 1. Calculate the inverse of signature s: $s_1=s^{-1}\mod n$ 2. Calculate $R'=(h*s_1)*G+(r*s_1)*pubkey$, and take its x-coordinate r'=R'.x 3. The result is $r==r'$ We propose an adjustment to the signing and verification steps to facilitate stealth address usage: #### Aggregate ECDSA Signing Given private key of owner: $priv_{owner}$, shared secret key: $priv_{shared}$ and message: $m$ 1. Perform step 1 - 4 as in ECDSA signing using $priv_{owner}$ 2. Calculate the aggregate signature $s'=s(h+r*priv_{shared})\mod n$ 3. The aggregate signature is $(r,s')$ #### Aggregate ECDSA Verifying The aggregated signature $$\begin{equation} \begin{split} s' &=s(h+r*priv_{shared}) \\ &=k^{-1}(h+r*priv_{owner})(h+r*priv_{shared}) \\ &=k^{-1}[h^2+hr*(priv_{owner}+priv_{shared})+r^2*priv_{owner}*priv_{shared}] \end{split} \end{equation}$$ We notice that: $$pub_{stealth}=G*(priv_{owner}+priv_{shared})\\ dh_{owner\_shared}=G*{priv_{owner}*priv_{shared}} $$ We can thus verify the aggregate signature by: 1. Calculate the inverse of aggregate signature $$s_1=s'^{-1}\mod n$$ 2. Calculate $R'$ and take its x-coordinate $r'=R'.x$ $$R'=(h^2*s_1)*G+(h*r*s_1)*pubkey_{stealth}+(r^2*s)*dh_{owner\_shared}$$ 4. The result is $r==r'$ ### c. Masked Message Signature Performing secp256k1 elliptic curve operations, specifically point addition and multiplication on chain is prohibitively costly. For instance, validating a user operation with an aggregated signature consumes around 1 million gas. To mitigate this, we introduce a masked message scheme. This approach allows for the verification of signatures from a stealth address using only the owner's private key, bypassing the need for on-chain elliptic curve operations. #### Signature Transformation Consider a signature pair $(r,s)$ produced by the owner's private key $priv_{owner}$ signing a message $h$. This can equivalently be viewed as a signature produced by the stealth address's private key $priv_{stealth}$ signing a modified message $h'$: \begin{split} s &=k^{-1}*(h+r*priv_{owner}) \\ &=k^{-1}*(h-r*priv_{shared}+r*(priv_{owner}+priv_{shared})) \\ &=k^{-1}*(h'+r*(priv_{stealth})) \end{split} This mathematical transformation enables the recovery of the stealth address using the signature signed by the owner's private key. The key challenge lies in deriving $h'$ from $h$ on-chain. #### Masked Message To address this, we propose a method where, given $m'$, we can prove it is derived from $m$ without revealing $priv_{shared}$. This is achievable using a zero-knowledge proof. However, direct exposure of $m'$ risks revealing $priv_{shared}$, as it can be computed from the original message $m$ and signature $r$. To prevent this, we introduce a masking mechanism using a random number $r'$, which is a one-way function of message $m$ and private key $priv_{shared}$. $$m'=m-Hash(m,priv_{shared})$$ #### Masked ECDSA Signing Given the owner's private key $priv_{owner}$, shared secret key $priv_{shared}$ and a message $m$, the following steps are taken: 1. Generate a random number $r'=Hash(m,priv_{shared})$ and calculate $m'=m-r'$ 2. Sign $m'$ using the owner's private key $priv_{owner}$ producing the signature $(r,s)$ when $s=k^{-1}*(m'+r*priv_{owner})$ 3. Formulate message $m''$ \begin{split} m'' &=m'-r*priv_{shared} \\ &=m-Hash(m,priv_{shared})-r*priv_{shared} \end{split} 4. Generate a zero-knowledge proof $p$ to validate that $m''$ is derived from $m$ without disclosing $priv_{shared}$ #### Masked ECDSA Verifying 1. Confirm that $p$ is valid zero-knowledge proof 2. Recover the stealth address from the signature $(r,s)$ Utilizing the masked scheme decreases the gas consumption to just **one-sixth** compared to the aggregated signature. ## VI. Workflow In this section, we outline the complete workflow for creating a smart account with the Stealth Address Account Abstraction (AA) Plugin. ### a. Create stealth smart account 1. **Generate Ephemeral Key Pair**: Start by generating a random ephemeral key $r$ 2. **Compute Shared Secret**: Use the random ephemeral private key $r$ and the owner's public key $B$ to compute the Diffie-Hellman shared secret, $s$, through hashing: $$s=Hash(rB)$$ 3. **Compute Stealth Public Key and Address**: Calculate the stealth public key by adding the public keys of the owner and the shared secret: $$pub_{stealth}=pub_{owner}+pub_{shared}$$ Then calculate the stealth address: $$addr_{stealth}=bytes20(keccak256(pub_{stealth})$$ 4. **Compute Diffie-Hellman Key**: Now calculate the Diffie-Hellman key between the owner and the shared secret: $$dh_{owner\_shared}=pub_{owner}*s$$ 5. **Call CreateAccount**: Finally, call the createAccount function in the Smart Account Factory Contract. <figure style="text-align: center;"> <img src="https://hackmd.io/_uploads/Sypwwqfga.png" alt="smart-account-manager" style="margin: 0 auto;"> </figure> Within the framework of our validator, we'll securely store the stealth address, stealth public key, and the Diffie-Hellman key. Importantly, to bolster user privacy, **the owner's address will not be stored** in the validator. This design ensures that there is no explicit connection between the smart account and its respective owner. ``` struct StealthAddressValidatorStorage { uint256 stealthPubkey; uint256 dhkey; address stealthAddress; uint8 stealthPubkeyPrefix; uint8 dhkeyPrefix; } ``` ### b. Verify signature The signature in the userOp is constructed with the signature or aggregated signature generated by stealth smart account owner with the 1 Byte prefix **mode**. **Mode 0: ECDSA Verification** If the mode is set to 0, the signature will be verified using standard ECDSA verification logic that matches the stealth address's private key. In other words, the signature is considered valid if it can be mathematically confirmed to have been generated by the stealth address's private key. **Mode 1: Aggregated Signature Verification** If the mode is different from 0, the signature will undergo aggregated signature verification as detailed in the previously discussed procedure for [Aggregate ECDSA Verifying](#b-Aggregate-signature). **Mode 2: Masked Signature Verification** When the mode is set to 2, the signature is verified using the masked signature verification method as previously described in [Masked Signature Verifying](#Masked-ECDSA-Verifying). <figure style="text-align: center;"> <img src="https://hackmd.io/_uploads/HyqIuvXKp.png" alt="smart-account-manager" style="margin: 0 auto;"> <figcaption style="text-align: center;">Signature of stealth smart account </figcaption> </figure> ## VII. Benchmark | | Stealth address signature | Aggregated signature | Masked signature | | ----|---------- |---------- |------------ | | Gas | 3,921 | 1,380,946 | 262,126 | | Support all wallet | x | o | o | ## VIII. What's next **ERC-5564 Compatibility and Stealth Address Scanning** While our stealth smart accounts are self-created, eliminating the need for separate viewing and spending keys used in standard stealth address implementations, our aim is to ensure compatibility with [ERC-5564](https://eips.ethereum.org/EIPS/eip-5564). This will enable senders to transfer tokens to recipients while maintaining the recipient's anonymity. One of the issues widely [discussed](https://ethresear.ch/t/open-problem-improving-stealth-addresses/7438) with stealth addresses is stealth address scanning overhead. It's worth exploring an efficient solution to minimize the cost in the realm of smart account. **Enhancing privacy through Account Abstraction** Since the stealth smart accounts are essentially smart contract wallets, they offer a greater degree of flexibility. For example, we could use a paymaster to sponsor userOps in such a way that the link between the stealth smart account and its owner remains concealed.

    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