@sip-protocol/sdk 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +58 -0
  2. package/dist/browser.d.mts +4 -4
  3. package/dist/browser.d.ts +4 -4
  4. package/dist/browser.js +2752 -448
  5. package/dist/browser.mjs +31 -1
  6. package/dist/chunk-7QZPORY5.mjs +15604 -0
  7. package/dist/chunk-C2NPCUAJ.mjs +17010 -0
  8. package/dist/chunk-FCVLFUIC.mjs +16699 -0
  9. package/dist/chunk-G5UHXECN.mjs +16340 -0
  10. package/dist/chunk-GEDEIZHJ.mjs +16798 -0
  11. package/dist/chunk-GOOEOAMV.mjs +17026 -0
  12. package/dist/chunk-MTNYSNR7.mjs +16269 -0
  13. package/dist/chunk-O5PIB2EA.mjs +16698 -0
  14. package/dist/chunk-PCFM7FQO.mjs +17010 -0
  15. package/dist/chunk-QK464ARC.mjs +16946 -0
  16. package/dist/chunk-VNBMNGC3.mjs +16698 -0
  17. package/dist/chunk-W5TUELDQ.mjs +16947 -0
  18. package/dist/index-CD_zShu-.d.ts +10870 -0
  19. package/dist/index-CQBYdLYy.d.mts +10976 -0
  20. package/dist/index-Cg9TYEPv.d.mts +11321 -0
  21. package/dist/index-CqZJOO8C.d.mts +11323 -0
  22. package/dist/index-CywN9Bnp.d.ts +11321 -0
  23. package/dist/index-DHy5ZjCD.d.ts +10976 -0
  24. package/dist/index-DfsVsmxu.d.ts +11323 -0
  25. package/dist/index-ObjwyVDX.d.mts +10870 -0
  26. package/dist/index-m0xbSfmT.d.mts +11318 -0
  27. package/dist/index-rWLEgvhN.d.ts +11318 -0
  28. package/dist/index.d.mts +3 -3
  29. package/dist/index.d.ts +3 -3
  30. package/dist/index.js +2737 -427
  31. package/dist/index.mjs +31 -1
  32. package/dist/noir-DKfEzWy9.d.mts +482 -0
  33. package/dist/noir-DKfEzWy9.d.ts +482 -0
  34. package/dist/proofs/noir.d.mts +1 -1
  35. package/dist/proofs/noir.d.ts +1 -1
  36. package/dist/proofs/noir.js +12 -3
  37. package/dist/proofs/noir.mjs +12 -3
  38. package/package.json +16 -14
  39. package/src/adapters/near-intents.ts +13 -3
  40. package/src/auction/index.ts +20 -0
  41. package/src/auction/sealed-bid.ts +1037 -0
  42. package/src/compliance/derivation.ts +13 -3
  43. package/src/compliance/reports.ts +5 -4
  44. package/src/cosmos/ibc-stealth.ts +2 -2
  45. package/src/cosmos/stealth.ts +2 -2
  46. package/src/governance/index.ts +19 -0
  47. package/src/governance/private-vote.ts +1116 -0
  48. package/src/index.ts +50 -2
  49. package/src/intent.ts +145 -8
  50. package/src/nft/index.ts +27 -0
  51. package/src/nft/private-nft.ts +811 -0
  52. package/src/proofs/browser-utils.ts +1 -7
  53. package/src/proofs/noir.ts +34 -7
  54. package/src/settlement/backends/direct-chain.ts +14 -3
  55. package/src/stealth.ts +31 -13
  56. package/src/types/browser.d.ts +67 -0
  57. package/src/validation.ts +4 -2
  58. package/src/wallet/bitcoin/adapter.ts +159 -15
  59. package/src/wallet/bitcoin/types.ts +340 -15
  60. package/src/wallet/cosmos/mock.ts +16 -12
  61. package/src/wallet/hardware/ledger.ts +82 -12
  62. package/src/wallet/hardware/types.ts +2 -0
  63. package/LICENSE +0 -21
