@weave_protocol/domere 1.0.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/PLANNING.md +231 -0
- package/README.md +50 -0
- package/dist/anchoring/ethereum.d.ts +135 -0
- package/dist/anchoring/ethereum.d.ts.map +1 -0
- package/dist/anchoring/ethereum.js +474 -0
- package/dist/anchoring/ethereum.js.map +1 -0
- package/dist/anchoring/index.d.ts +93 -0
- package/dist/anchoring/index.d.ts.map +1 -0
- package/dist/anchoring/index.js +184 -0
- package/dist/anchoring/index.js.map +1 -0
- package/dist/anchoring/merkle.d.ts +91 -0
- package/dist/anchoring/merkle.d.ts.map +1 -0
- package/dist/anchoring/merkle.js +203 -0
- package/dist/anchoring/merkle.js.map +1 -0
- package/dist/anchoring/solana.d.ts +85 -0
- package/dist/anchoring/solana.d.ts.map +1 -0
- package/dist/anchoring/solana.js +301 -0
- package/dist/anchoring/solana.js.map +1 -0
- package/dist/constants.d.ts +130 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +536 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/language/code-analyzer.d.ts +80 -0
- package/dist/language/code-analyzer.d.ts.map +1 -0
- package/dist/language/code-analyzer.js +489 -0
- package/dist/language/code-analyzer.js.map +1 -0
- package/dist/language/detector.d.ts +53 -0
- package/dist/language/detector.d.ts.map +1 -0
- package/dist/language/detector.js +248 -0
- package/dist/language/detector.js.map +1 -0
- package/dist/language/index.d.ts +61 -0
- package/dist/language/index.d.ts.map +1 -0
- package/dist/language/index.js +109 -0
- package/dist/language/index.js.map +1 -0
- package/dist/language/nl-analyzer.d.ts +59 -0
- package/dist/language/nl-analyzer.d.ts.map +1 -0
- package/dist/language/nl-analyzer.js +350 -0
- package/dist/language/nl-analyzer.js.map +1 -0
- package/dist/language/semantic.d.ts +48 -0
- package/dist/language/semantic.d.ts.map +1 -0
- package/dist/language/semantic.js +329 -0
- package/dist/language/semantic.js.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/memory.d.ts +48 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +211 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/thread/drift.d.ts +43 -0
- package/dist/thread/drift.d.ts.map +1 -0
- package/dist/thread/drift.js +248 -0
- package/dist/thread/drift.js.map +1 -0
- package/dist/thread/index.d.ts +9 -0
- package/dist/thread/index.d.ts.map +1 -0
- package/dist/thread/index.js +9 -0
- package/dist/thread/index.js.map +1 -0
- package/dist/thread/intent.d.ts +68 -0
- package/dist/thread/intent.d.ts.map +1 -0
- package/dist/thread/intent.js +333 -0
- package/dist/thread/intent.js.map +1 -0
- package/dist/thread/manager.d.ts +85 -0
- package/dist/thread/manager.d.ts.map +1 -0
- package/dist/thread/manager.js +305 -0
- package/dist/thread/manager.js.map +1 -0
- package/dist/thread/weave.d.ts +61 -0
- package/dist/thread/weave.d.ts.map +1 -0
- package/dist/thread/weave.js +158 -0
- package/dist/thread/weave.js.map +1 -0
- package/dist/tools/index.d.ts +18 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +102 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types.d.ts +466 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +48 -0
- package/dist/types.js.map +1 -0
- package/package.json +24 -0
- package/src/anchoring/ethereum.ts +568 -0
- package/src/anchoring/index.ts +236 -0
- package/src/anchoring/merkle.ts +256 -0
- package/src/anchoring/solana.ts +370 -0
- package/src/constants.ts +566 -0
- package/src/index.ts +43 -0
- package/src/language/code-analyzer.ts +564 -0
- package/src/language/detector.ts +297 -0
- package/src/language/index.ts +129 -0
- package/src/language/nl-analyzer.ts +411 -0
- package/src/language/semantic.ts +385 -0
- package/src/storage/index.ts +6 -0
- package/src/storage/memory.ts +271 -0
- package/src/thread/drift.ts +319 -0
- package/src/thread/index.ts +9 -0
- package/src/thread/intent.ts +409 -0
- package/src/thread/manager.ts +414 -0
- package/src/thread/weave.ts +205 -0
- package/src/tools/index.ts +107 -0
- package/src/types.ts +736 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dōmere - The Judge Protocol
|
|
3
|
+
* Anchoring Module
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { MerkleTree, BatchAnchor } from './merkle.js';
|
|
7
|
+
export { SolanaAnchorClient, SOLANA_PROGRAM_IDL } from './solana.js';
|
|
8
|
+
export { EthereumAnchorClient, ETHEREUM_CONTRACT_ABI, ETHEREUM_CONTRACT_SOURCE } from './ethereum.js';
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
AnchorRequest,
|
|
12
|
+
AnchorResult,
|
|
13
|
+
AnchorVerification,
|
|
14
|
+
BlockchainNetwork,
|
|
15
|
+
Thread,
|
|
16
|
+
} from '../types.js';
|
|
17
|
+
import { AnchoringError } from '../types.js';
|
|
18
|
+
import { SolanaAnchorClient } from './solana.js';
|
|
19
|
+
import { EthereumAnchorClient } from './ethereum.js';
|
|
20
|
+
import { MerkleTree, BatchAnchor } from './merkle.js';
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Unified Anchoring Service
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
export class AnchoringService {
|
|
27
|
+
private solana: SolanaAnchorClient;
|
|
28
|
+
private ethereum: EthereumAnchorClient;
|
|
29
|
+
private pendingBatch: BatchAnchor | null = null;
|
|
30
|
+
|
|
31
|
+
constructor(config?: {
|
|
32
|
+
solana?: { rpc_url?: string; program_id?: string };
|
|
33
|
+
ethereum?: { rpc_url?: string; contract_address?: string };
|
|
34
|
+
}) {
|
|
35
|
+
this.solana = new SolanaAnchorClient(config?.solana);
|
|
36
|
+
this.ethereum = new EthereumAnchorClient(config?.ethereum);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Prepare thread for anchoring
|
|
41
|
+
*/
|
|
42
|
+
prepareThreadAnchor(thread: Thread): AnchorRequest {
|
|
43
|
+
// Build Merkle tree from hop signatures
|
|
44
|
+
const hopSignatures = thread.hops.map(hop => hop.hop_signature);
|
|
45
|
+
const tree = new MerkleTree(hopSignatures.length > 0 ? hopSignatures : ['empty']);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
thread_id: thread.id,
|
|
49
|
+
merkle_root: tree.getRootHex(),
|
|
50
|
+
hop_count: thread.hops.length,
|
|
51
|
+
intent_hash: thread.intent.hash,
|
|
52
|
+
compliant: thread.status !== 'violated',
|
|
53
|
+
network: 'solana', // Default to Solana
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Estimate anchoring cost
|
|
59
|
+
*/
|
|
60
|
+
async estimateCost(network: BlockchainNetwork): Promise<{
|
|
61
|
+
network: BlockchainNetwork;
|
|
62
|
+
network_fee: string;
|
|
63
|
+
protocol_fee: string;
|
|
64
|
+
total: string;
|
|
65
|
+
currency: string;
|
|
66
|
+
}> {
|
|
67
|
+
if (network === 'solana' || network === 'solana-devnet') {
|
|
68
|
+
const cost = await this.solana.estimateCost();
|
|
69
|
+
return {
|
|
70
|
+
network,
|
|
71
|
+
network_fee: cost.network_fee_sol,
|
|
72
|
+
protocol_fee: cost.protocol_fee_sol,
|
|
73
|
+
total: cost.total_sol,
|
|
74
|
+
currency: 'SOL',
|
|
75
|
+
};
|
|
76
|
+
} else {
|
|
77
|
+
const cost = await this.ethereum.estimateGas();
|
|
78
|
+
return {
|
|
79
|
+
network,
|
|
80
|
+
network_fee: cost.estimated_eth,
|
|
81
|
+
protocol_fee: cost.protocol_fee_eth,
|
|
82
|
+
total: cost.total_eth,
|
|
83
|
+
currency: 'ETH',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create anchor transaction (unsigned)
|
|
90
|
+
*/
|
|
91
|
+
async createAnchorTransaction(request: AnchorRequest): Promise<{
|
|
92
|
+
network: BlockchainNetwork;
|
|
93
|
+
transaction_data: unknown;
|
|
94
|
+
estimated_cost: unknown;
|
|
95
|
+
instructions: string;
|
|
96
|
+
}> {
|
|
97
|
+
if (request.network === 'solana' || request.network === 'solana-devnet') {
|
|
98
|
+
const tx = await this.solana.createAnchorTransaction(request);
|
|
99
|
+
return {
|
|
100
|
+
network: request.network,
|
|
101
|
+
transaction_data: tx.unsigned_transaction,
|
|
102
|
+
estimated_cost: tx.estimated_cost,
|
|
103
|
+
instructions: `Sign this transaction with your Solana wallet to anchor thread ${request.thread_id}`,
|
|
104
|
+
};
|
|
105
|
+
} else {
|
|
106
|
+
const tx = await this.ethereum.createAnchorTransaction(request);
|
|
107
|
+
return {
|
|
108
|
+
network: request.network,
|
|
109
|
+
transaction_data: tx,
|
|
110
|
+
estimated_cost: tx.estimated_cost,
|
|
111
|
+
instructions: `Sign this transaction with your Ethereum wallet to anchor thread ${request.thread_id}`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Submit signed transaction
|
|
118
|
+
*/
|
|
119
|
+
async submitSignedTransaction(
|
|
120
|
+
network: BlockchainNetwork,
|
|
121
|
+
signedTransaction: string
|
|
122
|
+
): Promise<AnchorResult> {
|
|
123
|
+
if (network === 'solana' || network === 'solana-devnet') {
|
|
124
|
+
return this.solana.submitSignedTransaction(signedTransaction);
|
|
125
|
+
} else {
|
|
126
|
+
return this.ethereum.submitSignedTransaction(signedTransaction);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Verify anchor on-chain
|
|
132
|
+
*/
|
|
133
|
+
async verifyAnchor(
|
|
134
|
+
network: BlockchainNetwork,
|
|
135
|
+
threadId: string,
|
|
136
|
+
expectedMerkleRoot: string
|
|
137
|
+
): Promise<AnchorVerification> {
|
|
138
|
+
if (network === 'solana' || network === 'solana-devnet') {
|
|
139
|
+
return this.solana.verifyAnchor(threadId, expectedMerkleRoot);
|
|
140
|
+
} else {
|
|
141
|
+
return this.ethereum.verifyAnchor(threadId, expectedMerkleRoot);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Start a batch for efficient anchoring
|
|
147
|
+
*/
|
|
148
|
+
startBatch(): void {
|
|
149
|
+
if (this.pendingBatch) {
|
|
150
|
+
throw new AnchoringError('Batch already in progress');
|
|
151
|
+
}
|
|
152
|
+
this.pendingBatch = new BatchAnchor();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Add thread to batch
|
|
157
|
+
*/
|
|
158
|
+
addToBatch(thread: Thread): void {
|
|
159
|
+
if (!this.pendingBatch) {
|
|
160
|
+
throw new AnchoringError('No batch in progress');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const hopSignatures = thread.hops.map(hop => hop.hop_signature);
|
|
164
|
+
const tree = new MerkleTree(hopSignatures.length > 0 ? hopSignatures : ['empty']);
|
|
165
|
+
|
|
166
|
+
this.pendingBatch.add(thread.id, JSON.stringify({
|
|
167
|
+
thread_id: thread.id,
|
|
168
|
+
merkle_root: tree.getRootHex(),
|
|
169
|
+
intent_hash: thread.intent.hash,
|
|
170
|
+
compliant: thread.status !== 'violated',
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Finalize batch and get root
|
|
176
|
+
*/
|
|
177
|
+
finalizeBatch(): {
|
|
178
|
+
root: string;
|
|
179
|
+
count: number;
|
|
180
|
+
thread_ids: string[];
|
|
181
|
+
} {
|
|
182
|
+
if (!this.pendingBatch) {
|
|
183
|
+
throw new AnchoringError('No batch in progress');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const root = this.pendingBatch.finalize();
|
|
187
|
+
const threadIds = this.pendingBatch.getItemIds();
|
|
188
|
+
const count = this.pendingBatch.getCount();
|
|
189
|
+
|
|
190
|
+
return { root, count, thread_ids: threadIds };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get proof for specific thread in batch
|
|
195
|
+
*/
|
|
196
|
+
getBatchProof(threadId: string): {
|
|
197
|
+
proof: string[];
|
|
198
|
+
positions: number[];
|
|
199
|
+
root: string;
|
|
200
|
+
} | null {
|
|
201
|
+
if (!this.pendingBatch) {
|
|
202
|
+
throw new AnchoringError('No batch in progress');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return this.pendingBatch.getProof(threadId);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Clear batch
|
|
210
|
+
*/
|
|
211
|
+
clearBatch(): void {
|
|
212
|
+
this.pendingBatch = null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get explorer URL for transaction
|
|
217
|
+
*/
|
|
218
|
+
getExplorerUrl(network: BlockchainNetwork, txHash: string): string {
|
|
219
|
+
if (network === 'solana' || network === 'solana-devnet') {
|
|
220
|
+
return this.solana.getExplorerUrl(txHash);
|
|
221
|
+
} else {
|
|
222
|
+
return this.ethereum.getExplorerUrl(txHash);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get client for direct access
|
|
228
|
+
*/
|
|
229
|
+
getClient(network: BlockchainNetwork): SolanaAnchorClient | EthereumAnchorClient {
|
|
230
|
+
if (network === 'solana' || network === 'solana-devnet') {
|
|
231
|
+
return this.solana;
|
|
232
|
+
} else {
|
|
233
|
+
return this.ethereum;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dōmere - The Judge Protocol
|
|
3
|
+
* Merkle Tree Utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as crypto from 'crypto';
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Merkle Tree
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
export class MerkleTree {
|
|
13
|
+
private leaves: Buffer[];
|
|
14
|
+
private layers: Buffer[][];
|
|
15
|
+
private root: Buffer | null = null;
|
|
16
|
+
|
|
17
|
+
constructor(leaves: (string | Buffer)[]) {
|
|
18
|
+
this.leaves = leaves.map(leaf =>
|
|
19
|
+
typeof leaf === 'string' ? this.hashLeaf(leaf) : leaf
|
|
20
|
+
);
|
|
21
|
+
this.layers = [this.leaves];
|
|
22
|
+
this.buildTree();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build the Merkle tree
|
|
27
|
+
*/
|
|
28
|
+
private buildTree(): void {
|
|
29
|
+
let currentLayer = this.leaves;
|
|
30
|
+
|
|
31
|
+
while (currentLayer.length > 1) {
|
|
32
|
+
const newLayer: Buffer[] = [];
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < currentLayer.length; i += 2) {
|
|
35
|
+
if (i + 1 < currentLayer.length) {
|
|
36
|
+
// Sort pair for consistent ordering
|
|
37
|
+
const [left, right] = this.sortPair(currentLayer[i], currentLayer[i + 1]);
|
|
38
|
+
newLayer.push(this.hashPair(left, right));
|
|
39
|
+
} else {
|
|
40
|
+
// Promote odd node
|
|
41
|
+
newLayer.push(currentLayer[i]);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.layers.push(newLayer);
|
|
46
|
+
currentLayer = newLayer;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.root = currentLayer.length > 0 ? currentLayer[0] : this.hashLeaf('empty');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get the Merkle root
|
|
54
|
+
*/
|
|
55
|
+
getRoot(): Buffer {
|
|
56
|
+
return this.root!;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get root as hex string
|
|
61
|
+
*/
|
|
62
|
+
getRootHex(): string {
|
|
63
|
+
return this.root!.toString('hex');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get root as 32-byte array (for blockchain)
|
|
68
|
+
*/
|
|
69
|
+
getRootBytes32(): number[] {
|
|
70
|
+
return Array.from(this.root!);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get proof for a leaf at given index
|
|
75
|
+
*/
|
|
76
|
+
getProof(index: number): {
|
|
77
|
+
proof: Buffer[];
|
|
78
|
+
proofHex: string[];
|
|
79
|
+
positions: number[]; // 0 = left, 1 = right
|
|
80
|
+
} {
|
|
81
|
+
if (index < 0 || index >= this.leaves.length) {
|
|
82
|
+
throw new Error('Index out of bounds');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const proof: Buffer[] = [];
|
|
86
|
+
const positions: number[] = [];
|
|
87
|
+
let currentIndex = index;
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < this.layers.length - 1; i++) {
|
|
90
|
+
const layer = this.layers[i];
|
|
91
|
+
const isRight = currentIndex % 2 === 1;
|
|
92
|
+
const siblingIndex = isRight ? currentIndex - 1 : currentIndex + 1;
|
|
93
|
+
|
|
94
|
+
if (siblingIndex < layer.length) {
|
|
95
|
+
proof.push(layer[siblingIndex]);
|
|
96
|
+
positions.push(isRight ? 0 : 1); // Sibling position
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
currentIndex = Math.floor(currentIndex / 2);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
proof,
|
|
104
|
+
proofHex: proof.map(p => p.toString('hex')),
|
|
105
|
+
positions,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Verify a proof
|
|
111
|
+
*/
|
|
112
|
+
static verify(
|
|
113
|
+
leaf: string | Buffer,
|
|
114
|
+
proof: Buffer[],
|
|
115
|
+
positions: number[],
|
|
116
|
+
root: Buffer
|
|
117
|
+
): boolean {
|
|
118
|
+
let current = typeof leaf === 'string'
|
|
119
|
+
? crypto.createHash('sha256').update(leaf).digest()
|
|
120
|
+
: leaf;
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < proof.length; i++) {
|
|
123
|
+
const [left, right] = positions[i] === 0
|
|
124
|
+
? [proof[i], current]
|
|
125
|
+
: [current, proof[i]];
|
|
126
|
+
|
|
127
|
+
current = crypto.createHash('sha256')
|
|
128
|
+
.update(Buffer.concat([left, right]))
|
|
129
|
+
.digest();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return current.equals(root);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get all layers (for debugging)
|
|
137
|
+
*/
|
|
138
|
+
getLayers(): Buffer[][] {
|
|
139
|
+
return this.layers;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get leaf count
|
|
144
|
+
*/
|
|
145
|
+
getLeafCount(): number {
|
|
146
|
+
return this.leaves.length;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Hash a leaf
|
|
151
|
+
*/
|
|
152
|
+
private hashLeaf(data: string): Buffer {
|
|
153
|
+
return crypto.createHash('sha256').update(data).digest();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Hash a pair of nodes
|
|
158
|
+
*/
|
|
159
|
+
private hashPair(left: Buffer, right: Buffer): Buffer {
|
|
160
|
+
return crypto.createHash('sha256')
|
|
161
|
+
.update(Buffer.concat([left, right]))
|
|
162
|
+
.digest();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Sort pair for consistent ordering
|
|
167
|
+
*/
|
|
168
|
+
private sortPair(a: Buffer, b: Buffer): [Buffer, Buffer] {
|
|
169
|
+
return Buffer.compare(a, b) <= 0 ? [a, b] : [b, a];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ============================================================================
|
|
174
|
+
// Batch Anchoring
|
|
175
|
+
// ============================================================================
|
|
176
|
+
|
|
177
|
+
export class BatchAnchor {
|
|
178
|
+
private items: Map<string, { data: string; index: number }> = new Map();
|
|
179
|
+
private tree: MerkleTree | null = null;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Add item to batch
|
|
183
|
+
*/
|
|
184
|
+
add(id: string, data: string): void {
|
|
185
|
+
if (this.tree) {
|
|
186
|
+
throw new Error('Batch already finalized');
|
|
187
|
+
}
|
|
188
|
+
this.items.set(id, { data, index: this.items.size });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Finalize batch and build tree
|
|
193
|
+
*/
|
|
194
|
+
finalize(): string {
|
|
195
|
+
if (this.items.size === 0) {
|
|
196
|
+
throw new Error('Cannot finalize empty batch');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const leaves = Array.from(this.items.values())
|
|
200
|
+
.sort((a, b) => a.index - b.index)
|
|
201
|
+
.map(item => item.data);
|
|
202
|
+
|
|
203
|
+
this.tree = new MerkleTree(leaves);
|
|
204
|
+
return this.tree.getRootHex();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get proof for specific item
|
|
209
|
+
*/
|
|
210
|
+
getProof(id: string): {
|
|
211
|
+
proof: string[];
|
|
212
|
+
positions: number[];
|
|
213
|
+
root: string;
|
|
214
|
+
} | null {
|
|
215
|
+
if (!this.tree) {
|
|
216
|
+
throw new Error('Batch not finalized');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const item = this.items.get(id);
|
|
220
|
+
if (!item) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const { proofHex, positions } = this.tree.getProof(item.index);
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
proof: proofHex,
|
|
228
|
+
positions,
|
|
229
|
+
root: this.tree.getRootHex(),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get root
|
|
235
|
+
*/
|
|
236
|
+
getRoot(): string {
|
|
237
|
+
if (!this.tree) {
|
|
238
|
+
throw new Error('Batch not finalized');
|
|
239
|
+
}
|
|
240
|
+
return this.tree.getRootHex();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get item count
|
|
245
|
+
*/
|
|
246
|
+
getCount(): number {
|
|
247
|
+
return this.items.size;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get all item IDs
|
|
252
|
+
*/
|
|
253
|
+
getItemIds(): string[] {
|
|
254
|
+
return Array.from(this.items.keys());
|
|
255
|
+
}
|
|
256
|
+
}
|