DataPrivacyFramework.sol enables building privacy-preserving applications like a ticketing system, where ticket prices, supplies, and purchase history remain private while still enabling necessary computations and verifications.
- Privacy: Values are encrypted end-to-end, never visible on-chain
- Computability: Can perform operations on encrypted data
- Access Control: Built-in permission system controls who can access what
- Selective Disclosure: Results can be encrypted for specific users
- Auditability: Computations are verifiable without revealing inputs
import "@coti-io/coti-contracts/contracts/access/DataPrivacyFramework/extensions/DataPrivacyFrameworkMpc.sol";
contract YourContract is DataPrivacyFrameworkMpc {
constructor() DataPrivacyFrameworkMpc(false, false) {
// false, false = default permissions for addresses and operations
// Set up permissions
setPermission(InputData(msg.sender, "admin", true, 0, 0, false, false, 0, address(0), ""));
}
}
How setPermission(InputData(msg.sender, "admin", true, 0, 0, false, false, 0, address(0), "")) Works
- InputData Struct Breakdown
The InputData struct has these fields, and here's what each parameter means:
struct InputData {
address caller; // msg.sender (contract deployer)
string operation; // "admin"
bool active; // true (permission is active)
uint256 timestampBefore; // 0 (no expiration date)
uint256 timestampAfter; // 0 (no start delay)
bool falseKey; // false (don't force denial)
bool trueKey; // false (don't force approval)
uint256 uintParameter; // 0 (no uint parameter constraint)
address addressParameter; // address(0) (no address parameter constraint)
string stringParameter; // "" (no string parameter constraint)
}So the call translates to:
InputData(
msg.sender, // caller = contract deployer's address
"admin", // operation = "admin"
true, // active = permission is enabled
0, // timestampBefore = no expiration
0, // timestampAfter = no start delay
false, // falseKey = don't force deny
false, // trueKey = don't force allow
0, // uintParameter = no constraint
address(0), // addressParameter = no constraint
"" // stringParameter = no constraint
)The function creates a new permission entry:
function setPermission(InputData memory inputData) public returns (bool) {
if (permissions[inputData.caller][inputData.operation] == 0) {
// First time setting this permission
permissions[inputData.caller][inputData.operation] = _conditionsCount;
conditions[_conditionsCount] = Condition(
_conditionsCount, // id
inputData.caller, // msg.sender
inputData.operation, // "admin"
inputData.active, // true
inputData.falseKey, // false
inputData.trueKey, // false
inputData.timestampBefore, // 0
inputData.timestampAfter, // 0
inputData.uintParameter, // 0
inputData.addressParameter, // address(0)
inputData.stringParameter // ""
);
++_conditionsCount;
++activePermissions[inputData.caller];
}
// ... else update existing permission
}The framework uses a two-level mapping system:
mapping(address => mapping(string => uint256)) public permissions; // caller => operation => condition_id
mapping(uint256 => Condition) public conditions; // condition_id => condition detailsSo after the call:
permissions[msg.sender]["admin"]=1(condition ID)conditions[1]= theConditionstruct with your parametersactivePermissions[msg.sender]=1(this address has 1 active permission)
- How It Works in Practice
When someone calls a function protected by:
onlyAllowedUserOperation("admin", 0, address(0), "")The modifier checks:
- Is the
"admin"operation allowed? ✅ (set in constructor:allowedOperations["admin"] = true) - Does
msg.senderhave permission for"admin"?- Look up
permissions[msg.sender]["admin"]→ gets condition ID - Look up
conditions[conditionId]→ gets the condition details - Check if condition is active and constraints are met
- Look up
In the constructor, this call:
setPermission(InputData(msg.sender, "admin", true, 0, 0, false, false, 0, address(0), ""));Grants the contract deployer full admin privileges by:
- ✅ Active:
true- permission is enabled - ✅ No time limits:
timestampBefore: 0, timestampAfter: 0- always valid - ✅ No forced outcomes:
falseKey: false, trueKey: false- normal evaluation - ✅ No parameter constraints: all parameters are neutral/empty
This means the deployer can call any function requiring "admin" privileges without restrictions.
- Alternative Examples
Here are some other permission configurations:
// Time-limited permission (valid until timestamp 1735689600)
setPermission(InputData(user, "admin", true, 1735689600, 0, false, false, 0, address(0), ""));
// Permission that always allows (trueKey = true)
setPermission(InputData(user, "admin", true, 0, 0, false, true, 0, address(0), ""));
// Permission with uint parameter constraint (only for eventId = 123)
setPermission(InputData(user, "purchase_ticket", true, 0, 0, false, false, 123, address(0), ""));
// Disabled permission
setPermission(InputData(user, "admin", false, 0, 0, false, false, 0, address(0), ""));This permission system provides fine-grained access control for your privacy-preserving smart contracts!
function setItem(string memory name, itUint64 calldata value)
external
onlyAllowedUserOperation("op_set_item", 0, address(0), "")
{
// Validate and convert user input to computation-ready format
gtUint64 gtEncryptedValue = MpcCore.validateCiphertext(value);
// Store as encrypted ciphertext
encryptedValues[name] = MpcCore.offBoard(gtEncryptedValue);
}
function getItem(string memory name)
external
onlyAllowedUserOperation("op_get_item", 0, address(0), name)
{
// Convert stored ciphertext to computation format
gtUint64 gtEncryptedValue = MpcCore.onBoard(encryptedValues[name]);
// Encrypt specifically for the requesting user
ctUint64 ctEncryptedInput = MpcCore.offBoardToUser(gtEncryptedValue, msg.sender);
// Emit as event (cannot return encrypted values directly)
emit encryptedUint(msg.sender, ctEncryptedInput);
}
function getClearOilCotiPrice()
external
onlyAllowedUserOperation("op_get_clear_oil_usd_price", 0, address(0), "")
onlyAllowedUserOperation("op_get_clear_coti_usd_price", 0, address(0), "")
{
// Load encrypted values
gtUint64 a = MpcCore.onBoard(encryptedValues["oil_usd_price"]);
gtUint64 b = MpcCore.onBoard(encryptedValues["coti_usd_price"]);
// Perform computation on encrypted data
gtUint64 c = MpcCore.div(a, b); // Oil/COTI ratio
// Decrypt and emit result
emit clearUint(msg.sender, MpcCore.decrypt(c));
}
@Cluade4 @DataPrivacyFramework.sol @OnChainDatabase.sol @MPCCore.sol @MPCInterface.sol
Based on the structure of OnChainDatabase.sol
Create a single file COTI Smart Contract that extends DataPrivacyFramework.sol for a ticketing smart contract
The Ticketing contract should ensures user privacy. Eg: keep ticket, event and ticketOwnership should use MPC Data types for private information. Never save garbledtext in storage, use ctUint64
The contract should allow users to purchase and store event tickets digitally,
with each ticket uniquely tied to its owner to prevent counterfeiting and unauthorized
resale.
Implementing MPC for privacy allows users to prove ticket ownership
without revealing personal information.
Smart contracts should be able to automate ticket transfers
and enforce resale policies, ensuring fair pricing and reducing fraud.
Save smart contract on sc folder
RESULT --> PrivateTicketingDebug.sol
import { Wallet, itUint } from '@coti-io/coti-ethers';
// Initialize wallet and onboard for encryption
const wallet = new Wallet(privateKey, provider);
await wallet.generateOrRecoverAes();
// Encrypt data for contract input
const encryptedPrice: itUint = await wallet.encryptValue(
100n, // price value
contractAddress,
contract.interface.getFunction("setItem").selector
);
// Call contract with encrypted data
await contract.setItem("oil_price", encryptedPrice);
@Claude4 @coti-ethers @solidity/PrivateTicketingDebug.sol
save all your work and only change sc-client folder
create a typescript client that compiles, deploy the PrivateTicketing smart contract
and exposes all the functions of the PrivateTicketing using @coti-ethers library
deploy it on COTI.Testnet at
COTI Network Configuration
COTI_RPC_URL=https://testnet.coti.io/rpc
COTI_CHAIN_ID=7082400
ACCOUNT_ENCRYPTION_KEY='49d9a80420ffe615de763b8cebd64dca'
PRIVATE_KEY=9ba41a77e1408b426b9fe43078aad218ee16f06fafad4896411a07611532fd5c
PRIVATE_KEY_1=9ba41a77e1408b426b9fe43078aad218ee16f06fafad4896411a07611532fd5c
PRIVATE_KEY_2=0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321
RESULT --> typescript-client
cd typescript-client
node dist/demo.jsnode
@typescript-client/dist/demo.js @typescript-client/dist/PrivateTicketingClient.ts
@coti-ethers/build/cotiethers.js
Given the client above for the coti smart contract deployed at
COTI Network Configuration
COTI_RPC_URL=https://testnet.coti.io/rpc
COTI_CHAIN_ID=7082400
Example Private Keys (REPLACE WITH YOUR TEST KEYS)
ACCOUNT_ENCRYPTION_KEY='49d9a80420ffe615de763b8cebd64dca'
PRIVATE_KEY=9ba41a77e1408b426b9fe43078aad218ee16f06fafad4896411a07611532fd5c
PRIVATE_KEY_1=9ba41a77e1408b426b9fe43078aad218ee16f06fafad4896411a07611532fd5c
PRIVATE_KEY_2=0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321
Optional: Deploy contract address (leave empty to deploy new)
CONTRACT_ADDRESS=0xbdFAb135CAcCF157216d36Bb822aC37419A3387B
Optional: Gas settings
GAS_LIMIT=500000
GAS_PRICE=1000000000
Create a React UI for the app using the coti-etherJS client files attached for smart contract comunication
