@sip-protocol/sdk 0.6.0 → 0.6.1

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