Skip to content

Commit

Permalink
feat: improvements
Browse files Browse the repository at this point in the history
- SignedDocuments now transform all data fields to string
- add issuer.id as mandatory
- remove statusType
- fix Issuer type name from generated v1 schema
  • Loading branch information
Nebulis committed Dec 6, 2019
1 parent 76fbeb4 commit 145c32b
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 145 deletions.
30 changes: 26 additions & 4 deletions src/@types/document.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
export type SignatureProofAlgorithm = "SHA3MerkleProof";

export interface SignedDocument<T = any> extends SchematisedDocument<T> {
signature: Signature;
}

export interface Signature {
type: SignatureProofAlgorithm;
targetHash: string;
Expand All @@ -21,3 +17,29 @@ export interface SchematisedDocument<T = any> {
schema?: string;
privacy?: ObfuscationMetadata;
}

export interface SignedDocument<T = any> {
version: string;
signature: Signature;
data: DeepStringify<T>;
schema?: string;
privacy?: ObfuscationMetadata;
}

// feel free to improve, as long as this project compile without changes :)
// once salted, every property is turned into a string
type DeepStringify<T> = {
[P in keyof T]: T[P] extends Array<number> // if it's a []number
? Array<string> // return []string
: T[P] extends Array<string> // if it's []string
? Array<string> // return []string
: T[P] extends Record<string, any> // if it's an object
? DeepStringify<T[P]> // apply stringify on the object
: T[P] extends Array<Record<string, infer U>> // if it's an array of object
? DeepStringify<U> // apply stringify on the array
: number extends T[P] // if it's a number
? string // make it a string
: undefined extends T[P] // if it's an optional field
? DeepStringify<T[P]> // stringify the type
: string; // default to string
};
3 changes: 2 additions & 1 deletion src/privacy/privacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { toBuffer } from "../utils";
import { unsaltData } from "./salt";
import { SchematisedDocument, SignedDocument } from "../@types/document";

export const getData = <T extends { data: any }>(document: T): T["data"] => {
type Extract<P> = P extends SignedDocument<infer T> ? T : never;
export const getData = <T extends { data: any }>(document: T): Extract<T> => {
return unsaltData(document.data);
};

Expand Down
1 change: 1 addition & 0 deletions src/schema/1.0/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
"type": "array",
"items": {
"type": "object",
"title": "issuer",
"oneOf": [
{
"$ref": "#/definitions/tokenRegistry"
Expand Down
2 changes: 1 addition & 1 deletion src/schema/2.0/sample-document.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"reference": "SERIAL_NUMBER_123",
"name": "Singapore Driving Licence",
"statusType": "CredentialStatusType",
"validFrom": "2010-01-01T19:23:24Z",
"$template": {
"name": "CUSTOM_TEMPLATE",
"type": "EMBEDDED_RENDERER",
"url": "https://localhost:3000/renderer"
},
"issuer": {
"id": "https://example.com",
"name": "DEMO STORE",
"identityProof": {
"type": "DNS-TXT",
Expand Down
9 changes: 2 additions & 7 deletions src/schema/2.0/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"required": ["type", "location"]
}
},
"required": ["name", "identityProof"],
"required": ["id", "name", "identityProof"],
"additionalProperties": false
}
},
Expand Down Expand Up @@ -67,10 +67,6 @@
},
"description": "Specific verifiable credential type as explained by https://www.w3.org/TR/vc-data-model/#types"
},
"statusType": {
"type": "string",
"description": "A valid credential status type as explained by https://www.w3.org/TR/vc-data-model/#types"
},
"validFrom": {
"type": "string",
"format": "date-time",
Expand Down Expand Up @@ -98,7 +94,6 @@
"url": {
"type": "string",
"description": "URL of a decentralised renderer to render this document",
"format": "uri",
"pattern": "^(https?)://"
}
},
Expand Down Expand Up @@ -165,6 +160,6 @@
}
},

"required": ["reference", "issuer", "statusType", "$template", "proof", "name", "validFrom"],
"required": ["reference", "issuer", "$template", "proof", "name", "validFrom"],
"additionalProperties": true
}
77 changes: 19 additions & 58 deletions src/schema/2.0/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,64 +181,6 @@ describe("open-attestation/2.0", () => {
});
});

