@@ -0,0 +1,39 @@
version: 2
jobs:
build:
docker:
- image: circleci/rust:stretch
resource_class: xlarge
steps:
- checkout
- run:
name: Version Information
command: rustc --version; cargo --version; rustup --version
- run:
name: Install Dependencies
command: |
sudo sh -c 'echo "deb http://deb.debian.org/debian stretch-backports main" > /etc/apt/sources.list.d/backports.list'
sudo apt-get update
sudo apt-get install -y protobuf-compiler/stretch-backports cmake golang curl
sudo apt-get clean
sudo rm -r /var/lib/apt/lists/*
rustup component add clippy rustfmt
- run:
name: Setup Env
command: |
echo 'export TAG=0.1.${CIRCLE_BUILD_NUM}' >> $BASH_ENV
echo 'export IMAGE_NAME=myapp' >> $BASH_ENV
- run:
name: Linting
command: |
./scripts/clippy.sh
cargo fmt -- --check
- run:
name: Build All Targets
command: RUST_BACKTRACE=1 cargo build -j 16 --all --all-targets
- run:
name: Run All Unit Tests
command: cargo test --all --exclude testsuite
- run:
name: Run All End to End Tests
command: RUST_TEST_THREADS=2 cargo test --package testsuite
@@ -0,0 +1,3 @@
.git/
**/.terraform/
target/
@@ -0,0 +1,12 @@
# Ensure that text files that any contributor introduces to the repository
# have their line endings normalized to LF
* text=auto

# All known text filetypes
*.md text
*.proto text
*.rs text
*.sh text eol=lf
*.toml text
*.txt text
*.yml text
@@ -0,0 +1,43 @@
---
name: "\U0001F41B Bug report"
about: Create a bug report to help improve Libra Core
title: "[Bug]"
labels: bug
assignees: ''

---

# 🐛 Bug

<!-- A clear and concise description of what the bug is.
To report a security issue, please email security@libra.org. -->

## To reproduce

** Code snippet to reproduce **
```rust
# Your code goes here
# Please make sure it does not require any external dependencies
```

** Stack trace/error message **
```
// Paste the output here
```

## Expected Behavior

<!-- A clear and concise description of what you expected to happen. -->

## System information

**Please complete the following information:**
- <!-- Libra Version -->
- <!-- Rust Version -->
- <!-- Computer OS -->


## Additional context

Add any other context about the problem here.
@@ -0,0 +1,32 @@
---
name: "\U0001F680 Feature request"
about: Suggest a new feature in Libra Core
title: "[Feature Request]"
labels: enhancement
assignees: ''

---

# 🚀 Feature Request

<!-- A clear and concise description of the feature you are requesting -->

## Motivation

**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
<!-- Please link to any relevant issues or other PRs! -->

## Pitch

**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->

**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->

**Are you willing to open a pull request?** (See [CONTRIBUTING](../CONTRIBUTING.md))

## Additional context

<!-- Add any other context or screenshots about the feature request here. -->
@@ -0,0 +1,10 @@
---
name: ❓ Questions/Help
about: If you have questions, please check Discourse
---

## ❓ Questions and Help

### Please note that this issue tracker is not a help form and this issue will be closed.

