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

feat: tradetrust v4 #338

Merged
merged 6 commits into from
Jun 21, 2024
Merged
Changes from 1 commit
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
Next Next commit
feat: tradetrust v4
rongquan1 committed Jun 12, 2024
commit d71735a9cea0c38d8f958ca42e841f314745c244
1 change: 1 addition & 0 deletions .github/workflows/test_ci.yml
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ on:
push:
branches:
- master
- beta

jobs:
test:
785 changes: 743 additions & 42 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -53,15 +53,15 @@
"@govtechsg/ethers-contract-hook": "^2.2.0",
"@hapi/joi": "^17.1.1",
"@rjsf/core": "^3.1.0",
"@tradetrust-tt/decentralized-renderer-react-components": "^3.14.3",
"@tradetrust-tt/decentralized-renderer-react-components": "3.15.0-beta.1",
"@tradetrust-tt/dnsprove": "^2.12.2",
"@tradetrust-tt/document-store": "^2.7.0",
"@tradetrust-tt/token-registry": "^4.10.1",
"@tradetrust-tt/tradetrust": "^6.9.4",
"@tradetrust-tt/tradetrust": "6.10.0-beta.1",
"@tradetrust-tt/tradetrust-cli": "^2.18.1",
"@tradetrust-tt/tradetrust-ui-components": "^2.22.2",
"@tradetrust-tt/tradetrust-utils": "^1.13.4",
"@tradetrust-tt/tt-verify": "^8.8.5",
"@tradetrust-tt/tt-verify": "8.10.0-beta.1",
"ajv": "^8.6.0",
"axios": "^0.21.4",
"babel-jest": "^29.7.0",
8 changes: 7 additions & 1 deletion src/common/API/storageAPI.tsx
Original file line number Diff line number Diff line change
@@ -37,7 +37,13 @@ export const uploadToStorage = async (
doc: WrappedDocument,
documentStorage: DocumentStorage
): Promise<AxiosResponse> => {
const { links } = utils.isRawV3Document(doc.rawDocument) ? doc.rawDocument.credentialSubject : doc.rawDocument;
const { links } = (
utils.isRawTTV4Document(doc.rawDocument)
? doc.rawDocument.credentialSubject
: utils.isRawV3Document(doc.rawDocument)
? doc.rawDocument.credentialSubject
: doc.rawDocument
) as any;
const qrCodeObj = decodeQrCode(links.self.href);
const uri = qrCodeObj.payload.uri;

115 changes: 102 additions & 13 deletions src/common/hook/useQueue/utils/publish.tsx
Original file line number Diff line number Diff line change
@@ -2,11 +2,16 @@ import {
utils,
wrapDocuments as wrapDocumentsV2,
__unsafe__use__it__at__your__own__risks__wrapDocuments as wrapDocumentsV3,
_unsafe_use_it_at_your_own_risk_v4_alpha_tt_wrapDocuments as wrapDocumentsTTV4,
TTv4,
signDocument,
SignedWrappedDocument,
SUPPORTED_SIGNING_ALGORITHM,
} from "@tradetrust-tt/tradetrust";
import { Signer } from "ethers";
import { defaultsDeep, groupBy } from "lodash";
import { IdentityProofType } from "../../../../constants";
import { publishDnsDidVerifiableDocumentJob } from "../../../../services/publishing";
import { publishDnsDidVerifiableDocumentJob, publishIdvcVerifiableDocumentJob } from "../../../../services/publishing";
import {
ActionsUrlObject,
Config,
@@ -17,7 +22,7 @@ import {
RawDocument,
} from "../../../../types";
import { getQueueNumber } from "../../../API/storageAPI";
import { encodeQrCode, getDataV3, getDocumentNetwork } from "../../../utils";
import { encodeQrCode, getDataV3, getDataTTV4, getDocumentNetwork } from "../../../utils";
import { ChainInfo, supportedMainnet } from "../../../../constants/chainInfo";

const redirectUrl = (network: Network) => {
@@ -52,7 +57,16 @@ const getReservedStorageUrl = async (documentStorage: DocumentStorage, network:
};

const getContractAddressFromRawDoc = (document: any) => {
if (utils.isRawV3Document(document)) {
if (utils.isRawTTV4Document(document)) {
if (document.issuer.identityProof.identityProofType.toString() === IdentityProofType.DNSDid)
return IdentityProofType.DNSDid;
if (
document.issuer.identityProof.identityProofType.toString() === IdentityProofType.Idvc &&
document.credentialStatus.credentialStatusType !== TTv4.CredentialStatusType.TokenRegistry
)
return IdentityProofType.Idvc;
return document.credentialStatus.location;
} else if (utils.isRawV3Document(document)) {
return document.openAttestationMetadata.identityProof.type.toString() === IdentityProofType.DNSDid
? IdentityProofType.DNSDid
: document.openAttestationMetadata.proof.value;
@@ -79,7 +93,13 @@ export const getRawDocuments = async (forms: FormEntry[], config: Config): Promi
const formDefaults = formConfig.defaults;
const documentNetwork = getDocumentNetwork(config.network);
let formData;
if (utils.isRawV3Document(data.formData)) {
if (utils.isRawTTV4Document(data.formData)) {
formData = {
...documentNetwork,
credentialSubject: { ...getDataTTV4(data.formData), ...qrUrl },
...(data.formData.attachments && { attachments: data.formData.attachments }),
};
} else if (utils.isRawV3Document(data.formData)) {
formData = {
...documentNetwork,
credentialSubject: { ...getDataV3(data.formData), ...qrUrl }, // https://github.com/TradeTrust/document-creator-website/issues/256, using `getDataV3` here so not to break existing flows
@@ -105,7 +125,29 @@ export const getRawDocuments = async (forms: FormEntry[], config: Config): Promi
};

const wrapDocuments = async (rawDocuments: any[]) => {
return utils.isRawV3Document(rawDocuments[0]) ? await wrapDocumentsV3(rawDocuments) : wrapDocumentsV2(rawDocuments);
return utils.isRawTTV4Document(rawDocuments[0])
? await wrapDocumentsTTV4(rawDocuments)
: utils.isRawV3Document(rawDocuments[0])
? await wrapDocumentsV3(rawDocuments)
: wrapDocumentsV2(rawDocuments);
};

const signTTV4WrappedDocuments = async (
wrappedDocuments: TTv4.WrappedDocument[],
signers: Signer
): Promise<TTv4.WrappedDocument[]> => {
const signedDocumentsList: SignedWrappedDocument<TTv4.TradeTrustDocument>[] = [];
const signingDocuments = wrappedDocuments.map(async (doc) => {
try {
const signedDocument = await signDocument(doc, SUPPORTED_SIGNING_ALGORITHM.Secp256k1VerificationKey2018, signers);
signedDocumentsList.push(signedDocument);
} catch (e) {
throw new Error(`Error signing document: ${doc.issuer.id}`);
}
});

await Promise.allSettled(signingDocuments);
return signedDocumentsList;
};

const processVerifiableDocuments = async (
@@ -145,10 +187,15 @@ export const groupDocumentsIntoJobs = async (
const groupedVerifiableDocuments = groupBy(verifiableDocuments, "contractAddress");
const verifiableDocumentsWithDocumentStore = { ...groupedVerifiableDocuments };
delete verifiableDocumentsWithDocumentStore[IdentityProofType.DNSDid];
delete verifiableDocumentsWithDocumentStore[IdentityProofType.Idvc];
const verifiableDocumentsWithDnsDid =
Object.keys(groupedVerifiableDocuments).indexOf(IdentityProofType.DNSDid) >= 0
? [...groupedVerifiableDocuments[IdentityProofType.DNSDid]]
: [];
const verifiableDocumentsWithIdvc =
Object.keys(groupedVerifiableDocuments).indexOf(IdentityProofType.Idvc) >= 0
? [...groupedVerifiableDocuments[IdentityProofType.Idvc]]
: [];
const documentStoreAddresses = Object.keys(verifiableDocumentsWithDocumentStore);

let nonce = currentNonce;
@@ -162,6 +209,9 @@ export const groupDocumentsIntoJobs = async (
const verifiableDocumentsV3 = verifiableDocumentsWithDocumentStore[contractAddress].filter((docs) => {
return utils.isRawV3Document(docs.rawDocument);
});
const verifiableDocumentsTTV4 = verifiableDocumentsWithDocumentStore[contractAddress].filter((docs) => {
return utils.isRawTTV4Document(docs.rawDocument);
});

if (verifiableDocumentsV2.length > 0) {
const verifiableDocumentV2Job = await processVerifiableDocuments(nonce, contractAddress, verifiableDocumentsV2);
@@ -174,6 +224,12 @@ export const groupDocumentsIntoJobs = async (
jobs.push(verifiableDocumentV3Job);
nonce += TX_NEEDED_FOR_VERIFIABLE_DOCUMENTS;
}

if (verifiableDocumentsTTV4.length > 0) {
const verifiableDocumentV4Job = await processVerifiableDocuments(nonce, contractAddress, verifiableDocumentsTTV4);
jobs.push(verifiableDocumentV4Job);
nonce += TX_NEEDED_FOR_VERIFIABLE_DOCUMENTS;
}
}

// Process all verifiable document with DNS-DID next
@@ -196,20 +252,53 @@ export const groupDocumentsIntoJobs = async (
nonce += TX_NEEDED_FOR_VERIFIABLE_DOCUMENTS;
}

// Process all verifiable document with IDVC next
if (verifiableDocumentsWithIdvc.length > 0) {
console.log("sdasddsxas");
const didRawDocuments = verifiableDocumentsWithIdvc.map((doc) => doc.rawDocument);
const wrappedIdvcDocuments = await wrapDocuments(didRawDocuments);
// Sign IDVC document here as we preparing the jobs
const signedIdvcDocument = await publishIdvcVerifiableDocumentJob(wrappedIdvcDocuments, signer);
jobs.push({
type: verifiableDocumentsWithIdvc[0].type,
nonce,
contractAddress: IdentityProofType.Idvc,
documents: verifiableDocumentsWithIdvc.map((doc, index) => ({
...doc,
wrappedDocument: signedIdvcDocument[index],
})),
merkleRoot: wrappedIdvcDocuments[0].signature?.merkleRoot,
payload: {},
});
nonce += TX_NEEDED_FOR_VERIFIABLE_DOCUMENTS;
}

// Process all transferable records next
for (const transferableRecord of transferableRecords) {
const { type, contractAddress, rawDocument, payload } = transferableRecord;
const transferableDocuments = await wrapDocuments([rawDocument]);
const merkleRoot = utils.getMerkleRoot(transferableDocuments[0]);

jobs.push({
type,
nonce,
contractAddress,
documents: [{ ...transferableRecord, wrappedDocument: transferableDocuments[0] }],
merkleRoot: merkleRoot,
payload,
});
if (utils.isWrappedTTV4Document(transferableDocuments[0])) {
const signedDocuments = await signTTV4WrappedDocuments(transferableDocuments, signer);
jobs.push({
type,
nonce,
contractAddress,
documents: [{ ...transferableRecord, wrappedDocument: signedDocuments[0] }],
merkleRoot: merkleRoot,
payload,
});
} else {
jobs.push({
type,
nonce,
contractAddress,
documents: [{ ...transferableRecord, wrappedDocument: transferableDocuments[0] }],
merkleRoot: merkleRoot,
payload,
});
}
nonce += TX_NEEDED_FOR_TRANSFERABLE_RECORDS;
}

6 changes: 6 additions & 0 deletions src/common/hook/useQueue/utils/revoke.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import sampleV2DidDocument from "../../../../test/fixtures/sample-files/v2/did/sample-v2-did-wrapped.json";
import sampleWrappedDocument from "../../../../test/fixtures/sample-files/v2/wrapped/sample-wrapped-document.json";
import sampleV3DidDocument from "../../../../test/fixtures/sample-files/v3/did/sample-v3-did-wrapped.json";
import sampleV4DidDocument from "../../../../test/fixtures/sample-files/v4/did/sample-v4-did-signed.json";
import { getRevokeAddress, getRevokingJobs } from "./revoke";

describe("getRevokingJobs", () => {
@@ -29,4 +30,9 @@ describe("getRevokeAddress", () => {
const revokeAddress = getRevokeAddress(sampleV3DidDocument);
expect(revokeAddress).toBe("0x8bA63EAB43342AAc3AdBB4B827b68Cf4aAE5Caca");
});

it("should get the revocation store address for TTv4 DID verifiable document", () => {
const revokeAddress = getRevokeAddress(sampleV4DidDocument);
expect(revokeAddress).toBe("0xA594f6e10564e87888425c7CC3910FE1c800aB0B");
});
});
2 changes: 2 additions & 0 deletions src/common/hook/useQueue/utils/revoke.tsx
Original file line number Diff line number Diff line change
@@ -21,6 +21,8 @@ export const getRevokeAddress = (document: any) => {
revokeAddress = issuer.revocation?.location || "";
} else if (utils.isWrappedV3Document(document)) {
revokeAddress = document.openAttestationMetadata.proof.revocation?.location || "";
} else if (utils.isWrappedTTV4Document(document)) {
revokeAddress = document.credentialStatus.location || "";
}
// for dns-txt document with document store
if (!revokeAddress) {
23 changes: 22 additions & 1 deletion src/common/utils.tsx
Original file line number Diff line number Diff line change
@@ -113,6 +113,20 @@ export const getDocumentNetwork = (network: Network): NetworkObject => {
};
};

/*
* getDataTTV4
* Omit fields that are VC + TT V4 related, we are only interested in document data.
*/
export const getDataTTV4: any = (data: any) => {
/* eslint-disable @typescript-eslint/no-unused-vars */
const { type, validFrom, issuer, credentialSubject, credentialStatus, renderMethod, attachments, network, ...rest } =
data; // omit these fields
/* eslint-enable @typescript-eslint/no-unused-vars */

delete rest["@context"]; // omit these fields
return rest;
};

/*
* getDataV3
* Omit fields that are VC + OA V3 related, we are only interested in document data.
@@ -161,6 +175,11 @@ export const hasVcContext = (document: any) => {
return !!document["@context"]; // Unable to use utils.isRawV3Document, due how document data is handled throughout the application
};

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const hasCredentialStatus = (document: any) => {
return !!document["credentialStatus"]; // Unable to use utils.isRawTTV4Document, due how document data is handled throughout the application
};

const hasTemplate = (document: any) => {
return !!document["$template"]; // Unable to use utils.isRawV2Document, due how document data is handled throughout the application
};
@@ -174,7 +193,9 @@ const hasTemplate = (document: any) => {
* 3. user input flow - single document, data manually filled by user.
*/
export const getDataToValidate: any = (data: any) => {
if (hasVcContext(data)) {
if (hasCredentialStatus(data)) {
return getDataTTV4(data);
} else if (hasVcContext(data)) {
return getDataV3(data);
} else if (hasTemplate(data)) {
return getDataV2(data);
10 changes: 8 additions & 2 deletions src/components/DynamicFormContainer/DynamicFormLayout.tsx
Original file line number Diff line number Diff line change
@@ -15,8 +15,9 @@ import { DocumentPreview } from "./DocumentPreview";
import { DynamicForm } from "./DynamicForm";
import { DynamicFormHeader } from "./DynamicFormHeader";
import { FormErrorBanner } from "./FormErrorBanner";
import { validateData, getDataToValidate } from "./../../common/utils";
import { validateData, getDataToValidate, getDataV3, getDataTTV4 } from "./../../common/utils";
import { FormErrors } from "./../../types";
import { utils } from "@tradetrust-tt/tradetrust";

export const DynamicFormLayout: FunctionComponent = () => {
const [showDeleteModal, setDeleteModal] = useState(false);
@@ -113,7 +114,12 @@ export const DynamicFormLayout: FunctionComponent = () => {
}, timeout);
};

const currentUnwrappedData = defaultsDeep({}, currentForm.data.formData, currentFormTemplate.defaults);
const unwrappedData = defaultsDeep({}, currentForm.data.formData, currentFormTemplate.defaults);
const currentUnwrappedData = utils.isRawTTV4Document(unwrappedData)
? { ...unwrappedData, credentialSubject: { ...getDataTTV4(unwrappedData) } }
: utils.isRawV3Document(unwrappedData)
? { ...unwrappedData, credentialSubject: { ...getDataV3(unwrappedData) } }
: unwrappedData;

return (
<OnCloseGuard active={activeFormIndex !== undefined}>
Loading