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

Share index + identifier (custom + improvements) #86

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
59 changes: 58 additions & 1 deletion packages/nest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ const { dataRoot } = await fs.registerExchangeKey('key-id', publicKey)
const receiverDataRoot = dataRoot

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

const { dataRoot } = await fs.share(pathToPrivateItem, receiverDataRoot)
const sharerDataRoot = dataRoot
Expand All @@ -155,6 +155,63 @@ const share = await fs.receive(sharerDataRoot, { publicKey, privateKey })
await share.read('utf8')
```

## Receiving older/multiple shares

By default when using `fs.receive()` you'll receive the latest share made to the given public key and matching the identifier of the sharer. If you want to load older, or multiple, shares, you'll have to provide the share index.

```ts
// `share` gives you the `shareIndex`, along with the `dataRoot`
const { shareIndex, dataRoot } = await fs.share(
pathToPrivateItem,
receiverDataRoot
)

// This can then be used by the receiver
const share = await fs.receive(
sharerDataRoot,
{ publicKey, privateKey },
{ shareIndex }
)
```

If you don't want to pass along the share index, you can see if any shares are available for a given number (starts from zero).

```ts
// To do this, load the file system of the sharer.
const sharerFs = await FileSystem.fromCID(sharerDataRoot, { blockstore })

const bool = await sharerFs.hasShare({
exchangePublicKey,
shareIndex: 0,
})

if (bool === true)
console.log(`Share with index '0' exists for public key: ${publicKey}`)
```

## Sharing a private file with a custom identifier

Instead of using `fs.assignIdentifier()`, you can pass an identifier straight to the `fs.share()` function. Do note that this identifier will have to be communicated to the receiver of the share, which is not the case when using `fs.assignIdentifier()`. Unless of course the receiver can somehow determine this identifier themselves.

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

// Sharer
const { dataRoot } = await fs.share(pathToPrivateItem, receiverDataRoot, {
identifier: 'id',
})
const sharerDataRoot = dataRoot

// Receiver
const share = await fs.receive(
sharerDataRoot,
{ publicKey, privateKey },
{ identifier: 'id' }
)
```

