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