Please contact the development team on [Discourse](https://community.libra.org)
@@ -0,0 +1,21 @@
<!--
Thank you for sending a PR. We appreciate you spending time to help improve the Libra project.
The project is undergoing daily changes. Pull Requests will be reviewed and responded to as time permits.
-->

## Motivation

(Write your motivation for proposed changes here.)

### Have you read the [Contributing Guidelines on pull requests](https://github.com/libra/libra/master/CONTRIBUTING.md#pull-requests)?

(Write your answer here.)

## Test Plan

(Share your test plan here. If you changed code, please provide us with clear instrutions for verifying that your changes work.)

## Related PRs

(If this PR adds or changes functionality, please take some time to update the docs at https://github.com/libra/website, and link to your PR here.)
@@ -0,0 +1,33 @@
# Rust specific ignores
/target
**/*.rs.bk
# Cargo.lock is needed for deterministic testing and repeatable builds.
#
# Having it in the repo slows down development cycle.
#
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock

# Ignore generated files in proto folders
**/proto/*.rs
!**/proto/mod.rs
!**/proto/converter.rs
**/proto/*/*.rs
!**/proto/*/mod.rs
!**/proto/*/converter.rs

# IDE
.idea
.idea/*
*.iml
.vscode

# Ignore wallet mnemonic files used for deterministic key derivation
*.mnemonic

# Generated Parser File by LALRPOP
language/compiler/src/parser/syntax.rs
language/move_ir/

# GDB related
**/.gdb_history
@@ -0,0 +1,3 @@
# Code of Conduct

The Libra Core project has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://developers.libra.org/docs/policies/code-of-conduct) so that you can and understand what actions will and will not be tolerated.
@@ -0,0 +1,55 @@
# Contributing to Libra

Our goal is to make contributing to the Libra project easy and transparent.

> **Note**: As the Libra Core project is currently an early-stage prototype, it is undergoing rapid development. While we welcome contributions, before making substantial contributions be sure to discuss them in the Discourse forum to ensure that they fit into the project roadmap.
## On Contributing


### Libra Core

To contribute to the Libra Core implementation, first start with the proper development copy.

To get the development installation with all the necessary dependencies for linting, testing, and building the documentation, run the following:
```bash
git clone https://github.com/libra/libra.git
cd libra
./scripts/dev_setup.sh
cargo build
cargo test
```

## Our Development Process

#### Code Style, Hints, and Testing

Refer to our [Coding Guidelines](https://developers.libra.org/docs/coding-guidelines) for detailed guidance about how to contribute to the project.

#### Documentation

Libra's website is also open source (the
code can be found in this [repository](https://github.com/libra/website/)).
It is built using [Docusaurus](https://docusaurus.io/):

If you know Markdown, you can already contribute! This lives in the the [website repo](https://github.com/libra/website).

## Pull Requests
During the initial phase of heavy development, we plan to only audit and review pull requests. As the codebase stabilizes, we will be better able to accept pull requests from the community.

1. Fork the repo and create your branch from `master`.
2. If you have added code that should be tested, add unit tests.
3. If you have changed APIs, update the documentation. Make sure the
documentation builds.
4. Ensure the test suite passes.
5. Make sure your code passes both linters.
6. If you haven't already, complete the Contributor License Agreement (CLA).
7. Submit your pull request.

## Contributor License Agreement

For pull request to be accepted by any Libra projects, a CLA must be signed. You will only need to do this once to work on any of Libra's open source projects. Individuals contributing on their own behalf can sign the [Individual CLA](https://github.com/libra/libra/blob/master/contributing/individual-cla.pdf). If you are contributing on behalf of your employer, please ask them to sign the [Corporate CLA](https://github.com/libra/libra/blob/master/contributing/corporate-cla.pdf).

## Issues

Libra uses [GitHub issues](https://github.com/libra/libra/issues) to track bugs. Please include necessary information and instructions to reproduce your issue.
@@ -0,0 +1,67 @@
[workspace]

members = [
"admission_control/admission_control_service",
"admission_control/admission_control_proto",
"client",
"client/libra_wallet",
"common/canonical_serialization",
"common/crash_handler",
"common/debug_interface",
"common/executable_helpers",
"common/failure_ext",
"common/grpcio-client",
"common/jemalloc",
"common/logger",
"common/metrics",
"common/proptest_helpers",
"common/proto_conv",
"config",
"config/config_builder",
"config/generate_keypair",
"consensus",
"crypto/legacy_crypto",
"crypto/nextgen_crypto",
"crypto/secret_service",
"execution/execution_client",
"execution/execution_proto",
"execution/execution_service",
"execution/executor",
"language/bytecode_verifier",
"language/bytecode_verifier/invalid_mutations",
"language/functional_tests",
"language/compiler",
"language/stdlib/natives",
"language/vm",
"language/vm/vm_runtime",
"language/vm/cost_synthesis",
"language/vm/vm_runtime/vm_cache_map",
"language/vm/vm_genesis",
"language/vm/vm_runtime/vm_runtime_tests",
"libra_node",
"libra_swarm",
"network",
"network/memsocket",
"network/netcore",
"network/noise",
"mempool",
"storage/accumulator",
"storage/libradb",
"storage/schemadb",
"storage/scratchpad",
"storage/sparse_merkle",
"storage/storage_client",
"storage/state_view",
"storage/storage_proto",
"storage/storage_service",
"testsuite",
"testsuite/libra_fuzzer",
"types",
"vm_validator",
]

[profile.release]
debug = true

[profile.bench]
debug = true
201 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.
@@ -0,0 +1,63 @@
<a href="https://developers.libra.org">
<img width="200" src="./.assets/libra.png" alt="Libra Logo" />
</a>

<hr/>

[![CircleCI](https://circleci.com/gh/libra/libra.svg?style=shield)](https://circleci.com/gh/libra/libra)
[![License](https://img.shields.io/badge/license-Apache-green.svg)](LICENSE.md)

Libra Core implements a decentralized, programmable database which provides a financial infrastructure that can empower billions of people.

## Note to Developers
* Libra Core is a prototype.
* The APIs are constantly evolving and designed to demonstrate types of functionality. Expect substantial changes before the release.
* We’ve launched a testnet that is a live demonstration of an early prototype of the Libra Blockchain software.

## Contributing

Read our [Contrbuting guide](https://developers.libra.org/docs/community/contributing). Find out what’s coming on our [blog](https://developers.libra.org/blog/2019/06/18/The-Path-Forward).

## Getting Started

### Learn About Libra
* [Welcome](https://developers.libra.org/docs/welcome-to-libra)
* [Libra Protocol: Key Concepts](https://developers.libra.org/docs/libra-protocol)
* [Life of a Transaction](https://developers.libra.org/docs/life-of-a-transaction)

### Try Libra Core
* [My First Transaction](https://developers.libra.org/docs/my-first-transaction)
* [Getting Started With Move](https://developers.libra.org/docs/move-overview)

### Technical Papers
* [The Libra Blockchain](https://developers.libra.org/docs/the-libra-blockchain-paper)
* [Move: A Language With Programmable Resources](https://developers.libra.org/docs/move-paper)
* [State Machine Replication in the Libra Blockchain](https://developers.libra.org/docs/state-machine-replication-paper)

### Blog
* [Libra: The Path Forward](https://developers.libra.org/blog/2019/06/18/the-path-forward/)

### Libra Codebase

* [Libra Core Overview](https://developers.libra.org/docs/libra-core-overview)
* [Admission Control](https://developers.libra.org/docs/crates/admission-control)
* [Bytecode Verifier](https://developers.libra.org/docs/crates/bytecode-verifier)
* [Consensus](https://developers.libra.org/docs/crates/consensus)
* [Crypto](https://developers.libra.org/docs/crates/crypto)
* [Execution](https://developers.libra.org/docs/crates/execution)
* [Mempool](https://developers.libra.org/docs/crates/mempool)
* [Move IR Compiler](https://developers.libra.org/docs/crates/ir-to-bytecode)
* [Move Language](https://developers.libra.org/docs/crates/move-language)
* [Network](https://developers.libra.org/docs/crates/network)
* [Storage](https://developers.libra.org/docs/crates/storage)
* [Virtual Machine](https://developers.libra.org/docs/crates/vm)


## Community

* Join us on the [Libra Discourse](https://community.libra.org).
* Get the latest updates to our project by signing up for our [newsletter](https://developers.libra.org/newsletter_form).

## License

Libra Core is licensed as [Apache 2.0](https://github.com/libra/libra/blob/master/LICENSE).
@@ -0,0 +1,5 @@
# Security Policies and Procedures

Please see Libra's
[security policies](https://developers.libra.org/docs/policies/security) and
procedures for reporting vulnerabilities.
@@ -0,0 +1,43 @@
---
id: admission-control
title: Admission Control
custom_edit_url: https://github.com/libra/libra/edit/master/admission_control/README.md
---
# Admission Control

Admission Control (AC) is the public API endpoint for Libra and it takes public gRPC requests from clients.

## Overview
Admission Control (AC) serves two types of requests from clients:
1. SubmitTransaction - To submit a transaction to the associated validator.
2. UpdateToLatestLedger - To query storage, e.g., account state, transaction log, proofs, etc.

## Implementation Details
Admission Control (AC) implements two public APIs:
1. SubmitTransaction(SubmitTransactionRequest)
* Multiple validations will be performed against the request:
* The Transaction signature is checked first. If this check fails, AdmissionControlStatus::Rejected is returned to client.
* The Transaction is then validated by vm_validator. If this fails, the corresponding VMStatus is returned to the client.
* Once the transaction passes all validations, AC queries the sender's account balance and the latest sequence number from storage and sends them to Mempool along with the client request.
* If Mempool returns MempoolAddTransactionStatus::Valid, AdmissionControlStatus::Accepted is returned to the client indicating successful submission. Otherwise, corresponding AdmissionControlStatus is returned to the client.
2. UpdateToLatestLedger(UpdateToLatestLedgerRequest). No extra processing is performed in AC.
* The request is directly passed to storage for query.

## Folder Structure
```
.
├── README.md
├── admission_control_proto
│   └── src
│   └── proto # Protobuf definition files
└── admission_control_service
└── src # gRPC service source files
├── admission_control_node.rs # Wrapper to run AC in a separate thread
├── admission_control_service.rs # gRPC service and main logic
├── main.rs # Main entry to run AC as a binary
└── unit_tests # Tests
```

## This module interacts with:
The Mempool component, to submit transactions from clients.
The Storage component, to query validator storage.
@@ -0,0 +1,22 @@
[package]
name = "admission_control_proto"
version = "0.1.0"
authors = ["Libra Association <opensource@libra.org>"]
license = "Apache-2.0"
publish = false
edition = "2018"

[dependencies]
futures = "0.1.25"
futures03 = { version = "=0.3.0-alpha.16", package = "futures-preview" }
grpcio = "0.4.3"
protobuf = "2.6"

failure = { package = "failure_ext", path = "../../common/failure_ext" }
logger = { path = "../../common/logger" }
mempool = { path = "../../mempool" }
proto_conv = { path = "../../common/proto_conv" }
types = { path = "../../types" }

[build-dependencies]
build_helpers = { path = "../../common/build_helpers" }
@@ -0,0 +1,19 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! This compiles all the `.proto` files under `src/` directory.
//!
//! For example, if there is a file `src/a/b/c.proto`, it will generate `src/a/b/c.rs` and
//! `src/a/b/c_grpc.rs`.

fn main() {
let proto_root = "src";
let dependent_root = "../../types/src/proto";
let mempool_dependent_root = "../../mempool/src/proto/shared";

build_helpers::build_helpers::compile_proto(
proto_root,
vec![dependent_root, mempool_dependent_root],
true,
);
}
@@ -0,0 +1,110 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

pub mod proto;

use crate::proto::admission_control::AdmissionControlStatus as ProtoAdmissionControlStatus;
use failure::prelude::*;
use logger::prelude::*;
use mempool::MempoolAddTransactionStatus;
use proto_conv::{FromProto, IntoProto};
use types::vm_error::VMStatus;

/// AC response status of submit_transaction to clients.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum AdmissionControlStatus {
/// Validator accepted the transaction.
Accepted = 0,
/// The sender is blacklisted.
Blacklisted = 1,
/// The transaction is rejected, e.g. due to incorrect signature.
Rejected = 2,
}

impl IntoProto for AdmissionControlStatus {
type ProtoType = crate::proto::admission_control::AdmissionControlStatus;

fn into_proto(self) -> Self::ProtoType {
match self {
AdmissionControlStatus::Accepted => ProtoAdmissionControlStatus::Accepted,
AdmissionControlStatus::Blacklisted => ProtoAdmissionControlStatus::Blacklisted,
AdmissionControlStatus::Rejected => ProtoAdmissionControlStatus::Rejected,
}
}
}

impl FromProto for AdmissionControlStatus {
type ProtoType = crate::proto::admission_control::AdmissionControlStatus;

fn from_proto(object: Self::ProtoType) -> Result<Self> {
let ret = match object {
ProtoAdmissionControlStatus::Accepted => AdmissionControlStatus::Accepted,
ProtoAdmissionControlStatus::Blacklisted => AdmissionControlStatus::Blacklisted,
ProtoAdmissionControlStatus::Rejected => AdmissionControlStatus::Rejected,
};
Ok(ret)
}
}

/// Rust structure for SubmitTransactionResponse protobuf definition.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct SubmitTransactionResponse {
/// AC status returned to client if any, it includes can be either error or accepted status.
pub ac_status: Option<AdmissionControlStatus>,
/// Mempool error status if any.
pub mempool_error: Option<MempoolAddTransactionStatus>,
/// VM error status if any.
pub vm_error: Option<VMStatus>,
/// The id of validator associated with this AC.
pub validator_id: Vec<u8>,
}

impl IntoProto for SubmitTransactionResponse {
type ProtoType = crate::proto::admission_control::SubmitTransactionResponse;

fn into_proto(self) -> Self::ProtoType {
let mut proto = Self::ProtoType::new();
if let Some(ac_st) = self.ac_status {
proto.set_ac_status(ac_st.into_proto());
} else if let Some(mem_err) = self.mempool_error {
proto.set_mempool_status(mem_err.into_proto());
} else if let Some(vm_st) = self.vm_error {
proto.set_vm_status(vm_st.into_proto());
} else {
error!("No status is available in SubmitTransactionResponse!");
}
proto.set_validator_id(self.validator_id);
proto
}
}

impl FromProto for SubmitTransactionResponse {
type ProtoType = crate::proto::admission_control::SubmitTransactionResponse;

fn from_proto(mut object: Self::ProtoType) -> Result<Self> {
let ac_status = if object.has_ac_status() {
Some(AdmissionControlStatus::from_proto(object.get_ac_status())?)
} else {
None
};
let mempool_error = if object.has_mempool_status() {
Some(MempoolAddTransactionStatus::from_proto(
object.get_mempool_status(),
)?)
} else {
None
};
let vm_error = if object.has_vm_status() {
Some(VMStatus::from_proto(object.take_vm_status())?)
} else {
None
};

Ok(SubmitTransactionResponse {
ac_status,
mempool_error,
vm_error,
validator_id: object.take_validator_id(),
})
}
}
@@ -0,0 +1,76 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

syntax = "proto3";

package admission_control;

import "get_with_proof.proto";
import "transaction.proto";
import "proof.proto";
import "ledger_info.proto";
import "vm_errors.proto";
import "mempool_status.proto";

// -----------------------------------------------------------------------------
// ---------------- Submit transaction
// -----------------------------------------------------------------------------
// The request for transaction submission.
message SubmitTransactionRequest {
// Transaction signed by wallet.
types.SignedTransaction signed_txn = 1;
}

// Additional statuses that are possible from admission control in addition
// to VM statuses.
enum AdmissionControlStatus {
Accepted = 0;
Blacklisted = 1;
Rejected = 2;
}

// The response for transaction submission.
//
// How does a client know if their transaction was included?
// A response from the transaction submission only means that the transaction
// was successfully added to mempool, but not that it is guaranteed to be
// included in the chain. Each transaction should include an expiration time in
// the signed transaction. Let's call this T0. As a client, I submit my
// transaction to a validator. I now need to poll for the transaction I
// submitted. I can use the query that takes my account and sequence number. If
// I receive back that the transaction is completed, I will verify the proofs to
// ensure that this is the transaction I expected. If I receive a response that
// my transaction is not yet completed, I must check the latest timestamp in the
// ledgerInfo that I receive back from the query. If this time is greater than
// T0, I can be certain that my transaction will never be included. If this
// time is less than T0, I need to continue polling.
message SubmitTransactionResponse {
// The status of a transaction submission can either be a VM status, or
// some other admission control/mempool specific status e.g. Blacklisted.
oneof status {
types.VMStatus vm_status = 1;
AdmissionControlStatus ac_status = 2;
mempool.MempoolAddTransactionStatus mempool_status = 3;
}
// Public key(id) of the validator that processed this transaction
bytes validator_id = 4;
}

// -----------------------------------------------------------------------------
// ---------------- Service definition
// -----------------------------------------------------------------------------
service AdmissionControl {
// Public API to submit transaction to a validator.
rpc SubmitTransaction(SubmitTransactionRequest)
returns (SubmitTransactionResponse) {}

// This API is used to update the client to the latest ledger version and
// optionally also request 1..n other pieces of data. This allows for batch
// queries. All queries return proofs that a client should check to validate
// the data. Note that if a client only wishes to update to the latest
// LedgerInfo and receive the proof of this latest version, they can simply
// omit the requested_items (or pass an empty list)
rpc UpdateToLatestLedger(
types.UpdateToLatestLedgerRequest)
returns (types.UpdateToLatestLedgerResponse) {}
}
@@ -0,0 +1,10 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use mempool::proto::shared::mempool_status;
use types::proto::*;

/// Auto generated proto src files
pub mod admission_control;
/// Auto generated proto src files
pub mod admission_control_grpc;
@@ -0,0 +1,35 @@
[package]
name = "admission_control_service"
version = "0.1.0"
authors = ["Libra Association <opensource@libra.org>"]
license = "Apache-2.0"
publish = false
edition = "2018"

[dependencies]
futures = "0.1.25"
futures03 = { version = "=0.3.0-alpha.16", package = "futures-preview" }
grpcio = "0.4.3"
lazy_static = "1.3.0"
protobuf = "2.6"

admission_control_proto = { path = "../admission_control_proto" }
config = { path = "../../config" }
crypto = { path = "../../crypto/legacy_crypto" }
debug_interface = { path = "../../common/debug_interface" }
failure = { package = "failure_ext", path = "../../common/failure_ext" }
executable_helpers = { path = "../../common/executable_helpers"}
grpc_helpers = { path = "../../common/grpc_helpers"}
logger = { path = "../../common/logger" }
mempool = { path = "../../mempool" }
metrics = { path = "../../common/metrics" }
proto_conv = { path = "../../common/proto_conv" }
storage_client = { path = "../../storage/storage_client" }
types = { path = "../../types" }
vm_validator = { path = "../../vm_validator" }

[dev-dependencies]
storage_service = { path = "../../storage/storage_service" }

[build-dependencies]
build_helpers = { path = "../../common/build_helpers" }
@@ -0,0 +1,133 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::admission_control_service::AdmissionControlService;
use admission_control_proto::proto::admission_control_grpc;
use config::config::NodeConfig;
use debug_interface::{node_debug_service::NodeDebugService, proto::node_debug_interface_grpc};
use failure::prelude::*;
use grpc_helpers::spawn_service_thread;
use grpcio::{ChannelBuilder, EnvBuilder, Environment};
use logger::prelude::*;
use mempool::proto::{mempool_client::MempoolClientTrait, mempool_grpc::MempoolClient};
use std::{sync::Arc, thread};
use storage_client::{StorageRead, StorageReadServiceClient};
use vm_validator::vm_validator::VMValidator;

/// Struct to run Admission Control service in a dedicated process. It will be used to spin up
/// extra AC instances to talk to the same validator.
pub struct AdmissionControlNode {
/// Config used to setup environment for this Admission Control service instance.
node_config: NodeConfig,
}

impl Drop for AdmissionControlNode {
fn drop(&mut self) {
info!("Drop AdmissionControl node");
}
}

impl AdmissionControlNode {
/// Construct a new AdmissionControlNode instance using NodeConfig.
pub fn new(node_config: NodeConfig) -> Self {
AdmissionControlNode { node_config }
}

/// Setup environment and start a new Admission Control service.
pub fn run(&self) -> Result<()> {
logger::set_global_log_collector(
self.node_config
.log_collector
.get_log_collector_type()
.unwrap(),
self.node_config.log_collector.is_async,
self.node_config.log_collector.chan_size,
);
info!("Starting AdmissionControl node",);
// Start receiving requests
let client_env = Arc::new(EnvBuilder::new().name_prefix("grpc-ac-mem-").build());
let mempool_connection_str = format!(
"{}:{}",
self.node_config.mempool.address, self.node_config.mempool.mempool_service_port
);
let mempool_channel =
ChannelBuilder::new(Arc::clone(&client_env)).connect(&mempool_connection_str);

self.run_with_clients(
Arc::clone(&client_env),
Arc::new(MempoolClient::new(mempool_channel)),
Some(Arc::new(StorageReadServiceClient::new(
Arc::clone(&client_env),
&self.node_config.storage.address,
self.node_config.storage.port,
))),
)
}

/// This method will start a node using the provided clients to external services.
/// For now, mempool is a mandatory argument, and storage is Option. If it doesn't exist,
/// it'll be generated before starting the node.
pub fn run_with_clients<M: MempoolClientTrait + 'static>(
&self,
env: Arc<Environment>,
mp_client: Arc<M>,
storage_client: Option<Arc<StorageReadServiceClient>>,
) -> Result<()> {
// create storage client if doesnt exist
let storage_client: Arc<dyn StorageRead> = match storage_client {
Some(c) => c,
None => Arc::new(StorageReadServiceClient::new(
env,
&self.node_config.storage.address,
self.node_config.storage.port,
)),
};

let vm_validator = Arc::new(VMValidator::new(
&self.node_config,
Arc::clone(&storage_client),
));

let handle = AdmissionControlService::new(
mp_client,
storage_client,
vm_validator,
self.node_config
.admission_control
.need_to_check_mempool_before_validation,
);
let service = admission_control_grpc::create_admission_control(handle);

let _ac_service_handle = spawn_service_thread(
service,
self.node_config.admission_control.address.clone(),
self.node_config
.admission_control
.admission_control_service_port,
"admission_control",
);

// Start Debug interface
let debug_service =
node_debug_interface_grpc::create_node_debug_interface(NodeDebugService::new());
let _debug_handle = spawn_service_thread(
debug_service,
self.node_config.admission_control.address.clone(),
self.node_config
.debug_interface
.admission_control_node_debug_port,
"debug_service",
);

info!(
"Started AdmissionControl node on port {}",
self.node_config
.admission_control
.admission_control_service_port
);

loop {
thread::park();
}
}
}
@@ -0,0 +1,230 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! Admission Control (AC) is a module acting as the only public end point. It receives api requests
//! from external clients (such as wallets) and performs necessary processing before sending them to
//! next step.

use crate::OP_COUNTERS;
use admission_control_proto::proto::{
admission_control::{
AdmissionControlStatus, SubmitTransactionRequest, SubmitTransactionResponse,
},
admission_control_grpc::AdmissionControl,
};
use failure::prelude::*;
use futures::future::Future;
use futures03::executor::block_on;
use grpc_helpers::provide_grpc_response;
use logger::prelude::*;
use mempool::proto::{
mempool::{AddTransactionWithValidationRequest, HealthCheckRequest},
mempool_client::MempoolClientTrait,
shared::mempool_status::MempoolAddTransactionStatus::{self, MempoolIsFull},
};
use metrics::counters::SVC_COUNTERS;
use proto_conv::{FromProto, IntoProto};
use std::sync::Arc;
use storage_client::StorageRead;
use types::{
proto::get_with_proof::{UpdateToLatestLedgerRequest, UpdateToLatestLedgerResponse},
transaction::SignedTransaction,
};
use vm_validator::vm_validator::{get_account_state, TransactionValidation};

#[cfg(test)]
#[path = "unit_tests/admission_control_service_test.rs"]
mod admission_control_service_test;

/// Struct implementing trait (service handle) AdmissionControlService.
#[derive(Clone)]
pub struct AdmissionControlService<M, V> {
/// gRPC client connecting Mempool.
mempool_client: Arc<M>,
/// gRPC client to send read requests to Storage.
storage_read_client: Arc<dyn StorageRead>,
/// VM validator instance to validate transactions sent from wallets.
vm_validator: Arc<V>,
/// Flag indicating whether we need to check mempool before validation, drop txn if check
/// fails.
need_to_check_mempool_before_validation: bool,
}

impl<M: 'static, V> AdmissionControlService<M, V>
where
M: MempoolClientTrait,
V: TransactionValidation,
{
/// Constructs a new AdmissionControlService instance.
pub fn new(
mempool_client: Arc<M>,
storage_read_client: Arc<dyn StorageRead>,
vm_validator: Arc<V>,
need_to_check_mempool_before_validation: bool,
) -> Self {
AdmissionControlService {
mempool_client,
storage_read_client,
vm_validator,
need_to_check_mempool_before_validation,
}
}

/// Validate transaction signature, then via VM, and add it to Mempool if it passes VM check.
pub(crate) fn submit_transaction_inner(
&self,
req: SubmitTransactionRequest,
) -> Result<SubmitTransactionResponse> {
// Drop requests first if mempool is full (validator is lagging behind) so not to consume
// unnecessary resources.
if !self.can_send_txn_to_mempool()? {
debug!("Mempool is full");
OP_COUNTERS.inc_by("submit_txn.rejected.mempool_full", 1);
let mut response = SubmitTransactionResponse::new();
response.set_mempool_status(MempoolIsFull);
return Ok(response);
}

let signed_txn_proto = req.get_signed_txn();

let signed_txn = match SignedTransaction::from_proto(signed_txn_proto.clone()) {
Ok(t) => t,
Err(e) => {
security_log(SecurityEvent::InvalidTransactionAC)
.error(&e)
.data(&signed_txn_proto)
.log();
let mut response = SubmitTransactionResponse::new();
response.set_ac_status(AdmissionControlStatus::Rejected);
OP_COUNTERS.inc_by("submit_txn.rejected.invalid_txn", 1);
return Ok(response);
}
};

let gas_cost = signed_txn.max_gas_amount();
let validation_status = self
.vm_validator
.validate_transaction(signed_txn.clone())
.wait()
.map_err(|e| {
security_log(SecurityEvent::InvalidTransactionAC)
.error(&e)
.data(&signed_txn)
.log();
e
})?;
if let Some(validation_status) = validation_status {
let mut response = SubmitTransactionResponse::new();
OP_COUNTERS.inc_by("submit_txn.vm_validation.failure", 1);
debug!(
"txn failed in vm validation, status: {:?}, txn: {:?}",
validation_status, signed_txn
);
response.set_vm_status(validation_status.into_proto());
return Ok(response);
}
let sender = signed_txn.sender();
let account_state = block_on(get_account_state(self.storage_read_client.clone(), sender));
let mut add_transaction_request = AddTransactionWithValidationRequest::new();
add_transaction_request.signed_txn = req.signed_txn.clone();
add_transaction_request.set_max_gas_cost(gas_cost);

if let Ok((sequence_number, balance)) = account_state {
add_transaction_request.set_account_balance(balance);
add_transaction_request.set_latest_sequence_number(sequence_number);
}

self.add_txn_to_mempool(add_transaction_request)
}

fn can_send_txn_to_mempool(&self) -> Result<bool> {
if self.need_to_check_mempool_before_validation {
let req = HealthCheckRequest::new();
let is_mempool_healthy = self.mempool_client.health_check(&req)?.get_is_healthy();
return Ok(is_mempool_healthy);
}
Ok(true)
}

/// Add signed transaction to mempool once it passes vm check
fn add_txn_to_mempool(
&self,
add_transaction_request: AddTransactionWithValidationRequest,
) -> Result<SubmitTransactionResponse> {
let mempool_result = self
.mempool_client
.add_transaction_with_validation(&add_transaction_request)?;

debug!("[GRPC] Done with transaction submission request");
let mut response = SubmitTransactionResponse::new();
if mempool_result.get_status() == MempoolAddTransactionStatus::Valid {
OP_COUNTERS.inc_by("submit_txn.txn_accepted", 1);
response.set_ac_status(AdmissionControlStatus::Accepted);
} else {
debug!(
"txn failed in mempool, status: {:?}, txn: {:?}",
mempool_result,
add_transaction_request.get_signed_txn()
);
OP_COUNTERS.inc_by("submit_txn.mempool.failure", 1);
response.set_mempool_status(mempool_result.get_status());
}
Ok(response)
}

/// Pass the UpdateToLatestLedgerRequest to Storage for read query.
fn update_to_latest_ledger_inner(
&self,
req: UpdateToLatestLedgerRequest,
) -> Result<UpdateToLatestLedgerResponse> {
let rust_req = types::get_with_proof::UpdateToLatestLedgerRequest::from_proto(req)?;
let (response_items, ledger_info_with_sigs, validator_change_events) = self
.storage_read_client
.update_to_latest_ledger(rust_req.client_known_version, rust_req.requested_items)?;
let rust_resp = types::get_with_proof::UpdateToLatestLedgerResponse::new(
response_items,
ledger_info_with_sigs,
validator_change_events,
);
Ok(rust_resp.into_proto())
}
}

impl<M: 'static, V> AdmissionControl for AdmissionControlService<M, V>
where
M: MempoolClientTrait,
V: TransactionValidation,
{
/// Submit a transaction to the validator this AC instance connecting to.
/// The specific transaction will be first validated by VM and then passed
/// to Mempool for further processing.
fn submit_transaction(
&mut self,
ctx: ::grpcio::RpcContext<'_>,
req: SubmitTransactionRequest,
sink: ::grpcio::UnarySink<SubmitTransactionResponse>,
) {
debug!("[GRPC] AdmissionControl::submit_transaction");
let _timer = SVC_COUNTERS.req(&ctx);
let resp = self.submit_transaction_inner(req);
provide_grpc_response(resp, ctx, sink);
}

/// This API is used to update the client to the latest ledger version and optionally also
/// request 1..n other pieces of data. This allows for batch queries. All queries return
/// proofs that a client should check to validate the data.
/// Note that if a client only wishes to update to the latest LedgerInfo and receive the proof
/// of this latest version, they can simply omit the requested_items (or pass an empty list).
/// AC will not directly process this request but pass it to Storage instead.
fn update_to_latest_ledger(
&mut self,
ctx: grpcio::RpcContext<'_>,
req: types::proto::get_with_proof::UpdateToLatestLedgerRequest,
sink: grpcio::UnarySink<types::proto::get_with_proof::UpdateToLatestLedgerResponse>,
) {
debug!("[GRPC] AdmissionControl::update_to_latest_ledger");
let _timer = SVC_COUNTERS.req(&ctx);
let resp = self.update_to_latest_ledger_inner(req);
provide_grpc_response(resp, ctx, sink);
}
}
@@ -0,0 +1,25 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

#![deny(missing_docs)]

//! Admission Control
//!
//! Admission Control (AC) is the public API end point taking public gRPC requests from clients.
//! AC serves two types of request from clients:
//! 1. SubmitTransaction, to submit transaction to associated validator.
//! 2. UpdateToLatestLedger, to query storage, e.g. account state, transaction log, and proofs.

/// Wrapper to run AC in a separate process.
pub mod admission_control_node;
/// AC gRPC service.
pub mod admission_control_service;
use lazy_static::lazy_static;
use metrics::OpMetrics;

lazy_static! {
static ref OP_COUNTERS: OpMetrics = OpMetrics::new_and_registered("admission_control");
}

#[cfg(test)]
mod unit_tests;
@@ -0,0 +1,22 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use admission_control_service::admission_control_node;
use executable_helpers::helpers::{
setup_executable, ARG_CONFIG_PATH, ARG_DISABLE_LOGGING, ARG_PEER_ID,
};

/// Run a Admission Control service in its own process.
/// It will also setup global logger and initialize config.
fn main() {
let (config, _logger, _args) = setup_executable(
"Libra AdmissionControl node".to_string(),
vec![ARG_PEER_ID, ARG_CONFIG_PATH, ARG_DISABLE_LOGGING],
);

let admission_control_node = admission_control_node::AdmissionControlNode::new(config);

admission_control_node
.run()
.expect("Unable to run AdmissionControl node");
}
@@ -0,0 +1,280 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{
admission_control_service::{
AdmissionControlService, SubmitTransactionRequest,
SubmitTransactionResponse as ProtoSubmitTransactionResponse,
},
unit_tests::LocalMockMempool,
};
use admission_control_proto::{AdmissionControlStatus, SubmitTransactionResponse};
use crypto::{
hash::CryptoHash,
signing::{generate_keypair, sign_message},
};
use mempool::MempoolAddTransactionStatus;
use proto_conv::FromProto;
use protobuf::{Message, UnknownFields};
use std::sync::Arc;
use storage_service::mocks::mock_storage_client::MockStorageReadClient;
use types::{
account_address::{AccountAddress, ADDRESS_LENGTH},
test_helpers::transaction_test_helpers::get_test_signed_txn,
transaction::RawTransactionBytes,
vm_error::{ExecutionStatus, VMStatus, VMValidationStatus},
};
use vm_validator::mocks::mock_vm_validator::MockVMValidator;

fn create_ac_service_for_ut() -> AdmissionControlService<LocalMockMempool, MockVMValidator> {
AdmissionControlService::new(
Arc::new(LocalMockMempool::new()),
Arc::new(MockStorageReadClient),
Arc::new(MockVMValidator),
false,
)
}

fn assert_status(response: ProtoSubmitTransactionResponse, status: VMStatus) {
let rust_resp = SubmitTransactionResponse::from_proto(response).unwrap();
if rust_resp.ac_status.is_some() {
assert_eq!(
rust_resp.ac_status.unwrap(),
AdmissionControlStatus::Accepted
);
} else {
let decoded_response = rust_resp.vm_error.unwrap();
assert_eq!(decoded_response, status)
}
}

#[test]
fn test_submit_txn_inner_vm() {
let ac_service = create_ac_service_for_ut();
// create request
let mut req: SubmitTransactionRequest = SubmitTransactionRequest::new();
let sender = AccountAddress::new([0; ADDRESS_LENGTH]);
let keypair = generate_keypair();
req.set_signed_txn(get_test_signed_txn(
sender,
0,
keypair.0.clone(),
keypair.1,
None,
));
let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
assert_status(
response,
VMStatus::Validation(VMValidationStatus::SendingAccountDoesNotExist(
"TEST".to_string(),
)),
);
let sender = AccountAddress::new([1; ADDRESS_LENGTH]);
req.set_signed_txn(get_test_signed_txn(
sender,
0,
keypair.0.clone(),
keypair.1,
None,
));
let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
assert_status(
response,
VMStatus::Validation(VMValidationStatus::InvalidSignature),
);
let sender = AccountAddress::new([2; ADDRESS_LENGTH]);
req.set_signed_txn(get_test_signed_txn(
sender,
0,
keypair.0.clone(),
keypair.1,
None,
));
let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
assert_status(
response,
VMStatus::Validation(VMValidationStatus::InsufficientBalanceForTransactionFee),
);
let sender = AccountAddress::new([3; ADDRESS_LENGTH]);
req.set_signed_txn(get_test_signed_txn(
sender,
0,
keypair.0.clone(),
keypair.1,
None,
));
let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
assert_status(
response,
VMStatus::Validation(VMValidationStatus::SequenceNumberTooNew),
);
let sender = AccountAddress::new([4; ADDRESS_LENGTH]);
req.set_signed_txn(get_test_signed_txn(
sender,
0,
keypair.0.clone(),
keypair.1,
None,
));
let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
assert_status(
response,
VMStatus::Validation(VMValidationStatus::SequenceNumberTooOld),
);
let sender = AccountAddress::new([5; ADDRESS_LENGTH]);
req.set_signed_txn(get_test_signed_txn(
sender,
0,
keypair.0.clone(),
keypair.1,
None,
));
let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
assert_status(
response,
VMStatus::Validation(VMValidationStatus::TransactionExpired),
);
let sender = AccountAddress::new([6; ADDRESS_LENGTH]);
req.set_signed_txn(get_test_signed_txn(
sender,
0,
keypair.0.clone(),
keypair.1,
None,
));
let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
assert_status(
response,
VMStatus::Validation(VMValidationStatus::InvalidAuthKey),
);
let sender = AccountAddress::new([8; ADDRESS_LENGTH]);
req.set_signed_txn(get_test_signed_txn(
sender,
0,
keypair.0.clone(),
keypair.1,
None,
));
let response = ac_service.submit_transaction_inner(req.clone()).unwrap();
assert_status(response, VMStatus::Execution(ExecutionStatus::Executed));
let sender = AccountAddress::new([8; ADDRESS_LENGTH]);
let test_key = generate_keypair();
req.set_signed_txn(get_test_signed_txn(
sender,
0,
keypair.0.clone(),
test_key.1,
None,
));
let response = SubmitTransactionResponse::from_proto(
ac_service.submit_transaction_inner(req.clone()).unwrap(),
)
.unwrap();
assert_eq!(
response.ac_status.unwrap(),
AdmissionControlStatus::Rejected,
);
}

#[test]
fn test_reject_unknown_fields() {
let ac_service = create_ac_service_for_ut();
let mut req: SubmitTransactionRequest = SubmitTransactionRequest::new();
let keypair = generate_keypair();
let sender = AccountAddress::random();
let mut signed_txn = get_test_signed_txn(sender, 0, keypair.0.clone(), keypair.1, None);
let mut raw_txn = protobuf::parse_from_bytes::<::types::proto::transaction::RawTransaction>(
signed_txn.raw_txn_bytes.as_ref(),
)
.unwrap();
let mut unknown_fields = UnknownFields::new();
unknown_fields.add_fixed32(1, 2);
raw_txn.unknown_fields = unknown_fields;

let bytes = raw_txn.write_to_bytes().unwrap();
let hash = RawTransactionBytes(&bytes).hash();
let signature = sign_message(hash, &keypair.0).unwrap();

signed_txn.set_raw_txn_bytes(bytes);
signed_txn.set_sender_signature(signature.to_compact().to_vec());
req.set_signed_txn(signed_txn);
let response = SubmitTransactionResponse::from_proto(
ac_service.submit_transaction_inner(req.clone()).unwrap(),
)
.unwrap();
assert_eq!(
response.ac_status.unwrap(),
AdmissionControlStatus::Rejected
);
}

#[test]
fn test_submit_txn_inner_mempool() {
let ac_service = create_ac_service_for_ut();
let mut req: SubmitTransactionRequest = SubmitTransactionRequest::new();
let keypair = generate_keypair();
let insufficient_balance_add = AccountAddress::new([100; ADDRESS_LENGTH]);
req.set_signed_txn(get_test_signed_txn(
insufficient_balance_add,
0,
keypair.0.clone(),
keypair.1,
None,
));
let response = SubmitTransactionResponse::from_proto(
ac_service.submit_transaction_inner(req.clone()).unwrap(),
)
.unwrap();
assert_eq!(
response.mempool_error.unwrap(),
MempoolAddTransactionStatus::InsufficientBalance,
);
let invalid_seq_add = AccountAddress::new([101; ADDRESS_LENGTH]);
req.set_signed_txn(get_test_signed_txn(
invalid_seq_add,
0,
keypair.0.clone(),
keypair.1,
None,
));
let response = SubmitTransactionResponse::from_proto(
ac_service.submit_transaction_inner(req.clone()).unwrap(),
)
.unwrap();
assert_eq!(
response.mempool_error.unwrap(),
MempoolAddTransactionStatus::InvalidSeqNumber,
);
let sys_error_add = AccountAddress::new([102; ADDRESS_LENGTH]);
req.set_signed_txn(get_test_signed_txn(
sys_error_add,
0,
keypair.0.clone(),
keypair.1,
None,
));
let response = SubmitTransactionResponse::from_proto(
ac_service.submit_transaction_inner(req.clone()).unwrap(),
)
.unwrap();
assert_eq!(
response.mempool_error.unwrap(),
MempoolAddTransactionStatus::InvalidUpdate,
);
let accepted_add = AccountAddress::new([103; ADDRESS_LENGTH]);
req.set_signed_txn(get_test_signed_txn(
accepted_add,
0,
keypair.0.clone(),
keypair.1,
None,
));
let response = SubmitTransactionResponse::from_proto(
ac_service.submit_transaction_inner(req.clone()).unwrap(),
)
.unwrap();
assert_eq!(
response.ac_status.unwrap(),
AdmissionControlStatus::Accepted,
);
}
@@ -0,0 +1,65 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use mempool::proto::{
mempool::{
AddTransactionWithValidationRequest, AddTransactionWithValidationResponse,
HealthCheckRequest, HealthCheckResponse,
},
mempool_client::MempoolClientTrait,
shared::mempool_status::MempoolAddTransactionStatus,
};
use proto_conv::FromProto;
use std::time::SystemTime;
use types::{account_address::ADDRESS_LENGTH, transaction::SignedTransaction};

// Define a local mempool to use for unit tests here, ignore methods not used by the test
#[derive(Clone)]
pub struct LocalMockMempool {
created_time: SystemTime,
}

impl LocalMockMempool {
pub fn new() -> Self {
Self {
created_time: SystemTime::now(),
}
}
}

impl MempoolClientTrait for LocalMockMempool {
fn add_transaction_with_validation(
&self,
req: &AddTransactionWithValidationRequest,
) -> ::grpcio::Result<AddTransactionWithValidationResponse> {
let mut resp = AddTransactionWithValidationResponse::new();
let insufficient_balance_add = [100_u8; ADDRESS_LENGTH];
let invalid_seq_add = [101_u8; ADDRESS_LENGTH];
let sys_error_add = [102_u8; ADDRESS_LENGTH];
let accepted_add = [103_u8; ADDRESS_LENGTH];
let mempool_full = [104_u8; ADDRESS_LENGTH];
let signed_txn = SignedTransaction::from_proto(req.get_signed_txn().clone()).unwrap();
let sender = signed_txn.sender();
if sender.as_ref() == insufficient_balance_add {
resp.set_status(MempoolAddTransactionStatus::InsufficientBalance);
} else if sender.as_ref() == invalid_seq_add {
resp.set_status(MempoolAddTransactionStatus::InvalidSeqNumber);
} else if sender.as_ref() == sys_error_add {
resp.set_status(MempoolAddTransactionStatus::InvalidUpdate);
} else if sender.as_ref() == accepted_add {
resp.set_status(MempoolAddTransactionStatus::Valid);
} else if sender.as_ref() == mempool_full {
resp.set_status(MempoolAddTransactionStatus::MempoolIsFull);
}
Ok(resp)
}
fn health_check(&self, _req: &HealthCheckRequest) -> ::grpcio::Result<HealthCheckResponse> {
let mut ret = HealthCheckResponse::new();
let duration_ms = SystemTime::now()
.duration_since(self.created_time)
.unwrap()
.as_millis();
ret.set_is_healthy(duration_ms > 500 || duration_ms < 300);
Ok(ret)
}
}
@@ -0,0 +1,41 @@
[package]
name = "client"
version = "0.1.0"
authors = ["Libra Association <opensource@libra.org>"]
license = "Apache-2.0"
publish = false
edition = "2018"

[dependencies]
bincode = "1.1.1"
chrono = "0.4.6"
futures = "0.1.23"
grpcio = "0.4.3"
hex = "0.3.2"
hyper = "0.12"
itertools = "0.8.0"
proptest = "0.9.2"
protobuf = "2.6"
rand = "0.6.5"
rustyline = "4.1.0"
tokio = "0.1.16"
rust_decimal = "1.0.1"
num-traits = "0.2"
serde = { version = "1.0.89", features = ["derive"] }
structopt = "0.2.15"

admission_control_proto = { version = "0.1.0", path = "../admission_control/admission_control_proto" }
config = { path = "../config" }
crash_handler = { path = "../common/crash_handler" }
crypto = { path = "../crypto/legacy_crypto" }
failure = { package = "failure_ext", path = "../common/failure_ext" }
libc = "0.2.48"
libra_wallet = { path = "./libra_wallet" }
logger = { path = "../common/logger" }
metrics = { path = "../common/metrics" }
proto_conv = { path = "../common/proto_conv" }
types = { path = "../types" }
vm_genesis = { path = "../language/vm/vm_genesis" }

[dev-dependencies]
tempfile = "3.0.6"
@@ -0,0 +1,43 @@
[package]
name = "libra_wallet"
version = "0.1.0"
authors = ["Libra Association <opensource@libra.org>"]
license = "Apache-2.0"
publish = false
edition = "2018"

[dependencies.ed25519-dalek]
version = "1.0.0-pre.1"

[dependencies.types]
path = "../../types"

[dependencies.libra_crypto]
path = "../../crypto/legacy_crypto"
package = "crypto"

[dependencies.proto_conv]
path = "../../common/proto_conv"

[dependencies.failure]
path = "../../common/failure_ext"
package = "failure_ext"

[dependencies]
rust-crypto = "0.2"
log = "0.4"
simple_logger = "0.5"
rand = "0.6.5"
rand_chacha = "0.1.1"
rand_core = "0.4.0"
hex = "0.3"
byteorder = "1.2.6"
serde = "1"
serde_derive = "1"
serde_json = "1.0.31"
tiny-keccak = "1.4.2"
protobuf = "2.6"
sha3 = "0.8.2"

[dev-dependencies]
tempfile = "3.0.6"
@@ -0,0 +1,15 @@
# Libra Wallet

Libra Wallet is a pure-rust implementation of hierarchical key derivation for SecretKey material in Libra.

# Overview

`libra_wallet` is a library providing hierarchical key derivation for SecretKey material in Libra. The following crate is largely inspired by [`rust-wallet`](https://github.com/rust-bitcoin/rust-wallet) with minor modifications to the key derivation function. Note that Libra makes use of the ed25519 Edwards Curve Digital Signature Algorithm (EdDSA) over the Edwards Cruve cruve25519. Therefore, BIP32-like PublicKey derivation is not possible without falling back to a traditional non-deterministic Schnorr signature algorithm. For this reason, we modified the key derivation function to a simpler alternative.

The `internal_macros.rs` is taken from [`rust-bitcoin`](https://github.com/rust-bitcoin/rust-bitcoin/blob/master/src/internal_macros.rs) and `mnemonic.rs` is a slightly modified version of the file with the same name from [`rust-wallet`](https://github.com/rust-bitcoin/rust-wallet/blob/master/wallet/src/mnemonic.rs), while `error.rs`, `key_factor.rs` and `wallet_library.rs` are modified to present a minimalist wallet library for the Libra Client. Note that `mnemonic.rs` from `rust-wallet` adheres to the [`BIP39`](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) spec.

# Implementation Details

`key_factory.rs` implements the key derivation functions. The `KeyFactory` struct holds the Master Secret Material used to derive the Child Key(s). The constructor of a particular `KeyFactory` accepts a `[u8; 64]` `Seed` and computes both the `Master` Secret Material as well as the `ChainCode` from the HMAC-512 of the `Seed`. Finally, the `KeyFactory` allows to derive a child PrivateKey at a particular `ChildNumber` from the Master and ChainCode, as well as the `ChildNumber`'s u64 member.

`wallet_library.rs` is a thin wrapper around `KeyFactory` which enables to keep track of Libra `AccountAddresses` and the information required to restore the current wallet from a `Mnemonic` backup. The `WalletLibrary` struct includes constructors that allow to generate a new `WalletLibrary` from OS randomness or generate a `WalletLibrary` from an instance of `Mnemonic`. `WalletLibrary` also allows to generate new addresses in-order or out-of-order via the `fn new_address` and `fn new_address_at_child_number`. Finally, `WalletLibrary` is capable of signing a Libra `RawTransaction` withe PrivateKey associated to the `AccountAddress` submitted. Note that in the future, Libra will support rotating authentication keys and therefore, `WalletLibrary` will need to understand more general inputs when mapping `AuthenticationKeys` to `PrivateKeys`
@@ -0,0 +1,83 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use failure;
use libra_crypto::hkdf::HkdfError;
use std::{convert, error::Error, fmt, io};

/// We define our own Result type in order to not have to import the libra/common/failture_ext
pub type Result<T> = ::std::result::Result<T, WalletError>;

/// Libra Wallet Error is a convenience enum for generating arbitarary WalletErrors. Curently, only
/// the LibraWalletGeneric error is being used, but there are plans to add more specific errors as
/// LibraWallet matures
pub enum WalletError {
/// generic error message
LibraWalletGeneric(String),
}

impl Error for WalletError {
fn description(&self) -> &str {
match *self {
WalletError::LibraWalletGeneric(ref s) => s,
}
}

fn cause(&self) -> Option<&Error> {
match *self {
WalletError::LibraWalletGeneric(_) => None,
}
}
}

impl fmt::Display for WalletError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
WalletError::LibraWalletGeneric(ref s) => write!(f, "LibraWalletGeneric: {}", s),
}
}
}

impl fmt::Debug for WalletError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
(self as &fmt::Display).fmt(f)
}
}

impl convert::From<WalletError> for io::Error {
fn from(_err: WalletError) -> io::Error {
match _err {
WalletError::LibraWalletGeneric(s) => io::Error::new(io::ErrorKind::Other, s),
}
}
}

impl convert::From<io::Error> for WalletError {
fn from(err: io::Error) -> WalletError {
WalletError::LibraWalletGeneric(err.description().to_string())
}
}

impl convert::From<failure::prelude::Error> for WalletError {
fn from(err: failure::prelude::Error) -> WalletError {
WalletError::LibraWalletGeneric(format!("{}", err))
}
}

impl convert::From<protobuf::error::ProtobufError> for WalletError {
fn from(err: protobuf::error::ProtobufError) -> WalletError {
WalletError::LibraWalletGeneric(err.description().to_string())
}
}

impl convert::From<ed25519_dalek::SignatureError> for WalletError {
fn from(err: ed25519_dalek::SignatureError) -> WalletError {
WalletError::LibraWalletGeneric(format!("{}", err))
}
}

impl convert::From<HkdfError> for WalletError {
fn from(err: HkdfError) -> WalletError {
WalletError::LibraWalletGeneric(format!("{}", err))
}
}
@@ -0,0 +1,245 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! The following macros are slightly modified from rust-bitcoin. The original file may be found
//! here:
//!
//! https://github.com/rust-bitcoin/rust-bitcoin/blob/master/src/internal_macros.rs

macro_rules! impl_array_newtype {
($thing:ident, $ty:ty, $len:expr) => {
impl $thing {
#[inline]
/// Converts the object to a raw pointer
pub fn as_ptr(&self) -> *const $ty {
let &$thing(ref dat) = self;
dat.as_ptr()
}

#[inline]
/// Converts the object to a mutable raw pointer
pub fn as_mut_ptr(&mut self) -> *mut $ty {
let &mut $thing(ref mut dat) = self;
dat.as_mut_ptr()
}

#[inline]
/// Returns the length of the object as an array
pub fn len(&self) -> usize {
$len
}

#[inline]
/// Returns whether the object, as an array, is empty. Always false.
pub fn is_empty(&self) -> bool {
false
}

#[inline]
/// Returns the underlying bytes.
pub fn as_bytes(&self) -> &[$ty; $len] {
&self.0
}

#[inline]
/// Returns the underlying bytes.
pub fn to_bytes(&self) -> [$ty; $len] {
self.0.clone()
}

#[inline]
/// Returns the underlying bytes.
pub fn into_bytes(self) -> [$ty; $len] {
self.0
}
}

impl<'a> From<&'a [$ty]> for $thing {
fn from(data: &'a [$ty]) -> $thing {
assert_eq!(data.len(), $len);
let mut ret = [0; $len];
ret.copy_from_slice(&data[..]);
$thing(ret)
}
}

impl ::std::ops::Index<usize> for $thing {
type Output = $ty;

#[inline]
fn index(&self, index: usize) -> &$ty {
let &$thing(ref dat) = self;
&dat[index]
}
}

impl_index_newtype!($thing, $ty);

impl PartialEq for $thing {
#[inline]
fn eq(&self, other: &$thing) -> bool {
&self[..] == &other[..]
}
}

impl Eq for $thing {}

impl PartialOrd for $thing {
#[inline]
fn partial_cmp(&self, other: &$thing) -> Option<::std::cmp::Ordering> {
Some(self.cmp(&other))
}
}

impl Ord for $thing {
#[inline]
fn cmp(&self, other: &$thing) -> ::std::cmp::Ordering {
// manually implement comparison to get little-endian ordering
// (we need this for our numeric types; non-numeric ones shouldn't
// be ordered anyway except to put them in BTrees or whatever, and
// they don't care how we order as long as we're consistent).
for i in 0..$len {
if self[$len - 1 - i] < other[$len - 1 - i] {
return ::std::cmp::Ordering::Less;
}
if self[$len - 1 - i] > other[$len - 1 - i] {
return ::std::cmp::Ordering::Greater;
}
}
::std::cmp::Ordering::Equal
}
}

#[cfg_attr(feature = "clippy", allow(expl_impl_clone_on_copy))] // we don't define the `struct`, we have to explicitly impl
impl Clone for $thing {
#[inline]
fn clone(&self) -> $thing {
$thing::from(&self[..])
}
}

impl Copy for $thing {}

impl ::std::hash::Hash for $thing {
#[inline]
fn hash<H>(&self, state: &mut H)
where
H: ::std::hash::Hasher,
{
(&self[..]).hash(state);
}

fn hash_slice<H>(data: &[$thing], state: &mut H)
where
H: ::std::hash::Hasher,
{
for d in data.iter() {
(&d[..]).hash(state);
}
}
}
};
}

macro_rules! impl_array_newtype_encodable {
($thing:ident, $ty:ty, $len:expr) => {
#[cfg(feature = "serde")]
impl<'de> $crate::serde::Deserialize<'de> for $thing {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: $crate::serde::Deserializer<'de>,
{
use $crate::std::fmt::{self, Formatter};

struct Visitor;
impl<'de> $crate::serde::de::Visitor<'de> for Visitor {
type Value = $thing;

fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("a fixed size array")
}

#[inline]
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: $crate::serde::de::SeqAccess<'de>,
{
let mut ret: [$ty; $len] = [0; $len];
for item in ret.iter_mut() {
*item = match seq.next_element()? {
Some(c) => c,
None => {
return Err($crate::serde::de::Error::custom("end of stream"))
}
};
}
Ok($thing(ret))
}
}

deserializer.deserialize_seq(Visitor)
}
}

#[cfg(feature = "serde")]
impl $crate::serde::Serialize for $thing {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: $crate::serde::Serializer,
{
let &$thing(ref dat) = self;
(&dat[..]).serialize(serializer)
}
}
};
}

macro_rules! impl_array_newtype_show {
($thing:ident) => {
impl ::std::fmt::Debug for $thing {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(f, concat!(stringify!($thing), "({:?})"), &self[..])
}
}
};
}

macro_rules! impl_index_newtype {
($thing:ident, $ty:ty) => {
impl ::std::ops::Index<::std::ops::Range<usize>> for $thing {
type Output = [$ty];

#[inline]
fn index(&self, index: ::std::ops::Range<usize>) -> &[$ty] {
&self.0[index]
}
}

impl ::std::ops::Index<::std::ops::RangeTo<usize>> for $thing {
type Output = [$ty];

#[inline]
fn index(&self, index: ::std::ops::RangeTo<usize>) -> &[$ty] {
&self.0[index]
}
}

impl ::std::ops::Index<::std::ops::RangeFrom<usize>> for $thing {
type Output = [$ty];

#[inline]
fn index(&self, index: ::std::ops::RangeFrom<usize>) -> &[$ty] {
&self.0[index]
}
}

impl ::std::ops::Index<::std::ops::RangeFull> for $thing {
type Output = [$ty];

#[inline]
fn index(&self, _: ::std::ops::RangeFull) -> &[$ty] {
&self.0[..]
}
}
};
}
@@ -0,0 +1,47 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! A module to generate, store and load known users accounts.
//! The concept of known users can be helpful for testing to provide reproducable results.

use crate::*;
use failure::prelude::*;
use std::{
fs::File,
io::{BufRead, BufReader, Write},
path::Path,
};

/// Delimiter used to ser/deserialize account data.
pub const DELIMITER: &str = ";";

/// Recover wallet from the path specified.
pub fn recover<P: AsRef<Path>>(path: &P) -> Result<WalletLibrary> {
let input = File::open(path)?;
let mut buffered = BufReader::new(input);

let mut line = String::new();
let _ = buffered.read_line(&mut line)?;
let parts: Vec<&str> = line.split(DELIMITER).collect();
ensure!(parts.len() == 2, format!("Invalid entry '{}'", line));

let mnemonic = Mnemonic::from(&parts[0].to_string()[..])?;
let mut wallet = WalletLibrary::new_from_mnemonic(mnemonic);
wallet.generate_addresses(parts[1].trim().to_string().parse::<u64>()?)?;

Ok(wallet)
}

/// Write wallet seed to file.
pub fn write_recovery<P: AsRef<Path>>(wallet: &WalletLibrary, path: &P) -> Result<()> {
let mut output = File::create(path)?;
writeln!(
output,
"{}{}{}",
wallet.mnemonic().to_string(),
DELIMITER,
wallet.key_leaf()
)?;

Ok(())
}
@@ -0,0 +1,240 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! The following is a minimalist version of a hierarchical key derivation library for the
//! LibraWallet.
//!
//! Note that the Libra Blockchain makes use of ed25519 Edwards Digital Signature Algorithm
//! (EdDSA) and therefore, BIP32 Public Key derivation is not available without falling back to
//! a non-deterministic Schnorr signature scheme. As LibraWallet is meant to be a minimalist
//! reference implementation of a simple wallet, the following does not deviate from the
//! ed25519 spec. In a future iteration of this wallet, we will also provide an implementation
//! of a Schnorr variant over curve25519 and demonstrate our proposal for BIP32-like public key
//! derivation.
//!
//! Note further that the Key Derivation Function (KDF) chosen in the derivation of Child
//! Private Keys adheres to [HKDF RFC 5869](https://tools.ietf.org/html/rfc5869).

use byteorder::{ByteOrder, LittleEndian};
use crypto::{hmac::Hmac as CryptoHmac, pbkdf2::pbkdf2, sha3::Sha3};
use ed25519_dalek;
use libra_crypto::{hash::HashValue, hkdf::Hkdf};
use serde::{Deserialize, Serialize};
use sha3::Sha3_256;
use std::{convert::TryFrom, ops::AddAssign};
use tiny_keccak::Keccak;
use types::account_address::AccountAddress;

use crate::{error::Result, mnemonic::Mnemonic};

/// Master is a set of raw bytes that are used for child key derivation
pub struct Master([u8; 32]);
impl_array_newtype!(Master, u8, 32);
impl_array_newtype_show!(Master);
impl_array_newtype_encodable!(Master, u8, 32);

/// A child number for a derived key, used to derive a certain private key from the Master
#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct ChildNumber(pub(crate) u64);

impl ChildNumber {
/// Constructor from u64
pub fn new(child_number: u64) -> Self {
Self(child_number)
}

/// Bump the ChildNumber
pub fn increment(&mut self) {
self.add_assign(Self(1));
}
}

impl std::ops::AddAssign for ChildNumber {
fn add_assign(&mut self, other: Self) {
*self = Self(self.0 + other.0)
}
}

impl std::convert::AsRef<u64> for ChildNumber {
fn as_ref(&self) -> &u64 {
&self.0
}
}

impl std::convert::AsMut<u64> for ChildNumber {
fn as_mut(&mut self) -> &mut u64 {
&mut self.0
}
}

/// Derived private key.
pub struct ExtendedPrivKey {
/// Child number of the key used to derive from Parent.
_child_number: ChildNumber,
/// Private key.
private_key: ed25519_dalek::SecretKey,
}

impl ExtendedPrivKey {
/// Constructor for creating an ExtendedPrivKey from a ed25519 PrivateKey. Note that the
/// ChildNumber are not used in this iteration of LibraWallet, but in order to
/// enable more general Hierarchical KeyDerivation schemes, we include it for completeness.
pub fn new(_child_number: ChildNumber, private_key: ed25519_dalek::SecretKey) -> Self {
Self {
_child_number,
private_key,
}
}

/// Returns the PublicKey associated to a particular ExtendedPrivKey
pub fn get_public(&self) -> ed25519_dalek::PublicKey {
(&self.private_key).into()
}

/// Computes the sha3 hash of the PublicKey and attempts to construct a Libra AccountAddress
/// from the raw bytes of the pubkey hash
pub fn get_address(&self) -> Result<AccountAddress> {
let public_key = self.get_public();
let mut keccak = Keccak::new_sha3_256();
let mut hash = [0u8; 32];
keccak.update(&public_key.to_bytes());
keccak.finalize(&mut hash);
let addr = AccountAddress::try_from(&hash[..])?;
Ok(addr)
}

/// Libra specific sign function that is capable of signing an arbitrary HashValue
/// NOTE: In Libra, we do not sign the raw bytes of a transaction, instead we sign the raw
/// bytes of the sha3 hash of the raw bytes of a transaction. It is important to note that the
/// raw bytes of the sha3 hash will be hashed again as part of the ed25519 signature algorithm.
/// In other words: In Libra, the message used for signature and verification is the sha3 hash
/// of the transaction. This sha3 hash is then hashed again using SHA512 to arrive at the
/// deterministic nonce for the EdDSA.
pub fn sign(&self, msg: HashValue) -> ed25519_dalek::Signature {
let public_key: ed25519_dalek::PublicKey = (&self.private_key).into();
let expanded_secret_key: ed25519_dalek::ExpandedSecretKey =
ed25519_dalek::ExpandedSecretKey::from(&self.private_key);
expanded_secret_key.sign(msg.as_ref(), &public_key)
}
}

/// Wrapper struct from which we derive child keys
pub struct KeyFactory {
master: Master,
}

impl KeyFactory {
const MNEMONIC_SALT_PREFIX: &'static [u8] = b"LIBRA WALLET: mnemonic salt prefix$";
const MASTER_KEY_SALT: &'static [u8] = b"LIBRA WALLET: master key salt$";
const INFO_PREFIX: &'static [u8] = b"LIBRA WALLET: derived key$";
/// Instantiate a new KeyFactor from a Seed, where the [u8; 64] raw bytes of the Seed are used
/// to derive both the Master
pub fn new(seed: &Seed) -> Result<Self> {
let hkdf_extract = Hkdf::<Sha3_256>::extract(Some(KeyFactory::MASTER_KEY_SALT), &seed.0)?;

Ok(Self {
master: Master::from(&hkdf_extract[..32]),
})
}

/// Getter for the Master
pub fn master(&self) -> &[u8] {
&self.master.0[..]
}

/// Derive a particular PrivateKey at a certain ChildNumber
///
/// Note that the function below adheres to [HKDF RFC 5869](https://tools.ietf.org/html/rfc5869).
pub fn private_child(&self, child: ChildNumber) -> Result<ExtendedPrivKey> {
// application info in the HKDF context is defined as Libra derived key$child_number.
let mut le_n = [0u8; 8];
LittleEndian::write_u64(&mut le_n, child.0);
let mut info = KeyFactory::INFO_PREFIX.to_vec();
info.extend_from_slice(&le_n);

let hkdf_expand = Hkdf::<Sha3_256>::expand(&self.master(), Some(&info), 32)?;
let sk = ed25519_dalek::SecretKey::from_bytes(&hkdf_expand)?;

Ok(ExtendedPrivKey::new(child, sk))
}
}

/// Seed is the ouput of a one-way function, which accepts a Mnemonic as input
pub struct Seed([u8; 32]);

impl Seed {
/// Get the underlying Seed internal data
pub fn data(&self) -> Vec<u8> {
self.0.to_vec()
}
}

impl Seed {
/// This constructor implements the one-way function that allows to generate a Seed from a
/// particular Mnemonic and salt. WalletLibrary implements a fixed salt, but a user could
/// choose a user-defined salt instead of the hardcoded one.
pub fn new(mnemonic: &Mnemonic, salt: &str) -> Seed {
let mut mac = CryptoHmac::new(Sha3::sha3_256(), mnemonic.to_string().as_bytes());
let mut output = [0u8; 32];

let mut msalt = KeyFactory::MNEMONIC_SALT_PREFIX.to_vec();
msalt.extend_from_slice(salt.as_bytes());

pbkdf2(&mut mac, &msalt, 2048, &mut output);
Seed(output)
}
}

#[test]
fn assert_default_child_number() {
assert_eq!(ChildNumber::default(), ChildNumber(0));
}

#[test]
fn test_key_derivation() {
let data = hex::decode("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f").unwrap();
let mnemonic = Mnemonic::from("legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will").unwrap();
assert_eq!(
mnemonic.to_string(),
Mnemonic::mnemonic(&data).unwrap().to_string()
);
let seed = Seed::new(&mnemonic, "LIBRA");

let key_factory = KeyFactory::new(&seed).unwrap();
assert_eq!(
"16274c9618ed59177ca948529c1884ba65c57984d562ec2b4e5aa1ee3e3903be",
hex::encode(&key_factory.master())
);

// Check child_0 key derivation.
let child_private_0 = key_factory.private_child(ChildNumber(0)).unwrap();
assert_eq!(
"358a375f36d74c30b7f3299b62d712b307725938f8cc931100fbd10a434fc8b9",
hex::encode(&child_private_0.private_key.to_bytes()[..])
);

// Check determinism, regenerate child_0.
let child_private_0_again = key_factory.private_child(ChildNumber(0)).unwrap();
assert_eq!(
hex::encode(&child_private_0.private_key.to_bytes()[..]),
hex::encode(&child_private_0_again.private_key.to_bytes()[..])
);

// Check child_1 key derivation.
let child_private_1 = key_factory.private_child(ChildNumber(1)).unwrap();
assert_eq!(
"a325fe7d27b1b49f191cc03525951fec41b6ffa2d4b3007bb1d9dd353b7e56a6",
hex::encode(&child_private_1.private_key.to_bytes()[..])
);

let mut child_1_again = ChildNumber(0);
child_1_again.increment();
assert_eq!(ChildNumber(1), child_1_again);

// Check determinism, regenerate child_1, but by incrementing ChildNumber(0).
let child_private_1_from_increment = key_factory.private_child(child_1_again).unwrap();
assert_eq!(
"a325fe7d27b1b49f191cc03525951fec41b6ffa2d4b3007bb1d9dd353b7e56a6",
hex::encode(&child_private_1_from_increment.private_key.to_bytes()[..])
);
}
@@ -0,0 +1,24 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

/// Error crate
pub mod error;

/// Internal macros
#[macro_use]
pub mod internal_macros;

/// Utils for read/write
pub mod io_utils;

/// Utils for key derivation
pub mod key_factory;

/// Utils for mnemonic seed
pub mod mnemonic;

/// Utils for wallet library
pub mod wallet_library;

/// Default imports
pub use crate::{mnemonic::Mnemonic, wallet_library::WalletLibrary};