@originals/sdk 1.5.0 ā 1.6.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/dist/adapters/FeeOracleMock.js +2 -2
- package/dist/bitcoin/OrdinalsClient.d.ts +1 -1
- package/dist/bitcoin/OrdinalsClient.js +10 -8
- package/dist/bitcoin/PSBTBuilder.js +1 -1
- package/dist/bitcoin/utxo-selection.js +2 -2
- package/dist/cel/ExternalReferenceManager.d.ts +57 -0
- package/dist/cel/ExternalReferenceManager.js +73 -0
- package/dist/cel/OriginalsCel.d.ts +245 -0
- package/dist/cel/OriginalsCel.js +349 -0
- package/dist/cel/algorithms/createEventLog.d.ts +32 -0
- package/dist/cel/algorithms/createEventLog.js +56 -0
- package/dist/cel/algorithms/deactivateEventLog.d.ts +35 -0
- package/dist/cel/algorithms/deactivateEventLog.js +91 -0
- package/dist/cel/algorithms/index.d.ts +10 -0
- package/dist/cel/algorithms/index.js +10 -0
- package/dist/cel/algorithms/updateEventLog.d.ts +34 -0
- package/dist/cel/algorithms/updateEventLog.js +82 -0
- package/dist/cel/algorithms/verifyEventLog.d.ts +45 -0
- package/dist/cel/algorithms/verifyEventLog.js +255 -0
- package/dist/cel/algorithms/witnessEvent.d.ts +29 -0
- package/dist/cel/algorithms/witnessEvent.js +75 -0
- package/dist/cel/cli/create.d.ts +36 -0
- package/dist/cel/cli/create.js +282 -0
- package/dist/cel/cli/index.d.ts +11 -0
- package/dist/cel/cli/index.js +351 -0
- package/dist/cel/cli/inspect.d.ts +30 -0
- package/dist/cel/cli/inspect.js +475 -0
- package/dist/cel/cli/migrate.d.ts +41 -0
- package/dist/cel/cli/migrate.js +405 -0
- package/dist/cel/cli/verify.d.ts +31 -0
- package/dist/cel/cli/verify.js +205 -0
- package/dist/cel/hash.d.ts +46 -0
- package/dist/cel/hash.js +66 -0
- package/dist/cel/index.d.ts +15 -0
- package/dist/cel/index.js +15 -0
- package/dist/cel/layers/BtcoCelManager.d.ts +121 -0
- package/dist/cel/layers/BtcoCelManager.js +329 -0
- package/dist/cel/layers/PeerCelManager.d.ts +151 -0
- package/dist/cel/layers/PeerCelManager.js +299 -0
- package/dist/cel/layers/WebVHCelManager.d.ts +122 -0
- package/dist/cel/layers/WebVHCelManager.js +291 -0
- package/dist/cel/layers/index.d.ts +13 -0
- package/dist/cel/layers/index.js +16 -0
- package/dist/cel/serialization/cbor.d.ts +42 -0
- package/dist/cel/serialization/cbor.js +163 -0
- package/dist/cel/serialization/index.d.ts +9 -0
- package/dist/cel/serialization/index.js +9 -0
- package/dist/cel/serialization/json.d.ts +41 -0
- package/dist/cel/serialization/json.js +180 -0
- package/dist/cel/types.d.ts +149 -0
- package/dist/cel/types.js +7 -0
- package/dist/cel/witnesses/BitcoinWitness.d.ts +83 -0
- package/dist/cel/witnesses/BitcoinWitness.js +116 -0
- package/dist/cel/witnesses/HttpWitness.d.ts +79 -0
- package/dist/cel/witnesses/HttpWitness.js +163 -0
- package/dist/cel/witnesses/WitnessService.d.ts +49 -0
- package/dist/cel/witnesses/WitnessService.js +10 -0
- package/dist/cel/witnesses/index.d.ts +10 -0
- package/dist/cel/witnesses/index.js +7 -0
- package/dist/core/OriginalsSDK.js +5 -1
- package/dist/crypto/Signer.js +14 -6
- package/dist/crypto/noble-init.js +20 -1
- package/dist/did/BtcoDidResolver.d.ts +2 -2
- package/dist/did/BtcoDidResolver.js +12 -8
- package/dist/did/DIDManager.js +6 -4
- package/dist/did/KeyManager.d.ts +1 -1
- package/dist/did/KeyManager.js +7 -4
- package/dist/did/WebVHManager.js +1 -1
- package/dist/did/createBtcoDidDocument.js +2 -1
- package/dist/events/types.d.ts +4 -1
- package/dist/examples/create-module-original.js +1 -1
- package/dist/examples/full-lifecycle-flow.js +2 -2
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/kinds/KindRegistry.js +59 -29
- package/dist/lifecycle/BatchOperations.d.ts +5 -3
- package/dist/lifecycle/BatchOperations.js +11 -5
- package/dist/lifecycle/LifecycleManager.d.ts +1 -1
- package/dist/lifecycle/LifecycleManager.js +42 -33
- package/dist/lifecycle/OriginalsAsset.js +2 -2
- package/dist/migration/MigrationManager.js +67 -3
- package/dist/storage/LocalStorageAdapter.js +4 -1
- package/dist/storage/MemoryStorageAdapter.js +7 -7
- package/dist/types/network.js +6 -3
- package/dist/utils/Logger.d.ts +6 -6
- package/dist/utils/Logger.js +5 -3
- package/dist/utils/MetricsCollector.js +1 -1
- package/dist/utils/bitcoin-address.js +4 -2
- package/dist/utils/cbor.js +16 -3
- package/dist/utils/encoding.d.ts +4 -4
- package/dist/utils/encoding.js +7 -6
- package/dist/utils/hash.js +6 -1
- package/dist/utils/serialization.d.ts +2 -2
- package/dist/utils/serialization.js +7 -5
- package/dist/utils/telemetry.js +6 -2
- package/dist/utils/validation.js +8 -4
- package/dist/vc/CredentialManager.d.ts +8 -8
- package/dist/vc/CredentialManager.js +46 -33
- package/dist/vc/Issuer.d.ts +2 -2
- package/dist/vc/Issuer.js +5 -1
- package/dist/vc/Verifier.d.ts +2 -2
- package/dist/vc/Verifier.js +12 -6
- package/dist/vc/documentLoader.d.ts +5 -3
- package/dist/vc/documentLoader.js +5 -4
- package/package.json +4 -1
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI Migrate Command
|
|
4
|
+
*
|
|
5
|
+
* Migrates a CEL asset between layers in the Originals Protocol.
|
|
6
|
+
*
|
|
7
|
+
* Migration paths:
|
|
8
|
+
* - peer ā webvh: Requires --domain
|
|
9
|
+
* - webvh ā btco: Requires --wallet
|
|
10
|
+
*
|
|
11
|
+
* Usage: originals-cel migrate --log <path> --to <layer> [options]
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import { WebVHCelManager } from '../layers/WebVHCelManager';
|
|
16
|
+
import { BtcoCelManager } from '../layers/BtcoCelManager';
|
|
17
|
+
import { parseEventLogJson } from '../serialization/json';
|
|
18
|
+
import { parseEventLogCbor } from '../serialization/cbor';
|
|
19
|
+
import { serializeEventLogJson } from '../serialization/json';
|
|
20
|
+
import { serializeEventLogCbor } from '../serialization/cbor';
|
|
21
|
+
import { multikey } from '../../crypto/Multikey';
|
|
22
|
+
/**
|
|
23
|
+
* Valid target layers for migration
|
|
24
|
+
*/
|
|
25
|
+
const VALID_LAYERS = ['webvh', 'btco'];
|
|
26
|
+
/**
|
|
27
|
+
* Loads and parses an event log from a file
|
|
28
|
+
* Supports both JSON (.json) and CBOR (.cbor) formats
|
|
29
|
+
*/
|
|
30
|
+
function loadEventLog(filePath) {
|
|
31
|
+
if (!fs.existsSync(filePath)) {
|
|
32
|
+
throw new Error(`File not found: ${filePath}`);
|
|
33
|
+
}
|
|
34
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
35
|
+
const content = fs.readFileSync(filePath);
|
|
36
|
+
if (ext === '.cbor') {
|
|
37
|
+
// Parse as CBOR binary
|
|
38
|
+
return parseEventLogCbor(new Uint8Array(content));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Default to JSON parsing
|
|
42
|
+
return parseEventLogJson(content.toString('utf-8'));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Detects the current layer from an event log by examining events
|
|
47
|
+
*/
|
|
48
|
+
function detectCurrentLayer(log) {
|
|
49
|
+
if (!log.events || log.events.length === 0) {
|
|
50
|
+
throw new Error('Cannot detect layer from empty event log');
|
|
51
|
+
}
|
|
52
|
+
let currentLayer = 'peer';
|
|
53
|
+
for (const event of log.events) {
|
|
54
|
+
const data = event.data;
|
|
55
|
+
if (event.type === 'create' && data.layer) {
|
|
56
|
+
currentLayer = data.layer;
|
|
57
|
+
}
|
|
58
|
+
else if (event.type === 'update' && data.layer && data.targetDid) {
|
|
59
|
+
// This is a migration event
|
|
60
|
+
currentLayer = data.layer;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return currentLayer;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Extracts the current DID from an event log
|
|
67
|
+
*/
|
|
68
|
+
function getCurrentDid(log) {
|
|
69
|
+
if (!log.events || log.events.length === 0) {
|
|
70
|
+
throw new Error('Cannot extract DID from empty event log');
|
|
71
|
+
}
|
|
72
|
+
let currentDid;
|
|
73
|
+
for (const event of log.events) {
|
|
74
|
+
const data = event.data;
|
|
75
|
+
if (event.type === 'create' && data.did) {
|
|
76
|
+
currentDid = data.did;
|
|
77
|
+
}
|
|
78
|
+
else if (event.type === 'update' && data.targetDid) {
|
|
79
|
+
// This is a migration event
|
|
80
|
+
currentDid = data.targetDid;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!currentDid) {
|
|
84
|
+
throw new Error('Could not determine current DID from event log');
|
|
85
|
+
}
|
|
86
|
+
return currentDid;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Validates the migration path
|
|
90
|
+
*/
|
|
91
|
+
function validateMigrationPath(currentLayer, targetLayer) {
|
|
92
|
+
if (currentLayer === 'btco') {
|
|
93
|
+
throw new Error('Cannot migrate from btco layer - it is the final layer');
|
|
94
|
+
}
|
|
95
|
+
if (currentLayer === 'peer' && targetLayer === 'btco') {
|
|
96
|
+
throw new Error('Cannot migrate directly from peer to btco. Must migrate to webvh first.');
|
|
97
|
+
}
|
|
98
|
+
if (currentLayer === 'webvh' && targetLayer === 'webvh') {
|
|
99
|
+
throw new Error('Asset is already at webvh layer');
|
|
100
|
+
}
|
|
101
|
+
if (currentLayer === targetLayer) {
|
|
102
|
+
throw new Error(`Asset is already at ${targetLayer} layer`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Loads an Ed25519 private key from a wallet file
|
|
107
|
+
* Supports both raw multibase format and JSON format { privateKey: "z..." }
|
|
108
|
+
*/
|
|
109
|
+
async function loadWalletKey(walletPath) {
|
|
110
|
+
if (!fs.existsSync(walletPath)) {
|
|
111
|
+
throw new Error(`Wallet file not found: ${walletPath}`);
|
|
112
|
+
}
|
|
113
|
+
const content = fs.readFileSync(walletPath, 'utf-8').trim();
|
|
114
|
+
let privateKey;
|
|
115
|
+
// Try parsing as JSON first
|
|
116
|
+
try {
|
|
117
|
+
const parsed = JSON.parse(content);
|
|
118
|
+
if (parsed.privateKey) {
|
|
119
|
+
privateKey = parsed.privateKey;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
throw new Error('JSON wallet file must contain "privateKey" field');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
// Not JSON, treat as raw multibase key
|
|
127
|
+
if (content.startsWith('z')) {
|
|
128
|
+
privateKey = content;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
throw new Error('Wallet file must contain a z-base58btc multibase-encoded Ed25519 private key');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Validate and decode to get public key
|
|
135
|
+
const decoded = multikey.decodePrivateKey(privateKey);
|
|
136
|
+
if (decoded.type !== 'Ed25519') {
|
|
137
|
+
throw new Error(`Expected Ed25519 key, got ${decoded.type}`);
|
|
138
|
+
}
|
|
139
|
+
// Derive public key from private key
|
|
140
|
+
const ed25519 = await import('@noble/ed25519');
|
|
141
|
+
const publicKeyBytes = await ed25519.getPublicKeyAsync(decoded.key);
|
|
142
|
+
const publicKey = multikey.encodePublicKey(publicKeyBytes, 'Ed25519');
|
|
143
|
+
return { privateKey, publicKey };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Creates a signer function from a private key
|
|
147
|
+
*/
|
|
148
|
+
function createSigner(privateKey, publicKey) {
|
|
149
|
+
return async (data) => {
|
|
150
|
+
const ed25519 = await import('@noble/ed25519');
|
|
151
|
+
const decoded = multikey.decodePrivateKey(privateKey);
|
|
152
|
+
// Serialize data for signing (deterministic JSON)
|
|
153
|
+
const dataStr = JSON.stringify(data, Object.keys(data).sort());
|
|
154
|
+
const dataBytes = new TextEncoder().encode(dataStr);
|
|
155
|
+
// Sign the data
|
|
156
|
+
const signature = await ed25519.signAsync(dataBytes, decoded.key);
|
|
157
|
+
const proofValue = multikey.encodeMultibase(new Uint8Array(signature));
|
|
158
|
+
return {
|
|
159
|
+
type: 'DataIntegrityProof',
|
|
160
|
+
cryptosuite: 'eddsa-jcs-2022',
|
|
161
|
+
created: new Date().toISOString(),
|
|
162
|
+
verificationMethod: `did:key:${publicKey}#${publicKey}`,
|
|
163
|
+
proofPurpose: 'assertionMethod',
|
|
164
|
+
proofValue,
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Creates a mock BitcoinManager for testing/development
|
|
170
|
+
* In production, this would be a real BitcoinManager instance
|
|
171
|
+
*/
|
|
172
|
+
function createMockBitcoinManager() {
|
|
173
|
+
// For CLI use, we create a minimal mock that satisfies the interface
|
|
174
|
+
// Real Bitcoin integration requires additional configuration
|
|
175
|
+
return {
|
|
176
|
+
inscribeData: async (data) => {
|
|
177
|
+
// Generate mock Bitcoin transaction details
|
|
178
|
+
const timestamp = Date.now();
|
|
179
|
+
const mockTxid = `cli_mock_tx_${timestamp.toString(16)}`;
|
|
180
|
+
const mockInscriptionId = `cli_mock_inscription_${timestamp.toString(16)}i0`;
|
|
181
|
+
console.error('\nā ļø Note: Using mock Bitcoin manager for CLI migration.');
|
|
182
|
+
console.error(' For real Bitcoin inscriptions, use the SDK programmatically.\n');
|
|
183
|
+
return {
|
|
184
|
+
txid: mockTxid,
|
|
185
|
+
inscriptionId: mockInscriptionId,
|
|
186
|
+
satoshi: 10000,
|
|
187
|
+
blockHeight: 0, // Will be set when confirmed
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
getInscription: async (inscriptionId) => {
|
|
191
|
+
return { inscriptionId, confirmed: false };
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Execute the migrate command
|
|
197
|
+
*/
|
|
198
|
+
export async function migrateCommand(flags) {
|
|
199
|
+
// Check for help flag
|
|
200
|
+
if (flags.help || flags.h) {
|
|
201
|
+
return {
|
|
202
|
+
success: true,
|
|
203
|
+
message: 'Use --help with the main CLI for full help text',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// Validate required arguments
|
|
207
|
+
if (!flags.log) {
|
|
208
|
+
return {
|
|
209
|
+
success: false,
|
|
210
|
+
message: 'Error: --log is required. Usage: originals-cel migrate --log <path> --to <layer>',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (!flags.to) {
|
|
214
|
+
return {
|
|
215
|
+
success: false,
|
|
216
|
+
message: 'Error: --to is required. Valid layers: webvh, btco',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
// Validate target layer
|
|
220
|
+
const targetLayer = flags.to.toLowerCase();
|
|
221
|
+
if (!VALID_LAYERS.includes(targetLayer)) {
|
|
222
|
+
return {
|
|
223
|
+
success: false,
|
|
224
|
+
message: `Error: Invalid target layer "${flags.to}". Valid layers: webvh, btco`,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
// Validate layer-specific requirements
|
|
228
|
+
if (targetLayer === 'webvh' && !flags.domain) {
|
|
229
|
+
return {
|
|
230
|
+
success: false,
|
|
231
|
+
message: 'Error: --domain is required for webvh migration. Usage: originals-cel migrate --log <path> --to webvh --domain example.com',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (targetLayer === 'btco' && !flags.wallet) {
|
|
235
|
+
return {
|
|
236
|
+
success: false,
|
|
237
|
+
message: 'Error: --wallet is required for btco migration. Usage: originals-cel migrate --log <path> --to btco --wallet ./wallet.key',
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
// Load the event log
|
|
241
|
+
let eventLog;
|
|
242
|
+
try {
|
|
243
|
+
eventLog = loadEventLog(flags.log);
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
return {
|
|
247
|
+
success: false,
|
|
248
|
+
message: `Error: Failed to load event log: ${e.message}`,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
// Detect current layer and validate migration path
|
|
252
|
+
let currentLayer;
|
|
253
|
+
try {
|
|
254
|
+
currentLayer = detectCurrentLayer(eventLog);
|
|
255
|
+
validateMigrationPath(currentLayer, targetLayer);
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
258
|
+
return {
|
|
259
|
+
success: false,
|
|
260
|
+
message: `Error: Invalid migration path: ${e.message}`,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// Get source DID
|
|
264
|
+
let sourceDid;
|
|
265
|
+
try {
|
|
266
|
+
sourceDid = getCurrentDid(eventLog);
|
|
267
|
+
}
|
|
268
|
+
catch (e) {
|
|
269
|
+
return {
|
|
270
|
+
success: false,
|
|
271
|
+
message: `Error: ${e.message}`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
// Load wallet key for signing
|
|
275
|
+
let privateKey;
|
|
276
|
+
let publicKey;
|
|
277
|
+
if (flags.wallet) {
|
|
278
|
+
try {
|
|
279
|
+
const loaded = await loadWalletKey(flags.wallet);
|
|
280
|
+
privateKey = loaded.privateKey;
|
|
281
|
+
publicKey = loaded.publicKey;
|
|
282
|
+
}
|
|
283
|
+
catch (e) {
|
|
284
|
+
return {
|
|
285
|
+
success: false,
|
|
286
|
+
message: `Error: Failed to load wallet: ${e.message}`,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// Generate temporary key for signing (for webvh migration without wallet)
|
|
292
|
+
const ed25519 = await import('@noble/ed25519');
|
|
293
|
+
const privateKeyBytes = ed25519.utils.randomPrivateKey();
|
|
294
|
+
const publicKeyBytes = await ed25519.getPublicKeyAsync(privateKeyBytes);
|
|
295
|
+
privateKey = multikey.encodePrivateKey(privateKeyBytes, 'Ed25519');
|
|
296
|
+
publicKey = multikey.encodePublicKey(publicKeyBytes, 'Ed25519');
|
|
297
|
+
console.error('\nā ļø Generated temporary signing key. Save the key below for future operations:');
|
|
298
|
+
console.error(`Private Key: ${privateKey}`);
|
|
299
|
+
console.error(`Public Key: ${publicKey}\n`);
|
|
300
|
+
}
|
|
301
|
+
// Create signer
|
|
302
|
+
const signer = createSigner(privateKey, publicKey);
|
|
303
|
+
// Perform migration
|
|
304
|
+
let migratedLog;
|
|
305
|
+
let targetDid;
|
|
306
|
+
try {
|
|
307
|
+
if (targetLayer === 'webvh') {
|
|
308
|
+
// Migrate to webvh layer
|
|
309
|
+
const manager = new WebVHCelManager(signer, flags.domain, []);
|
|
310
|
+
migratedLog = await manager.migrate(eventLog);
|
|
311
|
+
// Extract target DID from migration event
|
|
312
|
+
const migrationEvent = migratedLog.events[migratedLog.events.length - 1];
|
|
313
|
+
const migrationData = migrationEvent.data;
|
|
314
|
+
targetDid = migrationData.targetDid;
|
|
315
|
+
}
|
|
316
|
+
else if (targetLayer === 'btco') {
|
|
317
|
+
// Migrate to btco layer
|
|
318
|
+
const bitcoinManager = createMockBitcoinManager();
|
|
319
|
+
const manager = new BtcoCelManager(signer, bitcoinManager);
|
|
320
|
+
migratedLog = await manager.migrate(eventLog);
|
|
321
|
+
// Extract target DID from migration event
|
|
322
|
+
const migrationEvent = migratedLog.events[migratedLog.events.length - 1];
|
|
323
|
+
const migrationData = migrationEvent.data;
|
|
324
|
+
targetDid = migrationData.targetDid;
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
return {
|
|
328
|
+
success: false,
|
|
329
|
+
message: `Error: Unknown target layer: ${targetLayer}`,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
catch (e) {
|
|
334
|
+
return {
|
|
335
|
+
success: false,
|
|
336
|
+
message: `Error: Migration failed: ${e.message}`,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
// Determine output format
|
|
340
|
+
const format = (flags.format || 'json').toLowerCase();
|
|
341
|
+
if (format !== 'json' && format !== 'cbor') {
|
|
342
|
+
return {
|
|
343
|
+
success: false,
|
|
344
|
+
message: 'Error: --format must be "json" or "cbor"',
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
// Serialize output
|
|
348
|
+
let output;
|
|
349
|
+
try {
|
|
350
|
+
if (format === 'cbor') {
|
|
351
|
+
output = serializeEventLogCbor(migratedLog);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
output = serializeEventLogJson(migratedLog);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
catch (e) {
|
|
358
|
+
return {
|
|
359
|
+
success: false,
|
|
360
|
+
message: `Error: Failed to serialize event log: ${e.message}`,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
// Write output
|
|
364
|
+
if (flags.output) {
|
|
365
|
+
try {
|
|
366
|
+
if (format === 'cbor') {
|
|
367
|
+
fs.writeFileSync(flags.output, output);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
fs.writeFileSync(flags.output, output, 'utf-8');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch (e) {
|
|
374
|
+
return {
|
|
375
|
+
success: false,
|
|
376
|
+
message: `Error: Failed to write output file: ${e.message}`,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
// Write to stdout
|
|
382
|
+
if (format === 'cbor') {
|
|
383
|
+
// For CBOR, output as base64 to stdout since it's binary
|
|
384
|
+
const base64 = Buffer.from(output).toString('base64');
|
|
385
|
+
process.stdout.write(base64);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
process.stdout.write(output);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Build success message
|
|
392
|
+
const layerTransition = `${currentLayer} ā ${targetLayer}`;
|
|
393
|
+
const outputInfo = flags.output ? ` and saved to ${flags.output}` : '';
|
|
394
|
+
console.error(`\nā
Migration complete: ${layerTransition}`);
|
|
395
|
+
console.error(` Source DID: ${sourceDid}`);
|
|
396
|
+
console.error(` Target DID: ${targetDid}${outputInfo}\n`);
|
|
397
|
+
return {
|
|
398
|
+
success: true,
|
|
399
|
+
message: `Migration complete: ${layerTransition}`,
|
|
400
|
+
log: migratedLog,
|
|
401
|
+
sourceDid,
|
|
402
|
+
targetDid,
|
|
403
|
+
targetLayer,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI Verify Command
|
|
4
|
+
*
|
|
5
|
+
* Verifies a CEL event log by checking all proofs and the hash chain integrity.
|
|
6
|
+
* Outputs event-by-event breakdown with witness attestation details.
|
|
7
|
+
*
|
|
8
|
+
* Usage: originals-cel verify --log <path> [options]
|
|
9
|
+
*/
|
|
10
|
+
import type { VerificationResult } from '../types';
|
|
11
|
+
/**
|
|
12
|
+
* Flags parsed from command line arguments
|
|
13
|
+
*/
|
|
14
|
+
export interface VerifyFlags {
|
|
15
|
+
log?: string;
|
|
16
|
+
help?: boolean;
|
|
17
|
+
h?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Result of the verify command
|
|
21
|
+
*/
|
|
22
|
+
export interface VerifyResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
message: string;
|
|
25
|
+
verified?: boolean;
|
|
26
|
+
result?: VerificationResult;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Execute the verify command
|
|
30
|
+
*/
|
|
31
|
+
export declare function verifyCommand(flags: VerifyFlags): Promise<VerifyResult>;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI Verify Command
|
|
4
|
+
*
|
|
5
|
+
* Verifies a CEL event log by checking all proofs and the hash chain integrity.
|
|
6
|
+
* Outputs event-by-event breakdown with witness attestation details.
|
|
7
|
+
*
|
|
8
|
+
* Usage: originals-cel verify --log <path> [options]
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import { verifyEventLog } from '../algorithms/verifyEventLog';
|
|
13
|
+
import { parseEventLogJson } from '../serialization/json';
|
|
14
|
+
import { parseEventLogCbor } from '../serialization/cbor';
|
|
15
|
+
/**
|
|
16
|
+
* Check if a proof is a WitnessProof (has witnessedAt field)
|
|
17
|
+
*/
|
|
18
|
+
function isWitnessProof(proof) {
|
|
19
|
+
return 'witnessedAt' in proof && typeof proof.witnessedAt === 'string';
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Loads and parses an event log from a file
|
|
23
|
+
* Supports both JSON (.json) and CBOR (.cbor) formats
|
|
24
|
+
*/
|
|
25
|
+
function loadEventLog(filePath) {
|
|
26
|
+
if (!fs.existsSync(filePath)) {
|
|
27
|
+
throw new Error(`File not found: ${filePath}`);
|
|
28
|
+
}
|
|
29
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
30
|
+
const content = fs.readFileSync(filePath);
|
|
31
|
+
if (ext === '.cbor') {
|
|
32
|
+
// Parse as CBOR binary
|
|
33
|
+
return parseEventLogCbor(new Uint8Array(content));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Default to JSON parsing
|
|
37
|
+
return parseEventLogJson(content.toString('utf-8'));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Format a timestamp for display
|
|
42
|
+
*/
|
|
43
|
+
function formatTimestamp(timestamp) {
|
|
44
|
+
try {
|
|
45
|
+
const date = new Date(timestamp);
|
|
46
|
+
return date.toISOString();
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return timestamp;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Format proof details for display
|
|
54
|
+
*/
|
|
55
|
+
function formatProofDetails(proof, indent = ' ') {
|
|
56
|
+
const lines = [];
|
|
57
|
+
lines.push(`${indent}Cryptosuite: ${proof.cryptosuite}`);
|
|
58
|
+
lines.push(`${indent}Created: ${formatTimestamp(proof.created)}`);
|
|
59
|
+
lines.push(`${indent}Verification Method: ${proof.verificationMethod}`);
|
|
60
|
+
lines.push(`${indent}Proof Purpose: ${proof.proofPurpose}`);
|
|
61
|
+
// Truncate proof value for display
|
|
62
|
+
const truncatedProof = proof.proofValue.length > 60
|
|
63
|
+
? `${proof.proofValue.substring(0, 57)}...`
|
|
64
|
+
: proof.proofValue;
|
|
65
|
+
lines.push(`${indent}Proof Value: ${truncatedProof}`);
|
|
66
|
+
// Add witness-specific info
|
|
67
|
+
if (isWitnessProof(proof)) {
|
|
68
|
+
lines.push(`${indent}š Witnessed At: ${formatTimestamp(proof.witnessedAt)}`);
|
|
69
|
+
}
|
|
70
|
+
return lines.join('\n');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Output verification result to stdout
|
|
74
|
+
*/
|
|
75
|
+
function outputResult(log, result) {
|
|
76
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
77
|
+
console.log(' CEL Event Log Verification Report');
|
|
78
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
79
|
+
// Overall result
|
|
80
|
+
if (result.verified) {
|
|
81
|
+
console.log('ā
VERIFICATION PASSED\n');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log('ā VERIFICATION FAILED\n');
|
|
85
|
+
}
|
|
86
|
+
// Summary
|
|
87
|
+
console.log(`š Summary:`);
|
|
88
|
+
console.log(` Total Events: ${result.events.length}`);
|
|
89
|
+
const proofsPassed = result.events.filter(e => e.proofValid).length;
|
|
90
|
+
const chainPassed = result.events.filter(e => e.chainValid).length;
|
|
91
|
+
console.log(` Proofs Valid: ${proofsPassed}/${result.events.length}`);
|
|
92
|
+
console.log(` Chain Valid: ${chainPassed}/${result.events.length}`);
|
|
93
|
+
// Count witness proofs
|
|
94
|
+
let totalWitnessProofs = 0;
|
|
95
|
+
for (const event of log.events) {
|
|
96
|
+
for (const proof of event.proof) {
|
|
97
|
+
if (isWitnessProof(proof)) {
|
|
98
|
+
totalWitnessProofs++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (totalWitnessProofs > 0) {
|
|
103
|
+
console.log(` Witness Attestations: ${totalWitnessProofs}`);
|
|
104
|
+
}
|
|
105
|
+
console.log('');
|
|
106
|
+
// Event-by-event breakdown
|
|
107
|
+
console.log('š Event-by-Event Breakdown:');
|
|
108
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
109
|
+
for (let i = 0; i < result.events.length; i++) {
|
|
110
|
+
const eventResult = result.events[i];
|
|
111
|
+
const event = log.events[i];
|
|
112
|
+
const proofIcon = eventResult.proofValid ? 'ā
' : 'ā';
|
|
113
|
+
const chainIcon = eventResult.chainValid ? 'ā
' : 'ā';
|
|
114
|
+
console.log(`\n Event ${i + 1} (${eventResult.type})`);
|
|
115
|
+
console.log(` āā Proof: ${proofIcon} ${eventResult.proofValid ? 'Valid' : 'Invalid'}`);
|
|
116
|
+
console.log(` āā Chain: ${chainIcon} ${eventResult.chainValid ? 'Valid' : 'Invalid'}`);
|
|
117
|
+
// Show proof details
|
|
118
|
+
if (event.proof && event.proof.length > 0) {
|
|
119
|
+
console.log(`\n Proofs (${event.proof.length}):`);
|
|
120
|
+
for (let j = 0; j < event.proof.length; j++) {
|
|
121
|
+
const proof = event.proof[j];
|
|
122
|
+
const isWitness = isWitnessProof(proof);
|
|
123
|
+
const proofLabel = isWitness ? 'š Witness Proof' : 'š Controller Proof';
|
|
124
|
+
console.log(`\n [${j + 1}] ${proofLabel}`);
|
|
125
|
+
console.log(formatProofDetails(proof, ' '));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Show errors for this event
|
|
129
|
+
if (eventResult.errors && eventResult.errors.length > 0) {
|
|
130
|
+
console.log(`\n ā ļø Errors:`);
|
|
131
|
+
for (const error of eventResult.errors) {
|
|
132
|
+
console.log(` - ${error}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Show global errors if any
|
|
137
|
+
if (result.errors && result.errors.length > 0) {
|
|
138
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
139
|
+
console.log('ā ļø All Errors:');
|
|
140
|
+
for (const error of result.errors) {
|
|
141
|
+
console.log(` - ${error}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Execute the verify command
|
|
148
|
+
*/
|
|
149
|
+
export async function verifyCommand(flags) {
|
|
150
|
+
// Check for help flag
|
|
151
|
+
if (flags.help || flags.h) {
|
|
152
|
+
return {
|
|
153
|
+
success: true,
|
|
154
|
+
message: 'Use --help with the main CLI for full help text',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// Validate required arguments
|
|
158
|
+
if (!flags.log) {
|
|
159
|
+
return {
|
|
160
|
+
success: false,
|
|
161
|
+
message: 'Error: --log is required. Usage: originals-cel verify --log <path>',
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// Load the event log
|
|
165
|
+
let eventLog;
|
|
166
|
+
try {
|
|
167
|
+
eventLog = loadEventLog(flags.log);
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
message: `Error: Failed to load event log: ${e.message}`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// Verify the event log
|
|
176
|
+
let result;
|
|
177
|
+
try {
|
|
178
|
+
result = await verifyEventLog(eventLog);
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
message: `Error: Verification failed: ${e.message}`,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// Output the result
|
|
187
|
+
outputResult(eventLog, result);
|
|
188
|
+
// Return result with exit code information
|
|
189
|
+
if (result.verified) {
|
|
190
|
+
return {
|
|
191
|
+
success: true,
|
|
192
|
+
verified: true,
|
|
193
|
+
message: 'Verification passed: All proofs valid and hash chain intact',
|
|
194
|
+
result,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
return {
|
|
199
|
+
success: true, // Command ran successfully, but verification failed
|
|
200
|
+
verified: false,
|
|
201
|
+
message: `Verification failed: ${result.errors.length} error(s) found`,
|
|
202
|
+
result,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CEL Hash Utilities
|
|
3
|
+
*
|
|
4
|
+
* Computes and verifies Multibase-encoded Multihash digests as specified
|
|
5
|
+
* in the CEL specification for external references.
|
|
6
|
+
*
|
|
7
|
+
* @see https://w3c-ccg.github.io/cel-spec/
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Computes a CEL-compliant digestMultibase from content.
|
|
11
|
+
*
|
|
12
|
+
* Uses sha2-256 hash algorithm and encodes as Multibase base64url-nopad (prefix 'u').
|
|
13
|
+
*
|
|
14
|
+
* @param content - The raw bytes to hash
|
|
15
|
+
* @returns Multibase-encoded digest string (e.g., "uEiDm9F...")
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const digest = computeDigestMultibase(new TextEncoder().encode("hello"));
|
|
20
|
+
* // Returns: "uLCA..."
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function computeDigestMultibase(content: Uint8Array): string;
|
|
24
|
+
/**
|
|
25
|
+
* Verifies that content matches a given digestMultibase.
|
|
26
|
+
*
|
|
27
|
+
* @param content - The raw bytes to verify
|
|
28
|
+
* @param digest - The expected digestMultibase string
|
|
29
|
+
* @returns True if the content hash matches the digest, false otherwise
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const content = new TextEncoder().encode("hello");
|
|
34
|
+
* const digest = computeDigestMultibase(content);
|
|
35
|
+
* const isValid = verifyDigestMultibase(content, digest); // true
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function verifyDigestMultibase(content: Uint8Array, digest: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Decodes a digestMultibase string back to raw hash bytes.
|
|
41
|
+
*
|
|
42
|
+
* @param digest - The Multibase-encoded digest string
|
|
43
|
+
* @returns The raw SHA-256 hash bytes
|
|
44
|
+
* @throws Error if the digest is not a valid Multibase base64url string
|
|
45
|
+
*/
|
|
46
|
+
export declare function decodeDigestMultibase(digest: string): Uint8Array;
|