@sip-protocol/sdk 0.4.0 → 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/browser.d.mts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +1866 -153
- package/dist/browser.mjs +14 -2
- package/dist/chunk-DMHBKRWV.mjs +14712 -0
- package/dist/chunk-HGU6HZRC.mjs +231 -0
- package/dist/chunk-J4Q4NJ2U.mjs +13544 -0
- package/dist/chunk-W2B7T6WU.mjs +14714 -0
- package/dist/index-5jAdWMA-.d.ts +8973 -0
- package/dist/index-B9Vkpaao.d.mts +8973 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1851 -138
- package/dist/index.mjs +14 -2
- package/dist/proofs/noir.mjs +1 -1
- package/package.json +1 -1
- package/src/compliance/compliance-manager.ts +87 -0
- package/src/compliance/conditional-threshold.ts +379 -0
- package/src/compliance/conditional.ts +382 -0
- package/src/compliance/derivation.ts +489 -0
- package/src/compliance/index.ts +50 -8
- package/src/compliance/pdf.ts +365 -0
- package/src/compliance/reports.ts +644 -0
- package/src/compliance/threshold.ts +529 -0
- package/src/compliance/types.ts +223 -0
- package/src/errors.ts +8 -0
- package/src/index.ts +29 -1
package/dist/index.js
CHANGED
|
@@ -32,10 +32,14 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
ATTESTATION_VERSION: () => ATTESTATION_VERSION,
|
|
34
34
|
AptosStealthService: () => AptosStealthService,
|
|
35
|
+
AuditorKeyDerivation: () => AuditorKeyDerivation,
|
|
36
|
+
AuditorType: () => AuditorType,
|
|
35
37
|
BaseWalletAdapter: () => BaseWalletAdapter,
|
|
36
38
|
CHAIN_NUMERIC_IDS: () => CHAIN_NUMERIC_IDS,
|
|
37
39
|
COSMOS_CHAIN_PREFIXES: () => CHAIN_PREFIXES,
|
|
38
40
|
ComplianceManager: () => ComplianceManager,
|
|
41
|
+
ComplianceReporter: () => ComplianceReporter,
|
|
42
|
+
ConditionalDisclosure: () => ConditionalDisclosure,
|
|
39
43
|
CosmosStealthService: () => CosmosStealthService,
|
|
40
44
|
CryptoError: () => CryptoError,
|
|
41
45
|
DEFAULT_THRESHOLD: () => DEFAULT_THRESHOLD,
|
|
@@ -87,6 +91,7 @@ __export(index_exports, {
|
|
|
87
91
|
SmartRouter: () => SmartRouter,
|
|
88
92
|
SolanaWalletAdapter: () => SolanaWalletAdapter,
|
|
89
93
|
SwapStatus: () => SwapStatus,
|
|
94
|
+
ThresholdViewingKey: () => ThresholdViewingKey,
|
|
90
95
|
Treasury: () => Treasury,
|
|
91
96
|
TrezorWalletAdapter: () => TrezorWalletAdapter,
|
|
92
97
|
ValidationError: () => ValidationError,
|
|
@@ -171,6 +176,7 @@ __export(index_exports, {
|
|
|
171
176
|
generateEd25519StealthAddress: () => generateEd25519StealthAddress,
|
|
172
177
|
generateEd25519StealthMetaAddress: () => generateEd25519StealthMetaAddress,
|
|
173
178
|
generateIntentId: () => generateIntentId,
|
|
179
|
+
generatePdfReport: () => generatePdfReport,
|
|
174
180
|
generateRandomBytes: () => generateRandomBytes,
|
|
175
181
|
generateStealthAddress: () => generateStealthAddress,
|
|
176
182
|
generateStealthMetaAddress: () => generateStealthMetaAddress,
|
|
@@ -303,6 +309,14 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
|
303
309
|
ErrorCode2["SIGNATURE_FAILED"] = "SIP_3005";
|
|
304
310
|
ErrorCode2["INVALID_CURVE_POINT"] = "SIP_3006";
|
|
305
311
|
ErrorCode2["INVALID_SCALAR"] = "SIP_3007";
|
|
312
|
+
ErrorCode2["INVALID_KEY_SIZE"] = "SIP_3008";
|
|
313
|
+
ErrorCode2["INVALID_ENCRYPTED_DATA"] = "SIP_3009";
|
|
314
|
+
ErrorCode2["INVALID_COMMITMENT"] = "SIP_3010";
|
|
315
|
+
ErrorCode2["INVALID_TIME_LOCK"] = "SIP_3011";
|
|
316
|
+
ErrorCode2["INVALID_SHARE"] = "SIP_3012";
|
|
317
|
+
ErrorCode2["INVALID_THRESHOLD"] = "SIP_3013";
|
|
318
|
+
ErrorCode2["CRYPTO_OPERATION_FAILED"] = "SIP_3014";
|
|
319
|
+
ErrorCode2["INVALID_FORMAT"] = "SIP_3015";
|
|
306
320
|
ErrorCode2["PROOF_FAILED"] = "SIP_4000";
|
|
307
321
|
ErrorCode2["PROOF_GENERATION_FAILED"] = "SIP_4001";
|
|
308
322
|
ErrorCode2["PROOF_VERIFICATION_FAILED"] = "SIP_4002";
|
|
@@ -8952,6 +8966,79 @@ var ComplianceManager = class _ComplianceManager {
|
|
|
8952
8966
|
getAllReports() {
|
|
8953
8967
|
return Array.from(this.reports.values());
|
|
8954
8968
|
}
|
|
8969
|
+
// ─── Dashboard Data API ──────────────────────────────────────────────────────
|
|
8970
|
+
/**
|
|
8971
|
+
* Get list of auditors for dashboard UI
|
|
8972
|
+
*
|
|
8973
|
+
* Returns a simplified view of auditors with essential info.
|
|
8974
|
+
* Alias for getAllAuditors() but returns AuditorRegistration directly.
|
|
8975
|
+
*
|
|
8976
|
+
* @returns Array of auditor registrations
|
|
8977
|
+
*/
|
|
8978
|
+
getAuditorList() {
|
|
8979
|
+
return this.getAllAuditors();
|
|
8980
|
+
}
|
|
8981
|
+
/**
|
|
8982
|
+
* Get pending disclosure requests for dashboard
|
|
8983
|
+
*
|
|
8984
|
+
* Returns disclosure requests waiting for approval/denial.
|
|
8985
|
+
* This is for disclosure REQUESTS, not disclosed transactions.
|
|
8986
|
+
*
|
|
8987
|
+
* @returns Array of pending disclosure requests
|
|
8988
|
+
*/
|
|
8989
|
+
getPendingDisclosures() {
|
|
8990
|
+
return this.getPendingRequests();
|
|
8991
|
+
}
|
|
8992
|
+
/**
|
|
8993
|
+
* Get disclosure history for a specific auditor
|
|
8994
|
+
*
|
|
8995
|
+
* Returns all disclosed transactions that were shared with this auditor,
|
|
8996
|
+
* sorted by disclosure date (most recent first).
|
|
8997
|
+
*
|
|
8998
|
+
* @param auditorId - Auditor ID to get history for
|
|
8999
|
+
* @returns Array of disclosed transactions for this auditor
|
|
9000
|
+
*/
|
|
9001
|
+
getDisclosureHistory(auditorId) {
|
|
9002
|
+
return this.getDisclosedTransactions(auditorId).sort((a, b) => b.disclosedAt - a.disclosedAt);
|
|
9003
|
+
}
|
|
9004
|
+
/**
|
|
9005
|
+
* Get compliance metrics for dashboard
|
|
9006
|
+
*
|
|
9007
|
+
* Calculates key compliance metrics including:
|
|
9008
|
+
* - Total auditors (active + inactive)
|
|
9009
|
+
* - Total disclosures made
|
|
9010
|
+
* - Pending disclosure requests
|
|
9011
|
+
* - Approval rate (approved / total resolved requests)
|
|
9012
|
+
* - Average processing time for disclosure requests
|
|
9013
|
+
*
|
|
9014
|
+
* @returns Compliance metrics object
|
|
9015
|
+
*/
|
|
9016
|
+
getComplianceMetrics() {
|
|
9017
|
+
const allAuditors = this.getAllAuditors();
|
|
9018
|
+
const allDisclosures = this.getDisclosedTransactions();
|
|
9019
|
+
const pendingRequests = this.getPendingRequests();
|
|
9020
|
+
const allRequests = Array.from(this.disclosureRequests.values());
|
|
9021
|
+
const resolvedRequests = allRequests.filter((r) => r.status !== "pending");
|
|
9022
|
+
const approvedRequests = resolvedRequests.filter((r) => r.status === "approved");
|
|
9023
|
+
const approvalRate = resolvedRequests.length > 0 ? approvedRequests.length / resolvedRequests.length : 0;
|
|
9024
|
+
let averageProcessingTime;
|
|
9025
|
+
if (resolvedRequests.length > 0) {
|
|
9026
|
+
const totalProcessingTime = resolvedRequests.reduce((sum, req) => {
|
|
9027
|
+
if (req.resolvedAt) {
|
|
9028
|
+
return sum + (req.resolvedAt - req.requestedAt);
|
|
9029
|
+
}
|
|
9030
|
+
return sum;
|
|
9031
|
+
}, 0);
|
|
9032
|
+
averageProcessingTime = totalProcessingTime / resolvedRequests.length;
|
|
9033
|
+
}
|
|
9034
|
+
return {
|
|
9035
|
+
totalAuditors: allAuditors.length,
|
|
9036
|
+
totalDisclosures: allDisclosures.length,
|
|
9037
|
+
pendingDisclosures: pendingRequests.length,
|
|
9038
|
+
approvalRate,
|
|
9039
|
+
averageProcessingTime
|
|
9040
|
+
};
|
|
9041
|
+
}
|
|
8955
9042
|
// ─── Audit Log ───────────────────────────────────────────────────────────────
|
|
8956
9043
|
/**
|
|
8957
9044
|
* Get audit log entries
|
|
@@ -9150,147 +9237,1767 @@ var ComplianceManager = class _ComplianceManager {
|
|
|
9150
9237
|
highRisk,
|
|
9151
9238
|
flaggedTransactions: flagged
|
|
9152
9239
|
}
|
|
9153
|
-
};
|
|
9154
|
-
if (includeTransactions) {
|
|
9155
|
-
data.transactions = transactions;
|
|
9240
|
+
};
|
|
9241
|
+
if (includeTransactions) {
|
|
9242
|
+
data.transactions = transactions;
|
|
9243
|
+
}
|
|
9244
|
+
return data;
|
|
9245
|
+
}
|
|
9246
|
+
generateCSV(transactions) {
|
|
9247
|
+
const headers = [
|
|
9248
|
+
"Transaction ID",
|
|
9249
|
+
"Disclosure ID",
|
|
9250
|
+
"Type",
|
|
9251
|
+
"Direction",
|
|
9252
|
+
"Token",
|
|
9253
|
+
"Amount",
|
|
9254
|
+
"Sender",
|
|
9255
|
+
"Recipient",
|
|
9256
|
+
"Chain",
|
|
9257
|
+
"Privacy Level",
|
|
9258
|
+
"Timestamp",
|
|
9259
|
+
"TX Hash",
|
|
9260
|
+
"Block",
|
|
9261
|
+
"Risk Score",
|
|
9262
|
+
"Purpose",
|
|
9263
|
+
"Memo"
|
|
9264
|
+
];
|
|
9265
|
+
const rows = transactions.map((tx) => [
|
|
9266
|
+
tx.transactionId,
|
|
9267
|
+
tx.disclosureId,
|
|
9268
|
+
tx.type,
|
|
9269
|
+
tx.direction,
|
|
9270
|
+
tx.token.symbol,
|
|
9271
|
+
tx.amount.toString(),
|
|
9272
|
+
tx.sender,
|
|
9273
|
+
tx.recipient,
|
|
9274
|
+
tx.chain,
|
|
9275
|
+
tx.privacyLevel,
|
|
9276
|
+
new Date(tx.timestamp * 1e3).toISOString(),
|
|
9277
|
+
tx.txHash,
|
|
9278
|
+
tx.blockNumber.toString(),
|
|
9279
|
+
tx.riskScore?.toString() ?? "",
|
|
9280
|
+
tx.purpose ?? "",
|
|
9281
|
+
tx.memo ?? ""
|
|
9282
|
+
]);
|
|
9283
|
+
const escapeForCSV = (val) => {
|
|
9284
|
+
let escaped = val;
|
|
9285
|
+
if (/^[=+\-@|\t]/.test(escaped)) {
|
|
9286
|
+
escaped = `'${escaped}`;
|
|
9287
|
+
}
|
|
9288
|
+
return `"${escaped.replace(/"/g, '""')}"`;
|
|
9289
|
+
};
|
|
9290
|
+
const csvRows = [headers, ...rows].map((row) => row.map(escapeForCSV).join(","));
|
|
9291
|
+
return csvRows.join("\n");
|
|
9292
|
+
}
|
|
9293
|
+
};
|
|
9294
|
+
function generateId(prefix) {
|
|
9295
|
+
return `${prefix}_${(0, import_utils20.bytesToHex)((0, import_utils20.randomBytes)(12))}`;
|
|
9296
|
+
}
|
|
9297
|
+
function validateRegisterAuditorParams(params) {
|
|
9298
|
+
if (!params.organization?.trim()) {
|
|
9299
|
+
throw new ValidationError(
|
|
9300
|
+
"organization is required",
|
|
9301
|
+
"organization",
|
|
9302
|
+
void 0,
|
|
9303
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9304
|
+
);
|
|
9305
|
+
}
|
|
9306
|
+
if (!params.contactName?.trim()) {
|
|
9307
|
+
throw new ValidationError(
|
|
9308
|
+
"contact name is required",
|
|
9309
|
+
"contactName",
|
|
9310
|
+
void 0,
|
|
9311
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9312
|
+
);
|
|
9313
|
+
}
|
|
9314
|
+
if (!params.contactEmail?.trim()) {
|
|
9315
|
+
throw new ValidationError(
|
|
9316
|
+
"contact email is required",
|
|
9317
|
+
"contactEmail",
|
|
9318
|
+
void 0,
|
|
9319
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9320
|
+
);
|
|
9321
|
+
}
|
|
9322
|
+
if (!params.publicKey?.trim()) {
|
|
9323
|
+
throw new ValidationError(
|
|
9324
|
+
"public key is required",
|
|
9325
|
+
"publicKey",
|
|
9326
|
+
void 0,
|
|
9327
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9328
|
+
);
|
|
9329
|
+
}
|
|
9330
|
+
if (!params.scope) {
|
|
9331
|
+
throw new ValidationError(
|
|
9332
|
+
"audit scope is required",
|
|
9333
|
+
"scope",
|
|
9334
|
+
void 0,
|
|
9335
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9336
|
+
);
|
|
9337
|
+
}
|
|
9338
|
+
}
|
|
9339
|
+
function validateReportParams(params) {
|
|
9340
|
+
if (!params.title?.trim()) {
|
|
9341
|
+
throw new ValidationError(
|
|
9342
|
+
"report title is required",
|
|
9343
|
+
"title",
|
|
9344
|
+
void 0,
|
|
9345
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9346
|
+
);
|
|
9347
|
+
}
|
|
9348
|
+
if (!params.type) {
|
|
9349
|
+
throw new ValidationError(
|
|
9350
|
+
"report type is required",
|
|
9351
|
+
"type",
|
|
9352
|
+
void 0,
|
|
9353
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9354
|
+
);
|
|
9355
|
+
}
|
|
9356
|
+
if (!params.format) {
|
|
9357
|
+
throw new ValidationError(
|
|
9358
|
+
"report format is required",
|
|
9359
|
+
"format",
|
|
9360
|
+
void 0,
|
|
9361
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9362
|
+
);
|
|
9363
|
+
}
|
|
9364
|
+
if (params.startDate === void 0 || params.startDate === null || params.endDate === void 0 || params.endDate === null) {
|
|
9365
|
+
throw new ValidationError(
|
|
9366
|
+
"date range is required",
|
|
9367
|
+
"dateRange",
|
|
9368
|
+
void 0,
|
|
9369
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9370
|
+
);
|
|
9371
|
+
}
|
|
9372
|
+
if (params.startDate >= params.endDate) {
|
|
9373
|
+
throw new ValidationError(
|
|
9374
|
+
"start date must be before end date",
|
|
9375
|
+
"dateRange",
|
|
9376
|
+
void 0,
|
|
9377
|
+
"SIP_2001" /* INVALID_INPUT */
|
|
9378
|
+
);
|
|
9379
|
+
}
|
|
9380
|
+
}
|
|
9381
|
+
|
|
9382
|
+
// src/compliance/reports.ts
|
|
9383
|
+
var import_sha25615 = require("@noble/hashes/sha256");
|
|
9384
|
+
var import_utils21 = require("@noble/hashes/utils");
|
|
9385
|
+
|
|
9386
|
+
// src/compliance/pdf.ts
|
|
9387
|
+
function generatePdfReport(report, options = {}) {
|
|
9388
|
+
const {
|
|
9389
|
+
title = "SIP Protocol Audit Report",
|
|
9390
|
+
organization = "",
|
|
9391
|
+
includeTransactions = true,
|
|
9392
|
+
maxTransactions = 100
|
|
9393
|
+
} = options;
|
|
9394
|
+
const content = buildPdfContent(report, {
|
|
9395
|
+
title,
|
|
9396
|
+
organization,
|
|
9397
|
+
includeTransactions,
|
|
9398
|
+
maxTransactions
|
|
9399
|
+
});
|
|
9400
|
+
return generatePdfBinary(content, title);
|
|
9401
|
+
}
|
|
9402
|
+
function buildPdfContent(report, options) {
|
|
9403
|
+
const lines = [];
|
|
9404
|
+
lines.push(options.title);
|
|
9405
|
+
if (options.organization && options.organization.trim() !== "") {
|
|
9406
|
+
lines.push(`Organization: ${options.organization}`);
|
|
9407
|
+
}
|
|
9408
|
+
lines.push(`Report ID: ${report.reportId}`);
|
|
9409
|
+
lines.push(
|
|
9410
|
+
`Generated: ${report.generatedAt.toISOString().replace("T", " ").slice(0, 19)} UTC`
|
|
9411
|
+
);
|
|
9412
|
+
lines.push("");
|
|
9413
|
+
lines.push("Report Period");
|
|
9414
|
+
lines.push(` Start: ${formatDate(report.period.start)}`);
|
|
9415
|
+
lines.push(` End: ${formatDate(report.period.end)}`);
|
|
9416
|
+
lines.push("");
|
|
9417
|
+
lines.push("Summary Statistics");
|
|
9418
|
+
lines.push(` Total Transactions: ${report.summary.transactionCount}`);
|
|
9419
|
+
lines.push(
|
|
9420
|
+
` Total Volume: ${formatBigInt(report.summary.totalVolume)}`
|
|
9421
|
+
);
|
|
9422
|
+
lines.push(
|
|
9423
|
+
` Unique Counterparties: ${report.summary.uniqueCounterparties}`
|
|
9424
|
+
);
|
|
9425
|
+
lines.push("");
|
|
9426
|
+
if (options.includeTransactions && report.transactions.length > 0) {
|
|
9427
|
+
lines.push("Transaction Details");
|
|
9428
|
+
lines.push("");
|
|
9429
|
+
const txToShow = Math.min(
|
|
9430
|
+
report.transactions.length,
|
|
9431
|
+
options.maxTransactions
|
|
9432
|
+
);
|
|
9433
|
+
for (let i = 0; i < txToShow; i++) {
|
|
9434
|
+
const tx = report.transactions[i];
|
|
9435
|
+
lines.push(`Transaction ${i + 1}/${report.transactions.length}`);
|
|
9436
|
+
lines.push(` ID: ${tx.id}`);
|
|
9437
|
+
lines.push(` Sender: ${truncateAddress(tx.sender)}`);
|
|
9438
|
+
lines.push(` Recipient: ${truncateAddress(tx.recipient)}`);
|
|
9439
|
+
lines.push(` Amount: ${formatAmount(tx.amount)}`);
|
|
9440
|
+
lines.push(
|
|
9441
|
+
` Timestamp: ${formatTimestamp(tx.timestamp)}`
|
|
9442
|
+
);
|
|
9443
|
+
if (tx.txHash) {
|
|
9444
|
+
lines.push(` Tx Hash: ${truncateHash(tx.txHash)}`);
|
|
9445
|
+
}
|
|
9446
|
+
if (tx.metadata && Object.keys(tx.metadata).length > 0) {
|
|
9447
|
+
lines.push(` Metadata: ${JSON.stringify(tx.metadata)}`);
|
|
9448
|
+
}
|
|
9449
|
+
lines.push("");
|
|
9450
|
+
}
|
|
9451
|
+
if (report.transactions.length > options.maxTransactions) {
|
|
9452
|
+
lines.push(
|
|
9453
|
+
`... and ${report.transactions.length - options.maxTransactions} more transactions`
|
|
9454
|
+
);
|
|
9455
|
+
lines.push("");
|
|
9456
|
+
}
|
|
9457
|
+
}
|
|
9458
|
+
lines.push("---");
|
|
9459
|
+
lines.push(
|
|
9460
|
+
"This report was generated by SIP Protocol compliance tools."
|
|
9461
|
+
);
|
|
9462
|
+
lines.push("For verification, please contact your compliance officer.");
|
|
9463
|
+
return lines.join("\n");
|
|
9464
|
+
}
|
|
9465
|
+
function generatePdfBinary(content, title) {
|
|
9466
|
+
const objects = [];
|
|
9467
|
+
objects.push(
|
|
9468
|
+
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj"
|
|
9469
|
+
);
|
|
9470
|
+
objects.push(
|
|
9471
|
+
"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj"
|
|
9472
|
+
);
|
|
9473
|
+
objects.push(
|
|
9474
|
+
"3 0 obj\n<< /Type /Page /Parent 2 0 R /Resources 4 0 R /MediaBox [0 0 612 792] /Contents 5 0 R >>\nendobj"
|
|
9475
|
+
);
|
|
9476
|
+
objects.push(
|
|
9477
|
+
"4 0 obj\n<< /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Courier >> >> >>\nendobj"
|
|
9478
|
+
);
|
|
9479
|
+
const contentStream = buildContentStream(content);
|
|
9480
|
+
objects.push(
|
|
9481
|
+
`5 0 obj
|
|
9482
|
+
<< /Length ${contentStream.length} >>
|
|
9483
|
+
stream
|
|
9484
|
+
${contentStream}
|
|
9485
|
+
endstream
|
|
9486
|
+
endobj`
|
|
9487
|
+
);
|
|
9488
|
+
const now = /* @__PURE__ */ new Date();
|
|
9489
|
+
const pdfDate = formatPdfDate(now);
|
|
9490
|
+
objects.push(
|
|
9491
|
+
`6 0 obj
|
|
9492
|
+
<< /Title (${title}) /Author (SIP Protocol) /Creator (SIP SDK) /CreationDate (${pdfDate}) >>
|
|
9493
|
+
endobj`
|
|
9494
|
+
);
|
|
9495
|
+
const pdfParts = [];
|
|
9496
|
+
pdfParts.push("%PDF-1.4\n%\xE2\xE3\xCF\xD3\n");
|
|
9497
|
+
const xrefOffsets = [0];
|
|
9498
|
+
let currentOffset = pdfParts.join("").length;
|
|
9499
|
+
for (const obj of objects) {
|
|
9500
|
+
xrefOffsets.push(currentOffset);
|
|
9501
|
+
pdfParts.push(obj + "\n");
|
|
9502
|
+
currentOffset = pdfParts.join("").length;
|
|
9503
|
+
}
|
|
9504
|
+
const xrefStart = currentOffset;
|
|
9505
|
+
pdfParts.push("xref\n");
|
|
9506
|
+
pdfParts.push(`0 ${xrefOffsets.length}
|
|
9507
|
+
`);
|
|
9508
|
+
for (let i = 0; i < xrefOffsets.length; i++) {
|
|
9509
|
+
if (i === 0) {
|
|
9510
|
+
pdfParts.push("0000000000 65535 f \n");
|
|
9511
|
+
} else {
|
|
9512
|
+
const offset = String(xrefOffsets[i]).padStart(10, "0");
|
|
9513
|
+
pdfParts.push(`${offset} 00000 n
|
|
9514
|
+
`);
|
|
9515
|
+
}
|
|
9516
|
+
}
|
|
9517
|
+
pdfParts.push("trailer\n");
|
|
9518
|
+
pdfParts.push(
|
|
9519
|
+
`<< /Size ${xrefOffsets.length} /Root 1 0 R /Info 6 0 R >>
|
|
9520
|
+
`
|
|
9521
|
+
);
|
|
9522
|
+
pdfParts.push("startxref\n");
|
|
9523
|
+
pdfParts.push(`${xrefStart}
|
|
9524
|
+
`);
|
|
9525
|
+
pdfParts.push("%%EOF");
|
|
9526
|
+
const pdfString = pdfParts.join("");
|
|
9527
|
+
const encoder = new TextEncoder();
|
|
9528
|
+
return encoder.encode(pdfString);
|
|
9529
|
+
}
|
|
9530
|
+
function buildContentStream(text) {
|
|
9531
|
+
const lines = text.split("\n");
|
|
9532
|
+
const commands = [];
|
|
9533
|
+
commands.push("BT");
|
|
9534
|
+
commands.push("/F1 10 Tf");
|
|
9535
|
+
commands.push("12 TL");
|
|
9536
|
+
let y = 742;
|
|
9537
|
+
for (const line of lines) {
|
|
9538
|
+
commands.push(`50 ${y} Td`);
|
|
9539
|
+
const escaped = escapePdfString(line);
|
|
9540
|
+
commands.push(`(${escaped}) Tj`);
|
|
9541
|
+
y -= 12;
|
|
9542
|
+
if (y < 50) {
|
|
9543
|
+
break;
|
|
9544
|
+
}
|
|
9545
|
+
}
|
|
9546
|
+
commands.push("ET");
|
|
9547
|
+
return commands.join("\n");
|
|
9548
|
+
}
|
|
9549
|
+
function escapePdfString(str) {
|
|
9550
|
+
return str.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)").replace(/\r/g, "\\r").replace(/\n/g, "\\n");
|
|
9551
|
+
}
|
|
9552
|
+
function formatPdfDate(date) {
|
|
9553
|
+
const year = date.getUTCFullYear();
|
|
9554
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
9555
|
+
const day = String(date.getUTCDate()).padStart(2, "0");
|
|
9556
|
+
const hour = String(date.getUTCHours()).padStart(2, "0");
|
|
9557
|
+
const minute = String(date.getUTCMinutes()).padStart(2, "0");
|
|
9558
|
+
const second = String(date.getUTCSeconds()).padStart(2, "0");
|
|
9559
|
+
return `D:${year}${month}${day}${hour}${minute}${second}Z`;
|
|
9560
|
+
}
|
|
9561
|
+
function formatDate(date) {
|
|
9562
|
+
return date.toISOString().split("T")[0];
|
|
9563
|
+
}
|
|
9564
|
+
function formatBigInt(value) {
|
|
9565
|
+
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
9566
|
+
}
|
|
9567
|
+
function formatAmount(amount) {
|
|
9568
|
+
try {
|
|
9569
|
+
const num = BigInt(amount);
|
|
9570
|
+
return formatBigInt(num);
|
|
9571
|
+
} catch {
|
|
9572
|
+
return amount;
|
|
9573
|
+
}
|
|
9574
|
+
}
|
|
9575
|
+
function formatTimestamp(timestamp) {
|
|
9576
|
+
const date = new Date(timestamp * 1e3);
|
|
9577
|
+
return date.toISOString().replace("T", " ").slice(0, 19) + " UTC";
|
|
9578
|
+
}
|
|
9579
|
+
function truncateAddress(address) {
|
|
9580
|
+
if (address.length <= 12) {
|
|
9581
|
+
return address;
|
|
9582
|
+
}
|
|
9583
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
9584
|
+
}
|
|
9585
|
+
function truncateHash(hash2) {
|
|
9586
|
+
if (hash2.length <= 16) {
|
|
9587
|
+
return hash2;
|
|
9588
|
+
}
|
|
9589
|
+
return `${hash2.slice(0, 8)}...${hash2.slice(-8)}`;
|
|
9590
|
+
}
|
|
9591
|
+
|
|
9592
|
+
// src/compliance/reports.ts
|
|
9593
|
+
var ComplianceReporter = class {
|
|
9594
|
+
/**
|
|
9595
|
+
* Generate an audit report from encrypted transactions
|
|
9596
|
+
*
|
|
9597
|
+
* Decrypts transactions using the provided viewing key, filters by date range,
|
|
9598
|
+
* and generates a comprehensive report with summary statistics.
|
|
9599
|
+
*
|
|
9600
|
+
* @param params - Report generation parameters
|
|
9601
|
+
* @returns Audit report with decrypted transactions and statistics
|
|
9602
|
+
* @throws {ValidationError} If parameters are invalid
|
|
9603
|
+
* @throws {CryptoError} If decryption fails
|
|
9604
|
+
*/
|
|
9605
|
+
async generateAuditReport(params) {
|
|
9606
|
+
this.validateParams(params);
|
|
9607
|
+
const viewingKey = this.normalizeViewingKey(params.viewingKey);
|
|
9608
|
+
const decryptedTransactions = this.decryptTransactions(
|
|
9609
|
+
params.transactions,
|
|
9610
|
+
viewingKey,
|
|
9611
|
+
params.startDate,
|
|
9612
|
+
params.endDate
|
|
9613
|
+
);
|
|
9614
|
+
const summary = this.calculateSummary(decryptedTransactions);
|
|
9615
|
+
const period = this.determinePeriod(
|
|
9616
|
+
decryptedTransactions,
|
|
9617
|
+
params.startDate,
|
|
9618
|
+
params.endDate
|
|
9619
|
+
);
|
|
9620
|
+
const reportId = `audit_${generateRandomBytes(16).slice(2)}`;
|
|
9621
|
+
return {
|
|
9622
|
+
reportId,
|
|
9623
|
+
generatedAt: /* @__PURE__ */ new Date(),
|
|
9624
|
+
period,
|
|
9625
|
+
transactions: decryptedTransactions,
|
|
9626
|
+
summary
|
|
9627
|
+
};
|
|
9628
|
+
}
|
|
9629
|
+
/**
|
|
9630
|
+
* Validate report generation parameters
|
|
9631
|
+
*/
|
|
9632
|
+
validateParams(params) {
|
|
9633
|
+
if (!params.viewingKey) {
|
|
9634
|
+
throw new ValidationError(
|
|
9635
|
+
"viewingKey is required",
|
|
9636
|
+
"viewingKey",
|
|
9637
|
+
void 0,
|
|
9638
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9639
|
+
);
|
|
9640
|
+
}
|
|
9641
|
+
if (!params.transactions) {
|
|
9642
|
+
throw new ValidationError(
|
|
9643
|
+
"transactions array is required",
|
|
9644
|
+
"transactions",
|
|
9645
|
+
void 0,
|
|
9646
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9647
|
+
);
|
|
9648
|
+
}
|
|
9649
|
+
if (!Array.isArray(params.transactions)) {
|
|
9650
|
+
throw new ValidationError(
|
|
9651
|
+
"transactions must be an array",
|
|
9652
|
+
"transactions",
|
|
9653
|
+
{ received: typeof params.transactions },
|
|
9654
|
+
"SIP_2001" /* INVALID_INPUT */
|
|
9655
|
+
);
|
|
9656
|
+
}
|
|
9657
|
+
if (params.format !== "json" && params.format !== "pdf") {
|
|
9658
|
+
throw new ValidationError(
|
|
9659
|
+
"only JSON and PDF formats are supported",
|
|
9660
|
+
"format",
|
|
9661
|
+
{ received: params.format },
|
|
9662
|
+
"SIP_2001" /* INVALID_INPUT */
|
|
9663
|
+
);
|
|
9664
|
+
}
|
|
9665
|
+
if (params.startDate && params.endDate) {
|
|
9666
|
+
if (params.startDate > params.endDate) {
|
|
9667
|
+
throw new ValidationError(
|
|
9668
|
+
"startDate must be before endDate",
|
|
9669
|
+
"startDate",
|
|
9670
|
+
{
|
|
9671
|
+
startDate: params.startDate.toISOString(),
|
|
9672
|
+
endDate: params.endDate.toISOString()
|
|
9673
|
+
},
|
|
9674
|
+
"SIP_2001" /* INVALID_INPUT */
|
|
9675
|
+
);
|
|
9676
|
+
}
|
|
9677
|
+
}
|
|
9678
|
+
}
|
|
9679
|
+
/**
|
|
9680
|
+
* Normalize viewing key to ViewingKey object
|
|
9681
|
+
*/
|
|
9682
|
+
normalizeViewingKey(viewingKey) {
|
|
9683
|
+
if (typeof viewingKey === "string") {
|
|
9684
|
+
const keyHex = viewingKey.startsWith("0x") ? viewingKey.slice(2) : viewingKey;
|
|
9685
|
+
const keyBytes = (0, import_utils21.hexToBytes)(keyHex);
|
|
9686
|
+
const hashBytes = (0, import_sha25615.sha256)(keyBytes);
|
|
9687
|
+
return {
|
|
9688
|
+
key: `0x${keyHex}`,
|
|
9689
|
+
path: "m/0",
|
|
9690
|
+
hash: `0x${(0, import_utils21.bytesToHex)(hashBytes)}`
|
|
9691
|
+
};
|
|
9692
|
+
}
|
|
9693
|
+
return viewingKey;
|
|
9694
|
+
}
|
|
9695
|
+
/**
|
|
9696
|
+
* Decrypt transactions and filter by date range
|
|
9697
|
+
*/
|
|
9698
|
+
decryptTransactions(encrypted, viewingKey, startDate, endDate) {
|
|
9699
|
+
const decrypted = [];
|
|
9700
|
+
const errors = [];
|
|
9701
|
+
for (let i = 0; i < encrypted.length; i++) {
|
|
9702
|
+
try {
|
|
9703
|
+
const txData = decryptWithViewing(encrypted[i], viewingKey);
|
|
9704
|
+
if (startDate || endDate) {
|
|
9705
|
+
const txDate = new Date(txData.timestamp * 1e3);
|
|
9706
|
+
if (startDate && txDate < startDate) {
|
|
9707
|
+
continue;
|
|
9708
|
+
}
|
|
9709
|
+
if (endDate && txDate > endDate) {
|
|
9710
|
+
continue;
|
|
9711
|
+
}
|
|
9712
|
+
}
|
|
9713
|
+
decrypted.push({
|
|
9714
|
+
id: `tx_${i}`,
|
|
9715
|
+
sender: txData.sender,
|
|
9716
|
+
recipient: txData.recipient,
|
|
9717
|
+
amount: txData.amount,
|
|
9718
|
+
timestamp: txData.timestamp
|
|
9719
|
+
});
|
|
9720
|
+
} catch (error) {
|
|
9721
|
+
errors.push({ index: i, error });
|
|
9722
|
+
}
|
|
9723
|
+
}
|
|
9724
|
+
if (decrypted.length === 0 && encrypted.length > 0) {
|
|
9725
|
+
throw new CryptoError(
|
|
9726
|
+
`Failed to decrypt any transactions. First error: ${errors[0]?.error.message}`,
|
|
9727
|
+
"SIP_3002" /* DECRYPTION_FAILED */,
|
|
9728
|
+
{
|
|
9729
|
+
context: {
|
|
9730
|
+
totalTransactions: encrypted.length,
|
|
9731
|
+
failedCount: errors.length
|
|
9732
|
+
}
|
|
9733
|
+
}
|
|
9734
|
+
);
|
|
9735
|
+
}
|
|
9736
|
+
return decrypted;
|
|
9737
|
+
}
|
|
9738
|
+
/**
|
|
9739
|
+
* Calculate summary statistics
|
|
9740
|
+
*/
|
|
9741
|
+
calculateSummary(transactions) {
|
|
9742
|
+
let totalVolume = 0n;
|
|
9743
|
+
const counterparties = /* @__PURE__ */ new Set();
|
|
9744
|
+
for (const tx of transactions) {
|
|
9745
|
+
try {
|
|
9746
|
+
const amount = BigInt(tx.amount);
|
|
9747
|
+
totalVolume += amount;
|
|
9748
|
+
} catch (error) {
|
|
9749
|
+
console.warn(`Skipping invalid amount in transaction ${tx.id}: ${tx.amount}`);
|
|
9750
|
+
}
|
|
9751
|
+
counterparties.add(tx.sender);
|
|
9752
|
+
counterparties.add(tx.recipient);
|
|
9753
|
+
}
|
|
9754
|
+
return {
|
|
9755
|
+
totalVolume,
|
|
9756
|
+
transactionCount: transactions.length,
|
|
9757
|
+
uniqueCounterparties: counterparties.size
|
|
9758
|
+
};
|
|
9759
|
+
}
|
|
9760
|
+
/**
|
|
9761
|
+
* Determine report period from transactions and params
|
|
9762
|
+
*/
|
|
9763
|
+
determinePeriod(transactions, startDate, endDate) {
|
|
9764
|
+
if (startDate && endDate) {
|
|
9765
|
+
return { start: startDate, end: endDate };
|
|
9766
|
+
}
|
|
9767
|
+
if (transactions.length === 0) {
|
|
9768
|
+
const now = /* @__PURE__ */ new Date();
|
|
9769
|
+
return { start: now, end: now };
|
|
9770
|
+
}
|
|
9771
|
+
let minTimestamp = transactions[0].timestamp;
|
|
9772
|
+
let maxTimestamp = transactions[0].timestamp;
|
|
9773
|
+
for (const tx of transactions) {
|
|
9774
|
+
if (tx.timestamp < minTimestamp) {
|
|
9775
|
+
minTimestamp = tx.timestamp;
|
|
9776
|
+
}
|
|
9777
|
+
if (tx.timestamp > maxTimestamp) {
|
|
9778
|
+
maxTimestamp = tx.timestamp;
|
|
9779
|
+
}
|
|
9780
|
+
}
|
|
9781
|
+
return {
|
|
9782
|
+
start: startDate || new Date(minTimestamp * 1e3),
|
|
9783
|
+
end: endDate || new Date(maxTimestamp * 1e3)
|
|
9784
|
+
};
|
|
9785
|
+
}
|
|
9786
|
+
/**
|
|
9787
|
+
* Export audit report to PDF format
|
|
9788
|
+
*
|
|
9789
|
+
* Generates a professionally formatted PDF document from an audit report.
|
|
9790
|
+
* Works in both Node.js and browser environments.
|
|
9791
|
+
*
|
|
9792
|
+
* @param report - The audit report to export
|
|
9793
|
+
* @param options - PDF export options
|
|
9794
|
+
* @returns PDF document as Uint8Array
|
|
9795
|
+
*
|
|
9796
|
+
* @example
|
|
9797
|
+
* ```typescript
|
|
9798
|
+
* const reporter = new ComplianceReporter()
|
|
9799
|
+
* const report = await reporter.generateAuditReport({...})
|
|
9800
|
+
*
|
|
9801
|
+
* const pdfBytes = reporter.exportToPdf(report, {
|
|
9802
|
+
* title: 'Q1 2025 Audit Report',
|
|
9803
|
+
* organization: 'ACME Corp',
|
|
9804
|
+
* })
|
|
9805
|
+
*
|
|
9806
|
+
* // Save to file (Node.js)
|
|
9807
|
+
* fs.writeFileSync('report.pdf', pdfBytes)
|
|
9808
|
+
* ```
|
|
9809
|
+
*/
|
|
9810
|
+
exportToPdf(report, options) {
|
|
9811
|
+
return generatePdfReport(report, options);
|
|
9812
|
+
}
|
|
9813
|
+
/**
|
|
9814
|
+
* Export transactions to regulatory compliance formats
|
|
9815
|
+
*
|
|
9816
|
+
* Decrypts and exports transactions in formats required by regulators:
|
|
9817
|
+
* - FATF: Financial Action Task Force Travel Rule format
|
|
9818
|
+
* - FINCEN: FinCEN Suspicious Activity Report (SAR) format
|
|
9819
|
+
* - CSV: Generic comma-separated values format
|
|
9820
|
+
*
|
|
9821
|
+
* @param params - Export parameters
|
|
9822
|
+
* @returns Regulatory export in the specified format
|
|
9823
|
+
* @throws {ValidationError} If parameters are invalid
|
|
9824
|
+
* @throws {CryptoError} If decryption fails
|
|
9825
|
+
*
|
|
9826
|
+
* @example
|
|
9827
|
+
* ```typescript
|
|
9828
|
+
* const reporter = new ComplianceReporter()
|
|
9829
|
+
*
|
|
9830
|
+
* // FATF Travel Rule export
|
|
9831
|
+
* const fatfExport = await reporter.exportForRegulator({
|
|
9832
|
+
* viewingKey: myViewingKey,
|
|
9833
|
+
* transactions: encryptedTxs,
|
|
9834
|
+
* jurisdiction: 'EU',
|
|
9835
|
+
* format: 'FATF',
|
|
9836
|
+
* currency: 'EUR',
|
|
9837
|
+
* })
|
|
9838
|
+
*
|
|
9839
|
+
* // FINCEN SAR export (US only)
|
|
9840
|
+
* const fincenExport = await reporter.exportForRegulator({
|
|
9841
|
+
* viewingKey: myViewingKey,
|
|
9842
|
+
* transactions: suspiciousTxs,
|
|
9843
|
+
* jurisdiction: 'US',
|
|
9844
|
+
* format: 'FINCEN',
|
|
9845
|
+
* })
|
|
9846
|
+
*
|
|
9847
|
+
* // CSV export
|
|
9848
|
+
* const csvExport = await reporter.exportForRegulator({
|
|
9849
|
+
* viewingKey: myViewingKey,
|
|
9850
|
+
* transactions: encryptedTxs,
|
|
9851
|
+
* jurisdiction: 'SG',
|
|
9852
|
+
* format: 'CSV',
|
|
9853
|
+
* })
|
|
9854
|
+
* ```
|
|
9855
|
+
*/
|
|
9856
|
+
async exportForRegulator(params) {
|
|
9857
|
+
this.validateRegulatoryParams(params);
|
|
9858
|
+
const viewingKey = this.normalizeViewingKey(params.viewingKey);
|
|
9859
|
+
const decryptedTransactions = this.decryptTransactions(
|
|
9860
|
+
params.transactions,
|
|
9861
|
+
viewingKey,
|
|
9862
|
+
params.startDate,
|
|
9863
|
+
params.endDate
|
|
9864
|
+
);
|
|
9865
|
+
const reportId = `reg_${generateRandomBytes(16).slice(2)}`;
|
|
9866
|
+
switch (params.format) {
|
|
9867
|
+
case "FATF":
|
|
9868
|
+
return this.exportToFATF(
|
|
9869
|
+
reportId,
|
|
9870
|
+
decryptedTransactions,
|
|
9871
|
+
params.jurisdiction,
|
|
9872
|
+
params.currency || "USD"
|
|
9873
|
+
);
|
|
9874
|
+
case "FINCEN":
|
|
9875
|
+
return this.exportToFINCEN(
|
|
9876
|
+
reportId,
|
|
9877
|
+
decryptedTransactions,
|
|
9878
|
+
params.startDate,
|
|
9879
|
+
params.endDate,
|
|
9880
|
+
params.currency || "USD"
|
|
9881
|
+
);
|
|
9882
|
+
case "CSV":
|
|
9883
|
+
return this.exportToCSV(
|
|
9884
|
+
reportId,
|
|
9885
|
+
decryptedTransactions,
|
|
9886
|
+
params.jurisdiction,
|
|
9887
|
+
params.currency || "USD"
|
|
9888
|
+
);
|
|
9889
|
+
default:
|
|
9890
|
+
throw new ValidationError(
|
|
9891
|
+
`unsupported format: ${params.format}`,
|
|
9892
|
+
"format",
|
|
9893
|
+
{ received: params.format },
|
|
9894
|
+
"SIP_2001" /* INVALID_INPUT */
|
|
9895
|
+
);
|
|
9896
|
+
}
|
|
9897
|
+
}
|
|
9898
|
+
/**
|
|
9899
|
+
* Validate regulatory export parameters
|
|
9900
|
+
*/
|
|
9901
|
+
validateRegulatoryParams(params) {
|
|
9902
|
+
if (!params.viewingKey) {
|
|
9903
|
+
throw new ValidationError(
|
|
9904
|
+
"viewingKey is required",
|
|
9905
|
+
"viewingKey",
|
|
9906
|
+
void 0,
|
|
9907
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9908
|
+
);
|
|
9909
|
+
}
|
|
9910
|
+
if (!params.transactions) {
|
|
9911
|
+
throw new ValidationError(
|
|
9912
|
+
"transactions array is required",
|
|
9913
|
+
"transactions",
|
|
9914
|
+
void 0,
|
|
9915
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9916
|
+
);
|
|
9917
|
+
}
|
|
9918
|
+
if (!Array.isArray(params.transactions)) {
|
|
9919
|
+
throw new ValidationError(
|
|
9920
|
+
"transactions must be an array",
|
|
9921
|
+
"transactions",
|
|
9922
|
+
{ received: typeof params.transactions },
|
|
9923
|
+
"SIP_2001" /* INVALID_INPUT */
|
|
9924
|
+
);
|
|
9925
|
+
}
|
|
9926
|
+
if (!params.jurisdiction) {
|
|
9927
|
+
throw new ValidationError(
|
|
9928
|
+
"jurisdiction is required",
|
|
9929
|
+
"jurisdiction",
|
|
9930
|
+
void 0,
|
|
9931
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9932
|
+
);
|
|
9933
|
+
}
|
|
9934
|
+
const validJurisdictions = ["US", "EU", "UK", "SG"];
|
|
9935
|
+
if (!validJurisdictions.includes(params.jurisdiction)) {
|
|
9936
|
+
throw new ValidationError(
|
|
9937
|
+
`invalid jurisdiction. Must be one of: ${validJurisdictions.join(", ")}`,
|
|
9938
|
+
"jurisdiction",
|
|
9939
|
+
{ received: params.jurisdiction },
|
|
9940
|
+
"SIP_2001" /* INVALID_INPUT */
|
|
9941
|
+
);
|
|
9942
|
+
}
|
|
9943
|
+
if (!params.format) {
|
|
9944
|
+
throw new ValidationError(
|
|
9945
|
+
"format is required",
|
|
9946
|
+
"format",
|
|
9947
|
+
void 0,
|
|
9948
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
9949
|
+
);
|
|
9950
|
+
}
|
|
9951
|
+
const validFormats = ["FATF", "FINCEN", "CSV"];
|
|
9952
|
+
if (!validFormats.includes(params.format)) {
|
|
9953
|
+
throw new ValidationError(
|
|
9954
|
+
`invalid format. Must be one of: ${validFormats.join(", ")}`,
|
|
9955
|
+
"format",
|
|
9956
|
+
{ received: params.format },
|
|
9957
|
+
"SIP_2001" /* INVALID_INPUT */
|
|
9958
|
+
);
|
|
9959
|
+
}
|
|
9960
|
+
if (params.format === "FINCEN" && params.jurisdiction !== "US") {
|
|
9961
|
+
throw new ValidationError(
|
|
9962
|
+
"FINCEN format is only available for US jurisdiction",
|
|
9963
|
+
"format",
|
|
9964
|
+
{ jurisdiction: params.jurisdiction, format: params.format },
|
|
9965
|
+
"SIP_2001" /* INVALID_INPUT */
|
|
9966
|
+
);
|
|
9967
|
+
}
|
|
9968
|
+
if (params.startDate && params.endDate) {
|
|
9969
|
+
if (params.startDate > params.endDate) {
|
|
9970
|
+
throw new ValidationError(
|
|
9971
|
+
"startDate must be before endDate",
|
|
9972
|
+
"startDate",
|
|
9973
|
+
{
|
|
9974
|
+
startDate: params.startDate.toISOString(),
|
|
9975
|
+
endDate: params.endDate.toISOString()
|
|
9976
|
+
},
|
|
9977
|
+
"SIP_2001" /* INVALID_INPUT */
|
|
9978
|
+
);
|
|
9979
|
+
}
|
|
9980
|
+
}
|
|
9981
|
+
}
|
|
9982
|
+
/**
|
|
9983
|
+
* Export to FATF Travel Rule format
|
|
9984
|
+
*/
|
|
9985
|
+
exportToFATF(reportId, transactions, jurisdiction, currency) {
|
|
9986
|
+
const fatfTransactions = transactions.map((tx) => ({
|
|
9987
|
+
originatorAccount: tx.sender,
|
|
9988
|
+
beneficiaryAccount: tx.recipient,
|
|
9989
|
+
amount: tx.amount,
|
|
9990
|
+
currency,
|
|
9991
|
+
transactionRef: tx.id,
|
|
9992
|
+
timestamp: new Date(tx.timestamp * 1e3).toISOString()
|
|
9993
|
+
}));
|
|
9994
|
+
return {
|
|
9995
|
+
reportId,
|
|
9996
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9997
|
+
jurisdiction,
|
|
9998
|
+
transactions: fatfTransactions
|
|
9999
|
+
};
|
|
10000
|
+
}
|
|
10001
|
+
/**
|
|
10002
|
+
* Export to FINCEN SAR format
|
|
10003
|
+
*/
|
|
10004
|
+
exportToFINCEN(reportId, transactions, startDate, endDate, currency = "USD") {
|
|
10005
|
+
let totalAmount = 0n;
|
|
10006
|
+
for (const tx of transactions) {
|
|
10007
|
+
try {
|
|
10008
|
+
totalAmount += BigInt(tx.amount);
|
|
10009
|
+
} catch (error) {
|
|
10010
|
+
}
|
|
10011
|
+
}
|
|
10012
|
+
const period = this.determinePeriod(transactions, startDate, endDate);
|
|
10013
|
+
const fincenTransactions = transactions.map((tx) => ({
|
|
10014
|
+
transactionDate: new Date(tx.timestamp * 1e3).toISOString(),
|
|
10015
|
+
amount: tx.amount,
|
|
10016
|
+
currency,
|
|
10017
|
+
narrativeSummary: `Transfer from ${tx.sender} to ${tx.recipient}`,
|
|
10018
|
+
transactionRef: tx.id,
|
|
10019
|
+
parties: {
|
|
10020
|
+
sender: tx.sender,
|
|
10021
|
+
recipient: tx.recipient
|
|
10022
|
+
}
|
|
10023
|
+
}));
|
|
10024
|
+
return {
|
|
10025
|
+
reportId,
|
|
10026
|
+
filingType: "SAR",
|
|
10027
|
+
reportDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10028
|
+
jurisdiction: "US",
|
|
10029
|
+
summary: {
|
|
10030
|
+
transactionCount: transactions.length,
|
|
10031
|
+
totalAmount: totalAmount.toString(),
|
|
10032
|
+
period: {
|
|
10033
|
+
start: period.start.toISOString(),
|
|
10034
|
+
end: period.end.toISOString()
|
|
10035
|
+
}
|
|
10036
|
+
},
|
|
10037
|
+
transactions: fincenTransactions
|
|
10038
|
+
};
|
|
10039
|
+
}
|
|
10040
|
+
/**
|
|
10041
|
+
* Export to CSV format
|
|
10042
|
+
*/
|
|
10043
|
+
exportToCSV(reportId, transactions, jurisdiction, currency) {
|
|
10044
|
+
const headers = [
|
|
10045
|
+
"Transaction ID",
|
|
10046
|
+
"Timestamp",
|
|
10047
|
+
"Sender",
|
|
10048
|
+
"Recipient",
|
|
10049
|
+
"Amount",
|
|
10050
|
+
"Currency"
|
|
10051
|
+
];
|
|
10052
|
+
const rows = transactions.map((tx) => [
|
|
10053
|
+
tx.id,
|
|
10054
|
+
new Date(tx.timestamp * 1e3).toISOString(),
|
|
10055
|
+
tx.sender,
|
|
10056
|
+
tx.recipient,
|
|
10057
|
+
tx.amount,
|
|
10058
|
+
currency
|
|
10059
|
+
]);
|
|
10060
|
+
return {
|
|
10061
|
+
reportId,
|
|
10062
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10063
|
+
jurisdiction,
|
|
10064
|
+
headers,
|
|
10065
|
+
rows
|
|
10066
|
+
};
|
|
10067
|
+
}
|
|
10068
|
+
};
|
|
10069
|
+
|
|
10070
|
+
// src/compliance/conditional.ts
|
|
10071
|
+
var import_sha25616 = require("@noble/hashes/sha256");
|
|
10072
|
+
var import_utils22 = require("@noble/hashes/utils");
|
|
10073
|
+
var import_chacha3 = require("@noble/ciphers/chacha.js");
|
|
10074
|
+
var ConditionalDisclosure = class {
|
|
10075
|
+
/**
|
|
10076
|
+
* Create a time-locked disclosure
|
|
10077
|
+
*
|
|
10078
|
+
* Encrypts the viewing key with a deterministic key derived from
|
|
10079
|
+
* the commitment and reveal time. The key can only be reconstructed
|
|
10080
|
+
* after the specified time/block height.
|
|
10081
|
+
*
|
|
10082
|
+
* @param params - Time-lock parameters
|
|
10083
|
+
* @returns Time-lock result with encrypted key
|
|
10084
|
+
* @throws {ValidationError} If parameters are invalid
|
|
10085
|
+
* @throws {CryptoError} If encryption fails
|
|
10086
|
+
*/
|
|
10087
|
+
createTimeLocked(params) {
|
|
10088
|
+
if (!params.viewingKey || !params.viewingKey.startsWith("0x")) {
|
|
10089
|
+
throw new ValidationError(
|
|
10090
|
+
"Invalid viewing key format",
|
|
10091
|
+
"viewingKey",
|
|
10092
|
+
{ viewingKey: params.viewingKey },
|
|
10093
|
+
"SIP_2006" /* INVALID_KEY */
|
|
10094
|
+
);
|
|
10095
|
+
}
|
|
10096
|
+
if (!params.commitment || !params.commitment.startsWith("0x")) {
|
|
10097
|
+
throw new ValidationError(
|
|
10098
|
+
"Invalid commitment format",
|
|
10099
|
+
"commitment",
|
|
10100
|
+
{ commitment: params.commitment },
|
|
10101
|
+
"SIP_3010" /* INVALID_COMMITMENT */
|
|
10102
|
+
);
|
|
10103
|
+
}
|
|
10104
|
+
let revealAfterSeconds;
|
|
10105
|
+
let type;
|
|
10106
|
+
if (params.revealAfter instanceof Date) {
|
|
10107
|
+
revealAfterSeconds = Math.floor(params.revealAfter.getTime() / 1e3);
|
|
10108
|
+
type = "timestamp";
|
|
10109
|
+
} else if (typeof params.revealAfter === "number") {
|
|
10110
|
+
if (params.revealAfter > 1e10) {
|
|
10111
|
+
revealAfterSeconds = Math.floor(params.revealAfter / 1e3);
|
|
10112
|
+
type = "timestamp";
|
|
10113
|
+
} else {
|
|
10114
|
+
revealAfterSeconds = params.revealAfter;
|
|
10115
|
+
type = "blockheight";
|
|
10116
|
+
}
|
|
10117
|
+
if (revealAfterSeconds <= 0) {
|
|
10118
|
+
throw new ValidationError(
|
|
10119
|
+
"Reveal time/block height must be positive",
|
|
10120
|
+
"revealAfter",
|
|
10121
|
+
{ revealAfter: revealAfterSeconds },
|
|
10122
|
+
"SIP_3011" /* INVALID_TIME_LOCK */
|
|
10123
|
+
);
|
|
10124
|
+
}
|
|
10125
|
+
} else {
|
|
10126
|
+
throw new ValidationError(
|
|
10127
|
+
"Invalid revealAfter type (must be Date or number)",
|
|
10128
|
+
"revealAfter",
|
|
10129
|
+
{ revealAfter: params.revealAfter },
|
|
10130
|
+
"SIP_3011" /* INVALID_TIME_LOCK */
|
|
10131
|
+
);
|
|
10132
|
+
}
|
|
10133
|
+
try {
|
|
10134
|
+
const encryptionKey = this._deriveEncryptionKey(
|
|
10135
|
+
params.commitment,
|
|
10136
|
+
revealAfterSeconds
|
|
10137
|
+
);
|
|
10138
|
+
const nonce = (0, import_utils22.randomBytes)(24);
|
|
10139
|
+
const viewingKeyBytes = (0, import_utils22.hexToBytes)(params.viewingKey.slice(2));
|
|
10140
|
+
const cipher = (0, import_chacha3.xchacha20poly1305)(encryptionKey, nonce);
|
|
10141
|
+
const encryptedKey = cipher.encrypt(viewingKeyBytes);
|
|
10142
|
+
const commitmentData = new Uint8Array([
|
|
10143
|
+
...viewingKeyBytes,
|
|
10144
|
+
...this._numberToBytes(revealAfterSeconds)
|
|
10145
|
+
]);
|
|
10146
|
+
const commitmentHash = (0, import_sha25616.sha256)(commitmentData);
|
|
10147
|
+
return {
|
|
10148
|
+
encryptedKey: "0x" + (0, import_utils22.bytesToHex)(encryptedKey),
|
|
10149
|
+
nonce: "0x" + (0, import_utils22.bytesToHex)(nonce),
|
|
10150
|
+
revealAfter: revealAfterSeconds,
|
|
10151
|
+
verificationCommitment: "0x" + (0, import_utils22.bytesToHex)(commitmentHash),
|
|
10152
|
+
encryptionCommitment: params.commitment,
|
|
10153
|
+
type
|
|
10154
|
+
};
|
|
10155
|
+
} catch (error) {
|
|
10156
|
+
if (error instanceof ValidationError) {
|
|
10157
|
+
throw error;
|
|
10158
|
+
}
|
|
10159
|
+
throw new CryptoError(
|
|
10160
|
+
"Failed to create time-locked disclosure",
|
|
10161
|
+
"SIP_3001" /* ENCRYPTION_FAILED */,
|
|
10162
|
+
{
|
|
10163
|
+
cause: error instanceof Error ? error : void 0,
|
|
10164
|
+
operation: "createTimeLocked"
|
|
10165
|
+
}
|
|
10166
|
+
);
|
|
10167
|
+
}
|
|
10168
|
+
}
|
|
10169
|
+
/**
|
|
10170
|
+
* Check if a time-lock is unlocked and retrieve the viewing key
|
|
10171
|
+
*
|
|
10172
|
+
* @param timeLock - Time-lock result to check
|
|
10173
|
+
* @param currentTimeOrBlock - Current time (Date/number) or block height (number)
|
|
10174
|
+
* @returns Unlock result with viewing key if unlocked
|
|
10175
|
+
* @throws {ValidationError} If time-lock format is invalid
|
|
10176
|
+
* @throws {CryptoError} If decryption fails
|
|
10177
|
+
*/
|
|
10178
|
+
checkUnlocked(timeLock, currentTimeOrBlock) {
|
|
10179
|
+
if (!timeLock.encryptedKey || !timeLock.encryptedKey.startsWith("0x")) {
|
|
10180
|
+
throw new ValidationError(
|
|
10181
|
+
"Invalid encrypted key format",
|
|
10182
|
+
"encryptedKey",
|
|
10183
|
+
{ encryptedKey: timeLock.encryptedKey },
|
|
10184
|
+
"SIP_3009" /* INVALID_ENCRYPTED_DATA */
|
|
10185
|
+
);
|
|
10186
|
+
}
|
|
10187
|
+
if (!timeLock.nonce || !timeLock.nonce.startsWith("0x")) {
|
|
10188
|
+
throw new ValidationError(
|
|
10189
|
+
"Invalid nonce format",
|
|
10190
|
+
"nonce",
|
|
10191
|
+
{ nonce: timeLock.nonce },
|
|
10192
|
+
"SIP_3009" /* INVALID_ENCRYPTED_DATA */
|
|
10193
|
+
);
|
|
10194
|
+
}
|
|
10195
|
+
if (!timeLock.verificationCommitment || !timeLock.verificationCommitment.startsWith("0x")) {
|
|
10196
|
+
throw new ValidationError(
|
|
10197
|
+
"Invalid verification commitment format",
|
|
10198
|
+
"verificationCommitment",
|
|
10199
|
+
{ commitment: timeLock.verificationCommitment },
|
|
10200
|
+
"SIP_3010" /* INVALID_COMMITMENT */
|
|
10201
|
+
);
|
|
10202
|
+
}
|
|
10203
|
+
if (!timeLock.encryptionCommitment || !timeLock.encryptionCommitment.startsWith("0x")) {
|
|
10204
|
+
throw new ValidationError(
|
|
10205
|
+
"Invalid encryption commitment format",
|
|
10206
|
+
"encryptionCommitment",
|
|
10207
|
+
{ commitment: timeLock.encryptionCommitment },
|
|
10208
|
+
"SIP_3010" /* INVALID_COMMITMENT */
|
|
10209
|
+
);
|
|
10210
|
+
}
|
|
10211
|
+
let currentValue;
|
|
10212
|
+
if (currentTimeOrBlock instanceof Date) {
|
|
10213
|
+
currentValue = Math.floor(currentTimeOrBlock.getTime() / 1e3);
|
|
10214
|
+
} else if (typeof currentTimeOrBlock === "number") {
|
|
10215
|
+
currentValue = currentTimeOrBlock;
|
|
10216
|
+
} else {
|
|
10217
|
+
currentValue = Math.floor(Date.now() / 1e3);
|
|
10218
|
+
}
|
|
10219
|
+
const unlocked = currentValue >= timeLock.revealAfter;
|
|
10220
|
+
if (!unlocked) {
|
|
10221
|
+
return { unlocked: false };
|
|
10222
|
+
}
|
|
10223
|
+
try {
|
|
10224
|
+
const encryptionKey = this._deriveEncryptionKey(
|
|
10225
|
+
timeLock.encryptionCommitment,
|
|
10226
|
+
timeLock.revealAfter
|
|
10227
|
+
);
|
|
10228
|
+
const nonce = (0, import_utils22.hexToBytes)(timeLock.nonce.slice(2));
|
|
10229
|
+
const encryptedData = (0, import_utils22.hexToBytes)(timeLock.encryptedKey.slice(2));
|
|
10230
|
+
const cipher = (0, import_chacha3.xchacha20poly1305)(encryptionKey, nonce);
|
|
10231
|
+
const decryptedBytes = cipher.decrypt(encryptedData);
|
|
10232
|
+
const viewingKey = "0x" + (0, import_utils22.bytesToHex)(decryptedBytes);
|
|
10233
|
+
return {
|
|
10234
|
+
unlocked: true,
|
|
10235
|
+
viewingKey
|
|
10236
|
+
};
|
|
10237
|
+
} catch (error) {
|
|
10238
|
+
if (error instanceof ValidationError || error instanceof CryptoError) {
|
|
10239
|
+
throw error;
|
|
10240
|
+
}
|
|
10241
|
+
throw new CryptoError(
|
|
10242
|
+
"Failed to decrypt time-locked viewing key",
|
|
10243
|
+
"SIP_3002" /* DECRYPTION_FAILED */,
|
|
10244
|
+
{
|
|
10245
|
+
cause: error instanceof Error ? error : void 0,
|
|
10246
|
+
operation: "checkUnlocked"
|
|
10247
|
+
}
|
|
10248
|
+
);
|
|
10249
|
+
}
|
|
10250
|
+
}
|
|
10251
|
+
/**
|
|
10252
|
+
* Verify a time-lock commitment
|
|
10253
|
+
*
|
|
10254
|
+
* Verifies that the verification commitment in the time-lock matches the hash
|
|
10255
|
+
* of the provided viewing key and reveal time.
|
|
10256
|
+
*
|
|
10257
|
+
* @param timeLock - Time-lock to verify
|
|
10258
|
+
* @param viewingKey - Viewing key to verify against
|
|
10259
|
+
* @returns True if commitment is valid
|
|
10260
|
+
*/
|
|
10261
|
+
verifyCommitment(timeLock, viewingKey) {
|
|
10262
|
+
try {
|
|
10263
|
+
const viewingKeyBytes = (0, import_utils22.hexToBytes)(viewingKey.slice(2));
|
|
10264
|
+
const commitmentData = new Uint8Array([
|
|
10265
|
+
...viewingKeyBytes,
|
|
10266
|
+
...this._numberToBytes(timeLock.revealAfter)
|
|
10267
|
+
]);
|
|
10268
|
+
const expectedCommitment = (0, import_sha25616.sha256)(commitmentData);
|
|
10269
|
+
const actualCommitment = (0, import_utils22.hexToBytes)(timeLock.verificationCommitment.slice(2));
|
|
10270
|
+
if (expectedCommitment.length !== actualCommitment.length) {
|
|
10271
|
+
return false;
|
|
10272
|
+
}
|
|
10273
|
+
let diff = 0;
|
|
10274
|
+
for (let i = 0; i < expectedCommitment.length; i++) {
|
|
10275
|
+
diff |= expectedCommitment[i] ^ actualCommitment[i];
|
|
10276
|
+
}
|
|
10277
|
+
return diff === 0;
|
|
10278
|
+
} catch {
|
|
10279
|
+
return false;
|
|
10280
|
+
}
|
|
10281
|
+
}
|
|
10282
|
+
/**
|
|
10283
|
+
* Derive deterministic encryption key from commitment and reveal time
|
|
10284
|
+
*
|
|
10285
|
+
* @private
|
|
10286
|
+
*/
|
|
10287
|
+
_deriveEncryptionKey(commitment, revealAfter) {
|
|
10288
|
+
const commitmentBytes = (0, import_utils22.hexToBytes)(commitment.slice(2));
|
|
10289
|
+
const timeBytes = this._numberToBytes(revealAfter);
|
|
10290
|
+
const combined = new Uint8Array([...commitmentBytes, ...timeBytes]);
|
|
10291
|
+
const key = (0, import_sha25616.sha256)(combined);
|
|
10292
|
+
if (key.length !== 32) {
|
|
10293
|
+
throw new CryptoError(
|
|
10294
|
+
"Derived key must be 32 bytes",
|
|
10295
|
+
"SIP_3008" /* INVALID_KEY_SIZE */,
|
|
10296
|
+
{
|
|
10297
|
+
context: { actualSize: key.length, expectedSize: 32 },
|
|
10298
|
+
operation: "_deriveEncryptionKey"
|
|
10299
|
+
}
|
|
10300
|
+
);
|
|
10301
|
+
}
|
|
10302
|
+
return key;
|
|
10303
|
+
}
|
|
10304
|
+
/**
|
|
10305
|
+
* Convert number to 8-byte big-endian representation
|
|
10306
|
+
*
|
|
10307
|
+
* @private
|
|
10308
|
+
*/
|
|
10309
|
+
_numberToBytes(num) {
|
|
10310
|
+
const bytes = new Uint8Array(8);
|
|
10311
|
+
const view = new DataView(bytes.buffer);
|
|
10312
|
+
view.setBigUint64(0, BigInt(Math.floor(num)), false);
|
|
10313
|
+
return bytes;
|
|
10314
|
+
}
|
|
10315
|
+
};
|
|
10316
|
+
|
|
10317
|
+
// src/compliance/conditional-threshold.ts
|
|
10318
|
+
var import_secp256k17 = require("@noble/curves/secp256k1");
|
|
10319
|
+
var import_sha25617 = require("@noble/hashes/sha256");
|
|
10320
|
+
var import_utils23 = require("@noble/hashes/utils");
|
|
10321
|
+
var CURVE_ORDER2 = import_secp256k17.secp256k1.CURVE.n;
|
|
10322
|
+
|
|
10323
|
+
// src/compliance/threshold.ts
|
|
10324
|
+
var import_sha25618 = require("@noble/hashes/sha256");
|
|
10325
|
+
var import_utils24 = require("@noble/hashes/utils");
|
|
10326
|
+
var FIELD_PRIME = 2n ** 256n - 189n;
|
|
10327
|
+
var ThresholdViewingKey = class {
|
|
10328
|
+
/**
|
|
10329
|
+
* Create threshold shares from a viewing key
|
|
10330
|
+
*
|
|
10331
|
+
* @param params - Configuration parameters
|
|
10332
|
+
* @returns Threshold shares with commitment
|
|
10333
|
+
* @throws ValidationError if parameters are invalid
|
|
10334
|
+
*
|
|
10335
|
+
* @example
|
|
10336
|
+
* ```typescript
|
|
10337
|
+
* const threshold = ThresholdViewingKey.create({
|
|
10338
|
+
* threshold: 3,
|
|
10339
|
+
* totalShares: 5,
|
|
10340
|
+
* viewingKey: '0xabc123...',
|
|
10341
|
+
* })
|
|
10342
|
+
* ```
|
|
10343
|
+
*/
|
|
10344
|
+
static create(params) {
|
|
10345
|
+
this.validateParams(params.threshold, params.totalShares);
|
|
10346
|
+
this.validateViewingKey(params.viewingKey);
|
|
10347
|
+
const secret = this.viewingKeyToSecret(params.viewingKey);
|
|
10348
|
+
const keyLength = params.viewingKey.slice(2).length;
|
|
10349
|
+
const coefficients = this.generateCoefficients(params.threshold, secret);
|
|
10350
|
+
const commitment = this.createCommitment(secret, coefficients);
|
|
10351
|
+
const shares = [];
|
|
10352
|
+
for (let i = 1; i <= params.totalShares; i++) {
|
|
10353
|
+
const x = BigInt(i);
|
|
10354
|
+
const y = this.evaluatePolynomial(coefficients, x);
|
|
10355
|
+
shares.push(this.encodeShare(x, y, keyLength, commitment));
|
|
10356
|
+
}
|
|
10357
|
+
return {
|
|
10358
|
+
shares,
|
|
10359
|
+
commitment,
|
|
10360
|
+
threshold: params.threshold,
|
|
10361
|
+
totalShares: params.totalShares
|
|
10362
|
+
};
|
|
10363
|
+
}
|
|
10364
|
+
/**
|
|
10365
|
+
* Reconstruct viewing key from threshold shares
|
|
10366
|
+
*
|
|
10367
|
+
* @param shares - Array of encoded shares (must be >= threshold)
|
|
10368
|
+
* @returns Reconstructed viewing key
|
|
10369
|
+
* @throws ValidationError if insufficient or invalid shares
|
|
10370
|
+
*
|
|
10371
|
+
* @example
|
|
10372
|
+
* ```typescript
|
|
10373
|
+
* const viewingKey = ThresholdViewingKey.reconstruct([
|
|
10374
|
+
* 'share1',
|
|
10375
|
+
* 'share2',
|
|
10376
|
+
* 'share3',
|
|
10377
|
+
* ])
|
|
10378
|
+
* ```
|
|
10379
|
+
*/
|
|
10380
|
+
static reconstruct(shares) {
|
|
10381
|
+
if (!shares || shares.length === 0) {
|
|
10382
|
+
throw new ValidationError(
|
|
10383
|
+
"at least one share is required",
|
|
10384
|
+
"shares",
|
|
10385
|
+
{ received: shares },
|
|
10386
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
10387
|
+
);
|
|
10388
|
+
}
|
|
10389
|
+
const decodedShares = shares.map((s) => this.decodeShare(s));
|
|
10390
|
+
const commitment = decodedShares[0].commitment;
|
|
10391
|
+
const keyLength = decodedShares[0].keyLength;
|
|
10392
|
+
for (const share of decodedShares) {
|
|
10393
|
+
if (share.commitment !== commitment) {
|
|
10394
|
+
throw new ValidationError(
|
|
10395
|
+
"shares must all have the same commitment",
|
|
10396
|
+
"shares",
|
|
10397
|
+
{ commitment },
|
|
10398
|
+
"SIP_3012" /* INVALID_SHARE */
|
|
10399
|
+
);
|
|
10400
|
+
}
|
|
10401
|
+
if (share.keyLength !== keyLength) {
|
|
10402
|
+
throw new ValidationError(
|
|
10403
|
+
"shares must all have the same key length",
|
|
10404
|
+
"shares",
|
|
10405
|
+
{ keyLength },
|
|
10406
|
+
"SIP_3012" /* INVALID_SHARE */
|
|
10407
|
+
);
|
|
10408
|
+
}
|
|
10409
|
+
}
|
|
10410
|
+
const xCoords = new Set(decodedShares.map((s) => s.x.toString()));
|
|
10411
|
+
if (xCoords.size !== decodedShares.length) {
|
|
10412
|
+
throw new ValidationError(
|
|
10413
|
+
"shares must have unique x-coordinates",
|
|
10414
|
+
"shares",
|
|
10415
|
+
void 0,
|
|
10416
|
+
"SIP_3012" /* INVALID_SHARE */
|
|
10417
|
+
);
|
|
10418
|
+
}
|
|
10419
|
+
const secret = this.lagrangeInterpolate(decodedShares);
|
|
10420
|
+
return this.secretToViewingKey(secret, keyLength);
|
|
10421
|
+
}
|
|
10422
|
+
/**
|
|
10423
|
+
* Verify a share without revealing the viewing key
|
|
10424
|
+
*
|
|
10425
|
+
* @param share - Encoded share to verify
|
|
10426
|
+
* @param expectedCommitment - Expected commitment hash
|
|
10427
|
+
* @returns True if share is valid
|
|
10428
|
+
*
|
|
10429
|
+
* @example
|
|
10430
|
+
* ```typescript
|
|
10431
|
+
* const isValid = ThresholdViewingKey.verifyShare(
|
|
10432
|
+
* 'share1',
|
|
10433
|
+
* 'commitment_hash'
|
|
10434
|
+
* )
|
|
10435
|
+
* ```
|
|
10436
|
+
*/
|
|
10437
|
+
static verifyShare(share, expectedCommitment) {
|
|
10438
|
+
try {
|
|
10439
|
+
const decoded = this.decodeShare(share);
|
|
10440
|
+
return decoded.commitment === expectedCommitment;
|
|
10441
|
+
} catch {
|
|
10442
|
+
return false;
|
|
10443
|
+
}
|
|
10444
|
+
}
|
|
10445
|
+
// ─── Private Helper Methods ─────────────────────────────────────────────────
|
|
10446
|
+
/**
|
|
10447
|
+
* Validate threshold and total shares parameters
|
|
10448
|
+
*/
|
|
10449
|
+
static validateParams(threshold, totalShares) {
|
|
10450
|
+
if (!Number.isInteger(threshold) || threshold < 2) {
|
|
10451
|
+
throw new ValidationError(
|
|
10452
|
+
"threshold must be an integer >= 2",
|
|
10453
|
+
"threshold",
|
|
10454
|
+
{ received: threshold },
|
|
10455
|
+
"SIP_3013" /* INVALID_THRESHOLD */
|
|
10456
|
+
);
|
|
10457
|
+
}
|
|
10458
|
+
if (!Number.isInteger(totalShares) || totalShares < threshold) {
|
|
10459
|
+
throw new ValidationError(
|
|
10460
|
+
"totalShares must be an integer >= threshold",
|
|
10461
|
+
"totalShares",
|
|
10462
|
+
{ received: totalShares, threshold },
|
|
10463
|
+
"SIP_3013" /* INVALID_THRESHOLD */
|
|
10464
|
+
);
|
|
10465
|
+
}
|
|
10466
|
+
if (totalShares > 255) {
|
|
10467
|
+
throw new ValidationError(
|
|
10468
|
+
"totalShares must be <= 255",
|
|
10469
|
+
"totalShares",
|
|
10470
|
+
{ received: totalShares },
|
|
10471
|
+
"SIP_3013" /* INVALID_THRESHOLD */
|
|
10472
|
+
);
|
|
10473
|
+
}
|
|
10474
|
+
}
|
|
10475
|
+
/**
|
|
10476
|
+
* Validate viewing key format
|
|
10477
|
+
*/
|
|
10478
|
+
static validateViewingKey(viewingKey) {
|
|
10479
|
+
if (!viewingKey || typeof viewingKey !== "string") {
|
|
10480
|
+
throw new ValidationError(
|
|
10481
|
+
"viewingKey is required",
|
|
10482
|
+
"viewingKey",
|
|
10483
|
+
{ received: viewingKey },
|
|
10484
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
10485
|
+
);
|
|
10486
|
+
}
|
|
10487
|
+
if (!viewingKey.startsWith("0x")) {
|
|
10488
|
+
throw new ValidationError(
|
|
10489
|
+
"viewingKey must be hex-encoded (start with 0x)",
|
|
10490
|
+
"viewingKey",
|
|
10491
|
+
{ received: viewingKey },
|
|
10492
|
+
"SIP_3015" /* INVALID_FORMAT */
|
|
10493
|
+
);
|
|
10494
|
+
}
|
|
10495
|
+
if (viewingKey.length < 66) {
|
|
10496
|
+
throw new ValidationError(
|
|
10497
|
+
"viewingKey must be at least 32 bytes",
|
|
10498
|
+
"viewingKey",
|
|
10499
|
+
{ received: viewingKey.length },
|
|
10500
|
+
"SIP_3015" /* INVALID_FORMAT */
|
|
10501
|
+
);
|
|
10502
|
+
}
|
|
10503
|
+
}
|
|
10504
|
+
/**
|
|
10505
|
+
* Convert viewing key to secret (bigint)
|
|
10506
|
+
*/
|
|
10507
|
+
static viewingKeyToSecret(viewingKey) {
|
|
10508
|
+
const bytes = (0, import_utils24.hexToBytes)(viewingKey.slice(2));
|
|
10509
|
+
let secret = 0n;
|
|
10510
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
10511
|
+
secret = secret << 8n | BigInt(bytes[i]);
|
|
10512
|
+
}
|
|
10513
|
+
return this.mod(secret, FIELD_PRIME);
|
|
10514
|
+
}
|
|
10515
|
+
/**
|
|
10516
|
+
* Convert secret (bigint) back to viewing key
|
|
10517
|
+
* @param secret - The secret as bigint
|
|
10518
|
+
* @param hexLength - Length of the hex string (without 0x prefix)
|
|
10519
|
+
*/
|
|
10520
|
+
static secretToViewingKey(secret, hexLength) {
|
|
10521
|
+
let hex = secret.toString(16);
|
|
10522
|
+
hex = hex.padStart(hexLength, "0");
|
|
10523
|
+
return `0x${hex}`;
|
|
10524
|
+
}
|
|
10525
|
+
/**
|
|
10526
|
+
* Generate random polynomial coefficients
|
|
10527
|
+
* Polynomial: f(x) = a₀ + a₁x + a₂x² + ... + aₙ₋₁xⁿ⁻¹
|
|
10528
|
+
* where a₀ = secret
|
|
10529
|
+
*/
|
|
10530
|
+
static generateCoefficients(threshold, secret) {
|
|
10531
|
+
const coefficients = [secret];
|
|
10532
|
+
for (let i = 1; i < threshold; i++) {
|
|
10533
|
+
const randomCoeff = this.randomFieldElement();
|
|
10534
|
+
coefficients.push(randomCoeff);
|
|
10535
|
+
}
|
|
10536
|
+
return coefficients;
|
|
10537
|
+
}
|
|
10538
|
+
/**
|
|
10539
|
+
* Generate a random field element
|
|
10540
|
+
*/
|
|
10541
|
+
static randomFieldElement() {
|
|
10542
|
+
const bytes = (0, import_utils24.randomBytes)(32);
|
|
10543
|
+
let value = 0n;
|
|
10544
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
10545
|
+
value = value << 8n | BigInt(bytes[i]);
|
|
10546
|
+
}
|
|
10547
|
+
return this.mod(value, FIELD_PRIME);
|
|
10548
|
+
}
|
|
10549
|
+
/**
|
|
10550
|
+
* Evaluate polynomial at point x
|
|
10551
|
+
* f(x) = a₀ + a₁x + a₂x² + ... + aₙ₋₁xⁿ⁻¹
|
|
10552
|
+
*/
|
|
10553
|
+
static evaluatePolynomial(coefficients, x) {
|
|
10554
|
+
let result = 0n;
|
|
10555
|
+
let xPower = 1n;
|
|
10556
|
+
for (const coeff of coefficients) {
|
|
10557
|
+
result = this.mod(result + this.mod(coeff * xPower, FIELD_PRIME), FIELD_PRIME);
|
|
10558
|
+
xPower = this.mod(xPower * x, FIELD_PRIME);
|
|
10559
|
+
}
|
|
10560
|
+
return result;
|
|
10561
|
+
}
|
|
10562
|
+
/**
|
|
10563
|
+
* Create commitment hash from secret and coefficients
|
|
10564
|
+
*/
|
|
10565
|
+
static createCommitment(secret, coefficients) {
|
|
10566
|
+
const data = [secret, ...coefficients].map((c) => c.toString(16).padStart(64, "0")).join("");
|
|
10567
|
+
const hash2 = (0, import_sha25618.sha256)((0, import_utils24.hexToBytes)(data));
|
|
10568
|
+
return (0, import_utils24.bytesToHex)(hash2);
|
|
10569
|
+
}
|
|
10570
|
+
/**
|
|
10571
|
+
* Encode share as string: "x:y:len:commitment"
|
|
10572
|
+
*/
|
|
10573
|
+
static encodeShare(x, y, keyLength, commitment) {
|
|
10574
|
+
const xHex = x.toString(16).padStart(2, "0");
|
|
10575
|
+
const yHex = y.toString(16).padStart(64, "0");
|
|
10576
|
+
const lenHex = keyLength.toString(16).padStart(4, "0");
|
|
10577
|
+
return `${xHex}:${yHex}:${lenHex}:${commitment}`;
|
|
10578
|
+
}
|
|
10579
|
+
/**
|
|
10580
|
+
* Decode share from string
|
|
10581
|
+
*/
|
|
10582
|
+
static decodeShare(share) {
|
|
10583
|
+
if (!share || typeof share !== "string") {
|
|
10584
|
+
throw new ValidationError(
|
|
10585
|
+
"share must be a non-empty string",
|
|
10586
|
+
"share",
|
|
10587
|
+
{ received: share },
|
|
10588
|
+
"SIP_3012" /* INVALID_SHARE */
|
|
10589
|
+
);
|
|
10590
|
+
}
|
|
10591
|
+
const parts = share.split(":");
|
|
10592
|
+
if (parts.length !== 4) {
|
|
10593
|
+
throw new ValidationError(
|
|
10594
|
+
'share must have format "x:y:len:commitment"',
|
|
10595
|
+
"share",
|
|
10596
|
+
{ received: share },
|
|
10597
|
+
"SIP_3012" /* INVALID_SHARE */
|
|
10598
|
+
);
|
|
10599
|
+
}
|
|
10600
|
+
const [xHex, yHex, lenHex, commitment] = parts;
|
|
10601
|
+
try {
|
|
10602
|
+
const x = BigInt(`0x${xHex}`);
|
|
10603
|
+
const y = BigInt(`0x${yHex}`);
|
|
10604
|
+
const keyLength = parseInt(lenHex, 16);
|
|
10605
|
+
if (x <= 0n) {
|
|
10606
|
+
throw new ValidationError(
|
|
10607
|
+
"share x-coordinate must be positive",
|
|
10608
|
+
"share",
|
|
10609
|
+
{ x },
|
|
10610
|
+
"SIP_3012" /* INVALID_SHARE */
|
|
10611
|
+
);
|
|
10612
|
+
}
|
|
10613
|
+
if (keyLength < 64) {
|
|
10614
|
+
throw new ValidationError(
|
|
10615
|
+
"key length must be at least 64 (32 bytes)",
|
|
10616
|
+
"share",
|
|
10617
|
+
{ keyLength },
|
|
10618
|
+
"SIP_3012" /* INVALID_SHARE */
|
|
10619
|
+
);
|
|
10620
|
+
}
|
|
10621
|
+
return { x, y, keyLength, commitment };
|
|
10622
|
+
} catch (error) {
|
|
10623
|
+
throw new ValidationError(
|
|
10624
|
+
"failed to decode share",
|
|
10625
|
+
"share",
|
|
10626
|
+
{ error: error.message },
|
|
10627
|
+
"SIP_3012" /* INVALID_SHARE */
|
|
10628
|
+
);
|
|
9156
10629
|
}
|
|
9157
|
-
return data;
|
|
9158
10630
|
}
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
"Purpose",
|
|
9176
|
-
"Memo"
|
|
9177
|
-
];
|
|
9178
|
-
const rows = transactions.map((tx) => [
|
|
9179
|
-
tx.transactionId,
|
|
9180
|
-
tx.disclosureId,
|
|
9181
|
-
tx.type,
|
|
9182
|
-
tx.direction,
|
|
9183
|
-
tx.token.symbol,
|
|
9184
|
-
tx.amount.toString(),
|
|
9185
|
-
tx.sender,
|
|
9186
|
-
tx.recipient,
|
|
9187
|
-
tx.chain,
|
|
9188
|
-
tx.privacyLevel,
|
|
9189
|
-
new Date(tx.timestamp * 1e3).toISOString(),
|
|
9190
|
-
tx.txHash,
|
|
9191
|
-
tx.blockNumber.toString(),
|
|
9192
|
-
tx.riskScore?.toString() ?? "",
|
|
9193
|
-
tx.purpose ?? "",
|
|
9194
|
-
tx.memo ?? ""
|
|
9195
|
-
]);
|
|
9196
|
-
const escapeForCSV = (val) => {
|
|
9197
|
-
let escaped = val;
|
|
9198
|
-
if (/^[=+\-@|\t]/.test(escaped)) {
|
|
9199
|
-
escaped = `'${escaped}`;
|
|
10631
|
+
/**
|
|
10632
|
+
* Lagrange interpolation to reconstruct secret
|
|
10633
|
+
* Evaluates polynomial at x=0 to get f(0) = secret
|
|
10634
|
+
*/
|
|
10635
|
+
static lagrangeInterpolate(shares) {
|
|
10636
|
+
let secret = 0n;
|
|
10637
|
+
for (let i = 0; i < shares.length; i++) {
|
|
10638
|
+
const xi = shares[i].x;
|
|
10639
|
+
const yi = shares[i].y;
|
|
10640
|
+
let numerator = 1n;
|
|
10641
|
+
let denominator = 1n;
|
|
10642
|
+
for (let j = 0; j < shares.length; j++) {
|
|
10643
|
+
if (i === j) continue;
|
|
10644
|
+
const xj = shares[j].x;
|
|
10645
|
+
numerator = this.mod(numerator * this.mod(-xj, FIELD_PRIME), FIELD_PRIME);
|
|
10646
|
+
denominator = this.mod(denominator * this.mod(xi - xj, FIELD_PRIME), FIELD_PRIME);
|
|
9200
10647
|
}
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
return
|
|
10648
|
+
const coeff = this.mod(numerator * this.modInverse(denominator, FIELD_PRIME), FIELD_PRIME);
|
|
10649
|
+
secret = this.mod(secret + this.mod(yi * coeff, FIELD_PRIME), FIELD_PRIME);
|
|
10650
|
+
}
|
|
10651
|
+
return secret;
|
|
9205
10652
|
}
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
if (!params.organization?.trim()) {
|
|
9212
|
-
throw new ValidationError(
|
|
9213
|
-
"organization is required",
|
|
9214
|
-
"organization",
|
|
9215
|
-
void 0,
|
|
9216
|
-
"SIP_2008" /* MISSING_REQUIRED */
|
|
9217
|
-
);
|
|
10653
|
+
/**
|
|
10654
|
+
* Modular arithmetic: a mod m
|
|
10655
|
+
*/
|
|
10656
|
+
static mod(a, m) {
|
|
10657
|
+
return (a % m + m) % m;
|
|
9218
10658
|
}
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
)
|
|
10659
|
+
/**
|
|
10660
|
+
* Modular multiplicative inverse using Extended Euclidean Algorithm
|
|
10661
|
+
* Returns x such that (a * x) mod m = 1
|
|
10662
|
+
*/
|
|
10663
|
+
static modInverse(a, m) {
|
|
10664
|
+
const a0 = this.mod(a, m);
|
|
10665
|
+
if (a0 === 0n) {
|
|
10666
|
+
throw new CryptoError(
|
|
10667
|
+
"modular inverse does not exist (a = 0)",
|
|
10668
|
+
"SIP_3014" /* CRYPTO_OPERATION_FAILED */,
|
|
10669
|
+
{ operation: "modInverse" }
|
|
10670
|
+
);
|
|
10671
|
+
}
|
|
10672
|
+
let [old_r, r] = [a0, m];
|
|
10673
|
+
let [old_s, s] = [1n, 0n];
|
|
10674
|
+
while (r !== 0n) {
|
|
10675
|
+
const quotient = old_r / r;
|
|
10676
|
+
[old_r, r] = [r, old_r - quotient * r];
|
|
10677
|
+
[old_s, s] = [s, old_s - quotient * s];
|
|
10678
|
+
}
|
|
10679
|
+
if (old_r !== 1n) {
|
|
10680
|
+
throw new CryptoError(
|
|
10681
|
+
"modular inverse does not exist (gcd != 1)",
|
|
10682
|
+
"SIP_3014" /* CRYPTO_OPERATION_FAILED */,
|
|
10683
|
+
{ operation: "modInverse" }
|
|
10684
|
+
);
|
|
10685
|
+
}
|
|
10686
|
+
return this.mod(old_s, m);
|
|
9226
10687
|
}
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
10688
|
+
};
|
|
10689
|
+
|
|
10690
|
+
// src/compliance/derivation.ts
|
|
10691
|
+
var import_sha25619 = require("@noble/hashes/sha256");
|
|
10692
|
+
var import_sha5123 = require("@noble/hashes/sha512");
|
|
10693
|
+
var import_hmac2 = require("@noble/hashes/hmac");
|
|
10694
|
+
var import_utils25 = require("@noble/hashes/utils");
|
|
10695
|
+
var AuditorType = /* @__PURE__ */ ((AuditorType2) => {
|
|
10696
|
+
AuditorType2[AuditorType2["PRIMARY"] = 0] = "PRIMARY";
|
|
10697
|
+
AuditorType2[AuditorType2["REGULATORY"] = 1] = "REGULATORY";
|
|
10698
|
+
AuditorType2[AuditorType2["INTERNAL"] = 2] = "INTERNAL";
|
|
10699
|
+
AuditorType2[AuditorType2["TAX"] = 3] = "TAX";
|
|
10700
|
+
return AuditorType2;
|
|
10701
|
+
})(AuditorType || {});
|
|
10702
|
+
var AuditorKeyDerivation = class {
|
|
10703
|
+
/**
|
|
10704
|
+
* SIP Protocol coin type (BIP-44 registered)
|
|
10705
|
+
*
|
|
10706
|
+
* Note: This is a placeholder. In production, register with SLIP-44:
|
|
10707
|
+
* https://github.com/satoshilabs/slips/blob/master/slip-0044.md
|
|
10708
|
+
*/
|
|
10709
|
+
static COIN_TYPE = 1234;
|
|
10710
|
+
/**
|
|
10711
|
+
* BIP-44 purpose field
|
|
10712
|
+
*/
|
|
10713
|
+
static PURPOSE = 44;
|
|
10714
|
+
/**
|
|
10715
|
+
* Hardened derivation flag (2^31)
|
|
10716
|
+
*/
|
|
10717
|
+
static HARDENED = 2147483648;
|
|
10718
|
+
/**
|
|
10719
|
+
* Generate BIP-44 derivation path
|
|
10720
|
+
*
|
|
10721
|
+
* @param auditorType - Type of auditor key
|
|
10722
|
+
* @param account - Account index (default: 0)
|
|
10723
|
+
* @returns BIP-44 style path string
|
|
10724
|
+
*
|
|
10725
|
+
* @example
|
|
10726
|
+
* ```typescript
|
|
10727
|
+
* AuditorKeyDerivation.derivePath(AuditorType.REGULATORY)
|
|
10728
|
+
* // Returns: "m/44'/1234'/0'/1"
|
|
10729
|
+
*
|
|
10730
|
+
* AuditorKeyDerivation.derivePath(AuditorType.TAX, 5)
|
|
10731
|
+
* // Returns: "m/44'/1234'/5'/3"
|
|
10732
|
+
* ```
|
|
10733
|
+
*/
|
|
10734
|
+
static derivePath(auditorType, account = 0) {
|
|
10735
|
+
this.validateAuditorType(auditorType);
|
|
10736
|
+
this.validateAccount(account);
|
|
10737
|
+
return `m/${this.PURPOSE}'/${this.COIN_TYPE}'/${account}'/${auditorType}`;
|
|
9234
10738
|
}
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
10739
|
+
/**
|
|
10740
|
+
* Derive a viewing key for an auditor
|
|
10741
|
+
*
|
|
10742
|
+
* Uses BIP-32 style hierarchical deterministic key derivation:
|
|
10743
|
+
* 1. Derive master key from seed
|
|
10744
|
+
* 2. Harden purpose (44')
|
|
10745
|
+
* 3. Harden coin type (1234')
|
|
10746
|
+
* 4. Harden account index
|
|
10747
|
+
* 5. Derive auditor type (non-hardened)
|
|
10748
|
+
*
|
|
10749
|
+
* @param params - Derivation parameters
|
|
10750
|
+
* @returns Derived viewing key with metadata
|
|
10751
|
+
*
|
|
10752
|
+
* @throws {ValidationError} If parameters are invalid
|
|
10753
|
+
*
|
|
10754
|
+
* @example
|
|
10755
|
+
* ```typescript
|
|
10756
|
+
* const regulatoryKey = AuditorKeyDerivation.deriveViewingKey({
|
|
10757
|
+
* masterSeed: randomBytes(32),
|
|
10758
|
+
* auditorType: AuditorType.REGULATORY,
|
|
10759
|
+
* })
|
|
10760
|
+
*
|
|
10761
|
+
* console.log(regulatoryKey.path) // "m/44'/1234'/0'/1"
|
|
10762
|
+
* console.log(regulatoryKey.viewingKey.key) // "0x..."
|
|
10763
|
+
* ```
|
|
10764
|
+
*/
|
|
10765
|
+
static deriveViewingKey(params) {
|
|
10766
|
+
const { masterSeed, auditorType, account = 0 } = params;
|
|
10767
|
+
this.validateMasterSeed(masterSeed);
|
|
10768
|
+
this.validateAuditorType(auditorType);
|
|
10769
|
+
this.validateAccount(account);
|
|
10770
|
+
const path = this.derivePath(auditorType, account);
|
|
10771
|
+
const indices = [
|
|
10772
|
+
this.PURPOSE | this.HARDENED,
|
|
10773
|
+
// 44' (hardened)
|
|
10774
|
+
this.COIN_TYPE | this.HARDENED,
|
|
10775
|
+
// 1234' (hardened)
|
|
10776
|
+
account | this.HARDENED,
|
|
10777
|
+
// account' (hardened)
|
|
10778
|
+
auditorType
|
|
10779
|
+
// auditorType (non-hardened)
|
|
10780
|
+
];
|
|
10781
|
+
const masterData = (0, import_hmac2.hmac)(import_sha5123.sha512, (0, import_utils25.utf8ToBytes)("SIP-MASTER-SEED"), masterSeed);
|
|
10782
|
+
let currentKey = new Uint8Array(masterData.slice(0, 32));
|
|
10783
|
+
let chainCode = new Uint8Array(masterData.slice(32, 64));
|
|
10784
|
+
try {
|
|
10785
|
+
for (let i = 0; i < indices.length; i++) {
|
|
10786
|
+
const index = indices[i];
|
|
10787
|
+
const derived = this.deriveChildKey(currentKey, chainCode, index);
|
|
10788
|
+
if (i > 0) {
|
|
10789
|
+
secureWipe(currentKey);
|
|
10790
|
+
}
|
|
10791
|
+
currentKey = new Uint8Array(derived.key);
|
|
10792
|
+
chainCode = new Uint8Array(derived.chainCode);
|
|
10793
|
+
}
|
|
10794
|
+
const keyHex = `0x${(0, import_utils25.bytesToHex)(currentKey)}`;
|
|
10795
|
+
const hashBytes = (0, import_sha25619.sha256)(currentKey);
|
|
10796
|
+
const hash2 = `0x${(0, import_utils25.bytesToHex)(hashBytes)}`;
|
|
10797
|
+
const viewingKey = {
|
|
10798
|
+
key: keyHex,
|
|
10799
|
+
path,
|
|
10800
|
+
hash: hash2
|
|
10801
|
+
};
|
|
10802
|
+
return {
|
|
10803
|
+
path,
|
|
10804
|
+
viewingKey,
|
|
10805
|
+
auditorType,
|
|
10806
|
+
account
|
|
10807
|
+
};
|
|
10808
|
+
} finally {
|
|
10809
|
+
secureWipe(currentKey);
|
|
10810
|
+
secureWipe(chainCode);
|
|
10811
|
+
}
|
|
9242
10812
|
}
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
10813
|
+
/**
|
|
10814
|
+
* Derive multiple viewing keys at once
|
|
10815
|
+
*
|
|
10816
|
+
* Efficiently derives keys for multiple auditor types from the same
|
|
10817
|
+
* master seed. This is more efficient than calling deriveViewingKey
|
|
10818
|
+
* multiple times as it reuses intermediate derivations.
|
|
10819
|
+
*
|
|
10820
|
+
* @param params - Derivation parameters
|
|
10821
|
+
* @returns Array of derived viewing keys
|
|
10822
|
+
*
|
|
10823
|
+
* @example
|
|
10824
|
+
* ```typescript
|
|
10825
|
+
* const keys = AuditorKeyDerivation.deriveMultiple({
|
|
10826
|
+
* masterSeed: randomBytes(32),
|
|
10827
|
+
* auditorTypes: [
|
|
10828
|
+
* AuditorType.PRIMARY,
|
|
10829
|
+
* AuditorType.REGULATORY,
|
|
10830
|
+
* AuditorType.INTERNAL,
|
|
10831
|
+
* ],
|
|
10832
|
+
* })
|
|
10833
|
+
*
|
|
10834
|
+
* // keys[0] -> PRIMARY key (m/44'/1234'/0'/0)
|
|
10835
|
+
* // keys[1] -> REGULATORY key (m/44'/1234'/0'/1)
|
|
10836
|
+
* // keys[2] -> INTERNAL key (m/44'/1234'/0'/2)
|
|
10837
|
+
* ```
|
|
10838
|
+
*/
|
|
10839
|
+
static deriveMultiple(params) {
|
|
10840
|
+
const { masterSeed, auditorTypes, account = 0 } = params;
|
|
10841
|
+
this.validateMasterSeed(masterSeed);
|
|
10842
|
+
this.validateAccount(account);
|
|
10843
|
+
if (!auditorTypes || auditorTypes.length === 0) {
|
|
10844
|
+
throw new ValidationError(
|
|
10845
|
+
"at least one auditor type is required",
|
|
10846
|
+
"auditorTypes",
|
|
10847
|
+
{ received: auditorTypes },
|
|
10848
|
+
"SIP_2008" /* MISSING_REQUIRED */
|
|
10849
|
+
);
|
|
10850
|
+
}
|
|
10851
|
+
for (const type of auditorTypes) {
|
|
10852
|
+
this.validateAuditorType(type);
|
|
10853
|
+
}
|
|
10854
|
+
const uniqueTypes = Array.from(new Set(auditorTypes));
|
|
10855
|
+
const commonIndices = [
|
|
10856
|
+
this.PURPOSE | this.HARDENED,
|
|
10857
|
+
// 44' (hardened)
|
|
10858
|
+
this.COIN_TYPE | this.HARDENED,
|
|
10859
|
+
// 1234' (hardened)
|
|
10860
|
+
account | this.HARDENED
|
|
10861
|
+
// account' (hardened)
|
|
10862
|
+
];
|
|
10863
|
+
const masterData = (0, import_hmac2.hmac)(import_sha5123.sha512, (0, import_utils25.utf8ToBytes)("SIP-MASTER-SEED"), masterSeed);
|
|
10864
|
+
let commonKey = new Uint8Array(masterData.slice(0, 32));
|
|
10865
|
+
let commonChainCode = new Uint8Array(masterData.slice(32, 64));
|
|
10866
|
+
try {
|
|
10867
|
+
for (let i = 0; i < commonIndices.length; i++) {
|
|
10868
|
+
const index = commonIndices[i];
|
|
10869
|
+
const derived = this.deriveChildKey(commonKey, commonChainCode, index);
|
|
10870
|
+
if (i > 0) {
|
|
10871
|
+
secureWipe(commonKey);
|
|
10872
|
+
}
|
|
10873
|
+
commonKey = new Uint8Array(derived.key);
|
|
10874
|
+
commonChainCode = new Uint8Array(derived.chainCode);
|
|
10875
|
+
}
|
|
10876
|
+
const results = [];
|
|
10877
|
+
for (const auditorType of uniqueTypes) {
|
|
10878
|
+
const derived = this.deriveChildKey(commonKey, commonChainCode, auditorType);
|
|
10879
|
+
try {
|
|
10880
|
+
const keyHex = `0x${(0, import_utils25.bytesToHex)(derived.key)}`;
|
|
10881
|
+
const hashBytes = (0, import_sha25619.sha256)(derived.key);
|
|
10882
|
+
const hash2 = `0x${(0, import_utils25.bytesToHex)(hashBytes)}`;
|
|
10883
|
+
const path = this.derivePath(auditorType, account);
|
|
10884
|
+
const viewingKey = {
|
|
10885
|
+
key: keyHex,
|
|
10886
|
+
path,
|
|
10887
|
+
hash: hash2
|
|
10888
|
+
};
|
|
10889
|
+
results.push({
|
|
10890
|
+
path,
|
|
10891
|
+
viewingKey,
|
|
10892
|
+
auditorType,
|
|
10893
|
+
account
|
|
10894
|
+
});
|
|
10895
|
+
} finally {
|
|
10896
|
+
secureWipe(derived.key);
|
|
10897
|
+
secureWipe(derived.chainCode);
|
|
10898
|
+
}
|
|
10899
|
+
}
|
|
10900
|
+
return results;
|
|
10901
|
+
} finally {
|
|
10902
|
+
secureWipe(commonKey);
|
|
10903
|
+
secureWipe(commonChainCode);
|
|
10904
|
+
}
|
|
9250
10905
|
}
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
10906
|
+
/**
|
|
10907
|
+
* Get human-readable name for auditor type
|
|
10908
|
+
*
|
|
10909
|
+
* @param auditorType - Auditor type enum value
|
|
10910
|
+
* @returns Friendly name string
|
|
10911
|
+
*/
|
|
10912
|
+
static getAuditorTypeName(auditorType) {
|
|
10913
|
+
switch (auditorType) {
|
|
10914
|
+
case 0 /* PRIMARY */:
|
|
10915
|
+
return "Primary";
|
|
10916
|
+
case 1 /* REGULATORY */:
|
|
10917
|
+
return "Regulatory";
|
|
10918
|
+
case 2 /* INTERNAL */:
|
|
10919
|
+
return "Internal";
|
|
10920
|
+
case 3 /* TAX */:
|
|
10921
|
+
return "Tax Authority";
|
|
10922
|
+
default:
|
|
10923
|
+
return `Unknown (${auditorType})`;
|
|
10924
|
+
}
|
|
9260
10925
|
}
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
10926
|
+
// ─── Private Helpers ─────────────────────────────────────────────────────────
|
|
10927
|
+
/**
|
|
10928
|
+
* Derive a child key using BIP-32 HMAC-SHA512 derivation
|
|
10929
|
+
*
|
|
10930
|
+
* @param parentKey - Parent key bytes (32 bytes)
|
|
10931
|
+
* @param chainCode - Parent chain code (32 bytes)
|
|
10932
|
+
* @param index - Child index (use | HARDENED for hardened derivation)
|
|
10933
|
+
* @returns Derived key and chain code
|
|
10934
|
+
*/
|
|
10935
|
+
static deriveChildKey(parentKey, chainCode, index) {
|
|
10936
|
+
const isHardened = (index & this.HARDENED) !== 0;
|
|
10937
|
+
const data = new Uint8Array(37);
|
|
10938
|
+
if (isHardened) {
|
|
10939
|
+
data[0] = 0;
|
|
10940
|
+
data.set(parentKey, 1);
|
|
10941
|
+
} else {
|
|
10942
|
+
data[0] = 1;
|
|
10943
|
+
data.set(parentKey, 1);
|
|
10944
|
+
}
|
|
10945
|
+
const indexView = new DataView(data.buffer, 33, 4);
|
|
10946
|
+
indexView.setUint32(0, index, false);
|
|
10947
|
+
const hmacResult = (0, import_hmac2.hmac)(import_sha5123.sha512, chainCode, data);
|
|
10948
|
+
const childKey = new Uint8Array(hmacResult.slice(0, 32));
|
|
10949
|
+
const childChainCode = new Uint8Array(hmacResult.slice(32, 64));
|
|
10950
|
+
return {
|
|
10951
|
+
key: childKey,
|
|
10952
|
+
chainCode: childChainCode
|
|
10953
|
+
};
|
|
9268
10954
|
}
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
10955
|
+
/**
|
|
10956
|
+
* Validate master seed
|
|
10957
|
+
*/
|
|
10958
|
+
static validateMasterSeed(seed) {
|
|
10959
|
+
if (!seed || seed.length < 32) {
|
|
10960
|
+
throw new ValidationError(
|
|
10961
|
+
"master seed must be at least 32 bytes",
|
|
10962
|
+
"masterSeed",
|
|
10963
|
+
{ received: seed?.length ?? 0 },
|
|
10964
|
+
"SIP_2001" /* INVALID_INPUT */
|
|
10965
|
+
);
|
|
10966
|
+
}
|
|
9276
10967
|
}
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
10968
|
+
/**
|
|
10969
|
+
* Validate auditor type
|
|
10970
|
+
*/
|
|
10971
|
+
static validateAuditorType(type) {
|
|
10972
|
+
const validTypes = [
|
|
10973
|
+
0 /* PRIMARY */,
|
|
10974
|
+
1 /* REGULATORY */,
|
|
10975
|
+
2 /* INTERNAL */,
|
|
10976
|
+
3 /* TAX */
|
|
10977
|
+
];
|
|
10978
|
+
if (!validTypes.includes(type)) {
|
|
10979
|
+
throw new ValidationError(
|
|
10980
|
+
`invalid auditor type: ${type}`,
|
|
10981
|
+
"auditorType",
|
|
10982
|
+
{ received: type, valid: validTypes },
|
|
10983
|
+
"SIP_2001" /* INVALID_INPUT */
|
|
10984
|
+
);
|
|
10985
|
+
}
|
|
9284
10986
|
}
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
10987
|
+
/**
|
|
10988
|
+
* Validate account index
|
|
10989
|
+
*/
|
|
10990
|
+
static validateAccount(account) {
|
|
10991
|
+
if (!Number.isInteger(account) || account < 0 || account >= this.HARDENED) {
|
|
10992
|
+
throw new ValidationError(
|
|
10993
|
+
`account must be a non-negative integer less than ${this.HARDENED}`,
|
|
10994
|
+
"account",
|
|
10995
|
+
{ received: account },
|
|
10996
|
+
"SIP_2001" /* INVALID_INPUT */
|
|
10997
|
+
);
|
|
10998
|
+
}
|
|
9292
10999
|
}
|
|
9293
|
-
}
|
|
11000
|
+
};
|
|
9294
11001
|
|
|
9295
11002
|
// src/wallet/errors.ts
|
|
9296
11003
|
var import_types18 = require("@sip-protocol/types");
|
|
@@ -12439,7 +14146,7 @@ function createTrezorAdapter(config) {
|
|
|
12439
14146
|
|
|
12440
14147
|
// src/wallet/hardware/mock.ts
|
|
12441
14148
|
var import_types51 = require("@sip-protocol/types");
|
|
12442
|
-
var
|
|
14149
|
+
var import_utils26 = require("@noble/hashes/utils");
|
|
12443
14150
|
var MockLedgerAdapter = class extends BaseWalletAdapter {
|
|
12444
14151
|
chain;
|
|
12445
14152
|
name = "mock-ledger";
|
|
@@ -12684,15 +14391,15 @@ var MockLedgerAdapter = class extends BaseWalletAdapter {
|
|
|
12684
14391
|
}
|
|
12685
14392
|
}
|
|
12686
14393
|
generateMockAddress(index) {
|
|
12687
|
-
const bytes = (0,
|
|
14394
|
+
const bytes = (0, import_utils26.randomBytes)(20);
|
|
12688
14395
|
bytes[0] = index;
|
|
12689
|
-
return `0x${(0,
|
|
14396
|
+
return `0x${(0, import_utils26.bytesToHex)(bytes)}`;
|
|
12690
14397
|
}
|
|
12691
14398
|
generateMockPublicKey(index) {
|
|
12692
|
-
const bytes = (0,
|
|
14399
|
+
const bytes = (0, import_utils26.randomBytes)(33);
|
|
12693
14400
|
bytes[0] = 2;
|
|
12694
14401
|
bytes[1] = index;
|
|
12695
|
-
return `0x${(0,
|
|
14402
|
+
return `0x${(0, import_utils26.bytesToHex)(bytes)}`;
|
|
12696
14403
|
}
|
|
12697
14404
|
generateMockSignature(data) {
|
|
12698
14405
|
const sig = new Uint8Array(65);
|
|
@@ -12701,7 +14408,7 @@ var MockLedgerAdapter = class extends BaseWalletAdapter {
|
|
|
12701
14408
|
sig[32 + i] = (data[i % data.length] ?? 0) ^ i * 11;
|
|
12702
14409
|
}
|
|
12703
14410
|
sig[64] = 27;
|
|
12704
|
-
return `0x${(0,
|
|
14411
|
+
return `0x${(0, import_utils26.bytesToHex)(sig)}`;
|
|
12705
14412
|
}
|
|
12706
14413
|
delay(ms) {
|
|
12707
14414
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -12890,15 +14597,15 @@ var MockTrezorAdapter = class extends BaseWalletAdapter {
|
|
|
12890
14597
|
}
|
|
12891
14598
|
}
|
|
12892
14599
|
generateMockAddress(index) {
|
|
12893
|
-
const bytes = (0,
|
|
14600
|
+
const bytes = (0, import_utils26.randomBytes)(20);
|
|
12894
14601
|
bytes[0] = index + 100;
|
|
12895
|
-
return `0x${(0,
|
|
14602
|
+
return `0x${(0, import_utils26.bytesToHex)(bytes)}`;
|
|
12896
14603
|
}
|
|
12897
14604
|
generateMockPublicKey(index) {
|
|
12898
|
-
const bytes = (0,
|
|
14605
|
+
const bytes = (0, import_utils26.randomBytes)(33);
|
|
12899
14606
|
bytes[0] = 3;
|
|
12900
14607
|
bytes[1] = index + 100;
|
|
12901
|
-
return `0x${(0,
|
|
14608
|
+
return `0x${(0, import_utils26.bytesToHex)(bytes)}`;
|
|
12902
14609
|
}
|
|
12903
14610
|
generateMockSignature(data) {
|
|
12904
14611
|
const sig = new Uint8Array(65);
|
|
@@ -12907,7 +14614,7 @@ var MockTrezorAdapter = class extends BaseWalletAdapter {
|
|
|
12907
14614
|
sig[32 + i] = (data[i % data.length] ?? 0) ^ i * 17;
|
|
12908
14615
|
}
|
|
12909
14616
|
sig[64] = 28;
|
|
12910
|
-
return `0x${(0,
|
|
14617
|
+
return `0x${(0, import_utils26.bytesToHex)(sig)}`;
|
|
12911
14618
|
}
|
|
12912
14619
|
delay(ms) {
|
|
12913
14620
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -12926,10 +14633,14 @@ var import_types54 = require("@sip-protocol/types");
|
|
|
12926
14633
|
0 && (module.exports = {
|
|
12927
14634
|
ATTESTATION_VERSION,
|
|
12928
14635
|
AptosStealthService,
|
|
14636
|
+
AuditorKeyDerivation,
|
|
14637
|
+
AuditorType,
|
|
12929
14638
|
BaseWalletAdapter,
|
|
12930
14639
|
CHAIN_NUMERIC_IDS,
|
|
12931
14640
|
COSMOS_CHAIN_PREFIXES,
|
|
12932
14641
|
ComplianceManager,
|
|
14642
|
+
ComplianceReporter,
|
|
14643
|
+
ConditionalDisclosure,
|
|
12933
14644
|
CosmosStealthService,
|
|
12934
14645
|
CryptoError,
|
|
12935
14646
|
DEFAULT_THRESHOLD,
|
|
@@ -12981,6 +14692,7 @@ var import_types54 = require("@sip-protocol/types");
|
|
|
12981
14692
|
SmartRouter,
|
|
12982
14693
|
SolanaWalletAdapter,
|
|
12983
14694
|
SwapStatus,
|
|
14695
|
+
ThresholdViewingKey,
|
|
12984
14696
|
Treasury,
|
|
12985
14697
|
TrezorWalletAdapter,
|
|
12986
14698
|
ValidationError,
|
|
@@ -13065,6 +14777,7 @@ var import_types54 = require("@sip-protocol/types");
|
|
|
13065
14777
|
generateEd25519StealthAddress,
|
|
13066
14778
|
generateEd25519StealthMetaAddress,
|
|
13067
14779
|
generateIntentId,
|
|
14780
|
+
generatePdfReport,
|
|
13068
14781
|
generateRandomBytes,
|
|
13069
14782
|
generateStealthAddress,
|
|
13070
14783
|
generateStealthMetaAddress,
|