Skip to content

Commit db26c24

Browse files
zh-jqcursoragent
andcommitted
vey-keyless: add Intel crypto_mb backend
Add vey-crypto-mb and an optional batched RSA/ECDSA/Ed25519 path with CI, coverage, and docs. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 30b57f6 commit db26c24

41 files changed

Lines changed: 1982 additions & 11 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/codecov.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
run: |
4747
sudo apt-get update
4848
sudo apt-get install capnproto libjemalloc-dev libmimalloc-dev libc-ares-dev libssl-dev libpython3-dev liblua5.4-dev
49-
sudo apt-get install libbpf-dev
49+
sudo apt-get install libbpf-dev libcrypto-mb-dev
5050
- name: Install binutils
5151
run: |
5252
cargo install cargo-binutils@0.3.6
@@ -151,7 +151,7 @@ jobs:
151151
- name: Install build dependencies
152152
run: |
153153
sudo apt-get update
154-
sudo apt-get install capnproto libmimalloc-dev libssl-dev
154+
sudo apt-get install capnproto libmimalloc-dev libssl-dev libcrypto-mb-dev pkg-config
155155
- name: Install binutils
156156
run: |
157157
cargo install cargo-binutils@0.3.6

.github/workflows/linux-basic.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
- name: Cargo build
5959
run: cargo build --features jemalloc,ebpf,lua54
6060
- name: Cargo test
61-
run: cargo test --workspace --features jemalloc,lua54
61+
run: cargo test --workspace --features jemalloc,lua54 --exclude vey-crypto-mb
6262
clippy:
6363
name: Clippy
6464
runs-on: ubuntu-26.04
@@ -80,4 +80,4 @@ jobs:
8080
- name: Cargo clean
8181
run: cargo clean
8282
- name: Cargo clippy
83-
run: cargo clippy --tests --workspace --features jemalloc,ebpf,lua54 -- --deny warnings
83+
run: cargo clippy --tests --workspace --features jemalloc,ebpf,lua54 --exclude vey-crypto-mb -- --deny warnings

.github/workflows/linux-extra.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,34 @@ jobs:
122122
run: cargo build --features openssl-async-job -p ${{ matrix.component }}
123123
- name: Cargo clippy
124124
run: cargo clippy --features openssl-async-job -p ${{ matrix.component }} -- --deny warnings
125+
crypto-mb:
126+
name: Build with crypto_mb (x86_64)
127+
runs-on: ubuntu-26.04
128+
steps:
129+
- name: Checkout sources
130+
uses: actions/checkout@v7
131+
with:
132+
submodules: true
133+
- name: Install stable toolchain
134+
uses: actions-rust-lang/setup-rust-toolchain@v1
135+
with:
136+
toolchain: stable
137+
components: clippy
138+
- name: Install dependencies
139+
run: |
140+
sudo apt-get update
141+
sudo apt-get install capnproto libssl-dev libcrypto-mb-dev pkg-config
142+
- name: Cargo clean
143+
run: cargo clean
144+
- name: Cargo build
145+
run: cargo build -p vey-crypto-mb -p vey-keyless --features crypto-mb
146+
- name: Cargo clippy
147+
run: |
148+
cargo clippy -p vey-crypto-mb -- --deny warnings
149+
cargo clippy -p vey-keyless --features crypto-mb -- --deny warnings
150+
- name: Cargo test
151+
run: |
152+
lscpu
153+
cargo test -p vey-crypto-mb
154+
- name: Cargo test vey-keyless
155+
run: cargo test -p vey-keyless --features crypto-mb

.github/workflows/macos-basic.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,4 @@ jobs:
5050
- name: Cargo clippy
5151
run: cargo clippy --tests --features jemalloc,lua55 -- --deny warnings
5252
- name: Cargo test
53-
run: cargo test --workspace --features jemalloc,lua55 --exclude vey-journal --exclude vey-reuseport
53+
run: cargo test --workspace --features jemalloc,lua55 --exclude vey-journal --exclude vey-reuseport --exclude vey-crypto-mb

