You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@teaclave.apache.org by yu...@apache.org on 2025/09/05 02:35:06 UTC

(teaclave-trustzone-sdk) 01/01: projects: add ConfidentialKlave

This is an automated email from the ASF dual-hosted git repository.

yuanz pushed a commit to branch confidentialklave
in repository https://gitbox.apache.org/repos/asf/teaclave-trustzone-sdk.git

commit 04b78a2d9ee1ca703d49bcbd2cc894192840d83d
Author: Yuan Zhuang <yu...@apache.org>
AuthorDate: Fri Sep 5 02:32:47 2025 +0000

    projects: add ConfidentialKlave
---
 projects/web3/ConfidentialKlave/.cargo/config      |  49 ++
 projects/web3/ConfidentialKlave/Makefile           |  40 ++
 .../web3/ConfidentialKlave/attestation/Cargo.toml  |  51 ++
 .../web3/ConfidentialKlave/attestation/src/cert.rs | 485 ++++++++++++++++++
 .../web3/ConfidentialKlave/attestation/src/date.rs | 315 ++++++++++++
 .../web3/ConfidentialKlave/attestation/src/lib.rs  | 547 +++++++++++++++++++++
 .../attestation/src/report_verifier.rs             | 217 ++++++++
 .../web3/ConfidentialKlave/attestation/src/sign.rs | 211 ++++++++
 .../ConfidentialKlave/attestation/src/utils.rs     | 140 ++++++
 .../ConfidentialKlave/attestation/src/verify.rs    | 109 ++++
 .../web3/ConfidentialKlave/basic_utils/Cargo.toml  |  35 ++
 .../web3/ConfidentialKlave/basic_utils/src/hash.rs |  38 ++
 .../web3/ConfidentialKlave/basic_utils/src/lib.rs  |  26 +
 .../ConfidentialKlave/basic_utils/src/random.rs    |  36 ++
 .../web3/ConfidentialKlave/basic_utils/src/time.rs |  32 ++
 .../web3/ConfidentialKlave/constants/Cargo.toml    |  30 ++
 projects/web3/ConfidentialKlave/constants/build.rs |  38 ++
 .../web3/ConfidentialKlave/constants/src/lib.rs    |  20 +
 projects/web3/ConfidentialKlave/core/Cargo.toml    |  57 +++
 .../core/src/device_cmd_handler.rs                 | 175 +++++++
 .../web3/ConfidentialKlave/core/src/key_utils.rs   | 137 ++++++
 projects/web3/ConfidentialKlave/core/src/lib.rs    | 174 +++++++
 .../core/src/secure_storage_client.rs              |  93 ++++
 .../ConfidentialKlave/core/src/state_manager.rs    | 367 ++++++++++++++
 .../core/src/tls/config_builder.rs                 | 111 +++++
 .../core/src/tls/handler_context.rs                |  55 +++
 .../web3/ConfidentialKlave/core/src/tls/mod.rs     |  21 +
 .../web3/ConfidentialKlave/core/src/tls/session.rs | 113 +++++
 .../core/src/tls/session_manager.rs                | 105 ++++
 projects/web3/ConfidentialKlave/core/src/user.rs   | 166 +++++++
 .../ConfidentialKlave/core/src/user_registry.rs    |  90 ++++
 projects/web3/ConfidentialKlave/crypto/Cargo.toml  |  39 ++
 projects/web3/ConfidentialKlave/crypto/src/aead.rs | 201 ++++++++
 .../web3/ConfidentialKlave/crypto/src/ecdsa.rs     | 103 ++++
 projects/web3/ConfidentialKlave/crypto/src/lib.rs  |  21 +
 projects/web3/ConfidentialKlave/proto/Cargo.toml   |  35 ++
 .../web3/ConfidentialKlave/proto/src/device.rs     | 107 ++++
 projects/web3/ConfidentialKlave/proto/src/lib.rs   |  88 ++++
 .../ConfidentialKlave/proto/src/transaction.rs     |  94 ++++
 .../web3/ConfidentialKlave/proto/src/wallet.rs     |  56 +++
 .../web3/ConfidentialKlave/rust-toolchain.toml     |  21 +
 .../ConfidentialKlave/scripts/generate_certs.sh    |  53 ++
 .../ConfidentialKlave/scripts/install_certs.sh     |  44 ++
 .../ConfidentialKlave/secure_storage/Cargo.toml    |  48 ++
 .../ConfidentialKlave/secure_storage/src/lib.rs    |  35 ++
 .../secure_storage/src/local/mod.rs                |  19 +
 .../secure_storage/src/local/rsa.rs                |  90 ++++
 .../secure_storage/src/local/storage_backend.rs    |  48 ++
 .../secure_storage/src/optee/mod.rs                |  19 +
 .../secure_storage/src/optee/rsa.rs                | 165 +++++++
 .../secure_storage/src/optee/storage_backend.rs    | 103 ++++
 .../secure_storage/src/secure_db.rs                | 109 ++++
 .../secure_storage/src/tee_key.rs                  | 202 ++++++++
 projects/web3/ConfidentialKlave/tee-cli/Cargo.toml |  43 ++
 projects/web3/ConfidentialKlave/tee-cli/Makefile   |  51 ++
 projects/web3/ConfidentialKlave/tee-cli/src/cli.rs |  65 +++
 .../web3/ConfidentialKlave/tee-cli/src/main.rs     |  93 ++++
 .../web3/ConfidentialKlave/tls-host/Cargo.toml     |  37 ++
 projects/web3/ConfidentialKlave/tls-host/Makefile  |  45 ++
 .../web3/ConfidentialKlave/tls-host/src/cli.rs     |  39 ++
 .../web3/ConfidentialKlave/tls-host/src/main.rs    | 123 +++++
 .../tls-host/src/proxy_service.rs                  |  89 ++++
 .../tls-host/src/ta_connect_service.rs             | 160 ++++++
 .../tls-host/src/ta_tls_command.rs                 |  34 ++
 projects/web3/ConfidentialKlave/tls-ta/Cargo.toml  |  64 +++
 projects/web3/ConfidentialKlave/tls-ta/Makefile    |  60 +++
 projects/web3/ConfidentialKlave/tls-ta/Xargo.toml  |  25 +
 projects/web3/ConfidentialKlave/tls-ta/build.rs    |  62 +++
 .../ConfidentialKlave/tls-ta/rust-toolchain.toml   |  23 +
 projects/web3/ConfidentialKlave/tls-ta/src/main.rs | 296 +++++++++++
 .../web3/ConfidentialKlave/tls-ta/ta_aarch64.lds   |  92 ++++
 projects/web3/ConfidentialKlave/tls-ta/ta_arm.lds  |  91 ++++
 .../web3/ConfidentialKlave/tls-ta/ta_static.rs     |  98 ++++
 projects/web3/ConfidentialKlave/tls_uuid.txt       |   1 +
 projects/web3/ConfidentialKlave/types/Cargo.toml   |  66 +++
 .../types/src/external/account/btc.rs              | 167 +++++++
 .../types/src/external/account/eth.rs              |  44 ++
 .../types/src/external/account/mod.rs              | 102 ++++
 .../types/src/external/approval_chain.rs           | 330 +++++++++++++
 .../types/src/external/approval_chain_basic.rs     |  97 ++++
 .../types/src/external/asset_type.rs               | 309 ++++++++++++
 .../ConfidentialKlave/types/src/external/erc20.rs  |  67 +++
 .../types/src/external/external_address.rs         | 142 ++++++
 .../ConfidentialKlave/types/src/external/mod.rs    |  53 ++
 .../types/src/external/numeric.rs                  | 104 ++++
 .../types/src/external/operators_basic.rs          |  60 +++
 .../types/src/external/transfer.rs                 | 220 +++++++++
 .../ConfidentialKlave/types/src/external/tx.rs     |  56 +++
 .../ConfidentialKlave/types/src/external/user.rs   |  56 +++
 .../types/src/external/viewers_basic.rs            |  52 ++
 projects/web3/ConfidentialKlave/types/src/lib.rs   |  31 ++
 .../web3/ConfidentialKlave/types/src/serde_util.rs | 145 ++++++
 .../ConfidentialKlave/types/src/share/account.rs   | 161 ++++++
 .../types/src/share/address/btc.rs                 | 139 ++++++
 .../types/src/share/address/eth.rs                 |  41 ++
 .../types/src/share/address/mod.rs                 |  19 +
 .../types/src/share/approval_operations.rs         |  32 ++
 .../types/src/share/basic_types.rs                 |  95 ++++
 .../ConfidentialKlave/types/src/share/ck_hash.rs   |  42 ++
 .../ConfidentialKlave/types/src/share/device.rs    |  68 +++
 .../web3/ConfidentialKlave/types/src/share/mod.rs  |  71 +++
 .../ConfidentialKlave/types/src/share/network.rs   | 225 +++++++++
 .../types/src/share/ta_approval_chain.rs           | 192 ++++++++
 .../types/src/share/ta_approval_chain_basic.rs     |  76 +++
 .../types/src/share/ta_operators_basic.rs          |  52 ++
 .../types/src/share/ta_user_info.rs                |  64 +++
 .../types/src/share/tee_config.rs                  |  63 +++
 .../types/src/share/tee_status.rs                  |  70 +++
 .../ConfidentialKlave/types/src/share/tx/btc.rs    | 111 +++++
 .../ConfidentialKlave/types/src/share/tx/eth.rs    |  79 +++
 .../ConfidentialKlave/types/src/share/tx/mod.rs    |  87 ++++
 .../types/src/share/tx/multichain.rs               |  51 ++
 .../web3/ConfidentialKlave/types/src/share/user.rs | 128 +++++
 .../ConfidentialKlave/types/src/share/wallet.rs    |  84 ++++
 .../web3/ConfidentialKlave/types/src/share/xpub.rs |  93 ++++
 .../web3/ConfidentialKlave/types/src/storable.rs   |  50 ++
 .../web3/ConfidentialKlave/types/src/trust/mod.rs  |  38 ++
 .../types/src/trust/tee_device_info.rs             |  37 ++
 .../types/src/trust/tee_wallet.rs                  | 196 ++++++++
 .../ConfidentialKlave/types/src/trust/tx/btc.rs    | 146 ++++++
 .../ConfidentialKlave/types/src/trust/tx/mod.rs    |  19 +
 .../ConfidentialKlave/types/src/trust/tx/tee_tx.rs | 113 +++++
 .../types/src/trust/wallet_secret.rs               |  75 +++
 .../ConfidentialKlave/types/src/trust/xpriv/btc.rs | 158 ++++++
 .../ConfidentialKlave/types/src/trust/xpriv/eth.rs |  44 ++
 .../types/src/trust/xpriv/master.rs                | 177 +++++++
 .../ConfidentialKlave/types/src/trust/xpriv/mod.rs |  20 +
 127 files changed, 12591 insertions(+)

