@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.
- package/README.md +58 -0
- package/dist/browser.d.mts +4 -4
- package/dist/browser.d.ts +4 -4
- package/dist/browser.js +2745 -457
- package/dist/browser.mjs +31 -1
- package/dist/chunk-7QZPORY5.mjs +15604 -0
- package/dist/chunk-C2NPCUAJ.mjs +17010 -0
- package/dist/chunk-FCVLFUIC.mjs +16699 -0
- package/dist/chunk-G5UHXECN.mjs +16340 -0
- package/dist/chunk-GEDEIZHJ.mjs +16798 -0
- package/dist/chunk-MTNYSNR7.mjs +16269 -0
- package/dist/chunk-O5PIB2EA.mjs +16698 -0
- package/dist/chunk-PCFM7FQO.mjs +17010 -0
- package/dist/chunk-QK464ARC.mjs +16946 -0
- package/dist/chunk-VNBMNGC3.mjs +16698 -0
- package/dist/chunk-W5TUELDQ.mjs +16947 -0
- package/dist/index-CD_zShu-.d.ts +10870 -0
- package/dist/index-CQBYdLYy.d.mts +10976 -0
- package/dist/index-Cg9TYEPv.d.mts +11321 -0
- package/dist/index-CqZJOO8C.d.mts +11323 -0
- package/dist/index-CywN9Bnp.d.ts +11321 -0
- package/dist/index-DHy5ZjCD.d.ts +10976 -0
- package/dist/index-DfsVsmxu.d.ts +11323 -0
- package/dist/index-ObjwyVDX.d.mts +10870 -0
- package/dist/index-m0xbSfmT.d.mts +11318 -0
- package/dist/index-rWLEgvhN.d.ts +11318 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2730 -436
- package/dist/index.mjs +31 -1
- package/dist/noir-DKfEzWy9.d.mts +482 -0
- package/dist/noir-DKfEzWy9.d.ts +482 -0
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/proofs/noir.js +12 -3
- package/dist/proofs/noir.mjs +12 -3
- package/package.json +16 -14
- package/src/adapters/near-intents.ts +13 -3
- package/src/auction/index.ts +20 -0
- package/src/auction/sealed-bid.ts +1037 -0
- package/src/compliance/derivation.ts +13 -3
- package/src/compliance/reports.ts +5 -4
- package/src/cosmos/ibc-stealth.ts +2 -2
- package/src/cosmos/stealth.ts +2 -2
- package/src/governance/index.ts +19 -0
- package/src/governance/private-vote.ts +1116 -0
- package/src/index.ts +50 -2
- package/src/intent.ts +145 -8
- package/src/nft/index.ts +27 -0
- package/src/nft/private-nft.ts +811 -0
- package/src/proofs/browser-utils.ts +1 -7
- package/src/proofs/noir.ts +34 -7
- package/src/settlement/backends/direct-chain.ts +14 -3
- package/src/types/browser.d.ts +67 -0
- package/src/validation.ts +4 -2
- package/src/wallet/bitcoin/adapter.ts +159 -15
- package/src/wallet/bitcoin/types.ts +340 -15
- package/src/wallet/cosmos/mock.ts +16 -12
- package/src/wallet/hardware/ledger.ts +82 -12
- package/src/wallet/hardware/types.ts +2 -0
- 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 {
|
|
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:
|
|
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:
|
|
5330
|
-
|
|
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
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
5773
|
-
return
|
|
5874
|
+
generateStealthAddress(recipientMetaAddress) {
|
|
5875
|
+
return generateAptosStealthAddress(recipientMetaAddress);
|
|
5774
5876
|
}
|
|
5775
5877
|
/**
|
|
5776
|
-
*
|
|
5878
|
+
* Convert an ed25519 public key to Aptos address format
|
|
5777
5879
|
*
|
|
5778
|
-
*
|
|
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
|
|
5781
|
-
* @param
|
|
5782
|
-
* @param
|
|
5783
|
-
* @returns
|
|
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
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
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
|
|
14119
|
+
* SIP Protocol coin type for BIP-44 derivation
|
|
13886
14120
|
*
|
|
13887
|
-
*
|
|
13888
|
-
*
|
|
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
|
-
*
|
|
13998
|
-
*
|
|
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
|
-
*
|
|
14002
|
-
*
|
|
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
|
|
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
|
|
14016
|
-
*
|
|
14017
|
-
*
|
|
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
|
-
|
|
14021
|
-
|
|
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
|
-
"
|
|
14027
|
-
"
|
|
14028
|
-
{ received: auditorTypes },
|
|
14029
|
-
"SIP_2008" /* MISSING_REQUIRED */
|
|
16147
|
+
"scanKey must be 32 bytes",
|
|
16148
|
+
"scanKey"
|
|
14030
16149
|
);
|
|
14031
16150
|
}
|
|
14032
|
-
|
|
14033
|
-
|
|
16151
|
+
if (viewingKey.length !== 32) {
|
|
16152
|
+
throw new ValidationError(
|
|
16153
|
+
"viewingKey must be 32 bytes",
|
|
16154
|
+
"viewingKey"
|
|
16155
|
+
);
|
|
14034
16156
|
}
|
|
14035
|
-
|
|
14036
|
-
|
|
14037
|
-
|
|
14038
|
-
|
|
14039
|
-
|
|
14040
|
-
|
|
14041
|
-
|
|
14042
|
-
|
|
14043
|
-
|
|
14044
|
-
|
|
14045
|
-
|
|
14046
|
-
|
|
14047
|
-
|
|
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
|
-
|
|
14055
|
-
|
|
14056
|
-
|
|
14057
|
-
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
|
|
14061
|
-
|
|
14062
|
-
|
|
14063
|
-
|
|
14064
|
-
|
|
14065
|
-
|
|
14066
|
-
|
|
14067
|
-
|
|
14068
|
-
|
|
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
|
-
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
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
|
-
*
|
|
14089
|
-
*
|
|
14090
|
-
* @param auditorType - Auditor type enum value
|
|
14091
|
-
* @returns Friendly name string
|
|
16215
|
+
* Validate createPrivateOwnership parameters
|
|
14092
16216
|
*/
|
|
14093
|
-
|
|
14094
|
-
|
|
14095
|
-
|
|
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
|
-
|
|
14108
|
-
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
|
|
14115
|
-
|
|
14116
|
-
|
|
14117
|
-
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
|
|
14123
|
-
|
|
14124
|
-
|
|
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
|
|
16259
|
+
* Validate proveOwnership parameters
|
|
14138
16260
|
*/
|
|
14139
|
-
|
|
14140
|
-
if (!
|
|
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
|
-
"
|
|
14143
|
-
"
|
|
14144
|
-
|
|
14145
|
-
|
|
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
|
|
16285
|
+
* Validate ownership proof structure
|
|
14151
16286
|
*/
|
|
14152
|
-
|
|
14153
|
-
|
|
14154
|
-
|
|
14155
|
-
|
|
14156
|
-
|
|
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
|
-
|
|
14162
|
-
"
|
|
14163
|
-
|
|
14164
|
-
|
|
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
|
-
*
|
|
16329
|
+
* Create a message for proof generation
|
|
14170
16330
|
*/
|
|
14171
|
-
|
|
14172
|
-
|
|
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
|
-
|
|
14175
|
-
"
|
|
14176
|
-
|
|
14177
|
-
|
|
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
|
|
16816
|
-
|
|
16817
|
-
|
|
16818
|
-
|
|
16819
|
-
|
|
16820
|
-
|
|
16821
|
-
|
|
16822
|
-
|
|
16823
|
-
|
|
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
|
|
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,
|
|
19854
|
+
const bytes = (0, import_utils29.randomBytes)(20);
|
|
17576
19855
|
bytes[0] = index;
|
|
17577
|
-
return `0x${(0,
|
|
19856
|
+
return `0x${(0, import_utils29.bytesToHex)(bytes)}`;
|
|
17578
19857
|
}
|
|
17579
19858
|
generateMockPublicKey(index) {
|
|
17580
|
-
const bytes = (0,
|
|
19859
|
+
const bytes = (0, import_utils29.randomBytes)(33);
|
|
17581
19860
|
bytes[0] = 2;
|
|
17582
19861
|
bytes[1] = index;
|
|
17583
|
-
return `0x${(0,
|
|
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,
|
|
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,
|
|
20060
|
+
const bytes = (0, import_utils29.randomBytes)(20);
|
|
17782
20061
|
bytes[0] = index + 100;
|
|
17783
|
-
return `0x${(0,
|
|
20062
|
+
return `0x${(0, import_utils29.bytesToHex)(bytes)}`;
|
|
17784
20063
|
}
|
|
17785
20064
|
generateMockPublicKey(index) {
|
|
17786
|
-
const bytes = (0,
|
|
20065
|
+
const bytes = (0, import_utils29.randomBytes)(33);
|
|
17787
20066
|
bytes[0] = 3;
|
|
17788
20067
|
bytes[1] = index + 100;
|
|
17789
|
-
return `0x${(0,
|
|
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,
|
|
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,
|