@blindmarket/sdk 0.1.8 → 0.5.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/executor/WorkerRuntime.d.ts +21 -0
- package/dist/executor/WorkerRuntime.js +56 -27
- package/dist/index.d.ts +75 -9
- package/dist/index.js +54 -5
- package/dist/tools/helpers.js +14 -5
- package/dist/types.d.ts +7 -2
- package/package.json +8 -1
- package/biome.json +0 -19
- package/examples/agent-demo.ts +0 -121
- package/scripts/gen-crypto-fixtures.mjs +0 -72
- package/src/agent/Agent.ts +0 -122
- package/src/agent/index.ts +0 -1
- package/src/api/index.ts +0 -1
- package/src/chain/BlindEscrowClient.ts +0 -196
- package/src/chain/BlindReputationClient.ts +0 -60
- package/src/chain/ChainClient.ts +0 -65
- package/src/chain/IBlindMarketChain.ts +0 -109
- package/src/chain/TaskRegistryClient.ts +0 -91
- package/src/chain/abi/BlindEscrow.json +0 -1113
- package/src/chain/abi/BlindReputation.json +0 -447
- package/src/chain/abi/TaskRegistry.json +0 -586
- package/src/chain/abi/index.ts +0 -5
- package/src/chain/createChainAdapter.ts +0 -40
- package/src/chain/domain-types.ts +0 -92
- package/src/chain/errors.ts +0 -43
- package/src/chain/evm/EVMChainAdapter.ts +0 -212
- package/src/chain/evm/index.ts +0 -1
- package/src/chain/index.ts +0 -12
- package/src/compute/index.ts +0 -1
- package/src/crypto/aes.ts +0 -104
- package/src/crypto/constants.ts +0 -7
- package/src/crypto/ecies.ts +0 -60
- package/src/crypto/envelope.ts +0 -131
- package/src/crypto/hkdf.ts +0 -45
- package/src/crypto/index.ts +0 -6
- package/src/crypto/keys.ts +0 -56
- package/src/errors/BlindMarketError.ts +0 -38
- package/src/errors/codes.ts +0 -67
- package/src/errors/index.ts +0 -3
- package/src/errors/subclasses.ts +0 -59
- package/src/events/EventBus.ts +0 -45
- package/src/events/index.ts +0 -2
- package/src/events/types.ts +0 -13
- package/src/executor/WorkerRuntime.ts +0 -338
- package/src/executor/index.ts +0 -8
- package/src/index.ts +0 -636
- package/src/keystore/InMemoryKeyStore.ts +0 -146
- package/src/keystore/KeyStore.ts +0 -19
- package/src/keystore/index.ts +0 -2
- package/src/network/index.ts +0 -3
- package/src/network/presets.ts +0 -66
- package/src/network/resolve.ts +0 -31
- package/src/network/types.ts +0 -20
- package/src/signer/EthersSigner.ts +0 -82
- package/src/signer/PrivateKeySigner.ts +0 -19
- package/src/signer/Signer.ts +0 -40
- package/src/signer/index.ts +0 -3
- package/src/storage/MemoryStorage.ts +0 -49
- package/src/storage/Storage.ts +0 -15
- package/src/storage/ZgStorage.ts +0 -124
- package/src/storage/index.ts +0 -3
- package/src/testing/index.ts +0 -1
- package/src/tools/adapters.ts +0 -177
- package/src/tools/helpers.ts +0 -199
- package/src/tools/index.ts +0 -59
- package/src/tools/types.ts +0 -47
- package/src/types.ts +0 -297
- package/src/verifier/index.ts +0 -1
- package/src/worker/Worker.ts +0 -98
- package/src/worker/index.ts +0 -1
- package/test/agent.test.ts +0 -193
- package/test/chain.test.ts +0 -145
- package/test/crypto.aes.test.ts +0 -81
- package/test/crypto.compat.test.ts +0 -33
- package/test/crypto.ecies.test.ts +0 -64
- package/test/envelope.test.ts +0 -82
- package/test/errors.test.ts +0 -64
- package/test/events.test.ts +0 -59
- package/test/fixtures/crypto.json +0 -16
- package/test/keystore.test.ts +0 -103
- package/test/network.test.ts +0 -53
- package/test/signer.test.ts +0 -66
- package/test/smoke.test.ts +0 -7
- package/test/storage.test.ts +0 -55
- package/test/worker.test.ts +0 -181
- package/tsconfig.build.json +0 -1
- package/tsconfig.json +0 -14
- package/tsup.config.ts +0 -32
- package/vitest.config.ts +0 -17
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import type { Address, Hex, TaskId, TaskStatus } from '../types.js';
|
|
2
|
-
|
|
3
|
-
// ── Chain-agnostic domain types ───────────────────────────────────────────
|
|
4
|
-
// These types are the BlindMarket domain model, independent of any specific
|
|
5
|
-
// blockchain. Each chain adapter translates between these and its native
|
|
6
|
-
// contract types.
|
|
7
|
-
|
|
8
|
-
export type BlockchainType = 'evm';
|
|
9
|
-
|
|
10
|
-
/** Base fields shared by all chain network configs. */
|
|
11
|
-
export interface NetworkBase {
|
|
12
|
-
name: string;
|
|
13
|
-
chainType: BlockchainType;
|
|
14
|
-
rpc: string[];
|
|
15
|
-
explorer?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** EVM-compatible chain network config (0G, Ethereum, Polygon, etc.). */
|
|
19
|
-
export interface EVMNetwork extends NetworkBase {
|
|
20
|
-
chainType: 'evm';
|
|
21
|
-
chainId: number;
|
|
22
|
-
contracts: {
|
|
23
|
-
escrow: Address;
|
|
24
|
-
registry: Address;
|
|
25
|
-
reputation: Address;
|
|
26
|
-
usdc: Address;
|
|
27
|
-
};
|
|
28
|
-
indexer?: string;
|
|
29
|
-
broker?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Union of all supported chain network configs. */
|
|
33
|
-
export type Network = EVMNetwork;
|
|
34
|
-
|
|
35
|
-
/** Parameters for creating a task on any chain. */
|
|
36
|
-
export interface CreateTaskParams {
|
|
37
|
-
taskHash: Hex;
|
|
38
|
-
token: Address | string;
|
|
39
|
-
amount: bigint;
|
|
40
|
-
category: string;
|
|
41
|
-
locationZone: string;
|
|
42
|
-
deadline: bigint;
|
|
43
|
-
/** Per-task verifier address (agent-verification mode). Optional. */
|
|
44
|
-
verifierAgent?: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Decoded on-chain task representation (chain-agnostic). */
|
|
48
|
-
export interface DomainTask {
|
|
49
|
-
taskId: TaskId;
|
|
50
|
-
agent: string;
|
|
51
|
-
worker: string;
|
|
52
|
-
token: string;
|
|
53
|
-
amount: bigint;
|
|
54
|
-
taskHash: Hex;
|
|
55
|
-
evidenceHash: Hex;
|
|
56
|
-
status: TaskStatus;
|
|
57
|
-
category: string;
|
|
58
|
-
locationZone: string;
|
|
59
|
-
createdAt: Date;
|
|
60
|
-
deadline: Date;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Task metadata used for discovery / listing. */
|
|
64
|
-
export interface DomainTaskMeta {
|
|
65
|
-
taskId: TaskId;
|
|
66
|
-
agent: string;
|
|
67
|
-
category: string;
|
|
68
|
-
locationZone: string;
|
|
69
|
-
reward: bigint;
|
|
70
|
-
createdAt: Date;
|
|
71
|
-
isOpen: boolean;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** On-chain reputation snapshot (chain-agnostic). */
|
|
75
|
-
export interface DomainReputation {
|
|
76
|
-
tasksCompleted: bigint;
|
|
77
|
-
totalScore: bigint;
|
|
78
|
-
disputes: bigint;
|
|
79
|
-
avgScore: number;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Transaction receipt (chain-agnostic). */
|
|
83
|
-
export interface DomainTxReceipt {
|
|
84
|
-
hash: string;
|
|
85
|
-
blockNumber?: number;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Signer abstraction that works across chains. */
|
|
89
|
-
export interface ChainSigner {
|
|
90
|
-
getAddress(): Promise<string>;
|
|
91
|
-
getChainType(): BlockchainType;
|
|
92
|
-
}
|
package/src/chain/errors.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { ChainError } from '../errors/index.js';
|
|
2
|
-
|
|
3
|
-
interface EthersLikeError {
|
|
4
|
-
code?: string;
|
|
5
|
-
shortMessage?: string;
|
|
6
|
-
message?: string;
|
|
7
|
-
reason?: string;
|
|
8
|
-
data?: unknown;
|
|
9
|
-
info?: unknown;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Map an ethers-thrown error to a typed ChainError. Best-effort: walks the
|
|
14
|
-
* ethers error shape for a revert reason, maps common codes (INSUFFICIENT_FUNDS,
|
|
15
|
-
* NETWORK_ERROR, TIMEOUT), and falls back to TX_REVERTED with the original
|
|
16
|
-
* message in context.
|
|
17
|
-
*/
|
|
18
|
-
export function wrapChainError(err: unknown, hint?: string): ChainError {
|
|
19
|
-
const e = (err ?? {}) as EthersLikeError;
|
|
20
|
-
const msg = e.shortMessage ?? e.reason ?? e.message ?? 'unknown chain error';
|
|
21
|
-
const withHint = hint ? `${hint}: ${msg}` : msg;
|
|
22
|
-
|
|
23
|
-
switch (e.code) {
|
|
24
|
-
case 'INSUFFICIENT_FUNDS':
|
|
25
|
-
return new ChainError('CHAIN/INSUFFICIENT_BALANCE', withHint, { cause: err, retriable: false });
|
|
26
|
-
case 'NONCE_EXPIRED':
|
|
27
|
-
case 'REPLACEMENT_UNDERPRICED':
|
|
28
|
-
return new ChainError('CHAIN/NONCE_CONFLICT', withHint, { cause: err, retriable: true });
|
|
29
|
-
case 'NETWORK_ERROR':
|
|
30
|
-
case 'SERVER_ERROR':
|
|
31
|
-
case 'TIMEOUT':
|
|
32
|
-
return new ChainError('CHAIN/TX_TIMED_OUT', withHint, { cause: err, retriable: true });
|
|
33
|
-
case 'UNSUPPORTED_OPERATION':
|
|
34
|
-
case 'UNKNOWN_ERROR':
|
|
35
|
-
case 'CALL_EXCEPTION':
|
|
36
|
-
return new ChainError('CHAIN/TX_REVERTED', withHint, {
|
|
37
|
-
cause: err,
|
|
38
|
-
...(e.data !== undefined ? { context: { data: e.data } } : {}),
|
|
39
|
-
});
|
|
40
|
-
default:
|
|
41
|
-
return new ChainError('CHAIN/TX_REVERTED', withHint, { cause: err });
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import { ethers } from 'ethers';
|
|
2
|
-
import type { Address, Hex } from '../../types.js';
|
|
3
|
-
import { ChainClient, type ChainClientOptions } from '../ChainClient.js';
|
|
4
|
-
import { resolveNetwork } from '../../network/resolve.js';
|
|
5
|
-
import type { EthersSigner } from '../../signer/EthersSigner.js';
|
|
6
|
-
import type { Signer } from '../../signer/Signer.js';
|
|
7
|
-
import type {
|
|
8
|
-
CreateTaskParams,
|
|
9
|
-
DomainReputation,
|
|
10
|
-
DomainTask,
|
|
11
|
-
DomainTaskMeta,
|
|
12
|
-
DomainTxReceipt,
|
|
13
|
-
EVMNetwork,
|
|
14
|
-
} from '../domain-types.js';
|
|
15
|
-
import type { IBlindMarketChain } from '../IBlindMarketChain.js';
|
|
16
|
-
import type { OpenTaskMeta } from '../TaskRegistryClient.js';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* EVM-compatible implementation of IBlindMarketChain.
|
|
20
|
-
*
|
|
21
|
-
* Wraps the existing ChainClient (which bundles BlindEscrowClient,
|
|
22
|
-
* TaskRegistryClient, and BlindReputationClient) and translates domain
|
|
23
|
-
* operations into ethers v6 contract calls on EVM chains (0G, Ethereum, etc.).
|
|
24
|
-
*/
|
|
25
|
-
export class EVMChainAdapter implements IBlindMarketChain {
|
|
26
|
-
readonly network: EVMNetwork;
|
|
27
|
-
private client: ChainClient;
|
|
28
|
-
private signer: EthersSigner | undefined;
|
|
29
|
-
|
|
30
|
-
constructor(opts: ChainClientOptions) {
|
|
31
|
-
const resolved = resolveNetwork(opts.network);
|
|
32
|
-
if (resolved.chainType !== 'evm') {
|
|
33
|
-
throw new Error(`EVMChainAdapter requires an EVM network, got ${resolved.chainType}`);
|
|
34
|
-
}
|
|
35
|
-
this.network = resolved;
|
|
36
|
-
this.client = new ChainClient(opts);
|
|
37
|
-
this.signer = opts.signer as EthersSigner | undefined;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async getAddress(): Promise<string> {
|
|
41
|
-
if (!this.signer) throw new Error('No signer configured');
|
|
42
|
-
return this.signer.getAddress();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ── Task lifecycle ──────────────────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
async createTask(params: CreateTaskParams): Promise<{ taskId: bigint; receipt: DomainTxReceipt }> {
|
|
48
|
-
const result = await this.client.escrow.createTask({
|
|
49
|
-
taskHash: params.taskHash,
|
|
50
|
-
token: params.token as Address,
|
|
51
|
-
amount: params.amount,
|
|
52
|
-
category: params.category,
|
|
53
|
-
locationZone: params.locationZone,
|
|
54
|
-
deadline: params.deadline,
|
|
55
|
-
});
|
|
56
|
-
return {
|
|
57
|
-
taskId: result.taskId,
|
|
58
|
-
receipt: {
|
|
59
|
-
hash: result.receipt.hash,
|
|
60
|
-
blockNumber: result.receipt.blockNumber,
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async assignWorker(taskId: bigint, worker: string): Promise<DomainTxReceipt> {
|
|
66
|
-
const receipt = await this.client.escrow.assignWorker(taskId, worker as Address);
|
|
67
|
-
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async submitEvidence(taskId: bigint, evidenceHash: string): Promise<DomainTxReceipt> {
|
|
71
|
-
const receipt = await this.client.escrow.submitEvidence(taskId, evidenceHash as Hex);
|
|
72
|
-
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async completeVerification(taskId: bigint, passed: boolean): Promise<DomainTxReceipt> {
|
|
76
|
-
const receipt = await this.client.escrow.completeVerification(taskId, passed);
|
|
77
|
-
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async cancelTask(taskId: bigint): Promise<DomainTxReceipt> {
|
|
81
|
-
const receipt = await this.client.escrow.cancelTask(taskId);
|
|
82
|
-
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async claimTimeout(taskId: bigint): Promise<DomainTxReceipt> {
|
|
86
|
-
const receipt = await this.client.escrow.claimTimeout(taskId);
|
|
87
|
-
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async raiseDispute(taskId: bigint): Promise<DomainTxReceipt> {
|
|
91
|
-
const receipt = await this.client.escrow.raiseDispute(taskId);
|
|
92
|
-
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async resolveDispute(taskId: bigint, workerFavored: boolean): Promise<DomainTxReceipt> {
|
|
96
|
-
const receipt = await this.client.escrow.resolveDispute(taskId, workerFavored);
|
|
97
|
-
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async getTask(taskId: bigint): Promise<DomainTask> {
|
|
101
|
-
const onChain = await this.client.escrow.getTask(taskId);
|
|
102
|
-
return {
|
|
103
|
-
...onChain,
|
|
104
|
-
category: '', // OnChainTask doesn't decode these contract fields;
|
|
105
|
-
locationZone: '', // they are present in the Solidity Task struct.
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async isTaskExpired(taskId: bigint): Promise<boolean> {
|
|
110
|
-
return this.client.escrow.isTaskExpired(taskId);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ── Task discovery ──────────────────────────────────────────────────────
|
|
114
|
-
|
|
115
|
-
async publishTask(taskId: bigint, _meta: Omit<DomainTaskMeta, 'taskId'> & { taskId?: bigint }): Promise<DomainTxReceipt> {
|
|
116
|
-
// The EVM contracts publish tasks via the escrow's TaskCreated event
|
|
117
|
-
// (handled by the backend indexer). This is a no-op at the adapter level
|
|
118
|
-
// for EVM — the registry is updated by the escrow contract internally.
|
|
119
|
-
// Return a stub receipt for interface compatibility.
|
|
120
|
-
return { hash: '0x0', blockNumber: 0 };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async closeTask(taskId: bigint): Promise<DomainTxReceipt> {
|
|
124
|
-
const receipt = await this.client.registry.closeTask(taskId);
|
|
125
|
-
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async getOpenTasks(offset: bigint, limit: bigint): Promise<DomainTaskMeta[]> {
|
|
129
|
-
const taskIds = await this.client.registry.getOpenTasks(offset, limit);
|
|
130
|
-
const metas = await Promise.all(
|
|
131
|
-
taskIds.map(async (tid: bigint) => {
|
|
132
|
-
const meta = await this.client.registry.getTaskMeta(tid);
|
|
133
|
-
return {
|
|
134
|
-
taskId: tid,
|
|
135
|
-
agent: '' as Address, // EVM TaskRegistry stores agent; use meta directly
|
|
136
|
-
category: meta.category,
|
|
137
|
-
locationZone: meta.locationZone,
|
|
138
|
-
reward: meta.reward,
|
|
139
|
-
createdAt: meta.createdAt,
|
|
140
|
-
isOpen: true,
|
|
141
|
-
} as DomainTaskMeta;
|
|
142
|
-
}),
|
|
143
|
-
);
|
|
144
|
-
return metas;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async openTaskCount(): Promise<bigint> {
|
|
148
|
-
return this.client.registry.openTaskCount();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async totalTasks(): Promise<bigint> {
|
|
152
|
-
return this.client.registry.totalTasks();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// ── Reputation ──────────────────────────────────────────────────────────
|
|
156
|
-
|
|
157
|
-
async rateWorker(worker: string, score: number, taskId: bigint): Promise<DomainTxReceipt> {
|
|
158
|
-
const receipt = await this.client.reputation.rate(worker as Address, score, taskId);
|
|
159
|
-
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async recordDispute(worker: string, taskId: bigint): Promise<DomainTxReceipt> {
|
|
163
|
-
// EVM BlindReputation doesn't expose recordDispute as a standalone client
|
|
164
|
-
// method in the SDK — the escrow calls it internally. For external callers
|
|
165
|
-
// we throw. The escrow contract handles dispute recording via its own path.
|
|
166
|
-
throw new Error('recordDispute is handled internally by BlindEscrow on EVM chains');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async getReputation(worker: string): Promise<DomainReputation> {
|
|
170
|
-
return this.client.reputation.getReputation(worker as Address);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async hasBeenRated(worker: string, taskId: bigint): Promise<boolean> {
|
|
174
|
-
return this.client.reputation.hasBeenRated(worker as Address, taskId);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// ── Agent identity ──────────────────────────────────────────────────────
|
|
178
|
-
|
|
179
|
-
async mintAgent(params: {
|
|
180
|
-
to: string;
|
|
181
|
-
encryptedURI: string;
|
|
182
|
-
metadataHash: string;
|
|
183
|
-
}): Promise<{ tokenId: bigint; receipt: DomainTxReceipt }> {
|
|
184
|
-
// INFT minting is handled by the backend (services/chain.ts → inft.mint()).
|
|
185
|
-
// The SDK doesn't expose a direct INFT client. For direct EVM calls,
|
|
186
|
-
// consumers can use the raw ethers.Contract via the unwrapped signer.
|
|
187
|
-
throw new Error('INFT minting is backend-managed on EVM chains. Use the REST API or raw ethers.Contract.');
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async getEncryptedURI(_tokenId: bigint): Promise<string> {
|
|
191
|
-
throw new Error('INFT URI reading is backend-managed on EVM chains. Use the REST API or raw ethers.Contract.');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// ── Queries ─────────────────────────────────────────────────────────────
|
|
195
|
-
|
|
196
|
-
async getBalance(address: string): Promise<bigint> {
|
|
197
|
-
return this.client.provider.getBalance(address);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// ── Accessors for direct ethers access (advanced use) ───────────────────
|
|
201
|
-
|
|
202
|
-
/** Get the underlying ethers Provider for raw queries. */
|
|
203
|
-
getProvider(): ethers.Provider {
|
|
204
|
-
return this.client.provider;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/** Get the underlying ethers Signer (if configured). */
|
|
208
|
-
getEthersSigner(): ethers.Signer | undefined {
|
|
209
|
-
if (!this.signer) return undefined;
|
|
210
|
-
return (this.signer as unknown as { inner?: ethers.Signer }).inner;
|
|
211
|
-
}
|
|
212
|
-
}
|
package/src/chain/evm/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { EVMChainAdapter } from './EVMChainAdapter.js';
|
package/src/chain/index.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export * from './abi/index.js';
|
|
2
|
-
export * from './errors.js';
|
|
3
|
-
export * from './BlindEscrowClient.js';
|
|
4
|
-
export * from './TaskRegistryClient.js';
|
|
5
|
-
export * from './BlindReputationClient.js';
|
|
6
|
-
export * from './ChainClient.js';
|
|
7
|
-
|
|
8
|
-
// Chain-agnostic domain interface (EVM only — 0G Chain)
|
|
9
|
-
export * from './IBlindMarketChain.js';
|
|
10
|
-
export * from './domain-types.js';
|
|
11
|
-
export * from './createChainAdapter.js';
|
|
12
|
-
export * from './evm/index.js';
|
package/src/compute/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/src/crypto/aes.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { CryptoError } from '../errors/index.js';
|
|
2
|
-
import { AES_MIN_BLOB, IV_LENGTH, KEY_LENGTH, TAG_LENGTH } from './constants.js';
|
|
3
|
-
|
|
4
|
-
function getCrypto(): Crypto {
|
|
5
|
-
const c = (globalThis as { crypto?: Crypto }).crypto;
|
|
6
|
-
if (!c?.subtle) {
|
|
7
|
-
throw new CryptoError('CRYPTO/UNSUPPORTED_CIPHER', 'Web Crypto (crypto.subtle) is not available');
|
|
8
|
-
}
|
|
9
|
-
return c;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function toArrayBuffer(u: Uint8Array): ArrayBuffer {
|
|
13
|
-
const ab = new ArrayBuffer(u.byteLength);
|
|
14
|
-
new Uint8Array(ab).set(u);
|
|
15
|
-
return ab;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Generate a fresh 32-byte AES-256 key. */
|
|
19
|
-
export async function generateAesKey(): Promise<Uint8Array> {
|
|
20
|
-
const key = new Uint8Array(KEY_LENGTH);
|
|
21
|
-
getCrypto().getRandomValues(key);
|
|
22
|
-
return key;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* AES-256-GCM encrypt. Output format: [12 IV][16 tag][ciphertext].
|
|
27
|
-
* This ordering matches backend/src/services/crypto.ts and frontend/src/lib/crypto.ts
|
|
28
|
-
* byte-for-byte, so SDK blobs decrypt anywhere in the stack.
|
|
29
|
-
*/
|
|
30
|
-
export async function aesEncrypt(plaintext: Uint8Array, key: Uint8Array): Promise<Uint8Array> {
|
|
31
|
-
if (key.byteLength !== KEY_LENGTH) {
|
|
32
|
-
throw new CryptoError(
|
|
33
|
-
'CRYPTO/INVALID_KEY',
|
|
34
|
-
`AES key must be ${KEY_LENGTH} bytes, got ${key.byteLength}`,
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
const c = getCrypto();
|
|
38
|
-
const iv = new Uint8Array(IV_LENGTH);
|
|
39
|
-
c.getRandomValues(iv);
|
|
40
|
-
try {
|
|
41
|
-
const ck = await c.subtle.importKey('raw', toArrayBuffer(key), 'AES-GCM', false, ['encrypt']);
|
|
42
|
-
const wcOut = new Uint8Array(
|
|
43
|
-
await c.subtle.encrypt(
|
|
44
|
-
{ name: 'AES-GCM', iv: toArrayBuffer(iv), tagLength: TAG_LENGTH * 8 },
|
|
45
|
-
ck,
|
|
46
|
-
toArrayBuffer(plaintext),
|
|
47
|
-
),
|
|
48
|
-
);
|
|
49
|
-
// WebCrypto returns [ciphertext || tag]. Rewrite to [IV || tag || ciphertext].
|
|
50
|
-
const ctLen = wcOut.length - TAG_LENGTH;
|
|
51
|
-
const ct = wcOut.subarray(0, ctLen);
|
|
52
|
-
const tag = wcOut.subarray(ctLen);
|
|
53
|
-
const blob = new Uint8Array(IV_LENGTH + TAG_LENGTH + ctLen);
|
|
54
|
-
blob.set(iv, 0);
|
|
55
|
-
blob.set(tag, IV_LENGTH);
|
|
56
|
-
blob.set(ct, IV_LENGTH + TAG_LENGTH);
|
|
57
|
-
return blob;
|
|
58
|
-
} catch (cause) {
|
|
59
|
-
throw new CryptoError('CRYPTO/DECRYPT_FAILED', 'AES-GCM encryption failed', { cause });
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* AES-256-GCM decrypt. Input format: [12 IV][16 tag][ciphertext].
|
|
65
|
-
* Throws CRYPTO/INTEGRITY_CHECK on truncation or tag mismatch.
|
|
66
|
-
*/
|
|
67
|
-
export async function aesDecrypt(blob: Uint8Array, key: Uint8Array): Promise<Uint8Array> {
|
|
68
|
-
if (key.byteLength !== KEY_LENGTH) {
|
|
69
|
-
throw new CryptoError(
|
|
70
|
-
'CRYPTO/INVALID_KEY',
|
|
71
|
-
`AES key must be ${KEY_LENGTH} bytes, got ${key.byteLength}`,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
if (blob.byteLength < AES_MIN_BLOB) {
|
|
75
|
-
throw new CryptoError(
|
|
76
|
-
'CRYPTO/INTEGRITY_CHECK',
|
|
77
|
-
`AES blob too short: need ≥${AES_MIN_BLOB} bytes, got ${blob.byteLength}`,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
const iv = blob.subarray(0, IV_LENGTH);
|
|
81
|
-
const tag = blob.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
82
|
-
const ct = blob.subarray(IV_LENGTH + TAG_LENGTH);
|
|
83
|
-
const combined = new Uint8Array(ct.length + TAG_LENGTH);
|
|
84
|
-
combined.set(ct, 0);
|
|
85
|
-
combined.set(tag, ct.length);
|
|
86
|
-
try {
|
|
87
|
-
const c = getCrypto();
|
|
88
|
-
const ck = await c.subtle.importKey('raw', toArrayBuffer(key), 'AES-GCM', false, ['decrypt']);
|
|
89
|
-
const pt = await c.subtle.decrypt(
|
|
90
|
-
{ name: 'AES-GCM', iv: toArrayBuffer(iv), tagLength: TAG_LENGTH * 8 },
|
|
91
|
-
ck,
|
|
92
|
-
toArrayBuffer(combined),
|
|
93
|
-
);
|
|
94
|
-
return new Uint8Array(pt);
|
|
95
|
-
} catch (cause) {
|
|
96
|
-
throw new CryptoError('CRYPTO/DECRYPT_FAILED', 'AES-GCM decryption failed (wrong key or tampered data)', { cause });
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** SHA-256 of arbitrary bytes. Returns 32-byte digest. */
|
|
101
|
-
export async function sha256(data: Uint8Array): Promise<Uint8Array> {
|
|
102
|
-
const digest = await getCrypto().subtle.digest('SHA-256', toArrayBuffer(data));
|
|
103
|
-
return new Uint8Array(digest);
|
|
104
|
-
}
|
package/src/crypto/constants.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export const IV_LENGTH = 12;
|
|
2
|
-
export const TAG_LENGTH = 16;
|
|
3
|
-
export const KEY_LENGTH = 32;
|
|
4
|
-
export const ECIES_PUBKEY_LENGTH = 65;
|
|
5
|
-
export const ECIES_HKDF_INFO = new TextEncoder().encode('BlindMarket-ECIES-v1');
|
|
6
|
-
export const AES_MIN_BLOB = IV_LENGTH + TAG_LENGTH;
|
|
7
|
-
export const ECIES_MIN_BLOB = ECIES_PUBKEY_LENGTH + AES_MIN_BLOB;
|
package/src/crypto/ecies.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { ethers } from 'ethers';
|
|
2
|
-
import { CryptoError } from '../errors/index.js';
|
|
3
|
-
import { aesDecrypt, aesEncrypt } from './aes.js';
|
|
4
|
-
import { ECIES_MIN_BLOB, ECIES_PUBKEY_LENGTH, KEY_LENGTH } from './constants.js';
|
|
5
|
-
import { hkdfSha256 } from './hkdf.js';
|
|
6
|
-
import { assertUncompressedPubKey, bytesToHex, hexToBytes, strip } from './keys.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* ECIES-secp256k1 encrypt to a recipient's uncompressed public key.
|
|
10
|
-
* Wire format: [65 ephemeral pubkey][12 IV][16 tag][ciphertext].
|
|
11
|
-
* Byte-compatible with backend/src/services/crypto.ts.
|
|
12
|
-
*/
|
|
13
|
-
export async function eciesEncrypt(plaintext: Uint8Array, recipientPubKey: string): Promise<Uint8Array> {
|
|
14
|
-
assertUncompressedPubKey(recipientPubKey);
|
|
15
|
-
const ephemeral = ethers.Wallet.createRandom();
|
|
16
|
-
const shared = deriveSharedSecretX(ephemeral.privateKey, `0x${strip(recipientPubKey)}`);
|
|
17
|
-
const aesKey = await hkdfSha256(shared, KEY_LENGTH);
|
|
18
|
-
const aesBlob = await aesEncrypt(plaintext, aesKey);
|
|
19
|
-
const ephPub = hexToBytes(strip(ephemeral.signingKey.publicKey));
|
|
20
|
-
if (ephPub.byteLength !== ECIES_PUBKEY_LENGTH) {
|
|
21
|
-
throw new CryptoError('CRYPTO/INVALID_KEY', `ephemeral pubkey length ${ephPub.byteLength}`);
|
|
22
|
-
}
|
|
23
|
-
const blob = new Uint8Array(ECIES_PUBKEY_LENGTH + aesBlob.byteLength);
|
|
24
|
-
blob.set(ephPub, 0);
|
|
25
|
-
blob.set(aesBlob, ECIES_PUBKEY_LENGTH);
|
|
26
|
-
return blob;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** ECIES-secp256k1 decrypt with the recipient's private key. */
|
|
30
|
-
export async function eciesDecrypt(blob: Uint8Array, recipientPrivKey: string): Promise<Uint8Array> {
|
|
31
|
-
if (blob.byteLength < ECIES_MIN_BLOB) {
|
|
32
|
-
throw new CryptoError(
|
|
33
|
-
'CRYPTO/INTEGRITY_CHECK',
|
|
34
|
-
`ECIES blob too short: need ≥${ECIES_MIN_BLOB} bytes, got ${blob.byteLength}`,
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
const ephPub = blob.subarray(0, ECIES_PUBKEY_LENGTH);
|
|
38
|
-
const aesBlob = blob.subarray(ECIES_PUBKEY_LENGTH);
|
|
39
|
-
const shared = deriveSharedSecretX(`0x${strip(recipientPrivKey)}`, `0x${bytesToHex(ephPub)}`);
|
|
40
|
-
const aesKey = await hkdfSha256(shared, KEY_LENGTH);
|
|
41
|
-
return aesDecrypt(aesBlob, aesKey);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Compute the ECDH shared secret X coordinate (32 bytes) between a private key
|
|
46
|
-
* and a peer's public key. Matches Node createECDH.computeSecret() output used by
|
|
47
|
-
* the backend — ethers v6 returns 0x04||X||Y, so we slice X.
|
|
48
|
-
*/
|
|
49
|
-
function deriveSharedSecretX(privHex: string, pubHex: string): Uint8Array {
|
|
50
|
-
const sk = new ethers.SigningKey(privHex);
|
|
51
|
-
const shared = sk.computeSharedSecret(pubHex);
|
|
52
|
-
const bytes = hexToBytes(strip(shared));
|
|
53
|
-
if (bytes.byteLength !== 65 || bytes[0] !== 0x04) {
|
|
54
|
-
throw new CryptoError(
|
|
55
|
-
'CRYPTO/INVALID_KEY',
|
|
56
|
-
`unexpected shared secret shape (len=${bytes.byteLength}, prefix=${bytes[0]?.toString(16) ?? 'none'})`,
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
return bytes.subarray(1, 33);
|
|
60
|
-
}
|
package/src/crypto/envelope.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { CryptoError } from '../errors/index.js';
|
|
2
|
-
import { aesDecrypt, aesEncrypt, generateAesKey } from './aes.js';
|
|
3
|
-
import { eciesEncrypt } from './ecies.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Evidence envelope — the single blob a worker uploads to Storage containing:
|
|
7
|
-
* 1. The AES key wrapped to the agent's pubkey (so agent can decrypt later)
|
|
8
|
-
* 2. The AES key wrapped to the TEE enclave's pubkey (so Sealed Inference can decrypt)
|
|
9
|
-
* 3. The evidence ciphertext under that AES key
|
|
10
|
-
*
|
|
11
|
-
* Wire format (big-endian):
|
|
12
|
-
* bytes 0..1 magic = 0x42 0x42 ("BB")
|
|
13
|
-
* byte 2 version = 0x01
|
|
14
|
-
* bytes 3..6 wrappedAgentLen (uint32)
|
|
15
|
-
* bytes 7.. wrappedAgent (ECIES blob)
|
|
16
|
-
* 4-byte uint32 wrappedEnclaveLen
|
|
17
|
-
* bytes wrappedEnclave (ECIES blob)
|
|
18
|
-
* bytes aesCiphertext (rest of the blob — AES blob [IV||tag||ct])
|
|
19
|
-
*
|
|
20
|
-
* The layout is versioned so we can rev it without breaking old blobs.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
const MAGIC_0 = 0x42;
|
|
24
|
-
const MAGIC_1 = 0x42;
|
|
25
|
-
const VERSION = 0x01;
|
|
26
|
-
const HEADER_LEN = 3;
|
|
27
|
-
|
|
28
|
-
export interface EvidenceEnvelope {
|
|
29
|
-
wrappedForAgent: Uint8Array;
|
|
30
|
-
wrappedForEnclave: Uint8Array;
|
|
31
|
-
aesCiphertext: Uint8Array;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface SealEvidenceInput {
|
|
35
|
-
evidence: Uint8Array;
|
|
36
|
-
agentPubKey: string;
|
|
37
|
-
enclavePubKey: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface SealEvidenceOutput {
|
|
41
|
-
envelope: Uint8Array;
|
|
42
|
-
aesKey: Uint8Array;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Seal evidence for the agent + TEE enclave. Generates a fresh AES key,
|
|
47
|
-
* encrypts the evidence, ECIES-wraps the key twice, and concatenates the
|
|
48
|
-
* result as an EvidenceEnvelope. Returns both the on-wire envelope and
|
|
49
|
-
* the raw AES key (for audit / diagnostic use — do NOT persist beyond
|
|
50
|
-
* the submission).
|
|
51
|
-
*/
|
|
52
|
-
export async function sealEvidence(input: SealEvidenceInput): Promise<SealEvidenceOutput> {
|
|
53
|
-
const aesKey = await generateAesKey();
|
|
54
|
-
const aesCiphertext = await aesEncrypt(input.evidence, aesKey);
|
|
55
|
-
const wrappedForAgent = await eciesEncrypt(aesKey, input.agentPubKey);
|
|
56
|
-
const wrappedForEnclave = await eciesEncrypt(aesKey, input.enclavePubKey);
|
|
57
|
-
const envelope = pack({ wrappedForAgent, wrappedForEnclave, aesCiphertext });
|
|
58
|
-
return { envelope, aesKey };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Open an evidence envelope as the agent — extracts the AES key wrapped
|
|
63
|
-
* to the agent's pubkey, decrypts the ciphertext, and returns the plaintext
|
|
64
|
-
* evidence. The enclave-wrapped key is ignored by agents (TEE-only).
|
|
65
|
-
*/
|
|
66
|
-
export async function openEvidenceAsAgent(
|
|
67
|
-
envelope: Uint8Array,
|
|
68
|
-
agentPrivKey: string,
|
|
69
|
-
): Promise<Uint8Array> {
|
|
70
|
-
const parsed = parse(envelope);
|
|
71
|
-
const { eciesDecrypt } = await import('./ecies.js');
|
|
72
|
-
const aesKey = await eciesDecrypt(parsed.wrappedForAgent, agentPrivKey);
|
|
73
|
-
return aesDecrypt(parsed.aesCiphertext, aesKey);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function pack(parts: EvidenceEnvelope): Uint8Array {
|
|
77
|
-
const { wrappedForAgent, wrappedForEnclave, aesCiphertext } = parts;
|
|
78
|
-
const total =
|
|
79
|
-
HEADER_LEN +
|
|
80
|
-
4 + wrappedForAgent.byteLength +
|
|
81
|
-
4 + wrappedForEnclave.byteLength +
|
|
82
|
-
aesCiphertext.byteLength;
|
|
83
|
-
const out = new Uint8Array(total);
|
|
84
|
-
const view = new DataView(out.buffer);
|
|
85
|
-
let off = 0;
|
|
86
|
-
out[off++] = MAGIC_0;
|
|
87
|
-
out[off++] = MAGIC_1;
|
|
88
|
-
out[off++] = VERSION;
|
|
89
|
-
view.setUint32(off, wrappedForAgent.byteLength, false);
|
|
90
|
-
off += 4;
|
|
91
|
-
out.set(wrappedForAgent, off);
|
|
92
|
-
off += wrappedForAgent.byteLength;
|
|
93
|
-
view.setUint32(off, wrappedForEnclave.byteLength, false);
|
|
94
|
-
off += 4;
|
|
95
|
-
out.set(wrappedForEnclave, off);
|
|
96
|
-
off += wrappedForEnclave.byteLength;
|
|
97
|
-
out.set(aesCiphertext, off);
|
|
98
|
-
return out;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function parse(blob: Uint8Array): EvidenceEnvelope {
|
|
102
|
-
if (blob.byteLength < HEADER_LEN + 4 + 4) {
|
|
103
|
-
throw new CryptoError('CRYPTO/INTEGRITY_CHECK', `envelope too short: ${blob.byteLength}`);
|
|
104
|
-
}
|
|
105
|
-
if (blob[0] !== MAGIC_0 || blob[1] !== MAGIC_1) {
|
|
106
|
-
throw new CryptoError('CRYPTO/INTEGRITY_CHECK', 'bad envelope magic');
|
|
107
|
-
}
|
|
108
|
-
if (blob[2] !== VERSION) {
|
|
109
|
-
throw new CryptoError('CRYPTO/UNSUPPORTED_CIPHER', `unknown envelope version ${blob[2]}`);
|
|
110
|
-
}
|
|
111
|
-
const view = new DataView(blob.buffer, blob.byteOffset, blob.byteLength);
|
|
112
|
-
let off = HEADER_LEN;
|
|
113
|
-
const agentLen = view.getUint32(off, false);
|
|
114
|
-
off += 4;
|
|
115
|
-
if (off + agentLen + 4 > blob.byteLength) {
|
|
116
|
-
throw new CryptoError('CRYPTO/INTEGRITY_CHECK', 'envelope truncated (agent)');
|
|
117
|
-
}
|
|
118
|
-
const wrappedForAgent = blob.slice(off, off + agentLen);
|
|
119
|
-
off += agentLen;
|
|
120
|
-
const enclaveLen = view.getUint32(off, false);
|
|
121
|
-
off += 4;
|
|
122
|
-
if (off + enclaveLen > blob.byteLength) {
|
|
123
|
-
throw new CryptoError('CRYPTO/INTEGRITY_CHECK', 'envelope truncated (enclave)');
|
|
124
|
-
}
|
|
125
|
-
const wrappedForEnclave = blob.slice(off, off + enclaveLen);
|
|
126
|
-
off += enclaveLen;
|
|
127
|
-
const aesCiphertext = blob.slice(off);
|
|
128
|
-
return { wrappedForAgent, wrappedForEnclave, aesCiphertext };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export const __internals = { pack, parse };
|