describe("statusType", () => {
it("should be invalid if statusType is missing", () => {
expect.assertions(2);
const document = { ...sampleDoc };
delete document.statusType;
try {
issueDocument(document, { externalSchemaId: $id });
} catch (e) {
expect(e).toHaveProperty("message", "Invalid document");
expect(e).toHaveProperty("validationErrors", [
{
keyword: "required",
dataPath: "",
schemaPath: "#/required",
params: { missingProperty: "statusType" },
message: "should have required property 'statusType'"
}
]);
}
});
it("should be invalid if statusType is undefined", () => {
expect.assertions(2);
const document = { ...sampleDoc, statusType: undefined };
try {
issueDocument(document, { externalSchemaId: $id });
} catch (e) {
expect(e).toHaveProperty("message", "Invalid document");
expect(e).toHaveProperty("validationErrors", [
{
keyword: "required",
dataPath: "",
schemaPath: "#/required",
params: { missingProperty: "statusType" },
message: "should have required property 'statusType'"
}
]);
}
});
it("should be invalid if statusType is null", () => {
expect.assertions(2);
const document = { ...sampleDoc, statusType: null };
try {
issueDocument(document, { externalSchemaId: $id });
} catch (e) {
expect(e).toHaveProperty("message", "Invalid document");
expect(e).toHaveProperty("validationErrors", [
{
keyword: "type",
dataPath: ".statusType",
schemaPath: "#/properties/statusType/type",
params: { type: "string" },
message: "should be string"
}
]);
}
});
});

