diff --git a/script/DeployOpacityExamples.s.sol b/script/DeployOpacityExamples.s.sol index 5bf61a4..5f90f23 100644 --- a/script/DeployOpacityExamples.s.sol +++ b/script/DeployOpacityExamples.s.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.30; import "forge-std/Script.sol"; import "@eigenlayer-middleware/BLSSignatureChecker.sol"; import "@eigenlayer-middleware/interfaces/IRegistryCoordinator.sol"; +import {BN254} from "@eigenlayer-middleware/libraries/BN254.sol"; import "../src/examples/SimpleVerificationConsumer.sol"; import "../src/examples/StorageQueryConsumer.sol"; @@ -16,6 +17,10 @@ contract DeployOpacityExamples is Script { // Registry Coordinator address (testnet holesky) address constant REGISTRY_COORDINATOR = 0x3e43AA225b5cB026C5E8a53f62572b10D526a50B; + // Watchtower BLS public key coordinates (can be set via environment variables) + uint256 public watchtowerPubkeyX; + uint256 public watchtowerPubkeyY; + // Deployed contract addresses BLSSignatureChecker public blsSignatureChecker; SimpleVerificationConsumer public simpleVerificationConsumer; @@ -24,9 +29,30 @@ contract DeployOpacityExamples is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + // Try to get watchtower pubkey from environment, otherwise use defaults for testing + try vm.envUint("WATCHTOWER_PUBKEY_X") returns (uint256 _x) { + watchtowerPubkeyX = _x; + } catch { + // Default watchtower pubkey for testing (should be replaced in production) + watchtowerPubkeyX = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + console.log("WARNING: Using default watchtower pubkey X. Set WATCHTOWER_PUBKEY_X env var for production."); + } + + try vm.envUint("WATCHTOWER_PUBKEY_Y") returns (uint256 _y) { + watchtowerPubkeyY = _y; + } catch { + // Default watchtower pubkey for testing (should be replaced in production) + watchtowerPubkeyY = 0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321; + console.log("WARNING: Using default watchtower pubkey Y. Set WATCHTOWER_PUBKEY_Y env var for production."); + } + console.log("Starting OpacitySDK deployment..."); console.log("Deployer address:", vm.addr(deployerPrivateKey)); console.log("Registry Coordinator:", REGISTRY_COORDINATOR); + console.log("Watchtower Pubkey X:", watchtowerPubkeyX); + console.log("Watchtower Pubkey Y:", watchtowerPubkeyY); + + BN254.G1Point memory watchtowerPubkey = BN254.G1Point({X: watchtowerPubkeyX, Y: watchtowerPubkeyY}); vm.startBroadcast(deployerPrivateKey); @@ -37,12 +63,18 @@ contract DeployOpacityExamples is Script { // Step 2: Deploy Simple Verification Consumer console.log("\n=== Step 2: Deploying Simple Verification Consumer ==="); - simpleVerificationConsumer = new SimpleVerificationConsumer(address(blsSignatureChecker)); + simpleVerificationConsumer = new SimpleVerificationConsumer( + address(blsSignatureChecker), + watchtowerPubkey + ); console.log("Simple Verification Consumer deployed at:", address(simpleVerificationConsumer)); // Step 3: Deploy Storage Query Consumer console.log("\n=== Step 3: Deploying Storage Query Consumer ==="); - storageQueryConsumer = new StorageQueryConsumer(address(blsSignatureChecker)); + storageQueryConsumer = new StorageQueryConsumer( + address(blsSignatureChecker), + watchtowerPubkey + ); console.log("Storage Query Consumer deployed at:", address(storageQueryConsumer)); vm.stopBroadcast(); @@ -59,6 +91,8 @@ contract DeployOpacityExamples is Script { console.log(" DEPLOYMENT SUMMARY"); console.log("========================================"); console.log("Registry Coordinator: ", REGISTRY_COORDINATOR); + console.log("Watchtower Pubkey X: ", watchtowerPubkeyX); + console.log("Watchtower Pubkey Y: ", watchtowerPubkeyY); console.log("BLS Signature Checker: ", address(blsSignatureChecker)); console.log("Simple Verification Consumer:", address(simpleVerificationConsumer)); console.log("Storage Query Consumer: ", address(storageQueryConsumer)); @@ -66,19 +100,30 @@ contract DeployOpacityExamples is Script { // Verify the contracts are properly linked console.log("\n=== Verification Checks ==="); - console.log("Simple Consumer BLS Address: ", address(simpleVerificationConsumer.blsSignatureChecker())); + console.log("Simple Consumer BLS Address:", address(simpleVerificationConsumer.blsSignatureChecker())); console.log("Storage Consumer BLS Address:", address(storageQueryConsumer.blsSignatureChecker())); + (uint256 simpleX, uint256 simpleY) = simpleVerificationConsumer.watchtowerPubkey(); + (uint256 storageX, uint256 storageY) = storageQueryConsumer.watchtowerPubkey(); + console.log("Simple Consumer Watchtower X:", simpleX); + console.log("Simple Consumer Watchtower Y:", simpleY); + console.log("Storage Consumer Watchtower X:", storageX); + console.log("Storage Consumer Watchtower Y:", storageY); + bool simpleLinked = address(simpleVerificationConsumer.blsSignatureChecker()) == address(blsSignatureChecker); bool storageLinked = address(storageQueryConsumer.blsSignatureChecker()) == address(blsSignatureChecker); + bool simpleWatchtowerSet = simpleX == watchtowerPubkeyX && simpleY == watchtowerPubkeyY; + bool storageWatchtowerSet = storageX == watchtowerPubkeyX && storageY == watchtowerPubkeyY; - console.log("Simple Consumer properly linked: ", simpleLinked); + console.log("Simple Consumer properly linked:", simpleLinked); console.log("Storage Consumer properly linked:", storageLinked); + console.log("Simple Consumer watchtower set:", simpleWatchtowerSet); + console.log("Storage Consumer watchtower set:", storageWatchtowerSet); - if (simpleLinked && storageLinked) { - console.log("All contracts deployed and linked successfully!"); + if (simpleLinked && storageLinked && simpleWatchtowerSet && storageWatchtowerSet) { + console.log("\nAll contracts deployed and configured successfully!"); } else { - console.log("Contract linking verification failed!"); + console.log("\nWARNING: Contract configuration verification failed!"); } } } diff --git a/src/OpacitySDK.sol b/src/OpacitySDK.sol index 8ecaff6..749b36c 100644 --- a/src/OpacitySDK.sol +++ b/src/OpacitySDK.sol @@ -2,9 +2,8 @@ pragma solidity ^0.8.30; import "@eigenlayer-middleware/BLSSignatureChecker.sol"; -import { - IBLSSignatureChecker, IBLSSignatureCheckerTypes -} from "@eigenlayer-middleware/interfaces/IBLSSignatureChecker.sol"; +import {IBLSSignatureCheckerTypes} from "@eigenlayer-middleware/interfaces/IBLSSignatureChecker.sol"; +import {BN254} from "@eigenlayer-middleware/libraries/BN254.sol"; /** * @title OpacitySDK @@ -12,6 +11,9 @@ import { * @dev Inherit from this contract to add opacity verification capabilities to your contract */ abstract contract OpacitySDK { + // Sentinel value indicating watchtower signed (not in nonSignerPubkeys array) + uint32 public constant WATCHTOWER_SIGNED = type(uint32).max; + /** * @notice Struct containing all parameters needed for verification * @param quorumNumbers The quorum numbers to check signatures for @@ -23,6 +25,7 @@ abstract contract OpacitySDK { * @param value The value associated with the operation * @param operatorThreshold The operator threshold value for the operation * @param signature The signature string + * @param watchtowerNonSignerIndex Index of watchtower in nonSignerPubkeys array, or WATCHTOWER_SIGNED if watchtower signed */ struct VerificationParams { bytes quorumNumbers; @@ -34,29 +37,46 @@ abstract contract OpacitySDK { string value; uint256 operatorThreshold; string signature; + uint32 watchtowerNonSignerIndex; } // The BLS signature checker contract BLSSignatureChecker public immutable blsSignatureChecker; + // Watchtower's BLS public key (G1 point on BN254 curve) + BN254.G1Point public watchtowerPubkey; + + // Whether watchtower verification is enabled (default: true) + bool public watchtowerEnabled = true; + // Constants for stake threshold checking uint8 public constant THRESHOLD_DENOMINATOR = 100; uint8 public QUORUM_THRESHOLD = 1; uint32 public BLOCK_STALE_MEASURE = 300; + // Events + event WatchtowerPubkeyUpdated(uint256 oldX, uint256 oldY, uint256 newX, uint256 newY); + event WatchtowerStatusChanged(bool enabled); + // Custom errors error InvalidSignature(); error InsufficientQuorumThreshold(); error StaleBlockNumber(); error FutureBlockNumber(); + error WatchtowerDidNotSign(); + error InvalidWatchtowerPubkey(); + error InvalidWatchtowerNonSignerIndex(); /** * @notice Constructor for OpacitySDK * @param _blsSignatureChecker Address of the deployed BLS signature checker contract + * @param _watchtowerPubkey The watchtower's BLS public key (G1 point) */ - constructor(address _blsSignatureChecker) { + constructor(address _blsSignatureChecker, BN254.G1Point memory _watchtowerPubkey) { require(_blsSignatureChecker != address(0), "Invalid BLS signature checker address"); + require(_watchtowerPubkey.X != 0 || _watchtowerPubkey.Y != 0, "Invalid watchtower pubkey"); blsSignatureChecker = BLSSignatureChecker(_blsSignatureChecker); + watchtowerPubkey = _watchtowerPubkey; } /** @@ -81,12 +101,12 @@ abstract contract OpacitySDK { ) ); - // Verify the signatures using checkSignatures + // Verify operator quorum via BLS signature check (IBLSSignatureCheckerTypes.QuorumStakeTotals memory stakeTotals,) = blsSignatureChecker.checkSignatures( msgHash, params.quorumNumbers, params.referenceBlockNumber, params.nonSignerStakesAndSignature ); - // Check that signatories own at least 66% of each quorum + // Check that signatories own at least the required threshold of each quorum for (uint256 i = 0; i < params.quorumNumbers.length; i++) { require( stakeTotals.signedStakeForQuorum[i] * THRESHOLD_DENOMINATOR @@ -95,9 +115,71 @@ abstract contract OpacitySDK { ); } + // Verify watchtower signed if enabled (O(1) check using provided index) + if (watchtowerEnabled) { + _verifyWatchtowerSigned( + params.nonSignerStakesAndSignature.nonSignerPubkeys, + params.watchtowerNonSignerIndex + ); + } + return true; } + /** + * @notice Internal function to verify watchtower is one of the signers (O(1)) + * @param nonSignerPubkeys Array of BLS public keys of operators who did NOT sign + * @param watchtowerIndex Index of watchtower in nonSignerPubkeys, or WATCHTOWER_SIGNED if they signed + * @dev Reverts if watchtower pubkey is found at the specified index + */ + function _verifyWatchtowerSigned( + BN254.G1Point[] memory nonSignerPubkeys, + uint32 watchtowerIndex + ) internal view { + // If index is WATCHTOWER_SIGNED, watchtower claims to have signed (not in non-signers array) + if (watchtowerIndex == WATCHTOWER_SIGNED) { + return; // Watchtower signed + } + + // Validate index is within bounds + require(watchtowerIndex < nonSignerPubkeys.length, InvalidWatchtowerNonSignerIndex()); + + // Check if the pubkey at the given index matches watchtower's pubkey + BN254.G1Point memory pubkeyAtIndex = nonSignerPubkeys[watchtowerIndex]; + + if (pubkeyAtIndex.X == watchtowerPubkey.X && pubkeyAtIndex.Y == watchtowerPubkey.Y) { + // Watchtower is in the non-signers list at this index - they didn't sign + revert WatchtowerDidNotSign(); + } + + // If pubkey at index doesn't match watchtower, the index is invalid + // (watchtower might be elsewhere in the array or not at all) + revert InvalidWatchtowerNonSignerIndex(); + } + + /** + * @notice Update the watchtower's BLS public key + * @param _newPubkey The new watchtower BLS public key (G1 point) + * @dev Can only be called by the contract owner/admin + */ + function updateWatchtowerPubkey(BN254.G1Point memory _newPubkey) external virtual { + require(_newPubkey.X != 0 || _newPubkey.Y != 0, InvalidWatchtowerPubkey()); + uint256 oldX = watchtowerPubkey.X; + uint256 oldY = watchtowerPubkey.Y; + watchtowerPubkey = _newPubkey; + emit WatchtowerPubkeyUpdated(oldX, oldY, _newPubkey.X, _newPubkey.Y); + } + + /** + * @notice Enable or disable watchtower verification + * @param enabled Whether to enable watchtower verification + * @dev Can only be called by the contract owner/admin + */ + function setWatchtowerStatus(bool enabled) external virtual { + watchtowerEnabled = enabled; + emit WatchtowerStatusChanged(enabled); + } + /** * @notice Get the current quorum threshold * @return The current quorum threshold percentage diff --git a/src/examples/SimpleVerificationConsumer.sol b/src/examples/SimpleVerificationConsumer.sol index 6fc4207..4b50da7 100644 --- a/src/examples/SimpleVerificationConsumer.sol +++ b/src/examples/SimpleVerificationConsumer.sol @@ -2,16 +2,18 @@ pragma solidity ^0.8.30; import "../OpacitySDK.sol"; -import "@eigenlayer-middleware/interfaces/IBLSSignatureChecker.sol"; +import {BN254} from "@eigenlayer-middleware/libraries/BN254.sol"; contract SimpleVerificationConsumer is OpacitySDK { - event DataVerified(address user, string platform, string resource, string value, bool isValid); + event DataVerified(address user, string platform, string resource, string value, bool isValid, bool watchtowerVerified); /** * @notice Constructor for SimpleVerificationConsumer * @param _blsSignatureChecker Address of the deployed BLS signature checker contract + * @param _watchtowerPubkey The watchtower's BLS public key (G1 point) */ - constructor(address _blsSignatureChecker) OpacitySDK(_blsSignatureChecker) {} + constructor(address _blsSignatureChecker, BN254.G1Point memory _watchtowerPubkey) + OpacitySDK(_blsSignatureChecker, _watchtowerPubkey) {} /** * @notice Verify user data using VerificationParams struct @@ -21,9 +23,25 @@ contract SimpleVerificationConsumer is OpacitySDK { function verifyUserData(VerificationParams calldata params) public returns (bool) { try this.verify(params) returns (bool verified) { // Verification successful - emit event - emit DataVerified(params.userAddress, params.platform, params.resource, params.value, verified); // derefrence by using the struct params + emit DataVerified( + params.userAddress, + params.platform, + params.resource, + params.value, + verified, + watchtowerEnabled + ); return verified; } catch { + // Verification failed - emit event with false + emit DataVerified( + params.userAddress, + params.platform, + params.resource, + params.value, + false, + watchtowerEnabled + ); return false; } } diff --git a/src/examples/StorageQueryConsumer.sol b/src/examples/StorageQueryConsumer.sol index 9b8ed59..defd44b 100644 --- a/src/examples/StorageQueryConsumer.sol +++ b/src/examples/StorageQueryConsumer.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.30; import "../OpacitySDK.sol"; -import "@eigenlayer-middleware/interfaces/IBLSSignatureChecker.sol"; +import {BN254} from "@eigenlayer-middleware/libraries/BN254.sol"; /** * @title StorageQueryConsumer @@ -15,17 +15,20 @@ contract StorageQueryConsumer is OpacitySDK { string verifiedValue; uint256 timestamp; bytes32 verificationHash; + bool watchtowerVerified; } mapping(address => VerificationResult) public userVerifications; - event DataVerified(address indexed user, string verifiedValue, bytes32 verificationHash, bool success); + event DataVerified(address indexed user, string verifiedValue, bytes32 verificationHash, bool success, bool watchtowerVerified); /** * @notice Constructor for StorageQueryConsumer * @param _blsSignatureChecker Address of the deployed BLS signature checker contract + * @param _watchtowerPubkey The watchtower's BLS public key (G1 point) */ - constructor(address _blsSignatureChecker) OpacitySDK(_blsSignatureChecker) {} + constructor(address _blsSignatureChecker, BN254.G1Point memory _watchtowerPubkey) + OpacitySDK(_blsSignatureChecker, _watchtowerPubkey) {} /** * @notice Verify private data using VerificationParams struct @@ -48,10 +51,11 @@ contract StorageQueryConsumer is OpacitySDK { isVerified: verified, verifiedValue: params.value, timestamp: block.timestamp, - verificationHash: verificationHash + verificationHash: verificationHash, + watchtowerVerified: watchtowerEnabled }); - emit DataVerified(params.userAddress, params.value, verificationHash, verified); // derefrence by using the struct params + emit DataVerified(params.userAddress, params.value, verificationHash, verified, watchtowerEnabled); return (verified, params.value); } catch { return (false, ""); @@ -75,14 +79,15 @@ contract StorageQueryConsumer is OpacitySDK { * @return verifiedValue The verified value * @return timestamp When the verification was made * @return verificationHash The hash of the verification + * @return watchtowerVerified Whether watchtower was involved in verification */ function getUserVerification(address user) external view - returns (bool isValid, string memory verifiedValue, uint256 timestamp, bytes32 verificationHash) + returns (bool isValid, string memory verifiedValue, uint256 timestamp, bytes32 verificationHash, bool watchtowerVerified) { VerificationResult memory result = userVerifications[user]; - return (result.isVerified, result.verifiedValue, result.timestamp, result.verificationHash); + return (result.isVerified, result.verifiedValue, result.timestamp, result.verificationHash, result.watchtowerVerified); } /** diff --git a/test/OpacitySDKWatchtower.t.sol b/test/OpacitySDKWatchtower.t.sol new file mode 100644 index 0000000..a08539c --- /dev/null +++ b/test/OpacitySDKWatchtower.t.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.30; + +import "forge-std/Test.sol"; +import "../src/OpacitySDK.sol"; +import {IBLSSignatureCheckerTypes} from "@eigenlayer-middleware/interfaces/IBLSSignatureChecker.sol"; +import {BN254} from "@eigenlayer-middleware/libraries/BN254.sol"; + +// Test contract that extends OpacitySDK for testing +contract TestableOpacitySDK is OpacitySDK { + constructor(address _blsSignatureChecker, BN254.G1Point memory _watchtowerPubkey) + OpacitySDK(_blsSignatureChecker, _watchtowerPubkey) {} +} + +contract OpacitySDKWatchtowerTest is Test { + TestableOpacitySDK public sdk; + address public blsSignatureChecker; + + // Watchtower BLS pubkey (example values for testing) + uint256 public watchtowerPubkeyX = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + uint256 public watchtowerPubkeyY = 0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321; + + address public user = address(0x1234); + + function setUp() public { + // Deploy mock BLS signature checker (just a simple address for testing) + blsSignatureChecker = address(0x5678); + + // Deploy SDK with watchtower BLS pubkey + BN254.G1Point memory watchtowerPubkey = BN254.G1Point({X: watchtowerPubkeyX, Y: watchtowerPubkeyY}); + sdk = new TestableOpacitySDK(blsSignatureChecker, watchtowerPubkey); + } + + function testWatchtowerCanBeDisabled() public { + // Disable watchtower + sdk.setWatchtowerStatus(false); + assertFalse(sdk.watchtowerEnabled()); + } + + function testWatchtowerStatusChange() public { + // Test disabling + vm.expectEmit(true, false, false, false); + emit OpacitySDK.WatchtowerStatusChanged(false); + sdk.setWatchtowerStatus(false); + assertFalse(sdk.watchtowerEnabled()); + + // Test enabling + vm.expectEmit(true, false, false, false); + emit OpacitySDK.WatchtowerStatusChanged(true); + sdk.setWatchtowerStatus(true); + assertTrue(sdk.watchtowerEnabled()); + } + + function testWatchtowerEnabledByDefault() public view { + assertTrue(sdk.watchtowerEnabled()); + } + + function testWatchtowerPubkeyInitialization() public view { + (uint256 x, uint256 y) = sdk.watchtowerPubkey(); + assertEq(x, watchtowerPubkeyX); + assertEq(y, watchtowerPubkeyY); + } + + function testCannotDeployWithZeroWatchtowerPubkey() public { + BN254.G1Point memory zeroPubkey = BN254.G1Point({X: 0, Y: 0}); + vm.expectRevert("Invalid watchtower pubkey"); + new TestableOpacitySDK(blsSignatureChecker, zeroPubkey); + } + + function testCannotDeployWithZeroBlsSignatureChecker() public { + BN254.G1Point memory watchtowerPubkey = BN254.G1Point({X: watchtowerPubkeyX, Y: watchtowerPubkeyY}); + vm.expectRevert("Invalid BLS signature checker address"); + new TestableOpacitySDK(address(0), watchtowerPubkey); + } + + function testUpdateWatchtowerPubkey() public { + uint256 newX = 0xaaaa; + uint256 newY = 0xbbbb; + + vm.expectEmit(true, true, true, true); + emit OpacitySDK.WatchtowerPubkeyUpdated(watchtowerPubkeyX, watchtowerPubkeyY, newX, newY); + + BN254.G1Point memory newPubkey = BN254.G1Point({X: newX, Y: newY}); + sdk.updateWatchtowerPubkey(newPubkey); + + (uint256 x, uint256 y) = sdk.watchtowerPubkey(); + assertEq(x, newX); + assertEq(y, newY); + } + + function testCannotUpdateWatchtowerPubkeyToZero() public { + BN254.G1Point memory zeroPubkey = BN254.G1Point({X: 0, Y: 0}); + vm.expectRevert(OpacitySDK.InvalidWatchtowerPubkey.selector); + sdk.updateWatchtowerPubkey(zeroPubkey); + } + + function testWatchtowerSignedSentinel() public view { + // WATCHTOWER_SIGNED should be max uint32 + assertEq(sdk.WATCHTOWER_SIGNED(), type(uint32).max); + } + + function testVerificationParamsWithWatchtowerSignedIndex() public view { + // Create params where watchtower signed (index = WATCHTOWER_SIGNED) + OpacitySDK.VerificationParams memory params = _createValidParams(); + params.watchtowerNonSignerIndex = sdk.WATCHTOWER_SIGNED(); + + // Verify the params are correctly set + assertEq(params.watchtowerNonSignerIndex, type(uint32).max); + } + + function testVerificationParamsWithWatchtowerDidNotSign() public view { + // Create params where watchtower did not sign (index points to watchtower in non-signers) + OpacitySDK.VerificationParams memory params = _createValidParams(); + + // Add watchtower pubkey to nonSignerPubkeys at index 0 + BN254.G1Point[] memory nonSigners = new BN254.G1Point[](1); + nonSigners[0] = BN254.G1Point({X: watchtowerPubkeyX, Y: watchtowerPubkeyY}); + params.nonSignerStakesAndSignature.nonSignerPubkeys = nonSigners; + params.watchtowerNonSignerIndex = 0; // Points to watchtower + + // Verify the setup + assertEq(params.nonSignerStakesAndSignature.nonSignerPubkeys.length, 1); + assertEq(params.nonSignerStakesAndSignature.nonSignerPubkeys[0].X, watchtowerPubkeyX); + assertEq(params.watchtowerNonSignerIndex, 0); + } + + // Helper functions + function _createValidParams() internal view returns (OpacitySDK.VerificationParams memory) { + OpacitySDK.VerificationParams memory params; + params.quorumNumbers = hex"00"; + params.referenceBlockNumber = uint32(block.number - 1); + + // Create empty arrays for non-signers (watchtower signed in this case) + BN254.G1Point[] memory nonSignerPubkeys = new BN254.G1Point[](0); + BN254.G1Point[] memory quorumApks = new BN254.G1Point[](1); + uint32[] memory totalStakeIndices = new uint32[](1); + uint32[][] memory nonSignerStakeIndices = new uint32[][](0); + + params.nonSignerStakesAndSignature = IBLSSignatureCheckerTypes.NonSignerStakesAndSignature({ + nonSignerQuorumBitmapIndices: new uint32[](0), + nonSignerPubkeys: nonSignerPubkeys, + quorumApks: quorumApks, + apkG2: BN254.G2Point({X: [uint256(0), uint256(0)], Y: [uint256(0), uint256(0)]}), + sigma: BN254.G1Point({X: uint256(0), Y: uint256(0)}), + quorumApkIndices: new uint32[](1), + totalStakeIndices: totalStakeIndices, + nonSignerStakeIndices: nonSignerStakeIndices + }); + + params.userAddress = user; + params.platform = "twitter"; + params.resource = "username"; + params.value = "testuser"; + params.operatorThreshold = 66; + params.signature = "test_signature"; + params.watchtowerNonSignerIndex = sdk.WATCHTOWER_SIGNED(); // Default: watchtower signed + + return params; + } + + function _calculateMsgHash(OpacitySDK.VerificationParams memory params) internal pure returns (bytes32) { + return keccak256( + abi.encode( + params.userAddress, + params.platform, + params.resource, + params.value, + params.operatorThreshold, + params.signature + ) + ); + } +}