## 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.
Expand Down
34 changes: 19 additions & 15 deletions packages/nest/src/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ export class FileSystem {
): Promise<{
path: Path.Partitioned<Private>
capsuleKey: Uint8Array
shareId: string
shareIndex: number
}>
async createPrivateNode(
node: {
Expand All @@ -300,7 +300,7 @@ export class FileSystem {
): Promise<{
path: Path.Partitioned<Private>
capsuleKey: Uint8Array
shareId?: string
shareIndex?: number
}> {
const { kind, path } = node
const absolutePosixPath = Path.toPosix(path, { absolute: true })
Expand Down Expand Up @@ -382,14 +382,14 @@ export class FileSystem {

// Share to self, pt. 2
if (node.exchangeKeyPair !== undefined) {
const { shareId } = await this.share(pathWithPartition, dataRoot, {
const { shareIndex } = await this.share(pathWithPartition, dataRoot, {
mutationOptions,
})

return {
path: pathWithPartition,
capsuleKey: accessKey.toBytes(),
shareId,
shareIndex,
}
}

Expand Down Expand Up @@ -418,7 +418,7 @@ export class FileSystem {
publicKey: CryptoKey | Uint8Array
privateKey: CryptoKey
}
shareId?: string
shareIndex?: number
}
): Promise<void> {
await this.mountPrivateNodes([node])
Expand Down Expand Up @@ -446,7 +446,7 @@ export class FileSystem {
publicKey: CryptoKey | Uint8Array
privateKey: CryptoKey
}
shareId?: string
shareIndex?: number
}
>
): Promise<void> {
Expand All @@ -470,7 +470,7 @@ export class FileSystem {
await this.calculateDataRoot(),
args.exchangeKeyPair,
{
shareId: args.shareId,
shareIndex: args.shareIndex,
sharerBlockstore: this.#blockstore,
}
).then((a) => a.sharedNode)
Expand Down Expand Up @@ -854,7 +854,8 @@ export class FileSystem {
* @param exchangeKeyPair.publicKey A RSA-OAEP-256 public key in the form of a `CryptoKey` or its modulus bytes
* @param exchangeKeyPair.privateKey A RSA-OAEP-256 private key in the form of a `CryptoKey`
* @param opts Optional overrides
* @param opts.shareId Specify what shareId to use, otherwise this'll load the last share that was made to the given exchange key.
* @param opts.identifier Specify which identifier was used for the share, by default it looks up the sharer's root identifier.
* @param opts.shareIndex Specify what share index to use, otherwise this'll load the last share that was made to the given exchange key.
* @param opts.sharerBlockstore Specify what blockstore to use to load the sharer's file system.
* @param opts.sharerRootTreeClass Specify what root tree class was used for the sharer's file system.
*
Expand All @@ -867,7 +868,8 @@ export class FileSystem {
privateKey: CryptoKey
},
opts: {
shareId?: string
identifier?: string
shareIndex?: number
sharerBlockstore?: Blockstore
sharerRootTreeClass?: typeof RootTree
} = {}
Expand Down Expand Up @@ -916,6 +918,7 @@ export class FileSystem {
* @param path Path to the private file or directory to share (with 'private' prefix)
* @param receiverDataRoot Data root CID of the receiver
* @param opts Optional overrides
* @param opts.identifier Provide another id to use instead the root identifier
* @param opts.receiverBlockstore Specify what blockstore to use to load the receiver's file system
* @param opts.receiverRootTreeClass Specify what root tree class was used for the receiver's file system
* @param opts.mutationOptions Mutation options
Expand All @@ -926,29 +929,30 @@ export class FileSystem {
path: Partitioned<Private>,
receiverDataRoot: CID,
opts: {
identifier?: string
mutationOptions?: MutationOptions
receiverBlockstore?: Blockstore
receiverRootTreeClass?: typeof RootTree
} = {}
): Promise<{ shareId: string } & MutationResult<Private>> {
let shareId: string | undefined
): Promise<{ shareIndex: number } & MutationResult<Private>> {
let shareIndex: number | undefined

const result = await this.#infusedTransaction(
async (t) => {
const shareResult = await t.share(path, receiverDataRoot, opts)
shareId = shareResult.shareId
shareIndex = shareResult.shareIndex
},
path,
opts.mutationOptions
)

if (shareId === undefined) {
throw new Error('`shareId` was not set')
if (shareIndex === undefined) {
throw new Error('`shareIndex` was not set')
}

return {
...result,
shareId,
shareIndex,
}
}

Expand Down
8 changes: 4 additions & 4 deletions packages/nest/src/share.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,29 @@ import { dataFromBytes } from './data.js'
// CLASS

export class Share {
readonly id: string
readonly index: number

readonly #blockstore: Blockstore
readonly #privateNodes: MountedPrivateNodes
readonly #rootTree: RootTree
readonly #rng: Rng

/**
* @param id
* @param index
* @param blockstore
* @param privateNodes
* @param rng
* @param rootTree
* @internal
*/
constructor(
id: string,
index: number,
blockstore: Blockstore,
privateNodes: MountedPrivateNodes,
rng: Rng,
rootTree: RootTree
) {
this.id = id
this.index = index
this.#blockstore = blockstore
this.#privateNodes = privateNodes
this.#rng = rng
Expand Down
38 changes: 20 additions & 18 deletions packages/nest/src/sharing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import { ExchangeKey } from './exchange-key.js'
* @param exchangeKeyPair.publicKey
* @param exchangeKeyPair.privateKey
* @param opts
* @param opts.shareId
* @param opts.identifier
* @param opts.shareIndex
* @param opts.sharerBlockstore
* @param opts.sharerRootTreeClass
*/
Expand All @@ -35,12 +36,13 @@ export async function loadShare(
privateKey: CryptoKey
},
opts: {
shareId?: string
identifier?: string
shareIndex?: number
sharerBlockstore: Blockstore
sharerRootTreeClass?: typeof RootTree
}
): Promise<{
shareId: string
shareIndex: number
sharedNode: PrivateNode
sharerRootTree: RootTree
}> {
Expand All @@ -58,30 +60,30 @@ export async function loadShare(

const sharerForest = sharerRootTree.privateForest()
const sharerCounter = sharerRootTree.shareCounter()
const sharerIdentifier = sharerRootTree.did()
if (sharerIdentifier === undefined)
const identifier = opts.identifier ?? sharerRootTree.did()

if (identifier === undefined)
throw new Error("The sharer's file system is missing an identifier")

// Find the share number
const shareNumber: undefined | number | bigint =
opts.shareId === undefined
? await findLatestShareCounter(
0,
sharerCounter < 1 ? 1 : sharerCounter,
publicKeyResult,
sharerIdentifier,
sharerForest,
Store.wnfs(sharerBlockstore)
)
: Number.parseInt(opts.shareId)
opts.shareIndex ??
(await findLatestShareCounter(
0,
sharerCounter < 1 ? 1 : sharerCounter,
publicKeyResult,
identifier,
sharerForest,
Store.wnfs(sharerBlockstore)
))

if (shareNumber === undefined)
throw new Error('Failed to determine share number')
throw new Error('Cannot find any share with these parameters')

// Determine share name
const shareLabel = createShareName(
Number(shareNumber),
sharerIdentifier,
identifier,
publicKeyResult,
sharerForest
)
Expand All @@ -105,7 +107,7 @@ export async function loadShare(
)

return {
shareId: Number(shareNumber).toString(),
shareIndex: Number(shareNumber),
sharedNode,
sharerRootTree,
}
Expand Down
Loading
Loading