diff --git a/projects/web3/ConfidentialKlave/.cargo/config b/projects/web3/ConfidentialKlave/.cargo/config
new file mode 100644
index 0000000..9576150
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/.cargo/config
@@ -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.
+
+[target.aarch64-unknown-optee-trustzone]
+linker = "aarch64-linux-gnu-ld"
+ar = "aarch64-linux-gnu-gcc-ar"
+rustflags = [
+    "-C", "link-arg=-e__ta_entry",
+    "-C", "link-arg=-nostdlib",
+    "-C", "link-arg=-Tta.lds",
+    "-C", "link-arg=--sort-section=alignment",
+    "-C", "link-arg=-pie",
+    "-C", "link-arg=-Os",
+#    "-C", "link-dead-code",
+]
+
+[target.aarch64-unknown-linux-gnu]
+linker = "aarch64-linux-gnu-gcc"
+ar = "aarch64-linux-gnu-gcc-ar"
+
+[target.arm-unknown-optee-trustzone]
+linker = "arm-linux-gnueabihf-ld.bfd"
+ar = "arm-linux-gnueabihf-ar"
+rustflags = [
+    "-C", "link-arg=-e__ta_entry",
+    "-C", "link-arg=-nostdlib",
+    "-C", "link-arg=-Tta.lds",
+    "-C", "link-arg=--sort-section=alignment",
+    "-C", "link-arg=-pie",
+    "-C", "link-dead-code",
+]
+
+[target.arm-unknown-linux-gnueabihf]
+linker = "arm-linux-gnueabihf-gcc"
+ar = "arm-linux-gnueabihf-gcc-ar"
diff --git a/projects/web3/ConfidentialKlave/Makefile b/projects/web3/ConfidentialKlave/Makefile
new file mode 100644
index 0000000..953e70a
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/Makefile
@@ -0,0 +1,40 @@
+# 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.
+
+DIST_DIRS := tee-cli tls-host tls-ta
+SUBDIRS := $(DIST_DIRS)
+
+.PHONY: all dist clean clippy $(SUBDIRS)
+
+all: dist
+
+test: $(TEST_DIRS)
+
+dist: $(DIST_DIRS)
+
+$(SUBDIRS):
+	@$(MAKE) -s -C $@
+
+clippy:
+	@for dir in $(SUBDIRS); do \
+		$(MAKE) -s -C $$dir clippy; \
+	done
+
+clean:
+	@for dir in $(SUBDIRS); do \
+		$(MAKE) -s -C $$dir clean; \
+	done
diff --git a/projects/web3/ConfidentialKlave/attestation/Cargo.toml b/projects/web3/ConfidentialKlave/attestation/Cargo.toml
new file mode 100644
index 0000000..02f18b9
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/attestation/Cargo.toml
@@ -0,0 +1,51 @@
+# 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 = "attestation"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+basic_utils = { path = "../basic_utils" }
+crypto = { path = "../crypto" }
+
+bit-vec = "0.6"
+num-bigint = "0.4"
+yasna = { version = "0.4", features = ["chrono", "bit-vec", "num-bigint"] }
+chrono = { version = "=0.4.26", default-features = false }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+anyhow = "1.0"
+
+rustls = { git = "https://github.com/DemesneGH/rustls-optee.git", branch = "0.21.0-optee", features = ["dangerous_configuration"]}
+rustls-webpki = "=0.100.1"
+webpki-roots = "=0.22.0"
+webpki = "=0.22.0"
+rustls-pemfile = "1.0"
+
+[target.'cfg(target_os="optee")'.dependencies]
+optee-utee-sys = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee/optee-utee-sys" }
+optee-utee = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee" }
+
+[patch.crates-io]
+proc-macro2 = { git = "https://github.com/DemesneGH/proc-macro2.git" }
+ring = { git = "https://github.com/DemesneGH/ring-optee.git", branch = "0.16.20-optee" }
+
+[patch."https://github.com/apache/incubator-teaclave-trustzone-sdk.git"]
+optee-utee = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee" }
+libc = { path = "../../incubator-teaclave-trustzone-sdk/rust/libc" }
\ No newline at end of file
diff --git a/projects/web3/ConfidentialKlave/attestation/src/cert.rs b/projects/web3/ConfidentialKlave/attestation/src/cert.rs
new file mode 100644
index 0000000..6367c6a
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/attestation/src/cert.rs
@@ -0,0 +1,485 @@
+// 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.
+
+//! This module provides a X.509 certificate parser.
+
+#![allow(clippy::unused_unit)]
+#![allow(clippy::needless_lifetimes)]
+
+use std::convert::TryFrom;
+use std::prelude::v1::*;
+
+use yasna::models::TaggedDerValue;
+use yasna::ASN1Error;
+use yasna::{ASN1ErrorKind, ASN1Result};
+use yasna::{BERReader, BERReaderSeq, BERReaderSet};
+use yasna::{DERWriter, DERWriterSeq, DERWriterSet};
+
+pub type Writer<'a> = DERWriter<'a>;
+pub type Reader<'a, 'b> = BERReader<'a, 'b>;
+
+pub trait ConsWriter {
+    fn next(&mut self) -> Writer<'_>;
+}
+
+pub trait ConsReader<'a> {
+    fn next(&mut self, tags: &[yasna::Tag]) -> ASN1Result<Reader<'a, '_>>;
+}
+
+impl ConsWriter for DERWriterSeq<'_> {
+    fn next(&mut self) -> Writer<'_> {
+        self.next()
+    }
+}
+
+impl ConsWriter for DERWriterSet<'_> {
+    fn next(&mut self) -> Writer<'_> {
+        self.next()
+    }
+}
+
+impl<'a> ConsReader<'a> for BERReaderSeq<'a, '_> {
+    fn next(&mut self, _tags: &[yasna::Tag]) -> ASN1Result<Reader<'a, '_>> {
+        Ok(self.next())
+    }
+}
+
+impl<'a> ConsReader<'a> for BERReaderSet<'a, '_> {
+    fn next(&mut self, tags: &[yasna::Tag]) -> ASN1Result<Reader<'a, '_>> {
+        self.next(tags)
+    }
+}
+
+pub trait Asn1Ty {
+    type ValueTy;
+    const TAG: yasna::Tag;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) -> ();
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy>;
+}
+
+pub trait Asn1ConsTy
+where
+    Self: std::marker::Sized,
+{
+    type ValueTy;
+
+    fn dump<W: ConsWriter>(writer: &mut W, value: Self::ValueTy) -> ();
+    fn load<'a, R>(reader: &mut R) -> ASN1Result<Self::ValueTy>
+    where
+        R: ConsReader<'a>;
+}
+
+pub trait Asn1Tag {
+    const TAG: yasna::Tag;
+}
+
+const fn context_tag(tnum: u64) -> yasna::Tag {
+    yasna::Tag {
+        tag_class: yasna::TagClass::ContextSpecific,
+        tag_number: tnum,
+    }
+}
+
+mod no_instance {
+    #![allow(dead_code)]
+
+    use super::{Asn1ConsTy, Asn1Tag, Asn1Ty};
+    use std::marker::PhantomData;
+
+    pub(crate) struct CtxT0;
+    pub(crate) struct CtxT1;
+    pub(crate) struct CtxT3;
+
+    pub(crate) struct Boolean;
+    pub(crate) struct Oid;
+    pub(crate) struct U8;
+    pub(crate) struct I8;
+    pub(crate) struct BigUint;
+    pub(crate) struct Utf8Str;
+    pub(crate) struct UtcTime;
+    pub(crate) struct BitVec;
+    pub(crate) struct Bytes;
+    pub(crate) struct DnsNameEntry;
+    pub(crate) struct AlterNamesPayloadSeq;
+    pub(crate) struct Tagged<T: Asn1Tag, S: Asn1Ty> {
+        t: PhantomData<T>,
+        s: PhantomData<S>,
+    }
+    pub(crate) struct Sequence<U: Asn1Ty, V: Asn1ConsTy> {
+        u: PhantomData<U>,
+        v: PhantomData<V>,
+    }
+    pub(crate) struct Set<U: Asn1Ty, V: Asn1ConsTy> {
+        u: PhantomData<U>,
+        v: PhantomData<V>,
+    }
+    pub(crate) struct Cons<U: Asn1Ty, V: Asn1ConsTy> {
+        u: PhantomData<U>,
+        v: PhantomData<V>,
+    }
+    pub(crate) struct Nil;
+}
+
+use no_instance::*;
+
+impl Asn1Tag for CtxT0 {
+    const TAG: yasna::Tag = context_tag(0);
+}
+
+impl Asn1Tag for CtxT1 {
+    const TAG: yasna::Tag = context_tag(1);
+}
+
+impl Asn1Tag for CtxT3 {
+    const TAG: yasna::Tag = context_tag(3);
+}
+
+impl<U: Asn1Ty, V: Asn1ConsTy> Asn1ConsTy for Cons<U, V> {
+    type ValueTy = (U::ValueTy, V::ValueTy);
+
+    fn dump<W: ConsWriter>(writer: &mut W, value: Self::ValueTy) -> () {
+        U::dump(writer.next(), value.0);
+        V::dump(writer, value.1);
+    }
+
+    fn load<'a, R>(reader: &mut R) -> ASN1Result<Self::ValueTy>
+    where
+        R: ConsReader<'a>,
+    {
+        let first = U::load(reader.next(&[U::TAG])?)?;
+        let second = V::load(reader)?;
+        Ok((first, second))
+    }
+}
+
+impl Asn1ConsTy for Nil {
+    type ValueTy = ();
+
+    fn dump<W: ConsWriter>(_writer: &mut W, _value: Self::ValueTy) -> () {}
+    fn load<'a, R>(_reader: &mut R) -> ASN1Result<Self::ValueTy>
+    where
+        R: ConsReader<'a>,
+    {
+        Ok(())
+    }
+}
+
+impl<T: Asn1Tag, S: Asn1Ty> Asn1Ty for Tagged<T, S> {
+    type ValueTy = S::ValueTy;
+    const TAG: yasna::Tag = T::TAG;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        writer.write_tagged(T::TAG, |writer| S::dump(writer, value));
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        reader.read_tagged(T::TAG, S::load)
+    }
+}
+
+impl<U: Asn1Ty, V: Asn1ConsTy> Asn1Ty for Sequence<U, V> {
+    type ValueTy = (U::ValueTy, V::ValueTy);
+    const TAG: yasna::Tag = yasna::tags::TAG_SEQUENCE;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        writer.write_sequence(|writer| {
+            U::dump(writer.next(), value.0);
+            V::dump(writer, value.1);
+        });
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        reader.read_sequence(|reader| {
+            let first = U::load(reader.next())?;
+            let second = V::load(reader)?;
+            Ok((first, second))
+        })
+    }
+}
+
+impl<U: Asn1Ty, V: Asn1ConsTy> Asn1Ty for Set<U, V> {
+    type ValueTy = (U::ValueTy, V::ValueTy);
+    const TAG: yasna::Tag = yasna::tags::TAG_SET;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        writer.write_set(|writer| {
+            U::dump(writer.next(), value.0);
+            V::dump(writer, value.1);
+        });
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        reader.read_set(|reader| {
+            let first = U::load(reader.next(&[U::TAG])?)?;
+            let second = V::load(reader)?;
+            Ok((first, second))
+        })
+    }
+}
+
+impl Asn1Ty for U8 {
+    type ValueTy = u8;
+    const TAG: yasna::Tag = yasna::tags::TAG_INTEGER;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        writer.write_u8(value);
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        reader.read_u8()
+    }
+}
+
+impl Asn1Ty for I8 {
+    type ValueTy = i8;
+    const TAG: yasna::Tag = yasna::tags::TAG_INTEGER;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        writer.write_i8(value);
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        reader.read_i8()
+    }
+}
+
+impl Asn1Ty for Boolean {
+    type ValueTy = bool;
+    const TAG: yasna::Tag = yasna::tags::TAG_BOOLEAN;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        writer.write_bool(value);
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        reader.read_bool()
+    }
+}
+
+impl Asn1Ty for BigUint {
+    type ValueTy = num_bigint::BigUint;
+    const TAG: yasna::Tag = yasna::tags::TAG_INTEGER;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        writer.write_biguint(&value);
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        reader.read_biguint()
+    }
+}
+
+impl Asn1Ty for Utf8Str {
+    type ValueTy = String;
+    const TAG: yasna::Tag = yasna::tags::TAG_UTF8STRING;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        writer.write_utf8_string(value.as_str());
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        reader.read_utf8string()
+    }
+}
+
+impl Asn1Ty for Oid {
+    type ValueTy = yasna::models::ObjectIdentifier;
+    const TAG: yasna::Tag = yasna::tags::TAG_OID;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        writer.write_oid(&value);
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        reader.read_oid()
+    }
+}
+
+impl Asn1Ty for UtcTime {
+    type ValueTy = yasna::models::UTCTime;
+    const TAG: yasna::Tag = yasna::tags::TAG_UTCTIME;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        writer.write_utctime(&value);
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        reader.read_utctime()
+    }
+}
+
+impl Asn1Ty for BitVec {
+    type ValueTy = bit_vec::BitVec;
+    const TAG: yasna::Tag = yasna::tags::TAG_BITSTRING;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        writer.write_bitvec(&value);
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        reader.read_bitvec()
+    }
+}
+
+impl Asn1Ty for Bytes {
+    type ValueTy = Vec<u8>;
+    const TAG: yasna::Tag = yasna::tags::TAG_OCTETSTRING;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        writer.write_bytes(value.as_slice());
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        reader.read_bytes()
+    }
+}
+
+impl Asn1Ty for AlterNamesPayloadSeq {
+    type ValueTy = AlterNamesPayload;
+    const TAG: yasna::Tag = yasna::tags::TAG_OCTETSTRING;
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        writer.write_bytes(value.as_slice());
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        let bytes = reader.read_bytes()?;
+        AlterNamesPayload::try_from(bytes.as_slice())
+    }
+}
+
+pub struct DnsName(String);
+
+impl DnsName {
+    pub fn new(name: String) -> Self {
+        Self(name)
+    }
+
+    pub fn into_vec(self) -> Vec<u8> {
+        self.0.into_bytes()
+    }
+}
+
+impl Asn1Ty for DnsNameEntry {
+    type ValueTy = DnsName;
+    const TAG: yasna::Tag = context_tag(2);
+
+    fn dump(writer: Writer<'_>, value: Self::ValueTy) {
+        let der_value = TaggedDerValue::from_tag_and_bytes(Self::TAG, value.into_vec());
+        writer.write_tagged_der(&der_value);
+    }
+
+    fn load<'a>(reader: Reader<'a, '_>) -> ASN1Result<Self::ValueTy> {
+        let der_value = reader.read_tagged_der()?;
+        let bytes = der_value.value().to_vec();
+        let name = String::from_utf8(bytes).map_err(|_| ASN1Error::new(ASN1ErrorKind::Invalid))?;
+        Ok(DnsName::new(name))
+    }
+}
+
+macro_rules! cons {
+    () => { Nil };
+    ($t: ty) => { Cons<$t, Nil> };
+    ($t: ty, $($tt: ty),* $(,)?) => {
+        Cons<$t, cons! { $($tt),* }>
+    };
+}
+
+macro_rules! asn1_seq_ty {
+    ($t: ty) => { Sequence<$t, Nil> };
+    ($t: ty, $($tt: ty),* $(,)?) => {
+        Sequence<$t, cons! { $($tt),* }>
+    };
+}
+
+macro_rules! asn1_set_ty {
+    ($t: ty) => { Set<$t, Nil> };
+    ($t: ty, $($tt: ty),* $(,)?) => {
+        Set<$t, cons! { $($tt),* }>
+    };
+}
+
+macro_rules! asn1_seq {
+    () => { () };
+    ($e: expr) => {
+        ($e, ())
+    };
+    ($e: expr , $($ee: expr),* $(,)?) => {
+        ($e, asn1_seq!{ $($ee),* } )
+    };
+}
+
+pub(crate) type Version = Tagged<CtxT0, I8>;
+pub(crate) type Serial = U8;
+pub(crate) type CertSignAlgo = asn1_seq_ty!(Oid);
+pub(crate) type ValidRange = asn1_seq_ty!(UtcTime, UtcTime);
+pub(crate) type Issuer = asn1_seq_ty!(asn1_set_ty!(asn1_seq_ty!(Oid, Utf8Str)));
+pub(crate) type Subject = Issuer;
+pub(crate) type PubKeyAlgo = asn1_seq_ty!(Oid, Oid);
+pub(crate) type PubKey = asn1_seq_ty!(PubKeyAlgo, BitVec);
+pub(crate) type RaCertExt = asn1_seq_ty!(Oid, Bytes);
+pub(crate) type AlterSubjectNames = asn1_seq_ty!(DnsNameEntry, DnsNameEntry);
+
+pub(crate) type AlterNamesExt = asn1_seq_ty!(Oid, AlterNamesPayloadSeq);
+pub(crate) type CertExtensions = Tagged<CtxT3, asn1_seq_ty!(RaCertExt, AlterNamesExt)>;
+pub(crate) type TbsCert = asn1_seq_ty!(
+    Version,
+    Serial,
+    CertSignAlgo,
+    Issuer,
+    ValidRange,
+    Subject,
+    PubKey,
+    CertExtensions,
+);
+pub(crate) type CertSig = BitVec;
+pub(crate) type X509 = asn1_seq_ty!(TbsCert, CertSignAlgo, CertSig);
+
+pub(crate) type BasicConstrainExt = asn1_seq_ty!(Oid, Boolean, Bytes);
+pub(crate) type IntermediateCertExtensions = Tagged<CtxT3, asn1_seq_ty!(BasicConstrainExt)>;
+pub(crate) type IntermediateCert = asn1_seq_ty!(
+    Version,
+    Serial,
+    CertSignAlgo,
+    Issuer,
+    ValidRange,
+    Subject,
+    PubKey,
+    IntermediateCertExtensions,
+);
+pub(crate) type _IntermediateX509 = asn1_seq_ty!(IntermediateCert, CertSignAlgo, CertSig);
+
+pub(crate) struct AlterNamesPayload(Vec<u8>);
+impl AlterNamesPayload {
+    pub fn new(name1: DnsName, name2: DnsName) -> Self {
+        let payload =
+            yasna::construct_der(|writer| AlterSubjectNames::dump(writer, asn1_seq!(name1, name2)));
+        AlterNamesPayload(payload)
+    }
+    pub fn as_slice(&self) -> &[u8] {
+        self.0.as_slice()
+    }
+}
+
+impl TryFrom<&[u8]> for AlterNamesPayload {
+    type Error = ASN1Error;
+    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
+        let payload = yasna::parse_der(bytes, AlterSubjectNames::load)?;
+        Ok(AlterNamesPayload::new(payload.0, payload.1 .0))
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/attestation/src/date.rs b/projects/web3/ConfidentialKlave/attestation/src/date.rs
new file mode 100644
index 0000000..5153fd1
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/attestation/src/date.rs
@@ -0,0 +1,315 @@
+// 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::fmt;
+
+/// Represents a date and time.
+///
+/// This structure stores date and time components as individual fields,
+/// providing a simple way to work with temporal data in attestation contexts.
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct DateTime {
+    /// Seconds after the minute - [0, 59]
+    pub sec: i32,
+    /// Minutes after the hour - [0, 59]
+    pub min: i32,
+    /// Hours after midnight - [0, 23]
+    pub hour: i32,
+    /// Day of the month - [1, 31]
+    pub day: i32,
+    /// Months since January - [1, 12]
+    pub month: i32,
+    /// Years (full year, e.g., 2025)
+    pub year: i32,
+}
+
+impl DateTime {
+    /// Creates a new DateTime instance with default values (all zeros).
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Creates a DateTime from epoch seconds.
+    ///
+    /// # Arguments
+    /// * `timestamp` - Unix timestamp in seconds since January 1, 1970
+    ///
+    /// # Returns
+    /// A new DateTime instance representing the given timestamp
+    pub fn from_timestamp(timestamp: i64) -> Self {
+        let mut dt = Self::new();
+        seconds_to_datetime(timestamp, &mut dt);
+        dt
+    }
+
+    /// Extracts the date component from this DateTime.
+    pub fn date(&self) -> Date {
+        Date {
+            day: self.day,
+            month: self.month,
+            year: self.year,
+        }
+    }
+
+    /// Validates that all fields are within their expected ranges.
+    pub fn is_valid(&self) -> bool {
+        self.sec >= 0
+            && self.sec <= 59
+            && self.min >= 0
+            && self.min <= 59
+            && self.hour >= 0
+            && self.hour <= 23
+            && self.day >= 1
+            && self.day <= 31
+            && self.month >= 1
+            && self.month <= 12
+            && self.year >= 1970 // Assuming epoch-based timestamps
+    }
+
+    /// Returns the total seconds since midnight.
+    pub fn seconds_since_midnight(&self) -> i32 {
+        self.hour * 3600 + self.min * 60 + self.sec
+    }
+}
+
+impl fmt::Display for DateTime {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(
+            f,
+            "{:02}{:02}{:02}{:02}{:02}{:02}Z",
+            self.year % 100,
+            self.month,
+            self.day,
+            self.hour,
+            self.min,
+            self.sec
+        )
+    }
+}
+
+/// Represents a date without time information.
+///
+/// This structure stores only the date components (year, month, day),
+/// useful when time information is not needed or relevant.
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct Date {
+    /// Day of the month - [1, 31]
+    pub day: i32,
+    /// Months since January - [1, 12]
+    pub month: i32,
+    /// Years (full year, e.g., 2025)
+    pub year: i32,
+}
+
+impl Date {
+    /// Creates a new Date instance with default values (all zeros).
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Creates a Date with specific year, month, and day values.
+    ///
+    /// # Arguments
+    /// * `year` - The year (e.g., 2025)
+    /// * `month` - The month [1, 12]
+    /// * `day` - The day of the month [1, 31]
+    pub fn from_ymd(year: i32, month: i32, day: i32) -> Self {
+        Self { year, month, day }
+    }
+
+    /// Validates that all fields are within their expected ranges.
+    ///
+    /// Note: This performs basic range validation but doesn't check
+    /// for month-specific day limits (e.g., February 30th would pass).
+    pub fn is_valid(&self) -> bool {
+        self.day >= 1 && self.day <= 31 && self.month >= 1 && self.month <= 12 && self.year >= 1970
+        // Assuming epoch-based timestamps
+    }
+
+    /// Checks if the year is a leap year.
+    pub fn is_leap_year(&self) -> bool {
+        is_leap_year(self.year as i64)
+    }
+
+    /// Returns the number of days in the current month.
+    pub fn days_in_month(&self) -> i32 {
+        get_days_in_month(self.year as i64, self.month as i64) as i32
+    }
+}
+
+impl fmt::Display for Date {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:04}-{:02}-{:02} UTC", self.year, self.month, self.day)
+    }
+}
+
+/// Convert epoch seconds into date time.
+///
+/// Converts a Unix timestamp (seconds since January 1, 1970 00:00:00 UTC)
+/// into a DateTime structure. This function handles leap years correctly
+/// and updates the provided DateTime instance in-place.
+///
+/// # Arguments
+/// * `ts` - Unix timestamp in seconds
+/// * `tm` - Mutable reference to DateTime structure to be updated
+///
+/// # Note
+/// This function assumes the input timestamp is valid (>= 0) and
+/// represents a time after the Unix epoch.
+pub fn seconds_to_datetime(ts: i64, tm: &mut DateTime) {
+    // Constants
+    const SECONDS_PER_MINUTE: i64 = 60;
+    const SECONDS_PER_HOUR: i64 = 3600;
+    const SECONDS_PER_DAY: i64 = 86400;
+
+    // Calculate time components from seconds within the day
+    let day_seconds = ts % SECONDS_PER_DAY;
+    let mut day_number = ts / SECONDS_PER_DAY;
+
+    tm.sec = (day_seconds % SECONDS_PER_MINUTE) as i32;
+    tm.min = ((day_seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE) as i32;
+    tm.hour = (day_seconds / SECONDS_PER_HOUR) as i32;
+
+    // Calculate year
+    let mut year = 1970i64;
+    loop {
+        let days_in_year = if is_leap_year(year) { 366 } else { 365 };
+        if day_number >= days_in_year {
+            day_number -= days_in_year;
+            year += 1;
+        } else {
+            break;
+        }
+    }
+    tm.year = year as i32;
+
+    // Calculate month and day
+    let mut month = 1;
+    while month <= 12 {
+        let days_in_month = get_days_in_month(year, month);
+        if day_number >= days_in_month {
+            day_number -= days_in_month;
+            month += 1;
+        } else {
+            break;
+        }
+    }
+    tm.month = month as i32;
+    tm.day = (day_number + 1) as i32; // Convert from 0-based to 1-based day
+}
+
+/// Helper function to determine if a year is a leap year.
+///
+/// A year is a leap year if:
+/// - It's divisible by 4, AND
+/// - If it's divisible by 100, it must also be divisible by 400
+fn is_leap_year(year: i64) -> bool {
+    year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
+}
+
+/// Helper function to get the number of days in a specific month of a year.
+///
+/// # Arguments
+/// * `year` - The year (used to determine if it's a leap year for February)
+/// * `month` - The month [1, 12]
+///
+/// # Returns
+/// Number of days in the specified month
+fn get_days_in_month(year: i64, month: i64) -> i64 {
+    match month {
+        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
+        4 | 6 | 9 | 11 => 30,
+        2 => {
+            if is_leap_year(year) {
+                29
+            } else {
+                28
+            }
+        }
+        _ => 0, // Invalid month
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_datetime_from_timestamp() {
+        // Test Unix epoch
+        let dt = DateTime::from_timestamp(0);
+        assert_eq!(dt.year, 1970);
+        assert_eq!(dt.month, 1);
+        assert_eq!(dt.day, 1);
+        assert_eq!(dt.hour, 0);
+        assert_eq!(dt.min, 0);
+        assert_eq!(dt.sec, 0);
+
+        // Test timestamp for January 1, 1971 00:00:00 UTC (365 days = 31536000 seconds)
+        let dt = DateTime::from_timestamp(31536000);
+        assert_eq!(dt.year, 1971);
+        assert_eq!(dt.month, 1);
+        assert_eq!(dt.day, 1);
+        assert_eq!(dt.hour, 0);
+        assert_eq!(dt.min, 0);
+        assert_eq!(dt.sec, 0);
+
+        // Test timestamp for January 2, 1970 12:30:45 UTC
+        let dt = DateTime::from_timestamp(86400 + 12 * 3600 + 30 * 60 + 45); // 131445
+        assert_eq!(dt.year, 1970);
+        assert_eq!(dt.month, 1);
+        assert_eq!(dt.day, 2);
+        assert_eq!(dt.hour, 12);
+        assert_eq!(dt.min, 30);
+        assert_eq!(dt.sec, 45);
+    }
+
+    #[test]
+    fn test_leap_year() {
+        assert!(is_leap_year(2000)); // Divisible by 400
+        assert!(is_leap_year(2004)); // Divisible by 4
+        assert!(!is_leap_year(1900)); // Divisible by 100 but not 400
+        assert!(!is_leap_year(2001)); // Not divisible by 4
+    }
+
+    #[test]
+    fn test_days_in_month() {
+        assert_eq!(get_days_in_month(2023, 1), 31); // January
+        assert_eq!(get_days_in_month(2023, 2), 28); // February (non-leap)
+        assert_eq!(get_days_in_month(2024, 2), 29); // February (leap)
+        assert_eq!(get_days_in_month(2023, 4), 30); // April
+    }
+
+    #[test]
+    fn test_datetime_validation() {
+        let valid_dt = DateTime::from_timestamp(1262391174);
+        assert!(valid_dt.is_valid());
+
+        let mut invalid_dt = DateTime::new();
+        invalid_dt.sec = 70; // Invalid seconds
+        assert!(!invalid_dt.is_valid());
+    }
+
+    #[test]
+    fn test_date_validation() {
+        let valid_date = Date::from_ymd(2023, 6, 15);
+        assert!(valid_date.is_valid());
+
+        let invalid_date = Date::from_ymd(2023, 13, 15); // Invalid month
+        assert!(!invalid_date.is_valid());
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/attestation/src/lib.rs b/projects/web3/ConfidentialKlave/attestation/src/lib.rs
new file mode 100644
index 0000000..db2a288
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/attestation/src/lib.rs
@@ -0,0 +1,547 @@
+// 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.
+
+#[macro_use]
+pub mod cert;
+pub mod date;
+pub mod report_verifier;
+pub mod sign;
+pub mod utils;
+pub mod verify;
+
+#[cfg(test)]
+mod tests {
+    use crate::report_verifier::*;
+    use crate::sign::{sign_csr_with_extension, sign_intermediate};
+    use crate::utils::*;
+    use crate::verify;
+    use crate::verify::AttestationCertChain;
+    use crypto::{EcdsaKeyPair, ECDSA_P256_SHA256_ASN1_SIGNING, ECDSA_P384_SHA384_ASN1_SIGNING};
+
+    use anyhow::Result;
+    use rustls::RootCertStore;
+    use rustls::{ConnectionCommon, SideData};
+    use std::collections::HashSet;
+    use std::convert::TryInto;
+    use std::io::{self, Read};
+    use std::iter::FromIterator;
+    use std::ops::{Deref, DerefMut};
+    use std::sync::Arc;
+
+    const TEST_CERT_VAILD_SECONDS: i64 = 60 * 60 * 24 * 365;
+
+    fn setup_ra_client_config(
+        report_payload: &[u8],
+    ) -> Result<(rustls::ClientConfig, AttestationCertChain)> {
+        // Note: for generate test-ca certs, please refer to the tls_server example in Teaclave SDK
+        let ca_cert_path = "test-ca/ecdsa/ca.cert";
+        let inter_cert_path = "test-ca/ecdsa/inter.cert";
+        let inter_key_path = "test-ca/ecdsa/inter.key";
+        let client_key_path = "test-ca/ecdsa/client.key";
+
+        // inter.key sign client cert
+        let at_key = load_pem_key(inter_key_path)?;
+        let ak_kp = EcdsaKeyPair::from_bytes(&at_key.0, &ECDSA_P256_SHA256_ASN1_SIGNING)?;
+
+        let client_key = load_pem_key(client_key_path)?;
+        let client_kp = EcdsaKeyPair::from_bytes(&client_key.0, &ECDSA_P256_SHA256_ASN1_SIGNING)?;
+        let client_pub = client_kp.pub_key();
+        assert_eq!(
+            client_pub[0], 0x4,
+            "Missing uncompressed public key indicator."
+        );
+
+        let client_der = sign_csr_with_extension(
+            &ak_kp,
+            client_pub,
+            "CK ECDSA level 2 intermediate",
+            "CK client",
+            ["client", "localhost"],
+            report_payload,
+            TEST_CERT_VAILD_SECONDS,
+        )?;
+        let client_cert = rustls::Certificate(client_der);
+
+        // setup ca cert
+        let mut root_store = RootCertStore::empty();
+        let ca_cert = load_pem_single_cert(ca_cert_path)?;
+        root_store.add_parsable_certificates(&[ca_cert.0.clone()]);
+
+        // setup key and client cert fullchain
+        let inter_cert = load_pem_single_cert(inter_cert_path)?;
+        let acc = AttestationCertChain {
+            end: client_cert.clone().0,
+            intermediate: inter_cert.clone().0,
+            root: ca_cert.clone().0,
+        };
+        let cert_chain = vec![client_cert, inter_cert, ca_cert];
+
+        let config = rustls::ClientConfig::builder()
+            .with_safe_defaults()
+            .with_root_certificates(root_store)
+            .with_single_cert(cert_chain, client_key)
+            .unwrap();
+
+        Ok((config, acc))
+    }
+
+    fn setup_fixed_client_config() -> Result<rustls::ClientConfig> {
+        let ca_cert_path = "test-ca/ecdsa/ca.cert";
+        let client_fullchain_path = "test-ca/ecdsa/client.fullchain";
+        let client_key_path = "test-ca/ecdsa/client.key";
+
+        // setup ca cert
+        let mut root_store = RootCertStore::empty();
+        let ca_cert = load_pem_single_cert(ca_cert_path)?;
+        root_store.add_parsable_certificates(&[ca_cert.0.clone()]);
+
+        // setup key and client cert fullchain
+        let cert_chain = load_pem_cert(client_fullchain_path)?;
+        let client_key = load_pem_key(client_key_path)?;
+
+        let config = rustls::ClientConfig::builder()
+            .with_safe_defaults()
+            .with_root_certificates(root_store)
+            .with_single_cert(cert_chain, client_key)
+            .unwrap();
+
+        Ok(config)
+    }
+
+    fn server_verifier(report: &AttestationReport) -> bool {
+        println!("[+] server_verifier: attestation report: {:?}", report);
+        true
+    }
+
+    fn setup_client_config_with_verifier() -> Result<rustls::ClientConfig> {
+        let ca_cert_path = "test-ca/ecdsa/ca.cert";
+        let client_fullchain_path = "test-ca/ecdsa/client.fullchain";
+        let client_key_path = "test-ca/ecdsa/client.key";
+
+        let accepted_enclave_attrs = vec![EnclaveAttr {
+            measurement: vec![0u8],
+        }];
+        let root_cert_bytes = include_bytes!("../test-ca/ecdsa/ca.der");
+        let verifier = Arc::new(AttestationReportVerifier::new(
+            accepted_enclave_attrs,
+            root_cert_bytes,
+            server_verifier,
+        ));
+        let mut root_store = RootCertStore::empty();
+        let ca_cert = load_pem_single_cert(ca_cert_path)?;
+        root_store.add_parsable_certificates(&[ca_cert.0.clone()]);
+        let cert_chain = load_pem_cert(client_fullchain_path)?;
+        let client_key = load_pem_key(client_key_path)?;
+
+        let mut config = rustls::ClientConfig::builder()
+            .with_safe_defaults()
+            .with_root_certificates(root_store)
+            .with_single_cert(cert_chain, client_key)
+            .unwrap();
+        config.dangerous().set_certificate_verifier(verifier);
+
+        Ok(config)
+    }
+
+    fn setup_client_config_with_dyn_inter_cert_verifier() -> Result<rustls::ClientConfig> {
+        let ca_cert_path = "test-ca/ecdsa/ca.cert";
+        let client_fullchain_path = "test-ca/ecdsa/client.fullchain";
+        let client_key_path = "test-ca/ecdsa/client.key";
+
+        let accepted_enclave_attrs = vec![EnclaveAttr {
+            measurement: vec![0u8],
+        }];
+        let root_cert_bytes = include_bytes!("../test-ca/ecdsa/ca.der");
+        let verifier = Arc::new(AttestationReportVerifier::new(
+            accepted_enclave_attrs,
+            root_cert_bytes,
+            server_verifier,
+        ));
+        let mut root_store = RootCertStore::empty();
+        let ca_cert = load_pem_single_cert(ca_cert_path)?;
+        root_store.add_parsable_certificates(&[ca_cert.0.clone()]);
+        let cert_chain = load_pem_cert(client_fullchain_path)?;
+        let client_key = load_pem_key(client_key_path)?;
+
+        let mut config = rustls::ClientConfig::builder()
+            .with_safe_defaults()
+            .with_root_certificates(root_store)
+            .with_single_cert(cert_chain, client_key)
+            .unwrap();
+        config.dangerous().set_certificate_verifier(verifier);
+
+        Ok(config)
+    }
+
+    fn setup_fixed_server_config() -> Result<rustls::ServerConfig> {
+        let ca_cert_path = "test-ca/ecdsa/ca.cert";
+        let server_fullchain_path = "test-ca/ecdsa/end.fullchain";
+        let server_key_path = "test-ca/ecdsa/end.key";
+
+        let ca_cert = load_pem_single_cert(ca_cert_path)?;
+        let mut client_auth_roots = rustls::RootCertStore::empty();
+        client_auth_roots.add(&ca_cert)?;
+        let client_auth = rustls::server::AllowAnyAuthenticatedClient::new(client_auth_roots);
+        let cert = load_pem_cert(server_fullchain_path)?;
+        let key = load_pem_key(server_key_path)?;
+
+        let config = rustls::ServerConfig::builder()
+            .with_safe_defaults()
+            .with_client_cert_verifier(Arc::new(client_auth))
+            .with_single_cert(cert, key)
+            .unwrap();
+
+        Ok(config)
+    }
+
+    fn setup_ra_server_config(
+        report_payload: &[u8],
+    ) -> Result<(rustls::ServerConfig, AttestationCertChain)> {
+        let ca_cert_path = "test-ca/ecdsa/ca.cert";
+        let inter_cert_path = "test-ca/ecdsa/inter.cert";
+        let inter_key_path = "test-ca/ecdsa/inter.key";
+        let end_key_path = "test-ca/ecdsa/end.key";
+
+        // server verify client cert
+        let ca_cert = load_pem_single_cert(ca_cert_path)?;
+        let mut client_auth_roots = rustls::RootCertStore::empty();
+        client_auth_roots.add(&ca_cert)?;
+        let client_auth = rustls::server::AllowAnyAuthenticatedClient::new(client_auth_roots);
+
+        // inter.key sign server end cert
+        let at_key = load_pem_key(inter_key_path)?;
+        let ak_kp = EcdsaKeyPair::from_bytes(&at_key.0, &ECDSA_P256_SHA256_ASN1_SIGNING)?;
+
+        let end_key = load_pem_key(end_key_path)?;
+        let end_kp = EcdsaKeyPair::from_bytes(&end_key.0, &ECDSA_P256_SHA256_ASN1_SIGNING)?;
+        let end_pub = end_kp.pub_key();
+        assert_eq!(
+            end_pub[0], 0x4,
+            "Missing uncompressed public key indicator."
+        );
+
+        let end_der = sign_csr_with_extension(
+            &ak_kp,
+            end_pub,
+            "CK ECDSA level 2 intermediate",
+            "testserver.com",
+            ["testserver.com", "localhost"],
+            report_payload,
+            TEST_CERT_VAILD_SECONDS,
+        )?;
+        let end_cert = rustls::Certificate(end_der.clone());
+        println!("setup server config: sign");
+
+        // setup key and server cert fullchain
+        let inter_cert = load_pem_single_cert(inter_cert_path)?;
+        let acc = AttestationCertChain {
+            end: end_cert.clone().0,
+            intermediate: inter_cert.clone().0,
+            root: ca_cert.clone().0,
+        };
+
+        let cert_chain = vec![end_cert, inter_cert, ca_cert];
+
+        let config = rustls::ServerConfig::builder()
+            .with_safe_defaults()
+            .with_client_cert_verifier(Arc::new(client_auth))
+            .with_single_cert(cert_chain, end_key)
+            .unwrap();
+
+        Ok((config, acc))
+    }
+
+    fn client_verifier(_cert: &[u8]) -> bool {
+        println!("[+] client_verifier");
+        true
+    }
+
+    fn setup_ra_dyn_inter_cert_server_config(
+        report_payload: &[u8],
+    ) -> Result<(rustls::ServerConfig, AttestationCertChain)> {
+        let ca_cert_path = "test-ca/ecdsa/ca.cert";
+        let ca_key_path = "test-ca/ecdsa/ca.key";
+
+        // load ca cert and key
+        let ca_cert = load_pem_single_cert(ca_cert_path)?;
+        let ca_key = load_pem_key(ca_key_path)?;
+        let mut client_auth_roots = rustls::RootCertStore::empty();
+        client_auth_roots.add(&ca_cert)?;
+        let client_auth = rustls::server::AllowAnyAuthenticatedClient::new(client_auth_roots);
+
+        // generate intermediate cert
+        let ca_kp = EcdsaKeyPair::from_bytes(&ca_key.0, &ECDSA_P384_SHA384_ASN1_SIGNING)?;
+        let inter_kp = EcdsaKeyPair::new()?;
+        let inter_pub = inter_kp.pub_key();
+        assert_eq!(
+            inter_pub[0], 0x4,
+            "Missing uncompressed public key indicator."
+        );
+        let inter_der = sign_intermediate(
+            &ca_kp,
+            inter_pub,
+            "CK ECDSA CA",
+            "CK ECDSA level 2 intermediate",
+            TEST_CERT_VAILD_SECONDS,
+        )?;
+        let inter_cert = rustls::Certificate(inter_der.clone());
+        println!("setup intermedate cert finished");
+
+        // inter.key sign server end cert
+        let end_kp = EcdsaKeyPair::new()?;
+        let end_pub = end_kp.pub_key();
+        assert_eq!(
+            end_pub[0], 0x4,
+            "Missing uncompressed public key indicator."
+        );
+
+        let end_der = sign_csr_with_extension(
+            &inter_kp,
+            end_pub,
+            "CK ECDSA level 2 intermediate",
+            "testserver.com",
+            ["testserver.com", "localhost"],
+            report_payload,
+            TEST_CERT_VAILD_SECONDS,
+        )?;
+        println!("setup intermedate cert finished");
+        let end_cert = rustls::Certificate(end_der.clone());
+
+        // setup cert fullchain
+        let acc = AttestationCertChain {
+            end: end_cert.clone().0,
+            intermediate: inter_cert.clone().0,
+            root: ca_cert.clone().0,
+        };
+
+        let cert_chain = vec![end_cert, inter_cert, ca_cert];
+        let end_key = rustls::PrivateKey(end_kp.prv_key().to_vec());
+        let config = rustls::ServerConfig::builder()
+            .with_safe_defaults()
+            .with_client_cert_verifier(Arc::new(client_auth))
+            .with_single_cert(cert_chain, end_key)
+            .unwrap();
+
+        Ok((config, acc))
+    }
+
+    fn setup_server_config_with_verifier() -> Result<rustls::ServerConfig> {
+        let server_fullchain_path = "test-ca/ecdsa/end.fullchain";
+        let server_key_path = "test-ca/ecdsa/end.key";
+
+        let accepted_pub_keys = HashSet::from_iter([vec![0u8; 64]]);
+        let client_auth = ClientVerifier::new(accepted_pub_keys, client_verifier);
+
+        let cert = load_pem_cert(server_fullchain_path)?;
+        let key = load_pem_key(server_key_path)?;
+
+        let config = rustls::ServerConfig::builder()
+            .with_safe_defaults()
+            .with_client_cert_verifier(Arc::new(client_auth))
+            .with_single_cert(cert, key)
+            .unwrap();
+
+        Ok(config)
+    }
+
+    fn transfer<L, R, LS, RS>(left: &mut L, right: &mut R, expect_data: Option<usize>)
+    where
+        L: DerefMut + Deref<Target = ConnectionCommon<LS>>,
+        R: DerefMut + Deref<Target = ConnectionCommon<RS>>,
+        LS: SideData,
+        RS: SideData,
+    {
+        let mut tls_buf = [0u8; 262144];
+        let mut data_left = expect_data;
+        let mut data_buf = [0u8; 8192];
+
+        loop {
+            let mut sz = 0;
+
+            while left.wants_write() {
+                let written = left.write_tls(&mut tls_buf[sz..].as_mut()).unwrap();
+                if written == 0 {
+                    break;
+                }
+
+                sz += written;
+            }
+
+            if sz == 0 {
+                return;
+            }
+
+            let mut offs = 0;
+            loop {
+                match right.read_tls(&mut tls_buf[offs..sz].as_ref()) {
+                    Ok(read) => {
+                        right.process_new_packets().unwrap();
+                        offs += read;
+                    }
+                    Err(err) => {
+                        panic!("error on transfer {}..{}: {}", offs, sz, err);
+                    }
+                }
+
+                if let Some(left) = &mut data_left {
+                    loop {
+                        let sz = match right.reader().read(&mut data_buf) {
+                            Ok(sz) => sz,
+                            Err(err) if err.kind() == io::ErrorKind::WouldBlock => break,
+                            Err(err) => panic!("failed to read data: {}", err),
+                        };
+
+                        *left -= sz;
+                        if *left == 0 {
+                            break;
+                        }
+                    }
+                }
+                if sz == offs {
+                    break;
+                }
+            }
+        }
+    }
+
+    pub fn do_handshake(
+        client: &mut rustls::ClientConnection,
+        server: &mut rustls::ServerConnection,
+    ) -> bool {
+        while server.is_handshaking() || client.is_handshaking() {
+            transfer(client, server, None);
+            server.process_new_packets().unwrap();
+            transfer(server, client, None);
+            client.process_new_packets().unwrap();
+        }
+        true
+    }
+
+    #[test]
+    fn tls_ra_works() {
+        let server_config = setup_fixed_server_config().unwrap();
+        let mut server = rustls::ServerConnection::new(Arc::new(server_config)).unwrap();
+
+        let report = AttestationReport::default();
+        let payload = serde_json::to_vec(&report).unwrap();
+
+        let (client_config, acc) = setup_ra_client_config(&payload).unwrap();
+        let mut client =
+            rustls::ClientConnection::new(Arc::new(client_config), "localhost".try_into().unwrap())
+                .unwrap();
+
+        do_handshake(&mut client, &mut server);
+
+        /* Ideal solution: get certs in server handler */
+        let certs = server.peer_certificates().unwrap();
+        let report_recv = verify::extract_report_from_cert(&certs[0]).unwrap();
+        assert_eq!(report, report_recv);
+
+        /* Temporary solution: get AttestationCertChain in POST parameters */
+        let ca_cert_path = "test-ca/ecdsa/ca.cert";
+        let server_ca = load_pem_single_cert(ca_cert_path).unwrap();
+        verify::verify_cert_chain(acc, &server_ca).unwrap();
+        let _cert_info = verify::extract_info_from_cert(&certs[0]).unwrap();
+        let report_recv = verify::extract_report_from_cert(&certs[0]).unwrap();
+        assert_eq!(report, report_recv);
+    }
+
+    #[test]
+    fn mutual_tls_works() {
+        let client_config = setup_fixed_client_config().unwrap();
+        let server_config = setup_fixed_server_config().unwrap();
+
+        let mut client =
+            rustls::ClientConnection::new(Arc::new(client_config), "localhost".try_into().unwrap())
+                .unwrap();
+        let mut server = rustls::ServerConnection::new(Arc::new(server_config)).unwrap();
+        do_handshake(&mut client, &mut server);
+    }
+
+    #[test]
+    fn tls_server_ra_works() {
+        let report = AttestationReport::default();
+        let payload = serde_json::to_vec(&report).unwrap();
+        let (server_config, _acc) = setup_ra_server_config(&payload).unwrap();
+        let mut server = rustls::ServerConnection::new(Arc::new(server_config)).unwrap();
+
+        let client_config = setup_fixed_client_config().unwrap();
+        let mut client =
+            rustls::ClientConnection::new(Arc::new(client_config), "localhost".try_into().unwrap())
+                .unwrap();
+        do_handshake(&mut client, &mut server);
+
+        /* Ideal solution: get certs in server handler */
+        let server_ca = load_pem_single_cert("test-ca/ecdsa/ca.cert").unwrap();
+        let certs = client.peer_certificates().unwrap();
+        let chain = AttestationCertChain {
+            end: certs[0].0.clone(),
+            intermediate: certs[1].0.clone(),
+            root: certs[2].0.clone(),
+        };
+
+        verify::verify_cert_chain(chain, &server_ca).unwrap();
+        let report_recv = verify::extract_report_from_cert(&certs[0]).unwrap();
+        assert_eq!(report, report_recv);
+    }
+
+    // test client public key from server side
+    #[test]
+    fn test_client_verifier_works() {
+        // client cert should be generated by yasna in order to extract the public key
+        let report = AttestationReport::default();
+        let payload = serde_json::to_vec(&report).unwrap();
+        let (client_config, _acc) = setup_ra_client_config(&payload).unwrap();
+        let server_config = setup_server_config_with_verifier().unwrap();
+
+        let mut client =
+            rustls::ClientConnection::new(Arc::new(client_config), "localhost".try_into().unwrap())
+                .unwrap();
+        let mut server = rustls::ServerConnection::new(Arc::new(server_config)).unwrap();
+        assert!(do_handshake(&mut client, &mut server));
+    }
+
+    // test server cert from client side
+    #[test]
+    fn test_server_verifier_works() {
+        let client_config = setup_client_config_with_verifier().unwrap();
+        let report = AttestationReport::default();
+        let payload = serde_json::to_vec(&report).unwrap();
+        let (server_config, _acc) = setup_ra_server_config(&payload).unwrap();
+
+        let mut client =
+            rustls::ClientConnection::new(Arc::new(client_config), "localhost".try_into().unwrap())
+                .unwrap();
+        let mut server = rustls::ServerConnection::new(Arc::new(server_config)).unwrap();
+        assert!(do_handshake(&mut client, &mut server));
+    }
+
+    // test dyn intermediate cert from client side
+    #[test]
+    fn test_dyn_inter_server_verifier_works() {
+        let client_config = setup_client_config_with_dyn_inter_cert_verifier().unwrap();
+        let report = AttestationReport::default();
+        let payload = serde_json::to_vec(&report).unwrap();
+        let (server_config, _acc) = setup_ra_dyn_inter_cert_server_config(&payload).unwrap();
+
+        let mut client =
+            rustls::ClientConnection::new(Arc::new(client_config), "localhost".try_into().unwrap())
+                .unwrap();
+        let mut server = rustls::ServerConnection::new(Arc::new(server_config)).unwrap();
+        assert!(do_handshake(&mut client, &mut server));
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/attestation/src/report_verifier.rs b/projects/web3/ConfidentialKlave/attestation/src/report_verifier.rs
new file mode 100644
index 0000000..effbff8
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/attestation/src/report_verifier.rs
@@ -0,0 +1,217 @@
+// 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 crate::verify::{self, AttestationCertChain};
+use anyhow::{ensure, Result};
+use basic_utils::keccak_hash_to_bytes;
+use serde::{Deserialize, Serialize};
+use std::collections::HashSet;
+#[cfg(not(target_os = "optee"))]
+use std::time::SystemTime;
+
+#[cfg(target_os = "optee")]
+use optee_utee::trace_println as println;
+#[cfg(target_os = "optee")]
+use rustls::optee_time::SystemTime;
+
+#[derive(Serialize, Deserialize, PartialEq, Debug)]
+pub struct AttestationReport {
+    pub measurement: Vec<u8>,
+}
+impl AttestationReport {
+    pub fn from_cert(
+        presented_certs: &[rustls::Certificate],
+        report_ca_cert: &[u8],
+    ) -> Result<Self> {
+        // Extract information for attestation from TLS certification.
+        ensure!(
+            presented_certs.len() >= 3,
+            "[+] AttestationReport: presented_certs.len() >= 3"
+        );
+        let end_cert = presented_certs[0].clone().0;
+        let acc = AttestationCertChain {
+            end: end_cert.clone(),
+            intermediate: presented_certs[1].clone().0,
+            root: presented_certs[2].clone().0,
+        };
+        verify::verify_cert_chain(acc, &rustls::Certificate(report_ca_cert.to_vec())).unwrap();
+        let report = verify::extract_report_from_cert(&rustls::Certificate(end_cert)).unwrap();
+        Ok(report)
+    }
+}
+impl Default for AttestationReport {
+    fn default() -> Self {
+        Self {
+            measurement: vec![0u8; 32],
+        }
+    }
+}
+
+#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
+pub struct EnclaveAttr {
+    pub measurement: Vec<u8>,
+}
+
+/// User defined verification function to further verify the attestation report.
+pub type AttestationReportVerificationFn = fn(&AttestationReport) -> bool;
+
+/// Type used to verify attestation reports (this can be set as a certificate
+/// verifier in `rustls::ClientConfig`).
+#[derive(Clone)]
+pub struct AttestationReportVerifier {
+    /// Valid enclave attributes (only enclaves with attributes in this vector
+    /// will be accepted).
+    pub accepted_enclave_attrs: Vec<EnclaveAttr>,
+    /// Root certificate of the attestation service provider (e.g., IAS).
+    pub root_ca: Vec<u8>,
+    /// User defined function to verify the attestation report.
+    pub verifier: AttestationReportVerificationFn,
+}
+
+impl AttestationReportVerifier {
+    pub fn new(
+        accepted_enclave_attrs: Vec<EnclaveAttr>,
+        root_ca: &[u8],
+        verifier: AttestationReportVerificationFn,
+    ) -> Self {
+        Self {
+            accepted_enclave_attrs,
+            root_ca: root_ca.to_vec(),
+            verifier,
+        }
+    }
+
+    /// Verify whether the `MR_SIGNER` and `MR_ENCLAVE` in the attestation report is
+    /// accepted by us, which are defined in `accepted_enclave_attrs`.
+    fn verify_measures(&self, _attestation_report: &AttestationReport) -> bool {
+        println!("[+] AttestationReportVerifier::verify measures");
+        true
+    }
+
+    /// Verify TLS certificate.
+    fn verify_server_cert(&self, presented_certs: &[rustls::Certificate]) -> bool {
+        println!("[+] AttestationReportVerifier::verify server cert");
+
+        let report = match AttestationReport::from_cert(presented_certs, &self.root_ca) {
+            Ok(report) => report,
+            Err(e) => {
+                println!(
+                    "[+] AttestationReportVerifier::cert verification error {:?}",
+                    e
+                );
+                return false;
+            }
+        };
+        println!(
+            "[+] AttestationReportVerifier::extract report successfully, report: {:?}",
+            &report
+        );
+
+        self.verify_measures(&report) && (self.verifier)(&report)
+    }
+}
+
+impl rustls::client::ServerCertVerifier for AttestationReportVerifier {
+    fn verify_server_cert(
+        &self,
+        end_entity: &rustls::Certificate,
+        intermediates: &[rustls::Certificate],
+        _server_name: &rustls::ServerName,
+        _scts: &mut dyn Iterator<Item = &[u8]>,
+        _ocsp_response: &[u8],
+        _now: SystemTime,
+    ) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
+        // This call automatically verifies certificate signature
+        println!("[+] ServerCertVerifier::verify server cert");
+        let mut presented_certs = intermediates.to_vec();
+        presented_certs.insert(0, end_entity.clone());
+        if self.verify_server_cert(presented_certs.as_slice()) {
+            Ok(rustls::client::ServerCertVerified::assertion())
+        } else {
+            Err(rustls::Error::General(
+                "server cert verification failed".to_string(),
+            ))
+        }
+    }
+}
+
+/// Type used to verify client certificates from server side
+pub type ClientVerifierFn = fn(&[u8]) -> bool;
+#[derive(Clone)]
+pub struct ClientVerifier {
+    pub accepted_pubkey_hash: HashSet<Vec<u8>>,
+    pub verifier_fn: ClientVerifierFn,
+}
+impl ClientVerifier {
+    pub fn new(accepted_pubkey_hash: HashSet<Vec<u8>>, verifier_fn: ClientVerifierFn) -> Self {
+        Self {
+            accepted_pubkey_hash,
+            verifier_fn,
+        }
+    }
+}
+
+impl rustls::server::ClientCertVerifier for ClientVerifier {
+    fn offer_client_auth(&self) -> bool {
+        true
+    }
+
+    #[allow(deprecated)]
+    fn client_auth_root_subjects(&self) -> &[rustls::DistinguishedName] {
+        &[]
+    }
+
+    fn verify_client_cert(
+        &self,
+        end_entity: &rustls::Certificate,
+        _intermediates: &[rustls::Certificate],
+        now: SystemTime,
+    ) -> Result<rustls::server::ClientCertVerified, rustls::Error> {
+        #[cfg(not(target_os = "optee"))]
+        let _now =
+            webpki::Time::try_from(now).map_err(|_| rustls::Error::FailedToGetCurrentTime)?;
+        #[cfg(target_os = "optee")]
+        let _now = webpki::Time::from_seconds_since_unix_epoch(now.secs());
+
+        println!("[+] ClientVerifier: invoke verify_client_cert");
+        // extract pubkey from client cert
+        // check if pub key exists in accepted_pub_keys
+        let pub_key = match verify::extract_pub_key_from_cert(end_entity) {
+            Ok(pub_key) => pub_key,
+            Err(e) => {
+                println!(
+                    "[+] ClientVerifier: extract_pub_key_from_cert error {:?}",
+                    e
+                );
+                return Err(rustls::Error::General(
+                    "client cert verification failed".to_string(),
+                ));
+            }
+        };
+        println!("[+] ClientVerifier: pub_key: {:?}", pub_key);
+
+        // since attestation is a independent module, it doesn't import the "types"
+        // so we implement the hash function here
+        let pubkey_hash = keccak_hash_to_bytes(&pub_key)[..8].to_vec();
+        if self.accepted_pubkey_hash.contains(&pubkey_hash) {
+            return Ok(rustls::server::ClientCertVerified::assertion());
+        }
+
+        println!("[+] ClientVerifier: pub_key is not authorized");
+        Err(rustls::Error::HandshakeNotComplete)
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/attestation/src/sign.rs b/projects/web3/ConfidentialKlave/attestation/src/sign.rs
new file mode 100644
index 0000000..d736a19
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/attestation/src/sign.rs
@@ -0,0 +1,211 @@
+// 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.
+
+//! This module implements ECDSA (NIST P-256 curve) keys related functions. You
+//! can export private key to a DER format or create a certificate with
+//! extension for TLS-based remote attestation.
+
+use crate::date::{seconds_to_datetime, DateTime};
+use anyhow::{bail, Result};
+use basic_utils::get_system_time_seconds;
+use crypto::EcdsaKeyPair;
+use yasna::models::UTCTime;
+
+/// create_cert_with_extension makes a self-signed x509-v3 cert with SGX
+/// attestation report as extensions.
+/// @reference [Internet X.509 Public Key Infrastructure Certificate and
+/// Certificate Revocation List (CRL) Profile][1]
+///
+/// [1]: https://tools.ietf.org/pdf/rfc5280.pdf
+pub fn sign_csr_with_extension(
+    key_pair: &EcdsaKeyPair,
+    pub_key: &[u8],
+    issuer: &str,
+    subject: &str,
+    alter_dns_names: [&str; 2],
+    payload: &[u8],
+    cert_valid_seconds: i64,
+) -> Result<Vec<u8>> {
+    use crate::cert::*;
+    use bit_vec::BitVec;
+    use yasna::models::ObjectIdentifier;
+
+    // Construct useful OIDs.
+    let ecdsa_with_sha256_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 4, 3, 2]);
+    let common_name_oid = ObjectIdentifier::from_slice(&[2, 5, 4, 3]);
+    let ec_public_key_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 2, 1]);
+    let prime256v1_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 3, 1, 7]);
+    let comment_oid = ObjectIdentifier::from_slice(&[2, 16, 840, 1, 113_730, 1, 13]);
+    let subject_alt_name_oid = ObjectIdentifier::from_slice(&[2, 5, 29, 17]);
+
+    let alter_names = (
+        DnsName::new(alter_dns_names[0].to_string()),
+        DnsName::new(alter_dns_names[1].to_string()),
+    );
+
+    let (issue_ts, expire_ts) = valid_time_range(cert_valid_seconds)?;
+    // Construct certificate with payload in extension in DER.
+    let tbs_cert_der = yasna::construct_der(|writer| {
+        let version = 2i8;
+        let serial = 1u8;
+        let cert_sign_algo = asn1_seq!(ecdsa_with_sha256_oid.clone());
+        let issuer = asn1_seq!(asn1_seq!(asn1_seq!(
+            common_name_oid.clone(),
+            issuer.to_owned()
+        )));
+        let valid_range = asn1_seq!(issue_ts, expire_ts);
+        let subject = asn1_seq!(asn1_seq!(asn1_seq!(
+            common_name_oid.clone(),
+            subject.to_string(),
+        )));
+        let pub_key = asn1_seq!(
+            asn1_seq!(ec_public_key_oid, prime256v1_oid,),
+            BitVec::from_bytes(pub_key),
+        );
+
+        let sgx_ra_cert_ext = asn1_seq!(comment_oid, payload.to_owned());
+
+        let alter_names_payload = AlterNamesPayload::new(alter_names.0, alter_names.1);
+        let alter_names = asn1_seq!(subject_alt_name_oid, alter_names_payload);
+
+        let cert_ext = asn1_seq!(sgx_ra_cert_ext, alter_names);
+
+        let tbs_cert = asn1_seq!(
+            version,
+            serial,
+            cert_sign_algo,
+            issuer,
+            valid_range,
+            subject,
+            pub_key,
+            cert_ext,
+        );
+        TbsCert::dump(writer, tbs_cert);
+    });
+
+    let sig = key_pair.sign(tbs_cert_der.as_slice()).unwrap();
+    let sig_der = yasna::construct_der(|writer| writer.write_der(sig.as_ref()));
+
+    let der = yasna::construct_der(|writer| {
+        writer.write_sequence(|writer| {
+            writer.next().write_der(tbs_cert_der.as_slice());
+            CertSignAlgo::dump(writer.next(), asn1_seq!(ecdsa_with_sha256_oid.clone()));
+            writer
+                .next()
+                .write_bitvec(&BitVec::from_bytes(sig_der.as_slice()));
+        });
+    });
+
+    Ok(der)
+}
+
+pub fn sign_intermediate(
+    key_pair: &EcdsaKeyPair,
+    pub_key: &[u8],
+    issuer: &str,
+    subject: &str,
+    cert_valid_seconds: i64,
+) -> Result<Vec<u8>> {
+    use crate::cert::*;
+    use bit_vec::BitVec;
+    use yasna::models::ObjectIdentifier;
+    // Construct useful OIDs.
+    let ecdsa_with_sha384_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 4, 3, 3]);
+    let common_name_oid = ObjectIdentifier::from_slice(&[2, 5, 4, 3]);
+    let ec_public_key_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 2, 1]);
+    let prime256v1_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 3, 1, 7]);
+    let basic_constrain_oid = ObjectIdentifier::from_slice(&[2, 5, 29, 19]);
+
+    let (issue_ts, expire_ts) = valid_time_range(cert_valid_seconds)?;
+    // Construct certificate with payload in extension in DER.
+    let tbs_cert_der = yasna::construct_der(|writer| {
+        let version = 2i8;
+        let serial = 1u8;
+        let cert_sign_algo = asn1_seq!(ecdsa_with_sha384_oid.clone());
+        let issuer = asn1_seq!(asn1_seq!(asn1_seq!(
+            common_name_oid.clone(),
+            issuer.to_owned()
+        )));
+        println!("issuer: {:?}", issuer);
+        let valid_range = asn1_seq!(issue_ts, expire_ts);
+        let subject = asn1_seq!(asn1_seq!(asn1_seq!(
+            common_name_oid.clone(),
+            subject.to_string(),
+        )));
+        let pub_key = asn1_seq!(
+            asn1_seq!(ec_public_key_oid, prime256v1_oid,),
+            BitVec::from_bytes(pub_key),
+        );
+        let basic_constrain = asn1_seq!(
+            basic_constrain_oid,
+            true,
+            [0x30, 0x03, 0x01, 0x01, 0xff].to_vec()
+        );
+        let cert_ext = asn1_seq!(basic_constrain,);
+
+        let tbs_cert = asn1_seq!(
+            version,
+            serial,
+            cert_sign_algo,
+            issuer,
+            valid_range,
+            subject,
+            pub_key,
+            cert_ext,
+        );
+        IntermediateCert::dump(writer, tbs_cert);
+    });
+
+    let sig = key_pair.sign(tbs_cert_der.as_slice()).unwrap();
+    let sig_der = yasna::construct_der(|writer| writer.write_der(sig.as_ref()));
+
+    let der = yasna::construct_der(|writer| {
+        writer.write_sequence(|writer| {
+            writer.next().write_der(tbs_cert_der.as_slice());
+            CertSignAlgo::dump(writer.next(), asn1_seq!(ecdsa_with_sha384_oid.clone()));
+            writer
+                .next()
+                .write_bitvec(&BitVec::from_bytes(sig_der.as_slice()));
+        });
+    });
+    Ok(der)
+}
+
+fn valid_time_range(cert_valid_seconds: i64) -> Result<(UTCTime, UTCTime)> {
+    let issue_ts = get_system_time_seconds()? - 10; // set issued time as 10 seconds earlier
+    let mut issue_date_time = DateTime::default();
+    seconds_to_datetime(issue_ts, &mut issue_date_time);
+    let expire_ts = issue_ts + cert_valid_seconds;
+    let mut expire_date_time = DateTime::default();
+    seconds_to_datetime(expire_ts, &mut expire_date_time);
+    println!("issue_ts: {:?}, date: {}", issue_ts, issue_date_time);
+    println!("expire_ts: {:?}, date: {}", expire_ts, expire_date_time);
+
+    let issue_utc_time = match UTCTime::parse(format!("{}", issue_date_time).as_bytes()) {
+        Some(t) => t,
+        None => {
+            bail!("parse issue date time failed")
+        }
+    };
+    let expire_utc_time = match UTCTime::parse(format!("{}", expire_date_time).as_bytes()) {
+        Some(t) => t,
+        None => {
+            bail!("parse expire date time failed")
+        }
+    };
+    Ok((issue_utc_time, expire_utc_time))
+}
diff --git a/projects/web3/ConfidentialKlave/attestation/src/utils.rs b/projects/web3/ConfidentialKlave/attestation/src/utils.rs
new file mode 100644
index 0000000..f11b766
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/attestation/src/utils.rs
@@ -0,0 +1,140 @@
+// 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 crate::report_verifier::AttestationReport;
+use crate::sign::{sign_csr_with_extension, sign_intermediate};
+use basic_utils::get_system_time_seconds;
+use crypto::{EcdsaKeyPair, ECDSA_P256_SHA256_ASN1_SIGNING, ECDSA_P384_SHA384_ASN1_SIGNING};
+
+use anyhow::{Context, Result};
+use std::convert::TryInto;
+use std::fs;
+use std::io::BufReader;
+
+pub fn load_pem_cert(filename: &str) -> Result<Vec<rustls::Certificate>> {
+    let certfile = fs::File::open(filename).with_context(|| "cannot open certificate file")?;
+    let mut reader = BufReader::new(certfile);
+    let certs = rustls_pemfile::certs(&mut reader)
+        .map_err(|_| anyhow::anyhow!("pemfile::load_pem_cert failed"));
+    Ok(certs
+        .unwrap()
+        .iter()
+        .map(|v| rustls::Certificate(v.clone()))
+        .collect())
+}
+
+pub fn load_pem_single_cert(filename: &str) -> Result<rustls::Certificate> {
+    let certs = load_pem_cert(filename)?;
+    assert!(certs.len() == 1);
+    Ok(certs[0].clone())
+}
+
+pub fn load_pem_cert_from_bytes(bytes: &[u8]) -> Result<Vec<rustls::Certificate>> {
+    let cursor = std::io::Cursor::new(bytes);
+    let mut reader = BufReader::new(cursor);
+    let certs = rustls_pemfile::certs(&mut reader)
+        .map_err(|_| anyhow::anyhow!("pemfile::load_pem_cert failed"));
+    Ok(certs
+        .unwrap()
+        .iter()
+        .map(|v| rustls::Certificate(v.clone()))
+        .collect())
+}
+
+pub fn load_pem_key(filename: &str) -> Result<rustls::PrivateKey> {
+    let keyfile = fs::File::open(filename).with_context(|| "cannot open private key file")?;
+    let mut reader = BufReader::new(keyfile);
+    let keys = rustls_pemfile::pkcs8_private_keys(&mut reader)
+        .map_err(|_| anyhow::anyhow!("pemfile::pkcs8_private_keys failed"))?;
+    assert!(keys.len() == 1);
+    Ok(rustls::PrivateKey(keys[0].clone()))
+}
+
+pub fn load_pem_key_from_bytes(bytes: &[u8]) -> Result<rustls::PrivateKey> {
+    let cursor = std::io::Cursor::new(bytes);
+    let mut reader = BufReader::new(cursor);
+    let keys = rustls_pemfile::pkcs8_private_keys(&mut reader)
+        .map_err(|_| anyhow::anyhow!("pemfile::pkcs8_private_keys failed"))?;
+    assert!(keys.len() == 1);
+    Ok(rustls::PrivateKey(keys[0].clone()))
+}
+
+pub fn generate_end_cert(
+    ca_key: &[u8],
+    end_key: &[u8],
+    user_name: &str,
+    cert_valid_seconds: i64,
+) -> Result<Vec<u8>> {
+    let ca_key = load_pem_key_from_bytes(ca_key)?;
+    let ca_kp = EcdsaKeyPair::from_bytes(&ca_key.0, &ECDSA_P384_SHA384_ASN1_SIGNING)?;
+    let end_kp = EcdsaKeyPair::from_bytes(end_key, &ECDSA_P256_SHA256_ASN1_SIGNING)?;
+    let end_pub = end_kp.pub_key();
+    assert_eq!(
+        end_pub[0], 0x4,
+        "Missing uncompressed public key indicator."
+    );
+
+    let report = AttestationReport::default();
+    let payload = serde_json::to_vec(&report).unwrap();
+    let end_der = sign_csr_with_extension(
+        &ca_kp,
+        end_pub,
+        "CK ECDSA CA",
+        &("CK client: ".to_string() + user_name),
+        ["client", "localhost"],
+        &payload,
+        cert_valid_seconds,
+    )?;
+    Ok(end_der)
+}
+
+pub fn generate_inter_cert(
+    ca_key: &[u8],
+    inter_pub_key: &[u8],
+    cert_valid_seconds: i64,
+) -> Result<Vec<u8>> {
+    let ca_key = load_pem_key_from_bytes(ca_key)?;
+    let ca_kp = EcdsaKeyPair::from_bytes(&ca_key.0, &ECDSA_P384_SHA384_ASN1_SIGNING)?;
+    assert!(
+        inter_pub_key[0] == 0x4,
+        "Missing uncompressed public key indicator."
+    );
+
+    let inter_der = sign_intermediate(
+        &ca_kp,
+        inter_pub_key,
+        "CK ECDSA CA",
+        "CK ECDSA level 2 intermediate",
+        cert_valid_seconds,
+    )?;
+    Ok(inter_der)
+}
+pub fn generate_key_pair() -> Result<(Vec<u8>, Vec<u8>)> {
+    let key_pair = EcdsaKeyPair::new()?;
+    let priv_key = key_pair.prv_key();
+    let pub_key = key_pair.pub_key();
+    Ok((pub_key.to_vec(), priv_key.to_vec()))
+}
+
+pub fn get_webpki_time() -> Result<webpki::Time> {
+    let time = webpki::Time::from_seconds_since_unix_epoch(
+        get_system_time_seconds()?
+            .try_into()
+            .map_err(|_| anyhow::anyhow!("time conversion failed"))?,
+    );
+    Ok(time)
+}
diff --git a/projects/web3/ConfidentialKlave/attestation/src/verify.rs b/projects/web3/ConfidentialKlave/attestation/src/verify.rs
new file mode 100644
index 0000000..3400d2d
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/attestation/src/verify.rs
@@ -0,0 +1,109 @@
+// 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 crate::{report_verifier::AttestationReport, utils::get_webpki_time};
+use anyhow::{anyhow, Result};
+use serde::{Deserialize, Serialize};
+use std::convert::TryFrom;
+
+#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+pub struct CertInfo {
+    pub issuer: String,
+    pub subject: String,
+}
+
+#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+pub struct AttestationCertChain {
+    pub end: Vec<u8>,
+    pub intermediate: Vec<u8>,
+    pub root: Vec<u8>,
+}
+
+type SignatureAlgorithms = &'static [&'static webpki::SignatureAlgorithm];
+static SUPPORTED_SIG_ALGS: SignatureAlgorithms = &[
+    &webpki::ECDSA_P256_SHA256,
+    &webpki::ECDSA_P256_SHA384,
+    &webpki::ECDSA_P384_SHA256,
+    &webpki::ECDSA_P384_SHA384,
+];
+
+/// Construct a AttestationReport from a X509 certificate and verify
+/// attestation report with the report_ca_cert which is from the attestation
+/// service provider.
+pub fn extract_report_from_cert(cert: &rustls::Certificate) -> Result<AttestationReport> {
+    use crate::cert::*;
+    let cert = &cert.0;
+    // Extract information for attestation from TLS certification.
+    let x509 = yasna::parse_der(cert, X509::load).unwrap();
+    let tbs_cert: <TbsCert as Asn1Ty>::ValueTy = x509.0;
+    let pub_key: <PubKey as Asn1Ty>::ValueTy = ((((((tbs_cert.1).1).1).1).1).1).0;
+    let _pub_k = (pub_key.1).0;
+    let cert_ext: <RaCertExt as Asn1Ty>::ValueTy = (((((((tbs_cert.1).1).1).1).1).1).1).0 .0;
+    let cert_ext_payload: Vec<u8> = (cert_ext.1).0;
+
+    // Convert to endorsed report
+    let report = serde_json::from_slice(&cert_ext_payload)?;
+    Ok(report)
+}
+
+/// extract issuer and subject from certificate
+pub fn extract_info_from_cert(cert: &rustls::Certificate) -> Result<CertInfo> {
+    use crate::cert::*;
+    let cert = &cert.0;
+    // Extract information from certificate
+    let x509 = yasna::parse_der(cert, X509::load).unwrap();
+    let tbs_cert: <TbsCert as Asn1Ty>::ValueTy = x509.0;
+    let issuer: <Issuer as Asn1Ty>::ValueTy = (((tbs_cert.1).1).1).0;
+    let issuer_payload = (((issuer.0).0).1).0;
+    let subject: <Subject as Asn1Ty>::ValueTy = (((((tbs_cert.1).1).1).1).1).0;
+    let subject_payload = (((subject.0).0).1).0;
+
+    Ok(CertInfo {
+        issuer: issuer_payload,
+        subject: subject_payload,
+    })
+}
+
+/// extract public key from certificate
+pub fn extract_pub_key_from_cert(cert: &rustls::Certificate) -> Result<Vec<u8>> {
+    use crate::cert::*;
+    let cert = &cert.0;
+    // Extract information from certificate
+    let x509 = yasna::parse_der(cert, X509::load).unwrap();
+    let tbs_cert: <TbsCert as Asn1Ty>::ValueTy = x509.0;
+    let pub_key: <PubKey as Asn1Ty>::ValueTy = ((((((tbs_cert.1).1).1).1).1).1).0;
+    let pub_k = (pub_key.1).0;
+
+    Ok(pub_k.to_bytes())
+}
+
+pub fn verify_cert_chain(acc: AttestationCertChain, ca_cert: &rustls::Certificate) -> Result<()> {
+    let end_cert = webpki::EndEntityCert::try_from(acc.end.as_slice())?;
+    let anchor = webpki::TrustAnchor::try_from_cert_der(ca_cert.0.as_ref())
+        .map_err(|_| anyhow!("invalid root cert"))?;
+    let trust_anchors = vec![anchor];
+
+    let chain = vec![&acc.intermediate[..], &acc.root[..]];
+    let time = get_webpki_time()?;
+    end_cert.verify_is_valid_tls_server_cert(
+        SUPPORTED_SIG_ALGS,
+        &webpki::TlsServerTrustAnchors(&trust_anchors),
+        &chain,
+        time,
+    )?;
+    Ok(())
+}
diff --git a/projects/web3/ConfidentialKlave/basic_utils/Cargo.toml b/projects/web3/ConfidentialKlave/basic_utils/Cargo.toml
new file mode 100644
index 0000000..1c29720
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/basic_utils/Cargo.toml
@@ -0,0 +1,35 @@
+# 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 = "basic_utils"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+sha3 = "0.10.6"
+serde = { version = "=1.0.163", features = ["derive"] }
+anyhow = "1.0"
+hex = "0.4"
+
+# cargo will try to find the optee-utee path on x86_64, disable them:
+[target.'cfg(target_os="optee")'.dependencies]
+optee-utee-sys = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee/optee-utee-sys"}
+optee-utee = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee"}
+
+[target.'cfg(target_os="optee")'.patch.crates-io]
+proc-macro2 = { git = "https://github.com/DemesneGH/proc-macro2.git"}
diff --git a/projects/web3/ConfidentialKlave/basic_utils/src/hash.rs b/projects/web3/ConfidentialKlave/basic_utils/src/hash.rs
new file mode 100644
index 0000000..9563280
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/basic_utils/src/hash.rs
@@ -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.
+
+use serde::Serialize;
+use sha3::{Digest, Keccak256};
+
+pub fn keccak_hash<T>(data: &T) -> String
+where
+    T: ?Sized + Serialize + AsRef<[u8]>,
+{
+    let mut hasher = Keccak256::new();
+    hasher.update(data);
+    let result = hasher.finalize();
+    hex::encode(result)
+}
+
+pub fn keccak_hash_to_bytes<T>(data: &T) -> [u8; 32]
+where
+    T: ?Sized + Serialize + AsRef<[u8]>,
+{
+    let mut hasher = Keccak256::new();
+    hasher.update(data);
+    hasher.finalize().into()
+}
diff --git a/projects/web3/ConfidentialKlave/basic_utils/src/lib.rs b/projects/web3/ConfidentialKlave/basic_utils/src/lib.rs
new file mode 100644
index 0000000..65d37f2
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/basic_utils/src/lib.rs
@@ -0,0 +1,26 @@
+// 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 random;
+pub use random::*;
+mod hash;
+pub use hash::*;
+mod time;
+pub use time::*;
+
+#[cfg(target_os = "optee")]
+pub use optee_utee::trace_println as println;
diff --git a/projects/web3/ConfidentialKlave/basic_utils/src/random.rs b/projects/web3/ConfidentialKlave/basic_utils/src/random.rs
new file mode 100644
index 0000000..1368851
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/basic_utils/src/random.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 anyhow::Result;
+
+#[cfg(target_os = "optee")]
+pub fn generate_random_bytes(len: usize) -> Result<Vec<u8>> {
+    use optee_utee::Random;
+    let mut random_bytes = vec![0u8; len];
+    Random::generate(random_bytes.as_mut() as _);
+    Ok(random_bytes)
+}
+
+#[cfg(not(target_os = "optee"))]
+pub fn generate_random_bytes(len: usize) -> Result<Vec<u8>> {
+    use std::io::Read;
+    let mut rng =
+        std::fs::File::open("/dev/urandom").map_err(|_| anyhow::anyhow!("cannot open /dev/urandom"))?;
+    let mut buf = vec![0u8; len];
+    rng.read_exact(&mut buf)?;
+    Ok(buf)
+}
diff --git a/projects/web3/ConfidentialKlave/basic_utils/src/time.rs b/projects/web3/ConfidentialKlave/basic_utils/src/time.rs
new file mode 100644
index 0000000..ae79487
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/basic_utils/src/time.rs
@@ -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.
+
+use anyhow::Result;
+
+#[cfg(not(target_os = "optee"))]
+pub fn get_system_time_seconds() -> Result<i64> {
+    let seconds = std::time::SystemTime::now()
+        .duration_since(std::time::SystemTime::UNIX_EPOCH)?
+        .as_secs() as i64;
+    Ok(seconds)
+}
+#[cfg(target_os = "optee")]
+pub fn get_system_time_seconds() -> Result<i64> {
+    let mut time = optee_utee::Time::new();
+    time.ree_time();
+    Ok(time.seconds as i64)
+}
diff --git a/projects/web3/ConfidentialKlave/constants/Cargo.toml b/projects/web3/ConfidentialKlave/constants/Cargo.toml
new file mode 100644
index 0000000..94e9b0b
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/constants/Cargo.toml
@@ -0,0 +1,30 @@
+# 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 = "constants"
+version = "0.2.0"
+authors = ["Teaclave Contributors <de...@teaclave.apache.org>"]
+license = "Apache-2.0"
+repository = "https://github.com/apache/incubator-teaclave-trustzone-sdk.git"
+description = "Constant values for TA build, e.g. UUID"
+edition = "2018"
+
+[dependencies]
+
+[build_dependencies]
+uuid = { version = "=1.3.0" }
\ No newline at end of file
diff --git a/projects/web3/ConfidentialKlave/constants/build.rs b/projects/web3/ConfidentialKlave/constants/build.rs
new file mode 100644
index 0000000..85ff108
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/constants/build.rs
@@ -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.
+
+use std::env;
+use std::fs;
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+
+const UUID_FILES: [&str; 1] = ["tls_uuid.txt"];
+
+fn main() {
+    for each_uuid_file in UUID_FILES {
+        let uuid = match fs::read_to_string("../".to_owned() + each_uuid_file) {
+            Ok(u) => u.trim().to_string(),
+            Err(_) => {
+                panic!("Cannot find {}", each_uuid_file);
+            }
+        };
+        let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
+        let mut buffer = File::create(out.join(each_uuid_file)).unwrap();
+        write!(buffer, "{}", uuid).unwrap();
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/constants/src/lib.rs b/projects/web3/ConfidentialKlave/constants/src/lib.rs
new file mode 100644
index 0000000..f8ad1fa
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/constants/src/lib.rs
@@ -0,0 +1,20 @@
+// 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 const OUTPUT_MAX_SIZE: usize = 5000;
+
+pub const TLS_UUID: &str = include_str!(concat!(env!("OUT_DIR"), "/tls_uuid.txt"));
diff --git a/projects/web3/ConfidentialKlave/core/Cargo.toml b/projects/web3/ConfidentialKlave/core/Cargo.toml
new file mode 100644
index 0000000..cb28b64
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/core/Cargo.toml
@@ -0,0 +1,57 @@
+# 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 = "core"
+version = "0.2.0"
+authors = ["Teaclave Contributors <de...@teaclave.apache.org>"]
+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"
+
+[features]
+ta_unit_test = []
+
+[dependencies]
+basic_utils = { path = "../basic_utils" }
+proto = { path = "../proto" }
+types = { path = "../types" }
+crypto = { path = "../crypto" }
+secure_storage = { path = "../secure_storage" }
+attestation = { path = "../attestation" }
+
+serde = "=1.0.163"
+serde_json = "1.0"
+hex = { version = "0.4", features = ["serde"] }
+anyhow = "1.0"
+rustls = { git = "https://github.com/DemesneGH/rustls-optee.git", branch = "0.21.0-optee", features = ["dangerous_configuration"]}
+lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
+bincode = "1.3.3"
+rustls-pemfile = "1.0"
+
+[target.'cfg(target_os="optee")'.dependencies]
+optee-utee-sys = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee/optee-utee-sys" }
+optee-utee = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee" }
+
+[patch.crates-io]
+proc-macro2 = { git = "https://github.com/DemesneGH/proc-macro2.git" }
+ring = { git = "https://github.com/DemesneGH/ring-optee.git", branch = "0.16.20-optee" }
+
+[patch."https://github.com/apache/incubator-teaclave-trustzone-sdk.git"]
+optee-utee = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee" }
+libc = { path = "../../incubator-teaclave-trustzone-sdk/rust/libc" }
\ No newline at end of file
diff --git a/projects/web3/ConfidentialKlave/core/src/device_cmd_handler.rs b/projects/web3/ConfidentialKlave/core/src/device_cmd_handler.rs
new file mode 100644
index 0000000..218ccf0
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/core/src/device_cmd_handler.rs
@@ -0,0 +1,175 @@
+// 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 crate::{
+    create_keypair, decrypt_data_by_rsa_keypair, encrypt_data_by_rsa_pubkey, verify_root_signature,
+    SecureStorageClient,
+};
+use anyhow::{bail, ensure, Result};
+use basic_utils::println;
+use proto::{
+    BackupWalletInput, BackupWalletOutput, InitBoardOutput, RestoreWalletInput, RestoreWalletOutput,
+};
+use secure_storage::{TeeKey, TeeKeyObject, TeeKeyUsage};
+use std::convert::TryInto;
+use types::share::{CkEncryptedPayload, DeviceID, TeeOnlineStatus, WalletID};
+use types::trust::{TeeDeviceInfo, TeeWallet, TeeWalletForBackup};
+
+pub fn init_board() -> Result<InitBoardOutput> {
+    let db_client = SecureStorageClient::init();
+    let signing_pubkey = create_keypair(&TeeKeyUsage::Signing, &db_client).unwrap();
+    let device_id: DeviceID = signing_pubkey.clone().into();
+    let tee_device_info = TeeDeviceInfo::new(device_id.clone());
+    db_client.put(&tee_device_info)?;
+
+    let backup_pubkey = create_keypair(&TeeKeyUsage::Backup, &db_client).unwrap();
+    let output = InitBoardOutput {
+        device_id,
+        signing_pubkey,
+        backup_pubkey,
+    };
+    Ok(output)
+}
+
+pub fn backup_wallet(input: BackupWalletInput) -> Result<BackupWalletOutput> {
+    // verify signature
+    ensure!(
+        input.signature.is_some(),
+        "[-] backup_wallet: signature is missing"
+    );
+    verify_root_signature(&input.serialize()?, &input.signature.unwrap())?;
+    println!("[+] backup_wallet: signature verified");
+
+    // load each wallet
+    let mut backup_wallets: Vec<TeeWalletForBackup> = vec![];
+    let db_client = SecureStorageClient::init();
+
+    // if input.target_wallets is empty, backup all wallets
+    let target_wallets = if input.target_wallets.is_empty() {
+        println!("[+] backup_wallet: target_wallets is empty, backing up all wallets");
+        let all_wallets: Vec<WalletID> = db_client
+            .list_entries::<WalletID, TeeWallet>()?
+            .keys()
+            .cloned()
+            .collect();
+        all_wallets
+    } else {
+        println!(
+            "[+] backup_wallet: target_wallets: {:?}",
+            input.target_wallets
+        );
+        input.target_wallets.iter().cloned().collect()
+    };
+
+    for wallet_id in &target_wallets {
+        println!("[+] backup_wallet: wallet_id: {:?}", wallet_id);
+        let wallet: TeeWalletForBackup = db_client.get::<_, TeeWallet>(wallet_id)?.try_into()?;
+        // print the checksum of each priv key for double check
+        println!(
+            "[+] backup_wallet: wallet_id: {:?}, priv_key checksum: {:?}",
+            wallet_id,
+            wallet.xpriv_checksum()
+        );
+        backup_wallets.push(wallet);
+    }
+
+    // encrypt serialized Vec<TeeWallet>
+    let plain_data = bincode::serialize(&backup_wallets)?;
+    println!("[+] backup_wallet: plain_data len: {:?}", plain_data.len());
+    let encrypted_payload: CkEncryptedPayload =
+        encrypt_data_by_rsa_pubkey(plain_data, input.backup_to_device_pubkey)?;
+    let output = BackupWalletOutput {
+        backup_to_device: input.backup_to_device_id,
+        wallet_ids: backup_wallets.iter().map(|w| w.id()).collect(),
+        encrypted: encrypted_payload,
+    };
+    Ok(output)
+}
+
+pub fn restore_wallet(input: RestoreWalletInput) -> Result<RestoreWalletOutput> {
+    // check destination device
+    let db_client = SecureStorageClient::init();
+    let tee_device_info: TeeDeviceInfo =
+        db_client.get::<_, TeeDeviceInfo>(&"tee_device_info".to_string())?;
+    ensure!(
+        tee_device_info.device_id == input.backup_to_device,
+        "[-] restore_wallet: destination device mismatch: current {:?} != input {:?}",
+        tee_device_info.device_id,
+        input.backup_to_device
+    );
+
+    // load device backup key
+    let backup_key = db_client.get::<String, TeeKey>(&TeeKeyUsage::Backup.into())?;
+    let rsa_keypair = match backup_key.object() {
+        TeeKeyObject::RsaKeyPair(rsa_keypair) => rsa_keypair,
+        _ => bail!("[-] restore_wallet: invalid key object"),
+    };
+    let decrypted_data = decrypt_data_by_rsa_keypair(input.encrypted, rsa_keypair)?;
+    let backup_wallets: Vec<TeeWalletForBackup> = bincode::deserialize(&decrypted_data)?;
+    let restore_wallet_ids = backup_wallets.iter().map(|w| w.id()).collect();
+    // check if wallet ids same as input
+    ensure!(
+        restore_wallet_ids == input.wallet_ids,
+        "[-] restore_wallet: wallet id mismatch"
+    );
+
+    // restore each wallet
+    for wallet in backup_wallets {
+        println!(
+            "[+] restore_wallet: wallet_id: {:?}, priv_key checksum: {:?}",
+            wallet.id(),
+            wallet.xpriv_checksum()
+        );
+        let mut tee_wallet: TeeWallet = wallet.try_into()?;
+        db_client.put::<_, TeeWallet>(&tee_wallet)?;
+
+        let all_account_xpriv = tee_wallet.derive_all_xpriv_for_restore()?;
+        for account_xpriv in all_account_xpriv {
+            db_client.put(&account_xpriv)?;
+        }
+        println!("[-] StateManager::init(): restore all AccountXpriv");
+
+        print!("Restored wallet: {:?}", tee_wallet.id());
+    }
+
+    let output = RestoreWalletOutput {
+        wallet_ids: restore_wallet_ids,
+    };
+    Ok(output)
+}
+
+pub fn clear_wallet_storage() -> Result<()> {
+    let db_client = SecureStorageClient::init();
+    // list all TeeWallet
+    let wallet_ids: Vec<WalletID> = db_client
+        .list_entries::<WalletID, TeeWallet>()?
+        .keys()
+        .cloned()
+        .collect();
+    println!("[+] clear_wallet_storage: wallet_ids: {:?}", wallet_ids);
+    // delete all TeeWallet
+    for wallet_id in wallet_ids {
+        db_client.delete_entry::<WalletID, TeeWallet>(&wallet_id)?;
+    }
+    println!("[+] clear_wallet_storage: all wallets deleted");
+    // update TeeOnlineStatus
+    let tee_online_status = TeeOnlineStatus::WaitingForSync;
+    db_client.put(&tee_online_status)?;
+    println!("[+] clear_wallet_storage: TeeOnlineStatus updated to WaitingForSync");
+
+    Ok(())
+}
diff --git a/projects/web3/ConfidentialKlave/core/src/key_utils.rs b/projects/web3/ConfidentialKlave/core/src/key_utils.rs
new file mode 100644
index 0000000..f2fbde6
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/core/src/key_utils.rs
@@ -0,0 +1,137 @@
+// 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::convert::{TryFrom, TryInto};
+
+use crate::{secure_storage_client::SecureStorageClient, ROOT_PUBKEY};
+use anyhow::{bail, Result};
+use basic_utils::println;
+use crypto::{verify_signature_p384, AesGcm128Key, EcdsaKeyPair};
+use secure_storage::{
+    EcdsaKeyPairBytes, RsaKeyObject, RsaKeyPair, RsaPublicKey, TeeKey, TeeKeyObject, TeeKeyUsage,
+};
+use types::share::{CkEncryptedPayload, CkPublicKey, CkSignature};
+
+const RSA_KEY_SIZE: usize = 2048;
+
+pub fn create_keypair(usage: &TeeKeyUsage, db_client: &SecureStorageClient) -> Result<CkPublicKey> {
+    if let Ok(tee_key) = db_client.get::<String, TeeKey>(&usage.into()) {
+        println!("[-] create_keypair: keypair already exists, skipping creation");
+        return tee_key.try_into();
+    }
+    match usage {
+        TeeKeyUsage::Backup => {
+            println!("[+] create_keypair: creating backup keypair...");
+            let (key, pub_key) = create_backup_keypair(RSA_KEY_SIZE)?;
+            db_client.put(&key)?;
+            Ok(pub_key)
+        }
+        TeeKeyUsage::Signing => {
+            println!("[+] create_keypair: creating signing keypair...");
+            let (key, pub_key) = create_signing_keypair()?;
+            db_client.put(&key)?;
+            Ok(pub_key)
+        }
+    }
+}
+
+fn create_backup_keypair(key_size: usize) -> Result<(TeeKey, CkPublicKey)> {
+    let mut kp_object = RsaKeyObject::allocate_keypair_object(key_size)?;
+    kp_object.generate_key()?;
+    let kp: RsaKeyPair = kp_object.try_into()?;
+    let pubkey = kp.export_public_key()?;
+    println!("[+] create_rsa_keypair: pubkey: {:?}", &pubkey);
+    let key = TeeKey::new(TeeKeyUsage::Backup, TeeKeyObject::RsaKeyPair(kp))?;
+    Ok((key, pubkey))
+}
+
+fn create_signing_keypair() -> Result<(TeeKey, CkPublicKey)> {
+    let ecdsa_keypair = EcdsaKeyPair::new()?;
+    let key = TeeKey::new(
+        TeeKeyUsage::Signing,
+        TeeKeyObject::EcdsaKeyPair(EcdsaKeyPairBytes {
+            pkcs8_bytes: ecdsa_keypair.prv_key().to_vec(),
+        }),
+    )?;
+    Ok((key, CkPublicKey::from(ecdsa_keypair.pub_key().to_vec())))
+}
+
+pub fn load_signing_keypair(db_client: &SecureStorageClient) -> Result<EcdsaKeyPair> {
+    let key: TeeKey = db_client.get::<String, TeeKey>(&TeeKeyUsage::Signing.into())?;
+    match key.object() {
+        TeeKeyObject::EcdsaKeyPair(ecdsa_key_pair_bytes) => ecdsa_key_pair_bytes.try_into(),
+        _ => bail!("[-] load_inter_keypair_from_secure_storage: invalid key object"),
+    }
+}
+
+pub fn load_backup_keypair(db_client: &SecureStorageClient) -> Result<RsaKeyPair> {
+    let key: TeeKey = db_client.get::<String, TeeKey>(&TeeKeyUsage::Backup.into())?;
+    match key.object() {
+        TeeKeyObject::RsaKeyPair(rsa_key_pair) => Ok(rsa_key_pair),
+        _ => bail!("[-] load_backup_keypair_from_secure_storage: invalid key object"),
+    }
+}
+
+pub fn verify_root_signature(data: &[u8], signature: &CkSignature) -> Result<()> {
+    verify_signature_p384(ROOT_PUBKEY, data, signature.as_bytes())
+}
+
+pub fn encrypt_data_by_rsa_pubkey(
+    data: Vec<u8>,
+    pubkey: CkPublicKey,
+) -> Result<CkEncryptedPayload> {
+    // generate random data key
+    let data_key = AesGcm128Key::generate()?;
+    let mut data_in_out = data.clone();
+    println!("[+] generate data_key");
+    // encrypt data with data key
+    let _cmac = data_key.encrypt(&mut data_in_out)?;
+    println!("[+] encrypted_data_len: {:?}", &data_in_out.len());
+    let serialized_data_key = bincode::serialize(&data_key)?;
+
+    // use rsa public key to encrypt data key
+    let public_key = RsaPublicKey::try_from(pubkey)?;
+    // allocate public key object for OP-TEE
+    let mut rsa_public_key_object: RsaKeyObject = public_key.try_into()?;
+    let encrypted_data_key = rsa_public_key_object.encrypt(&serialized_data_key)?;
+    println!(
+        "[+] encrypted_data_key len : {:?}",
+        &encrypted_data_key.len()
+    );
+
+    Ok(CkEncryptedPayload {
+        encrypted_data: data_in_out,
+        encrypted_data_key,
+    })
+}
+
+pub fn decrypt_data_by_rsa_keypair(
+    encrypted_payload: CkEncryptedPayload,
+    keypair: RsaKeyPair,
+) -> Result<Vec<u8>> {
+    let mut key_object: RsaKeyObject = keypair.try_into()?;
+    // decrypt data key
+    let serialized_data_key = key_object.decrypt(&encrypted_payload.encrypted_data_key)?;
+    let data_key: AesGcm128Key = bincode::deserialize(&serialized_data_key)?;
+    println!("[+] decrypt data_key");
+
+    // decrypt data
+    let mut data_in_out = encrypted_payload.encrypted_data.clone();
+    let _cmac = data_key.decrypt(&mut data_in_out)?;
+    println!("[+] decrypted_data_len: {:?}", &data_in_out.len());
+    Ok(data_in_out)
+}
diff --git a/projects/web3/ConfidentialKlave/core/src/lib.rs b/projects/web3/ConfidentialKlave/core/src/lib.rs
new file mode 100644
index 0000000..de059ed
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/core/src/lib.rs
@@ -0,0 +1,174 @@
+// 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 key_utils;
+pub use key_utils::*;
+mod secure_storage_client;
+pub use secure_storage_client::SecureStorageClient;
+mod state_manager;
+pub use state_manager::StateManager;
+mod tls;
+pub use tls::handler_context::{InterCertConfig, TlsContext};
+pub use tls::session_manager::TlsSessionManager;
+mod user;
+pub use user::*;
+mod user_registry;
+pub use user_registry::UserRegistry;
+mod device_cmd_handler;
+pub use device_cmd_handler::*;
+
+const ROOT_PUBKEY: &[u8] = include_bytes!("../../pubkeys/ca.pub");
+const SYSTEM_PUBKEY: &[u8] = include_bytes!("../../pubkeys/system.pub");
+
+#[cfg(feature = "ta_unit_test")]
+pub mod tests {
+    use super::*;
+    use test_utils::check_all_passed;
+
+    pub fn run_tests() -> bool {
+        trace_println!("[+] core::run_tests");
+        check_all_passed!(
+            wallet::tests::run_tests(),
+            eth::tests::run_tests(),
+            twallet::tests::run_tests()
+        )
+    }
+}
+
+#[cfg(not(target_os = "optee"))]
+pub mod tests {
+    use std::{convert::TryInto, sync::RwLock};
+
+    use anyhow::{anyhow, ensure, Result};
+    use proto::{
+        CreateTransactionInput, CreateTransactionOutput, InitBoardOutput, SyncWithTeeInput,
+        SyncWithTeeOutput, TaCommand, TlsCommandRequest,
+    };
+    use secure_storage::TeeKeyUsage;
+    use types::share::{
+        CkHash, DeviceID, EthTransaction, MultiChainTransaction, TeeConfig, WalletID,
+    };
+
+    use crate::{
+        key_utils::create_keypair, secure_storage_client::SecureStorageClient,
+        state_manager::StateManager, tls::handler_context::TlsContext, user::TxOperator,
+        user_registry::UserRegistry, verify_root_signature, ROOT_PUBKEY, SYSTEM_PUBKEY,
+    };
+    use lazy_static::lazy_static;
+    lazy_static! {
+        static ref STATE_MANAGER: RwLock<StateManager> = RwLock::new(StateManager::init().unwrap());
+        static ref USER_REGISTRY: RwLock<UserRegistry> = RwLock::new(UserRegistry::init());
+    }
+
+    #[test]
+    fn test_ta_workflow() {
+        // init board: triggered by local CA
+        let db_client = SecureStorageClient::init();
+        let signing_pubkey = create_keypair(&TeeKeyUsage::Signing, &db_client).unwrap();
+        let device_id: DeviceID = signing_pubkey.clone().into();
+        let backup_pubkey = create_keypair(&TeeKeyUsage::Backup, &db_client).unwrap();
+        let output = InitBoardOutput {
+            device_id,
+            signing_pubkey,
+            backup_pubkey,
+        };
+        // register device on authority and get the tee certificate
+
+        // start server: triggered by local CA
+        // init TlsSessionManager with the tee certificate
+        // start listening
+
+        // test sync to tee
+        // prepare input out of tee
+        let input = SyncWithTeeInput {
+            signed_config: TeeConfig {
+                user_registry: Vec::new(),
+                wallets: vec![],
+                config_version: 0,
+                signature: None,
+            },
+        };
+        let request = TlsCommandRequest {
+            command: TaCommand::SyncWithTee,
+            request: bincode::serialize(&input).unwrap(),
+        };
+        // process cmd in tee
+        let mocked_tls_context = TlsContext::new(SYSTEM_PUBKEY.to_vec());
+        process_tls_cmd(mocked_tls_context, request).unwrap();
+
+        // test create transaction
+        // prepare input out of tee
+        let tx = MultiChainTransaction::Eth(EthTransaction {
+            chain: 1,
+            nonce: None,
+            gas_price: 0,
+            gas: 0,
+            from_wallet: WalletID::new().unwrap(),
+            from_account: [0u8; 20].into(),
+            to: [0u8; 20],
+            value: 0,
+            data: vec![],
+        });
+        let input = CreateTransactionInput { tx };
+        let request = TlsCommandRequest {
+            command: TaCommand::CreateTransaction,
+            request: bincode::serialize(&input).unwrap(),
+        };
+        // process cmd in tee
+        let mocked_tls_context = TlsContext::new(SYSTEM_PUBKEY.clone().to_vec());
+        process_tls_cmd(mocked_tls_context, request).unwrap();
+    }
+
+    fn process_tls_cmd(context: TlsContext, request: TlsCommandRequest) -> Result<()> {
+        let mut state_manager = STATE_MANAGER.write().unwrap();
+        let mut user_registry = USER_REGISTRY.write().unwrap();
+
+        match request.command {
+            TaCommand::SyncWithTee => {
+                let input: SyncWithTeeInput = bincode::deserialize(&request.request)?;
+                let config = input.signed_config;
+                ensure!(config.signature.is_some(), "signature is missing");
+                verify_root_signature(&config.serialize()?, &config.signature.unwrap())?;
+
+                user_registry.set_users(config.user_registry);
+                let (latest_wallets, config_version) =
+                    state_manager.set_wallets(config.wallets, config.config_version)?;
+                let output = SyncWithTeeOutput {
+                    latest_wallets,
+                    config_version,
+                };
+            }
+            TaCommand::CreateTransaction => {
+                let input: CreateTransactionInput = bincode::deserialize(&request.request)?;
+                // verify tls client
+                let client_pubkey_hash: CkHash = context.try_into()?;
+                let _operator: TxOperator = user_registry.auth_as_role(&client_pubkey_hash)?;
+                // create tx
+                let (tx_id, latest_approval_chain) =
+                    state_manager.create_tx(&_operator, input.tx)?;
+                let output = CreateTransactionOutput {
+                    tx_id,
+                    latest_approval_chain,
+                };
+            }
+            _ => {
+                unimplemented!()
+            }
+        };
+        Ok(())
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/core/src/secure_storage_client.rs b/projects/web3/ConfidentialKlave/core/src/secure_storage_client.rs
new file mode 100644
index 0000000..f64e562
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/core/src/secure_storage_client.rs
@@ -0,0 +1,93 @@
+// 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::Result;
+use secure_storage::SecureStorageDb;
+use std::{
+    collections::HashMap,
+    convert::TryFrom,
+    hash::Hash,
+    sync::{Arc, RwLock},
+};
+use types::Storable;
+
+pub struct SecureStorageClient {
+    db: Arc<RwLock<SecureStorageDb>>,
+}
+
+impl SecureStorageClient {
+    pub fn init() -> Self {
+        Self {
+            db: Arc::new(RwLock::new(
+                SecureStorageDb::open("db".to_string()).unwrap(),
+            )),
+        }
+    }
+
+    pub fn get<K, V>(&self, key: &K) -> Result<V>
+    where
+        K: TryFrom<String> + Into<String> + Clone,
+        V: Storable<K>,
+    {
+        let key: String = (*key).clone().into();
+        let storage_key = V::concat_key(&key);
+        let value = self.db.read().unwrap().get(&storage_key)?;
+        // if entry is not found, return Err
+        Ok(bincode::deserialize(&value)?)
+    }
+
+    pub fn put<K, V>(&self, value: &V) -> Result<()>
+    where
+        K: TryFrom<String> + Into<String> + Clone,
+        V: Storable<K>,
+    {
+        let key = value.storage_key();
+        let value = bincode::serialize(value)?;
+        self.db.write().unwrap().put(key, value)?;
+        Ok(())
+    }
+
+    pub fn delete_entry<K, V>(&self, key: &K) -> Result<()>
+    where
+        K: TryFrom<String> + Into<String> + Clone,
+        V: Storable<K>,
+    {
+        let key: String = (*key).clone().into();
+        let storage_key = V::concat_key(&key);
+        self.db.write().unwrap().delete(storage_key)?;
+        Ok(())
+    }
+
+    pub fn list_entries<K, V>(&self) -> Result<HashMap<K, V>>
+    where
+        K: TryFrom<String> + Into<String> + Clone + Eq + Hash,
+        V: Storable<K>,
+    {
+        let map = self
+            .db
+            .read()
+            .unwrap()
+            .list_entries_with_prefix(V::table_name())?;
+        let mut result = HashMap::new();
+        for (_k, v) in map {
+            let value: V = bincode::deserialize(&v)?;
+            let key = value.unique_id();
+            result.insert(key, value);
+        }
+        Ok(result)
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/core/src/state_manager.rs b/projects/web3/ConfidentialKlave/core/src/state_manager.rs
new file mode 100644
index 0000000..0e64ae2
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/core/src/state_manager.rs
@@ -0,0 +1,367 @@
+// 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 crate::secure_storage_client::SecureStorageClient;
+use crate::user::{Approver, System, TaUser, TxOperator};
+use basic_utils::println;
+use proto::{LatestWalletInfo, TaTransactionDetail};
+use types::share::{
+    ChainType, CkSignature, DeviceID, LegacyTransaction, MultiChainAccount, MultiChainTransaction,
+    Secp256k1, TaApprovalChain, TaWalletInfo, TeeOnlineStatus, TransactionID, WalletID,
+    SUPPORTED_CHAIN_TYPES,
+};
+use types::trust::{
+    AccountXpriv, BtcAccountXpriv, CkChangeInfo, CkRecipientInfo, CkUtxoInfo, EthAccountXpriv,
+    TeeDeviceInfo, TeeTransaction, TeeWallet, WalletSecret,
+};
+
+use anyhow::{bail, ensure, Result};
+use std::collections::HashSet;
+use std::convert::TryInto;
+
+// manage all commands through TLS
+pub struct StateManager {
+    db_handler: SecureStorageClient,
+    device_id: DeviceID,
+    tee_status: TeeOnlineStatus,
+    existing_wallet_ids: HashSet<WalletID>,
+}
+
+impl StateManager {
+    pub fn init() -> Result<Self> {
+        let db_handler = SecureStorageClient::init();
+        let device_info = match db_handler
+            .get::<String, TeeDeviceInfo>(&"tee_device_info".to_string())
+        {
+            Ok(device_info) => device_info,
+            Err(_) => {
+                bail!("[-] StateManager::init(): error: device info not found, device has to be initialized first")
+            }
+        };
+        let tee_status = match db_handler.get::<String, TeeOnlineStatus>(&"tee_status".to_string())
+        {
+            Ok(tee_status) => tee_status,
+            Err(_) => TeeOnlineStatus::init(),
+        };
+        let existing_wallet_ids = db_handler
+            .list_entries::<WalletID, TeeWallet>()?
+            .keys()
+            .cloned()
+            .collect();
+
+        Ok(Self {
+            db_handler,
+            device_id: device_info.device_id,
+            tee_status,
+            existing_wallet_ids,
+        })
+    }
+
+    pub fn set_wallets(
+        &mut self,
+        wallets: Vec<TaWalletInfo>,
+        config_version: u64,
+    ) -> Result<(Vec<LatestWalletInfo>, u64)> {
+        // check if config_version is valid
+        if self.tee_status.is_running() {
+            ensure!(
+                config_version > self.tee_status.config_version()?,
+                "config_version should be greater than internal config_version"
+            );
+        }
+
+        let wallet_ids_from_input: HashSet<WalletID> =
+            wallets.iter().map(|w| w.wallet_id.clone()).collect();
+        // ensure wallet_ids_from_input contains all existing_wallet_ids
+        ensure!(
+            self.existing_wallet_ids.is_subset(&wallet_ids_from_input),
+            "wallets should contain all existing wallets"
+        );
+
+        for wallet_info in wallets {
+            let wallet_id = wallet_info.wallet_id.clone();
+            match self.load_wallet(&wallet_id) {
+                // if wallet exist, update
+                Ok(mut tee_wallet) => {
+                    println!(
+                        "[-] StateManager::set_wallets(): {:?} exist, update",
+                        wallet_id
+                    );
+                    tee_wallet.update_approval_chain(wallet_info.approval_chain)?;
+                    tee_wallet.update_authorized_operators(wallet_info.authorized_operators)?;
+                    self.db_handler.put(&tee_wallet)?;
+                }
+                Err(_) => {
+                    // if wallet doesn't exist, create
+                    println!(
+                        "[-] StateManager::set_wallets(): {:?} doesn't exist, create",
+                        wallet_id
+                    );
+                    let _wallet = self.create_wallet(wallet_info)?;
+                    self.existing_wallet_ids.insert(wallet_id);
+                }
+            }
+        }
+
+        self.tee_status.set_running(config_version);
+        let wallets = self.list_wallets()?;
+        Ok((wallets, config_version))
+    }
+
+    fn list_wallets(&self) -> Result<Vec<LatestWalletInfo>> {
+        let mut latest_wallets = Vec::new();
+        for wallet in self
+            .db_handler
+            .list_entries::<WalletID, TeeWallet>()?
+            .values()
+        {
+            let wallet_info = LatestWalletInfo {
+                wallet_id: wallet.id(),
+                approval_chain: wallet.approval_chain_basic().clone(),
+                authorized_operators: wallet.authorized_operators_basic().clone(),
+                accounts: wallet.accounts().clone(),
+            };
+            latest_wallets.push(wallet_info);
+        }
+        Ok(latest_wallets)
+    }
+
+    pub fn create_tx(
+        &self,
+        _operator: &TxOperator,
+        tx: MultiChainTransaction,
+    ) -> Result<(TransactionID, TaApprovalChain)> {
+        // check if tee is online
+        ensure!(self.tee_status.is_running(), "TEE service is not running");
+
+        let from_wallet = self
+            .db_handler
+            .get::<WalletID, TeeWallet>(tx.from_wallet())?;
+        ensure!(
+            from_wallet.has_account(tx.from_account()),
+            "account not found in wallet"
+        );
+        let tx_approval_chain: TaApprovalChain = from_wallet.approval_chain_basic().clone().into();
+        let tee_tx = TeeTransaction::new(
+            tx.hash()?,
+            tx.from_wallet().clone(),
+            tx_approval_chain.clone(),
+            from_wallet.authorized_operators_basic().clone(),
+        )?;
+        self.db_handler.put(&tee_tx)?;
+        Ok((tee_tx.id().clone(), tx_approval_chain))
+    }
+
+    pub fn approve_tx(
+        &self,
+        approver: &Approver,
+        tx_id: TransactionID,
+        current_approval_chain: &TaApprovalChain,
+    ) -> Result<TaApprovalChain> {
+        // check if tee is online
+        ensure!(self.tee_status.is_running(), "TEE service is not running");
+
+        let mut tee_tx = self
+            .db_handler
+            .get::<TransactionID, TeeTransaction>(&tx_id)?;
+        tee_tx.approve(approver.0.user_id(), current_approval_chain)?;
+        self.db_handler.put(&tee_tx)?;
+        Ok(tee_tx.get_approval_chain().clone())
+    }
+
+    pub fn reject_tx(
+        &self,
+        approver: &Approver,
+        tx_id: TransactionID,
+        current_approval_chain: &TaApprovalChain,
+    ) -> Result<TaApprovalChain> {
+        // check if tee is online
+        ensure!(self.tee_status.is_running(), "TEE service is not running");
+
+        let mut tee_tx = self
+            .db_handler
+            .get::<TransactionID, TeeTransaction>(&tx_id)?;
+        tee_tx.reject(approver.0.user_id(), current_approval_chain)?;
+        self.db_handler.put(&tee_tx)?;
+        Ok(tee_tx.get_approval_chain().clone())
+    }
+
+    pub fn recall_tx(
+        &self,
+        _operator: &TxOperator,
+        tx_id: &TransactionID,
+        current_approval_chain: &TaApprovalChain,
+    ) -> Result<()> {
+        // check if tee is online
+        ensure!(self.tee_status.is_running(), "TEE service is not running");
+
+        let tee_tx = self
+            .db_handler
+            .get::<TransactionID, TeeTransaction>(tx_id)?;
+        ensure!(
+            tee_tx.is_approval_chain_up_to_date(current_approval_chain),
+            "hash doesn't match"
+        );
+        self.db_handler
+            .delete_entry::<TransactionID, TeeTransaction>(tx_id)?;
+        println!("[-] StateManager::recall_tx(): {:?} recalled", tx_id);
+        Ok(())
+    }
+
+    pub fn sign_tx(
+        &self,
+        user: &TaUser,
+        tx_id: TransactionID,
+        external_tx: MultiChainTransaction,
+    ) -> Result<CkSignature> {
+        // user should be operator or system
+        ensure!(
+            user.is_tx_operator() || user.is_system(),
+            "user is not operator or system"
+        );
+        // check if tee is online
+        ensure!(self.tee_status.is_running(), "TEE service is not running");
+
+        let tee_tx = self
+            .db_handler
+            .get::<TransactionID, TeeTransaction>(&tx_id)?;
+        // tx should be ready for sign
+        ensure!(tee_tx.is_ready_for_sign(), "tx is not ready for sign");
+        // tx hash should match
+        ensure!(
+            tee_tx.hash_matches(&external_tx.hash()?),
+            "tx hash doesn't match"
+        );
+
+        let account_xpriv = self
+            .db_handler
+            .get::<_, AccountXpriv>(external_tx.from_account())?;
+        let signed_payload: CkSignature = self.process_sign(external_tx, account_xpriv)?;
+        println!("[-] StateManager::sign_tx(): {:?} signed", tx_id);
+        self.db_handler
+            .delete_entry::<TransactionID, TeeTransaction>(&tx_id)?;
+
+        Ok(signed_payload)
+    }
+
+    fn process_sign(
+        &self,
+        multichain_tx: MultiChainTransaction,
+        xpriv: AccountXpriv,
+    ) -> Result<CkSignature> {
+        if let MultiChainTransaction::Eth(tx) = multichain_tx {
+            let legacy_tx: LegacyTransaction = tx.try_into()?;
+            let signed_payload = EthAccountXpriv(xpriv).sign_tx(&legacy_tx)?;
+            Ok(signed_payload.into())
+        } else if let MultiChainTransaction::Btc(tx) = multichain_tx {
+            let secp = Secp256k1::new();
+            let xpub = xpriv.xpub(&secp);
+            let ck_utxos: Vec<CkUtxoInfo> = tx
+                .utxo_list
+                .into_iter()
+                .map(|utxo| CkUtxoInfo::try_from(&xpub, utxo, &secp))
+                .collect::<Result<Vec<CkUtxoInfo>>>()?;
+            let ck_recipients: Vec<CkRecipientInfo> = tx
+                .recipient_list
+                .into_iter()
+                .map(|recipient| recipient.try_into())
+                .collect::<Result<Vec<CkRecipientInfo>>>()?;
+            let ck_change = CkChangeInfo::try_from(&xpub, tx.change, &secp)?;
+
+            BtcAccountXpriv(xpriv).sign_tx(ck_utxos, ck_recipients, ck_change, &secp)
+        } else {
+            bail!("unsupported chain type")
+        }
+    }
+
+    pub fn list_tx(&self, _system: &System) -> Result<Vec<TaTransactionDetail>> {
+        // check if tee is online
+        ensure!(self.tee_status.is_running(), "TEE service is not running");
+
+        let all_tee_tx = self
+            .db_handler
+            .list_entries::<TransactionID, TeeTransaction>()?;
+        let mut tx_details = Vec::new();
+        for tee_tx in all_tee_tx.values() {
+            let tx_detail = TaTransactionDetail {
+                tx_id: tee_tx.id().clone(),
+                tx_hash: tee_tx.hash().clone(),
+                from_wallet: tee_tx.from_wallet().clone(),
+                latest_approval_chain: tee_tx.get_approval_chain().clone(),
+                authorized_operators: tee_tx.authorized_operators().clone(),
+            };
+            tx_details.push(tx_detail);
+        }
+        Ok(tx_details)
+    }
+
+    pub fn get_tee_status(&self) -> TeeOnlineStatus {
+        self.tee_status.clone()
+    }
+
+    pub fn get_device_id(&self) -> DeviceID {
+        self.device_id.clone()
+    }
+
+    fn create_wallet(&mut self, wallet_info: TaWalletInfo) -> Result<TeeWallet> {
+        let wallet_secret = WalletSecret::new(wallet_info.wallet_id.clone())?;
+        let mut tee_wallet = TeeWallet::new(
+            wallet_info.wallet_id.clone(),
+            wallet_info.approval_chain,
+            wallet_info.authorized_operators,
+            &wallet_secret,
+        )?;
+        let accounts = self.add_account_inner(&mut tee_wallet, &SUPPORTED_CHAIN_TYPES)?;
+        println!(
+            "[-] StateManager::create_wallet(): {:?} created with accounts: {:?}",
+            wallet_info.wallet_id, accounts
+        );
+        self.db_handler.put(&tee_wallet)?;
+        Ok(tee_wallet)
+    }
+
+    fn load_wallet(&self, wallet_id: &WalletID) -> Result<TeeWallet> {
+        self.db_handler.get::<WalletID, TeeWallet>(wallet_id)
+    }
+
+    fn _wallet_exist(&self, wallet_id: &WalletID) -> bool {
+        self.existing_wallet_ids.contains(wallet_id)
+    }
+
+    fn add_account_inner(
+        &mut self,
+        tee_wallet: &mut TeeWallet,
+        chain_types: &[ChainType],
+    ) -> Result<Vec<MultiChainAccount>> {
+        let mut multichain_accounts = Vec::new();
+        for chain_type in chain_types {
+            match chain_type {
+                ChainType::Eth => {
+                    let (account_xpriv, account_xpub) = tee_wallet.add_eth_account()?;
+                    self.db_handler.put(&account_xpriv.0)?;
+                    multichain_accounts.push(MultiChainAccount::Eth(account_xpub));
+                }
+                ChainType::Btc => {
+                    let (account_xpriv, account_xpub) = tee_wallet.add_btc_account()?;
+                    self.db_handler.put(&account_xpriv.0)?;
+                    multichain_accounts.push(MultiChainAccount::Btc(account_xpub));
+                }
+            }
+        }
+        Ok(multichain_accounts)
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/core/src/tls/config_builder.rs b/projects/web3/ConfidentialKlave/core/src/tls/config_builder.rs
new file mode 100644
index 0000000..28fbdab
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/core/src/tls/config_builder.rs
@@ -0,0 +1,111 @@
+// 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 attestation::report_verifier::{AttestationReport, ClientVerifier};
+use attestation::sign::sign_csr_with_extension;
+use basic_utils::println;
+use crypto::EcdsaKeyPair;
+use types::share::CkHash;
+
+use crate::tls::handler_context::InterCertConfig;
+use anyhow::Result;
+use rustls::{ServerConfig, WantsVerifier};
+use std::collections::HashSet;
+use std::io::BufReader;
+use std::sync::Arc;
+
+/// Validation days of cert for TLS connection.
+const CERT_VALID_DAYS: i64 = 365i64;
+const CERT_VALID_SECONDS: i64 = CERT_VALID_DAYS * 24 * 60 * 60;
+
+// this verifier is not used in the current version
+fn client_verifier(_pubkey: &[u8]) -> bool {
+    // println!("[+] ClientVerifier: pub_key: {:?}", pubkey);
+    true
+}
+
+pub fn include_ca_cert() -> Vec<rustls::Certificate> {
+    let bytes = include_bytes!("../../../pubkeys/ca.cert").to_vec();
+    let cursor = std::io::Cursor::new(bytes);
+    let mut reader = BufReader::new(cursor);
+    let certs = rustls_pemfile::certs(&mut reader)
+        .map_err(|_| anyhow::anyhow!("pemfile::include_ca_cert failed"));
+    certs
+        .unwrap()
+        .iter()
+        .map(|v| rustls::Certificate(v.clone()))
+        .collect()
+}
+
+pub struct TlsServerConfigBuilder {
+    pub config_builder: rustls::ConfigBuilder<ServerConfig, WantsVerifier>,
+    pub cert_chain: Vec<rustls::Certificate>,
+    pub end_key: rustls::PrivateKey,
+}
+impl TlsServerConfigBuilder {
+    pub fn new(inter_cert_config: InterCertConfig, inter_keypair: EcdsaKeyPair) -> Result<Self> {
+        let ca_cert = include_ca_cert();
+        let attestation_report = AttestationReport {
+            measurement: vec![0u8],
+        };
+        let report_payload = serde_json::to_vec(&attestation_report).unwrap();
+
+        let end_kp = EcdsaKeyPair::new()?;
+        let end_pub = end_kp.pub_key();
+        let end_key = rustls::PrivateKey(end_kp.prv_key().to_vec());
+        assert_eq!(
+            end_pub[0], 0x4,
+            "Missing uncompressed public key indicator."
+        );
+
+        let end_der = sign_csr_with_extension(
+            &inter_keypair,
+            end_pub,
+            &inter_cert_config.issuer,  //"CK ECDSA level 2 intermediate",
+            &inter_cert_config.subject, // "testserver.com",
+            [&inter_cert_config.subject, "localhost"], // ["testserver.com", "localhost"],
+            &report_payload,
+            CERT_VALID_SECONDS,
+        )?;
+        let end_cert = rustls::Certificate(end_der.clone());
+        println!("[+] ServerConfigBuilder::new(): sign end cert finished");
+        let cert_chain = vec![end_cert, inter_cert_config.inter_cert, ca_cert[0].clone()];
+        let config_builder = rustls::ServerConfig::builder().with_safe_defaults();
+        println!("[+] ServerConfigBuilder::new(): setup server config finished");
+
+        Ok(Self {
+            config_builder,
+            cert_chain,
+            end_key,
+        })
+    }
+    pub fn build_config_with_accepted_pubkey_hash(
+        &self,
+        accepted_pubkey_hash: HashSet<CkHash>,
+    ) -> Result<Arc<ServerConfig>> {
+        let hash: HashSet<Vec<u8>> = accepted_pubkey_hash.iter().map(|h| h.0.to_vec()).collect();
+        let client_auth = Arc::new(ClientVerifier::new(hash, client_verifier));
+        let config_builder = self.config_builder.clone();
+        let mut config = config_builder
+            .with_client_cert_verifier(client_auth)
+            .with_single_cert(self.cert_chain.clone(), self.end_key.clone())
+            .unwrap();
+        config.session_storage = Arc::new(rustls::server::NoServerSessionStorage {});
+
+        Ok(Arc::new(config))
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/core/src/tls/handler_context.rs b/projects/web3/ConfidentialKlave/core/src/tls/handler_context.rs
new file mode 100644
index 0000000..53265f7
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/core/src/tls/handler_context.rs
@@ -0,0 +1,55 @@
+// 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::Result;
+use std::convert::TryFrom;
+use types::share::CkHash;
+
+// send to processing cmds in core/src/lib.rs
+pub struct TlsContext {
+    client_pubkey: Vec<u8>,
+}
+impl TlsContext {
+    pub fn new(client_pubkey: Vec<u8>) -> Self {
+        TlsContext { client_pubkey }
+    }
+    pub fn client_pubkey(&self) -> Vec<u8> {
+        self.client_pubkey.clone()
+    }
+}
+impl TryFrom<TlsContext> for CkHash {
+    type Error = anyhow::Error;
+    fn try_from(ctx: TlsContext) -> Result<Self> {
+        CkHash::new(ctx.client_pubkey)
+    }
+}
+
+// config for tee sign the end cert
+pub struct InterCertConfig {
+    pub inter_cert: rustls::Certificate,
+    pub issuer: String,
+    pub subject: String,
+}
+impl InterCertConfig {
+    pub fn new(inter_cert: Vec<u8>, issuer: String, subject: String) -> Self {
+        InterCertConfig {
+            inter_cert: rustls::Certificate(inter_cert),
+            issuer,
+            subject,
+        }
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/core/src/tls/mod.rs b/projects/web3/ConfidentialKlave/core/src/tls/mod.rs
new file mode 100644
index 0000000..23ec300
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/core/src/tls/mod.rs
@@ -0,0 +1,21 @@
+// 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 config_builder;
+pub mod handler_context;
+pub mod session;
+pub mod session_manager;
diff --git a/projects/web3/ConfidentialKlave/core/src/tls/session.rs b/projects/web3/ConfidentialKlave/core/src/tls/session.rs
new file mode 100644
index 0000000..1fa157a
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/core/src/tls/session.rs
@@ -0,0 +1,113 @@
+// 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 basic_utils::println;
+use std::convert::TryFrom;
+use std::io::Cursor;
+use std::io::{Read, Write};
+use std::sync::Arc;
+use std::vec;
+
+use crate::tls::handler_context::TlsContext;
+use attestation::verify::extract_pub_key_from_cert;
+
+pub struct TlsSession {
+    connection: rustls::ServerConnection,
+    id: u32,
+    client_pubkey_cache: Vec<u8>,
+}
+impl TlsSession {
+    pub fn new(id: u32, server_config: Arc<rustls::ServerConfig>) -> Result<Self> {
+        let connection = rustls::ServerConnection::new(server_config)?;
+        println!("[+] TlsSession: new() id: {}", id);
+
+        Ok(TlsSession {
+            connection,
+            id,
+            client_pubkey_cache: Vec::new(),
+        })
+    }
+    pub fn connection(&mut self) -> &mut rustls::ServerConnection {
+        &mut self.connection
+    }
+    pub fn client_pubkey_cache(&mut self) -> Result<Vec<u8>> {
+        if self.client_pubkey_cache.is_empty() {
+            self.save_client_pubkey()?;
+        }
+        Ok(self.client_pubkey_cache.clone())
+    }
+    pub fn id(&self) -> u32 {
+        self.id
+    }
+
+    pub fn read_request(&mut self, buf: &[u8]) -> Result<Vec<u8>> {
+        let mut rd = Cursor::new(buf);
+        let tls_connection = self.connection();
+        let _rc = tls_connection.read_tls(&mut rd)?;
+        let io_state = tls_connection.process_new_packets()?;
+
+        // Read and process all available plaintext.
+        let bytes_to_read = io_state.plaintext_bytes_to_read();
+        if bytes_to_read > 0 {
+            println!(
+                "[+] read_request: plaintext_bytes_to_read: {:?}",
+                bytes_to_read
+            );
+            let mut outbuf = vec![0u8; bytes_to_read];
+            tls_connection.reader().read_exact(&mut outbuf)?;
+            // save client pubkey
+            self.save_client_pubkey()?;
+            return Ok(outbuf);
+        }
+        Ok(Vec::new())
+    }
+    pub fn write_response(&mut self, buf: &[u8]) -> Result<()> {
+        let tls_connection = self.connection();
+        tls_connection.writer().write_all(buf)?;
+        Ok(())
+    }
+    pub fn return_response_to_client(&mut self, buf: &mut [u8]) -> Result<usize> {
+        let tls_connection = self.connection();
+        let mut wr = Cursor::new(buf);
+        let mut rc = 0;
+        while tls_connection.wants_write() {
+            rc += tls_connection.write_tls(&mut wr)?;
+        }
+        println!("[+] return_response_to_client: rc: {:?}", rc);
+        Ok(rc)
+    }
+
+    fn save_client_pubkey(&mut self) -> Result<()> {
+        let cert = self
+            .connection
+            .peer_certificates()
+            .ok_or_else(|| anyhow!("no peer cert"))?[0]
+            .clone();
+        let client_pubkey = extract_pub_key_from_cert(&cert)?;
+        println!("[+] TlsSession: save_client_pubkey as: {:?}", client_pubkey);
+        self.client_pubkey_cache = client_pubkey;
+        Ok(())
+    }
+}
+impl TryFrom<&mut TlsSession> for TlsContext {
+    type Error = anyhow::Error;
+    fn try_from(tls_session: &mut TlsSession) -> Result<Self> {
+        let client_pubkey = tls_session.client_pubkey_cache()?;
+        Ok(TlsContext::new(client_pubkey))
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/core/src/tls/session_manager.rs b/projects/web3/ConfidentialKlave/core/src/tls/session_manager.rs
new file mode 100644
index 0000000..af6cf32
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/core/src/tls/session_manager.rs
@@ -0,0 +1,105 @@
+// 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 basic_utils::println;
+use std::collections::{HashMap, HashSet};
+use std::convert::TryInto;
+use types::share::CkHash;
+
+use crate::key_utils::load_signing_keypair;
+use crate::secure_storage_client::SecureStorageClient;
+use crate::tls::config_builder::TlsServerConfigBuilder;
+use crate::tls::handler_context::{InterCertConfig, TlsContext};
+use crate::tls::session::TlsSession;
+
+pub struct TlsSessionManager {
+    inner: HashMap<u32, TlsSession>,
+    config_builder: Option<TlsServerConfigBuilder>,
+}
+impl TlsSessionManager {
+    pub fn init() -> Self {
+        Self {
+            inner: HashMap::new(),
+            config_builder: None,
+        }
+    }
+    pub fn init_tls_server(&mut self, inter_cert_config: InterCertConfig) -> Result<()> {
+        let db_client = SecureStorageClient::init();
+        let inter_kp = load_signing_keypair(&db_client)?;
+        let config_builder = TlsServerConfigBuilder::new(inter_cert_config, inter_kp)?;
+        println!("[+] init_tls_server: server config builder initialized");
+        self.config_builder = Some(config_builder);
+        Ok(())
+    }
+    pub fn new_tls_session(
+        &mut self,
+        session_id: u32,
+        accepted_pubkey_hash: HashSet<CkHash>,
+    ) -> Result<()> {
+        let server_config = self
+            .config_builder
+            .as_ref()
+            .ok_or_else(|| anyhow!("server config not initialized"))?
+            .build_config_with_accepted_pubkey_hash(accepted_pubkey_hash)?;
+        println!(
+            "[+] new_tls_session: server config constructed for session_id: {:?}",
+            session_id
+        );
+
+        let tls_session = TlsSession::new(session_id, server_config)?;
+        self.inner.insert(session_id, tls_session);
+        Ok(())
+    }
+    pub fn do_tls_read(&mut self, session_id: u32, buf: &[u8]) -> Result<Vec<u8>> {
+        let tls_session = self
+            .inner
+            .get_mut(&session_id)
+            .ok_or_else(|| anyhow!("session_id not found"))?;
+        tls_session.read_request(buf)
+    }
+    pub fn write_response(&mut self, session_id: u32, response: &[u8]) -> Result<()> {
+        let tls_session = self
+            .inner
+            .get_mut(&session_id)
+            .ok_or_else(|| anyhow!("session_id not found"))?;
+        tls_session.write_response(response)
+    }
+    pub fn do_tls_write(&mut self, session_id: u32, buf: &mut [u8]) -> Result<usize> {
+        let tls_session = self
+            .inner
+            .get_mut(&session_id)
+            .ok_or_else(|| anyhow!("session_id not found"))?;
+        tls_session.return_response_to_client(buf)
+    }
+    pub fn close_tls_session(&mut self, session_id: u32) -> Result<()> {
+        let _tls_session = self
+            .inner
+            .remove(&session_id)
+            .ok_or_else(|| anyhow!("session_id not found"))?;
+        Ok(())
+    }
+    pub fn construct_tls_context(&mut self, session_id: u32) -> Result<TlsContext> {
+        let tls_session = self
+            .inner
+            .get_mut(&session_id)
+            .ok_or_else(|| anyhow!("session_id not found"))?;
+        tls_session
+            .try_into()
+            .map_err(|e| anyhow!("construct_tls_context failed: {:?}", e))
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/core/src/user.rs b/projects/web3/ConfidentialKlave/core/src/user.rs
new file mode 100644
index 0000000..e80de21
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/core/src/user.rs
@@ -0,0 +1,166 @@
+// 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 serde::{Deserialize, Serialize};
+use std::convert::TryFrom;
+
+use types::share::TaUserInfo;
+
+pub const ADMIN_ID: [u8; 8] = [0x00; 8];
+pub const SYSTEM_ID: [u8; 8] = [0x01; 8];
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Admin(pub TaUserInfo);
+
+impl TryFrom<TaUserInfo> for Admin {
+    type Error = anyhow::Error;
+
+    fn try_from(info: TaUserInfo) -> Result<Self> {
+        if info.is_admin() {
+            Ok(Self(info))
+        } else {
+            Err(anyhow!("[-] Admin::try_from(): not an admin"))
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct System(pub TaUserInfo);
+
+impl TryFrom<TaUserInfo> for System {
+    type Error = anyhow::Error;
+
+    fn try_from(info: TaUserInfo) -> Result<Self> {
+        if info.is_system() {
+            Ok(Self(info))
+        } else {
+            Err(anyhow!("[-] System::try_from(): not a system"))
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Approver(pub TaUserInfo);
+
+impl TryFrom<TaUserInfo> for Approver {
+    type Error = anyhow::Error;
+
+    fn try_from(info: TaUserInfo) -> Result<Self> {
+        if info.is_approver() {
+            Ok(Self(info))
+        } else {
+            Err(anyhow!("[-] Approver::try_from(): not an approver"))
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct TxOperator(pub TaUserInfo);
+
+impl TryFrom<TaUserInfo> for TxOperator {
+    type Error = anyhow::Error;
+
+    fn try_from(info: TaUserInfo) -> Result<Self> {
+        if info.is_tx_operator() {
+            Ok(Self(info))
+        } else {
+            Err(anyhow!("[-] TxOperator::try_from(): not a tx operator"))
+        }
+    }
+}
+
+impl TryFrom<TaUserInfo> for TaUser {
+    type Error = anyhow::Error;
+
+    fn try_from(info: TaUserInfo) -> Result<Self> {
+        if info.is_admin() {
+            Ok(TaUser::Admin(Admin(info)))
+        } else if info.is_system() {
+            Ok(TaUser::System(System(info)))
+        } else if info.is_approver() {
+            Ok(TaUser::Approver(Approver(info)))
+        } else if info.is_tx_operator() {
+            Ok(TaUser::TxOperator(TxOperator(info)))
+        } else {
+            Ok(TaUser::Unauthorized)
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+pub enum TaUser {
+    Admin(Admin),
+    System(System),
+    Approver(Approver),
+    TxOperator(TxOperator),
+    Unauthorized,
+}
+
+impl TaUser {
+    pub fn is_admin(&self) -> bool {
+        match self {
+            TaUser::Admin(_) => true,
+            _ => false,
+        }
+    }
+
+    pub fn is_approver(&self) -> bool {
+        match self {
+            TaUser::Approver(_) => true,
+            _ => false,
+        }
+    }
+
+    pub fn is_tx_operator(&self) -> bool {
+        match self {
+            TaUser::TxOperator(_) => true,
+            _ => false,
+        }
+    }
+
+    pub fn is_system(&self) -> bool {
+        match self {
+            TaUser::System(_) => true,
+            _ => false,
+        }
+    }
+}
+
+impl From<Admin> for TaUser {
+    fn from(admin: Admin) -> Self {
+        TaUser::Admin(admin)
+    }
+}
+
+impl From<Approver> for TaUser {
+    fn from(approver: Approver) -> Self {
+        TaUser::Approver(approver)
+    }
+}
+
+impl From<TxOperator> for TaUser {
+    fn from(tx_operator: TxOperator) -> Self {
+        TaUser::TxOperator(tx_operator)
+    }
+}
+
+impl From<System> for TaUser {
+    fn from(system: System) -> Self {
+        TaUser::System(system)
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/core/src/user_registry.rs b/projects/web3/ConfidentialKlave/core/src/user_registry.rs
new file mode 100644
index 0000000..13aec0f
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/core/src/user_registry.rs
@@ -0,0 +1,90 @@
+// 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 std::collections::{HashMap, HashSet};
+use std::convert::TryFrom;
+
+use crate::user::{ADMIN_ID, SYSTEM_ID};
+use crate::{ROOT_PUBKEY, SYSTEM_PUBKEY};
+use basic_utils::println;
+use types::share::{CkHash, CkHasher, CkPublicKey, Role, RoleSet, TaUserInfo, UserID};
+
+pub struct UserRegistry {
+    pub inner: HashMap<CkHash, TaUserInfo>,
+}
+
+impl UserRegistry {
+    pub fn init() -> Self {
+        let mut inner = HashMap::new();
+
+        // hardcoded admin
+        let admin_pubkey_hash = match CkPublicKey::new(ROOT_PUBKEY.to_vec()).hash() {
+            Ok(hash) => hash,
+            Err(_) => {
+                println!("[-] UserRegistry::init(): failed to hash admin pubkey, set as default");
+                CkHash::default()
+            }
+        };
+        let admin_info = TaUserInfo::new(
+            admin_pubkey_hash.clone(),
+            UserID::new(ADMIN_ID),
+            RoleSet(vec![Role::Admin]),
+        );
+        inner.insert(admin_pubkey_hash, admin_info);
+
+        // hardcoded system
+        let system_pubkey_hash = match CkPublicKey::new(SYSTEM_PUBKEY.to_vec()).hash() {
+            Ok(hash) => hash,
+            Err(_) => {
+                println!("[-] UserRegistry::init(): failed to hash system pubkey, set as default");
+                CkHash::default()
+            }
+        };
+        let system_info = TaUserInfo::new(
+            system_pubkey_hash.clone(),
+            UserID::new(SYSTEM_ID),
+            RoleSet(vec![Role::System]),
+        );
+        inner.insert(system_pubkey_hash, system_info);
+
+        Self { inner }
+    }
+
+    pub fn auth_as_role<U: TryFrom<TaUserInfo>>(&self, pubkey_hash: &CkHash) -> Result<U> {
+        let user_info = self
+            .inner
+            .get(pubkey_hash)
+            .ok_or_else(|| anyhow!("[-] UserRegistry::auth_as_role(): pubkey not found"))?;
+        U::try_from(user_info.clone())
+            .map_err(|_e| anyhow!("[-] UserRegistry::auth_as_role() convert error"))
+    }
+
+    pub fn set_users(&mut self, user_info: Vec<TaUserInfo>) {
+        for info in user_info {
+            self.inner.insert(info.pubkey_hash().clone(), info);
+        }
+    }
+
+    pub fn get_accepted_pubkey_hash(&self) -> HashSet<CkHash> {
+        let mut pubkey_hash_set = HashSet::new();
+        for pubkey_hash in self.inner.keys() {
+            pubkey_hash_set.insert(pubkey_hash.clone());
+        }
+        pubkey_hash_set
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/crypto/Cargo.toml b/projects/web3/ConfidentialKlave/crypto/Cargo.toml
new file mode 100644
index 0000000..afd430f
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/crypto/Cargo.toml
@@ -0,0 +1,39 @@
+# 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 = "crypto"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+basic_utils = { path = "../basic_utils" }
+serde = { version = "=1.0.163", features = ["derive"] }
+anyhow = "1.0"
+ring = { git = "https://github.com/DemesneGH/ring-optee.git", branch = "0.16.20-optee" }
+
+[target.'cfg(target_os="optee")'.dependencies]
+optee-utee-sys = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee/optee-utee-sys" }
+optee-utee = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee" }
+
+[patch.crates-io]
+proc-macro2 = { git = "https://github.com/DemesneGH/proc-macro2.git" }
+ring = { git = "https://github.com/DemesneGH/ring-optee.git", branch = "0.16.20-optee" }
+
+[patch."https://github.com/apache/incubator-teaclave-trustzone-sdk.git"]
+optee-utee = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee" }
+libc = { path = "../../incubator-teaclave-trustzone-sdk/rust/libc" }
\ No newline at end of file
diff --git a/projects/web3/ConfidentialKlave/crypto/src/aead.rs b/projects/web3/ConfidentialKlave/crypto/src/aead.rs
new file mode 100644
index 0000000..acbe954
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/crypto/src/aead.rs
@@ -0,0 +1,201 @@
+// 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 basic_utils::generate_random_bytes;
+use ring::aead;
+use serde::{Deserialize, Serialize};
+
+// AES_GCM 128
+// encrypting/decrypting data-key
+pub const AES_GCM_128_KEY_LENGTH: usize = 16;
+pub const AES_GCM_128_IV_LENGTH: usize = 12;
+pub const CMAC_LENGTH: usize = 16;
+type CMac = [u8; CMAC_LENGTH];
+
+/// The above code defines a struct for AES-GCM 128-bit encryption and decryption with methods for
+/// encrypting and decrypting data and generating a CMAC.
+///
+/// Properties:
+///
+/// * `key`: A 128-bit AES key used for encryption and decryption in the AES-GCM mode of operation.
+/// * `iv`: `iv` stands for initialization vector. It is a fixed-size array of bytes used as an input to
+/// the AES-GCM encryption algorithm to ensure that the same plaintext message encrypted with the same
+/// key produces different ciphertexts. The `iv` is typically randomly generated for each encryption
+/// operation and must be kept
+#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct AesGcm128Key {
+    pub key: [u8; AES_GCM_128_KEY_LENGTH],
+    pub iv: [u8; AES_GCM_128_IV_LENGTH],
+}
+
+/// This is an implementation of methods for the `AesGcm128Key` struct.
+impl AesGcm128Key {
+    pub const _SCHEMA: &'static str = "aes-gcm-128";
+
+    /// This function creates a new instance of AesGcm128Key with the given key and initialization vector.
+    ///
+    /// Arguments:
+    ///
+    /// * `in_key`: in_key is a reference to a byte slice that represents the encryption key used in
+    /// AES-GCM-128 encryption. The length of the slice should be 16 bytes (AES_GCM_128_KEY_LENGTH).
+    /// * `in_iv`: `in_iv` is a slice of bytes representing the initialization vector (IV) used in AES-GCM
+    /// encryption. The IV is a random value that is used to ensure that each encryption operation produces
+    /// a unique ciphertext, even if the same plaintext is encrypted multiple times. The length of the IV is
+    /// typically
+    ///
+    /// Returns:
+    ///
+    /// The `new` function is returning a `Result` containing an `AesGcm128Key` struct.
+    pub fn new(in_key: &[u8], in_iv: &[u8]) -> Result<Self> {
+        let mut key = [0u8; AES_GCM_128_KEY_LENGTH];
+        let mut iv = [0u8; AES_GCM_128_IV_LENGTH];
+        key.copy_from_slice(in_key);
+        iv.copy_from_slice(in_iv);
+
+        Ok(AesGcm128Key { key, iv })
+    }
+
+    pub fn generate() -> Result<Self> {
+        let key = generate_random_bytes(AES_GCM_128_KEY_LENGTH)?;
+        let iv = generate_random_bytes(AES_GCM_128_IV_LENGTH)?;
+
+        Ok(AesGcm128Key {
+            key: key.try_into().map_err(|_| anyhow!("try_into error"))?,
+            iv: iv.try_into().map_err(|_| anyhow!("try_into error"))?,
+        })
+    }
+
+    /// This function decrypts a message using AES-128-GCM and returns a CMAC authentication tag.
+    ///
+    /// Arguments:
+    ///
+    /// * `in_out`: `in_out` is a mutable reference to a `Vec<u8>` that contains the ciphertext to be
+    /// decrypted. The decrypted plaintext will be written back to the same `Vec<u8>` in place of the
+    /// ciphertext.
+    ///
+    /// Returns:
+    ///
+    /// a `Result` containing a `CMac` value.
+    pub fn decrypt(&self, in_out: &mut Vec<u8>) -> Result<CMac> {
+        let plaintext_len = aead_decrypt(&aead::AES_128_GCM, in_out, &self.key, &self.iv)?.len();
+        let mut cmac: CMac = [0u8; CMAC_LENGTH];
+        cmac.copy_from_slice(&in_out[plaintext_len..]);
+        in_out.truncate(plaintext_len);
+        Ok(cmac)
+    }
+
+    /// This function encrypts a given input using AES-128-GCM and returns a CMAC value.
+    ///
+    /// Arguments:
+    ///
+    /// * `in_out`: `in_out` is a mutable reference to a vector of bytes (`Vec<u8>`). This vector contains
+    /// the plaintext to be encrypted and will also be used to store the resulting ciphertext. The function
+    /// will modify this vector in place to encrypt the plaintext and append the resulting CMAC
+    /// (Cipher-based Message
+    ///
+    /// Returns:
+    ///
+    /// a `Result` containing a `CMac` value.
+    pub fn encrypt(&self, in_out: &mut Vec<u8>) -> Result<CMac> {
+        aead_encrypt(&aead::AES_128_GCM, in_out, &self.key, &self.iv)?;
+        let mut cmac: CMac = [0u8; CMAC_LENGTH];
+        let n = in_out.len();
+        let cybertext_len = n - CMAC_LENGTH;
+        cmac.copy_from_slice(&in_out[cybertext_len..]);
+        Ok(cmac)
+    }
+}
+
+/// This function performs AEAD encryption on a given input using a specified algorithm, key, and
+/// initialization vector.
+///
+/// Arguments:
+///
+/// * `alg`: a reference to an AEAD (Authenticated Encryption with Associated Data) algorithm, which is
+/// used for encrypting and authenticating data.
+/// * `in_out`: `in_out` is a mutable reference to a vector of bytes that contains the plaintext to be
+/// encrypted. After encryption, the ciphertext will be appended to this vector.
+/// * `key`: The `key` parameter is a byte slice that contains the secret key used for encryption. It is
+/// used to create an `UnboundKey` object, which is then used to create a `LessSafeKey` object for
+/// encryption.
+/// * `iv`: `iv` stands for initialization vector. It is a fixed-size input to the encryption algorithm
+/// that is typically used to ensure that the same plaintext input does not produce the same ciphertext
+/// output. The `iv` should be unique for each encryption operation.
+///
+/// Returns:
+///
+/// a `Result` type, which can either be `Ok(())` if the encryption was successful or an error if
+/// something went wrong during the encryption process.
+pub fn aead_encrypt(
+    alg: &'static aead::Algorithm,
+    in_out: &mut Vec<u8>,
+    key: &[u8],
+    iv: &[u8],
+) -> Result<()> {
+    let key =
+        aead::UnboundKey::new(alg, key).map_err(|_| anyhow!("aead::UnboundKey::new() Error"))?;
+    let nonce = aead::Nonce::try_assume_unique_for_key(iv)
+        .map_err(|_| anyhow!("aead::Nonce::try_assume_unique_for_key() Error"))?;
+    let aad = aead::Aad::from([0u8; 8]);
+
+    let enc_key = aead::LessSafeKey::new(key);
+    enc_key
+        .seal_in_place_append_tag(nonce, aad, in_out)
+        .map_err(|_| anyhow!("aead::LessSafeKey::seal_in_place_append_tag() Error"))?;
+    Ok(())
+}
+
+/// This function performs authenticated encryption with associated data (AEAD) decryption using a given
+/// algorithm, key, initialization vector, and input/output buffer.
+///
+/// Arguments:
+///
+/// * `alg`: The AEAD (Authenticated Encryption with Associated Data) algorithm to be used for
+/// decryption.
+/// * `in_out`: `in_out` is a mutable reference to a byte slice that contains the ciphertext to be
+/// decrypted. The function will modify this slice in place to store the decrypted plaintext.
+/// * `key`: The `key` parameter is a byte slice representing the secret key used for decryption. It is
+/// used to create an `UnboundKey` object, which is then used to create a `LessSafeKey` object for
+/// decryption.
+/// * `iv`: `iv` stands for initialization vector. It is a fixed-size input to the encryption algorithm
+/// that is typically used to ensure that the same plaintext input does not produce the same ciphertext
+/// output. It is also used to derive the initial state of the encryption algorithm. In the context of
+/// this function, `iv
+///
+/// Returns:
+///
+/// a `Result` that contains a mutable reference to the input/output buffer (`&'a mut [u8]`) if the
+/// decryption is successful. If there is an error during decryption, the function will return an error.
+pub fn aead_decrypt<'a>(
+    alg: &'static aead::Algorithm,
+    in_out: &'a mut [u8],
+    key: &[u8],
+    iv: &[u8],
+) -> Result<&'a mut [u8]> {
+    let key =
+        aead::UnboundKey::new(alg, key).map_err(|_| anyhow!("aead::UnboundKey::new() Error"))?;
+    let nonce = aead::Nonce::try_assume_unique_for_key(iv)
+        .map_err(|_| anyhow!("aead::Nonce::try_assume_unique_for_key() Error"))?;
+    let aad = aead::Aad::from([0u8; 8]);
+
+    let dec_key = aead::LessSafeKey::new(key);
+    let slice = dec_key
+        .open_in_place(nonce, aad, in_out)
+        .map_err(|_| anyhow!("aead::LessSafeKey::open_in_place() Error"))?;
+    Ok(slice)
+}
diff --git a/projects/web3/ConfidentialKlave/crypto/src/ecdsa.rs b/projects/web3/ConfidentialKlave/crypto/src/ecdsa.rs
new file mode 100644
index 0000000..bcd140a
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/crypto/src/ecdsa.rs
@@ -0,0 +1,103 @@
+// 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 ring::rand;
+use ring::signature::{self, EcdsaSigningAlgorithm, KeyPair, ECDSA_P384_SHA384_ASN1};
+pub use ring::signature::{
+    ECDSA_P256_SHA256_ASN1, ECDSA_P256_SHA256_ASN1_SIGNING, ECDSA_P384_SHA384_ASN1_SIGNING,
+};
+
+/// EcdsaKeyPair stores a pair of ECDSA (private, public) key based on the
+/// NIST P-256 curve (a.k.a secp256r1).
+pub struct EcdsaKeyPair {
+    key_pair: signature::EcdsaKeyPair,
+    pkcs8_bytes: Vec<u8>,
+}
+
+impl EcdsaKeyPair {
+    /// Generate a ECDSA key pair.
+    pub fn new() -> Result<Self> {
+        let rng = rand::SystemRandom::new();
+        let pkcs8_bytes = signature::EcdsaKeyPair::generate_pkcs8(
+            &signature::ECDSA_P256_SHA256_ASN1_SIGNING,
+            &rng,
+        )
+        .unwrap();
+        let key_pair = signature::EcdsaKeyPair::from_pkcs8(
+            &signature::ECDSA_P256_SHA256_ASN1_SIGNING,
+            pkcs8_bytes.as_ref(),
+        )
+        .unwrap();
+        Ok(Self {
+            key_pair,
+            pkcs8_bytes: pkcs8_bytes.as_ref().to_vec(),
+        })
+    }
+
+    pub fn from_bytes(pkcs8_bytes: &[u8], algo: &'static EcdsaSigningAlgorithm) -> Result<Self> {
+        let key_pair = signature::EcdsaKeyPair::from_pkcs8(algo, pkcs8_bytes).unwrap();
+        Ok(Self {
+            key_pair,
+            pkcs8_bytes: pkcs8_bytes.to_vec(),
+        })
+    }
+
+    pub fn pub_key(&self) -> &[u8] {
+        self.key_pair.public_key().as_ref()
+    }
+
+    pub fn prv_key(&self) -> &[u8] {
+        self.pkcs8_bytes.as_ref()
+    }
+
+    pub fn sign(&self, msg: &[u8]) -> Result<Vec<u8>> {
+        let rng = rand::SystemRandom::new();
+        match self.key_pair.sign(&rng, msg) {
+            Ok(sig) => Ok(sig.as_ref().to_vec()),
+            Err(_) => Err(anyhow!("failed to sign")),
+        }
+    }
+}
+
+pub fn sign_bytes_p256(priv_key: &[u8], bytes: &[u8]) -> Result<Vec<u8>> {
+    let key_pair = EcdsaKeyPair::from_bytes(priv_key, &ECDSA_P256_SHA256_ASN1_SIGNING)?;
+    let sig = key_pair.sign(bytes)?;
+    Ok(sig.to_vec())
+}
+
+pub fn verify_signature_p256(pub_key: &[u8], bytes: &[u8], sig: &[u8]) -> Result<()> {
+    let peer_pub_key = signature::UnparsedPublicKey::new(&ECDSA_P256_SHA256_ASN1, pub_key);
+    match peer_pub_key.verify(bytes, sig) {
+        Ok(_) => Ok(()),
+        Err(e) => Err(anyhow!("signature verification failed: {:?}", e)),
+    }
+}
+
+pub fn sign_bytes_p384(priv_key: &[u8], bytes: &[u8]) -> Result<Vec<u8>> {
+    let key_pair = EcdsaKeyPair::from_bytes(priv_key, &ECDSA_P384_SHA384_ASN1_SIGNING)?;
+    let sig = key_pair.sign(bytes)?;
+    Ok(sig.to_vec())
+}
+
+pub fn verify_signature_p384(pub_key: &[u8], bytes: &[u8], sig: &[u8]) -> Result<()> {
+    let peer_pub_key = signature::UnparsedPublicKey::new(&ECDSA_P384_SHA384_ASN1, pub_key);
+    match peer_pub_key.verify(bytes, sig) {
+        Ok(_) => Ok(()),
+        Err(e) => Err(anyhow!("signature verification failed: {:?}", e)),
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/crypto/src/lib.rs b/projects/web3/ConfidentialKlave/crypto/src/lib.rs
new file mode 100644
index 0000000..a2d2d0c
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/crypto/src/lib.rs
@@ -0,0 +1,21 @@
+// 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 aead;
+pub use aead::*;
+mod ecdsa;
+pub use ecdsa::*;
diff --git a/projects/web3/ConfidentialKlave/proto/Cargo.toml b/projects/web3/ConfidentialKlave/proto/Cargo.toml
new file mode 100644
index 0000000..e902d51
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/proto/Cargo.toml
@@ -0,0 +1,35 @@
+# 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.2.0"
+authors = ["Teaclave Contributors <de...@teaclave.apache.org>"]
+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]
+types = { path = "../types" }
+
+serde = { version = "1.0", features = ["derive"] }
+anyhow = "1.0.50"
+bincode = "1.3.3"
+
+[patch.crates-io]
+proc-macro2 = { git = "https://github.com/DemesneGH/proc-macro2.git" }
\ No newline at end of file
diff --git a/projects/web3/ConfidentialKlave/proto/src/device.rs b/projects/web3/ConfidentialKlave/proto/src/device.rs
new file mode 100644
index 0000000..b04c657
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/proto/src/device.rs
@@ -0,0 +1,107 @@
+// 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::collections::HashSet;
+
+use anyhow::Result;
+use serde::{Deserialize, Serialize};
+use types::share::{
+    CkEncryptedPayload, CkPublicKey, CkSignature, DeviceID, TeeOnlineStatus, WalletID,
+};
+
+// GetTeeStatus
+#[derive(Serialize, Deserialize, Debug)]
+pub struct GetTeeStatusInput {}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct GetTeeStatusOutput {
+    pub device_id: DeviceID,
+    pub tee_status: TeeOnlineStatus,
+}
+
+// Backup commands (local):
+// - InitBoard
+// - BackupWallet
+// - RestoreWallet
+
+// InitBoard
+#[derive(Serialize, Deserialize, Debug)]
+pub struct InitBoardInput {}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct InitBoardOutput {
+    pub device_id: DeviceID,
+    pub signing_pubkey: CkPublicKey,
+    pub backup_pubkey: CkPublicKey,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct BackupWalletInput {
+    pub backup_to_device_id: DeviceID,
+    pub backup_to_device_pubkey: CkPublicKey,
+    pub target_wallets: HashSet<WalletID>,
+    pub signature: Option<CkSignature>, // signature of requests
+}
+
+impl BackupWalletInput {
+    pub fn new_unsigned(
+        backup_to_device_id: DeviceID,
+        backup_to_device_pubkey: CkPublicKey,
+        target_wallets: HashSet<WalletID>,
+    ) -> Self {
+        Self {
+            backup_to_device_id,
+            backup_to_device_pubkey,
+            target_wallets,
+            signature: None,
+        }
+    }
+
+    pub fn serialize(&self) -> Result<Vec<u8>> {
+        let mut bytes = Vec::new();
+        bytes.extend(bincode::serialize(&self.backup_to_device_id)?);
+        bytes.extend(bincode::serialize(&self.backup_to_device_pubkey)?);
+        let mut target_wallets: Vec<WalletID> = self.target_wallets.iter().cloned().collect();
+        target_wallets.sort();
+        bytes.extend(bincode::serialize(&target_wallets)?);
+        Ok(bytes)
+    }
+
+    pub fn set_signature(&mut self, signature: CkSignature) {
+        self.signature = Some(signature);
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct BackupWalletOutput {
+    pub backup_to_device: DeviceID,
+    pub wallet_ids: HashSet<WalletID>, // backup wallets
+    pub encrypted: CkEncryptedPayload,
+}
+
+// RestoreWallet
+#[derive(Serialize, Deserialize, Debug)]
+pub struct RestoreWalletInput {
+    pub backup_to_device: DeviceID,
+    pub wallet_ids: HashSet<WalletID>, // backup wallets
+    pub encrypted: CkEncryptedPayload,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct RestoreWalletOutput {
+    pub wallet_ids: HashSet<WalletID>, // successfully restored wallets
+}
diff --git a/projects/web3/ConfidentialKlave/proto/src/lib.rs b/projects/web3/ConfidentialKlave/proto/src/lib.rs
new file mode 100644
index 0000000..f608c69
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/proto/src/lib.rs
@@ -0,0 +1,88 @@
+// 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 device;
+mod transaction;
+mod wallet;
+
+pub use device::*;
+pub use transaction::*;
+pub use wallet::*;
+
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct TlsCommandRequest {
+    pub command: TaCommand,
+    pub request: Vec<u8>,
+}
+
+#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
+pub enum TaCommand {
+    // tls cmds
+    NewTlsSession,
+    CloseTlsSession,
+    DoTlsRead,
+    DoTlsWrite,
+    StartServer,
+    // wallet cmds
+    SyncWithTee,
+    AddAccount,
+    SyncFromTeeWallet, // deprecated
+    // transaction cmds
+    CreateTransaction,
+    ApproveTransaction,
+    RecallTransaction,
+    SignTransaction,
+    ListPendingTransaction,
+    // backup cmds
+    InitBoard,
+    BackupWallet,
+    RestoreWallet,
+    ClearWalletStorage,
+    // tee status cmds
+    GetTeeStatus,
+    // unknown command
+    Unknown,
+}
+
+impl From<u32> for TaCommand {
+    #[inline]
+    fn from(value: u32) -> TaCommand {
+        match value {
+            0 => TaCommand::NewTlsSession,
+            1 => TaCommand::CloseTlsSession,
+            2 => TaCommand::DoTlsRead,
+            3 => TaCommand::DoTlsWrite,
+            4 => TaCommand::StartServer,
+            5 => TaCommand::SyncWithTee,
+            6 => TaCommand::AddAccount,
+            7 => TaCommand::SyncFromTeeWallet,
+            8 => TaCommand::CreateTransaction,
+            9 => TaCommand::ApproveTransaction,
+            10 => TaCommand::RecallTransaction,
+            11 => TaCommand::SignTransaction,
+            12 => TaCommand::ListPendingTransaction,
+            13 => TaCommand::InitBoard,
+            14 => TaCommand::BackupWallet,
+            15 => TaCommand::RestoreWallet,
+            16 => TaCommand::ClearWalletStorage,
+            17 => TaCommand::GetTeeStatus,
+            _ => TaCommand::Unknown,
+        }
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/proto/src/transaction.rs b/projects/web3/ConfidentialKlave/proto/src/transaction.rs
new file mode 100644
index 0000000..6bc90eb
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/proto/src/transaction.rs
@@ -0,0 +1,94 @@
+// 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 types::share::{
+    ApprovalOperation, CkHash, CkSignature, MultiChainTransaction, TaApprovalChain,
+    TaOperatorsBasic, TransactionID, WalletID,
+};
+
+// Transaction cmds (tls):
+// - CreateTransaction
+// - ApproveTransaction
+// - DeleteTransaction
+// - SignTransaction
+// - ListPendingTransaction
+
+// CreateTransaction
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct CreateTransactionInput {
+    pub tx: MultiChainTransaction,
+}
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct CreateTransactionOutput {
+    pub tx_id: TransactionID,
+    pub latest_approval_chain: TaApprovalChain,
+}
+
+// ApproveTransaction
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct ApproveTransactionInput {
+    pub tx_id: TransactionID,
+    pub current_approval_chain: TaApprovalChain,
+    pub operation: ApprovalOperation,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct ApproveTransactionOutput {
+    pub latest_approval_chain: TaApprovalChain,
+}
+
+// DeleteTransaction
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct RecallTransactionInput {
+    pub tx_id: TransactionID,
+    pub current_approval_chain: TaApprovalChain,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct RecallTransactionOutput {
+    pub tx_id: TransactionID,
+}
+
+// SignTransaction
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct SignTransactionInput {
+    pub tx_id: TransactionID,
+    pub tx: MultiChainTransaction,
+}
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct SignTransactionOutput {
+    pub signed_tx: CkSignature,
+}
+
+// ListPendingTransaction
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct ListPendingTransactionInput {}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct TaTransactionDetail {
+    pub tx_id: TransactionID,
+    pub tx_hash: CkHash,
+    pub from_wallet: WalletID,
+    pub latest_approval_chain: TaApprovalChain,
+    pub authorized_operators: TaOperatorsBasic,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct ListPendingTransactionOutput {
+    pub tx_details: Vec<TaTransactionDetail>,
+}
diff --git a/projects/web3/ConfidentialKlave/proto/src/wallet.rs b/projects/web3/ConfidentialKlave/proto/src/wallet.rs
new file mode 100644
index 0000000..beea3bc
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/proto/src/wallet.rs
@@ -0,0 +1,56 @@
+// 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 std::collections::HashSet;
+use types::share::{
+    MultiChainAccount, TaApprovalChainBasic, TaOperatorsBasic, TeeConfig, UserID, WalletID,
+};
+
+// Wallet cmds (tls):
+// - SyncWithTee
+// - AddAccount (unimplemented)
+
+// SyncWithTee
+// Create a new wallet or update the wallet info in the TEE
+// return LatestTeeInfo
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct SyncWithTeeInput {
+    pub signed_config: TeeConfig,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct LatestWalletInfo {
+    pub wallet_id: WalletID,
+    pub approval_chain: TaApprovalChainBasic,
+    pub authorized_operators: TaOperatorsBasic,
+    pub accounts: HashSet<MultiChainAccount>,
+}
+
+impl LatestWalletInfo {
+    pub fn all_participants(&self) -> HashSet<&UserID> {
+        let mut participants = self.approval_chain.distinct_approvers();
+        participants.extend(self.authorized_operators.distinct_operators());
+        participants
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct SyncWithTeeOutput {
+    pub latest_wallets: Vec<LatestWalletInfo>,
+    pub config_version: u64,
+}
diff --git a/projects/web3/ConfidentialKlave/rust-toolchain.toml b/projects/web3/ConfidentialKlave/rust-toolchain.toml
new file mode 100644
index 0000000..50c4299
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/rust-toolchain.toml
@@ -0,0 +1,21 @@
+# 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.
+
+# Toolchain override for rustup
+
+[toolchain]
+channel = "nightly-2023-03-20"
diff --git a/projects/web3/ConfidentialKlave/scripts/generate_certs.sh b/projects/web3/ConfidentialKlave/scripts/generate_certs.sh
new file mode 100755
index 0000000..6c06655
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/scripts/generate_certs.sh
@@ -0,0 +1,53 @@
+#!/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 -e
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
+TMP_DIR=$SCRIPT_DIR/tmp
+if [ -d $TMP_DIR ]; then
+  echo "Temporary directory already exists"
+  exit 0
+fi
+mkdir $TMP_DIR
+
+openssl ecparam -name secp384r1 -out $TMP_DIR/ca.pem
+openssl req -nodes \
+          -x509 \
+          -newkey ec:$TMP_DIR/ca.pem \
+          -keyout $TMP_DIR/ca.key \
+          -out $TMP_DIR/ca.cert \
+          -sha256 \
+          -batch \
+          -days 3650 \
+          -subj "/CN=CK ECDSA CA"
+openssl asn1parse -in $TMP_DIR/ca.cert -out $TMP_DIR/ca.der > /dev/null
+openssl ec -in $TMP_DIR/ca.key -pubout -outform DER -out $TMP_DIR/ca.pub.der
+PUBKEY=$(openssl pkey -pubin -inform DER -in $TMP_DIR/ca.pub.der -text -noout)
+UNCOMPRESSED=$(echo $PUBKEY | sed ':a;N;$!ba;s/\n//g' | sed -e 's/[[:space:]]//g' | grep -o -E '04(:[[:xdigit:]]+)+')
+echo $UNCOMPRESSED | xxd -r -p > $TMP_DIR/ca.pub
+echo "CA certificate (for authority) generated"
+
+openssl ecparam -genkey -name prime256v1 -noout -out $TMP_DIR/system.pem
+openssl pkcs8 -topk8 -inform PEM -in $TMP_DIR/system.pem -out $TMP_DIR/system.key -nocrypt
+openssl ec -in $TMP_DIR/system.pem -pubout -outform DER -out $TMP_DIR/system.pub.der
+PUBKEY=$(openssl pkey -pubin -inform DER -in $TMP_DIR/system.pub.der -text -noout)
+UNCOMPRESSED=$(echo $PUBKEY | sed ':a;N;$!ba;s/\n//g' | sed -e 's/[[:space:]]//g' | grep -o -E '04(:[[:xdigit:]]+)+')
+echo $UNCOMPRESSED | xxd -r -p > $TMP_DIR/system.pub
+echo "System key pair (for webapi) generated"
diff --git a/projects/web3/ConfidentialKlave/scripts/install_certs.sh b/projects/web3/ConfidentialKlave/scripts/install_certs.sh
new file mode 100755
index 0000000..8a3cd69
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/scripts/install_certs.sh
@@ -0,0 +1,44 @@
+#!/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 -e
+
+# Path to ConfidentialKlave
+CK_ROOT_PATH=$1
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
+TMP=$SCRIPT_DIR/tmp
+
+# check if paths are set
+if [ -z "$CK_ROOT_PATH" ]; then
+  echo "Usage: install_certs.sh <CK-root-path>"
+  exit 1
+fi
+if [ ! -d $TMP ]; then
+  echo "$TMP does not exist. Please run generate_certs.sh first."
+  exit 1 
+fi
+
+# install pubkeys into ConfidentialKlave
+CK_PUBKEY_PATH=$CK_ROOT_PATH/pubkeys
+if [ ! -d $CK_PUBKEY_PATH ]; then
+  mkdir $CK_PUBKEY_PATH
+fi
+cp $TMP/ca.cert $TMP/ca.pub $TMP/system.pub $CK_PUBKEY_PATH
+
+echo "Certs and pubkeys installed to $CK_PUBKEY_PATH"
diff --git a/projects/web3/ConfidentialKlave/secure_storage/Cargo.toml b/projects/web3/ConfidentialKlave/secure_storage/Cargo.toml
new file mode 100644
index 0000000..cab9ce3
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/secure_storage/Cargo.toml
@@ -0,0 +1,48 @@
+# 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 = "secure_storage"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+basic_utils = { path = "../basic_utils" }
+types = { path = "../types" }
+crypto = { path = "../crypto" }
+attestation = { path = "../attestation" }
+
+serde = "=1.0.163"
+serde_json = "1.0"
+serde_with = "1.14"
+
+hex = { version = "0.4", features = ["serde"] }
+secp256k1 = "0.24.3"
+anyhow = "1.0"
+bincode = "1.3.3"
+
+[target.'cfg(target_os="optee")'.dependencies]
+optee-utee-sys = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee/optee-utee-sys" }
+optee-utee = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee" }
+
+[patch.crates-io]
+proc-macro2 = { git = "https://github.com/DemesneGH/proc-macro2.git" }
+ring = { git = "https://github.com/DemesneGH/ring-optee.git", branch = "0.16.20-optee" }
+
+[patch."https://github.com/apache/incubator-teaclave-trustzone-sdk.git"]
+optee-utee = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee" }
+libc = { path = "../../incubator-teaclave-trustzone-sdk/rust/libc" }
\ No newline at end of file
diff --git a/projects/web3/ConfidentialKlave/secure_storage/src/lib.rs b/projects/web3/ConfidentialKlave/secure_storage/src/lib.rs
new file mode 100644
index 0000000..12486b2
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/secure_storage/src/lib.rs
@@ -0,0 +1,35 @@
+// 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 secure_db;
+pub use secure_db::*;
+mod tee_key;
+pub use tee_key::*;
+
+#[cfg(target_os = "optee")]
+mod optee;
+#[cfg(target_os = "optee")]
+pub use optee::{rsa::*, storage_backend::*};
+
+#[cfg(not(target_os = "optee"))]
+mod local;
+#[cfg(not(target_os = "optee"))]
+pub use local::rsa::*;
+#[cfg(not(target_os = "optee"))]
+pub use local::storage_backend::*;
+#[cfg(not(target_os = "optee"))]
+pub use std::println;
diff --git a/projects/web3/ConfidentialKlave/secure_storage/src/local/mod.rs b/projects/web3/ConfidentialKlave/secure_storage/src/local/mod.rs
new file mode 100644
index 0000000..1036100
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/secure_storage/src/local/mod.rs
@@ -0,0 +1,19 @@
+// 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 rsa;
+pub mod storage_backend;
diff --git a/projects/web3/ConfidentialKlave/secure_storage/src/local/rsa.rs b/projects/web3/ConfidentialKlave/secure_storage/src/local/rsa.rs
new file mode 100644
index 0000000..c37e4ba
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/secure_storage/src/local/rsa.rs
@@ -0,0 +1,90 @@
+// 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 crate::{RsaKeyPair, RsaPrivateKey, RsaPublicKey};
+use anyhow::{bail, Result};
+use std::convert::TryFrom;
+use std::vec;
+
+pub struct RsaKeyObject {
+    key_size: usize,      // The number of bits in the modulus. can be 256, ..., 4096
+    key_object: Vec<u8>,  // mocked for local testing
+    key_object_type: u32, // mocked for local testing
+}
+
+impl RsaKeyObject {
+    pub fn new(key_size: usize, key_object: Vec<u8>, key_object_type: u32) -> Result<Self> {
+        Ok(Self {
+            key_size,
+            key_object,
+            key_object_type,
+        })
+    }
+
+    pub fn allocate_keypair_object(key_size: usize) -> Result<Self> {
+        if key_size % 256 != 0 || key_size < 256 || key_size > 4096 {
+            bail!("wrong key size, should be multiple of 256, between 256 and 4096");
+        }
+        Ok(Self {
+            key_size,
+            key_object: vec![],
+            key_object_type: 0,
+        })
+    }
+
+    pub fn generate_key(&mut self) -> Result<()> {
+        Ok(())
+    }
+
+    pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
+        // unimplemented, reserved for local compilation. We use optee backend for TAs.
+        unimplemented!()
+    }
+
+    pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
+        // unimplemented, reserved for local compilation. We use optee backend for TAs.
+        unimplemented!()
+    }
+}
+
+impl TryFrom<RsaKeyObject> for RsaKeyPair {
+    type Error = anyhow::Error;
+
+    fn try_from(rsa_key_object: RsaKeyObject) -> Result<Self> {
+        let pubkey = RsaPublicKey::new(rsa_key_object.key_size, &[], &[])?;
+        let privkey = RsaPrivateKey::new(&[])?;
+        Ok(RsaKeyPair::new(rsa_key_object.key_size, pubkey, privkey))
+    }
+}
+
+impl TryInto<RsaKeyObject> for RsaPublicKey {
+    type Error = anyhow::Error;
+
+    fn try_into(self) -> Result<RsaKeyObject> {
+        // unimplemented, reserved for local compilation. We use optee backend for TAs.
+        unimplemented!()
+    }
+}
+
+impl TryInto<RsaKeyObject> for RsaKeyPair {
+    type Error = anyhow::Error;
+
+    fn try_into(self) -> Result<RsaKeyObject> {
+        // unimplemented, reserved for local compilation. We use optee backend for TAs.
+        unimplemented!()
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/secure_storage/src/local/storage_backend.rs b/projects/web3/ConfidentialKlave/secure_storage/src/local/storage_backend.rs
new file mode 100644
index 0000000..9aa86bf
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/secure_storage/src/local/storage_backend.rs
@@ -0,0 +1,48 @@
+// 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 std::io::{Read, Write};
+
+pub fn save_in_secure_storage(obj_id: &[u8], data: &[u8]) -> Result<()> {
+    // save data to file, file name is hex(obj_id)
+    let file_name = hex::encode(obj_id);
+    let mut file = std::fs::File::create(&file_name)?;
+    file.write_all(data).map_err(|e| {
+        anyhow!(
+            "Failed to write data to file: {}, error: {}",
+            &file_name,
+            e.to_string()
+        )
+    })
+}
+
+pub fn load_from_secure_storage(obj_id: &[u8]) -> Result<Vec<u8>> {
+    // load data from file, file name is hex(obj_id)
+    let file_name = hex::encode(obj_id);
+    let mut file = std::fs::File::open(file_name)?;
+    let mut data = Vec::new();
+    file.read_to_end(&mut data)?;
+    Ok(data)
+}
+
+pub fn delete_from_secure_storage(obj_id: Vec<u8>) -> Result<()> {
+    // delete file, file name is hex(obj_id)
+    let file_name = hex::encode(obj_id);
+    std::fs::remove_file(file_name)?;
+    Ok(())
+}
diff --git a/projects/web3/ConfidentialKlave/secure_storage/src/optee/mod.rs b/projects/web3/ConfidentialKlave/secure_storage/src/optee/mod.rs
new file mode 100644
index 0000000..1036100
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/secure_storage/src/optee/mod.rs
@@ -0,0 +1,19 @@
+// 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 rsa;
+pub mod storage_backend;
diff --git a/projects/web3/ConfidentialKlave/secure_storage/src/optee/rsa.rs b/projects/web3/ConfidentialKlave/secure_storage/src/optee/rsa.rs
new file mode 100644
index 0000000..6bfd539
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/secure_storage/src/optee/rsa.rs
@@ -0,0 +1,165 @@
+// 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 crate::{RsaKeyPair, RsaPrivateKey, RsaPublicKey};
+use anyhow::{anyhow, bail, ensure, Result};
+use basic_utils::println;
+use optee_utee::{AlgorithmId, Asymmetric, OperationMode};
+pub use optee_utee::{
+    Attribute, AttributeId, AttributeMemref, TransientObject, TransientObjectType,
+};
+use std::convert::{TryFrom, TryInto};
+
+// the wrapper of optee_utee::TransientObject
+pub struct RsaKeyObject {
+    key_size: usize, // The number of bits in the modulus. can be 256, ..., 4096
+    key_object: TransientObject,
+    _key_object_type: TransientObjectType,
+}
+impl RsaKeyObject {
+    pub fn allocate_keypair_object(key_size: usize) -> Result<Self> {
+        let object = TransientObject::allocate(TransientObjectType::RsaKeypair, key_size)
+            .map_err(|e| anyhow!(e))?;
+        Ok(Self {
+            key_size,
+            key_object: object,
+            _key_object_type: TransientObjectType::RsaKeypair,
+        })
+    }
+    pub fn allocate_public_key_object(key_size: usize) -> Result<Self> {
+        if key_size % 256 != 0 || key_size < 256 || key_size > 4096 {
+            bail!("wrong key size");
+        }
+        let object = TransientObject::allocate(TransientObjectType::RsaPublicKey, key_size)
+            .map_err(|e| anyhow!(e))?;
+        Ok(Self {
+            key_size,
+            key_object: object,
+            _key_object_type: TransientObjectType::RsaPublicKey,
+        })
+    }
+    pub fn generate_key(&mut self) -> Result<()> {
+        self.key_object
+            .generate_key(self.key_size, &[])
+            .map_err(|e| anyhow!(e))?;
+        Ok(())
+    }
+    // get the value of an attribute
+    pub fn ref_attribute(&self, attribute_id: AttributeId) -> Result<Vec<u8>> {
+        let mut attribute = vec![0u8; self.key_size / 8];
+        let output_size = self
+            .key_object
+            .ref_attribute(attribute_id, &mut attribute)
+            .map_err(|e| anyhow!(e))?;
+        println!(
+            "RsaKeyObject::ref_attribute(): output_size: {}",
+            output_size
+        );
+        Ok(attribute[..output_size].to_vec())
+    }
+    // set the value of attributes
+    pub fn populate(&mut self, attrs: &[Attribute]) -> Result<()> {
+        self.key_object.populate(attrs).map_err(|e| anyhow!(e))
+    }
+    pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>> {
+        let key_info = self.key_object.info().map_err(|e| anyhow!(e))?;
+        let size_in_bytes = key_info.object_size() / 8;
+        ensure!(plaintext.len() <= size_in_bytes, "plaintext too long");
+
+        match Asymmetric::allocate(
+            AlgorithmId::RsaesPkcs1V15,
+            OperationMode::Encrypt,
+            key_info.object_size(),
+        ) {
+            Err(e) => bail!(e),
+            Ok(cipher) => {
+                cipher.set_key(&self.key_object).map_err(|e| anyhow!(e))?;
+                match cipher.encrypt(&[], &plaintext) {
+                    Err(e) => bail!(e),
+                    Ok(cipher) => Ok(cipher.to_vec()),
+                }
+            }
+        }
+    }
+    pub fn decrypt(&mut self, ciphertext: &[u8]) -> Result<Vec<u8>> {
+        let key_info = self.key_object.info().map_err(|e| anyhow!(e))?;
+        let size_in_bytes = key_info.object_size() / 8;
+        ensure!(ciphertext.len() <= size_in_bytes, "ciphertext too long");
+
+        match Asymmetric::allocate(
+            AlgorithmId::RsaesPkcs1V15,
+            OperationMode::Decrypt,
+            key_info.object_size(),
+        ) {
+            Err(e) => bail!(e),
+            Ok(cipher) => {
+                cipher.set_key(&self.key_object)?;
+                match cipher.decrypt(&[], &ciphertext) {
+                    Err(e) => bail!(e),
+                    Ok(plain) => Ok(plain.to_vec()),
+                }
+            }
+        }
+    }
+}
+
+impl TryFrom<RsaKeyObject> for RsaKeyPair {
+    type Error = anyhow::Error;
+    fn try_from(key_pair_object: RsaKeyObject) -> Result<Self> {
+        let key_size = key_pair_object.key_size;
+        let modulus = key_pair_object.ref_attribute(AttributeId::RsaModulus)?;
+        let pub_exponent = key_pair_object.ref_attribute(AttributeId::RsaPublicExponent)?;
+        let priv_exponent = key_pair_object.ref_attribute(AttributeId::RsaPrivateExponent)?;
+
+        let public_key = RsaPublicKey::new(key_size, &modulus, &pub_exponent)?;
+        let private_key = RsaPrivateKey::new(&priv_exponent)?;
+        Ok(Self::new(key_size, public_key, private_key))
+    }
+}
+impl TryInto<RsaKeyObject> for RsaKeyPair {
+    type Error = anyhow::Error;
+    fn try_into(self) -> Result<RsaKeyObject> {
+        let mut key_pair_object = RsaKeyObject::allocate_keypair_object(self.key_size())?;
+        let attrs = vec![
+            AttributeMemref::from_ref(AttributeId::RsaModulus, &self.public_key().modulus()).into(),
+            AttributeMemref::from_ref(
+                AttributeId::RsaPublicExponent,
+                &self.public_key().exponent(),
+            )
+            .into(),
+            AttributeMemref::from_ref(
+                AttributeId::RsaPrivateExponent,
+                &self.private_key().exponent(),
+            )
+            .into(),
+        ];
+        key_pair_object.populate(attrs.as_slice())?;
+        Ok(key_pair_object)
+    }
+}
+impl TryInto<RsaKeyObject> for RsaPublicKey {
+    type Error = anyhow::Error;
+    fn try_into(self) -> Result<RsaKeyObject> {
+        let mut key_pair_object = RsaKeyObject::allocate_public_key_object(self.key_size())?;
+        let attrs = vec![
+            AttributeMemref::from_ref(AttributeId::RsaModulus, &self.modulus()).into(),
+            AttributeMemref::from_ref(AttributeId::RsaPublicExponent, &self.exponent()).into(),
+        ];
+        key_pair_object.populate(attrs.as_slice())?;
+        Ok(key_pair_object)
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/secure_storage/src/optee/storage_backend.rs b/projects/web3/ConfidentialKlave/secure_storage/src/optee/storage_backend.rs
new file mode 100644
index 0000000..bdc01fd
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/secure_storage/src/optee/storage_backend.rs
@@ -0,0 +1,103 @@
+// 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<Vec<u8>> {
+    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 = object.read(&mut buf).unwrap();
+
+            if read_bytes != obj_info.data_size() as u32 {
+                bail!("[-] {:?}: failed to read data", &obj_id);
+            }
+
+            unsafe {
+                buf.set_len(read_bytes as usize);
+            }
+        }
+    }
+
+    Ok(buf)
+}
+
+pub fn delete_from_secure_storage(obj_id: Vec<u8>) -> Result<()> {
+    match PersistentObject::open(
+        ObjectStorageConstants::Private,
+        &mut obj_id.clone(),
+        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/ConfidentialKlave/secure_storage/src/secure_db.rs b/projects/web3/ConfidentialKlave/secure_storage/src/secure_db.rs
new file mode 100644
index 0000000..64dfc5d
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/secure_storage/src/secure_db.rs
@@ -0,0 +1,109 @@
+// 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 crate::{delete_from_secure_storage, load_from_secure_storage, save_in_secure_storage};
+use anyhow::{bail, ensure, Result};
+use basic_utils::println;
+use std::collections::{HashMap, HashSet};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct SecureStorageDb {
+    name: String,
+    key_list: HashSet<String>,
+}
+
+impl SecureStorageDb {
+    pub fn open(name: String) -> Result<Self> {
+        match load_from_secure_storage(name.as_bytes()) {
+            Ok(data) => {
+                let key_list = bincode::deserialize(&data)?;
+                Ok(Self { name, key_list })
+            }
+            Err(e) => {
+                println!(
+                    "[-] SecureStorage::load(): load error: {}. Create new...",
+                    e
+                );
+                Ok(Self {
+                    name,
+                    key_list: HashSet::new(),
+                })
+            }
+        }
+    }
+
+    pub fn put(&mut self, key: String, value: Vec<u8>) -> Result<()> {
+        match save_in_secure_storage(key.as_bytes(), &value) {
+            Ok(_) => {
+                self.key_list.insert(key);
+                self.store_key_list()?;
+            }
+            Err(e) => {
+                bail!("[+] SecureStorage::insert(): save error: {}", e);
+            }
+        };
+        Ok(())
+    }
+
+    pub fn get(&self, key: &str) -> Result<Vec<u8>> {
+        ensure!(self.key_list.contains(key), "Key not found");
+        match load_from_secure_storage(key.as_bytes()) {
+            Ok(data) => Ok(data),
+            Err(e) => {
+                bail!("[+] SecureStorage::get(): load error: {}", e);
+            }
+        }
+    }
+
+    pub fn delete(&mut self, key: String) -> Result<()> {
+        match delete_from_secure_storage(key.as_bytes().to_vec()) {
+            Ok(_) => {
+                self.key_list.remove(&key);
+                self.store_key_list()?;
+            }
+            Err(e) => {
+                bail!("[+] SecureStorage::delete(): delete error: {}", e);
+            }
+        };
+        Ok(())
+    }
+
+    pub fn clear(&mut self) -> Result<()> {
+        for key in self.key_list.clone() {
+            self.delete(key)?;
+        }
+        self.key_list.clear();
+        Ok(())
+    }
+
+    pub fn list_entries_with_prefix(&self, prefix: &str) -> Result<HashMap<String, Vec<u8>>> {
+        let mut result = HashMap::new();
+        for key in &self.key_list {
+            if key.starts_with(prefix) {
+                let value = self.get(key)?;
+                result.insert(key.clone(), value);
+            }
+        }
+        Ok(result)
+    }
+
+    fn store_key_list(&self) -> Result<()> {
+        let key_list = bincode::serialize(&self.key_list)?;
+        save_in_secure_storage(self.name.as_bytes(), &key_list)?;
+        Ok(())
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/secure_storage/src/tee_key.rs b/projects/web3/ConfidentialKlave/secure_storage/src/tee_key.rs
new file mode 100644
index 0000000..c1dd6a2
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/secure_storage/src/tee_key.rs
@@ -0,0 +1,202 @@
+// 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 crypto::{EcdsaKeyPair, ECDSA_P256_SHA256_ASN1_SIGNING};
+use serde::{Deserialize, Serialize};
+use std::convert::{TryFrom, TryInto};
+use types::{share::CkPublicKey, Storable};
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub enum TeeKeyUsage {
+    Backup,
+    Signing,
+}
+
+impl From<TeeKeyUsage> for String {
+    fn from(usage: TeeKeyUsage) -> String {
+        (&usage).into()
+    }
+}
+
+impl From<&TeeKeyUsage> for String {
+    fn from(usage: &TeeKeyUsage) -> String {
+        match usage {
+            TeeKeyUsage::Backup => "keystore-backup".to_string(),
+            TeeKeyUsage::Signing => "keystore-signing".to_string(),
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct TeeKey {
+    usage: TeeKeyUsage, // as storage key
+    object: TeeKeyObject,
+}
+
+impl TeeKey {
+    pub fn new(usage: TeeKeyUsage, object: TeeKeyObject) -> Result<Self> {
+        Ok(Self { usage, object })
+    }
+
+    pub fn object(&self) -> TeeKeyObject {
+        self.object.clone()
+    }
+}
+
+impl Storable<String> for TeeKey {
+    fn unique_id(&self) -> String {
+        self.usage.clone().into()
+    }
+}
+
+impl TryFrom<TeeKey> for CkPublicKey {
+    type Error = anyhow::Error;
+
+    fn try_from(tee_key: TeeKey) -> Result<Self> {
+        match tee_key.object {
+            TeeKeyObject::RsaKeyPair(key_pair) => key_pair.export_public_key(),
+            TeeKeyObject::RsaPublicKey(public_key) => public_key.clone().try_into(),
+            TeeKeyObject::EcdsaKeyPair(ecdsa_key_pair_bytes) => {
+                let kp = EcdsaKeyPair::try_from(ecdsa_key_pair_bytes)?;
+                Ok(CkPublicKey::from(kp.pub_key().to_vec()))
+            }
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub enum TeeKeyObject {
+    RsaKeyPair(RsaKeyPair),          // for encrypting backup data key
+    RsaPublicKey(RsaPublicKey),      // for decrypting backup data key
+    EcdsaKeyPair(EcdsaKeyPairBytes), // for signing TLS end certificate, as intermediate CA
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct EcdsaKeyPairBytes {
+    pub pkcs8_bytes: Vec<u8>,
+}
+
+impl TryFrom<EcdsaKeyPairBytes> for EcdsaKeyPair {
+    type Error = anyhow::Error;
+
+    fn try_from(ecdsa_key_pair_bytes: EcdsaKeyPairBytes) -> Result<Self> {
+        let key_pair = EcdsaKeyPair::from_bytes(
+            &ecdsa_key_pair_bytes.pkcs8_bytes,
+            &ECDSA_P256_SHA256_ASN1_SIGNING,
+        )?;
+        Ok(key_pair)
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct RsaKeyPair {
+    key_size: usize, // The number of bits in the modulus. can be 256, ..., 4096
+    public_key: RsaPublicKey,
+    private_key: RsaPrivateKey,
+}
+
+impl RsaKeyPair {
+    pub fn new(key_size: usize, public_key: RsaPublicKey, private_key: RsaPrivateKey) -> Self {
+        Self {
+            key_size,
+            public_key,
+            private_key,
+        }
+    }
+
+    pub fn export_public_key(&self) -> Result<CkPublicKey> {
+        self.public_key.clone().try_into()
+    }
+
+    pub fn public_key(&self) -> &RsaPublicKey {
+        &self.public_key
+    }
+
+    pub fn private_key(&self) -> &RsaPrivateKey {
+        &self.private_key
+    }
+
+    pub fn key_size(&self) -> usize {
+        self.key_size
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct RsaPrivateKey {
+    exponent: Vec<u8>,
+}
+
+impl RsaPrivateKey {
+    pub fn new(e: &[u8]) -> Result<Self> {
+        Ok(Self {
+            exponent: e.to_vec(),
+        })
+    }
+
+    pub fn exponent(&self) -> &[u8] {
+        &self.exponent
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct RsaPublicKey {
+    key_size: usize,
+    modulus: Vec<u8>,
+    exponent: Vec<u8>,
+}
+
+impl RsaPublicKey {
+    pub fn new(key_size: usize, m: &[u8], e: &[u8]) -> Result<Self> {
+        Ok(Self {
+            key_size,
+            modulus: m.to_vec(),
+            exponent: e.to_vec(),
+        })
+    }
+
+    pub fn key_size(&self) -> usize {
+        self.key_size
+    }
+
+    pub fn modulus(&self) -> &[u8] {
+        &self.modulus
+    }
+
+    pub fn exponent(&self) -> &[u8] {
+        &self.exponent
+    }
+}
+
+impl TryFrom<RsaPublicKey> for CkPublicKey {
+    type Error = anyhow::Error;
+
+    fn try_from(rsa_public_key: RsaPublicKey) -> Result<Self> {
+        // traditional RSA public key format: modulus + exponent
+        // we add a 4-byte prefix to indicate the key size
+        let bytes = bincode::serialize(&rsa_public_key).map_err(|e| anyhow!(e))?;
+        Ok(CkPublicKey::from(bytes))
+    }
+}
+
+impl TryFrom<CkPublicKey> for RsaPublicKey {
+    type Error = anyhow::Error;
+
+    fn try_from(ck_public_key: CkPublicKey) -> Result<Self> {
+        bincode::deserialize(ck_public_key.as_bytes()).map_err(|e| anyhow!(e))
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/tee-cli/Cargo.toml b/projects/web3/ConfidentialKlave/tee-cli/Cargo.toml
new file mode 100644
index 0000000..7e94f38
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tee-cli/Cargo.toml
@@ -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.
+
+[package]
+name = "tee-cli"
+version = "0.2.0"
+authors = ["Teaclave Contributors <de...@teaclave.apache.org>"]
+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" }
+types = { path = "../types" }
+constants = { path = "../constants" }
+optee-teec = { path = "../../incubator-teaclave-trustzone-sdk/optee-teec" }
+libc = "0.2.48"
+anyhow = "1.0"
+bincode = "1.3.3"
+hex = "0.3.1"
+structopt = "0.3"
+chrono = "0.4"
+sha3 = "0.10.6"
+serde = { version = "1.0", features = ["derive"] }
+base64 = "0.21"
+
+[profile.release]
+lto = true
\ No newline at end of file
diff --git a/projects/web3/ConfidentialKlave/tee-cli/Makefile b/projects/web3/ConfidentialKlave/tee-cli/Makefile
new file mode 100644
index 0000000..d9dd99a
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tee-cli/Makefile
@@ -0,0 +1,51 @@
+# 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 := tee-cli
+ARCH ?= aarch64
+
+OPTEE_DIR ?= ../../../optee
+
+ifeq ($(ARCH), arm)
+	OPTEE_BIN := $(OPTEE_DIR)/toolchains/aarch32/bin
+	OBJCOPY := $(OPTEE_BIN)/arm-linux-gnueabihf-objcopy
+	TARGET := arm-unknown-linux-gnueabihf
+else
+	OPTEE_BIN := $(OPTEE_DIR)/toolchains/$(ARCH)/bin
+	OBJCOPY := $(OPTEE_BIN)/aarch64-linux-gnu-objcopy
+	TARGET := aarch64-unknown-linux-gnu
+endif
+
+OUT_DIR := $(CURDIR)/target/$(TARGET)/release
+
+
+all: fmt host strip
+
+host:
+	@cargo build --target $(TARGET) --release
+
+strip:
+	@$(OBJCOPY) --strip-unneeded $(OUT_DIR)/$(NAME) $(OUT_DIR)/$(NAME)
+
+fmt: 
+	@cargo fmt
+
+clippy: 
+	@cargo clippy --target $(TARGET) --release --verbose
+
+clean:
+	@cargo clean
diff --git a/projects/web3/ConfidentialKlave/tee-cli/src/cli.rs b/projects/web3/ConfidentialKlave/tee-cli/src/cli.rs
new file mode 100644
index 0000000..0b85f78
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tee-cli/src/cli.rs
@@ -0,0 +1,65 @@
+// 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::path::PathBuf;
+use structopt::StructOpt;
+
+#[derive(Debug, StructOpt)]
+pub struct InitBoardOpt {}
+
+#[derive(Debug, StructOpt)]
+pub struct BackupWalletOpt {
+    /// backup devices' public keys signed by authority
+    #[structopt(short, long, required = true)]
+    pub signed_backup_list: PathBuf,
+}
+
+#[derive(Debug, StructOpt)]
+pub struct RestoreWalletOpt {
+    /// encrypted data
+    #[structopt(short, long, required = true)]
+    pub encrypted_data: PathBuf,
+}
+
+#[derive(Debug, StructOpt)]
+pub struct ClearWalletStorageOpt {}
+
+#[derive(Debug, StructOpt)]
+pub enum Command {
+    /// Initialize board, generate keypairs and return the public keys. If the board is already initialized, return the public keys.
+    #[structopt(name = "init-board")]
+    InitBoard(InitBoardOpt),
+
+    /// Backup wallet data
+    #[structopt(name = "backup-wallets")]
+    BackupWallet(BackupWalletOpt),
+
+    /// Restore wallet data
+    #[structopt(name = "restore-wallets")]
+    RestoreWallet(RestoreWalletOpt),
+
+    /// Clear wallet storage
+    #[structopt(name = "clear-wallet-storage")]
+    ClearWalletStorage(ClearWalletStorageOpt),
+}
+
+#[derive(Debug, StructOpt)]
+#[structopt(name = "device-cli-tool", about = "CK device management tool.")]
+pub struct Opt {
+    #[structopt(subcommand)]
+    pub command: Command,
+}
diff --git a/projects/web3/ConfidentialKlave/tee-cli/src/main.rs b/projects/web3/ConfidentialKlave/tee-cli/src/main.rs
new file mode 100644
index 0000000..8876ef9
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tee-cli/src/main.rs
@@ -0,0 +1,93 @@
+// 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;
+
+use anyhow::Result;
+use base64::{engine::general_purpose, Engine as _};
+use constants::{OUTPUT_MAX_SIZE, TLS_UUID};
+use optee_teec::{Context, Operation, ParamType, Uuid};
+use optee_teec::{ParamTmpRef, ParamValue};
+use proto::{BackupWalletOutput, InitBoardOutput, RestoreWalletOutput, TaCommand};
+use std::fs;
+use structopt::StructOpt;
+use types::share::DeviceID;
+
+fn invoke_command(command: TaCommand, input: &[u8]) -> optee_teec::Result<Vec<u8>> {
+    let mut ctx = Context::new()?;
+    let uuid = Uuid::parse_str(TLS_UUID)
+        .map_err(|_| optee_teec::Error::new(optee_teec::ErrorKind::ItemNotFound))?;
+    let mut session = ctx.open_session(uuid)?;
+
+    println!("CA: command: {:?}", command);
+    // reserved for tls session id
+    let p0 = ParamValue::new(0, 0, ParamType::ValueInput);
+    // input buffer
+    let p1 = ParamTmpRef::new_input(input);
+
+    // output buffer
+    let mut output = vec![0u8; OUTPUT_MAX_SIZE];
+    let p2 = ParamTmpRef::new_output(output.as_mut_slice());
+    // output buffer size
+    let p3 = ParamValue::new(0, 0, ParamType::ValueInout);
+
+    let mut operation = Operation::new(0, p0, p1, p2, p3);
+    session.invoke_command(command as u32, &mut operation)?;
+
+    let output_len = operation.parameters().3.a() as usize;
+    Ok(output[..output_len].to_vec())
+}
+
+fn main() -> Result<()> {
+    let args = cli::Opt::from_args();
+    match args.command {
+        cli::Command::InitBoard(_opt) => {
+            let serialized_output = invoke_command(TaCommand::InitBoard, &[0u8])?;
+            let output: InitBoardOutput = bincode::deserialize(&serialized_output)?;
+            let device_id: DeviceID = output.signing_pubkey.into();
+            assert_eq!(device_id, output.device_id);
+            let device_id_string: String = output.device_id.into();
+            let encoded_pubkeys = general_purpose::STANDARD.encode(&serialized_output);
+            let output_file_name = device_id_string + ".pubkeys";
+            fs::write(&output_file_name, &encoded_pubkeys)?;
+            println!("Encoded Public keys: \n{}", &encoded_pubkeys);
+            println!("Encoded Public keys written to {}", output_file_name);
+        }
+        cli::Command::BackupWallet(opt) => {
+            let signed_backup_list = fs::read(&opt.signed_backup_list)?; // serialized BackupWalletInput
+            let serialized_output = invoke_command(TaCommand::BackupWallet, &signed_backup_list)?;
+            let output: BackupWalletOutput = bincode::deserialize(&serialized_output)?;
+
+            let device_id: String = output.backup_to_device.clone().into();
+            let data_file_name = device_id + ".encrypted";
+            // write serialized EncryptedDataOutput to file
+            fs::write(&data_file_name, serialized_output)?;
+            println!("Encrypted data written to {}", data_file_name);
+        }
+        cli::Command::RestoreWallet(opt) => {
+            let encrypted_data = fs::read(&opt.encrypted_data)?;
+            let serialized_output = invoke_command(TaCommand::RestoreWallet, &encrypted_data)?;
+            let output: RestoreWalletOutput = bincode::deserialize(&serialized_output)?;
+            println!("Restored wallets: {:?}", output.wallet_ids);
+        }
+        cli::Command::ClearWalletStorage(_opt) => {
+            let _ = invoke_command(TaCommand::ClearWalletStorage, &[0u8])?;
+            println!("ClearWalletStorage finished");
+        }
+    }
+    Ok(())
+}
diff --git a/projects/web3/ConfidentialKlave/tls-host/Cargo.toml b/projects/web3/ConfidentialKlave/tls-host/Cargo.toml
new file mode 100644
index 0000000..c225065
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-host/Cargo.toml
@@ -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.
+
+[package]
+name = "tee-wallet-service"
+version = "0.2.0"
+authors = ["Teaclave Contributors <de...@teaclave.apache.org>"]
+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 = "0.2.48"
+proto = { path = "../proto" }
+constants = { path = "../constants" }
+optee-teec = { path = "../../incubator-teaclave-trustzone-sdk/optee-teec" }
+structopt = "0.3"
+anyhow = "1.0"
+tokio = { version = "1.0", features = ["full"] }
+
+[profile.release]
+lto = true
diff --git a/projects/web3/ConfidentialKlave/tls-host/Makefile b/projects/web3/ConfidentialKlave/tls-host/Makefile
new file mode 100644
index 0000000..a246e36
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-host/Makefile
@@ -0,0 +1,45 @@
+# 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 := tee-wallet-service
+ARCH ?= aarch64
+
+OPTEE_DIR ?= ../../../optee
+
+ifeq ($(ARCH), arm)
+	OPTEE_BIN := $(OPTEE_DIR)/toolchains/aarch32/bin
+	OBJCOPY := $(OPTEE_BIN)/arm-linux-gnueabihf-objcopy
+	TARGET := arm-unknown-linux-gnueabihf
+else
+	OPTEE_BIN := $(OPTEE_DIR)/toolchains/$(ARCH)/bin
+	OBJCOPY := $(OPTEE_BIN)/aarch64-linux-gnu-objcopy
+	TARGET := aarch64-unknown-linux-gnu
+endif
+
+OUT_DIR := $(CURDIR)/target/$(TARGET)/release
+
+
+all: host strip
+
+host:
+	@cargo build --target $(TARGET) --release
+
+strip:
+	@$(OBJCOPY) --strip-unneeded $(OUT_DIR)/$(NAME) $(OUT_DIR)/$(NAME)
+
+clean:
+	@cargo clean
diff --git a/projects/web3/ConfidentialKlave/tls-host/src/cli.rs b/projects/web3/ConfidentialKlave/tls-host/src/cli.rs
new file mode 100644
index 0000000..637ddc2
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-host/src/cli.rs
@@ -0,0 +1,39 @@
+// 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::path::PathBuf;
+use structopt::StructOpt;
+
+#[derive(Debug, StructOpt)]
+pub struct StartServerOpt {
+    #[structopt(short, long, required = true)]
+    pub tee_cert: PathBuf,
+}
+
+#[derive(Debug, StructOpt)]
+pub enum Command {
+    /// Start server
+    #[structopt(name = "start-server")]
+    StartServer(StartServerOpt),
+}
+
+#[derive(Debug, StructOpt)]
+#[structopt(name = "tee-wallet-service", about = "CK TEE wallet service.")]
+pub struct Opt {
+    #[structopt(subcommand)]
+    pub command: Command,
+}
diff --git a/projects/web3/ConfidentialKlave/tls-host/src/main.rs b/projects/web3/ConfidentialKlave/tls-host/src/main.rs
new file mode 100644
index 0000000..4fee9ec
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-host/src/main.rs
@@ -0,0 +1,123 @@
+// 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 ta_connect_service;
+use ta_connect_service::*;
+mod proxy_service;
+use proxy_service::*;
+mod ta_tls_command;
+
+use std::fs;
+use structopt::StructOpt;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::net::{TcpListener, TcpStream};
+use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
+
+use anyhow::Result;
+
+const MAX_PAYLOAD: u16 = 16384 + 2048;
+const HEADER_SIZE: u16 = 1 + 2 + 2;
+pub const MAX_WIRE_SIZE: usize = (MAX_PAYLOAD + HEADER_SIZE) as usize;
+
+#[tokio::main]
+async fn main() -> optee_teec::Result<()> {
+    let args = cli::Opt::from_args();
+    let tee_cert = match args.command {
+        cli::Command::StartServer(opt) => {
+            let tee_cert = match fs::read(&opt.tee_cert) {
+                Ok(file) => file,
+                Err(e) => {
+                    println!("Error reading tee_cert: {}", e);
+                    return Ok(());
+                }
+            };
+            tee_cert
+        }
+    };
+
+    let (sender, receiver) = unbounded_channel();
+    std::thread::spawn(move || match TaConnectService::start(tee_cert, receiver) {
+        Ok(_) => {}
+        Err(e) => {
+            println!("Error starting TaConnectService: {}", e);
+        }
+    });
+    println!("ta_connect_service started");
+
+    // listen for incoming connections
+    println!("listening");
+    let listener = TcpListener::bind("0.0.0.0:4433").await.unwrap();
+    let mut session_id: u32 = 0;
+    assert!(!sender.is_closed());
+    loop {
+        let sender_dup = sender.clone();
+        match listener.accept().await {
+            Ok((stream, _)) => {
+                session_id += 1;
+                println!("new client, session_id: {}", session_id);
+                tokio::spawn(async move {
+                    match handle_client(sender_dup, session_id, stream).await {
+                        Ok(_) => {}
+                        Err(e) => {
+                            println!("Error handling client: {}", e);
+                        }
+                    }
+                });
+            }
+            Err(e) => eprintln!("Error accepting connection: {:?}", e),
+        }
+    }
+}
+
+async fn handle_client(
+    sender: UnboundedSender<ProxyRequest>,
+    session_id: u32,
+    mut stream: TcpStream,
+) -> Result<()> {
+    let mut proxy_service = ProxyService::new(sender, session_id);
+    proxy_service.new_tls_session().await?;
+    println!("new session: {}", session_id);
+    loop {
+        let mut buf = [0u8; MAX_WIRE_SIZE];
+        match stream.read(&mut buf).await {
+            Ok(0) | Err(_) => {
+                proxy_service.close_tls_session().await?;
+                println!("close session: {}", session_id);
+                break Ok(());
+            }
+            Ok(n) => {
+                println!("read bytes: {}, session_id: {}", n, session_id);
+                proxy_service.send_to_ta(buf[..n].to_vec()).await?;
+                println!("do_tls_read finished");
+            }
+        }
+        let buf = proxy_service.receive_from_ta().await?;
+        println!(
+            "do_tls_write finished, session_id: {}, buf len: {}",
+            session_id,
+            buf.len()
+        );
+        let res = stream.write_all(&buf).await;
+        if res.is_err() {
+            println!("error: {:?}, close tls session: {}", res, session_id);
+            proxy_service.close_tls_session().await?;
+            println!("close stream finished");
+            break Ok(());
+        }
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/tls-host/src/proxy_service.rs b/projects/web3/ConfidentialKlave/tls-host/src/proxy_service.rs
new file mode 100644
index 0000000..7507222
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-host/src/proxy_service.rs
@@ -0,0 +1,89 @@
+// 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::Result;
+use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
+
+use crate::ta_tls_command::{TaTlsRequest, TaTlsRequestCommand, TaTlsResponse};
+
+pub struct ProxyRequest {
+    pub request: TaTlsRequest,
+    pub xsender: UnboundedSender<Result<TaTlsResponse>>,
+}
+
+pub struct ProxyService {
+    sender: UnboundedSender<ProxyRequest>,
+    session_id: u32,
+}
+impl ProxyService {
+    pub fn new(sender: UnboundedSender<ProxyRequest>, session_id: u32) -> Self {
+        ProxyService { sender, session_id }
+    }
+
+    pub async fn new_tls_session(&mut self) -> Result<()> {
+        let ta_tls_request = TaTlsRequest {
+            cmd: TaTlsRequestCommand::NewTlsSession,
+            session_id: self.session_id,
+            buffer: Vec::new(),
+        };
+        self.handle_request(ta_tls_request).await?;
+        Ok(())
+    }
+
+    pub async fn close_tls_session(&mut self) -> Result<()> {
+        let ta_tls_request = TaTlsRequest {
+            cmd: TaTlsRequestCommand::CloseTlsSession,
+            session_id: self.session_id,
+            buffer: Vec::new(),
+        };
+        self.handle_request(ta_tls_request).await?;
+        Ok(())
+    }
+
+    pub async fn send_to_ta(&mut self, buffer: Vec<u8>) -> Result<()> {
+        let ta_tls_request = TaTlsRequest {
+            cmd: TaTlsRequestCommand::SendToTa,
+            session_id: self.session_id,
+            buffer,
+        };
+        self.handle_request(ta_tls_request).await?;
+        Ok(())
+    }
+
+    pub async fn receive_from_ta(&mut self) -> Result<Vec<u8>> {
+        let ta_tls_request = TaTlsRequest {
+            cmd: TaTlsRequestCommand::ReceiveFromTa,
+            session_id: self.session_id,
+            buffer: Vec::new(),
+        };
+        let response = self.handle_request(ta_tls_request).await?;
+        Ok(response.buffer)
+    }
+
+    async fn handle_request(&mut self, request: TaTlsRequest) -> Result<TaTlsResponse> {
+        let (xsender, mut xreceiver) = unbounded_channel();
+        println!("x_channel created");
+        let proxy_request = ProxyRequest { request, xsender };
+        self.sender
+            .send(proxy_request)
+            .map_err(|e| anyhow::anyhow!("Error sending proxy request: {}", e))?;
+        xreceiver
+            .recv()
+            .await
+            .unwrap_or_else(|| anyhow::bail!("Error receiving proxy response"))
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/tls-host/src/ta_connect_service.rs b/projects/web3/ConfidentialKlave/tls-host/src/ta_connect_service.rs
new file mode 100644
index 0000000..0fed47b
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-host/src/ta_connect_service.rs
@@ -0,0 +1,160 @@
+// 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;
+use constants::TLS_UUID;
+use optee_teec::{Context, Operation, Session, Uuid};
+use optee_teec::{ParamNone, ParamTmpRef, ParamType, ParamValue};
+use proto::TaCommand;
+use tokio::sync::mpsc::UnboundedReceiver;
+
+use crate::proxy_service::ProxyRequest;
+use crate::ta_tls_command::{TaTlsRequestCommand, TaTlsResponse};
+
+const MAX_PAYLOAD: u16 = 16384 + 2048;
+const HEADER_SIZE: u16 = 1 + 2 + 2;
+pub const MAX_WIRE_SIZE: usize = (MAX_PAYLOAD + HEADER_SIZE) as usize;
+
+pub struct TaConnectService {}
+impl TaConnectService {
+    pub fn start(
+        tee_cert: Vec<u8>,
+        mut receiver: UnboundedReceiver<ProxyRequest>,
+    ) -> optee_teec::Result<()> {
+        println!("TaConnectService started");
+        let mut ctx = Context::new()?;
+        println!("Context created");
+        let uuid = Uuid::parse_str(TLS_UUID).unwrap();
+        let mut ta_session = ctx.open_session(uuid)?;
+        println!("Session opened");
+
+        let session_id: u32 = 0;
+        let p0 = ParamValue::new(session_id, 0, ParamType::ValueInput);
+
+        let p1 = ParamTmpRef::new_input(&tee_cert);
+        let mut operation = Operation::new(0, p0, p1, ParamNone, ParamNone);
+        ta_session.invoke_command(TaCommand::StartServer as u32, &mut operation)?;
+        println!("TA invoked StartServer");
+        loop {
+            let request = match receiver.blocking_recv() {
+                Some(request) => {
+                    println!(
+                        "TaConnectService received request, cmd: {:?}",
+                        request.request.cmd
+                    );
+                    request
+                }
+                None => continue,
+            };
+            let ta_tls_request = request.request;
+            let xsender = request.xsender;
+            let response = match ta_tls_request.cmd {
+                TaTlsRequestCommand::NewTlsSession => {
+                    match new_tls_session(&mut ta_session, ta_tls_request.session_id) {
+                        Ok(_) => Ok(TaTlsResponse { buffer: Vec::new() }),
+                        Err(e) => Err(anyhow!(
+                            "Error invoking new_tls_session, session id: {}, error: {}",
+                            ta_tls_request.session_id,
+                            e
+                        )),
+                    }
+                }
+                TaTlsRequestCommand::SendToTa => {
+                    match tls_send_to_ta(
+                        &mut ta_session,
+                        ta_tls_request.session_id,
+                        &ta_tls_request.buffer,
+                    ) {
+                        Ok(_) => Ok(TaTlsResponse { buffer: Vec::new() }),
+                        Err(e) => Err(anyhow!(
+                            "Error invoking tls_send_to_ta, session id: {}, error: {}",
+                            ta_tls_request.session_id,
+                            e
+                        )),
+                    }
+                }
+                TaTlsRequestCommand::ReceiveFromTa => {
+                    match tls_receive_from_ta(&mut ta_session, ta_tls_request.session_id) {
+                        Ok(buf) => Ok(TaTlsResponse { buffer: buf }),
+                        Err(e) => Err(anyhow!(
+                            "Error invoking tls_receive_from_ta, session id: {}, error: {}",
+                            ta_tls_request.session_id,
+                            e
+                        )),
+                    }
+                }
+                TaTlsRequestCommand::CloseTlsSession => {
+                    match close_tls_session(&mut ta_session, ta_tls_request.session_id) {
+                        Ok(_) => Ok(TaTlsResponse { buffer: Vec::new() }),
+                        Err(e) => Err(anyhow!(
+                            "Error invoking close_tls_session, session id: {}, error: {}",
+                            ta_tls_request.session_id,
+                            e
+                        )),
+                    }
+                }
+            };
+            match xsender.send(response) {
+                Ok(_) => {}
+                Err(e) => {
+                    println!("Error sending response: {}", e);
+                }
+            }
+        }
+    }
+}
+
+fn new_tls_session(ta_session: &mut Session, session_id: u32) -> optee_teec::Result<Vec<u8>> {
+    let p0 = ParamValue::new(session_id, 0, ParamType::ValueInput);
+    let mut operation = Operation::new(0, p0, ParamNone, ParamNone, ParamNone);
+    ta_session.invoke_command(TaCommand::NewTlsSession as u32, &mut operation)?;
+    println!("after invoking TA: new_tls_session");
+    Ok(Vec::new())
+}
+
+fn tls_send_to_ta(
+    ta_session: &mut Session,
+    session_id: u32,
+    buf: &[u8],
+) -> optee_teec::Result<Vec<u8>> {
+    let p0 = ParamValue::new(session_id, 0, ParamType::ValueInput);
+    let p1 = ParamTmpRef::new_input(buf);
+    let mut operation = Operation::new(0, p0, p1, ParamNone, ParamNone);
+    ta_session.invoke_command(TaCommand::DoTlsRead as u32, &mut operation)?;
+    println!("after invoking TA: do_tls_read");
+    Ok(Vec::new())
+}
+
+fn tls_receive_from_ta(ta_session: &mut Session, session_id: u32) -> optee_teec::Result<Vec<u8>> {
+    let mut buf = [0u8; MAX_WIRE_SIZE];
+    let p0 = ParamValue::new(session_id, 0, ParamType::ValueInput);
+    let p1 = ParamTmpRef::new_output(&mut buf);
+    let p2 = ParamValue::new(0, 0, ParamType::ValueOutput);
+    let mut operation = Operation::new(0, p0, p1, p2, ParamNone);
+    ta_session.invoke_command(TaCommand::DoTlsWrite as u32, &mut operation)?;
+    println!("after invoking TA: do_tls_write");
+    let buffer_size = operation.parameters().2.a() as usize;
+    Ok(buf[..buffer_size].to_vec())
+}
+
+fn close_tls_session(ta_session: &mut Session, session_id: u32) -> optee_teec::Result<()> {
+    let p0 = ParamValue::new(session_id, 0, ParamType::ValueInput);
+    let mut operation = Operation::new(0, p0, ParamNone, ParamNone, ParamNone);
+    ta_session.invoke_command(TaCommand::CloseTlsSession as u32, &mut operation)?;
+    println!("after invoking TA: close session");
+    Ok(())
+}
diff --git a/projects/web3/ConfidentialKlave/tls-host/src/ta_tls_command.rs b/projects/web3/ConfidentialKlave/tls-host/src/ta_tls_command.rs
new file mode 100644
index 0000000..8ca1355
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-host/src/ta_tls_command.rs
@@ -0,0 +1,34 @@
+// 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.
+
+#[derive(Debug)]
+pub enum TaTlsRequestCommand {
+    NewTlsSession,
+    CloseTlsSession,
+    SendToTa,
+    ReceiveFromTa,
+}
+
+pub struct TaTlsRequest {
+    pub session_id: u32,
+    pub cmd: TaTlsRequestCommand,
+    pub buffer: Vec<u8>,
+}
+
+pub struct TaTlsResponse {
+    pub buffer: Vec<u8>,
+}
diff --git a/projects/web3/ConfidentialKlave/tls-ta/Cargo.toml b/projects/web3/ConfidentialKlave/tls-ta/Cargo.toml
new file mode 100644
index 0000000..0289808
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-ta/Cargo.toml
@@ -0,0 +1,64 @@
+# 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 = "tls-ta"
+version = "0.2.0"
+authors = ["Teaclave Contributors <de...@teaclave.apache.org>"]
+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 = "../../incubator-teaclave-trustzone-sdk/rust/libc" }
+proto = { path = "../proto" }
+core = { path = "../core" }
+optee-utee-sys = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee/optee-utee-sys" }
+optee-utee = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee" }
+types = { path = "../types" }
+secure_storage = { path = "../secure_storage" }
+
+serde = "=1.0.163"
+serde_json = "1.0"
+anyhow = "1.0"
+bincode = "1.3.3"
+zeroize = "=1.6.0"
+ring = "=0.16.20"
+sct = "=0.7.0"
+lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
+
+[features]
+default = []
+enable_clear_storage = []
+
+[build_dependencies]
+uuid = { version = "0.8" }
+constants = { path = "../constants" }
+
+[profile.release]
+lto = true
+debug = false
+codegen-units = 1
+
+[patch."https://github.com/apache/incubator-teaclave-trustzone-sdk.git"]
+optee-utee = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee" }
+libc = { path = "../../incubator-teaclave-trustzone-sdk/rust/libc" }
+
+[patch.crates-io]
+proc-macro2 = { git = "https://github.com/DemesneGH/proc-macro2.git" }
+ring = { git = "https://github.com/DemesneGH/ring-optee.git", branch = "0.16.20-optee" }
diff --git a/projects/web3/ConfidentialKlave/tls-ta/Makefile b/projects/web3/ConfidentialKlave/tls-ta/Makefile
new file mode 100644
index 0000000..17d51e5
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-ta/Makefile
@@ -0,0 +1,60 @@
+# 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.
+
+OPTEE_DIR ?= ../../../optee
+OPTEE_OS_DIR ?= $(OPTEE_DIR)/optee_os
+UUID ?= $(shell cat "../tls_uuid.txt")
+
+ARCH ?= aarch64
+
+ifeq ($(ARCH), arm)
+	TA_SIGN_KEY ?= $(OPTEE_OS_DIR)/out/arm/export-ta_arm32/keys/default_ta.pem
+	SIGN := $(OPTEE_OS_DIR)/out/arm/export-ta_arm32/scripts/sign_encrypt.py
+	OPTEE_BIN := $(OPTEE_DIR)/toolchains/aarch32/bin
+	OBJCOPY := $(OPTEE_BIN)/arm-linux-gnueabihf-objcopy
+	TARGET := arm-unknown-optee-trustzone
+else
+	TA_SIGN_KEY ?= $(OPTEE_OS_DIR)/out/arm/export-ta_arm64/keys/default_ta.pem
+	SIGN := $(OPTEE_OS_DIR)/out/arm/export-ta_arm64/scripts/sign_encrypt.py
+	OPTEE_BIN := $(OPTEE_DIR)/toolchains/$(ARCH)/bin
+	OBJCOPY := $(OPTEE_BIN)/aarch64-linux-gnu-objcopy
+	TARGET := aarch64-unknown-optee-trustzone
+endif
+
+OUT_DIR := $(CURDIR)/target/$(TARGET)/release
+BIN_NAME := $(shell cargo metadata --no-deps --format-version 1 | grep -oP '(?<="name":")[^"]+' | sed -n 1p)
+
+all: fmt ta strip sign
+
+ta:
+	@xargo build --target $(TARGET) --release --verbose
+
+strip:
+	@$(OBJCOPY) --strip-unneeded $(OUT_DIR)/$(BIN_NAME) $(OUT_DIR)/stripped_ta
+
+sign:
+	@$(SIGN) --uuid $(UUID) --key $(TA_SIGN_KEY) --in $(OUT_DIR)/stripped_ta --out $(OUT_DIR)/$(UUID).ta
+	@echo "SIGN =>  ${UUID}"
+
+fmt: 
+	@cargo fmt
+
+clippy: 
+	@xargo clippy --target $(TARGET) --release --verbose
+
+clean:
+	@xargo clean
diff --git a/projects/web3/ConfidentialKlave/tls-ta/Xargo.toml b/projects/web3/ConfidentialKlave/tls-ta/Xargo.toml
new file mode 100644
index 0000000..d48b140
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-ta/Xargo.toml
@@ -0,0 +1,25 @@
+# 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 = "../../incubator-teaclave-trustzone-sdk/rust/rust/library/std"
+
+[patch.crates-io]
+libc =  { path = "../../incubator-teaclave-trustzone-sdk/rust/libc" }
+compiler_builtins =  { path = "../../incubator-teaclave-trustzone-sdk/rust/compiler-builtins" }
+rustc-std-workspace-core = { path = "../../incubator-teaclave-trustzone-sdk/rust/rust/library/rustc-std-workspace-core" }
+rustc-std-workspace-alloc = { path = "../../incubator-teaclave-trustzone-sdk/rust/rust/library/rustc-std-workspace-alloc" }
diff --git a/projects/web3/ConfidentialKlave/tls-ta/build.rs b/projects/web3/ConfidentialKlave/tls-ta/build.rs
new file mode 100644
index 0000000..9eaf594
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-ta/build.rs
@@ -0,0 +1,62 @@
+// 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 constants;
+use std::env;
+use std::fs::File;
+use std::io::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(constants::TLS_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 optee_os_dir = env::var("OPTEE_OS_DIR").unwrap_or("../../../optee/optee_os".to_string());
+    let search_path = match env::var("ARCH") {
+        Ok(ref v) if v == "arm" => {
+            File::create(out.join("ta.lds"))?.write_all(include_bytes!("ta_arm.lds"))?;
+            Path::new(&optee_os_dir).join("out/arm/export-ta_arm32/lib")
+        }
+        _ => {
+            File::create(out.join("ta.lds"))?.write_all(include_bytes!("ta_aarch64.lds"))?;
+            Path::new(&optee_os_dir).join("out/arm/export-ta_arm64/lib")
+        }
+    };
+    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");
+    Ok(())
+}
diff --git a/projects/web3/ConfidentialKlave/tls-ta/rust-toolchain.toml b/projects/web3/ConfidentialKlave/tls-ta/rust-toolchain.toml
new file mode 100644
index 0000000..d482968
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-ta/rust-toolchain.toml
@@ -0,0 +1,23 @@
+# 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.
+
+# Toolchain override for rustup
+
+[toolchain]
+channel = "nightly-2021-09-20"
+components = ["rust-src"]
+targets = ["aarch64-unknown-linux-gnu", "arm-unknown-linux-gnueabihf"]
diff --git a/projects/web3/ConfidentialKlave/tls-ta/src/main.rs b/projects/web3/ConfidentialKlave/tls-ta/src/main.rs
new file mode 100644
index 0000000..c854947
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-ta/src/main.rs
@@ -0,0 +1,296 @@
+// 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]
+
+use anyhow::{bail, ensure, Result};
+use core::{
+    backup_wallet, init_board, restore_wallet, Approver, InterCertConfig, StateManager, System,
+    TaUser, TlsContext, TlsSessionManager, TxOperator, UserRegistry,
+};
+use lazy_static::lazy_static;
+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::{
+    ApproveTransactionInput, ApproveTransactionOutput, BackupWalletInput, CreateTransactionInput,
+    CreateTransactionOutput, GetTeeStatusOutput, ListPendingTransactionInput,
+    ListPendingTransactionOutput, RecallTransactionInput, RecallTransactionOutput,
+    RestoreWalletInput, SignTransactionInput, SignTransactionOutput, SyncWithTeeInput,
+    SyncWithTeeOutput, TaCommand, TlsCommandRequest,
+};
+use serde::Serialize;
+use std::convert::TryInto;
+use std::io::Write;
+use std::sync::RwLock;
+use types::share::{ApprovalOperation, CkHash};
+
+lazy_static! {
+    static ref TLS_SESSION_MANAGER: RwLock<TlsSessionManager> =
+        RwLock::new(TlsSessionManager::init());
+    static ref STATE_MANAGER: RwLock<StateManager> = RwLock::new(StateManager::init().unwrap());
+    static ref USER_REGISTRY: RwLock<UserRegistry> = RwLock::new(UserRegistry::init());
+}
+
+#[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");
+}
+
+#[ta_invoke_command]
+fn invoke_command(cmd_id: u32, mut params: &mut Parameters) -> optee_utee::Result<()> {
+    trace_println!("[+] TA invoke command");
+    let session_id = unsafe { params.0.as_value().unwrap().a() };
+    trace_println!("[+] session id: {}, command id : {}", session_id, cmd_id);
+
+    match dispatch(cmd_id, session_id, &mut params) {
+        Ok(_) => Ok(()),
+        Err(e) => {
+            trace_println!("[-] tls command error: {}", e);
+            Err(Error::new(ErrorKind::BadParameters))
+        }
+    }
+}
+
+fn dispatch(cmd_id: u32, session_id: u32, mut params: &mut Parameters) -> Result<()> {
+    let mut tls_session_manager = TLS_SESSION_MANAGER.write().unwrap();
+    match TaCommand::from(cmd_id) {
+        // setup board: should be triggered by CA
+        TaCommand::InitBoard => {
+            trace_println!("[+] init_board");
+            let output = init_board()?;
+            restore_output(&mut params, output)
+        }
+        TaCommand::BackupWallet => {
+            let mut p1 = unsafe { params.1.as_memref().unwrap() };
+            let buffer = p1.buffer();
+            let input: BackupWalletInput = bincode::deserialize(&buffer)?;
+            let output = backup_wallet(input)?;
+            restore_output(&mut params, output)
+        }
+        TaCommand::RestoreWallet => {
+            let mut p1 = unsafe { params.1.as_memref().unwrap() };
+            let buffer = p1.buffer();
+            let input: RestoreWalletInput = bincode::deserialize(&buffer)?;
+            let output = restore_wallet(input)?;
+            restore_output(&mut params, output)
+        }
+        #[cfg(feature = "enable_clear_storage")]
+        TaCommand::ClearWalletStorage => {
+            trace_println!("[+] clear_wallet_storage");
+            clear_wallet_storage()?;
+            Ok(())
+        }
+        // set tee_inter_cert, and start the TA server for listening
+        TaCommand::StartServer => {
+            trace_println!("[+] start_server");
+            let mut p1 = unsafe { params.1.as_memref().unwrap() };
+            let tee_inter_cert = p1.buffer().to_vec();
+            let inter_cert_config = InterCertConfig::new(
+                tee_inter_cert.clone(),
+                "CK ECDSA level 2 intermediate".to_string(),
+                "testserver.com".to_string(),
+            );
+            tls_session_manager.init_tls_server(inter_cert_config)
+        }
+        // setup tls session, create a server connection of specific session id
+        TaCommand::NewTlsSession => {
+            trace_println!("[+] new_tls_session");
+            let user_registery = USER_REGISTRY.read().unwrap();
+            let accepted_pubkey_hash = user_registery.get_accepted_pubkey_hash();
+            tls_session_manager.new_tls_session(session_id, accepted_pubkey_hash)
+        }
+        // handshake and receive request
+        TaCommand::DoTlsRead => {
+            let mut p1 = unsafe { params.1.as_memref().unwrap() };
+            let buffer = p1.buffer();
+            trace_println!("[+] do_tls_read, buf len: {}", buffer.len());
+            let request = tls_session_manager.do_tls_read(session_id, buffer)?;
+            if !request.is_empty() {
+                let tls_context = tls_session_manager.construct_tls_context(session_id)?;
+                let command_request: TlsCommandRequest = bincode::deserialize(&request)?;
+                trace_println!("[+] command_request: {:?}", command_request.command);
+                let response = match process_inner_command(tls_context, command_request) {
+                    Ok(response) => response,
+                    Err(e) => {
+                        trace_println!("[-] process_inner_command error: {}", e);
+                        format!("TEE Error: {:?}", e).as_bytes().to_vec()
+                    }
+                };
+                trace_println!("[+] process_command done");
+                tls_session_manager.write_response(session_id, &response)?;
+            } else {
+                trace_println!("[+] request is empty");
+            }
+            Ok(())
+        }
+        TaCommand::DoTlsWrite => {
+            trace_println!("[+] do_tls_write");
+            let mut p1 = unsafe { params.1.as_memref().unwrap() };
+            let mut p2 = unsafe { params.2.as_value().unwrap() };
+            let mut buffer = p1.buffer();
+            let n = tls_session_manager.do_tls_write(session_id, &mut buffer)?;
+            p2.set_a(n as u32);
+            Ok(())
+        }
+        TaCommand::CloseTlsSession => {
+            trace_println!("[+] close_tls_session");
+            tls_session_manager.close_tls_session(session_id)
+        }
+        _ => bail!("Unsupported command"),
+    }
+}
+
+fn restore_output<T>(params: &mut Parameters, output: T) -> Result<()>
+where
+    T: Serialize,
+{
+    let output = bincode::serialize(&output)?;
+    let mut p2 = unsafe { params.2.as_memref()? };
+    let mut p3 = unsafe { params.3.as_value()? };
+    p2.buffer().write(&output)?;
+    p3.set_a(output.len() as u32);
+    trace_println!("[+] restore_output: finished");
+
+    Ok(())
+}
+
+// processing commands which invoked through TLS
+fn process_inner_command(context: TlsContext, request: TlsCommandRequest) -> Result<Vec<u8>> {
+    let mut state_manager = STATE_MANAGER.write().unwrap();
+    let mut user_registry = USER_REGISTRY.write().unwrap();
+    let client_pubkey_hash: CkHash = context.try_into()?;
+
+    let output = match request.command {
+        // wallet commands:
+        TaCommand::SyncWithTee => {
+            let input: SyncWithTeeInput = bincode::deserialize(&request.request)?;
+            let config = input.signed_config;
+            ensure!(config.signature.is_some(), "signature is missing");
+            let _system: System = user_registry.auth_as_role(&client_pubkey_hash)?;
+            trace_println!("[+] SyncWithTee: system authenticated");
+
+            user_registry.set_users(config.user_registry);
+            let (latest_wallets, config_version) =
+                state_manager.set_wallets(config.wallets, config.config_version)?;
+            let output = SyncWithTeeOutput {
+                latest_wallets,
+                config_version,
+            };
+            bincode::serialize(&output)?
+        }
+        // transaction commands:
+        TaCommand::CreateTransaction => {
+            let input: CreateTransactionInput = bincode::deserialize(&request.request)?;
+            let operator: TxOperator = user_registry.auth_as_role(&client_pubkey_hash)?;
+            trace_println!("[+] CreateTransaction: operator authenticated");
+            // create tx
+            let (tx_id, latest_approval_chain) = state_manager.create_tx(&operator, input.tx)?;
+            bincode::serialize(&CreateTransactionOutput {
+                tx_id,
+                latest_approval_chain,
+            })?
+        }
+        TaCommand::ApproveTransaction => {
+            let input: ApproveTransactionInput = bincode::deserialize(&request.request)?;
+            let approver: Approver = user_registry.auth_as_role(&client_pubkey_hash)?;
+            trace_println!("[+] ApproveTransaction: approver authenticated");
+            let latest_approval_chain = match input.operation {
+                ApprovalOperation::Approve => state_manager.approve_tx(
+                    &approver,
+                    input.tx_id,
+                    &input.current_approval_chain,
+                )?,
+                ApprovalOperation::Reject => state_manager.reject_tx(
+                    &approver,
+                    input.tx_id,
+                    &input.current_approval_chain,
+                )?,
+            };
+            bincode::serialize(&ApproveTransactionOutput {
+                latest_approval_chain,
+            })?
+        }
+        TaCommand::RecallTransaction => {
+            let input: RecallTransactionInput = bincode::deserialize(&request.request)?;
+            let operator: TxOperator = user_registry.auth_as_role(&client_pubkey_hash)?;
+            trace_println!("[+] RecallTransaction: operator authenticated");
+            state_manager.recall_tx(&operator, &input.tx_id, &input.current_approval_chain)?;
+            bincode::serialize(&RecallTransactionOutput { tx_id: input.tx_id })?
+        }
+        TaCommand::SignTransaction => {
+            let input: SignTransactionInput = bincode::deserialize(&request.request)?;
+            // Operator or System can sign tx
+            let user: TaUser = user_registry.auth_as_role(&client_pubkey_hash)?;
+            trace_println!("[+] SignTransaction: user authenticated");
+            let signed_tx = state_manager.sign_tx(&user, input.tx_id, input.tx)?;
+            bincode::serialize(&SignTransactionOutput { signed_tx })?
+        }
+        TaCommand::ListPendingTransaction => {
+            let _input: ListPendingTransactionInput = bincode::deserialize(&request.request)?;
+            let system: System = user_registry.auth_as_role(&client_pubkey_hash)?;
+            trace_println!("[+] ListPendingTransaction: system authenticated");
+            let tx_details = state_manager.list_tx(&system)?;
+            bincode::serialize(&ListPendingTransactionOutput { tx_details })?
+        }
+        // tee status command
+        TaCommand::GetTeeStatus => {
+            let _system: System = user_registry.auth_as_role(&client_pubkey_hash)?;
+            trace_println!("[+] GetTeeStatus: system authenticated");
+            let device_id = state_manager.get_device_id();
+            let tee_status = state_manager.get_tee_status();
+            bincode::serialize(&GetTeeStatusOutput {
+                device_id,
+                tee_status,
+            })?
+        }
+        _ => bail!("Unsupported command"),
+    };
+    Ok(output)
+}
+
+// TA configurations
+const TA_FLAGS: u32 = 0;
+const TA_DATA_SIZE: u32 = 4 * 1024 * 1024;
+const TA_STACK_SIZE: u32 = 512 * 1024;
+const TA_VERSION: &[u8] = b"0.2\0";
+const TA_DESCRIPTION: &[u8] = b"This is a tls server example.\0";
+const EXT_PROP_VALUE_1: &[u8] = b"TLS Server 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/ConfidentialKlave/tls-ta/ta_aarch64.lds b/projects/web3/ConfidentialKlave/tls-ta/ta_aarch64.lds
new file mode 100644
index 0000000..adb7603
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-ta/ta_aarch64.lds
@@ -0,0 +1,92 @@
+OUTPUT_FORMAT("elf64-littleaarch64", "elf64-bigaarch64", "elf64-littleaarch64")
+OUTPUT_ARCH(aarch64)
+
+PHDRS {
+	/*
+	 * Exec and rodata headers are hard coded to RX and RO
+	 * respectively. This is needed because the binary is relocatable
+	 * and the linker would automatically make any header writeable
+	 * that need to be updated during relocation.
+	 */
+	exec PT_LOAD FLAGS (5);		/* RX */
+	rodata PT_LOAD FLAGS (4);	/* RO */
+	rwdata PT_LOAD;
+	dyn PT_DYNAMIC;
+}
+
+SECTIONS {
+	.ta_head : {*(.ta_head)} :exec
+	.text : {
+		__text_start = .;
+		*(.text .text.*)
+		*(.stub)
+		*(.glue_7)
+		*(.glue_7t)
+		*(.gnu.linkonce.t.*)
+		/* Workaround for an erratum in ARM's VFP11 coprocessor */
+		*(.vfp11_veneer)
+		PROVIDE(__gnu_mcount_nc = __utee_mcount);
+		__text_end = .;
+	}
+        .plt : { *(.plt) }
+
+	.eh_frame : { *(.eh_frame) } :rodata
+	.rodata : {
+		*(.gnu.linkonce.r.*)
+		*(.rodata .rodata.*)
+	}
+	/* .ARM.exidx is sorted, so has to go in its own output section.  */
+	.ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) }
+        .ctors : { *(.ctors) }
+        .dtors : { *(.dtors) }
+	.rel.text : { *(.rel.text) *(.rel.gnu.linkonce.t*) }
+	.rela.text : { *(.rela.text) *(.rela.gnu.linkonce.t*) }
+	.rel.data : { *(.rel.data) *(.rel.gnu.linkonce.d*) }
+	.rela.data : { *(.rela.data) *(.rela.gnu.linkonce.d*) }
+	.rel.rodata : { *(.rel.rodata) *(.rel.gnu.linkonce.r*) }
+	.rela.rodata : { *(.rela.rodata) *(.rela.gnu.linkonce.r*) }
+	.rel.dyn : { *(.rel.dyn) }
+	.rel.got : { *(.rel.got) }
+	.rela.got : { *(.rela.got) }
+	.rel.ctors : { *(.rel.ctors) }
+	.rela.ctors : { *(.rela.ctors) }
+	.rel.dtors : { *(.rel.dtors) }
+	.rela.dtors : { *(.rela.dtors) }
+	.rel.init : { *(.rel.init) }
+	.rela.init : { *(.rela.init) }
+	.rel.fini : { *(.rel.fini) }
+	.rela.fini : { *(.rela.fini) }
+	.rel.bss : { *(.rel.bss) }
+	.rela.bss : { *(.rela.bss) }
+	.rel.plt : { *(.rel.plt) }
+	.rela.plt : { *(.rela.plt) }
+	.dynamic : { *(.dynamic) } :dyn :rodata
+	.dynsym : { *(.dynsym) } :rodata
+	.dynstr : { *(.dynstr) }
+	.hash : { *(.hash) }
+
+	/* Page align to allow dropping execute bit for RW data */
+	. = ALIGN(4096);
+
+	.data : { *(.data .data.* .gnu.linkonce.d.*) } :rwdata
+	.got : { *(.got.plt) *(.got) }
+	.bss : {
+		*(.bss .bss.* .gnu.linkonce.b.* COMMON)
+
+		/*
+		 * TA profiling with gprof
+		 * Reserve some space for the profiling buffer, only if the
+		 * TA is instrumented (i.e., some files were built with -pg).
+		 * Note that PROVIDE() above defines a symbol only if it is
+		 * referenced in the object files.
+		 * This also provides a way to detect at runtime if the TA is
+		 * instrumented or not.
+		 */
+		. = ALIGN(8);
+		__gprof_buf_start = .;
+		__gprof_buf_end = .;
+	}
+
+	/DISCARD/ : { *(.interp) }
+}
+
diff --git a/projects/web3/ConfidentialKlave/tls-ta/ta_arm.lds b/projects/web3/ConfidentialKlave/tls-ta/ta_arm.lds
new file mode 100644
index 0000000..e9601b5
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-ta/ta_arm.lds
@@ -0,0 +1,91 @@
+OUTPUT_FORMAT("elf32-littlearm")
+OUTPUT_ARCH(arm)
+
+PHDRS {
+	/*
+	 * Exec and rodata headers are hard coded to RX and RO
+	 * respectively. This is needed because the binary is relocatable
+	 * and the linker would automatically make any header writeable
+	 * that need to be updated during relocation.
+	 */
+	exec PT_LOAD FLAGS (5);		/* RX */
+	rodata PT_LOAD FLAGS (4);	/* RO */
+	rwdata PT_LOAD;
+	dyn PT_DYNAMIC;
+}
+
+SECTIONS {
+	.ta_head : {*(.ta_head)} :exec
+	.text : {
+		__text_start = .;
+		*(.text .text.*)
+		*(.stub)
+		*(.glue_7)
+		*(.glue_7t)
+		*(.gnu.linkonce.t.*)
+		/* Workaround for an erratum in ARM's VFP11 coprocessor */
+		*(.vfp11_veneer)
+		PROVIDE(__gnu_mcount_nc = __utee_mcount);
+		__text_end = .;
+	}
+        .plt : { *(.plt) }
+
+	.eh_frame : { *(.eh_frame) } :rodata
+	.rodata : {
+		*(.gnu.linkonce.r.*)
+		*(.rodata .rodata.*)
+	}
+	/* .ARM.exidx is sorted, so has to go in its own output section.  */
+	.ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) }
+        .ctors : { *(.ctors) }
+        .dtors : { *(.dtors) }
+	.got : { *(.got.plt) *(.got) }
+	.rel.text : { *(.rel.text) *(.rel.gnu.linkonce.t*) }
+	.rela.text : { *(.rela.text) *(.rela.gnu.linkonce.t*) }
+	.rel.data : { *(.rel.data) *(.rel.gnu.linkonce.d*) }
+	.rela.data : { *(.rela.data) *(.rela.gnu.linkonce.d*) }
+	.rel.rodata : { *(.rel.rodata) *(.rel.gnu.linkonce.r*) }
+	.rela.rodata : { *(.rela.rodata) *(.rela.gnu.linkonce.r*) }
+	.rel.dyn : { *(.rel.dyn) }
+	.rel.got : { *(.rel.got) }
+	.rela.got : { *(.rela.got) }
+	.rel.ctors : { *(.rel.ctors) }
+	.rela.ctors : { *(.rela.ctors) }
+	.rel.dtors : { *(.rel.dtors) }
+	.rela.dtors : { *(.rela.dtors) }
+	.rel.init : { *(.rel.init) }
+	.rela.init : { *(.rela.init) }
+	.rel.fini : { *(.rel.fini) }
+	.rela.fini : { *(.rela.fini) }
+	.rel.bss : { *(.rel.bss) }
+	.rela.bss : { *(.rela.bss) }
+	.rel.plt : { *(.rel.plt) }
+	.rela.plt : { *(.rela.plt) }
+	.dynamic : { *(.dynamic) } :dyn :rodata
+	.dynsym : { *(.dynsym) } :rodata
+	.dynstr : { *(.dynstr) }
+	.hash : { *(.hash) }
+
+	/* Page align to allow dropping execute bit for RW data */
+	. = ALIGN(4096);
+
+	.data : { *(.data .data.* .gnu.linkonce.d.*) } :rwdata
+	.bss : {
+		*(.bss .bss.* .gnu.linkonce.b.* COMMON)
+
+		/*
+		 * TA profiling with gprof
+		 * Reserve some space for the profiling buffer, only if the
+		 * TA is instrumented (i.e., some files were built with -pg).
+		 * Note that PROVIDE() above defines a symbol only if it is
+		 * referenced in the object files.
+		 * This also provides a way to detect at runtime if the TA is
+		 * instrumented or not.
+		 */
+		. = ALIGN(8);
+		__gprof_buf_start = .;
+		__gprof_buf_end = .;
+	}
+
+	/DISCARD/ : { *(.interp) }
+}
diff --git a/projects/web3/ConfidentialKlave/tls-ta/ta_static.rs b/projects/web3/ConfidentialKlave/tls-ta/ta_static.rs
new file mode 100644
index 0000000..b31f8c3
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls-ta/ta_static.rs
@@ -0,0 +1,98 @@
+// 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_mangle]
+pub static mut trace_level: libc::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: std::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: libc::size_t = std::mem::size_of::<u8>() * 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: libc::size_t = 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() -> libc::c_int {
+    return trace_level;
+}
diff --git a/projects/web3/ConfidentialKlave/tls_uuid.txt b/projects/web3/ConfidentialKlave/tls_uuid.txt
new file mode 100644
index 0000000..ba29fa5
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/tls_uuid.txt
@@ -0,0 +1 @@
+fba40f69-1a3d-4511-b707-159ffa40e618
diff --git a/projects/web3/ConfidentialKlave/types/Cargo.toml b/projects/web3/ConfidentialKlave/types/Cargo.toml
new file mode 100644
index 0000000..f93e560
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/Cargo.toml
@@ -0,0 +1,66 @@
+# 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 = "types"
+version = "0.2.0"
+authors = ["Teaclave Contributors <de...@teaclave.apache.org>"]
+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"
+
+[features]
+testnet = []
+
+# enable testnet:
+# default = [ "testnet" ]
+# enable mainnet:
+default = []
+
+[dependencies]
+basic_utils = { path = "../basic_utils" }
+
+serde = { version = "=1.0.163", features = ["derive"] }
+serde_derive = "1.0"
+serde_json = "1.0"
+serde_with = { version = "1.14", features = ["macros"], default-features = false }
+serde-hex = "0.1.0"
+hex = { version = "0.4", features = ["serde"] }
+uuid = { version = "=1.3.0", features = ["serde"], default-features = false }
+anyhow = "1.0"
+url = { version = "=2.1.1", features = ["serde"], default-features = false }
+bitcoin = { version = "=0.31.1", features = ["serde", "no-std"], default-features = false }
+bincode = "1.3"
+ethereum-tx-sign = "=6.1.0"
+bip39 = { version = "=2.0.0", default-features = false }
+
+[target.'cfg(target_os="optee")'.dependencies]
+optee-utee-sys = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee/optee-utee-sys" }
+optee-utee = { path = "../../incubator-teaclave-trustzone-sdk/optee-utee" }
+
+[patch.crates-io]
+proc-macro2 = { git = "https://github.com/DemesneGH/proc-macro2.git" }
+
+# Note: `hex_lit` is a transitive dependency of `bitcoin` and fails to build at
+# hex_lit-0.1.1/src/lib.rs:127 with the error:
+#   error[E0658]: function pointer casts are not allowed in constant functions
+# Workaround:
+# - Use a patched version of `hex_lit`.
+# - Add the following override in the Cargo.toml of each crate (`types/`, `proto/`, `core/`, `tls-ta/`):
+#     hex_lit = { path = "../hex_lit" }
+
diff --git a/projects/web3/ConfidentialKlave/types/src/external/account/btc.rs b/projects/web3/ConfidentialKlave/types/src/external/account/btc.rs
new file mode 100644
index 0000000..5e840ff
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/account/btc.rs
@@ -0,0 +1,167 @@
+// 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 crate::share::{AccountXpub, BtcAddress, ClientBtcAddress};
+use anyhow::Result;
+use bitcoin::bip32::DerivationPath;
+use bitcoin::secp256k1::{self, Secp256k1};
+use serde::{Deserialize, Serialize};
+use std::collections::HashSet;
+use std::str::FromStr;
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct BtcAccount {
+    account_xpub: AccountXpub,
+    invoice_derived: u32,
+    invoice_addresses: HashSet<ClientBtcAddress>, // task runner needs to get all addresses periodically, store them for later use
+    change_derived: u32,
+    change_addresses: HashSet<ClientBtcAddress>,
+}
+
+/// According to BIP-48:
+/// Currently the only script types covered by this BIP are Native Segwit (p2wsh) and Nested Segwit (p2sh-p2wsh).
+/// The following path represents Nested Segwit (p2sh-p2wsh) mainnet, account 0: 1': Nested Segwit (p2sh-p2wsh) m/48'/0'/0'/1'
+/// The following path represents Native Segwit (p2wsh) mainnet, account 0: 2': Native Segwit (p2wsh) m/48'/0'/0'/2'
+/// The recommended default for wallets is pay to witness script hash m/48'/0'/0'/2'.
+///
+/// According to P2WPKH: BIP 84
+/// [m / purpose' / coin_type' / account' /] / change / address_index
+pub(crate) fn sub_address_derive_path(
+    is_change: bool,
+    address_index: u32,
+) -> Result<DerivationPath> {
+    let change: u32 = match is_change {
+        true => 1,
+        false => 0,
+    };
+    let sub_path = format!("m/{}/{}", change, address_index);
+    DerivationPath::from_str(&sub_path).map_err(|_| anyhow::anyhow!("invalid derivation path"))
+}
+
+impl BtcAccount {
+    pub fn try_init(account_xpub: AccountXpub) -> Result<BtcAccount> {
+        let mut account = BtcAccount {
+            account_xpub,
+            invoice_derived: 0,
+            change_derived: 0,
+            invoice_addresses: HashSet::new(),
+            change_addresses: HashSet::new(),
+        };
+        let secp = Secp256k1::verification_only();
+        account.derive_invoice_address(&secp)?;
+        account.derive_change_address(&secp)?;
+        Ok(account)
+    }
+
+    fn _derive_address<C: secp256k1::Verification>(
+        &self,
+        is_change: bool,
+        index: u32,
+        secp: &Secp256k1<C>,
+    ) -> Result<BtcAddress> {
+        let sub_path = sub_address_derive_path(is_change, index)?;
+        let xpub = self
+            .account_xpub
+            .derive_pub(secp, &sub_path)
+            .map_err(|e| anyhow::anyhow!("derive address failed: {}", e))?;
+        let pk = bitcoin::PublicKey::new(xpub.public_key);
+        BtcAddress::try_from_pk(pk, sub_path, xpub.network)
+    }
+
+    pub fn derive_invoice_address<C: secp256k1::Verification>(
+        &mut self,
+        secp: &Secp256k1<C>,
+    ) -> Result<BtcAddress> {
+        let index = self.invoice_derived;
+        let addr = self._derive_address(false, index, secp)?;
+        self.invoice_derived += 1;
+        self.invoice_addresses.insert((&addr).into());
+        Ok(addr)
+    }
+
+    pub fn current_invoice_address(&self) -> Result<BtcAddress> {
+        let secp = Secp256k1::verification_only();
+        anyhow::ensure!(self.invoice_derived > 0, "no invoice address derived");
+        let index = self.invoice_derived - 1;
+        let addr = self._derive_address(false, index, &secp)?;
+        Ok(addr)
+    }
+
+    pub fn current_change_address(&self) -> Result<BtcAddress> {
+        let secp = Secp256k1::verification_only();
+        anyhow::ensure!(self.change_derived > 0, "no change address derived");
+        let index = self.change_derived - 1;
+        let addr = self._derive_address(false, index, &secp)?;
+        Ok(addr)
+    }
+
+    pub fn derive_change_address<C: secp256k1::Verification>(
+        &mut self,
+        secp: &Secp256k1<C>,
+    ) -> Result<BtcAddress> {
+        let index = self.change_derived;
+        let addr = self._derive_address(true, index, secp)?;
+        self.change_derived += 1;
+        self.change_addresses.insert((&addr).into());
+        Ok(addr)
+    }
+
+    pub fn invoice_address(&self) -> Result<BtcAddress> {
+        self.current_invoice_address()
+    }
+
+    pub fn id(&self) -> crate::share::AccountId {
+        self.compute_id()
+    }
+
+    pub fn all_client_addresses(&self) -> HashSet<ClientBtcAddress> {
+        let mut addresses = self.all_client_invoice_addresses();
+        addresses.extend(self.all_client_change_addresses());
+        addresses
+    }
+
+    pub fn all_client_invoice_addresses(&self) -> HashSet<ClientBtcAddress> {
+        self.invoice_addresses.clone().into_iter().collect()
+    }
+
+    pub fn all_client_change_addresses(&self) -> HashSet<ClientBtcAddress> {
+        self.change_addresses.clone().into_iter().collect()
+    }
+
+    pub fn all_addresses(&self) -> Result<Vec<BtcAddress>> {
+        let mut addresses = Vec::new();
+        // derive all invoice addresses
+        for i in 0..self.invoice_derived {
+            let addr = self._derive_address(false, i, &Secp256k1::verification_only())?;
+            addresses.push(addr);
+        }
+        // derive all change addresses
+        for i in 0..self.change_derived {
+            let addr = self._derive_address(true, i, &Secp256k1::verification_only())?;
+            addresses.push(addr);
+        }
+        Ok(addresses)
+    }
+}
+
+impl std::ops::Deref for BtcAccount {
+    type Target = AccountXpub;
+
+    fn deref(&self) -> &Self::Target {
+        &self.account_xpub
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/external/account/eth.rs b/projects/web3/ConfidentialKlave/types/src/external/account/eth.rs
new file mode 100644
index 0000000..486244b
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/account/eth.rs
@@ -0,0 +1,44 @@
+// 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 crate::share::{AccountXpub, EthAddress};
+use anyhow::Result;
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct EthAccount {
+    xpub: AccountXpub,
+}
+
+impl EthAccount {
+    pub fn new(xpub: AccountXpub) -> Self {
+        Self { xpub }
+    }
+
+    pub fn eth_address(&self) -> EthAddress {
+        let inner = self.id().take();
+        EthAddress::new(inner)
+    }
+
+    pub fn invoice_address(&self) -> Result<EthAddress> {
+        Ok(self.eth_address())
+    }
+
+    pub fn id(&self) -> crate::share::AccountId {
+        self.xpub.compute_id()
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/external/account/mod.rs b/projects/web3/ConfidentialKlave/types/src/external/account/mod.rs
new file mode 100644
index 0000000..a64b2cb
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/account/mod.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.
+
+pub mod btc;
+pub mod eth;
+
+use std::collections::HashSet;
+
+use crate::external::{BtcAccount, EthAccount};
+use crate::share::{
+    AccountId, BtcAddress, ClientBtcAddress, EthAddress, MultiChainAccount, NetworkType,
+};
+use anyhow::{Ok, Result};
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub enum CkAccount {
+    Eth(EthAccount),
+    Btc(BtcAccount),
+}
+
+impl CkAccount {
+    pub fn id(&self) -> AccountId {
+        match self {
+            CkAccount::Eth(account) => account.id(),
+            CkAccount::Btc(account) => account.id(),
+        }
+    }
+
+    pub fn invoice_address_string(&self) -> Result<String> {
+        match self {
+            CkAccount::Eth(account) => Ok(account.invoice_address()?.to_string()),
+            CkAccount::Btc(account) => Ok(account.invoice_address()?.to_string()),
+        }
+    }
+
+    pub fn take_eth_account(self) -> Option<EthAccount> {
+        match self {
+            CkAccount::Eth(account) => Some(account),
+            _ => None,
+        }
+    }
+
+    pub fn take_btc_account(self) -> Option<BtcAccount> {
+        match self {
+            CkAccount::Btc(account) => Some(account),
+            _ => None,
+        }
+    }
+
+    pub fn all_client_btc_addresses(&self) -> HashSet<ClientBtcAddress> {
+        match self {
+            CkAccount::Btc(account) => account.all_client_addresses(),
+            _ => HashSet::new(),
+        }
+    }
+
+    pub fn all_btc_addresses(&self) -> Result<Vec<BtcAddress>> {
+        match self {
+            CkAccount::Btc(account) => account.all_addresses(),
+            _ => Ok(Vec::new()),
+        }
+    }
+
+    pub fn btc_change_address(&self) -> Result<BtcAddress> {
+        match self {
+            CkAccount::Btc(account) => account.current_change_address(),
+            _ => Err(anyhow::anyhow!("Account is not BTC")),
+        }
+    }
+}
+
+impl std::convert::TryFrom<MultiChainAccount> for CkAccount {
+    type Error = anyhow::Error;
+
+    fn try_from(mca: MultiChainAccount) -> Result<Self> {
+        match mca {
+            MultiChainAccount::Eth(account) => {
+                let eth_account = EthAccount::new(account);
+                Ok(Self::Eth(eth_account))
+            }
+            MultiChainAccount::Btc(account) => {
+                let btc_account = BtcAccount::try_init(account)?;
+                Ok(Self::Btc(btc_account))
+            }
+        }
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/external/approval_chain.rs b/projects/web3/ConfidentialKlave/types/src/external/approval_chain.rs
new file mode 100644
index 0000000..56bdacb
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/approval_chain.rs
@@ -0,0 +1,330 @@
+// 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 crate::external::Email;
+use crate::external::{ApprovalChainBasic, ApprovalStageBasic};
+use crate::share::ApprovalStatus;
+use crate::share::UserID;
+use crate::share::{TaApprovalChain, TaApprovalStage};
+use anyhow::{anyhow, ensure, Result};
+use serde::{Deserialize, Serialize};
+use std::collections::{HashMap, HashSet};
+use std::iter::IntoIterator;
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct ApprovalInfo {
+    status: ApprovalStatus,
+    timestamp: Option<u64>,
+}
+
+impl ApprovalInfo {
+    pub fn new(status: ApprovalStatus) -> Self {
+        Self {
+            status,
+            timestamp: None,
+        }
+    }
+
+    pub fn status(&self) -> ApprovalStatus {
+        self.status
+    }
+
+    pub fn timestamp(&self) -> Option<u64> {
+        self.timestamp
+    }
+
+    pub fn try_update_status(&mut self, upcoming_status: ApprovalStatus) -> Result<()> {
+        if self.status == upcoming_status {
+            return Ok(());
+        }
+        self.status = upcoming_status;
+        let now = std::time::SystemTime::now()
+            .duration_since(std::time::UNIX_EPOCH)?
+            .as_secs();
+        self.timestamp = Some(now);
+        Ok(())
+    }
+
+    pub fn is_operated(&self) -> bool {
+        self.status != ApprovalStatus::Pending
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct ApprovalStage {
+    threshold: u64,
+    status: HashMap<Email, ApprovalInfo>,
+}
+
+impl ApprovalStage {
+    pub fn new(threshold: u64, approvers: HashSet<Email>) -> Self {
+        Self {
+            threshold,
+            status: approvers
+                .into_iter()
+                .map(|a| (a, ApprovalInfo::new(ApprovalStatus::Pending)))
+                .collect(),
+        }
+    }
+
+    pub fn try_update(
+        &mut self,
+        ta_stage: TaApprovalStage,
+        uid_to_email: &HashMap<UserID, Email>,
+    ) -> Result<()> {
+        ensure!(
+            self.threshold == ta_stage.get_threshold(),
+            "ApprovalStage::try_update: threshold not match"
+        );
+
+        let mut converted_stage = HashMap::new();
+        for (uid, upcoming_status) in ta_stage.status.into_iter() {
+            let email = uid_to_email
+                .get(&uid)
+                .ok_or_else(|| anyhow!("ApprovalStage::try_update: uid not found"))?
+                .clone();
+            converted_stage.insert(email, upcoming_status);
+        }
+
+        ensure!(
+            self.status.len() == converted_stage.len()
+                && self.status.keys().all(|k| converted_stage.contains_key(k)),
+            "ApprovalStage::try_update: email not match"
+        );
+
+        // safety: unwrap never panic, as we have already checked the keys
+        self.status.iter_mut().try_for_each(|(email, info)| {
+            info.try_update_status(*converted_stage.get(email).unwrap())
+        })?;
+        Ok(())
+    }
+
+    pub fn uid_email_mapping(&self) -> HashMap<UserID, Email> {
+        self.status
+            .keys()
+            .map(|email| (UserID::from(email.clone()), email.clone()))
+            .collect()
+    }
+
+    pub fn get_threshold(&self) -> u64 {
+        self.threshold
+    }
+
+    pub fn take_status(self) -> HashMap<Email, ApprovalInfo> {
+        self.status
+    }
+
+    pub fn get_status(&self) -> HashMap<Email, ApprovalInfo> {
+        self.status.clone()
+    }
+
+    pub fn status(&self) -> &HashMap<Email, ApprovalInfo> {
+        &self.status
+    }
+
+    pub fn is_approved(&self) -> bool {
+        self.status
+            .values()
+            .filter(|s| s.status() == ApprovalStatus::Approved)
+            .count() as u64
+            >= self.threshold
+    }
+
+    pub fn is_rejected(&self) -> bool {
+        self.status
+            .values()
+            .any(|s| s.status() == ApprovalStatus::Rejected)
+    }
+
+    pub fn contains(&self, user: &Email) -> bool {
+        self.status.keys().any(|k| k == user)
+    }
+
+    pub fn get_stage_status(&self) -> ApprovalStatus {
+        if self.is_rejected() {
+            ApprovalStatus::Rejected
+        } else if self.is_approved() {
+            ApprovalStatus::Approved
+        } else {
+            ApprovalStatus::Pending
+        }
+    }
+}
+
+impl std::ops::Deref for ApprovalStage {
+    type Target = HashMap<Email, ApprovalInfo>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.status
+    }
+}
+
+impl From<ApprovalStageBasic> for ApprovalStage {
+    fn from(stage: ApprovalStageBasic) -> Self {
+        Self {
+            threshold: stage.threshold,
+            status: stage
+                .approvers
+                .into_iter()
+                .map(|a| (a, ApprovalInfo::new(ApprovalStatus::Pending)))
+                .collect(),
+        }
+    }
+}
+
+impl From<ApprovalStage> for TaApprovalStage {
+    fn from(asb: ApprovalStage) -> Self {
+        let threshold = asb.get_threshold();
+        let status = asb
+            .get_status()
+            .into_iter()
+            .map(|(k, v)| (k.into(), v.status()))
+            .collect();
+        TaApprovalStage::from((threshold, status))
+    }
+}
+
+impl From<ApprovalChain> for TaApprovalChain {
+    fn from(ac: ApprovalChain) -> Self {
+        TaApprovalChain::new(ac.into_iter().map(|e| e.into()).collect::<Vec<_>>())
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct ApprovalChain(Vec<ApprovalStage>);
+
+impl ApprovalChain {
+    pub fn new(inner: Vec<ApprovalStage>) -> Self {
+        Self(inner)
+    }
+
+    pub fn try_update(
+        &mut self,
+        ta_chain: TaApprovalChain,
+        uid_to_email: &HashMap<UserID, Email>,
+    ) -> Result<()> {
+        ensure!(
+            self.0.len() == ta_chain.len(),
+            "ApprovalChain::try_update: stage not match"
+        );
+
+        let ta_stages = ta_chain.take_stages();
+        for (i, ta_stage) in ta_stages.into_iter().enumerate() {
+            let entry = self
+                .0
+                .get_mut(i)
+                .ok_or_else(|| anyhow!("ApprovalChain::try_update: stage not match"))?;
+            entry.try_update(ta_stage, uid_to_email)?;
+        }
+        Ok(())
+    }
+
+    pub fn any_reject(&self) -> bool {
+        self.0
+            .iter()
+            .any(|s| s.get_stage_status() == ApprovalStatus::Rejected)
+    }
+
+    pub fn any_pending(&self) -> bool {
+        self.0
+            .iter()
+            .any(|s| s.get_stage_status() == ApprovalStatus::Pending)
+    }
+
+    pub fn all_approved(&self) -> bool {
+        self.0
+            .iter()
+            .all(|s| s.get_stage_status() == ApprovalStatus::Approved)
+    }
+
+    pub fn current_stage_index(&self) -> Option<usize> {
+        self.0
+            .iter()
+            .position(|s| s.get_stage_status() == ApprovalStatus::Pending)
+    }
+
+    pub fn ready_for_current_stage(&self, user: &Email) -> bool {
+        if let Some(stage) = self.current_stage_index() {
+            self.0[stage].contains(user)
+        } else {
+            false
+        }
+    }
+
+    pub fn approvers_on_stage(&self, stage: usize) -> HashSet<Email> {
+        self.0[stage].status.keys().cloned().collect()
+    }
+
+    // operated approvers are those who have approved or rejected
+    pub fn get_operated_approvers(&self) -> HashSet<Email> {
+        let mut operated_approvers = HashSet::new();
+        for stage in self.0.iter() {
+            for (approver, info) in stage.status.iter() {
+                if info.is_operated() {
+                    operated_approvers.insert(approver.clone());
+                }
+            }
+        }
+        operated_approvers
+    }
+
+    pub fn approvers(&self) -> HashSet<Email> {
+        self.0
+            .iter()
+            .flat_map(|s| s.status.keys().cloned())
+            .collect()
+    }
+
+    pub fn uid_email_mapping(&self) -> HashMap<UserID, Email> {
+        self.0
+            .iter()
+            .flat_map(|s| s.uid_email_mapping().into_iter())
+            .collect()
+    }
+}
+
+impl std::ops::Deref for ApprovalChain {
+    type Target = Vec<ApprovalStage>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl std::ops::DerefMut for ApprovalChain {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl From<ApprovalChainBasic> for ApprovalChain {
+    fn from(chain: ApprovalChainBasic) -> Self {
+        Self(chain.into_iter().map(|stage| stage.into()).collect())
+    }
+}
+
+impl IntoIterator for ApprovalChain {
+    type Item = ApprovalStage;
+    type IntoIter = std::vec::IntoIter<Self::Item>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.0.into_iter()
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/external/approval_chain_basic.rs b/projects/web3/ConfidentialKlave/types/src/external/approval_chain_basic.rs
new file mode 100644
index 0000000..0a783c5
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/approval_chain_basic.rs
@@ -0,0 +1,97 @@
+// 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 crate::external::Email;
+use crate::share::{TaApprovalChainBasic, TaApprovalStageBasic};
+use serde::{Deserialize, Serialize};
+use std::collections::HashSet;
+use std::iter::IntoIterator;
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct ApprovalStageBasic {
+    pub threshold: u64,
+    pub approvers: HashSet<Email>,
+}
+
+impl ApprovalStageBasic {
+    pub fn new(threshold: u64, approvers: HashSet<Email>) -> Self {
+        Self {
+            threshold,
+            approvers,
+        }
+    }
+}
+
+impl From<ApprovalStageBasic> for TaApprovalStageBasic {
+    fn from(asb: ApprovalStageBasic) -> Self {
+        Self {
+            threshold: asb.threshold,
+            approvers: asb.approvers.into_iter().map(|e| e.into()).collect(),
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct ApprovalChainBasic(Vec<ApprovalStageBasic>);
+
+impl ApprovalChainBasic {
+    pub fn new(inner: Vec<ApprovalStageBasic>) -> Self {
+        Self(inner)
+    }
+
+    pub fn distinct_approvers(&self) -> HashSet<&Email> {
+        self.0
+            .iter()
+            .flat_map(|stage| stage.approvers.iter())
+            .collect()
+    }
+
+    pub fn contains(&self, user: &Email) -> bool {
+        self.0.iter().any(|stage| stage.approvers.contains(user))
+    }
+}
+
+impl IntoIterator for ApprovalChainBasic {
+    type Item = ApprovalStageBasic;
+    type IntoIter = std::vec::IntoIter<Self::Item>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.0.into_iter()
+    }
+}
+
+impl std::iter::FromIterator<ApprovalStageBasic> for ApprovalChainBasic {
+    fn from_iter<I: IntoIterator<Item = ApprovalStageBasic>>(iter: I) -> Self {
+        Self(iter.into_iter().collect())
+    }
+}
+
+impl std::ops::Deref for ApprovalChainBasic {
+    type Target = Vec<ApprovalStageBasic>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl From<ApprovalChainBasic> for TaApprovalChainBasic {
+    fn from(acb: ApprovalChainBasic) -> Self {
+        Self::new(acb.into_iter().map(|e| e.into()).collect::<Vec<_>>())
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/external/asset_type.rs b/projects/web3/ConfidentialKlave/types/src/external/asset_type.rs
new file mode 100644
index 0000000..a6e01f6
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/asset_type.rs
@@ -0,0 +1,309 @@
+// 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 crate::share::CkNetwork;
+use crate::share::MultiChainAccountId;
+use crate::share::NetworkType;
+use anyhow::{bail, Result};
+use enum_iterator::Sequence;
+use serde::{Deserialize, Serialize};
+use std::convert::TryFrom;
+use std::convert::TryInto;
+use std::hash::Hash;
+
+pub type ContractAddress = [u8; 20];
+
+// as memeber of other structs: TxTransferInfo, Balance,etc
+#[derive(Debug, Serialize, Deserialize, Clone, Copy, Eq, PartialEq, Hash, Sequence)]
+pub enum AssetType {
+    ETH,
+    USDT,
+    USDC,
+    BTC,
+    BSC,
+}
+
+impl AssetType {
+    pub fn config(&self) -> Box<dyn AssetConfig> {
+        match self {
+            AssetType::ETH => Box::new(Eth),
+            AssetType::USDT => Box::new(Usdt),
+            AssetType::USDC => Box::new(Usdc),
+            AssetType::BTC => Box::new(Btc),
+            AssetType::BSC => Box::new(Bsc),
+        }
+    }
+
+    pub fn is_ethereum_chain(&self) -> bool {
+        matches!(self, AssetType::ETH | AssetType::USDT | AssetType::USDC)
+    }
+
+    pub fn is_erc20(&self) -> bool {
+        matches!(self, AssetType::USDT | AssetType::USDC)
+    }
+
+    pub fn is_bitcoin_chain(&self) -> bool {
+        matches!(self, AssetType::BTC)
+    }
+
+    pub fn is_bsc_chain(&self) -> bool {
+        matches!(self, AssetType::BSC)
+    }
+
+    pub fn is_evm_compatible(&self) -> bool {
+        matches!(
+            self,
+            AssetType::ETH | AssetType::USDT | AssetType::USDC | AssetType::BSC
+        )
+    }
+
+    pub fn all_eth_assets() -> Vec<AssetType> {
+        vec![AssetType::ETH, AssetType::USDT, AssetType::USDC]
+    }
+
+    pub fn eth_native_type() -> AssetType {
+        AssetType::ETH
+    }
+
+    pub fn erc20_types() -> Vec<AssetType> {
+        vec![AssetType::USDT, AssetType::USDC]
+    }
+
+    pub fn all_btc_assets() -> Vec<AssetType> {
+        vec![AssetType::BTC]
+    }
+
+    pub fn all_bsc_assets() -> Vec<AssetType> {
+        vec![AssetType::BSC]
+    }
+
+    pub fn all_assets() -> Vec<AssetType> {
+        vec![
+            AssetType::ETH,
+            AssetType::USDT,
+            AssetType::USDC,
+            AssetType::BTC,
+            AssetType::BSC,
+        ]
+    }
+
+    pub fn all_evm_compatible_assets() -> Vec<AssetType> {
+        vec![
+            AssetType::ETH,
+            AssetType::USDT,
+            AssetType::USDC,
+            AssetType::BSC,
+        ]
+    }
+
+    pub fn as_ck_network(&self, network_type: NetworkType) -> CkNetwork {
+        match self {
+            AssetType::ETH | AssetType::USDT | AssetType::USDC => CkNetwork::Eth(network_type),
+            AssetType::BTC => CkNetwork::Btc(network_type),
+            AssetType::BSC => CkNetwork::Bsc(network_type),
+        }
+    }
+
+    pub fn associated_with_multichain_account_id(
+        &self,
+        multichain_account_id: &MultiChainAccountId,
+    ) -> bool {
+        match multichain_account_id {
+            MultiChainAccountId::Eth(_) => self.is_ethereum_chain(),
+            MultiChainAccountId::Btc(_) => self.is_bitcoin_chain(),
+        }
+    }
+
+    pub fn filter_assets_by_ck_network(ck_network: &CkNetwork) -> Vec<AssetType> {
+        AssetType::all_assets()
+            .into_iter()
+            .filter(|&asset| asset.as_ck_network(ck_network.network_type()) == *ck_network)
+            .collect()
+    }
+}
+
+impl TryFrom<String> for AssetType {
+    type Error = anyhow::Error;
+    fn try_from(value: String) -> Result<Self> {
+        match value.as_str() {
+            "ETH" => Ok(AssetType::ETH),
+            "USDT" => Ok(AssetType::USDT),
+            "USDC" => Ok(AssetType::USDC),
+            "BTC" => Ok(AssetType::BTC),
+            "BSC" => Ok(AssetType::BSC),
+            _ => bail!("invalid asset type"),
+        }
+    }
+}
+impl From<AssetType> for String {
+    fn from(value: AssetType) -> Self {
+        match value {
+            AssetType::ETH => "ETH".to_string(),
+            AssetType::USDT => "USDT".to_string(),
+            AssetType::USDC => "USDC".to_string(),
+            AssetType::BTC => "BTC".to_string(),
+            AssetType::BSC => "BSC".to_string(),
+        }
+    }
+}
+
+pub trait AssetConfig: Send + Sync {
+    fn currency_id(&self) -> String;
+    fn decimals(&self) -> u32;
+    fn contract_address(&self, network_type: &NetworkType) -> Option<ContractAddress>;
+    fn fee_asset_type(&self) -> AssetType;
+    fn is_erc20(&self) -> bool {
+        self.contract_address(&NetworkType::Mainnet).is_some()
+    }
+}
+
+#[derive(Copy, Clone)]
+pub struct Eth;
+impl AssetConfig for Eth {
+    fn currency_id(&self) -> String {
+        "ethereum".to_string()
+    }
+    fn decimals(&self) -> u32 {
+        18
+    }
+    fn contract_address(&self, _network_type: &NetworkType) -> Option<ContractAddress> {
+        None
+    }
+    fn fee_asset_type(&self) -> AssetType {
+        AssetType::ETH
+    }
+}
+
+#[derive(Copy, Clone)]
+pub struct Usdt;
+impl AssetConfig for Usdt {
+    fn currency_id(&self) -> String {
+        "tether".to_string()
+    }
+    fn decimals(&self) -> u32 {
+        6
+    }
+    fn contract_address(&self, network_type: &NetworkType) -> Option<ContractAddress> {
+        match network_type {
+            NetworkType::Mainnet => Some(
+                hex::decode("dac17f958d2ee523a2206206994597c13d831ec7")
+                    .unwrap_or_default()
+                    .try_into()
+                    .unwrap_or_default(),
+            ),
+            NetworkType::Testnet => Some(
+                // sepolia
+                hex::decode("aA8E23Fb1079EA71e0a56F48a2aA51851D8433D0")
+                    .unwrap_or_default()
+                    .try_into()
+                    .unwrap_or_default(),
+            ),
+        }
+    }
+    fn fee_asset_type(&self) -> AssetType {
+        AssetType::ETH
+    }
+}
+
+#[derive(Copy, Clone)]
+pub struct Usdc;
+impl AssetConfig for Usdc {
+    fn currency_id(&self) -> String {
+        "usd-coin".to_string()
+    }
+    fn decimals(&self) -> u32 {
+        6
+    }
+    fn is_erc20(&self) -> bool {
+        true
+    }
+    fn contract_address(&self, network_type: &NetworkType) -> Option<ContractAddress> {
+        match network_type {
+            NetworkType::Mainnet => Some(
+                hex::decode("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
+                    .unwrap_or_default()
+                    .try_into()
+                    .unwrap_or_default(),
+            ),
+            NetworkType::Testnet => Some(
+                // sepolia
+                hex::decode("2B0974b96511a728CA6342597471366D3444Aa2a")
+                    .unwrap_or_default()
+                    .try_into()
+                    .unwrap_or_default(),
+            ),
+        }
+    }
+    fn fee_asset_type(&self) -> AssetType {
+        AssetType::ETH
+    }
+}
+
+#[derive(Copy, Clone)]
+pub struct Btc;
+impl AssetConfig for Btc {
+    fn currency_id(&self) -> String {
+        "bitcoin".to_string()
+    }
+    fn decimals(&self) -> u32 {
+        8
+    }
+    fn contract_address(&self, _network_type: &NetworkType) -> Option<ContractAddress> {
+        None
+    }
+    fn fee_asset_type(&self) -> AssetType {
+        AssetType::BTC
+    }
+}
+
+#[derive(Copy, Clone)]
+pub struct Bsc;
+impl AssetConfig for Bsc {
+    fn currency_id(&self) -> String {
+        "binancecoin".to_string()
+    }
+    fn decimals(&self) -> u32 {
+        18
+    }
+    fn contract_address(&self, _network_type: &NetworkType) -> Option<ContractAddress> {
+        None
+    }
+    fn fee_asset_type(&self) -> AssetType {
+        AssetType::BSC
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use enum_iterator::all;
+
+    #[test]
+    fn it_works() {
+        // ierate over all asset types
+        for token in all::<super::AssetType>() {
+            let config = token.config();
+            println!("currency_id: {:?}", config.currency_id());
+            println!("decimals: {:?}", config.decimals());
+            println!("is_erc20: {:?}", config.is_erc20());
+            println!(
+                "contract_address: {:?}",
+                config.contract_address(&NetworkType::Mainnet)
+            );
+        }
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/external/erc20.rs b/projects/web3/ConfidentialKlave/types/src/external/erc20.rs
new file mode 100644
index 0000000..1e22872
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/erc20.rs
@@ -0,0 +1,67 @@
+// 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 crate::share::NetworkType;
+use anyhow::{Context, Result};
+use ethabi::Contract;
+
+#[derive(Debug)]
+pub struct Erc20TokenConfig {
+    erc20_abi: Contract,
+    network_type: NetworkType,
+}
+
+impl Erc20TokenConfig {
+    pub fn new(network_type: NetworkType) -> Result<Self> {
+        let bytes = include_bytes!("../../../abi/erc20_abi.json");
+        let erc20_abi = Contract::load(bytes.as_slice()).context("Failed to load erc20 abi")?;
+
+        Ok(Self {
+            erc20_abi,
+            network_type,
+        })
+    }
+    pub fn erc20_abi(&self) -> &Contract {
+        &self.erc20_abi
+    }
+    pub fn network_type(&self) -> &NetworkType {
+        &self.network_type
+    }
+}
+
+pub struct TransferAbiData {
+    pub to: [u8; 20],
+    pub value: u128,
+    pub encoded: Vec<u8>,
+}
+
+impl TransferAbiData {
+    pub fn new(to: [u8; 20], value: u128, erc20_abi: &Contract) -> Result<Self> {
+        let transfer_function = erc20_abi.function("transfer")?;
+        let inputs = vec![
+            ethabi::Token::Address(ethabi::Address::from_slice(&to)),
+            ethabi::Token::Uint(value.into()),
+        ];
+        let encoded = transfer_function
+            .encode_input(&inputs)
+            .map_err(|e| anyhow::anyhow!(e))?;
+        Ok(Self { to, value, encoded })
+    }
+    pub fn encoded(&self) -> Vec<u8> {
+        self.encoded.clone()
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/external/external_address.rs b/projects/web3/ConfidentialKlave/types/src/external/external_address.rs
new file mode 100644
index 0000000..7e31b50
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/external_address.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 crate::external::AssetType;
+use anyhow::Result;
+use bitcoin::address::NetworkUnchecked;
+use serde::{Deserialize, Serialize};
+use serde_hex::{SerHex, StrictPfx};
+
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
+#[serde(rename_all = "camelCase")]
+#[serde(tag = "recipientType", content = "address")]
+pub enum ExternalAddress {
+    Eth(#[serde(with = "SerHex::<StrictPfx>")] [u8; 20]),
+    Btc(String),
+}
+
+impl ExternalAddress {
+    pub fn try_from_eth(hex: &str) -> Result<Self> {
+        let addr = Self::check_evm_address(hex);
+        Ok(Self::Eth(addr))
+    }
+
+    fn check_evm_address(addr: &str) -> [u8; 20] {
+        // strip "0x" prefix if exist
+        let hex = addr.strip_prefix("0x").unwrap_or(addr);
+        let bytes = hex::decode(hex).unwrap();
+        let mut addr = [0u8; 20];
+        addr.copy_from_slice(&bytes);
+        addr
+    }
+
+    pub fn try_from_btc(addr: &str) -> Result<Self> {
+        let address: bitcoin::Address<NetworkUnchecked> = addr
+            .parse()
+            .map_err(|e| anyhow::anyhow!("invalid bitcoin address: {}", e))?;
+        if address.is_valid_for_network(bitcoin::Network::Testnet)
+            | address.is_valid_for_network(bitcoin::Network::Bitcoin)
+        {
+            Ok(Self::Btc(addr.into()))
+        } else {
+            anyhow::bail!("invalid btc address")
+        }
+    }
+
+    pub fn is_eth(&self) -> bool {
+        matches!(self, ExternalAddress::Eth(_))
+    }
+
+    pub fn is_btc(&self) -> bool {
+        matches!(self, ExternalAddress::Btc(_))
+    }
+
+    pub fn try_take_eth(self) -> Result<[u8; 20]> {
+        match self {
+            ExternalAddress::Eth(addr) => Ok(addr),
+            _ => anyhow::bail!("not eth address"),
+        }
+    }
+
+    pub fn try_take_btc(self) -> Result<String> {
+        match self {
+            ExternalAddress::Btc(addr) => Ok(addr),
+            _ => anyhow::bail!("not btc address"),
+        }
+    }
+}
+
+impl std::fmt::Display for ExternalAddress {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            ExternalAddress::Eth(addr) => write!(f, "0x{}", hex::encode(addr)),
+            ExternalAddress::Btc(addr) => write!(f, "{}", addr),
+        }
+    }
+}
+
+// dependency of "storable" and "types-client"
+#[derive(Debug, Serialize, Clone, PartialEq, Eq, Hash)]
+#[serde(rename_all = "camelCase")]
+pub struct ClientExternalAddress(pub String);
+// length check
+impl<'de> serde::de::Deserialize<'de> for ClientExternalAddress {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::de::Deserializer<'de>,
+    {
+        let value_str: &str = Deserialize::deserialize(deserializer)?;
+        if value_str.len() > 62 {
+            // bitcoin taproot address length
+            return Err(serde::de::Error::custom(
+                "ClientExternalAddress length exceeded",
+            ));
+        }
+        if value_str.is_empty() {
+            return Err(serde::de::Error::custom("ClientExternalAddress empty"));
+        }
+        Ok(ClientExternalAddress(value_str.to_string()))
+    }
+}
+
+impl ClientExternalAddress {
+    pub fn as_str(&self) -> &str {
+        &self.0
+    }
+
+    pub fn to_external_address(&self, asset_type: &AssetType) -> Result<ExternalAddress> {
+        if asset_type.is_evm_compatible() {
+            ExternalAddress::try_from_eth(self.as_str())
+        } else if asset_type.is_bitcoin_chain() {
+            ExternalAddress::try_from_btc(self.as_str())
+        } else {
+            anyhow::bail!("'to' address: unsupported asset type: {:?}", asset_type)
+        }
+    }
+}
+
+impl std::convert::From<ExternalAddress> for ClientExternalAddress {
+    fn from(external_address: ExternalAddress) -> Self {
+        Self(external_address.to_string())
+    }
+}
+
+impl std::fmt::Display for ClientExternalAddress {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/external/mod.rs b/projects/web3/ConfidentialKlave/types/src/external/mod.rs
new file mode 100644
index 0000000..034cf68
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/mod.rs
@@ -0,0 +1,53 @@
+// 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 account;
+mod approval_chain;
+mod approval_chain_basic;
+mod asset_type;
+mod erc20;
+mod external_address;
+mod numeric;
+mod operators_basic;
+mod transfer;
+mod tx;
+mod user;
+mod viewers_basic;
+
+pub use account::{btc::BtcAccount, eth::EthAccount, CkAccount};
+
+pub use tx::{NetworkErrMsg, NetworkTxHash, TxSubmissionResult};
+
+pub use transfer::{CkAmount, CkReversedTransferInfo, CkTransferInfo, FeeInfo};
+
+pub use user::Email;
+
+pub use approval_chain::{ApprovalChain, ApprovalInfo, ApprovalStage};
+pub use approval_chain_basic::{ApprovalChainBasic, ApprovalStageBasic};
+pub use operators_basic::OperatorsBasic;
+pub use viewers_basic::ViewersBasic;
+
+pub use asset_type::{AssetType, ContractAddress};
+
+pub use numeric::{
+    division_f64_f64, division_u128_u128, multiplication_f64_f64, multiplication_f64_u128,
+    multiplication_f64_u128_to_f64, round_f64,
+};
+
+pub use erc20::{Erc20TokenConfig, TransferAbiData};
+
+pub use external_address::{ClientExternalAddress, ExternalAddress};
diff --git a/projects/web3/ConfidentialKlave/types/src/external/numeric.rs b/projects/web3/ConfidentialKlave/types/src/external/numeric.rs
new file mode 100644
index 0000000..a90a374
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/numeric.rs
@@ -0,0 +1,104 @@
+// 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 rust_decimal::prelude::{FromPrimitive, ToPrimitive};
+use rust_decimal::Decimal;
+
+pub fn multiplication_f64_u128(a: f64, b: u128) -> Result<u128> {
+    let a =
+        Decimal::from_f64(a).ok_or_else(|| anyhow!("Failed to convert amount {} to decimal", a))?;
+    let b = Decimal::from_u128(b)
+        .ok_or_else(|| anyhow!("Failed to convert amount {} to decimal", b))?;
+    let c = a
+        .checked_mul(b)
+        .ok_or_else(|| anyhow!("Failed to multiply {} and {}", a, b))?;
+    let c = c
+        .to_u128()
+        .ok_or_else(|| anyhow!("Failed to convert amount {} to u128", c))?;
+    Ok(c)
+}
+
+pub fn multiplication_f64_u128_to_f64(a: f64, b: u128) -> Result<f64> {
+    let a =
+        Decimal::from_f64(a).ok_or_else(|| anyhow!("Failed to convert amount {} to decimal", a))?;
+    let b = Decimal::from_u128(b)
+        .ok_or_else(|| anyhow!("Failed to convert amount {} to decimal", b))?;
+    let c = a
+        .checked_mul(b)
+        .ok_or_else(|| anyhow!("Failed to multiply {} and {}", a, b))?;
+    let c = c.round_dp(8);
+    let c = c
+        .to_f64()
+        .ok_or_else(|| anyhow!("Failed to convert amount {} to f64", c))?;
+    Ok(c)
+}
+
+pub fn round_f64(a: f64) -> Result<f64> {
+    let a =
+        Decimal::from_f64(a).ok_or_else(|| anyhow!("Failed to convert amount {} to decimal", a))?;
+    let a = a.round_dp(8);
+    let a = a
+        .to_f64()
+        .ok_or_else(|| anyhow!("Failed to convert amount {} to f64", a))?;
+    Ok(a)
+}
+
+pub fn division_u128_u128(a: u128, b: u128) -> Result<f64> {
+    let a = Decimal::from_u128(a)
+        .ok_or_else(|| anyhow!("Failed to convert amount {} to decimal", a))?;
+    let b = Decimal::from_u128(b)
+        .ok_or_else(|| anyhow!("Failed to convert amount {} to decimal", b))?;
+    let c = a
+        .checked_div(b)
+        .ok_or_else(|| anyhow!("Failed to divide {} and {}", a, b))?;
+    let c = c.round_dp(18); // the minimum value is 1 wei for Ethereum
+    let c = c
+        .to_f64()
+        .ok_or_else(|| anyhow!("Failed to convert amount {} to f64", c))?;
+    Ok(c)
+}
+
+pub fn multiplication_f64_f64(a: f64, b: f64) -> Result<f64> {
+    let a =
+        Decimal::from_f64(a).ok_or_else(|| anyhow!("Failed to convert amount {} to decimal", a))?;
+    let b =
+        Decimal::from_f64(b).ok_or_else(|| anyhow!("Failed to convert amount {} to decimal", b))?;
+    let c = a
+        .checked_mul(b)
+        .ok_or_else(|| anyhow!("Failed to multiply {} and {}", a, b))?;
+    let c = c.round_dp(8);
+    let c = c
+        .to_f64()
+        .ok_or_else(|| anyhow!("Failed to convert amount {} to f64", c))?;
+    Ok(c)
+}
+
+pub fn division_f64_f64(a: f64, b: f64) -> Result<f64> {
+    let a =
+        Decimal::from_f64(a).ok_or_else(|| anyhow!("Failed to convert amount {} to decimal", a))?;
+    let b =
+        Decimal::from_f64(b).ok_or_else(|| anyhow!("Failed to convert amount {} to decimal", b))?;
+    let c = a
+        .checked_div(b)
+        .ok_or_else(|| anyhow!("Failed to divide {} and {}", a, b))?;
+    let c = c.round_dp(8);
+    let c = c
+        .to_f64()
+        .ok_or_else(|| anyhow!("Failed to convert amount {} to f64", c))?;
+    Ok(c)
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/external/operators_basic.rs b/projects/web3/ConfidentialKlave/types/src/external/operators_basic.rs
new file mode 100644
index 0000000..1e69f88
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/operators_basic.rs
@@ -0,0 +1,60 @@
+// 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 crate::external::Email;
+use crate::share::{TaOperatorsBasic, UserID};
+use serde::{Deserialize, Serialize};
+use std::collections::HashSet;
+use std::iter::IntoIterator;
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct OperatorsBasic(HashSet<Email>);
+
+impl OperatorsBasic {
+    pub fn new(inner: HashSet<Email>) -> Self {
+        Self(inner)
+    }
+
+    pub fn distinct_operators(&self) -> HashSet<&Email> {
+        self.0.iter().collect()
+    }
+}
+
+impl IntoIterator for OperatorsBasic {
+    type Item = Email;
+    type IntoIter = std::collections::hash_set::IntoIter<Email>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.0.into_iter()
+    }
+}
+
+impl std::ops::Deref for OperatorsBasic {
+    type Target = HashSet<Email>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl From<OperatorsBasic> for TaOperatorsBasic {
+    fn from(operators_basic: OperatorsBasic) -> Self {
+        let inner: HashSet<UserID> = operators_basic.into_iter().map(|e| e.into()).collect();
+        Self::new(inner)
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/external/transfer.rs b/projects/web3/ConfidentialKlave/types/src/external/transfer.rs
new file mode 100644
index 0000000..0f407d0
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/transfer.rs
@@ -0,0 +1,220 @@
+// 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::collections::HashMap;
+use std::convert::TryInto;
+
+use crate::external::{division_u128_u128, multiplication_f64_u128};
+use crate::external::{AssetType, ExternalAddress};
+use crate::serde_util;
+use crate::share::{AccountId, WalletID};
+use anyhow::Result;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct FeeInfo {
+    // in min units, e.g. wei, satoshi
+    // note: for ETH, it is about 10 * 10^9 wei (10 Gwei), which is in the scope of f64
+    // for BTC, it is about 1.0~20.0 sat/vbyte
+    pub fee_rate: f64,
+    #[serde(with = "serde_util::u128_string")]
+    pub units: u128,
+    pub asset_type: AssetType,
+}
+
+impl FeeInfo {
+    pub fn new(fee_rate: f64, units: u128, asset_type: AssetType) -> Self {
+        Self {
+            fee_rate,
+            units,
+            asset_type,
+        }
+    }
+
+    pub fn fee(&self) -> Result<CkAmount> {
+        let fee = multiplication_f64_u128(self.fee_rate, self.units)?;
+        Ok(CkAmount::new(fee, self.asset_type))
+    }
+
+    pub fn fee_rate(&self) -> f64 {
+        self.fee_rate
+    }
+
+    pub fn fee_rate_try_to_u128(&self) -> Result<u128> {
+        // for ETH, the fee_rate is integer, so we can try to convert it to u128
+        self.fee_rate
+            .to_string()
+            .parse::<u128>()
+            .map_err(|_| anyhow::anyhow!("fee_rate is not integer"))
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct CkTransferInfo {
+    pub from_wallet: WalletID,   // from external client
+    pub from_account: AccountId, // fulfilled by webapi
+    pub to: ExternalAddress,
+    pub amount: CkAmount,
+    pub fee_info: FeeInfo,
+}
+
+impl CkTransferInfo {
+    pub fn from_account(&self) -> AccountId {
+        self.from_account.clone()
+    }
+
+    pub fn from_wallet(&self) -> WalletID {
+        self.from_wallet.clone()
+    }
+
+    pub fn recipient_string(&self) -> String {
+        self.to.to_string()
+    }
+
+    pub fn amount(&self) -> &CkAmount {
+        &self.amount
+    }
+
+    pub fn asset_type(&self) -> AssetType {
+        self.amount.asset_type()
+    }
+
+    // sending amount + fee, maybe in different asset type, e.g. sending USDT, fee in ETH
+    pub fn total_spend(&self) -> Result<HashMap<AssetType, CkAmount>> {
+        let mut result = HashMap::new();
+        let fee = self.fee_info.fee()?;
+        // if amount.asset_type == fee.asset_type, we can use try_add directly
+        if self.amount.asset_type() == fee.asset_type() {
+            let mut amount = self.amount;
+            amount.try_add(&fee)?;
+            result.insert(self.amount.asset_type(), amount);
+        } else {
+            // if amount.asset_type != fee.asset_type, we need to insert both amount and fee
+            result.insert(self.amount.asset_type(), self.amount);
+            result.insert(fee.asset_type(), fee);
+        }
+
+        Ok(result)
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct CkReversedTransferInfo {
+    pub from: ExternalAddress,
+    pub to_account: AccountId, // fulfilled by task
+    pub to_wallet: WalletID,   // fulfilled by task
+    pub amount: CkAmount,
+}
+
+// CkAmount is for internal use only
+#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
+#[serde(rename_all = "camelCase")]
+pub struct CkAmount {
+    #[serde(with = "serde_util::u128_string")]
+    value: u128,
+    asset_type: AssetType,
+}
+
+impl CkAmount {
+    pub fn zero(asset_type: AssetType) -> Self {
+        Self {
+            value: 0,
+            asset_type,
+        }
+    }
+
+    pub fn new(value: u128, asset_type: AssetType) -> Self {
+        Self { value, asset_type }
+    }
+
+    pub fn value(&self) -> u128 {
+        self.value
+    }
+
+    pub fn asset_type(&self) -> AssetType {
+        self.asset_type
+    }
+
+    pub fn try_compare(&self, other: &CkAmount) -> Result<std::cmp::Ordering> {
+        if self.asset_type != other.asset_type {
+            anyhow::bail!(
+                "asset type not match, current: {:?}, requested: {:?}",
+                self.asset_type,
+                other.asset_type
+            )
+        }
+        Ok(self.value.cmp(&other.value))
+    }
+
+    pub fn try_add(&mut self, other: &CkAmount) -> Result<()> {
+        if self.asset_type != other.asset_type {
+            anyhow::bail!(
+                "asset type not match, current: {:?}, requested: {:?}",
+                self.asset_type,
+                other.asset_type
+            )
+        }
+        self.value = self
+            .value
+            .checked_add(other.value())
+            .ok_or_else(|| anyhow::anyhow!("add overflow: {} + {}", self.value, other.value()))?;
+        Ok(())
+    }
+
+    pub fn try_add_u128(&mut self, other: u128) -> Result<()> {
+        self.value = self
+            .value
+            .checked_add(other)
+            .ok_or_else(|| anyhow::anyhow!("add overflow: {} + {}", self.value, other))?;
+        Ok(())
+    }
+
+    pub fn try_sub(&mut self, other: &CkAmount) -> Result<()> {
+        if self.asset_type != other.asset_type {
+            anyhow::bail!(
+                "asset type not match, current: {:?}, requested: {:?}",
+                self.asset_type,
+                other.asset_type
+            )
+        }
+        self.value = self
+            .value
+            .checked_sub(other.value())
+            .ok_or_else(|| anyhow::anyhow!("{} is less than {}", self.value, other.value()))?;
+        Ok(())
+    }
+
+    pub fn try_sub_u128(&mut self, other: u128) -> Result<()> {
+        self.value = self
+            .value
+            .checked_sub(other)
+            .ok_or_else(|| anyhow::anyhow!("{} is less than {}", self.value, other))?;
+        Ok(())
+    }
+
+    // converting to email content
+    pub fn try_to_f64(&self) -> Result<f64> {
+        division_u128_u128(self.value, 10u128.pow(self.asset_type.config().decimals()))
+    }
+
+    // converting to bitcoin::Amount(u64)
+    pub fn try_to_u64(&self) -> Result<u64> {
+        self.value
+            .try_into()
+            .map_err(|_| anyhow::anyhow!("value {} is too large to convert to u64", self.value))
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/external/tx.rs b/projects/web3/ConfidentialKlave/types/src/external/tx.rs
new file mode 100644
index 0000000..de4bc70
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/tx.rs
@@ -0,0 +1,56 @@
+// 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};
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub enum TxSubmissionResult {
+    Accepted(NetworkTxHash),
+    Rejected(NetworkErrMsg),
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct NetworkTxHash(String);
+
+impl std::convert::From<String> for NetworkTxHash {
+    fn from(s: String) -> Self {
+        Self(s)
+    }
+}
+
+impl std::ops::Deref for NetworkTxHash {
+    type Target = String;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct NetworkErrMsg(String);
+
+impl std::convert::From<String> for NetworkErrMsg {
+    fn from(msg: String) -> Self {
+        Self(msg)
+    }
+}
+
+impl std::convert::From<std::io::Error> for NetworkErrMsg {
+    fn from(e: std::io::Error) -> Self {
+        Self(e.to_string())
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/external/user.rs b/projects/web3/ConfidentialKlave/types/src/external/user.rs
new file mode 100644
index 0000000..6a9d996
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/user.rs
@@ -0,0 +1,56 @@
+// 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 crate::share::UserID;
+use anyhow::{ensure, Result};
+use basic_utils::keccak_hash_to_bytes;
+use serde::{Deserialize, Serialize};
+use std::convert::TryFrom;
+
+// user email
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
+#[serde(rename_all = "camelCase")]
+pub struct Email(pub String);
+
+impl TryFrom<String> for Email {
+    type Error = anyhow::Error;
+
+    fn try_from(src: String) -> Result<Self> {
+        // check email format
+        let parts: Vec<&str> = src.split('@').collect();
+        ensure!(
+            parts.len() == 2 && !parts[0].is_empty() && parts[1].contains('.'),
+            "invalid email format"
+        );
+        Ok(Email(src))
+    }
+}
+
+impl From<Email> for String {
+    fn from(email: Email) -> Self {
+        email.0
+    }
+}
+
+impl From<Email> for UserID {
+    fn from(email: Email) -> Self {
+        let mut bytes = [0u8; 8];
+        let v = keccak_hash_to_bytes(&email.0);
+        bytes.copy_from_slice(&v[..8]);
+        UserID::new(bytes)
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/external/viewers_basic.rs b/projects/web3/ConfidentialKlave/types/src/external/viewers_basic.rs
new file mode 100644
index 0000000..3d3d134
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/external/viewers_basic.rs
@@ -0,0 +1,52 @@
+// 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 crate::external::Email;
+use serde::{Deserialize, Serialize};
+use std::collections::HashSet;
+use std::iter::IntoIterator;
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct ViewersBasic(HashSet<Email>);
+
+impl ViewersBasic {
+    pub fn new(inner: HashSet<Email>) -> Self {
+        Self(inner)
+    }
+
+    pub fn distinct_viewers(&self) -> HashSet<&Email> {
+        self.0.iter().collect()
+    }
+}
+
+impl IntoIterator for ViewersBasic {
+    type Item = Email;
+    type IntoIter = std::collections::hash_set::IntoIter<Email>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.0.into_iter()
+    }
+}
+
+impl std::ops::Deref for ViewersBasic {
+    type Target = HashSet<Email>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
diff --git a/projects/web3/ConfidentialKlave/types/src/lib.rs b/projects/web3/ConfidentialKlave/types/src/lib.rs
new file mode 100644
index 0000000..3267bb5
--- /dev/null
+++ b/projects/web3/ConfidentialKlave/types/src/lib.rs
@@ -0,0 +1,31 @@
+// 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
... 3495 lines suppressed ...


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@teaclave.apache.org
For additional commands, e-mail: commits-help@teaclave.apache.org