Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 52 additions & 7 deletions script/DeployOpacityExamples.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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;
Expand All @@ -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);

Expand All @@ -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();
Expand All @@ -59,26 +91,39 @@ 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));
console.log("========================================");

// 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!");
}
}
}
94 changes: 88 additions & 6 deletions src/OpacitySDK.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
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
* @notice Lightweight SDK for implementing opacity verification
* @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
Expand All @@ -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;
Expand All @@ -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;
}

/**
Expand All @@ -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
Expand All @@ -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
Expand Down
26 changes: 22 additions & 4 deletions src/examples/SimpleVerificationConsumer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
}
Expand Down
19 changes: 12 additions & 7 deletions src/examples/StorageQueryConsumer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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, "");
Expand All @@ -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);
}

/**
Expand Down
Loading
Loading