.github/workflows/windows-basic.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,4 @@ jobs:
5454
- name: Cargo clippy
5555
run: cargo clippy --no-default-features --features $env:WIN_FEATURES --tests -- --deny warnings
5656
- name: Cargo test
57-
run: cargo test --no-default-features --features $env:WIN_FEATURES --workspace --exclude vey-journal --exclude vey-reuseport
57+
run: cargo test --no-default-features --features $env:WIN_FEATURES --workspace --exclude vey-journal --exclude vey-reuseport --exclude vey-crypto-mb

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ members = [
2323
"lib/vey-clap",
2424
"lib/vey-codec",
2525
"lib/vey-compat",
26+
"lib/vey-crypto-mb",
2627
"lib/vey-ctl",
2728
"lib/vey-daemon",
2829
"lib/vey-datetime",
@@ -212,6 +213,7 @@ vey-cert-agent = { version = "0.3", path = "lib/vey-cert-agent" }
212213
vey-clap = { version = "0.2", path = "lib/vey-clap" }
213214
vey-codec = { version = "0.1", path = "lib/vey-codec" }
214215
vey-compat = { version = "0.2", path = "lib/vey-compat" }
216+
vey-crypto-mb = { version = "0.1", path = "lib/vey-crypto-mb" }
215217
vey-ctl = { version = "0.2", path = "lib/vey-ctl" }
216218
vey-daemon = { version = "0.4", path = "lib/vey-daemon" }
217219
vey-datetime = { version = "0.2", path = "lib/vey-datetime" }

lib/vey-crypto-mb/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "vey-crypto-mb"
3+
version = "0.1.0"
4+
license.workspace = true
5+
edition.workspace = true
6+
rust-version.workspace = true
7+
description = "Intel crypto_mb helpers for multi-buffer asymmetric crypto"
8+
9+
[dependencies]
10+
libc.workspace = true
11+
openssl.workspace = true
12+
openssl-sys.workspace = true
13+
14+
[build-dependencies]
15+
pkg-config = "0.3"

lib/vey-crypto-mb/build.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* SPDX-FileCopyrightText: 2026 VEY-OSS Developers.
4+
*/
5+
6+
fn main() {
7+
let lib = pkg_config::Config::new()
8+
.probe("crypto-mb")
9+
.expect("crypto-mb not found; install Intel crypto_mb and pkg-config metadata");
10+
11+
for path in &lib.include_paths {
12+
println!("cargo:rerun-if-changed={}", path.display());
13+
}
14+
}

lib/vey-crypto-mb/src/ecdsa.rs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* SPDX-FileCopyrightText: 2026 VEY-OSS Developers.
4+
*/
5+
6+
use std::ptr;
7+
8+
use openssl::bn::{BigNum, BigNumContext};
9+
use openssl::ecdsa::EcdsaSig;
10+
use openssl::foreign_types::{ForeignType, ForeignTypeRef};
11+
use openssl::pkey::{PKeyRef, Private};
12+
13+
use crate::MbStatus;
14+
use crate::ffi::{self, BATCH_SIZE, MBX_STATUS_OK};
15+
use crate::openssl_ffi;
16+
17+
/// Largest NIST curve field size we support (P-521).
18+
pub const MAX_FIELD_LEN: usize = 66;
19+
20+
#[derive(Clone, Copy)]
21+
pub enum Curve {
22+
P256,
23+
P384,
24+
P521,
25+
}
26+
27+
impl Curve {
28+
pub fn field_len(self) -> usize {
29+
match self {
30+
Curve::P256 => 32,
31+
Curve::P384 => 48,
32+
Curve::P521 => 66,
33+
}
34+
}
35+
}
36+
37+
pub struct EcdsaSlot {
38+
field_len: usize,
39+
msg: [u8; MAX_FIELD_LEN],
40+
sign_r: [u8; MAX_FIELD_LEN],
41+
sign_s: [u8; MAX_FIELD_LEN],
42+
eph: BigNum,
43+
/// Owned copy of the EC private scalar; must outlive `sign_mb8`.
44+
reg: BigNum,
45+
}
46+
47+
impl EcdsaSlot {
48+
pub fn prepare(curve: Curve, key: &PKeyRef<Private>, digest: &[u8]) -> Option<Self> {
49+
let field_len = curve.field_len();
50+
let ec = key.ec_key().ok()?;
51+
let mut order = BigNum::new().ok()?;
52+
let mut ctx = BigNumContext::new().ok()?;
53+
ec.group().order(&mut order, &mut ctx).ok()?;
54+
// `ec_key()` returns an owned key; copy the scalar so it stays valid
55+
// after `ec` is dropped at the end of this function.
56+
let reg = ec.private_key().to_owned().ok()?;
57+
58+
let mut msg = [0u8; MAX_FIELD_LEN];
59+
if digest.len() >= field_len {
60+
msg[..field_len].copy_from_slice(&digest[..field_len]);
61+
} else {
62+
msg[field_len - digest.len()..field_len].copy_from_slice(digest);
63+
}
64+
65+
let eph = priv_rand_range(&order)?;
66+
Some(EcdsaSlot {
67+
field_len,
68+
msg,
69+
sign_r: [0u8; MAX_FIELD_LEN],
70+
sign_s: [0u8; MAX_FIELD_LEN],
71+
eph,
72+
reg,
73+
})
74+
}
75+
76+
pub fn field_len(&self) -> usize {
77+
self.field_len
78+
}
79+
80+
pub fn sign_r(&self) -> &[u8] {
81+
&self.sign_r[..self.field_len]
82+
}
83+
84+
pub fn sign_s(&self) -> &[u8] {
85+
&self.sign_s[..self.field_len]
86+
}
87+
88+
/// Encode the signature as DER.
89+
pub fn der_signature(&self) -> Option<Vec<u8>> {
90+
let r_bn = BigNum::from_slice(self.sign_r()).ok()?;
91+
let s_bn = BigNum::from_slice(self.sign_s()).ok()?;
92+
let sig = EcdsaSig::from_private_components(r_bn, s_bn).ok()?;
93+
sig.to_der().ok()
94+
}
95+
}
96+
97+
/// Sign up to [`BATCH_SIZE`] slots. Returns per-lane status; only the first
98+
/// `slots.len().min(BATCH_SIZE)` entries are meaningful.
99+
pub fn sign_mb8(curve: Curve, slots: &mut [EcdsaSlot]) -> [MbStatus; BATCH_SIZE] {
100+
let n = slots.len().min(BATCH_SIZE);
101+
let mut statuses = [MBX_STATUS_OK; BATCH_SIZE];
102+
if n == 0 {
103+
return statuses;
104+
}
105+
106+
let mut pa_sign_r = [ptr::null_mut(); BATCH_SIZE];
107+
let mut pa_sign_s = [ptr::null_mut(); BATCH_SIZE];
108+
let mut pa_msg = [ptr::null(); BATCH_SIZE];
109+
let mut pa_eph = [ptr::null(); BATCH_SIZE];
110+
let mut pa_reg = [ptr::null(); BATCH_SIZE];
111+
112+
for (i, slot) in slots.iter_mut().take(n).enumerate() {
113+
pa_sign_r[i] = slot.sign_r.as_mut_ptr();
114+
pa_sign_s[i] = slot.sign_s.as_mut_ptr();
115+
pa_msg[i] = slot.msg.as_ptr();
116+
pa_eph[i] = slot.eph.as_ptr();
117+
pa_reg[i] = slot.reg.as_ptr();
118+
}
119+
120+
let status = unsafe {
121+
match curve {
122+
Curve::P256 => ffi::mbx_nistp256_ecdsa_sign_ssl_mb8(
123+
pa_sign_r.as_ptr(),
124+
pa_sign_s.as_ptr(),
125+
pa_msg.as_ptr(),
126+
pa_eph.as_ptr(),
127+
pa_reg.as_ptr(),
128+
ptr::null_mut(),
129+
),
130+
Curve::P384 => ffi::mbx_nistp384_ecdsa_sign_ssl_mb8(
131+
pa_sign_r.as_ptr(),
132+
pa_sign_s.as_ptr(),
133+
pa_msg.as_ptr(),
134+
pa_eph.as_ptr(),
135+
pa_reg.as_ptr(),
136+
ptr::null_mut(),
137+
),
138+
Curve::P521 => ffi::mbx_nistp521_ecdsa_sign_ssl_mb8(
139+
pa_sign_r.as_ptr(),
140+
pa_sign_s.as_ptr(),
141+
pa_msg.as_ptr(),
142+
pa_eph.as_ptr(),
143+
pa_reg.as_ptr(),
144+
ptr::null_mut(),
145+
),
146+
}
147+
};
148+
for (i, sts) in statuses.iter_mut().take(n).enumerate() {
149+
*sts = ffi::mbx_get_sts(status, i);
150+
}
151+
statuses
152+
}
153+
154+
fn priv_rand_range(order: &openssl::bn::BigNumRef) -> Option<BigNum> {
155+
let eph = BigNum::new().ok()?;
156+
for _ in 0..64 {
157+
let rc = unsafe { openssl_ffi::BN_priv_rand_range(eph.as_ptr(), order.as_ptr()) };
158+
let is_zero = unsafe { openssl_ffi::BN_is_zero(eph.as_ptr()) == 1 };
159+
if rc == 1 && !is_zero {
160+
return Some(eph);
161+
}
162+
}
163+
None
164+
}

0 commit comments

Comments
 (0)