Skip to content

Commit

Permalink
feat(ref-imp): #336 - added published field in method metadata + fixe…
Browse files Browse the repository at this point in the history
…d issue #833

* feat(ref-imp): #336 - added published field in method metadata
* fix(ref-imp): #833 - fixed bug where long-form DID is used in document of a published DID
  • Loading branch information
thehenrytsai committed Aug 14, 2020
1 parent fc1e8a9 commit f3f8318
Show file tree
Hide file tree
Showing 15 changed files with 96 additions and 39 deletions.
2 changes: 1 addition & 1 deletion lib/core/models/AnchoredOperationModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default interface AnchoredOperationModel {
didUniqueSuffix: string;
/** The type of operation. */
type: OperationType;
/** The logical blockchain time that this opeartion was anchored on the blockchain */
/** The logical blockchain time that this operation was anchored on the blockchain */
transactionTime: number;
/** The transaction number of the transaction this operation was batched within. */
transactionNumber: number;
Expand Down
3 changes: 3 additions & 0 deletions lib/core/versions/0.9.0/Did.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export default class Did {
public createOperation?: CreateOperation;
/** The short form. */
public shortForm: string;
/** The long form. */
public longForm: string | undefined;

/**
* Parses the input string as Sidetree DID.
Expand Down Expand Up @@ -51,6 +53,7 @@ export default class Did {
} else {
// This is long-form.
this.uniqueSuffix = did.substring(didPrefix.length, indexOfQuestionMarkChar);
this.longForm = did;
}

if (this.uniqueSuffix.length === 0) {
Expand Down
3 changes: 2 additions & 1 deletion lib/core/versions/0.9.0/DocumentComposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default class DocumentComposer {
/**
* Transforms the given DID state into a DID Document.
*/
public static transformToExternalDocument (didState: DidState, did: string): any {
public static transformToExternalDocument (didState: DidState, did: string, published: boolean): any {
// If the DID is deactivated.
if (didState.nextRecoveryCommitmentHash === undefined) {
return { status: 'deactivated' };
Expand Down Expand Up @@ -83,6 +83,7 @@ export default class DocumentComposer {
'@context': 'https://www.w3.org/ns/did-resolution/v1',
didDocument: didDocument,
methodMetadata: {
published,
recoveryCommitment: didState.nextRecoveryCommitmentHash,
updateCommitment: didState.nextUpdateCommitmentHash
}
Expand Down
4 changes: 0 additions & 4 deletions lib/core/versions/0.9.0/OperationProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ export default class OperationProcessor implements IOperationProcessor {
}

const newDidState = {
didUniqueSuffix: operation.didUniqueSuffix,
document,
nextRecoveryCommitmentHash: operation.suffixData.recoveryCommitment,
nextUpdateCommitmentHash: delta ? delta.updateCommitment : undefined,
Expand Down Expand Up @@ -229,9 +228,7 @@ export default class OperationProcessor implements IOperationProcessor {
}

const newDidState = {
didUniqueSuffix: operation.didUniqueSuffix,
document,
recoveryKey: operation.signedData.recoveryKey,
nextRecoveryCommitmentHash: operation.signedData.recoveryCommitment,
nextUpdateCommitmentHash: delta ? delta.updateCommitment : undefined,
lastOperationTransactionNumber: anchoredOperationModel.transactionNumber
Expand Down Expand Up @@ -266,7 +263,6 @@ export default class OperationProcessor implements IOperationProcessor {
const newDidState = {
document: didState.document,
// New values below.
recoveryKey: undefined,
nextRecoveryCommitmentHash: undefined,
nextUpdateCommitmentHash: undefined,
lastOperationTransactionNumber: anchoredOperationModel.transactionNumber
Expand Down
26 changes: 19 additions & 7 deletions lib/core/versions/0.9.0/RequestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ export default class RequestHandler implements IRequestHandler {
}

const did = `did:${this.didMethodName}:${operationModel.didUniqueSuffix}`;
const document = DocumentComposer.transformToExternalDocument(didState, did);
const published = false;
const document = DocumentComposer.transformToExternalDocument(didState, did, published);

return {
status: ResponseStatus.Succeeded,
Expand All @@ -153,10 +154,15 @@ export default class RequestHandler implements IRequestHandler {
const did = await Did.create(shortOrLongFormDid, this.didMethodName);

let didState: DidState | undefined;
let published = false;
if (did.isShortForm) {
didState = await this.resolver.resolve(did.uniqueSuffix);

if (didState !== undefined) {
published = true;
}
} else {
didState = await this.resolveLongFormDid(did);
[didState, published] = await this.resolveLongFormDid(did);
}

if (didState === undefined) {
Expand All @@ -166,7 +172,11 @@ export default class RequestHandler implements IRequestHandler {
};
}

const document = DocumentComposer.transformToExternalDocument(didState, shortOrLongFormDid);
// We reach here it means there is a DID Document to return.

// If DID is published, use the short-form DID; else use long-form DID in document.
const didStringToUseInDidDocument = published ? did.shortForm : did.longForm!;
const document = DocumentComposer.transformToExternalDocument(didState, didStringToUseInDidDocument, published);

return {
status: ResponseStatus.Succeeded,
Expand All @@ -190,22 +200,24 @@ export default class RequestHandler implements IRequestHandler {

/**
* Resolves the given long-form DID by resolving using operations found over the network first;
* if no operations found, the given create operation will is used to construct the DID state.
* if no operations found, the given create operation will be used to construct the DID state.
*
* @returns [DID state, published]
*/
private async resolveLongFormDid (did: Did): Promise<DidState | undefined> {
private async resolveLongFormDid (did: Did): Promise<[DidState | undefined, boolean]> {
// Attempt to resolve the DID by using operations found from the network first.
let didState = await this.resolver.resolve(did.uniqueSuffix);

// If DID state found then return it.
if (didState !== undefined) {
return didState;
return [didState, true];
}

// The code reaches here if this DID is not registered on the ledger.

didState = await this.applyCreateOperation(did.createOperation!);

return didState;
return [didState, false];
}

private async applyCreateOperation (createOperation: OperationModel): Promise<DidState | undefined> {
Expand Down
3 changes: 3 additions & 0 deletions lib/core/versions/latest/Did.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export default class Did {
public createOperation?: CreateOperation;
/** The short form. */
public shortForm: string;
/** The long form. */
public longForm: string | undefined;

/**
* Parses the input string as Sidetree DID.
Expand Down Expand Up @@ -51,6 +53,7 @@ export default class Did {
} else {
// This is long-form.
this.uniqueSuffix = did.substring(didPrefix.length, indexOfQuestionMarkChar);
this.longForm = did;
}

if (this.uniqueSuffix.length === 0) {
Expand Down
3 changes: 2 additions & 1 deletion lib/core/versions/latest/DocumentComposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default class DocumentComposer {
/**
* Transforms the given DID state into a DID Document.
*/
public static transformToExternalDocument (didState: DidState, did: string): any {
public static transformToExternalDocument (didState: DidState, did: string, published: boolean): any {
// If the DID is deactivated.
if (didState.nextRecoveryCommitmentHash === undefined) {
return { status: 'deactivated' };
Expand Down Expand Up @@ -83,6 +83,7 @@ export default class DocumentComposer {
'@context': 'https://www.w3.org/ns/did-resolution/v1',
didDocument: didDocument,
methodMetadata: {
published,
recoveryCommitment: didState.nextRecoveryCommitmentHash,
updateCommitment: didState.nextUpdateCommitmentHash
}
Expand Down
4 changes: 0 additions & 4 deletions lib/core/versions/latest/OperationProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ export default class OperationProcessor implements IOperationProcessor {
}

const newDidState = {
didUniqueSuffix: operation.didUniqueSuffix,
document,
nextRecoveryCommitmentHash: operation.suffixData.recoveryCommitment,
nextUpdateCommitmentHash: delta ? delta.updateCommitment : undefined,
Expand Down Expand Up @@ -229,9 +228,7 @@ export default class OperationProcessor implements IOperationProcessor {
}

const newDidState = {
didUniqueSuffix: operation.didUniqueSuffix,
document,
recoveryKey: operation.signedData.recoveryKey,
nextRecoveryCommitmentHash: operation.signedData.recoveryCommitment,
nextUpdateCommitmentHash: delta ? delta.updateCommitment : undefined,
lastOperationTransactionNumber: anchoredOperationModel.transactionNumber
Expand Down Expand Up @@ -266,7 +263,6 @@ export default class OperationProcessor implements IOperationProcessor {
const newDidState = {
document: didState.document,
// New values below.
recoveryKey: undefined,
nextRecoveryCommitmentHash: undefined,
nextUpdateCommitmentHash: undefined,
lastOperationTransactionNumber: anchoredOperationModel.transactionNumber
Expand Down
26 changes: 19 additions & 7 deletions lib/core/versions/latest/RequestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ export default class RequestHandler implements IRequestHandler {
}

const did = `did:${this.didMethodName}:${operationModel.didUniqueSuffix}`;
const document = DocumentComposer.transformToExternalDocument(didState, did);
const published = false;
const document = DocumentComposer.transformToExternalDocument(didState, did, published);

return {
status: ResponseStatus.Succeeded,
Expand All @@ -153,10 +154,15 @@ export default class RequestHandler implements IRequestHandler {
const did = await Did.create(shortOrLongFormDid, this.didMethodName);

let didState: DidState | undefined;
let published = false;
if (did.isShortForm) {
didState = await this.resolver.resolve(did.uniqueSuffix);

if (didState !== undefined) {
published = true;
}
} else {
didState = await this.resolveLongFormDid(did);
[didState, published] = await this.resolveLongFormDid(did);
}

if (didState === undefined) {
Expand All @@ -166,7 +172,11 @@ export default class RequestHandler implements IRequestHandler {
};
}

const document = DocumentComposer.transformToExternalDocument(didState, shortOrLongFormDid);
// We reach here it means there is a DID Document to return.

// If DID is published, use the short-form DID; else use long-form DID in document.
const didStringToUseInDidDocument = published ? did.shortForm : did.longForm!;
const document = DocumentComposer.transformToExternalDocument(didState, didStringToUseInDidDocument, published);

return {
status: ResponseStatus.Succeeded,
Expand All @@ -190,22 +200,24 @@ export default class RequestHandler implements IRequestHandler {

/**
* Resolves the given long-form DID by resolving using operations found over the network first;
* if no operations found, the given create operation will is used to construct the DID state.
* if no operations found, the given create operation will be used to construct the DID state.
*
* @returns [DID state, published]
*/
private async resolveLongFormDid (did: Did): Promise<DidState | undefined> {
private async resolveLongFormDid (did: Did): Promise<[DidState | undefined, boolean]> {
// Attempt to resolve the DID by using operations found from the network first.
let didState = await this.resolver.resolve(did.uniqueSuffix);

// If DID state found then return it.
if (didState !== undefined) {
return didState;
return [didState, true];
}

// The code reaches here if this DID is not registered on the ledger.

didState = await this.applyCreateOperation(did.createOperation!);

return didState;
return [didState, false];
}

private async applyCreateOperation (createOperation: OperationModel): Promise<DidState | undefined> {
Expand Down
35 changes: 30 additions & 5 deletions tests/core/DocumentComposer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('DocumentComposer', async () => {
describe('transformToExternalDocument', () => {
it('should output the expected resolution result given key(s) across all purpose types.', async () => {
const [anySigningPublicKey] = await OperationGenerator.generateKeyPair('anySigningKey'); // All purposes will be included by default.
const [authPublicKey] = await OperationGenerator.generateKeyPair('authePbulicKey', [PublicKeyPurpose.Auth]);
const [authPublicKey] = await OperationGenerator.generateKeyPair('authPublicKey', [PublicKeyPurpose.Auth]);
const document = {
public_keys: [anySigningPublicKey, authPublicKey]
};
Expand All @@ -23,10 +23,12 @@ describe('DocumentComposer', async () => {
nextUpdateCommitmentHash: 'anyCommitmentHash'
};

const result = DocumentComposer.transformToExternalDocument(didState, 'did:method:suffix');
const published = true;
const result = DocumentComposer.transformToExternalDocument(didState, 'did:method:suffix', published);

expect(result['@context']).toEqual('https://www.w3.org/ns/did-resolution/v1');
expect(result.methodMetadata).toEqual({
published: true,
recoveryCommitment: 'anyCommitmentHash',
updateCommitment: 'anyCommitmentHash'
});
Expand All @@ -43,7 +45,7 @@ describe('DocumentComposer', async () => {
authentication: [
'#anySigningKey', // reference because it is a general purpose key
{
id: '#authePbulicKey', // object here because it is an auth purpose only key
id: '#authPublicKey', // object here because it is an auth purpose only key
controller: '',
type: 'EcdsaSecp256k1VerificationKey2019',
publicKeyJwk: {
Expand All @@ -54,6 +56,28 @@ describe('DocumentComposer', async () => {
});
});

it('should output method metadata with the given `published` value.', async () => {
const [anySigningPublicKey] = await OperationGenerator.generateKeyPair('anySigningKey'); // All purposes will be included by default.
const [authPublicKey] = await OperationGenerator.generateKeyPair('authPublicKey', [PublicKeyPurpose.Auth]);
const document = {
public_keys: [anySigningPublicKey, authPublicKey]
};
const didState: DidState = {
document,
lastOperationTransactionNumber: 123,
nextRecoveryCommitmentHash: 'anyCommitmentHash',
nextUpdateCommitmentHash: 'anyCommitmentHash'
};

let published = false;
let result = DocumentComposer.transformToExternalDocument(didState, 'did:method:suffix', published);
expect(result.methodMetadata.published).toEqual(published);

published = true;
result = DocumentComposer.transformToExternalDocument(didState, 'did:method:suffix', published);
expect(result.methodMetadata.published).toEqual(published);
});

it('should return status deactivated if next recovery commit hash is undefined', async () => {
const [anySigningPublicKey] = await OperationGenerator.generateKeyPair('anySigningKey');
const [authPublicKey] = await OperationGenerator.generateKeyPair('authPublicKey', [PublicKeyPurpose.Auth]);
Expand All @@ -67,7 +91,8 @@ describe('DocumentComposer', async () => {
nextUpdateCommitmentHash: 'anyCommitmentHash'
};

const result = DocumentComposer.transformToExternalDocument(didState, 'did:method:suffix');
const published = true;
const result = DocumentComposer.transformToExternalDocument(didState, 'did:method:suffix', published);
expect(result).toEqual({ status: 'deactivated' });
});
});
Expand Down Expand Up @@ -483,7 +508,7 @@ describe('DocumentComposer', async () => {

describe('validateId()', async () => {
it('should throw if ID given is not using characters from Base64URL character set.', async () => {
const invalidId = 'AnInavlidIdWith#';
const invalidId = 'AnInvalidIdWith#';

const expectedError = new SidetreeError(ErrorCode.DocumentComposerIdNotUsingBase64UrlCharacterSet);
expect(() => { (DocumentComposer as any).validateId(invalidId); }).toThrow(expectedError);
Expand Down
5 changes: 3 additions & 2 deletions tests/core/RequestHandler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ describe('RequestHandler', () => {
});

describe('resolveLongFormDid()', async () => {
it('should return the resolved DID document if it is resolvable as a registered DID.', async () => {
it('should return the resolved DID document, and `published` value as `true` if it is resolvable as a registered DID.', async () => {
const [anySigningPublicKey] = await OperationGenerator.generateKeyPair('anySigningKey');
const document = {
publicKeys: [anySigningPublicKey]
Expand All @@ -373,8 +373,9 @@ describe('RequestHandler', () => {
};
spyOn((requestHandler as any).resolver, 'resolve').and.returnValue(Promise.resolve(mockedResolverReturnedDidState));

const didState = await (requestHandler as any).resolveLongFormDid('unused');
const [didState, published] = await (requestHandler as any).resolveLongFormDid('unused');

expect(published).toEqual(true);
expect(didState.document.publicKeys.length).toEqual(1);
expect(didState.document.publicKeys[0].jwk).toEqual(anySigningPublicKey.jwk);
});
Expand Down
Loading

0 comments on commit f3f8318

Please sign in to comment.