describe("validFrom", () => {
it("should be invalid if validFrom is missing", () => {
expect.assertions(2);
Expand Down Expand Up @@ -607,6 +549,25 @@ describe("open-attestation/2.0", () => {
]);
}
});
it("should be invalid if issuer has no id", () => {
expect.assertions(2);
const document = { ...sampleDoc, issuer: { ...sampleDoc.issuer } };
delete document.issuer.id;
try {
issueDocument(document, { externalSchemaId: $id });
} catch (e) {
expect(e).toHaveProperty("message", "Invalid document");
expect(e).toHaveProperty("validationErrors", [
{
keyword: "required",
dataPath: ".issuer",
schemaPath: "#/definitions/issuer/required",
params: { missingProperty: "id" },
message: "should have required property 'id'"
}
]);
}
});
it("should be invalid if issuer has no identityProof", () => {
expect.assertions(2);
const document = { ...sampleDoc, issuer: { ...sampleDoc.issuer } };
Expand Down
133 changes: 65 additions & 68 deletions src/schema/2.0/w3c.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,75 +5,72 @@ import { mapToW3cVc } from "./w3c";
describe("it should be correct", () => {
it("should really be correct", () => {
const document = issueDocument(sampleDoc);
expect(mapToW3cVc(document)).toEqual(
expect.objectContaining({
"@context": ["https://www.w3.org/2018/credentials/v1"],
type: ["VerifiableCredential"],
credentialSubject: {
type: "CredentialStatusType",
name: "Singapore Driving Licence",
recipient: {
name: "Recipient Name"
},
reference: "SERIAL_NUMBER_123",
unknownKey: "unknownValue",
$template: {
name: "CUSTOM_TEMPLATE",
type: "EMBEDDED_RENDERER",
url: "https://localhost:3000/renderer"
}
expect(mapToW3cVc(document)).toStrictEqual({
"@context": ["https://www.w3.org/2018/credentials/v1"],
type: ["VerifiableCredential"],
credentialSubject: {
name: "Singapore Driving Licence",
recipient: {
name: "Recipient Name"
},
validFrom: "2010-01-01T19:23:24Z",
validUntil: undefined,
issuer: {
id: undefined,
name: "DEMO STORE"
},
evidence: [
{
type: "DocumentVerification2018",
data: "BASE64_ENCODED_FILE",
filename: "sample.pdf",
mimeType: "application/pdf"
}
],
proof: {
method: "DOCUMENT_STORE",
type: "OpenAttestationSignature2018",
value: "0x9178F546D3FF57D7A6352bD61B80cCCD46199C2d",
identityProof: {
location: "tradetrust.io",
type: "DNS-TXT"
},
signature: {
merkleRoot: expect.any(String),
proof: [],
targetHash: expect.any(String),
type: "SHA3MerkleProof"
},
salts: [
{ type: "string", value: expect.any(String), path: "reference" },
{ type: "string", value: expect.any(String), path: "name" },
{ type: "string", value: expect.any(String), path: "statusType" },
{ type: "string", value: expect.any(String), path: "validFrom" },
{ type: "string", value: expect.any(String), path: "$template.name" },
{ type: "string", value: expect.any(String), path: "$template.type" },
{ type: "string", value: expect.any(String), path: "$template.url" },
{ type: "string", value: expect.any(String), path: "issuer.name" },
{ type: "string", value: expect.any(String), path: "issuer.identityProof.type" },
{ type: "string", value: expect.any(String), path: "issuer.identityProof.location" },
{ type: "string", value: expect.any(String), path: "proof.type" },
{ type: "string", value: expect.any(String), path: "proof.method" },
{ type: "string", value: expect.any(String), path: "proof.value" },
{ type: "string", value: expect.any(String), path: "recipient.name" },
{ type: "string", value: expect.any(String), path: "unknownKey" },
{ type: "string", value: expect.any(String), path: "attachments[0].type" },
{ type: "string", value: expect.any(String), path: "attachments[0].filename" },
{ type: "string", value: expect.any(String), path: "attachments[0].mimeType" },
{ type: "string", value: expect.any(String), path: "attachments[0].data" }
]
reference: "SERIAL_NUMBER_123",
unknownKey: "unknownValue",
$template: {
name: "CUSTOM_TEMPLATE",
type: "EMBEDDED_RENDERER",
url: "https://localhost:3000/renderer"
}
},
validFrom: "2010-01-01T19:23:24Z",
validUntil: undefined,
issuer: {
id: "https://example.com",
name: "DEMO STORE"
},
evidence: [
{
type: "DocumentVerification2018",
data: "BASE64_ENCODED_FILE",
filename: "sample.pdf",
mimeType: "application/pdf"
}
})
);
],
proof: {
method: "DOCUMENT_STORE",
type: "OpenAttestationSignature2018",
value: "0x9178F546D3FF57D7A6352bD61B80cCCD46199C2d",
identityProof: {
location: "tradetrust.io",
type: "DNS-TXT"
},
signature: {
merkleRoot: expect.any(String),
proof: [],
targetHash: expect.any(String),
type: "SHA3MerkleProof"
},
salts: [
{ type: "string", value: expect.any(String), path: "reference" },
{ type: "string", value: expect.any(String), path: "name" },
{ type: "string", value: expect.any(String), path: "validFrom" },
{ type: "string", value: expect.any(String), path: "$template.name" },
{ type: "string", value: expect.any(String), path: "$template.type" },
{ type: "string", value: expect.any(String), path: "$template.url" },
{ type: "string", value: expect.any(String), path: "issuer.id" },
{ type: "string", value: expect.any(String), path: "issuer.name" },
{ type: "string", value: expect.any(String), path: "issuer.identityProof.type" },
{ type: "string", value: expect.any(String), path: "issuer.identityProof.location" },
{ type: "string", value: expect.any(String), path: "proof.type" },
{ type: "string", value: expect.any(String), path: "proof.method" },
{ type: "string", value: expect.any(String), path: "proof.value" },
{ type: "string", value: expect.any(String), path: "recipient.name" },
{ type: "string", value: expect.any(String), path: "unknownKey" },
{ type: "string", value: expect.any(String), path: "attachments[0].type" },
{ type: "string", value: expect.any(String), path: "attachments[0].filename" },
{ type: "string", value: expect.any(String), path: "attachments[0].mimeType" },
{ type: "string", value: expect.any(String), path: "attachments[0].data" }
]
}
});
});
});
7 changes: 3 additions & 4 deletions src/schema/2.0/w3c.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,12 @@ const getSalts = (document: SignedDocument<v2.OpenAttestationDocument>): Salt[]
};

/**
* IT S A FUCKING POC GOT IT ?
* This function is not production ready and is a simple POC to demonstrate that we are able to transform our document to W3C VC
* https://www.w3.org/TR/vc-data-model/#types
*/
export const mapToW3cVc = (document: SignedDocument<v2.OpenAttestationDocument>): any => {
const { proof, issuer, attachments, type, validFrom, validUntil, statusType, "@context": context, ...rest } = getData(
document
);
const { proof, issuer, attachments, type, validFrom, validUntil, "@context": context, ...rest } = getData(document);
const salts = getSalts(document);
return {
"@context": ["https://www.w3.org/2018/credentials/v1", ...(context ?? [])],
Expand All @@ -41,7 +40,7 @@ export const mapToW3cVc = (document: SignedDocument<v2.OpenAttestationDocument>)
},
validFrom,
validUntil,
credentialSubject: { ...rest, type: statusType },
credentialSubject: { ...rest },
evidence: attachments,
proof: {
...proof,
Expand Down
4 changes: 2 additions & 2 deletions test/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ import {
TemplateType
} from "../src/__generated__/schemaV2";
// Disable tslint import/no-unresolved for this because it usually doesn't exist until build runs

type IssueDocumentReturnType = ReturnType<typeof issueDocument>;

const openAttestationData: OpenAttestationDocument = {
reference: "document identifier",
statusType: "CredentialStatusType",
validFrom: "2010-01-01T19:23:24Z",
name: "document owner name",
$template: {
Expand All @@ -29,6 +28,7 @@ const openAttestationData: OpenAttestationDocument = {
url: "http://some.example.com"
},
issuer: {
id: "http://some.example.com",
name: "DEMO STORE",
identityProof: {
type: IdentityProofType.DNSTxt,
Expand Down

0 comments on commit 145c32b

Please sign in to comment.