-
Notifications
You must be signed in to change notification settings - Fork 5.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Imports: Support imports from IPFS hashes #5290
Comments
How would you be able to identify which package you are importing if it's just a hash in the import statement? |
Doc comments on your |
The other option is sticking to a convention? You can reference linked data within an IPFS IPLD DAG structure (the stuff behind the hash) by name. This requires 'wrapping' the IPFS content in an external folder, but this isn't particularly burdensome.
|
That also works, and preserves the core immutability features we're looking for. Although, I think that import string would be better as
to preserve the fallback options I proposed above. |
Was going to create the exact same issue 😅 {
"imports": {
"library/": "p2p://<hash>"
}
} import foo from 'library/foo.js' And to go the extra mile that registry can just be another json file on ipfs managed by a decentralized blockchain based organization that is economically incentivized to keep a high quality registry with possibly different channels like |
IPFS is built on libp2p - it has both node.js and in-browser support. |
From a security perspective, IPFS is not completely necessary, nor does it actually solve the issue of content-defined library imports - since it provides no guarantee that the requested file would internally use a secure hash scheme to import its own dependencies, and so would their own dependencies' dependencies, and so on. I think it would also be productive to consider a URI scheme that is independent of any specific protocol (e.g.
For compatibility and future proofing: it seems reasonable to use IPFS's multiformat scheme that allows to support various kinds of hash algorithms. Edit: The filename doesn't necessarily need to embed the hash. This scheme may be as secure (not completely sure, need to consider it further..):
(Using a |
After thinking about this further I've found a simple alternative solution that guarantees 100% content addressed safety and reproducibility for the entire dependency tree: It would look like this:
Where The file might look like something like this (or might use JSON etc. this is only shown for simplicity):
Since the lock file is content addressed, it can be fetched from anywhere, either from IPFS, or from the web server itself (say in The lock file can also be effectively used as an IPFS-like merkledag (though unlike in IPFS, it doesn't represent a directory structure, but a collection of references to various sources), but since all the references use Technically, if some of the imports in the dependency tree already refer to their own lock file, then it may not be strictly necessary to include them in the lock file. However, since the storage requirements of a structure like this are relatively minimal in modern standards, it may be better not to rely on multiple sources and include everything in one file (even if techincally redundant). |
ha! I had similar thoughts here: denoland/dotland#406 (comment) |
@srdjan I've modified this approach through several iterations and eventually came to a solution that doesn't actually require the library authors to know anything about the existance of a lock file or even the hashing scheme. I'll try to clarify some aspect for the design : The hash part (
The lock file is individual to the dependecy tree of a particular
However, it doesn't have to. It can be stored locally or fetched from a p2p network like IPFS. As an importer of a third-party |
Alright, after considering it even more, I've realized that the lock file isn't even strictly necessary to be stored anywhere. Instead, it can be regenerated purely based on the content and structure of the dependency tree for verification purposes. It's actually pretty simple: Say I want to import this URI, but I also want a strong proof that ensures that what I get is always the same: import * from "https://example.com/path/name@version/module.ts" I use a tool that walks the dependency tree of import * from "https://example.com/path/name@version/module.ts#lock_cid=QmaLRet8qeYqNaq8xJeiqwjNnukSo3uEA8oWsDLoxxBv4Q" Now whenever someone encounters this annotated link. They have two options:
That's pretty much it. |
As an update, this happened. Chrome extended support for custom protocol handlers to include, among others, support for |
Going off of some comments that have been posed in this thread, would it make sense for there to be a convention that utilizes IPNS as part of its base path to host modules? So someone responsible for creating a module could host the directory at
Then the directory could be declared from within a {
"imports": {
"alias_name": {
"cid": "ipns/<pubkey_hash>",
"modules": ["foo_module", "bar_module"]
}
}
} The files themselves could then be imported as such: import React from "ipfs:alias_name/foo_module" The runtime could check the signatures of the modules being requested for import and error if they don't line up, or if the pubkey doesn't match its IPNS hash. |
For me, it seems like IPNS would kind of defeat the purpose of IPFS for Deno. I'd like the hash to be in the import statement, and the URL to always point to the same content, instead of needing a separate package.json file to map names to hashes. At that point, it just seems like a package lock file to a regular website whose content can change. |
IPFS uses multiaddr, which is incompatible with URL. |
Just wanted to chime in that it would be nice if we could have a way to add new protocol schemes for imports (or maybe for fetch as well from within code). I'm the main developer of Agregore, a p2p web browser that supports stuff like IPFS. I'd love it if we could reuse modules published for p2p web browsers like Agregore within Deno. In Agregore, we've extended the browser's protocol handlers to support ipfs/ipns/etc not just for reading with GET, but also for writing with PUT. On desktop we're doing this via the protocol API provided by Electron, and via some C++ changes on mobile. Similarly, the Node.js VM API enables us to provide custom "linkers" which can enable customizing module resolution for custom protocols (something I've played around with in webrun). Having a way to dynamically add protocol scheme handlers in Deno would make it easier to integrate stuff like IPFS in user-land without needing large dependencies. |
Actually, yeah, I think that would be a good way to go about it. If you could register new protocol schemes for Deno (probably with a |
While that would be a good start, and not something I'm opposed to, I think it would be a mistake for IPFS to not have built-in support. For example:
IPFS solves that problem (anyone could host the color package source code and the scripts could find it). But, If the only IPFS support is through custom protocols, my understanding is we're going to need a non-IPFS import, before we can use IPFS imports. E.g. it's still centralized with a possible single point of failure, unless we go with the primitive solution of pasting the entire custom protocol library at the top every time. TLDR; decentralization/stability only works if IPFS is natively supported. |
Yeah. It'd be good if it was built-in. It might not even be that hard - has anyone tried running js-ipfs inside Deno? It might just work. Sure, implementing support for talking to a user's existing node would be good, but I'm pretty sure the JS version is stable enough for our needs here. EDIT: Looks like the esm.sh transpilation CDN is having issues at the moment. The following should work right, but it isn't. import * as IPFS from "https://esm.sh/ipfs-core@0.15.4";
const node = await IPFS.create();
const stream = node.cat("QmPChd2hVbrJ6bfo3WBcTW4iZnpHm8TEzWkLHmLpXhF68A");
const decoder = new TextDecoder();
let data = "";
for await (const chunk of stream) {
// chunks of data are returned as a Uint8Array, convert it back to a string
data += decoder.decode(chunk, { stream: true });
}
console.log(data); // should output "Hello, <YOUR NAME HERE>" |
I've been getting familiar with running Deno in Rust, so I was thinking about exploring implementing it on the backend. The IPFS client for rust still needs some work though. The JavaScript client is probably the faster way. I might attempt to bundle it through parcel to esm. As a start, Deno could just check for files under |
Since deno hasn't got the infrastructure for custom protocols yet, I've started sketching up a tool based on Node.js in the meantime. 😁 https://github.com/AgregoreWeb/agregore-cli/blob/main/test.js#L34 With this it should be possible to run modules from IPFS and to customize the APIs available to them. (at least to start experimenting). Since it's only providing web APIs any scripts written for it should be portable to deno down the line. |
In Python when installing dependencies you can specify hashes:
Poetry by default generates lock file with hashes and if you export dependencies to a requirements.txt file it will include hashes as well. That way you can be sure nobody tempered with the content of your dependency module. |
We are not going to do this. |
Sad, but understandable |
I'm also sad, but I appreciate the clear communication. |
The problem
Files at URLs are mutable - they can be changed or deleted at any time, by anyone with access - whether that access is legitimate or not. If the host gets hacked, bad code could be injected for anyone who fetches it.
The solution
Imports from IPFS. IPFS is a content-addressed globally-distributed filesystem. Files are identified by a hash of their contents, so they can never be modified without changing the file identifier. A given hash will always point to the exact same file, forever.
Additionally, as it's globally distributed, the chance of files disappearing forever when they're depended on is nearly zero. No more of this. And none of this.
Implementation options
Right now, the IPFS community seems to be standardizing on
ipfs://
as a URI scheme for files on IPFS. We can use that to identify when an import is from IPFS, as opposed to a HTTP(S) import, and act accordingly.Now that we have the file identifier, there's a few options for fetching it.
Use the local IPFS node. Chances are, if a user wants to import from IPFS they're running a node that we can talk to to resolve files. IPFS nodes have an HTTP API that typically runs (for localhost) on 127.0.0.1:5001, and allows you to get files that way. An import from
ipfs://{hash}
can be fetched fromhttp://127.0.0.1:5001/ipfs/{hash}
- we can also optionally pin files to the user's node so they're reprovided to others on the IPFS network. Full local HTTP API docs are here.Use a public IPFS gateway. There are a fair number of them, and known gateways and their status are tracked here. File resolution is always at
{gateway}/ipfs/{hash}
.Run a local IPFS node. This would be the most difficult to handle, as it's not just a string manipulation to translate an
ipfs://
import into ahttp(s)://
import.Personally? I recommend using the installed local node and falling back to a select list of public gateways.
The text was updated successfully, but these errors were encountered: