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

feat: embedded key resolution #168

Merged
merged 18 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from 14 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
46 changes: 40 additions & 6 deletions packages/interface/src/capability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ import {
Result,
Failure,
PrincipalParser,
DIDResolver,
Signer,
URI,
UCANLink,
Await,
IssuedInvocationView,
UCANOptions,
DIDKey,
Verifier,
API,
} from './lib.js'

export interface Source {
Expand Down Expand Up @@ -358,18 +362,36 @@ export interface ProofResolver extends PrincipalOptions {
/**
* You can provide a proof resolver that validator will call when UCAN
* links to external proof. If resolver is not provided validator may not
* be able to explore correesponding path within a proof chain.
* be able to explore corresponding path within a proof chain.
*/
resolve?: (proof: Link) => Await<Result<Delegation, UnavailableProof>>
}

export interface ValidationOptions<C extends ParsedCapability>
extends Partial<CanIssue>,
export interface Docs {
[key: DID]: { key: DIDKey }
}

export interface Validator {
authority: Verifier
Gozala marked this conversation as resolved.
Show resolved Hide resolved
}

export interface ValidationOptions<
C extends ParsedCapability = ParsedCapability
> extends Partial<CanIssue>,
Validator,
PrincipalOptions,
ProofResolver {
ProofResolver,
DIDResolver {
capability: CapabilityParser<Match<C, any>>
}

export interface ClaimOptions
extends Partial<CanIssue>,
Validator,
PrincipalOptions,
ProofResolver,
DIDResolver {}

export interface DelegationError extends Failure {
name: 'InvalidClaim'
causes: (InvalidCapability | EscalatedDelegation | DelegationError)[]
Expand Down Expand Up @@ -405,6 +427,13 @@ export interface UnavailableProof extends Failure {
readonly link: UCANLink
}

export interface DIDResolutionError extends Failure {
readonly name: 'DIDResolutionError'
readonly did: UCAN.DID

readonly cause?: Unauthorized
}

export interface Expired extends Failure {
readonly name: 'Expired'
readonly delegation: Delegation
Expand Down Expand Up @@ -432,16 +461,21 @@ export type InvalidProof =
| NotValidBefore
| InvalidSignature
| InvalidAudience
| DIDResolutionError
| UnavailableProof

export interface Unauthorized extends Failure {
name: 'Unauthorized'
cause: InvalidCapability | InvalidProof | InvalidClaim

delegationErrors: DelegationError[]
unknownCapabilities: Capability[]
invalidProofs: InvalidProof[]
failedProofs: InvalidClaim[]
}

export interface InvalidClaim extends Failure {
issuer: UCAN.Principal
name: 'InvalidClaim'
capability: ParsedCapability
delegation: Delegation

message: string
Expand Down
34 changes: 32 additions & 2 deletions packages/interface/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
InvalidAudience,
Unauthorized,
UnavailableProof,
DIDResolutionError,
ParsedCapability,
CapabilityParser,
} from './capability.js'
Expand Down Expand Up @@ -239,7 +240,7 @@ export type InvocationError =
| Unauthorized

export interface InvocationContext extends CanIssue {
id: Principal
id: Verifier
my?: (issuer: DID) => Capability[]
resolve?: (proof: UCANLink) => Await<Result<Delegation, UnavailableProof>>

Expand Down Expand Up @@ -425,7 +426,7 @@ export interface ServerOptions
* Service DID which will be used to verify that received invocation
* audience matches it.
*/
readonly id: Principal
readonly id: Verifier
}

/**
Expand Down Expand Up @@ -491,6 +492,10 @@ export type URI<P extends Protocol = Protocol> = `${P}${string}` &
protocol: P
}>

export interface ComposedDIDParser extends PrincipalParser {
or(parser: PrincipalParser): ComposedDIDParser
}

/**
* A `PrincipalParser` provides {@link Verifier} instances that can validate UCANs issued
* by a given {@link Principal}.
Expand All @@ -499,6 +504,10 @@ export interface PrincipalParser {
parse(did: UCAN.DID): Verifier
}

export interface DIDResolver {
resolveDID?: (did: UCAN.DID) => Await<Result<DIDKey, DIDResolutionError>>
Copy link
Contributor

@gobengo gobengo Dec 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the long term, I bristle a bit seeing the only thing that a UCAN.DID can resolve to is a single DIDKey.

What if we had

resolveDID<DidMethod>?: (did: UCAN.DID<DidMethod>) => Await<Result<DIDResolution<DidMethod>, DIDResolutionError>>

and

type SingleKeypairDidDocument<ID extends UCAN.DID> = {
  id: ID,
  /** https://www.w3.org/TR/did-core/#also-known-as */
  alsoKnownAs: DIDKey
}
type DidResolution<ID> =
| KeyAliasDidDocument<ID>

I like that return type because it's a subset of actual did doc type

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternatively could have resolveDID -> resolveDIDKey.

I like that it gets nowhere near 'did resolution' nor reinforces the (IMO footgun) assumption that a DID will always cleanly resolve to a single default keypair.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right resolveDID is misleading name, this is not a function to resolve a DID document but rather to resolve a key. I'll rename both interface and a function.

}

/**
* Represents component that can create a signer from it's archive. Usually
* signer module would provide `from` function and therefor be an implementation
Expand All @@ -517,6 +526,23 @@ export interface SignerImporter<
from(archive: SignerArchive<ID, Alg>): Signer<ID, Alg>
}

export interface CompositeImporter<
Variants extends [SignerImporter, ...SignerImporter[]]
> {
from: Intersection<Variants[number]['from']>
or<Other extends SignerImporter>(
other: Other
): CompositeImporter<[Other, ...Variants]>
}

export interface Importer<Self extends Signer = Signer> {
from(archive: Archive<Self>): Self
}

export interface Archive<Self extends Signer> {
id: ReturnType<Signer['did']>
keys: { [Key: DIDKey]: KeyArchive<Signer['signatureCode']> }
}
/**
* Principal that can issue UCANs (and sign payloads). While it's primary role
* is to sign payloads it also extends `Verifier` interface so it could be used
Expand Down Expand Up @@ -598,6 +624,10 @@ export interface Signer<ID extends DID = DID, Alg extends SigAlg = SigAlg>
*/
export interface Verifier<ID extends DID = DID, Alg extends SigAlg = SigAlg>
extends UCAN.Verifier<ID, Alg> {
/**
* Returns unwrapped did:key of this principal.
*/
toDIDKey(): DIDKey
/**
* Wraps key of this verifier into a verifier with a different DID. This is
* primarily used to wrap {@link VerifierKey} into a {@link Verifier} that has
Expand Down
153 changes: 0 additions & 153 deletions packages/principal/src/did.js

This file was deleted.

28 changes: 20 additions & 8 deletions packages/principal/src/ed25519/signer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as API from './type.js'
import * as Verifier from './verifier.js'
import { base64pad } from 'multiformats/bases/base64'
import * as Signature from '@ipld/dag-ucan/signature'
import { withDID } from '../signer.js'
import * as Signer from '../signer.js'
export * from './type.js'

export const code = 0x1300
Expand Down Expand Up @@ -52,18 +52,26 @@ export const derive = async secret => {
}

/**
* @param {API.SignerArchive<API.DIDKey, typeof signatureCode>} archive
* @param {API.SignerArchive<API.DID, typeof signatureCode>} archive
* @returns {API.EdSigner}
*/
export const from = ({ id, keys }) => {
const key = keys[id]
if (key instanceof Uint8Array) {
return decode(key)
} else {
throw new Error(`Unsupported archive format`)
if (id.startsWith('did:key:')) {
const key = keys[/** @type {API.DIDKey} */ (id)]
if (key instanceof Uint8Array) {
return decode(key)
}
}
throw new TypeError(`Unsupported archive format`)
}

from
/**
* @template {API.SignerImporter} O
* @param {O} other
*/
export const or = other => Signer.or({ from }, other)

/**
* @param {Uint8Array} bytes
* @returns {API.EdSigner}
Expand Down Expand Up @@ -163,13 +171,17 @@ class Ed25519Signer extends Uint8Array {
return this.verifier.did()
}

toDIDKey() {
return this.verifier.toDIDKey()
}

/**
* @template {API.DID} ID
* @param {ID} id
* @returns {API.Signer<ID, typeof Signature.EdDSA>}
*/
withDID(id) {
return withDID(this, id)
return Signer.withDID(this, id)
}

/**
Expand Down
Loading