@originals/sdk 1.8.1 → 1.8.2
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/utils/hash.js +1 -0
- package/package.json +6 -5
- package/src/adapters/FeeOracleMock.ts +9 -0
- package/src/adapters/index.ts +5 -0
- package/src/adapters/providers/OrdHttpProvider.ts +126 -0
- package/src/adapters/providers/OrdMockProvider.ts +101 -0
- package/src/adapters/types.ts +66 -0
- package/src/bitcoin/BitcoinManager.ts +329 -0
- package/src/bitcoin/BroadcastClient.ts +54 -0
- package/src/bitcoin/OrdinalsClient.ts +120 -0
- package/src/bitcoin/PSBTBuilder.ts +106 -0
- package/src/bitcoin/fee-calculation.ts +38 -0
- package/src/bitcoin/providers/OrdNodeProvider.ts +92 -0
- package/src/bitcoin/providers/OrdinalsProvider.ts +56 -0
- package/src/bitcoin/providers/types.ts +59 -0
- package/src/bitcoin/transactions/commit.ts +465 -0
- package/src/bitcoin/transactions/index.ts +13 -0
- package/src/bitcoin/transfer.ts +43 -0
- package/src/bitcoin/utxo-selection.ts +322 -0
- package/src/bitcoin/utxo.ts +113 -0
- package/src/cel/ExternalReferenceManager.ts +87 -0
- package/src/cel/OriginalsCel.ts +460 -0
- package/src/cel/algorithms/createEventLog.ts +68 -0
- package/src/cel/algorithms/deactivateEventLog.ts +109 -0
- package/src/cel/algorithms/index.ts +11 -0
- package/src/cel/algorithms/updateEventLog.ts +99 -0
- package/src/cel/algorithms/verifyEventLog.ts +306 -0
- package/src/cel/algorithms/witnessEvent.ts +87 -0
- package/src/cel/cli/create.ts +330 -0
- package/src/cel/cli/index.ts +383 -0
- package/src/cel/cli/inspect.ts +549 -0
- package/src/cel/cli/migrate.ts +473 -0
- package/src/cel/cli/verify.ts +249 -0
- package/src/cel/hash.ts +71 -0
- package/src/cel/index.ts +16 -0
- package/src/cel/layers/BtcoCelManager.ts +408 -0
- package/src/cel/layers/PeerCelManager.ts +371 -0
- package/src/cel/layers/WebVHCelManager.ts +361 -0
- package/src/cel/layers/index.ts +27 -0
- package/src/cel/serialization/cbor.ts +189 -0
- package/src/cel/serialization/index.ts +10 -0
- package/src/cel/serialization/json.ts +209 -0
- package/src/cel/types.ts +160 -0
- package/src/cel/witnesses/BitcoinWitness.ts +184 -0
- package/src/cel/witnesses/HttpWitness.ts +241 -0
- package/src/cel/witnesses/WitnessService.ts +51 -0
- package/src/cel/witnesses/index.ts +11 -0
- package/src/contexts/credentials-v1.json +237 -0
- package/src/contexts/credentials-v2-examples.json +5 -0
- package/src/contexts/credentials-v2.json +340 -0
- package/src/contexts/credentials.json +237 -0
- package/src/contexts/data-integrity-v2.json +81 -0
- package/src/contexts/dids.json +58 -0
- package/src/contexts/ed255192020.json +93 -0
- package/src/contexts/ordinals-plus.json +23 -0
- package/src/contexts/originals.json +22 -0
- package/src/core/OriginalsSDK.ts +420 -0
- package/src/crypto/Multikey.ts +194 -0
- package/src/crypto/Signer.ts +262 -0
- package/src/crypto/noble-init.ts +138 -0
- package/src/did/BtcoDidResolver.ts +231 -0
- package/src/did/DIDManager.ts +705 -0
- package/src/did/Ed25519Verifier.ts +68 -0
- package/src/did/KeyManager.ts +239 -0
- package/src/did/WebVHManager.ts +499 -0
- package/src/did/createBtcoDidDocument.ts +60 -0
- package/src/did/providers/OrdinalsClientProviderAdapter.ts +68 -0
- package/src/events/EventEmitter.ts +222 -0
- package/src/events/index.ts +19 -0
- package/src/events/types.ts +331 -0
- package/src/examples/basic-usage.ts +78 -0
- package/src/examples/create-module-original.ts +435 -0
- package/src/examples/full-lifecycle-flow.ts +514 -0
- package/src/examples/run.ts +60 -0
- package/src/index.ts +204 -0
- package/src/kinds/KindRegistry.ts +320 -0
- package/src/kinds/index.ts +74 -0
- package/src/kinds/types.ts +470 -0
- package/src/kinds/validators/AgentValidator.ts +257 -0
- package/src/kinds/validators/AppValidator.ts +211 -0
- package/src/kinds/validators/DatasetValidator.ts +242 -0
- package/src/kinds/validators/DocumentValidator.ts +311 -0
- package/src/kinds/validators/MediaValidator.ts +269 -0
- package/src/kinds/validators/ModuleValidator.ts +225 -0
- package/src/kinds/validators/base.ts +276 -0
- package/src/kinds/validators/index.ts +12 -0
- package/src/lifecycle/BatchOperations.ts +381 -0
- package/src/lifecycle/LifecycleManager.ts +2156 -0
- package/src/lifecycle/OriginalsAsset.ts +524 -0
- package/src/lifecycle/ProvenanceQuery.ts +280 -0
- package/src/lifecycle/ResourceVersioning.ts +163 -0
- package/src/migration/MigrationManager.ts +587 -0
- package/src/migration/audit/AuditLogger.ts +176 -0
- package/src/migration/checkpoint/CheckpointManager.ts +112 -0
- package/src/migration/checkpoint/CheckpointStorage.ts +101 -0
- package/src/migration/index.ts +33 -0
- package/src/migration/operations/BaseMigration.ts +126 -0
- package/src/migration/operations/PeerToBtcoMigration.ts +105 -0
- package/src/migration/operations/PeerToWebvhMigration.ts +62 -0
- package/src/migration/operations/WebvhToBtcoMigration.ts +105 -0
- package/src/migration/rollback/RollbackManager.ts +170 -0
- package/src/migration/state/StateMachine.ts +92 -0
- package/src/migration/state/StateTracker.ts +156 -0
- package/src/migration/types.ts +356 -0
- package/src/migration/validation/BitcoinValidator.ts +107 -0
- package/src/migration/validation/CredentialValidator.ts +62 -0
- package/src/migration/validation/DIDCompatibilityValidator.ts +151 -0
- package/src/migration/validation/LifecycleValidator.ts +64 -0
- package/src/migration/validation/StorageValidator.ts +79 -0
- package/src/migration/validation/ValidationPipeline.ts +213 -0
- package/src/resources/ResourceManager.ts +655 -0
- package/src/resources/index.ts +21 -0
- package/src/resources/types.ts +202 -0
- package/src/storage/LocalStorageAdapter.ts +64 -0
- package/src/storage/MemoryStorageAdapter.ts +29 -0
- package/src/storage/StorageAdapter.ts +25 -0
- package/src/storage/index.ts +3 -0
- package/src/types/bitcoin.ts +98 -0
- package/src/types/common.ts +92 -0
- package/src/types/credentials.ts +89 -0
- package/src/types/did.ts +31 -0
- package/src/types/external-shims.d.ts +53 -0
- package/src/types/index.ts +7 -0
- package/src/types/network.ts +178 -0
- package/src/utils/EventLogger.ts +298 -0
- package/src/utils/Logger.ts +324 -0
- package/src/utils/MetricsCollector.ts +358 -0
- package/src/utils/bitcoin-address.ts +132 -0
- package/src/utils/cbor.ts +31 -0
- package/src/utils/encoding.ts +135 -0
- package/src/utils/hash.ts +12 -0
- package/src/utils/retry.ts +46 -0
- package/src/utils/satoshi-validation.ts +196 -0
- package/src/utils/serialization.ts +102 -0
- package/src/utils/telemetry.ts +44 -0
- package/src/utils/validation.ts +123 -0
- package/src/vc/CredentialManager.ts +955 -0
- package/src/vc/Issuer.ts +105 -0
- package/src/vc/Verifier.ts +54 -0
- package/src/vc/cryptosuites/bbs.ts +253 -0
- package/src/vc/cryptosuites/bbsSimple.ts +21 -0
- package/src/vc/cryptosuites/eddsa.ts +99 -0
- package/src/vc/documentLoader.ts +81 -0
- package/src/vc/proofs/data-integrity.ts +33 -0
- package/src/vc/utils/jsonld.ts +18 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuditLogger - Creates and manages migration audit records
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { MigrationAuditRecord, IAuditLogger } from '../types';
|
|
6
|
+
import { OriginalsConfig } from '../../types';
|
|
7
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
8
|
+
import { encodeBase64UrlMultibase } from '../../utils/encoding';
|
|
9
|
+
|
|
10
|
+
export class AuditLogger implements IAuditLogger {
|
|
11
|
+
private auditRecords: Map<string, MigrationAuditRecord[]>;
|
|
12
|
+
|
|
13
|
+
constructor(private config: OriginalsConfig) {
|
|
14
|
+
this.auditRecords = new Map();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Log a migration audit record
|
|
19
|
+
*/
|
|
20
|
+
async logMigration(record: MigrationAuditRecord): Promise<void> {
|
|
21
|
+
// Sign the audit record
|
|
22
|
+
const signature = await this.signAuditRecord(record);
|
|
23
|
+
const signedRecord = { ...record, signature };
|
|
24
|
+
|
|
25
|
+
// Store by source DID
|
|
26
|
+
const existingRecords = this.auditRecords.get(record.sourceDid) || [];
|
|
27
|
+
existingRecords.push(signedRecord);
|
|
28
|
+
this.auditRecords.set(record.sourceDid, existingRecords);
|
|
29
|
+
|
|
30
|
+
// Also store by target DID if available
|
|
31
|
+
if (record.targetDid) {
|
|
32
|
+
const targetRecords = this.auditRecords.get(record.targetDid) || [];
|
|
33
|
+
targetRecords.push(signedRecord);
|
|
34
|
+
this.auditRecords.set(record.targetDid, targetRecords);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Persist to storage if available (append-only, never overwrite)
|
|
38
|
+
await this.persistAuditRecord(signedRecord);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get migration history for a DID
|
|
43
|
+
*/
|
|
44
|
+
async getMigrationHistory(did: string): Promise<MigrationAuditRecord[]> {
|
|
45
|
+
return this.auditRecords.get(did) || [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get system-wide migration logs with filters
|
|
50
|
+
* Fixed dedupe logic: use signature to avoid timeline collapse
|
|
51
|
+
*/
|
|
52
|
+
async getSystemMigrationLogs(filters: Partial<MigrationAuditRecord>): Promise<MigrationAuditRecord[]> {
|
|
53
|
+
const allRecords: MigrationAuditRecord[] = [];
|
|
54
|
+
|
|
55
|
+
// Collect all unique records (dedupe by signature to preserve timeline)
|
|
56
|
+
const seen = new Set<string>();
|
|
57
|
+
for (const records of this.auditRecords.values()) {
|
|
58
|
+
for (const record of records) {
|
|
59
|
+
const dedupKey = record.signature || `${record.migrationId}-${record.timestamp}-${record.finalState}`;
|
|
60
|
+
if (!seen.has(dedupKey)) {
|
|
61
|
+
seen.add(dedupKey);
|
|
62
|
+
allRecords.push(record);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Apply filters
|
|
68
|
+
return allRecords.filter(record => {
|
|
69
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
70
|
+
if (record[key as keyof MigrationAuditRecord] !== value) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Sign an audit record for integrity
|
|
80
|
+
*
|
|
81
|
+
* TODO: Replace with real digital signatures (Ed25519/ECDSA)
|
|
82
|
+
* Current implementation uses SHA256 hash for integrity verification.
|
|
83
|
+
* In production, use config.signer.sign(bytes)/verify(bytes, signature) with:
|
|
84
|
+
* - Ed25519 for performance
|
|
85
|
+
* - ECDSA (secp256k1/secp256r1) for compatibility
|
|
86
|
+
*
|
|
87
|
+
* Example:
|
|
88
|
+
* const signer = config.signer; // Ed25519Signer or ES256KSigner
|
|
89
|
+
* const signature = await signer.sign(Buffer.from(canonical), privateKey);
|
|
90
|
+
* return encodeBase64UrlMultibase(signature);
|
|
91
|
+
*/
|
|
92
|
+
private async signAuditRecord(record: MigrationAuditRecord): Promise<string> {
|
|
93
|
+
// Create a canonical representation of the record (without signature)
|
|
94
|
+
const { signature, ...recordWithoutSig } = record as any;
|
|
95
|
+
const canonical = JSON.stringify(recordWithoutSig);
|
|
96
|
+
|
|
97
|
+
// Hash the canonical representation (placeholder for real signature)
|
|
98
|
+
const hash = sha256(Buffer.from(canonical, 'utf8'));
|
|
99
|
+
|
|
100
|
+
// Encode as multibase for storage
|
|
101
|
+
return encodeBase64UrlMultibase(Buffer.from(hash));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Verify an audit record signature
|
|
106
|
+
*/
|
|
107
|
+
async verifyAuditRecord(record: MigrationAuditRecord): Promise<boolean> {
|
|
108
|
+
if (!record.signature) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const expectedSignature = await this.signAuditRecord(record);
|
|
113
|
+
return expectedSignature === record.signature;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Persist audit record to storage (append-only, never overwrite)
|
|
118
|
+
* Updated key design: audit/migrations/{migrationId}/{timestamp}-{finalState}.json
|
|
119
|
+
*/
|
|
120
|
+
private async persistAuditRecord(record: MigrationAuditRecord): Promise<void> {
|
|
121
|
+
const storageAdapter = (this.config as any).storageAdapter;
|
|
122
|
+
if (storageAdapter && typeof storageAdapter.put === 'function') {
|
|
123
|
+
try {
|
|
124
|
+
const data = JSON.stringify(record);
|
|
125
|
+
// Use unique key to prevent overwriting: migrationId/timestamp-state
|
|
126
|
+
const key = `audit/migrations/${record.migrationId}/${record.timestamp}-${record.finalState}.json`;
|
|
127
|
+
await storageAdapter.put(key, Buffer.from(data), { contentType: 'application/json' });
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('Failed to persist audit record:', error);
|
|
130
|
+
// Continue - in-memory record is still available
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Load audit records from storage
|
|
137
|
+
*/
|
|
138
|
+
async loadAuditRecords(did: string): Promise<void> {
|
|
139
|
+
const storageAdapter = (this.config as any).storageAdapter;
|
|
140
|
+
if (!storageAdapter || typeof storageAdapter.list !== 'function') {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
// List all audit records
|
|
146
|
+
const files = await storageAdapter.list('audit/migrations/');
|
|
147
|
+
|
|
148
|
+
for (const file of files) {
|
|
149
|
+
try {
|
|
150
|
+
const data = await storageAdapter.get(file);
|
|
151
|
+
if (data) {
|
|
152
|
+
const record: MigrationAuditRecord = JSON.parse(data.toString());
|
|
153
|
+
|
|
154
|
+
// Add to in-memory store if it matches the DID
|
|
155
|
+
if (record.sourceDid === did || record.targetDid === did) {
|
|
156
|
+
const existingRecords = this.auditRecords.get(did) || [];
|
|
157
|
+
// Use signature for dedupe to prevent timeline collapse
|
|
158
|
+
const dedupKey = record.signature || `${record.migrationId}-${record.timestamp}`;
|
|
159
|
+
if (!existingRecords.find(r => {
|
|
160
|
+
const rKey = r.signature || `${r.migrationId}-${r.timestamp}`;
|
|
161
|
+
return rKey === dedupKey;
|
|
162
|
+
})) {
|
|
163
|
+
existingRecords.push(record);
|
|
164
|
+
this.auditRecords.set(did, existingRecords);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
// Skip invalid audit records
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error('Failed to load audit records:', error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CheckpointManager - Creates and manages migration checkpoints for rollback
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
6
|
+
import {
|
|
7
|
+
MigrationOptions,
|
|
8
|
+
MigrationCheckpoint,
|
|
9
|
+
ICheckpointManager
|
|
10
|
+
} from '../types';
|
|
11
|
+
import { OriginalsConfig } from '../../types';
|
|
12
|
+
import { DIDManager } from '../../did/DIDManager';
|
|
13
|
+
import { CredentialManager } from '../../vc/CredentialManager';
|
|
14
|
+
import { CheckpointStorage } from './CheckpointStorage';
|
|
15
|
+
|
|
16
|
+
export class CheckpointManager implements ICheckpointManager {
|
|
17
|
+
private storage: CheckpointStorage;
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
private config: OriginalsConfig,
|
|
21
|
+
private didManager: DIDManager,
|
|
22
|
+
private credentialManager: CredentialManager
|
|
23
|
+
) {
|
|
24
|
+
this.storage = new CheckpointStorage(config);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a checkpoint before migration
|
|
29
|
+
*/
|
|
30
|
+
async createCheckpoint(migrationId: string, options: MigrationOptions): Promise<MigrationCheckpoint> {
|
|
31
|
+
try {
|
|
32
|
+
const checkpointId = `chk_${uuidv4()}`;
|
|
33
|
+
|
|
34
|
+
// Resolve source DID document
|
|
35
|
+
const didDocument = await this.didManager.resolveDID(options.sourceDid);
|
|
36
|
+
if (!didDocument) {
|
|
37
|
+
throw new Error(`Could not resolve source DID: ${options.sourceDid}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Extract source layer
|
|
41
|
+
const sourceLayer = this.extractLayer(options.sourceDid);
|
|
42
|
+
if (!sourceLayer) {
|
|
43
|
+
throw new Error(`Invalid source DID format: ${options.sourceDid}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Create checkpoint
|
|
47
|
+
const checkpoint: MigrationCheckpoint = {
|
|
48
|
+
checkpointId,
|
|
49
|
+
migrationId,
|
|
50
|
+
timestamp: Date.now(),
|
|
51
|
+
sourceDid: options.sourceDid,
|
|
52
|
+
sourceLayer,
|
|
53
|
+
didDocument,
|
|
54
|
+
credentials: [], // Would be populated by querying credential store
|
|
55
|
+
storageReferences: {}, // Would be populated by querying storage adapter
|
|
56
|
+
lifecycleState: {}, // Would be populated by querying lifecycle manager
|
|
57
|
+
ownershipProofs: [], // Would be populated if available
|
|
58
|
+
metadata: options.metadata || {}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Store checkpoint
|
|
62
|
+
await this.storage.save(checkpoint);
|
|
63
|
+
|
|
64
|
+
return checkpoint;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new Error(`Failed to create checkpoint: ${error instanceof Error ? error.message : String(error)}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Retrieve a checkpoint by ID
|
|
72
|
+
*/
|
|
73
|
+
async getCheckpoint(checkpointId: string): Promise<MigrationCheckpoint | null> {
|
|
74
|
+
try {
|
|
75
|
+
return await this.storage.get(checkpointId);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(`Error retrieving checkpoint ${checkpointId}:`, error);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Delete a checkpoint (after successful migration or cleanup)
|
|
84
|
+
*/
|
|
85
|
+
async deleteCheckpoint(checkpointId: string): Promise<void> {
|
|
86
|
+
try {
|
|
87
|
+
await this.storage.delete(checkpointId);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(`Error deleting checkpoint ${checkpointId}:`, error);
|
|
90
|
+
// Don't throw - deletion failures shouldn't break migrations
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Clean up old checkpoints (older than 24 hours for successful migrations)
|
|
96
|
+
*/
|
|
97
|
+
async cleanupOldCheckpoints(): Promise<void> {
|
|
98
|
+
try {
|
|
99
|
+
const cutoffTime = Date.now() - (24 * 60 * 60 * 1000); // 24 hours
|
|
100
|
+
await this.storage.deleteOlderThan(cutoffTime);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('Error cleaning up old checkpoints:', error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private extractLayer(did: string): 'peer' | 'webvh' | 'btco' | null {
|
|
107
|
+
if (did.startsWith('did:peer:')) return 'peer';
|
|
108
|
+
if (did.startsWith('did:webvh:')) return 'webvh';
|
|
109
|
+
if (did.startsWith('did:btco:')) return 'btco';
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CheckpointStorage - Persists checkpoints to storage
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { MigrationCheckpoint } from '../types';
|
|
6
|
+
import { OriginalsConfig } from '../../types';
|
|
7
|
+
|
|
8
|
+
export class CheckpointStorage {
|
|
9
|
+
private checkpoints: Map<string, MigrationCheckpoint>;
|
|
10
|
+
|
|
11
|
+
constructor(private config: OriginalsConfig) {
|
|
12
|
+
this.checkpoints = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Save a checkpoint
|
|
17
|
+
*/
|
|
18
|
+
async save(checkpoint: MigrationCheckpoint): Promise<void> {
|
|
19
|
+
if (!checkpoint.checkpointId) {
|
|
20
|
+
throw new Error('Checkpoint must have an ID');
|
|
21
|
+
}
|
|
22
|
+
this.checkpoints.set(checkpoint.checkpointId, checkpoint);
|
|
23
|
+
|
|
24
|
+
// Optionally persist to configured storage adapter
|
|
25
|
+
const storageAdapter = (this.config as any).storageAdapter;
|
|
26
|
+
if (storageAdapter && typeof storageAdapter.put === 'function') {
|
|
27
|
+
try {
|
|
28
|
+
const data = JSON.stringify(checkpoint);
|
|
29
|
+
const key = `checkpoints/${checkpoint.checkpointId}.json`;
|
|
30
|
+
await storageAdapter.put(key, Buffer.from(data), { contentType: 'application/json' });
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Failed to persist checkpoint to storage:', error);
|
|
33
|
+
// Continue - in-memory checkpoint is still available
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Retrieve a checkpoint
|
|
40
|
+
*/
|
|
41
|
+
async get(checkpointId: string): Promise<MigrationCheckpoint | null> {
|
|
42
|
+
// Try in-memory first
|
|
43
|
+
const memoryCheckpoint = this.checkpoints.get(checkpointId);
|
|
44
|
+
if (memoryCheckpoint) {
|
|
45
|
+
return memoryCheckpoint;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Try loading from storage adapter
|
|
49
|
+
const storageAdapter = (this.config as any).storageAdapter;
|
|
50
|
+
if (storageAdapter && typeof storageAdapter.get === 'function') {
|
|
51
|
+
try {
|
|
52
|
+
const key = `checkpoints/${checkpointId}.json`;
|
|
53
|
+
const data = await storageAdapter.get(key);
|
|
54
|
+
if (data) {
|
|
55
|
+
const checkpoint = JSON.parse(data.toString());
|
|
56
|
+
this.checkpoints.set(checkpointId, checkpoint);
|
|
57
|
+
return checkpoint;
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
// Checkpoint not found in storage
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Delete a checkpoint
|
|
69
|
+
*/
|
|
70
|
+
async delete(checkpointId: string): Promise<void> {
|
|
71
|
+
this.checkpoints.delete(checkpointId);
|
|
72
|
+
|
|
73
|
+
// Also delete from storage adapter
|
|
74
|
+
const storageAdapter = (this.config as any).storageAdapter;
|
|
75
|
+
if (storageAdapter && typeof storageAdapter.delete === 'function') {
|
|
76
|
+
try {
|
|
77
|
+
const key = `checkpoints/${checkpointId}.json`;
|
|
78
|
+
await storageAdapter.delete(key);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
// Ignore deletion errors
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Delete checkpoints older than specified timestamp
|
|
87
|
+
*/
|
|
88
|
+
async deleteOlderThan(cutoffTime: number): Promise<void> {
|
|
89
|
+
const toDelete: string[] = [];
|
|
90
|
+
|
|
91
|
+
for (const [id, checkpoint] of this.checkpoints.entries()) {
|
|
92
|
+
if (checkpoint.timestamp < cutoffTime) {
|
|
93
|
+
toDelete.push(id);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const id of toDelete) {
|
|
98
|
+
await this.delete(id);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration module exports
|
|
3
|
+
* Provides DID layer migration capabilities with validation, checkpoints, and rollbacks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { MigrationManager } from './MigrationManager';
|
|
7
|
+
export * from './types';
|
|
8
|
+
|
|
9
|
+
// Validators
|
|
10
|
+
export { ValidationPipeline } from './validation/ValidationPipeline';
|
|
11
|
+
export { DIDCompatibilityValidator } from './validation/DIDCompatibilityValidator';
|
|
12
|
+
export { CredentialValidator } from './validation/CredentialValidator';
|
|
13
|
+
export { StorageValidator } from './validation/StorageValidator';
|
|
14
|
+
export { LifecycleValidator } from './validation/LifecycleValidator';
|
|
15
|
+
export { BitcoinValidator } from './validation/BitcoinValidator';
|
|
16
|
+
|
|
17
|
+
// Checkpoint and Rollback
|
|
18
|
+
export { CheckpointManager } from './checkpoint/CheckpointManager';
|
|
19
|
+
export { CheckpointStorage } from './checkpoint/CheckpointStorage';
|
|
20
|
+
export { RollbackManager } from './rollback/RollbackManager';
|
|
21
|
+
|
|
22
|
+
// State Management
|
|
23
|
+
export { StateTracker } from './state/StateTracker';
|
|
24
|
+
export { StateMachine } from './state/StateMachine';
|
|
25
|
+
|
|
26
|
+
// Audit
|
|
27
|
+
export { AuditLogger } from './audit/AuditLogger';
|
|
28
|
+
|
|
29
|
+
// Operations
|
|
30
|
+
export { BaseMigration } from './operations/BaseMigration';
|
|
31
|
+
export { PeerToWebvhMigration } from './operations/PeerToWebvhMigration';
|
|
32
|
+
export { WebvhToBtcoMigration } from './operations/WebvhToBtcoMigration';
|
|
33
|
+
export { PeerToBtcoMigration } from './operations/PeerToBtcoMigration';
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseMigration - Base class for all migration operations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
MigrationOptions,
|
|
7
|
+
MigrationResult,
|
|
8
|
+
MigrationError,
|
|
9
|
+
MigrationErrorType,
|
|
10
|
+
MigrationStateEnum,
|
|
11
|
+
CostEstimate
|
|
12
|
+
} from '../types';
|
|
13
|
+
import { OriginalsConfig, DIDDocument } from '../../types';
|
|
14
|
+
import { DIDManager } from '../../did/DIDManager';
|
|
15
|
+
import { CredentialManager } from '../../vc/CredentialManager';
|
|
16
|
+
import { StateTracker } from '../state/StateTracker';
|
|
17
|
+
import { EventEmitter } from '../../events/EventEmitter';
|
|
18
|
+
|
|
19
|
+
export abstract class BaseMigration {
|
|
20
|
+
protected eventEmitter: EventEmitter;
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
protected config: OriginalsConfig,
|
|
24
|
+
protected didManager: DIDManager,
|
|
25
|
+
protected credentialManager: CredentialManager,
|
|
26
|
+
protected stateTracker: StateTracker
|
|
27
|
+
) {
|
|
28
|
+
this.eventEmitter = new EventEmitter();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Execute the migration (to be implemented by subclasses)
|
|
33
|
+
*/
|
|
34
|
+
abstract executeMigration(
|
|
35
|
+
options: MigrationOptions,
|
|
36
|
+
migrationId: string
|
|
37
|
+
): Promise<{ targetDid: string; didDocument: DIDDocument }>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get estimated cost (to be implemented by subclasses)
|
|
41
|
+
*/
|
|
42
|
+
abstract estimateCost(options: MigrationOptions): Promise<CostEstimate>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Emit migration event
|
|
46
|
+
*/
|
|
47
|
+
protected async emitEvent(type: string, data: any): Promise<void> {
|
|
48
|
+
try {
|
|
49
|
+
await this.eventEmitter.emit({
|
|
50
|
+
type,
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
...data
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error(`Error emitting event ${type}:`, error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create migration error
|
|
61
|
+
*/
|
|
62
|
+
protected createError(
|
|
63
|
+
type: MigrationErrorType,
|
|
64
|
+
code: string,
|
|
65
|
+
message: string,
|
|
66
|
+
migrationId?: string,
|
|
67
|
+
details?: any
|
|
68
|
+
): MigrationError {
|
|
69
|
+
return {
|
|
70
|
+
type,
|
|
71
|
+
code,
|
|
72
|
+
message,
|
|
73
|
+
technicalDetails: details ? JSON.stringify(details) : undefined,
|
|
74
|
+
migrationId,
|
|
75
|
+
timestamp: Date.now()
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Update migration state with error handling
|
|
81
|
+
*/
|
|
82
|
+
protected async updateStateWithRetry(
|
|
83
|
+
migrationId: string,
|
|
84
|
+
updates: any,
|
|
85
|
+
maxRetries: number = 3
|
|
86
|
+
): Promise<void> {
|
|
87
|
+
let lastError: Error | null = null;
|
|
88
|
+
|
|
89
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
90
|
+
try {
|
|
91
|
+
await this.stateTracker.updateState(migrationId, updates);
|
|
92
|
+
return;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
95
|
+
|
|
96
|
+
if (attempt < maxRetries - 1) {
|
|
97
|
+
// Exponential backoff: 100ms, 200ms, 400ms
|
|
98
|
+
await new Promise(resolve => setTimeout(resolve, 100 * Math.pow(2, attempt)));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw lastError || new Error('Failed to update state after retries');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Resolve source DID document
|
|
108
|
+
*/
|
|
109
|
+
protected async resolveSourceDid(sourceDid: string): Promise<DIDDocument> {
|
|
110
|
+
const didDocument = await this.didManager.resolveDID(sourceDid);
|
|
111
|
+
if (!didDocument) {
|
|
112
|
+
throw new Error(`Could not resolve source DID: ${sourceDid}`);
|
|
113
|
+
}
|
|
114
|
+
return didDocument;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Extract layer from DID
|
|
119
|
+
*/
|
|
120
|
+
protected extractLayer(did: string): 'peer' | 'webvh' | 'btco' {
|
|
121
|
+
if (did.startsWith('did:peer:')) return 'peer';
|
|
122
|
+
if (did.startsWith('did:webvh:')) return 'webvh';
|
|
123
|
+
if (did.startsWith('did:btco:')) return 'btco';
|
|
124
|
+
throw new Error(`Unsupported DID method: ${did}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PeerToBtcoMigration - Handles direct migration from did:peer to did:btco
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
MigrationOptions,
|
|
7
|
+
CostEstimate,
|
|
8
|
+
MigrationStateEnum
|
|
9
|
+
} from '../types';
|
|
10
|
+
import { DIDDocument, OriginalsConfig } from '../../types';
|
|
11
|
+
import { BaseMigration } from './BaseMigration';
|
|
12
|
+
import { BitcoinManager } from '../../bitcoin/BitcoinManager';
|
|
13
|
+
import { DIDManager } from '../../did/DIDManager';
|
|
14
|
+
import { CredentialManager } from '../../vc/CredentialManager';
|
|
15
|
+
import { StateTracker } from '../state/StateTracker';
|
|
16
|
+
|
|
17
|
+
export class PeerToBtcoMigration extends BaseMigration {
|
|
18
|
+
private bitcoinManager: BitcoinManager;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
config: OriginalsConfig,
|
|
22
|
+
didManager: DIDManager,
|
|
23
|
+
credentialManager: CredentialManager,
|
|
24
|
+
stateTracker: StateTracker,
|
|
25
|
+
bitcoinManager: BitcoinManager
|
|
26
|
+
) {
|
|
27
|
+
super(config, didManager, credentialManager, stateTracker);
|
|
28
|
+
this.bitcoinManager = bitcoinManager;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Execute peer → btco migration (direct, skipping webvh layer)
|
|
33
|
+
*/
|
|
34
|
+
async executeMigration(
|
|
35
|
+
options: MigrationOptions,
|
|
36
|
+
migrationId: string
|
|
37
|
+
): Promise<{ targetDid: string; didDocument: DIDDocument }> {
|
|
38
|
+
// Resolve source DID
|
|
39
|
+
const sourceDid = await this.resolveSourceDid(options.sourceDid);
|
|
40
|
+
|
|
41
|
+
await this.updateStateWithRetry(migrationId, {
|
|
42
|
+
state: MigrationStateEnum.IN_PROGRESS,
|
|
43
|
+
currentOperation: 'Creating Bitcoin inscription',
|
|
44
|
+
progress: 30
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Create Bitcoin inscription with DID document
|
|
48
|
+
const manifest = {
|
|
49
|
+
didDocument: sourceDid,
|
|
50
|
+
migrationId,
|
|
51
|
+
timestamp: new Date().toISOString()
|
|
52
|
+
};
|
|
53
|
+
const payload = Buffer.from(JSON.stringify(manifest));
|
|
54
|
+
|
|
55
|
+
await this.updateStateWithRetry(migrationId, {
|
|
56
|
+
state: MigrationStateEnum.ANCHORING,
|
|
57
|
+
currentOperation: 'Anchoring to Bitcoin',
|
|
58
|
+
progress: 50
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const inscription = await this.bitcoinManager.inscribeData(
|
|
62
|
+
payload,
|
|
63
|
+
'application/json',
|
|
64
|
+
options.feeRate
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Use satoshi identifier or inscription ID
|
|
68
|
+
const satoshiId = inscription.satoshi || inscription.inscriptionId.split('i')[0];
|
|
69
|
+
|
|
70
|
+
await this.updateStateWithRetry(migrationId, {
|
|
71
|
+
currentOperation: 'Creating btco DID document',
|
|
72
|
+
progress: 80
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Migrate DID document to btco
|
|
76
|
+
const migratedDoc = await this.didManager.migrateToDIDBTCO(sourceDid, satoshiId);
|
|
77
|
+
|
|
78
|
+
await this.updateStateWithRetry(migrationId, {
|
|
79
|
+
currentOperation: 'Migration completed',
|
|
80
|
+
progress: 100,
|
|
81
|
+
targetDid: migratedDoc.id
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
targetDid: migratedDoc.id,
|
|
86
|
+
didDocument: migratedDoc
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Estimate cost for peer → btco migration
|
|
92
|
+
*/
|
|
93
|
+
async estimateCost(options: MigrationOptions): Promise<CostEstimate> {
|
|
94
|
+
const feeRate = options.feeRate || 10; // default 10 sat/vB
|
|
95
|
+
const estimatedSize = 1024; // ~1KB for typical DID document
|
|
96
|
+
const networkFees = estimatedSize * feeRate;
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
storageCost: 0,
|
|
100
|
+
networkFees,
|
|
101
|
+
totalCost: networkFees,
|
|
102
|
+
currency: 'sats'
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|