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

Chore: tidy v4 exports #278

Merged
merged 8 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
17 changes: 17 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@
"module": "dist/esm/index.js",
"browser": "dist/index.umd.js",
"types": "dist/types/index.d.ts",
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"types": "./dist/types/index.d.ts"
},
"./4.0": {
"import": "./dist/esm/4.0/exports/index.js",
"require": "./dist/cjs/4.0/exports/index.js",
"types": "./dist/types/4.0/exports/index.d.ts"
},
"./4.0/*": {
"import": "./dist/esm/4.0/exports/*.js",
"require": "./dist/cjs/4.0/exports/*.js",
"types": "./dist/types/4.0/exports/*.d.ts"
}
},
"scripts": {
"benchmark:qr-code": "ts-node --transpile-only benchmarks/qr-code",
"build": "npm run clean && npm run build:cjs && npm run build:esm && npm run build:umd && npm run build:type",
Expand Down
7 changes: 7 additions & 0 deletions src/2.0/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { unsaltData } from "./salt";
import { OpenAttestationDocument, WrappedDocument } from "./types";

type Extract<P> = P extends WrappedDocument<infer T> ? T : never;
export const getData = <T extends WrappedDocument<OpenAttestationDocument>>(document: T): Extract<T> => {
return unsaltData(document.data);
};
9 changes: 2 additions & 7 deletions src/4.0/__tests__/e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import {
obfuscate,
validateSchema,
verifySignature,
_unsafe_use_it_at_your_own_risk_v4_alpha_wrapDocument as wrapDocument,
_unsafe_use_it_at_your_own_risk_v4_alpha_wrapDocuments as wrapDocuments,
} from "../..";
import { obfuscate, validateSchema, verifySignature } from "../..";
import { cloneDeep, omit } from "lodash";
import { RAW_DOCUMENT_DID, SIGNED_WRAPPED_DOCUMENT_DID, WRAPPED_DOCUMENT_DID } from "../fixtures";
import { V4Document } from "../types";
import { wrapDocument, wrapDocuments } from "../wrap";

const DOCUMENT_ONE = {
...RAW_DOCUMENT_DID,
Expand Down
19 changes: 15 additions & 4 deletions src/4.0/__tests__/sign.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { signDocument } from "../../index";
import { SUPPORTED_SIGNING_ALGORITHM } from "../../shared/@types/sign";
import { Wallet } from "ethers";
import { WRAPPED_DOCUMENT_DID } from "../fixtures";
import { V4SignedWrappedDocument } from "../types";
import { signDocument } from "../sign";

describe("V4 sign", () => {
it("should sign a document", async () => {
Expand Down Expand Up @@ -87,8 +87,19 @@ describe("V4 sign", () => {
private: "0x812269266b34d2919f737daf22db95f02642f8cdc0ca673bf3f701599f4971f5",
}
)
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Unsupported document type: Only OpenAttestation v2, v3 or v4 documents can be signed"`
);
).rejects.toThrowErrorMatchingInlineSnapshot(`
"Document has not been properly wrapped:
{
"_errors": [],
"proof": {
"_errors": [],
"merkleRoot": {
"_errors": [
"Required"
]
}
}
}"
`);
});
});
11 changes: 10 additions & 1 deletion src/4.0/validate/context.ts → src/4.0/context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { expand, Options, JsonLdDocument } from "jsonld";
import { fetch } from "cross-fetch";
import { ContextUrl } from "../../shared/@types/document";

export const ContextUrl = {
v2_vc: "https://www.w3.org/ns/credentials/v2",
v4_alpha: "https://schemata.openattestation.com/com/openattestation/4.0/alpha-context.json",
} as const;

export const ContextType = {
BaseContext: "VerifiableCredential",
V4AlphaContext: "OpenAttestationCredential",
} as const;

const preloadedContextList = [ContextUrl.v2_vc, ContextUrl.v4_alpha];
const contexts: Map<string, any> = new Map();
Expand Down
2 changes: 1 addition & 1 deletion src/4.0/diagnose.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Diagnose } from "src/shared/utils/@types/diagnose";
import type { Diagnose } from "../shared/utils/@types/diagnose";
import { V4WrappedDocument, V4SignedWrappedDocument, V4Document } from "./types";