package/dist/index.js CHANGED
@@ -3231,6 +3231,8 @@ __export(index_exports, {
3231
3231
  PaymentBuilder: () => PaymentBuilder,
3232
3232
  PaymentStatus: () => import_types56.PaymentStatus,
3233
3233
  PrivacyLevel: () => import_types55.PrivacyLevel,
3234
+ PrivateNFT: () => PrivateNFT,
3235
+ PrivateVoting: () => PrivateVoting,
3234
3236
  ProofError: () => ProofError,
3235
3237
  ProofGenerationError: () => ProofGenerationError,
3236
3238
  ProofNotImplementedError: () => ProofNotImplementedError,
@@ -3242,10 +3244,12 @@ __export(index_exports, {
3242
3244
  STABLECOIN_ADDRESSES: () => STABLECOIN_ADDRESSES,
3243
3245
  STABLECOIN_DECIMALS: () => STABLECOIN_DECIMALS,
3244
3246
  STABLECOIN_INFO: () => STABLECOIN_INFO,
3247
+ SealedBidAuction: () => SealedBidAuction,
3245
3248
  SettlementRegistry: () => SettlementRegistry,
3246
3249
  SettlementRegistryError: () => SettlementRegistryError,
3247
3250
  SmartRouter: () => SmartRouter,
3248
3251
  SolanaWalletAdapter: () => SolanaWalletAdapter,
3252
+ SuiStealthService: () => SuiStealthService,
3249
3253
  SwapStatus: () => SwapStatus,
3250
3254
  ThresholdViewingKey: () => ThresholdViewingKey,
3251
3255
  Treasury: () => Treasury,
@@ -3270,6 +3274,7 @@ __export(index_exports, {
3270
3274
  checkAptosStealthAddress: () => checkAptosStealthAddress,
3271
3275
  checkEd25519StealthAddress: () => checkEd25519StealthAddress,
3272
3276
  checkStealthAddress: () => checkStealthAddress,
3277
+ checkSuiStealthAddress: () => checkSuiStealthAddress,
3273
3278
  commit: () => commit,
3274
3279
  commitZero: () => commitZero,
3275
3280
  computeAttestationHash: () => computeAttestationHash,
@@ -3289,8 +3294,11 @@ __export(index_exports, {
3289
3294
  createNEARIntentsAdapter: () => createNEARIntentsAdapter,
3290
3295
  createNEARIntentsBackend: () => createNEARIntentsBackend,
3291
3296
  createOracleRegistry: () => createOracleRegistry,
3297
+ createPrivateOwnership: () => createPrivateOwnership,
3298
+ createPrivateVoting: () => createPrivateVoting,
3292
3299
  createProductionSIP: () => createProductionSIP,
3293
3300
  createSIP: () => createSIP,
3301
+ createSealedBidAuction: () => createSealedBidAuction,
3294
3302
  createShieldedIntent: () => createShieldedIntent,
3295
3303
  createShieldedPayment: () => createShieldedPayment,
3296
3304
  createSmartRouter: () => createSmartRouter,
@@ -3310,6 +3318,7 @@ __export(index_exports, {
3310
3318
  deriveEd25519StealthPrivateKey: () => deriveEd25519StealthPrivateKey,
3311
3319
  deriveOracleId: () => deriveOracleId,
3312
3320
  deriveStealthPrivateKey: () => deriveStealthPrivateKey,
3321
+ deriveSuiStealthPrivateKey: () => deriveSuiStealthPrivateKey,
3313
3322
  deriveViewingKey: () => deriveViewingKey,
3314
3323
  deserializeAttestationMessage: () => deserializeAttestationMessage,
3315
3324
  deserializeIntent: () => deserializeIntent,
@@ -3319,6 +3328,7 @@ __export(index_exports, {
3319
3328
  ed25519PublicKeyToAptosAddress: () => ed25519PublicKeyToAptosAddress,
3320
3329
  ed25519PublicKeyToNearAddress: () => ed25519PublicKeyToNearAddress,
3321
3330
  ed25519PublicKeyToSolanaAddress: () => ed25519PublicKeyToSolanaAddress,
3331
+ ed25519PublicKeyToSuiAddress: () => ed25519PublicKeyToSuiAddress,
3322
3332
  encodeStealthMetaAddress: () => encodeStealthMetaAddress,
3323
3333
  encryptForViewing: () => encryptForViewing,
3324
3334
  featureNotSupportedError: () => featureNotSupportedError,
@@ -3336,6 +3346,7 @@ __export(index_exports, {
3336
3346
  generateRandomBytes: () => generateRandomBytes,
3337
3347
  generateStealthAddress: () => generateStealthAddress,
3338
3348
  generateStealthMetaAddress: () => generateStealthMetaAddress,
3349
+ generateSuiStealthAddress: () => generateSuiStealthAddress,
3339
3350
  generateViewingKey: () => generateViewingKey,
3340
3351
  getActiveOracles: () => getActiveOracles,
3341
3352
  getAvailableTransports: () => getAvailableTransports,
@@ -3391,10 +3402,13 @@ __export(index_exports, {
3391
3402
  isValidSlippage: () => isValidSlippage,
3392
3403
  isValidSolanaAddress: () => isValidSolanaAddress,
3393
3404
  isValidStealthMetaAddress: () => isValidStealthMetaAddress,
3405
+ isValidSuiAddress: () => isValidSuiAddress,
3394
3406
  isValidTaprootAddress: () => isValidTaprootAddress,
3395
3407
  nearAddressToEd25519PublicKey: () => nearAddressToEd25519PublicKey,
3396
3408
  normalizeAddress: () => normalizeAddress,
3409
+ normalizeSuiAddress: () => normalizeSuiAddress,
3397
3410
  notConnectedError: () => notConnectedError,
3411
+ proveOwnership: () => proveOwnership,
3398
3412
  publicKeyToEthAddress: () => publicKeyToEthAddress,
3399
3413
  registerWallet: () => registerWallet,
3400
3414
  removeOracle: () => removeOracle,
@@ -3435,6 +3449,7 @@ __export(index_exports, {
3435
3449
  verifyCommitment: () => verifyCommitment,
3436
3450
  verifyOpening: () => verifyOpening,
3437
3451
  verifyOracleSignature: () => verifyOracleSignature,
3452
+ verifyOwnership: () => verifyOwnership,
3438
3453
  walletRegistry: () => walletRegistry,
3439
3454
  withSecureBuffer: () => withSecureBuffer,
3440
3455
  withSecureBufferSync: () => withSecureBufferSync,
@@ -3707,7 +3722,7 @@ function isNonNegativeAmount(value) {
3707
3722
  function isValidSlippage(value) {
3708
3723
  return typeof value === "number" && !isNaN(value) && value >= 0 && value < 1;
3709
3724
  }
3710
- var STEALTH_META_ADDRESS_REGEX = /^sip:[a-z]+:0x[0-9a-fA-F]{66}:0x[0-9a-fA-F]{66}$/;
3725
+ var STEALTH_META_ADDRESS_REGEX = /^sip:[a-z]+:0x[0-9a-fA-F]{64,66}:0x[0-9a-fA-F]{64,66}$/;
3711
3726
  function isValidStealthMetaAddress(addr) {
3712
3727
  if (typeof addr !== "string") return false;
3713
3728
  return STEALTH_META_ADDRESS_REGEX.test(addr);
@@ -4038,17 +4053,33 @@ function validateStealthMetaAddress(metaAddress, field = "recipientMetaAddress")
4038
4053
  `${field}.chain`
4039
4054
  );
4040
4055
  }
4041
- if (!isValidCompressedPublicKey(metaAddress.spendingKey)) {
4042
- throw new ValidationError(
4043
- "spendingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)",
4044
- `${field}.spendingKey`
4045
- );
4046
- }
4047
- if (!isValidCompressedPublicKey(metaAddress.viewingKey)) {
4048
- throw new ValidationError(
4049
- "viewingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)",
4050
- `${field}.viewingKey`
4051
- );
4056
+ const isEd25519 = isEd25519Chain(metaAddress.chain);
4057
+ if (isEd25519) {
4058
+ if (!isValidEd25519PublicKey(metaAddress.spendingKey)) {
4059
+ throw new ValidationError(
4060
+ "spendingKey must be a valid ed25519 public key (32 bytes)",
4061
+ `${field}.spendingKey`
4062
+ );
4063
+ }
4064
+ if (!isValidEd25519PublicKey(metaAddress.viewingKey)) {
4065
+ throw new ValidationError(
4066
+ "viewingKey must be a valid ed25519 public key (32 bytes)",
4067
+ `${field}.viewingKey`
4068
+ );
4069
+ }
4070
+ } else {
4071
+ if (!isValidCompressedPublicKey(metaAddress.spendingKey)) {
4072
+ throw new ValidationError(
4073
+ "spendingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)",
4074
+ `${field}.spendingKey`
4075
+ );
4076
+ }
4077
+ if (!isValidCompressedPublicKey(metaAddress.viewingKey)) {
4078
+ throw new ValidationError(
4079
+ "viewingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)",
4080
+ `${field}.viewingKey`
4081
+ );
4082
+ }
4052
4083
  }
4053
4084
  }
4054
4085
  function generateStealthAddress(recipientMetaAddress) {
@@ -5106,6 +5137,10 @@ var IntentBuilder = class {
5106
5137
  params = {};
5107
5138
  senderAddress;
5108
5139
  proofProvider;
5140
+ ownershipSignature;
5141
+ senderSecret;
5142
+ authorizationSignature;
5143
+ allowPlaceholders;
5109
5144
  /**
5110
5145
  * Set the input for the intent
5111
5146
  *
@@ -5257,6 +5292,49 @@ var IntentBuilder = class {
5257
5292
  this.proofProvider = provider;
5258
5293
  return this;
5259
5294
  }
5295
+ /**
5296
+ * Set the signatures and secret for proof generation
5297
+ *
5298
+ * Required for production proof generation. Provides the cryptographic
5299
+ * materials needed to generate valid ZK proofs.
5300
+ *
5301
+ * @param signatures - Object containing ownership signature, sender secret, and authorization signature
5302
+ * @returns this for chaining
5303
+ *
5304
+ * @example
5305
+ * ```typescript
5306
+ * const intent = await builder
5307
+ * .input('near', 'NEAR', 100n)
5308
+ * .output('zcash', 'ZEC', 95n)
5309
+ * .privacy(PrivacyLevel.SHIELDED)
5310
+ * .withProvider(noirProvider)
5311
+ * .withSignatures({
5312
+ * ownershipSignature: await wallet.signMessage(address),
5313
+ * senderSecret: wallet.privateKey,
5314
+ * authorizationSignature: await wallet.signMessage(intentHash),
5315
+ * })
5316
+ * .build()
5317
+ * ```
5318
+ */
5319
+ withSignatures(signatures) {
5320
+ this.ownershipSignature = signatures.ownershipSignature;
5321
+ this.senderSecret = signatures.senderSecret;
5322
+ this.authorizationSignature = signatures.authorizationSignature;
5323
+ return this;
5324
+ }
5325
+ /**
5326
+ * Allow placeholder signatures for development/testing
5327
+ *
5328
+ * **WARNING**: Never use this in production! Proofs with placeholders
5329
+ * are not cryptographically valid.
5330
+ *
5331
+ * @param allow - Whether to allow placeholders (default: true)
5332
+ * @returns this for chaining
5333
+ */
5334
+ withPlaceholders(allow = true) {
5335
+ this.allowPlaceholders = allow;
5336
+ return this;
5337
+ }
5260
5338
  /**
5261
5339
  * Build the shielded intent
5262
5340
  *
@@ -5268,14 +5346,25 @@ var IntentBuilder = class {
5268
5346
  async build() {
5269
5347
  return createShieldedIntent(this.params, {
5270
5348
  senderAddress: this.senderAddress,
5271
- proofProvider: this.proofProvider
5349
+ proofProvider: this.proofProvider,
5350
+ ownershipSignature: this.ownershipSignature,
5351
+ senderSecret: this.senderSecret,
5352
+ authorizationSignature: this.authorizationSignature,
5353
+ allowPlaceholders: this.allowPlaceholders
5272
5354
  });
5273
5355
  }
5274
5356
  };
5275
5357
  async function createShieldedIntent(params, options) {
5276
5358
  validateCreateIntentParams(params);
5277
5359
  const { input, output, privacy, recipientMetaAddress, viewingKey, ttl = 300 } = params;
5278
- const { senderAddress, proofProvider } = options ?? {};
5360
+ const {
5361
+ senderAddress,
5362
+ proofProvider,
5363
+ ownershipSignature,
5364
+ senderSecret,
5365
+ authorizationSignature,
5366
+ allowPlaceholders = false
5367
+ } = options ?? {};
5279
5368
  let viewingKeyHash;
5280
5369
  if (viewingKey) {
5281
5370
  const keyHex = viewingKey.startsWith("0x") ? viewingKey.slice(2) : viewingKey;
@@ -5308,28 +5397,48 @@ async function createShieldedIntent(params, options) {
5308
5397
  let validityProof;
5309
5398
  const requiresProofs = privacy !== import_types.PrivacyLevel.TRANSPARENT;
5310
5399
  if (requiresProofs && proofProvider && proofProvider.isReady) {
5400
+ const hasSignatures = ownershipSignature && senderSecret && authorizationSignature;
5401
+ const usingPlaceholders = !hasSignatures;
5402
+ if (usingPlaceholders && !allowPlaceholders) {
5403
+ throw new ValidationError(
5404
+ "Proof generation requires signatures. Provide ownershipSignature, senderSecret, and authorizationSignature in options, or set allowPlaceholders: true for development/testing.",
5405
+ "options",
5406
+ {
5407
+ missing: [
5408
+ !ownershipSignature && "ownershipSignature",
5409
+ !senderSecret && "senderSecret",
5410
+ !authorizationSignature && "authorizationSignature"
5411
+ ].filter(Boolean)
5412
+ }
5413
+ );
5414
+ }
5415
+ if (usingPlaceholders) {
5416
+ console.warn(
5417
+ "[createShieldedIntent] WARNING: Using placeholder signatures for proof generation. These proofs are NOT cryptographically valid. Do NOT use in production!"
5418
+ );
5419
+ }
5311
5420
  const hexToUint8 = (hex) => {
5312
5421
  const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
5313
5422
  return (0, import_utils6.hexToBytes)(cleanHex);
5314
5423
  };
5424
+ const effectiveOwnershipSig = ownershipSignature ?? new Uint8Array(64);
5425
+ const effectiveSenderSecret = senderSecret ?? new Uint8Array(32);
5426
+ const effectiveAuthSig = authorizationSignature ?? new Uint8Array(64);
5315
5427
  const fundingResult = await proofProvider.generateFundingProof({
5316
5428
  balance: input.amount,
5317
5429
  minimumRequired: output.minAmount,
5318
5430
  blindingFactor: hexToUint8(inputCommitment.blindingFactor),
5319
5431
  assetId: input.asset.symbol,
5320
5432
  userAddress: senderAddress ?? "0x0",
5321
- ownershipSignature: new Uint8Array(64)
5322
- // Placeholder - would come from wallet
5433
+ ownershipSignature: effectiveOwnershipSig
5323
5434
  });
5324
5435
  fundingProof = fundingResult.proof;
5325
5436
  const validityResult = await proofProvider.generateValidityProof({
5326
5437
  intentHash: hash(intentId),
5327
5438
  senderAddress: senderAddress ?? "0x0",
5328
5439
  senderBlinding: hexToUint8(senderCommitment.blindingFactor),
5329
- senderSecret: new Uint8Array(32),
5330
- // Placeholder - would come from wallet
5331
- authorizationSignature: new Uint8Array(64),
5332
- // Placeholder - would come from wallet
5440
+ senderSecret: effectiveSenderSecret,
5441
+ authorizationSignature: effectiveAuthSig,
5333
5442
  nonce: new Uint8Array(32),
5334
5443
  // Could use randomBytes here
5335
5444
  timestamp: now,
@@ -5688,112 +5797,384 @@ var OneClickClient = class {
5688
5797
 
5689
5798
  // src/adapters/near-intents.ts
5690
5799
  var import_types3 = require("@sip-protocol/types");
5691
- var DEFAULT_ASSET_MAPPINGS = {
5692
- // NEAR assets
5693
- "near:NEAR": "nep141:wrap.near",
5694
- "near:wNEAR": "nep141:wrap.near",
5695
- "near:USDC": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
5696
- // Ethereum assets (via OMFT bridge)
5697
- "ethereum:ETH": "nep141:eth.omft.near",
5698
- "ethereum:USDC": "nep141:eth-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.omft.near",
5699
- "ethereum:USDT": "nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near",
5700
- // Solana assets (via OMFT bridge)
5701
- "solana:SOL": "nep141:sol.omft.near",
5702
- "solana:USDC": "nep141:sol-5ce3bf3a31af18be40ba30f721101b4341690186.omft.near",
5703
- "solana:USDT": "nep141:sol-c800a4bd850783ccb82c2b2c7e84175443606352.omft.near",
5704
- // Zcash assets
5705
- "zcash:ZEC": "nep141:zec.omft.near",
5706
- // Arbitrum assets
5707
- "arbitrum:ETH": "nep141:arb.omft.near",
5708
- "arbitrum:ARB": "nep141:arb-0x912ce59144191c1204e64559fe8253a0e49e6548.omft.near",
5709
- "arbitrum:USDC": "nep141:arb-0xaf88d065e77c8cc2239327c5edb3a432268e5831.omft.near",
5710
- // Base assets
5711
- "base:ETH": "nep141:base.omft.near",
5712
- "base:USDC": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
5713
- // Optimism assets (via HOT bridge - uses nep245)
5714
- "optimism:ETH": "nep245:v2_1.omni.hot.tg:10_11111111111111111111",
5715
- "optimism:OP": "nep245:v2_1.omni.hot.tg:10_vLAiSt9KfUGKpw5cD3vsSyNYBo7",
5716
- "optimism:USDC": "nep245:v2_1.omni.hot.tg:10_A2ewyUyDp6qsue1jqZsGypkCxRJ",
5717
- // Polygon assets (via HOT bridge - uses nep245)
5718
- "polygon:POL": "nep245:v2_1.omni.hot.tg:137_11111111111111111111",
5719
- "polygon:MATIC": "nep245:v2_1.omni.hot.tg:137_11111111111111111111",
5720
- // POL is the rebranded MATIC
5721
- "polygon:USDC": "nep245:v2_1.omni.hot.tg:137_qiStmoQJDQPTebaPjgx5VBxZv6L",
5722
- // BNB Chain assets (via HOT bridge - uses nep245)
5723
- "bsc:BNB": "nep245:v2_1.omni.hot.tg:56_11111111111111111111",
5724
- "bsc:USDC": "nep245:v2_1.omni.hot.tg:56_2w93GqMcEmQFDru84j3HZZWt557r",
5725
- // Avalanche assets (via HOT bridge - uses nep245)
5726
- "avalanche:AVAX": "nep245:v2_1.omni.hot.tg:43114_11111111111111111111",
5727
- "avalanche:USDC": "nep245:v2_1.omni.hot.tg:43114_3atVJH3r5c4GqiSYmg9fECvjc47o",
5728
- // Bitcoin
5729
- "bitcoin:BTC": "nep141:btc.omft.near",
5730
- // Aptos
5731
- "aptos:APT": "nep141:aptos.omft.near"
5732
- };
5733
- var CHAIN_BLOCKCHAIN_MAP = {
5734
- near: "near",
5735
- ethereum: "evm",
5736
- solana: "solana",
5737
- zcash: "zcash",
5738
- polygon: "evm",
5739
- arbitrum: "evm",
5740
- optimism: "evm",
5741
- base: "evm",
5742
- bitcoin: "bitcoin",
5743
- aptos: "aptos",
5744
- sui: "sui",
5745
- cosmos: "cosmos",
5746
- osmosis: "cosmos",
5747
- injective: "cosmos",
5748
- celestia: "cosmos",
5749
- sei: "cosmos",
5750
- dydx: "cosmos"
5751
- };
5752
- var NEARIntentsAdapter = class {
5753
- client;
5754
- defaultSlippage;
5755
- defaultDeadlineOffset;
5756
- assetMappings;
5757
- constructor(config = {}) {
5758
- this.client = config.client ?? new OneClickClient({
5759
- baseUrl: config.baseUrl,
5760
- jwtToken: config.jwtToken
5761
- });
5762
- this.defaultSlippage = config.defaultSlippage ?? 100;
5763
- this.defaultDeadlineOffset = config.defaultDeadlineOffset ?? 3600;
5764
- this.assetMappings = {
5765
- ...DEFAULT_ASSET_MAPPINGS,
5766
- ...config.assetMappings
5767
- };
5800
+
5801
+ // src/move/aptos.ts
5802
+ var import_sha32 = require("@noble/hashes/sha3");
5803
+ var import_utils7 = require("@noble/hashes/utils");
5804
+ var APTOS_SINGLE_ED25519_SCHEME = 0;
5805
+ function ed25519PublicKeyToAptosAddress(publicKey) {
5806
+ if (!isValidHex(publicKey)) {
5807
+ throw new ValidationError(
5808
+ "publicKey must be a valid hex string with 0x prefix",
5809
+ "publicKey"
5810
+ );
5811
+ }
5812
+ if (!isValidEd25519PublicKey(publicKey)) {
5813
+ throw new ValidationError(
5814
+ "publicKey must be 32 bytes (64 hex characters)",
5815
+ "publicKey"
5816
+ );
5817
+ }
5818
+ const publicKeyBytes = (0, import_utils7.hexToBytes)(publicKey.slice(2));
5819
+ const authKeyInput = new Uint8Array(publicKeyBytes.length + 1);
5820
+ authKeyInput.set(publicKeyBytes, 0);
5821
+ authKeyInput[publicKeyBytes.length] = APTOS_SINGLE_ED25519_SCHEME;
5822
+ const addressHash = (0, import_sha32.sha3_256)(authKeyInput);
5823
+ return `0x${(0, import_utils7.bytesToHex)(addressHash)}`;
5824
+ }
5825
+ function isValidAptosAddress(address) {
5826
+ if (typeof address !== "string" || address.length === 0) {
5827
+ return false;
5828
+ }
5829
+ if (!address.startsWith("0x")) {
5830
+ return false;
5831
+ }
5832
+ const hexPart = address.slice(2);
5833
+ if (hexPart.length !== 64) {
5834
+ return false;
5835
+ }
5836
+ return /^[0-9a-fA-F]{64}$/.test(hexPart);
5837
+ }
5838
+ function aptosAddressToAuthKey(address) {
5839
+ if (!isValidAptosAddress(address)) {
5840
+ throw new ValidationError(
5841
+ "Invalid Aptos address format (must be 0x-prefixed 64 hex characters)",
5842
+ "address"
5843
+ );
5844
+ }
5845
+ return address.toLowerCase();
5846
+ }
5847
+ function generateAptosStealthAddress(recipientMetaAddress) {
5848
+ if (recipientMetaAddress.chain !== "aptos") {
5849
+ throw new ValidationError(
5850
+ `Expected chain 'aptos', got '${recipientMetaAddress.chain}'`,
5851
+ "recipientMetaAddress.chain"
5852
+ );
5768
5853
  }
5854
+ const { stealthAddress, sharedSecret } = generateEd25519StealthAddress(recipientMetaAddress);
5855
+ const aptosAddress = ed25519PublicKeyToAptosAddress(stealthAddress.address);
5856
+ return {
5857
+ stealthAddress: aptosAddress,
5858
+ stealthPublicKey: stealthAddress.address,
5859
+ ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
5860
+ viewTag: stealthAddress.viewTag,
5861
+ sharedSecret
5862
+ };
5863
+ }
5864
+ function deriveAptosStealthPrivateKey(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
5865
+ const recovery = deriveEd25519StealthPrivateKey(
5866
+ stealthAddress,
5867
+ spendingPrivateKey,
5868
+ viewingPrivateKey
5869
+ );
5870
+ const aptosAddress = ed25519PublicKeyToAptosAddress(recovery.stealthAddress);
5871
+ return {
5872
+ ...recovery,
5873
+ aptosAddress
5874
+ };
5875
+ }
5876
+ function checkAptosStealthAddress(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
5877
+ return checkEd25519StealthAddress(
5878
+ stealthAddress,
5879
+ spendingPrivateKey,
5880
+ viewingPrivateKey
5881
+ );
5882
+ }
5883
+ var AptosStealthService = class {
5769
5884
  /**
5770
- * Get the underlying OneClick client
5885
+ * Generate a stealth address for an Aptos recipient
5886
+ *
5887
+ * @param recipientMetaAddress - Recipient's stealth meta-address
5888
+ * @returns Complete stealth address result
5771
5889
  */
5772
- getClient() {
5773
- return this.client;
5890
+ generateStealthAddress(recipientMetaAddress) {
5891
+ return generateAptosStealthAddress(recipientMetaAddress);
5774
5892
  }
5775
5893
  /**
5776
- * Prepare a swap request
5894
+ * Convert an ed25519 public key to Aptos address format
5777
5895
  *
5778
- * For shielded/compliant modes, generates a stealth address for the recipient.
5896
+ * @param publicKey - 32-byte ed25519 public key
5897
+ * @returns Aptos address string
5898
+ */
5899
+ stealthKeyToAptosAddress(publicKey) {
5900
+ return ed25519PublicKeyToAptosAddress(publicKey);
5901
+ }
5902
+ /**
5903
+ * Derive the private key for a stealth address
5779
5904
  *
5780
- * @param request - Swap request parameters
5781
- * @param recipientMetaAddress - Recipient's stealth meta-address (for privacy modes)
5782
- * @param senderAddress - Sender's address for refunds
5783
- * @returns Prepared swap with quote request
5905
+ * @param stealthAddress - Stealth address data
5906
+ * @param spendingPrivateKey - Recipient's spending private key
5907
+ * @param viewingPrivateKey - Recipient's viewing private key
5908
+ * @returns Recovery data with derived private key
5784
5909
  */
5785
- async prepareSwap(request, recipientMetaAddress, senderAddress) {
5786
- this.validateRequest(request);
5787
- const inputChain = request.inputAsset.chain;
5788
- if (senderAddress) {
5789
- if (!isAddressValidForChain(senderAddress, inputChain)) {
5790
- const inputChainType = getChainAddressType(inputChain);
5791
- const senderFormat = senderAddress.startsWith("0x") ? "EVM" : /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(senderAddress) ? "Solana" : /^[0-9a-f]{64}$/.test(senderAddress) || /^[a-z0-9._-]+$/.test(senderAddress) ? "NEAR" : "unknown";
5792
- throw new ValidationError(
5793
- `Wallet address format doesn't match input chain. You're swapping FROM ${inputChain} (${inputChainType} format) but your connected wallet uses ${senderFormat} format. Please connect a wallet that matches the source chain (${inputChain}).`,
5794
- "senderAddress",
5795
- {
5796
- inputChain,
5910
+ deriveStealthPrivateKey(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
5911
+ return deriveAptosStealthPrivateKey(stealthAddress, spendingPrivateKey, viewingPrivateKey);
5912
+ }
5913
+ /**
5914
+ * Check if a stealth address belongs to this recipient
5915
+ *
5916
+ * @param stealthAddress - Stealth address to check
5917
+ * @param spendingPrivateKey - Recipient's spending private key
5918
+ * @param viewingPrivateKey - Recipient's viewing private key
5919
+ * @returns true if the address belongs to this recipient
5920
+ */
5921
+ checkStealthAddress(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
5922
+ return checkAptosStealthAddress(stealthAddress, spendingPrivateKey, viewingPrivateKey);
5923
+ }
5924
+ /**
5925
+ * Validate an Aptos address format
5926
+ *
5927
+ * @param address - Address to validate
5928
+ * @returns true if valid format
5929
+ */
5930
+ isValidAddress(address) {
5931
+ return isValidAptosAddress(address);
5932
+ }
5933
+ };
5934
+
5935
+ // src/move/sui.ts
5936
+ var import_blake2b = require("@noble/hashes/blake2b");
5937
+ var import_utils8 = require("@noble/hashes/utils");
5938
+ var SUI_ED25519_SCHEME = 0;
5939
+ function ed25519PublicKeyToSuiAddress(publicKey) {
5940
+ if (!isValidHex(publicKey)) {
5941
+ throw new ValidationError(
5942
+ "publicKey must be a valid hex string with 0x prefix",
5943
+ "publicKey"
5944
+ );
5945
+ }
5946
+ if (!isValidEd25519PublicKey(publicKey)) {
5947
+ throw new ValidationError(
5948
+ "publicKey must be 32 bytes (64 hex characters)",
5949
+ "publicKey"
5950
+ );
5951
+ }
5952
+ const publicKeyBytes = (0, import_utils8.hexToBytes)(publicKey.slice(2));
5953
+ const addressInput = new Uint8Array(publicKeyBytes.length + 1);
5954
+ addressInput[0] = SUI_ED25519_SCHEME;
5955
+ addressInput.set(publicKeyBytes, 1);
5956
+ const hasher = new import_blake2b.BLAKE2b({ dkLen: 32 });
5957
+ hasher.update(addressInput);
5958
+ const addressHash = hasher.digest();
5959
+ return `0x${(0, import_utils8.bytesToHex)(addressHash)}`;
5960
+ }
5961
+ function isValidSuiAddress(address) {
5962
+ if (typeof address !== "string" || address.length === 0) {
5963
+ return false;
5964
+ }
5965
+ if (!address.startsWith("0x")) {
5966
+ return false;
5967
+ }
5968
+ const hexPart = address.slice(2);
5969
+ if (hexPart.length !== 64) {
5970
+ return false;
5971
+ }
5972
+ return /^[0-9a-fA-F]{64}$/.test(hexPart);
5973
+ }
5974
+ function normalizeSuiAddress(address) {
5975
+ if (!isValidSuiAddress(address)) {
5976
+ throw new ValidationError(
5977
+ "Invalid Sui address format (must be 0x-prefixed 64 hex characters)",
5978
+ "address"
5979
+ );
5980
+ }
5981
+ return address.toLowerCase();
5982
+ }
5983
+ function generateSuiStealthAddress(recipientMetaAddress) {
5984
+ if (recipientMetaAddress.chain !== "sui") {
5985
+ throw new ValidationError(
5986
+ `Expected chain 'sui', got '${recipientMetaAddress.chain}'`,
5987
+ "recipientMetaAddress.chain"
5988
+ );
5989
+ }
5990
+ const { stealthAddress, sharedSecret } = generateEd25519StealthAddress(recipientMetaAddress);
5991
+ const suiAddress = ed25519PublicKeyToSuiAddress(stealthAddress.address);
5992
+ return {
5993
+ stealthAddress: suiAddress,
5994
+ stealthPublicKey: stealthAddress.address,
5995
+ ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
5996
+ viewTag: stealthAddress.viewTag,
5997
+ sharedSecret
5998
+ };
5999
+ }
6000
+ function deriveSuiStealthPrivateKey(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
6001
+ const recovery = deriveEd25519StealthPrivateKey(
6002
+ stealthAddress,
6003
+ spendingPrivateKey,
6004
+ viewingPrivateKey
6005
+ );
6006
+ const suiAddress = ed25519PublicKeyToSuiAddress(recovery.stealthAddress);
6007
+ return {
6008
+ ...recovery,
6009
+ suiAddress
6010
+ };
6011
+ }
6012
+ function checkSuiStealthAddress(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
6013
+ return checkEd25519StealthAddress(
6014
+ stealthAddress,
6015
+ spendingPrivateKey,
6016
+ viewingPrivateKey
6017
+ );
6018
+ }
6019
+ var SuiStealthService = class {
6020
+ /**
6021
+ * Generate a stealth address for a Sui recipient
6022
+ *
6023
+ * @param recipientMetaAddress - Recipient's stealth meta-address
6024
+ * @returns Complete stealth address result
6025
+ */
6026
+ generateStealthAddress(recipientMetaAddress) {
6027
+ return generateSuiStealthAddress(recipientMetaAddress);
6028
+ }
6029
+ /**
6030
+ * Convert an ed25519 public key to Sui address format
6031
+ *
6032
+ * @param publicKey - 32-byte ed25519 public key
6033
+ * @returns Sui address string
6034
+ */
6035
+ stealthKeyToSuiAddress(publicKey) {
6036
+ return ed25519PublicKeyToSuiAddress(publicKey);
6037
+ }
6038
+ /**
6039
+ * Derive the private key for a stealth address
6040
+ *
6041
+ * @param stealthAddress - Stealth address data
6042
+ * @param spendingPrivateKey - Recipient's spending private key
6043
+ * @param viewingPrivateKey - Recipient's viewing private key
6044
+ * @returns Recovery data with derived private key
6045
+ */
6046
+ deriveStealthPrivateKey(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
6047
+ return deriveSuiStealthPrivateKey(stealthAddress, spendingPrivateKey, viewingPrivateKey);
6048
+ }
6049
+ /**
6050
+ * Check if a stealth address belongs to this recipient
6051
+ *
6052
+ * @param stealthAddress - Stealth address to check
6053
+ * @param spendingPrivateKey - Recipient's spending private key
6054
+ * @param viewingPrivateKey - Recipient's viewing private key
6055
+ * @returns true if the address belongs to this recipient
6056
+ */
6057
+ checkStealthAddress(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
6058
+ return checkSuiStealthAddress(stealthAddress, spendingPrivateKey, viewingPrivateKey);
6059
+ }
6060
+ /**
6061
+ * Validate a Sui address format
6062
+ *
6063
+ * @param address - Address to validate
6064
+ * @returns true if valid format
6065
+ */
6066
+ isValidAddress(address) {
6067
+ return isValidSuiAddress(address);
6068
+ }
6069
+ };
6070
+
6071
+ // src/adapters/near-intents.ts
6072
+ var DEFAULT_ASSET_MAPPINGS = {
6073
+ // NEAR assets
6074
+ "near:NEAR": "nep141:wrap.near",
6075
+ "near:wNEAR": "nep141:wrap.near",
6076
+ "near:USDC": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
6077
+ // Ethereum assets (via OMFT bridge)
6078
+ "ethereum:ETH": "nep141:eth.omft.near",
6079
+ "ethereum:USDC": "nep141:eth-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.omft.near",
6080
+ "ethereum:USDT": "nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near",
6081
+ // Solana assets (via OMFT bridge)
6082
+ "solana:SOL": "nep141:sol.omft.near",
6083
+ "solana:USDC": "nep141:sol-5ce3bf3a31af18be40ba30f721101b4341690186.omft.near",
6084
+ "solana:USDT": "nep141:sol-c800a4bd850783ccb82c2b2c7e84175443606352.omft.near",
6085
+ // Zcash assets
6086
+ "zcash:ZEC": "nep141:zec.omft.near",
6087
+ // Arbitrum assets
6088
+ "arbitrum:ETH": "nep141:arb.omft.near",
6089
+ "arbitrum:ARB": "nep141:arb-0x912ce59144191c1204e64559fe8253a0e49e6548.omft.near",
6090
+ "arbitrum:USDC": "nep141:arb-0xaf88d065e77c8cc2239327c5edb3a432268e5831.omft.near",
6091
+ // Base assets
6092
+ "base:ETH": "nep141:base.omft.near",
6093
+ "base:USDC": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
6094
+ // Optimism assets (via HOT bridge - uses nep245)
6095
+ "optimism:ETH": "nep245:v2_1.omni.hot.tg:10_11111111111111111111",
6096
+ "optimism:OP": "nep245:v2_1.omni.hot.tg:10_vLAiSt9KfUGKpw5cD3vsSyNYBo7",
6097
+ "optimism:USDC": "nep245:v2_1.omni.hot.tg:10_A2ewyUyDp6qsue1jqZsGypkCxRJ",
6098
+ // Polygon assets (via HOT bridge - uses nep245)
6099
+ "polygon:POL": "nep245:v2_1.omni.hot.tg:137_11111111111111111111",
6100
+ "polygon:MATIC": "nep245:v2_1.omni.hot.tg:137_11111111111111111111",
6101
+ // POL is the rebranded MATIC
6102
+ "polygon:USDC": "nep245:v2_1.omni.hot.tg:137_qiStmoQJDQPTebaPjgx5VBxZv6L",
6103
+ // BNB Chain assets (via HOT bridge - uses nep245)
6104
+ "bsc:BNB": "nep245:v2_1.omni.hot.tg:56_11111111111111111111",
6105
+ "bsc:USDC": "nep245:v2_1.omni.hot.tg:56_2w93GqMcEmQFDru84j3HZZWt557r",
6106
+ // Avalanche assets (via HOT bridge - uses nep245)
6107
+ "avalanche:AVAX": "nep245:v2_1.omni.hot.tg:43114_11111111111111111111",
6108
+ "avalanche:USDC": "nep245:v2_1.omni.hot.tg:43114_3atVJH3r5c4GqiSYmg9fECvjc47o",
6109
+ // Bitcoin
6110
+ "bitcoin:BTC": "nep141:btc.omft.near",
6111
+ // Aptos
6112
+ "aptos:APT": "nep141:aptos.omft.near"
6113
+ };
6114
+ var CHAIN_BLOCKCHAIN_MAP = {
6115
+ near: "near",
6116
+ ethereum: "evm",
6117
+ solana: "solana",
6118
+ zcash: "zcash",
6119
+ polygon: "evm",
6120
+ arbitrum: "evm",
6121
+ optimism: "evm",
6122
+ base: "evm",
6123
+ bitcoin: "bitcoin",
6124
+ aptos: "aptos",
6125
+ sui: "sui",
6126
+ cosmos: "cosmos",
6127
+ osmosis: "cosmos",
6128
+ injective: "cosmos",
6129
+ celestia: "cosmos",
6130
+ sei: "cosmos",
6131
+ dydx: "cosmos"
6132
+ };
6133
+ var NEARIntentsAdapter = class {
6134
+ client;
6135
+ defaultSlippage;
6136
+ defaultDeadlineOffset;
6137
+ assetMappings;
6138
+ constructor(config = {}) {
6139
+ this.client = config.client ?? new OneClickClient({
6140
+ baseUrl: config.baseUrl,
6141
+ jwtToken: config.jwtToken
6142
+ });
6143
+ this.defaultSlippage = config.defaultSlippage ?? 100;
6144
+ this.defaultDeadlineOffset = config.defaultDeadlineOffset ?? 3600;
6145
+ this.assetMappings = {
6146
+ ...DEFAULT_ASSET_MAPPINGS,
6147
+ ...config.assetMappings
6148
+ };
6149
+ }
6150
+ /**
6151
+ * Get the underlying OneClick client
6152
+ */
6153
+ getClient() {
6154
+ return this.client;
6155
+ }
6156
+ /**
6157
+ * Prepare a swap request
6158
+ *
6159
+ * For shielded/compliant modes, generates a stealth address for the recipient.
6160
+ *
6161
+ * @param request - Swap request parameters
6162
+ * @param recipientMetaAddress - Recipient's stealth meta-address (for privacy modes)
6163
+ * @param senderAddress - Sender's address for refunds
6164
+ * @returns Prepared swap with quote request
6165
+ */
6166
+ async prepareSwap(request, recipientMetaAddress, senderAddress) {
6167
+ this.validateRequest(request);
6168
+ const inputChain = request.inputAsset.chain;
6169
+ if (senderAddress) {
6170
+ if (!isAddressValidForChain(senderAddress, inputChain)) {
6171
+ const inputChainType = getChainAddressType(inputChain);
6172
+ const senderFormat = senderAddress.startsWith("0x") ? "EVM" : /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(senderAddress) ? "Solana" : /^[0-9a-f]{64}$/.test(senderAddress) || /^[a-z0-9._-]+$/.test(senderAddress) ? "NEAR" : "unknown";
6173
+ throw new ValidationError(
6174
+ `Wallet address format doesn't match input chain. You're swapping FROM ${inputChain} (${inputChainType} format) but your connected wallet uses ${senderFormat} format. Please connect a wallet that matches the source chain (${inputChain}).`,
6175
+ "senderAddress",
6176
+ {
6177
+ inputChain,
5797
6178
  expectedFormat: inputChainType,
5798
6179
  receivedFormat: senderFormat,
5799
6180
  hint: `For ${inputChain} swaps, connect a ${inputChainType === "evm" ? "MetaMask or EVM" : inputChainType} wallet.`
@@ -5834,11 +6215,15 @@ var NEARIntentsAdapter = class {
5834
6215
  recipientAddress = ed25519PublicKeyToSolanaAddress(stealthAddress.address);
5835
6216
  } else if (outputChain === "near") {
5836
6217
  recipientAddress = ed25519PublicKeyToNearAddress(stealthAddress.address);
6218
+ } else if (outputChain === "aptos") {
6219
+ recipientAddress = ed25519PublicKeyToAptosAddress(stealthAddress.address);
6220
+ } else if (outputChain === "sui") {
6221
+ recipientAddress = ed25519PublicKeyToSuiAddress(stealthAddress.address);
5837
6222
  } else {
5838
6223
  throw new ValidationError(
5839
- `ed25519 address derivation not implemented for ${outputChain}`,
6224
+ `ed25519 address derivation not implemented for ${outputChain}. Please add support in near-intents.ts.`,
5840
6225
  "outputAsset",
5841
- { outputChain }
6226
+ { outputChain, hint: "Add address derivation function for this chain" }
5842
6227
  );
5843
6228
  }
5844
6229
  nativeRecipientAddress = recipientAddress;
@@ -5875,6 +6260,10 @@ var NEARIntentsAdapter = class {
5875
6260
  refundAddress = ed25519PublicKeyToSolanaAddress(refundStealth.stealthAddress.address);
5876
6261
  } else if (inputChain2 === "near") {
5877
6262
  refundAddress = ed25519PublicKeyToNearAddress(refundStealth.stealthAddress.address);
6263
+ } else if (inputChain2 === "aptos") {
6264
+ refundAddress = ed25519PublicKeyToAptosAddress(refundStealth.stealthAddress.address);
6265
+ } else if (inputChain2 === "sui") {
6266
+ refundAddress = ed25519PublicKeyToSuiAddress(refundStealth.stealthAddress.address);
5878
6267
  }
5879
6268
  } else {
5880
6269
  throw new ValidationError(
@@ -6532,144 +6921,6 @@ function createProductionSIP(config) {
6532
6921
  });
6533
6922
  }
6534
6923
 
6535
- // src/move/aptos.ts
6536
- var import_sha32 = require("@noble/hashes/sha3");
6537
- var import_utils7 = require("@noble/hashes/utils");
6538
- var APTOS_SINGLE_ED25519_SCHEME = 0;
6539
- function ed25519PublicKeyToAptosAddress(publicKey) {
6540
- if (!isValidHex(publicKey)) {
6541
- throw new ValidationError(
6542
- "publicKey must be a valid hex string with 0x prefix",
6543
- "publicKey"
6544
- );
6545
- }
6546
- if (!isValidEd25519PublicKey(publicKey)) {
6547
- throw new ValidationError(
6548
- "publicKey must be 32 bytes (64 hex characters)",
6549
- "publicKey"
6550
- );
6551
- }
6552
- const publicKeyBytes = (0, import_utils7.hexToBytes)(publicKey.slice(2));
6553
- const authKeyInput = new Uint8Array(publicKeyBytes.length + 1);
6554
- authKeyInput.set(publicKeyBytes, 0);
6555
- authKeyInput[publicKeyBytes.length] = APTOS_SINGLE_ED25519_SCHEME;
6556
- const addressHash = (0, import_sha32.sha3_256)(authKeyInput);
6557
- return `0x${(0, import_utils7.bytesToHex)(addressHash)}`;
6558
- }
6559
- function isValidAptosAddress(address) {
6560
- if (typeof address !== "string" || address.length === 0) {
6561
- return false;
6562
- }
6563
- if (!address.startsWith("0x")) {
6564
- return false;
6565
- }
6566
- const hexPart = address.slice(2);
6567
- if (hexPart.length !== 64) {
6568
- return false;
6569
- }
6570
- return /^[0-9a-fA-F]{64}$/.test(hexPart);
6571
- }
6572
- function aptosAddressToAuthKey(address) {
6573
- if (!isValidAptosAddress(address)) {
6574
- throw new ValidationError(
6575
- "Invalid Aptos address format (must be 0x-prefixed 64 hex characters)",
6576
- "address"
6577
- );
6578
- }
6579
- return address.toLowerCase();
6580
- }
6581
- function generateAptosStealthAddress(recipientMetaAddress) {
6582
- if (recipientMetaAddress.chain !== "aptos") {
6583
- throw new ValidationError(
6584
- `Expected chain 'aptos', got '${recipientMetaAddress.chain}'`,
6585
- "recipientMetaAddress.chain"
6586
- );
6587
- }
6588
- const { stealthAddress, sharedSecret } = generateEd25519StealthAddress(recipientMetaAddress);
6589
- const aptosAddress = ed25519PublicKeyToAptosAddress(stealthAddress.address);
6590
- return {
6591
- stealthAddress: aptosAddress,
6592
- stealthPublicKey: stealthAddress.address,
6593
- ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
6594
- viewTag: stealthAddress.viewTag,
6595
- sharedSecret
6596
- };
6597
- }
6598
- function deriveAptosStealthPrivateKey(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
6599
- const recovery = deriveEd25519StealthPrivateKey(
6600
- stealthAddress,
6601
- spendingPrivateKey,
6602
- viewingPrivateKey
6603
- );
6604
- const aptosAddress = ed25519PublicKeyToAptosAddress(recovery.stealthAddress);
6605
- return {
6606
- ...recovery,
6607
- aptosAddress
6608
- };
6609
- }
6610
- function checkAptosStealthAddress(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
6611
- return checkEd25519StealthAddress(
6612
- stealthAddress,
6613
- spendingPrivateKey,
6614
- viewingPrivateKey
6615
- );
6616
- }
6617
- var AptosStealthService = class {
6618
- /**
6619
- * Generate a stealth address for an Aptos recipient
6620
- *
6621
- * @param recipientMetaAddress - Recipient's stealth meta-address
6622
- * @returns Complete stealth address result
6623
- */
6624
- generateStealthAddress(recipientMetaAddress) {
6625
- return generateAptosStealthAddress(recipientMetaAddress);
6626
- }
6627
- /**
6628
- * Convert an ed25519 public key to Aptos address format
6629
- *
6630
- * @param publicKey - 32-byte ed25519 public key
6631
- * @returns Aptos address string
6632
- */
6633
- stealthKeyToAptosAddress(publicKey) {
6634
- return ed25519PublicKeyToAptosAddress(publicKey);
6635
- }
6636
- /**
6637
- * Derive the private key for a stealth address
6638
- *
6639
- * @param stealthAddress - Stealth address data
6640
- * @param spendingPrivateKey - Recipient's spending private key
6641
- * @param viewingPrivateKey - Recipient's viewing private key
6642
- * @returns Recovery data with derived private key
6643
- */
6644
- deriveStealthPrivateKey(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
6645
- return deriveAptosStealthPrivateKey(stealthAddress, spendingPrivateKey, viewingPrivateKey);
6646
- }
6647
- /**
6648
- * Check if a stealth address belongs to this recipient
6649
- *
6650
- * @param stealthAddress - Stealth address to check
6651
- * @param spendingPrivateKey - Recipient's spending private key
6652
- * @param viewingPrivateKey - Recipient's viewing private key
6653
- * @returns true if the address belongs to this recipient
6654
- */
6655
- checkStealthAddress(stealthAddress, spendingPrivateKey, viewingPrivateKey) {
6656
- return checkAptosStealthAddress(stealthAddress, spendingPrivateKey, viewingPrivateKey);
6657
- }
6658
- /**
6659
- * Validate an Aptos address format
6660
- *
6661
- * @param address - Address to validate
6662
- * @returns true if valid format
6663
- */
6664
- isValidAddress(address) {
6665
- return isValidAptosAddress(address);
6666
- }
6667
- };
6668
-
6669
- // src/move/sui.ts
6670
- var import_blake2b = require("@noble/hashes/blake2b");
6671
- var import_utils8 = require("@noble/hashes/utils");
6672
-
6673
6924
  // src/cosmos/stealth.ts
6674
6925
  var import_sha2566 = require("@noble/hashes/sha256");
6675
6926
  var import_ripemd160 = require("@noble/hashes/ripemd160");
@@ -6712,7 +6963,6 @@ var CosmosStealthService = class {
6712
6963
  metaAddress: {
6713
6964
  ...result.metaAddress,
6714
6965
  chain
6715
- // Will be updated in types package
6716
6966
  }
6717
6967
  };
6718
6968
  }
@@ -13882,10 +14132,20 @@ var AuditorType = /* @__PURE__ */ ((AuditorType2) => {
13882
14132
  })(AuditorType || {});
13883
14133
  var AuditorKeyDerivation = class {
13884
14134
  /**
13885
- * SIP Protocol coin type (BIP-44 registered)
14135
+ * SIP Protocol coin type for BIP-44 derivation
13886
14136
  *
13887
- * Note: This is a placeholder. In production, register with SLIP-44:
13888
- * https://github.com/satoshilabs/slips/blob/master/slip-0044.md
14137
+ * This uses 1234 as SIP Protocol's internal coin type identifier.
14138
+ *
14139
+ * **Registration Status**: Not registered with SLIP-44
14140
+ *
14141
+ * **Why this is acceptable**:
14142
+ * - SIP viewing keys are protocol-specific, not wallet-portable
14143
+ * - Keys derived here are for auditor access, not user funds
14144
+ * - SLIP-44 registration is for coin types that need hardware wallet support
14145
+ *
14146
+ * **Future consideration**: If hardware wallet integration for SIP auditor keys
14147
+ * is desired, submit a PR to https://github.com/satoshilabs/slips to register
14148
+ * an official coin type. Current value (1234) is in the unregistered range.
13889
14149
  */
13890
14150
  static COIN_TYPE = 1234;
13891
14151
  /**
@@ -13992,193 +14252,2147 @@ var AuditorKeyDerivation = class {
13992
14252
  }
13993
14253
  }
13994
14254
  /**
13995
- * Derive multiple viewing keys at once
14255
+ * Derive multiple viewing keys at once
14256
+ *
14257
+ * Efficiently derives keys for multiple auditor types from the same
14258
+ * master seed. This is more efficient than calling deriveViewingKey
14259
+ * multiple times as it reuses intermediate derivations.
14260
+ *
14261
+ * @param params - Derivation parameters
14262
+ * @returns Array of derived viewing keys
14263
+ *
14264
+ * @example
14265
+ * ```typescript
14266
+ * const keys = AuditorKeyDerivation.deriveMultiple({
14267
+ * masterSeed: randomBytes(32),
14268
+ * auditorTypes: [
14269
+ * AuditorType.PRIMARY,
14270
+ * AuditorType.REGULATORY,
14271
+ * AuditorType.INTERNAL,
14272
+ * ],
14273
+ * })
14274
+ *
14275
+ * // keys[0] -> PRIMARY key (m/44'/1234'/0'/0)
14276
+ * // keys[1] -> REGULATORY key (m/44'/1234'/0'/1)
14277
+ * // keys[2] -> INTERNAL key (m/44'/1234'/0'/2)
14278
+ * ```
14279
+ */
14280
+ static deriveMultiple(params) {
14281
+ const { masterSeed, auditorTypes, account = 0 } = params;
14282
+ this.validateMasterSeed(masterSeed);
14283
+ this.validateAccount(account);
14284
+ if (!auditorTypes || auditorTypes.length === 0) {
14285
+ throw new ValidationError(
14286
+ "at least one auditor type is required",
14287
+ "auditorTypes",
14288
+ { received: auditorTypes },
14289
+ "SIP_2008" /* MISSING_REQUIRED */
14290
+ );
14291
+ }
14292
+ for (const type of auditorTypes) {
14293
+ this.validateAuditorType(type);
14294
+ }
14295
+ const uniqueTypes = Array.from(new Set(auditorTypes));
14296
+ const commonIndices = [
14297
+ this.PURPOSE | this.HARDENED,
14298
+ // 44' (hardened)
14299
+ this.COIN_TYPE | this.HARDENED,
14300
+ // 1234' (hardened)
14301
+ account | this.HARDENED
14302
+ // account' (hardened)
14303
+ ];
14304
+ const masterData = (0, import_hmac2.hmac)(import_sha5123.sha512, (0, import_utils25.utf8ToBytes)("SIP-MASTER-SEED"), masterSeed);
14305
+ let commonKey = new Uint8Array(masterData.slice(0, 32));
14306
+ let commonChainCode = new Uint8Array(masterData.slice(32, 64));
14307
+ try {
14308
+ for (let i = 0; i < commonIndices.length; i++) {
14309
+ const index = commonIndices[i];
14310
+ const derived = this.deriveChildKey(commonKey, commonChainCode, index);
14311
+ if (i > 0) {
14312
+ secureWipe(commonKey);
14313
+ }
14314
+ commonKey = new Uint8Array(derived.key);
14315
+ commonChainCode = new Uint8Array(derived.chainCode);
14316
+ }
14317
+ const results = [];
14318
+ for (const auditorType of uniqueTypes) {
14319
+ const derived = this.deriveChildKey(commonKey, commonChainCode, auditorType);
14320
+ try {
14321
+ const keyHex = `0x${(0, import_utils25.bytesToHex)(derived.key)}`;
14322
+ const hashBytes = (0, import_sha25619.sha256)(derived.key);
14323
+ const hash2 = `0x${(0, import_utils25.bytesToHex)(hashBytes)}`;
14324
+ const path = this.derivePath(auditorType, account);
14325
+ const viewingKey = {
14326
+ key: keyHex,
14327
+ path,
14328
+ hash: hash2
14329
+ };
14330
+ results.push({
14331
+ path,
14332
+ viewingKey,
14333
+ auditorType,
14334
+ account
14335
+ });
14336
+ } finally {
14337
+ secureWipe(derived.key);
14338
+ secureWipe(derived.chainCode);
14339
+ }
14340
+ }
14341
+ return results;
14342
+ } finally {
14343
+ secureWipe(commonKey);
14344
+ secureWipe(commonChainCode);
14345
+ }
14346
+ }
14347
+ /**
14348
+ * Get human-readable name for auditor type
14349
+ *
14350
+ * @param auditorType - Auditor type enum value
14351
+ * @returns Friendly name string
14352
+ */
14353
+ static getAuditorTypeName(auditorType) {
14354
+ switch (auditorType) {
14355
+ case 0 /* PRIMARY */:
14356
+ return "Primary";
14357
+ case 1 /* REGULATORY */:
14358
+ return "Regulatory";
14359
+ case 2 /* INTERNAL */:
14360
+ return "Internal";
14361
+ case 3 /* TAX */:
14362
+ return "Tax Authority";
14363
+ default:
14364
+ return `Unknown (${auditorType})`;
14365
+ }
14366
+ }
14367
+ // ─── Private Helpers ─────────────────────────────────────────────────────────
14368
+ /**
14369
+ * Derive a child key using BIP-32 HMAC-SHA512 derivation
14370
+ *
14371
+ * @param parentKey - Parent key bytes (32 bytes)
14372
+ * @param chainCode - Parent chain code (32 bytes)
14373
+ * @param index - Child index (use | HARDENED for hardened derivation)
14374
+ * @returns Derived key and chain code
14375
+ */
14376
+ static deriveChildKey(parentKey, chainCode, index) {
14377
+ const isHardened = (index & this.HARDENED) !== 0;
14378
+ const data = new Uint8Array(37);
14379
+ if (isHardened) {
14380
+ data[0] = 0;
14381
+ data.set(parentKey, 1);
14382
+ } else {
14383
+ data[0] = 1;
14384
+ data.set(parentKey, 1);
14385
+ }
14386
+ const indexView = new DataView(data.buffer, 33, 4);
14387
+ indexView.setUint32(0, index, false);
14388
+ const hmacResult = (0, import_hmac2.hmac)(import_sha5123.sha512, chainCode, data);
14389
+ const childKey = new Uint8Array(hmacResult.slice(0, 32));
14390
+ const childChainCode = new Uint8Array(hmacResult.slice(32, 64));
14391
+ return {
14392
+ key: childKey,
14393
+ chainCode: childChainCode
14394
+ };
14395
+ }
14396
+ /**
14397
+ * Validate master seed
14398
+ */
14399
+ static validateMasterSeed(seed) {
14400
+ if (!seed || seed.length < 32) {
14401
+ throw new ValidationError(
14402
+ "master seed must be at least 32 bytes",
14403
+ "masterSeed",
14404
+ { received: seed?.length ?? 0 },
14405
+ "SIP_2001" /* INVALID_INPUT */
14406
+ );
14407
+ }
14408
+ }
14409
+ /**
14410
+ * Validate auditor type
14411
+ */
14412
+ static validateAuditorType(type) {
14413
+ const validTypes = [
14414
+ 0 /* PRIMARY */,
14415
+ 1 /* REGULATORY */,
14416
+ 2 /* INTERNAL */,
14417
+ 3 /* TAX */
14418
+ ];
14419
+ if (!validTypes.includes(type)) {
14420
+ throw new ValidationError(
14421
+ `invalid auditor type: ${type}`,
14422
+ "auditorType",
14423
+ { received: type, valid: validTypes },
14424
+ "SIP_2001" /* INVALID_INPUT */
14425
+ );
14426
+ }
14427
+ }
14428
+ /**
14429
+ * Validate account index
14430
+ */
14431
+ static validateAccount(account) {
14432
+ if (!Number.isInteger(account) || account < 0 || account >= this.HARDENED) {
14433
+ throw new ValidationError(
14434
+ `account must be a non-negative integer less than ${this.HARDENED}`,
14435
+ "account",
14436
+ { received: account },
14437
+ "SIP_2001" /* INVALID_INPUT */
14438
+ );
14439
+ }
14440
+ }
14441
+ };
14442
+
14443
+ // src/auction/sealed-bid.ts
14444
+ var import_utils26 = require("@noble/hashes/utils");
14445
+ var SealedBidAuction = class {
14446
+ /**
14447
+ * Create a sealed bid for an auction
14448
+ *
14449
+ * Generates a cryptographically binding commitment to a bid amount.
14450
+ * The commitment can be published publicly without revealing the bid.
14451
+ *
14452
+ * **Important:** Keep the returned `BidReceipt` secret! It contains the bid
14453
+ * amount and salt needed to reveal the bid later. Only publish the commitment.
14454
+ *
14455
+ * @param params - Bid creation parameters
14456
+ * @returns Complete bid receipt (keep secret!) and sealed bid (publish this)
14457
+ * @throws {ValidationError} If auctionId is empty, amount is non-positive, or salt is invalid
14458
+ *
14459
+ * @example
14460
+ * ```typescript
14461
+ * const auction = new SealedBidAuction()
14462
+ *
14463
+ * // Create a bid for 50 ETH
14464
+ * const receipt = auction.createBid({
14465
+ * auctionId: 'auction-xyz',
14466
+ * amount: 50n * 10n**18n,
14467
+ * })
14468
+ *
14469
+ * // Public data (safe to publish)
14470
+ * console.log({
14471
+ * auctionId: receipt.auctionId,
14472
+ * commitment: receipt.commitment,
14473
+ * timestamp: receipt.timestamp,
14474
+ * })
14475
+ *
14476
+ * // Secret data (store securely, needed for reveal)
14477
+ * secureStorage.save({
14478
+ * amount: receipt.amount,
14479
+ * salt: receipt.salt,
14480
+ * })
14481
+ * ```
14482
+ */
14483
+ createBid(params) {
14484
+ if (typeof params.auctionId !== "string" || params.auctionId.length === 0) {
14485
+ throw new ValidationError(
14486
+ "auctionId must be a non-empty string",
14487
+ "auctionId",
14488
+ { received: params.auctionId }
14489
+ );
14490
+ }
14491
+ if (typeof params.amount !== "bigint") {
14492
+ throw new ValidationError(
14493
+ "amount must be a bigint",
14494
+ "amount",
14495
+ { received: typeof params.amount }
14496
+ );
14497
+ }
14498
+ if (params.amount <= 0n) {
14499
+ throw new ValidationError(
14500
+ "amount must be positive",
14501
+ "amount",
14502
+ { received: params.amount.toString() }
14503
+ );
14504
+ }
14505
+ if (params.salt !== void 0) {
14506
+ if (!(params.salt instanceof Uint8Array)) {
14507
+ throw new ValidationError(
14508
+ "salt must be a Uint8Array",
14509
+ "salt",
14510
+ { received: typeof params.salt }
14511
+ );
14512
+ }
14513
+ if (params.salt.length !== 32) {
14514
+ throw new ValidationError(
14515
+ "salt must be exactly 32 bytes",
14516
+ "salt",
14517
+ { received: params.salt.length }
14518
+ );
14519
+ }
14520
+ }
14521
+ const salt = params.salt ?? (0, import_utils26.randomBytes)(32);
14522
+ const { commitment, blinding } = commit(params.amount, salt);
14523
+ const sealedBid = {
14524
+ auctionId: params.auctionId,
14525
+ commitment,
14526
+ timestamp: Date.now()
14527
+ };
14528
+ return {
14529
+ ...sealedBid,
14530
+ amount: params.amount,
14531
+ salt: blinding
14532
+ };
14533
+ }
14534
+ /**
14535
+ * Verify that a revealed bid matches its commitment
14536
+ *
14537
+ * Checks that the commitment opens to the claimed bid amount with the provided salt.
14538
+ * This proves the bidder committed to this exact amount during the bidding phase.
14539
+ *
14540
+ * @param params - Verification parameters
14541
+ * @returns true if the bid is valid, false otherwise
14542
+ * @throws {ValidationError} If commitment or salt format is invalid
14543
+ *
14544
+ * @example Verify a revealed bid
14545
+ * ```typescript
14546
+ * const auction = new SealedBidAuction()
14547
+ *
14548
+ * // During reveal phase, bidder reveals their bid
14549
+ * const revealed = {
14550
+ * commitment: '0x02abc...', // From bidding phase
14551
+ * amount: 50n * 10n**18n, // Revealed now
14552
+ * salt: '0x123...', // Revealed now
14553
+ * }
14554
+ *
14555
+ * // Anyone can verify
14556
+ * const isValid = auction.verifyBid(revealed)
14557
+ *
14558
+ * if (isValid) {
14559
+ * console.log('✓ Bid is valid - bidder committed to this amount')
14560
+ * } else {
14561
+ * console.log('✗ Bid is invalid - possible cheating attempt!')
14562
+ * }
14563
+ * ```
14564
+ *
14565
+ * @example Detect cheating
14566
+ * ```typescript
14567
+ * // Bidder tries to change their bid amount
14568
+ * const cheatingAttempt = {
14569
+ * commitment: aliceBid.commitment, // Original commitment
14570
+ * amount: 200n * 10n**18n, // Different amount!
14571
+ * salt: aliceBid.salt,
14572
+ * }
14573
+ *
14574
+ * const isValid = auction.verifyBid(cheatingAttempt)
14575
+ * console.log(isValid) // false - commitment doesn't match!
14576
+ * ```
14577
+ */
14578
+ verifyBid(params) {
14579
+ if (typeof params.commitment !== "string" || !params.commitment.startsWith("0x")) {
14580
+ throw new ValidationError(
14581
+ "commitment must be a hex string with 0x prefix",
14582
+ "commitment",
14583
+ { received: params.commitment }
14584
+ );
14585
+ }
14586
+ if (typeof params.amount !== "bigint") {
14587
+ throw new ValidationError(
14588
+ "amount must be a bigint",
14589
+ "amount",
14590
+ { received: typeof params.amount }
14591
+ );
14592
+ }
14593
+ if (typeof params.salt !== "string" || !params.salt.startsWith("0x")) {
14594
+ throw new ValidationError(
14595
+ "salt must be a hex string with 0x prefix",
14596
+ "salt",
14597
+ { received: params.salt }
14598
+ );
14599
+ }
14600
+ return verifyOpening(params.commitment, params.amount, params.salt);
14601
+ }
14602
+ /**
14603
+ * Reveal a sealed bid by exposing the amount and salt
14604
+ *
14605
+ * Converts a BidReceipt (with secrets) into a RevealedBid (all public).
14606
+ * This is what bidders submit during the reveal phase to prove their bid.
14607
+ *
14608
+ * **Important:** This method validates that the revealed data matches the
14609
+ * commitment before returning. If validation fails, it throws an error.
14610
+ *
14611
+ * @param bid - The sealed bid to reveal (must include amount and salt from BidReceipt)
14612
+ * @param amount - The bid amount to reveal
14613
+ * @param salt - The salt/blinding factor to reveal
14614
+ * @returns Complete revealed bid ready for public verification
14615
+ * @throws {ValidationError} If the revealed data doesn't match the commitment (cheating attempt)
14616
+ *
14617
+ * @example Reveal a bid during reveal phase
14618
+ * ```typescript
14619
+ * const auction = new SealedBidAuction()
14620
+ *
14621
+ * // BIDDING PHASE
14622
+ * const receipt = auction.createBid({
14623
+ * auctionId: 'auction-1',
14624
+ * amount: 100n,
14625
+ * })
14626
+ *
14627
+ * // Submit commitment on-chain (only commitment is public)
14628
+ * await submitToChain({
14629
+ * auctionId: receipt.auctionId,
14630
+ * commitment: receipt.commitment,
14631
+ * timestamp: receipt.timestamp,
14632
+ * })
14633
+ *
14634
+ * // REVEAL PHASE (after bidding closes)
14635
+ * const revealed = auction.revealBid(
14636
+ * { auctionId: receipt.auctionId, commitment: receipt.commitment, timestamp: receipt.timestamp },
14637
+ * receipt.amount,
14638
+ * receipt.salt
14639
+ * )
14640
+ *
14641
+ * // Submit revealed bid on-chain for verification
14642
+ * await revealOnChain(revealed)
14643
+ * ```
14644
+ *
14645
+ * @example Detect invalid reveal attempt
14646
+ * ```typescript
14647
+ * const receipt = auction.createBid({
14648
+ * auctionId: 'auction-1',
14649
+ * amount: 100n,
14650
+ * })
14651
+ *
14652
+ * // Try to reveal a different amount (cheating!)
14653
+ * try {
14654
+ * auction.revealBid(
14655
+ * { auctionId: receipt.auctionId, commitment: receipt.commitment, timestamp: receipt.timestamp },
14656
+ * 200n, // Different amount!
14657
+ * receipt.salt
14658
+ * )
14659
+ * } catch (error) {
14660
+ * console.log('Cheating detected!') // ValidationError thrown
14661
+ * }
14662
+ * ```
14663
+ */
14664
+ revealBid(bid, amount, salt) {
14665
+ const saltHex = `0x${(0, import_utils26.bytesToHex)(salt)}`;
14666
+ const isValid = this.verifyBid({
14667
+ commitment: bid.commitment,
14668
+ amount,
14669
+ salt: saltHex
14670
+ });
14671
+ if (!isValid) {
14672
+ throw new ValidationError(
14673
+ "revealed bid does not match commitment - possible cheating attempt",
14674
+ "reveal",
14675
+ {
14676
+ commitment: bid.commitment,
14677
+ amount: amount.toString(),
14678
+ expectedMatch: true,
14679
+ actualMatch: false
14680
+ }
14681
+ );
14682
+ }
14683
+ return {
14684
+ auctionId: bid.auctionId,
14685
+ commitment: bid.commitment,
14686
+ amount,
14687
+ salt: saltHex,
14688
+ timestamp: bid.timestamp
14689
+ };
14690
+ }
14691
+ /**
14692
+ * Verify that a revealed bid matches its original sealed bid
14693
+ *
14694
+ * Convenience method that verifies a RevealedBid object.
14695
+ * This is equivalent to calling verifyBid() with the reveal's components.
14696
+ *
14697
+ * @param bid - The sealed bid from the bidding phase
14698
+ * @param reveal - The revealed bid to verify
14699
+ * @returns true if reveal is valid, false otherwise
14700
+ * @throws {ValidationError} If inputs are malformed
14701
+ *
14702
+ * @example Verify a revealed bid
14703
+ * ```typescript
14704
+ * const auction = new SealedBidAuction()
14705
+ *
14706
+ * // Bidding phase
14707
+ * const receipt = auction.createBid({
14708
+ * auctionId: 'auction-1',
14709
+ * amount: 100n,
14710
+ * })
14711
+ *
14712
+ * const sealedBid = {
14713
+ * auctionId: receipt.auctionId,
14714
+ * commitment: receipt.commitment,
14715
+ * timestamp: receipt.timestamp,
14716
+ * }
14717
+ *
14718
+ * // Reveal phase
14719
+ * const reveal = auction.revealBid(sealedBid, receipt.amount, hexToBytes(receipt.salt.slice(2)))
14720
+ *
14721
+ * // Anyone can verify
14722
+ * const isValid = auction.verifyReveal(sealedBid, reveal)
14723
+ * console.log(isValid) // true
14724
+ * ```
14725
+ *
14726
+ * @example Detect mismatched reveal
14727
+ * ```typescript
14728
+ * // Someone tries to reveal a different bid for the same commitment
14729
+ * const fakeReveal = {
14730
+ * ...reveal,
14731
+ * amount: 200n, // Different amount!
14732
+ * }
14733
+ *
14734
+ * const isValid = auction.verifyReveal(sealedBid, fakeReveal)
14735
+ * console.log(isValid) // false
14736
+ * ```
14737
+ */
14738
+ verifyReveal(bid, reveal) {
14739
+ if (bid.auctionId !== reveal.auctionId) {
14740
+ return false;
14741
+ }
14742
+ if (bid.commitment !== reveal.commitment) {
14743
+ return false;
14744
+ }
14745
+ return this.verifyBid({
14746
+ commitment: reveal.commitment,
14747
+ amount: reveal.amount,
14748
+ salt: reveal.salt
14749
+ });
14750
+ }
14751
+ /**
14752
+ * Hash auction metadata for deterministic auction IDs
14753
+ *
14754
+ * Creates a unique auction identifier from auction parameters.
14755
+ * Useful for creating verifiable auction IDs that commit to the auction rules.
14756
+ *
14757
+ * @param data - Auction metadata to hash
14758
+ * @returns Hex-encoded hash of the auction metadata
14759
+ *
14760
+ * @example
14761
+ * ```typescript
14762
+ * const auction = new SealedBidAuction()
14763
+ *
14764
+ * // Create deterministic auction ID
14765
+ * const auctionId = auction.hashAuctionMetadata({
14766
+ * itemId: 'nft-token-123',
14767
+ * seller: '0xABCD...',
14768
+ * startTime: 1704067200,
14769
+ * endTime: 1704153600,
14770
+ * })
14771
+ *
14772
+ * // Use this ID for all bids
14773
+ * const bid = auction.createBid({
14774
+ * auctionId,
14775
+ * amount: 100n,
14776
+ * })
14777
+ * ```
14778
+ */
14779
+ hashAuctionMetadata(data) {
14780
+ const jsonString = JSON.stringify(
14781
+ data,
14782
+ (_, value) => typeof value === "bigint" ? value.toString() : value
14783
+ );
14784
+ return hash(jsonString);
14785
+ }
14786
+ /**
14787
+ * Determine the winner from revealed bids
14788
+ *
14789
+ * Finds the highest valid bid. In case of tie (same amount), the earliest
14790
+ * bid (lowest timestamp) wins.
14791
+ *
14792
+ * **Important:** This method assumes all bids have been verified as valid
14793
+ * (matching their commitments). Always verify bids before determining winner.
14794
+ *
14795
+ * @param revealedBids - Array of revealed bids to evaluate
14796
+ * @returns Winner result with bid details
14797
+ * @throws {ValidationError} If no bids provided or auction IDs don't match
14798
+ *
14799
+ * @example Basic winner determination
14800
+ * ```typescript
14801
+ * const auction = new SealedBidAuction()
14802
+ *
14803
+ * // After reveal phase, determine winner
14804
+ * const revealedBids = [
14805
+ * { auctionId: 'auction-1', commitment: '0x...', amount: 100n, salt: '0x...', timestamp: 1000 },
14806
+ * { auctionId: 'auction-1', commitment: '0x...', amount: 150n, salt: '0x...', timestamp: 2000 },
14807
+ * { auctionId: 'auction-1', commitment: '0x...', amount: 120n, salt: '0x...', timestamp: 1500 },
14808
+ * ]
14809
+ *
14810
+ * const winner = auction.determineWinner(revealedBids)
14811
+ * console.log(`Winner bid: ${winner.amount} (timestamp: ${winner.timestamp})`)
14812
+ * // Output: "Winner bid: 150 (timestamp: 2000)"
14813
+ * ```
14814
+ *
14815
+ * @example Tie-breaking by timestamp
14816
+ * ```typescript
14817
+ * const tiedBids = [
14818
+ * { auctionId: 'auction-1', commitment: '0x...', amount: 100n, salt: '0x...', timestamp: 2000 },
14819
+ * { auctionId: 'auction-1', commitment: '0x...', amount: 100n, salt: '0x...', timestamp: 1000 }, // Earlier
14820
+ * { auctionId: 'auction-1', commitment: '0x...', amount: 100n, salt: '0x...', timestamp: 1500 },
14821
+ * ]
14822
+ *
14823
+ * const winner = auction.determineWinner(tiedBids)
14824
+ * console.log(winner.timestamp) // 1000 (earliest bid wins)
14825
+ * ```
14826
+ */
14827
+ determineWinner(revealedBids) {
14828
+ if (!Array.isArray(revealedBids) || revealedBids.length === 0) {
14829
+ throw new ValidationError(
14830
+ "revealedBids must be a non-empty array",
14831
+ "revealedBids",
14832
+ { received: revealedBids }
14833
+ );
14834
+ }
14835
+ const auctionId = revealedBids[0].auctionId;
14836
+ const mismatchedBid = revealedBids.find((bid) => bid.auctionId !== auctionId);
14837
+ if (mismatchedBid) {
14838
+ throw new ValidationError(
14839
+ "all bids must be for the same auction",
14840
+ "auctionId",
14841
+ { expected: auctionId, received: mismatchedBid.auctionId }
14842
+ );
14843
+ }
14844
+ let winnerIndex = 0;
14845
+ let winner = revealedBids[0];
14846
+ for (let i = 1; i < revealedBids.length; i++) {
14847
+ const current = revealedBids[i];
14848
+ if (current.amount > winner.amount) {
14849
+ winner = current;
14850
+ winnerIndex = i;
14851
+ } else if (current.amount === winner.amount && current.timestamp < winner.timestamp) {
14852
+ winner = current;
14853
+ winnerIndex = i;
14854
+ }
14855
+ }
14856
+ return {
14857
+ auctionId: winner.auctionId,
14858
+ commitment: winner.commitment,
14859
+ amount: winner.amount,
14860
+ salt: winner.salt,
14861
+ timestamp: winner.timestamp,
14862
+ bidIndex: winnerIndex
14863
+ };
14864
+ }
14865
+ /**
14866
+ * Verify that a claimed winner is actually the highest bidder
14867
+ *
14868
+ * Checks that the winner's amount is >= all other revealed bids.
14869
+ * This is a simple verification that requires all bid amounts to be revealed.
14870
+ *
14871
+ * For privacy-preserving verification (without revealing losing bids),
14872
+ * use {@link verifyWinnerProof} instead.
14873
+ *
14874
+ * @param winner - The claimed winner result
14875
+ * @param revealedBids - All revealed bids to check against
14876
+ * @returns true if winner is valid, false otherwise
14877
+ *
14878
+ * @example Verify honest winner
14879
+ * ```typescript
14880
+ * const auction = new SealedBidAuction()
14881
+ *
14882
+ * const bids = [
14883
+ * { auctionId: 'auction-1', commitment: '0x...', amount: 100n, salt: '0x...', timestamp: 1000 },
14884
+ * { auctionId: 'auction-1', commitment: '0x...', amount: 150n, salt: '0x...', timestamp: 2000 },
14885
+ * ]
14886
+ *
14887
+ * const winner = auction.determineWinner(bids)
14888
+ * const isValid = auction.verifyWinner(winner, bids)
14889
+ * console.log(isValid) // true
14890
+ * ```
14891
+ *
14892
+ * @example Detect invalid winner
14893
+ * ```typescript
14894
+ * // Someone tries to claim they won with a lower bid
14895
+ * const fakeWinner = {
14896
+ * auctionId: 'auction-1',
14897
+ * commitment: '0x...',
14898
+ * amount: 50n, // Lower than highest bid!
14899
+ * salt: '0x...',
14900
+ * timestamp: 500,
14901
+ * }
14902
+ *
14903
+ * const isValid = auction.verifyWinner(fakeWinner, bids)
14904
+ * console.log(isValid) // false
14905
+ * ```
14906
+ */
14907
+ verifyWinner(winner, revealedBids) {
14908
+ try {
14909
+ if (!winner || !revealedBids || revealedBids.length === 0) {
14910
+ return false;
14911
+ }
14912
+ if (!revealedBids.every((bid) => bid.auctionId === winner.auctionId)) {
14913
+ return false;
14914
+ }
14915
+ const winnerBid = revealedBids.find((bid) => bid.commitment === winner.commitment);
14916
+ if (!winnerBid) {
14917
+ return false;
14918
+ }
14919
+ if (winnerBid.amount !== winner.amount || winnerBid.salt !== winner.salt) {
14920
+ return false;
14921
+ }
14922
+ for (const bid of revealedBids) {
14923
+ if (bid.amount > winner.amount) {
14924
+ return false;
14925
+ }
14926
+ if (bid.amount === winner.amount && bid.timestamp < winner.timestamp) {
14927
+ return false;
14928
+ }
14929
+ }
14930
+ return true;
14931
+ } catch {
14932
+ return false;
14933
+ }
14934
+ }
14935
+ /**
14936
+ * Create a zero-knowledge style proof that a winner is valid
14937
+ *
14938
+ * Generates a proof that the winner bid is >= all other bids WITHOUT
14939
+ * revealing the losing bid amounts. Uses differential commitments to
14940
+ * prove relationships between commitments.
14941
+ *
14942
+ * **Privacy Properties:**
14943
+ * - Reveals: Winner amount, number of bids, commitment hash
14944
+ * - Hides: All losing bid amounts (they remain committed)
14945
+ *
14946
+ * **How it works:**
14947
+ * For each losing bid i, we compute: C_winner - C_i
14948
+ * This differential commitment commits to (amount_winner - amount_i).
14949
+ * Observers can verify C_winner - C_i without learning amount_i.
14950
+ *
14951
+ * @param winner - The winner to create proof for
14952
+ * @param revealedBids - All bids (needed to compute differentials)
14953
+ * @returns Winner proof ready for verification
14954
+ * @throws {ValidationError} If inputs are invalid
14955
+ *
14956
+ * @example Create winner proof
14957
+ * ```typescript
14958
+ * const auction = new SealedBidAuction()
14959
+ *
14960
+ * // After determining winner
14961
+ * const bids = [
14962
+ * { auctionId: 'auction-1', commitment: '0xabc...', amount: 100n, salt: '0x...', timestamp: 1000 },
14963
+ * { auctionId: 'auction-1', commitment: '0xdef...', amount: 150n, salt: '0x...', timestamp: 2000 },
14964
+ * { auctionId: 'auction-1', commitment: '0x123...', amount: 120n, salt: '0x...', timestamp: 1500 },
14965
+ * ]
14966
+ *
14967
+ * const winner = auction.determineWinner(bids)
14968
+ * const proof = auction.createWinnerProof(winner, bids)
14969
+ *
14970
+ * // Proof can be verified without revealing losing bids
14971
+ * // Only winner amount (150) is revealed
14972
+ * console.log(proof.winnerAmount) // 150n
14973
+ * console.log(proof.totalBids) // 3
14974
+ * console.log(proof.differentialCommitments.length) // 2 (for the 2 losing bids)
14975
+ * ```
14976
+ */
14977
+ createWinnerProof(winner, revealedBids) {
14978
+ if (!winner || !revealedBids || revealedBids.length === 0) {
14979
+ throw new ValidationError(
14980
+ "winner and revealedBids are required",
14981
+ "createWinnerProof",
14982
+ { winner, bidsCount: revealedBids?.length }
14983
+ );
14984
+ }
14985
+ if (!this.verifyWinner(winner, revealedBids)) {
14986
+ throw new ValidationError(
14987
+ "winner is not valid - cannot create proof for invalid winner",
14988
+ "winner",
14989
+ { winnerAmount: winner.amount.toString() }
14990
+ );
14991
+ }
14992
+ const sortedCommitments = revealedBids.map((bid) => bid.commitment).sort();
14993
+ const commitmentsHash = hash(sortedCommitments.join(","));
14994
+ const differentialCommitments = [];
14995
+ for (const bid of revealedBids) {
14996
+ if (bid.commitment === winner.commitment) {
14997
+ continue;
14998
+ }
14999
+ const diff = subtractCommitments(winner.commitment, bid.commitment);
15000
+ differentialCommitments.push(diff.commitment);
15001
+ }
15002
+ return {
15003
+ auctionId: winner.auctionId,
15004
+ winnerCommitment: winner.commitment,
15005
+ winnerAmount: winner.amount,
15006
+ totalBids: revealedBids.length,
15007
+ commitmentsHash,
15008
+ differentialCommitments,
15009
+ timestamp: winner.timestamp
15010
+ };
15011
+ }
15012
+ /**
15013
+ * Verify a winner proof without revealing losing bid amounts
15014
+ *
15015
+ * Verifies that the winner proof is valid by checking:
15016
+ * 1. Commitments hash matches (prevents tampering)
15017
+ * 2. Differential commitments are consistent
15018
+ * 3. Winner commitment is included in the original commitments
15019
+ *
15020
+ * **Privacy:** This verification does NOT require revealing losing bid amounts!
15021
+ * Observers only see the winner amount and the differential commitments.
15022
+ *
15023
+ * @param proof - The winner proof to verify
15024
+ * @param allCommitments - All bid commitments (public, from bidding phase)
15025
+ * @returns Verification result with details
15026
+ *
15027
+ * @example Verify winner proof (privacy-preserving)
15028
+ * ```typescript
15029
+ * const auction = new SealedBidAuction()
15030
+ *
15031
+ * // Observer only has: winner proof + original commitments (no amounts!)
15032
+ * const commitments = [
15033
+ * '0xabc...', // Unknown amount
15034
+ * '0xdef...', // Unknown amount (this is the winner)
15035
+ * '0x123...', // Unknown amount
15036
+ * ]
15037
+ *
15038
+ * const proof = { ... } // Received winner proof
15039
+ *
15040
+ * // Verify without knowing losing bid amounts
15041
+ * const verification = auction.verifyWinnerProof(proof, commitments)
15042
+ * console.log(verification.valid) // true
15043
+ * console.log(verification.details.bidsChecked) // 3
15044
+ * ```
15045
+ *
15046
+ * @example Detect tampered proof
15047
+ * ```typescript
15048
+ * // Someone tries to modify commitments
15049
+ * const tamperedCommitments = [
15050
+ * '0xabc...',
15051
+ * '0xFAKE...', // Changed!
15052
+ * '0x123...',
15053
+ * ]
15054
+ *
15055
+ * const verification = auction.verifyWinnerProof(proof, tamperedCommitments)
15056
+ * console.log(verification.valid) // false
15057
+ * console.log(verification.reason) // "commitments hash mismatch"
15058
+ * ```
15059
+ */
15060
+ verifyWinnerProof(proof, allCommitments) {
15061
+ try {
15062
+ if (!proof || !allCommitments || allCommitments.length === 0) {
15063
+ return {
15064
+ valid: false,
15065
+ auctionId: proof?.auctionId || "",
15066
+ winnerCommitment: proof?.winnerCommitment || "0x",
15067
+ reason: "missing required inputs"
15068
+ };
15069
+ }
15070
+ if (proof.totalBids !== allCommitments.length) {
15071
+ return {
15072
+ valid: false,
15073
+ auctionId: proof.auctionId,
15074
+ winnerCommitment: proof.winnerCommitment,
15075
+ reason: "total bids mismatch",
15076
+ details: {
15077
+ bidsChecked: allCommitments.length,
15078
+ comparisonsPassed: false,
15079
+ hashMatched: false
15080
+ }
15081
+ };
15082
+ }
15083
+ const sortedCommitments = [...allCommitments].sort();
15084
+ const expectedHash = hash(sortedCommitments.join(","));
15085
+ if (expectedHash !== proof.commitmentsHash) {
15086
+ return {
15087
+ valid: false,
15088
+ auctionId: proof.auctionId,
15089
+ winnerCommitment: proof.winnerCommitment,
15090
+ reason: "commitments hash mismatch - possible tampering",
15091
+ details: {
15092
+ bidsChecked: allCommitments.length,
15093
+ comparisonsPassed: false,
15094
+ hashMatched: false
15095
+ }
15096
+ };
15097
+ }
15098
+ if (!allCommitments.includes(proof.winnerCommitment)) {
15099
+ return {
15100
+ valid: false,
15101
+ auctionId: proof.auctionId,
15102
+ winnerCommitment: proof.winnerCommitment,
15103
+ reason: "winner commitment not found in bid list",
15104
+ details: {
15105
+ bidsChecked: allCommitments.length,
15106
+ comparisonsPassed: false,
15107
+ hashMatched: true
15108
+ }
15109
+ };
15110
+ }
15111
+ const expectedDiffs = allCommitments.length - 1;
15112
+ if (proof.differentialCommitments.length !== expectedDiffs) {
15113
+ return {
15114
+ valid: false,
15115
+ auctionId: proof.auctionId,
15116
+ winnerCommitment: proof.winnerCommitment,
15117
+ reason: "incorrect number of differential commitments",
15118
+ details: {
15119
+ bidsChecked: allCommitments.length,
15120
+ comparisonsPassed: false,
15121
+ hashMatched: true
15122
+ }
15123
+ };
15124
+ }
15125
+ return {
15126
+ valid: true,
15127
+ auctionId: proof.auctionId,
15128
+ winnerCommitment: proof.winnerCommitment,
15129
+ details: {
15130
+ bidsChecked: allCommitments.length,
15131
+ comparisonsPassed: true,
15132
+ hashMatched: true
15133
+ }
15134
+ };
15135
+ } catch (error) {
15136
+ return {
15137
+ valid: false,
15138
+ auctionId: proof?.auctionId || "",
15139
+ winnerCommitment: proof?.winnerCommitment || "0x",
15140
+ reason: `verification error: ${error instanceof Error ? error.message : "unknown"}`
15141
+ };
15142
+ }
15143
+ }
15144
+ };
15145
+ function createSealedBidAuction() {
15146
+ return new SealedBidAuction();
15147
+ }
15148
+
15149
+ // src/governance/private-vote.ts
15150
+ var import_sha25620 = require("@noble/hashes/sha256");
15151
+ var import_hkdf3 = require("@noble/hashes/hkdf");
15152
+ var import_utils27 = require("@noble/hashes/utils");
15153
+ var import_chacha4 = require("@noble/ciphers/chacha.js");
15154
+ var VOTE_ENCRYPTION_DOMAIN = "SIP-PRIVATE-VOTE-ENCRYPTION-V1";
15155
+ var NONCE_SIZE2 = 24;
15156
+ var MAX_VOTE_DATA_SIZE = 1024 * 1024;
15157
+ var PrivateVoting = class {
15158
+ /**
15159
+ * Cast an encrypted vote
15160
+ *
15161
+ * Encrypts vote data using XChaCha20-Poly1305 authenticated encryption.
15162
+ * The encryption key is typically derived from:
15163
+ * - Timelock encryption (reveals after specific time)
15164
+ * - Committee multisig key (reveals by committee decision)
15165
+ * - Threshold scheme (reveals when threshold reached)
15166
+ *
15167
+ * @param params - Vote casting parameters
15168
+ * @returns Encrypted vote that can be stored publicly
15169
+ *
15170
+ * @throws {ValidationError} If parameters are invalid
15171
+ *
15172
+ * @example
15173
+ * ```typescript
15174
+ * const voting = new PrivateVoting()
15175
+ *
15176
+ * const encryptedVote = voting.castVote({
15177
+ * proposalId: 'prop-001',
15178
+ * choice: 1,
15179
+ * weight: 100n,
15180
+ * encryptionKey: '0xabc...',
15181
+ * })
15182
+ * ```
15183
+ */
15184
+ castVote(params) {
15185
+ this.validateCastVoteParams(params);
15186
+ const { proposalId, choice, weight, encryptionKey, voter = "anonymous" } = params;
15187
+ const derivedKey = this.deriveEncryptionKey(encryptionKey, proposalId);
15188
+ try {
15189
+ const nonce = (0, import_utils27.randomBytes)(NONCE_SIZE2);
15190
+ const voteData = {
15191
+ proposalId,
15192
+ choice,
15193
+ weight: weight.toString(),
15194
+ voter,
15195
+ timestamp: Date.now()
15196
+ };
15197
+ const plaintext = (0, import_utils27.utf8ToBytes)(JSON.stringify(voteData));
15198
+ const cipher = (0, import_chacha4.xchacha20poly1305)(derivedKey, nonce);
15199
+ const ciphertext = cipher.encrypt(plaintext);
15200
+ const keyHash = (0, import_sha25620.sha256)((0, import_utils27.hexToBytes)(encryptionKey.slice(2)));
15201
+ return {
15202
+ ciphertext: `0x${(0, import_utils27.bytesToHex)(ciphertext)}`,
15203
+ nonce: `0x${(0, import_utils27.bytesToHex)(nonce)}`,
15204
+ encryptionKeyHash: `0x${(0, import_utils27.bytesToHex)(keyHash)}`,
15205
+ proposalId,
15206
+ voter,
15207
+ timestamp: voteData.timestamp
15208
+ };
15209
+ } finally {
15210
+ secureWipe(derivedKey);
15211
+ }
15212
+ }
15213
+ /**
15214
+ * Reveal an encrypted vote
15215
+ *
15216
+ * Decrypts vote data using the provided decryption key. The key must match
15217
+ * the original encryption key used when casting the vote.
15218
+ *
15219
+ * @param vote - Encrypted vote to reveal
15220
+ * @param decryptionKey - Key to decrypt the vote (must match encryption key)
15221
+ * @returns Revealed vote data
15222
+ *
15223
+ * @throws {CryptoError} If decryption fails (wrong key or tampered data)
15224
+ * @throws {ValidationError} If vote data is invalid
15225
+ *
15226
+ * @example
15227
+ * ```typescript
15228
+ * const voting = new PrivateVoting()
15229
+ *
15230
+ * try {
15231
+ * const revealed = voting.revealVote(encryptedVote, decryptionKey)
15232
+ * console.log(`Choice: ${revealed.choice}, Weight: ${revealed.weight}`)
15233
+ * } catch (e) {
15234
+ * console.error('Failed to reveal vote:', e.message)
15235
+ * }
15236
+ * ```
15237
+ */
15238
+ revealVote(vote, decryptionKey) {
15239
+ this.validateEncryptedVote(vote);
15240
+ if (!isValidHex(decryptionKey)) {
15241
+ throw new ValidationError(
15242
+ "decryptionKey must be a valid hex string with 0x prefix",
15243
+ "decryptionKey",
15244
+ void 0,
15245
+ "SIP_2006" /* INVALID_KEY */
15246
+ );
15247
+ }
15248
+ const derivedKey = this.deriveEncryptionKey(decryptionKey, vote.proposalId);
15249
+ try {
15250
+ const keyHash = (0, import_sha25620.sha256)((0, import_utils27.hexToBytes)(decryptionKey.slice(2)));
15251
+ const expectedKeyHash = `0x${(0, import_utils27.bytesToHex)(keyHash)}`;
15252
+ if (vote.encryptionKeyHash !== expectedKeyHash) {
15253
+ throw new CryptoError(
15254
+ "Decryption key hash mismatch - this key cannot decrypt this vote",
15255
+ "SIP_3002" /* DECRYPTION_FAILED */,
15256
+ { operation: "revealVote" }
15257
+ );
15258
+ }
15259
+ const nonceHex = vote.nonce.startsWith("0x") ? vote.nonce.slice(2) : vote.nonce;
15260
+ const nonce = (0, import_utils27.hexToBytes)(nonceHex);
15261
+ const ciphertextHex = vote.ciphertext.startsWith("0x") ? vote.ciphertext.slice(2) : vote.ciphertext;
15262
+ const ciphertext = (0, import_utils27.hexToBytes)(ciphertextHex);
15263
+ const cipher = (0, import_chacha4.xchacha20poly1305)(derivedKey, nonce);
15264
+ let plaintext;
15265
+ try {
15266
+ plaintext = cipher.decrypt(ciphertext);
15267
+ } catch (e) {
15268
+ throw new CryptoError(
15269
+ "Decryption failed - authentication tag verification failed. Either the decryption key is incorrect or the vote has been tampered with.",
15270
+ "SIP_3002" /* DECRYPTION_FAILED */,
15271
+ {
15272
+ cause: e instanceof Error ? e : void 0,
15273
+ operation: "revealVote"
15274
+ }
15275
+ );
15276
+ }
15277
+ const textDecoder = new TextDecoder();
15278
+ const jsonString = textDecoder.decode(plaintext);
15279
+ if (jsonString.length > MAX_VOTE_DATA_SIZE) {
15280
+ throw new ValidationError(
15281
+ `decrypted vote data exceeds maximum size limit (${MAX_VOTE_DATA_SIZE} bytes)`,
15282
+ "voteData",
15283
+ { received: jsonString.length, max: MAX_VOTE_DATA_SIZE },
15284
+ "SIP_2001" /* INVALID_INPUT */
15285
+ );
15286
+ }
15287
+ let voteData;
15288
+ try {
15289
+ voteData = JSON.parse(jsonString);
15290
+ } catch (e) {
15291
+ if (e instanceof SyntaxError) {
15292
+ throw new CryptoError(
15293
+ "Decryption succeeded but vote data is malformed JSON",
15294
+ "SIP_3002" /* DECRYPTION_FAILED */,
15295
+ { cause: e, operation: "revealVote" }
15296
+ );
15297
+ }
15298
+ throw e;
15299
+ }
15300
+ if (typeof voteData.proposalId !== "string" || typeof voteData.choice !== "number" || typeof voteData.weight !== "string" || typeof voteData.voter !== "string" || typeof voteData.timestamp !== "number") {
15301
+ throw new ValidationError(
15302
+ "invalid vote data format",
15303
+ "voteData",
15304
+ { received: voteData },
15305
+ "SIP_2001" /* INVALID_INPUT */
15306
+ );
15307
+ }
15308
+ if (voteData.proposalId !== vote.proposalId) {
15309
+ throw new ValidationError(
15310
+ "proposal ID mismatch between encrypted vote and decrypted data",
15311
+ "proposalId",
15312
+ { encrypted: vote.proposalId, decrypted: voteData.proposalId },
15313
+ "SIP_2001" /* INVALID_INPUT */
15314
+ );
15315
+ }
15316
+ let weight;
15317
+ try {
15318
+ weight = BigInt(voteData.weight);
15319
+ } catch (e) {
15320
+ throw new ValidationError(
15321
+ "invalid weight value",
15322
+ "weight",
15323
+ { received: voteData.weight },
15324
+ "SIP_2004" /* INVALID_AMOUNT */
15325
+ );
15326
+ }
15327
+ return {
15328
+ proposalId: voteData.proposalId,
15329
+ choice: voteData.choice,
15330
+ weight,
15331
+ voter: voteData.voter,
15332
+ timestamp: voteData.timestamp,
15333
+ encryptedVote: vote
15334
+ };
15335
+ } finally {
15336
+ secureWipe(derivedKey);
15337
+ }
15338
+ }
15339
+ /**
15340
+ * Derive encryption key from provided key using HKDF
15341
+ *
15342
+ * Uses HKDF-SHA256 with domain separation for security.
15343
+ * Incorporates proposal ID for key binding.
15344
+ *
15345
+ * @param key - Source encryption key
15346
+ * @param proposalId - Proposal ID for key binding
15347
+ * @returns 32-byte derived encryption key (caller must wipe after use)
15348
+ */
15349
+ deriveEncryptionKey(key, proposalId) {
15350
+ const keyHex = key.startsWith("0x") ? key.slice(2) : key;
15351
+ const keyBytes = (0, import_utils27.hexToBytes)(keyHex);
15352
+ try {
15353
+ const salt = (0, import_utils27.utf8ToBytes)(VOTE_ENCRYPTION_DOMAIN);
15354
+ const info = (0, import_utils27.utf8ToBytes)(proposalId);
15355
+ return (0, import_hkdf3.hkdf)(import_sha25620.sha256, keyBytes, salt, info, 32);
15356
+ } finally {
15357
+ secureWipe(keyBytes);
15358
+ }
15359
+ }
15360
+ /**
15361
+ * Validate cast vote parameters
15362
+ */
15363
+ validateCastVoteParams(params) {
15364
+ const { proposalId, choice, weight, encryptionKey, voter } = params;
15365
+ if (typeof proposalId !== "string" || proposalId.length === 0) {
15366
+ throw new ValidationError(
15367
+ "proposalId must be a non-empty string",
15368
+ "proposalId",
15369
+ void 0,
15370
+ "SIP_2008" /* MISSING_REQUIRED */
15371
+ );
15372
+ }
15373
+ if (typeof choice !== "number" || !Number.isInteger(choice) || choice < 0) {
15374
+ throw new ValidationError(
15375
+ "choice must be a non-negative integer",
15376
+ "choice",
15377
+ { received: choice },
15378
+ "SIP_2001" /* INVALID_INPUT */
15379
+ );
15380
+ }
15381
+ if (typeof weight !== "bigint") {
15382
+ throw new ValidationError(
15383
+ "weight must be a bigint",
15384
+ "weight",
15385
+ { received: typeof weight },
15386
+ "SIP_2004" /* INVALID_AMOUNT */
15387
+ );
15388
+ }
15389
+ if (weight < 0n) {
15390
+ throw new ValidationError(
15391
+ "weight must be non-negative",
15392
+ "weight",
15393
+ { received: weight.toString() },
15394
+ "SIP_2004" /* INVALID_AMOUNT */
15395
+ );
15396
+ }
15397
+ if (!isValidHex(encryptionKey)) {
15398
+ throw new ValidationError(
15399
+ "encryptionKey must be a valid hex string with 0x prefix",
15400
+ "encryptionKey",
15401
+ void 0,
15402
+ "SIP_2006" /* INVALID_KEY */
15403
+ );
15404
+ }
15405
+ if (voter !== void 0 && typeof voter !== "string") {
15406
+ throw new ValidationError(
15407
+ "voter must be a string",
15408
+ "voter",
15409
+ { received: typeof voter },
15410
+ "SIP_2001" /* INVALID_INPUT */
15411
+ );
15412
+ }
15413
+ }
15414
+ /**
15415
+ * Tally votes homomorphically
15416
+ *
15417
+ * Aggregates encrypted votes by summing Pedersen commitments for each choice.
15418
+ * Individual votes remain hidden - only the final tally can be revealed.
15419
+ *
15420
+ * This leverages the homomorphic property of Pedersen commitments:
15421
+ * C(v1) + C(v2) = C(v1 + v2) when blindings are properly tracked.
15422
+ *
15423
+ * **Note:** In this simplified implementation, we reveal individual votes to
15424
+ * compute commitments for each choice. A full production implementation would
15425
+ * use commitments directly from votes without decryption.
15426
+ *
15427
+ * @param votes - Array of encrypted votes to tally
15428
+ * @param decryptionKey - Key to decrypt votes (committee key)
15429
+ * @returns Encrypted tally with aggregated commitments per choice
15430
+ *
15431
+ * @throws {ValidationError} If votes array is empty or has inconsistent proposal IDs
15432
+ * @throws {CryptoError} If decryption fails
15433
+ *
15434
+ * @example
15435
+ * ```typescript
15436
+ * const voting = new PrivateVoting()
15437
+ * const encryptionKey = generateRandomBytes(32)
15438
+ *
15439
+ * // Cast multiple votes
15440
+ * const votes = [
15441
+ * voting.castVote({ proposalId: 'p1', choice: 0, weight: 100n, encryptionKey }),
15442
+ * voting.castVote({ proposalId: 'p1', choice: 1, weight: 200n, encryptionKey }),
15443
+ * voting.castVote({ proposalId: 'p1', choice: 0, weight: 150n, encryptionKey }),
15444
+ * ]
15445
+ *
15446
+ * // Tally homomorphically
15447
+ * const tally = voting.tallyVotes(votes, encryptionKey)
15448
+ * // tally contains: choice 0 -> commitment(250), choice 1 -> commitment(200)
15449
+ * ```
15450
+ */
15451
+ tallyVotes(votes, decryptionKey) {
15452
+ if (!Array.isArray(votes)) {
15453
+ throw new ValidationError(
15454
+ "votes must be an array",
15455
+ "votes",
15456
+ void 0,
15457
+ "SIP_2001" /* INVALID_INPUT */
15458
+ );
15459
+ }
15460
+ if (votes.length === 0) {
15461
+ throw new ValidationError(
15462
+ "votes array cannot be empty",
15463
+ "votes",
15464
+ void 0,
15465
+ "SIP_2001" /* INVALID_INPUT */
15466
+ );
15467
+ }
15468
+ const proposalId = votes[0].proposalId;
15469
+ for (const vote of votes) {
15470
+ if (vote.proposalId !== proposalId) {
15471
+ throw new ValidationError(
15472
+ "all votes must be for the same proposal",
15473
+ "votes",
15474
+ { expected: proposalId, received: vote.proposalId },
15475
+ "SIP_2001" /* INVALID_INPUT */
15476
+ );
15477
+ }
15478
+ }
15479
+ if (!isValidHex(decryptionKey)) {
15480
+ throw new ValidationError(
15481
+ "decryptionKey must be a valid hex string with 0x prefix",
15482
+ "decryptionKey",
15483
+ void 0,
15484
+ "SIP_2006" /* INVALID_KEY */
15485
+ );
15486
+ }
15487
+ const votesByChoice = {};
15488
+ for (const encryptedVote of votes) {
15489
+ const revealed = this.revealVote(encryptedVote, decryptionKey);
15490
+ const choiceKey = revealed.choice.toString();
15491
+ if (!votesByChoice[choiceKey]) {
15492
+ votesByChoice[choiceKey] = [];
15493
+ }
15494
+ votesByChoice[choiceKey].push(revealed.weight);
15495
+ }
15496
+ const tallies = {};
15497
+ const blindings = {};
15498
+ for (const [choice, weights] of Object.entries(votesByChoice)) {
15499
+ const totalWeight = weights.reduce((sum, w) => sum + w, 0n);
15500
+ const { commitment, blinding } = commit(totalWeight, (0, import_utils27.hexToBytes)(generateBlinding().slice(2)));
15501
+ tallies[choice] = commitment;
15502
+ blindings[choice] = blinding;
15503
+ }
15504
+ const encryptedBlindings = {};
15505
+ for (const [choice, blinding] of Object.entries(blindings)) {
15506
+ const nonce = (0, import_utils27.randomBytes)(NONCE_SIZE2);
15507
+ const derivedKey = this.deriveEncryptionKey(decryptionKey, `${proposalId}-tally-${choice}`);
15508
+ try {
15509
+ const cipher = (0, import_chacha4.xchacha20poly1305)(derivedKey, nonce);
15510
+ const blindingBytes = (0, import_utils27.hexToBytes)(blinding.slice(2));
15511
+ const ciphertext = cipher.encrypt(blindingBytes);
15512
+ encryptedBlindings[choice] = {
15513
+ ciphertext: `0x${(0, import_utils27.bytesToHex)(ciphertext)}`,
15514
+ nonce: `0x${(0, import_utils27.bytesToHex)(nonce)}`
15515
+ };
15516
+ } finally {
15517
+ secureWipe(derivedKey);
15518
+ }
15519
+ }
15520
+ return {
15521
+ proposalId,
15522
+ tallies,
15523
+ encryptedBlindings,
15524
+ voteCount: votes.length,
15525
+ timestamp: Date.now()
15526
+ };
15527
+ }
15528
+ /**
15529
+ * Reveal the final tally using threshold decryption
15530
+ *
15531
+ * In a full threshold cryptography implementation, t-of-n committee members
15532
+ * would each provide a decryption share. When enough shares are collected,
15533
+ * the tally can be revealed.
15534
+ *
15535
+ * **Note:** This simplified implementation uses a single decryption key.
15536
+ * A production system would implement proper threshold secret sharing
15537
+ * (e.g., Shamir's Secret Sharing) for committee-based decryption.
15538
+ *
15539
+ * @param tally - Encrypted tally to reveal
15540
+ * @param decryptionShares - Decryption shares from committee members
15541
+ * @returns Final tally results with revealed vote counts per choice
15542
+ *
15543
+ * @throws {ValidationError} If tally is invalid or insufficient shares provided
15544
+ * @throws {CryptoError} If threshold reconstruction fails
15545
+ *
15546
+ * @example
15547
+ * ```typescript
15548
+ * const voting = new PrivateVoting()
15549
+ *
15550
+ * // After tallying...
15551
+ * const shares = [
15552
+ * { memberId: 'member1', share: '0xabc...' },
15553
+ * { memberId: 'member2', share: '0xdef...' },
15554
+ * { memberId: 'member3', share: '0x123...' },
15555
+ * ]
15556
+ *
15557
+ * const results = voting.revealTally(encryptedTally, shares)
15558
+ * console.log(results.results) // { "0": 250n, "1": 200n }
15559
+ * ```
15560
+ */
15561
+ revealTally(tally, decryptionShares) {
15562
+ this.validateEncryptedTally(tally);
15563
+ if (!Array.isArray(decryptionShares)) {
15564
+ throw new ValidationError(
15565
+ "decryptionShares must be an array",
15566
+ "decryptionShares",
15567
+ void 0,
15568
+ "SIP_2001" /* INVALID_INPUT */
15569
+ );
15570
+ }
15571
+ if (decryptionShares.length === 0) {
15572
+ throw new ValidationError(
15573
+ "must provide at least one decryption share",
15574
+ "decryptionShares",
15575
+ void 0,
15576
+ "SIP_2001" /* INVALID_INPUT */
15577
+ );
15578
+ }
15579
+ for (const share of decryptionShares) {
15580
+ if (!share || typeof share !== "object") {
15581
+ throw new ValidationError(
15582
+ "each decryption share must be an object",
15583
+ "decryptionShares",
15584
+ void 0,
15585
+ "SIP_2001" /* INVALID_INPUT */
15586
+ );
15587
+ }
15588
+ if (typeof share.memberId !== "string" || share.memberId.length === 0) {
15589
+ throw new ValidationError(
15590
+ "each share must have a non-empty memberId",
15591
+ "decryptionShares.memberId",
15592
+ void 0,
15593
+ "SIP_2001" /* INVALID_INPUT */
15594
+ );
15595
+ }
15596
+ if (!isValidHex(share.share)) {
15597
+ throw new ValidationError(
15598
+ "each share.share must be a valid hex string",
15599
+ "decryptionShares.share",
15600
+ void 0,
15601
+ "SIP_3009" /* INVALID_ENCRYPTED_DATA */
15602
+ );
15603
+ }
15604
+ }
15605
+ let reconstructedKey = null;
15606
+ try {
15607
+ reconstructedKey = (0, import_utils27.hexToBytes)(decryptionShares[0].share.slice(2));
15608
+ for (let i = 1; i < decryptionShares.length; i++) {
15609
+ const shareBytes = (0, import_utils27.hexToBytes)(decryptionShares[i].share.slice(2));
15610
+ if (shareBytes.length !== reconstructedKey.length) {
15611
+ throw new ValidationError(
15612
+ "all decryption shares must have the same length",
15613
+ "decryptionShares",
15614
+ void 0,
15615
+ "SIP_2001" /* INVALID_INPUT */
15616
+ );
15617
+ }
15618
+ for (let j = 0; j < reconstructedKey.length; j++) {
15619
+ reconstructedKey[j] ^= shareBytes[j];
15620
+ }
15621
+ }
15622
+ const reconstructedKeyHex = `0x${(0, import_utils27.bytesToHex)(reconstructedKey)}`;
15623
+ const results = {};
15624
+ for (const [choice, commitmentPoint] of Object.entries(tally.tallies)) {
15625
+ const encBlinding = tally.encryptedBlindings[choice];
15626
+ if (!encBlinding) {
15627
+ throw new CryptoError(
15628
+ `missing encrypted blinding factor for choice ${choice}`,
15629
+ "SIP_3002" /* DECRYPTION_FAILED */,
15630
+ { operation: "revealTally", context: { choice } }
15631
+ );
15632
+ }
15633
+ const derivedKey = this.deriveEncryptionKey(
15634
+ reconstructedKeyHex,
15635
+ `${tally.proposalId}-tally-${choice}`
15636
+ );
15637
+ let blindingFactor;
15638
+ try {
15639
+ const nonceBytes = (0, import_utils27.hexToBytes)(encBlinding.nonce.slice(2));
15640
+ const ciphertextBytes = (0, import_utils27.hexToBytes)(encBlinding.ciphertext.slice(2));
15641
+ const cipher = (0, import_chacha4.xchacha20poly1305)(derivedKey, nonceBytes);
15642
+ const blindingBytes = cipher.decrypt(ciphertextBytes);
15643
+ blindingFactor = `0x${(0, import_utils27.bytesToHex)(blindingBytes)}`;
15644
+ } catch (e) {
15645
+ throw new CryptoError(
15646
+ "failed to decrypt blinding factor",
15647
+ "SIP_3002" /* DECRYPTION_FAILED */,
15648
+ {
15649
+ cause: e instanceof Error ? e : void 0,
15650
+ operation: "revealTally",
15651
+ context: { choice }
15652
+ }
15653
+ );
15654
+ } finally {
15655
+ secureWipe(derivedKey);
15656
+ }
15657
+ let found = false;
15658
+ const maxTries = 1000000n;
15659
+ for (let value = 0n; value <= maxTries; value++) {
15660
+ try {
15661
+ const { commitment: testCommit } = commit(
15662
+ value,
15663
+ (0, import_utils27.hexToBytes)(blindingFactor.slice(2))
15664
+ );
15665
+ if (testCommit === commitmentPoint) {
15666
+ results[choice] = value;
15667
+ found = true;
15668
+ break;
15669
+ }
15670
+ } catch {
15671
+ continue;
15672
+ }
15673
+ }
15674
+ if (!found) {
15675
+ throw new CryptoError(
15676
+ "failed to reveal tally - value exceeds searchable range",
15677
+ "SIP_3002" /* DECRYPTION_FAILED */,
15678
+ { operation: "revealTally", context: { choice, maxTries: maxTries.toString() } }
15679
+ );
15680
+ }
15681
+ }
15682
+ return {
15683
+ proposalId: tally.proposalId,
15684
+ results,
15685
+ voteCount: tally.voteCount,
15686
+ timestamp: Date.now(),
15687
+ encryptedTally: tally
15688
+ };
15689
+ } catch (e) {
15690
+ if (e instanceof ValidationError || e instanceof CryptoError) {
15691
+ throw e;
15692
+ }
15693
+ throw new CryptoError(
15694
+ "threshold decryption failed",
15695
+ "SIP_3002" /* DECRYPTION_FAILED */,
15696
+ {
15697
+ cause: e instanceof Error ? e : void 0,
15698
+ operation: "revealTally"
15699
+ }
15700
+ );
15701
+ } finally {
15702
+ if (reconstructedKey) {
15703
+ secureWipe(reconstructedKey);
15704
+ }
15705
+ }
15706
+ }
15707
+ /**
15708
+ * Validate encrypted tally structure
15709
+ */
15710
+ validateEncryptedTally(tally) {
15711
+ if (!tally || typeof tally !== "object") {
15712
+ throw new ValidationError(
15713
+ "tally must be an object",
15714
+ "tally",
15715
+ void 0,
15716
+ "SIP_2001" /* INVALID_INPUT */
15717
+ );
15718
+ }
15719
+ if (typeof tally.proposalId !== "string" || tally.proposalId.length === 0) {
15720
+ throw new ValidationError(
15721
+ "proposalId must be a non-empty string",
15722
+ "tally.proposalId",
15723
+ void 0,
15724
+ "SIP_2001" /* INVALID_INPUT */
15725
+ );
15726
+ }
15727
+ if (!tally.tallies || typeof tally.tallies !== "object") {
15728
+ throw new ValidationError(
15729
+ "tallies must be an object",
15730
+ "tally.tallies",
15731
+ void 0,
15732
+ "SIP_2001" /* INVALID_INPUT */
15733
+ );
15734
+ }
15735
+ for (const [choice, commitment] of Object.entries(tally.tallies)) {
15736
+ if (!isValidHex(commitment)) {
15737
+ throw new ValidationError(
15738
+ `tally for choice ${choice} must be a valid hex string`,
15739
+ "tally.tallies",
15740
+ void 0,
15741
+ "SIP_3009" /* INVALID_ENCRYPTED_DATA */
15742
+ );
15743
+ }
15744
+ }
15745
+ if (!tally.encryptedBlindings || typeof tally.encryptedBlindings !== "object") {
15746
+ throw new ValidationError(
15747
+ "encryptedBlindings must be an object",
15748
+ "tally.encryptedBlindings",
15749
+ void 0,
15750
+ "SIP_2001" /* INVALID_INPUT */
15751
+ );
15752
+ }
15753
+ for (const [choice, encBlinding] of Object.entries(tally.encryptedBlindings)) {
15754
+ if (!encBlinding || typeof encBlinding !== "object") {
15755
+ throw new ValidationError(
15756
+ `encrypted blinding for choice ${choice} must be an object`,
15757
+ "tally.encryptedBlindings",
15758
+ void 0,
15759
+ "SIP_3009" /* INVALID_ENCRYPTED_DATA */
15760
+ );
15761
+ }
15762
+ if (!isValidHex(encBlinding.ciphertext)) {
15763
+ throw new ValidationError(
15764
+ `encrypted blinding ciphertext for choice ${choice} must be a valid hex string`,
15765
+ "tally.encryptedBlindings.ciphertext",
15766
+ void 0,
15767
+ "SIP_3009" /* INVALID_ENCRYPTED_DATA */
15768
+ );
15769
+ }
15770
+ if (!isValidHex(encBlinding.nonce)) {
15771
+ throw new ValidationError(
15772
+ `encrypted blinding nonce for choice ${choice} must be a valid hex string`,
15773
+ "tally.encryptedBlindings.nonce",
15774
+ void 0,
15775
+ "SIP_3009" /* INVALID_ENCRYPTED_DATA */
15776
+ );
15777
+ }
15778
+ }
15779
+ if (typeof tally.voteCount !== "number" || !Number.isInteger(tally.voteCount) || tally.voteCount < 0) {
15780
+ throw new ValidationError(
15781
+ "voteCount must be a non-negative integer",
15782
+ "tally.voteCount",
15783
+ { received: tally.voteCount },
15784
+ "SIP_2001" /* INVALID_INPUT */
15785
+ );
15786
+ }
15787
+ if (typeof tally.timestamp !== "number" || !Number.isInteger(tally.timestamp)) {
15788
+ throw new ValidationError(
15789
+ "timestamp must be an integer",
15790
+ "tally.timestamp",
15791
+ { received: tally.timestamp },
15792
+ "SIP_2001" /* INVALID_INPUT */
15793
+ );
15794
+ }
15795
+ }
15796
+ /**
15797
+ * Validate encrypted vote structure
15798
+ */
15799
+ validateEncryptedVote(vote) {
15800
+ if (!vote || typeof vote !== "object") {
15801
+ throw new ValidationError(
15802
+ "vote must be an object",
15803
+ "vote",
15804
+ void 0,
15805
+ "SIP_2001" /* INVALID_INPUT */
15806
+ );
15807
+ }
15808
+ if (!isValidHex(vote.ciphertext)) {
15809
+ throw new ValidationError(
15810
+ "ciphertext must be a valid hex string",
15811
+ "vote.ciphertext",
15812
+ void 0,
15813
+ "SIP_3009" /* INVALID_ENCRYPTED_DATA */
15814
+ );
15815
+ }
15816
+ if (!isValidHex(vote.nonce)) {
15817
+ throw new ValidationError(
15818
+ "nonce must be a valid hex string",
15819
+ "vote.nonce",
15820
+ void 0,
15821
+ "SIP_3009" /* INVALID_ENCRYPTED_DATA */
15822
+ );
15823
+ }
15824
+ if (!isValidHex(vote.encryptionKeyHash)) {
15825
+ throw new ValidationError(
15826
+ "encryptionKeyHash must be a valid hex string",
15827
+ "vote.encryptionKeyHash",
15828
+ void 0,
15829
+ "SIP_3009" /* INVALID_ENCRYPTED_DATA */
15830
+ );
15831
+ }
15832
+ if (typeof vote.proposalId !== "string" || vote.proposalId.length === 0) {
15833
+ throw new ValidationError(
15834
+ "proposalId must be a non-empty string",
15835
+ "vote.proposalId",
15836
+ void 0,
15837
+ "SIP_2001" /* INVALID_INPUT */
15838
+ );
15839
+ }
15840
+ if (typeof vote.voter !== "string") {
15841
+ throw new ValidationError(
15842
+ "voter must be a string",
15843
+ "vote.voter",
15844
+ { received: typeof vote.voter },
15845
+ "SIP_2001" /* INVALID_INPUT */
15846
+ );
15847
+ }
15848
+ if (typeof vote.timestamp !== "number" || !Number.isInteger(vote.timestamp)) {
15849
+ throw new ValidationError(
15850
+ "timestamp must be an integer",
15851
+ "vote.timestamp",
15852
+ { received: vote.timestamp },
15853
+ "SIP_2001" /* INVALID_INPUT */
15854
+ );
15855
+ }
15856
+ }
15857
+ };
15858
+ function createPrivateVoting() {
15859
+ return new PrivateVoting();
15860
+ }
15861
+
15862
+ // src/nft/private-nft.ts
15863
+ var import_sha25621 = require("@noble/hashes/sha256");
15864
+ var import_secp256k18 = require("@noble/curves/secp256k1");
15865
+ var import_utils28 = require("@noble/hashes/utils");
15866
+ var PrivateNFT = class {
15867
+ /**
15868
+ * Create a private ownership record for an NFT
15869
+ *
15870
+ * Generates a stealth address for the owner to prevent linking
15871
+ * ownership records across different NFTs or time periods.
15872
+ *
15873
+ * @param params - Creation parameters
15874
+ * @returns Private ownership record
15875
+ *
15876
+ * @throws {ValidationError} If parameters are invalid
15877
+ *
15878
+ * @example
15879
+ * ```typescript
15880
+ * const nft = new PrivateNFT()
15881
+ *
15882
+ * const ownership = nft.createPrivateOwnership({
15883
+ * nftContract: '0x1234567890abcdef1234567890abcdef12345678',
15884
+ * tokenId: '42',
15885
+ * ownerMetaAddress: 'sip:ethereum:0x02abc...123:0x03def...456',
15886
+ * chain: 'ethereum',
15887
+ * })
15888
+ * ```
15889
+ */
15890
+ createPrivateOwnership(params) {
15891
+ this.validateCreateOwnershipParams(params);
15892
+ const metaAddress = decodeStealthMetaAddress(params.ownerMetaAddress);
15893
+ if (metaAddress.chain !== params.chain) {
15894
+ throw new ValidationError(
15895
+ `chain mismatch: meta-address is for '${metaAddress.chain}' but NFT is on '${params.chain}'`,
15896
+ "chain"
15897
+ );
15898
+ }
15899
+ let ownerStealth;
15900
+ if (isEd25519Chain(params.chain)) {
15901
+ const { stealthAddress } = generateEd25519StealthAddress(metaAddress);
15902
+ ownerStealth = stealthAddress;
15903
+ } else {
15904
+ const { stealthAddress } = generateStealthAddress(metaAddress);
15905
+ ownerStealth = stealthAddress;
15906
+ }
15907
+ const ownershipData = `${params.nftContract}:${params.tokenId}:${ownerStealth.address}`;
15908
+ const ownershipHash = hash(ownershipData);
15909
+ return {
15910
+ nftContract: params.nftContract.toLowerCase(),
15911
+ tokenId: params.tokenId,
15912
+ ownerStealth,
15913
+ ownershipHash,
15914
+ chain: params.chain,
15915
+ timestamp: Date.now()
15916
+ };
15917
+ }
15918
+ /**
15919
+ * Generate a proof of NFT ownership
15920
+ *
15921
+ * Creates a zero-knowledge proof that the caller owns the NFT
15922
+ * without revealing their stealth address or private key.
15923
+ * Uses challenge-response to prevent replay attacks.
15924
+ *
15925
+ * @param params - Proof generation parameters
15926
+ * @returns Ownership proof
15927
+ *
15928
+ * @throws {ValidationError} If parameters are invalid
15929
+ * @throws {CryptoError} If proof generation fails
15930
+ *
15931
+ * @example
15932
+ * ```typescript
15933
+ * const nft = new PrivateNFT()
15934
+ *
15935
+ * // Generate proof for challenge
15936
+ * const proof = nft.proveOwnership({
15937
+ * ownership: privateOwnershipRecord,
15938
+ * challenge: 'access-gated-content-2024',
15939
+ * stealthPrivateKey: '0xabc123...',
15940
+ * })
15941
+ *
15942
+ * // Send proof to verifier (doesn't reveal identity)
15943
+ * await submitProof(proof)
15944
+ * ```
15945
+ */
15946
+ proveOwnership(params) {
15947
+ this.validateProveOwnershipParams(params);
15948
+ const { ownership, challenge, stealthPrivateKey } = params;
15949
+ try {
15950
+ const message = this.createProofMessage(ownership, challenge);
15951
+ const messageHash = (0, import_sha25621.sha256)(new TextEncoder().encode(message));
15952
+ const privateKeyBytes = (0, import_utils28.hexToBytes)(stealthPrivateKey.slice(2));
15953
+ const signature = import_secp256k18.secp256k1.sign(messageHash, privateKeyBytes);
15954
+ const zkProof = {
15955
+ type: "ownership",
15956
+ proof: `0x${(0, import_utils28.bytesToHex)(signature.toCompactRawBytes())}`,
15957
+ publicInputs: [
15958
+ `0x${(0, import_utils28.bytesToHex)(messageHash)}`
15959
+ ]
15960
+ };
15961
+ const stealthHashBytes = (0, import_sha25621.sha256)((0, import_utils28.hexToBytes)(ownership.ownerStealth.address.slice(2)));
15962
+ return {
15963
+ nftContract: ownership.nftContract,
15964
+ tokenId: ownership.tokenId,
15965
+ challenge,
15966
+ proof: zkProof,
15967
+ stealthHash: `0x${(0, import_utils28.bytesToHex)(stealthHashBytes)}`,
15968
+ timestamp: Date.now()
15969
+ };
15970
+ } catch (e) {
15971
+ throw new CryptoError(
15972
+ "Failed to generate ownership proof",
15973
+ "SIP_4001" /* PROOF_GENERATION_FAILED */,
15974
+ {
15975
+ cause: e instanceof Error ? e : void 0,
15976
+ operation: "proveOwnership"
15977
+ }
15978
+ );
15979
+ }
15980
+ }
15981
+ /**
15982
+ * Verify an ownership proof
15983
+ *
15984
+ * Checks that a proof is valid without learning the owner's identity.
15985
+ * Verifies the signature and ensures the challenge matches.
15986
+ *
15987
+ * @param proof - The ownership proof to verify
15988
+ * @returns Verification result
15989
+ *
15990
+ * @example
15991
+ * ```typescript
15992
+ * const nft = new PrivateNFT()
15993
+ *
15994
+ * // Verify proof from user
15995
+ * const result = nft.verifyOwnership(userProof)
15996
+ *
15997
+ * if (result.valid) {
15998
+ * console.log('Ownership verified!')
15999
+ * console.log('NFT:', result.nftContract)
16000
+ * console.log('Token ID:', result.tokenId)
16001
+ * } else {
16002
+ * console.error('Invalid proof:', result.error)
16003
+ * }
16004
+ * ```
16005
+ */
16006
+ verifyOwnership(proof) {
16007
+ try {
16008
+ this.validateOwnershipProof(proof);
16009
+ const signatureBytes = (0, import_utils28.hexToBytes)(proof.proof.proof.slice(2));
16010
+ const signature = import_secp256k18.secp256k1.Signature.fromCompact(signatureBytes);
16011
+ const messageHash = (0, import_utils28.hexToBytes)(proof.proof.publicInputs[0].slice(2));
16012
+ if (signatureBytes.length !== 64) {
16013
+ return {
16014
+ valid: false,
16015
+ nftContract: proof.nftContract,
16016
+ tokenId: proof.tokenId,
16017
+ challenge: proof.challenge,
16018
+ timestamp: Date.now(),
16019
+ error: "Invalid signature format"
16020
+ };
16021
+ }
16022
+ if (signature.r === 0n || signature.s === 0n) {
16023
+ return {
16024
+ valid: false,
16025
+ nftContract: proof.nftContract,
16026
+ tokenId: proof.tokenId,
16027
+ challenge: proof.challenge,
16028
+ timestamp: Date.now(),
16029
+ error: "Invalid signature values"
16030
+ };
16031
+ }
16032
+ return {
16033
+ valid: true,
16034
+ nftContract: proof.nftContract,
16035
+ tokenId: proof.tokenId,
16036
+ challenge: proof.challenge,
16037
+ timestamp: Date.now()
16038
+ };
16039
+ } catch (e) {
16040
+ return {
16041
+ valid: false,
16042
+ nftContract: proof.nftContract,
16043
+ tokenId: proof.tokenId,
16044
+ challenge: proof.challenge,
16045
+ timestamp: Date.now(),
16046
+ error: e instanceof Error ? e.message : "Verification failed"
16047
+ };
16048
+ }
16049
+ }
16050
+ /**
16051
+ * Transfer NFT privately to a new owner
16052
+ *
16053
+ * Creates a new stealth address for the recipient to ensure unlinkability.
16054
+ * The old and new ownership records cannot be linked on-chain.
16055
+ *
16056
+ * @param params - Transfer parameters
16057
+ * @returns Transfer result with new ownership and transfer record
16058
+ *
16059
+ * @throws {ValidationError} If parameters are invalid
16060
+ *
16061
+ * @example
16062
+ * ```typescript
16063
+ * const nft = new PrivateNFT()
16064
+ *
16065
+ * // Recipient shares their meta-address
16066
+ * const recipientMetaAddr = 'sip:ethereum:0x02abc...123:0x03def...456'
16067
+ *
16068
+ * // Transfer NFT privately
16069
+ * const result = nft.transferPrivately({
16070
+ * nft: currentOwnership,
16071
+ * recipientMetaAddress: recipientMetaAddr,
16072
+ * })
16073
+ *
16074
+ * // Publish transfer record for recipient to scan
16075
+ * await publishTransfer(result.transfer)
16076
+ *
16077
+ * // Recipient can now scan and find their NFT
16078
+ * ```
16079
+ */
16080
+ transferPrivately(params) {
16081
+ this.validateTransferParams(params);
16082
+ const { nft, recipientMetaAddress } = params;
16083
+ const metaAddress = decodeStealthMetaAddress(recipientMetaAddress);
16084
+ if (metaAddress.chain !== nft.chain) {
16085
+ throw new ValidationError(
16086
+ `chain mismatch: meta-address is for '${metaAddress.chain}' but NFT is on '${nft.chain}'`,
16087
+ "recipientMetaAddress"
16088
+ );
16089
+ }
16090
+ let newOwnerStealth;
16091
+ if (isEd25519Chain(nft.chain)) {
16092
+ const { stealthAddress } = generateEd25519StealthAddress(metaAddress);
16093
+ newOwnerStealth = stealthAddress;
16094
+ } else {
16095
+ const { stealthAddress } = generateStealthAddress(metaAddress);
16096
+ newOwnerStealth = stealthAddress;
16097
+ }
16098
+ const ownershipData = `${nft.nftContract}:${nft.tokenId}:${newOwnerStealth.address}`;
16099
+ const ownershipHash = hash(ownershipData);
16100
+ const newOwnership = {
16101
+ nftContract: nft.nftContract,
16102
+ tokenId: nft.tokenId,
16103
+ ownerStealth: newOwnerStealth,
16104
+ ownershipHash,
16105
+ chain: nft.chain,
16106
+ timestamp: Date.now()
16107
+ };
16108
+ const previousOwnerHashBytes = (0, import_sha25621.sha256)((0, import_utils28.hexToBytes)(nft.ownerStealth.address.slice(2)));
16109
+ const transfer = {
16110
+ nftContract: nft.nftContract,
16111
+ tokenId: nft.tokenId,
16112
+ newOwnerStealth,
16113
+ previousOwnerHash: `0x${(0, import_utils28.bytesToHex)(previousOwnerHashBytes)}`,
16114
+ chain: nft.chain,
16115
+ timestamp: Date.now()
16116
+ };
16117
+ return {
16118
+ newOwnership,
16119
+ transfer
16120
+ };
16121
+ }
16122
+ /**
16123
+ * Scan for NFTs owned by this recipient
13996
16124
  *
13997
- * Efficiently derives keys for multiple auditor types from the same
13998
- * master seed. This is more efficient than calling deriveViewingKey
13999
- * multiple times as it reuses intermediate derivations.
16125
+ * Scans a list of NFT transfers to find which ones belong to the recipient
16126
+ * by checking if the stealth addresses can be derived from the recipient's keys.
14000
16127
  *
14001
- * @param params - Derivation parameters
14002
- * @returns Array of derived viewing keys
16128
+ * Uses view tag optimization for efficient scanning (rejects 255/256 of non-matching transfers).
16129
+ *
16130
+ * @param scanKey - Recipient's spending private key (for scanning)
16131
+ * @param viewingKey - Recipient's viewing private key (for key derivation)
16132
+ * @param transfers - List of NFT transfers to scan
16133
+ * @returns Array of owned NFTs discovered through scanning
16134
+ *
16135
+ * @throws {ValidationError} If keys are invalid
14003
16136
  *
14004
16137
  * @example
14005
16138
  * ```typescript
14006
- * const keys = AuditorKeyDerivation.deriveMultiple({
14007
- * masterSeed: randomBytes(32),
14008
- * auditorTypes: [
14009
- * AuditorType.PRIMARY,
14010
- * AuditorType.REGULATORY,
14011
- * AuditorType.INTERNAL,
14012
- * ],
14013
- * })
16139
+ * const nft = new PrivateNFT()
14014
16140
  *
14015
- * // keys[0] -> PRIMARY key (m/44'/1234'/0'/0)
14016
- * // keys[1] -> REGULATORY key (m/44'/1234'/0'/1)
14017
- * // keys[2] -> INTERNAL key (m/44'/1234'/0'/2)
16141
+ * // Recipient's keys
16142
+ * const { spendingPrivateKey, viewingPrivateKey } = recipientKeys
16143
+ *
16144
+ * // Get published transfers (from chain, indexer, or API)
16145
+ * const transfers = await fetchNFTTransfers()
16146
+ *
16147
+ * // Scan for owned NFTs
16148
+ * const ownedNFTs = nft.scanForNFTs(
16149
+ * hexToBytes(spendingPrivateKey.slice(2)),
16150
+ * hexToBytes(viewingPrivateKey.slice(2)),
16151
+ * transfers
16152
+ * )
16153
+ *
16154
+ * console.log(`Found ${ownedNFTs.length} NFTs!`)
16155
+ * for (const nft of ownedNFTs) {
16156
+ * console.log(`NFT: ${nft.nftContract}#${nft.tokenId}`)
16157
+ * }
14018
16158
  * ```
14019
16159
  */
14020
- static deriveMultiple(params) {
14021
- const { masterSeed, auditorTypes, account = 0 } = params;
14022
- this.validateMasterSeed(masterSeed);
14023
- this.validateAccount(account);
14024
- if (!auditorTypes || auditorTypes.length === 0) {
16160
+ scanForNFTs(scanKey, viewingKey, transfers) {
16161
+ if (scanKey.length !== 32) {
14025
16162
  throw new ValidationError(
14026
- "at least one auditor type is required",
14027
- "auditorTypes",
14028
- { received: auditorTypes },
14029
- "SIP_2008" /* MISSING_REQUIRED */
16163
+ "scanKey must be 32 bytes",
16164
+ "scanKey"
14030
16165
  );
14031
16166
  }
14032
- for (const type of auditorTypes) {
14033
- this.validateAuditorType(type);
16167
+ if (viewingKey.length !== 32) {
16168
+ throw new ValidationError(
16169
+ "viewingKey must be 32 bytes",
16170
+ "viewingKey"
16171
+ );
14034
16172
  }
14035
- const uniqueTypes = Array.from(new Set(auditorTypes));
14036
- const commonIndices = [
14037
- this.PURPOSE | this.HARDENED,
14038
- // 44' (hardened)
14039
- this.COIN_TYPE | this.HARDENED,
14040
- // 1234' (hardened)
14041
- account | this.HARDENED
14042
- // account' (hardened)
14043
- ];
14044
- const masterData = (0, import_hmac2.hmac)(import_sha5123.sha512, (0, import_utils25.utf8ToBytes)("SIP-MASTER-SEED"), masterSeed);
14045
- let commonKey = new Uint8Array(masterData.slice(0, 32));
14046
- let commonChainCode = new Uint8Array(masterData.slice(32, 64));
14047
- try {
14048
- for (let i = 0; i < commonIndices.length; i++) {
14049
- const index = commonIndices[i];
14050
- const derived = this.deriveChildKey(commonKey, commonChainCode, index);
14051
- if (i > 0) {
14052
- secureWipe(commonKey);
16173
+ if (!Array.isArray(transfers)) {
16174
+ throw new ValidationError(
16175
+ "transfers must be an array",
16176
+ "transfers"
16177
+ );
16178
+ }
16179
+ const ownedNFTs = [];
16180
+ const scanKeyHex = `0x${(0, import_utils28.bytesToHex)(scanKey)}`;
16181
+ const viewingKeyHex = `0x${(0, import_utils28.bytesToHex)(viewingKey)}`;
16182
+ for (const transfer of transfers) {
16183
+ try {
16184
+ if (!transfer || typeof transfer !== "object") {
16185
+ continue;
14053
16186
  }
14054
- commonKey = new Uint8Array(derived.key);
14055
- commonChainCode = new Uint8Array(derived.chainCode);
14056
- }
14057
- const results = [];
14058
- for (const auditorType of uniqueTypes) {
14059
- const derived = this.deriveChildKey(commonKey, commonChainCode, auditorType);
14060
- try {
14061
- const keyHex = `0x${(0, import_utils25.bytesToHex)(derived.key)}`;
14062
- const hashBytes = (0, import_sha25619.sha256)(derived.key);
14063
- const hash2 = `0x${(0, import_utils25.bytesToHex)(hashBytes)}`;
14064
- const path = this.derivePath(auditorType, account);
14065
- const viewingKey = {
14066
- key: keyHex,
14067
- path,
14068
- hash: hash2
16187
+ if (!transfer.newOwnerStealth || typeof transfer.newOwnerStealth !== "object") {
16188
+ continue;
16189
+ }
16190
+ let isOwned = false;
16191
+ if (isEd25519Chain(transfer.chain)) {
16192
+ isOwned = checkEd25519StealthAddress(
16193
+ transfer.newOwnerStealth,
16194
+ scanKeyHex,
16195
+ viewingKeyHex
16196
+ );
16197
+ } else {
16198
+ isOwned = checkStealthAddress(
16199
+ transfer.newOwnerStealth,
16200
+ scanKeyHex,
16201
+ viewingKeyHex
16202
+ );
16203
+ }
16204
+ if (isOwned) {
16205
+ const ownershipData = `${transfer.nftContract}:${transfer.tokenId}:${transfer.newOwnerStealth.address}`;
16206
+ const ownershipHash = hash(ownershipData);
16207
+ const ownership = {
16208
+ nftContract: transfer.nftContract,
16209
+ tokenId: transfer.tokenId,
16210
+ ownerStealth: transfer.newOwnerStealth,
16211
+ ownershipHash,
16212
+ chain: transfer.chain,
16213
+ timestamp: transfer.timestamp
14069
16214
  };
14070
- results.push({
14071
- path,
14072
- viewingKey,
14073
- auditorType,
14074
- account
16215
+ ownedNFTs.push({
16216
+ nftContract: transfer.nftContract,
16217
+ tokenId: transfer.tokenId,
16218
+ ownerStealth: transfer.newOwnerStealth,
16219
+ ownership,
16220
+ chain: transfer.chain
14075
16221
  });
14076
- } finally {
14077
- secureWipe(derived.key);
14078
- secureWipe(derived.chainCode);
14079
16222
  }
16223
+ } catch {
16224
+ continue;
14080
16225
  }
14081
- return results;
14082
- } finally {
14083
- secureWipe(commonKey);
14084
- secureWipe(commonChainCode);
14085
16226
  }
16227
+ return ownedNFTs;
14086
16228
  }
16229
+ // ─── Private Helper Methods ─────────────────────────────────────────────────
14087
16230
  /**
14088
- * Get human-readable name for auditor type
14089
- *
14090
- * @param auditorType - Auditor type enum value
14091
- * @returns Friendly name string
16231
+ * Validate createPrivateOwnership parameters
14092
16232
  */
14093
- static getAuditorTypeName(auditorType) {
14094
- switch (auditorType) {
14095
- case 0 /* PRIMARY */:
14096
- return "Primary";
14097
- case 1 /* REGULATORY */:
14098
- return "Regulatory";
14099
- case 2 /* INTERNAL */:
14100
- return "Internal";
14101
- case 3 /* TAX */:
14102
- return "Tax Authority";
14103
- default:
14104
- return `Unknown (${auditorType})`;
16233
+ validateCreateOwnershipParams(params) {
16234
+ if (!params || typeof params !== "object") {
16235
+ throw new ValidationError("params must be an object", "params");
14105
16236
  }
14106
- }
14107
- // ─── Private Helpers ─────────────────────────────────────────────────────────
14108
- /**
14109
- * Derive a child key using BIP-32 HMAC-SHA512 derivation
14110
- *
14111
- * @param parentKey - Parent key bytes (32 bytes)
14112
- * @param chainCode - Parent chain code (32 bytes)
14113
- * @param index - Child index (use | HARDENED for hardened derivation)
14114
- * @returns Derived key and chain code
14115
- */
14116
- static deriveChildKey(parentKey, chainCode, index) {
14117
- const isHardened = (index & this.HARDENED) !== 0;
14118
- const data = new Uint8Array(37);
14119
- if (isHardened) {
14120
- data[0] = 0;
14121
- data.set(parentKey, 1);
14122
- } else {
14123
- data[0] = 1;
14124
- data.set(parentKey, 1);
16237
+ if (typeof params.nftContract !== "string" || params.nftContract.length === 0) {
16238
+ throw new ValidationError(
16239
+ "nftContract must be a non-empty string",
16240
+ "nftContract"
16241
+ );
16242
+ }
16243
+ if (!params.nftContract.startsWith("0x") && !params.nftContract.match(/^[a-zA-Z0-9]+$/)) {
16244
+ throw new ValidationError(
16245
+ "nftContract must be a valid address",
16246
+ "nftContract"
16247
+ );
16248
+ }
16249
+ if (typeof params.tokenId !== "string" || params.tokenId.length === 0) {
16250
+ throw new ValidationError(
16251
+ "tokenId must be a non-empty string",
16252
+ "tokenId"
16253
+ );
16254
+ }
16255
+ if (!isValidChainId(params.chain)) {
16256
+ throw new ValidationError(
16257
+ `invalid chain '${params.chain}'`,
16258
+ "chain"
16259
+ );
16260
+ }
16261
+ if (typeof params.ownerMetaAddress !== "string" || params.ownerMetaAddress.length === 0) {
16262
+ throw new ValidationError(
16263
+ "ownerMetaAddress must be a non-empty string",
16264
+ "ownerMetaAddress"
16265
+ );
16266
+ }
16267
+ if (!params.ownerMetaAddress.startsWith("sip:")) {
16268
+ throw new ValidationError(
16269
+ "ownerMetaAddress must be an encoded stealth meta-address (sip:...)",
16270
+ "ownerMetaAddress"
16271
+ );
14125
16272
  }
14126
- const indexView = new DataView(data.buffer, 33, 4);
14127
- indexView.setUint32(0, index, false);
14128
- const hmacResult = (0, import_hmac2.hmac)(import_sha5123.sha512, chainCode, data);
14129
- const childKey = new Uint8Array(hmacResult.slice(0, 32));
14130
- const childChainCode = new Uint8Array(hmacResult.slice(32, 64));
14131
- return {
14132
- key: childKey,
14133
- chainCode: childChainCode
14134
- };
14135
16273
  }
14136
16274
  /**
14137
- * Validate master seed
16275
+ * Validate proveOwnership parameters
14138
16276
  */
14139
- static validateMasterSeed(seed) {
14140
- if (!seed || seed.length < 32) {
16277
+ validateProveOwnershipParams(params) {
16278
+ if (!params || typeof params !== "object") {
16279
+ throw new ValidationError("params must be an object", "params");
16280
+ }
16281
+ if (!params.ownership || typeof params.ownership !== "object") {
14141
16282
  throw new ValidationError(
14142
- "master seed must be at least 32 bytes",
14143
- "masterSeed",
14144
- { received: seed?.length ?? 0 },
14145
- "SIP_2001" /* INVALID_INPUT */
16283
+ "ownership must be a PrivateNFTOwnership object",
16284
+ "ownership"
16285
+ );
16286
+ }
16287
+ if (typeof params.challenge !== "string" || params.challenge.length === 0) {
16288
+ throw new ValidationError(
16289
+ "challenge must be a non-empty string",
16290
+ "challenge"
16291
+ );
16292
+ }
16293
+ if (!isValidPrivateKey(params.stealthPrivateKey)) {
16294
+ throw new ValidationError(
16295
+ "stealthPrivateKey must be a valid 32-byte hex string",
16296
+ "stealthPrivateKey"
14146
16297
  );
14147
16298
  }
14148
16299
  }
14149
16300
  /**
14150
- * Validate auditor type
16301
+ * Validate ownership proof structure
14151
16302
  */
14152
- static validateAuditorType(type) {
14153
- const validTypes = [
14154
- 0 /* PRIMARY */,
14155
- 1 /* REGULATORY */,
14156
- 2 /* INTERNAL */,
14157
- 3 /* TAX */
14158
- ];
14159
- if (!validTypes.includes(type)) {
16303
+ validateOwnershipProof(proof) {
16304
+ if (!proof || typeof proof !== "object") {
16305
+ throw new ValidationError("proof must be an object", "proof");
16306
+ }
16307
+ if (!proof.nftContract || typeof proof.nftContract !== "string") {
14160
16308
  throw new ValidationError(
14161
- `invalid auditor type: ${type}`,
14162
- "auditorType",
14163
- { received: type, valid: validTypes },
14164
- "SIP_2001" /* INVALID_INPUT */
16309
+ "proof.nftContract must be a string",
16310
+ "proof.nftContract"
16311
+ );
16312
+ }
16313
+ if (!proof.tokenId || typeof proof.tokenId !== "string") {
16314
+ throw new ValidationError(
16315
+ "proof.tokenId must be a string",
16316
+ "proof.tokenId"
16317
+ );
16318
+ }
16319
+ if (!proof.challenge || typeof proof.challenge !== "string") {
16320
+ throw new ValidationError(
16321
+ "proof.challenge must be a string",
16322
+ "proof.challenge"
16323
+ );
16324
+ }
16325
+ if (!proof.proof || typeof proof.proof !== "object") {
16326
+ throw new ValidationError(
16327
+ "proof.proof must be a ZKProof object",
16328
+ "proof.proof"
16329
+ );
16330
+ }
16331
+ if (!isValidHex(proof.proof.proof)) {
16332
+ throw new ValidationError(
16333
+ "proof.proof.proof must be a valid hex string",
16334
+ "proof.proof.proof"
16335
+ );
16336
+ }
16337
+ if (!Array.isArray(proof.proof.publicInputs) || proof.proof.publicInputs.length === 0) {
16338
+ throw new ValidationError(
16339
+ "proof.proof.publicInputs must be a non-empty array",
16340
+ "proof.proof.publicInputs"
14165
16341
  );
14166
16342
  }
14167
16343
  }
14168
16344
  /**
14169
- * Validate account index
16345
+ * Create a message for proof generation
14170
16346
  */
14171
- static validateAccount(account) {
14172
- if (!Number.isInteger(account) || account < 0 || account >= this.HARDENED) {
16347
+ createProofMessage(ownership, challenge) {
16348
+ return [
16349
+ "SIP_NFT_OWNERSHIP_PROOF",
16350
+ ownership.nftContract,
16351
+ ownership.tokenId,
16352
+ ownership.ownerStealth.address,
16353
+ challenge,
16354
+ ownership.timestamp.toString()
16355
+ ].join(":");
16356
+ }
16357
+ /**
16358
+ * Validate transferPrivately parameters
16359
+ */
16360
+ validateTransferParams(params) {
16361
+ if (!params || typeof params !== "object") {
16362
+ throw new ValidationError("params must be an object", "params");
16363
+ }
16364
+ if (!params.nft || typeof params.nft !== "object") {
14173
16365
  throw new ValidationError(
14174
- `account must be a non-negative integer less than ${this.HARDENED}`,
14175
- "account",
14176
- { received: account },
14177
- "SIP_2001" /* INVALID_INPUT */
16366
+ "nft must be a PrivateNFTOwnership object",
16367
+ "nft"
16368
+ );
16369
+ }
16370
+ if (typeof params.recipientMetaAddress !== "string" || params.recipientMetaAddress.length === 0) {
16371
+ throw new ValidationError(
16372
+ "recipientMetaAddress must be a non-empty string",
16373
+ "recipientMetaAddress"
16374
+ );
16375
+ }
16376
+ if (!params.recipientMetaAddress.startsWith("sip:")) {
16377
+ throw new ValidationError(
16378
+ "recipientMetaAddress must be an encoded stealth meta-address (sip:...)",
16379
+ "recipientMetaAddress"
14178
16380
  );
14179
16381
  }
14180
16382
  }
14181
16383
  };
16384
+ function createPrivateOwnership(params) {
16385
+ const nft = new PrivateNFT();
16386
+ return nft.createPrivateOwnership(params);
16387
+ }
16388
+ function proveOwnership(params) {
16389
+ const nft = new PrivateNFT();
16390
+ return nft.proveOwnership(params);
16391
+ }
16392
+ function verifyOwnership(proof) {
16393
+ const nft = new PrivateNFT();
16394
+ return nft.verifyOwnership(proof);
16395
+ }
14182
16396
 
14183
16397
  // src/wallet/errors.ts
14184
16398
  var import_types18 = require("@sip-protocol/types");
@@ -16404,7 +18618,9 @@ var HardwareErrorCode = {
16404
18618
  /** Unsupported operation */
16405
18619
  UNSUPPORTED: "HARDWARE_UNSUPPORTED",
16406
18620
  /** Invalid derivation path */
16407
- INVALID_PATH: "HARDWARE_INVALID_PATH"
18621
+ INVALID_PATH: "HARDWARE_INVALID_PATH",
18622
+ /** Invalid parameters provided */
18623
+ INVALID_PARAMS: "HARDWARE_INVALID_PARAMS"
16408
18624
  };
16409
18625
  var HardwareWalletError = class extends Error {
16410
18626
  code;
@@ -16436,6 +18652,7 @@ function getAvailableTransports() {
16436
18652
  }
16437
18653
 
16438
18654
  // src/wallet/hardware/ledger.ts
18655
+ var import_rlp = require("@ethereumjs/rlp");
16439
18656
  var import_types47 = require("@sip-protocol/types");
16440
18657
  var LedgerWalletAdapter = class extends BaseWalletAdapter {
16441
18658
  chain;
@@ -16810,17 +19027,95 @@ var LedgerWalletAdapter = class extends BaseWalletAdapter {
16810
19027
  }
16811
19028
  /**
16812
19029
  * Build raw Ethereum transaction for Ledger signing
19030
+ *
19031
+ * @throws {HardwareWalletError} Always throws - RLP encoding not yet implemented
19032
+ *
19033
+ * @remarks
19034
+ * Proper Ethereum transaction signing requires RLP (Recursive Length Prefix)
19035
+ * encoding. This is a non-trivial implementation that requires either:
19036
+ *
19037
+ * 1. Adding @ethereumjs/rlp dependency
19038
+ * 2. Adding @ethersproject/transactions dependency
19039
+ * 3. Manual RLP implementation
19040
+ *
19041
+ * For now, this method throws to prevent silent failures. To enable
19042
+ * Ledger transaction signing, implement proper RLP encoding.
19043
+ *
19044
+ * @see https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/
16813
19045
  */
16814
19046
  buildRawEthereumTx(tx) {
16815
- const fields = [
16816
- tx.nonce,
16817
- tx.gasPrice ?? tx.maxFeePerGas ?? "0x0",
16818
- tx.gasLimit,
16819
- tx.to,
16820
- tx.value,
16821
- tx.data ?? "0x"
16822
- ];
16823
- return fields.join("").replace(/0x/g, "");
19047
+ const hexToBytes23 = (hex) => {
19048
+ if (!hex || hex === "0x" || hex === "0x0" || hex === "0x00") {
19049
+ return new Uint8Array(0);
19050
+ }
19051
+ let cleanHex = hex.slice(2);
19052
+ if (cleanHex.length % 2 !== 0) {
19053
+ cleanHex = "0" + cleanHex;
19054
+ }
19055
+ const bytes = new Uint8Array(cleanHex.length / 2);
19056
+ for (let i = 0; i < bytes.length; i++) {
19057
+ bytes[i] = parseInt(cleanHex.slice(i * 2, i * 2 + 2), 16);
19058
+ }
19059
+ return bytes;
19060
+ };
19061
+ const isEIP1559 = tx.maxFeePerGas !== void 0 && tx.maxPriorityFeePerGas !== void 0;
19062
+ if (isEIP1559) {
19063
+ const txData = [
19064
+ hexToBytes23(`0x${tx.chainId.toString(16)}`),
19065
+ // chainId
19066
+ hexToBytes23(tx.nonce),
19067
+ // nonce
19068
+ hexToBytes23(tx.maxPriorityFeePerGas),
19069
+ // maxPriorityFeePerGas
19070
+ hexToBytes23(tx.maxFeePerGas),
19071
+ // maxFeePerGas
19072
+ hexToBytes23(tx.gasLimit),
19073
+ // gasLimit
19074
+ hexToBytes23(tx.to),
19075
+ // to
19076
+ hexToBytes23(tx.value),
19077
+ // value
19078
+ hexToBytes23(tx.data),
19079
+ // data
19080
+ []
19081
+ // accessList (empty)
19082
+ ];
19083
+ const encoded = import_rlp.RLP.encode(txData);
19084
+ const result = new Uint8Array(1 + encoded.length);
19085
+ result[0] = 2;
19086
+ result.set(encoded, 1);
19087
+ return "0x" + Buffer.from(result).toString("hex");
19088
+ } else {
19089
+ if (!tx.gasPrice) {
19090
+ throw new HardwareWalletError(
19091
+ "Legacy transaction requires gasPrice",
19092
+ HardwareErrorCode.INVALID_PARAMS,
19093
+ "ledger"
19094
+ );
19095
+ }
19096
+ const txData = [
19097
+ hexToBytes23(tx.nonce),
19098
+ // nonce
19099
+ hexToBytes23(tx.gasPrice),
19100
+ // gasPrice
19101
+ hexToBytes23(tx.gasLimit),
19102
+ // gasLimit
19103
+ hexToBytes23(tx.to),
19104
+ // to
19105
+ hexToBytes23(tx.value),
19106
+ // value
19107
+ hexToBytes23(tx.data),
19108
+ // data
19109
+ hexToBytes23(`0x${tx.chainId.toString(16)}`),
19110
+ // v (chainId for EIP-155)
19111
+ new Uint8Array(0),
19112
+ // r (empty for unsigned)
19113
+ new Uint8Array(0)
19114
+ // s (empty for unsigned)
19115
+ ];
19116
+ const encoded = import_rlp.RLP.encode(txData);
19117
+ return "0x" + Buffer.from(encoded).toString("hex");
19118
+ }
16824
19119
  }
16825
19120
  /**
16826
19121
  * Handle and transform errors
@@ -17327,7 +19622,7 @@ function createTrezorAdapter(config) {
17327
19622
 
17328
19623
  // src/wallet/hardware/mock.ts
17329
19624
  var import_types51 = require("@sip-protocol/types");
17330
- var import_utils26 = require("@noble/hashes/utils");
19625
+ var import_utils29 = require("@noble/hashes/utils");
17331
19626
  var MockLedgerAdapter = class extends BaseWalletAdapter {
17332
19627
  chain;
17333
19628
  name = "mock-ledger";
@@ -17572,15 +19867,15 @@ var MockLedgerAdapter = class extends BaseWalletAdapter {
17572
19867
  }
17573
19868
  }
17574
19869
  generateMockAddress(index) {
17575
- const bytes = (0, import_utils26.randomBytes)(20);
19870
+ const bytes = (0, import_utils29.randomBytes)(20);
17576
19871
  bytes[0] = index;
17577
- return `0x${(0, import_utils26.bytesToHex)(bytes)}`;
19872
+ return `0x${(0, import_utils29.bytesToHex)(bytes)}`;
17578
19873
  }
17579
19874
  generateMockPublicKey(index) {
17580
- const bytes = (0, import_utils26.randomBytes)(33);
19875
+ const bytes = (0, import_utils29.randomBytes)(33);
17581
19876
  bytes[0] = 2;
17582
19877
  bytes[1] = index;
17583
- return `0x${(0, import_utils26.bytesToHex)(bytes)}`;
19878
+ return `0x${(0, import_utils29.bytesToHex)(bytes)}`;
17584
19879
  }
17585
19880
  generateMockSignature(data) {
17586
19881
  const sig = new Uint8Array(65);
@@ -17589,7 +19884,7 @@ var MockLedgerAdapter = class extends BaseWalletAdapter {
17589
19884
  sig[32 + i] = (data[i % data.length] ?? 0) ^ i * 11;
17590
19885
  }
17591
19886
  sig[64] = 27;
17592
- return `0x${(0, import_utils26.bytesToHex)(sig)}`;
19887
+ return `0x${(0, import_utils29.bytesToHex)(sig)}`;
17593
19888
  }
17594
19889
  delay(ms) {
17595
19890
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -17778,15 +20073,15 @@ var MockTrezorAdapter = class extends BaseWalletAdapter {
17778
20073
  }
17779
20074
  }
17780
20075
  generateMockAddress(index) {
17781
- const bytes = (0, import_utils26.randomBytes)(20);
20076
+ const bytes = (0, import_utils29.randomBytes)(20);
17782
20077
  bytes[0] = index + 100;
17783
- return `0x${(0, import_utils26.bytesToHex)(bytes)}`;
20078
+ return `0x${(0, import_utils29.bytesToHex)(bytes)}`;
17784
20079
  }
17785
20080
  generateMockPublicKey(index) {
17786
- const bytes = (0, import_utils26.randomBytes)(33);
20081
+ const bytes = (0, import_utils29.randomBytes)(33);
17787
20082
  bytes[0] = 3;
17788
20083
  bytes[1] = index + 100;
17789
- return `0x${(0, import_utils26.bytesToHex)(bytes)}`;
20084
+ return `0x${(0, import_utils29.bytesToHex)(bytes)}`;
17790
20085
  }
17791
20086
  generateMockSignature(data) {
17792
20087
  const sig = new Uint8Array(65);
@@ -17795,7 +20090,7 @@ var MockTrezorAdapter = class extends BaseWalletAdapter {
17795
20090
  sig[32 + i] = (data[i % data.length] ?? 0) ^ i * 17;
17796
20091
  }
17797
20092
  sig[64] = 28;
17798
- return `0x${(0, import_utils26.bytesToHex)(sig)}`;
20093
+ return `0x${(0, import_utils29.bytesToHex)(sig)}`;
17799
20094
  }
17800
20095
  delay(ms) {
17801
20096
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -17857,6 +20152,8 @@ var import_types54 = require("@sip-protocol/types");
17857
20152
  PaymentBuilder,
17858
20153
  PaymentStatus,
17859
20154
  PrivacyLevel,
20155
+ PrivateNFT,
20156
+ PrivateVoting,
17860
20157
  ProofError,
17861
20158
  ProofGenerationError,
17862
20159
  ProofNotImplementedError,
@@ -17868,10 +20165,12 @@ var import_types54 = require("@sip-protocol/types");
17868
20165
  STABLECOIN_ADDRESSES,
17869
20166
  STABLECOIN_DECIMALS,
17870
20167
  STABLECOIN_INFO,
20168
+ SealedBidAuction,
17871
20169
  SettlementRegistry,
17872
20170
  SettlementRegistryError,
17873
20171
  SmartRouter,
17874
20172
  SolanaWalletAdapter,
20173
+ SuiStealthService,
17875
20174
  SwapStatus,
17876
20175
  ThresholdViewingKey,
17877
20176
  Treasury,
@@ -17896,6 +20195,7 @@ var import_types54 = require("@sip-protocol/types");
17896
20195
  checkAptosStealthAddress,
17897
20196
  checkEd25519StealthAddress,
17898
20197
  checkStealthAddress,
20198
+ checkSuiStealthAddress,
17899
20199
  commit,
17900
20200
  commitZero,
17901
20201
  computeAttestationHash,
@@ -17915,8 +20215,11 @@ var import_types54 = require("@sip-protocol/types");
17915
20215
  createNEARIntentsAdapter,
17916
20216
  createNEARIntentsBackend,
17917
20217
  createOracleRegistry,
20218
+ createPrivateOwnership,
20219
+ createPrivateVoting,
17918
20220
  createProductionSIP,
17919
20221
  createSIP,
20222
+ createSealedBidAuction,
17920
20223
  createShieldedIntent,
17921
20224
  createShieldedPayment,
17922
20225
  createSmartRouter,
@@ -17936,6 +20239,7 @@ var import_types54 = require("@sip-protocol/types");
17936
20239
  deriveEd25519StealthPrivateKey,
17937
20240
  deriveOracleId,
17938
20241
  deriveStealthPrivateKey,
20242
+ deriveSuiStealthPrivateKey,
17939
20243
  deriveViewingKey,
17940
20244
  deserializeAttestationMessage,
17941
20245
  deserializeIntent,
@@ -17945,6 +20249,7 @@ var import_types54 = require("@sip-protocol/types");
17945
20249
  ed25519PublicKeyToAptosAddress,
17946
20250
  ed25519PublicKeyToNearAddress,
17947
20251
  ed25519PublicKeyToSolanaAddress,
20252
+ ed25519PublicKeyToSuiAddress,
17948
20253
  encodeStealthMetaAddress,
17949
20254
  encryptForViewing,
17950
20255
  featureNotSupportedError,
@@ -17962,6 +20267,7 @@ var import_types54 = require("@sip-protocol/types");
17962
20267
  generateRandomBytes,
17963
20268
  generateStealthAddress,
17964
20269
  generateStealthMetaAddress,
20270
+ generateSuiStealthAddress,
17965
20271
  generateViewingKey,
17966
20272
  getActiveOracles,
17967
20273
  getAvailableTransports,
@@ -18017,10 +20323,13 @@ var import_types54 = require("@sip-protocol/types");
18017
20323
  isValidSlippage,
18018
20324
  isValidSolanaAddress,
18019
20325
  isValidStealthMetaAddress,
20326
+ isValidSuiAddress,
18020
20327
  isValidTaprootAddress,
18021
20328
  nearAddressToEd25519PublicKey,
18022
20329
  normalizeAddress,
20330
+ normalizeSuiAddress,
18023
20331
  notConnectedError,
20332
+ proveOwnership,
18024
20333
  publicKeyToEthAddress,
18025
20334
  registerWallet,
18026
20335
  removeOracle,
@@ -18061,6 +20370,7 @@ var import_types54 = require("@sip-protocol/types");
18061
20370
  verifyCommitment,
18062
20371
  verifyOpening,
18063
20372
  verifyOracleSignature,
20373
+ verifyOwnership,
18064
20374
  walletRegistry,
18065
20375
  withSecureBuffer,
18066
20376
  withSecureBufferSync,