Skip to content

Commit

Permalink
#212 - added attester filter to RecordsQuery
Browse files Browse the repository at this point in the history
* added support for attester filter
* refactored JWS related utilities
* refactored CID utilities
  • Loading branch information
thehenrytsai authored Feb 6, 2023
1 parent bb246a2 commit 018ff80
Show file tree
Hide file tree
Showing 27 changed files with 195 additions and 153 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
# Decentralized Web Node (DWN) SDK

Code Coverage
![Statements](https://img.shields.io/badge/statements-94.63%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-94.12%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-91.61%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-94.63%25-brightgreen.svg?style=flat)
![Statements](https://img.shields.io/badge/statements-94.74%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-94.12%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-91.61%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-94.74%25-brightgreen.svg?style=flat)

## Introduction

This repository contains a reference implementation of Decentralized Web Node (DWN) as per the [specification](https://identity.foundation/decentralized-web-node/spec/). This specification is in a draft state and very much so a WIP. For the foreseeable future, a lot of the work on DWN will be split across this repo and the repo that houses the specification, which you can find [here](https://github.com/decentralized-identity/decentralized-web-node). The current goal is to produce a [beta implementation](https://github.com/TBD54566975/dwn-sdk-js/milestone/1) by Q4 2022. This won't include all interfaces described in the spec, but enough to begin building applications.
This repository contains a reference implementation of Decentralized Web Node (DWN) as per the [specification](https://identity.foundation/decentralized-web-node/spec/). This specification is in a draft state and very much so a WIP. For the foreseeable future, a lot of the work on DWN will be split across this repo and the repo that houses the specification, which you can find [here](https://github.com/decentralized-identity/decentralized-web-node). The current goal is to produce a beta implementation by March 2023. This won't include all interfaces described in the DWN spec, but will be enough to begin building applications.

Proposals and issues for the specification itself should be submitted as pull requests to the [spec repo](https://github.com/decentralized-identity/decentralized-web-node).

Expand Down
3 changes: 3 additions & 0 deletions json-schemas/records/records-query.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
"protocol": {
"type": "string"
},
"attester": {
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/definitions/did"
},
"recipient": {
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/definitions/did"
},
Expand Down
8 changes: 4 additions & 4 deletions src/core/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CID } from 'multiformats';
import { DidResolver } from '../did/did-resolver.js';
import { GeneralJws } from '../jose/jws/general/types.js';
import { GeneralJwsVerifier } from '../jose/jws/general/verifier.js';
import { Jws } from '../utils/jws.js';
import { Message } from './message.js';
import { computeCid, parseCid } from '../utils/cid.js';

Expand Down Expand Up @@ -40,14 +41,13 @@ export async function validateAuthorizationIntegrity(
throw new Error('expected no more than 1 signature for authorization');
}

const payloadJson = GeneralJwsVerifier.decodePlainObjectPayload(message.authorization);
const payloadJson = Jws.decodePlainObjectPayload(message.authorization);
const { descriptorCid } = payloadJson;

// `descriptorCid` validation - ensure that the provided descriptorCid matches the CID of the actual message
const providedDescriptorCid = parseCid(descriptorCid); // parseCid throws an exception if parsing fails
const expectedDescriptorCid = await computeCid(message.descriptor);
if (!providedDescriptorCid.equals(expectedDescriptorCid)) {
throw new Error(`provided descriptorCid ${providedDescriptorCid} does not match expected CID ${expectedDescriptorCid}`);
if (descriptorCid !== expectedDescriptorCid) {
throw new Error(`provided descriptorCid ${descriptorCid} does not match expected CID ${expectedDescriptorCid}`);
}

// check to ensure that no other unexpected properties exist in payload.
Expand Down
13 changes: 6 additions & 7 deletions src/core/message.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import type { SignatureInput } from '../jose/jws/general/types.js';
import type { BaseDecodedAuthorizationPayload, BaseMessage, Descriptor, TimestampedMessage } from './types.js';

import { CID } from 'multiformats/cid';
import { computeCid } from '../utils/cid.js';
import { GeneralJws } from '../jose/jws/general/types.js';
import { GeneralJwsSigner } from '../jose/jws/general/signer.js';
import { GeneralJwsVerifier } from '../jose/jws/general/verifier.js';
import { Jws } from '../utils/jws.js';
import { lexicographicalCompare } from '../utils/string.js';
import { RecordsWriteMessage } from '../interfaces/records/types.js';
import { validateJsonSchema } from '../validator.js';
import { validateJsonSchema } from '../schema-validator.js';

export enum DwnInterfaceName {
Hooks = 'Hooks',
Expand All @@ -35,7 +34,7 @@ export abstract class Message {

constructor(message: BaseMessage) {
this.message = message;
this.authorizationPayload = GeneralJwsVerifier.decodePlainObjectPayload(message.authorization);
this.authorizationPayload = Jws.decodePlainObjectPayload(message.authorization);

this.author = Message.getAuthor(message);
}
Expand Down Expand Up @@ -64,15 +63,15 @@ export abstract class Message {
* Gets the DID of the author of the given message.
*/
public static getAuthor(message: BaseMessage): string {
const author = GeneralJwsVerifier.getDid(message.authorization.signatures[0]);
const author = Jws.getSignerDid(message.authorization.signatures[0]);
return author;
}

/**
* Gets the CID of the given message.
* NOTE: `encodedData` is ignored when computing the CID of message.
*/
public static async getCid(message: BaseMessage): Promise<CID> {
public static async getCid(message: BaseMessage): Promise<string> {
const messageCopy = { ...message };

if (messageCopy['encodedData'] !== undefined) {
Expand Down Expand Up @@ -130,7 +129,7 @@ export abstract class Message {
): Promise<GeneralJws> {
const descriptorCid = await computeCid(descriptor);

const authPayload: BaseDecodedAuthorizationPayload = { descriptorCid: descriptorCid.toString() };
const authPayload: BaseDecodedAuthorizationPayload = { descriptorCid };
const authPayloadStr = JSON.stringify(authPayload);
const authPayloadBytes = new TextEncoder().encode(authPayloadStr);

Expand Down
5 changes: 2 additions & 3 deletions src/interfaces/permissions/messages/permissions-grant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { SignatureInput } from '../../../jose/jws/general/types';
import type { PermissionConditions, PermissionScope } from '../types';
import type { PermissionsGrantDescriptor, PermissionsGrantMessage } from '../types';

import { CID } from 'multiformats/cid';
import { computeCid } from '../../../utils/cid';
import { getCurrentTimeInHighPrecision } from '../../../utils/time';
import { v4 as uuidv4 } from 'uuid';
Expand Down Expand Up @@ -131,8 +130,8 @@ export class PermissionsGrant extends Message {
return this.message.descriptor.scope;
}

private set delegatedFrom(cid: CID) {
this.message.descriptor.delegatedFrom = cid.toString();
private set delegatedFrom(cid: string) {
this.message.descriptor.delegatedFrom = cid;
}

private set delegationChain(parentGrant: PermissionsGrantMessage) {
Expand Down
12 changes: 2 additions & 10 deletions src/interfaces/records/messages/records-query.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { SignatureInput } from '../../../jose/jws/general/types.js';
import type { RecordsQueryDescriptor, RecordsQueryMessage } from '../types.js';
import type { RecordsQueryDescriptor, RecordsQueryFilter, RecordsQueryMessage } from '../types.js';

import { getCurrentTimeInHighPrecision } from '../../../utils/time.js';
import { Message } from '../../../core/message.js';
Expand All @@ -16,15 +16,7 @@ export enum DateSort {

export type RecordsQueryOptions = {
dateCreated?: string;
filter: {
recipient?: string;
protocol?: string;
contextId?: string;
schema?: string;
recordId?: string;
parentId?: string;
dataFormat?: string;
},
filter: RecordsQueryFilter;
dateSort?: DateSort;
authorizationSignatureInput: SignatureInput;
};
Expand Down
28 changes: 13 additions & 15 deletions src/interfaces/records/messages/records-write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import type { RecordsWriteAttestationPayload, RecordsWriteAuthorizationPayload,

import { Encoder } from '../../../utils/encoder.js';
import { GeneralJwsSigner } from '../../../jose/jws/general/signer.js';
import { GeneralJwsVerifier } from '../../../jose/jws/general/verifier.js';
import { getCurrentTimeInHighPrecision } from '../../../utils/time.js';
import { Jws } from '../../../utils/jws.js';
import { Message } from '../../../core/message.js';
import { MessageStore } from '../../../store/message-store.js';
import { ProtocolAuthorization } from '../../../core/protocol-authorization.js';
import { removeUndefinedProperties } from '../../../utils/object.js';

import { authorize, validateAuthorizationIntegrity } from '../../../core/auth.js';
import { computeCid, getDagPbCid, parseCid } from '../../../utils/cid.js';
import { computeCid, computeDagPbCid } from '../../../utils/cid.js';
import { DwnInterfaceName, DwnMethodName } from '../../../core/message.js';
import { GeneralJws, SignatureInput } from '../../../jose/jws/general/types.js';

Expand Down Expand Up @@ -78,7 +78,7 @@ export class RecordsWrite extends Message {
public static async create(options: RecordsWriteOptions): Promise<RecordsWrite> {
const currentTime = getCurrentTimeInHighPrecision();

const dataCid = await getDagPbCid(options.data);
const dataCid = await computeDagPbCid(options.data);
const descriptor: RecordsWriteDescriptor = {
interface : DwnInterfaceName.Records,
method : DwnMethodName.Write,
Expand All @@ -104,7 +104,7 @@ export class RecordsWrite extends Message {
// Error: `undefined` is not supported by the IPLD Data Model and cannot be encoded
removeUndefinedProperties(descriptor);

const author = GeneralJwsVerifier.extractDid(options.authorizationSignatureInput.protectedHeader.kid);
const author = Jws.extractDid(options.authorizationSignatureInput.protectedHeader.kid);

// `recordId` computation
const recordId = options.recordId ?? await RecordsWrite.getEntryId(author, descriptor);
Expand All @@ -121,7 +121,7 @@ export class RecordsWrite extends Message {
}

// `attestation` generation
const descriptorCid = (await computeCid(descriptor)).toString();
const descriptorCid = await computeCid(descriptor);
const attestation = await RecordsWrite.createAttestation(descriptorCid, options.attestationSignatureInputs);

// `authorization` generation
Expand Down Expand Up @@ -229,7 +229,7 @@ export class RecordsWrite extends Message {
// verify dataCid matches given data
if (this.message.encodedData !== undefined) {
const rawData = Encoder.base64UrlToBytes(this.message.encodedData);
const actualDataCid = (await getDagPbCid(rawData)).toString();
const actualDataCid = (await computeDagPbCid(rawData)).toString();

if (actualDataCid !== this.message.descriptor.dataCid) {
throw new Error('actual CID of data and `dataCid` in descriptor mismatch');
Expand Down Expand Up @@ -273,7 +273,7 @@ export class RecordsWrite extends Message {

// if `attestation` is given in message, make sure the correct `attestationCid` is in the `authorization`
if (this.message.attestation !== undefined) {
const expectedAttestationCid = (await computeCid(this.message.attestation)).toString();
const expectedAttestationCid = await computeCid(this.message.attestation);
const actualAttestationCid = this.authorizationPayload.attestationCid;
if (actualAttestationCid !== expectedAttestationCid) {
throw new Error(
Expand All @@ -297,14 +297,13 @@ export class RecordsWrite extends Message {
throw new Error(`Currently implementation only supports 1 attester, but got ${message.attestation.signatures.length}`);
}

const payloadJson = GeneralJwsVerifier.decodePlainObjectPayload(message.attestation);
const payloadJson = Jws.decodePlainObjectPayload(message.attestation);
const { descriptorCid } = payloadJson;

// `descriptorCid` validation - ensure that the provided descriptorCid matches the CID of the actual message
const providedDescriptorCid = parseCid(descriptorCid); // parseCid throws an exception if parsing fails
const expectedDescriptorCid = await computeCid(message.descriptor);
if (!providedDescriptorCid.equals(expectedDescriptorCid)) {
throw new Error(`descriptorCid ${providedDescriptorCid} does not match expected descriptorCid ${expectedDescriptorCid}`);
if (descriptorCid !== expectedDescriptorCid) {
throw new Error(`descriptorCid ${descriptorCid} does not match expected descriptorCid ${expectedDescriptorCid}`);
}

// check to ensure that no other unexpected properties exist in payload.
Expand All @@ -330,8 +329,7 @@ export class RecordsWrite extends Message {
(entryIdInput as any).author = author;

const cid = await computeCid(entryIdInput);
const cidString = cid.toString();
return cidString;
return cid;
};

/**
Expand Down Expand Up @@ -388,7 +386,7 @@ export class RecordsWrite extends Message {
descriptorCid
};

const attestationCid = attestation ? (await computeCid(attestation)).toString() : undefined;
const attestationCid = attestation ? await computeCid(attestation) : undefined;

if (contextId !== undefined) { authorizationPayload.contextId = contextId; } // assign `contextId` only if it is defined
if (attestationCid !== undefined) { authorizationPayload.attestationCid = attestationCid; } // assign `attestationCid` only if it is defined
Expand Down Expand Up @@ -445,7 +443,7 @@ export class RecordsWrite extends Message {
*/
public static getAttesters(message: RecordsWriteMessage): string[] {
const attestationSignatures = message.attestation?.signatures ?? [];
const attesters = attestationSignatures.map((signature) => GeneralJwsVerifier.getDid(signature));
const attesters = attestationSignatures.map((signature) => Jws.getSignerDid(signature));
return attesters;
}
}
21 changes: 12 additions & 9 deletions src/interfaces/records/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,21 @@ export type RecordsQueryDescriptor = {
interface: DwnInterfaceName.Records;
method: DwnMethodName.Query;
dateCreated: string;
filter: {
recipient?: string;
protocol?: string;
contextId?: string;
schema?: string;
recordId?: string;
parentId?: string;
dataFormat?: string;
}
filter: RecordsQueryFilter;
dateSort?: DateSort;
};

export type RecordsQueryFilter = {
attester?: string;
recipient?: string;
protocol?: string;
contextId?: string;
schema?: string;
recordId?: string;
parentId?: string;
dataFormat?: string;
};

export type RecordsWriteAttestationPayload = {
descriptorCid: string;
};
Expand Down
Loading

0 comments on commit 018ff80

Please sign in to comment.