Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Crypto Eth Agent

## 1. Project Basic Information
- **Project Name**: crypto_eth
- **Description**: A cryptographic service implementation for the CITA-Cloud platform, adopting Ethereum-compatible standards (secp256k1 for signatures and Keccak for hashing).
- **License**: Apache-2.0
- **Language**: Rust
- **Frameworks**: Tonic (gRPC), Tokio (Async Runtime), Cloud-Util (Common Utilities)

## 2. Core Capabilities

### 2.1 Cryptographic Operations
The service provides a gRPC interface (`CryptoService`) supporting the following operations:
- **Hashing**: Computes Keccak-256 hashes of data (`HashData`).
- **Signature Verification**: Verifies secp256k1 signatures (`VerifyDataHash` - *Note: function name suggests hash verification, but context implies signature/data integrity checks*).
- **Signing**: Signs messages using a local private key (`SignMessage`).
- **Recovery**: Recovers the public key/address from a signature (`RecoverSignature`).
- **Batch Verification**: Optimistically verifies a batch of transactions (`CheckTransactions`).

### 2.2 Observability & Maintenance
- **Health Check**: Implements the standard gRPC Health Check protocol.
- **Metrics**: Exposes Prometheus-compatible metrics via an HTTP server (default port 60005).
- **Logging**: Configurable logging via `log_config`.

## 3. Runtime Dependencies

### 3.1 Configuration
The service requires a TOML configuration file (default `config.toml`).

**Key Configuration Options:**
- `crypto_port` (Default: 50005): Port for the gRPC crypto service.
- `enable_metrics` (Default: true): Enable/disable metrics exporter.
- `metrics_port` (Default: 60005): Port for the metrics HTTP server.
- `domain`: Service domain name.
- `log_config`: Logging settings (level, rotation, etc.).

### 3.2 External Resources
- **Private Key File**: A file containing the private key for signing operations (path specified via CLI).

## 4. Usage Examples

### 4.1 Build
```bash
cargo build --release
```

### 4.2 Run
```bash
# Run with default configuration
./target/release/crypto run

# Run with custom config and private key
./target/release/crypto run -c config.toml -p private_key
```

### 4.3 Configuration Example (config.toml)
```toml
crypto_port = 50005
enable_metrics = true
metrics_port = 60005
domain = "crypto-eth"

[log_config]
log_level = "info"
```

## 5. API Reference (gRPC)
The service implements the `CryptoService` defined in `cita_cloud_proto`:

| Method | Description |
|--------|-------------|
| `GetCryptoInfo` | Returns metadata about the crypto module (name: "crypto_eth"). |
| `HashData` | Computes the hash of input data. |
| `VerifyDataHash` | Verifies if a hash matches the provided data. |
| `SignMessage` | Signs a message hash with the configured private key. |
| `RecoverSignature` | Recovers the address from a signature and message hash. |
| `CheckTransactions` | Verifies a stream/batch of raw transactions. |
20 changes: 10 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
[package]
name = "crypto"
version = "6.7.4"
version = "6.7.5"
authors = ["Rivtower Technologies <contact@rivtower.com>"]
license = "Apache-2.0"
edition = "2021"
edition = "2024"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "4.5", features = ["derive"] }
tonic = "0.12"
prost = "0.13"
tokio = { version = "1.41", features = ["full"] }
tonic = "0.14"
prost = "0.14"
tokio = { version = "1.49", features = ["full"] }
hex = "0.4"
tower = "0.5"
tiny-keccak = { version = "2.0", features = ["keccak"] }
secp256k1 = {version = "0.30", features = ["rand", "recovery"]}
secp256k1 = {version = "0.31", features = ["rand", "recovery"]}
lazy_static = "1.5"
rayon = "1.10"
rayon = "1.11"
serde = "1.0"
serde_derive = "1.0"
tracing = "0.1"

cloud-util = { package = "cloud-util", git = "https://github.com/cita-cloud/cloud-common-rs" }
cita_cloud_proto = { package = "cita_cloud_proto", git = "https://github.com/cita-cloud/cloud-common-rs" }
cloud-util = { package = "cloud-util", git = "https://github.com/cita-cloud/cloud-common-rs", branch = "update" }
cita_cloud_proto = { package = "cita_cloud_proto", git = "https://github.com/cita-cloud/cloud-common-rs", branch = "update" }

[dev-dependencies]
rand = "0.8" # must be match deps of secp256k1
rand = "0.9" # must be match deps of secp256k1

