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

Ucan-core refactor #82

Merged
merged 26 commits into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from 11 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
78 changes: 5 additions & 73 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,83 +5,15 @@
"author": "Daniel Holmgren <[email protected]>",
"repository": {
"type": "git",
"url": "https://github.com/fission-suite/ucan"
"url": "https://github.com/ucan-wg/ts-ucan"
},
"homepage": "https://guide.fission.codes",
"license": "Apache-2.0",
"engines": {
"node": ">=15"
},
"scripts": {
"prebuild": "rimraf dist",
"build": "yarn run dist",
"dev": "tsc --watch --module commonjs --outDir ./dist/cjs/ --sourceMap",
"dist": "yarn run dist:prep && yarn run dist:src && yarn run dist:cjs && yarn run dist:esm && yarn run dist:types && yarn run dist:pkg",
"dist:cjs": "tsc --project ./dist/ --module commonjs --outDir ./dist/cjs/ --sourceMap",
"dist:esm": "tsc --project ./dist/ --module es2020 --outDir ./dist/esm/ --sourceMap",
"dist:pkg": "node ./scripts/package.js",
"dist:prep": "copyfiles --error tsconfig.json ./dist/",
"dist:src": "copyfiles --error --up 1 \"./src/**/*\" ./dist/src/",
"dist:types": "tsc --project ./dist/ --emitDeclarationOnly --declaration --declarationDir ./dist/types/",
"lint": "eslint src/**/*.ts src/*.ts tests/**/*.ts tests/*.ts",
"prepare": "yarn build",
"publish-alpha": "yarn publish --tag alpha",
"publish-stable": "yarn publish --tag latest",
"test": "jest",
"test:watch": "jest --watch"
},
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"types": "./dist/types/index.d.ts"
},
"./*.js": {
"import": "./dist/esm/*.js",
"require": "./dist/cjs/*.js",
"types": "./dist/types/*.d.ts"
},
"./*": {
"import": "./dist/esm/*.js",
"require": "./dist/cjs/*.js",
"types": "./dist/types/*.d.ts"
}
},
"typesVersions": {
"*": {
"index.d.ts": [
"dist/types/index.d.ts"
],
"*": [
"dist/types/*"
]
}
},
"files": [
"dist",
"docs",
"CHANGELOG.md",
"LICENSE",
"README.md"
],
"dependencies": {
"@stablelib/ed25519": "^1.0.2",
"big-integer": "^1.6.51",
"one-webcrypto": "^1.0.3",
"uint8arrays": "^3.0.0"
},
"devDependencies": {
"@types/jest": "^27.4.1",
"@types/node": "^17.0.23",
"@typescript-eslint/eslint-plugin": "^5.18.0",
"@typescript-eslint/parser": "^5.18.0",
"copyfiles": "^2.4.1",
"eslint": "^8.12.0",
"fast-check": "^2.24.0",
"jest": "^27.5.1",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.4",
"typescript": "^4.6.3",
"yarn": "^1.22.18"
}
"private": true,
"workspaces": [
"packages/*"
]
}
File renamed without changes.
85 changes: 85 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"name": "@ucans/core",
"version": "0.9.1",
"description": "Core UCAN implementation",
"author": "Daniel Holmgren <[email protected]>",
"repository": {
"type": "git",
"url": "https://github.com/ucan-wg/ts-ucan"
},
"homepage": "https://guide.fission.codes",
"license": "Apache-2.0",
"engines": {
"node": ">=15"
},
"scripts": {
"prebuild": "rimraf dist",
"build": "yarn run dist",
"dev": "tsc --watch --module commonjs --outDir ./dist/cjs/ --sourceMap",
"dist": "yarn run dist:prep && yarn run dist:src && yarn run dist:cjs && yarn run dist:esm && yarn run dist:types && yarn run dist:pkg",
"dist:cjs": "tsc --project ./dist/ --module commonjs --outDir ./dist/cjs/ --sourceMap",
"dist:esm": "tsc --project ./dist/ --module es2020 --outDir ./dist/esm/ --sourceMap",
"dist:pkg": "node ../../scripts/package.js",
"dist:prep": "copyfiles --error tsconfig.json ./dist/",
"dist:src": "copyfiles --error --up 1 \"./src/**/*\" ./dist/src/",
"dist:types": "tsc --project ./dist/ --emitDeclarationOnly --declaration --declarationDir ./dist/types/",
"lint": "eslint src/**/*.ts src/*.ts tests/**/*.ts tests/*.ts",
"prepare": "yarn build",
"publish-alpha": "yarn publish --tag alpha",
"publish-stable": "yarn publish --tag latest",
"test": "jest",
"test:watch": "jest --watch"
},
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"types": "./dist/types/index.d.ts"
},
"./*.js": {
"import": "./dist/esm/*.js",
"require": "./dist/cjs/*.js",
"types": "./dist/types/*.d.ts"
},
"./*": {
"import": "./dist/esm/*.js",
"require": "./dist/cjs/*.js",
"types": "./dist/types/*.d.ts"
}
},
"typesVersions": {
"*": {
"index.d.ts": [
"dist/types/index.d.ts"
],
"*": [
"dist/types/*"
]
}
},
"files": [
"dist",
"docs",
"CHANGELOG.md",
"LICENSE",
"README.md"
],
"dependencies": {
"uint8arrays": "^3.0.0"
},
"devDependencies": {
"@types/jest": "^27.4.1",
"@types/node": "^17.0.23",
"@typescript-eslint/eslint-plugin": "^5.18.0",
"@typescript-eslint/parser": "^5.18.0",
"@ucans/plugins": "*",
"copyfiles": "^2.4.1",
"eslint": "^8.12.0",
"fast-check": "^2.24.0",
"jest": "^27.5.1",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.4",
"typescript": "^4.6.3",
"yarn": "^1.22.18"
}
}
File renamed without changes.
11 changes: 5 additions & 6 deletions src/builder.ts → packages/core/src/builder.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import * as token from "./token.js"
import * as util from "./util.js"

