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