export const v4Diagnose: Diagnose = ({ document, kind, debug }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/4.0/digest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { sortBy } from "lodash";
import { keccak256 } from "js-sha3";
import { V4Document, Salt } from "./types";
import { LeafValue, traverseAndFlatten } from "./traverseAndFlatten";
import { hashToBuffer } from "../shared/utils";
import { hashToBuffer } from "../shared/utils/hashing";

export const digestCredential = (document: V4Document, salts: Salt[], obfuscatedData: string[]) => {
// find all leaf nodes in the document and hash them
Expand Down
6 changes: 6 additions & 0 deletions src/4.0/exports/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from "./wrap";
export * from "./sign";
export * from "./obfuscate";
export * from "./verify";
export * from "./utils";
export * from "./types";
1 change: 1 addition & 0 deletions src/4.0/exports/obfuscate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { obfuscateVerifiableCredential as obfuscate, obfuscateErrors } from "../obfuscate";
1 change: 1 addition & 0 deletions src/4.0/exports/sign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { signDocument, signDocumentErrors } from "../sign";
5 changes: 5 additions & 0 deletions src/4.0/exports/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type {
V4Document as Document,
V4WrappedDocument as WrappedDocument,
V4SignedWrappedDocument as SignedWrappedDocument,
} from "../types";
6 changes: 6 additions & 0 deletions src/4.0/exports/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { v4Diagnose as diagnose } from "../diagnose";
export {
isV4Document as isDocument,
isV4WrappedDocument as isWrappedDocument,
isV4SignedWrappedDocument as isSignedWrappedDocument,
} from "../types";
1 change: 1 addition & 0 deletions src/4.0/exports/verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { verify } from "../verify";
1 change: 1 addition & 0 deletions src/4.0/exports/wrap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { wrapDocument, wrapDocuments, wrapDocumentErrors } from "../wrap";
25 changes: 21 additions & 4 deletions src/4.0/sign.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
import { sign } from "../shared/signer";
import { SigningKey, SUPPORTED_SIGNING_ALGORITHM } from "../shared/@types/sign";
import { SigningKey } from "../shared/@types/sign";
import { ethers } from "ethers";
import { V4Document, V4WrappedDocument, V4SignedWrappedDocument } from "./types";
import type { ZodError } from "zod";

export const signDocument = async <T extends V4Document>(
document: V4SignedWrappedDocument<T> | V4WrappedDocument<T>,
algorithm: SUPPORTED_SIGNING_ALGORITHM,
algorithm: "Secp256k1VerificationKey2018",
keyOrSigner: SigningKey | ethers.Signer
): Promise<V4SignedWrappedDocument<T>> => {
const parsedResults = V4WrappedDocument.pick({ proof: true }).passthrough().safeParse(document);
if (!parsedResults.success) {
throw new Error("Document has not been properly wrapped " + JSON.stringify(parsedResults.error));
throw new WrappedDocumentValidationError(parsedResults.error);
}

if (!SigningKey.guard(keyOrSigner) && keyOrSigner.signMessage === undefined) {
throw new Error(`Either a keypair or ethers.js Signer must be provided`);
}

const { proof: validatedProof } = parsedResults.data;
const merkleRoot = `0x${validatedProof.merkleRoot}`;
const signature = await sign(algorithm, merkleRoot, keyOrSigner);
const proof: V4SignedWrappedDocument["proof"] = {
...validatedProof,
key: SigningKey.guard(keyOrSigner) ? keyOrSigner.public : `did:ethr:${await keyOrSigner.getAddress()}#controller`,
key: "public" in keyOrSigner ? keyOrSigner.public : `did:ethr:${await keyOrSigner.getAddress()}#controller`,
signature,
};
return { ...document, proof };
};

class WrappedDocumentValidationError extends Error {
constructor(public error: ZodError) {
super(`Document has not been properly wrapped: \n ${JSON.stringify(error.format(), null, 2)}`);
Object.setPrototypeOf(this, WrappedDocumentValidationError.prototype);
}
}

export const signDocumentErrors = {
WrappedDocumentValidationError,
};
14 changes: 13 additions & 1 deletion src/4.0/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import z from "zod";
import { ContextUrl, ContextType } from "../shared/@types/document";
import { ContextUrl, ContextType } from "./context";

// Custom URI validation function
const URI_REGEX =
Expand Down Expand Up @@ -305,3 +305,15 @@ export type PartialDeep<T> = T extends string | number | bigint | boolean | null
: {
[K in keyof T]?: PartialDeep<T[K]>;
};

export const isV4Document = (document: unknown): document is V4Document => {
return V4Document.safeParse(document).success;
};

export const isV4WrappedDocument = (document: unknown): document is V4WrappedDocument => {
return V4WrappedDocument.safeParse(document).success;
};

export const isV4SignedWrappedDocument = (document: unknown): document is V4SignedWrappedDocument => {
return V4SignedWrappedDocument.safeParse(document).success;
};
1 change: 0 additions & 1 deletion src/4.0/validate/index.ts

This file was deleted.

9 changes: 6 additions & 3 deletions src/4.0/wrap.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { hashToBuffer, isStringArray } from "../shared/utils";
import { hashToBuffer } from "../shared/utils/hashing";
import { MerkleTree } from "../shared/merkle";
import { ContextType, ContextUrl } from "../shared/@types/document";
import { ContextUrl, ContextType, UnableToInterpretContextError, interpretContexts } from "./context";
import { NoExtraProperties, V4Document, V4WrappedDocument, W3cVerifiableCredential } from "./types";
import { digestCredential } from "../4.0/digest";
import { encodeSalt, salt } from "./salt";
import { UnableToInterpretContextError, interpretContexts } from "./validate";
import { ZodError } from "zod";

export const wrapDocument = async <T extends V4Document>(
Expand Down Expand Up @@ -151,3 +150,7 @@ export const wrapDocumentErrors = {
DataModelValidationError,
UnableToInterpretContextError,
};

function isStringArray(input: unknown): input is string[] {
return Array.isArray(input) && input.every((i) => typeof i === "string");
}
30 changes: 7 additions & 23 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@ import { digestCredential as digestCredentialV3 } from "./3.0/digest";
import { obfuscateVerifiableCredential as obfuscateVerifiableCredentialV3 } from "./3.0/obfuscate";
import { OpenAttestationDocument as OpenAttestationDocumentV3 } from "./__generated__/schema.3.0";

import * as v4 from "./4.0/types";
import { signDocument as signDocumentV4 } from "./4.0/sign";
import { verify as verifyV4 } from "./4.0/verify";
import { digestCredential as digestCredentialV4 } from "./4.0/digest";
import {
ObfuscateVerifiableCredentialResult,
obfuscateVerifiableCredential as obfuscateVerifiableCredentialV4,
} from "./4.0/obfuscate";
import { v4Diagnose } from "./4.0/diagnose";
import { V4WrappedDocument } from "./4.0/types";

export function wrapDocument<T extends OpenAttestationDocumentV2>(
data: T,
Expand Down Expand Up @@ -63,11 +61,6 @@ export function __unsafe__use__it__at__your__own__risks__wrapDocuments<T extends
return wrapDocumentsV3(dataArray, options ?? { version: SchemaId.v3 });
}

export {
wrapDocument as _unsafe_use_it_at_your_own_risk_v4_alpha_wrapDocument,
wrapDocuments as _unsafe_use_it_at_your_own_risk_v4_alpha_wrapDocuments,
} from "./4.0/wrap";

export const validateSchema = (document: WrappedDocument<any>): boolean => {
if (utils.isWrappedV2Document(document) || document?.version === SchemaId.v2)
return validate(document, getSchema(SchemaId.v2)).length === 0;
Expand All @@ -88,18 +81,10 @@ export function verifySignature<T extends WrappedDocument<OpenAttestationDocumen
throw new Error("Unsupported document type: Only OpenAttestation v2, v3 or v4 documents can be signature verified");
}

export function digest(document: OpenAttestationDocumentV3, salts: v3.Salt[], obfuscatedData: string[]): string;
export function digest(document: v4.V4Document, salts: v4.Salt[], obfuscatedData: string[]): string;
export function digest(
document: OpenAttestationDocumentV3 | v4.V4Document,
salts: v3.Salt[] | v4.Salt[],
obfuscatedData: string[]
): string {
export function digest(document: OpenAttestationDocumentV3, salts: v3.Salt[], obfuscatedData: string[]): string {
if (utils.isRawV3Document(document)) return digestCredentialV3(document, salts, obfuscatedData);
else if (utils.isRawV4Document(document)) return digestCredentialV4(document, salts, obfuscatedData);

throw new Error(
"Unsupported credential type: This function only supports digest generation for OpenAttestation v3 or v4 credentials"
"Unsupported credential type: This function only supports digest generation for OpenAttestation v3 credentials"
);
}

Expand All @@ -111,7 +96,7 @@ export function obfuscate<T extends OpenAttestationDocumentV3>(
document: WrappedDocument<T>,
fields: string[] | string
): WrappedDocument<T>;
export function obfuscate<T extends v4.V4WrappedDocument>(
export function obfuscate<T extends V4WrappedDocument>(
document: T,
fields: string[] | string
): ObfuscateVerifiableCredentialResult<T>;
Expand All @@ -127,7 +112,7 @@ export const isSchemaValidationError = (error: any): error is SchemaValidationEr
return !!error.validationErrors;
};

export async function signDocument<T extends v2.OpenAttestationDocument | v3.OpenAttestationDocument | v4.V4Document>(
export async function signDocument<T extends v2.OpenAttestationDocument | v3.OpenAttestationDocument>(
document: WrappedDocument<T> | SignedWrappedDocument<T>,
algorithm: SUPPORTED_SIGNING_ALGORITHM,
keyOrSigner: SigningKey | ethers.Signer
Expand All @@ -140,12 +125,11 @@ export async function signDocument<T extends v2.OpenAttestationDocument | v3.Ope
let results: unknown;
if (utils.isWrappedV2Document(document)) results = signDocumentV2(document, algorithm, keyOrSigner);
else if (utils.isWrappedV3Document(document)) results = signDocumentV3(document, algorithm, keyOrSigner);
else if (utils.isWrappedV4Document(document)) results = signDocumentV4(document, algorithm, keyOrSigner);

if (results) return results as SignedWrappedDocument<T>;

// Unreachable code atm until utils.isWrappedV2Document & utils.isWrappedV3Document becomes more strict
throw new Error("Unsupported document type: Only OpenAttestation v2, v3 or v4 documents can be signed");
throw new Error("Unsupported document type: Only OpenAttestation v2 or v3documents can be signed");
Copy link
Member

Choose a reason for hiding this comment

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

Something small:

Suggested change
throw new Error("Unsupported document type: Only OpenAttestation v2 or v3documents can be signed");
throw new Error("Unsupported document type: Only OpenAttestation v2 or v3 documents can be signed");

Copy link
Contributor Author

Choose a reason for hiding this comment

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

my bad, will fix HAHA

}

export { digestDocument } from "./2.0/digest";
Expand All @@ -159,4 +143,4 @@ export * from "./shared/signer";
export { getData } from "./shared/utils"; // keep it to avoid breaking change, moved from privacy to utils
export { v2 };
export { v3 };
export { v4 };
export * as v4 from "./4.0/exports";
10 changes: 0 additions & 10 deletions src/shared/@types/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,6 @@ export enum SchemaId {
v3 = "https://schema.openattestation.com/3.0/schema.json",
}

export const ContextUrl = {
v2_vc: "https://www.w3.org/ns/credentials/v2",
v4_alpha: "https://schemata.openattestation.com/com/openattestation/4.0/alpha-context.json",
} as const;

export const ContextType = {
BaseContext: "VerifiableCredential",
V4AlphaContext: "OpenAttestationCredential",
} as const;

export const OpenAttestationHexString = String.withConstraint(
(value) => ethers.utils.isHexString(`0x${value}`, 32) || `${value} has not the expected length of 32 bytes`
);
Expand Down
2 changes: 1 addition & 1 deletion src/shared/merkle/merkle.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Hash, hashArray, toBuffer, hashToBuffer, combineHashBuffers } from "../utils";
import { Hash, hashArray, toBuffer, hashToBuffer, combineHashBuffers } from "../utils/hashing";

function getNextLayer(elements: Buffer[]) {
return elements.reduce((layer: Buffer[], element, index, arr) => {
Expand Down
Loading
Loading