import { Keypair, Fact, UcanPayload, isKeypair, Ucan } from "./types.js"
import { Keypair, Fact, UcanPayload, isKeypair, Ucan, Didable } from "./types.js"
import { Capability, isCapability } from "./capability/index.js"
import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain } from "./attenuation.js"
import { Store } from "./store.js"
import { publicKeyBytesToDid } from "./did/transformers.js"


export interface BuildableState {
issuer: Keypair
issuer: Keypair & Didable
audience: string
expiration: number
}
Expand Down Expand Up @@ -85,7 +84,7 @@ export class Builder<State extends Partial<BuildableState>> {
*
* The UCAN must be signed with the private key of the issuer to be valid.
*/
issuedBy(issuer: Keypair): Builder<State & { issuer: Keypair }> {
issuedBy(issuer: Keypair & Didable): Builder<State & { issuer: Keypair & Didable }> {
if (!isKeypair(issuer)) {
throw new TypeError(`Expected a Keypair, but got ${issuer}`)
}
Expand Down Expand Up @@ -231,7 +230,7 @@ export class Builder<State extends Partial<BuildableState>> {
})
} else {
const store: Store = storeOrProof
const issuer = publicKeyBytesToDid(this.state.issuer.publicKey, this.state.issuer.keyType)
const issuer = this.state.issuer.did()
// we look up a proof that has our issuer as an audience
const result = util.first(store.findWithCapability(issuer, requiredCapability, issuer))
if (result != null) {
Expand Down Expand Up @@ -259,7 +258,7 @@ export class Builder<State extends Partial<BuildableState>> {
throw new Error(`Builder is missing one of the required properties before it can be built: issuer, audience and expiration.`)
}
return token.buildPayload({
issuer: publicKeyBytesToDid(this.state.issuer.publicKey, this.state.issuer.keyType),
issuer: this.state.issuer.did(),
audience: this.state.audience,

expiration: this.state.expiration,
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
5 changes: 1 addition & 4 deletions src/index.ts → packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
export * from "./attenuation.js"
export * from "./builder.js"
export * from "./did.js"
export * from "./keypair/ed25519.js"
export * from "./keypair/rsa.js"
export * from "./store.js"
export * from "./token.js"
export * from "./types.js"
export * from "./verify.js"
export * from "./plugins.js"

export * as keypair from "./keypair/index.js"
export * as capability from "./capability/index.js"

export { Capability, EncodedCapability, isCapability } from "./capability/index.js"
97 changes: 97 additions & 0 deletions packages/core/src/plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as uint8arrays from "uint8arrays"

export type DidKeyPlugin = {
prefix: Uint8Array
jwtAlg: string
verifySignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise<boolean>
}

export type DidMethodPlugin = {
checkJwtAlg: (did: string, jwtAlg: string) => boolean
verifySignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise<boolean>
}

export type Plugins = {
keys: DidKeyPlugin[]
methods: Record<string, DidMethodPlugin>
}

let plugins: Plugins | null = null

export const loadPlugins = (toLoad: Plugins): void => {
plugins = toLoad
}

export const verifyIssuerAlg = (did: string, jwtAlg: string): boolean => {
if(plugins === null) {
throw new Error("No plugins loaded")
}
const didMethod = parseDidMethod(did)
if(didMethod === "key") {
const bytes = parsePrefixedBytes(did)
for (const keyPlugin of plugins.keys) {
if(hasPrefix(bytes, keyPlugin.prefix)) {
return jwtAlg === keyPlugin.jwtAlg
}
}
} else {
const maybePlugin = plugins.methods[didMethod]
if(maybePlugin) {
return maybePlugin.checkJwtAlg(did, jwtAlg)
}
}
throw new Error(`DID method not supported by plugins: ${did}`)
}

export const verifySignature = async (did: string, data: Uint8Array, sig: Uint8Array): Promise<boolean> => {
if(plugins === null) {
throw new Error("No plugins loaded")
}
const didMethod = parseDidMethod(did)
if(didMethod === "key") {
const bytes = parsePrefixedBytes(did)
for (const keyPlugin of plugins.keys) {
if(hasPrefix(bytes, keyPlugin.prefix)) {
return keyPlugin.verifySignature(did, data, sig)
}
}
} else {
const maybePlugin = plugins.methods[didMethod]
if (maybePlugin) {
return maybePlugin.verifySignature(did, data, sig)
}
}
throw new Error(`DID method not supported by plugins: ${did}`)
}

export const hasPrefix = (
prefixedKey: Uint8Array,
prefix: Uint8Array
): boolean => {
return uint8arrays.equals(prefix, prefixedKey.subarray(0, prefix.byteLength))
}

const BASE58_DID_PREFIX = "did:key:z"

// @TODO would be better to follow the actual varint spec here (instead of guess & check):
// https://github.com/multiformats/unsigned-varint
const parsePrefixedBytes = (did: string): Uint8Array => {
if(!did.startsWith(BASE58_DID_PREFIX)) {
throw new Error(`Not a valid base58 formatted did:key: ${did}`)
}
return uint8arrays.fromString(
did.slice(BASE58_DID_PREFIX.length),
"base58btc"
)
}

const parseDidMethod = (did: string) => {
const parts = did.split(":")
if(parts[0] !== "did") {
throw new Error(`Not a DID: ${did}`)
}
if(parts[1].length < 1) {
throw new Error(`No DID method included: ${did}`)
}
return parts[1]
}
File renamed without changes.
File renamed without changes.
Loading