Skip to content

Commit fa1002d

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 fa1002d

40 files changed

Lines changed: 1998 additions & 11 deletions

.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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,32 @@ 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: cargo test -p vey-crypto-mb
152+
- name: Cargo test vey-keyless
153+
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: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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+
/// Enough for a P-521 ECDSA DER signature (`SEQUENCE` of two `INTEGER`s).
21+
pub const MAX_DER_LEN: usize = 160;
22+
23+
#[derive(Clone, Copy)]
24+
pub enum Curve {
25+
P256,
26+
P384,
27+
P521,
28+
}
29+
30+
impl Curve {
31+
pub fn field_len(self) -> usize {
32+
match self {
33+
Curve::P256 => 32,
34+
Curve::P384 => 48,
35+
Curve::P521 => 66,
36+
}
37+
}
38+
}
39+
40+
pub struct EcdsaSlot {
41+
field_len: usize,
42+
msg: [u8; MAX_FIELD_LEN],
43+
sign_r: [u8; MAX_FIELD_LEN],
44+
sign_s: [u8; MAX_FIELD_LEN],
45+
eph: BigNum,
46+
/// Owned copy of the EC private scalar; must outlive `sign_mb8`.
47+
reg: BigNum,
48+
}
49+
50+
impl EcdsaSlot {
51+
pub fn prepare(curve: Curve, key: &PKeyRef<Private>, digest: &[u8]) -> Option<Self> {
52+
let field_len = curve.field_len();
53+
let ec = key.ec_key().ok()?;
54+
let mut order = BigNum::new().ok()?;
55+
let mut ctx = BigNumContext::new().ok()?;
56+
ec.group().order(&mut order, &mut ctx).ok()?;
57+
// `ec_key()` returns an owned key; copy the scalar so it stays valid
58+
// after `ec` is dropped at the end of this function.
59+
let reg = ec.private_key().to_owned().ok()?;
60+
61+
let mut msg = [0u8; MAX_FIELD_LEN];
62+
if digest.len() >= field_len {
63+
msg[..field_len].copy_from_slice(&digest[..field_len]);
64+
} else {
65+
msg[field_len - digest.len()..field_len].copy_from_slice(digest);
66+
}
67+
68+
let eph = priv_rand_range(&order)?;
69+
Some(EcdsaSlot {
70+
field_len,
71+
msg,
72+
sign_r: [0u8; MAX_FIELD_LEN],
73+
sign_s: [0u8; MAX_FIELD_LEN],
74+
eph,
75+
reg,
76+
})
77+
}
78+
79+
pub fn field_len(&self) -> usize {
80+
self.field_len
81+
}
82+
83+
pub fn sign_r(&self) -> &[u8] {
84+
&self.sign_r[..self.field_len]
85+
}
86+
87+
pub fn sign_s(&self) -> &[u8] {
88+
&self.sign_s[..self.field_len]
89+
}
90+
91+
/// Encode the signature as DER into `out`. Returns the number of bytes written.
92+
pub fn write_der_signature(&self, out: &mut [u8]) -> Option<usize> {
93+
let r_bn = BigNum::from_slice(self.sign_r()).ok()?;
94+
let s_bn = BigNum::from_slice(self.sign_s()).ok()?;
95+
let sig = EcdsaSig::from_private_components(r_bn, s_bn).ok()?;
96+
unsafe {
97+
let needed = openssl_sys::i2d_ECDSA_SIG(sig.as_ptr(), ptr::null_mut());
98+
if needed <= 0 || needed as usize > out.len() {
99+
return None;
100+
}
101+
let mut p = out.as_mut_ptr();
102+
let written = openssl_sys::i2d_ECDSA_SIG(sig.as_ptr(), &mut p);
103+
if written != needed {
104+
return None;
105+
}
106+
Some(written as usize)
107+
}
108+
}
109+
}
110+
111+
/// Sign up to [`BATCH_SIZE`] slots. Returns per-lane status; only the first
112+
/// `slots.len().min(BATCH_SIZE)` entries are meaningful.
113+
pub fn sign_mb8(curve: Curve, slots: &mut [EcdsaSlot]) -> [MbStatus; BATCH_SIZE] {
114+
let n = slots.len().min(BATCH_SIZE);
115+
let mut statuses = [MBX_STATUS_OK; BATCH_SIZE];
116+
if n == 0 {
117+
return statuses;
118+
}
119+
120+
let mut pa_sign_r = [ptr::null_mut(); BATCH_SIZE];
121+
let mut pa_sign_s = [ptr::null_mut(); BATCH_SIZE];
122+
let mut pa_msg = [ptr::null(); BATCH_SIZE];
123+
let mut pa_eph = [ptr::null(); BATCH_SIZE];
124+
let mut pa_reg = [ptr::null(); BATCH_SIZE];
125+
126+
for (i, slot) in slots.iter_mut().take(n).enumerate() {
127+
pa_sign_r[i] = slot.sign_r.as_mut_ptr();
128+
pa_sign_s[i] = slot.sign_s.as_mut_ptr();
129+
pa_msg[i] = slot.msg.as_ptr();
130+
pa_eph[i] = slot.eph.as_ptr();
131+
pa_reg[i] = slot.reg.as_ptr();
132+
}
133+
134+
let status = unsafe {
135+
match curve {
136+
Curve::P256 => ffi::mbx_nistp256_ecdsa_sign_ssl_mb8(
137+
pa_sign_r.as_ptr(),
138+
pa_sign_s.as_ptr(),
139+
pa_msg.as_ptr(),
140+
pa_eph.as_ptr(),
141+
pa_reg.as_ptr(),
142+
ptr::null_mut(),
143+
),
144+
Curve::P384 => ffi::mbx_nistp384_ecdsa_sign_ssl_mb8(
145+
pa_sign_r.as_ptr(),
146+
pa_sign_s.as_ptr(),
147+
pa_msg.as_ptr(),
148+
pa_eph.as_ptr(),
149+
pa_reg.as_ptr(),
150+
ptr::null_mut(),
151+
),
152+
Curve::P521 => ffi::mbx_nistp521_ecdsa_sign_ssl_mb8(
153+
pa_sign_r.as_ptr(),
154+
pa_sign_s.as_ptr(),
155+
pa_msg.as_ptr(),
156+
pa_eph.as_ptr(),
157+
pa_reg.as_ptr(),
158+
ptr::null_mut(),
159+
),
160+
}
161+
};
162+
for (i, sts) in statuses.iter_mut().take(n).enumerate() {
163+
*sts = ffi::mbx_get_sts(status, i);
164+
}
165+
statuses
166+
}
167+
168+
fn priv_rand_range(order: &openssl::bn::BigNumRef) -> Option<BigNum> {
169+
let eph = BigNum::new().ok()?;
170+
for _ in 0..64 {
171+
let rc = unsafe { openssl_ffi::BN_priv_rand_range(eph.as_ptr(), order.as_ptr()) };
172+
let is_zero = unsafe { openssl_ffi::BN_is_zero(eph.as_ptr()) == 1 };
173+
if rc == 1 && !is_zero {
174+
return Some(eph);
175+
}
176+
}
177+
None
178+
}

0 commit comments

Comments
 (0)