@cooperation/vc-storage 1.0.29 → 1.0.31
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/dist/models/CredentialEngine.js +48 -49
- package/dist/tests/email.test.js +31 -0
- package/dist/tests/seed.test.js +75 -0
- package/dist/types/tests/email.test.d.ts +1 -0
- package/dist/types/tests/seed.test.d.ts +1 -0
- package/dist/types/utils/decodedSeed.d.ts +18 -0
- package/dist/utils/decodedSeed.js +77 -0
- package/package.json +6 -2
@@ -6,7 +6,7 @@ import { extractKeyPairFromCredential, generateDIDSchema, generateUnsignedRecomm
|
|
6
6
|
import { customDocumentLoader } from '../utils/digitalbazaar.js';
|
7
7
|
import { saveToGoogleDrive } from '../utils/google.js';
|
8
8
|
function delay(ms) {
|
9
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
10
10
|
}
|
11
11
|
/**
|
12
12
|
* Class representing the Credential Engine.
|
@@ -50,12 +50,19 @@ export class CredentialEngine {
|
|
50
50
|
this.keyPair = key;
|
51
51
|
return key;
|
52
52
|
}
|
53
|
-
generateKeyPair = async (address) => {
|
54
|
-
|
53
|
+
generateKeyPair = async (address, seed) => {
|
54
|
+
// Generate the key pair using the library's method
|
55
|
+
const keyPair = seed
|
56
|
+
? await Ed25519VerificationKey2020.generate({
|
57
|
+
seed: Buffer.from(seed).toString('hex'),
|
58
|
+
})
|
59
|
+
: await Ed25519VerificationKey2020.generate();
|
60
|
+
// Configure key pair attributes
|
55
61
|
const a = address || keyPair.publicKeyMultibase;
|
56
62
|
keyPair.controller = `did:key:${a}`;
|
57
63
|
keyPair.id = `${keyPair.controller}#${a}`;
|
58
64
|
keyPair.revoked = false;
|
65
|
+
// The `signer` is already provided by the `Ed25519VerificationKey2020` instance
|
59
66
|
return keyPair;
|
60
67
|
};
|
61
68
|
async verifyCreds(creds) {
|
@@ -74,11 +81,6 @@ export class CredentialEngine {
|
|
74
81
|
async createDID() {
|
75
82
|
try {
|
76
83
|
const keyPair = await this.generateKeyPair();
|
77
|
-
// const keyFile = await saveToGoogleDrive({
|
78
|
-
// storage: this.storage,
|
79
|
-
// data: keyPair,
|
80
|
-
// type: 'KEYPAIR',
|
81
|
-
// });
|
82
84
|
const didDocument = await generateDIDSchema(keyPair);
|
83
85
|
return { didDocument, keyPair };
|
84
86
|
}
|
@@ -242,45 +244,51 @@ export class CredentialEngine {
|
|
242
244
|
* @param {string} email - The email address to create the VC for
|
243
245
|
* @returns {Promise<{signedVC: any, fileId: string}>} The signed VC and its Google Drive file ID
|
244
246
|
*/
|
245
|
-
async generateAndSignEmailVC(email) {
|
247
|
+
async generateAndSignEmailVC(email, encodedSeed) {
|
246
248
|
try {
|
247
|
-
// Try to find existing keys and DIDs
|
248
|
-
const existingKeys = await this.findKeysAndDIDs();
|
249
249
|
let keyPair;
|
250
250
|
let didDocument;
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
didDocument = existingKeys.didDocument;
|
251
|
+
// Require SEED from environment
|
252
|
+
if (!encodedSeed) {
|
253
|
+
throw new Error('SEED environment variable not set. Cannot generate or use any DID.');
|
255
254
|
}
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
255
|
+
// Use deterministic keys from environment seed
|
256
|
+
const { getDidFromEnvSeed } = await import('../utils/decodedSeed');
|
257
|
+
const result = await getDidFromEnvSeed();
|
258
|
+
keyPair = result.keyPair;
|
259
|
+
didDocument = result.didDocument;
|
260
|
+
console.log('Using DID from environment seed:', didDocument.id);
|
261
|
+
// Ensure the key has proper ID and controller
|
262
|
+
if (!keyPair.id || !keyPair.controller) {
|
263
|
+
const verificationMethod = didDocument.verificationMethod?.[0] || didDocument.authentication?.[0];
|
264
|
+
if (verificationMethod) {
|
265
|
+
keyPair.id = typeof verificationMethod === 'string' ? verificationMethod : verificationMethod.id;
|
266
|
+
keyPair.controller = didDocument.id;
|
267
|
+
}
|
261
268
|
}
|
269
|
+
console.log('Creating email VC with DID:', didDocument.id);
|
262
270
|
// Generate unsigned email VC
|
263
271
|
const unsignedCredential = {
|
264
272
|
'@context': [
|
265
273
|
'https://www.w3.org/2018/credentials/v1',
|
266
274
|
{
|
267
|
-
|
268
|
-
|
269
|
-
'@id': 'https://example.com/EmailCredential'
|
270
|
-
}
|
271
|
-
}
|
275
|
+
email: 'https://schema.org/email',
|
276
|
+
EmailCredential: {
|
277
|
+
'@id': 'https://example.com/EmailCredential',
|
278
|
+
},
|
279
|
+
},
|
272
280
|
],
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
281
|
+
id: `urn:uuid:${uuidv4()}`,
|
282
|
+
type: ['VerifiableCredential', 'EmailCredential'],
|
283
|
+
issuer: {
|
284
|
+
id: didDocument.id,
|
285
|
+
},
|
286
|
+
issuanceDate: new Date().toISOString(),
|
287
|
+
expirationDate: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(), // 1 year from now
|
288
|
+
credentialSubject: {
|
289
|
+
id: `did:email:${email}`,
|
290
|
+
email: email,
|
277
291
|
},
|
278
|
-
'issuanceDate': new Date().toISOString(),
|
279
|
-
'expirationDate': new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(), // 1 year from now
|
280
|
-
'credentialSubject': {
|
281
|
-
'id': `did:email:${email}`,
|
282
|
-
'email': email
|
283
|
-
}
|
284
292
|
};
|
285
293
|
// Sign the VC
|
286
294
|
const suite = new Ed25519Signature2020({
|
@@ -292,14 +300,13 @@ export class CredentialEngine {
|
|
292
300
|
suite,
|
293
301
|
documentLoader: customDocumentLoader,
|
294
302
|
});
|
295
|
-
// Get root folders
|
296
303
|
const rootFolders = await this.storage.findFolders();
|
297
304
|
// Find or create Credentials folder
|
298
305
|
let credentialsFolder = rootFolders.find((f) => f.name === 'Credentials');
|
299
306
|
if (!credentialsFolder) {
|
300
307
|
credentialsFolder = await this.storage.createFolder({
|
301
308
|
folderName: 'Credentials',
|
302
|
-
parentFolderId: 'root'
|
309
|
+
parentFolderId: 'root',
|
303
310
|
});
|
304
311
|
// Wait and re-check to avoid duplicates
|
305
312
|
await delay(1500);
|
@@ -314,7 +321,7 @@ export class CredentialEngine {
|
|
314
321
|
if (!emailVcFolder) {
|
315
322
|
emailVcFolder = await this.storage.createFolder({
|
316
323
|
folderName: 'EMAIL_VC',
|
317
|
-
parentFolderId: credentialsFolder.id
|
324
|
+
parentFolderId: credentialsFolder.id,
|
318
325
|
});
|
319
326
|
// Wait and re-check to avoid duplicates
|
320
327
|
await delay(1500);
|
@@ -326,20 +333,12 @@ export class CredentialEngine {
|
|
326
333
|
// Save the VC in the EMAIL_VC folder
|
327
334
|
const file = await this.storage.saveFile({
|
328
335
|
data: {
|
329
|
-
fileName: `${email}
|
336
|
+
fileName: `${email}`,
|
330
337
|
mimeType: 'application/json',
|
331
|
-
body: signedVC
|
338
|
+
body: signedVC,
|
332
339
|
},
|
333
|
-
folderId: emailVcFolder.id
|
340
|
+
folderId: emailVcFolder.id,
|
334
341
|
});
|
335
|
-
// Only save key pair if it's new
|
336
|
-
if (!existingKeys) {
|
337
|
-
await saveToGoogleDrive({
|
338
|
-
storage: this.storage,
|
339
|
-
data: keyPair,
|
340
|
-
type: 'KEYPAIR',
|
341
|
-
});
|
342
|
-
}
|
343
342
|
return { signedVC, fileId: file.id };
|
344
343
|
}
|
345
344
|
catch (error) {
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { CredentialEngine } from '../models/CredentialEngine.js';
|
2
|
+
import { GoogleDriveStorage } from '../models/GoogleDriveStorage.js';
|
3
|
+
async function testEmailVC() {
|
4
|
+
try {
|
5
|
+
// Get Google Drive access token from environment
|
6
|
+
const accessToken = 'your access token';
|
7
|
+
// Initialize storage and engine
|
8
|
+
const storage = new GoogleDriveStorage(accessToken);
|
9
|
+
const engine = new CredentialEngine(storage);
|
10
|
+
// Test email
|
11
|
+
const testEmail = 'test@example.com';
|
12
|
+
console.log('Starting email VC generation test...');
|
13
|
+
console.log('Test email:', testEmail);
|
14
|
+
// Generate and sign the email VC
|
15
|
+
const result = await engine.generateAndSignEmailVC(testEmail);
|
16
|
+
console.log('\nTest Results:');
|
17
|
+
console.log('-------------');
|
18
|
+
console.log('File ID:', result.fileId);
|
19
|
+
console.log('Signed VC:', JSON.stringify(result.signedVC, null, 2));
|
20
|
+
// Test retrieving the VC from storage
|
21
|
+
console.log('\nRetrieving VC from storage...');
|
22
|
+
const retrievedVC = await storage.retrieve(result.fileId);
|
23
|
+
console.log('Retrieved VC:', retrievedVC ? 'Success' : 'Failed');
|
24
|
+
console.log('\nTest completed successfully!');
|
25
|
+
}
|
26
|
+
catch (error) {
|
27
|
+
console.error('Test failed:', error);
|
28
|
+
}
|
29
|
+
}
|
30
|
+
// Run the test
|
31
|
+
testEmailVC().catch(console.error);
|
@@ -0,0 +1,75 @@
|
|
1
|
+
// @ts-nocheck
|
2
|
+
import { decodeSecretKeySeed } from 'bnid';
|
3
|
+
import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
|
4
|
+
import { driver as keyDriver } from '@digitalbazaar/did-method-key';
|
5
|
+
import { CryptoLD } from 'crypto-ld';
|
6
|
+
// EXACT SAME SETUP AS YOUR WORKING SCRIPT
|
7
|
+
const cryptoLd = new CryptoLD();
|
8
|
+
cryptoLd.use(Ed25519VerificationKey2020);
|
9
|
+
// Reference decodeSeed implementation
|
10
|
+
const decodeSeed = async (secretKeySeed) => {
|
11
|
+
let secretKeySeedBytes;
|
12
|
+
if (secretKeySeed.startsWith('z')) {
|
13
|
+
secretKeySeedBytes = decodeSecretKeySeed({ secretKeySeed });
|
14
|
+
}
|
15
|
+
else if (secretKeySeed.length >= 32) {
|
16
|
+
secretKeySeedBytes = new TextEncoder().encode(secretKeySeed).slice(0, 32);
|
17
|
+
}
|
18
|
+
else {
|
19
|
+
throw TypeError('"secretKeySeed" must be at least 32 bytes, preferably multibase-encoded.');
|
20
|
+
}
|
21
|
+
return secretKeySeedBytes;
|
22
|
+
};
|
23
|
+
// EXACT SAME FUNCTION AS YOUR WORKING generateSeed BUT WITH PREDEFINED SEEDS
|
24
|
+
async function testGenerateSeed(encodedSeed) {
|
25
|
+
const seed = await decodeSeed(encodedSeed);
|
26
|
+
let didDocument;
|
27
|
+
// EXACT SAME CODE AS YOUR WORKING SCRIPT
|
28
|
+
const didKeyDriver = keyDriver();
|
29
|
+
didKeyDriver.use({
|
30
|
+
multibaseMultikeyHeader: 'z6Mk',
|
31
|
+
fromMultibase: Ed25519VerificationKey2020.from,
|
32
|
+
});
|
33
|
+
const verificationKeyPair = await Ed25519VerificationKey2020.generate({
|
34
|
+
seed,
|
35
|
+
});
|
36
|
+
({ didDocument } = await didKeyDriver.fromKeyPair({ verificationKeyPair }));
|
37
|
+
const did = didDocument.id;
|
38
|
+
return { seed: encodedSeed, decodedSeed: seed, did, didDocument, publicKey: verificationKeyPair.publicKeyMultibase };
|
39
|
+
}
|
40
|
+
// Test with different seeds
|
41
|
+
async function testDifferentSeeds() {
|
42
|
+
console.log('Testing different seeds with EXACT working script approach:\n');
|
43
|
+
const testSeeds = [
|
44
|
+
'z1AjjnVSNmZot5TJcgtFYhn83WASAcphrqgE95AQ436hrGR',
|
45
|
+
'z1AibHGc5Zek2kvdSyC22xGLZCvV7b77KWBFWiQmZGnD6rV',
|
46
|
+
'z1AgfwVqaN3zX1kw2iTvLLekCbXNRJA7XbiFZCQv9TfgKCT',
|
47
|
+
];
|
48
|
+
const results = [];
|
49
|
+
for (const testSeed of testSeeds) {
|
50
|
+
console.log(`Testing seed: ${testSeed}`);
|
51
|
+
console.log(`Decoded hex: ${Buffer.from(await decodeSeed(testSeed)).toString('hex')}`);
|
52
|
+
try {
|
53
|
+
const result = await testGenerateSeed(testSeed);
|
54
|
+
console.log(`Generated DID: ${result.did}`);
|
55
|
+
console.log(`Public key: ${result.publicKey}`);
|
56
|
+
results.push(result);
|
57
|
+
}
|
58
|
+
catch (error) {
|
59
|
+
console.error(`Error: ${error.message}`);
|
60
|
+
}
|
61
|
+
console.log('---');
|
62
|
+
}
|
63
|
+
console.log('\nResults:');
|
64
|
+
const uniqueDids = new Set(results.map((r) => r.did));
|
65
|
+
const uniqueKeys = new Set(results.map((r) => r.publicKey));
|
66
|
+
console.log(`Total results: ${results.length}`);
|
67
|
+
console.log(`Unique DIDs: ${uniqueDids.size}`);
|
68
|
+
console.log(`Unique public keys: ${uniqueKeys.size}`);
|
69
|
+
console.log(`Expected unique: ${testSeeds.length}`);
|
70
|
+
console.log(`Success: ${uniqueDids.size === testSeeds.length ? '✓ YES' : '✗ NO'}`);
|
71
|
+
results.forEach((result, i) => {
|
72
|
+
console.log(`${i + 1}. ${result.seed} -> ${result.did}`);
|
73
|
+
});
|
74
|
+
}
|
75
|
+
testDifferentSeeds().catch(console.error);
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,18 @@
|
|
1
|
+
export declare function decodeSeed(encodedSeed: string): Promise<Uint8Array>;
|
2
|
+
export declare const getDidFromEnvSeed: () => Promise<{
|
3
|
+
keyPair: any;
|
4
|
+
didDocument: {
|
5
|
+
'@context': string[];
|
6
|
+
id: string;
|
7
|
+
verificationMethod: {
|
8
|
+
id: string;
|
9
|
+
type: string;
|
10
|
+
controller: string;
|
11
|
+
publicKeyMultibase: any;
|
12
|
+
}[];
|
13
|
+
authentication: string[];
|
14
|
+
assertionMethod: string[];
|
15
|
+
capabilityDelegation: string[];
|
16
|
+
capabilityInvocation: string[];
|
17
|
+
};
|
18
|
+
}>;
|
@@ -0,0 +1,77 @@
|
|
1
|
+
import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
|
2
|
+
import { base58btc } from 'multiformats/bases/base58';
|
3
|
+
export async function decodeSeed(encodedSeed) {
|
4
|
+
try {
|
5
|
+
// Ensure the seed has the 'z' prefix
|
6
|
+
const seedWithPrefix = encodedSeed.startsWith('z') ? encodedSeed : `z${encodedSeed}`;
|
7
|
+
// Decode the entire string including the prefix
|
8
|
+
const decoded = base58btc.decode(seedWithPrefix);
|
9
|
+
// The decoded data includes a multicodec header (2 bytes: 0x00 0x20)
|
10
|
+
// We need to remove this header to get the actual 32-byte seed
|
11
|
+
if (decoded.length === 34 && decoded[0] === 0x00 && decoded[1] === 0x20) {
|
12
|
+
// Skip the first 2 bytes to get the actual seed
|
13
|
+
const seed = new Uint8Array(decoded.slice(2));
|
14
|
+
console.log('Decoded seed (removed 2-byte header):', Buffer.from(seed).toString('hex'));
|
15
|
+
return seed;
|
16
|
+
}
|
17
|
+
// If it's already 32 bytes, return as is
|
18
|
+
if (decoded.length === 32) {
|
19
|
+
console.log('Decoded seed (already 32 bytes):', Buffer.from(decoded).toString('hex'));
|
20
|
+
return new Uint8Array(decoded);
|
21
|
+
}
|
22
|
+
throw new Error(`Invalid seed length: ${decoded.length} bytes (expected 32 bytes after removing any headers)`);
|
23
|
+
}
|
24
|
+
catch (error) {
|
25
|
+
console.error('Error decoding seed:', error);
|
26
|
+
throw error;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
export const getDidFromEnvSeed = async () => {
|
30
|
+
// Get seed from environment variable
|
31
|
+
const encodedSeed = process.env.SEED;
|
32
|
+
if (!encodedSeed) {
|
33
|
+
throw new Error('SEED environment variable not set');
|
34
|
+
}
|
35
|
+
console.log('Using seed from environment:', encodedSeed);
|
36
|
+
// Decode the seed (this will remove the 2-byte header if present)
|
37
|
+
const seed = await decodeSeed(encodedSeed);
|
38
|
+
console.log('Decoded seed length:', seed.length);
|
39
|
+
// Create key pair from seed
|
40
|
+
const verificationKeyPair = await Ed25519VerificationKey2020.generate({
|
41
|
+
seed: seed,
|
42
|
+
type: 'Ed25519VerificationKey2020',
|
43
|
+
});
|
44
|
+
console.log('Generated public key:', verificationKeyPair.publicKeyMultibase);
|
45
|
+
// Create DID manually to avoid key agreement issues
|
46
|
+
const fingerprint = verificationKeyPair.fingerprint();
|
47
|
+
const did = `did:key:${fingerprint}`;
|
48
|
+
// Create a proper DID document
|
49
|
+
const didDocument = {
|
50
|
+
'@context': [
|
51
|
+
'https://www.w3.org/ns/did/v1',
|
52
|
+
'https://w3id.org/security/suites/ed25519-2020/v1',
|
53
|
+
'https://w3id.org/security/suites/x25519-2020/v1',
|
54
|
+
],
|
55
|
+
id: did,
|
56
|
+
verificationMethod: [
|
57
|
+
{
|
58
|
+
id: `${did}#${fingerprint}`,
|
59
|
+
type: 'Ed25519VerificationKey2020',
|
60
|
+
controller: did,
|
61
|
+
publicKeyMultibase: verificationKeyPair.publicKeyMultibase,
|
62
|
+
},
|
63
|
+
],
|
64
|
+
authentication: [`${did}#${fingerprint}`],
|
65
|
+
assertionMethod: [`${did}#${fingerprint}`],
|
66
|
+
capabilityDelegation: [`${did}#${fingerprint}`],
|
67
|
+
capabilityInvocation: [`${did}#${fingerprint}`],
|
68
|
+
};
|
69
|
+
// Set the key ID and controller on the key pair
|
70
|
+
verificationKeyPair.id = `${did}#${fingerprint}`;
|
71
|
+
verificationKeyPair.controller = did;
|
72
|
+
console.log('Generated DID:', did);
|
73
|
+
return {
|
74
|
+
keyPair: verificationKeyPair,
|
75
|
+
didDocument,
|
76
|
+
};
|
77
|
+
};
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@cooperation/vc-storage",
|
3
3
|
"type": "module",
|
4
|
-
"version": "1.0.
|
4
|
+
"version": "1.0.31",
|
5
5
|
"description": "Sign and store your verifiable credentials.",
|
6
6
|
"main": "dist/index.js",
|
7
7
|
"types": "dist/types/index.d.ts",
|
@@ -17,12 +17,16 @@
|
|
17
17
|
"license": "ISC",
|
18
18
|
"dependencies": {
|
19
19
|
"@digitalbazaar/did-method-key": "^5.2.0",
|
20
|
-
"@digitalbazaar/ed25519-signature-2020": "^5.
|
20
|
+
"@digitalbazaar/ed25519-signature-2020": "^5.4.0",
|
21
21
|
"@digitalbazaar/ed25519-verification-key-2020": "^4.1.0",
|
22
22
|
"@digitalbazaar/vc": "^6.3.0",
|
23
|
+
"add": "^2.0.6",
|
24
|
+
"bnid": "^3.0.0",
|
23
25
|
"crypto-js": "^4.2.0",
|
26
|
+
"crypto-ld": "^7.0.0",
|
24
27
|
"ethers": "^6.13.2",
|
25
28
|
"jest": "^29.7.0",
|
29
|
+
"multiformats": "^13.3.6",
|
26
30
|
"ts-jest": "^29.2.5",
|
27
31
|
"ts-node": "^10.9.2",
|
28
32
|
"tsc": "^2.0.4",
|