diff --git a/.github/workflows/vcl.yml b/.github/workflows/vcl.yml new file mode 100644 index 0000000..2aeb193 --- /dev/null +++ b/.github/workflows/vcl.yml @@ -0,0 +1,17 @@ +name: Rust + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Nightly default + run: rustup default nightly + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f49938a --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/target +/**/target +**/*.rs.bk +.DS_Store +.vscode/ +.idea/* +*.lock +*.iml +*.tar.gz diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..de39635 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,17 @@ +blank_lines_upper_bound = 1 +format_code_in_doc_comments = true +format_macro_matchers = true +format_strings = true +indent_style = "Block" +match_block_trailing_comma = true +max_width = 80 +merge_imports = true +normalize_comments = true +normalize_doc_attributes = true +overflow_delimited_expr = true +reorder_imports = true +reorder_modules = true +tab_spaces = 4 +use_try_shorthand = true +where_single_line = true +wrap_comments = true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..db12f39 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# 贡献代码 + +非常感谢能有心为WeDPR-Lab贡献代码! + +## 分支策略 + +项目采用[git-flow](https://jeffkreeftmeijer.com/git-flow/)的分支策略。 + +* master:最新的稳定分支 +* dev:待发布的稳定分支 +* feature-xxxx:一个正在开发xxxx特性分支 +* bugfix-xxxx:一个正在修bug xxxx的分支 + +## 贡献方法 + +### Issue + +可直接去[issues page](https://github.com/WeBankBlockchain/WeDPR-Lab-Core/issues)提issue。 + +### 修复bug + +1. Fork本仓库到个人仓库 +2. 从个人仓库的master分支拉出一个bugfix-xxxx分支 +3. 在bugfix-xxxx上修复bug +4. 测试修复的bug +5. PR(Pull Request)到本仓库的dev分支 +6. 等待社区review这个PR +7. PR合入,bug修复完成! + +### 开发新特性 + +1. Fork本仓库到个人仓库 +2. 从个人仓库的dev分支拉出一个feature-xxxx分支 +3. 在feature-xxxx上进行特性开发 +4. 不定期的从本仓库的dev分支pull最新的改动到feature-xxxx分支 +5. 测试新特性 +6. PR(Pull Request)到本参考的dev分支 +7. 等待社区review这个PR +8. PR合入,特性开发完成! diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0aea56b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] + +members = [ +"crypto", +"solution/verifiable_confidential_ledger", +"common/utils", +"common/macros", +"common/protos", +] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9853e08 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# 项目背景 + +![WeDPR](https://wedpr-lab.readthedocs.io/zh_CN/latest/_static/images/wedpr_logo.png) + +WeDPR是一系列**即时可用场景式**隐私保护高效解决方案套件和服务(参见[WeDPR白皮书](https://mp.weixin.qq.com/s?__biz=MzU0MDY4MDMzOA==&mid=2247483910&idx=1&sn=7b647dec9f046f1e6f94d103897f7efb&scene=19#wechat_redirect)),由微众银行区块链团队自主研发。方案致力于解决业务数字化中隐私不“隐”、共享协作不可控等隐私保护风险痛点,消除隐私主体的隐私顾虑和业务创新的合规壁垒,助力基于隐私数据的核心价值互联和新兴商业探索,营造公平、对等、共赢的多方数据协作环境,达成数据价值跨主体融合和数据治理的可控平衡。 + +WeDPR具备以下特色和优势: + +- **场景式解决方案**:已基于隐匿支付、匿名投票、匿名竞拍、匿名摇号和选择性认证披露等应用场景,提炼出公开可验证密文账本、多方密文决策、多方密文排名、多方密文计算、多方安全随机数生成、选择性密文披露等高效技术方案框架模板,5分钟一键构建示例应用。 +- **即时可用**:高性能、高易用、跨平台跨语言实现、不依赖中心化可信服务、不依赖可信硬件、支持国密算法标准、隐私效果公开可验证。 +- **透明可控**:隐私控制回归属主,杜绝数据未授权使用,在『数据可用而不可见』的基础上,进一步实现数据使用全程可监管、可追溯、可验证。 + +WeDPR全面拥抱开放,将陆续开源一系列核心算法组件,进一步提升系统安全性的透明度,提供更透明、更可信的隐私保护效果。WeDPR-Lab就是这一系列开源的**核心算法组件**的集合。 + +首组开源的核心算法组件将围绕**公开可验证密文账本**(Verifiable Confidential Ledger, VCL)解决方案进行,在数字化资产流通场景(如支付、清算)、涉及多方之间共享账本信息的场景(如供应链金融、跨境金融服务)等均可广泛应用。 + +本次开源(v1.0.0版本)包含的主要内容如下: + +- 公开可验证密文账本的一个交互式样例,实现密文金额发行、密文金额四则运算关系验证、密文金额范围验证等功能; +- Rust SDK,封装底层算法,提供易用、易扩展、跨语言的编程接口; +- 三类零知识证明算法的高效稳定实现,包括密文加和关系证明、密文乘积关系证明、密文范围证明; +- 其他基础工具代码。 + +我们期望能够通过代码开源的方式: + +- 有效降低使用隐私保护算法组件的技术门槛; +- 减少业务系统集成隐私保护特性的开发成本; +- 助力全行业伙伴安全、合规地开展数据业务。 + +欢迎社区伙伴参与WeDPR-Lab的共建,一起为可信开放数字新生态的构建打造坚实、可靠的技术底座。 + +## 安装 + +### 安装Rust环境 + +1. 安装nightly版本的Rust开发环境,可参考[示例安装指引](https://wiki.jikexueyuan.com/project/rust/nightly-rust.html)。 +2. 若有疑问,可进一步参考[Rust官方文档](https://www.rust-lang.org/tools/install)。 + +### 下载WeDPR-Lab源代码 + +使用git命令行工具,执行如下命令。 + +```bash +git clone https://github.com/WeBankBlockchain/WeDPR-Lab-Core.git +``` + +## 运行Demo + +### 运行公开可验证密文账本Demo + +```bash +cd WeDPR-Lab-Core/solution/verifiable_confidential_ledger +cargo run +``` + +在VCL解决方案子目录(即`verifiable_confidential_ledger`目录)中,运行cargo run之后,按照demo指引,选择演示的语言,按步骤输入即可进行体验。 + +## 接口文档 + +### 生成并查看Rust SDK接口文档 + +在本项目的根目录(即`WeDPR-Lab-Core`目录)中,运行如下命令。 + +```bash +cargo doc +``` + +以上命令将根据代码中的注释,在`target/doc`子目录中,生成的SDK接口文档。 + +进入`target/doc`文档目录后,会看到所有SDK相关的包名(包含WeDPR-Lab和其他依赖包),进入以下任意一个包名的目录,用网页浏览器打开其中的`index.html`文件,便可查看WeDPR-Lab相关的接口说明。 + +- verifiable_confidential_ledger +- wedpr_crypto +- wedpr_macros +- wedpr_protos +- wedpr_utils + +## 其他相关文档 + +- [WeDPR方案白皮书](https://mp.weixin.qq.com/s?__biz=MzU0MDY4MDMzOA==&mid=2247483910&idx=1&sn=7b647dec9f046f1e6f94d103897f7efb&scene=19#wechat_redirect) +- [WeDPR-Lab用户文档](https://wedpr-lab.readthedocs.io/zh_CN/latest/index.html) + +## 项目贡献 + +- 点亮我们的小星星(点击项目左上方Star按钮) +- 提交代码(Pull Request),参考我们的代码[贡献流程](./CONTRIBUTING.md) +- [提问和提交BUG](https://github.com/WeBankBlockchain/WeDPR-Lab-Core/issues) diff --git a/common/macros/Cargo.toml b/common/macros/Cargo.toml new file mode 100644 index 0000000..cfb2f28 --- /dev/null +++ b/common/macros/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "wedpr_macros" +version = "1.0.0" +authors = ["WeDPR "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/common/macros/src/lib.rs b/common/macros/src/lib.rs new file mode 100644 index 0000000..b971f4a --- /dev/null +++ b/common/macros/src/lib.rs @@ -0,0 +1,63 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Library of shared macros. + +/// Global flag of enabling debug output. +pub const ENABLE_DEBUG_OUTPUT: bool = true; + +/// Prints debug output that can be disabled by setting a global flag. +#[macro_export] +macro_rules! wedpr_println { + () => ( print!("\n")); + ($($arg:tt)*) => { + if $crate::ENABLE_DEBUG_OUTPUT { + print!("{}:{}: ", file!(), line!()); + println!($($arg)*); + } + }; +} + +/// Macros to handle errors and return bool type instead of Result type, which +/// are mainly used to simplify type conversions for Rust FFI. + +/// Converts a string into a point if succeeded, otherwise returns false. +#[macro_export] +macro_rules! string_to_point { + ($param:expr) => { + match string_to_point($param) { + Ok(v) => v, + Err(_) => { + wedpr_println!("macro string_to_point failed"); + return false; + }, + } + }; +} + +/// Converts a string into a scalar if succeeded, otherwise returns false. +#[macro_export] +macro_rules! string_to_scalar { + ($param:expr) => { + match string_to_scalar($param) { + Ok(v) => v, + Err(_) => { + wedpr_println!("macro string_to_scalar failed"); + return false; + }, + } + }; +} + +/// Converts a string into a bytes vector if succeeded, otherwise returns false. +#[macro_export] +macro_rules! string_to_bytes { + ($param:expr) => { + match string_to_bytes($param) { + Ok(v) => v, + Err(_) => { + wedpr_println!("macro string_to_bytes failed"); + return false; + }, + } + }; +} diff --git a/common/protos/Cargo.toml b/common/protos/Cargo.toml new file mode 100644 index 0000000..1d3265d --- /dev/null +++ b/common/protos/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "wedpr_protos" +version = "1.0.0" +authors = ["WeDPR "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +protobuf = "2.10.1" +protoc-rust = "2.10.1" diff --git a/common/protos/crypto/zkp.proto b/common/protos/crypto/zkp.proto new file mode 100644 index 0000000..9435620 --- /dev/null +++ b/common/protos/crypto/zkp.proto @@ -0,0 +1,19 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +syntax = "proto3"; + +package com.webank.wedpr.crypto.proto; +option java_package = "com.webank.wedpr.crypto.proto"; +option java_multiple_files = true; + +// ZKP data to verify the balance relationship among value commitments. +// For example, given C(x), C(y), C(z), this proof data can be used to +// verify whether x * y =? z. +message BalanceProof { + string c = 1; + string m1 = 2; + string m2 = 3; + string m3 = 4; + string m4 = 5; + string m5 = 6; +} \ No newline at end of file diff --git a/common/protos/src/generated/mod.rs b/common/protos/src/generated/mod.rs new file mode 100644 index 0000000..bfe8f3e --- /dev/null +++ b/common/protos/src/generated/mod.rs @@ -0,0 +1,3 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +pub mod zkp; diff --git a/common/protos/src/generated/zkp.rs b/common/protos/src/generated/zkp.rs new file mode 100644 index 0000000..e3eba26 --- /dev/null +++ b/common/protos/src/generated/zkp.rs @@ -0,0 +1,416 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +// This file is generated by rust-protobuf 2.16.2. Do not edit +// @generated + +// https://github.com/rust-lang/rust-clippy/issues/702 +#![allow(unknown_lints)] +#![allow(clippy::all)] + +#![allow(unused_attributes)] +#![rustfmt::skip] + +#![allow(box_pointers)] +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(trivial_casts)] +#![allow(unused_imports)] +#![allow(unused_results)] +//! Generated file from `crypto/zkp.proto` + +/// Generated files are compatible only with the same version +/// of protobuf runtime. +// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_16_2; + +#[derive(PartialEq,Clone,Default)] +pub struct BalanceProof { + // message fields + pub c: ::std::string::String, + pub m1: ::std::string::String, + pub m2: ::std::string::String, + pub m3: ::std::string::String, + pub m4: ::std::string::String, + pub m5: ::std::string::String, + // special fields + pub unknown_fields: ::protobuf::UnknownFields, + pub cached_size: ::protobuf::CachedSize, +} + +impl<'a> ::std::default::Default for &'a BalanceProof { + fn default() -> &'a BalanceProof { + ::default_instance() + } +} + +impl BalanceProof { + pub fn new() -> BalanceProof { + ::std::default::Default::default() + } + + // string c = 1; + + + pub fn get_c(&self) -> &str { + &self.c + } + pub fn clear_c(&mut self) { + self.c.clear(); + } + + // Param is passed by value, moved + pub fn set_c(&mut self, v: ::std::string::String) { + self.c = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_c(&mut self) -> &mut ::std::string::String { + &mut self.c + } + + // Take field + pub fn take_c(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.c, ::std::string::String::new()) + } + + // string m1 = 2; + + + pub fn get_m1(&self) -> &str { + &self.m1 + } + pub fn clear_m1(&mut self) { + self.m1.clear(); + } + + // Param is passed by value, moved + pub fn set_m1(&mut self, v: ::std::string::String) { + self.m1 = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_m1(&mut self) -> &mut ::std::string::String { + &mut self.m1 + } + + // Take field + pub fn take_m1(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.m1, ::std::string::String::new()) + } + + // string m2 = 3; + + + pub fn get_m2(&self) -> &str { + &self.m2 + } + pub fn clear_m2(&mut self) { + self.m2.clear(); + } + + // Param is passed by value, moved + pub fn set_m2(&mut self, v: ::std::string::String) { + self.m2 = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_m2(&mut self) -> &mut ::std::string::String { + &mut self.m2 + } + + // Take field + pub fn take_m2(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.m2, ::std::string::String::new()) + } + + // string m3 = 4; + + + pub fn get_m3(&self) -> &str { + &self.m3 + } + pub fn clear_m3(&mut self) { + self.m3.clear(); + } + + // Param is passed by value, moved + pub fn set_m3(&mut self, v: ::std::string::String) { + self.m3 = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_m3(&mut self) -> &mut ::std::string::String { + &mut self.m3 + } + + // Take field + pub fn take_m3(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.m3, ::std::string::String::new()) + } + + // string m4 = 5; + + + pub fn get_m4(&self) -> &str { + &self.m4 + } + pub fn clear_m4(&mut self) { + self.m4.clear(); + } + + // Param is passed by value, moved + pub fn set_m4(&mut self, v: ::std::string::String) { + self.m4 = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_m4(&mut self) -> &mut ::std::string::String { + &mut self.m4 + } + + // Take field + pub fn take_m4(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.m4, ::std::string::String::new()) + } + + // string m5 = 6; + + + pub fn get_m5(&self) -> &str { + &self.m5 + } + pub fn clear_m5(&mut self) { + self.m5.clear(); + } + + // Param is passed by value, moved + pub fn set_m5(&mut self, v: ::std::string::String) { + self.m5 = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_m5(&mut self) -> &mut ::std::string::String { + &mut self.m5 + } + + // Take field + pub fn take_m5(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.m5, ::std::string::String::new()) + } +} + +impl ::protobuf::Message for BalanceProof { + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> { + while !is.eof()? { + let (field_number, wire_type) = is.read_tag_unpack()?; + match field_number { + 1 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.c)?; + }, + 2 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.m1)?; + }, + 3 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.m2)?; + }, + 4 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.m3)?; + }, + 5 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.m4)?; + }, + 6 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.m5)?; + }, + _ => { + ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u32 { + let mut my_size = 0; + if !self.c.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.c); + } + if !self.m1.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.m1); + } + if !self.m2.is_empty() { + my_size += ::protobuf::rt::string_size(3, &self.m2); + } + if !self.m3.is_empty() { + my_size += ::protobuf::rt::string_size(4, &self.m3); + } + if !self.m4.is_empty() { + my_size += ::protobuf::rt::string_size(5, &self.m4); + } + if !self.m5.is_empty() { + my_size += ::protobuf::rt::string_size(6, &self.m5); + } + my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); + self.cached_size.set(my_size); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { + if !self.c.is_empty() { + os.write_string(1, &self.c)?; + } + if !self.m1.is_empty() { + os.write_string(2, &self.m1)?; + } + if !self.m2.is_empty() { + os.write_string(3, &self.m2)?; + } + if !self.m3.is_empty() { + os.write_string(4, &self.m3)?; + } + if !self.m4.is_empty() { + os.write_string(5, &self.m4)?; + } + if !self.m5.is_empty() { + os.write_string(6, &self.m5)?; + } + os.write_unknown_fields(self.get_unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn get_cached_size(&self) -> u32 { + self.cached_size.get() + } + + fn get_unknown_fields(&self) -> &::protobuf::UnknownFields { + &self.unknown_fields + } + + fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields { + &mut self.unknown_fields + } + + fn as_any(&self) -> &dyn (::std::any::Any) { + self as &dyn (::std::any::Any) + } + fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) { + self as &mut dyn (::std::any::Any) + } + fn into_any(self: ::std::boxed::Box) -> ::std::boxed::Box { + self + } + + fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor { + Self::descriptor_static() + } + + fn new() -> BalanceProof { + BalanceProof::new() + } + + fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT; + descriptor.get(|| { + let mut fields = ::std::vec::Vec::new(); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "c", + |m: &BalanceProof| { &m.c }, + |m: &mut BalanceProof| { &mut m.c }, + )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "m1", + |m: &BalanceProof| { &m.m1 }, + |m: &mut BalanceProof| { &mut m.m1 }, + )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "m2", + |m: &BalanceProof| { &m.m2 }, + |m: &mut BalanceProof| { &mut m.m2 }, + )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "m3", + |m: &BalanceProof| { &m.m3 }, + |m: &mut BalanceProof| { &mut m.m3 }, + )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "m4", + |m: &BalanceProof| { &m.m4 }, + |m: &mut BalanceProof| { &mut m.m4 }, + )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "m5", + |m: &BalanceProof| { &m.m5 }, + |m: &mut BalanceProof| { &mut m.m5 }, + )); + ::protobuf::reflect::MessageDescriptor::new_pb_name::( + "BalanceProof", + fields, + file_descriptor_proto() + ) + }) + } + + fn default_instance() -> &'static BalanceProof { + static instance: ::protobuf::rt::LazyV2 = ::protobuf::rt::LazyV2::INIT; + instance.get(BalanceProof::new) + } +} + +impl ::protobuf::Clear for BalanceProof { + fn clear(&mut self) { + self.c.clear(); + self.m1.clear(); + self.m2.clear(); + self.m3.clear(); + self.m4.clear(); + self.m5.clear(); + self.unknown_fields.clear(); + } +} + +impl ::std::fmt::Debug for BalanceProof { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for BalanceProof { + fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { + ::protobuf::reflect::ReflectValueRef::Message(self) + } +} + +static file_descriptor_proto_data: &'static [u8] = b"\ + \n\x10crypto/zkp.proto\x12\x1dcom.webank.wedpr.crypto.proto\"l\n\x0cBala\ + nceProof\x12\x0c\n\x01c\x18\x01\x20\x01(\tR\x01c\x12\x0e\n\x02m1\x18\x02\ + \x20\x01(\tR\x02m1\x12\x0e\n\x02m2\x18\x03\x20\x01(\tR\x02m2\x12\x0e\n\ + \x02m3\x18\x04\x20\x01(\tR\x02m3\x12\x0e\n\x02m4\x18\x05\x20\x01(\tR\x02\ + m4\x12\x0e\n\x02m5\x18\x06\x20\x01(\tR\x02m5B!\n\x1dcom.webank.wedpr.cry\ + pto.protoP\x01b\x06proto3\ +"; + +static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; + +fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto { + ::protobuf::parse_from_bytes(file_descriptor_proto_data).unwrap() +} + +pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { + file_descriptor_proto_lazy.get(|| { + parse_descriptor_proto() + }) +} diff --git a/common/protos/src/lib.rs b/common/protos/src/lib.rs new file mode 100644 index 0000000..cd1d2cc --- /dev/null +++ b/common/protos/src/lib.rs @@ -0,0 +1,5 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Library of protobuf definitions and their generated code. + +pub mod generated; diff --git a/common/protos/src/main.rs b/common/protos/src/main.rs new file mode 100644 index 0000000..1095e56 --- /dev/null +++ b/common/protos/src/main.rs @@ -0,0 +1,34 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Tool to compile proto files to rust files. + +extern crate protoc_rust; +use protoc_rust::{Codegen, Customize}; +use std::env; + +/// Uses `cargo run` in this sub crate (wedpr_protos) to compile proto files to +/// rust files. You need to update the generated files every time you modify the +/// existing proto files or add new proto files. +#[cfg_attr(tarpaulin, skip)] +fn main() { + let args: Vec = env::args().collect(); + if args.len() == 1 { + generate_proto_for_all(); + } else { + // TODO: Add more control on compiling proto files to rust files. + } +} + +/// Compiles proto files to rust files. +#[cfg_attr(tarpaulin, skip)] +fn generate_proto_for_all() { + Codegen::new() + .out_dir("./src/generated/") + .includes(&["."]) + .inputs(&["crypto/zkp.proto"]) + .customize(Customize { + ..Default::default() + }) + .run() + .expect("protoc should not fail"); +} diff --git a/common/utils/Cargo.toml b/common/utils/Cargo.toml new file mode 100644 index 0000000..73b4680 --- /dev/null +++ b/common/utils/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "wedpr_utils" +version = "1.0.0" +authors = ["WeDPR "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +failure = "0.1" diff --git a/common/utils/src/error.rs b/common/utils/src/error.rs new file mode 100644 index 0000000..4eef5dc --- /dev/null +++ b/common/utils/src/error.rs @@ -0,0 +1,15 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! WeDPR errors definitions. + +#[derive(Fail, Clone, Debug, Eq, PartialEq)] +pub enum WedprError { + #[fail(display = "Verification failed")] + VerificationError, + #[fail(display = "Argument is invalid")] + ArgumentError, + #[fail(display = "Data cannot be parsed")] + FormatError, + #[fail(display = "Data cannot be decoded")] + DecodeError, +} diff --git a/common/utils/src/lib.rs b/common/utils/src/lib.rs new file mode 100644 index 0000000..f444778 --- /dev/null +++ b/common/utils/src/lib.rs @@ -0,0 +1,8 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Library of shared utilities. + +#[macro_use] +extern crate failure; + +pub mod error; diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml new file mode 100644 index 0000000..0f8419a --- /dev/null +++ b/crypto/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "wedpr_crypto" +version = "1.0.0" +authors = ["WeDPR "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +base64 = "0.10.1" +bulletproofs = "1.0.4" +curve25519-dalek = { version = "1", features = ["serde"] } +lazy_static = "0.2" +merlin = "1" +rand = "0.6" +secp256k1 = { version = "0.17.2", features = ["recovery"] } +sha3 = "0.8" +wedpr_macros = { path = "../common/macros/"} +wedpr_protos = { path = "../common/protos/" } +wedpr_utils = { path = "../common/utils" } diff --git a/crypto/src/coder/base_x.rs b/crypto/src/coder/base_x.rs new file mode 100644 index 0000000..9aea284 --- /dev/null +++ b/crypto/src/coder/base_x.rs @@ -0,0 +1,42 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! BaseX encoding and decoding functions. + +extern crate base64; + +use crate::coder::Coder; +use wedpr_utils::error::WedprError; + +#[derive(Default, Debug, Clone)] +pub struct WedprBase64 {} + +/// Implements Base64 as a Coder instance. +impl Coder for WedprBase64 { + fn encode>(&self, input: &T) -> String { + base64::encode(input) + } + + fn decode(&self, input: &str) -> Result, WedprError> { + match base64::decode(input) { + Ok(v) => return Ok(v), + Err(_) => { + wedpr_println!("Base64 decoding failed, input was: {}", input); + return Err(WedprError::DecodeError); + }, + }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_base64() { + let base64 = WedprBase64::default(); + let str = "g6sLGLyLvnkmE6V0Ico="; + let bytes = base64.decode(&str).unwrap(); + let recovered_str = base64.encode(&bytes); + assert_eq!(str, recovered_str); + } +} diff --git a/crypto/src/coder/mod.rs b/crypto/src/coder/mod.rs new file mode 100644 index 0000000..176d4cd --- /dev/null +++ b/crypto/src/coder/mod.rs @@ -0,0 +1,15 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Data encoding and decoding functions. + +pub mod base_x; + +use wedpr_utils::error::WedprError; + +/// Trait of a replaceable coder algorithm. +pub trait Coder { + /// Converts bytes to an encoded string. + fn encode>(&self, input: &T) -> String; + /// Decodes an encoded string to a bytes vector. + fn decode(&self, input: &str) -> Result, WedprError>; +} diff --git a/crypto/src/constant.rs b/crypto/src/constant.rs new file mode 100644 index 0000000..221b7c5 --- /dev/null +++ b/crypto/src/constant.rs @@ -0,0 +1,56 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Shared constants. + +#![allow(deprecated)] +use curve25519_dalek::{ + constants::{RISTRETTO_BASEPOINT_COMPRESSED, RISTRETTO_BASEPOINT_POINT}, + ristretto::RistrettoPoint, +}; +extern crate secp256k1; +use crate::{ + coder::base_x::WedprBase64, hash::keccak256::WedprKeccak256, + signature::secp256k1::WedprSecp256k1Recover, +}; +use secp256k1::{All, Secp256k1, VerifyOnly}; +use sha3::Sha3_512; + +lazy_static! { + /// A base point used by various crypto algorithms. + pub static ref BASEPOINT_G1: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; + /// Another base point used by various crypto algorithms. + pub static ref BASEPOINT_G2: RistrettoPoint = + RistrettoPoint::hash_from_bytes::( + RISTRETTO_BASEPOINT_COMPRESSED.as_bytes() + ); + + /// Shared secp256k1 instance initialized for verification function only. + pub static ref SECP256K1_VERIFY: Secp256k1 = Secp256k1::verification_only(); + /// Shared secp256k1 instance initialized for all functions. + pub static ref SECP256K1_ALL: Secp256k1 = Secp256k1::new(); + + /// Shared coder algorithm reference for quick implementation replacement. + /// Other code should use this reference, and not directly use a specific implementation. + pub static ref CODER: WedprBase64 = WedprBase64::default(); + + /// Shared signature algorithm reference for quick implementation replacement. + /// Other code should use this reference, and not directly use a specific implementation. + pub static ref SIGNATURE: WedprSecp256k1Recover = + WedprSecp256k1Recover::default(); + + /// Shared hash algorithm reference for quick implementation replacement. + /// Other code should use this reference, and not directly use a specific implementation. + pub static ref HASH: WedprKeccak256 = WedprKeccak256::default(); +} + +/// Serialized data size of a point. +pub const RISTRETTO_POINT_SIZE_IN_BYTES: usize = 32; + +/// Constants only used by tests. +pub mod tests { + // secp256k1 test key pair + pub const SECP256K1_TEST_SECRET_KEY: &str = + "EMGwfgpqDQVUsVO7jxUniSNYxTPjGcbbf6eikaAIKog="; + pub const SECP256K1_TEST_PUBLIC_KEY: &str = + "ApBrmk0PHxPp4ElvnhlmwEiAklVdDbX+MicqML591eC2"; +} diff --git a/crypto/src/hash/keccak256.rs b/crypto/src/hash/keccak256.rs new file mode 100644 index 0000000..e39f2b3 --- /dev/null +++ b/crypto/src/hash/keccak256.rs @@ -0,0 +1,35 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Keccak256 hash functions. + +extern crate sha3; +use sha3::{Digest, Keccak256}; + +use crate::hash::Hash; + +#[derive(Default, Debug, Clone)] +pub struct WedprKeccak256 {} + +/// Implements FISCO-BCOS-compatible Keccak256 as a Hash instance. +impl Hash for WedprKeccak256 { + fn hash(&self, msg: &str) -> Vec { + let mut mes = Keccak256::default(); + mes.input(msg); + mes.result().to_vec() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::utils::bytes_to_string; + + #[test] + fn test_keccak256() { + let keccak256 = WedprKeccak256::default(); + let msg = "WeDPR"; + let computed_hash = bytes_to_string(&keccak256.hash(msg)); + let expected_hash = "g6sLGLyLvnkOSvBcQdKNSPx8m4XmAogueNMmE6V0Ico="; + assert_eq!(expected_hash, computed_hash); + } +} diff --git a/crypto/src/hash/mod.rs b/crypto/src/hash/mod.rs new file mode 100644 index 0000000..3760990 --- /dev/null +++ b/crypto/src/hash/mod.rs @@ -0,0 +1,11 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Data hash functions. + +pub mod keccak256; + +/// Trait of a replaceable hash algorithm. +pub trait Hash { + /// Generates a fixed length hash bytes vector from any string. + fn hash(&self, msg: &str) -> Vec; +} diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs new file mode 100644 index 0000000..cd1fc47 --- /dev/null +++ b/crypto/src/lib.rs @@ -0,0 +1,15 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Library of crypto and related functions. + +#[macro_use] +extern crate wedpr_macros; +#[macro_use] +extern crate lazy_static; + +pub mod coder; +pub mod constant; +pub mod hash; +pub mod signature; +pub mod utils; +pub mod zkp; diff --git a/crypto/src/signature/mod.rs b/crypto/src/signature/mod.rs new file mode 100644 index 0000000..f08752f --- /dev/null +++ b/crypto/src/signature/mod.rs @@ -0,0 +1,24 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Data signature functions. + +use wedpr_utils::error::WedprError; + +pub mod secp256k1; + +/// Trait of a replaceable signature algorithm. +pub trait Signature { + /// Signs a message hash with the private key. + fn sign( + &self, + private_key: &str, + msg_hash_str: &str, + ) -> Result; + /// Verifies a message hash with the public key. + fn verify( + &self, + public_key: &str, + msg_hash_str: &str, + signature: &str, + ) -> bool; +} diff --git a/crypto/src/signature/secp256k1.rs b/crypto/src/signature/secp256k1.rs new file mode 100644 index 0000000..1287906 --- /dev/null +++ b/crypto/src/signature/secp256k1.rs @@ -0,0 +1,147 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Secp256k1 signature functions. + +use wedpr_utils::error::WedprError; +extern crate secp256k1; +use self::secp256k1::{ + recovery::{RecoverableSignature, RecoveryId}, + Message, PublicKey, SecretKey, +}; + +use crate::{ + constant::{SECP256K1_ALL, SECP256K1_VERIFY}, + signature::Signature, + utils::{bytes_to_string, string_to_bytes}, +}; + +#[derive(Default, Debug, Clone)] +pub struct WedprSecp256k1Recover {} + +pub const FISCO_BCOS_SIGNATURE_DATA_LENGTH: usize = 65; +pub const FISCO_BCOS_SIGNATURE_END_INDEX: usize = + FISCO_BCOS_SIGNATURE_DATA_LENGTH - 1; + +/// Implements FISCO-BCOS-compatible Secp256k1 as a Signature instance. +/// The signature data contains two parts: +/// sig\[0..64\): signature for the message hash. +/// sig\[64\]: recovery id. +impl Signature for WedprSecp256k1Recover { + fn sign( + &self, + private_key: &str, + msg_hash_str: &str, + ) -> Result + { + let msg_hash = string_to_bytes(msg_hash_str)?; + let sk_str_bytes = string_to_bytes(private_key)?; + let secret_key = match SecretKey::from_slice(&sk_str_bytes) { + Ok(v) => v, + Err(_) => { + wedpr_println!("Getting private key failed"); + return Err(WedprError::FormatError); + }, + }; + let message_send = Message::from_slice(&msg_hash).expect("32 bytes"); + let sig = SECP256K1_ALL.sign_recoverable(&message_send, &secret_key); + let (recid, signature_bytes) = &sig.serialize_compact(); + let mut vec_sig = signature_bytes.to_vec(); + vec_sig.push(recid.to_i32() as u8); + Ok(bytes_to_string(&vec_sig)) + } + + fn verify( + &self, + public_key: &str, + msg_hash_str: &str, + signature: &str, + ) -> bool + { + let msg_hash = string_to_bytes!(msg_hash_str); + + let message_receive = Message::from_slice(&msg_hash).expect("32 bytes"); + let pk_str_bytes = string_to_bytes!(&public_key); + let pub_str_get = match PublicKey::from_slice(&pk_str_bytes) { + Ok(v) => v, + Err(_) => { + wedpr_println!("Parsing public key to failed"); + return false; + }, + }; + let sig_result_hex = string_to_bytes!(signature); + if sig_result_hex.len() != FISCO_BCOS_SIGNATURE_DATA_LENGTH { + wedpr_println!("Sigature length is not 65"); + return false; + }; + let recid = match RecoveryId::from_i32( + sig_result_hex[FISCO_BCOS_SIGNATURE_END_INDEX] as i32, + ) { + Ok(v) => v, + Err(_) => { + wedpr_println!("Parsing RecoveryId failed"); + return false; + }, + }; + + // The last byte is recovery id, we only need to get the first 64 bytes + // for signature data. + let signature_byte = &sig_result_hex[0..FISCO_BCOS_SIGNATURE_END_INDEX]; + + let get_sign_final = + match RecoverableSignature::from_compact(signature_byte, recid) { + Ok(v) => v, + Err(_) => { + wedpr_println!("Signature from_compact failed"); + return false; + }, + }; + let pk_recover_get = + match SECP256K1_VERIFY.recover(&message_receive, &get_sign_final) { + Ok(v) => v, + Err(_) => { + wedpr_println!("Signature recover failed"); + return false; + }, + }; + if pub_str_get != pk_recover_get { + wedpr_println!("Signature recover failed"); + return false; + } + return true; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + constant::{ + tests::{SECP256K1_TEST_PUBLIC_KEY, SECP256K1_TEST_SECRET_KEY}, + HASH, + }, + hash::Hash, + utils::bytes_to_string, + }; + + #[test] + fn test_secp256k1_recover() { + let secp256k1_recover = WedprSecp256k1Recover::default(); + + // The message hash (NOT the original message) is required for + // generating a valid signature. + let msg = "WeDPR"; + let msg_hash_str = bytes_to_string(&HASH.hash(msg)); + + let signature = secp256k1_recover + .sign(SECP256K1_TEST_SECRET_KEY, &msg_hash_str) + .unwrap(); + assert_eq!( + true, + secp256k1_recover.verify( + SECP256K1_TEST_PUBLIC_KEY, + &msg_hash_str, + &signature + ) + ); + } +} diff --git a/crypto/src/utils.rs b/crypto/src/utils.rs new file mode 100644 index 0000000..c269d7b --- /dev/null +++ b/crypto/src/utils.rs @@ -0,0 +1,152 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Common utility functions. +use crate::constant::RISTRETTO_POINT_SIZE_IN_BYTES; +use bulletproofs::RangeProof; +use curve25519_dalek::{ + ristretto::{CompressedRistretto, RistrettoPoint}, + scalar::Scalar, + traits::MultiscalarMul, +}; +use wedpr_utils::error::WedprError; + +use crate::{ + coder::Coder, + constant::{CODER, HASH}, + hash::Hash, +}; +use std::convert::TryInto; + +/// Converts bytes to an encoded string. +pub fn bytes_to_string>(input: &T) -> String { + CODER.encode(input) +} + +/// Converts an encoded string to a bytes vector. +pub fn string_to_bytes(input: &str) -> Result, WedprError> { + CODER.decode(input) +} + +/// Converts Scalar to an encoded string. +pub fn scalar_to_string(number: &Scalar) -> String { + bytes_to_string(&number.to_bytes()) +} + +/// Converts an encoded string to Scalar. +pub fn string_to_scalar(num: &str) -> Result { + let num_u8 = match string_to_bytes(num) { + Ok(v) => v, + Err(_) => { + wedpr_println!("string_to_scalar failed, string: {}", num); + return Err(WedprError::FormatError); + }, + }; + let get_num_u8 = to_bytes32_slice(&num_u8)?; + let scalar_num = Scalar::from_bits(*get_num_u8); + Ok(scalar_num) +} + +/// Converts RistrettoPoint to an encoded string. +pub fn point_to_string(point: &RistrettoPoint) -> String { + bytes_to_string(&point.compress().to_bytes()) +} + +/// Converts an encoded string to RistrettoPoint. +pub fn string_to_point(point: &str) -> Result { + let decode_tmp = string_to_bytes(point)?; + if decode_tmp.len() != RISTRETTO_POINT_SIZE_IN_BYTES { + wedpr_println!("string_to_point decode failed"); + return Err(WedprError::FormatError); + } + let point_value = + match CompressedRistretto::from_slice(&decode_tmp).decompress() { + Some(v) => v, + None => { + wedpr_println!( + "string_to_point decompress CompressedRistretto failed" + ); + return Err(WedprError::FormatError); + }, + }; + + Ok(point_value) +} + +/// Converts RangeProof to an encoded string. +pub fn rangeproof_to_string(proof: &RangeProof) -> String { + bytes_to_string(&proof.to_bytes()) +} + +/// Converts an arbitrary string to Scalar. +/// It will hash it first, and transform the numberic value of hash output to +/// Scalar. +pub fn hash_to_scalar(value: &str) -> Scalar { + let mut array = [0; 32]; + array.clone_from_slice(&HASH.hash(value)); + Scalar::from_bytes_mod_order(array) +} + +/// Gets a random Scalar. +pub fn get_random_scalar() -> Scalar { + Scalar::random(&mut rand::thread_rng()) +} + +/// Makes a commitment for value in point format. +pub fn make_commitment_point( + value: u64, + blinding: &Scalar, + value_basepoint: &RistrettoPoint, + blinding_basepoint: &RistrettoPoint, +) -> RistrettoPoint +{ + RistrettoPoint::multiscalar_mul(&[Scalar::from(value), *blinding], &[ + *value_basepoint, + *blinding_basepoint, + ]) +} + +// Private utility functions. + +/// Extracts a slice of &[u8; 32] from the given slice. +fn to_bytes32_slice(barry: &[u8]) -> Result<&[u8; 32], WedprError> { + let pop_u8 = match barry.try_into() { + Ok(v) => v, + Err(_) => return Err(WedprError::FormatError), + }; + Ok(pop_u8) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_scalar_conversion() { + let num = get_random_scalar(); + let num_str = scalar_to_string(&num); + let recovered_num = string_to_scalar(&num_str).unwrap(); + assert_eq!(num, recovered_num); + + let bad_str = "bad"; + assert_eq!( + WedprError::FormatError, + string_to_scalar(bad_str).unwrap_err() + ); + } + + #[test] + pub fn test_bytes_conversion() { + let str = "test"; + let bytes = string_to_bytes(&str).unwrap(); + let recovered_str = bytes_to_string(&bytes); + assert_eq!(str, recovered_str); + } + + #[test] + pub fn test_point_conversion() { + let point = RistrettoPoint::default(); + let point_str = point_to_string(&point); + let recovered_point = string_to_point(&point_str).unwrap(); + assert_eq!(point, recovered_point); + } +} diff --git a/crypto/src/zkp.rs b/crypto/src/zkp.rs new file mode 100644 index 0000000..cf1a37d --- /dev/null +++ b/crypto/src/zkp.rs @@ -0,0 +1,669 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Zero-knowledge proof (ZKP) functions. + +use crate::{ + constant::BASEPOINT_G2, + utils::{ + get_random_scalar, hash_to_scalar, make_commitment_point, + point_to_string, rangeproof_to_string, scalar_to_string, + string_to_bytes, string_to_scalar, + }, +}; +use bulletproofs::{BulletproofGens, PedersenGens, RangeProof}; +use curve25519_dalek::{ + ristretto::{CompressedRistretto, RistrettoPoint}, + scalar::Scalar, + traits::MultiscalarMul, +}; +use merlin::Transcript; +use rand::thread_rng; +use wedpr_protos::generated::zkp::BalanceProof; +use wedpr_utils::error::WedprError; + +/// Uses a smaller value to reduce time cost of using range proofs. +/// Uses a larger value to increase value limit of using range proofs. +const RANGE_SIZE_IN_BITS: usize = 32; +const DEFAULT_BYTES_MESSAGE: &[u8] = b"WeDPR"; + +/// Proves whether a value belongs to (0, 2^RANGE_SIZE_IN_BITS - 1], and create +/// a commitment for the value. It returns: +/// 1) the encoded string for the proof. +/// 2) the point representing the commitment created for the value. +/// 3) the random blinding value used in the above commitment. +pub fn prove_value_range(value: u64) -> (String, RistrettoPoint, Scalar) { + let blinding = Scalar::random(&mut thread_rng()); + let (proof_str, value_commitment_point) = + prove_value_range_with_blinding(value, &blinding); + + (proof_str, value_commitment_point, blinding) +} + +/// Proves whether a value belongs to (0, 2^RANGE_SIZE_IN_BITS - 1], and create +/// a commitment for the value with provided random blinding value. It returns: +/// 1) the encoded string for the proof. +/// 2) the point representing the commitment created for the value. +pub fn prove_value_range_with_blinding( + value: u64, + blinding: &Scalar, +) -> (String, RistrettoPoint) +{ + let (proof_str, value_commitment_point) = + prove_value_range_with_blinding_and_blinding_basepoint( + value, + &blinding, + // Cannot use BASEPOINT_G1 which has already been used by + // commitment generation. + &BASEPOINT_G2, + ); + (proof_str, value_commitment_point) +} + +/// Proves whether a value belongs to (0, 2^RANGE_SIZE_IN_BITS - 1], and create +/// a commitment for the value with provided random blinding value and blinding +/// basepoint. It returns: +/// 1) the encoded string for the proof. +/// 2) the point representing the commitment created for the value. +pub fn prove_value_range_with_blinding_and_blinding_basepoint( + value: u64, + blinding: &Scalar, + blinding_basepoint: &RistrettoPoint, +) -> (String, RistrettoPoint) +{ + let mut pc_gens = PedersenGens::default(); + // Allow replacing the blinding basepoint for customized protocol design. + pc_gens.B_blinding = blinding_basepoint.clone(); + let bp_gens = BulletproofGens::new(RANGE_SIZE_IN_BITS, 1); + let secret_value = value; + let mut prover_transcript = Transcript::new(DEFAULT_BYTES_MESSAGE); + let (proof, committed_value) = RangeProof::prove_single( + &bp_gens, + &pc_gens, + &mut prover_transcript, + secret_value, + &blinding, + RANGE_SIZE_IN_BITS, + ) + .expect("RangeProof prove_single should not fail"); + + ( + rangeproof_to_string(&proof), + committed_value + .decompress() + .expect("CompressedRistretto decompress should not fail"), + ) +} + +/// Verifies whether a value embedded in the commentment belongs to +/// (0, 2^RANGE_SIZE_IN_BITS - 1]. +pub fn verify_value_range(commitment: &RistrettoPoint, proof: &str) -> bool { + // Cannot use BASEPOINT_G1 which has already been used by commitment + // generation. + verify_value_range_with_blinding_basepoint(commitment, proof, &BASEPOINT_G2) +} + +/// Verifies whether a value embedded in the commentment belongs to +/// (0, 2^RANGE_SIZE_IN_BITS - 1], and use provided blinding basepoint. +pub fn verify_value_range_with_blinding_basepoint( + commitment: &RistrettoPoint, + proof: &str, + blinding_basepoint: &RistrettoPoint, +) -> bool +{ + let mut pc_gens = PedersenGens::default(); + // Allow replacing the blinding basepoint for customized protocol design. + pc_gens.B_blinding = blinding_basepoint.clone(); + let bp_gens = BulletproofGens::new(RANGE_SIZE_IN_BITS, 1); + let mut verifier_transcript = Transcript::new(DEFAULT_BYTES_MESSAGE); + let decode_proof_result = string_to_bytes!(proof); + let get_proof = match RangeProof::from_bytes(&decode_proof_result) { + Ok(v) => v, + Err(_) => { + wedpr_println!("RangeProof from_bytes failed"); + return false; + }, + }; + let commitment_value = commitment.compress(); + + match get_proof.verify_single( + &bp_gens, + &pc_gens, + &mut verifier_transcript, + &commitment_value, + RANGE_SIZE_IN_BITS, + ) { + Ok(_) => true, + Err(e) => { + wedpr_println!( + "RangeProof verify_single failed!, result = {:?}", + e + ); + return false; + }, + } +} + +/// Proves whether all values in the list belongs to +/// (0, 2^RANGE_SIZE_IN_BITS - 1], and create commitments for them with provided +/// random blinding values and blinding basepoint. +/// It returns: +/// 1) the encoded string for the aggregated proof. +/// 2) the point list representing the commitments created for the values. +pub fn prove_value_range_in_batch( + values: &[u64], + blindings: &[Scalar], + blinding_basepoint: &RistrettoPoint, +) -> Result<(String, Vec), WedprError> +{ + // Two slices should have the same length, and the length should be a + // multiple of 2. + if values.len() != blindings.len() || values.len() & 0x1 != 0 { + return Err(WedprError::ArgumentError); + } + let mut pc_gens = PedersenGens::default(); + // Allow replacing the blinding basepoint for customized protocol design. + pc_gens.B_blinding = blinding_basepoint.clone(); + let bp_gens = BulletproofGens::new(RANGE_SIZE_IN_BITS, values.len()); + let mut prover_transcript = Transcript::new(DEFAULT_BYTES_MESSAGE); + let (proof, committed_value) = match RangeProof::prove_multiple( + &bp_gens, + &pc_gens, + &mut prover_transcript, + values, + &blindings, + RANGE_SIZE_IN_BITS, + ) { + Ok(v) => v, + Err(_) => { + wedpr_println!("prove_value_range_in_batch failed"); + return Err(WedprError::FormatError); + }, + }; + let vector_commitment = committed_value + .iter() + .map(|i| { + i.decompress() + .expect("CompressedRistretto decompress should not fail") + }) + .collect(); + Ok((rangeproof_to_string(&proof), vector_commitment)) +} + +/// Verifies whether all values embedded in the commentment list belongs to +/// (0, 2^RANGE_SIZE_IN_BITS - 1]. +pub fn verify_value_range_in_batch( + commitments: &Vec, + proof: &str, + blinding_basepoint: &RistrettoPoint, +) -> bool +{ + let mut pc_gens = PedersenGens::default(); + // Allow replacing the blinding basepoint for customized protocol design. + pc_gens.B_blinding = blinding_basepoint.clone(); + let bp_gens = BulletproofGens::new(RANGE_SIZE_IN_BITS, commitments.len()); + let mut verifier_transcript = Transcript::new(DEFAULT_BYTES_MESSAGE); + let decode_proof_result = string_to_bytes!(proof); + // The length of decode_proof_result should be a multiple of 32 bytes. + let get_proof = match RangeProof::from_bytes(&decode_proof_result) { + Ok(v) => v, + Err(_) => { + wedpr_println!("RangeProof from_bytes failed"); + return false; + }, + }; + + let decode_commit: Vec = + commitments.iter().map(|i| i.compress()).collect(); + + match get_proof.verify_multiple( + &bp_gens, + &pc_gens, + &mut verifier_transcript, + &decode_commit, + RANGE_SIZE_IN_BITS, + ) { + Ok(_) => true, + Err(_) => { + wedpr_println!("RangeProof verify_multiple failed"); + return false; + }, + } +} + +/// Proves three commitments satisfying a sum relationship, i.e. +/// the values embeded in them satisfying c1_value + c2_value = c3_value. +/// c3_value is not in the argument list, and will be directly computed from +/// c1_value + c2_value. +/// c?_blinding are random blinding values used in the commitments. +/// The commitments (c?_value*value_basepoint+c?_blinding*blinding_basepoint) +/// are not in the argument list, as they are not directly used by the proof +/// generation. +/// It returns a proof for the above sum relationship. +pub fn prove_sum_relationship( + c1_value: u64, + c2_value: u64, + c1_blinding: &Scalar, + c2_blinding: &Scalar, + c3_blinding: &Scalar, + value_basepoint: &RistrettoPoint, + blinding_basepoint: &RistrettoPoint, +) -> BalanceProof +{ + let blinding_a = get_random_scalar(); + let blinding_b = get_random_scalar(); + let blinding_c = get_random_scalar(); + let blinding_d = get_random_scalar(); + let blinding_e = get_random_scalar(); + let c1_point = make_commitment_point( + c1_value, + &c1_blinding, + &value_basepoint, + &blinding_basepoint, + ); + let c2_point = make_commitment_point( + c2_value, + &c2_blinding, + &value_basepoint, + &blinding_basepoint, + ); + let c3_point = make_commitment_point( + c1_value + c2_value, + &c3_blinding, + &value_basepoint, + &blinding_basepoint, + ); + let t1_p = RistrettoPoint::multiscalar_mul(&[blinding_a, blinding_b], &[ + *value_basepoint, + *blinding_basepoint, + ]); + let t2_p = RistrettoPoint::multiscalar_mul(&[blinding_c, blinding_d], &[ + *value_basepoint, + *blinding_basepoint, + ]); + let t3_p = RistrettoPoint::multiscalar_mul( + &[(blinding_a + blinding_c), blinding_e], + &[*value_basepoint, *blinding_basepoint], + ); + let hash_str = format!( + "{}|{}|{}|{}|{}|{}|{}|{}", + &point_to_string(&t1_p), + &point_to_string(&t2_p), + &point_to_string(&t3_p), + &point_to_string(&c1_point), + &point_to_string(&c2_point), + &point_to_string(&c3_point), + &point_to_string(&value_basepoint), + &point_to_string(&blinding_basepoint) + ); + let check = hash_to_scalar(&hash_str); + let m1 = blinding_a - (check * (Scalar::from(c1_value))); + let m2 = blinding_b - (check * c1_blinding); + let m3 = blinding_c - (check * (Scalar::from(c2_value))); + let m4 = blinding_d - (check * (c2_blinding)); + let m5 = blinding_e - (check * (c3_blinding)); + + let mut proof = BalanceProof::new(); + proof.set_c(scalar_to_string(&check)); + proof.set_m1(scalar_to_string(&m1)); + proof.set_m2(scalar_to_string(&m2)); + proof.set_m3(scalar_to_string(&m3)); + proof.set_m4(scalar_to_string(&m4)); + proof.set_m5(scalar_to_string(&m5)); + proof +} + +/// Verifies three commitments satisfying a sum relationship, i.e. +/// the values embeded in c1_point, c2_point, c3_point satisfying +/// c1_value + c2_value = c3_value. +pub fn verify_sum_relationship( + c1_point: &RistrettoPoint, + c2_point: &RistrettoPoint, + c3_point: &RistrettoPoint, + proof: &BalanceProof, + value_basepoint: &RistrettoPoint, + blinding_basepoint: &RistrettoPoint, +) -> bool +{ + let check = string_to_scalar!(proof.get_c()); + let m1 = string_to_scalar!(proof.get_m1()); + let m2 = string_to_scalar!(proof.get_m2()); + let m3 = string_to_scalar!(proof.get_m3()); + let m4 = string_to_scalar!(proof.get_m4()); + let m5 = string_to_scalar!(proof.get_m5()); + let t1_v = RistrettoPoint::multiscalar_mul(&[m1, m2, check], &[ + *value_basepoint, + *blinding_basepoint, + *c1_point, + ]); + let t2_v = RistrettoPoint::multiscalar_mul(&[m3, m4, check], &[ + *value_basepoint, + *blinding_basepoint, + *c2_point, + ]); + let t3_v = RistrettoPoint::multiscalar_mul(&[m1 + (m3), m5, check], &[ + *value_basepoint, + *blinding_basepoint, + *c3_point, + ]); + let hash_str = format!( + "{}|{}|{}|{}|{}|{}|{}|{}", + &point_to_string(&t1_v), + &point_to_string(&t2_v), + &point_to_string(&t3_v), + &point_to_string(&c1_point), + &point_to_string(&c2_point), + &point_to_string(&c3_point), + &point_to_string(&value_basepoint), + &point_to_string(&blinding_basepoint) + ); + + let computed = hash_to_scalar(&hash_str); + computed.eq(&check) +} + +/// Proves three commitments satisfying a product relationship, i.e. +/// the values embeded in them satisfying c1_value * c2_value = c3_value. +/// c3_value is not in the argument list, and will be directly computed from +/// c1_value * c2_value. +/// c?_blinding are random blinding values used in the commitments. +/// The commitments (c?_value*value_basepoint+c?_blinding*blinding_basepoint) +/// are not in the argument list, as they are not directly used by the proof +/// generation. +/// It returns a proof for the above product relationship. +pub fn prove_product_relationship( + c1_value: u64, + c2_value: u64, + c1_blinding: &Scalar, + c2_blinding: &Scalar, + c3_blinding: &Scalar, + value_basepoint: &RistrettoPoint, + blinding_basepoint: &RistrettoPoint, +) -> BalanceProof +{ + let blinding_a = get_random_scalar(); + let blinding_b = get_random_scalar(); + let blinding_c = get_random_scalar(); + let blinding_d = get_random_scalar(); + let blinding_e = get_random_scalar(); + let c1_point = make_commitment_point( + c1_value, + &c1_blinding, + &value_basepoint, + &blinding_basepoint, + ); + let c2_point = make_commitment_point( + c2_value, + &c2_blinding, + &value_basepoint, + &blinding_basepoint, + ); + let c3_point = make_commitment_point( + c1_value * c2_value, + &c3_blinding, + &value_basepoint, + &blinding_basepoint, + ); + let t1_p = RistrettoPoint::multiscalar_mul(&[blinding_a, blinding_b], &[ + *value_basepoint, + *blinding_basepoint, + ]); + let t2_p = RistrettoPoint::multiscalar_mul(&[blinding_c, blinding_d], &[ + *value_basepoint, + *blinding_basepoint, + ]); + let t3_p = RistrettoPoint::multiscalar_mul( + &[blinding_a * (blinding_c), blinding_e], + &[*value_basepoint, *blinding_basepoint], + ); + let hash_str = format!( + "{}|{}|{}|{}|{}|{}|{}|{}", + &point_to_string(&t1_p), + &point_to_string(&t2_p), + &point_to_string(&t3_p), + &point_to_string(&c1_point), + &point_to_string(&c2_point), + &point_to_string(&c3_point), + &point_to_string(&value_basepoint), + &point_to_string(&blinding_basepoint) + ); + + let check = hash_to_scalar(&hash_str); + let value1 = Scalar::from(c1_value); + let value2 = Scalar::from(c2_value); + let m1 = blinding_a - (check * (value1)); + let m2 = blinding_b - (check * c1_blinding); + let m3 = blinding_c - (check * (value2)); + let m4 = blinding_d - (check * c2_blinding); + let c_index2 = check * check; + let m5 = blinding_e + + c_index2 + * ((value1 * c2_blinding) - c3_blinding + (value2 * c1_blinding)) + - check * ((blinding_a * c2_blinding) + (blinding_c * c1_blinding)); + + let mut proof = BalanceProof::new(); + proof.set_c(scalar_to_string(&check)); + proof.set_m1(scalar_to_string(&m1)); + proof.set_m2(scalar_to_string(&m2)); + proof.set_m3(scalar_to_string(&m3)); + proof.set_m4(scalar_to_string(&m4)); + proof.set_m5(scalar_to_string(&m5)); + proof +} + +/// Verifies three commitments satisfying a product relationship, i.e. +/// the values embeded in c1_point, c2_point, c3_point satisfying +/// c1_value * c2_value = c3_value. +pub fn verify_product_relationship( + c1_point: &RistrettoPoint, + c2_point: &RistrettoPoint, + c3_point: &RistrettoPoint, + proof: &BalanceProof, + value_basepoint: &RistrettoPoint, + blinding_basepoint: &RistrettoPoint, +) -> bool +{ + let check = string_to_scalar!(proof.get_c()); + let m1 = string_to_scalar!(proof.get_m1()); + let m2 = string_to_scalar!(proof.get_m2()); + let m3 = string_to_scalar!(proof.get_m3()); + let m4 = string_to_scalar!(proof.get_m4()); + let m5 = string_to_scalar!(proof.get_m5()); + + let t1_v = RistrettoPoint::multiscalar_mul(&[m1, m2, check], &[ + *value_basepoint, + *blinding_basepoint, + *c1_point, + ]); + let t2_v = RistrettoPoint::multiscalar_mul(&[m3, m4, check], &[ + *value_basepoint, + *blinding_basepoint, + *c2_point, + ]); + let t3_v = RistrettoPoint::multiscalar_mul( + &[m1 * m3, m5, check * check, check * m3, check * m1], + &[ + *value_basepoint, + *blinding_basepoint, + *c3_point, + *c1_point, + *c2_point, + ], + ); + let hash_str = format!( + "{}|{}|{}|{}|{}|{}|{}|{}", + &point_to_string(&t1_v), + &point_to_string(&t2_v), + &point_to_string(&t3_v), + &point_to_string(&c1_point), + &point_to_string(&c2_point), + &point_to_string(&c3_point), + &point_to_string(&value_basepoint), + &point_to_string(&blinding_basepoint) + ); + + let computed = hash_to_scalar(&hash_str); + computed.eq(&check) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + constant::{BASEPOINT_G1, BASEPOINT_G2}, + utils::{get_random_scalar, make_commitment_point}, + }; + + #[test] + fn test_range_proof() { + // Range proof for a single value. + let (proof_c1, c1_point, _) = prove_value_range(1); + assert_eq!(true, verify_value_range(&c1_point, &proof_c1)); + + // A negative value will fail when it is out of the expected range after + // the conversion. + let (proof_c2, c2_point, _) = prove_value_range(-1i64 as u64); + assert_eq!(false, verify_value_range(&c2_point, &proof_c2)); + + // Range proof for a list of values. + let blinding_basepoint = *BASEPOINT_G2; + let values: Vec = vec![1, 2, 3, 4]; + let blindings: Vec = + (0..values.len()).map(|_| get_random_scalar()).collect(); + + let (proof_batch, point_list) = prove_value_range_in_batch( + &values, + &blindings, + &blinding_basepoint, + ) + .unwrap(); + + assert_eq!( + true, + verify_value_range_in_batch( + &point_list, + &proof_batch, + &blinding_basepoint, + ) + ); + + // Since the input size is not a multiple of 2, the batch prove function + // will fail. + let values2: Vec = vec![1, 2, 3]; + let blindings2: Vec = + (0..values2.len()).map(|_| get_random_scalar()).collect(); + + assert_eq!( + WedprError::ArgumentError, + prove_value_range_in_batch( + &values2, + &blindings2, + &blinding_basepoint, + ) + .unwrap_err() + ); + } + + #[test] + fn test_sum_relationship_proof() { + let c1_value = 30u64; + let c2_value = 10u64; + let c1_blinding = get_random_scalar(); + let c2_blinding = get_random_scalar(); + let c3_blinding = get_random_scalar(); + let value_basepoint = *BASEPOINT_G1; + let blinding_basepoint = *BASEPOINT_G2; + + let proof = prove_sum_relationship( + c1_value, + c2_value, + &c1_blinding, + &c2_blinding, + &c3_blinding, + &value_basepoint, + &blinding_basepoint, + ); + let c1_point = make_commitment_point( + c1_value, + &c1_blinding, + &value_basepoint, + &blinding_basepoint, + ); + let c2_point = make_commitment_point( + c2_value, + &c2_blinding, + &value_basepoint, + &blinding_basepoint, + ); + // c3 = c1 + c2 + let c3_point = make_commitment_point( + c1_value + c2_value, + &c3_blinding, + &value_basepoint, + &blinding_basepoint, + ); + + assert_eq!( + true, + verify_sum_relationship( + &c1_point, + &c2_point, + &c3_point, + &proof, + &value_basepoint, + &blinding_basepoint + ) + ); + } + + #[test] + fn test_product_relationship_proof() { + let c1_value = 30u64; + let c2_value = 10u64; + let c1_blinding = get_random_scalar(); + let c2_blinding = get_random_scalar(); + let c3_blinding = get_random_scalar(); + let value_basepoint = *BASEPOINT_G1; + let blinding_basepoint = *BASEPOINT_G2; + + let proof = prove_product_relationship( + c1_value, + c2_value, + &c1_blinding, + &c2_blinding, + &c3_blinding, + &value_basepoint, + &blinding_basepoint, + ); + let c1_point = make_commitment_point( + c1_value, + &c1_blinding, + &value_basepoint, + &blinding_basepoint, + ); + let c2_point = make_commitment_point( + c2_value, + &c2_blinding, + &value_basepoint, + &blinding_basepoint, + ); + // c3 = c1 * c2 + let c3_point = make_commitment_point( + c1_value * c2_value, + &c3_blinding, + &value_basepoint, + &blinding_basepoint, + ); + + assert_eq!( + true, + verify_product_relationship( + &c1_point, + &c2_point, + &c3_point, + &proof, + &value_basepoint, + &blinding_basepoint + ) + ); + } +} diff --git a/release_note.txt b/release_note.txt new file mode 100644 index 0000000..60453e6 --- /dev/null +++ b/release_note.txt @@ -0,0 +1 @@ +v1.0.0 \ No newline at end of file diff --git a/solution/verifiable_confidential_ledger/Cargo.toml b/solution/verifiable_confidential_ledger/Cargo.toml new file mode 100644 index 0000000..bf1a34a --- /dev/null +++ b/solution/verifiable_confidential_ledger/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "verifiable_confidential_ledger" +version = "1.0.0" +authors = ["WeDPR "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +colored = "1.8" +curve25519-dalek = { version = "1", features = ["serde"] } +wedpr_crypto = { path = "../../crypto/" } +wedpr_macros = { path = "../../common/macros/" } +wedpr_protos = { path = "../../common/protos/" } +wedpr_utils = { path = "../../common/utils" } + +[dev-dependencies] +criterion = "0.2" + +[[bench]] +name = "vcl" +harness = false diff --git a/solution/verifiable_confidential_ledger/benches/vcl.rs b/solution/verifiable_confidential_ledger/benches/vcl.rs new file mode 100644 index 0000000..39fe3b7 --- /dev/null +++ b/solution/verifiable_confidential_ledger/benches/vcl.rs @@ -0,0 +1,134 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Performance tests for VCL components. + +extern crate criterion; +use criterion::{criterion_group, criterion_main, Criterion}; + +extern crate verifiable_confidential_ledger; +use verifiable_confidential_ledger::{vcl, vcl::make_credit}; + +fn create_prove_sum_balance_helper(c: &mut Criterion) { + let label = format!("create_prove_sum_balance_helper"); + + let c1_value = 10; + let c2_value = 20; + + let (_, c1_secret) = make_credit(c1_value); + let (_, c2_secret) = make_credit(c2_value); + let (_, c3_secret) = make_credit(c1_value + c2_value); + + c.bench_function(&label, move |b| { + b.iter(|| { + let _ = vcl::prove_sum_balance(&c1_secret, &c2_secret, &c3_secret); + }); + }); +} + +fn create_verify_sum_balance_helper(c: &mut Criterion) { + let label = format!("create_verify_sum_balance_helper"); + + let c1_value = 10; + let c2_value = 20; + + let (c1_credit, c1_secret) = make_credit(c1_value); + let (c2_credit, c2_secret) = make_credit(c2_value); + let (c3_credit, c3_secret) = make_credit(c1_value + c2_value); + + let sum_proof = vcl::prove_sum_balance(&c1_secret, &c2_secret, &c3_secret); + + c.bench_function(&label, move |b| { + b.iter(|| { + assert_eq!( + true, + vcl::verify_sum_balance( + &c1_credit, &c2_credit, &c3_credit, &sum_proof + ) + ); + }); + }); +} + +fn create_prove_product_balance_helper(c: &mut Criterion) { + let label = format!("create_prove_product_balance_helper"); + + let c1_value = 10; + let c2_value = 20; + + let (_, c1_secret) = make_credit(c1_value); + let (_, c2_secret) = make_credit(c2_value); + let (_, c3_secret) = make_credit(c1_value * c2_value); + + c.bench_function(&label, move |b| { + b.iter(|| { + let _ = + vcl::prove_product_balance(&c1_secret, &c2_secret, &c3_secret); + }); + }); +} + +fn create_verify_product_balance_helper(c: &mut Criterion) { + let label = format!("create_verify_product_balance_helper"); + + let c1_value = 10; + let c2_value = 20; + + let (c1_credit, c1_secret) = make_credit(c1_value); + let (c2_credit, c2_secret) = make_credit(c2_value); + let (c3_credit, c3_secret) = make_credit(c1_value * c2_value); + + let product_proof = + vcl::prove_product_balance(&c1_secret, &c2_secret, &c3_secret); + + c.bench_function(&label, move |b| { + b.iter(|| { + assert_eq!( + true, + vcl::verify_product_balance( + &c1_credit, + &c2_credit, + &c3_credit, + &product_proof + ) + ); + }); + }); +} + +fn create_prove_range_helper(c: &mut Criterion) { + let label = format!("create_prove_range_helper"); + + let (_, c1_secret) = make_credit(10); + + c.bench_function(&label, move |b| { + b.iter(|| { + let _ = vcl::prove_range(&c1_secret); + }); + }); +} + +fn create_verify_range_helper(c: &mut Criterion) { + let label = format!("create_verify_range_helper"); + + let (c1_credit, c1_secret) = make_credit(10); + let range_proof = vcl::prove_range(&c1_secret); + + c.bench_function(&label, move |b| { + b.iter(|| { + assert_eq!(true, vcl::verify_range(&c1_credit, &range_proof)); + }); + }); +} + +criterion_group! { + name = vcl_benches; + config = Criterion::default().sample_size(10); + targets = + create_prove_sum_balance_helper, + create_verify_sum_balance_helper, + create_prove_product_balance_helper, + create_verify_product_balance_helper, + create_prove_range_helper, + create_verify_range_helper +} +criterion_main!(vcl_benches); diff --git a/solution/verifiable_confidential_ledger/src/lib.rs b/solution/verifiable_confidential_ledger/src/lib.rs new file mode 100644 index 0000000..a4f33a8 --- /dev/null +++ b/solution/verifiable_confidential_ledger/src/lib.rs @@ -0,0 +1,5 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Library of verifiable confidential ledger (VCL) solution. + +pub mod vcl; diff --git a/solution/verifiable_confidential_ledger/src/main.rs b/solution/verifiable_confidential_ledger/src/main.rs new file mode 100644 index 0000000..bdb81b4 --- /dev/null +++ b/solution/verifiable_confidential_ledger/src/main.rs @@ -0,0 +1,502 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Minimalist demo of verifiable confidential ledger. + +use colored::*; +use std; +use verifiable_confidential_ledger::vcl; +use wedpr_crypto; + +fn main() { + print_highlight2( + "#\n# Welcome to verifiable confidential ledger (VCL) demo!", + "# 欢迎来到公开可验证密文账本demo演示!\n#", + ); + println!(); + + print_alert2( + "Please select the display language used for this demonstration: ▼▼▼", + "请选择演示所使用的显示语言:▼▼▼", + ); + + print_alert2( + " ▶ Enter \"1\" to select English (default option)", + " ▶ 输入 \"2\" 选择中文", + ); + println!(); + + let mut choice = wait_for_input(); + loop { + // The default option. + if choice == "1" || choice.is_empty() { + flow_en(); + break; + } else if choice == "2" { + flow_cn(); + break; + } else { + print_alert2( + "输入错误!请重新输入:", + "Invalid input! Please try again:", + ); + choice = wait_for_input(); + } + } +} + +fn flow_cn() { + print_wide("账本demo流程中,你将体验如何通过账本金额的明文数据生成对应的密文凭证, \ + 并了解密文凭证配套零知识证明的基础用法"); + + println!( + "{} {} {}\n", + "【演示进度】", + "<<开具密文凭证>>".yellow(), + "↦ 生成凭证的约束关系证明 ↦ 验证凭证的约束关系证明", + ); + + print_alert("现在请输入第一个待转换成密文凭证c1的账本金额:▼▼▼"); + print_highlight( + "在这个demo中,我们暂定金额上限为10000(真实业务可按需扩展),\ + 请输入0到10000之间的整数", + ); + + let value1 = wait_for_number_cn(); + let (c1_credit, c1_secret) = vcl::make_credit(value1); + println!("\n{}", "关于c1的完整密文凭证共包含以下两部分".green()); + println!(" ▶ 公开部分:可公开验证的密文凭证\n{}", &c1_credit); + println!( + " ▶ 私有部分:用于生成以上的密文凭证的秘密参数,\ + 代表了对应账本金额的所有权\n{}", + c1_secret + ); + print_wide( + "为了简化描述,我们使用“密文凭证”指代公开部分;对于私有部分,\ + 用户应妥善保存,保护其对凭证的所有权", + ); + + print_alert("现在请输入第二个待转换成密文凭证c2的账本金额:▼▼▼"); + let value2 = wait_for_number_cn(); + let (c2_credit, c2_secret) = vcl::make_credit(value2); + + print_alert("请输入第三个待转换成密文凭证c3的账本金额:▼▼▼"); + let value3 = wait_for_number_cn(); + let (c3_credit, c3_secret) = vcl::make_credit(value3); + + print_wide("至此,我们一共生成了如下一系列公开可验证的密文凭证"); + println!( + "c1:{}", + wedpr_crypto::utils::point_to_string(&c1_credit.get_point()) + ); + println!( + "c2:{}", + wedpr_crypto::utils::point_to_string(&c2_credit.get_point()) + ); + println!( + "c3:{}", + wedpr_crypto::utils::point_to_string(&c3_credit.get_point()) + ); + pause_cn(); + + println!( + "\n{} {} {}\n", + "【演示进度】开具密文凭证 ↦ ", + "<<生成凭证的约束关系证明>> ".yellow(), + "↦ 验证凭证的约束关系证明", + ); + + print_highlight("以下将演示这些密文凭证的基础用法:"); + print_highlight( + "证明和验证c1,c2,c3中隐匿的金额c1_value,c2_value,\ + c3_value是否满足如下约束关系:", + ); + print_highlight(" c1_value + c2_value =? c3_value"); + print_highlight(" c1_value * c2_value =? c3_value"); + print_highlight(" c1_value >=? 0"); + print_highlight( + "若不满足对应的约束关系,只能生成错误的证明,在验证环节将会验证失败。", + ); + + print_wide(" ▶ 是否存在加和关系?尝试证明c1_value + c2_value =? c3_value"); + let sum_proof = vcl::prove_sum_balance(&c1_secret, &c2_secret, &c3_secret); + println!("加和关系的证明数据:\n{:?}", sum_proof); + pause_cn(); + + print_wide(" ▶ 是否存在乘积关系?尝试证明c1_value * c2_value =? c3_value"); + let product_proof = + vcl::prove_product_balance(&c1_secret, &c2_secret, &c3_secret); + println!("乘积关系的证明数据:\n{:?}", product_proof); + pause_cn(); + + print_wide(" ▶ 是否是非负数?尝试证明c1_value >=? 0"); + let range_proof_c1 = vcl::prove_range(&c1_secret); + println!("非负数的证明数据:\n{:?}", range_proof_c1); + pause_cn(); + + println!( + "\n{} {}\n", + "【演示进度】开具密文凭证 ↦ 生成凭证的约束关系证明 ↦ ", + "<<验证凭证的约束关系证明>>".yellow(), + ); + + print_highlight("现在我们在密文的基础上,一一验证以上约束关系是否正确。"); + print_highlight( + "验证过程中只需要用到密文凭证c1, c2, c3,和相关的证明数据。", + ); + print_alert("无需披露敏感金额的明文数据!"); + + print_wide("对应的验证结果如下:"); + + let meet_sum_balance = + vcl::verify_sum_balance(&c1_credit, &c2_credit, &c3_credit, &sum_proof); + if meet_sum_balance { + print_highlight("✓ 密文验证成功:c1_value + c2_value = c3_value"); + } else { + print_alert("✗ 密文验证失败:c1_value + c2_value != c3_value"); + } + let meet_product_balance = vcl::verify_product_balance( + &c1_credit, + &c2_credit, + &c3_credit, + &product_proof, + ); + if meet_product_balance { + print_highlight("✓ 密文验证成功:c1_value * c2_value = c3_value"); + } else { + print_alert("✗ 密文验证失败:c1_value * c2_value != c3_value"); + } + let meet_range_constraint = vcl::verify_range(&c1_credit, &range_proof_c1); + if meet_range_constraint { + print_highlight("✓ 密文验证成功:c1_value >= 0"); + } else { + print_alert("✗ 密文验证失败:c1_value < 0"); + } + pause_cn(); + + print_highlight( + "如上所示,验证者可以使用公开密文数据来核实账本中金额的正确性,\ + 而且无需获知敏感金额的明文数据。", + ); + print_wide( + "以上演示的四则运算关系和非负数等基础证明验证过程,\ + 其效果等价于对以下明文数据直接进行核实:", + ); + + if meet_sum_balance { + println!("✓ 金额符合预期:{} + {} = {}", value1, value2, value3); + } else { + println!("✗ 金额偏离预期:{} + {} != {}", value1, value2, value3); + } + if meet_product_balance { + println!("✓ 金额符合预期:{} * {} = {}", value1, value2, value3); + } else { + println!("✗ 金额偏离预期:{} * {} != {}", value1, value2, value3); + } + if meet_range_constraint { + println!("✓ 金额符合预期:{} >= 0", value1); + } else { + println!("✗ 金额偏离预期:{} < 0", value1); + } + pause_cn(); + + print_alert("十分感谢您的试用!"); + println!( + "\n{}\n\n{}\n{}\n", + "关于WeDPR,如需了解更多,欢迎通过以下方式联系我们", + "1. 微信公众号【微众银行区块链】", + "2. 官方邮箱【wedpr@webank.com】" + ); + println!(); +} + +fn flow_en() { + print_wide( + "In this demo, you will experience how to generate confidential \ + credit from a plaintext ledger amount, and to apply basic \ + zero-knowledge proof (ZKP) on those confidential credits.", + ); + + println!( + "{}\n{}\n{}\n{}\n", + "[Demo progress]", + "↦ <>".yellow(), + "↦ Prove constraints among credential credits", + "↦ Verify constraints among credential credits", + ); + + print_alert( + "Now please enter the plaintext value of the first ledger amount to \ + be converted into a confidential credit c1. ▼▼▼", + ); + print_highlight( + "In this demo, we use 10000 as the amount limit, which could be \ + extended for a higher limit in real applications. Please enter a \ + integer number between 0 and 10000.", + ); + + let value1 = wait_for_number_en(); + let (c1_credit, c1_secret) = vcl::make_credit(value1); + println!( + "\n{}", + "A complete confidential credit for c1 consists of two parts.".green() + ); + println!( + " ▶ Public part: a publicly verifiable credential credit.\n{}", + &c1_credit + ); + println!( + " ▶ Private part: secret parameters used to generate a publicly \ + verifiable credential credit, which represents the ownership of the \ + ledger amount.\n{}", + c1_secret + ); + print_wide( + "For simplicity, we use confidential credit only for the public part. \ + For the private part, users should store it carefully, to protect \ + their ownership.", + ); + + print_alert( + "Now please enter the plaintext value of the first ledger amount to \ + be converted into a confidential credit c2. ▼▼▼", + ); + let value2 = wait_for_number_en(); + let (c2_credit, c2_secret) = vcl::make_credit(value2); + + print_alert( + "Please enter the plaintext value of the first ledger amount to be \ + converted into a confidential credit c3. ▼▼▼", + ); + let value3 = wait_for_number_en(); + let (c3_credit, c3_secret) = vcl::make_credit(value3); + + print_wide( + "So far, we have generated the following publicly verifiable \ + credential credits:", + ); + println!( + "c1: {}", + wedpr_crypto::utils::point_to_string(&c1_credit.get_point()) + ); + println!( + "c2: {}", + wedpr_crypto::utils::point_to_string(&c2_credit.get_point()) + ); + println!( + "c3: {}", + wedpr_crypto::utils::point_to_string(&c3_credit.get_point()) + ); + pause_en(); + + println!( + "{}\n{}\n{}\n{}\n", + "[Demo progress]", + "↦ Issue confidential credits", + "↦ <>".yellow(), + "↦ Verify constraints among credential credits", + ); + + print_highlight( + "Now we will demonstrate the basic usage of using credential credits \ + together with ZKP.", + ); + print_highlight( + "Try to prove and verify whether the values c1_value, c2_value, \ + c3_value embedded in c1, c2, c3 satisfying the following constraints:", + ); + print_highlight(" c1_value + c2_value =? c3_value"); + print_highlight(" c1_value * c2_value =? c3_value"); + print_highlight(" c1_value >=? 0"); + print_highlight( + "If a constraint is not satisfied, the generated bogus proof will \ + fail to pass the verification.", + ); + + print_wide( + " ▶ Attempt to prove a sum relationship: c1_value + c2_value =? \ + c3_value", + ); + let sum_proof = vcl::prove_sum_balance(&c1_secret, &c2_secret, &c3_secret); + println!("Proof data for the sum relationship:\n{:?}", sum_proof); + pause_en(); + + print_wide( + " ▶ Attempt to prove a product relationship: c1_value * c2_value =? \ + c3_value", + ); + let product_proof = + vcl::prove_product_balance(&c1_secret, &c2_secret, &c3_secret); + println!( + "Proof data for the product relationship:\n{:?}", + product_proof + ); + pause_en(); + + print_wide(" ▶ Attempt to prove a non-negative constraint: c1_value >=? 0"); + let range_proof_c1 = vcl::prove_range(&c1_secret); + println!( + "Proof data for the non-negative constraint:\n{:?}", + range_proof_c1 + ); + pause_en(); + + println!( + "{}\n{}\n{}\n{}\n", + "[Demo progress]", + "↦ Issue confidential credits", + "↦ Prove constraints among credential credits", + "↦ <>".yellow(), + ); + + print_highlight("Verification time!"); + print_highlight( + "The above constraint can be verified only by using confidential \ + credits (e.g. c1, c2, c3) and the corresponding ZKP proof.", + ); + print_alert("No need to reveal sensitive plaintext ledger amount!"); + + print_wide("Verification results are listed below:"); + + let meet_sum_balance = + vcl::verify_sum_balance(&c1_credit, &c2_credit, &c3_credit, &sum_proof); + if meet_sum_balance { + print_highlight("✓ Pass: c1_value + c2_value = c3_value"); + } else { + print_alert("✗ Fail: c1_value + c2_value != c3_value"); + } + let meet_product_balance = vcl::verify_product_balance( + &c1_credit, + &c2_credit, + &c3_credit, + &product_proof, + ); + if meet_product_balance { + print_highlight("✓ Pass: c1_value * c2_value = c3_value"); + } else { + print_alert("✗ Fail: c1_value * c2_value != c3_value"); + } + let meet_range_constraint = vcl::verify_range(&c1_credit, &range_proof_c1); + if meet_range_constraint { + print_highlight("✓ Pass: c1_value >= 0"); + } else { + print_alert("✗ Fail: c1_value < 0"); + } + pause_en(); + + print_highlight( + "As seen above, a verifier can use confidential credit to verify the \ + correctness of ledger amount in VCL without knowing its sensitive \ + plaintext values.", + ); + print_wide( + "The above basic prove-verification processes for arithmetic \ + constraints and range constraints achieve the same effect as the \ + direct validation on the plaintext data.", + ); + + if meet_sum_balance { + println!("✓ Ledger looks good: {} + {} = {}", value1, value2, value3); + } else { + println!( + "✗ Ledger seems wrong: {} + {} != {}", + value1, value2, value3 + ); + } + if meet_product_balance { + println!("✓ Ledger looks good: {} * {} = {}", value1, value2, value3); + } else { + println!( + "✗ Ledger seems wrong: {} * {} != {}", + value1, value2, value3 + ); + } + if meet_range_constraint { + println!("✓ Ledger looks good: {} >= 0", value1); + } else { + println!("✗ Ledger seems wrong: {} < 0", value1); + } + pause_en(); + + print_alert("Thank you for your time!"); + println!( + "\n{}\n\n{}\n", + "Welcome to contact us for more information about WeDPR by the \ + following Email:", + "wedpr@webank.com" + ); + println!(); +} + +// Utility functions + +fn print_highlight(message: &str) { + println!("{}", message.green()); +} + +fn print_highlight2(message1: &str, message2: &str) { + println!("{}\n{}", message1.green(), message2.green()); +} + +fn print_alert(message: &str) { + println!("{}", message.yellow()); +} + +fn print_alert2(message1: &str, message2: &str) { + println!("{}\n{}", message1.yellow(), message2.yellow()); +} + +fn print_wide(message: &str) { + println!("\n{}\n", message); +} + +fn wait_for_input() -> String { + let mut input = String::new(); + std::io::stdin() + .read_line(&mut input) + .expect("Failed to read line."); + input.replace("\n", "") +} + +// In this demo, we set the upper limit of input value to 10000. +const MAX_INPUT_VALUE: i64 = 10000; + +fn wait_for_number(error_message: &str) -> u64 { + let mut input = wait_for_input(); + let mut input_num = input.parse::(); + loop { + match input_num { + // TODO: Enable negative input in the demo. + Ok(v) if (v >= 0) && (v <= MAX_INPUT_VALUE) => return v as u64, + _ => { + print_alert(error_message); + input = wait_for_input(); + input_num = input.parse::(); + }, + } + } +} + +fn wait_for_number_cn() -> u64 { + wait_for_number("请输入有效数字:") +} + +fn wait_for_number_en() -> u64 { + wait_for_number("Please input a valid number:") +} + +fn pause(info_message: &str) { + let mut enter_continue = String::new(); + print_wide(info_message); + std::io::stdin() + .read_line(&mut enter_continue) + .expect("read_line should not fail"); + println!("... {}\n", enter_continue.trim()); +} + +fn pause_cn() { + pause("按任意键继续..."); +} + +fn pause_en() { + pause("Press any key to continue..."); +} diff --git a/solution/verifiable_confidential_ledger/src/vcl.rs b/solution/verifiable_confidential_ledger/src/vcl.rs new file mode 100644 index 0000000..646622d --- /dev/null +++ b/solution/verifiable_confidential_ledger/src/vcl.rs @@ -0,0 +1,293 @@ +// Copyright 2020 WeDPR Lab Project Authors. Licensed under Apache-2.0. + +//! Core functions of verifiable confidential ledger (VCL). + +use curve25519_dalek::{ristretto::RistrettoPoint, scalar::Scalar}; +use std::fmt; +use wedpr_crypto::{ + self, + constant::{BASEPOINT_G1, BASEPOINT_G2}, + utils::{point_to_string, scalar_to_string}, +}; +use wedpr_protos::generated::zkp::BalanceProof; + +/// Owner secret used to spend a confidential credit. +#[derive(Default, Debug, Clone)] +pub struct OwnerSecret { + credit_value: u64, + secret_blinding: Scalar, +} + +/// Confidential credit record stored in VCL. +#[derive(Default, Debug, Clone)] +pub struct ConfidentialCredit { + point: RistrettoPoint, +} + +impl fmt::Display for OwnerSecret { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "credit_value = {}, secret_blinding = {}", + self.credit_value, + scalar_to_string(&self.secret_blinding) + ) + } +} + +impl ConfidentialCredit { + pub fn get_point(&self) -> RistrettoPoint { + self.point + } +} + +impl fmt::Display for ConfidentialCredit { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "point = {}", point_to_string(&self.point)) + } +} + +/// Makes a confidential credit record and owner secret for a numberic value. +pub fn make_credit(value: u64) -> (ConfidentialCredit, OwnerSecret) { + let blinding_r = wedpr_crypto::utils::get_random_scalar(); + let commitment_point = wedpr_crypto::utils::make_commitment_point( + value, + &blinding_r, + &BASEPOINT_G1, + &BASEPOINT_G2, + ); + + ( + ConfidentialCredit { + point: commitment_point, + }, + OwnerSecret { + credit_value: value, + secret_blinding: blinding_r, + }, + ) +} + +/// Proves three confidential credit records satisfying a sum relationship, i.e. +/// the values embeded in them satisfying c1_value + c2_value = c3_value. +/// c?_secret are the owner secrets for spending those commitments. +/// It returns a proof for the above sum relationship. +pub fn prove_sum_balance( + c1_secret: &OwnerSecret, + c2_secret: &OwnerSecret, + c3_secret: &OwnerSecret, +) -> BalanceProof +{ + wedpr_crypto::zkp::prove_sum_relationship( + c1_secret.credit_value, + c2_secret.credit_value, + &c1_secret.secret_blinding, + &c2_secret.secret_blinding, + &c3_secret.secret_blinding, + &BASEPOINT_G1, + &BASEPOINT_G2, + ) +} + +/// Verifies three commitments satisfying a sum relationship, i.e. +/// the values embeded in c1_credit, c2_credit, c3_credit satisfying +/// c1_value + c2_value = c3_value. +pub fn verify_sum_balance( + c1_credit: &ConfidentialCredit, + c2_credit: &ConfidentialCredit, + c3_credit: &ConfidentialCredit, + proof: &BalanceProof, +) -> bool +{ + wedpr_crypto::zkp::verify_sum_relationship( + &c1_credit.get_point(), + &c2_credit.get_point(), + &c3_credit.get_point(), + proof, + &BASEPOINT_G1, + &BASEPOINT_G2, + ) +} + +/// Proves three confidential credit records satisfying a product relationship, +/// i.e. the values embeded in them satisfying c1_value * c2_value = c3_value. +/// c?_secret are the owner secrets for spending those commitments. +/// It returns a proof for the above product relationship. +pub fn prove_product_balance( + c1_secret: &OwnerSecret, + c2_secret: &OwnerSecret, + c3_secret: &OwnerSecret, +) -> BalanceProof +{ + wedpr_crypto::zkp::prove_product_relationship( + c1_secret.credit_value, + c2_secret.credit_value, + &c1_secret.secret_blinding, + &c2_secret.secret_blinding, + &c3_secret.secret_blinding, + &BASEPOINT_G1, + &BASEPOINT_G2, + ) +} + +/// Verifies three commitments satisfying a product relationship, i.e. +/// the values embeded in c1_credit, c2_credit, c3_credit satisfying +/// c1_value * c2_value = c3_value. +pub fn verify_product_balance( + c1_credit: &ConfidentialCredit, + c2_credit: &ConfidentialCredit, + c3_credit: &ConfidentialCredit, + proof: &BalanceProof, +) -> bool +{ + wedpr_crypto::zkp::verify_product_relationship( + &c1_credit.get_point(), + &c2_credit.get_point(), + &c3_credit.get_point(), + proof, + &BASEPOINT_G1, + &BASEPOINT_G2, + ) +} + +/// Proves whether the value embeded in a confidential credit record belongs +/// to (0, 2^RANGE_SIZE_IN_BITS - 1]. +pub fn prove_range(secret: &OwnerSecret) -> String { + let (proof, _) = wedpr_crypto::zkp::prove_value_range_with_blinding( + secret.credit_value, + &secret.secret_blinding, + ); + proof +} + +/// Verifies whether the value embeded in a confidential credit record belongs +/// to (0, 2^RANGE_SIZE_IN_BITS - 1]. +pub fn verify_range(c1: &ConfidentialCredit, proof: &str) -> bool { + wedpr_crypto::zkp::verify_value_range(&c1.get_point(), proof) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sum_balance_proof() { + let (c1_credit, c1_secret) = make_credit(10); + let (c2_credit, c2_secret) = make_credit(20); + // 10 + 20 = 30 + let (correct_c3_credit, correct_c3_secret) = make_credit(30); + let correct_proof = + prove_sum_balance(&c1_secret, &c2_secret, &correct_c3_secret); + // 10 + 20 != 31 + let (wrong_c3_credit, wrong_c3_secret) = make_credit(31); + let wrong_proof = + prove_sum_balance(&c1_secret, &c2_secret, &wrong_c3_secret); + + assert_eq!( + true, + verify_sum_balance( + &c1_credit, + &c2_credit, + &correct_c3_credit, + &correct_proof + ) + ); + + // Incorrect proof combinations. + assert_eq!( + false, + verify_sum_balance( + &c1_credit, + &c2_credit, + &wrong_c3_credit, + &wrong_proof + ) + ); + assert_eq!( + false, + verify_sum_balance( + &c1_credit, + &c2_credit, + &correct_c3_credit, + &wrong_proof + ) + ); + assert_eq!( + false, + verify_sum_balance( + &c1_credit, + &c2_credit, + &wrong_c3_credit, + &correct_proof + ) + ); + } + + #[test] + fn test_product_balance_proof() { + let (c1_credit, c1_secret) = make_credit(10); + let (c2_credit, c2_secret) = make_credit(20); + let (correct_c3_credit, correct_c3_secret) = make_credit(200); + let (wrong_c3_credit, wrong_c3_secret) = make_credit(31); + // 10 * 20 = 200 + let correct_proof = + prove_product_balance(&c1_secret, &c2_secret, &correct_c3_secret); + // 10 * 20 != 31 + let wrong_proof = + prove_product_balance(&c1_secret, &c2_secret, &wrong_c3_secret); + + assert_eq!( + true, + verify_product_balance( + &c1_credit, + &c2_credit, + &correct_c3_credit, + &correct_proof + ) + ); + + // Incorrect proof combinations. + assert_eq!( + false, + verify_product_balance( + &c1_credit, + &c2_credit, + &wrong_c3_credit, + &wrong_proof + ) + ); + assert_eq!( + false, + verify_product_balance( + &c1_credit, + &c2_credit, + &correct_c3_credit, + &wrong_proof + ) + ); + assert_eq!( + false, + verify_product_balance( + &c1_credit, + &c2_credit, + &wrong_c3_credit, + &correct_proof + ) + ); + } + + #[test] + fn test_range_proof() { + let (c1_credit, c1_secret) = make_credit(65535); + let (c2_credit, c2_secret) = make_credit(20); + + let range_proof_c1 = prove_range(&c1_secret); + let range_proof_c2 = prove_range(&c2_secret); + + assert_eq!(true, verify_range(&c1_credit, &range_proof_c1)); + assert_eq!(true, verify_range(&c2_credit, &range_proof_c2)); + + assert_eq!(false, verify_range(&c2_credit, &range_proof_c1)); + assert_eq!(false, verify_range(&c1_credit, &range_proof_c2)); + } +}