├── .github └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── .nvmrc ├── LICENSE ├── Makefile ├── README.md ├── demos ├── .eslintrc.cjs ├── README.md ├── common │ ├── ConsoleLog.tsx │ ├── Contracts.tsx │ ├── Suave.tsx │ └── assets │ │ └── flashbots.png ├── confstore │ ├── README.md │ ├── index.html │ ├── public │ │ └── favicon.png │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── index.css │ │ ├── main.tsx │ │ └── vite-env.d.ts │ └── vite.config.ts ├── package-lock.json ├── package.json ├── timelock │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── public │ │ └── favicon.png │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── index.css │ │ ├── main.tsx │ │ └── vite-env.d.ts │ └── vite.config.ts ├── tsconfig.json └── tsconfig.node.json ├── deployment.json ├── ffi ├── ffi-fetchquote-dcap.py ├── local_random.sh └── sha512.sh ├── foundry.toml ├── lib └── revm-services │ └── ExternalServices.sol ├── package-lock.json ├── package.json ├── scripts ├── bootstrap_kettle.ts ├── common.ts ├── configure_tcbinfo.ts ├── deploy.ts ├── deploy_examples.ts ├── onboard_kettle.ts ├── test_examples.ts └── verify_contracts.ts ├── src ├── Andromeda.sol ├── AndromedaForge.sol ├── AndromedaRemote.sol ├── BIP32.sol ├── DcapVerifier.sol ├── IAndromeda.sol ├── KeyHelper.sol ├── KeyManager.sol ├── crypto │ ├── EllipticCurve.sol │ ├── bn256g1.sol │ ├── encryption.sol │ └── secp256k1.sol ├── examples │ ├── Auction.sol │ ├── RedisConfidentialStore.sol │ ├── SpeedrunAuction.sol │ ├── Timelock.sol │ └── httpcall.sol ├── hash │ └── IHash.sol ├── scripts │ └── TimelockSetup.sol └── utils │ ├── Utils.sol │ └── fmspc.sol ├── test ├── AndromedaForge.t.sol ├── BIP32.t.sol ├── Crypto.t.sol ├── DcapVerifier.t.sol ├── KeyManager.t.sol ├── examples │ ├── Auction.t.sol │ ├── SpeedrunAuction.t.sol │ └── Timelock.t.sol └── fixtures │ ├── quotingenclave-identity.json │ ├── tcbInfo.json │ ├── tcbInfo2.json │ └── testquote.hex └── tsconfig.json /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | 16 | # Editors 17 | *~ 18 | #*# 19 | 20 | # Vendor 21 | node_modules/ 22 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/solidity-stringutils"] 5 | path = lib/solidity-stringutils 6 | url = https://github.com/Arachnid/solidity-stringutils 7 | [submodule "lib/solady"] 8 | path = lib/solady 9 | url = https://github.com/vectorized/solady 10 | [submodule "lib/ens-contracts"] 11 | path = lib/ens-contracts 12 | url = https://github.com/ensdomains/ens-contracts 13 | [submodule "lib/automata-dcap-v3-attestation"] 14 | path = lib/automata-dcap-v3-attestation 15 | url = https://github.com/amiller/automata-dcap-v3-attestation 16 | [submodule "lib/sgx-tcbInfos"] 17 | path = lib/sgx-tcbInfos 18 | url = https://github.com/Ruteri/sgx-tcbInfos 19 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.16.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Flashbots 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build-debug 2 | build-debug: 3 | forge build --revert-strings debug 4 | 5 | .PHONY: build 6 | build: 7 | forge build 8 | 9 | .PHONY: test 10 | test: 11 | forge test --ffi 12 | 13 | .PHONY: format 14 | format: 15 | forge fmt . 16 | 17 | .PHONY: deploy 18 | deploy: build 19 | npx ts-node -T scripts/deploy.ts 20 | 21 | .PHONY: verify-contracts 22 | verify-contracts: build deploy 23 | npx ts-node -T scripts/verify_contracts.ts 24 | 25 | .PHONY: bootstrap 26 | bootstrap: build 27 | npx ts-node -T scripts/bootstrap_kettle.ts 28 | 29 | .PHONY: onboard 30 | onboard: build 31 | npx ts-node -T scripts/onboard_kettle.ts 32 | 33 | .PHONY: test-examples 34 | test-examples: build 35 | npx ts-node -T scripts/test_examples.ts 36 | 37 | .PHONY: deploy-examples 38 | deploy-examples: build deploy bootstrap 39 | npx ts-node -T scripts/deploy_examples.ts 40 | 41 | .PHONY: configure-all-tcbinfos 42 | configure-all-tcbinfos: 43 | # Non-PHONY! If needed, clear it manually 44 | cd lib/sgx-tcbInfos && make 45 | export TCB_INFO_FILES="$(shell find ./lib/sgx-tcbInfos/assets -name "tcbinfo.json" -printf "%p ")"; \ 46 | npx ts-node -T scripts/configure_tcbinfo.ts 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > This repository is a work in progress, and for now only functions as a showcase. This code *is not intended to secure any valuable information*. 3 | 4 | # Andromeda Sirrah Contracts 5 | 6 | This repository contains the smart contracts and development environment for SUAVE's intermediate programming layer, where Solidity contracts directly control SGX trusted hardware features like attestation and sealing. This also contains the code and examples that go along with the post "Sirrah: Speedrunning a TEE Coprocessor." 7 | 8 | ## Andromeda Precompiles 9 | 10 | The Andromeda precompiles are a minimal way to add Trusted Hardware Enclaves to the Solidity environment. 11 | The interface is defined in [./src/IAndromeda.sol](./src/IAndromeda.sol)). It basically adds four new things: 12 | 13 | - *Sampling random bytes*. 14 | 15 | ```solidity 16 | function localRandom() view external returns(bytes32); 17 | ``` 18 | 19 | This returns new random bytes each time it is called, sampled using the `RDRAND` x86 instruction. 20 | 21 | - *Process storage* 22 | 23 | ```solidity 24 | function volatileSet(bytes32 tag, bytes32 value) external; 25 | function volatileGet(bytes32 tag) external returns(bytes32 value); 26 | ``` 27 | 28 | This provides a key value storage. Each caller has its own isolated storage. If a message call reverts, it will *not* undo the side effects of `volatileSet`. All this storage is cleared each time the process restarts. 29 | 30 | - *Persistent storage* 31 | 32 | ```solidity 33 | function sealingKey() view external; 34 | ``` 35 | This provides a persistent key. For each caller, each MRENCLAVE on each CPU gets its own one of these. This is used so the enclave can store an encrypted file and retrieve it later if the process restarts. 36 | 37 | - *Remote attestation* 38 | 39 | ```solidity 40 | function attestSgx(bytes32 appData) external returns (bytes memory att); 41 | function verifySgx(address caller, bytes32 appData, bytes memory att) external view returns (bool);` 42 | ``` 43 | 44 | This produces evidence that the `appData` was requested by the `caller`. The verification routine is pure Solidity, and does not require any special precompiles. 45 | 46 | - *External services* 47 | 48 | Currently there are two external services defined: redis (persistent) key-value store and redis pubsub: 49 | ```solidity 50 | interface Redis { 51 | function set(string memory key, bytes memory value) external; 52 | function get(string memory key) external returns (bytes memory); 53 | } 54 | interface RedisPubsub { 55 | function publish(string memory topic, bytes memory msg) external; 56 | function get_message(string memory topic) external returns (bytes memory); 57 | function subscribe(string memory topic) external; 58 | function unsubscribe(string memory topic) external; 59 | } 60 | ``` 61 | 62 | For interacting with external services see [lib/revm-services/Interfaces.sol](lib/revm-services/Interfaces.sol) and the [RedisConfidentialStore example contract](src/examples/RedisConfidentialStore.sol). 63 | 64 | 65 | ## Implementations 66 | We provide three implementations of the Andromeda interface: 67 | 68 | 1. A forge mockup environment. This is sufficient for logical testing of smart contracts. [./src/AndromedaForge.sol](./src/AndromedaForge.sol) 69 | 2. A remote environment using actual remote attestations, computed via a remote service (dummy attester service) [./src/AndromedaRemote.sol](./src/AndromedaRemote.sol) 70 | 3. Invoke the actual the precompiles recognized by the Andromeda EVM in separate repository, [suave-andromeda-revm](https://github.com/flashbots/suave-andromeda-revm/). 71 | 72 | To reiterate, the forge development environment here (implementations 1 and 2) does NOT require any use of SGX, so you can develop on any machine (even on TEE-level components like a Key Manager and TCB recovery handling). 73 | 74 | ## Speedrunning a Second Price auction 75 | 76 | Here's the motivating scenario to go along with the blogpost: Looking at [./src/examples/SpeedrunAuction.sol:LeakyAuction](./src/examples/SpeedrunAuction.sol), we can see a second price auction in plain ordinary Solidity. But, due to a lack of privacy, this is vulnerable to griefing through MEV. 77 | 78 | The point of this demo is to solve this problem using the Andromeda precompiles. See: [./src/examples/Auction.sol:SealedAuction](./src/examples/Auction.sol) 79 | 80 | ## Key Manager 81 | 82 | The "speedrun" was a little unsatisfying because you have to bootstrap a new key each time you carry out an auction. Instead, we want to have a singletone Key Manager that encapsulates a single bootstrapping ceremony, and thereafter *many applications* as well as *many separate kettles* can provide confidential coprocessing service. The proposed key manager has the following features: 83 | 84 | - Verification of a raw SGX attestation (expensive in Solidity gas) only needs to occur once per Kettle. After initial registration, ordinary digital signatures can be used instead. Much cheaper. 85 | - Multiple Kettles can join. Newly registered Kettles receive a copy of the key from existing Kettles that already have it 86 | - A single instance of the Key Manager contract can be used by other contracts. 87 | 88 | This is still a simplified strawman example, as it does not support upgrading the enclave, revoking keys, etc. This is meant as a starting point, and those ideas can be explored in Solidity. 89 | 90 | ## Timelock encryption demo 91 | 92 | As one demo, we include a sample application in the form of a timelock decryption service. 93 | The code is at [./src/examples/Timelock.sol:Timelock](./src/examples/Timelock.sol) 94 | and the smart contract is found on the Rigil test network [https://explorer.rigil.suave.flashbots.net/address/0x6858162E579DFC66a623AE1bA357d67BF026dDD6](https://explorer.rigil.suave.flashbots.net/address/0x6858162E579DFC66a623AE1bA357d67BF026dDD6). 95 | 96 | The application is very simple: messages are encrypted to the public key of the contract. A TEE kettle can only decrypt them only after the light client reports that a deadline has passed on the blockchain. 97 | 98 | The frontend is hosted at https://timelock.sirrah.suave.flashbots.net/ 99 | You'll need to point your web3 browser extension like Metamask to [point to a Rigil endpoint](https://github.com/flashbots/suave-specs/tree/main/specs/rigil). If you don't have Rigil testnet coins you can get some at [faucet.rigil.suave.flashbots.net](https://faucet.rigil.suave.flashbots.net). 100 | 101 | ## Confidential bundle store demo 102 | 103 | In another demo, [RedisConfidentialStore](./src/examples/RedisConfidentialStore.sol), we show how we can use redis's key-value store and pubsub to implement a replicated bundle database which preserves integrity and confidentiality. 104 | This specific demo aims to bring us closer to feature parity with Rigil by implementing the [confidential data store](https://suave.flashbots.net/technical/specs/rigil/confidential-data-store). 105 | 106 | This demo is also very simple, and allows inserting bundles, indexing them (by the block they target), and fetching them. The demo is built in a way that also allows replicating bundles across multiple kettles (as long as they are onboarded to the same key manager - not shown on the demo). 107 | The RedisConfidentialStore contract is deployed to the Rigil testnet, and can be found at [https://explorer.rigil.suave.flashbots.net/address/0xF1b9942f1DBf1dD9538FC2ee8e2FC533b7070366](https://explorer.rigil.suave.flashbots.net/address/0xF1b9942f1DBf1dD9538FC2ee8e2FC533b7070366). 108 | 109 | ## Usage 110 | 111 | Relies on [Foundry](https://getfoundry.sh/) for contrats, [Python 3](https://www.python.org/downloads/) for various utilities, and [npm](https://nodejs.org/en) for automation and demo. 112 | 113 | For ease of use we provide the following `make` targets: 114 | * `make build` to build contracts 115 | * `make format` to format contracts 116 | * `make test` to test contracts 117 | * `make deploy` to deploy contracts 118 | * `make configure-all-tcbinfos` to configure `Andromeda` contracts with TCBInfo from Intel 119 | * `make bootstrap` to bootstrap a kettle for `KeyManager` 120 | * `make onboard` to onboard a kettle to `KeyManager` from one already bootstrapped 121 | * `make deploy-examples` to deploy `SealedAuction` and `Timelock` for use in the demo webapp 122 | * `make test-examples` to automatically deploy and test `SealedAuction` and `Timelock` on chain 123 | 124 | Deployed contracts are kept track of in the [deployment.json](deployment.json) file. If you want to re-deploy a contract, simply remove it from the `ADDR_OVERRIDES` section. The various deployment scripts write to the file on successful deployments. 125 | 126 | ## Rigil 127 | 128 | If you want to build and deploy only some of the contracts, here are ones predeployed to Rigil. 129 | 130 | 1. Contracts 131 | 132 | In [deployment.json] change the `ADDR_OVERRIDES` to include: 133 | 134 | ``` 135 | "ADDR_OVERRIDES": { 136 | "out/SigVerifyLib.sol/SigVerifyLib.json": "0xed16804dB4D00A61e85569362ac10ef66126B13e", 137 | "out/Andromeda.sol/Andromeda.json": "0x76832d4d9823eCD154598Ce2969D5C4e794E84c4" 138 | } 139 | ``` 140 | 141 | 2. Demo apps 142 | 143 | If you want to use predeployed `Timelock` demo, one is available on Rigil. Include the following in the `ADDR_OVERRIDES`: 144 | 145 | ``` 146 | "ADDR_OVERRIDES": { 147 | "out/Timelock.sol/Timelock.json": "0x6858162E579DFC66a623AE1bA357d67BF026dDD6", 148 | "out/RedisConfidentialStore.sol/BundleConfidentialStore.json": "0xF1b9942f1DBf1dD9538FC2ee8e2FC533b7070366" 149 | } 150 | ``` 151 | 152 | > [!WARNING] 153 | > The addresses will change, so don't depend on them too much. This is intended for quick prototyping rather than something that is highly available. 154 | 155 | 156 | ### Rigil kettle 157 | 158 | If you don't want to run a kettle yourself, you can always connect to the development TEE kettle at https://kettle.sirrah.suave.flashbots.net. 159 | 160 | ## License 161 | 162 | The code in this project is free software under the [MIT license](LICENSE). 163 | -------------------------------------------------------------------------------- /demos/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /demos/README.md: -------------------------------------------------------------------------------- 1 | # Andromeda Sirrah Demos 2 | 3 | This directory contains a demo webapp for the [Timelock contract](../src/examples/Timelock.sol). 4 | You can see the live demo at `http://timelock.sirrah.suave.flashbots.net:5173`. 5 | 6 | ## Building and running the demos 7 | 8 | 1. Make sure you have `bun` installed (`npm i --global bun`) 9 | 2. Run `npm install` in the parent directory 10 | 3. Run `bun install` instead of the usual `npm install` in the demos directory 11 | 4. Build the demo with `bun vite build ` (`bun run build`) 12 | 5. Run the demo with `bun vite ` (`bun run timelock` or `bun run confstore`) 13 | 14 | Running the Timelock demo webapp requires that you have the Timelock contract address configured in the [../deployment.json](../deployment.json) file like so: 15 | ``` 16 | "ARTIFACTS": { 17 | "out/Timelock.sol/Timelock.json": { 18 | "address": "0x6858162E579DFC66a623AE1bA357d67BF026dDD6", 19 | "constructor_args": [] 20 | }, 21 | "out/RedisConfidentialStore.sol/BundleConfidentialStore.json": { 22 | "address": "0xF1b9942f1DBf1dD9538FC2ee8e2FC533b7070366", 23 | "constructor_args": [] 24 | } 25 | } 26 | ``` 27 | 28 | If you want to build and deploy the demo contracts from scratch, see the parent [../README.md](../README.md). 29 | 30 | ## Signing chain transactions 31 | 32 | The demo will sign chain transactions with either the raw private key (if one is provided through [../deployment.json](../deployment.json)), or with MetaMask. 33 | > [!WARNING] 34 | > **DO NOT PUT YOUR PRIVATE KEY IN THE DEPLOYMENT FILE IF YOU INTEND TO EXPOSE THE WEBAPP.** Since this is a React app, all of the contents of imported files could be accessible to whoever connects to your application. If you intend to expose the demo, rely on MetaMask instead. 35 | -------------------------------------------------------------------------------- /demos/common/ConsoleLog.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | import { decodeFunctionResult, decodeFunctionData } from "viem"; 4 | 5 | export function ConsoleLog() { 6 | const [consoleLog, setConsoleLog] = useState(""); 7 | 8 | function updateConsoleLog(newLog: string) { 9 | const currentTime = new Date().toLocaleString("en-US", { 10 | hour: "numeric", 11 | minute: "numeric", 12 | second: "numeric", 13 | hour12: false, 14 | }); 15 | setConsoleLog( 16 | (consoleLog) => 17 | log_font_color("lightgrey", "[" + currentTime + "]: ") + 18 | newLog + 19 | "
" + 20 | consoleLog, 21 | ); 22 | } 23 | 24 | useEffect(() => { 25 | return () => { 26 | setConsoleLog(""); 27 | }; 28 | }, []); 29 | 30 | return [consoleLog, updateConsoleLog]; 31 | } 32 | 33 | export function log_font_color(color: string, str: string) { 34 | return '' + str + ""; 35 | } 36 | 37 | export function format_explorer_link(target: { 38 | address?: string; 39 | tx?: string; 40 | }) { 41 | const link = 42 | target.address !== undefined 43 | ? "https://explorer.rigil.suave.flashbots.net/address/" + target.address 44 | : "https://explorer.rigil.suave.flashbots.net/tx/" + target.tx; 45 | const value = target.address !== undefined ? target.address : target.tx; 46 | return ' " + callResLog, 199 | ); 200 | 201 | const offchainDecryptResult = decodeFunctionResult({ 202 | abi: [decryptAbi], 203 | data: executionResult.Success.output.Call, 204 | }) as `0x${string}`; 205 | console.log( 206 | "Successfully decrypted message: '" + 207 | fromHex(offchainDecryptResult, "string").trim() + 208 | "'", 209 | ); 210 | 211 | return fromHex(offchainDecryptResult, "string").trim(); 212 | } 213 | 214 | return ( 215 | <> 216 |
217 | 218 | Flashbots logo 219 | 220 |
221 |
222 | {isTimelockInitialized && !messagePromptHidden && ( 223 |
224 | setMessage(e.target.value)} 228 | /> 229 |
230 | )} 231 | {!messagePromptHidden && ( 232 | 256 | )} 257 | 258 | {messagePromptHidden && deadline > 0n && ( 259 |
260 | Waiting {deadline?.toString()} seconds for timelock to expire... 261 |
262 | )} 263 | {messagePromptHidden && deadline === 0n && ( 264 |
265 | {decryptedMessage === undefined 266 | ? "Decrypting message..." 267 | : "Decrypted message: " + decryptedMessage + ""} 268 |
269 | )} 270 | 271 |
272 |
273 |
277 |
278 |
279 | No Rigil money? Get some at{" "} 280 | 281 | the faucet 282 | 283 |
284 |
285 | 286 | ); 287 | } 288 | 289 | function assert(condition: unknown, msg?: string): asserts condition { 290 | if (condition === false) throw new Error(msg); 291 | } 292 | 293 | export default App; 294 | -------------------------------------------------------------------------------- /demos/timelock/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /demos/timelock/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | import "./index.css"; 5 | import { MetaMaskProvider } from "@metamask/sdk-react"; 6 | 7 | ReactDOM.createRoot(document.getElementById("root")!).render( 8 | <> 9 | 19 | 20 | , 21 | ); 22 | -------------------------------------------------------------------------------- /demos/timelock/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /demos/timelock/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | define: { 7 | global: {}, 8 | }, 9 | resolve: { 10 | alias: { 11 | "node-fetch": "isomorphic-fetch", 12 | }, 13 | }, 14 | plugins: [react()], 15 | }); 16 | -------------------------------------------------------------------------------- /demos/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /demos/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /deployment.json: -------------------------------------------------------------------------------- 1 | { 2 | "RPC_URL": "https://rpc.rigil.suave.flashbots.net", 3 | "KETTLE_RPC": "https://kettle.sirrah.suave.flashbots.net", 4 | "PRIVATE_KEY": "", 5 | "TCB_INFO_FILE": "lib/automata-dcap-v3-attestation/contracts/assets/tcbInfo2.json", 6 | "QE_IDENTITY_FILE": "lib/automata-dcap-v3-attestation/contracts/assets/identity.json", 7 | "SIGVERIFY_LIB_ARTIFACT": "out/SigVerifyLib.sol/SigVerifyLib.json", 8 | "ANDROMEDA_ARTIFACT": "out/Andromeda.sol/Andromeda.json", 9 | "HTTPCALL_ARTIFACT": "out/httpcall.sol/HTTP.json", 10 | "TRUSTED_MRENCLAVES": [ 11 | "0x2c2facadbb86dfc9989f28b09ca142e3447ad682e00384d2f4611cf690dbab61", 12 | "0xbac927cb42088a9bf87719e157f891cc23b422f5e1ddaa21988c43c65449093b" 13 | ], 14 | "TRUSTED_MRSIGNERS": [ 15 | "0xf0365ce7081fda379914c703fe08648db1cce3747e8c10f74ff742926399f15a", 16 | "0xd1e46f2f04cb4d5c774bf97312c7cda09dd0115e08c81f41e1b01b9a50e09d6d" 17 | ], 18 | "KEY_MANAGER_SN_ARTIFACT": "out/KeyManager.sol/KeyManager_v0.json", 19 | "SEALED_AUCTION_ARTIFACT": "out/Auction.sol/SealedAuction.json", 20 | "TIMELOCK_ARTIFACT": "out/Timelock.sol/Timelock.json", 21 | "BUNDLE_STORE_ARTIFACT": "out/RedisConfidentialStore.sol/BundleConfidentialStore.json", 22 | "ARTIFACTS": {} 23 | } 24 | -------------------------------------------------------------------------------- /ffi/ffi-fetchquote-dcap.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | from urllib.request import urlopen, Request 4 | #import eth_abi 5 | import base64 6 | from binascii import hexlify 7 | 8 | if __name__ == '__main__': 9 | if len(sys.argv) < 2: 10 | print('Usage: python ffi-fetchquote-dcap.py 00....00 (a 64-byte hex)') 11 | sys.exit(1) 12 | msg = sys.argv[1] 13 | assert len(bytes.fromhex(msg)) == 64 14 | url = f"https://dcap-dummy.sirrah.suave.flashbots.net/dcap/{msg}" 15 | req = Request(url, headers={'User-Agent' : "Magic Browser"}) 16 | obj = urlopen(req).read() 17 | sys.stdout.buffer.write(obj+b'\n') 18 | #print("abidata:") 19 | #print(hexlify(abidata)) 20 | #print("sig:") 21 | #print(hexlify(sig)) 22 | -------------------------------------------------------------------------------- /ffi/local_random.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | dd if=/dev/urandom bs=1 count=32 2> /dev/null | xxd -p -c64 3 | -------------------------------------------------------------------------------- /ffi/sha512.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if argument is provided 4 | if [ -z "$1" ] 5 | then 6 | echo "No argument supplied. Please provide a string to hash." 7 | exit 1 8 | fi 9 | 10 | # Compute and print the SHA512 hash 11 | echo -n "$1" | sha512sum | awk '{ print $1 }' 12 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | fs_permissions = [{ access = "read", path = "lib/automata-dcap-v3-attestation/contracts/assets"}, 7 | { access = "read", path = "test/fixtures"}] 8 | 9 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 10 | -------------------------------------------------------------------------------- /lib/revm-services/ExternalServices.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.19; 2 | 3 | interface RedisPubsub { 4 | function publish(string memory topic, bytes memory msg) external; 5 | function get_message(string memory topic) external returns (bytes memory); 6 | 7 | function subscribe(string memory topic) external; 8 | function unsubscribe(string memory topic) external; 9 | } 10 | 11 | contract WithRedisPubsub { 12 | ExternalServiceImpl private impl; 13 | constructor() { 14 | bytes memory config; 15 | impl = new ExternalServiceImpl("pubsub", config); 16 | } 17 | 18 | function pubsub() internal view returns (RedisPubsub) { 19 | return RedisPubsub(address(impl)); 20 | } 21 | } 22 | 23 | interface Redis { 24 | function set(string memory key, bytes memory value) external; 25 | function get(string memory key) external returns (bytes memory); 26 | } 27 | 28 | contract WithRedis { 29 | ExternalServiceImpl private impl; 30 | constructor() { 31 | bytes memory config; 32 | impl = new ExternalServiceImpl("redis", config); 33 | } 34 | 35 | function redis() internal view returns (Redis) { 36 | return Redis(address(impl)); 37 | } 38 | } 39 | 40 | address constant SM_ADDR = address(0x3507); // Can be a library, a precompile, or a contract 41 | 42 | interface SM { 43 | function getService(string memory service_name, bytes memory config) external returns (bytes32 handle, bytes memory err); 44 | function callService(bytes32 handle, bytes memory cdata) external returns (bytes memory); 45 | 46 | /* context is supplemented by the precompile! */ 47 | struct Context { 48 | uint256 blockNumber; 49 | address origin; 50 | address caller; 51 | } 52 | function callServiceWithContext(Context memory env, bytes32 handle, bytes memory cdata) external returns (bytes memory); 53 | } 54 | 55 | contract ExternalServiceImpl { 56 | string private service; 57 | bytes private config; 58 | constructor(string memory _service, bytes memory _config) { 59 | service = _service; 60 | config = _config; 61 | _volatile_handle = bytes32(0x0); 62 | } 63 | 64 | bytes32 private _volatile_handle; /* Only kept for a single mevm context */ 65 | fallback(bytes calldata cdata) external returns (bytes memory) { 66 | if (_volatile_handle == bytes32(0x0)) { 67 | (bool s_ok, bytes memory s_data) = SM_ADDR.staticcall(abi.encodeWithSelector(SM.getService.selector, service, config)); 68 | require(s_ok, string(abi.encodePacked("getService for ", service, " failed: ", string(s_data)))); 69 | (bytes32 handle, bytes memory err) = abi.decode(s_data, (bytes32, bytes)); 70 | require(err.length == 0, string(abi.encodePacked("could not initialize ", service, ": ", string(err)))); 71 | _volatile_handle = handle; 72 | } 73 | 74 | (bool c_ok, bytes memory c_data) = SM_ADDR.staticcall(abi.encodeWithSelector(SM.callService.selector, _volatile_handle, cdata)); 75 | require(c_ok, string(abi.encodePacked(service, " call failed: ", string(c_data)))); 76 | return c_data; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "andromeda-sirrah-contracts", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "ethers": "^6.9.1", 9 | "isomorphic-fetch": "^3.0.0", 10 | "node-fetch": "^2.7.0", 11 | "ts-node": "^10.9.2" 12 | } 13 | }, 14 | "node_modules/@adraffy/ens-normalize": { 15 | "version": "1.10.1", 16 | "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", 17 | "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" 18 | }, 19 | "node_modules/@cspotcode/source-map-support": { 20 | "version": "0.8.1", 21 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 22 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 23 | "dependencies": { 24 | "@jridgewell/trace-mapping": "0.3.9" 25 | }, 26 | "engines": { 27 | "node": ">=12" 28 | } 29 | }, 30 | "node_modules/@jridgewell/resolve-uri": { 31 | "version": "3.1.2", 32 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 33 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 34 | "engines": { 35 | "node": ">=6.0.0" 36 | } 37 | }, 38 | "node_modules/@jridgewell/sourcemap-codec": { 39 | "version": "1.4.15", 40 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 41 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" 42 | }, 43 | "node_modules/@jridgewell/trace-mapping": { 44 | "version": "0.3.9", 45 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 46 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 47 | "dependencies": { 48 | "@jridgewell/resolve-uri": "^3.0.3", 49 | "@jridgewell/sourcemap-codec": "^1.4.10" 50 | } 51 | }, 52 | "node_modules/@noble/curves": { 53 | "version": "1.2.0", 54 | "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", 55 | "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", 56 | "dependencies": { 57 | "@noble/hashes": "1.3.2" 58 | }, 59 | "funding": { 60 | "url": "https://paulmillr.com/funding/" 61 | } 62 | }, 63 | "node_modules/@noble/hashes": { 64 | "version": "1.3.2", 65 | "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", 66 | "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", 67 | "engines": { 68 | "node": ">= 16" 69 | }, 70 | "funding": { 71 | "url": "https://paulmillr.com/funding/" 72 | } 73 | }, 74 | "node_modules/@tsconfig/node10": { 75 | "version": "1.0.9", 76 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 77 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" 78 | }, 79 | "node_modules/@tsconfig/node12": { 80 | "version": "1.0.11", 81 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 82 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" 83 | }, 84 | "node_modules/@tsconfig/node14": { 85 | "version": "1.0.3", 86 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 87 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" 88 | }, 89 | "node_modules/@tsconfig/node16": { 90 | "version": "1.0.4", 91 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 92 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" 93 | }, 94 | "node_modules/@types/node": { 95 | "version": "20.11.18", 96 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.18.tgz", 97 | "integrity": "sha512-ABT5VWnnYneSBcNWYSCuR05M826RoMyMSGiFivXGx6ZUIsXb9vn4643IEwkg2zbEOSgAiSogtapN2fgc4mAPlw==", 98 | "peer": true, 99 | "dependencies": { 100 | "undici-types": "~5.26.4" 101 | } 102 | }, 103 | "node_modules/acorn": { 104 | "version": "8.11.3", 105 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", 106 | "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", 107 | "bin": { 108 | "acorn": "bin/acorn" 109 | }, 110 | "engines": { 111 | "node": ">=0.4.0" 112 | } 113 | }, 114 | "node_modules/acorn-walk": { 115 | "version": "8.3.2", 116 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", 117 | "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", 118 | "engines": { 119 | "node": ">=0.4.0" 120 | } 121 | }, 122 | "node_modules/aes-js": { 123 | "version": "4.0.0-beta.5", 124 | "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", 125 | "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" 126 | }, 127 | "node_modules/arg": { 128 | "version": "4.1.3", 129 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 130 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" 131 | }, 132 | "node_modules/create-require": { 133 | "version": "1.1.1", 134 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 135 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" 136 | }, 137 | "node_modules/diff": { 138 | "version": "4.0.2", 139 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 140 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 141 | "engines": { 142 | "node": ">=0.3.1" 143 | } 144 | }, 145 | "node_modules/ethers": { 146 | "version": "6.11.1", 147 | "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.11.1.tgz", 148 | "integrity": "sha512-mxTAE6wqJQAbp5QAe/+o+rXOID7Nw91OZXvgpjDa1r4fAbq2Nu314oEZSbjoRLacuCzs7kUC3clEvkCQowffGg==", 149 | "funding": [ 150 | { 151 | "type": "individual", 152 | "url": "https://github.com/sponsors/ethers-io/" 153 | }, 154 | { 155 | "type": "individual", 156 | "url": "https://www.buymeacoffee.com/ricmoo" 157 | } 158 | ], 159 | "dependencies": { 160 | "@adraffy/ens-normalize": "1.10.1", 161 | "@noble/curves": "1.2.0", 162 | "@noble/hashes": "1.3.2", 163 | "@types/node": "18.15.13", 164 | "aes-js": "4.0.0-beta.5", 165 | "tslib": "2.4.0", 166 | "ws": "8.5.0" 167 | }, 168 | "engines": { 169 | "node": ">=14.0.0" 170 | } 171 | }, 172 | "node_modules/ethers/node_modules/@types/node": { 173 | "version": "18.15.13", 174 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", 175 | "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" 176 | }, 177 | "node_modules/isomorphic-fetch": { 178 | "version": "3.0.0", 179 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", 180 | "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", 181 | "dependencies": { 182 | "node-fetch": "^2.6.1", 183 | "whatwg-fetch": "^3.4.1" 184 | } 185 | }, 186 | "node_modules/make-error": { 187 | "version": "1.3.6", 188 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 189 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" 190 | }, 191 | "node_modules/node-fetch": { 192 | "version": "2.7.0", 193 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 194 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 195 | "dependencies": { 196 | "whatwg-url": "^5.0.0" 197 | }, 198 | "engines": { 199 | "node": "4.x || >=6.0.0" 200 | }, 201 | "peerDependencies": { 202 | "encoding": "^0.1.0" 203 | }, 204 | "peerDependenciesMeta": { 205 | "encoding": { 206 | "optional": true 207 | } 208 | } 209 | }, 210 | "node_modules/tr46": { 211 | "version": "0.0.3", 212 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 213 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 214 | }, 215 | "node_modules/ts-node": { 216 | "version": "10.9.2", 217 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", 218 | "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", 219 | "dependencies": { 220 | "@cspotcode/source-map-support": "^0.8.0", 221 | "@tsconfig/node10": "^1.0.7", 222 | "@tsconfig/node12": "^1.0.7", 223 | "@tsconfig/node14": "^1.0.0", 224 | "@tsconfig/node16": "^1.0.2", 225 | "acorn": "^8.4.1", 226 | "acorn-walk": "^8.1.1", 227 | "arg": "^4.1.0", 228 | "create-require": "^1.1.0", 229 | "diff": "^4.0.1", 230 | "make-error": "^1.1.1", 231 | "v8-compile-cache-lib": "^3.0.1", 232 | "yn": "3.1.1" 233 | }, 234 | "bin": { 235 | "ts-node": "dist/bin.js", 236 | "ts-node-cwd": "dist/bin-cwd.js", 237 | "ts-node-esm": "dist/bin-esm.js", 238 | "ts-node-script": "dist/bin-script.js", 239 | "ts-node-transpile-only": "dist/bin-transpile.js", 240 | "ts-script": "dist/bin-script-deprecated.js" 241 | }, 242 | "peerDependencies": { 243 | "@swc/core": ">=1.2.50", 244 | "@swc/wasm": ">=1.2.50", 245 | "@types/node": "*", 246 | "typescript": ">=2.7" 247 | }, 248 | "peerDependenciesMeta": { 249 | "@swc/core": { 250 | "optional": true 251 | }, 252 | "@swc/wasm": { 253 | "optional": true 254 | } 255 | } 256 | }, 257 | "node_modules/tslib": { 258 | "version": "2.4.0", 259 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", 260 | "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" 261 | }, 262 | "node_modules/typescript": { 263 | "version": "5.3.3", 264 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", 265 | "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", 266 | "peer": true, 267 | "bin": { 268 | "tsc": "bin/tsc", 269 | "tsserver": "bin/tsserver" 270 | }, 271 | "engines": { 272 | "node": ">=14.17" 273 | } 274 | }, 275 | "node_modules/undici-types": { 276 | "version": "5.26.5", 277 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 278 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 279 | "peer": true 280 | }, 281 | "node_modules/v8-compile-cache-lib": { 282 | "version": "3.0.1", 283 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 284 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" 285 | }, 286 | "node_modules/webidl-conversions": { 287 | "version": "3.0.1", 288 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 289 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 290 | }, 291 | "node_modules/whatwg-fetch": { 292 | "version": "3.6.20", 293 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", 294 | "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" 295 | }, 296 | "node_modules/whatwg-url": { 297 | "version": "5.0.0", 298 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 299 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 300 | "dependencies": { 301 | "tr46": "~0.0.3", 302 | "webidl-conversions": "^3.0.0" 303 | } 304 | }, 305 | "node_modules/ws": { 306 | "version": "8.5.0", 307 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", 308 | "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", 309 | "engines": { 310 | "node": ">=10.0.0" 311 | }, 312 | "peerDependencies": { 313 | "bufferutil": "^4.0.1", 314 | "utf-8-validate": "^5.0.2" 315 | }, 316 | "peerDependenciesMeta": { 317 | "bufferutil": { 318 | "optional": true 319 | }, 320 | "utf-8-validate": { 321 | "optional": true 322 | } 323 | } 324 | }, 325 | "node_modules/yn": { 326 | "version": "3.1.1", 327 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 328 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 329 | "engines": { 330 | "node": ">=6" 331 | } 332 | } 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "ethers": "^6.9.1", 4 | "isomorphic-fetch": "^3.0.0", 5 | "node-fetch": "^2.7.0", 6 | "ts-node": "^10.9.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /scripts/bootstrap_kettle.ts: -------------------------------------------------------------------------------- 1 | import net from "net"; 2 | 3 | import { ethers, JsonRpcProvider } from "ethers"; 4 | 5 | import { artifact_addr, connect_kettle, attach_artifact, deploy_artifact, kettle_execute, kettle_advance} from "./common" 6 | 7 | import * as LocalConfig from '../deployment.json' 8 | 9 | async function main() { 10 | const kettle = connect_kettle(LocalConfig.KETTLE_RPC); 11 | 12 | await kettle_advance(kettle); 13 | 14 | const provider = new JsonRpcProvider(LocalConfig.RPC_URL); 15 | const wallet = new ethers.Wallet(LocalConfig.PRIVATE_KEY, provider); 16 | 17 | /* Assumes andromeda is configured, might not be */ 18 | const Andromeda = await attach_artifact(LocalConfig.ANDROMEDA_ARTIFACT, wallet, artifact_addr(LocalConfig.ANDROMEDA_ARTIFACT)); 19 | const [KM, _] = await deploy_artifact(LocalConfig.KEY_MANAGER_SN_ARTIFACT, wallet, Andromeda.target); 20 | 21 | let keyManagerPub = await KM.xPub(); 22 | if (keyManagerPub !== "0x0000000000000000000000000000000000000000") { 23 | console.log("Key manager already bootstrapped with "+keyManagerPub); 24 | return 25 | } 26 | 27 | await kettle_advance(kettle); 28 | const bootstrapTxData = await KM.offchain_Bootstrap.populateTransaction(); 29 | let resp = await kettle_execute(kettle, bootstrapTxData.to, bootstrapTxData.data); 30 | 31 | const executionResult = JSON.parse(resp); 32 | if (executionResult.Success === undefined) { 33 | throw("execution did not succeed: "+JSON.stringify(resp)); 34 | } 35 | 36 | const offchainBootstrapResult = KM.interface.decodeFunctionResult(KM.offchain_Bootstrap.fragment, executionResult.Success.output.Call).toObject(); 37 | 38 | const onchainBootstrapTx = await (await KM.onchain_Bootstrap(offchainBootstrapResult._xPub, offchainBootstrapResult.att)).wait(); 39 | console.log("bootstrapped "+offchainBootstrapResult._xPub+" in "+onchainBootstrapTx.hash); 40 | } 41 | 42 | main().catch((error) => { 43 | console.error(error); 44 | process.exitCode = 1; 45 | }); 46 | -------------------------------------------------------------------------------- /scripts/common.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import fs from 'fs'; 3 | import net from "net"; 4 | import fetch from "node-fetch"; 5 | 6 | import * as LocalConfig from '../deployment.json'; 7 | 8 | /* Utility functions */ 9 | 10 | export function artifacts(): {[key: string]: {[key: string]: {"address": string, "constructor_args": any[]}}} { 11 | let ARTIFACTS: {[key: string]: {[key: string]: {"address": string, "constructor_args": any[]}}} = LocalConfig.ARTIFACTS; 12 | return ARTIFACTS; 13 | } 14 | 15 | export function artifact_addr(path: string): string|null { 16 | let all_artifacts = artifacts(); 17 | if (path in all_artifacts) { 18 | return all_artifacts[path]["address"]; 19 | } 20 | return null 21 | } 22 | 23 | export function attach_artifact(path: string, signer: ethers.Signer, address: string): ethers.Contract { 24 | const factory = ethers.ContractFactory.fromSolidity(fs.readFileSync(path, "utf-8"), signer); 25 | return factory.attach(address); 26 | } 27 | 28 | export async function deploy_artifact_direct(path: string, signer: ethers.Signer, ...args: any[]): Promise { 29 | const factory = ethers.ContractFactory.fromSolidity(fs.readFileSync(path, "utf-8"), signer); 30 | const contract = await (await factory.deploy(...args)).waitForDeployment(); 31 | console.log("Deployed `"+path+"` to "+contract.target); 32 | 33 | return contract; 34 | } 35 | 36 | export async function deploy_artifact(path: string, signer: ethers.Signer, ...args: any[]): Promise<[ethers.Contract, boolean]> { 37 | let ARTIFACTS = artifacts(); 38 | if (path in ARTIFACTS) { 39 | const addr = ARTIFACTS[path]["address"]; 40 | // TODO: we could force redeployment if constructor args changed 41 | console.log("found address "+addr+" for "+path+", attaching it instead of deploying a new one"); 42 | return new Promise((resolve) => { 43 | resolve([attach_artifact(path, signer, addr), true]); 44 | }); 45 | } 46 | const contract = await deploy_artifact_direct(path, signer, ...args); 47 | 48 | ARTIFACTS[path] = { 49 | "address": contract.target, 50 | "constructor_args": args, 51 | } 52 | let updatedConfig = { 53 | ...LocalConfig, 54 | ARTIFACTS, 55 | }; 56 | delete updatedConfig.default; 57 | 58 | fs.writeFileSync("deployment.json", JSON.stringify(updatedConfig, null, 2)); 59 | 60 | return [contract, false]; 61 | } 62 | 63 | /* Contract utils */ 64 | export async function derive_key(address: string, kettle: net.Socket | string, KM: ethers.Contract) { 65 | const offchainDeriveTxData = await KM.offchain_DeriveKey.populateTransaction(address); 66 | let resp = await kettle_execute(kettle, offchainDeriveTxData.to, offchainDeriveTxData.data); 67 | 68 | let executionResult = JSON.parse(resp); 69 | if (executionResult.Success === undefined) { 70 | throw("execution did not succeed: "+JSON.stringify(resp)); 71 | } 72 | 73 | const offchainDeriveResult = KM.interface.decodeFunctionResult(KM.offchain_DeriveKey.fragment, executionResult.Success.output.Call).toObject(); 74 | const onchainDeriveTx = await (await KM.onchain_DeriveKey(address, offchainDeriveResult.dPub, offchainDeriveResult.sig)).wait(); 75 | 76 | console.log("submitted derive key for "+address+" in "+onchainDeriveTx.hash); 77 | } 78 | 79 | 80 | /* Kettle utils */ 81 | export function connect_kettle(conn: object | string): net.Socket | string { 82 | if (typeof conn === 'string') { 83 | return conn; 84 | } 85 | return net.connect(conn); 86 | } 87 | export async function kettle_advance(server: net.Socket | string): Promise { 88 | let resp = await send_to_kettle(server, "advance"); 89 | if (resp !== 'advanced') { 90 | throw("kettle did not advance, refusing to continue: "+resp); 91 | } 92 | return resp; 93 | } 94 | export async function kettle_execute(server: net.Socket | string, to: string, data: string): Promise { 95 | const cmd = 'execute {"caller":"0x0000000000000000000000000000000000000000","gas_limit":21000000,"gas_price":"0x0","transact_to":{"Call":"'+to+'"},"value":"0x0","data":"'+data+'","nonce":0,"chain_id":null,"access_list":[],"gas_priority_fee":null,"blob_hashes":[],"max_fee_per_blob_gas":null}'; 96 | return await send_to_kettle(server, cmd); 97 | } 98 | 99 | export async function send_to_kettle(server: net.Socket | string, cmd: string): Promise { 100 | if (typeof server === 'string') { 101 | const response = await fetch(server, { 102 | method: 'POST', 103 | headers: {'Content-Type': 'text/plain'}, 104 | body: cmd 105 | }); 106 | 107 | return (await response.text()).trim().replace(/^"|"$/gm,'').replace(/\\"/gm,'"'); 108 | } 109 | 110 | return new Promise((resolve) => { 111 | let outBuf = ""; 112 | server.on('data', function(b) { 113 | outBuf += b.toString("utf-8"); 114 | if (outBuf.endsWith("\n")) { 115 | resolve(outBuf.trim().replace(/^"|"$/gm,'').replace(/\\"/gm,'"')); 116 | } 117 | }); 118 | server.write(cmd+"\n"); 119 | }); 120 | } 121 | -------------------------------------------------------------------------------- /scripts/configure_tcbinfo.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { ethers, JsonRpcProvider } from "ethers"; 3 | 4 | import { TCBInfoStruct } from "lib/automata-dcap-v3-attestation/typechain-types/contracts/AutomataDcapV3Attestation"; 5 | 6 | import { artifact_addr, attach_artifact } from "./common.ts" 7 | 8 | import * as LocalConfig from '../deployment.json' 9 | 10 | async function main() { 11 | const tcbInfoFiles = (process.env.TCB_INFO_FILES ?? '').trim().split(" "); 12 | 13 | if (tcbInfoFiles.length === 0) { 14 | throw new Error("TCB_INFO_FILES environment variable is not defined."); 15 | } 16 | 17 | const provider = new JsonRpcProvider(LocalConfig.RPC_URL); 18 | const wallet = new ethers.Wallet(LocalConfig.PRIVATE_KEY, provider); 19 | 20 | const Andromeda = await attach_artifact(LocalConfig.ANDROMEDA_ARTIFACT, wallet, artifact_addr(LocalConfig.ANDROMEDA_ARTIFACT)); 21 | 22 | for (const tcbInfoFile in tcbInfoFiles) { 23 | const tcbInfo = JSON.parse(fs.readFileSync(tcbInfoFiles[tcbInfoFile], 'utf8')) as TCBInfoStruct.TCBInfoStruct.tcbInfo; 24 | const isAlreadyDeployed = (await Andromeda.tcbInfo(tcbInfo.fmspc))[1] === tcbInfo.fmspc; 25 | if (!isAlreadyDeployed) { 26 | const tcbInfoTx = await (await Andromeda.configureTcbInfoJson(tcbInfo.fmspc, tcbInfo)).wait(); 27 | console.log("configured "+tcbInfo.fmspc+" in "+tcbInfoTx.hash); 28 | } else { 29 | console.log(tcbInfo.fmspc+" already configured"); 30 | } 31 | } 32 | } 33 | 34 | main().catch((error) => { 35 | console.error(error); 36 | process.exitCode = 1; 37 | }); 38 | -------------------------------------------------------------------------------- /scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | import { ethers, JsonRpcProvider } from "ethers"; 4 | 5 | import { TCBInfoStruct, EnclaveIdStruct } from "lib/automata-dcap-v3-attestation/typechain-types/contracts/AutomataDcapV3Attestation"; 6 | 7 | import { deploy_artifact } from "./common.ts" 8 | 9 | import * as LocalConfig from '../deployment.json' 10 | 11 | 12 | async function deploy() { 13 | const provider = new JsonRpcProvider(LocalConfig.RPC_URL); 14 | const wallet = new ethers.Wallet(LocalConfig.PRIVATE_KEY, provider); 15 | 16 | const [SigVerifyLib,] = await deploy_artifact(LocalConfig.SIGVERIFY_LIB_ARTIFACT, wallet); 17 | const [Andromeda, andomedaFound] = await deploy_artifact(LocalConfig.ANDROMEDA_ARTIFACT, wallet, SigVerifyLib.target); 18 | 19 | if (andomedaFound) { 20 | console.log("Andromeda already deployed, not configuring it"); 21 | } else { 22 | const enclaveId = JSON.parse(fs.readFileSync(LocalConfig.QE_IDENTITY_FILE, 'utf8')) as EnclaveIdStruct.EnclaveIdStruct; 23 | const enclaveIdTx = await (await Andromeda.configureQeIdentityJson(enclaveId)).wait(); 24 | console.log("configured QeIdentidy in "+enclaveIdTx.hash); 25 | 26 | for (let i = 0; i < LocalConfig.TRUSTED_MRENCLAVES.length; i++) { 27 | const tx = await (await Andromeda.setMrEnclave(LocalConfig.TRUSTED_MRENCLAVES[i], true)).wait(); 28 | console.log("Set mr_enclave "+LocalConfig.TRUSTED_MRENCLAVES[i]+" as trusted in "+tx.hash); 29 | } 30 | 31 | for (let i = 0; i < LocalConfig.TRUSTED_MRSIGNERS.length; i++) { 32 | const tx = await (await Andromeda.setMrSigner(LocalConfig.TRUSTED_MRSIGNERS[i], true)).wait(); 33 | console.log("Set mr_signer "+LocalConfig.TRUSTED_MRSIGNERS[i]+" as trusted in "+tx.hash); 34 | } 35 | } 36 | 37 | const [KeyManagerSN,] = await deploy_artifact(LocalConfig.KEY_MANAGER_SN_ARTIFACT, wallet, Andromeda.target); 38 | } 39 | 40 | deploy().catch((error) => { 41 | console.error(error); 42 | process.exitCode = 1; 43 | }); 44 | -------------------------------------------------------------------------------- /scripts/deploy_examples.ts: -------------------------------------------------------------------------------- 1 | import net from "net"; 2 | 3 | import { ethers, JsonRpcProvider } from "ethers"; 4 | 5 | import { artifact_addr, connect_kettle, deploy_artifact, deploy_artifact_direct, attach_artifact, kettle_advance, kettle_execute, derive_key } from "./common" 6 | 7 | import * as LocalConfig from '../deployment.json' 8 | 9 | async function deploy() { 10 | const kettle = connect_kettle(LocalConfig.KETTLE_RPC); 11 | 12 | const provider = new JsonRpcProvider(LocalConfig.RPC_URL); 13 | const wallet = new ethers.Wallet(LocalConfig.PRIVATE_KEY, provider); 14 | const KM = await attach_artifact(LocalConfig.KEY_MANAGER_SN_ARTIFACT, wallet, artifact_addr(LocalConfig.KEY_MANAGER_SN_ARTIFACT)); 15 | 16 | const [HttpCall, _] = await deploy_artifact(LocalConfig.HTTPCALL_ARTIFACT, wallet); 17 | const [BundleStore, foundBS] = await deploy_artifact(LocalConfig.BUNDLE_STORE_ARTIFACT, wallet, KM.target, []); 18 | const [Timelock, foundTL] = await deploy_artifact(LocalConfig.TIMELOCK_ARTIFACT, wallet, KM.target); 19 | /* Not currently used in demos */ 20 | // const SealedAuction = await deploy_artifact_direct(LocalConfig.SEALED_AUCTION_ARTIFACT, wallet, KM.target, 5); 21 | 22 | await kettle_advance(kettle); 23 | 24 | if (!foundBS) { 25 | await derive_key(await BundleStore.getAddress(), kettle, KM); 26 | } 27 | if (!foundTL) { 28 | await derive_key(await Timelock.getAddress(), kettle, KM); 29 | } 30 | /* Not currently used in demos */ 31 | // await derive_key(await SealedAuction.getAddress(), kettle, KM); 32 | } 33 | 34 | deploy().catch((error) => { 35 | console.error(error); 36 | process.exitCode = 1; 37 | }); 38 | -------------------------------------------------------------------------------- /scripts/onboard_kettle.ts: -------------------------------------------------------------------------------- 1 | import net from "net"; 2 | 3 | import { ethers, JsonRpcProvider } from "ethers"; 4 | 5 | import { artifact_addr, attach_artifact, deploy_artifact, kettle_execute, kettle_advance} from "./common.ts" 6 | 7 | import * as LocalConfig from '../deployment.json' 8 | 9 | async function main() { 10 | const new_kettle_socket = net.connect({port: "5556"}); 11 | 12 | const provider = new JsonRpcProvider(LocalConfig.RPC_URL); 13 | const wallet = new ethers.Wallet(LocalConfig.PRIVATE_KEY, provider); 14 | 15 | /* Assumes andromeda is configured, might not be */ 16 | const Andromeda = await attach_artifact(LocalConfig.ANDROMEDA_ARTIFACT, wallet, artifact_addr(LocalConfig.ANDROMEDA_ARTIFACT)); 17 | 18 | const [KM, _] = await deploy_artifact(LocalConfig.KEY_MANAGER_SN_ARTIFACT, wallet, Andromeda.target); 19 | 20 | let resp = await kettle_advance(new_kettle_socket); 21 | if (resp !== 'advanced') { 22 | throw("kettle did not advance, refusing to continue: "+resp); 23 | } 24 | 25 | const registerTxData = await KM.offchain_Register.populateTransaction(); 26 | resp = await kettle_execute(new_kettle_socket, registerTxData.to, registerTxData.data); 27 | 28 | let executionResult = JSON.parse(resp); 29 | if (executionResult.Success === undefined) { 30 | throw("execution did not succeed: "+JSON.stringify(resp)); 31 | } 32 | 33 | const offchainRegisterResult = KM.interface.decodeFunctionResult(KM.offchain_Register.fragment, executionResult.Success.output.Call).toObject(); 34 | 35 | let ciphertext = null; 36 | const onboard_event = (await KM.queryFilter("Onboard", 0)) 37 | .map((e) => KM.interface.decodeEventLog("Onboard", e.data, e.topics)) 38 | .filter((e) => e[0] === offchainRegisterResult.addr).pop(); 39 | 40 | if (onboard_event) { 41 | console.log("onboard event for "+offchainRegisterResult.addr+" exists, skip registering "); 42 | 43 | ciphertext = onboard_event[1]; 44 | } else { 45 | const registered_kettle_socket = net.connect({port: "5557"}); 46 | 47 | const onchainRegisterTx = await (await KM.onchain_Register(offchainRegisterResult.addr, offchainRegisterResult.myPub, offchainRegisterResult.att)).wait(); 48 | console.log("registered "+offchainRegisterResult.addr+" in "+onchainRegisterTx.hash); 49 | 50 | resp = await kettle_advance(registered_kettle_socket); 51 | if (resp !== 'advanced') { 52 | throw("kettle did not advance, refusing to continue: "+resp); 53 | } 54 | 55 | const onboardTxData = await KM.offchain_Onboard.populateTransaction(offchainRegisterResult.addr); 56 | resp = await kettle_execute(registered_kettle_socket, onboardTxData.to, onboardTxData.data); 57 | 58 | executionResult = JSON.parse(resp); 59 | if (executionResult.Success === undefined) { 60 | throw("execution did not succeed: "+JSON.stringify(resp)); 61 | } 62 | 63 | const offchainOnboardResult = KM.interface.decodeFunctionResult(KM.offchain_Onboard.fragment, executionResult.Success.output.Call).toObject(); 64 | 65 | const onchainOnboardTx = await (await KM.onchain_Onboard(offchainRegisterResult.addr, offchainOnboardResult.ciphertext)).wait(); 66 | 67 | console.log("submitted onboard ciphertext for "+offchainRegisterResult.addr+" in "+onchainOnboardTx.hash); 68 | 69 | ciphertext = offchainOnboardResult.ciphertext; 70 | } 71 | 72 | const finishOnboardTxData = await KM.finish_Onboard.populateTransaction(ciphertext); 73 | resp = await kettle_execute(new_kettle_socket, finishOnboardTxData.to, finishOnboardTxData.data); 74 | 75 | executionResult = JSON.parse(resp); 76 | if (executionResult.Success === undefined) { 77 | throw("execution did not succeed: "+JSON.stringify(resp)); 78 | } 79 | 80 | console.log("onboarded "+offchainRegisterResult.addr); 81 | } 82 | 83 | main().catch((error) => { 84 | console.error(error); 85 | process.exitCode = 1; 86 | }); 87 | -------------------------------------------------------------------------------- /scripts/test_examples.ts: -------------------------------------------------------------------------------- 1 | import net from "net"; 2 | 3 | import { ethers, JsonRpcProvider } from "ethers"; 4 | 5 | import { artifact_addr, connect_kettle, deploy_artifact, deploy_artifact_direct, attach_artifact, kettle_advance, kettle_execute, derive_key } from "./common" 6 | 7 | import * as LocalConfig from '../deployment.json' 8 | 9 | const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); 10 | 11 | async function testHC(HttpCall: ethers.Contract, kettle: net.Socket | string) { 12 | console.log("Testing httpcall contract..."); 13 | const httpcallData = await HttpCall.makeHttpCall.populateTransaction(); 14 | 15 | let resp = await kettle_execute(kettle, httpcallData.to, httpcallData.data); 16 | 17 | let executionResult = JSON.parse(resp); 18 | if (executionResult.Success === undefined) { 19 | throw("execution did not succeed: "+JSON.stringify(resp)); 20 | } 21 | 22 | const callResult = HttpCall.interface.decodeFunctionResult(HttpCall.makeHttpCall.fragment, executionResult.Success.output.Call); 23 | console.log("Response from http call:", callResult); 24 | } 25 | 26 | async function testSA(SealedAuction: ethers.Contract, kettle: net.Socket | string) { 27 | console.log("Testing sealed auction contract..."); 28 | 29 | const encryptedOrder = await SealedAuction.encryptOrder(2,ethers.zeroPadBytes("0xdead2123",32)); 30 | await (await SealedAuction.submitEncrypted(encryptedOrder)).wait(); 31 | 32 | await sleep(10000); 33 | 34 | await kettle_advance(kettle); 35 | 36 | const offchainFinalizeTxData = await SealedAuction.offchain_Finalize.populateTransaction(); 37 | let resp = await kettle_execute(kettle, offchainFinalizeTxData.to, offchainFinalizeTxData.data); 38 | 39 | let executionResult = JSON.parse(resp); 40 | if (executionResult.Success === undefined) { 41 | throw("execution did not succeed: "+JSON.stringify(resp)); 42 | } 43 | 44 | const offchainFinalizeResult = SealedAuction.interface.decodeFunctionResult(SealedAuction.offchain_Finalize.fragment, executionResult.Success.output.Call).toObject(); 45 | 46 | await (await SealedAuction.onchain_Finalize(offchainFinalizeResult.secondPrice_, offchainFinalizeResult.att)).wait(); 47 | 48 | console.log("successfully finalized auction with '"+offchainFinalizeResult.secondPrice_+"' as second price"); 49 | } 50 | 51 | async function testTL(Timelock: ethers.Contract, kettle: net.Socket | string) { 52 | console.log("Testing timelock contract..."); 53 | 54 | // Tests Timelock contract 55 | let message = "Suave timelock test message!32xr"; 56 | const encryptedMessage = await Timelock.encryptMessage(message,ethers.zeroPadBytes("0xdead2123",32)); 57 | const submitEncryptedTx = await (await Timelock.submitEncrypted(encryptedMessage)).wait(); 58 | 59 | console.log("submitted encrypted message "+encryptedMessage+" in "+submitEncryptedTx.hash); 60 | 61 | await sleep(72000); 62 | 63 | await kettle_advance(kettle); 64 | 65 | const offchainDecryptTxData = await Timelock.decrypt.populateTransaction(encryptedMessage); 66 | let resp = await kettle_execute(kettle, offchainDecryptTxData.to, offchainDecryptTxData.data); 67 | 68 | let executionResult = JSON.parse(resp); 69 | if (executionResult.Success === undefined) { 70 | throw("execution did not succeed: "+JSON.stringify(resp)); 71 | } 72 | 73 | const offchainDecryptResult = Timelock.interface.decodeFunctionResult(Timelock.decrypt.fragment, executionResult.Success.output.Call).toObject(); 74 | console.log("successfully decrypted message: '"+ethers.toUtf8String(offchainDecryptResult.message)+"'"); 75 | } 76 | 77 | async function deploy() { 78 | const kettle = connect_kettle(LocalConfig.KETTLE_RPC); 79 | 80 | await kettle_advance(kettle); 81 | const provider = new JsonRpcProvider(LocalConfig.RPC_URL); 82 | const wallet = new ethers.Wallet(LocalConfig.PRIVATE_KEY, provider); 83 | const KM = await attach_artifact(LocalConfig.KEY_MANAGER_SN_ARTIFACT, wallet, artifact_addr(LocalConfig.KEY_MANAGER_SN_ARTIFACT)); 84 | 85 | const [HttpCall, _] = await deploy_artifact(LocalConfig.HTTPCALL_ARTIFACT, wallet); 86 | await kettle_advance(kettle); 87 | 88 | await testHC(HttpCall, kettle); 89 | 90 | const [Timelock, foundTL] = await deploy_artifact(LocalConfig.TIMELOCK_ARTIFACT, wallet, KM.target); 91 | await kettle_advance(kettle); 92 | if (!foundTL) { 93 | await derive_key(await Timelock.getAddress(), kettle, KM); 94 | await kettle_advance(kettle); 95 | } 96 | await testTL(Timelock, kettle); 97 | 98 | const SealedAuction = await deploy_artifact_direct(LocalConfig.SEALED_AUCTION_ARTIFACT, wallet, KM.target, 5); 99 | await kettle_advance(kettle); 100 | 101 | await derive_key(await SealedAuction.getAddress(), kettle, KM); 102 | await testSA(SealedAuction, kettle); 103 | } 104 | 105 | deploy().catch((error) => { 106 | console.error(error); 107 | process.exitCode = 1; 108 | }); 109 | -------------------------------------------------------------------------------- /scripts/verify_contracts.ts: -------------------------------------------------------------------------------- 1 | import * as LocalConfig from '../deployment.json' 2 | 3 | import { execSync } from "child_process"; 4 | 5 | import { artifacts } from "./common.ts"; 6 | 7 | function verify_contract(constructor_args, contract_addr, contract_name) { 8 | let cmd = "forge v --verifier blockscout --verifier-url https://explorer.rigil.suave.flashbots.net/api"+(constructor_args.length > 0? " --constructor-args "+constructor_args.join(","):"")+" "+contract_addr+" "+contract_name+" --compiler-version v0.8.19"; 9 | console.log("Running ", cmd); 10 | execSync(cmd); 11 | } 12 | 13 | async function verify_all() { 14 | let all_artifacts = artifacts(); 15 | Object.keys(all_artifacts).forEach(artifact_path => { 16 | let artifact = all_artifacts[artifact_path]; 17 | let artifact_name = artifact_path.match(/([^\/]*).json/)[1]; 18 | console.log("Verifying the following: ", (artifact["constructor_args"], artifact["address"], artifact_name)); 19 | verify_contract(artifact["constructor_args"], artifact["address"], artifact_name); 20 | }); 21 | } 22 | 23 | verify_all().catch((error) => { 24 | console.error(error); 25 | process.exitCode = 1; 26 | }); 27 | -------------------------------------------------------------------------------- /src/Andromeda.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.8; 3 | 4 | import {IAndromeda} from "src/IAndromeda.sol"; 5 | import {DcapDemo} from "src/DcapVerifier.sol"; 6 | import {V3Struct} from "automata-dcap-v3-attestation/lib/QuoteV3Auth/V3Struct.sol"; 7 | import {V3Parser} from "automata-dcap-v3-attestation/lib/QuoteV3Auth/V3Parser.sol"; 8 | 9 | contract Andromeda is IAndromeda, DcapDemo { 10 | constructor(address sigVerifyLib) DcapDemo(sigVerifyLib) {} 11 | 12 | address public constant ATTEST_ADDR = 0x0000000000000000000000000000000000040700; 13 | address public constant VOLATILESET_ADDR = 0x0000000000000000000000000000000000040701; 14 | address public constant VOLATILEGET_ADDR = 0x0000000000000000000000000000000000040702; 15 | address public constant RANDOM_ADDR = 0x0000000000000000000000000000000000040703; 16 | address public constant SEALINGKEY_ADDR = 0x0000000000000000000000000000000000040704; 17 | address public constant SHA512_ADDR = 0x0000000000000000000000000000000000050700; 18 | address public constant DO_HTTP_REQUEST = 0x0000000000000000000000000000000043200002; 19 | 20 | function volatileSet(bytes32 key, bytes32 value) external override { 21 | bytes memory cdata = abi.encodePacked([key, value]); 22 | (bool success, bytes memory _out) = VOLATILESET_ADDR.staticcall(cdata); 23 | _out; 24 | require(success); 25 | } 26 | 27 | function volatileGet(bytes32 key) external override returns (bytes32) { 28 | (bool success, bytes memory value) = VOLATILEGET_ADDR.staticcall(abi.encodePacked((key))); 29 | require(success); 30 | require(value.length == 32); 31 | return abi.decode(value, (bytes32)); 32 | } 33 | 34 | function attestSgx(bytes32 appData) external override returns (bytes memory) { 35 | (bool success, bytes memory attestBytes) = ATTEST_ADDR.staticcall(abi.encodePacked(msg.sender, appData)); 36 | require(success); 37 | return attestBytes; 38 | } 39 | 40 | function verifySgx(address caller, bytes32 appData, bytes memory att) public view returns (bool) { 41 | bytes memory userdata = abi.encode(address(this), abi.encodePacked(caller, appData)); 42 | bytes memory userReport = abi.encodePacked(sha256(userdata), uint256(0)); 43 | (,, V3Struct.EnclaveReport memory r,,) = V3Parser.parseInput(att); 44 | if (keccak256(r.reportData) != keccak256(userReport)) { 45 | return false; 46 | } 47 | return this.verifyAttestation(att); 48 | } 49 | 50 | function localRandom() external view override returns (bytes32) { 51 | (bool success, bytes memory randomBytes) = RANDOM_ADDR.staticcall(""); 52 | require(success); 53 | require(randomBytes.length == 32); 54 | return bytes32(randomBytes); 55 | } 56 | 57 | function sealingKey(bytes32 key) external view returns (bytes32) { 58 | (bool success, bytes memory sealingBytes) = SEALINGKEY_ADDR.staticcall(""); 59 | require(success); 60 | require(sealingBytes.length == 32); 61 | return bytes32(keccak256(abi.encode(bytes32(sealingBytes), key))); 62 | } 63 | 64 | function sha512(bytes memory data) external view override returns (bytes memory) { 65 | require(data.length > 0, "sha512: data length must be greater than 0"); 66 | (bool success, bytes memory output) = SHA512_ADDR.staticcall(data); 67 | require(success); 68 | require(output.length == 64); 69 | return output; 70 | } 71 | 72 | function doHTTPRequest(IAndromeda.HttpRequest memory request) external returns (bytes memory) { 73 | (bool success, bytes memory data) = DO_HTTP_REQUEST.call(abi.encode(request)); 74 | require(success); 75 | return abi.decode(data, (bytes)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/AndromedaForge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.8; 3 | 4 | import "solidity-stringutils/strings.sol"; 5 | 6 | import {IAndromeda} from "src/IAndromeda.sol"; 7 | 8 | interface Vm { 9 | function ffi(string[] calldata commandInput) external view returns (bytes memory result); 10 | function setEnv(string calldata name, string calldata value) external; 11 | function envOr(string calldata key, bytes32 defaultValue) external returns (bytes32 value); 12 | } 13 | 14 | contract AndromedaForge is IAndromeda { 15 | using strings for *; 16 | 17 | bytes32 constant salt = hex"234902409284092384092384"; 18 | 19 | Vm constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 20 | 21 | function attestSgx(bytes32 appData) public view returns (bytes memory) { 22 | // Make a fake attestation just using a salt 23 | bytes32 hash = keccak256(abi.encode(salt, msg.sender, appData)); 24 | return abi.encodePacked(hash); 25 | } 26 | 27 | function verifySgx(address caller, bytes32 appData, bytes memory att) public pure returns (bool) { 28 | // Recreate the fake attestation 29 | bytes32 hash = keccak256(abi.encode(salt, caller, appData)); 30 | return hash == abi.decode(att, (bytes32)); 31 | } 32 | 33 | function localRandom() public view returns (bytes32) { 34 | string[] memory inputs = new string[](2); 35 | inputs[0] = "sh"; 36 | inputs[1] = "ffi/local_random.sh"; 37 | bytes memory res = vm.ffi(inputs); 38 | return bytes32(res); 39 | } 40 | 41 | function sealingKey(bytes32 tag) public view returns (bytes32) { 42 | // Make a fake sealing key just using a salt 43 | return bytes32(keccak256(abi.encode(activeHost, salt, msg.sender, tag))); 44 | } 45 | 46 | function toEnv(string memory host, address caller, bytes32 tag) internal pure returns (string memory) { 47 | strings.slice memory m = "SUAVE_VOLATILE_".toSlice().concat(iToHex(abi.encodePacked(caller)).toSlice()).toSlice( 48 | ).concat("_".toSlice()).toSlice(); 49 | return m.concat(host.toSlice()).toSlice().concat("_".toSlice()).toSlice().concat( 50 | iToHex(abi.encodePacked(tag)).toSlice() 51 | ); 52 | } 53 | 54 | function volatileSet(bytes32 tag, bytes32 value) public { 55 | address caller = msg.sender; 56 | string memory env = toEnv(activeHost, caller, tag); 57 | vm.setEnv(env, iToHex(abi.encodePacked(value))); 58 | } 59 | 60 | function volatileGet(bytes32 tag) public returns (bytes32) { 61 | address caller = msg.sender; 62 | string memory env = toEnv(activeHost, caller, tag); 63 | return vm.envOr(env, bytes32("")); 64 | } 65 | 66 | // Currently active host 67 | string activeHost = "default"; 68 | 69 | function switchHost(string memory host) public { 70 | activeHost = host; 71 | } 72 | 73 | function iToHex(bytes memory buffer) public pure returns (string memory) { 74 | bytes memory converted = new bytes(buffer.length * 2); 75 | bytes memory _base = "0123456789abcdef"; 76 | for (uint256 i = 0; i < buffer.length; i++) { 77 | converted[i * 2] = _base[uint8(buffer[i]) / _base.length]; 78 | converted[i * 2 + 1] = _base[uint8(buffer[i]) % _base.length]; 79 | } 80 | return string(abi.encodePacked("0x", converted)); 81 | } 82 | 83 | function sha512(bytes memory data) external view override returns (bytes memory) { 84 | require(data.length > 0, "sha512: data length must be greater than 0"); 85 | string[] memory inputs = new string[](3); 86 | inputs[0] = "sh"; 87 | inputs[1] = "ffi/sha512.sh"; 88 | inputs[2] = string(data); 89 | return vm.ffi(inputs); 90 | } 91 | 92 | function doHTTPRequest(IAndromeda.HttpRequest memory) external pure returns (bytes memory) { 93 | return bytes(""); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/AndromedaRemote.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.8; 3 | 4 | import "solidity-stringutils/strings.sol"; 5 | 6 | //import "forge-std/Vm.sol"; 7 | import "forge-std/StdJson.sol"; 8 | import "forge-std/console2.sol"; 9 | 10 | import {IAndromeda} from "src/IAndromeda.sol"; 11 | import {SigVerifyLib} from "automata-dcap-v3-attestation/utils/SigVerifyLib.sol"; 12 | import {DcapDemo} from "src/DcapVerifier.sol"; 13 | import {EnclaveIdStruct, TCBInfoStruct} from "automata-dcap-v3-attestation/AutomataDcapV3Attestation.sol"; 14 | import {V3Struct} from "automata-dcap-v3-attestation/lib/QuoteV3Auth/V3Struct.sol"; 15 | import {V3Parser} from "automata-dcap-v3-attestation/lib/QuoteV3Auth/V3Parser.sol"; 16 | 17 | interface Vm { 18 | function warp(uint) external view; 19 | function ffi(string[] calldata commandInput) external view returns (bytes memory result); 20 | function setEnv(string calldata name, string calldata value) external; 21 | function envOr(string calldata key, bytes32 defaultValue) external returns (bytes32 value); 22 | function readFile(string calldata path) external view returns (string memory data); 23 | function prank(address caller) external; 24 | function parseJson(string memory json, string memory key) external view; 25 | function parseBytes(string memory b) external view returns (bytes memory); 26 | function projectRoot() external view returns (string memory); 27 | } 28 | 29 | contract AndromedaRemote is IAndromeda, DcapDemo { 30 | using strings for *; 31 | using stdJson for string; 32 | 33 | Vm constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 34 | 35 | constructor(address sigVerifyLib) DcapDemo(sigVerifyLib) {} 36 | 37 | function initialize() public { 38 | // This is the dummy enclave from the service 39 | // https://github.com/amiller/gramine-dummy-attester/tree/dcap 40 | setMrSigner(bytes32(0x1cf2e52911410fbf3f199056a98d58795a559a2e800933f7fcd13d048462271c), true); 41 | setMrEnclave(bytes32(0xed24ce78cd65438dc3b74e549b1ad5c591dba88e2078e676fca600ebbec21370), true); 42 | 43 | // Set the timestamp (to avoid certificate expiry check); 44 | vm.warp(1718132125); 45 | 46 | // By setting the report check, we now have to check mrsigner 47 | // AND mrenclave. Even though this is a little redundant! 48 | // We should modify upstream for this. 49 | // In the meantime we can make it easy to set mrsigner trusted 50 | //toggleLocalReportCheck(); 51 | 52 | // Load Quoting Enclave identity (part of the tcb, signed by intel) 53 | { 54 | string memory p = "test/fixtures/quotingenclave-identity.json"; 55 | EnclaveIdStruct.EnclaveId memory s = parseIdentity(p); 56 | vm.prank(address(owner)); 57 | this.configureQeIdentityJson(s); 58 | } 59 | // Load one of the TCB Infos. These are signed by Intel. But here the signature isn't checked. FIXME 60 | { 61 | TCBInfoStruct.TCBInfo memory s = parseTcbInfo("test/fixtures/tcbInfo.json"); 62 | vm.prank(address(owner)); 63 | this.configureTcbInfoJson(s.fmspc, s); 64 | } 65 | { 66 | TCBInfoStruct.TCBInfo memory s = parseTcbInfo("test/fixtures/tcbInfo2.json"); 67 | vm.prank(address(owner)); 68 | this.configureTcbInfoJson(s.fmspc, s); 69 | } 70 | } 71 | 72 | function attestSgx(bytes32 appData) public view returns (bytes memory) { 73 | console2.log("attestSgx remote"); 74 | bytes memory userdata = abi.encode(address(this), abi.encodePacked(msg.sender, appData)); 75 | console2.logBytes(userdata); 76 | bytes memory userReport = abi.encodePacked(sha256(userdata), uint(0)); 77 | string[] memory inputs = new string[](3); 78 | inputs[0] = "python"; 79 | inputs[1] = "ffi/ffi-fetchquote-dcap.py"; 80 | inputs[2] = iToHex(abi.encodePacked(userReport)); 81 | bytes memory res = vm.ffi(inputs); 82 | return res; 83 | } 84 | 85 | function verifySgx(address caller, bytes32 appData, bytes memory att) public view returns (bool) { 86 | console2.log("verifySgx remote"); 87 | //console2.logBytes(att); 88 | bytes memory userdata = abi.encode(address(this), abi.encodePacked(caller, appData)); 89 | bytes memory userReport = abi.encodePacked(sha256(userdata), uint(0)); 90 | (,, V3Struct.EnclaveReport memory r,,) = V3Parser.parseInput(att); 91 | if (keccak256(r.reportData) != keccak256(userReport)) { 92 | return false; 93 | } 94 | return this.verifyAttestation(att); 95 | } 96 | 97 | function localRandom() public view returns (bytes32) { 98 | string[] memory inputs = new string[](2); 99 | inputs[0] = "sh"; 100 | inputs[1] = "ffi/local_random.sh"; 101 | bytes memory res = vm.ffi(inputs); 102 | return bytes32(res); 103 | } 104 | 105 | function sealingKey(bytes32 tag) public view returns (bytes32) { 106 | bytes32 salt = hex"234902409284092384092384"; 107 | // Make a fake sealing key just using a salt 108 | return bytes32(keccak256(abi.encode(activeHost, salt, msg.sender, tag))); 109 | } 110 | 111 | function toEnv(string memory host, address caller, bytes32 tag) internal pure returns (string memory) { 112 | strings.slice memory m = "SUAVE_VOLATILE_".toSlice().concat(iToHex(abi.encodePacked(caller)).toSlice()).toSlice( 113 | ).concat("_".toSlice()).toSlice(); 114 | return m.concat(host.toSlice()).toSlice().concat("_".toSlice()).toSlice().concat( 115 | iToHex(abi.encodePacked(tag)).toSlice() 116 | ); 117 | } 118 | 119 | function volatileSet(bytes32 tag, bytes32 value) public { 120 | address caller = msg.sender; 121 | string memory env = toEnv(activeHost, caller, tag); 122 | vm.setEnv(env, iToHex(abi.encodePacked(value))); 123 | } 124 | 125 | function volatileGet(bytes32 tag) public returns (bytes32) { 126 | address caller = msg.sender; 127 | string memory env = toEnv(activeHost, caller, tag); 128 | return vm.envOr(env, bytes32("")); 129 | } 130 | 131 | function doHTTPRequest(IAndromeda.HttpRequest memory) external pure returns (bytes memory) { 132 | return bytes(""); 133 | } 134 | 135 | // Currently active host 136 | string activeHost = "default"; 137 | 138 | function switchHost(string memory host) public { 139 | activeHost = host; 140 | } 141 | 142 | function iToHex(bytes memory buffer) public pure returns (string memory) { 143 | bytes memory converted = new bytes(buffer.length * 2); 144 | bytes memory _base = "0123456789abcdef"; 145 | for (uint256 i = 0; i < buffer.length; i++) { 146 | converted[i * 2] = _base[uint8(buffer[i]) / _base.length]; 147 | converted[i * 2 + 1] = _base[uint8(buffer[i]) % _base.length]; 148 | } 149 | return string(converted); 150 | //return string(abi.encodePacked("0x", converted)); 151 | } 152 | 153 | function sha512(bytes memory data) external view override returns (bytes memory) { 154 | require(data.length > 0, "sha512: data length must be greater than 0"); 155 | string[] memory inputs = new string[](3); 156 | inputs[0] = "sh"; 157 | inputs[1] = "ffi/sha512.sh"; 158 | inputs[2] = string(data); 159 | return vm.ffi(inputs); 160 | } 161 | 162 | /////////////////////////////////////////////////////// 163 | // Forge functions for tcbInfo and qeIdentity from file 164 | /////////////////////////////////////////////////////// 165 | 166 | struct EnclaveId { 167 | bytes attributes; 168 | bytes attributesMask; 169 | uint16 isvprodid; 170 | bytes miscselect; 171 | bytes miscselectMask; 172 | bytes mrsigner; 173 | TcbLevel[] tcbLevels; 174 | } 175 | 176 | struct TcbLevel { 177 | EnclaveIdStruct.TcbObj tcb; 178 | string tcbStatus; 179 | } 180 | 181 | struct TCBInfo { 182 | string fmspc; 183 | string pceid; 184 | TCBLevelObj[] tcbLevels; 185 | } 186 | 187 | struct TCBLevelObj { 188 | uint256 pcesvn; 189 | uint256[] sgxTcbCompSvnArr; 190 | TCBInfoStruct.TCBStatus status; 191 | } 192 | 193 | function parseIdentity(string memory path) public view returns (EnclaveIdStruct.EnclaveId memory r) { 194 | string memory json = vm.readFile(path); 195 | bytes memory enclaveId = json.parseRaw(".enclaveIdentity"); 196 | EnclaveId memory t = abi.decode(enclaveId, (EnclaveId)); 197 | r.miscselect = bytes4(vm.parseBytes(string(t.miscselect))); 198 | r.miscselectMask = bytes4(vm.parseBytes(string(t.miscselectMask))); 199 | r.isvprodid = t.isvprodid; 200 | r.attributes = bytes16(vm.parseBytes(string(t.attributes))); 201 | r.attributesMask = bytes16(vm.parseBytes(string(t.attributesMask))); 202 | r.mrsigner = bytes32(vm.parseBytes(string(t.mrsigner))); 203 | r.tcbLevels = new EnclaveIdStruct.TcbLevel[](t.tcbLevels.length); 204 | for (uint256 i = 0; i < t.tcbLevels.length; i++) { 205 | r.tcbLevels[i].tcb = t.tcbLevels[i].tcb; 206 | if (t.tcbLevels[i].tcbStatus.toSlice().equals("UpToDate".toSlice())) { 207 | r.tcbLevels[i].tcbStatus = EnclaveIdStruct.EnclaveIdStatus.OK; 208 | } else { 209 | r.tcbLevels[i].tcbStatus = EnclaveIdStruct.EnclaveIdStatus.SGX_ENCLAVE_REPORT_ISVSVN_REVOKED; 210 | } 211 | } 212 | } 213 | 214 | function parseTcbInfo(string memory path) public view returns (TCBInfoStruct.TCBInfo memory) { 215 | string memory json = vm.readFile(path); 216 | bytes memory tcbInfo = json.parseRaw("."); 217 | //console2.logBytes(tcbInfo); 218 | TCBInfo memory t = abi.decode(tcbInfo, (TCBInfo)); 219 | TCBInfoStruct.TCBInfo memory r; 220 | r.fmspc = t.fmspc; 221 | r.pceid = t.pceid; 222 | r.tcbLevels = new TCBInfoStruct.TCBLevelObj[](t.tcbLevels.length); 223 | for (uint256 i = 0; i < t.tcbLevels.length; i++) { 224 | r.tcbLevels[i].pcesvn = t.tcbLevels[i].pcesvn; 225 | r.tcbLevels[i].status = t.tcbLevels[i].status; 226 | r.tcbLevels[i].sgxTcbCompSvnArr = t.tcbLevels[i].sgxTcbCompSvnArr; 227 | } 228 | return r; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/BIP32.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.8; 3 | 4 | import {IHash} from "src/hash/IHash.sol"; 5 | import {PKE} from "../src/crypto/encryption.sol"; 6 | import {Utils} from "src/utils/Utils.sol"; 7 | 8 | contract BIP32 { 9 | // Derivation domain separator for BIP39 keys array (Suave seed) 10 | bytes public constant BIP32_DERIVATION_DOMAIN = hex"416E64726F6D6564612073656564"; 11 | // Hardened key indexes start from 0x80000000 (2³¹) and above 12 | uint32 public constant HARDENED_START_INDEX = 0x80000000; 13 | 14 | struct ExtendedKeyAttributes { 15 | // Index of the key in the parent's children 16 | uint32 childNumber; 17 | bytes32 chainCode; 18 | } 19 | struct ExtendedPrivateKey { 20 | bytes32 key; 21 | ExtendedKeyAttributes attributes; 22 | } 23 | struct ExtendedPublicKey { 24 | bytes key; 25 | ExtendedKeyAttributes attributes; 26 | } 27 | 28 | IHash private hasher; 29 | 30 | constructor(IHash _hasher) { 31 | hasher = _hasher; 32 | } 33 | 34 | function split(bytes memory data) public pure returns(bytes32 key, bytes32 chain_code) { 35 | require(data.length == 64, "BIP32: data length must be 64 bytes"); 36 | assembly { 37 | // this starts with 32 index because the first 32 bytes are the length of the bytes array 38 | key := mload(add(data, 32)) // Load first 32 bytes 39 | chain_code := mload(add(data, 64)) // Load second 32 bytes 40 | } 41 | } 42 | 43 | // Based on the context, it generates either the extended private key or extended public key 44 | function newFromSeed(bytes memory seed) public view returns (ExtendedPrivateKey memory) { 45 | // if seed length is not 16 32 or 64 bytes, throw 46 | require(seed.length == 16 || seed.length == 32 || seed.length == 64, "BIP32: seed length must be 16, 32 or 64 bytes"); 47 | bytes memory output = hasher.sha512(abi.encodePacked(Utils.bytesToHexString(abi.encodePacked(BIP32_DERIVATION_DOMAIN, seed)))); 48 | (bytes32 secret_key, bytes32 chain_code) = split(output); 49 | return ExtendedPrivateKey(secret_key, ExtendedKeyAttributes(0, chain_code)); 50 | } 51 | 52 | // derive extended child key pair from a parent seed 53 | function deriveChildKeyPairFromSeed(bytes memory seed, uint32 index) external view returns (ExtendedPrivateKey memory xPriv, ExtendedPublicKey memory xPub) { 54 | ExtendedPrivateKey memory parent = newFromSeed(seed); 55 | return deriveChildKeyPair(parent, index); 56 | } 57 | 58 | // derive extended child key pair from a parent extended private key 59 | function deriveChildKeyPair(ExtendedPrivateKey memory parent, uint32 index) public view returns (ExtendedPrivateKey memory xPriv, ExtendedPublicKey memory xPub) { 60 | // hardened key derivation if index > 0x80000000 61 | if (index >= 0x80000000) { 62 | bytes memory data = abi.encodePacked(hex"00", parent.key, index); 63 | bytes memory output = hasher.sha512(abi.encodePacked(Utils.bytesToHexString(abi.encodePacked(parent.attributes.chainCode, data)))); 64 | (bytes32 secret_key, bytes32 chain_code) = split(output); 65 | ExtendedKeyAttributes memory extKeyAttr = ExtendedKeyAttributes(index, chain_code); 66 | xPriv = ExtendedPrivateKey(secret_key, extKeyAttr); 67 | xPub = ExtendedPublicKey(PKE.derivePubKey(secret_key), extKeyAttr); 68 | } else { 69 | bytes memory data = abi.encodePacked(PKE.derivePubKey(parent.key), index); 70 | bytes memory output = hasher.sha512(abi.encodePacked(Utils.bytesToHexString(abi.encodePacked(parent.attributes.chainCode, data)))); 71 | (bytes32 secret_key, bytes32 chain_code) = split(output); 72 | ExtendedKeyAttributes memory extKeyAttr = ExtendedKeyAttributes(index, chain_code); 73 | xPriv = ExtendedPrivateKey(secret_key, extKeyAttr); 74 | xPub = ExtendedPublicKey(PKE.derivePubKey(secret_key), extKeyAttr); 75 | } 76 | } 77 | 78 | // derive child extended key pairs from a given string path 79 | function deriveChildKeyPairFromPath(bytes memory seed, string memory path) external view returns (ExtendedPrivateKey memory xPriv, ExtendedPublicKey memory xPub) { 80 | bytes memory pathBytes = bytes(path); 81 | // require that the path is not empty and the first character of the path is "m" or "M" 82 | require(pathBytes.length > 0 || pathBytes[0] == bytes1('m') || pathBytes[0] == bytes1('M'), "BIP32: invalid path"); 83 | 84 | ExtendedPrivateKey memory data = newFromSeed(seed); 85 | // if the path is just "m" or "M", return the extended private key and extended public key 86 | if(pathBytes.length <= 2) { 87 | return (data, ExtendedPublicKey(PKE.derivePubKey(data.key), data.attributes)); 88 | } 89 | 90 | uint32 index = 0; 91 | // iterate through the path and derive the child key pair at each level and check the occurrence of ' to define hardened derivation or not 92 | for (uint i = 2; i < pathBytes.length; i++) { 93 | if (pathBytes[i] == bytes1('\'')) { 94 | //check if index + 2147483648 does not exceed uint32 max value 95 | require(index <= 2147483647, "BIP32: invalid path"); 96 | index += 2147483648; 97 | } else if (pathBytes[i] == bytes1('/')) { 98 | (xPriv, xPub) = deriveChildKeyPair(data, index); 99 | data = xPriv; 100 | index = 0; 101 | } else { 102 | // check if the character is not a number and throw instead of converting it to a number 103 | require(uint8(pathBytes[i]) >= 48 && uint8(pathBytes[i]) <= 57, "BIP32: invalid path"); 104 | index = index * 10 + uint32(uint8(pathBytes[i]) - 48); 105 | // TODO further formating checks are probably needed to avoid invalid formats, such as m/000123 or m/' or m/1'' or m// etc... 106 | } 107 | } 108 | (xPriv, xPub) = deriveChildKeyPair(data, index); 109 | } 110 | 111 | // derive child public key from a parent public key 112 | function derivePubKeyFromParentPubKey(ExtendedPublicKey memory parent, uint32 index) external view returns (ExtendedPublicKey memory xPub) { 113 | require(index < 0x80000000, "BIP32: can't derive hardened public keys from a parent public key"); 114 | bytes memory data = abi.encodePacked(parent.key, index); 115 | bytes memory output = hasher.sha512(abi.encodePacked(Utils.bytesToHexString(abi.encodePacked(parent.attributes.chainCode, data)))); 116 | (bytes32 secret_key, bytes32 chain_code) = split(output); 117 | ExtendedKeyAttributes memory extKeyAttr = ExtendedKeyAttributes(index, chain_code); 118 | xPub = ExtendedPublicKey(PKE.derivePubKey(secret_key), extKeyAttr); 119 | } 120 | 121 | // derive public key from a given private key 122 | function derivePubKey(ExtendedPrivateKey memory xPriv) external view returns (ExtendedPublicKey memory xPub) { 123 | return ExtendedPublicKey(PKE.derivePubKey(xPriv.key), xPriv.attributes); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/DcapVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.8; 3 | 4 | import "solidity-stringutils/strings.sol"; 5 | //import { Base64 } from "openzeppelin/utils/Base64.sol"; 6 | //import { BytesUtils } from "ens-contracts/dnssec-oracle/BytesUtils.sol"; 7 | 8 | import { 9 | AutomataDcapV3Attestation, 10 | TCBInfoStruct, 11 | ISigVerifyLib 12 | } from "automata-dcap-v3-attestation/AutomataDcapV3Attestation.sol"; 13 | 14 | import "forge-std/console.sol"; 15 | import "forge-std/Vm.sol"; 16 | import "forge-std/StdJson.sol"; 17 | 18 | contract DcapDemo is AutomataDcapV3Attestation { 19 | constructor(address sigVerifyLib) AutomataDcapV3Attestation(sigVerifyLib) { 20 | toggleLocalReportCheck(); 21 | } 22 | 23 | function _attestationTcbIsValid(TCBInfoStruct.TCBStatus status) internal pure override returns (bool valid) { 24 | // Let's be very permissive! 25 | return ( 26 | status == TCBInfoStruct.TCBStatus.OK || status == TCBInfoStruct.TCBStatus.TCB_SW_HARDENING_NEEDED 27 | || status == TCBInfoStruct.TCBStatus.TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED 28 | || status == TCBInfoStruct.TCBStatus.TCB_CONFIGURATION_NEEDED 29 | || status == TCBInfoStruct.TCBStatus.TCB_OUT_OF_DATE 30 | || status == TCBInfoStruct.TCBStatus.TCB_OUT_OF_DATE_CONFIGURATION_NEEDED 31 | || status == TCBInfoStruct.TCBStatus.TCB_REVOKED 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/IAndromeda.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.8; 3 | 4 | import "solidity-stringutils/strings.sol"; 5 | import {IHash} from "src/hash/IHash.sol"; 6 | 7 | interface IAndromeda is IHash { 8 | function attestSgx(bytes32 appData) external returns (bytes memory); 9 | function verifySgx(address caller, bytes32 appData, bytes memory att) external view returns (bool); 10 | function volatileSet(bytes32 tag, bytes32 value) external; 11 | function volatileGet(bytes32 tag) external returns (bytes32); 12 | function localRandom() external view returns (bytes32); 13 | function sealingKey(bytes32 tag) external view returns (bytes32); 14 | 15 | struct HttpRequest { 16 | string url; 17 | string method; 18 | string[] headers; 19 | bytes body; 20 | bool withFlashbotsSignature; 21 | } 22 | function doHTTPRequest(HttpRequest memory request) external returns (bytes memory); 23 | } 24 | -------------------------------------------------------------------------------- /src/KeyHelper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.13; 2 | 3 | import "./KeyManager.sol"; 4 | 5 | contract AuthHelper { 6 | KeyManager_v0 private keymgr; 7 | address owner; 8 | 9 | constructor(KeyManager_v0 _keymgr) { 10 | keymgr = _keymgr; // Note that we only derive privkey, so we don't require derivekey be called with the address of this (private) contract. The key manager has to be bootstrapped. 11 | owner = msg.sender; // Can we really trust the sender though? The kettle could be spoofing it 12 | } 13 | 14 | function hashSecret() public returns (bytes32) { 15 | // We deploy a different contract as the derivePriv will be different in this scenario! 16 | require(msg.sender == owner); 17 | return keymgr.derivedPriv(); 18 | } 19 | } 20 | 21 | contract KeyHelper { 22 | KeyManager_v0 private keymgr; 23 | AuthHelper private auth_helper; 24 | 25 | constructor(KeyManager_v0 _keymgr) { 26 | keymgr = _keymgr; 27 | auth_helper = new AuthHelper(_keymgr); 28 | } 29 | 30 | /* 31 | To initialize, some Kettle must invoke: 32 | `keymgr.offchain_DeriveKey(auc) -> dPub,v,r,s` 33 | `keymgr.onchain_DeriveKey(auc,dPub,v,r,s)` 34 | */ 35 | function isInitialized() public view returns (bool) { 36 | return pubkey().length != 0; 37 | } 38 | function pubkey() view public returns (bytes memory) { 39 | return keymgr.derivedPub(address(this)); 40 | } 41 | function offchain_pubkey() private returns (bytes memory) { 42 | bytes memory onchainPubkey = pubkey(); 43 | if (onchainPubkey.length != 0) { 44 | return onchainPubkey; 45 | } 46 | 47 | return PKE.derivePubKey(privkey()); 48 | } 49 | function privkey() private returns (bytes32) { 50 | return keymgr.derivedPriv(); 51 | } 52 | function hashSecret() private returns (bytes32) { 53 | return auth_helper.hashSecret(); 54 | } 55 | 56 | function encrypt(bytes memory message, bytes32 r) internal view returns (bytes memory) { 57 | return PKE.encrypt(pubkey(), r, message); 58 | } 59 | function encrypt(string memory message, bytes32 r) internal view returns (string memory) { 60 | return string(encrypt(abi.encodePacked(message), r)); 61 | } 62 | function encrypt(bytes memory message) internal view returns (bytes memory) { 63 | return encrypt(message, keymgr.Suave().localRandom()); 64 | } 65 | function encrypt(string memory message) internal view returns (string memory) { 66 | return string(encrypt(abi.encodePacked(message), keymgr.Suave().localRandom())); 67 | } 68 | 69 | function decrypt(bytes memory ciphertext) internal returns (bytes memory message) { 70 | // Note that it's possible to decrypt an auth_encrypted message 71 | return PKE.decrypt(privkey(), ciphertext); 72 | } 73 | 74 | function auth_encrypt(bytes memory message, bytes32 r) internal returns (bytes memory) { 75 | bytes memory ciphertext = PKE.encrypt(offchain_pubkey(), r, message); 76 | // !!!! We should be using a different key for the hash! Coming soon once we have key derivation 77 | return abi.encode(ciphertext, keccak256(abi.encodePacked(hashSecret(), ciphertext))); 78 | } 79 | function auth_encrypt(string memory message, bytes32 r) internal returns (string memory) { 80 | return string(auth_encrypt(abi.encodePacked(message), r)); 81 | } 82 | function auth_encrypt(bytes memory message) internal returns (bytes memory) { 83 | return auth_encrypt(message, keymgr.Suave().localRandom()); 84 | } 85 | function auth_encrypt(string memory message) internal returns (string memory) { 86 | return string(auth_encrypt(abi.encodePacked(message), keymgr.Suave().localRandom())); 87 | } 88 | 89 | function auth_decrypt(bytes memory message) internal returns (bool, bytes memory) { 90 | (bytes memory ciphertext, bytes32 hash) = abi.decode(message, (bytes, bytes32)); 91 | bool auth_ok = hash == keccak256(abi.encodePacked(hashSecret(), ciphertext)); 92 | if (!auth_ok) { 93 | return (false, ""); 94 | } 95 | return (true, PKE.decrypt(privkey(), ciphertext)); 96 | } 97 | } 98 | 99 | -------------------------------------------------------------------------------- /src/KeyManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.8; 3 | 4 | import {IAndromeda} from "src/IAndromeda.sol"; 5 | import {Secp256k1} from "src/crypto/secp256k1.sol"; 6 | import {PKE, Curve} from "src/crypto/encryption.sol"; 7 | import {BIP32} from "src/BIP32.sol"; 8 | 9 | abstract contract KeyManagerBase { 10 | // This base class provides the functionality of a singleton 11 | // Private Key holder. It allows many applications to share the 12 | // same bootstrapped instance. 13 | 14 | // Anyone can see the master public key 15 | address public xPub; 16 | 17 | // Attestation is now possible using the caller as domain 18 | // separator 19 | function attest(bytes32 appData) public returns (bytes memory) { 20 | bytes32 digest = keccak256(abi.encodePacked(msg.sender, appData)); 21 | 22 | return Secp256k1.sign(uint256(xPriv()), digest); 23 | } 24 | 25 | function verify(address caller, bytes32 appData, bytes memory sig) public view returns (bool) { 26 | bytes32 digest = keccak256(abi.encodePacked(caller, appData)); 27 | return Secp256k1.verify(xPub, digest, sig); 28 | } 29 | 30 | ////////////////////////////////// 31 | // To be implemented by subclasses 32 | ////////////////////////////////// 33 | 34 | // Only the contract in confidential mode can access the 35 | // master private key 36 | function xPriv() internal virtual returns (bytes32); 37 | 38 | function _derivedPriv(address a) internal virtual returns (bytes32); 39 | 40 | // Key derivation for encryption 41 | 42 | // Because we are using hardened derivation, for each 43 | // contract we will need someone to sign it off chain 44 | mapping(address => bytes) public derivedPub; 45 | 46 | function onchain_DeriveKey( 47 | address a, 48 | bytes memory dPub, 49 | // Signature from a valid kettle 50 | bytes memory sig 51 | ) public { 52 | bytes32 digest = keccak256(abi.encodePacked(a, dPub)); 53 | require(Secp256k1.verify(xPub, digest, sig)); 54 | derivedPub[a] = dPub; 55 | } 56 | 57 | function offchain_DeriveKey(address a) public returns (bytes memory dPub, bytes memory sig) { 58 | // TODO followup: disover an API to allow key derivation with an index/path for hardened and non-hardened key derivations 59 | bytes32 dPriv = _derivedPriv(a); 60 | dPub = PKE.derivePubKey(dPriv); 61 | bytes32 digest = keccak256(abi.encodePacked(a, dPub)); 62 | sig = Secp256k1.sign(uint256(xPriv()), digest); 63 | require(Secp256k1.verify(xPub, digest, sig)); 64 | } 65 | } 66 | 67 | contract KeyManager_v0 is KeyManagerBase { 68 | IAndromeda public Suave; 69 | BIP32 public bip32; 70 | 71 | constructor(address _Suave) { 72 | Suave = IAndromeda(_Suave); 73 | bip32 = new BIP32(Suave); 74 | } 75 | 76 | function addressToBIP32HardenedIndex(address a) internal view returns (uint32) { 77 | return uint32(uint256(keccak256(abi.encodePacked(a))) | bip32.HARDENED_START_INDEX()); 78 | } 79 | 80 | // Any contract in confidential mode can request a 81 | // hardened derived key 82 | function _derivedPriv(address a) internal override returns (bytes32) { 83 | bytes32 seed = getSeed(); 84 | uint32 addrIndex = addressToBIP32HardenedIndex(a); 85 | (BIP32.ExtendedPrivateKey memory _xPriv, BIP32.ExtendedPublicKey memory _xPub) = bip32.deriveChildKeyPairFromSeed(abi.encodePacked(seed), addrIndex); 86 | return _xPriv.key; 87 | } 88 | 89 | function derivedPriv() public returns (bytes32) { 90 | return _derivedPriv(msg.sender); 91 | } 92 | 93 | function getSeed() private returns (bytes32) { 94 | return Suave.volatileGet("seed"); 95 | } 96 | 97 | function setSeed(bytes32 seed) private { 98 | Suave.volatileSet("seed", seed); 99 | } 100 | 101 | function xPriv() internal override returns (bytes32) { 102 | return bip32.newFromSeed(abi.encodePacked(getSeed())).key; 103 | } 104 | 105 | // 1. Bootstrap phase 106 | function offchain_Bootstrap() public returns (address _xPub, bytes memory att) { 107 | require(xPub == address(0)); 108 | bytes32 seed = Suave.localRandom(); 109 | bytes32 xPrivKey = bip32.newFromSeed(abi.encodePacked(seed)).key; 110 | _xPub = Secp256k1.deriveAddress(uint256(xPrivKey)); 111 | setSeed(seed); 112 | att = Suave.attestSgx(keccak256(abi.encodePacked("xPub", _xPub))); 113 | } 114 | 115 | 116 | function onchain_Bootstrap(address _xPub, bytes memory att) public { 117 | require(xPub == address(0)); // only once 118 | require(Suave.verifySgx(address(this), keccak256(abi.encodePacked("xPub", _xPub)), att)); 119 | xPub = _xPub; 120 | } 121 | 122 | // 2. New node register phase 123 | // Mapping to nonzero indicates valid Kettle 124 | mapping(address => bytes) registry; 125 | 126 | function offchain_Register() public returns (address addr, bytes memory myPub, bytes memory att) { 127 | require(keccak256(registry[addr]) == keccak256(bytes(""))); 128 | 129 | bytes32 myPriv = Suave.sealingKey("myPriv"); 130 | myPub = PKE.derivePubKey(myPriv); 131 | addr = address(Secp256k1.deriveAddress(uint256(myPriv))); 132 | att = Suave.attestSgx(keccak256(abi.encodePacked("myPub", myPub, addr))); 133 | return (addr, myPub, att); 134 | } 135 | 136 | function onchain_Register(address addr, bytes memory myPub, bytes memory att) public { 137 | require(keccak256(registry[addr]) == keccak256(bytes(""))); 138 | require(Suave.verifySgx(address(this), keccak256(abi.encodePacked("myPub", myPub, addr)), att)); 139 | registry[addr] = myPub; 140 | } 141 | 142 | // 3. Onboard a new node phase 143 | // 3a. A Kettle that already has the key onboards the new node 144 | function offchain_Onboard(address newkettle) public returns (bytes memory ciphertext) { 145 | bytes32 r = Suave.localRandom(); 146 | return PKE.encrypt(registry[newkettle], r, abi.encodePacked(getSeed())); 147 | } 148 | 149 | event Onboard(address addr, bytes ciphertext); 150 | 151 | function onchain_Onboard(address addr, bytes memory ciphertext) public { 152 | // Note: nothing guarantees all ciphertexts on chain are valid 153 | emit Onboard(addr, ciphertext); 154 | } 155 | 156 | function finish_Onboard(bytes memory ciphertext) public { 157 | bytes32 myPriv = Suave.sealingKey("myPriv"); 158 | bytes32 seed = abi.decode(PKE.decrypt(myPriv, ciphertext), (bytes32)); 159 | bytes32 _xPriv = bip32.newFromSeed(abi.encodePacked(seed)).key; 160 | require(Secp256k1.deriveAddress(uint256(_xPriv)) == xPub); 161 | setSeed(seed); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/crypto/EllipticCurve.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @title Elliptic Curve Library 7 | * @dev Library providing arithmetic operations over elliptic curves. 8 | * This library does not check whether the inserted points belong to the curve 9 | * `isOnCurve` function should be used by the library user to check the aforementioned statement. 10 | * @author Witnet Foundation 11 | */ 12 | library EllipticCurve { 13 | // Pre-computed constant for 2 ** 255 14 | uint256 private constant U255_MAX_PLUS_1 = 15 | 57896044618658097711785492504343953926634992332820282019728792003956564819968; 16 | 17 | /// @dev Modular euclidean inverse of a number (mod p). 18 | /// @param _x The number 19 | /// @param _pp The modulus 20 | /// @return q such that x*q = 1 (mod _pp) 21 | function invMod(uint256 _x, uint256 _pp) internal pure returns (uint256) { 22 | require(_x != 0 && _x != _pp && _pp != 0, "Invalid number"); 23 | uint256 q = 0; 24 | uint256 newT = 1; 25 | uint256 r = _pp; 26 | uint256 t; 27 | while (_x != 0) { 28 | t = r / _x; 29 | (q, newT) = (newT, addmod(q, (_pp - mulmod(t, newT, _pp)), _pp)); 30 | (r, _x) = (_x, r - t * _x); 31 | } 32 | 33 | return q; 34 | } 35 | 36 | /// @dev Modular exponentiation, b^e % _pp. 37 | /// Source: https://github.com/androlo/standard-contracts/blob/master/contracts/src/crypto/ECCMath.sol 38 | /// @param _base base 39 | /// @param _exp exponent 40 | /// @param _pp modulus 41 | /// @return r such that r = b**e (mod _pp) 42 | function expMod(uint256 _base, uint256 _exp, uint256 _pp) internal pure returns (uint256) { 43 | require(_pp != 0, "EllipticCurve: modulus is zero"); 44 | 45 | if (_base == 0) return 0; 46 | if (_exp == 0) return 1; 47 | 48 | uint256 r = 1; 49 | uint256 bit = U255_MAX_PLUS_1; 50 | assembly { 51 | for {} gt(bit, 0) {} { 52 | r := mulmod(mulmod(r, r, _pp), exp(_base, iszero(iszero(and(_exp, bit)))), _pp) 53 | r := mulmod(mulmod(r, r, _pp), exp(_base, iszero(iszero(and(_exp, div(bit, 2))))), _pp) 54 | r := mulmod(mulmod(r, r, _pp), exp(_base, iszero(iszero(and(_exp, div(bit, 4))))), _pp) 55 | r := mulmod(mulmod(r, r, _pp), exp(_base, iszero(iszero(and(_exp, div(bit, 8))))), _pp) 56 | bit := div(bit, 16) 57 | } 58 | } 59 | 60 | return r; 61 | } 62 | 63 | /// @dev Converts a point (x, y, z) expressed in Jacobian coordinates to affine coordinates (x', y', 1). 64 | /// @param _x coordinate x 65 | /// @param _y coordinate y 66 | /// @param _z coordinate z 67 | /// @param _pp the modulus 68 | /// @return (x', y') affine coordinates 69 | function toAffine(uint256 _x, uint256 _y, uint256 _z, uint256 _pp) internal pure returns (uint256, uint256) { 70 | uint256 zInv = invMod(_z, _pp); 71 | uint256 zInv2 = mulmod(zInv, zInv, _pp); 72 | uint256 x2 = mulmod(_x, zInv2, _pp); 73 | uint256 y2 = mulmod(_y, mulmod(zInv, zInv2, _pp), _pp); 74 | 75 | return (x2, y2); 76 | } 77 | 78 | /// @dev Derives the y coordinate from a compressed-format point x [[SEC-1]](https://www.secg.org/SEC1-Ver-1.0.pdf). 79 | /// @param _prefix parity byte (0x02 even, 0x03 odd) 80 | /// @param _x coordinate x 81 | /// @param _aa constant of curve 82 | /// @param _bb constant of curve 83 | /// @param _pp the modulus 84 | /// @return y coordinate y 85 | function deriveY(uint8 _prefix, uint256 _x, uint256 _aa, uint256 _bb, uint256 _pp) 86 | internal 87 | pure 88 | returns (uint256) 89 | { 90 | require(_prefix == 0x02 || _prefix == 0x03, "EllipticCurve:innvalid compressed EC point prefix"); 91 | 92 | // x^3 + ax + b 93 | uint256 y2 = addmod(mulmod(_x, mulmod(_x, _x, _pp), _pp), addmod(mulmod(_x, _aa, _pp), _bb, _pp), _pp); 94 | y2 = expMod(y2, (_pp + 1) / 4, _pp); 95 | // uint256 cmp = yBit ^ y_ & 1; 96 | uint256 y = (y2 + _prefix) % 2 == 0 ? y2 : _pp - y2; 97 | 98 | return y; 99 | } 100 | 101 | /// @dev Check whether point (x,y) is on curve defined by a, b, and _pp. 102 | /// @param _x coordinate x of P1 103 | /// @param _y coordinate y of P1 104 | /// @param _aa constant of curve 105 | /// @param _bb constant of curve 106 | /// @param _pp the modulus 107 | /// @return true if x,y in the curve, false else 108 | function isOnCurve(uint256 _x, uint256 _y, uint256 _aa, uint256 _bb, uint256 _pp) internal pure returns (bool) { 109 | if (0 == _x || _x >= _pp || 0 == _y || _y >= _pp) { 110 | return false; 111 | } 112 | // y^2 113 | uint256 lhs = mulmod(_y, _y, _pp); 114 | // x^3 115 | uint256 rhs = mulmod(mulmod(_x, _x, _pp), _x, _pp); 116 | if (_aa != 0) { 117 | // x^3 + a*x 118 | rhs = addmod(rhs, mulmod(_x, _aa, _pp), _pp); 119 | } 120 | if (_bb != 0) { 121 | // x^3 + a*x + b 122 | rhs = addmod(rhs, _bb, _pp); 123 | } 124 | 125 | return lhs == rhs; 126 | } 127 | 128 | /// @dev Calculate inverse (x, -y) of point (x, y). 129 | /// @param _x coordinate x of P1 130 | /// @param _y coordinate y of P1 131 | /// @param _pp the modulus 132 | /// @return (x, -y) 133 | function ecInv(uint256 _x, uint256 _y, uint256 _pp) internal pure returns (uint256, uint256) { 134 | return (_x, (_pp - _y) % _pp); 135 | } 136 | 137 | /// @dev Add two points (x1, y1) and (x2, y2) in affine coordinates. 138 | /// @param _x1 coordinate x of P1 139 | /// @param _y1 coordinate y of P1 140 | /// @param _x2 coordinate x of P2 141 | /// @param _y2 coordinate y of P2 142 | /// @param _aa constant of the curve 143 | /// @param _pp the modulus 144 | /// @return (qx, qy) = P1+P2 in affine coordinates 145 | function ecAdd(uint256 _x1, uint256 _y1, uint256 _x2, uint256 _y2, uint256 _aa, uint256 _pp) 146 | internal 147 | pure 148 | returns (uint256, uint256) 149 | { 150 | uint256 x = 0; 151 | uint256 y = 0; 152 | uint256 z = 0; 153 | 154 | // Double if x1==x2 else add 155 | if (_x1 == _x2) { 156 | // y1 = -y2 mod p 157 | if (addmod(_y1, _y2, _pp) == 0) { 158 | return (0, 0); 159 | } else { 160 | // P1 = P2 161 | (x, y, z) = jacDouble(_x1, _y1, 1, _aa, _pp); 162 | } 163 | } else { 164 | (x, y, z) = jacAdd(_x1, _y1, 1, _x2, _y2, 1, _pp); 165 | } 166 | // Get back to affine 167 | return toAffine(x, y, z, _pp); 168 | } 169 | 170 | /// @dev Substract two points (x1, y1) and (x2, y2) in affine coordinates. 171 | /// @param _x1 coordinate x of P1 172 | /// @param _y1 coordinate y of P1 173 | /// @param _x2 coordinate x of P2 174 | /// @param _y2 coordinate y of P2 175 | /// @param _aa constant of the curve 176 | /// @param _pp the modulus 177 | /// @return (qx, qy) = P1-P2 in affine coordinates 178 | function ecSub(uint256 _x1, uint256 _y1, uint256 _x2, uint256 _y2, uint256 _aa, uint256 _pp) 179 | internal 180 | pure 181 | returns (uint256, uint256) 182 | { 183 | // invert square 184 | (uint256 x, uint256 y) = ecInv(_x2, _y2, _pp); 185 | // P1-square 186 | return ecAdd(_x1, _y1, x, y, _aa, _pp); 187 | } 188 | 189 | /// @dev Multiply point (x1, y1, z1) times d in affine coordinates. 190 | /// @param _k scalar to multiply 191 | /// @param _x coordinate x of P1 192 | /// @param _y coordinate y of P1 193 | /// @param _aa constant of the curve 194 | /// @param _pp the modulus 195 | /// @return (qx, qy) = d*P in affine coordinates 196 | function ecMul(uint256 _k, uint256 _x, uint256 _y, uint256 _aa, uint256 _pp) 197 | internal 198 | pure 199 | returns (uint256, uint256) 200 | { 201 | // Jacobian multiplication 202 | (uint256 x1, uint256 y1, uint256 z1) = jacMul(_k, _x, _y, 1, _aa, _pp); 203 | // Get back to affine 204 | return toAffine(x1, y1, z1, _pp); 205 | } 206 | 207 | /// @dev Adds two points (x1, y1, z1) and (x2 y2, z2). 208 | /// @param _x1 coordinate x of P1 209 | /// @param _y1 coordinate y of P1 210 | /// @param _z1 coordinate z of P1 211 | /// @param _x2 coordinate x of square 212 | /// @param _y2 coordinate y of square 213 | /// @param _z2 coordinate z of square 214 | /// @param _pp the modulus 215 | /// @return (qx, qy, qz) P1+square in Jacobian 216 | function jacAdd(uint256 _x1, uint256 _y1, uint256 _z1, uint256 _x2, uint256 _y2, uint256 _z2, uint256 _pp) 217 | internal 218 | pure 219 | returns (uint256, uint256, uint256) 220 | { 221 | if (_x1 == 0 && _y1 == 0) return (_x2, _y2, _z2); 222 | if (_x2 == 0 && _y2 == 0) return (_x1, _y1, _z1); 223 | 224 | // We follow the equations described in https://pdfs.semanticscholar.org/5c64/29952e08025a9649c2b0ba32518e9a7fb5c2.pdf Section 5 225 | uint256[4] memory zs; // z1^2, z1^3, z2^2, z2^3 226 | zs[0] = mulmod(_z1, _z1, _pp); 227 | zs[1] = mulmod(_z1, zs[0], _pp); 228 | zs[2] = mulmod(_z2, _z2, _pp); 229 | zs[3] = mulmod(_z2, zs[2], _pp); 230 | 231 | // u1, s1, u2, s2 232 | zs = [mulmod(_x1, zs[2], _pp), mulmod(_y1, zs[3], _pp), mulmod(_x2, zs[0], _pp), mulmod(_y2, zs[1], _pp)]; 233 | 234 | // In case of zs[0] == zs[2] && zs[1] == zs[3], double function should be used 235 | require(zs[0] != zs[2] || zs[1] != zs[3], "Use jacDouble function instead"); 236 | 237 | uint256[4] memory hr; 238 | //h 239 | hr[0] = addmod(zs[2], _pp - zs[0], _pp); 240 | //r 241 | hr[1] = addmod(zs[3], _pp - zs[1], _pp); 242 | //h^2 243 | hr[2] = mulmod(hr[0], hr[0], _pp); 244 | // h^3 245 | hr[3] = mulmod(hr[2], hr[0], _pp); 246 | // qx = -h^3 -2u1h^2+r^2 247 | uint256 qx = addmod(mulmod(hr[1], hr[1], _pp), _pp - hr[3], _pp); 248 | qx = addmod(qx, _pp - mulmod(2, mulmod(zs[0], hr[2], _pp), _pp), _pp); 249 | // qy = -s1*z1*h^3+r(u1*h^2 -x^3) 250 | uint256 qy = mulmod(hr[1], addmod(mulmod(zs[0], hr[2], _pp), _pp - qx, _pp), _pp); 251 | qy = addmod(qy, _pp - mulmod(zs[1], hr[3], _pp), _pp); 252 | // qz = h*z1*z2 253 | uint256 qz = mulmod(hr[0], mulmod(_z1, _z2, _pp), _pp); 254 | return (qx, qy, qz); 255 | } 256 | 257 | /// @dev Doubles a points (x, y, z). 258 | /// @param _x coordinate x of P1 259 | /// @param _y coordinate y of P1 260 | /// @param _z coordinate z of P1 261 | /// @param _aa the a scalar in the curve equation 262 | /// @param _pp the modulus 263 | /// @return (qx, qy, qz) 2P in Jacobian 264 | function jacDouble(uint256 _x, uint256 _y, uint256 _z, uint256 _aa, uint256 _pp) 265 | internal 266 | pure 267 | returns (uint256, uint256, uint256) 268 | { 269 | if (_z == 0) return (_x, _y, _z); 270 | 271 | // We follow the equations described in https://pdfs.semanticscholar.org/5c64/29952e08025a9649c2b0ba32518e9a7fb5c2.pdf Section 5 272 | // Note: there is a bug in the paper regarding the m parameter, M=3*(x1^2)+a*(z1^4) 273 | // x, y, z at this point represent the squares of _x, _y, _z 274 | uint256 x = mulmod(_x, _x, _pp); //x1^2 275 | uint256 y = mulmod(_y, _y, _pp); //y1^2 276 | uint256 z = mulmod(_z, _z, _pp); //z1^2 277 | 278 | // s 279 | uint256 s = mulmod(4, mulmod(_x, y, _pp), _pp); 280 | // m 281 | uint256 m = addmod(mulmod(3, x, _pp), mulmod(_aa, mulmod(z, z, _pp), _pp), _pp); 282 | 283 | // x, y, z at this point will be reassigned and rather represent qx, qy, qz from the paper 284 | // This allows to reduce the gas cost and stack footprint of the algorithm 285 | // qx 286 | x = addmod(mulmod(m, m, _pp), _pp - addmod(s, s, _pp), _pp); 287 | // qy = -8*y1^4 + M(S-T) 288 | y = addmod(mulmod(m, addmod(s, _pp - x, _pp), _pp), _pp - mulmod(8, mulmod(y, y, _pp), _pp), _pp); 289 | // qz = 2*y1*z1 290 | z = mulmod(2, mulmod(_y, _z, _pp), _pp); 291 | 292 | return (x, y, z); 293 | } 294 | 295 | /// @dev Multiply point (x, y, z) times d. 296 | /// @param _d scalar to multiply 297 | /// @param _x coordinate x of P1 298 | /// @param _y coordinate y of P1 299 | /// @param _z coordinate z of P1 300 | /// @param _aa constant of curve 301 | /// @param _pp the modulus 302 | /// @return (qx, qy, qz) d*P1 in Jacobian 303 | function jacMul(uint256 _d, uint256 _x, uint256 _y, uint256 _z, uint256 _aa, uint256 _pp) 304 | internal 305 | pure 306 | returns (uint256, uint256, uint256) 307 | { 308 | // Early return in case that `_d == 0` 309 | if (_d == 0) { 310 | return (_x, _y, _z); 311 | } 312 | 313 | uint256 remaining = _d; 314 | uint256 qx = 0; 315 | uint256 qy = 0; 316 | uint256 qz = 1; 317 | 318 | // Double and add algorithm 319 | while (remaining != 0) { 320 | if ((remaining & 1) != 0) { 321 | (qx, qy, qz) = jacAdd(qx, qy, qz, _x, _y, _z, _pp); 322 | } 323 | remaining = remaining / 2; 324 | (_x, _y, _z) = jacDouble(_x, _y, _z, _aa, _pp); 325 | } 326 | return (qx, qy, qz); 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/crypto/bn256g1.sol: -------------------------------------------------------------------------------- 1 | // This file is MIT Licensed. 2 | // 3 | // From: https://gist.githubusercontent.com/chriseth/f9be9d9391efc5beb9704255a8e2989d/raw/4d0fb90847df1d4e04d507019031888df8372239/snarktest.solidity 4 | // 5 | // Copyright 2017 Christian Reitwiessner 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | 10 | pragma solidity ^0.8.0; 11 | 12 | library Curve { 13 | // p = p(u) = 36u^4 + 36u^3 + 24u^2 + 6u + 1 14 | uint256 internal constant FIELD_ORDER = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; 15 | 16 | // Number of elements in the field (often called `q`) 17 | // n = n(u) = 36u^4 + 36u^3 + 18u^2 + 6u + 1 18 | uint256 internal constant GEN_ORDER = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; 19 | 20 | uint256 internal constant CURVE_B = 3; 21 | 22 | // a = (p+1) / 4 23 | uint256 internal constant CURVE_A = 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52; 24 | 25 | struct G1Point { 26 | uint256 X; 27 | uint256 Y; 28 | } 29 | 30 | // Encoding of field elements is: X[0] * z + X[1] 31 | struct G2Point { 32 | uint256[2] X; 33 | uint256[2] Y; 34 | } 35 | 36 | // (P+1) / 4 37 | function A() internal pure returns (uint256) { 38 | return CURVE_A; 39 | } 40 | 41 | function P() internal pure returns (uint256) { 42 | return FIELD_ORDER; 43 | } 44 | 45 | function N() internal pure returns (uint256) { 46 | return GEN_ORDER; 47 | } 48 | 49 | function P1() internal pure returns (G1Point memory r) { 50 | return G1Point(1, 2); 51 | } 52 | 53 | function HashToPoint(uint256 s) internal view returns (G1Point memory r) { 54 | uint256 beta = 0; 55 | uint256 y = 0; 56 | 57 | // XXX: Gen Order (n) or Field Order (p) ? 58 | uint256 x = s % GEN_ORDER; 59 | 60 | while (true) { 61 | (beta, y) = FindYforX(x); 62 | 63 | // y^2 == beta 64 | if (beta == mulmod(y, y, FIELD_ORDER)) { 65 | return G1Point(x, y); 66 | } 67 | 68 | x = addmod(x, 1, FIELD_ORDER); 69 | } 70 | } 71 | 72 | /** 73 | * Given X, find Y 74 | * 75 | * where y = sqrt(x^3 + b) 76 | * 77 | * Returns: (x^3 + b), y 78 | */ 79 | function FindYforX(uint256 x) internal view returns (uint256, uint256) { 80 | // beta = (x^3 + b) % p 81 | uint256 beta = addmod(mulmod(mulmod(x, x, FIELD_ORDER), x, FIELD_ORDER), CURVE_B, FIELD_ORDER); 82 | 83 | // y^2 = x^3 + b 84 | // this acts like: y = sqrt(beta) 85 | uint256 y = expMod(beta, CURVE_A, FIELD_ORDER); 86 | 87 | return (beta, y); 88 | } 89 | 90 | // a - b = c; 91 | function submod(uint256 a, uint256 b) internal pure returns (uint256) { 92 | uint256 a_nn; 93 | 94 | if (a > b) { 95 | a_nn = a; 96 | } else { 97 | a_nn = a + GEN_ORDER; 98 | } 99 | 100 | return addmod(a_nn - b, 0, GEN_ORDER); 101 | } 102 | 103 | function expMod(uint256 _base, uint256 _exponent, uint256 _modulus) internal view returns (uint256 retval) { 104 | bool success; 105 | uint256[1] memory output; 106 | uint256[6] memory input; 107 | input[0] = 0x20; // baseLen = new(big.Int).SetBytes(getData(input, 0, 32)) 108 | input[1] = 0x20; // expLen = new(big.Int).SetBytes(getData(input, 32, 32)) 109 | input[2] = 0x20; // modLen = new(big.Int).SetBytes(getData(input, 64, 32)) 110 | input[3] = _base; 111 | input[4] = _exponent; 112 | input[5] = _modulus; 113 | assembly { 114 | success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20) 115 | // Use "invalid" to make gas estimation work 116 | switch success 117 | case 0 { invalid() } 118 | } 119 | require(success); 120 | return output[0]; 121 | } 122 | 123 | function P2() internal pure returns (G2Point memory) { 124 | return G2Point( 125 | [ 126 | 11559732032986387107991004021392285783925812861821192530917403151452391805634, 127 | 10857046999023057135944570762232829481370756359578518086990519993285655852781 128 | ], 129 | [ 130 | 4082367875863433681332203403145435568316851327593401208105741076214120093531, 131 | 8495653923123431417604973247489272438418190587263600148770280649306958101930 132 | ] 133 | ); 134 | } 135 | 136 | function g1neg(G1Point memory p) internal pure returns (G1Point memory) { 137 | // The prime q in the base field F_q for G1 138 | uint256 q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; 139 | if (p.X == 0 && p.Y == 0) { 140 | return G1Point(0, 0); 141 | } 142 | return G1Point(p.X, q - (p.Y % q)); 143 | } 144 | 145 | function g1add(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) { 146 | uint256[4] memory input; 147 | input[0] = p1.X; 148 | input[1] = p1.Y; 149 | input[2] = p2.X; 150 | input[3] = p2.Y; 151 | bool success; 152 | assembly { 153 | success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) 154 | // Use "invalid" to make gas estimation work 155 | switch success 156 | case 0 { invalid() } 157 | } 158 | require(success); 159 | } 160 | 161 | function g1mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) { 162 | uint256[3] memory input; 163 | input[0] = p.X; 164 | input[1] = p.Y; 165 | input[2] = s; 166 | bool success; 167 | assembly { 168 | success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) 169 | // Use "invalid" to make gas estimation work 170 | switch success 171 | case 0 { invalid() } 172 | } 173 | require(success); 174 | } 175 | 176 | function pairing(G1Point[] memory p1, G2Point[] memory p2) internal view returns (bool) { 177 | require(p1.length == p2.length); 178 | uint256 elements = p1.length; 179 | uint256 inputSize = elements * 6; 180 | uint256[] memory input = new uint256[](inputSize); 181 | for (uint256 i = 0; i < elements; i++) { 182 | input[i * 6 + 0] = p1[i].X; 183 | input[i * 6 + 1] = p1[i].Y; 184 | input[i * 6 + 2] = p2[i].X[0]; 185 | input[i * 6 + 3] = p2[i].X[1]; 186 | input[i * 6 + 4] = p2[i].Y[0]; 187 | input[i * 6 + 5] = p2[i].Y[1]; 188 | } 189 | uint256[1] memory out; 190 | bool success; 191 | assembly { 192 | success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) 193 | // Use "invalid" to make gas estimation work 194 | switch success 195 | case 0 { invalid() } 196 | } 197 | require(success); 198 | return out[0] != 0; 199 | } 200 | 201 | function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2) 202 | internal 203 | view 204 | returns (bool) 205 | { 206 | G1Point[] memory p1 = new G1Point[](2); 207 | G2Point[] memory p2 = new G2Point[](2); 208 | p1[0] = a1; 209 | p1[1] = b1; 210 | p2[0] = a2; 211 | p2[1] = b2; 212 | return pairing(p1, p2); 213 | } 214 | 215 | function pairingProd3( 216 | G1Point memory a1, 217 | G2Point memory a2, 218 | G1Point memory b1, 219 | G2Point memory b2, 220 | G1Point memory c1, 221 | G2Point memory c2 222 | ) internal view returns (bool) { 223 | G1Point[] memory p1 = new G1Point[](3); 224 | G2Point[] memory p2 = new G2Point[](3); 225 | p1[0] = a1; 226 | p1[1] = b1; 227 | p1[2] = c1; 228 | p2[0] = a2; 229 | p2[1] = b2; 230 | p2[2] = c2; 231 | return pairing(p1, p2); 232 | } 233 | 234 | function pairingProd4( 235 | G1Point memory a1, 236 | G2Point memory a2, 237 | G1Point memory b1, 238 | G2Point memory b2, 239 | G1Point memory c1, 240 | G2Point memory c2, 241 | G1Point memory d1, 242 | G2Point memory d2 243 | ) internal view returns (bool) { 244 | G1Point[] memory p1 = new G1Point[](4); 245 | G2Point[] memory p2 = new G2Point[](4); 246 | p1[0] = a1; 247 | p1[1] = b1; 248 | p1[2] = c1; 249 | p1[3] = d1; 250 | p2[0] = a2; 251 | p2[1] = b2; 252 | p2[2] = c2; 253 | p2[3] = d2; 254 | return pairing(p1, p2); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/crypto/encryption.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./bn256g1.sol"; 5 | import "./secp256k1.sol"; 6 | import "./EllipticCurve.sol"; 7 | 8 | library PKE { 9 | function derivePubKey(bytes32 secretKey) internal view returns (bytes memory) { 10 | Curve.G1Point memory p = Curve.g1mul(Curve.P1(), uint256(secretKey)); 11 | return abi.encodePacked(p.X, p.Y); 12 | } 13 | 14 | function encrypt(bytes memory pubkey, bytes32 r, bytes memory message) internal view returns (bytes memory) { 15 | (uint256 gx, uint256 gy) = abi.decode(pubkey, (uint256, uint256)); 16 | Curve.G1Point memory pub = Curve.G1Point(gx, gy); 17 | return _encrypt(pub, r, message); 18 | } 19 | 20 | // Improvised... replace with ECIES or similar later? 21 | function _encrypt(Curve.G1Point memory pub, bytes32 r, bytes memory m) internal view returns (bytes memory) { 22 | Curve.G1Point memory sk = Curve.g1mul(pub, uint256(r)); 23 | Curve.G1Point memory mypub = Curve.g1mul(Curve.P1(), uint256(r)); 24 | 25 | // Encrypt using the curve point as secret key 26 | bytes32 key = bytes32(sk.X); 27 | (bytes memory ciphertext, bytes32 tag) = SimpleEncryption.encrypt(key, m); 28 | return abi.encode(mypub.X, mypub.Y, ciphertext, tag); 29 | } 30 | 31 | function decrypt(bytes32 secretKey, bytes memory ciph) internal view returns (bytes memory) { 32 | (uint256 X, uint256 Y, bytes memory ciphertext, bytes32 tag) = 33 | abi.decode(ciph, (uint256, uint256, bytes, bytes32)); 34 | Curve.G1Point memory mypub = Curve.G1Point(X, Y); 35 | 36 | Curve.G1Point memory sk = Curve.g1mul(mypub, uint256(secretKey)); 37 | // Decrypt using the curve point as secret key 38 | bytes32 key = bytes32(sk.X); 39 | bytes memory message = SimpleEncryption.decrypt(key, ciphertext, tag); 40 | return message; 41 | } 42 | } 43 | 44 | library SimpleEncryption { 45 | // This function produces a masking stream using a PRF (in this case, keccak256) 46 | function produceMaskingStream(bytes32 key, uint256 counter) private pure returns (bytes32) { 47 | return keccak256(abi.encodePacked(key, counter)); 48 | } 49 | 50 | function encrypt(bytes32 key, bytes memory message) internal pure returns (bytes memory ciphertext, bytes32 tag) { 51 | require(message.length % 32 == 0, "Message length should be a multiple of 32"); 52 | 53 | ciphertext = new bytes(message.length); 54 | 55 | // Encrypt the message using the masking stream 56 | for (uint256 i = 0; i < message.length; i += 32) { 57 | bytes32 mask = produceMaskingStream(key, i / 32); 58 | for (uint256 j = 0; j < 32; j++) { 59 | ciphertext[i + j] = message[i + j] ^ mask[j]; 60 | } 61 | } 62 | 63 | // Compute the MAC 64 | tag = keccak256(abi.encodePacked(key, ciphertext)); 65 | 66 | return (ciphertext, tag); 67 | } 68 | 69 | function decrypt(bytes32 key, bytes memory ciphertext, bytes32 tag) internal pure returns (bytes memory) { 70 | require(ciphertext.length % 32 == 0, "Ciphertext length should be a multiple of 32"); 71 | 72 | // Verify the MAC 73 | require(keccak256(abi.encodePacked(key, ciphertext)) == tag, "Invalid tag, decryption failed"); 74 | 75 | bytes memory decryptedMessage = new bytes(ciphertext.length); 76 | 77 | // Decrypt the message using the masking stream 78 | for (uint256 i = 0; i < ciphertext.length; i += 32) { 79 | bytes32 mask = produceMaskingStream(key, i / 32); 80 | for (uint256 j = 0; j < 32; j++) { 81 | decryptedMessage[i + j] = ciphertext[i + j] ^ mask[j]; 82 | } 83 | } 84 | 85 | return decryptedMessage; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/crypto/secp256k1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "./EllipticCurve.sol"; 4 | 5 | library Secp256k1 { 6 | uint256 internal constant GX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798; 7 | uint256 internal constant GY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8; 8 | uint256 internal constant AA = 0; 9 | uint256 internal constant BB = 7; 10 | uint256 internal constant PP = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F; 11 | uint256 internal constant NN = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; 12 | 13 | function derivePubKey(uint256 privKey) internal pure returns (uint256 qx, uint256 qy) { 14 | (qx, qy) = EllipticCurve.ecMul(privKey, GX, GY, AA, PP); 15 | } 16 | 17 | function verify(address signer, bytes32 digest, bytes memory sig) internal pure returns (bool) { 18 | uint8 v; 19 | bytes32 r; 20 | bytes32 s; 21 | assembly { 22 | v := mload(add(sig, 1)) 23 | r := mload(add(sig, 33)) 24 | s := mload(add(sig, 65)) 25 | } 26 | return signer == ecrecover(digest, v, r, s); 27 | } 28 | 29 | function sign(uint256 privateKey, bytes32 digest) internal pure returns (bytes memory) { 30 | // Step 0: Deterministic choice of k 31 | // See RFC 6979 32 | // TODO: replace this with wiser choice 33 | uint256 k = uint256( 34 | sha256( 35 | abi.encodePacked( 36 | uint256(0x0101010101010101010101010101010101010101010101010101010101010101), uint8(0), digest 37 | ) 38 | ) 39 | ); 40 | 41 | // Step 1: Ephemeral Key Pair Generation 42 | (uint256 x1, uint256 y1) = EllipticCurve.ecMul(k, GX, GY, AA, PP); // Ephemeral internal key 43 | 44 | // Step 2: Calculate r and s 45 | uint256 r = x1 % PP; 46 | require(r != 0, "Invalid r value"); 47 | 48 | uint256 k_inv = EllipticCurve.invMod(k, NN); // Modular inverse of k 49 | uint256 hashInt = uint256(digest); 50 | uint256 s = mulmod(k_inv, addmod(hashInt, mulmod(r, privateKey, NN), NN), NN); 51 | require(s != 0, "Invalid s value"); 52 | 53 | // Step 3: Determine recovery id (v) 54 | uint8 v; 55 | uint256 y_parity = y1 % 2; 56 | 57 | // Typically, 27 is added to v for legacy reasons, and an additional 2 if the chain ID is included 58 | // Chain ID is omitted in this example 59 | if (y_parity == 0) { 60 | v = 27; 61 | } else { 62 | v = 28; 63 | } 64 | return abi.encodePacked(v, bytes32(r), bytes32(s)); 65 | } 66 | 67 | function deriveAddress(uint256 privKey) internal pure returns (address) { 68 | (uint256 qx, uint256 qy) = derivePubKey(privKey); 69 | bytes memory ser = bytes.concat(bytes32(qx), bytes32(qy)); 70 | return address(uint160(uint256(keccak256(ser)))); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/examples/Auction.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.13; 2 | 3 | import "../crypto/secp256k1.sol"; 4 | import "../crypto/encryption.sol"; 5 | import "../KeyManager.sol"; 6 | 7 | // We'l start with a Leaky second price auction 8 | contract LeakyAuction { 9 | // All Deposits in before trade 10 | mapping(address => uint256) public balance; 11 | 12 | constructor() { 13 | auctionEndTime = 2; 14 | } 15 | 16 | // Concluding time (block height) 17 | uint256 public auctionEndTime; 18 | 19 | // To be set after the auction concludes (this is the output) 20 | uint256 public secondPrice; 21 | 22 | // Store the Bids (in plaintext for now) 23 | mapping(address => uint256) public bids; 24 | address[] public bidders; 25 | 26 | // Accept a bid in plaintext 27 | event BidPlaced(address sender, uint256 bid); 28 | 29 | function submitBid(uint256 bid) public virtual { 30 | require(block.number <= auctionEndTime); 31 | require(bids[msg.sender] == 0); 32 | bids[msg.sender] = bid; 33 | bidders.push(msg.sender); 34 | emit BidPlaced(msg.sender, bid); 35 | } 36 | 37 | // Wrap up the auction and compute the 2nd price 38 | event Concluded(uint256 secondPrice); 39 | 40 | function conclude() public { 41 | require(block.number > auctionEndTime); 42 | require(secondPrice == 0); 43 | // Compute the second price 44 | uint256 best = 0; 45 | for (uint256 i = 0; i < bidders.length; i++) { 46 | uint256 bid = bids[bidders[i]]; 47 | if (bid > best) { 48 | secondPrice = best; 49 | best = bid; 50 | } else if (bid > secondPrice) { 51 | secondPrice = bid; 52 | } 53 | } 54 | emit Concluded(secondPrice); 55 | } 56 | } 57 | 58 | // Now we'll fix the auction using the TEE coprocessor 59 | contract SealedAuction is LeakyAuction { 60 | KeyManager_v0 keymgr; 61 | 62 | constructor(KeyManager_v0 _keymgr, uint256 delay_blocks) { 63 | keymgr = _keymgr; 64 | auctionEndTime = block.number + delay_blocks; 65 | } 66 | 67 | /* 68 | To initialize `SealedAuction auc`, some Kettle must invoke: 69 | `keymgr.offchain_DeriveKey(auc) -> dPub,v,r,s` 70 | `keymgr.onchain_DeriveKey(auc,dPub,v,r,s)` 71 | */ 72 | function isInitialized() public view returns (bool) { 73 | return keymgr.derivedPub(address(this)).length != 0; 74 | } 75 | 76 | // Now we will store encrypted orders 77 | mapping(address => bytes) encBids; 78 | 79 | event EncryptedBidPlaced(address sender); 80 | 81 | function submitEncrypted(bytes memory ciphertext) public { 82 | require(block.number <= auctionEndTime); 83 | require(encBids[msg.sender].length == 0); 84 | encBids[msg.sender] = ciphertext; 85 | bidders.push(msg.sender); 86 | emit EncryptedBidPlaced(msg.sender); 87 | } 88 | 89 | // Helper function for a client to run locally 90 | function encryptOrder(uint256 bid, bytes32 r) public view returns (bytes memory) { 91 | return PKE.encrypt(keymgr.derivedPub(address(this)), r, abi.encodePacked(bid)); 92 | } 93 | 94 | // Called by any kettle to compute the second price 95 | function offchain_Finalize() public returns (uint256 secondPrice_, bytes memory att) { 96 | require(block.number > auctionEndTime); 97 | 98 | // Store our local key 99 | bytes32 dPriv = keymgr.derivedPriv(); 100 | 101 | // Decrypt each bid and compute second price 102 | uint256 best = 0; 103 | for (uint256 i = 0; i < bidders.length; i++) { 104 | bytes memory ciphertext = encBids[bidders[i]]; 105 | uint256 bid = abi.decode(PKE.decrypt(dPriv, ciphertext), (uint256)); 106 | if (bid > best) { 107 | secondPrice_ = best; 108 | best = bid; 109 | } else if (bid > secondPrice_) { 110 | secondPrice_ = bid; 111 | } 112 | } 113 | 114 | // Use the key manager attest 115 | att = keymgr.attest(bytes32(secondPrice_)); 116 | } 117 | 118 | // Post the second price on-chain 119 | event Finalized(uint256 secondPrice); 120 | 121 | function onchain_Finalize(uint256 secondPrice_, bytes memory sig) public { 122 | require(block.number > auctionEndTime); 123 | require(keymgr.verify(address(this), bytes32(secondPrice_), sig)); 124 | secondPrice = secondPrice_; 125 | emit Finalized(secondPrice); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/examples/RedisConfidentialStore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.13; 2 | 3 | import "../../lib/revm-services/ExternalServices.sol"; 4 | import "../crypto/secp256k1.sol"; 5 | import "../crypto/encryption.sol"; 6 | 7 | import "../KeyHelper.sol"; 8 | 9 | struct Bundle { 10 | uint256 height; 11 | bytes transaction; 12 | uint256 profit; 13 | } 14 | 15 | // TODO: should probably be gated behind onlyOwner 16 | contract RedisStore is KeyHelper, WithRedis, WithRedisPubsub { 17 | bytes32 r; 18 | constructor(KeyManager_v0 keymgr, bytes32 _r) KeyHelper(keymgr) WithRedis() WithRedisPubsub() { 19 | r = _r; 20 | } 21 | 22 | function set(string memory key, bytes memory data) public { 23 | redis().set(_format_key(key), auth_encrypt(data)); 24 | } 25 | 26 | function get(string memory key) public returns (bool, bytes memory) { 27 | bytes memory data = redis().get(_format_key(key)); 28 | if (data.length == 0) { 29 | return (false, ""); 30 | } 31 | return auth_decrypt(data); 32 | } 33 | 34 | function publish(string memory topic, bytes memory message) public { 35 | pubsub().publish(_format_topic(topic), auth_encrypt(message)); 36 | } 37 | 38 | function subscribe(string memory topic) public { 39 | pubsub().subscribe(_format_topic(topic)); 40 | } 41 | 42 | function unsubscribe(string memory topic) public { 43 | pubsub().unsubscribe(_format_topic(topic)); 44 | } 45 | 46 | function get_message(string memory topic) public returns (bool /* msg present */, bool /* auth ok */, bytes memory /* message */) { 47 | // Requires subscribe is called first (per kettle!) 48 | bytes memory message = pubsub().get_message(_format_topic(topic)); 49 | if (message.length == 0) { 50 | return (false, false, ""); 51 | } 52 | (bool auth_ok, bytes memory decrypted_msg) = auth_decrypt(message); 53 | return (true, auth_ok, decrypted_msg); 54 | } 55 | 56 | function _format_key(string memory key) private view returns (string memory) { 57 | return string(abi.encodePacked(keccak256(abi.encodePacked(r, key)))); 58 | } 59 | function _format_topic(string memory topic) private view returns (string memory) { 60 | return string(abi.encodePacked(keccak256(abi.encodePacked(r, topic)))); 61 | } 62 | } 63 | 64 | contract BundleConfidentialStore is KeyHelper { 65 | RedisStore private redis; 66 | constructor(KeyManager_v0 _keymgr, address[] memory _allowedContracts) KeyHelper(_keymgr) { 67 | redis = new RedisStore(_keymgr, keccak256(abi.encodePacked(tx.origin, msg.sender, block.number))); 68 | for (uint i = 0; i < _allowedContracts.length; i++) { 69 | allowedContracts[_allowedContracts[i]] = true; 70 | } 71 | } 72 | 73 | /* Encrypt yourself using the pubkey, or use your own local suave chain node! */ 74 | function encryptBundle(Bundle memory bundle, bytes32 r) public view returns (bytes memory) { 75 | return encrypt(abi.encode(bundle), r); 76 | } 77 | 78 | /* Debug functions exposing internals. Those are here just for the demo! */ 79 | function publishEncryptedBundle(bytes memory encryptedBundle) external { 80 | Bundle memory bundle = abi.decode(decrypt(encryptedBundle), (Bundle)); 81 | redis.publish("bundles", abi.encode(bundle)); 82 | } 83 | function pollAndReturnBundle() external returns (bool msg_present, bool auth_ok, Bundle memory bundle) { 84 | (bool _msg_present, bool _auth_ok, bytes memory raw_message) = redis.get_message("bundles"); 85 | msg_present = _msg_present; 86 | auth_ok = _auth_ok; 87 | if (_msg_present && _auth_ok && raw_message.length != 0) { 88 | bundle = abi.decode(raw_message, (Bundle)); 89 | internal_addBundle(bundle); 90 | } 91 | 92 | return (msg_present, auth_ok, bundle); 93 | } 94 | function dbg_getBundlesByHeight(uint256 height) external returns (Bundle[] memory bundles) { 95 | allowedContracts[msg.sender] = true; 96 | return getBundlesByHeight(height); 97 | } 98 | /* End of debug functions */ 99 | 100 | 101 | function addBundle(Bundle memory bundle) public onlyAllowed { 102 | internal_addBundle(bundle); 103 | 104 | // Note: bundle already includes profit, does not have to be re-calculated 105 | redis.publish("bundles", abi.encode(bundle)); 106 | } 107 | 108 | function internal_addBundle(Bundle memory bundle) internal { 109 | // TODO: check if bundle is already present! 110 | 111 | bytes32 bundleHash = keccak256(abi.encode(bundle)); 112 | redis.set(string(abi.encodePacked("bundle-", bundleHash)), abi.encode(bundle)); 113 | 114 | // Updates bundle by height index 115 | // TODO: this would be much faster with a key index / hset 116 | bytes32[] memory n_bundles; 117 | (bool found, bytes memory c_bundles_raw) = redis.get(string(abi.encodePacked("bundles-", bundle.height))); 118 | if (found && c_bundles_raw.length > 0) { 119 | bytes32[] memory c_bundles = abi.decode(c_bundles_raw, (bytes32[])); 120 | n_bundles = new bytes32[](c_bundles.length+1); 121 | n_bundles[c_bundles.length] = bundleHash; 122 | for (uint i = 0; i < c_bundles.length; i++) { 123 | n_bundles[i] = c_bundles[i]; 124 | } 125 | } else { 126 | n_bundles = new bytes32[](1); 127 | n_bundles[0] = bundleHash; 128 | } 129 | 130 | redis.set(string(abi.encodePacked("bundles-", bundle.height)), abi.encode(n_bundles)); 131 | } 132 | 133 | function getBundlesByHeight(uint256 height) public onlyAllowed returns (Bundle[] memory bundles) { 134 | (bool index_found, bytes memory c_bundles_raw) = redis.get(string(abi.encodePacked("bundles-", height))); 135 | if (!index_found || c_bundles_raw.length == 0) { 136 | return bundles; 137 | } 138 | 139 | bytes32[] memory c_bundles = abi.decode(c_bundles_raw, (bytes32[])); 140 | bundles = new Bundle[](c_bundles.length); 141 | for (uint i = 0; i < c_bundles.length; i++) { 142 | (bool bundle_found, bytes memory bundle_raw) = redis.get(string(abi.encodePacked("bundle-", c_bundles[i]))); 143 | if (!bundle_found || bundle_raw.length > 0) { 144 | bundles[i] = abi.decode(bundle_raw, (Bundle)); 145 | } else { 146 | // wat do? 147 | } 148 | } 149 | } 150 | 151 | // Call on each kettle to queue synchronization messages 152 | function subscribe() external { 153 | redis.subscribe("bundles"); 154 | } 155 | 156 | // Call on each kettle to process synchronization messages 157 | // Returns how many messages were processed 158 | function synchronize(uint maxMsgs) external returns (uint) { 159 | for (uint i = 0; i < maxMsgs; i++) { 160 | (bool msg_present, bool msg_auth_ok, bytes memory raw_message) = redis.get_message("bundles"); 161 | if (!msg_present) { 162 | return i; 163 | } else if (!msg_auth_ok || raw_message.length == 0) { 164 | continue; 165 | } 166 | 167 | Bundle memory bundle = abi.decode(raw_message, (Bundle)); 168 | internal_addBundle(bundle); 169 | } 170 | 171 | return maxMsgs; 172 | } 173 | 174 | 175 | mapping (address => bool) allowedContracts; 176 | modifier onlyAllowed() { 177 | require(msg.sender == address(this) || allowedContracts[msg.sender], "caller not allowed"); 178 | _; 179 | } 180 | 181 | 182 | /* If you want to pass in encrypted data, first encrypt it (yourself using the pubkey or encrypt() with your local node), it and then call */ 183 | /* Usually however, you'll want to handle encryption in the parent contract (see DBBSample) */ 184 | function _decrypt_and_call(bytes memory ciphertext, bytes memory cdata) onlyAllowed external returns (bytes memory) { 185 | bytes memory plaintext = decrypt(ciphertext); 186 | (bool call_ok, bytes memory return_data) = address(this).call(bytes.concat(cdata, plaintext)); 187 | require(call_ok); 188 | return return_data; 189 | } 190 | } 191 | 192 | /* Usage example */ 193 | contract DBBSample is KeyHelper { 194 | BundleConfidentialStore store; 195 | Builder builder; 196 | constructor(KeyManager_v0 keymgr) KeyHelper(keymgr) { 197 | address[] memory allowedContracts = new address[](1); 198 | allowedContracts[0] = address(this); 199 | store = new BundleConfidentialStore(keymgr, allowedContracts); 200 | builder = new Builder(); 201 | } 202 | 203 | // Call for every kettle to start accepting bundles from them! 204 | function offchain_onboardKettle() public { 205 | store.subscribe(); 206 | } 207 | 208 | /* Encrypt yourself using the pubkey, or use your own local suave chain node! */ 209 | function encryptBundle(Bundle memory bundle) public view returns (bytes memory) { 210 | return encrypt(abi.encode(bundle)); 211 | } 212 | function decryptBundle(bytes memory ciphertext) internal returns (Bundle memory) { 213 | return abi.decode(decrypt(ciphertext), (Bundle)); 214 | } 215 | 216 | function submitEncryptedBundle(bytes memory encryptedBundle) external { 217 | Bundle memory bundle = decryptBundle(encryptedBundle); 218 | // Note that bundle is not trusted despite being encrypted! 219 | 220 | Bundle memory simulatedBundle = builder.simulate(bundle); 221 | store.addBundle(simulatedBundle); 222 | } 223 | 224 | /* Call before buildBlock to fetch bundles from other kettles */ 225 | /* Returns whether there are more messages to be synchronized */ 226 | /* If reverts with out of gas, there are still possibly messages pending! */ 227 | function synchronize_store() external returns (bool) { 228 | /* TODO: run until no more gas */ 229 | return store.synchronize(10) != 10; 230 | } 231 | 232 | function buildBlock(uint256 height) external { 233 | /* Make sure you are calling synchronize_store in the background! */ 234 | Bundle[] memory bundles = store.getBundlesByHeight(height); 235 | uint256 _blockProfit = builder.buildBlock(bundles); 236 | /* Do something with the block */ 237 | } 238 | } 239 | 240 | contract Builder { 241 | function simulate(Bundle memory bundle) public pure returns (Bundle memory) { 242 | bundle.profit = 1; 243 | return bundle; 244 | } 245 | 246 | function buildBlock(Bundle[] memory bundles) public pure returns (uint256) { 247 | uint256 profit = 0; 248 | for (uint i = 0; i < bundles.length; i++) { 249 | profit += bundles[i].profit; 250 | } 251 | return profit; 252 | } 253 | } 254 | 255 | 256 | -------------------------------------------------------------------------------- /src/examples/SpeedrunAuction.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.13; 2 | 3 | import "../crypto/secp256k1.sol"; 4 | import "../crypto/encryption.sol"; 5 | 6 | import {AndromedaForge} from "src/AndromedaForge.sol"; 7 | import {Secp256k1} from "src/crypto/secp256k1.sol"; 8 | import {PKE, Curve} from "src/crypto/encryption.sol"; 9 | 10 | // We'l start with a Leaky second price auction 11 | contract LeakyAuction { 12 | // All Deposits in before trade 13 | mapping(address => uint256) public balance; 14 | 15 | // Concluding time (block height) 16 | uint256 public constant auctionEndTime = 2; 17 | 18 | // To be set after the auction concludes (this is the output) 19 | uint256 public secondPrice; 20 | 21 | // Store the Bids (in plaintext for now) 22 | mapping(address => uint256) public bids; 23 | address[] public bidders; 24 | 25 | // Accept a bid in plaintext 26 | event BidPlaced(address sender, uint256 bid); 27 | 28 | function submitBid(uint256 bid) public virtual { 29 | require(block.number <= auctionEndTime); 30 | require(bids[msg.sender] == 0); 31 | bids[msg.sender] = bid; 32 | bidders.push(msg.sender); 33 | emit BidPlaced(msg.sender, bid); 34 | } 35 | 36 | // Wrap up the auction and compute the 2nd price 37 | event Concluded(uint256 secondPrice); 38 | 39 | function conclude() public { 40 | require(block.number > auctionEndTime); 41 | require(secondPrice == 0); 42 | // Compute the second price 43 | uint256 best = 0; 44 | for (uint256 i = 0; i < bidders.length; i++) { 45 | uint256 bid = bids[bidders[i]]; 46 | if (bid > best) { 47 | secondPrice = best; 48 | best = bid; 49 | } else if (bid > secondPrice) { 50 | secondPrice = bid; 51 | } 52 | } 53 | emit Concluded(secondPrice); 54 | } 55 | } 56 | 57 | contract KeyManager { 58 | AndromedaForge public Suave; 59 | 60 | constructor(AndromedaForge _Suave) { 61 | Suave = _Suave; 62 | } 63 | 64 | // Public key (once initialized) will be visible 65 | bytes xPub; 66 | 67 | // Private key will only be accessible in confidential mode 68 | function xPriv() internal returns (bytes32) { 69 | return Suave.volatileGet("xPriv"); 70 | } 71 | 72 | // To initialize the key, some kettle must call this... 73 | function offchain_Bootstrap() public returns (bytes memory _xPub, bytes memory att) { 74 | bytes32 xPriv_ = Suave.localRandom(); 75 | _xPub = PKE.derivePubKey(xPriv_); 76 | Suave.volatileSet("xPriv", xPriv_); 77 | att = Suave.attestSgx(keccak256(abi.encodePacked("xPub", _xPub))); 78 | } 79 | 80 | // ... and then post it on chain, verifying the attestation 81 | function onchain_Bootstrap(bytes memory _xPub, bytes memory att) public { 82 | require(xPub.length == 0); // only once 83 | Suave.verifySgx(address(this), keccak256(abi.encodePacked("xPub", _xPub)), att); 84 | xPub = _xPub; 85 | } 86 | } 87 | 88 | // Now we'll fix the auction using the TEE coprocessor 89 | contract SealedAuction is LeakyAuction, KeyManager { 90 | constructor(AndromedaForge _Suave) KeyManager(_Suave) {} 91 | 92 | // Now we will store encrypted orders 93 | mapping(address => bytes) encBids; 94 | 95 | event EncryptedBidPlaced(address sender); 96 | 97 | function submitEncrypted(bytes memory ciphertext) public { 98 | require(block.number <= auctionEndTime); 99 | require(encBids[msg.sender].length == 0); 100 | encBids[msg.sender] = ciphertext; 101 | bidders.push(msg.sender); 102 | emit EncryptedBidPlaced(msg.sender); 103 | } 104 | 105 | // Helper function for a client to run locally 106 | function encryptOrder(uint256 bid, bytes32 r) public view returns (bytes memory) { 107 | return PKE.encrypt(xPub, r, abi.encodePacked(bid)); 108 | } 109 | 110 | // Called by any kettle to compute the second price 111 | function offline_Finalize() public returns (uint256 secondPrice_, bytes memory att) { 112 | require(block.number > auctionEndTime); 113 | bytes32 xPriv = xPriv(); 114 | 115 | // Decrypt each bid and compute second price 116 | uint256 best = 0; 117 | for (uint256 i = 0; i < bidders.length; i++) { 118 | bytes memory ciphertext = encBids[bidders[i]]; 119 | uint256 bid = abi.decode(PKE.decrypt(xPriv, ciphertext), (uint256)); 120 | if (bid > best) { 121 | secondPrice_ = best; 122 | best = bid; 123 | } else if (bid > secondPrice_) { 124 | secondPrice_ = bid; 125 | } 126 | } 127 | 128 | // Use the key manager attest 129 | att = Suave.attestSgx(keccak256(abi.encodePacked("2ndprice", secondPrice_))); 130 | } 131 | 132 | // Post the second price on-chain 133 | function onchain_Finalize(uint256 secondPrice_, bytes memory att) public { 134 | require(block.number > auctionEndTime); 135 | Suave.verifySgx(address(this), keccak256(abi.encodePacked("2ndprice", secondPrice_)), att); 136 | secondPrice = secondPrice_; 137 | emit Concluded(secondPrice); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/examples/Timelock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.13; 2 | 3 | import "../crypto/secp256k1.sol"; 4 | import "../crypto/encryption.sol"; 5 | import "../KeyManager.sol"; 6 | 7 | contract Timelock { 8 | KeyManager_v0 keymgr; 9 | 10 | constructor(KeyManager_v0 _keymgr) { 11 | keymgr = _keymgr; 12 | } 13 | 14 | /* 15 | To initialize, some Kettle must invoke: 16 | `keymgr.offchain_DeriveKey(auc) -> dPub,v,r,s` 17 | `keymgr.onchain_DeriveKey(auc,dPub,v,r,s)` 18 | */ 19 | function isInitialized() public view returns (bool) { 20 | return keymgr.derivedPub(address(this)).length != 0; 21 | } 22 | 23 | // Mapping from ciphertext to release date 24 | mapping(bytes32 => uint256) public deadlines; 25 | 26 | event EncryptedMessage(bytes ciphertext, bytes32 contentHash, uint256 deadline); 27 | 28 | // Helper function for a client to run locally 29 | function encryptMessage(string memory message, bytes32 r) public view returns (bytes memory) { 30 | return PKE.encrypt(keymgr.derivedPub(address(this)), r, abi.encodePacked(message)); 31 | } 32 | 33 | // Post an encrypted message 34 | uint256 constant DELAY_BLOCKS = 15; // Wait 15 blocks ~1minute 35 | 36 | function submitEncrypted(bytes memory ciphertext) public { 37 | bytes32 contentHash = keccak256(ciphertext); 38 | deadlines[contentHash] = block.number + DELAY_BLOCKS; 39 | emit EncryptedMessage(ciphertext, contentHash, deadlines[contentHash]); 40 | } 41 | 42 | // Decrypt the message if the block height is high enough 43 | function decrypt(bytes memory ciphertext) public returns (bytes memory message) { 44 | bytes32 contentHash = keccak256(ciphertext); 45 | require(deadlines[contentHash] != 0, "no such message"); 46 | require(block.number >= deadlines[contentHash], "deadline not reached yet"); 47 | return PKE.decrypt(keymgr.derivedPriv(), ciphertext); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/examples/httpcall.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.13; 2 | 3 | import "../crypto/secp256k1.sol"; 4 | import "../crypto/encryption.sol"; 5 | import "../KeyManager.sol"; 6 | 7 | contract HTTP { 8 | address public constant DO_HTTP_REQUEST = 0x0000000000000000000000000000000043200002; 9 | 10 | function makeHttpCall() public returns (string memory) { 11 | HttpRequest memory request; 12 | request.url = "https://status.flashbots.net/summary.json"; 13 | request.method = "GET"; 14 | return string(doHTTPRequest(request)); 15 | } 16 | 17 | // from suave-std 18 | struct HttpRequest { 19 | string url; 20 | string method; 21 | string[] headers; 22 | bytes body; 23 | bool withFlashbotsSignature; 24 | } 25 | 26 | function doHTTPRequest(HttpRequest memory request) internal returns (bytes memory) { 27 | (bool success, bytes memory data) = DO_HTTP_REQUEST.call(abi.encode(request)); 28 | require(success); 29 | return abi.decode(data, (bytes)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/hash/IHash.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.8; 3 | 4 | interface IHash { 5 | function sha512(bytes memory data) external view returns (bytes memory); 6 | } 7 | -------------------------------------------------------------------------------- /src/scripts/TimelockSetup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script} from "forge-std/Script.sol"; 5 | import {console2} from "forge-std/console2.sol"; 6 | import {AndromedaRemote} from "src/AndromedaRemote.sol"; 7 | import {SigVerifyLib} from "automata-dcap-v3-attestation/utils/SigVerifyLib.sol"; 8 | import {KeyManager_v0} from "src/KeyManager.sol"; 9 | 10 | contract TimelockSetup is Script { 11 | function run() public { 12 | console2.log("Running TimelockSetup"); 13 | SigVerifyLib lib = SigVerifyLib(vm.envAddress("sigVerifyLib")); 14 | AndromedaRemote andromeda = AndromedaRemote(vm.envAddress("andromeda")); 15 | console2.log("andromeda=%s", address(andromeda)); 16 | andromeda.initialize(); 17 | // vm.warp(1701528486); 18 | 19 | andromeda.setMrSigner(bytes32(0x93adbda6205882743aedecbbebfb4bae7f132a9bbbeac9497fcd3c140dffe52c), true); 20 | } 21 | } 22 | 23 | // run this with a different private key if needed 24 | contract TimelockSetup2 is Script { 25 | function run() public { 26 | console2.log("Running TimelockSetup2"); 27 | // To ensure we don't use the same address with volatile storage 28 | // vm.prank(vm.addr(uint256(keccak256("examples/Timelock.t.sol")))); 29 | KeyManager_v0 keymgr = new KeyManager_v0( 30 | address(vm.envAddress("andromeda")) 31 | ); 32 | (address xPub, bytes memory att) = keymgr.offchain_Bootstrap(); 33 | keymgr.onchain_Bootstrap(xPub, att); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/Utils.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | library Utils { 4 | function bytesToHexString(bytes memory data) internal pure returns (string memory) { 5 | bytes memory alphabet = "0123456789abcdef"; 6 | 7 | bytes memory str = new bytes(2 * data.length); 8 | for (uint i = 0; i < data.length; i++) { 9 | str[i*2] = alphabet[uint(uint8(data[i] >> 4))]; 10 | str[1+i*2] = alphabet[uint(uint8(data[i] & 0x0f))]; 11 | } 12 | return string(str); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/fmspc.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import {V3Struct} from "../../lib/automata-dcap-v3-attestation/contracts/lib/QuoteV3Auth/V3Struct.sol"; 4 | import {V3Parser} from "../../lib/automata-dcap-v3-attestation/contracts/lib/QuoteV3Auth/V3Parser.sol"; 5 | import {PEMCertChainLib} from "../../lib/automata-dcap-v3-attestation/contracts/lib/PEMCertChainLib.sol"; 6 | import {IPEMCertChainLib} from "../../lib/automata-dcap-v3-attestation/contracts/lib/interfaces/IPEMCertChainLib.sol"; 7 | 8 | // Internal Libraries 9 | import {Base64} from "solady/src/Milady.sol"; 10 | 11 | contract FmspcParser { 12 | IPEMCertChainLib public immutable pemCertLib; 13 | constructor() { 14 | pemCertLib = new PEMCertChainLib(); 15 | } 16 | 17 | function extract_fmspc_from_bootstrap(address, bytes calldata att) public returns (bool, string memory) { 18 | return extract_fmspc(att); 19 | } 20 | 21 | function extract_fmspc(bytes calldata quote) public returns (bool, string memory) { 22 | ( 23 | bool successful, 24 | , 25 | , 26 | , 27 | V3Struct.ECDSAQuoteV3AuthData memory authDataV3 28 | ) = V3Parser.parseInput(quote); 29 | if (!successful) { 30 | return (false, "could not parse quote"); 31 | } 32 | 33 | IPEMCertChainLib.ECSha256Certificate[] memory parsedQuoteCerts; 34 | { 35 | // 660k gas 36 | ( 37 | bool certParsedSuccessfully, 38 | bytes[] memory quoteCerts 39 | ) = pemCertLib.splitCertificateChain( 40 | authDataV3.certification.certData, 41 | 3 42 | ); 43 | if (!certParsedSuccessfully) { 44 | return (false, "could not parse cert"); 45 | } 46 | 47 | // 536k gas 48 | parsedQuoteCerts = new IPEMCertChainLib.ECSha256Certificate[](3); 49 | for (uint256 i = 0; i < 3; i++) { 50 | quoteCerts[i] = Base64.decode(string(quoteCerts[i])); 51 | bool isPckCert = i == 0; // additional parsing for PCKCert 52 | bool certDecodedSuccessfully; 53 | (certDecodedSuccessfully, parsedQuoteCerts[i]) = pemCertLib 54 | .decodeCert(quoteCerts[i], isPckCert); 55 | if (!certDecodedSuccessfully) { 56 | return (false, "could not decode cert"); 57 | } 58 | } 59 | } 60 | 61 | string memory parsedFmspc = parsedQuoteCerts[0] 62 | .pck 63 | .sgxExtension 64 | .fmspc; 65 | return (true, parsedFmspc); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/AndromedaForge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import {AndromedaForge} from "../src/AndromedaForge.sol"; 6 | 7 | contract AndromedaForgeTest is Test { 8 | AndromedaForge public andromeda; 9 | 10 | function setUp() public { 11 | andromeda = new AndromedaForge(); 12 | } 13 | 14 | function test_localrandom() public view { 15 | bytes32 a = andromeda.localRandom(); 16 | bytes32 b = andromeda.localRandom(); 17 | require(a != b); 18 | } 19 | 20 | function test_sealingkey() public view { 21 | bytes32 msghash = keccak256(abi.encodePacked("hi")); 22 | 23 | bytes32 a = andromeda.sealingKey(msghash); 24 | bytes32 b = andromeda.sealingKey(msghash); 25 | require(a == b); 26 | } 27 | 28 | function test_attest() public { 29 | bytes32 msghash = keccak256(abi.encodePacked("hi")); 30 | 31 | // Attestation should check 32 | bytes memory att = andromeda.attestSgx(msghash); 33 | assert(andromeda.verifySgx(address(this), msghash, att)); 34 | 35 | // Unattested should not 36 | bytes32 msghash2 = keccak256(abi.encodePacked("hi2")); 37 | assertFalse(andromeda.verifySgx(address(this), msghash2, att)); 38 | 39 | // Callers should have different domains 40 | assertFalse(andromeda.verifySgx(address(andromeda), msghash, att)); 41 | } 42 | 43 | function test_SetGet() public { 44 | bytes32 value = keccak256(abi.encodePacked("hi")); 45 | 46 | // Initially it is 0 47 | bytes32 value2 = andromeda.volatileGet(bytes32("test")); 48 | assertEq(value2, ""); 49 | 50 | // After setting it is hash("hi") 51 | andromeda.volatileSet(bytes32("test"), value); 52 | bytes32 value3 = andromeda.volatileGet(bytes32("test")); 53 | assertEq(value3, value); 54 | 55 | // Setting again overwrites 56 | bytes32 v2 = keccak256("asdf"); 57 | andromeda.volatileSet(bytes32("test"), v2); 58 | bytes32 v2check = andromeda.volatileGet(bytes32("test")); 59 | assertEq(v2check, v2); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/BIP32.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.13; 2 | 3 | import {Test, console2} from "forge-std/Test.sol"; 4 | import {BIP32} from "../src/BIP32.sol"; 5 | import {AndromedaForge} from "src/AndromedaForge.sol"; 6 | 7 | 8 | contract BIP32_Test is Test { 9 | BIP32 bip32; 10 | AndromedaForge andromeda; 11 | 12 | function setUp() public { 13 | andromeda = new AndromedaForge(); 14 | bip32 = new BIP32(andromeda); 15 | } 16 | 17 | function testSHA512() public view { 18 | bytes memory data = "test"; 19 | bytes memory result = andromeda.sha512(abi.encodePacked(data)); 20 | bytes memory expected = bytes(hex"ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff"); 21 | require(keccak256(result) == keccak256(expected)); 22 | } 23 | 24 | function testSplitFunction() public view { 25 | (bytes32 left, bytes32 right) = bip32.split(bytes(hex"70fc1da84876732b9ab9db31d877059091ec094613681db29aaa78fa6b03af93b71a989780bde3370eb595f505d58d0a0059186a336c40b8581da2df2193592c")); 26 | bytes32 expected_left = 0x70fc1da84876732b9ab9db31d877059091ec094613681db29aaa78fa6b03af93; 27 | bytes32 expected_right = 0xb71a989780bde3370eb595f505d58d0a0059186a336c40b8581da2df2193592c; 28 | require(left == expected_left); 29 | require(right == expected_right); 30 | } 31 | 32 | function testDeriveKey() public view { 33 | bytes memory seed = abi.encodePacked("MySecretPassword"); 34 | 35 | // derive the master key directly and when using a seed and a path 36 | (BIP32.ExtendedPrivateKey memory xPriv) = bip32.newFromSeed(seed); 37 | (BIP32.ExtendedPrivateKey memory pathXPriv, BIP32.ExtendedPublicKey memory cXPub) = bip32.deriveChildKeyPairFromPath(seed, "m"); 38 | require(xPriv.key == pathXPriv.key); 39 | } 40 | 41 | function testDeriveNonHardenedKey() public view { 42 | bytes memory seed = abi.encodePacked("MySecretPassword"); 43 | 44 | // derive the master key directly and when using a seed and a path 45 | (BIP32.ExtendedPrivateKey memory xPriv) = bip32.newFromSeed(seed); 46 | 47 | // non hardened child derivation using index and path 48 | (BIP32.ExtendedPrivateKey memory cxPriv, BIP32.ExtendedPublicKey memory cxPub) = bip32.deriveChildKeyPair(xPriv, 0); 49 | (BIP32.ExtendedPrivateKey memory pathCXPriv, BIP32.ExtendedPublicKey memory pathCXPub) = bip32.deriveChildKeyPairFromPath(seed, "m/0"); 50 | require(cxPriv.key == pathCXPriv.key); 51 | } 52 | 53 | function testDeriveHardenedKey() public view { 54 | bytes memory seed = abi.encodePacked("MySecretPassword"); 55 | 56 | // derive the master key directly and when using a seed and a path 57 | (BIP32.ExtendedPrivateKey memory xPriv) = bip32.newFromSeed(seed); 58 | 59 | // hardened child derivation using index and path 60 | (BIP32.ExtendedPrivateKey memory hcxPriv, BIP32.ExtendedPublicKey memory hcxPub) = bip32.deriveChildKeyPair(xPriv, 2147483648); 61 | (BIP32.ExtendedPrivateKey memory pathHCXPriv, BIP32.ExtendedPublicKey memory pathHCXPub) = bip32.deriveChildKeyPairFromPath(seed, "m/0'"); 62 | require(hcxPriv.key == pathHCXPriv.key); 63 | } 64 | 65 | function testDeriveNonHardenedFromHardenedKey() public view { 66 | bytes memory seed = abi.encodePacked("MySecretPassword"); 67 | 68 | // derive the master key directly and when using a seed and a path 69 | (BIP32.ExtendedPrivateKey memory xPriv) = bip32.newFromSeed(seed); 70 | 71 | // hardened child derivation using index and path 72 | (BIP32.ExtendedPrivateKey memory hcxPriv, BIP32.ExtendedPublicKey memory hcxPub) = bip32.deriveChildKeyPair(xPriv, 2147483648); 73 | 74 | // derive a non hardened child key from a hardened one (mixing both) 75 | (BIP32.ExtendedPrivateKey memory nhcxPriv, BIP32.ExtendedPublicKey memory nhcxPub) = bip32.deriveChildKeyPair(hcxPriv, 0); 76 | (BIP32.ExtendedPrivateKey memory pathNHCXPriv, BIP32.ExtendedPublicKey memory pathNHCXPub) = bip32.deriveChildKeyPairFromPath(seed, "m/0'/0"); 77 | require(nhcxPriv.key == pathNHCXPriv.key); 78 | } 79 | 80 | function testDeriveChildPubKeyFromParentPubKey() public view { 81 | bytes memory seed = abi.encodePacked("MySecretPassword"); 82 | 83 | // derive the master key directly and when using a seed and a path 84 | (BIP32.ExtendedPrivateKey memory xPriv) = bip32.newFromSeed(seed); 85 | 86 | // derive the master public key 87 | (BIP32.ExtendedPublicKey memory xPub) = bip32.derivePubKey(xPriv); 88 | 89 | // derive a child public key from the master public key 90 | (BIP32.ExtendedPrivateKey memory cxPriv, BIP32.ExtendedPublicKey memory cxPub) = bip32.deriveChildKeyPair(xPriv, 0); 91 | (BIP32.ExtendedPublicKey memory childXPub) = bip32.derivePubKeyFromParentPubKey(xPub, 0); 92 | require(keccak256(childXPub.key) == keccak256(cxPub.key)); 93 | } 94 | 95 | function testDeriveChildPubKeyFromParentPubKeyFail() public view { 96 | bytes memory seed = abi.encodePacked("MySecretPassword"); 97 | 98 | // derive the master key directly and when using a seed and a path 99 | (BIP32.ExtendedPrivateKey memory xPriv) = bip32.newFromSeed(seed); 100 | 101 | // derive the master public key 102 | (BIP32.ExtendedPublicKey memory xPub) = bip32.derivePubKey(xPriv); 103 | 104 | // derive a child public key from the master public key 105 | (BIP32.ExtendedPrivateKey memory cxPriv, BIP32.ExtendedPublicKey memory cxPub) = bip32.deriveChildKeyPair(xPriv, 2147483648); 106 | try bip32.derivePubKeyFromParentPubKey(xPub, 2147483648) { 107 | revert("Should have failed"); 108 | } catch Error(string memory) { 109 | // Pass the test because the function call should fail for hardened derivation from parent pubkey 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /test/Crypto.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | 6 | import "../src/crypto/encryption.sol"; 7 | import "../src/crypto/secp256k1.sol"; 8 | 9 | contract CryptoTest is Test { 10 | function test_signing() public { 11 | bytes32 sk = bytes32(0xce4cd60396002795176e7597cac85f4b1515cd6f367d78f285c7974fa1a753fa); 12 | address a = Secp256k1.deriveAddress(uint256(sk)); 13 | bytes32 digest = keccak256(abi.encodePacked("hi")); 14 | bytes memory sig = Secp256k1.sign(uint256(sk), digest); 15 | assertTrue(Secp256k1.verify(a, digest, sig)); 16 | } 17 | 18 | function test_address() public { 19 | bytes32 sk = bytes32(0x4646464646464646464646464646464646464646464646464646464646464646); 20 | (uint256 qx, uint256 qy) = Secp256k1.derivePubKey(uint256(sk)); 21 | address a = Secp256k1.deriveAddress(uint256(sk)); 22 | bytes memory ser = bytes.concat(bytes32(qx), bytes32(qy)); 23 | assertEq(address(0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F), a); 24 | } 25 | 26 | function test_encryption() public { 27 | bytes32 secretKey = bytes32(uint256(0x4646464646464646464646464646464646464646464646464646464646464646)); 28 | Curve.G1Point memory pub = Curve.g1mul(Curve.P1(), uint256(secretKey)); 29 | 30 | bytes memory message = bytes("hello there suave, #32bytes"); 31 | 32 | // Encrypt the message to the auction contract 33 | bytes32 r = bytes32(uint256(0x1231251)); 34 | bytes memory ciphertext = PKE.encrypt(abi.encodePacked(pub.X, pub.Y), r, message); 35 | 36 | // Decrypt the message (using hardcoded auction contract secretkey): 37 | bytes memory message2 = PKE.decrypt(secretKey, ciphertext); 38 | assertEq(message, message2); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/DcapVerifier.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.8; 3 | 4 | import "solidity-stringutils/strings.sol"; 5 | import {BytesUtils} from "ens-contracts/dnssec-oracle/BytesUtils.sol"; 6 | 7 | import "forge-std/console2.sol"; 8 | import "forge-std/Test.sol"; 9 | 10 | import {DcapDemo} from "src/DcapVerifier.sol"; 11 | import {EnclaveIdStruct, TCBInfoStruct} from "automata-dcap-v3-attestation/AutomataDcapV3Attestation.sol"; 12 | import {SigVerifyLib} from "automata-dcap-v3-attestation/utils/SigVerifyLib.sol"; 13 | 14 | import {AndromedaRemote} from "src/AndromedaRemote.sol"; 15 | 16 | contract DcapVerifyTest is Test { 17 | using stdJson for string; 18 | 19 | using strings for *; 20 | using BytesUtils for *; 21 | 22 | AndromedaRemote public andromeda; 23 | 24 | function setUp() public { 25 | SigVerifyLib lib = new SigVerifyLib(); 26 | andromeda = new AndromedaRemote(address(lib)); 27 | andromeda.initialize(); 28 | } 29 | 30 | function testDecode() public { 31 | EnclaveIdStruct.EnclaveId memory s; 32 | andromeda.configureQeIdentityJson(s); 33 | } 34 | 35 | function testVerify() public { 36 | // For the included test quote 37 | andromeda.setMrEnclave(bytes32(0x185237a9e29c9c47ea060b3740a285ce2e36a0b7b11e049488f4c0c77329a7a0), true); 38 | 39 | // Set the timestamp (to avoid certificate expiry check); 40 | vm.warp(1701528486); 41 | 42 | // Test a pre-recorded attestation 43 | string memory s = vm.readFile("test/fixtures/testquote.hex"); 44 | bytes memory quote = vm.parseBytes(s); 45 | assert(andromeda.verifyAttestation(quote)); 46 | } 47 | 48 | function testRemote() public { 49 | // Use the FFI interface to verify a fresh quote 50 | bytes32 appData = keccak256("test hi"); 51 | bytes memory quote = andromeda.attestSgx(appData); 52 | assert(andromeda.verifyAttestation(quote)); 53 | assert(andromeda.verifySgx(address(this), appData, quote)); 54 | assertFalse(andromeda.verifySgx(address(this), keccak256("no"), quote)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/KeyManager.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import {KeyManager_v0} from "../src/KeyManager.sol"; 6 | import {PKE} from "../src/crypto/encryption.sol"; 7 | import {AndromedaForge} from "src/AndromedaForge.sol"; 8 | import "forge-std/Vm.sol"; 9 | 10 | contract KeyManager_v0_Test is Test { 11 | AndromedaForge andromeda; 12 | KeyManager_v0 keymgr; 13 | 14 | Vm.Wallet alice; 15 | Vm.Wallet bob; 16 | Vm.Wallet carol; 17 | 18 | function setUp() public { 19 | andromeda = new AndromedaForge(); 20 | vm.prank(vm.addr(uint(keccak256("KeyManager.t.sol")))); 21 | keymgr = new KeyManager_v0(address(andromeda)); 22 | 23 | alice = vm.createWallet("alice"); 24 | bob = vm.createWallet("bob"); 25 | } 26 | 27 | function testKeyManager() public { 28 | // 1. Bootstrap 29 | // 1a. Offchain generate the key 30 | andromeda.switchHost("alice"); 31 | (address xPub, bytes memory att) = keymgr.offchain_Bootstrap(); 32 | // 1b. Post the key and attestation on-chain 33 | keymgr.onchain_Bootstrap(xPub, att); 34 | 35 | // 2. Register a new node 36 | // 2a. Offchain generate a register request 37 | andromeda.switchHost("bob"); 38 | (address bob_kettle, bytes memory bPub, bytes memory attB) = keymgr 39 | .offchain_Register(); 40 | // 2b. Onchain submit the request 41 | keymgr.onchain_Register(bob_kettle, bPub, attB); 42 | 43 | // 2.1 Register a new node 44 | // 2.1a. Offchain generate a register request 45 | andromeda.switchHost("charlie"); 46 | (address charlie_kettle, bytes memory cPub, bytes memory attC) = keymgr 47 | .offchain_Register(); 48 | // 2.1b. Onchain submit the request 49 | keymgr.onchain_Register(charlie_kettle, cPub, attC); 50 | 51 | assert(bob_kettle != charlie_kettle); 52 | 53 | // 3. Help onboard a new node 54 | // 3a. Offchain generate a ciphertext with the key 55 | andromeda.switchHost("alice"); 56 | bytes memory ciphertext = keymgr.offchain_Onboard(bob_kettle); 57 | // 3b. Onchain post the ciphertext 58 | keymgr.onchain_Onboard(bob_kettle, ciphertext); 59 | // 3c. Load the data received 60 | andromeda.switchHost("bob"); 61 | keymgr.finish_Onboard(ciphertext); 62 | 63 | // 3.1. Help onboard a second node 64 | ciphertext = keymgr.offchain_Onboard(charlie_kettle); 65 | // 3.1b. Onchain post the ciphertext 66 | keymgr.onchain_Onboard(charlie_kettle, ciphertext); 67 | // 3.1c. Load the data received 68 | andromeda.switchHost("charlie"); 69 | keymgr.finish_Onboard(ciphertext); 70 | } 71 | 72 | function testDerived() public { 73 | // Do the bootstrap 74 | (address xPub, bytes memory att) = keymgr.offchain_Bootstrap(); 75 | keymgr.onchain_Bootstrap(xPub, att); 76 | 77 | // Show the derived key associated with this contract. 78 | (bytes memory dPub, bytes memory sig) = keymgr.offchain_DeriveKey( 79 | address(this) 80 | ); 81 | keymgr.onchain_DeriveKey(address(this), dPub, sig); 82 | 83 | bytes32 dPriv = keymgr.derivedPriv(); 84 | assertEq(PKE.derivePubKey(dPriv), keymgr.derivedPub(address(this))); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/examples/Auction.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {AndromedaRemote} from "src/AndromedaRemote.sol"; 5 | import {SigVerifyLib} from "automata-dcap-v3-attestation/utils/SigVerifyLib.sol"; 6 | 7 | import {Test, console2} from "forge-std/Test.sol"; 8 | 9 | import {KeyManager_v0} from "src/KeyManager.sol"; 10 | import {LeakyAuction, SealedAuction, PKE, Curve} from "src/examples/Auction.sol"; 11 | 12 | contract SealedAuctionTest is Test { 13 | AndromedaRemote andromeda; 14 | KeyManager_v0 keymgr; 15 | 16 | address alice; 17 | address bob; 18 | 19 | function setUp() public { 20 | SigVerifyLib lib = new SigVerifyLib(); 21 | andromeda = new AndromedaRemote(address(lib)); 22 | andromeda.initialize(); 23 | 24 | // To ensure we don't use the same address with volatile storage 25 | vm.prank(vm.addr(uint256(keccak256("examples/Auction.t.sol")))); 26 | keymgr = new KeyManager_v0(address(andromeda)); 27 | (address xPub, bytes memory att) = keymgr.offchain_Bootstrap(); 28 | keymgr.onchain_Bootstrap(xPub, att); 29 | 30 | alice = vm.addr(uint256(keccak256("alice"))); 31 | bob = vm.addr(uint256(keccak256("bob"))); 32 | } 33 | 34 | function test_leaky() public { 35 | LeakyAuction auc = new LeakyAuction(); 36 | 37 | vm.prank(alice); 38 | auc.submitBid(10); 39 | vm.prank(bob); 40 | auc.submitBid(8); 41 | 42 | vm.roll(3); 43 | auc.conclude(); 44 | assertEq(auc.secondPrice(), 8); 45 | } 46 | 47 | function test_sealed() public { 48 | SealedAuction auc = new SealedAuction(keymgr, 2); 49 | 50 | // Initialize the derived public key 51 | assertEq(auc.isInitialized(), false); 52 | (bytes memory dPub, bytes memory sig) = keymgr.offchain_DeriveKey(address(auc)); 53 | keymgr.onchain_DeriveKey(address(auc), dPub, sig); 54 | assertEq(auc.isInitialized(), true); 55 | 56 | // Submit encrypted orders 57 | bytes memory aBid = auc.encryptOrder(10, bytes32(uint256(0xdead2123))); 58 | bytes memory bBid = auc.encryptOrder(8, bytes32(uint256(0xcafe1232))); 59 | vm.prank(alice); 60 | auc.submitEncrypted(aBid); 61 | vm.prank(bob); 62 | auc.submitEncrypted(bBid); 63 | 64 | vm.roll(4); 65 | 66 | // Off chain compute the solution 67 | (uint256 secondPrice, bytes memory sig2) = auc.offchain_Finalize(); 68 | 69 | // Subit the solution onchain 70 | auc.onchain_Finalize(secondPrice, sig2); 71 | assertEq(auc.secondPrice(), 8); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/examples/SpeedrunAuction.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {AndromedaForge} from "src/AndromedaForge.sol"; 5 | import {Test, console2} from "forge-std/Test.sol"; 6 | import {LeakyAuction, SealedAuction} from "src/examples/SpeedrunAuction.sol"; 7 | 8 | contract SpeedrunAuction is Test { 9 | AndromedaForge andromeda; 10 | 11 | address alice; 12 | address bob; 13 | 14 | function setUp() public { 15 | andromeda = new AndromedaForge(); 16 | 17 | alice = vm.addr(uint256(keccak256("alice"))); 18 | bob = vm.addr(uint256(keccak256("bob"))); 19 | } 20 | 21 | function test_leaky() public { 22 | LeakyAuction auc = new LeakyAuction(); 23 | 24 | vm.prank(alice); 25 | auc.submitBid(10); 26 | vm.prank(bob); 27 | auc.submitBid(8); 28 | 29 | vm.roll(3); 30 | auc.conclude(); 31 | assertEq(auc.secondPrice(), 8); 32 | } 33 | 34 | function test_sealed() public { 35 | vm.prank(address(vm.addr(uint(keccak256("SpeedrunAuction.t.sol"))))); 36 | SealedAuction auc = new SealedAuction(andromeda); 37 | 38 | // Have a Kettle initialize the key 39 | (bytes memory xPub, bytes memory att) = auc.offchain_Bootstrap(); 40 | auc.onchain_Bootstrap(xPub, att); 41 | 42 | // Submit encrypted orders 43 | bytes memory aBid = auc.encryptOrder(10, bytes32(uint256(0xdead2123))); 44 | bytes memory bBid = auc.encryptOrder(8, bytes32(uint256(0xcafe1232))); 45 | vm.prank(alice); 46 | auc.submitEncrypted(aBid); 47 | vm.prank(bob); 48 | auc.submitEncrypted(bBid); 49 | 50 | vm.roll(3); 51 | 52 | // Off chain compute the solution 53 | (uint256 secondPrice, bytes memory sig2) = auc.offline_Finalize(); 54 | 55 | // Subit the solution onchain 56 | auc.onchain_Finalize(secondPrice, sig2); 57 | assertEq(auc.secondPrice(), 8); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/examples/Timelock.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {AndromedaRemote} from "src/AndromedaRemote.sol"; 5 | import {SigVerifyLib} from "automata-dcap-v3-attestation/utils/SigVerifyLib.sol"; 6 | 7 | import {Test, console2} from "forge-std/Test.sol"; 8 | import "src/crypto/secp256k1.sol"; 9 | import {KeyManager_v0} from "src/KeyManager.sol"; 10 | import {PKE, Curve} from "src/crypto/encryption.sol"; 11 | import {Timelock} from "src/examples/Timelock.sol"; 12 | 13 | contract TimelockTest is Test { 14 | AndromedaRemote andromeda; 15 | KeyManager_v0 keymgr; 16 | 17 | address alice; 18 | address bob; 19 | 20 | function setUp() public { 21 | SigVerifyLib lib = new SigVerifyLib(); 22 | andromeda = new AndromedaRemote(address(lib)); 23 | andromeda.initialize(); 24 | 25 | // To ensure we don't use the same address with volatile storage 26 | vm.prank(vm.addr(uint256(keccak256("examples/Timelock.t.sol")))); 27 | keymgr = new KeyManager_v0(address(andromeda)); 28 | (address xPub, bytes memory att) = keymgr.offchain_Bootstrap(); 29 | keymgr.onchain_Bootstrap(xPub, att); 30 | 31 | alice = vm.addr(uint256(keccak256("alice"))); 32 | bob = vm.addr(uint256(keccak256("bob"))); 33 | } 34 | 35 | function test_timelock() public { 36 | Timelock timelock = new Timelock(keymgr); 37 | 38 | // Initialize the derived public key 39 | assertEq(timelock.isInitialized(), false); 40 | (bytes memory dPub, bytes memory sig) = keymgr.offchain_DeriveKey( 41 | address(timelock) 42 | ); 43 | keymgr.onchain_DeriveKey(address(timelock), dPub, sig); 44 | assertEq(timelock.isInitialized(), true); 45 | 46 | // Submit encrypted orders 47 | string memory message = "Suave timelock test message!32xr"; 48 | bytes memory ciph = timelock.encryptMessage( 49 | message, 50 | bytes32(uint(0xdead2123)) 51 | ); 52 | timelock.submitEncrypted(ciph); 53 | 54 | vm.roll(60); 55 | 56 | // Off chain compute the solution 57 | bytes memory output = timelock.decrypt(ciph); 58 | string memory dec = string(output); 59 | assertEq(message, dec); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/fixtures/quotingenclave-identity.json: -------------------------------------------------------------------------------- 1 | {"enclaveIdentity": {"miscselect": "00000000", "miscselectMask": "FFFFFFFF", "isvprodid": 1, "attributes": "11000000000000000000000000000000", "attributesMask": "FBFFFFFFFFFFFFFF0000000000000000", "mrsigner": "8C4F5775D796503E96137F77C68A829A0056AC8DED70140B081B094490C57BFF", "tcbLevels": [{"tcb": {"isvsvn": 8}, "tcbStatus": "UpToDate"}, {"tcb": {"isvsvn": 6}, "tcbStatus": "OutOfDate"}, {"tcb": {"isvsvn": 5}, "tcbStatus": "OutOfDate"}, {"tcb": {"isvsvn": 4}, "tcbStatus": "OutOfDate"}, {"tcb": {"isvsvn": 2}, "tcbStatus": "OutOfDate"}, {"tcb": {"isvsvn": 1}, "tcbStatus": "OutOfDate"}]}} 2 | -------------------------------------------------------------------------------- /test/fixtures/tcbInfo.json: -------------------------------------------------------------------------------- 1 | {"fmspc": "00906ea10000", "pceid": "0000", "tcbLevels": [{"pcesvn": 13, "sgxTcbCompSvnArr": [20, 20, 2, 4, 1, 128, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 1}, {"pcesvn": 13, "sgxTcbCompSvnArr": [20, 20, 2, 4, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 2}, {"pcesvn": 13, "sgxTcbCompSvnArr": [19, 19, 2, 4, 1, 128, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 13, "sgxTcbCompSvnArr": [19, 19, 2, 4, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 5}, {"pcesvn": 11, "sgxTcbCompSvnArr": [17, 17, 2, 4, 1, 128, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 10, "sgxTcbCompSvnArr": [17, 17, 2, 4, 1, 128, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 11, "sgxTcbCompSvnArr": [17, 17, 2, 4, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 5}, {"pcesvn": 10, "sgxTcbCompSvnArr": [17, 17, 2, 4, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 5}, {"pcesvn": 10, "sgxTcbCompSvnArr": [15, 15, 2, 4, 1, 128, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 10, "sgxTcbCompSvnArr": [15, 15, 2, 4, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 5}, {"pcesvn": 10, "sgxTcbCompSvnArr": [14, 14, 2, 4, 1, 128, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 10, "sgxTcbCompSvnArr": [14, 14, 2, 4, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 5}, {"pcesvn": 9, "sgxTcbCompSvnArr": [13, 13, 2, 4, 1, 128, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 9, "sgxTcbCompSvnArr": [13, 13, 2, 4, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 5}, {"pcesvn": 7, "sgxTcbCompSvnArr": [6, 6, 2, 4, 1, 128, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 7, "sgxTcbCompSvnArr": [6, 6, 2, 4, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 5}, {"pcesvn": 7, "sgxTcbCompSvnArr": [5, 5, 2, 4, 1, 128, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 6, "sgxTcbCompSvnArr": [5, 5, 2, 4, 1, 128, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 7, "sgxTcbCompSvnArr": [5, 5, 2, 4, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 5}, {"pcesvn": 6, "sgxTcbCompSvnArr": [5, 5, 2, 4, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 5}, {"pcesvn": 5, "sgxTcbCompSvnArr": [4, 4, 2, 4, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 4, "sgxTcbCompSvnArr": [2, 2, 2, 4, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}]} 2 | -------------------------------------------------------------------------------- /test/fixtures/tcbInfo2.json: -------------------------------------------------------------------------------- 1 | {"pceid": "0000", "fmspc": "00a067110000", "tcbLevels": [{"pcesvn": 13, "sgxTcbCompSvnArr": [11, 11, 2, 2, 255, 1, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 1}, {"pcesvn": 13, "sgxTcbCompSvnArr": [11, 11, 2, 2, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 2}, {"pcesvn": 13, "sgxTcbCompSvnArr": [10, 10, 2, 2, 255, 1, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 13, "sgxTcbCompSvnArr": [10, 10, 2, 2, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 5}, {"pcesvn": 13, "sgxTcbCompSvnArr": [9, 9, 2, 2, 255, 1, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 13, "sgxTcbCompSvnArr": [9, 9, 2, 2, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 5}, {"pcesvn": 11, "sgxTcbCompSvnArr": [5, 5, 2, 2, 255, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 10, "sgxTcbCompSvnArr": [5, 5, 2, 2, 255, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}, {"pcesvn": 11, "sgxTcbCompSvnArr": [5, 5, 2, 2, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 5}, {"pcesvn": 10, "sgxTcbCompSvnArr": [5, 5, 2, 2, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 5}, {"pcesvn": 5, "sgxTcbCompSvnArr": [5, 5, 2, 2, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "status": 4}]} -------------------------------------------------------------------------------- /test/fixtures/testquote.hex: -------------------------------------------------------------------------------- 1 | 03000200000000000a000f00939a7233f79c4ca9940a0db3957f0607ce48836fd48a951172fe155220a719bd0000000014140207018001000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000700000000000000185237a9e29c9c47ea060b3740a285ce2e36a0b7b11e049488f4c0c77329a7a000000000000000000000000000000000000000000000000000000000000000001cf2e52911410fbf3f199056a98d58795a559a2e800933f7fcd13d048462271c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd5405e4d54a682000000000000000000000000000000000000000000000000000000000000000044100000c90b484614ab445b55d3dd9f824967b1848157790a10782170c422d494b7534bc2d562ed9e99823044c887e5b25fa6c551ea56970aa01cfb84d5f6b083e6d5fabe6177e039634cfbca4739ac246fda7df8c312a98f30f57b63f3c8921fce51d90a93031f97f769637be9b028e7b007a4e458d4fa717befbd81b06905082580131414020701800100000000000000000000000000000000000000000000000000000000000000000000000000000000001500000000000000070000000000000096b347a64e5a045e27369c26e6dcda51fd7c850e9b3a3a79e718f43261dee1e400000000000000000000000000000000000000000000000000000000000000008c4f5775d796503e96137f77c68a829a0056ac8ded70140b081b094490c57bff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b0f056c5355f6c770413938c2a41ed1b5c34ecb35f85fa539f16cca7a30d6da90000000000000000000000000000000000000000000000000000000000000000db06fc2defb1353d94035cf090c5677609eae506b69526c7346ac26a1821b73453fd5198175dff5516c8d0b9e5ef83a90498249b5950dc932ab7a9beaf6e87a32000000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f0500dc0d00002d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949456a544343424453674177494241674956414a5172493559365a78484836785034424941715a4e6b326e4c7a594d416f4743437147534d343942414d430a4d484578497a416842674e5642414d4d476b6c756447567349464e48574342515130736755484a765932567a6332397949454e424d526f77474159445651514b0a4442464a626e526c6243424462334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e560a4241674d416b4e424d517377435159445651514745774a56557a4165467730794d7a45784d5441784e7a45334d4452614677307a4d4445784d5441784e7a45330a4d4452614d484178496a416742674e5642414d4d47556c756447567349464e4857434251513073675132567964476c6d61574e6864475578476a415942674e560a42416f4d45556c756447567349454e76636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b470a413155454341774351304578437a414a42674e5642415954416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741450a77625468574d583134443163657835317875614958456771517a69636e744b7a48454a32536f31336e384a427050314a67383673764263462f7070715a554e710a68524b4642667469584c6d4f536c614955784e6e51364f434171677767674b6b4d42384741315564497751594d426141464e446f71747031312f6b75535265590a504873555a644456386c6c4e4d477747413155644877526c4d474d77596142666f463247573268306448427a4f693876595842704c6e527964584e305a57527a0a5a584a3261574e6c63793570626e526c6243356a62323076633264344c324e6c636e52705a6d6c6a5958527062323476646a517663474e7259334a7350324e680a5058427962324e6c63334e7663695a6c626d4e765a476c755a7a316b5a584977485159445652304f42425945464a57566e41395a63736f3753356b6a2f647a4a0a594f3034534175644d41344741315564447745422f775145417749477744414d42674e5648524d4241663845416a41414d4949423141594a4b6f5a496876684e0a415130424249494278544343416345774867594b4b6f5a496876684e41513042415151514d6d5867725757774c59554164456d6c766c366153444343415751470a43697147534962345451454e41514977676746554d42414743797147534962345451454e41514942416745554d42414743797147534962345451454e415149430a416745554d42414743797147534962345451454e41514944416745434d42414743797147534962345451454e41514945416745454d42414743797147534962340a5451454e41514946416745424d42454743797147534962345451454e41514947416749416744415142677371686b69472b4530424451454342774942414441510a42677371686b69472b45304244514543434149424144415142677371686b69472b45304244514543435149424144415142677371686b69472b453042445145430a436749424144415142677371686b69472b45304244514543437749424144415142677371686b69472b45304244514543444149424144415142677371686b69470a2b45304244514543445149424144415142677371686b69472b45304244514543446749424144415142677371686b69472b4530424451454344774942414441510a42677371686b69472b45304244514543454149424144415142677371686b69472b45304244514543455149424454416642677371686b69472b453042445145430a4567515146425143424147414141414141414141414141414144415142676f71686b69472b45304244514544424149414144415542676f71686b69472b4530420a44514545424159416b473668414141774477594b4b6f5a496876684e4151304242516f424144414b42676771686b6a4f5051514441674e4841444245416942560a6e584667364277466a6945474230417162424e702b4b56734a477245744f4f49666e6f365450387031414967536c4430574e39595261575968346534656835330a314637434537664964724f55414c5177757632735948513d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436d444343416a36674177494241674956414e446f71747031312f6b7553526559504873555a644456386c6c4e4d416f4743437147534d343942414d430a4d476778476a415942674e5642414d4d45556c756447567349464e48574342536232393049454e424d526f77474159445651514b4442464a626e526c624342440a62334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e564241674d416b4e424d5173770a435159445651514745774a56557a4165467730784f4441314d6a45784d4455774d5442614677307a4d7a41314d6a45784d4455774d5442614d484578497a41680a42674e5642414d4d476b6c756447567349464e48574342515130736755484a765932567a6332397949454e424d526f77474159445651514b4442464a626e526c0a6243424462334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e564241674d416b4e420a4d517377435159445651514745774a56557a425a4d424d4742797147534d34394167454743437147534d34394177454841304941424c39712b4e4d7032494f670a74646c31626b2f75575a352b5447516d38614369387a373866732b664b435133642b75447a586e56544154325a68444369667949754a77764e33774e427039690a484253534d4a4d4a72424f6a6762737767626777487759445652306a42426777466f4155496d554d316c71644e496e7a6737535655723951477a6b6e427177770a556759445652306642457377535442486f45576751345a426148523063484d364c79396a5a584a3061575a70593246305a584d7564484a316333526c5a484e6c0a636e5a705932567a4c6d6c75644756734c6d4e766253394a626e526c62464e4857464a76623352445153356b5a584977485159445652304f42425945464e446f0a71747031312f6b7553526559504873555a644456386c6c4e4d41344741315564447745422f77514541774942426a415342674e5648524d4241663845434441470a4151482f416745414d416f4743437147534d343942414d43413067414d4555434951434a6754627456714f795a316d336a716941584d365159613672357357530a34792f4737793875494a4778647749675271507642534b7a7a516167424c517135733541373070646f6961524a387a2f3075447a344e675639316b3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436a7a4343416a53674177494241674955496d554d316c71644e496e7a6737535655723951477a6b6e42717777436759494b6f5a497a6a3045417749770a614445614d4267474131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e760a636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a0a42674e5642415954416c56544d423458445445344d4455794d5445774e4455784d466f58445451354d54497a4d54497a4e546b314f566f77614445614d4267470a4131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e76636e4276636d46300a615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a42674e56424159540a416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414543366e45774d4449595a4f6a2f69505773437a61454b69370a314f694f534c52466857476a626e42564a66566e6b59347533496a6b4459594c304d784f346d717379596a6c42616c54565978465032734a424b357a6c4b4f420a757a43427544416642674e5648534d4547444157674251695a517a575770303069664f44744a5653763141624f5363477244425342674e5648523845537a424a0a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e567a6447566b63325679646d6c6a5a584d75615735300a5a577775593239744c306c756447567355306459556d397664454e424c6d526c636a416442674e564851344546675155496d554d316c71644e496e7a673753560a55723951477a6b6e4271777744675944565230504151482f42415144416745474d42494741315564457745422f7751494d4159424166384341514577436759490a4b6f5a497a6a3045417749445351417752674968414f572f35516b522b533943695344634e6f6f774c7550524c735747662f59693747535839344267775477670a41694541344a306c72486f4d732b586f356f2f7358364f39515778485241765a55474f6452513763767152586171493d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a00 -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | --------------------------------------------------------------------------------