Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Lykhoyda committed May 25, 2024
2 parents 58caccc + 24524bd commit ab54581
Showing 20 changed files with 2,636 additions and 55 deletions.
136 changes: 136 additions & 0 deletions marketplace/contracts/PoEMarketplace.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

/**
* @title ProofOfExploitMarketplace
* @dev A marketplace for white-hat hackers to sell proofs of exploits to
* smart contract stakeholders.
*/
contract ProofOfExploitMarketplace is ERC721 {

struct Exploit {
address creator;
string description;
uint256 keyHash;
uint256 price;
bool redeemed;
Buyer buyer;
}

struct Buyer {
uint256[2] publicKey;
bool hasPurchased;
}

/// map of all exploits (which are also tokens)
mapping(uint256 => Exploit) public exploits;

/// map of buyer information
mapping(address => Buyer) public buyers;

/// keep track of our exploits (which are also tokens)
uint256 public exploitCount;

event ExploitPosted(uint256 indexed id, address indexed creator, uint256 price);
event TokenPurchased(uint256 indexed id, address indexed buyer);
event ExploitRedeemed(uint256 indexed id, address indexed buyer);

constructor()
ERC721("ProofOfExploitToken", "PET")
{}

/**
* @notice Posts a new exploit to the marketplace.
* @param description A description of the exploit.
* @param price The price for the exploit.
* @param keyHash The hash of the key of the encryption of the proof of the exploit.
* @return The ID of the newly created exploit (token).
*/
function postExploit(
string calldata description,
uint256 price,
uint256 keyHash
) external returns (uint256) {
uint256 id = exploitCount;
exploitCount++;

exploits[id] = Exploit({
creator: msg.sender,
description: description,
price: price,
redeemed: false,
keyHash: keyHash,
buyer: Buyer([uint256(0), uint256(0)], false)
});

_mint(msg.sender, id);

emit ExploitPosted(id, msg.sender, price);
return id;
}

/**
* @notice Purchases a token for a specific exploit.
* @param exploitId The ID of the exploit to purchase.
* @param publicKey The public key of the buyer.
*/
function purchaseToken(uint256 exploitId, uint256[2] calldata publicKey) external payable {
Exploit storage exploit = exploits[exploitId];
require(msg.value >= exploit.price, "Insufficient funds");

buyers[msg.sender] = Buyer({
publicKey: publicKey,
hasPurchased: true
});

_transfer(exploit.creator, msg.sender, exploitId);

exploit.buyer = buyers[msg.sender];

emit TokenPurchased(exploitId, msg.sender);
}

/**
* @notice Redeems an exploit by providing the encrypted key.
* @param tokenId The ID of the token (exploit).
* @param encryptedKey The encrypted key.
*/
function redeemExploit(uint256 tokenId, uint256 encryptedKey) external {
// make sure the exploit exists:
require(tokenId >= 0 && tokenId < exploitCount, "Exploit does not exist");
Exploit storage exploit = exploits[tokenId];
require(exploit.creator == msg.sender, "Only the creator can redeem");
require(!exploit.redeemed, "Exploit already redeemed");

//require(
// Verifier.verifyEncryptionProof(a, b, c, input),
// 'DataMarketplaceCore/proof-invalid'
//);

exploit.redeemed = true;
exploit.keyHash = encryptedKey;

address buyer = ownerOf(tokenId);
require(buyers[buyer].hasPurchased, "No buyer for this token");

payable(exploit.creator).transfer(exploit.price);

emit ExploitRedeemed(tokenId, buyer);
}

/**
* @notice Allows a token holder to retrieve the encrypted key for the exploit.
* @param tokenId The ID of the token (exploit).
* @return The encrypted key.
*/
function getExploitKey(uint256 tokenId) external view returns (uint256) {
require(ownerOf(tokenId) == msg.sender, "Only the owner can get the key");

Exploit storage exploit = exploits[tokenId];
require(exploit.redeemed, "Exploit not redeemed yet");

return exploit.keyHash;
}
}
675 changes: 675 additions & 0 deletions marketplace/data/PoEMarketplace_abi.json

Large diffs are not rendered by default.

165 changes: 165 additions & 0 deletions marketplace/package-lock.json
15 changes: 15 additions & 0 deletions marketplace/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "marketplace",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@openzeppelin/contracts": "^5.0.2",
"viem": "^2.12.1"
}
}
63 changes: 63 additions & 0 deletions marketplace/scripts/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
createPublicClient,
createWalletClient,
custom,
http,
toHex,
parseAbiItem } from 'viem'
import { sepolia } from 'viem/chains'

import abi from '../data/PoEMarketplace_abi.json'

