-
Notifications
You must be signed in to change notification settings - Fork 2
/
ProofSetChainCreate.js
216 lines (185 loc) · 8.77 KB
/
ProofSetChainCreate.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
/*
Steps to create a signed verifiable credential with a proof set and then a
proof chain using Ed25519 signatures.
representation.
*/
import { mkdir, readFile, writeFile } from 'fs/promises';
import jsonld from 'jsonld';
import { localLoader } from './documentLoader.js';
import { base58btc } from "multiformats/bases/base58";
import {ed25519 as ed} from '@noble/curves/ed25519';
import { sha256 } from '@noble/hashes/sha256';
import { bytesToHex, concatBytes } from '@noble/hashes/utils';
// Create output directory for the results
const baseDir = "./output/eddsa-set-chain-2022/";
let status = await mkdir(baseDir, {recursive: true});
jsonld.documentLoader = localLoader; // Local loader for JSON-LD
const keyPairs = JSON.parse(
await readFile(
new URL('./input/multiKeyPairs.json', import.meta.url)
)
);
// Read input document from a file or just specify it right here.
let document = JSON.parse(
await readFile(
new URL('./input/unsigned.json', import.meta.url)
)
);
// Signed Document Creation Steps:
// Canonize the document
let cannon = await jsonld.canonize(document);
// console.log("Canonized unsigned document:")
// console.log(cannon);
// writeFile(baseDir + 'canonDocDataInt.txt', cannon);
// Hash canonized document
let docHash = sha256(cannon); // @noble/hash will convert string to bytes via UTF-8
// console.log("Hash of canonized document in hex:")
// console.log(bytesToHex(docHash));
// writeFile(baseDir + 'docHashDataInt.txt', bytesToHex(docHash));
// Set up proof set, two different signers proof will be an array based on two
let setKeys = [keyPairs.keyPair1, keyPairs.keyPair2];
let proofSet = [];
const proofIds = ["urn:uuid:26329423-bec9-4b2e-88cb-a7c7d9dc4544",
"urn:uuid:8cc9022b-6b14-4cf3-8571-74972c5feb54",
"urn:uuid:d94f792a-c546-4d06-b38a-da070ab56c23",
"urn:uuid:24148446-6ce5-49a6-b221-d29f1ca8a2f9"];
// Proof Sets processing loop
for (let i = 0; i < setKeys.length; i++) {
// different proof configurations
// Set proof options per draft
let proofConfig = {};
proofConfig.type = "DataIntegrityProof";
proofConfig.id = proofIds[i];
proofConfig.cryptosuite = "eddsa-rdfc-2022";
proofConfig.created = "2023-02-24T23:36:38Z";
// proofConfig.verificationMethod = "https://vc.example/issuers/5678" + (i+1) +
// "#" + setKeys[i].publicKeyMultibase;
proofConfig.verificationMethod = 'did:key:' + setKeys[i].publicKeyMultibase
+ '#' + setKeys[i].publicKeyMultibase;
proofConfig.proofPurpose = "assertionMethod";
writeFile(baseDir + `proofSetConfig${i+1}.json`, JSON.stringify(proofConfig, null, 2));
proofConfig["@context"] = document["@context"];
// canonize the proof config
let proofCanon = await jsonld.canonize(proofConfig);
// console.log("Proof Configuration Canonized:");
// console.log(proofCanon);
// writeFile(baseDir + 'proofCanonDataInt.txt', proofCanon);
// Hash canonized proof config
let proofHash = sha256(proofCanon); // @noble/hash will convert string to bytes via UTF-8
// console.log("Hash of canonized proof in hex:")
// console.log(bytesToHex(proofHash));
// writeFile(baseDir + 'proofHashDataInt.txt', bytesToHex(proofHash));
// Combine hashes
let combinedHash = concatBytes(proofHash, docHash);
// writeFile(baseDir + 'combinedHashDataInt.txt', bytesToHex(combinedHash));
// Sign
let privKey = base58btc.decode(setKeys[i].privateKeyMultibase);
privKey = privKey.slice(2, 34); // only want the first 2-34 bytes
// console.log(`Secret key length ${privKey.length}, value in hex:`);
let signature = await ed.sign(combinedHash, privKey);
// writeFile(baseDir + 'sigHexDataInt.txt', bytesToHex(signature));
// console.log("Computed Signature from private key:");
// console.log(base58btc.encode(signature));
// writeFile(baseDir + 'sigBTC58DataInt.txt', base58btc.encode(signature));
proofConfig.proofValue = base58btc.encode(signature);
delete proofConfig['@context'];
writeFile(baseDir + `proofSetConfigSigned${i+1}.json`, JSON.stringify(proofConfig, null, 2));
proofSet.push(proofConfig);
}
// Construct Signed Document
let signedDocument = Object.assign({}, document);
signedDocument.proof = proofSet;
// console.log(JSON.stringify(signedDocument, null, 2));
writeFile(baseDir + 'signedProofSet.json', JSON.stringify(signedDocument, null, 2));
// **Proof Chains** starting from previous signed document
const chainKeys = [keyPairs.keyPair3, keyPairs.keyPair4];
// Third proof depends on both proofs in the proof set, Fourth proof just depends on third proof
const previousProofs = [proofIds.slice(0,2), proofIds[2]];
for (let i = 0; i < chainKeys.length; i++) {
let allProofs = signedDocument.proof;
// Set up the proof configuration for the chain
let proofConfigChain = {};
proofConfigChain.type = "DataIntegrityProof";
if (i !== (chainKeys.length - 1)) { // Don't need id for last item in chain
proofConfigChain.id = proofIds[i+2];
}
proofConfigChain.cryptosuite = "eddsa-rdfc-2022";
proofConfigChain.created = `2023-02-26T22:${i}6:38Z`; // Signing later for realism ;-)
// proofConfigChain.verificationMethod = "https://vc.example/issuers/5678" + (i + 3) +
// "#" + keyPairs.keyPair3.publicKeyMultibase;
proofConfigChain.verificationMethod = 'did:key:' + chainKeys[i].publicKeyMultibase +
'#' + chainKeys[i].publicKeyMultibase;
proofConfigChain.proofPurpose = "assertionMethod";
proofConfigChain.previousProof = previousProofs[i]; // Want to include both proofs from the proof set
writeFile(baseDir + `proofChainConfig${i+1}.json`, JSON.stringify(proofConfigChain, null, 2));
proofConfigChain["@context"] = document["@context"];
// Dave's algorithm update
let matchingProofs = findMatchingProofs(proofConfigChain.previousProof, allProofs);
document.proof = matchingProofs;
console.log(`Matching proofs for i = ${i}`);
console.log(matchingProofs);
// Canonize the "chained" document
writeFile(baseDir + `proofChainTempDoc${i+1}.json`, JSON.stringify(document, null, 2));
cannon = await jsonld.canonize(document);
// console.log("Canonized chained document:")
// console.log(cannon);
// writeFile(baseDir + 'canonDocDataInt.txt', cannon);
// Hash canonized chained document
docHash = sha256(cannon); // @noble/hash will convert string to bytes via UTF-8
// console.log("Hash of canonized document in hex:")
// console.log(bytesToHex(docHash));
// writeFile(baseDir + 'docHashDataInt.txt', bytesToHex(docHash));
// canonize the proof config
let proofCanon = await jsonld.canonize(proofConfigChain);
// console.log("Proof Configuration Chain Canonized:");
// console.log(proofCanon);
// writeFile(baseDir + 'proofCanonDataInt.txt', proofCanon);
// Hash canonized proof config
let proofHash = sha256(proofCanon); // @noble/hash will convert string to bytes via UTF-8
// console.log("Hash of canonized proof in hex:")
// console.log(bytesToHex(proofHash));
// writeFile(baseDir + 'proofHashDataInt.txt', bytesToHex(proofHash));
// Combine hashes
let combinedHash = concatBytes(proofHash, docHash);
// writeFile(baseDir + 'combinedHashDataInt.txt', bytesToHex(combinedHash));
// Sign
let privKey = base58btc.decode(chainKeys[i].privateKeyMultibase);
privKey = privKey.slice(2, 34); // only want the first 2-34 bytes
// console.log(`Secret key length ${privKey.length}, value in hex:`);
let signature = await ed.sign(combinedHash, privKey);
// writeFile(baseDir + 'sigHexDataInt.txt', bytesToHex(signature));
// console.log("Computed Chain Signature from private key:");
// console.log(base58btc.encode(signature));
// writeFile(baseDir + 'sigBTC58DataInt.txt', base58btc.encode(signature));
proofConfigChain.proofValue = base58btc.encode(signature);
delete proofConfigChain['@context'];
writeFile(baseDir + `proofChainConfigSigned${i+1}.json`, JSON.stringify(proofConfigChain, null, 2));
// Construct Signed Document
signedDocument = Object.assign({}, document);
signedDocument.proof = allProofs.concat(proofConfigChain);
// console.log(JSON.stringify(signedDocument, null, 2));
writeFile(baseDir + `signedProofChain${i+1}.json`, JSON.stringify(signedDocument, null, 2));
}
// function to get all matching proofs (only first level no dependencies)
// prevProofs is either a string or an array
// proofs is an array of proofs
function findMatchingProofs(prevProofs, proofs) {
console.log(`findMatch called with ${prevProofs}`);
let matches = [];
if (Array.isArray(prevProofs)) {
prevProofs.forEach(pp => {
let matchProof = proofs.find(p => p.id === pp);
if (!matchProof) {
throw new Error(`Missing proof for id = ${pp}`);
}
matches.push(matchProof);
})
} else {
let matchProof = proofs.find(p => p.id === prevProofs);
if (!matchProof) {
throw new Error(`Missing proof for id = ${prevProofs}`);
}
matches.push(matchProof);
}
return matches;
}