Skip to content
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

Add private sharing & refactor paths #83

Merged
merged 7 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions examples/audio/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "demo",
"name": "audio",
"type": "module",
"version": "1.0.0",
"private": true,
"description": "",
"author": "Steven Vandevelde <[email protected]> (tokono.ma)",
Expand Down
1 change: 0 additions & 1 deletion examples/demo/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "demo",
"type": "module",
"version": "1.0.0",
"private": true,
"description": "",
"author": "Steven Vandevelde <[email protected]> (tokono.ma)",
Expand Down
3 changes: 1 addition & 2 deletions examples/web3storage/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "demo",
"name": "web3storage",
"type": "module",
"version": "1.0.0",
"private": true,
"description": "",
"author": "Steven Vandevelde <[email protected]> (tokono.ma)",
Expand Down
6 changes: 3 additions & 3 deletions examples/web3storage/rsbuild.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineConfig } from "@rsbuild/core";
import { defineConfig } from '@rsbuild/core'

export default defineConfig({
html: {
template: "./src/index.html",
template: './src/index.html',
},
});
})
75 changes: 64 additions & 11 deletions packages/nest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ A layer around the `wnfs` package that provides a `FileSystem` class, a root tre

- A file system class that allows for an easy-to-use mutable API.
- A root tree, holding references to all the needed individual parts (public fs, private forest, exchange, etc)
- A unix-fs compatibility layer for the public file system (allows for public files to be viewed through, for example, IPFS gateways)
- A mounting system for private nodes, mount specific paths.
- A unix-fs compatibility layer for the public file system (allows for public files to be viewed through, for example, IPFS gateways)
- Private data sharing + helpers
- Provides a transaction system, rewinding the state if an error occurs.
- Creates a private forest automatically with a RSA modules using the Web Crypto API (supported on multiple platforms)
- Creates a private forest automatically with a RSA modulus using the Web Crypto API (supported on multiple platforms)
- In addition to the default symmetric key, use an RSA-OAEP asymmetric key to mount a private node (essentially sharing to self). Can be used to load a private directory, or file, using a passkey + the PRF extension.
- Ability to verify commits to the file system. If a commit, aka. modification, is not verified, it will result in a no-op.
- And more: typed paths, events, path helpers, data casting, …

Expand All @@ -23,17 +25,15 @@ pnpm install @wnfs-wg/nest

## Usage

Scenario 1:<br />
🚀 Create a new file system, create a new file and read it back.

```ts
import { FileSystem, Path } from '@wnfs-wg/nest'

// Provide some block store of the `Blockstore` type from the `interface-blockstore` package
import { IDBBlockstore } from 'blockstore-idb'
```

Scenario 1:<br />
🚀 Create a new file system, create a new file and read it back.

```ts
const blockstore = new IDBBlockstore('path/to/store')
await blockstore.open()

Expand All @@ -42,14 +42,14 @@ const fs = await FileSystem.create({
})

// Create the private node of which we'll keep the encryption key around.
const { capsuleKey } = await fs.mountPrivateNode({
const { capsuleKey } = await fs.createPrivateNode({
path: Path.root(), // ie. root private directory
})

// Write & Read
await fs.write(Path.file('private', 'file'), 'utf8', '🪺')
await fs.write(['private', 'file'], 'utf8', '🪺')

const contents = await fs.read(Path.file('private', 'file'), 'utf8')
const contents = await fs.read(['private', 'file'], 'utf8')
```

Scenario 2:<br />
Expand All @@ -66,7 +66,7 @@ of our root tree, the pointer to our file system.
let fsPointer: CID = await fs.calculateDataRoot()

// When we make a modification to the file system a verification is performed.
await fs.write(Path.file('private', 'file'), 'utf8', '🪺')
await fs.write(['private', 'file'], 'utf8', '🪺')

// If the commit is approved, the changes are reflected in the file system and
// the `commit` and `publish` events are emitted.
Expand Down Expand Up @@ -120,6 +120,59 @@ fs.rename
fs.write
```

## Identifier

```ts
fs.identifier()
fs.assignIdentifier('did')
```

## Private Data Sharing

Flow:

1. The receiver of a share register their exchange key. An app could do this automatically when the app starts, or at some other time.
2. The data root of the receiver is passed to the sharer. Ideally this is done through some naming system. For example, you use DNS to map a username to the data root (eg. `TXT file-system.tokono.ma` could resolve to the data root, a CID). That said, this could also be done without a naming system, maybe by presenting a QR code.
3. Make sure the sharer's file system has an identity assigned.
4. The sharer creates the share.
5. This step is the reverse of step 2, where we pass the sharer's data root to the receiver.
6. Use the shared item.

```ts
// Step 1 & 2 (Receiver)
const { dataRoot } = await fs.registerExchangeKey('key-id', publicKey)
const receiverDataRoot = dataRoot

// Step 3, 4 & 5 (Sharer)
await fs.assignIdentifier('did')

const { dataRoot } = await fs.share(pathToPrivateItem, receiverDataRoot)
const sharerDataRoot = dataRoot

// Step 6 (Receiver)
const share = await fs.receive(sharerDataRoot, { publicKey, privateKey })

await share.read('utf8')
```

## Manage private node using exchange key pair

Instead of keeping the (symmetric) capsule key around we can use an (asymmetric) exchange key pair to mount a private node. This basically creates a share for ourselves.

```ts
// 🚀 Create & mount
await fs.createPrivateNode({
path: Path.root(),
exchangeKeyPair: { publicKey, privateKey }, // 🔑 Pass in key pair here
})

// 🧳 Load
await fs.mountPrivateNode({
path: Path.root(),
exchangeKeyPair: { publicKey, privateKey },
})
```

## Transactions

```ts
Expand Down
12 changes: 10 additions & 2 deletions packages/nest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
"types": "./dist/src/events.d.ts",
"default": "./dist/src/events.js"
},
"./exchange-key": {
"types": "./dist/src/exchange-key.d.ts",
"default": "./dist/src/exchange-key.js"
},
"./path": {
"types": "./dist/src/path.d.ts",
"default": "./dist/src/path.js"
Expand Down Expand Up @@ -80,6 +84,9 @@
"events": [
"dist/src/events"
],
"exchange-key": [
"dist/src/exchange-key"
],
"path": [
"dist/src/path"
],
Expand All @@ -99,7 +106,7 @@
"src"
],
"scripts": {
"lint": "tsc --build && eslint . --quiet --ignore-pattern='README.md' && prettier --check '**/*.{js,ts,yml,json}' --ignore-path ../../.gitignore",
"lint": "tsc --build && eslint . --quiet && prettier --check '**/*.{js,ts,yml,json}' --ignore-path ../../.gitignore",
"build": "tsc --build",
"test": "pnpm run test:node && pnpm run test:browser",
"test:node": "mocha 'test/**/!(*.browser).test.ts' --bail --timeout 30000",
Expand Down Expand Up @@ -142,7 +149,8 @@
"mocha": true
},
"ignorePatterns": [
"dist"
"dist",
"README.md"
],
"rules": {
"@typescript-eslint/no-unused-vars": [
Expand Down
Loading