Irakli Gozalishvili
    • 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
    • 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 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
    5
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Content Claims Protocol UCAN based protocol allowing actors to share information about specific content (identified by CID). > We base the protocol on top of [UCAN invocation specification 0.2](https://github.com/ucan-wg/invocation/blob/v0.2/README.md#23-ipld-schema) ## NextGen IPFS Content Discovery Content discovery in IPFS today is “peer based,” meaning that an IPFS client first performs a “content discovery” step and then a “peer discovery” step to read data in the following way: 1. Client finds peerids that have announced they have a CID 2. Client finds transport protocols for the given peerid that it can use to read IPFS data from that peer. 3. Client retreives data from that peer. ![](https://hackmd.io/_uploads/r1pmgLuVh.png) File reads in this system requires that two entities, the client and the peer serving the data, both support an IPFS aware transport protocol. The peer serving a given CID is required to serve data at a specific endpoint and keep that endpoint operational for data to be available. When a publisher wants to hire a remote storage device this protocol design presents a challenge since these remote storage devices don’t speak native IPFS protocols, therefor IPFS clients can’t read content from them directly. We end up having to place a node between these remote storage devices and the client, proxying the data and incurring unnecessary egress. ![](https://i.imgur.com/Jan0Opg.png) Content Claims change this. Publishers post **verifiable claims** about IPFS data they publish in remote storage devices into Content Discovery networks and services. From these claims, IPFS clients can request data directly from remote storage devices over standard transports like HTTP. ![](https://i.imgur.com/4iutSwA.png) With content claims, any actor can provide data into the network without making the data available over an IPFS aware transport protocol. Rather than "serving" the data, content providers post verifiable claims about the data and its location. Clients can use these claims to read directly, over any existing transport (mostly HTTP), and in the act of reading the data will verify the related claims. This removes a **substantial** source of cost from providing content into the network. Content can be published into the network "at rest" on any permanent or temporary storage device that supports reading over HTTP. ## Claim Types The requirements for content claims break down into a few isolated components, each representing a specific claim. These claims are assembled together to represent the proof information necessary for retrieving content. All claim types map a **single** CID to "claim information." While you can derive block indexes from these claims (see: Inclusion Claims), each individual claim is indexed by a single **significant** CID (file root, dir root, etc) referrenced by `content` field. These claims include examples of a unixfs file encoded into CAR files, but the protocol itself makes heavy use of CIDs in order to support a variety of future protocols and other use cases. Since this protocol builds upon the UCAN Invocation Specification, all of these claims are contained within a message from an *issuer* to a *destination*. This means it can be used to send specific actors unique route information apart from public content discovery networks, and can also be used to send messages to public content discovery networks by simply addressing them to a DID representing the discovery network. ### Location Claims * Claims that a CID is available at a URL. * Block Level Interface, * GET Body MUST match multihash digest in CID. * Using CAR CID, or any future BlockSet() identifier, allows this to be a multi-block interface. ```javascript { "op": "assert/location", "rsc": "https://web3.storage", "input": { "content" : CID /* CAR CID */, "location": "https://r2.cf/bag...car", "range" : [ start, end ] /* Optional: Byte Range in URL } } ``` From this, we can derive a verifiable Block interface over HTTP or any other URL based address. This could even be used with `BitSwap` using `bitswap://`. Filecoin Storage Providers (running boost) would be addressable via the [lowest cost read method available (HTTP GET Range in Piece)](https://boost.filecoin.io/http-retrieval#retrieving-a-full-piece). And you can provide multiple locations for the same content using a list. ```javascript { "op": "assert/location", "rsc": "https://web3.storage", "input": { "content": CID /* CAR CID */, "location": [ "https://r2.cf/bag...car", "s3://bucket/bag...car" ], "range": [ start, end ] /* Optional: Byte Range in URL } } ``` ### Equivalency Claims We also have cases in which the same data is referred to by another CID and/or multihash. Equivalency claims represent this association as a verifiable claim. ```javascript { "op": "assert/equals", "rsc": "https://web3.storage", "input": { "content": CID /* CAR CID */, "equals": CID /* Commp CID */ } } ``` We should expect content discovery services to index these claims by `"content"` CID, since that is standard across all claims, but since this is a cryptographic equivalency, equivalency claim aware systems are encouraged to index both. ### Inclusion Claims * Claims that a CID includes the contents claimed in another CID. * Multi-Block Level Interface, * One CID is included in another CID. * When that's a CARv2 CID, the CID also provides a block level index of the referenced CAR CIDs contents. * Using CAR CIDs and CARv2 Indexes, get a multi-block interface. * Combined with HTTP location information for the CAR CID, this means we can read individual block sections using HTTP Ranges. ```javascript { "op": "assert/inclusion", "rsc": "https://web3.storage", "input": { "content": CID /* CAR CID */, "includes": CID /* CARv2 Index CID */, "proof": CID /* Optional: zero-knowledge proof */ } } ``` This can also be used to provide verifable claims for sub-deal inclusions in Filecoin. ```javascript { "op": "assert/inclusion", "rsc": "https://web3.storage", "input": { "content": CID /* PieceCID (CommP) */, "includes": CID /* Sub-Deal CID (CommP) */, "proof": CID /* Sub-Deal Inclusion Proof */ } } ``` ### Partition Claims * Claims that a CID’s graph can be read from the blocks found in parts, * `content` (Root CID) * `blocks` (List of ordered CIDs) * `parts` (List of archives [CAR CIDs] containing the blocks) ```javascript { "op": "assert/partition", "rsc": "https://web3.storage", "input": { "content": CID /* Content Root CID */, "blocks": CID, /* CIDs CID */ "parts": [ CID /* CAR CID */, CID /* CAR CID */, ... ] } } ``` # Consuming Claims An IPFS client wishing to perform a verifiable `read()` of IPFS data can construct one from verifiable claims. Given the amount of cryptography and protocol expertise necessary to perform these operations a few examples are detailed below. ## IPFS File Publishing An IPFS File is a contiuous set of bytes (source_file) that has been encoded into a merkle-dag. The resulting tree is referenced by CID using the `dag-pb` codec. When files are encoded and published into the network, they are often packed into CAR files. One CAR file might contain **many** IPFS Files, and one large file could be encoded into **many** CAR Files. CAR files can be referenced by CID (digest of the CAR) and are typically exchanged transactionally (block level interface). This can get confusing, as transaction (block level) interfaces now ***contain*** multi-block interfaces. ### Large File Large file uploads can be problematic when performed as a single pass/fail transaction, so it has become routine to break large upload transactions into smaller chunks. The default encoder for web3.storage (w3up) is configurable but defaults to ~100MB, so as the file is encoded into the unixfs block structure it is streamed to CAR encodings of the configured size. Each of these is uploaded transactionally, and if the operation were aborted and started again it would effectively resume as long as the file and settings hadn't changed. When the encode is complete the root CID of the unixfs merkle tree will be available as a `dag-pb` CID. This means we need a claim structure that can cryptographically route from this `dag-pb` CID to the data ***inside*** the resulting CAR files. Content Claims allow us to expose cryptography in the IPFS encoding to client such that clients can read **directly from the encoded CAR**, rather than requiring an intermediary to *assemble* the unixfs merkle tree for them. So, after we stream encode a bunch of CARs and upload them, we encode and publish: * A **Partition Claim** for the ***Content Root CID***, which includes: * An ordered list of every CID we encoded. * A list of CAR CIDs where those CIDs can be found. * **Inclusion Claims** for every ***CAR CID***, which includes: * A CARv2 index of the CAR file. Now, if we publish these CAR files to Filecoin, we're going to want to capture another address (CommP) for the CAR file. We then include an * **Equivalency Claim** for every ***CAR CID***, which claims this CAR CID is equivalent to CommP for that CAR file. Once that CAR data is aggregated into Filecoin deals, the list of CommP addresses included in each deal can be used to compute a sub-inclusion proof. At this time you may also publish: * **Inclusion Claims** for every ***Sub-Deal CID*** (CommP) which claims each Sub-Deal CID is "included" in each Piece CID (also CommP), which includes: * The referenced sub-deal inclusion proof. From this point forward, you can lookup Filecoin Storage Providers onchain 😁 The preferred (lowest cost) method of reading data from Storage Providers is through an HTTP interface that requests data by Piece CID. You could describe these locations, perhaps playing the role of chain oracle, as: * **Location Claims** for every ***CAR CID*** in every Storage Provider, which includes: * The offsets in the Piece addressable by HTTP Range Header. And of course, you can also use **Location Claims** for any other HTTP accessible storage system you ever decide to put a CAR into. ### *`read(DagPBCID, offset, length)`* Now that we have a better idea of what sorts of claims are being published, let's construct a read operation from the claims. It's out of scope for this specification to define the exact means by which you **discover** claims, but content discovery systems are expected to index these claims by `content` CID (in theory, you could index every block in the CARv2 indexes, but we should assume many actors don't want to pay for that, so everything in this specification works with only requires indexing the `content` CID in each claim). However, once a publisher sends these claims in public networks, they should presume all exposed addresses in the claims are "discoverable" from a security and privacy perspective. Once you have the claims for a given `dag-pb` CID, like the claim examples above, you can: * Build a Set() of CAR CIDs claimed to be holding relevant blocks and * Build a Map() of inclusions indexed by CAR CID and * depending on your CARv2 library you may want to parse the CARv2 indexes into something you can read quickly from, and * Build a Map() of locations indexed by CAR CID, * and you may even be able to build those concurrently. Take the list of blocks from the partition claim and find the smallest number of CAR CIDs containing a complete Set. Using the CARv2 indexes, you can now determine the byte offsets within every CAR for every block. You should also take the time to improve the read performance by closing the distance between many of those offsets so that you have fewer individual reads for contiuous sections of data, that will also reduce the burden your client will put on data providers since the same data will be fetched in fewer requests. You're now free to request this data from all CARs concurrently in a single round-trip, wherever they are. #### From Remote HTTP Endpoint You can use HTTP Range Headers to request the required block sections over HTTP. Keep in mind that CAR CID claims have support for offsets that would need to be included in the offset calculation you'd then need to perform *within* the CAR. #### From Filecoin Sub-Inclusion Proofs Pretty simple really: * The **Equivalency Claims** tells you the sub-deal CID. * The **Inclusion Claims** for those sub-deal CIDs give you the Piece CIDs that Storage Providers commit to on chain. #### From BitSwap 🤯 One amazing thing about these protocols is that by simply using a BitSwap URL, we have BitSwap peers as "locations" for CAR CIDs in the same **Location Claim** protocol. One could build a system/protocol for "loading" CAR files into BitSwap peers and then publish Location Claims. Even over BitSwap, this would represent a performance improvement over the current client protocols as you avoid round-trips traversing the graph. ## IPLD Schema ```ipldsch # For UCAN IPLD Schema see # https://github.com/ucan-wg/ucan-ipld type URL string type URLs [URL] type CIDs [&Any] type Assertion union { | AssertLocation "assert/location" | AssertInclusion "assert/inclusion" | AssertPartition "assert/partition" } representation inline { discriminantKey "op" } type AssertLocation { on URL input ContentLocation } type AssertLocation { on URL input ContentInclusion } type AssertLocation { on URL input ContentPartition } type Range { start Int end Int } representation tuple type Locations union { | URL string | URLs list } representation kinded type ContentLocation { content &Any location Locations range optional Range } type ContentInclusion { content &Any includes &Any proof optional &Any } type ContentPartition { # Content that is partitioned content &Any # Links to the CARs in which content is contained parts [&ContentArchive] # Block addresses in read order blocks &CIDs } ``` # Encodingless IPFS Here we will describe some general cryptography that arrives at an "Encodingless IPFS" as well as a specific use of such cryptography in the form of UCAN based content claims. By "encodingless" we mean that IPFS file trees are never actually encoded and written to disc or network. This removal of the file tree as a primary point of representation allows us to treat all virtual memory space, and space within that space, as essentially open ended and appearing in the form of numerous encodings and transports including all existing encodings and transports. Perhaps the most obvious feature of this cryptography is that by not being described as a new encoding it works with all existing data on the internet in whatever system that data is already in as it already appears. This conquers many "second system" challenges inherent in IPFS today. ## Generic Inclusions To the extent that any new memory is written into by way of cryptographic process, here it will merely be the packing of hashes of the same algorithm and length together. We don't assume or require persistence of the packed entity, as it is often only necessary to build in order to arrive at a local proof. When referencing these proofs, the address is assumed to be using the same hash algorithm and length as the hashes appearing in it, and is referred to by RAW CID when encoding as a CID. The reason we stick with RAW is because the `multihash` already contains the ONLY variable we have consumed in this representation. Having only one possible representation (per hash function) means we don't take on the indeterminism of the CID's codec field unnecessary (as we have no useful codec information to signal). ## Multihash Inclusion Claims First, we need a simple inclusion claim. ```javascript { "op": "assert/multihash-inclusions", "rsc": "https://web3.storage", "input": { "content": RawCID /* Hash of Content */, "includes": RawCID /* Hash of Proof */ } } ``` This claims the included proof occurs *within* the content. In the case of a raw file, the "content" hash would be the regular hash of that file, and "includes" would be the hash of a proof that is a bunch of hashes that are claimed to *be included* in the content. If consumers of the claim already have information they can use to arrive at the these inclusions, this is all you need. But, most consumers will benefit greatly from such information being encoded into the claim so there are the following optional parameters. ### Byte Length and Range The following options define information that allows clients to easily find inclusions in byte representations. ```javascript { "op": "assert/multihash-inclusions", "rsc": "https://web3.storage", "input": { "content": RawCID /* Hash of Content */, "includes": RawCID /* Hash of Proof */, "lengths": [ 1024, 1024, 5 ] } } ``` ```javascript { "op": "assert/multihash-inclusions", "rsc": "https://web3.storage", "input": { "content": RawCID /* Hash of Content */, "includes": RawCID /* Hash of Proof */, "ranges": [ [0, 3], [17, 99], [4, 7] ] } } ``` Note that, because the proofs and information arriving at those proofs are separate, the utility of both the inclusion proofs and the flexibility to encode and transmit proof assembly information are expanded and do not result in any loss in determinism because the encoding of numbers is left to other encoding layers that are presumed to already have a consensus regarding number encodings. ## Diffing and Updating Files Consider a use case in which a game has two downloadable assets: Asset-A and Asset-B. Asset-B is a variance of Asset-A. Asset-A and Asset-B may be updated in the future and these updates may or may not coincide. Here we provide the means for the theoretical smallest possible data throughput necessary to distribute these assets, assuming the publisher, or *any consumer of the data in a position to write their own client*, is willing to publish a claim using whatever their preferred [content defined chunking algorithm](https://joshleeb.com/posts/content-defined-chunking.html). Clients are presumed to hold a consensus about chunking outside this specification but claim authors are encouraged to use the open address space in UCAN claims to describe these chunkers as best they can in-protocol. We will also presume that files are being distributed in their original form. This may mean being distributed along with the original game, or independently from an HTTP server. If the asset location appears in a claim then it would look like other location claims. ```javascript { "op": "assert/location", "rsc": "https://web3.storage", "input": { "content": RawCID /* Hash of Content */, "location": 'http://assert.server/Asset-A.fbx' } } ``` ### Diffing In the case that a user has already downloaded Asset-A, they can apply the chunker locally and arrive a local proof. If they aquire the proof for Asset-B and a location claim for Asset-B then they should be able to read only the sectors of the file that differ from Asset-A and Asset-B. The computation of the diff never has to be done ahead of time so long as there is consensus about the chunker settings. ### Updating Since updates to local files are computed the same way for updates and diffs there is nothing else to do. In fact, given the likelihood that many assets share sectors it's best to gain as much knowledge through local proofs as possible whenever updating or looking to aquire new files. All of this can be done without any new network or consensus protocol, with data wherever it already exists in whatever protocol of whatever generation of the internet it happens to be in (FTP is back! #probablynot). ## Equivalencies The above examples have been cases in which the local client still arrives at a complete state, which puts them in a position to verify the entire claim. A hazard exists in content addressed networks if claims are trusted in scenarios in which they are never completely verified. Rather than a game asset, let's use the example of a video. ```javascript { "op": "assert/multihash-inclusions", "rsc": "https://web3.storage", "input": { "content": RawCID /* Hash of Content */, "includes": RawCID /* Hash of Proof */, "lengths": [ 38, 1024, 1024, ... ] } } { "op": "assert/location", "rsc": "https://web3.storage", "input": { "content": RawCID /* Hash of Content */, "location": 'http://assert.server/video.mkv' } } ``` Now, in a content addressed network, if I were to play this video I'd want to ask for the first couple blocks worth of data in the inclusion proof first. Similar to when I read data in the diffing and updating use cases, I can read the early ranges of data and verify each block against the proof. However, if I start playing this video to a user, I will have compromised the security the entire system of cryptography everyone built to make this safe 😅 Until we have the entire video, we don't know for certain that any of these blocks are actually included in the "content." It's still a "claim" and while it is verifiable it has no means of **partial** or **incremental** verification because the only thing that connects the content hash and the proof information is an as-yet-to-be-verified claim. We have, at this point, the cryptography to create hash addressed virtual memory ("content") and can even go as far as to understand that address as a larger address space with verifiable parts as long as we verify the entirety of the inclusion before we consider it authenticated. For a video, the distance between the sub-inclusion authentication (part) and the parent (file) are not something a user can wait on before trust is established. Numerous systems exist to solve this with a merkle tree encoding, IPFS being an obvious example. Since the root hash is derived from leaves readers have means of *incremental verification* that begins with at the first block. We will solve those same problems here **not** by defining a *new* fully deterministic tree encoding but by simply posting verifiable claims to deterministally generatable encodings. Any new encoding will, by definition, be indeterministic since its own choice of encoding is a branch of determinism from the single conceptual root of the un-encoded state. Posting a cryptographic equivalency between the hash of a file and an identifier for an encoded tree representation of that file have the result of offering **all** the benefit of that tree *without creating a persistent secondary encoding of the original data*. Even better, since such trees are derived from an entirely deterministic representation of the proof they do not have the same indeterminism problems IPFS-alone has. In fact, this should give content authors the ability to post equivalencies between variances in such representations. And this isn't limited to IPFS. Torrent files can also be generated in much the same way, as can any future merkle encoding of files. Folks have noticed that blake3 hashes have some interesting characteristics that can substitute for many of the afforementioned IPFS tree characteristics. Such characteristics will appear here next to **any** content hashed under **any** algorithm the moment an equivalency claim is posted. ```javascript { "op": "assert/equals", "rsc": "https://web3.storage", "input": { "content": RawCID /* Hash of Content */, "equals": Blake3CID } } ``` ### Deterministic UnixFS File Since there are so many potential tree encodings we will concern ourselves with only one in this specification, and that is a simple, flat, deterministic unixs-fs encoding. Rather than consider this an exercise in either unixfs tree shaping or protobuf encoding lets just arrange bytes around their equivalent proof information to arrive at the smallest byte representation available in valid unixfs as a single block. Since the tree is not expected to be persisted or transported, there's no basis from which to form a block size or appropriate tree shape so "smallest bytes" seems the only relevant factor to consider. ```javascript { "op": "assert/equals", "rsc": "https://web3.storage", "input": { "content": RawCID /* Hash of Content */, "equals": DagPbCID /* IPFS File CID */, "deterministic": true } } ``` TODO: maybe gozala can author this encoding :)

    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