const CONTRACT = process.env.CONTRACT_ADDRESS || '0x..'

const publicClient = createPublicClient({
chain: sepolia,
transport: http()
})

const client = createWalletClient({
chain: sepolia,
transport: custom((window as any).ethereum)
})

export const [account] = await client.getAddresses()

// generic function to write to the contract and return tx hash
const writeContract = async (functionName:string, args: any[]) => {

const { request } = await publicClient.simulateContract({
account,
address: CONTRACT as `0x${string}`,
abi: abi,
functionName: functionName,
args: args
})
return await client.writeContract(request)
}

// watches for the TokenPurchased event emitted during the purchaseToken function call.
// This will be used to trigger the redeem function call.
export const unwatch = publicClient.watchEvent({
address: CONTRACT as `0x${string}`,
event: parseAbiItem('event TokenPurchased(uint256 indexed id, address indexed buyer)'),
// TO DO - call function with logs to derive public key https://viem.sh/docs/utilities/recoverPublicKey#recoverpublickey
onLogs: logs => console.log(logs)
})

// A White Hat Hacker can post an exploit to the marketplace
export const postExploit = async (description:string, price:bigint, hash:bigint) => await writeContract('postExploit', [description, price, hash])

// A vendor can purchase the exploit token from the marketplace
export const purchaseToken = async (tokenId:number, publicKey:bigint[]) => await writeContract('purchaseToken', [tokenId, publicKey])

// The White Hat Hacker uses the vendor's public key derived from the purchaseToken transaction to compute the proofs and receive the payment
export const redeem = async (tokenId:number, key:bigint) => await writeContract('redeemExploit', [tokenId, key])

// Finally, the vendor can retrieve the shared key from the exploit token
export const retrieveKey = async (tokenId:number) => publicClient.readContract({
address: CONTRACT as `0x${string}`,
abi: abi,
functionName: 'getExploitKey',
args: [tokenId]
})
35 changes: 35 additions & 0 deletions marketplace/scripts/getContractState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import dotenv from 'dotenv'
import { GetStorageAtReturnType, createPublicClient, http, toHex } from 'viem'
import { sepolia } from 'viem/chains'
import fs from 'fs'

const publicClient = createPublicClient({
chain: sepolia,
transport: http()
})

async function getStorageAtSlot(target:string, slot:number) {
return await publicClient.getStorageAt({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
slot: toHex(0)
})
}

async function fetchAndStoreContractState(target:string) {
const slots = 100; // Number of storage slots to fetch. Adjust as needed.
const state: GetStorageAtReturnType[] = [];

for (let slot = 0; slot < slots; slot++) {
const storage:GetStorageAtReturnType = await getStorageAtSlot(target, slot)
if (storage) {
state.push(storage);
}
}

// Store the state in a JSON file
fs.writeFileSync('contractState.json', JSON.stringify(state));

console.log('Contract state saved.');
}

fetchAndStoreContractState('0x..');
39 changes: 0 additions & 39 deletions sp1_prover/.vscode/settings.json

This file was deleted.

2 changes: 1 addition & 1 deletion sp1_prover/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[workspace]
exclude = ["zkpoex", "ecdh"]
members = ["zkpoex-script", "ecdh-script","evm-runner"]
members = ["zkpoex-script", "ecdh-script", "evm-runner"]
resolver = "2"
1,397 changes: 1,397 additions & 0 deletions sp1_prover/contracts/src/fixtures/zkpoex_fixture.json

Large diffs are not rendered by default.

8 changes: 2 additions & 6 deletions sp1_prover/ecdh-script/src/bin/prove.rs
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ use sp1_sdk::{HashableKey, ProverClient, SP1Stdin};
/// The ELF (executable and linkable format) file for the Succinct RISC-V zkVM.
///
/// This file is generated by running `cargo prove build` inside the `program` directory.
pub const FIBONACCI_ELF: &[u8] = include_bytes!("../../../ecdh/elf/riscv32im-succinct-zkvm-elf");
pub const ECDH_ELF: &[u8] = include_bytes!("../../../ecdh/elf/riscv32im-succinct-zkvm-elf");

