diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bd0747e..3054c897 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,6 +126,9 @@ jobs: # Build OP-TEE Rust examples for Arm 64-bit both host and TA make -j`nproc` + + # Build project + (cd projects/web3/eth_wallet && make -j`nproc`) - name: Run tests for Arm 64-bit both host and TA run: | apt update && apt install libslirp-dev -y @@ -162,6 +165,9 @@ jobs: # Build OP-TEE Rust examples for Arm 64-bit both host and TA make -j`nproc` + + # Build project + (cd projects/web3/eth_wallet && make -j`nproc`) - name: Run tests for Arm 32-bit both host and TA run: | apt update && apt install libslirp-dev -y diff --git a/ci/ci.sh b/ci/ci.sh index da122261..c58a03f8 100755 --- a/ci/ci.sh +++ b/ci/ci.sh @@ -44,6 +44,7 @@ if [ "$STD" ]; then ./test_udp_socket.sh ./test_tls_client.sh ./test_tls_server.sh + ./test_eth_wallet.sh fi popd diff --git a/projects/README.md b/projects/README.md new file mode 100644 index 00000000..3ad8f82c --- /dev/null +++ b/projects/README.md @@ -0,0 +1,44 @@ +# Projects in Multiple Scenarios + +Trusted Execution Environments (TEEs) play a vital role in providing critical +security solutions across various scenarios. The Teaclave TrustZone SDK empowers +developers to implement robust use cases such as Web3 private key protection, +authentication, and more. + +The `projects/` directory showcases real-world scenarios and essential +primitives designed to help developers build secure applications tailored to +their needs. + +Currently, we have released a Web3-focused scenario, with plans to expand the +project and introduce more use cases in the future. + +## Available Scenarios + +- **Web3**: Available in `projects/web3/`, this scenario offers utilities for + Web3 development, such as key custodians and decentralized identifiers (DIDs). + It currently includes a basic Ethereum wallet that demonstrates how to + securely create a wallet and sign transactions using wallet-derived keys + within the TEE. + +## Upcoming Scenarios + +- **X509 Certificate Signing & Verification**: This scenario provides + foundational Public Key Infrastructure (PKI) primitives for securely issuing + self-signed certificates and verifying externally provided leaf certificates + using a trusted certificate store. The Trusted Application (TA) inside the TEE + handles secure key pair generation and certificate issuance, facilitating + identity verification for secure communications. This primitive is + particularly valuable for establishing trusted communication channels between + nodes or devices. + +- **Remote Attestation**: This foundational primitive enables remote attestation + of a Trusted Application (TA) to ensure it is running within a Trusted + Execution Environment (TEE). It utilizes TLS and X509 PKI to establish a + secure communication channel. + +- **Multi-Factor Authentication (MFA)**: This example demonstrates how to + implement MFA by securely provisioning the public keys of trusted MFA devices + (e.g., a user’s cellphone) within the Trusted Application (TA). When high-risk + operations like key usage or transaction signing require user confirmation, + the TA securely verifies user-provided details via the trusted MFA device, + eliminating reliance on third-party services. diff --git a/projects/web3/README.md b/projects/web3/README.md new file mode 100644 index 00000000..e501c2ae --- /dev/null +++ b/projects/web3/README.md @@ -0,0 +1,41 @@ +# Reference Implementation Examples for Web3 Trusted Applications + +Teaclave TrustZone SDK allows developers to create Trusted Applications (TAs) in +Rust, offering a memory-safe and secure environment. Many examples in this +repository are ported from OP-TEE C examples. With Rust's ecosystem and support +for Rust-std in Teaclave TrustZone SDK, developers can build secure TAs to +protect confidential information. + +In Web3, private key protection is vital for securing on-chain identities and +assets. TAs safeguard the entire lifecycle of Web3 credentials used in wallets +or validator key protection. In DePIN, TAs enable secure device attestation, +helping to prevent Sybil attacks. + +This directory contains a collection of reference implementations of TAs, +specifically tailored for Web3 use cases. These examples demonstrate how to use +Rust within TrustZone to support basic Web3 use cases. We will gradually +open-source each of them as reference implementation examples for Web3 TAs. Web3 +builders can leverage these examples to integrate secure functionalities into +their projects, particularly in environments where OP-TEE and TrustZone +technologies are employed. + +## Basic Web3 Wallet + +**AVAILABLE** in [eth-wallet/](./eth-wallet) + +A wallet abstraction featuring key functionalities like secure key management +and transaction signing. The key management includes secure seed generation, +mnemonic derivation, and safe key storage within external TEE-protected +environments. For transaction signing, we demonstrate how to securely sign an +Ethereum transaction using wallet-derived keys inside the TEE, ensuring the +private keys never leave the trusted environment. + +## Decentralized Identifier (DID) + +**To Be Released** + +This example will illustrate how to integrate Decentralized Identifiers (DIDs) +into TAs. DIDs enable self-sovereign identity by proving ownership without +relying on central authorities. Secure key management for creating and operating +DIDs ensures reliable device identification, mitigating the risk of fake devices +in DePIN. diff --git a/projects/web3/eth_wallet/Makefile b/projects/web3/eth_wallet/Makefile new file mode 100644 index 00000000..c266055b --- /dev/null +++ b/projects/web3/eth_wallet/Makefile @@ -0,0 +1,33 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# If _HOST or _TA specific compiler/target are not specified, then use common +# compiler/target for both +CROSS_COMPILE_HOST ?= aarch64-linux-gnu- +CROSS_COMPILE_TA ?= aarch64-linux-gnu- +TARGET_HOST ?= aarch64-unknown-linux-gnu +TARGET_TA ?= aarch64-unknown-linux-gnu + +all: + $(q)make -C host TARGET_HOST=$(TARGET_HOST) \ + CROSS_COMPILE_HOST=$(CROSS_COMPILE_HOST) + $(q)make -C ta TARGET_TA=$(TARGET_TA) \ + CROSS_COMPILE_TA=$(CROSS_COMPILE_TA) + +clean: + $(q)make -C host clean + $(q)make -C ta clean diff --git a/projects/web3/eth_wallet/README.md b/projects/web3/eth_wallet/README.md new file mode 100644 index 00000000..55524574 --- /dev/null +++ b/projects/web3/eth_wallet/README.md @@ -0,0 +1,234 @@ +# Eth-Wallet: A Sample Trusted Application for Wallet Abstraction and Transaction Signing + +This repository provides a reference implementation of an Ethereum wallet as a +Trusted Application (TA) written in Rust. The primary goal is to ensure that +secret credentials (such as private keys) remain securely within the Trusted +Execution Environment (TEE) throughout their entire lifecycle, enhancing +security and privacy for Ethereum-based operations. This reference +implementation can be extended to support additional wallet features or adapted +to other blockchain platforms with similar requirements for secure key +management. The implementation provides basic wallet abstractions, including: + +- Key Generation: Securely generating random seeds within the TEE. +- Key Derivation: Deriving keys from seeds within the TEE. +- Key Persistency: Storing cryptographic keys securely in the TEE. +- Transaction Signing: Signing Ethereum transactions without exposing private + keys to the normal world. +- Key Erase: Erasing keys when they are no longer needed. + +## Security Assumptions + +This demo assumes the following security foundations: + +1. **Trusted Environment**: + + - The device supports OP-TEE as the TEE operating system. + - Both the TEE OS and the Trusted Application (TA) are considered secure and + trusted. + +2. **Hardware-Specific Security Capabilities** + - The hardware provides secure storage capabilities to protect cryptographic + keys. + - The device includes secure display capabilities (or a Multi-Factor + Authentication device as alternative) for secure user interface. (MFA + integration is planned for another demo project) + - Note that these capabilities depend on specific hardware implementations. + While this demo provides a default implementation, it should be customized + to suit the target hardware. + +### Important Notes on Security Design + +This demo focuses on showcasing core functionalities and may not implement all +security measures required for a production-grade key custodian solution across +the entire key lifecycle. Developers should address the following considerations +when adapting this demo for real-world use cases: + +- **Secure User Interface**: + In the `create_wallet` function, the mnemonic is returned to the Normal World + for backup. This approach is inherently risky. For production systems, it is + strongly recommended to display the mnemonic on a Trusted UI or secure + display. Additionally, transactions should be confirmed by the user through + this secure display. As secure display implementations are hardware-specific, + this demo does not include such functionality. + +- **Secure Storage Limitations**: + Keys in this demo are stored in an encrypted file on the Normal World File + System. While this approach ensures basic protection, root access in the + Normal World could delete this file, leading to key loss. For production + scenarios, consider more reliable storage solutions like Replay Protected + Memory Block (RPMB), which is hardware-specific and not included in this demo. + +For developers, please note that this demo is intended as a foundational +reference and must be enhanced with hardware-specific adaptations for +production-grade security. + +## Structure + +- [TA](./ta): The Trusted Application (TA) that performs all secure operations + related to the wallet. This component runs within the TrustZone TEE, ensuring + that secret credentials never leave the secure environment. +- [CA](./host): The Client Application (CA) that runs in the normal world and + communicates with the TA. It is responsible for user interaction and + non-sensitive operations. +- [Proto](./proto): Contains shared structures and definitions used by both the + TA and CA to facilitate communication between the two environments. + +## Setup + +To set up the environment, follow the instructions in the +[Apache Teaclave TrustZone SDK README](https://github.com/apache/incubator-teaclave-trustzone-sdk/blob/master/README.md). + +## Functionalities + +- **Create Wallet**: Generate a new Ethereum wallet with a unique ID. +- **Derive Address**: Derive an Ethereum address from a wallet. +- **Sign Transaction**: Sign Ethereum transactions securely within the TEE. +- **Remove Wallet**: Delete a wallet and its associated keys from the TEE. + +## Usage + +### Build + +``` +$ cd projects/eth_wallet-rs +$ make +``` + +### Run + +After QEMU boots: + +```bash +Welcome to Buildroot, type root or test to login +buildroot login: root +# mkdir shared && mount -t 9p -o trans=virtio host shared +# cd shared/ +# ls +be2dc9a0-02b4-4b33-ba21-9964dbdf1573.ta +eth_wallet-rs +# cp be2dc9a0-02b4-4b33-ba21-9964dbdf1573.ta /lib/optee_armtz/ +# ./eth_wallet-rs +``` + +### Command-Line Interface + +```bash +A simple Ethereum wallet based on TEE + +USAGE: +eth_wallet-rs + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +SUBCOMMANDS: + create-wallet Create a new wallet + derive-address Derive an address from a wallet + help Prints this message or the help of the given subcommand(s) + remove-wallet Remove a wallet + sign-transaction Sign a transaction + test Run tests +``` + +## Example Commands + +### Create a Wallet + +```bash +# ./eth_wallet-rs create-wallet +``` + +**CA Output:** + +```text +CA: command: CreateWallet +CA: invoke_command success +Wallet ID: aa5798a1-3c89-4708-b316-712aea4f59e2 +``` + +**TA Output:** + +```text +[+] TA create +[+] TA open session +[+] TA invoke command +[+] Wallet created: Wallet { id: aa5798a1-3c89-4708-b316-712aea4f59e2, entropy: [...] } +[+] Wallet ID: aa5798a1-3c89-4708-b316-712aea4f59e2 +[+] Wallet saved in secure storage +``` + +### Derive an Address + +```bash +# ./eth_wallet-rs derive-address -w aa5798a1-3c89-4708-b316-712aea4f59e2 +``` + +**CA Output:** + +```text +CA: command: DeriveAddress +CA: invoke_command success +Address: 0x7ca2b64a29bbf7a77bf8a3187ab09f50413826ea +Public key: 03e1289e07eca6fe47c4825ea52f7cd27e3143ac5d65d5842aa5f59b5eba2d58df +``` + +**TA Output:** + +```text +[+] TA invoke command +[+] Deriving address: secure object loaded +[+] Wallet::derive_pub_key(): pub key: "xpub6FhY8TmVeQ6Yo5ViNX6LK3mM66nMJDe4ZumHmznLNRkK2wEhGoEjaossvKmjgETpFHNGs9CFjUS7HK1un9Djzw9jfsukyNxu53b87abRJUv" +[+] Wallet::derive_pub_key(): non-extended pub key: 03e1289e07eca6fe47c4825ea52f7cd27e3143ac5d65d5842aa5f59b5eba2d58df +[+] Wallet::derive_address(): address: [124, 162, 182, 74, 41, 187, 247, 167, 123, 248, 163, 24, 122, 176, 159, 80, 65, 56, 38, 234] +[+] Deriving address: address: [124, 162, 182, 74, 41, 187, 247, 167, 123, 248, 163, 24, 122, 176, 159, 80, 65, 56, 38, 234] +[+] Deriving address: public key: [3, 225, 40, 158, 7, 236, 166, 254, 71, 196, 130, 94, 165, 47, 124, 210, 126, 49, 67, 172, 93, 101, 213, 132, 42, 165, 245, 155, 94, 186, 45, 88, 223] +``` + +### Sign a Transaction + +```bash +# ./eth_wallet-rs sign-transaction -t 0xc0ffee254729296a45a3885639AC7E10F9d54979 -v 100 -w aa5798a1-3c89-4708-b316-712aea4f59e2 +``` + +**CA Output:** + +```text +CA: command: SignTransaction +CA: invoke_command success +Signature: "f86380843b9aca0082520894c0ffee254729296a45a3885639ac7e10f9d5497964802ea0774fc5a364c3d7e3f4e039f8da96b66fb0a5d51cad7524e54a0c9013fb473304a033922ecf964f02c6ebdd7380bc86fe759b65c87dc9e09677d983622e35334931" +``` + +**TA Output:** + +```text +[+] TA invoke command +[+] Sign transaction: secure object loaded +[+] Wallet::derive_prv_key() finished +[+] sign_transaction: signed transaction bytes: [248, 99, 128, 132, 59, 154, 202, 0, 130, 82, 8, 148, 192, 255, 238, 37, 71, 41, 41, 106, 69, 163, 136, 86, 57, 172, 126, 16, 249, 213, 73, 121, 100, 128, 46, 160, 119, 79, 197, 163, 100, 195, 215, 227, 244, 224, 57, 248, 218, 150, 182, 111, 176, 165, 213, 28, 173, 117, 36, 229, 74, 12, 144, 19, 251, 71, 51, 4, 160, 51, 146, 46, 207, 150, 79, 2, 198, 235, 221, 115, 128, 188, 134, 254, 117, 155, 101, 200, 125, 201, 224, 150, 119, 217, 131, 98, 46, 53, 51, 73, 49] +[+] Sign transaction: signature: [248, 99, 128, 132, 59, 154, 202, 0, 130, 82, 8, 148, 192, 255, 238, 37, 71, 41, 41, 106, 69, 163, 136, 86, 57, 172, 126, 16, 249, 213, 73, 121, 100, 128, 46, 160, 119, 79, 197, 163, 100, 195, 215, 227, 244, 224, 57, 248, 218, 150 + +, 182, 111, 176, 165, 213, 28, 173, 117, 36, 229, 74, 12, 144, 19, 251, 71, 51, 4, 160, 51, 146, 46, 207, 150, 79, 2, 198, 235, 221, 115, 128, 188, 134, 254, 117, 155, 101, 200, 125, 201, 224, 150, 119, 217, 131, 98, 46, 53, 51, 73, 49] +``` + +### Remove a Wallet + +```bash +# ./eth_wallet-rs remove-wallet -w aa5798a1-3c89-4708-b316-712aea4f59e2 +``` + +**CA Output:** + +```text +CA: command: RemoveWallet +CA: invoke_command success +Wallet removed +``` + +**TA Output:** + +```text +[+] TA invoke command +[+] Removing wallet: secure object loaded +[+] Wallet removed from secure storage +``` diff --git a/projects/web3/eth_wallet/host/Cargo.toml b/projects/web3/eth_wallet/host/Cargo.toml new file mode 100644 index 00000000..e1e36bf7 --- /dev/null +++ b/projects/web3/eth_wallet/host/Cargo.toml @@ -0,0 +1,38 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[package] +name = "eth_wallet-rs" +version = "0.1.0" +authors = ["Teaclave Contributors "] +license = "Apache-2.0" +repository = "https://github.com/apache/incubator-teaclave-trustzone-sdk.git" +description = "An example of Rust OP-TEE TrustZone SDK." +edition = "2018" + +[dependencies] +proto = { path = "../proto" } +optee-teec = { path = "../../../../optee-teec" } + +structopt = "0.3" +bincode = "1.3.3" +anyhow = "1.0" +uuid = { version = "1.8", features = ["serde"] } +hex = { version = "0.4", features = ["serde"] } + +[profile.release] +lto = true diff --git a/projects/web3/eth_wallet/host/Makefile b/projects/web3/eth_wallet/host/Makefile new file mode 100644 index 00000000..b1c50c41 --- /dev/null +++ b/projects/web3/eth_wallet/host/Makefile @@ -0,0 +1,41 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +NAME := eth_wallet-rs + +TARGET_HOST ?= aarch64-unknown-linux-gnu +CROSS_COMPILE_HOST ?= aarch64-linux-gnu- +OBJCOPY := $(CROSS_COMPILE_HOST)objcopy +LINKER_CFG := target.$(TARGET_HOST).linker=\"$(CROSS_COMPILE_HOST)gcc\" + +OUT_DIR := $(CURDIR)/target/$(TARGET_HOST)/release + +ifeq ($(STD),) +all: + @echo "Please `export STD=y` then rerun `source environment` to build the STD version" +else +all: host strip +endif + +host: + @cargo build --target $(TARGET_HOST) --release --config $(LINKER_CFG) + +strip: host + @$(OBJCOPY) --strip-unneeded $(OUT_DIR)/$(NAME) $(OUT_DIR)/$(NAME) + +clean: + @cargo clean diff --git a/projects/web3/eth_wallet/host/src/cli.rs b/projects/web3/eth_wallet/host/src/cli.rs new file mode 100644 index 00000000..68815186 --- /dev/null +++ b/projects/web3/eth_wallet/host/src/cli.rs @@ -0,0 +1,100 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use anyhow::{bail, Result}; +use structopt::StructOpt; + +// decode hex string to [u8; 20] +pub fn decode_hex_to_address(src: &str) -> Result<[u8; 20]> { + // strip the 0x prefix + let src = src.trim_start_matches("0x"); + let vec = hex::decode(src)?; + if vec.len() < 20 { + bail!("invalid address length: {}", vec.len()); + } + let mut array = [0u8; 20]; + array.copy_from_slice(&vec[..20]); + Ok(array) +} + +// decode string to uuid +pub fn decode_str_to_uuid(s: &str) -> Result { + uuid::Uuid::parse_str(s).map_err(|e| e.into()) +} + +#[derive(Debug, StructOpt)] +pub struct CreateWalletOpt {} + +#[derive(Debug, StructOpt)] +pub struct RemoveWalletOpt { + #[structopt(short, long, required = true)] + pub wallet_id: uuid::Uuid, +} + +#[derive(Debug, StructOpt)] +pub struct DeriveAddressOpt { + #[structopt(short, long, required = true)] + pub wallet_id: uuid::Uuid, + #[structopt(short, long, required = true, default_value = "m/44'/60'/0'/0/0")] + pub hd_path: String, +} + +#[derive(Debug, StructOpt)] +pub struct SignTransactionOpt { + #[structopt(short, long, required = true, parse(try_from_str = decode_str_to_uuid))] + pub wallet_id: uuid::Uuid, + #[structopt(short, long, default_value = "m/44'/60'/0'/0/0")] + pub hd_path: String, + #[structopt(short, long, default_value = "5")] + pub chain_id: u64, + #[structopt(short, long, default_value = "0")] + pub nonce: u128, + #[structopt(short, long, required = true, parse(try_from_str = decode_hex_to_address))] + pub to: [u8; 20], + #[structopt(short, long, required = true)] + pub value: u128, + #[structopt(short = "p", long, default_value = "1000000000")] + pub gas_price: u128, + #[structopt(short, long, default_value = "21000")] + pub gas: u128, +} + +#[derive(Debug, StructOpt)] +pub enum Command { + /// Create a new wallet. + #[structopt(name = "create-wallet")] + CreateWallet(CreateWalletOpt), + /// Remove a wallet. + #[structopt(name = "remove-wallet")] + RemoveWallet(RemoveWalletOpt), + /// Derive an address from a wallet. + #[structopt(name = "derive-address")] + DeriveAddress(DeriveAddressOpt), + /// Sign a transaction. + #[structopt(name = "sign-transaction")] + SignTransaction(SignTransactionOpt), + /// Run tests + #[structopt(name = "test")] + Test, +} + +#[derive(Debug, StructOpt)] +#[structopt(name = "eth_wallet", about = "A simple Ethereum wallet based on TEE")] +pub struct Opt { + #[structopt(subcommand)] + pub command: Command, +} diff --git a/projects/web3/eth_wallet/host/src/main.rs b/projects/web3/eth_wallet/host/src/main.rs new file mode 100644 index 00000000..6c4d71af --- /dev/null +++ b/projects/web3/eth_wallet/host/src/main.rs @@ -0,0 +1,152 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +mod cli; +mod tests; + +use optee_teec::{Context, Operation, ParamType, Uuid}; +use optee_teec::{ParamNone, ParamTmpRef, ParamValue}; + +use anyhow::{bail, Result}; +use structopt::StructOpt; + +const OUTPUT_MAX_SIZE: usize = 1024; + +fn invoke_command(command: proto::Command, input: &[u8]) -> optee_teec::Result> { + let mut ctx = Context::new()?; + let uuid = Uuid::parse_str(proto::UUID) + .map_err(|_| optee_teec::Error::new(optee_teec::ErrorKind::ItemNotFound))?; + let mut session = ctx.open_session(uuid)?; + + println!("CA: command: {:?}", command); + // input buffer + let p0 = ParamTmpRef::new_input(input); + // output buffer + let mut output = vec![0u8; OUTPUT_MAX_SIZE]; + let p1 = ParamTmpRef::new_output(output.as_mut_slice()); + // output buffer size + let p2 = ParamValue::new(0, 0, ParamType::ValueInout); + + let mut operation = Operation::new(0, p0, p1, p2, ParamNone); + match session.invoke_command(command as u32, &mut operation) { + Ok(()) => { + println!("CA: invoke_command success"); + let output_len = operation.parameters().2.a() as usize; + Ok(output[..output_len].to_vec()) + } + Err(e) => { + let output_len = operation.parameters().2.a() as usize; + let err_message = String::from_utf8_lossy(&output[..output_len]); + println!("CA: invoke_command failed: {:?}", err_message); + Err(e) + } + } +} + +pub fn create_wallet() -> Result { + let serialized_output = invoke_command(proto::Command::CreateWallet, &[])?; + let output: proto::CreateWalletOutput = bincode::deserialize(&serialized_output)?; + Ok(output.wallet_id) +} + +pub fn remove_wallet(wallet_id: uuid::Uuid) -> Result<()> { + let input = proto::RemoveWalletInput { wallet_id }; + let _output = invoke_command(proto::Command::RemoveWallet, &bincode::serialize(&input)?)?; + Ok(()) +} + +pub fn derive_address(wallet_id: uuid::Uuid, hd_path: &str) -> Result<[u8; 20]> { + let input = proto::DeriveAddressInput { + wallet_id, + hd_path: hd_path.to_string(), + }; + let serialized_output = + invoke_command(proto::Command::DeriveAddress, &bincode::serialize(&input)?)?; + let output: proto::DeriveAddressOutput = bincode::deserialize(&serialized_output)?; + Ok(output.address) +} + +pub fn sign_transaction( + wallet_id: uuid::Uuid, + hd_path: &str, + chain_id: u64, + nonce: u128, + to: [u8; 20], + value: u128, + gas_price: u128, + gas: u128, +) -> Result> { + let transaction = proto::EthTransaction { + chain_id, + nonce, + to: Some(to), + value, + gas_price, + gas, + data: vec![], + }; + let input = proto::SignTransactionInput { + wallet_id, + hd_path: hd_path.to_string(), + transaction, + }; + let serialized_output = invoke_command( + proto::Command::SignTransaction, + &bincode::serialize(&input)?, + )?; + let output: proto::SignTransactionOutput = bincode::deserialize(&serialized_output)?; + Ok(output.signature) +} + +fn main() -> Result<()> { + let args = cli::Opt::from_args(); + match args.command { + cli::Command::CreateWallet(_opt) => { + let wallet_id = create_wallet()?; + println!("Wallet ID: {}", wallet_id); + } + cli::Command::RemoveWallet(opt) => { + remove_wallet(opt.wallet_id)?; + println!("Wallet removed"); + } + cli::Command::DeriveAddress(opt) => { + let address = derive_address(opt.wallet_id, &opt.hd_path)?; + println!("Address: 0x{}", hex::encode(&address)); + } + cli::Command::SignTransaction(opt) => { + let signature = sign_transaction( + opt.wallet_id, + &opt.hd_path, + opt.chain_id, + opt.nonce, + opt.to, + opt.value, + opt.gas_price, + opt.gas, + )?; + println!("Signature: {}", hex::encode(&signature)); + } + cli::Command::Test => { + tests::tests::test_workflow(); + println!("Tests passed"); + } + _ => { + bail!("Unsupported command"); + } + } + Ok(()) +} diff --git a/projects/web3/eth_wallet/host/src/tests.rs b/projects/web3/eth_wallet/host/src/tests.rs new file mode 100644 index 00000000..439cc170 --- /dev/null +++ b/projects/web3/eth_wallet/host/src/tests.rs @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +pub mod tests { + use crate::*; + + pub fn test_workflow() { + // Simulate the workflow of creating a wallet, deriving an address, and signing a transaction + let wallet_id = create_wallet().unwrap(); + let address = derive_address(wallet_id, "m/44'/60'/0'/0/0").unwrap(); + let result = sign_transaction( + wallet_id, + "m/44'/60'/0'/0/0", + 5, + 0, + address, + 100, + 1000000000, + 21000, + ); + assert!(result.is_ok()); + } +} diff --git a/projects/web3/eth_wallet/proto/Cargo.toml b/projects/web3/eth_wallet/proto/Cargo.toml new file mode 100644 index 00000000..1bd36816 --- /dev/null +++ b/projects/web3/eth_wallet/proto/Cargo.toml @@ -0,0 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[package] +name = "proto" +version = "0.3.0" +authors = ["Teaclave Contributors "] +license = "Apache-2.0" +repository = "https://github.com/apache/incubator-teaclave-trustzone-sdk.git" +description = "Data structures and functions shared by host and TA." +edition = "2018" + +[dependencies] +uuid = { version = "1.8", features = ["serde"] } +serde = { version = "1.0", features = ["derive"] } + +[build-dependencies] +uuid = { version = "1.8", default-features = false } diff --git a/projects/web3/eth_wallet/proto/build.rs b/projects/web3/eth_wallet/proto/build.rs new file mode 100644 index 00000000..b9d06122 --- /dev/null +++ b/projects/web3/eth_wallet/proto/build.rs @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::fs; +use std::path::PathBuf; +use std::fs::File; +use std::env; +use std::io::Write; + +fn main() { + let uuid = match fs::read_to_string("../uuid.txt") { + Ok(u) => { + u.trim().to_string() + }, + Err(_) => { + panic!("Cannot find uuid.txt"); + } + }; + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let mut buffer = File::create(out.join("uuid.txt")).unwrap(); + write!(buffer, "{}", uuid).unwrap(); +} diff --git a/projects/web3/eth_wallet/proto/src/in_out.rs b/projects/web3/eth_wallet/proto/src/in_out.rs new file mode 100644 index 00000000..36a5bcee --- /dev/null +++ b/projects/web3/eth_wallet/proto/src/in_out.rs @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CreateWalletInput {} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CreateWalletOutput { + pub wallet_id: Uuid, + pub mnemonic: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RemoveWalletInput { + pub wallet_id: Uuid, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RemoveWalletOutput {} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DeriveAddressInput { + pub wallet_id: Uuid, + pub hd_path: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DeriveAddressOutput { + pub address: [u8; 20], + pub public_key: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct EthTransaction { + pub chain_id: u64, + pub nonce: u128, + pub to: Option<[u8; 20]>, + pub value: u128, + pub gas_price: u128, + pub gas: u128, + pub data: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SignTransactionInput { + pub wallet_id: Uuid, + pub hd_path: String, + pub transaction: EthTransaction, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SignTransactionOutput { + pub signature: Vec, +} diff --git a/projects/web3/eth_wallet/proto/src/lib.rs b/projects/web3/eth_wallet/proto/src/lib.rs new file mode 100644 index 00000000..87c42bb3 --- /dev/null +++ b/projects/web3/eth_wallet/proto/src/lib.rs @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +mod in_out; +pub use in_out::*; + +#[derive(Debug)] +pub enum Command { + CreateWallet, + RemoveWallet, + DeriveAddress, + SignTransaction, + Unknown, +} + +impl From for Command { + #[inline] + fn from(value: u32) -> Command { + match value { + 0 => Command::CreateWallet, + 1 => Command::RemoveWallet, + 2 => Command::DeriveAddress, + 3 => Command::SignTransaction, + _ => Command::Unknown, + } + } +} + +pub const UUID: &str = &include_str!(concat!(env!("OUT_DIR"), "/uuid.txt")); diff --git a/projects/web3/eth_wallet/ta/Cargo.toml b/projects/web3/eth_wallet/ta/Cargo.toml new file mode 100644 index 00000000..e118e599 --- /dev/null +++ b/projects/web3/eth_wallet/ta/Cargo.toml @@ -0,0 +1,49 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[package] +name = "ta" +version = "0.3.0" +authors = ["Teaclave Contributors "] +license = "Apache-2.0" +repository = "https://github.com/apache/incubator-teaclave-trustzone-sdk.git" +description = "An example of Rust OP-TEE TrustZone SDK." +edition = "2018" + +[dependencies] +libc = { path = "../../../../rust/libc" } +proto = { path = "../proto" } +optee-utee-sys = { path = "../../../../optee-utee/optee-utee-sys" } +optee-utee = { path = "../../../../optee-utee" } + +anyhow = "1.0" +uuid = { version = "1.8", default-features = false } +bip32 = { version = "0.3.0", features = ["bip39"]} +hex = { version = "0.4", features = ["serde"] } +serde = { version = "1.0", features = ["derive"] } +sha3 = "0.10.6" +secp256k1 = "0.27.0" +ethereum-tx-sign = "6.1.3" +bincode = "1.3.3" + +[build-dependencies] +uuid = { version = "1.8", default-features = false } +proto = { path = "../proto" } + +[profile.release] +lto = false +opt-level = 3 diff --git a/projects/web3/eth_wallet/ta/Makefile b/projects/web3/eth_wallet/ta/Makefile new file mode 100644 index 00000000..46037a8e --- /dev/null +++ b/projects/web3/eth_wallet/ta/Makefile @@ -0,0 +1,50 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# STD-ONLY example + +UUID ?= $(shell cat "../uuid.txt") + +TARGET_TA ?= aarch64-unknown-linux-gnu +CROSS_COMPILE_TA ?= aarch64-linux-gnu- +OBJCOPY := $(CROSS_COMPILE_TA)objcopy +LINKER_CFG := target.$(TARGET_TA).linker=\"$(CROSS_COMPILE_TA)ld.bfd\" + +TA_SIGN_KEY ?= $(TA_DEV_KIT_DIR)/keys/default_ta.pem +SIGN := $(TA_DEV_KIT_DIR)/scripts/sign_encrypt.py +OUT_DIR := $(CURDIR)/target/$(TARGET_TA)/release + +ifeq ($(STD),) +all: + @echo "Please `export STD=y` then rerun `source environment` to build the STD version" +else +all: ta strip sign +endif + +# set the cross compile for building inner libraries, such as C libraries in ring +ta: + @CROSS_COMPILE=$(CROSS_COMPILE_TA) xargo build --target $(TARGET_TA) --release --config $(LINKER_CFG) + +strip: ta + @$(OBJCOPY) --strip-unneeded $(OUT_DIR)/ta $(OUT_DIR)/stripped_ta + +sign: strip + @$(SIGN) --uuid $(UUID) --key $(TA_SIGN_KEY) --in $(OUT_DIR)/stripped_ta --out $(OUT_DIR)/$(UUID).ta + @echo "SIGN => ${UUID}" + +clean: + @cargo clean \ No newline at end of file diff --git a/projects/web3/eth_wallet/ta/Xargo.toml b/projects/web3/eth_wallet/ta/Xargo.toml new file mode 100644 index 00000000..251ee70e --- /dev/null +++ b/projects/web3/eth_wallet/ta/Xargo.toml @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[dependencies.std] +path = "../../../../rust/rust/library/std" + +[patch.crates-io] +libc = { path = "../../../../rust/libc" } +rustc-std-workspace-core = { path = "../../../../rust/rust/library/rustc-std-workspace-core" } +rustc-std-workspace-alloc = { path = "../../../../rust/rust/library/rustc-std-workspace-alloc" } diff --git a/projects/web3/eth_wallet/ta/build.rs b/projects/web3/eth_wallet/ta/build.rs new file mode 100644 index 00000000..9efe628c --- /dev/null +++ b/projects/web3/eth_wallet/ta/build.rs @@ -0,0 +1,108 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use proto; +use std::env; +use std::fs::File; +use std::io::{BufRead, BufReader, Write}; +use std::path::{Path, PathBuf}; +use uuid::Uuid; + +fn main() -> std::io::Result<()> { + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + + let mut buffer = File::create(out.join("user_ta_header.rs"))?; + buffer.write_all(include_bytes!("ta_static.rs"))?; + + let tee_uuid = Uuid::parse_str(proto::UUID).unwrap(); + let (time_low, time_mid, time_hi_and_version, clock_seq_and_node) = tee_uuid.as_fields(); + + write!(buffer, "\n")?; + write!( + buffer, + "const TA_UUID: optee_utee_sys::TEE_UUID = optee_utee_sys::TEE_UUID {{ + timeLow: {:#x}, + timeMid: {:#x}, + timeHiAndVersion: {:#x}, + clockSeqAndNode: {:#x?}, +}};", + time_low, time_mid, time_hi_and_version, clock_seq_and_node + )?; + + let mut aarch64_flag = true; + match env::var("TARGET_TA") { + Ok(ref v) if v == "arm-unknown-linux-gnueabihf" || v == "arm-unknown-optee" => { + println!("cargo:rustc-link-arg=--no-warn-mismatch"); + aarch64_flag = false; + } + _ => {} + }; + + let optee_os_dir = env::var("TA_DEV_KIT_DIR").unwrap(); + let search_path = Path::new(&optee_os_dir).join("lib"); + + let optee_os_path = &PathBuf::from(optee_os_dir.clone()); + let mut ta_lds = File::create(out.join("ta.lds"))?; + let f = File::open(optee_os_path.join("src/ta.ld.S"))?; + let f = BufReader::new(f); + + for line in f.lines() { + let l = line?; + + if aarch64_flag { + if l.starts_with('#') + || l == "OUTPUT_FORMAT(\"elf32-littlearm\")" + || l == "OUTPUT_ARCH(arm)" + { + continue; + } + } else { + if l.starts_with('#') + || l == "OUTPUT_FORMAT(\"elf64-littleaarch64\")" + || l == "OUTPUT_ARCH(aarch64)" + { + continue; + } + } + + if l == "\t. = ALIGN(4096);" { + write!(ta_lds, "\t. = ALIGN(65536);\n")?; + } else { + write!(ta_lds, "{}\n", l)?; + } + } + + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=ta.lds"); + + println!("cargo:rustc-link-search={}", search_path.display()); + println!("cargo:rustc-link-lib=static=utee"); + println!("cargo:rustc-link-lib=static=utils"); + println!("cargo:rustc-link-arg=-Tta.lds"); + println!("cargo:rustc-link-arg=-e__ta_entry"); + println!("cargo:rustc-link-arg=-pie"); + println!("cargo:rustc-link-arg=-Os"); + println!("cargo:rustc-link-arg=--sort-section=alignment"); + + let mut dyn_list = File::create(out.join("dyn_list"))?; + write!( + dyn_list, + "{{ __elf_phdr_info; trace_ext_prefix; trace_level; ta_head; }};\n" + )?; + println!("cargo:rustc-link-arg=--dynamic-list=dyn_list"); + Ok(()) +} diff --git a/projects/web3/eth_wallet/ta/src/hash.rs b/projects/web3/eth_wallet/ta/src/hash.rs new file mode 100644 index 00000000..9b5f3a60 --- /dev/null +++ b/projects/web3/eth_wallet/ta/src/hash.rs @@ -0,0 +1,28 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use serde::Serialize; +use sha3::{Digest, Keccak256}; + +pub fn keccak_hash_to_bytes(data: &T) -> Vec +where + T: ?Sized + Serialize + AsRef<[u8]>, +{ + let mut hasher = Keccak256::new(); + hasher.update(data); + hasher.finalize().to_vec() +} diff --git a/projects/web3/eth_wallet/ta/src/main.rs b/projects/web3/eth_wallet/ta/src/main.rs new file mode 100644 index 00000000..d889a26c --- /dev/null +++ b/projects/web3/eth_wallet/ta/src/main.rs @@ -0,0 +1,181 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#![no_main] + +mod hash; +mod secure_storage; +mod wallet; + +use crate::secure_storage::{ + delete_from_secure_storage, load_from_secure_storage, save_in_secure_storage, +}; +use optee_utee::{ + ta_close_session, ta_create, ta_destroy, ta_invoke_command, ta_open_session, trace_println, +}; +use optee_utee::{Error, ErrorKind, Parameters}; +use proto::Command; + +use anyhow::{anyhow, bail, Result}; +use std::convert::TryInto; +use std::io::Write; +use wallet::Wallet; + +#[ta_create] +fn create() -> optee_utee::Result<()> { + trace_println!("[+] TA create"); + Ok(()) +} + +#[ta_open_session] +fn open_session(_params: &mut Parameters) -> optee_utee::Result<()> { + trace_println!("[+] TA open session"); + Ok(()) +} + +#[ta_close_session] +fn close_session() { + trace_println!("[+] TA close session"); +} + +#[ta_destroy] +fn destroy() { + trace_println!("[+] TA destroy"); +} + +#[cfg(debug_assertions)] +macro_rules! dbg_println { + ($($arg:tt)*) => (trace_println!($($arg)*)); +} + +#[cfg(not(debug_assertions))] +macro_rules! dbg_println { + ($($arg:tt)*) => {}; +} + +fn create_wallet(_input: &proto::CreateWalletInput) -> Result { + let wallet = Wallet::new()?; + let wallet_id = wallet.get_id(); + let mnemonic = wallet.get_mnemonic()?; + dbg_println!("[+] Wallet ID: {:?}", wallet_id); + + let secure_object: Vec = wallet.try_into()?; + save_in_secure_storage(wallet_id.as_bytes(), &secure_object)?; + dbg_println!("[+] Wallet saved in secure storage"); + + Ok(proto::CreateWalletOutput { + wallet_id, + mnemonic, + }) +} + +fn remove_wallet(input: &proto::RemoveWalletInput) -> Result { + dbg_println!("[+] Removing wallet: {:?}", input.wallet_id); + + delete_from_secure_storage(input.wallet_id.as_bytes())?; + dbg_println!("[+] Wallet removed"); + + Ok(proto::RemoveWalletOutput {}) +} + +fn derive_address(input: &proto::DeriveAddressInput) -> Result { + let secure_object = load_from_secure_storage(input.wallet_id.as_bytes()) + .map_err(|e| anyhow!("[+] Deriving address: error: wallet not found: {:?}", e))?; + dbg_println!("[+] Deriving address: secure object loaded"); + + let wallet: Wallet = secure_object.try_into()?; + let (address, public_key) = wallet.derive_address(&input.hd_path)?; + dbg_println!("[+] Deriving address: address: {:?}", address); + dbg_println!("[+] Deriving address: public key: {:?}", public_key); + + Ok(proto::DeriveAddressOutput { + address, + public_key, + }) +} + +fn sign_transaction(input: &proto::SignTransactionInput) -> Result { + let secure_object = load_from_secure_storage(input.wallet_id.as_bytes()) + .map_err(|e| anyhow!("[+] Sign transaction: error: wallet not found: {:?}", e))?; + dbg_println!("[+] Sign transaction: secure object loaded"); + + let wallet: Wallet = secure_object.try_into()?; + let signature = wallet.sign_transaction(&input.hd_path, &input.transaction)?; + dbg_println!("[+] Sign transaction: signature: {:?}", signature); + + Ok(proto::SignTransactionOutput { signature }) +} + +fn handle_invoke(command: Command, serialized_input: &[u8]) -> Result> { + fn process Result>( + serialized_input: &[u8], + handler: F, + ) -> Result> { + let input: T = bincode::deserialize(serialized_input)?; + let output = handler(&input)?; + let serialized_output = bincode::serialize(&output)?; + Ok(serialized_output) + } + + match command { + Command::CreateWallet => process(serialized_input, create_wallet), + Command::RemoveWallet => process(serialized_input, remove_wallet), + Command::DeriveAddress => process(serialized_input, derive_address), + Command::SignTransaction => process(serialized_input, sign_transaction), + _ => bail!("Unsupported command"), + } +} + +#[ta_invoke_command] +fn invoke_command(cmd_id: u32, params: &mut Parameters) -> optee_utee::Result<()> { + dbg_println!("[+] TA invoke command"); + let mut p0 = unsafe { params.0.as_memref()? }; + let mut p1 = unsafe { params.1.as_memref()? }; + let mut p2 = unsafe { params.2.as_value()? }; + + let output_vec = match handle_invoke(Command::from(cmd_id), p0.buffer()) { + Ok(output) => output, + Err(e) => { + let err_message = format!("{:?}", e).as_bytes().to_vec(); + p1.buffer() + .write(&err_message) + .map_err(|_| Error::new(ErrorKind::BadState))?; + p2.set_a(err_message.len() as u32); + return Err(Error::new(ErrorKind::BadParameters)); + } + }; + p1.buffer() + .write(&output_vec) + .map_err(|_| Error::new(ErrorKind::BadState))?; + p2.set_a(output_vec.len() as u32); + + Ok(()) +} + +// TA configurations +const TA_FLAGS: u32 = 0; +const TA_DATA_SIZE: u32 = 1024 * 1024; +const TA_STACK_SIZE: u32 = 128 * 1024; +const TA_VERSION: &[u8] = b"0.3\0"; +const TA_DESCRIPTION: &[u8] = b"This is an example of Ethereum wallet TA\0"; +const EXT_PROP_VALUE_1: &[u8] = b"Ethereum wallet TA\0"; +const EXT_PROP_VALUE_2: u32 = 0x0010; +const TRACE_LEVEL: i32 = 4; +const TRACE_EXT_PREFIX: &[u8] = b"TA\0"; +const TA_FRAMEWORK_STACK_SIZE: u32 = 2048; + +include!(concat!(env!("OUT_DIR"), "/user_ta_header.rs")); diff --git a/projects/web3/eth_wallet/ta/src/secure_storage.rs b/projects/web3/eth_wallet/ta/src/secure_storage.rs new file mode 100644 index 00000000..808adb78 --- /dev/null +++ b/projects/web3/eth_wallet/ta/src/secure_storage.rs @@ -0,0 +1,106 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use anyhow::{bail, Result}; +use optee_utee::{DataFlag, ObjectStorageConstants, PersistentObject}; + +pub fn save_in_secure_storage(obj_id: &[u8], data: &[u8]) -> Result<()> { + let obj_data_flag = DataFlag::ACCESS_READ + | DataFlag::ACCESS_WRITE + | DataFlag::ACCESS_WRITE_META + | DataFlag::OVERWRITE; + + let mut init_data: [u8; 0] = [0; 0]; + match PersistentObject::create( + ObjectStorageConstants::Private, + obj_id, + obj_data_flag, + None, + &mut init_data, + ) { + Err(e) => { + bail!("[-] {:?}: failed to create object: {:?}", &obj_id, e); + } + + Ok(mut object) => match object.write(&data) { + Ok(()) => { + return Ok(()); + } + Err(e_write) => { + object.close_and_delete()?; + std::mem::forget(object); + bail!( + "[-] {:?}: failed to write data to object: {:?}", + &obj_id, + e_write + ); + } + }, + } +} + +pub fn load_from_secure_storage(obj_id: &[u8]) -> Result> { + let mut buf = vec![0; 5000]; + + match PersistentObject::open( + ObjectStorageConstants::Private, + obj_id, + DataFlag::ACCESS_READ | DataFlag::SHARE_READ, + ) { + Err(e) => bail!("[-] {:?}: failed to open object: {:?}", &obj_id, e), + + Ok(object) => { + let obj_info = object.info()?; + + if obj_info.data_size() > buf.len() { + bail!("[-] {:?}: data size is too large", &obj_id); + } + let read_bytes = match object.read(&mut buf) { + Ok(read_bytes) => read_bytes, + Err(e) => { + bail!("[-] {:?}: failed to read data: {:?}", &obj_id, e); + } + }; + + if read_bytes != obj_info.data_size() as u32 { + bail!("[-] {:?}: failed to read data", &obj_id); + } + + buf.truncate(read_bytes as usize); + } + } + + Ok(buf) +} + +pub fn delete_from_secure_storage(obj_id: &[u8]) -> Result<()> { + match PersistentObject::open( + ObjectStorageConstants::Private, + &mut obj_id.to_vec(), + DataFlag::ACCESS_READ | DataFlag::ACCESS_WRITE_META, + ) { + Err(e) => { + bail!("[-] {:?}: failed to open object: {:?}", &obj_id, e); + } + + Ok(mut object) => { + object.close_and_delete()?; + std::mem::forget(object); + return Ok(()); + } + } +} diff --git a/projects/web3/eth_wallet/ta/src/wallet.rs b/projects/web3/eth_wallet/ta/src/wallet.rs new file mode 100644 index 00000000..4072061d --- /dev/null +++ b/projects/web3/eth_wallet/ta/src/wallet.rs @@ -0,0 +1,142 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use anyhow::{anyhow, Result}; +use bip32::{Mnemonic, XPrv}; +use serde::{Deserialize, Serialize}; +use std::convert::{TryFrom, TryInto}; +use uuid::Uuid; + +use crate::hash::keccak_hash_to_bytes; +use ethereum_tx_sign::Transaction; +use optee_utee::Random; +use proto::EthTransaction; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct Wallet { + id: Uuid, + entropy: Vec, +} + +impl Wallet { + pub fn new() -> Result { + let mut entropy = vec![0u8; 32]; + Random::generate(entropy.as_mut() as _); + + let mut random_bytes = vec![0u8; 16]; + Random::generate(random_bytes.as_mut() as _); + let uuid = uuid::Builder::from_random_bytes( + random_bytes + .try_into() + .map_err(|_| anyhow!("[-] Wallet::new(): invalid random bytes"))?, + ) + .into_uuid(); + + Ok(Self { + id: uuid, + entropy: entropy, + }) + } + + pub fn get_id(&self) -> Uuid { + self.id.clone() + } + + pub fn get_mnemonic(&self) -> Result { + let mnemonic = Mnemonic::from_entropy( + self.entropy.as_slice().try_into()?, + bip32::Language::English, + ); + Ok(mnemonic.phrase().to_string()) + } + + pub fn get_seed(&self) -> Result> { + let mnemonic = Mnemonic::from_entropy( + self.entropy.as_slice().try_into()?, + bip32::Language::English, + ); + let seed = mnemonic.to_seed(""); // empty passwords + Ok(seed.as_bytes().to_vec()) + } + + pub fn derive_prv_key(&self, hd_path: &str) -> Result> { + let path = hd_path.parse()?; + let child_xprv = XPrv::derive_from_path(self.get_seed()?, &path)?; + let child_xprv_bytes = child_xprv.to_bytes(); + Ok(child_xprv_bytes.to_vec()) + } + + pub fn derive_pub_key(&self, hd_path: &str) -> Result> { + let path = hd_path.parse()?; + let child_xprv = XPrv::derive_from_path(self.get_seed()?, &path)?; + // public key + let child_xpub_bytes = child_xprv.public_key().to_bytes(); + Ok(child_xpub_bytes.to_vec()) + } + + pub fn derive_address(&self, hd_path: &str) -> Result<([u8; 20], Vec)> { + let public_key_bytes = self.derive_pub_key(hd_path)?; + // uncompress public key + let public_key = secp256k1::PublicKey::from_slice(&public_key_bytes)?; + let uncompressed_public_key = &public_key.serialize_uncompressed()[1..]; + + // pubkey to address + let address = &keccak_hash_to_bytes(&uncompressed_public_key)[12..]; + Ok((address.try_into()?, public_key_bytes)) + } + + pub fn sign_transaction(&self, hd_path: &str, transaction: &EthTransaction) -> Result> { + let xprv = self.derive_prv_key(hd_path)?; + let legacy_transaction = ethereum_tx_sign::LegacyTransaction { + chain: transaction.chain_id, + nonce: transaction.nonce, + gas_price: transaction.gas_price, + gas: transaction.gas, + to: transaction.to, + value: transaction.value, + data: transaction.data.clone(), + }; + let ecdsa = legacy_transaction.ecdsa(&xprv).map_err(|e| { + let ethereum_tx_sign::Error::Secp256k1(inner_error) = e; + inner_error + })?; + let signature = legacy_transaction.sign(&ecdsa); + Ok(signature) + } +} + +impl TryFrom for Vec { + type Error = anyhow::Error; + + fn try_from(wallet: Wallet) -> Result> { + bincode::serialize(&wallet).map_err(|e| anyhow!("[-] Wallet::try_into(): {:?}", e)) + } +} + +impl TryFrom> for Wallet { + type Error = anyhow::Error; + + fn try_from(data: Vec) -> Result { + bincode::deserialize(&data).map_err(|e| anyhow!("[-] Wallet::try_from(): {:?}", e)) + } +} + +impl Drop for Wallet { + fn drop(&mut self) { + self.entropy.iter_mut().for_each(|x| *x = 0); + } +} diff --git a/projects/web3/eth_wallet/ta/ta_static.rs b/projects/web3/eth_wallet/ta/ta_static.rs new file mode 100644 index 00000000..20e1d977 --- /dev/null +++ b/projects/web3/eth_wallet/ta/ta_static.rs @@ -0,0 +1,102 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use core::ffi::{c_int, c_void}; +use core::mem; +use core::primitive::u64; + +#[no_mangle] +pub static mut trace_level: c_int = TRACE_LEVEL; + +#[no_mangle] +pub static trace_ext_prefix: &[u8] = TRACE_EXT_PREFIX; + +#[no_mangle] +#[link_section = ".ta_head"] +pub static ta_head: optee_utee_sys::ta_head = optee_utee_sys::ta_head { + uuid: TA_UUID, + stack_size: TA_STACK_SIZE + TA_FRAMEWORK_STACK_SIZE, + flags: TA_FLAGS, + depr_entry: u64::MAX, +}; + +#[no_mangle] +#[link_section = ".bss"] +pub static ta_heap: [u8; TA_DATA_SIZE as usize] = [0; TA_DATA_SIZE as usize]; + +#[no_mangle] +pub static ta_heap_size: usize = mem::size_of::() * TA_DATA_SIZE as usize; +static FLAG_BOOL: bool = (TA_FLAGS & optee_utee_sys::TA_FLAG_SINGLE_INSTANCE) != 0; +static FLAG_MULTI: bool = (TA_FLAGS & optee_utee_sys::TA_FLAG_MULTI_SESSION) != 0; +static FLAG_INSTANCE: bool = (TA_FLAGS & optee_utee_sys::TA_FLAG_INSTANCE_KEEP_ALIVE) != 0; + +#[no_mangle] +pub static ta_num_props: usize = 9; + +#[no_mangle] +pub static ta_props: [optee_utee_sys::user_ta_property; 9] = [ + optee_utee_sys::user_ta_property { + name: optee_utee_sys::TA_PROP_STR_SINGLE_INSTANCE, + prop_type: optee_utee_sys::user_ta_prop_type::USER_TA_PROP_TYPE_BOOL, + value: &FLAG_BOOL as *const bool as *mut _, + }, + optee_utee_sys::user_ta_property { + name: optee_utee_sys::TA_PROP_STR_MULTI_SESSION, + prop_type: optee_utee_sys::user_ta_prop_type::USER_TA_PROP_TYPE_BOOL, + value: &FLAG_MULTI as *const bool as *mut _, + }, + optee_utee_sys::user_ta_property { + name: optee_utee_sys::TA_PROP_STR_KEEP_ALIVE, + prop_type: optee_utee_sys::user_ta_prop_type::USER_TA_PROP_TYPE_BOOL, + value: &FLAG_INSTANCE as *const bool as *mut _, + }, + optee_utee_sys::user_ta_property { + name: optee_utee_sys::TA_PROP_STR_DATA_SIZE, + prop_type: optee_utee_sys::user_ta_prop_type::USER_TA_PROP_TYPE_U32, + value: &TA_DATA_SIZE as *const u32 as *mut _, + }, + optee_utee_sys::user_ta_property { + name: optee_utee_sys::TA_PROP_STR_STACK_SIZE, + prop_type: optee_utee_sys::user_ta_prop_type::USER_TA_PROP_TYPE_U32, + value: &TA_STACK_SIZE as *const u32 as *mut _, + }, + optee_utee_sys::user_ta_property { + name: optee_utee_sys::TA_PROP_STR_VERSION, + prop_type: optee_utee_sys::user_ta_prop_type::USER_TA_PROP_TYPE_STRING, + value: TA_VERSION as *const [u8] as *mut _, + }, + optee_utee_sys::user_ta_property { + name: optee_utee_sys::TA_PROP_STR_DESCRIPTION, + prop_type: optee_utee_sys::user_ta_prop_type::USER_TA_PROP_TYPE_STRING, + value: TA_DESCRIPTION as *const [u8] as *mut _, + }, + optee_utee_sys::user_ta_property { + name: "gp.ta.description\0".as_ptr(), + prop_type: optee_utee_sys::user_ta_prop_type::USER_TA_PROP_TYPE_STRING, + value: EXT_PROP_VALUE_1 as *const [u8] as *mut _, + }, + optee_utee_sys::user_ta_property { + name: "gp.ta.version\0".as_ptr(), + prop_type: optee_utee_sys::user_ta_prop_type::USER_TA_PROP_TYPE_U32, + value: &EXT_PROP_VALUE_2 as *const u32 as *mut _, + }, +]; + +#[no_mangle] +pub unsafe extern "C" fn tahead_get_trace_level() -> c_int { + return trace_level; +} diff --git a/projects/web3/eth_wallet/uuid.txt b/projects/web3/eth_wallet/uuid.txt new file mode 100644 index 00000000..c0c0f3f4 --- /dev/null +++ b/projects/web3/eth_wallet/uuid.txt @@ -0,0 +1 @@ +be2dc9a0-02b4-4b33-ba21-9964dbdf1573 diff --git a/tests/test_eth_wallet.sh b/tests/test_eth_wallet.sh new file mode 100755 index 00000000..3e414336 --- /dev/null +++ b/tests/test_eth_wallet.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set -xe + +# Include base script +source setup.sh + +# Copy TA and host binary +cp ../projects/web3/eth_wallet/ta/target/$TARGET_TA/release/*.ta shared +cp ../projects/web3/eth_wallet/host/target/$TARGET_HOST/release/eth_wallet-rs shared + +# Run script specific commands in QEMU +run_in_qemu "cp *.ta /lib/optee_armtz/\n" +run_in_qemu "./eth_wallet-rs test\n" +run_in_qemu "^C" + +# Script specific checks +{ + grep -q "Tests passed" screenlog.0 +} || { + cat -v screenlog.0 + false +} + +rm screenlog.0 \ No newline at end of file