-
Notifications
You must be signed in to change notification settings - Fork 49
Shutterized Dispute Kit #1965
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
jaybuidl
wants to merge
33
commits into
dev
Choose a base branch
from
feat/shutter-dispute-kit
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Shutterized Dispute Kit #1965
Changes from 12 commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
2bffa61
chore: shutter script experiment
jaybuidl 3570b46
feat(shutter): added decryption logic
jaybuidl ddae29a
fix: better error handling
jaybuidl 04e3f5c
feat: naive shutterized dispute kit and auto-voting bot
jaybuidl be6e1ce
feat: commitment hashing and verification onchain
jaybuidl 020dbab
chore: cleanup
jaybuidl 30804a8
feat: support for multiple voteIDs at once, fixed salt handling by bot
jaybuidl 1a9b72d
chore: cleanup before integration into a fully fletched dispute kit
jaybuidl b580556
feat: fully fletched DisputeKitShutter
jaybuidl 8819241
chore: removed redundant node-fetch
jaybuidl cd016b3
chore: cleanup
jaybuidl 8e46e05
feat: fully fletched DisputeKitShutter
jaybuidl 98ec3ba
chore: deployment of DisputeKitShutter in devnet
jaybuidl 74d1506
fix: missing parameter, upgraded DisputeKitShutter
jaybuidl b40abc2
chore: upgraded DisputeKitClassic
jaybuidl cb7997f
fix: external call to castCommit() changes msg.sender, fixed by extra…
jaybuidl ee2ceb6
chore: enable shutter DK on the devnet general court
jaybuidl 317aed6
feat: support for shutter disputekit in devnet
kemuru 05dcdb0
fix: create new classic dispute entity correctly with correct round i…
kemuru 44ea55e
fix: correct round check
kemuru a995e1e
chore: subgraph update scripts now relies on a template and removes d…
jaybuidl ca14ada
Merge branch 'dev' into feat/shutter-dispute-kit
jaybuidl f3f235c
Merge branch 'feat/shutter-dispute-kit' into feat(subgraph)/support-f…
kemuru 4b85135
Merge branch 'dev' into feat/shutter-dispute-kit
jaybuidl edc4a7d
Merge branch 'dev' into feat/shutter-dispute-kit
jaybuidl 78b2951
chore: fix to support deployment to Alchemy
jaybuidl 6b32fc3
chore: lock file
jaybuidl 74625e1
Merge pull request #1966 from kleros/feat(subgraph)/support-for-multi…
jaybuidl 0dad7c4
Merge branch 'dev' into feat/shutter-dispute-kit
kemuru 6cd4369
Merge branch 'dev' into feat/shutter-dispute-kit
kemuru 14994db
fix: bug fix in subgraph
kemuru 142273c
Merge pull request #1995 from kleros/fix(subgraph)/localrounds-fix
kemuru 5989928
chore: subgraphs version bump
jaybuidl File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
import { encryptData, decrypt as shutterDecrypt } from "@shutter-network/shutter-sdk"; | ||
import { Hex, stringToHex, hexToString } from "viem"; | ||
import crypto from "crypto"; | ||
import "isomorphic-fetch"; | ||
|
||
// Time in seconds to wait before the message can be decrypted | ||
export const DECRYPTION_DELAY = 5; | ||
|
||
interface ShutterApiMessageData { | ||
eon: number; | ||
identity: string; | ||
identity_prefix: string; | ||
eon_key: string; | ||
tx_hash: string; | ||
} | ||
|
||
interface ShutterApiResponse { | ||
message: ShutterApiMessageData; | ||
error?: string; | ||
} | ||
|
||
interface ShutterDecryptionKeyData { | ||
decryption_key: string; | ||
identity: string; | ||
decryption_timestamp: number; | ||
} | ||
|
||
/** | ||
* Fetches encryption data from the Shutter API | ||
* @param decryptionTimestamp Unix timestamp when decryption should be possible | ||
* @returns Promise with the eon key and identity | ||
*/ | ||
async function fetchShutterData(decryptionTimestamp: number): Promise<ShutterApiMessageData> { | ||
try { | ||
console.log(`Sending request to Shutter API with decryption timestamp: ${decryptionTimestamp}`); | ||
|
||
// Generate a random identity prefix | ||
const identityPrefix = generateRandomBytes32(); | ||
console.log(`Generated identity prefix: ${identityPrefix}`); | ||
|
||
const response = await fetch("https://shutter-api.shutter.network/api/register_identity", { | ||
method: "POST", | ||
headers: { | ||
accept: "application/json", | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ | ||
decryptionTimestamp, | ||
identityPrefix, | ||
}), | ||
}); | ||
|
||
// Log the response status | ||
console.log(`API response status: ${response.status}`); | ||
|
||
// Get the response text | ||
const responseText = await response.text(); | ||
|
||
if (!response.ok) { | ||
throw new Error(`API request failed with status ${response.status}: ${responseText}`); | ||
} | ||
|
||
// Parse the JSON response | ||
let jsonResponse: ShutterApiResponse; | ||
try { | ||
jsonResponse = JSON.parse(responseText); | ||
} catch (error) { | ||
throw new Error(`Failed to parse API response as JSON: ${responseText}`); | ||
} | ||
|
||
// Check if we have the message data | ||
if (!jsonResponse.message) { | ||
throw new Error(`API response missing message data: ${JSON.stringify(jsonResponse)}`); | ||
} | ||
|
||
return jsonResponse.message; | ||
} catch (error) { | ||
console.error("Error fetching data from Shutter API:", error); | ||
throw error; | ||
} | ||
} | ||
|
||
/** | ||
* Fetches the decryption key from the Shutter API | ||
* @param identity The identity used for encryption | ||
* @returns Promise with the decryption key data | ||
*/ | ||
async function fetchDecryptionKey(identity: string): Promise<ShutterDecryptionKeyData> { | ||
console.log(`Fetching decryption key for identity: ${identity}`); | ||
|
||
const response = await fetch(`https://shutter-api.shutter.network/api/get_decryption_key?identity=${identity}`, { | ||
method: "GET", | ||
headers: { | ||
accept: "application/json", | ||
}, | ||
}); | ||
|
||
// Get the response text | ||
const responseText = await response.text(); | ||
|
||
// Try to parse the error response even if the request failed | ||
let jsonResponse; | ||
try { | ||
jsonResponse = JSON.parse(responseText); | ||
} catch (error) { | ||
throw new Error(`Failed to parse API response as JSON: ${responseText}`); | ||
} | ||
|
||
// Handle the "too early" error case specifically | ||
if (!response.ok) { | ||
if (jsonResponse?.description?.includes("timestamp not reached yet")) { | ||
throw new Error( | ||
`Cannot decrypt yet: The decryption timestamp has not been reached.\n` + | ||
`Please wait at least ${DECRYPTION_DELAY} seconds after encryption before attempting to decrypt.\n` + | ||
`Error details: ${jsonResponse.description}` | ||
); | ||
} | ||
throw new Error(`API request failed with status ${response.status}: ${responseText}`); | ||
} | ||
|
||
// Check if we have the message data | ||
if (!jsonResponse.message) { | ||
throw new Error(`API response missing message data: ${JSON.stringify(jsonResponse)}`); | ||
} | ||
|
||
return jsonResponse.message; | ||
} | ||
|
||
/** | ||
* Ensures a string is a valid hex string with 0x prefix | ||
* @param hexString The hex string to validate | ||
* @returns The validated hex string with 0x prefix | ||
*/ | ||
function ensureHexString(hexString: string | undefined): `0x${string}` { | ||
if (!hexString) { | ||
throw new Error("Hex string is undefined or null"); | ||
} | ||
|
||
// Add 0x prefix if it doesn't exist | ||
const prefixedHex = hexString.startsWith("0x") ? hexString : `0x${hexString}`; | ||
return prefixedHex as `0x${string}`; | ||
} | ||
|
||
/** | ||
* Generates a random 32 bytes | ||
* @returns Random 32 bytes as a hex string with 0x prefix | ||
*/ | ||
function generateRandomBytes32(): `0x${string}` { | ||
return ("0x" + | ||
crypto | ||
.getRandomValues(new Uint8Array(32)) | ||
.reduce((acc, byte) => acc + byte.toString(16).padStart(2, "0"), "")) as Hex; | ||
} | ||
|
||
/** | ||
* Encrypts a message using the Shutter API | ||
* @param message The message to encrypt | ||
* @returns Promise with the encrypted commitment and identity | ||
*/ | ||
export async function encrypt(message: string): Promise<{ encryptedCommitment: string; identity: string }> { | ||
// Set decryption timestamp | ||
const decryptionTimestamp = Math.floor(Date.now() / 1000) + DECRYPTION_DELAY; | ||
|
||
// Fetch encryption data from Shutter API | ||
console.log(`Fetching encryption data for decryption at timestamp ${decryptionTimestamp}...`); | ||
const shutterData = await fetchShutterData(decryptionTimestamp); | ||
|
||
// Extract the eon key and identity from the response and ensure they have the correct format | ||
const eonKeyHex = ensureHexString(shutterData.eon_key); | ||
const identityHex = ensureHexString(shutterData.identity); | ||
|
||
// Message to encrypt | ||
const msgHex = stringToHex(message); | ||
|
||
// Generate a random sigma | ||
const sigmaHex = generateRandomBytes32(); | ||
|
||
console.log("Eon Key:", eonKeyHex); | ||
console.log("Identity:", identityHex); | ||
console.log("Sigma:", sigmaHex); | ||
|
||
// Encrypt the message | ||
const encryptedCommitment = await encryptData(msgHex, identityHex, eonKeyHex, sigmaHex); | ||
|
||
return { encryptedCommitment, identity: identityHex }; | ||
} | ||
|
||
/** | ||
* Decrypts a message using the Shutter API | ||
* @param encryptedMessage The encrypted message to decrypt | ||
* @param identity The identity used for encryption | ||
* @returns Promise with the decrypted message | ||
*/ | ||
export async function decrypt(encryptedMessage: string, identity: string): Promise<string> { | ||
// Fetch the decryption key | ||
const decryptionKeyData = await fetchDecryptionKey(identity); | ||
console.log("Decryption key:", decryptionKeyData.decryption_key); | ||
|
||
// Ensure the decryption key is properly formatted | ||
const decryptionKey = ensureHexString(decryptionKeyData.decryption_key); | ||
|
||
// Decrypt the message | ||
const decryptedHexMessage = await shutterDecrypt(encryptedMessage, decryptionKey); | ||
|
||
// Convert the decrypted hex message back to a string | ||
return hexToString(decryptedHexMessage as `0x${string}`); | ||
} | ||
|
||
async function main() { | ||
try { | ||
const command = process.argv[2]?.toLowerCase(); | ||
|
||
if (!command) { | ||
console.error(` | ||
Usage: yarn ts-node shutter.ts <command> [arguments] | ||
|
||
Commands: | ||
encrypt <message> Encrypt a message | ||
decrypt <encrypted message> <identity> Decrypt a message (requires the identity used during encryption) | ||
|
||
Examples: | ||
yarn ts-node shutter.ts encrypt "my secret message" | ||
yarn ts-node shutter.ts decrypt "encrypted-data" "0x1234..."`); | ||
process.exit(1); | ||
} | ||
|
||
switch (command) { | ||
case "encrypt": { | ||
const message = process.argv[3]; | ||
if (!message) { | ||
console.error("Error: Missing message to encrypt"); | ||
console.error("Usage: yarn ts-node shutter.ts encrypt <message>"); | ||
process.exit(1); | ||
} | ||
const { encryptedCommitment, identity } = await encrypt(message); | ||
console.log("\nEncrypted Commitment:", encryptedCommitment); | ||
console.log("Identity:", identity); | ||
break; | ||
} | ||
case "decrypt": { | ||
const [encryptedMessage, identity] = [process.argv[3], process.argv[4]]; | ||
if (!encryptedMessage || !identity) { | ||
console.error("Error: Missing required arguments for decrypt"); | ||
console.error("Usage: yarn ts-node shutter.ts decrypt <encrypted-message> <identity>"); | ||
console.error("Note: The identity is the one returned during encryption"); | ||
process.exit(1); | ||
} | ||
const decryptedMessage = await decrypt(encryptedMessage, identity); | ||
console.log("\nDecrypted Message:", decryptedMessage); | ||
break; | ||
} | ||
default: { | ||
console.error(`Error: Unknown command '${command}'`); | ||
console.error("Valid commands are: encrypt, decrypt"); | ||
process.exit(1); | ||
} | ||
} | ||
} catch (error) { | ||
console.error("\nError:", error); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
// Execute if run directly | ||
if (require.main === module) { | ||
main(); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crypto.getRandomValues
is not available in Node.jsgetRandomValues
is a Web-Crypto API. In a Node environment usecrypto.randomBytes
.This prevents runtime crashes when the script is executed with
ts-node
.📝 Committable suggestion