/// The arguments for the prove command.
#[derive(Parser, Debug)]
@@ -61,7 +61,7 @@ fn main() {
let client = ProverClient::new();

// Setup the program.
let (pk, vk) = client.setup(FIBONACCI_ELF);
let (pk, vk) = client.setup(ECDH_ELF);

// Setup the inputs.;
let mut stdin = SP1Stdin::new();
@@ -72,10 +72,6 @@ fn main() {
.prove_groth16(&pk, stdin)
.expect("failed to generate proof");

// Deserialize the public values.
// let bytes = proof.public_values.as_slice();
// let inputs = EcdhPublicInputs::abi_decode(bytes, false).unwrap();

// Create the testing fixture so we can test things end-ot-end.
let fixture = SP1FibonacciProofFixture {
local_sk: args.local_sk,
2 changes: 1 addition & 1 deletion sp1_prover/ecdh/Cargo.toml
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ alloy-sol-types = "0.7.2"
alloy-primitives = "0.7.2"
sp1-zkvm = { git = "https://github.com/succinctlabs/sp1.git" }

static-dh-ecdh = { path = "../../static-dh-ecdh" }
static-dh-ecdh = { git = "https://github.com/nulltea/static-dh-ecdh" }
chacha20 = "*"
sha3 = { version = "0.10.6", default-features = false }
hex = { version = "0.4", default-features = false, features = [ "alloc" ] }
2 changes: 2 additions & 0 deletions sp1_prover/zk-poex/Cargo.toml
Original file line number Diff line number Diff line change
@@ -11,3 +11,5 @@ evm-runner = { path = "../evm-runner" }
chacha20 = "*"
sha3 = { version = "0.10.6", default-features = false }
hex = { version = "0.4", default-features = false, features = [ "alloc" ] }
tlock = "*"
serde = { version = "1.0", default-features = false, features = ["derive"] }
Binary file modified sp1_prover/zk-poex/elf/riscv32im-succinct-zkvm-elf
Binary file not shown.
27 changes: 21 additions & 6 deletions sp1_prover/zk-poex/src/main.rs
Original file line number Diff line number Diff line change
@@ -15,8 +15,8 @@ use evm_runner::{run_simulation, RunEvmResult};
use sha3::{Digest, Keccak256};

pub fn main() {
let (key, nonce, calldata, blockchain_settings) =
sp1_zkvm::io::read::<([u8; 32], [u8; 12], String, String)>();
let (key, nonce, calldata, blockchain_settings, drand_master_pk, round) =
sp1_zkvm::io::read::<([u8; 32], [u8; 12], String, String, Vec<u8>, u64)>();

let RunEvmResult {
before,
@@ -27,17 +27,32 @@ pub fn main() {

let mut cipher = ChaCha20::new(&key.into(), &nonce.into());

let mut buffer = private_inputs_concat.as_bytes().to_vec();
let mut chacha_cipher = private_inputs_concat.as_bytes().to_vec();

cipher.apply_keystream(&mut buffer);
cipher.apply_keystream(&mut chacha_cipher);

let ciphertext = hex::encode(buffer.clone());
// let mut tlock_cipher = vec![];
// tlock::encrypt(
// &mut tlock_cipher,
// &key[..],
// &drand_master_pk,
// round,
// )
// .unwrap();

let mut hasher = Keccak256::new();
hasher.update(key);
let key_hash = hasher.finalize();
let key_hash_str = hex::encode(key_hash);

// Commit to the public values of the program.
sp1_zkvm::io::commit(&[before, after, hash_private_inputs, ciphertext, key_hash_str]);
sp1_zkvm::io::commit(&(
before,
after,
hash_private_inputs,
chacha_cipher,
key_hash_str,
// tlock_cipher,
// round,
));
}
5 changes: 5 additions & 0 deletions sp1_prover/zkpoex-script/Cargo.toml
Original file line number Diff line number Diff line change
@@ -19,6 +19,11 @@ clap = { version = "4.0", features = ["derive", "env"] }
tracing = "0.1.40"
alloy-sol-types = "0.7.2"
rand = "*"
drand_core = "*"
humantime = "*"
bincode = "*"
tlock = "*"


[build-dependencies]
sp1-helper = { git = "https://github.com/succinctlabs/sp1.git", rev = "277f1b4cfee5129bd40d74748f3d241cdfa56e63" }
116 changes: 115 additions & 1 deletion sp1_prover/zkpoex-script/src/bin/prove.rs
Original file line number Diff line number Diff line change
@@ -6,10 +6,15 @@
//! RUST_LOG=info cargo run --package fibonacci-script --bin prove --release
//! ```
use std::path::PathBuf;
use std::{
ops::Add,
path::PathBuf,
time::{Duration, SystemTime, UNIX_EPOCH},
};

use alloy_sol_types::{sol, SolType};
use clap::Parser;
use drand_core::chain::ChainInfo;
use rand::Rng;
use serde::{Deserialize, Serialize};
use sp1_sdk::{HashableKey, ProverClient, SP1Stdin};
@@ -43,6 +48,14 @@ struct ProveArgs {
"#
)]
blockchain_settings: String,

#[clap(
short,
long,
help = "disclose after (y/w/d/h/m/s/ms)",
default_value = "90d"
)]
pub duration: Option<humantime::Duration>,
}

/// A fixture that can be used to test the verification of SP1 zkVM proofs inside Solidity.
@@ -51,6 +64,12 @@ struct ProveArgs {
struct SP1ZkPoExProofFixture {
key: [u8; 32],
nonce: [u8; 12],
round: u64,
before: String,
after: String,
hash_private_inputs: String,
chacha_cipher: Vec<u8>,
tlock_cipher: Vec<u8>,
calldata: String,
blockchain_settings: String,
vkey: String,
@@ -68,6 +87,33 @@ fn main() {
let key: [u8; 32] = rng.gen();
let nonce: [u8; 12] = rng.gen();

let client: drand_core::HttpClient =
"https://api.drand.sh/dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493"
.try_into()
.unwrap();
let info = client.chain_info().unwrap();

let drand_master_key = info.public_key();



let round = {
let d = args
.duration
.expect("duration is expected if round_number isn't specified")
.into();
round_after(&info, d)
};

let mut tlock_cipher = vec![];
tlock::encrypt(
&mut tlock_cipher,
&key[..],
&drand_master_key,
round,
)
.unwrap();

// Setup the prover client.
let client = ProverClient::new();

@@ -81,6 +127,8 @@ fn main() {
nonce,
args.calldata.clone(),
args.blockchain_settings.clone(),
drand_master_key,
round,
));

// Generate the proof.
@@ -94,10 +142,39 @@ fn main() {
)
.expect("failed to write fixture");

let (before, after, hash_private_inputs, chacha_cipher, _): (
String,
String,
String,
Vec<u8>,
String,
// Vec<u8>,
// u64,
) = bincode::deserialize(proof.public_values.as_slice())
.expect("failed to deserialize public values");

std::fs::write(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("./zkpoex_chacha"),
&chacha_cipher,
)
.expect("failed to write fixture");

std::fs::write(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("./zkpoex_tlock"),
&tlock_cipher,
)
.expect("failed to write fixture");

// Create the testing fixture so we can test things end-ot-end.
let fixture = SP1ZkPoExProofFixture {
before,
after,
hash_private_inputs,
key,
nonce,
round,
chacha_cipher,
tlock_cipher,
calldata: args.calldata,
blockchain_settings: args.blockchain_settings,
vkey: vk.bytes32().to_string(),
@@ -126,3 +203,40 @@ fn main() {
)
.expect("failed to write fixture");
}

pub fn round_at(chain_info: &ChainInfo, t: SystemTime) -> u64 {
let since_epoch = t.duration_since(UNIX_EPOCH).unwrap();
let t_unix = since_epoch.as_secs();
current_round(
t_unix,
Duration::from_secs(chain_info.period()),
chain_info.genesis_time(),
)
}

pub fn round_after(chain_info: &ChainInfo, d: Duration) -> u64 {
let t = SystemTime::now().add(d);
round_at(chain_info, t)
}

pub fn current_round(now: u64, period: Duration, genesis: u64) -> u64 {
let (next_round, _) = next_round(now, period, genesis);

if next_round <= 1 {
next_round
} else {
next_round - 1
}
}

pub fn next_round(now: u64, period: Duration, genesis: u64) -> (u64, u64) {
if now < genesis {
return (1, genesis);
}

let from_genesis = now - genesis;
let next_round = (((from_genesis as f64) / (period.as_secs() as f64)).floor() + 1f64) as u64;
let next_time = genesis + next_round * period.as_secs();

(next_round, next_time)
}
Binary file added sp1_prover/zkpoex-script/zkpoex_chacha
Binary file not shown.
2 changes: 1 addition & 1 deletion sp1_prover/zkpoex-script/zkpoex_enc_key
Original file line number Diff line number Diff line change
@@ -1 +1 @@
HÉf¯þ4†Ò­<`§ž-`õÅCS-H•òÀÂ7Ò2yë
ú0H‡i·È7ïæ (çhb”D{-]‰ušvöÛ
2 changes: 2 additions & 0 deletions sp1_prover/zkpoex-script/zkpoex_tlock
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
‚–W<×.³±1ôL¡MIn‚^ì|aɧBMmçÍƂœò¾¨²Uá¹ùŸ?ÚÀv
«;gÝ|Å‘î$GjÔÐ+ÄxÌǖ$-T2ÙµÔ3§ï¦À}ëÒjò«ýH(Íi:á÷îÜË&êVünó]÷·à|žìp†Û˜û—Ó_oŞ
Binary file modified sp1_prover/zkpoex.bincode
Binary file not shown.

0 comments on commit ab54581

Please sign in to comment.