[profile.release.package."*"]
# Set the default for dependencies.
Expand Down
28 changes: 14 additions & 14 deletions src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ fn secp256k1_sign(
msg: &[u8],
) -> Result<[u8; SECP256K1_SIGNATURE_BYTES_LEN], StatusCodeEnum> {
let context = &SECP256K1;
let sec = secp256k1::SecretKey::from_slice(privkey).unwrap();
if let Ok(message) = secp256k1::Message::from_digest_slice(msg) {
let s = context.sign_ecdsa_recoverable(&message, &sec);
let sec = secp256k1::SecretKey::from_byte_array(privkey.try_into().unwrap()).unwrap();
if let Ok(digest) = msg.try_into() {
let message = secp256k1::Message::from_digest(digest);
let s = context.sign_ecdsa_recoverable(message, &sec);
let (rec_id, data) = s.serialize_compact();
let mut data_arr = [0; SECP256K1_SIGNATURE_BYTES_LEN];

Expand All @@ -60,17 +61,16 @@ fn secp256k1_recover(signature: &[u8], message: &[u8]) -> Result<Vec<u8>, Status
let context = &SECP256K1;
if let Ok(rid) =
secp256k1::ecdsa::RecoveryId::try_from(signature[SECP256K1_SIGNATURE_BYTES_LEN - 1] as i32)
{
if let Ok(rsig) = secp256k1::ecdsa::RecoverableSignature::from_compact(
&& let Ok(rsig) = secp256k1::ecdsa::RecoverableSignature::from_compact(
&signature[0..SECP256K1_SIGNATURE_BYTES_LEN - 1],
rid,
) {
if let Ok(msg) = secp256k1::Message::from_digest_slice(message) {
if let Ok(publ) = context.recover_ecdsa(&msg, &rsig) {
let serialized = publ.serialize_uncompressed();
return Ok(serialized[1..65].to_vec());
}
}
)
&& let Ok(digest) = message.try_into()
{
let msg = secp256k1::Message::from_digest(digest);
if let Ok(publ) = context.recover_ecdsa(msg, &rsig) {
let serialized = publ.serialize_uncompressed();
return Ok(serialized[1..65].to_vec());
}
}

Expand All @@ -93,7 +93,7 @@ pub fn verify_data_hash(data: &[u8], hash: &[u8]) -> Result<(), StatusCodeEnum>

pub fn sk2pk(sk: &[u8]) -> Vec<u8> {
let context = &SECP256K1;
let sec = secp256k1::SecretKey::from_slice(sk).unwrap();
let sec = secp256k1::SecretKey::from_byte_array(sk.try_into().unwrap()).unwrap();
let pub_key = secp256k1::PublicKey::from_secret_key(context, &sec);
let serialized = pub_key.serialize_uncompressed();
serialized[1..].to_vec()
Expand Down Expand Up @@ -231,7 +231,7 @@ mod tests {
StatusCodeEnum,
> {
let context = &SECP256K1;
let (sec_key, pub_key) = context.generate_keypair(&mut rand::thread_rng());
let (sec_key, pub_key) = context.generate_keypair(&mut rand::rng());

let serialized = pub_key.serialize_uncompressed();
let mut pub_key = [0u8; SECP256K1_PUBKEY_BYTES_LEN];
Expand Down
4 changes: 2 additions & 2 deletions src/health_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
// limitations under the License.

use cita_cloud_proto::health_check::{
health_check_response::ServingStatus, health_server::Health, HealthCheckRequest,
HealthCheckResponse,
HealthCheckRequest, HealthCheckResponse, health_check_response::ServingStatus,
health_server::Health,
};
use tonic::{Request, Response, Status};

Expand Down
12 changes: 5 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,19 @@ use cita_cloud_proto::blockchain::RawTransactions;
use cita_cloud_proto::common::StatusCode;
use cita_cloud_proto::common::{Empty, Hash, HashResponse};
use cita_cloud_proto::crypto::{
crypto_service_server::CryptoService, crypto_service_server::CryptoServiceServer,
GetCryptoInfoResponse, HashDataRequest, RecoverSignatureRequest, RecoverSignatureResponse,
SignMessageRequest, SignMessageResponse, VerifyDataHashRequest,
crypto_service_server::CryptoService, crypto_service_server::CryptoServiceServer,
};
use cita_cloud_proto::health_check::health_server::HealthServer;
use cita_cloud_proto::status_code::StatusCodeEnum;
use clap::Parser;
use cloud_util::metrics::{run_metrics_exporter, MiddlewareLayer};
use cloud_util::metrics::{MiddlewareLayer, run_metrics_exporter};
use config::CryptoConfig;
use eth::{ADDR_BYTES_LEN, SECP256K1_SIGNATURE_BYTES_LEN};
use health_check::HealthCheckServer;
use std::net::AddrParseError;
use tonic::{transport::Server, Request, Response, Status};
use tonic::{Request, Response, Status, transport::Server};
use util::clap_about;

/// crypto service
Expand Down Expand Up @@ -69,8 +69,6 @@ struct RunOpts {
}

fn main() {
::std::env::set_var("RUST_BACKTRACE", "full");

let opts: Opts = Opts::parse();

// You can handle information about subcommands by requesting their matches by name
Expand Down Expand Up @@ -253,10 +251,10 @@ async fn run(opts: RunOpts) -> Result<(), StatusCodeEnum> {
};

info!("start crypto_eth grpc server");
if layer.is_some() {
if let Some(layer) = layer {
info!("metrics on");
Server::builder()
.layer(layer.unwrap())
.layer(layer)
.add_service(CryptoServiceServer::new(CryptoServer::new(Crypto::new(
&opts.private_key_path,
))))
Expand Down
Loading