@trustvc/trustvc 1.2.11 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/dist/cjs/core/documentBuilder.js +198 -2
- package/dist/cjs/core/index.js +7 -0
- package/dist/esm/core/documentBuilder.js +198 -2
- package/dist/esm/core/index.js +1 -0
- package/dist/types/core/documentBuilder.d.ts +43 -3
- package/dist/types/core/index.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ TrustVC is a comprehensive wrapper library designed to simplify the signing and
|
|
|
24
24
|
- [TradeTrustToken](#tradetrusttoken)
|
|
25
25
|
- [a) Token Registry v4](#a-token-registry-v4)
|
|
26
26
|
- [b) Token Registry V5](#b-token-registry-v5)
|
|
27
|
+
- [7. **Document Builder**](#7-document-builder)
|
|
27
28
|
|
|
28
29
|
## Installation
|
|
29
30
|
|
|
@@ -587,3 +588,155 @@ function rejectTransferOwners(bytes calldata _remark) external;
|
|
|
587
588
|
```
|
|
588
589
|
|
|
589
590
|
For more information on Token Registry and Title Escrow contracts **version v5**, please visit the readme of [TradeTrust Token Registry V5](https://github.com/TradeTrust/token-registry/blob/master/README.md)
|
|
591
|
+
|
|
592
|
+
### 7. **Document Builder**
|
|
593
|
+
> The `DocumentBuilder` class helps build and manage W3C Verifiable Credentials (VCs) with credential status features. It supports creating documents with two types of credential statuses: `transferableRecords` and `verifiableDocument`. It can sign the document using a private key, verify its signature, and serialize the document to a JSON format. Additionally, it allows for configuration of document rendering methods and expiration dates.
|
|
594
|
+
|
|
595
|
+
#### Usage
|
|
596
|
+
|
|
597
|
+
##### Create a new DocumentBuilder instance
|
|
598
|
+
To create a new document, instantiate the `DocumentBuilder` with the base document (Verifiable Credential) that you want to build.
|
|
599
|
+
|
|
600
|
+
To learn more about defining custom contexts, check out the [Credential Subject - Custom Contexts guide](https://docs.tradetrust.io/docs/how-tos/credential-subject).
|
|
601
|
+
|
|
602
|
+
```ts
|
|
603
|
+
// Adds a custom vocabulary used to define terms in the `credentialSubject`.
|
|
604
|
+
// Users can define their own context if they have domain-specific fields or custom data structures.
|
|
605
|
+
const builder = new DocumentBuilder({
|
|
606
|
+
'@context': 'https://w3c-ccg.github.io/citizenship-vocab/contexts/citizenship-v1.jsonld'
|
|
607
|
+
});
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
##### Set Credential Subject
|
|
611
|
+
Set the subject of the Verifiable Credential, which typically contains information about the entity the credential is issued to.
|
|
612
|
+
|
|
613
|
+
```ts
|
|
614
|
+
builder.credentialSubject({
|
|
615
|
+
id: 'did:example:123',
|
|
616
|
+
name: 'John Doe',
|
|
617
|
+
});
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
##### Configure Credential Status
|
|
621
|
+
You can configure the credential status as either `transferableRecords` or `verifiableDocument`.
|
|
622
|
+
|
|
623
|
+
**Transferable Records**
|
|
624
|
+
```ts
|
|
625
|
+
builder.credentialStatus({
|
|
626
|
+
// Refers to the supported network.
|
|
627
|
+
// See: https://docs.tradetrust.io/docs/introduction/key-components-of-tradetrust/blockchain/supported-network
|
|
628
|
+
chain: 'Ethereum',
|
|
629
|
+
chainId: 1,
|
|
630
|
+
tokenRegistry: '0x1234567890abcdef...',
|
|
631
|
+
rpcProviderUrl: 'https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID',
|
|
632
|
+
});
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
> ⚠️ **Disclaimer:**
|
|
636
|
+
> This builder **does not mint** documents on-chain. If you're using `transferableRecords`, you'll need to mint the document.
|
|
637
|
+
> [See the minting guide here](https://docs.tradetrust.io/docs/how-tos/credential-status#2-minting-the-credential)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
**Verifiable Document**
|
|
641
|
+
```ts
|
|
642
|
+
builder.credentialStatus({
|
|
643
|
+
url: 'https://example.com/status-list',
|
|
644
|
+
// `index: <placeholder>` refers to the bit position in the status list that will be set for revocation.
|
|
645
|
+
// Note: A document with the specific index must be marked as not revoked in the status list.
|
|
646
|
+
index: <placeholder>,
|
|
647
|
+
purpose: 'revocation',
|
|
648
|
+
});
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
##### Set Expiration Date
|
|
652
|
+
You can set an expiration date for the document.
|
|
653
|
+
|
|
654
|
+
```ts
|
|
655
|
+
builder.expirationDate('2026-01-01T00:00:00Z');
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
##### Define Rendering Method
|
|
659
|
+
Set the rendering method to be used for the document.
|
|
660
|
+
|
|
661
|
+
```ts
|
|
662
|
+
builder.renderMethod({
|
|
663
|
+
id: 'https://example.com/rendering-method',
|
|
664
|
+
type: 'EMBEDDED_RENDERER',
|
|
665
|
+
templateName: 'BILL_OF_LADING',
|
|
666
|
+
});
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
##### Sign the Document
|
|
670
|
+
To sign the document, provide a `PrivateKeyPair` from `@trustvc/trustvc`.
|
|
671
|
+
|
|
672
|
+
```ts
|
|
673
|
+
const privateKey: PrivateKeyPair = {
|
|
674
|
+
id: 'did:example:456#key1',
|
|
675
|
+
controller: 'did:example:456',
|
|
676
|
+
type: VerificationType.Bls12381G2Key2020,
|
|
677
|
+
publicKeyBase58: 'your-public-key-base58',
|
|
678
|
+
privateKeyBase58: 'your-private-key-base58',
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
const signedDocument = await builder.sign(privateKey);
|
|
682
|
+
console.log(signedDocument);
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
Example Output After Signing
|
|
686
|
+
```json
|
|
687
|
+
{
|
|
688
|
+
"@context": [
|
|
689
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
690
|
+
"https://w3c-ccg.github.io/citizenship-vocab/contexts/citizenship-v1.jsonld",
|
|
691
|
+
"https://w3id.org/vc/status-list/2021/v1",
|
|
692
|
+
"https://trustvc.io/context/render-method-context.json",
|
|
693
|
+
"https://w3id.org/security/bbs/v1"
|
|
694
|
+
],
|
|
695
|
+
"type": ["VerifiableCredential"],
|
|
696
|
+
"credentialSubject": {
|
|
697
|
+
"id": "did:example:123",
|
|
698
|
+
"name": "John Doe"
|
|
699
|
+
},
|
|
700
|
+
"expirationDate": "2026-01-01T00:00:00Z",
|
|
701
|
+
"renderMethod": [
|
|
702
|
+
{
|
|
703
|
+
"id": "https://example.com/rendering-method",
|
|
704
|
+
"type": "EMBEDDED_RENDERER",
|
|
705
|
+
"templateName": "BILL_OF_LADING"
|
|
706
|
+
}
|
|
707
|
+
],
|
|
708
|
+
"credentialStatus": {
|
|
709
|
+
"id": "https://example.com/status-list#<placeholder>",
|
|
710
|
+
"type": "StatusList2021Entry",
|
|
711
|
+
"statusPurpose": "revocation",
|
|
712
|
+
"statusListIndex": "<placeholder>",
|
|
713
|
+
"statusListCredential": "https://example.com/status-list"
|
|
714
|
+
},
|
|
715
|
+
"issuer": "did:example:456",
|
|
716
|
+
"issuanceDate": "2025-01-01T00:00:00Z",
|
|
717
|
+
"id": "urn:bnid:_:0195fec2-4ae1-7cca-9182-03fd7da5142b",
|
|
718
|
+
"proof": {
|
|
719
|
+
"type": "BbsBlsSignature2020",
|
|
720
|
+
"created": "2025-01-01T00:00:01Z",
|
|
721
|
+
"proofPurpose": "assertionMethod",
|
|
722
|
+
"proofValue": "rV56L+QYozATRy3GOVLomzUo99sXtw2x0Cy9dEkHJ15wi4cS12cQJRIwzONVi3YscdhaSKoqD1jWmwb5A/khLZnDq5eo3QzDgTVClYuV86opL3HJyoS4+t2rRt3wl+chnATy2jqr5zMEvcVJ3gdXpQ==",
|
|
723
|
+
"verificationMethod": "did:example:456#key1"
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
##### Verify the Document
|
|
729
|
+
To verify the signature of the signed document:
|
|
730
|
+
|
|
731
|
+
```ts
|
|
732
|
+
const isVerified = await builder.verify();
|
|
733
|
+
console.log(isVerified); // true or false
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
##### Convert Document to JSON String
|
|
737
|
+
To get the current state of the document as a JSON string:
|
|
738
|
+
|
|
739
|
+
```ts
|
|
740
|
+
const documentJson = builder.toString();
|
|
741
|
+
console.log(documentJson);
|
|
742
|
+
```
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var w3c = require('../w3c');
|
|
4
|
+
var w3cCredentialStatus = require('@trustvc/w3c-credential-status');
|
|
5
|
+
var w3cVc = require('@trustvc/w3c-vc');
|
|
6
|
+
var ethers = require('ethers');
|
|
7
|
+
var tokenRegistryV4$1 = require('@tradetrust-tt/token-registry-v4');
|
|
8
|
+
var tokenRegistryV5$1 = require('@tradetrust-tt/token-registry-v5');
|
|
9
|
+
var tokenRegistryV4 = require('../token-registry-v4');
|
|
10
|
+
var tokenRegistryV5 = require('../token-registry-v5');
|
|
11
|
+
var tradetrustUtils = require('@tradetrust-tt/tradetrust-utils');
|
|
12
|
+
|
|
3
13
|
var __defProp = Object.defineProperty;
|
|
4
14
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
15
|
class DocumentBuilder {
|
|
@@ -7,8 +17,194 @@ class DocumentBuilder {
|
|
|
7
17
|
__name(this, "DocumentBuilder");
|
|
8
18
|
}
|
|
9
19
|
document;
|
|
10
|
-
|
|
11
|
-
|
|
20
|
+
// Holds the document to be built and signed.
|
|
21
|
+
documentType = "w3c";
|
|
22
|
+
// Default to W3C
|
|
23
|
+
selectedStatusType = null;
|
|
24
|
+
// Tracks selected status type.
|
|
25
|
+
statusConfig = {};
|
|
26
|
+
// Configuration for the credential status.
|
|
27
|
+
rpcProviderUrl;
|
|
28
|
+
// Holds the RPC provider URL for verifying token registry.
|
|
29
|
+
requiredFields = ["credentialSubject"];
|
|
30
|
+
// Required fields that must be present in the document.
|
|
31
|
+
isSigned = false;
|
|
32
|
+
// Tracks if a document is signed
|
|
33
|
+
/**
|
|
34
|
+
* Constructor to initialize the document builder.
|
|
35
|
+
* @param {Partial<VerifiableCredential>} input - The input document.
|
|
36
|
+
* @param {string} [documentType] - The type of the document (default is "w3c").
|
|
37
|
+
*/
|
|
38
|
+
constructor(input, documentType = "w3c") {
|
|
39
|
+
this.document = this.initializeDocument(input);
|
|
40
|
+
this.documentType = documentType;
|
|
41
|
+
}
|
|
42
|
+
// Sets the credential subject of the document.
|
|
43
|
+
credentialSubject(subject) {
|
|
44
|
+
if (this.isSigned) throw new Error("Configuration Error: Document is already signed.");
|
|
45
|
+
this.document.credentialSubject = subject;
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
// Configures the credential status of the document based on the provided type (Transferable Records or Verifiable Document).
|
|
49
|
+
credentialStatus(config) {
|
|
50
|
+
if (this.isSigned) throw new Error("Configuration Error: Document is already signed.");
|
|
51
|
+
const isTransferable = this.isTransferableRecordsConfig(config);
|
|
52
|
+
const isVerifiable = this.isVerifiableDocumentConfig(config);
|
|
53
|
+
if (isTransferable && isVerifiable) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"Configuration Error: Do not mix transferable records and verifiable document properties."
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (isTransferable) {
|
|
59
|
+
this.selectedStatusType = "transferableRecords";
|
|
60
|
+
this.statusConfig = {
|
|
61
|
+
type: "TransferableRecords",
|
|
62
|
+
tokenNetwork: { chain: config.chain, chainId: config.chainId },
|
|
63
|
+
tokenRegistry: config.tokenRegistry
|
|
64
|
+
};
|
|
65
|
+
this.rpcProviderUrl = config.rpcProviderUrl;
|
|
66
|
+
this.addContext("https://trustvc.io/context/transferable-records-context.json");
|
|
67
|
+
} else if (isVerifiable) {
|
|
68
|
+
this.selectedStatusType = "verifiableDocument";
|
|
69
|
+
this.statusConfig = {
|
|
70
|
+
id: `${config.url}#${config.index}`,
|
|
71
|
+
type: "StatusList2021Entry",
|
|
72
|
+
statusPurpose: config.purpose || "revocation",
|
|
73
|
+
// Set status purpose to "revocation" by default.
|
|
74
|
+
statusListIndex: config.index,
|
|
75
|
+
statusListCredential: config.url
|
|
76
|
+
};
|
|
77
|
+
this.addContext("https://w3id.org/vc/status-list/2021/v1");
|
|
78
|
+
} else {
|
|
79
|
+
throw new Error("Configuration Error: Missing required fields for credential status.");
|
|
80
|
+
}
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
// Sets the expiration date of the document.
|
|
84
|
+
expirationDate(date) {
|
|
85
|
+
if (this.isSigned) throw new Error("Configuration Error: Document is already signed.");
|
|
86
|
+
this.document.expirationDate = typeof date === "string" ? date : date.toISOString();
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
// Defines the rendering method for the document.
|
|
90
|
+
renderMethod(method) {
|
|
91
|
+
if (this.isSigned) throw new Error("Configuration Error: Document is already signed.");
|
|
92
|
+
this.document.renderMethod = [method];
|
|
93
|
+
this.addContext("https://trustvc.io/context/render-method-context.json");
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
// Sign the document using the provided private key and an optional cryptographic suite.
|
|
97
|
+
async sign(privateKey, cryptoSuite) {
|
|
98
|
+
if (this.isSigned) throw new Error("Configuration Error: Document is already signed.");
|
|
99
|
+
if (this.selectedStatusType) {
|
|
100
|
+
this.document.credentialStatus = this.statusConfig;
|
|
101
|
+
}
|
|
102
|
+
this.validateRequiredFields(this.document);
|
|
103
|
+
if (this.selectedStatusType === "verifiableDocument") {
|
|
104
|
+
w3cCredentialStatus.assertCredentialStatus(this.document.credentialStatus);
|
|
105
|
+
const verificationResult = await w3cVc.verifyCredentialStatus(this.document.credentialStatus);
|
|
106
|
+
if (verificationResult.error)
|
|
107
|
+
throw new Error(`Credential Verification Failed: ${verificationResult.error}`);
|
|
108
|
+
if (verificationResult.status)
|
|
109
|
+
throw new Error("Credential Verification Failed: Invalid credential status detected.");
|
|
110
|
+
} else if (this.selectedStatusType === "transferableRecords") {
|
|
111
|
+
w3cCredentialStatus.assertTransferableRecords(this.document.credentialStatus, "sign");
|
|
112
|
+
await this.verifyTokenRegistry();
|
|
113
|
+
}
|
|
114
|
+
this.document.issuer = privateKey.id.split("#")[0];
|
|
115
|
+
this.document.issuanceDate = this.document.issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
|
|
116
|
+
this.addContext("https://w3id.org/security/bbs/v1");
|
|
117
|
+
const signedVC = await w3c.signW3C(this.document, privateKey, cryptoSuite);
|
|
118
|
+
if (signedVC.error) throw new Error(`Signing Error: ${signedVC.error}`);
|
|
119
|
+
this.isSigned = true;
|
|
120
|
+
return signedVC.signed;
|
|
121
|
+
}
|
|
122
|
+
// Verify the document.
|
|
123
|
+
async verify() {
|
|
124
|
+
if (!this.isSigned) throw new Error("Verification Error: Document is not signed yet.");
|
|
125
|
+
const verificationResult = await w3c.verifyW3CSignature(
|
|
126
|
+
this.document
|
|
127
|
+
);
|
|
128
|
+
if (verificationResult.error)
|
|
129
|
+
throw new Error(`Verification Error: ${verificationResult.error}`);
|
|
130
|
+
return verificationResult.verified;
|
|
131
|
+
}
|
|
132
|
+
// Returns the current state of the document as a JSON string.
|
|
133
|
+
toString() {
|
|
134
|
+
return JSON.stringify(this.document, null, 2);
|
|
135
|
+
}
|
|
136
|
+
// Type guard for transferable records configuration
|
|
137
|
+
isTransferableRecordsConfig(config) {
|
|
138
|
+
return config && typeof config.tokenRegistry === "string" && typeof config.chain === "string" && typeof config.chainId === "number" && typeof config.rpcProviderUrl === "string";
|
|
139
|
+
}
|
|
140
|
+
// Type guard for verifiable document configuration
|
|
141
|
+
isVerifiableDocumentConfig(config) {
|
|
142
|
+
return config && typeof config.url === "string" && typeof config.index === "number";
|
|
143
|
+
}
|
|
144
|
+
// Private helper method to validate that the required fields are present in the input document.
|
|
145
|
+
validateRequiredFields(input) {
|
|
146
|
+
this.requiredFields.forEach((field) => {
|
|
147
|
+
if (!input[field]) {
|
|
148
|
+
throw new Error(`Validation Error: Missing required field "${field}" in the credential.`);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// Private helper method to initialize the document with required context and type, adding the necessary context URL.
|
|
153
|
+
initializeDocument(input) {
|
|
154
|
+
if (input.proof) throw new Error("Configuration Error: Document is already signed.");
|
|
155
|
+
return {
|
|
156
|
+
...input,
|
|
157
|
+
"@context": this.buildContext(input["@context"]),
|
|
158
|
+
type: Array.from(new Set([].concat(input.type || [], "VerifiableCredential")))
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// Private helper method to build the context for the document, ensuring uniqueness and adding the default W3C context.
|
|
162
|
+
buildContext(context) {
|
|
163
|
+
return [
|
|
164
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
165
|
+
...Array.isArray(context) ? context : context ? [context] : []
|
|
166
|
+
].filter((v, i, a) => a.indexOf(v) === i);
|
|
167
|
+
}
|
|
168
|
+
// Private helper method to add a new context to the document if it does not already exist.
|
|
169
|
+
addContext(context) {
|
|
170
|
+
if (!this.document["@context"].includes(context)) {
|
|
171
|
+
this.document["@context"].push(context);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Private helper method to verify that the token registry supports the required interface for transferable records.
|
|
175
|
+
async verifyTokenRegistry() {
|
|
176
|
+
const chainId = this.document.credentialStatus.tokenNetwork.chainId;
|
|
177
|
+
if (!(chainId in tradetrustUtils.SUPPORTED_CHAINS)) {
|
|
178
|
+
throw new Error(`Unsupported Chain: Chain ID ${chainId} is not supported.`);
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const provider = new ethers.ethers.providers.JsonRpcProvider(this.rpcProviderUrl);
|
|
182
|
+
const isV4Supported = await this.supportsInterface(
|
|
183
|
+
tokenRegistryV4.v4Contracts.TradeTrustToken__factory,
|
|
184
|
+
tokenRegistryV4$1.constants.contractInterfaceId.TradeTrustTokenMintable,
|
|
185
|
+
provider
|
|
186
|
+
);
|
|
187
|
+
const isV5Supported = await this.supportsInterface(
|
|
188
|
+
tokenRegistryV5.v5Contracts.TradeTrustToken__factory,
|
|
189
|
+
tokenRegistryV5$1.constants.contractInterfaceId.TradeTrustTokenMintable,
|
|
190
|
+
provider
|
|
191
|
+
);
|
|
192
|
+
if (!isV4Supported && !isV5Supported)
|
|
193
|
+
throw new Error("Token registry version is not supported.");
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (error.message === "Token registry version is not supported.") {
|
|
196
|
+
throw error;
|
|
197
|
+
} else {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Network Error: Unable to verify token registry. Please check the RPC URL or token registry address.`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Private helper method to check if a contract supports a specific interface ID.
|
|
205
|
+
async supportsInterface(contractFactory, interfaceId, provider) {
|
|
206
|
+
const contract = contractFactory.connect(this.statusConfig.tokenRegistry, provider);
|
|
207
|
+
return contract.supportsInterface(interfaceId);
|
|
12
208
|
}
|
|
13
209
|
}
|
|
14
210
|
|
package/dist/cjs/core/index.js
CHANGED
|
@@ -4,6 +4,7 @@ var decrypt = require('./decrypt');
|
|
|
4
4
|
var encrypt = require('./encrypt');
|
|
5
5
|
var verify = require('./verify');
|
|
6
6
|
var endorsementChain = require('./endorsement-chain');
|
|
7
|
+
var documentBuilder = require('./documentBuilder');
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
|
|
@@ -31,3 +32,9 @@ Object.keys(endorsementChain).forEach(function (k) {
|
|
|
31
32
|
get: function () { return endorsementChain[k]; }
|
|
32
33
|
});
|
|
33
34
|
});
|
|
35
|
+
Object.keys(documentBuilder).forEach(function (k) {
|
|
36
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function () { return documentBuilder[k]; }
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import { signW3C, verifyW3CSignature } from '../w3c';
|
|
2
|
+
import { assertCredentialStatus, assertTransferableRecords } from '@trustvc/w3c-credential-status';
|
|
3
|
+
import { verifyCredentialStatus } from '@trustvc/w3c-vc';
|
|
4
|
+
import { ethers } from 'ethers';
|
|
5
|
+
import { constants } from '@tradetrust-tt/token-registry-v4';
|
|
6
|
+
import { constants as constants$1 } from '@tradetrust-tt/token-registry-v5';
|
|
7
|
+
import { v4Contracts } from '../token-registry-v4';
|
|
8
|
+
import { v5Contracts } from '../token-registry-v5';
|
|
9
|
+
import { SUPPORTED_CHAINS } from '@tradetrust-tt/tradetrust-utils';
|
|
10
|
+
|
|
1
11
|
var __defProp = Object.defineProperty;
|
|
2
12
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
13
|
class DocumentBuilder {
|
|
@@ -5,8 +15,194 @@ class DocumentBuilder {
|
|
|
5
15
|
__name(this, "DocumentBuilder");
|
|
6
16
|
}
|
|
7
17
|
document;
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
// Holds the document to be built and signed.
|
|
19
|
+
documentType = "w3c";
|
|
20
|
+
// Default to W3C
|
|
21
|
+
selectedStatusType = null;
|
|
22
|
+
// Tracks selected status type.
|
|
23
|
+
statusConfig = {};
|
|
24
|
+
// Configuration for the credential status.
|
|
25
|
+
rpcProviderUrl;
|
|
26
|
+
// Holds the RPC provider URL for verifying token registry.
|
|
27
|
+
requiredFields = ["credentialSubject"];
|
|
28
|
+
// Required fields that must be present in the document.
|
|
29
|
+
isSigned = false;
|
|
30
|
+
// Tracks if a document is signed
|
|
31
|
+
/**
|
|
32
|
+
* Constructor to initialize the document builder.
|
|
33
|
+
* @param {Partial<VerifiableCredential>} input - The input document.
|
|
34
|
+
* @param {string} [documentType] - The type of the document (default is "w3c").
|
|
35
|
+
*/
|
|
36
|
+
constructor(input, documentType = "w3c") {
|
|
37
|
+
this.document = this.initializeDocument(input);
|
|
38
|
+
this.documentType = documentType;
|
|
39
|
+
}
|
|
40
|
+
// Sets the credential subject of the document.
|
|
41
|
+
credentialSubject(subject) {
|
|
42
|
+
if (this.isSigned) throw new Error("Configuration Error: Document is already signed.");
|
|
43
|
+
this.document.credentialSubject = subject;
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
// Configures the credential status of the document based on the provided type (Transferable Records or Verifiable Document).
|
|
47
|
+
credentialStatus(config) {
|
|
48
|
+
if (this.isSigned) throw new Error("Configuration Error: Document is already signed.");
|
|
49
|
+
const isTransferable = this.isTransferableRecordsConfig(config);
|
|
50
|
+
const isVerifiable = this.isVerifiableDocumentConfig(config);
|
|
51
|
+
if (isTransferable && isVerifiable) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
"Configuration Error: Do not mix transferable records and verifiable document properties."
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (isTransferable) {
|
|
57
|
+
this.selectedStatusType = "transferableRecords";
|
|
58
|
+
this.statusConfig = {
|
|
59
|
+
type: "TransferableRecords",
|
|
60
|
+
tokenNetwork: { chain: config.chain, chainId: config.chainId },
|
|
61
|
+
tokenRegistry: config.tokenRegistry
|
|
62
|
+
};
|
|
63
|
+
this.rpcProviderUrl = config.rpcProviderUrl;
|
|
64
|
+
this.addContext("https://trustvc.io/context/transferable-records-context.json");
|
|
65
|
+
} else if (isVerifiable) {
|
|
66
|
+
this.selectedStatusType = "verifiableDocument";
|
|
67
|
+
this.statusConfig = {
|
|
68
|
+
id: `${config.url}#${config.index}`,
|
|
69
|
+
type: "StatusList2021Entry",
|
|
70
|
+
statusPurpose: config.purpose || "revocation",
|
|
71
|
+
// Set status purpose to "revocation" by default.
|
|
72
|
+
statusListIndex: config.index,
|
|
73
|
+
statusListCredential: config.url
|
|
74
|
+
};
|
|
75
|
+
this.addContext("https://w3id.org/vc/status-list/2021/v1");
|
|
76
|
+
} else {
|
|
77
|
+
throw new Error("Configuration Error: Missing required fields for credential status.");
|
|
78
|
+
}
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
// Sets the expiration date of the document.
|
|
82
|
+
expirationDate(date) {
|
|
83
|
+
if (this.isSigned) throw new Error("Configuration Error: Document is already signed.");
|
|
84
|
+
this.document.expirationDate = typeof date === "string" ? date : date.toISOString();
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
// Defines the rendering method for the document.
|
|
88
|
+
renderMethod(method) {
|
|
89
|
+
if (this.isSigned) throw new Error("Configuration Error: Document is already signed.");
|
|
90
|
+
this.document.renderMethod = [method];
|
|
91
|
+
this.addContext("https://trustvc.io/context/render-method-context.json");
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
// Sign the document using the provided private key and an optional cryptographic suite.
|
|
95
|
+
async sign(privateKey, cryptoSuite) {
|
|
96
|
+
if (this.isSigned) throw new Error("Configuration Error: Document is already signed.");
|
|
97
|
+
if (this.selectedStatusType) {
|
|
98
|
+
this.document.credentialStatus = this.statusConfig;
|
|
99
|
+
}
|
|
100
|
+
this.validateRequiredFields(this.document);
|
|
101
|
+
if (this.selectedStatusType === "verifiableDocument") {
|
|
102
|
+
assertCredentialStatus(this.document.credentialStatus);
|
|
103
|
+
const verificationResult = await verifyCredentialStatus(this.document.credentialStatus);
|
|
104
|
+
if (verificationResult.error)
|
|
105
|
+
throw new Error(`Credential Verification Failed: ${verificationResult.error}`);
|
|
106
|
+
if (verificationResult.status)
|
|
107
|
+
throw new Error("Credential Verification Failed: Invalid credential status detected.");
|
|
108
|
+
} else if (this.selectedStatusType === "transferableRecords") {
|
|
109
|
+
assertTransferableRecords(this.document.credentialStatus, "sign");
|
|
110
|
+
await this.verifyTokenRegistry();
|
|
111
|
+
}
|
|
112
|
+
this.document.issuer = privateKey.id.split("#")[0];
|
|
113
|
+
this.document.issuanceDate = this.document.issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
|
|
114
|
+
this.addContext("https://w3id.org/security/bbs/v1");
|
|
115
|
+
const signedVC = await signW3C(this.document, privateKey, cryptoSuite);
|
|
116
|
+
if (signedVC.error) throw new Error(`Signing Error: ${signedVC.error}`);
|
|
117
|
+
this.isSigned = true;
|
|
118
|
+
return signedVC.signed;
|
|
119
|
+
}
|
|
120
|
+
// Verify the document.
|
|
121
|
+
async verify() {
|
|
122
|
+
if (!this.isSigned) throw new Error("Verification Error: Document is not signed yet.");
|
|
123
|
+
const verificationResult = await verifyW3CSignature(
|
|
124
|
+
this.document
|
|
125
|
+
);
|
|
126
|
+
if (verificationResult.error)
|
|
127
|
+
throw new Error(`Verification Error: ${verificationResult.error}`);
|
|
128
|
+
return verificationResult.verified;
|
|
129
|
+
}
|
|
130
|
+
// Returns the current state of the document as a JSON string.
|
|
131
|
+
toString() {
|
|
132
|
+
return JSON.stringify(this.document, null, 2);
|
|
133
|
+
}
|
|
134
|
+
// Type guard for transferable records configuration
|
|
135
|
+
isTransferableRecordsConfig(config) {
|
|
136
|
+
return config && typeof config.tokenRegistry === "string" && typeof config.chain === "string" && typeof config.chainId === "number" && typeof config.rpcProviderUrl === "string";
|
|
137
|
+
}
|
|
138
|
+
// Type guard for verifiable document configuration
|
|
139
|
+
isVerifiableDocumentConfig(config) {
|
|
140
|
+
return config && typeof config.url === "string" && typeof config.index === "number";
|
|
141
|
+
}
|
|
142
|
+
// Private helper method to validate that the required fields are present in the input document.
|
|
143
|
+
validateRequiredFields(input) {
|
|
144
|
+
this.requiredFields.forEach((field) => {
|
|
145
|
+
if (!input[field]) {
|
|
146
|
+
throw new Error(`Validation Error: Missing required field "${field}" in the credential.`);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// Private helper method to initialize the document with required context and type, adding the necessary context URL.
|
|
151
|
+
initializeDocument(input) {
|
|
152
|
+
if (input.proof) throw new Error("Configuration Error: Document is already signed.");
|
|
153
|
+
return {
|
|
154
|
+
...input,
|
|
155
|
+
"@context": this.buildContext(input["@context"]),
|
|
156
|
+
type: Array.from(new Set([].concat(input.type || [], "VerifiableCredential")))
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// Private helper method to build the context for the document, ensuring uniqueness and adding the default W3C context.
|
|
160
|
+
buildContext(context) {
|
|
161
|
+
return [
|
|
162
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
163
|
+
...Array.isArray(context) ? context : context ? [context] : []
|
|
164
|
+
].filter((v, i, a) => a.indexOf(v) === i);
|
|
165
|
+
}
|
|
166
|
+
// Private helper method to add a new context to the document if it does not already exist.
|
|
167
|
+
addContext(context) {
|
|
168
|
+
if (!this.document["@context"].includes(context)) {
|
|
169
|
+
this.document["@context"].push(context);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Private helper method to verify that the token registry supports the required interface for transferable records.
|
|
173
|
+
async verifyTokenRegistry() {
|
|
174
|
+
const chainId = this.document.credentialStatus.tokenNetwork.chainId;
|
|
175
|
+
if (!(chainId in SUPPORTED_CHAINS)) {
|
|
176
|
+
throw new Error(`Unsupported Chain: Chain ID ${chainId} is not supported.`);
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const provider = new ethers.providers.JsonRpcProvider(this.rpcProviderUrl);
|
|
180
|
+
const isV4Supported = await this.supportsInterface(
|
|
181
|
+
v4Contracts.TradeTrustToken__factory,
|
|
182
|
+
constants.contractInterfaceId.TradeTrustTokenMintable,
|
|
183
|
+
provider
|
|
184
|
+
);
|
|
185
|
+
const isV5Supported = await this.supportsInterface(
|
|
186
|
+
v5Contracts.TradeTrustToken__factory,
|
|
187
|
+
constants$1.contractInterfaceId.TradeTrustTokenMintable,
|
|
188
|
+
provider
|
|
189
|
+
);
|
|
190
|
+
if (!isV4Supported && !isV5Supported)
|
|
191
|
+
throw new Error("Token registry version is not supported.");
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (error.message === "Token registry version is not supported.") {
|
|
194
|
+
throw error;
|
|
195
|
+
} else {
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Network Error: Unable to verify token registry. Please check the RPC URL or token registry address.`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Private helper method to check if a contract supports a specific interface ID.
|
|
203
|
+
async supportsInterface(contractFactory, interfaceId, provider) {
|
|
204
|
+
const contract = contractFactory.connect(this.statusConfig.tokenRegistry, provider);
|
|
205
|
+
return contract.supportsInterface(interfaceId);
|
|
10
206
|
}
|
|
11
207
|
}
|
|
12
208
|
|
package/dist/esm/core/index.js
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
|
+
import { PrivateKeyPair } from '@trustvc/w3c-issuer';
|
|
2
|
+
import { VerifiableCredential, SignedVerifiableCredential } from '@trustvc/w3c-vc';
|
|
3
|
+
|
|
4
|
+
interface W3CVerifiableDocumentConfig {
|
|
5
|
+
url: string;
|
|
6
|
+
index: number;
|
|
7
|
+
purpose?: string;
|
|
8
|
+
}
|
|
9
|
+
interface W3CTransferableRecordsConfig {
|
|
10
|
+
chain: string;
|
|
11
|
+
chainId: number;
|
|
12
|
+
tokenRegistry: string;
|
|
13
|
+
rpcProviderUrl: string;
|
|
14
|
+
}
|
|
15
|
+
interface RenderMethod {
|
|
16
|
+
id: string;
|
|
17
|
+
type: string;
|
|
18
|
+
templateName: string;
|
|
19
|
+
}
|
|
1
20
|
declare class DocumentBuilder {
|
|
2
|
-
document
|
|
3
|
-
|
|
21
|
+
private document;
|
|
22
|
+
private documentType;
|
|
23
|
+
private selectedStatusType;
|
|
24
|
+
private statusConfig;
|
|
25
|
+
private rpcProviderUrl;
|
|
26
|
+
private requiredFields;
|
|
27
|
+
private isSigned;
|
|
28
|
+
constructor(input: Partial<VerifiableCredential>, documentType?: string);
|
|
29
|
+
credentialSubject(subject: Partial<VerifiableCredential>): this;
|
|
30
|
+
credentialStatus(config: W3CTransferableRecordsConfig | W3CVerifiableDocumentConfig): this;
|
|
31
|
+
expirationDate(date: string | Date): this;
|
|
32
|
+
renderMethod(method: RenderMethod): this;
|
|
33
|
+
sign(privateKey: PrivateKeyPair, cryptoSuite?: string): Promise<SignedVerifiableCredential>;
|
|
34
|
+
verify(): Promise<boolean>;
|
|
35
|
+
toString(): string;
|
|
36
|
+
private isTransferableRecordsConfig;
|
|
37
|
+
private isVerifiableDocumentConfig;
|
|
38
|
+
private validateRequiredFields;
|
|
39
|
+
private initializeDocument;
|
|
40
|
+
private buildContext;
|
|
41
|
+
private addContext;
|
|
42
|
+
private verifyTokenRegistry;
|
|
43
|
+
private supportsInterface;
|
|
4
44
|
}
|
|
5
45
|
|
|
6
|
-
export { DocumentBuilder };
|
|
46
|
+
export { DocumentBuilder, type RenderMethod, type W3CTransferableRecordsConfig, type W3CVerifiableDocumentConfig };
|
|
@@ -7,9 +7,11 @@ export { fetchEventTime, mergeTransfersV4, mergeTransfersV5, sortLogChain } from
|
|
|
7
7
|
export { getEndorsementChain } from './endorsement-chain/retrieveEndorsementChain.js';
|
|
8
8
|
export { EndorsementChain, ParsedLog, TitleEscrowTransferEvent, TitleEscrowTransferEventType, TokenTransferEvent, TokenTransferEventType, TradeTrustTokenEventType, TransferBaseEvent, TransferEvent, TransferEventType, TypedEvent } from './endorsement-chain/types.js';
|
|
9
9
|
export { TitleEscrowInterface, fetchEndorsementChain, getTitleEscrowAddress, isTitleEscrowVersion } from './endorsement-chain/useEndorsementChain.js';
|
|
10
|
+
export { DocumentBuilder, RenderMethod, W3CTransferableRecordsConfig, W3CVerifiableDocumentConfig } from './documentBuilder.js';
|
|
10
11
|
import '@trustvc/w3c-vc';
|
|
11
12
|
import '@tradetrust-tt/tt-verify/dist/types/src/types/core';
|
|
12
13
|
import 'ethersV6';
|
|
13
14
|
import '@ethersproject/abstract-provider';
|
|
14
15
|
import 'ethers';
|
|
15
16
|
import 'ethers/lib/utils';
|
|
17
|
+
import '@trustvc/w3c-issuer';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export { fetchEventTime, mergeTransfersV4, mergeTransfersV5, sortLogChain } from
|
|
|
19
19
|
export { getEndorsementChain } from './core/endorsement-chain/retrieveEndorsementChain.js';
|
|
20
20
|
export { EndorsementChain, ParsedLog, TitleEscrowTransferEvent, TitleEscrowTransferEventType, TokenTransferEvent, TokenTransferEventType, TradeTrustTokenEventType, TransferBaseEvent, TransferEvent, TransferEventType, TypedEvent } from './core/endorsement-chain/types.js';
|
|
21
21
|
export { TitleEscrowInterface, fetchEndorsementChain, getTitleEscrowAddress, isTitleEscrowVersion } from './core/endorsement-chain/useEndorsementChain.js';
|
|
22
|
+
export { DocumentBuilder, RenderMethod, W3CTransferableRecordsConfig, W3CVerifiableDocumentConfig } from './core/documentBuilder.js';
|
|
22
23
|
export { signOA } from './open-attestation/sign.js';
|
|
23
24
|
export { KeyPair } from './open-attestation/types.js';
|
|
24
25
|
export { diagnose, getAssetId, getDocumentData, getIssuerAddress, getTemplateURL, isObfuscated, isRawV2Document, isRawV3Document, isSignedWrappedV2Document, isSignedWrappedV3Document, isTransferableAsset, isWrappedV2Document, isWrappedV3Document } from './open-attestation/utils.js';
|