Finish did:ion tooling

This commit is contained in:
Paul Frazee 2022-06-06 15:48:59 -05:00
parent 9f16b45626
commit 05593da63a
3 changed files with 147 additions and 82 deletions

@ -28,6 +28,7 @@ export { KeyType, generateFromSeedOptions } from './keypairs.js'
export type CreateParams = IonDocumentModel
export type UpdateParams = OpUpdateParams
export type RecoverParams = IonDocumentModel
export type KeyParams = generateFromSeedOptions & { keyType: KeyType }
export * as OPS from './ops.js'
export interface DidIonSerializedState {
shortForm: string
@ -52,16 +53,15 @@ export async function resolve(
}
export async function create(
type: KeyType,
doc: CreateParams,
options: {
ionResolveEndpoint?: string
ionChallengeEndpoint?: string
ionSolutionEndpoint?: string
} & generateFromSeedOptions,
} & KeyParams,
): Promise<IonDidDocAPI> {
const did = new IonDidDocAPI(options)
await did.create(type, doc, options)
await did.create(doc, options)
return did
}
@ -146,7 +146,7 @@ export class IonDidDocAPI extends WritableDidDocAPI {
assertActive() {
const lastOp = this.getLastOperation()
if (lastOp?.operation === 'deactivate') {
throw 'Cannot perform further operations on a deactivated DID'
throw new Error('Cannot perform further operations on a deactivated DID')
}
}
@ -197,17 +197,13 @@ export class IonDidDocAPI extends WritableDidDocAPI {
// writable api
// =
async create(
type: KeyType,
doc: CreateParams,
options: generateFromSeedOptions,
): Promise<OpCreate> {
async create(doc: CreateParams, options: KeyParams): Promise<OpCreate> {
this.assertActive()
const op: OpCreate = {
operation: 'create',
content: doc,
recovery: await generateKeyPair(type, options),
update: await generateKeyPair(type, options),
recovery: await generateKeyPair(options.keyType, options),
update: await generateKeyPair(options.keyType, options),
}
const reqBody = IonSdk.IonRequest.createCreateRequest({
recoveryKey: op.recovery.publicJwk,
@ -221,75 +217,78 @@ export class IonDidDocAPI extends WritableDidDocAPI {
return op
}
// async update(params: UpdateParams): Promise<OpUpdate> {
// this.assertActive()
// const op: OpUpdate = {
// operation: 'update',
// content: params,
// previous: this.getPreviousOperation('update'),
// update: await generateKeyPair(),
// }
// if (!op.previous.update) {
// throw new Error('Update key not found on previous ops')
// }
// const reqBody = IonSdk.IonRequest.createUpdateRequest({
// didSuffix: this.getSuffix(),
// signer: IonSdk.LocalSigner.create(op.previous.update.privateJwk),
// updatePublicKey: op.previous.update.publicJwk,
// nextUpdatePublicKey: op.update.publicJwk,
// servicesToAdd: op.content.addServices,
// idsOfServicesToRemove: op.content?.removeServices,
// publicKeysToAdd: op.content?.addPublicKeys,
// idsOfPublicKeysToRemove: op.content?.removePublicKeys,
// })
// await this._submitAnchorRequest(reqBody)
// this._ops.push(op)
// return op
// }
async update(params: UpdateParams, options: KeyParams): Promise<OpUpdate> {
this.assertActive()
const op: OpUpdate = {
operation: 'update',
content: params,
previous: this.getPreviousOperation('update'),
update: await generateKeyPair(options.keyType, options),
}
if (!op.previous.update) {
throw new Error('Update key not found on previous ops')
}
const reqBody = await IonSdk.IonRequest.createUpdateRequest({
didSuffix: this.getSuffix(),
signer: IonSdk.LocalSigner.create(op.previous.update.privateJwk),
updatePublicKey: op.previous.update.publicJwk,
nextUpdatePublicKey: op.update.publicJwk,
servicesToAdd: op.content.addServices,
idsOfServicesToRemove: op.content?.removeServices,
publicKeysToAdd: op.content?.addPublicKeys,
idsOfPublicKeysToRemove: op.content?.removePublicKeys,
})
await this._submitAnchorRequest(reqBody)
this._ops.push(op)
await this._resolveDidDoc()
return op
}
// async recover(doc: RecoverParams): Promise<OpRecover> {
// this.assertActive()
// const op: OpRecover = {
// operation: 'recover',
// content: doc,
// previous: this.getPreviousOperation('recover'),
// recovery: await generateKeyPair(),
// update: await generateKeyPair(),
// }
// if (!op.previous.recovery) {
// throw new Error('Recovery key not found on previous ops')
// }
// const reqBody = IonSdk.IonRequest.createRecoverRequest({
// didSuffix: this.getSuffix(),
// signer: IonSdk.LocalSigner.create(op.previous.recovery.privateJwk),
// recoveryPublicKey: op.previous.recovery.publicJwk,
// nextRecoveryPublicKey: op.recovery.publicJwk,
// nextUpdatePublicKey: op.update.publicJwk,
// document: op.content,
// })
// await this._submitAnchorRequest(reqBody)
// this._ops.push(op)
// return op
// }
async recover(doc: RecoverParams, options: KeyParams): Promise<OpRecover> {
this.assertActive()
const op: OpRecover = {
operation: 'recover',
content: doc,
previous: this.getPreviousOperation('recover'),
recovery: await generateKeyPair(options.keyType, options),
update: await generateKeyPair(options.keyType, options),
}
if (!op.previous.recovery) {
throw new Error('Recovery key not found on previous ops')
}
const reqBody = await IonSdk.IonRequest.createRecoverRequest({
didSuffix: this.getSuffix(),
signer: IonSdk.LocalSigner.create(op.previous.recovery.privateJwk),
recoveryPublicKey: op.previous.recovery.publicJwk,
nextRecoveryPublicKey: op.recovery.publicJwk,
nextUpdatePublicKey: op.update.publicJwk,
document: op.content,
})
await this._submitAnchorRequest(reqBody)
this._ops.push(op)
await this._resolveDidDoc()
return op
}
// async deactivate(): Promise<OpDeactivate> {
// this.assertActive()
// const op: OpDeactivate = {
// operation: 'deactivate',
// previous: this.getPreviousOperation('deactivate'),
// }
// if (!op.previous.recovery) {
// throw new Error('Recovery key not found on previous ops')
// }
// const reqBody = IonSdk.IonRequest.createDeactivateRequest({
// didSuffix: this.getSuffix(),
// recoveryPublicKey: op.previous.recovery.publicJwk,
// signer: IonSdk.LocalSigner.create(op.previous.recovery.privateJwk),
// })
// await this._submitAnchorRequest(reqBody)
// this._ops.push(op)
// return op
// }
async deactivate(): Promise<OpDeactivate> {
this.assertActive()
const op: OpDeactivate = {
operation: 'deactivate',
previous: this.getPreviousOperation('deactivate'),
}
if (!op.previous.recovery) {
throw new Error('Recovery key not found on previous ops')
}
const reqBody = await IonSdk.IonRequest.createDeactivateRequest({
didSuffix: this.getSuffix(),
recoveryPublicKey: op.previous.recovery.publicJwk,
signer: IonSdk.LocalSigner.create(op.previous.recovery.privateJwk),
})
await this._submitAnchorRequest(reqBody)
this._ops.push(op)
await this._resolveDidDoc()
return op
}
serialize(): DidIonSerializedState {
return {

@ -77,19 +77,84 @@ test('Create, update, recover, and deactivate did:ion (dummy server)', async (t)
type: 'SomeService',
serviceEndpoint: 'https://example.com',
}
const service2 = {
id: Buffer.from('#service2', 'utf8').toString('base64'), // TODO: is this right?
type: 'SomeService',
serviceEndpoint: 'https://foobar.com',
}
// create
const did = await ion.create(
'secp256k1',
{ services: [service] },
{
keyType: 'secp256k1',
secureRandom: () => crypto.randomBytes(32),
ionResolveEndpoint: server?.resolveEndpoint,
ionChallengeEndpoint: server?.challengeEndpoint,
ionSolutionEndpoint: server?.solutionEndpoint,
},
)
const did2 = await resolve(did.getURI())
const did2 = await ion.resolve(did.getURI(), server?.resolveEndpoint)
t.deepEqual(did.didDoc, did2.didDoc)
t.is(did.getService(service.type)?.serviceEndpoint, service.serviceEndpoint)
// update
await did.update(
{
addServices: [service2],
},
{ keyType: 'secp256k1', secureRandom: () => crypto.randomBytes(32) },
)
const did3 = await ion.resolve(did.getURI(), server?.resolveEndpoint)
t.deepEqual(did.didDoc, did3.didDoc)
t.deepEqual(
did
.listServices()
.map((s) => s.serviceEndpoint)
.sort(),
[service.serviceEndpoint, service2.serviceEndpoint].sort(),
)
// update2
await did.update(
{
removeServices: [service.id],
},
{ keyType: 'secp256k1', secureRandom: () => crypto.randomBytes(32) },
)
const did4 = await ion.resolve(did.getURI(), server?.resolveEndpoint)
t.deepEqual(did.didDoc, did4.didDoc)
t.deepEqual(
did
.listServices()
.map((s) => s.serviceEndpoint)
.sort(),
[service2.serviceEndpoint].sort(),
)
// recover
await did.recover(
{
services: [service],
},
{ keyType: 'secp256k1', secureRandom: () => crypto.randomBytes(32) },
)
const did5 = await ion.resolve(did.getURI(), server?.resolveEndpoint)
t.deepEqual(did.didDoc, did5.didDoc)
t.is(did.getService(service.type)?.serviceEndpoint, service.serviceEndpoint)
// deactivate
await did.deactivate()
const did6 = await ion.resolve(did.getURI(), server?.resolveEndpoint)
t.deepEqual(did.didDoc, did6.didDoc)
await t.throwsAsync(() => {
return did.update(
{
addServices: [service2],
},
{ keyType: 'secp256k1', secureRandom: () => crypto.randomBytes(32) },
)
})
})
test('Resolve throws on malformed did:ions', async (t) => {

@ -175,8 +175,9 @@ async function getJsonBody(req: http.IncomingMessage) {
return undefined
}
function createShortFormDid(createRequest: any): string {
const didUniqueSuffix = computeDidUniqueSuffix(createRequest.suffixData)
function createShortFormDid(request: any): string {
if (request.didSuffix) return `did:ion:${request.didSuffix}`
const didUniqueSuffix = computeDidUniqueSuffix(request.suffixData)
return `did:ion:${didUniqueSuffix}`
}