Skip to content

[cryptography] Accelerate secp256r1 verification and decompression #4767

Description

@patrick-ogrady

Improve P-256 (secp256r1) verification and point-decompression throughput, using Constantinople's WebAuthn integration as a concrete consumer. Compare optimized ordinary verification with hinted algebraic batching before choosing an implementation or signature format.

Research and measurements: 2026-09-14.

Proposed direction: expose Commonware's native AWS-LC verifier for Constantinople's exact raw WebAuthn payload for the immediate approximately 3.1x opportunity. For the next improvement, compare independent AVX-512 IFMA verification against browser-produced recovery/full-point signatures with algebraic batching. The scope includes browser-side signature changes; the existing 64-byte representation is not a constraint. Intel SIMD currently has the strongest direct evidence at 10.8 us per signature; hinted batching is a serious competing design, not a presumed winner. The measurements do not establish Ed25519 batch parity or a 10x end-to-end transaction improvement.

Commonware baseline: ecb6895. Constantinople baseline: 42d0b3a. These are the source revisions used for the investigation.

Measurements on c8a

One pinned physical core, CPU 2, on c8a.4xlarge / AMD EPYC 9R45 (Zen 5), Ubuntu 24.04. The guest exposes AVX512IFMA and AVX512VBMI2, and crypto_mb reports an available eight-lane P-256 backend. Rust 1.95.0, release builds, target-cpu=native. No concurrent compilation or benchmarking during timing.

Verification path Time per signature Scope
Constantinople-shaped cached RustCrypto P-256 0.14 110.09 us Core signature parse + SHA-256 + verification, distinct keys/messages, std feature
Same RustCrypto path with precomputed-tables 109.99 us Same fixtures and method; no meaningful gain
AWS-LC unparsed key, fixed64 signature 35.14 us SHA-256 + native key parsing + signature verification
AWS-LC retained parsed key, fixed64 signature 34.32 us Key construction excluded
Existing Commonware standard P-256 API 35.27 us Includes namespace framing and hashing
Existing Commonware recoverable P-256 API 117.16 us RustCrypto recovery followed by exact claimed-key comparison
Intel crypto_mb, eight distinct keys 10.806 us Amortized throughput; 86.445 us for all eight; digest and decoded keys supplied
OpenSSL native scalar control for crypto_mb 37.797 us Same already prepared inputs; eight sequential verifications
Intel crypto_mb, one active lane 89.328 us One-request latency with seven inactive lanes
Existing Ed25519 single API 16.081 us Ordinary single verification
Existing Ed25519 batch, 1000 distinct keys 5.7145 us Same message; 5.7145 ms per batch
New separate Curve25519 crate, 1000 distinct keys/messages 3.9184 us 3.9184 ms per batch; temporary compiler adjustment described below

The directly controlled native comparison is 3.50x throughput for eight-lane crypto_mb versus scalar OpenSSL. Relative to the existing AWS-LC and Constantinople-shaped measurements, the raw SIMD number is approximately 3.25x and 10.2x faster respectively, but those are comparisons between different scopes and working sets. Hashing, key preparation, cache lookup, WebAuthn JSON checks, batching overhead, and scheduling must be measured in the real integration before claiming a transaction speedup.

For a sense of the remaining gap, 10.806 us is about 1.89x the existing Ed25519 batch cost and 2.76x the new Curve25519 batch cost. These are throughput comparisons, not equal-work latency comparisons. SIMD improves ordinary ECDSA substantially but does not demonstrate Ed25519 batch parity.

The new Curve25519 crate is separate from cryptography::ed25519 in this checkout. Its unmodified x86 assembly failed with both Rust 1.95.0 and 1.98.1 because two discarded ZMM output operands lacked explicit vector types. The experimental remote checkout assigned __m512i types without changing the instruction sequence, and the benchmark asserted that each valid batch succeeded. The successful measurement used Rust 1.95.0. This is an exploratory result, not a validated production fix. The exact temporary diff is included in the reproduction section below.

The Constantinople integration is the first opportunity

Constantinople auth.rs calls the cached RustCrypto VerifyingKey directly. It does not call Commonware's accelerated standard verifier. Its public-key cache stores that RustCrypto key, and its P-256 batch already distributes independent checks using Strategy::fold. Adding ordinary thread parallelism would duplicate a capability already present.

Commonware standard.rs already uses AWS-LC on x86_64 and AArch64. The native backend has mature scalar assembly, public double-scalar multiplication, and projective-coordinate signature comparison. Reimplementing those optimizations would not address the consumer's actual bypass.

The least invasive next change is an explicit Commonware raw-payload verifier, or an exactly specified prehashed equivalent, and a Constantinople call-site migration. Preserve:

  • The digest SHA256(authenticator_data || SHA256(original client_data_json bytes)).
  • The existing WebAuthn type, challenge and user-verification checks.
  • The low-S policy and validation of decoded public points; the current compressed public keys can remain in place. The signature representation may change to support batching.
  • Existing bounded P-256 key caching and runtime-agnostic parallel scheduling.

An empty Commonware namespace is not an unnamespaced WebAuthn payload: the ordinary verifier still adds framing. The raw entry point must explicitly bypass that framing, and a prehashed entry point must not hash the digest again.

AWS-LC's retained ParsedPublicKey can live with the validated cached key. In the controlled Rust harness it saved about 2.4% versus UnparsedPublicKey. Avoiding fixed64-to-DER conversion saved another approximately 0.3 us. These are useful small refinements; the initial 3.1x opportunity comes from changing the backend actually used by the consumer.

SIMD batching that preserves the current signatures

Intel's eight-lane ECDSA API accepts ordinary P-256 signature components, message representatives, and public points, and returns a result for each lane. Its implementation evaluates the usual verification equation independently in each lane. No recovery ID, signer change, or probabilistic combined verification result is needed.

An IFMA implementation arranges arithmetic from eight separate verifications across vector lanes. In a typical radix-2^52 representation, five limbs hold each 256-bit field value; IFMA instructions accelerate the products and accumulation. This is a concrete path supported by the native benchmark, rather than an assumed eightfold gain from having eight lanes.

The SIMD candidate needs a bulk standard-verification API with runtime dispatch, an AWS-LC fallback, and an eight-lane backend. Constantinople can retain the existing metadata checks, compute the exact WebAuthn digests, then schedule groups of eight ready P-256 inputs through its existing Strategy. The owning batch API can return one boolean if the caller only needs all-valid; internally, lane results make error isolation straightforward.

The measured 86 us group latency and 89 us one-active-lane cost make a scalar fallback important. Tail handling and the dispatch threshold should be chosen from a production-shaped benchmark. Do not introduce waiting solely to fill a group when that would harm latency; transaction/block batches already provide ready work.

Current Intel 512-bit dispatch checks BMI2, AVX512F/DQ/BW/IFMA/VBMI2 and OS register-state support. Its newer 256-bit path requires the separate AVX-IFMA feature in addition to AVX2; ordinary AVX2 is not sufficient for that backend. Pinned dispatcher.

Use crypto_mb as a reference implementation and benchmark comparison. A Commonware-owned implementation fits the workspace's preference to own core algorithms but is a substantial cryptographic engineering task: field and scalar arithmetic, inversion, joint multiplication, input transposition, runtime dispatch, and differential validation. Before adoption, test equivalence with the public standard-verification contract, including scalar ranges, low-S, point validity, every lane position and tail size, and rare projective-coordinate comparison cases. The present experiment only validates its benchmark fixtures and altered-message rejection; it is not a cryptographic audit.

VROOM and other published work

VROOM, USENIX Security 2026 uses residue-number-system arithmetic to distribute a single large modular computation across vector lanes. Its published abstract reports 4.0x RSA-4096 verification and 4.05x BLS verification improvements over its chosen native baselines. Those are not P-256 ECDSA results.

The released artifact demonstrates AVX-512 IFMA/RNS arithmetic and BLS12-381 implementations, with reference timings on Intel Sapphire Rapids. P-256 would need its own parameterization, conversions, field/scalar operations and end-to-end ECDSA evaluation. P-256's relatively small, specially structured field modulus and existing optimized scalar backend make the benefit uncertain. The current evidence makes VROOM a candidate for a later arithmetic prototype, especially if single-verification latency becomes the limiting requirement, but the measured independent-lane approach is the better first SIMD project.

Fastcrypto's ICPE 2024 paper, section 3.2.1, reports a 5.5x P-256 improvement over its former implementation using Arkworks arithmetic and tuned multiplication/precomputation. That ratio is not against today's AWS-LC or this P-256 0.14 build. Its generator-table measurements are useful design evidence, not an additional multiplier to apply to the c8a results.

Simply enabling p256 0.14's precomputed-tables feature did not help here. Its table backend overrides standalone generator multiplication, while ECDSA calls the joint mul_by_generator_and_mul_add_vartime method and inherits the general joint implementation. Exploiting that table for verification would require a different multiplication path and a benchmark of the resulting tradeoff.

The older Drucker/Gueron P-256 verification paper describes variable-time verification, projective x-coordinate comparison, and large per-key tables. Its published baseline is from 2018-era implementations. AWS-LC already contains the first two techniques. Large per-account tables need evidence of sufficient reuse and acceptable cache footprint; do not extrapolate small-working-set results to Constantinople's account cache.

Algebraic batching

Batch Verification of Modified ECDSA Signatures, April 2026 implements recoverable and full-nonce-point P-256 variants. Table 2 reports 40.46% and 53.87% time reductions at batch size 512 with HSS-rand: approximately 1.68x and 2.17x throughput. Table 4's Bos-Coster MSM raises these to approximately 2.02x and 2.76x. At batch 32 the corresponding Bos-Coster gains are about 1.72x and 2.21x. Measurements use OpenSSL 3.5.0, a Core i7-1360P performance core fixed at 2.2 GHz, GCC 11.4.0, and distinct keys/messages. These are different hardware, algorithms and baseline from the c8a experiment. They do not establish that hinted batching beats Intel SIMD.

Ordinary ECDSA transmits r and s, omitting the nonce point R. Its verification equation is sR = zG + rQ together with x(R) mod n = r. This MSM-based aggregate method needs the particular R for every signature. The standard signature's x-coordinate reduction loses the information that distinguishes candidate points. Computing R locally from the signature, message and claimed key costs essentially the individual verification one hoped to avoid; moving that computation into the browser makes it a one-time client cost instead of repeated validator work.

Commonware's 65-byte recoverable type already has the recovery ID needed to reconstruct R. For that type, a promising design is to validate each item, batch-invert the nonzero s scalars, compute u=z/s and v=r/s, and test the normalized equation:

sum_i a_i R_i - (sum_i a_i u_i) G - sum_i (a_i v_i) Q_i = identity

Fresh independent 128-bit random coefficients are chosen after the complete batch is fixed. This leaves short coefficients on the R terms, full-width coefficients on the Q terms, and one generator term. Repeated claimed keys can be grouped after weighting. That coefficient structure resembles Ed25519 batching and is a credible target for an optimized P-256 multiscalar multiplication implementation. Batch inversion and shared-key grouping are additional opportunities; no c8a measurements establish their benefit yet.

The aggregate equation does not replace point validation, scalar ranges, low-S enforcement, the claimed public-key binding, or the individual x(R) mod n checks. Recovery must handle both x=r and the rare legal x=r+n branch without overflow, preserve recovery-ID parity, and reject invalid points. A nonzero fixed residual is accepted with probability at most 2^-128 for a uniform independent 128-bit coefficient domain; predictable or reused coefficients do not have that guarantee. Failure localization requires splitting the batch or individual verification. The more complex HSS correlated-coefficient construction needs its own contract and soundness review before use.

Constantinople's browser can produce the hint after the authenticator returns its signature. Parse and normalize the existing signature to low S first, compute the exact WebAuthn digest z, then derive R=s^-1(zG+rQ) from public information. Check the resulting point and emit the desired representation. Computing R after normalization automatically selects the point consistent with the normalized s; computing it before normalization would require negating R when s is negated. No private key or nonce extraction is needed. The same work can be done by an untrusted proposer and amortized across downstream validators.

The concrete browser insertion point is signWithPasskey, immediately after its existing DER conversion and low-S normalization. The assertion supplies the original JSON/authenticator bytes, and the wallet profile supplies the public key. The transaction encoder hashes the transaction body before appending the signature, so changing the signature representation need not alter the body challenge. The ECDSA digest is nevertheless the WebAuthn digest above, not just that challenge. These hashing and signature-encoding roles follow the WebAuthn specification.

The explorer currently has no P-256 point-arithmetic dependency or exposed browser point-recovery component. Hint generation needs a small JS or WASM arithmetic component; reusing workspace Rust arithmetic through WASM is one candidate. Native changes would be concentrated in the signature codec and Secp256r1Item/batch path in auth.rs, including its encoded-size bounds. The existing metadata checks still precede aggregation. This establishes integration feasibility, not browser performance or a winning validator design.

Candidate Fixed signature bytes Validator tradeoff
Standard (r, s), independent SIMD 64 Measured 10.8 us/signature at eight occupied lanes; no algebraic aggregation
Recovery ID + r s
Full R=(x,y) plus s 96 Enables aggregation without R decompression; derives r=x(R) mod n; adds 32 bytes over standard

The 65-byte candidate is recid || r || s. The 96-byte candidate is x(R) || y(R) || s, replacing r with the full point; each coordinate/scalar occupies 32 big-endian bytes. The 96-byte format assumes an explicitly specified fixed pair of coordinates; standard uncompressed SEC1 adds a prefix and makes it 97 bytes. A compressed R plus s is 65 bytes and retains the square-root/decompression cost. There is no need to send both full R and a redundant r. Each hinted form must be validated by the verifier; supplying a point is not evidence of authenticity by itself.

Full R is a useful candidate when minimizing validator CPU, while a recovery hint is attractive when network/storage size matters. Both incur client point arithmetic and larger encoded transactions. The net winner depends on real batch sizes, repeated-account frequency, cache behavior, browser preparation cost, and network cost. Hinted batching still needs a fast P-256 MSM implementation; using the existing generic arithmetic may lose to Intel's independent-lane routine.

The next comparative benchmark should place AWS-LC, Intel MB8, normalized recoverable batching and normalized full-R batching on the same c8a core. Include complete decode/hash/point checks, browser preparation measured separately, sizes 1/8/32/128/512/1000 and nonmultiples of eight, distinct and repeated keys, and unsuccessful batches. Use the measured MB8 result as the target before choosing a new signature format. Combining IFMA arithmetic with an aggregate MSM is a separate implementation to test: published batching gains and the 3.5x SIMD gain are not multiplicative predictions.

The paper's OpenSSL artifact offers a starting comparison, but its existing in-memory benchmarks do not account for complete serialized-point validation and its conventional batch loop inverts s individually. A faithful practical candidate should include Montgomery batch inversion and the serialized validation boundary. HSS/Bos-Coster can be measured as an additional research reference; the simpler independently randomized equation has a much clearer soundness argument for a first implementation. The full comparison remains unmeasured.

SIMD point decompression

P-256 public-key and nonce-point decompression can use independent AVX-512 IFMA lanes too. Given x and a parity bit, compute t=x^3-3x+b, then y=t^((p+1)/4), since the P-256 field prime is 3 modulo 4. The fixed exponent gives every lane the same sequence of squarings and multiplications, followed by root verification and parity selection. RustCrypto's P-256 square-root implementation provides the scalar addition-chain reference. Canonical coordinate/prefix checks and individual invalid-point results still belong to the decoder.

This is the same parallelization pattern as Commonware's eight-lane Curve25519 decompressor, with different field arithmetic and exponent. The Intel P-256 public API exposes no compressed-point batch decoder; its verifier receives coordinates. A P-256 decompression kernel would need to be implemented over the vector field backend. The measured 10.8 us native verification result excludes it.

In Constantinople, batch unique P-256 public-key cache misses before inserting decoded keys into the existing cache. Repeated cached keys already avoid decompression. For recoverable signatures, the same square-root kernel can process every nonce R after exact recovery-ID reconstruction, including the possible x=r+n branch. Compact-hint benchmarks should include this optimization when compared with full-R signatures: vectorized decompression could narrow full-R's CPU advantage while retaining 65-byte signatures. P-256 SIMD decompression remains unmeasured.

Experimental method and artifacts

The Rust microbenchmark uses 256 deterministic, low-S fixtures with 32-byte messages, same-key and distinct-key cases, and five roughly one-second samples. Key generation and retained-key preparation are excluded; native key construction is measured separately. The Constantinople-shaped arm reproduces the core cached-key verification call, not its complete WebAuthn wrapper. Both std variants were checked to match the consumer's actual feature resolution. Existing Criterion benches used 30 samples, 1 second warmup and 4 seconds requested measurement time.

The native harness uses Intel crypto_mb commit 7d5324757273ba1d7c9b425619e63ee8b4a9cb5b, NASM 3.01, GCC 13, and system OpenSSL 3.0.13. It cycles eight fixed public keys/message representatives with signatures generated and normalized outside timing, compares equivalent raw native inputs, and checks valid and altered-message results. Each result is the median of five samples of 4000 calls. The one-key case repeats one signature/message; it is not a many-message account-cache benchmark.

Intel's current declared OpenSSL minimum is newer than Ubuntu 24.04's library. The experiment used a versionless CMake discovery option; the native arithmetic kernels compiled and the harness checks passed. This is an experimental build combination and not a supported production integration recommendation.

Work to evaluate

  • Expose the exact raw/prehashed WebAuthn verification contract through Commonware's native backend and compare the integrated Constantinople path.
  • Prototype independent eight-lane P-256 verification with exact CPU/OS feature dispatch and a scalar path for unsupported CPUs and underfilled batches.
  • Prototype SIMD point decompression for unique public-key cache misses and recoverable signature nonce points.
  • Compare 65-byte recoverable signatures, including SIMD decompression, with 96/97-byte full-point signatures using normalized independent randomizers, batch inversion, and repeated-key grouping.
  • Measure browser hint generation separately; preserve the authenticator-signed digest and low-S/point/scalar validation through the complete browser-to-validator path.
  • Run a same-host comparison with complete parsing, hashing, point validation and failure handling across small/large/tail batches and distinct/repeated accounts. Evaluate both isolated kernels and the production-shaped cache/scheduler path.
  • Choose the implementation and signature representation from measured CPU, bandwidth, latency, portability and maintenance costs. Treat VROOM and HSS variants as additional research candidates.

Use the workspace's runtime-agnostic Strategy abstractions and existing public API/codec conventions. Differentially check scalar and SIMD acceptance, every lane/tail position, canonical point/scalar ranges, the rare r+n recovery branch, low-S parity behavior, and valid/invalid batches. Benchmark checks below are not a substitute for that validation.

Reproduction appendix

The sources below are the standalone experimental harnesses. They do not modify the workspace. The temporary test instance was terminated after measurement.

The Rust harness uses 256 deterministic low-S fixtures. The original full comparison used p256's ecdsa feature without std; the final paired precomputed-table experiment enabled std in both variants to match Constantinople's resolved features. Both measured approximately 110 us. The source and lockfile below contain the final std configuration.

Save the three Rust files below in one directory, build with Rust 1.95.0 in release mode and RUSTFLAGS="-C target-cpu=native", then pin the executable to CPU 2 on an idle c8a host. Set P256_BENCH_ONLY_RUSTCRYPTO=1 to run only the RustCrypto arm; build with --features precomputed for the paired table variant. The native shell runner builds pinned crypto_mb and runs the C harness on CPU 2. Its OpenSSL version-discovery override is an experimental build configuration, as described above.

The Commonware Criterion runs used the pinned source revision, Rust 1.95.0, target-cpu=native, 30 samples, one-second warmup, four-second measurement time, and sequential execution. Relevant targets are commonware-cryptography's secp256r1/ed25519 benches and commonware-cryptography-curve25519's batch_verify bench. All fixture generation was outside timing.

Native SIMD benchmark source and runner

p256_mb_bench.c

#define _POSIX_C_SOURCE 200809L

#include <crypto_mb/cpu_features.h>
#include <crypto_mb/ec_nistp256.h>
#include <crypto_mb/status.h>
#include <openssl/bn.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/obj_mac.h>

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

enum {
    LANES = 8,
    WARMUP_GROUPS = 1000,
    DEFAULT_GROUPS = 20000,
    DEFAULT_REPETITIONS = 9
};

typedef struct {
    EC_KEY *key;
    ECDSA_SIG *sig;
    uint8_t digest[32];
    uint8_t r_be[32];
    uint8_t s_be[32];
    int64u x_le[4];
    int64u y_le[4];
} lane_input;

static volatile uint64_t sink;

static void fail(const char *what) {
    fprintf(stderr, "FAIL: %s\n", what);
    exit(1);
}

static uint64_t now_ns(void) {
    struct timespec ts;
    if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) fail("clock_gettime");
    return (uint64_t)ts.tv_sec * UINT64_C(1000000000) + (uint64_t)ts.tv_nsec;
}

static int bn_to_be_padded(uint8_t *out, size_t len, const BIGNUM *value) {
#ifdef OPENSSL_IS_AWSLC
    return BN_bn2bin_padded(out, len, value);
#else
    return BN_bn2binpad(value, out, (int)len) == (int)len;
#endif
}

static int bn_to_le_padded(uint8_t *out, size_t len, const BIGNUM *value) {
#ifdef OPENSSL_IS_AWSLC
    return BN_bn2le_padded(out, len, value);
#else
    return BN_bn2lebinpad(value, out, (int)len) == (int)len;
#endif
}

static void init_lane(lane_input *in, unsigned lane) {
    BIGNUM *priv = BN_new();
    BIGNUM *x = BN_new();
    BIGNUM *y = BN_new();
    EC_POINT *pub = NULL;
    const EC_GROUP *group;
    const BIGNUM *r;
    const BIGNUM *s;
    if (priv == NULL || x == NULL || y == NULL) fail("BN_new");

    in->key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
    if (in->key == NULL) fail("EC_KEY_new_by_curve_name");
    group = EC_KEY_get0_group(in->key);
    pub = EC_POINT_new(group);
    if (pub == NULL) fail("EC_POINT_new");

    /* Fixed private keys and digests make the input working set reproducible. */
    if (!BN_set_word(priv, lane + 1) || !EC_KEY_set_private_key(in->key, priv) ||
        !EC_POINT_mul(group, pub, priv, NULL, NULL, NULL) ||
        !EC_KEY_set_public_key(in->key, pub))
        fail("construct P-256 key");
    for (size_t i = 0; i < sizeof(in->digest); i++)
        in->digest[i] = (uint8_t)(0x31u + 17u * lane + 29u * i);

    in->sig = ECDSA_do_sign(in->digest, sizeof(in->digest), in->key);
    if (in->sig == NULL ||
        ECDSA_do_verify(in->digest, sizeof(in->digest), in->sig, in->key) != 1)
        fail("ECDSA sign/self-verify");
    ECDSA_SIG_get0(in->sig, &r, &s);
    BIGNUM *order = BN_new();
    BIGNUM *half_order = BN_new();
    if (order == NULL || half_order == NULL || !EC_GROUP_get_order(group, order, NULL) ||
        !BN_rshift1(half_order, order)) fail("curve order");
    if (BN_cmp(s, half_order) > 0) {
        BIGNUM *new_r = BN_dup(r);
        BIGNUM *new_s = BN_new();
        if (new_r == NULL || new_s == NULL || !BN_sub(new_s, order, s) ||
            !ECDSA_SIG_set0(in->sig, new_r, new_s)) fail("normalize low S");
        ECDSA_SIG_get0(in->sig, &r, &s);
    }
    BN_free(half_order);
    BN_free(order);
    if (!bn_to_be_padded(in->r_be, sizeof(in->r_be), r) ||
        !bn_to_be_padded(in->s_be, sizeof(in->s_be), s) ||
        !EC_POINT_get_affine_coordinates_GFp(group, pub, x, y, NULL) ||
        !bn_to_le_padded((uint8_t *)in->x_le, sizeof(in->x_le), x) ||
        !bn_to_le_padded((uint8_t *)in->y_le, sizeof(in->y_le), y))
        fail("export lane input");

    EC_POINT_free(pub);
    BN_free(y);
    BN_free(x);
    BN_free(priv);
}

static void free_lane(lane_input *in) {
    ECDSA_SIG_free(in->sig);
    EC_KEY_free(in->key);
}

static mbx_status call_mb(lane_input in[LANES], int distinct, int active,
                          int invalid_lane0) {
    const uint8_t *r[LANES];
    const uint8_t *s[LANES];
    const uint8_t *digest[LANES];
    const int64u *x[LANES];
    const int64u *y[LANES];
    uint8_t bad_digest[32];
    memcpy(bad_digest, in[0].digest, sizeof(bad_digest));
    bad_digest[0] ^= 1;
    for (int i = 0; i < LANES; i++) {
        lane_input *v = &in[distinct ? i : 0];
        r[i] = i < active ? v->r_be : NULL;
        s[i] = i < active ? v->s_be : NULL;
        digest[i] = i < active
                        ? ((invalid_lane0 && i == 0) ? bad_digest : v->digest)
                        : NULL;
        x[i] = i < active ? v->x_le : NULL;
        y[i] = i < active ? v->y_le : NULL;
    }
    return mbx_nistp256_ecdsa_verify_mb8(r, s, digest, x, y, NULL, NULL);
}

static uint64_t call_openssl(lane_input in[LANES], int distinct, int active,
                             int invalid_lane0) {
    uint64_t ok = 0;
    uint8_t bad_digest[32];
    memcpy(bad_digest, in[0].digest, sizeof(bad_digest));
    bad_digest[0] ^= 1;
    for (int i = 0; i < active; i++) {
        lane_input *v = &in[distinct ? i : 0];
        const uint8_t *d = invalid_lane0 && i == 0 ? bad_digest : v->digest;
        ok += (uint64_t)(ECDSA_do_verify(d, sizeof(v->digest), v->sig, v->key) == 1);
    }
    return ok;
}

static void check_mb_status(mbx_status status, int active) {
    for (int i = 0; i < LANES; i++) {
        int got = MBX_GET_STS(status, i);
        int want = i < active ? MBX_STATUS_OK : MBX_STATUS_NULL_PARAM_ERR;
        if (i < active ? got != want : (got & MBX_STATUS_NULL_PARAM_ERR) == 0) {
            fprintf(stderr, "FAIL: crypto_mb lane %d status=%d want=%d\n", i, got, want);
            exit(1);
        }
    }
}

static int compare_u64(const void *a, const void *b) {
    uint64_t x = *(const uint64_t *)a;
    uint64_t y = *(const uint64_t *)b;
    return (x > y) - (x < y);
}

static uint64_t median(uint64_t *samples, uint64_t repetitions) {
    qsort(samples, repetitions, sizeof(*samples), compare_u64);
    return samples[repetitions / 2];
}

static void check_invalid_case(lane_input in[LANES], int distinct, int active) {
    mbx_status status = call_mb(in, distinct, active, 1);
    for (int i = 0; i < LANES; i++) {
        int got = MBX_GET_STS(status, i);
        int want = i == 0
                       ? MBX_STATUS_SIGNATURE_ERR
                       : (i < active ? MBX_STATUS_OK : MBX_STATUS_NULL_PARAM_ERR);
        if (i < active ? got != want : (got & MBX_STATUS_NULL_PARAM_ERR) == 0) {
            fprintf(stderr,
                    "FAIL: crypto_mb invalid check lane %d status=%d want=%d\n",
                    i, got, want);
            exit(1);
        }
    }
    if (call_openssl(in, distinct, active, 1) != (uint64_t)active - 1)
        fail("OpenSSL invalid-signature check");
}

static void bench_case(lane_input in[LANES], int distinct, int active,
                       uint64_t groups, uint64_t repetitions) {
    const char *working_set = distinct ? "8_distinct_keys" : "1_repeated_key";
    uint64_t *mb_samples = calloc(repetitions, sizeof(*mb_samples));
    uint64_t *openssl_samples = calloc(repetitions, sizeof(*openssl_samples));
    uint64_t ok = 0;
    mbx_status status = 0;
    if (mb_samples == NULL || openssl_samples == NULL) fail("calloc");

    check_mb_status(call_mb(in, distinct, active, 0), active);
    check_invalid_case(in, distinct, active);

    for (int i = 0; i < WARMUP_GROUPS; i++)
        status |= call_mb(in, distinct, active, 0);
    check_mb_status(status, active);
    for (int i = 0; i < WARMUP_GROUPS; i++)
        ok += call_openssl(in, distinct, active, 0);
    if (ok != (uint64_t)WARMUP_GROUPS * (uint64_t)active)
        fail("OpenSSL warmup verify");

    for (uint64_t rep = 0; rep < repetitions; rep++) {
        uint64_t start = now_ns();
        for (uint64_t i = 0; i < groups; i++)
            sink ^= (uint32_t)call_mb(in, distinct, active, 0);
        mb_samples[rep] = now_ns() - start;

        ok = 0;
        start = now_ns();
        for (uint64_t i = 0; i < groups; i++)
            ok += call_openssl(in, distinct, active, 0);
        openssl_samples[rep] = now_ns() - start;
        if (ok != groups * (uint64_t)active) fail("OpenSSL timed verify");
        sink ^= ok;
    }

    uint64_t elapsed = median(mb_samples, repetitions);
    printf("backend=crypto_mb working_set=%s active_lanes=%d groups=%" PRIu64
           " repetitions=%" PRIu64
           " median_ns_per_group=%.2f median_ns_per_active_verify=%.2f"
           " median_verifies_per_sec=%.2f valid_check=ok invalid_check=ok\n",
           working_set, active, groups, repetitions, (double)elapsed / groups,
           (double)elapsed / (groups * (uint64_t)active),
           (double)(groups * (uint64_t)active) * 1e9 / elapsed);

    elapsed = median(openssl_samples, repetitions);
    printf("backend=openssl working_set=%s active_lanes=%d groups=%" PRIu64
           " repetitions=%" PRIu64
           " median_ns_per_group=%.2f median_ns_per_active_verify=%.2f"
           " median_verifies_per_sec=%.2f valid_check=ok invalid_check=ok\n",
           working_set, active, groups, repetitions, (double)elapsed / groups,
           (double)elapsed / (groups * (uint64_t)active),
           (double)(groups * (uint64_t)active) * 1e9 / elapsed);

    free(openssl_samples);
    free(mb_samples);
}

int main(int argc, char **argv) {
    uint64_t groups = DEFAULT_GROUPS;
    uint64_t repetitions = DEFAULT_REPETITIONS;
    lane_input in[LANES] = {0};
    int64u features;
    MBX_ALGO_INFO p256_info;
    if (argc > 3) fail("usage: p256_mb_bench [groups] [odd repetitions]");
    if (argc >= 2) {
        char *end = NULL;
        groups = strtoull(argv[1], &end, 10);
        if (end == argv[1] || *end != '\0' || groups == 0) fail("invalid groups");
    }
    if (argc == 3) {
        char *end = NULL;
        repetitions = strtoull(argv[2], &end, 10);
        if (end == argv[2] || *end != '\0' || repetitions == 0 ||
            (repetitions & 1) == 0)
            fail("repetitions must be a positive odd number");
    }

    features = mbx_get_cpu_features();
    p256_info = mbx_get_algo_info(MBX_ALGO_ECDSA_NIST_P256);
    printf("crypto_mb_cpu_features=0x%" PRIx64 " p256_width_mask=0x%" PRIx64 "\n",
           (uint64_t)features, (uint64_t)p256_info);
    if ((p256_info & MBX_WIDTH_MB8) == 0) {
        fprintf(stderr,
                "UNSUPPORTED: crypto_mb reports no P-256 mb8 backend. Check flags for "
                "avx512ifma+avx512vbmi2 or avx_ifma; generic AVX-512/AVX2 is insufficient.\n");
        return 2;
    }

    for (unsigned i = 0; i < LANES; i++) init_lane(&in[i], i);
    bench_case(in, 0, LANES, groups, repetitions);
    bench_case(in, 1, LANES, groups, repetitions);
    bench_case(in, 0, 1, groups, repetitions);
    for (unsigned i = 0; i < LANES; i++) free_lane(&in[i]);
    fprintf(stderr, "sink=%" PRIu64 "\n", sink);
    return 0;
}

run_p256_mb_bench_system_openssl.sh

#!/usr/bin/env bash
set -euo pipefail

bench_root="${1:?supply an absolute build-directory path}"
harness="${2:?supply the absolute path to p256_mb_bench.c}"
intel_commit=7d5324757273ba1d7c9b425619e63ee8b4a9cb5b

mkdir -p "$bench_root"
cd "$bench_root"

if [[ ! -f "$harness" ]]; then
  echo "missing harness: $harness" >&2
  exit 1
fi

nasm_version=""
if command -v nasm >/dev/null 2>&1; then
  nasm_version="$(nasm -v | awk '{print $3}')"
fi
if [[ -z "$nasm_version" ]] ||
   [[ "$(printf '%s\n' 2.16.02 "$nasm_version" | sort -V | head -n1)" != 2.16.02 ]]; then
  curl -L --fail --silent --show-error \
    -o nasm-3.01.tar.gz \
    https://www.nasm.us/pub/nasm/releasebuilds/3.01/nasm-3.01.tar.gz
  if [[ ! -d nasm-3.01 ]]; then
    tar -xzf nasm-3.01.tar.gz
  fi
  (
    cd nasm-3.01
    ./configure --prefix="$bench_root/nasm-install"
    make -j"$(getconf _NPROCESSORS_ONLN)"
    make install
  )
  export PATH="$bench_root/nasm-install/bin:$PATH"
fi

if [[ ! -d cryptography-primitives/.git ]]; then
  git clone https://github.com/intel/cryptography-primitives.git
fi
git -C cryptography-primitives fetch origin "$intel_commit"
git -C cryptography-primitives checkout --detach "$intel_commit"

openssl version -a
pkg-config --modversion openssl
nasm -v
cc --version

# BORINGSSL only selects versionless find_package(OpenSSL) in this CMake file.
# It does not define OPENSSL_IS_BORINGSSL or change the raw crypto_mb kernels.
cmake -S cryptography-primitives/sources/ippcp/crypto_mb \
  -B crypto-mb-build -GNinja \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX="$bench_root/crypto-mb-install" \
  -DBORINGSSL=ON \
  -DOPENSSL_ROOT_DIR=/usr
ninja -C crypto-mb-build install

cc -O3 -DNDEBUG -Wall -Wextra -Werror -Wno-deprecated-declarations \
  -I"$bench_root/crypto-mb-install/include" \
  "$harness" \
  -L"$bench_root/crypto-mb-install/lib" \
  -Wl,-rpath,"$bench_root/crypto-mb-install/lib" \
  -lcrypto_mb \
  $(pkg-config --cflags --libs openssl) \
  -lpthread -ldl \
  -o p256_mb_bench

uname -a
lscpu
grep -m1 '^flags' /proc/cpuinfo || true
taskset -c 2 ./p256_mb_bench 4000 5
Rust comparison harness and exact dependency lockfile

Cargo.toml

[package]
name = "aws-lc-p256-verify-benches"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
aws-lc-rs = "=1.17.1"
p256 = { version = "=0.14.0", default-features = false, features = ["ecdsa", "std"] }
rand_chacha = "=0.10.0"
rand_core = "=0.10.1"

[features]
precomputed = ["p256/precomputed-tables"]

src/main.rs

use aws_lc_rs::signature::{
    ECDSA_P256_SHA256_ASN1, ECDSA_P256_SHA256_FIXED, ParsedPublicKey, UnparsedPublicKey,
};
use p256::{
    ecdsa::{Signature, SigningKey, VerifyingKey, signature::{Signer, Verifier}},
    elliptic_curve::Generate,
};
use rand_chacha::ChaCha20Rng;
use rand_core::{Rng, SeedableRng};
use std::{
    hint::black_box,
    time::{Duration, Instant},
};

const CASES: usize = 256;
const REPLICATES: usize = 5;
const SAMPLE_TIME: Duration = Duration::from_secs(1);

struct Fixture {
    public: [u8; 65],
    message: [u8; 32],
    fixed_signature: [u8; 64],
    der_signature: Vec<u8>,
}

fn fixtures(same_key: bool) -> Vec<Fixture> {
    let mut rng = ChaCha20Rng::from_seed([0x5a; 32]);
    let fixed_key = SigningKey::generate_from_rng(&mut rng);
    (0..CASES)
        .map(|_| {
            let key = if same_key {
                fixed_key.clone()
            } else {
                SigningKey::generate_from_rng(&mut rng)
            };
            let mut message = [0; 32];
            rng.fill_bytes(&mut message);
            let signature: Signature = key.sign(&message);
            let signature = signature.normalize_s();
            Fixture {
                public: key
                    .verifying_key()
                    .to_sec1_point(false)
                    .as_bytes()
                    .try_into()
                    .unwrap(),
                message,
                fixed_signature: signature.to_bytes().into(),
                der_signature: signature.to_der().as_bytes().to_vec(),
            }
        })
        .collect()
}

fn require_valid(fixtures: &[Fixture]) {
    for fixture in fixtures {
        UnparsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, &fixture.public)
            .verify(&fixture.message, &fixture.fixed_signature)
            .unwrap();
        ParsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, &fixture.public)
            .unwrap()
            .verify_sig(&fixture.message, &fixture.fixed_signature)
            .unwrap();
        ParsedPublicKey::new(&ECDSA_P256_SHA256_ASN1, &fixture.public)
            .unwrap()
            .verify_sig(&fixture.message, &fixture.der_signature)
            .unwrap();

        let mut altered = fixture.fixed_signature;
        altered[63] ^= 1;
        assert!(
            UnparsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, &fixture.public)
                .verify(&fixture.message, &altered)
                .is_err()
        );
    }
}

fn measure(name: &str, mut operation: impl FnMut() -> usize) {
    println!("{name}");
    for replicate in 1..=REPLICATES {
        let start = Instant::now();
        let mut operations = 0_u64;
        while start.elapsed() < SAMPLE_TIME {
            operations += operation() as u64;
        }
        let elapsed = start.elapsed().as_secs_f64();
        println!(
            "  replicate={replicate} ops={operations} seconds={elapsed:.6} ops_per_sec={:.2}",
            operations as f64 / elapsed
        );
    }
}

fn run_case(label: &str, fixtures: &[Fixture]) {
    require_valid(fixtures);
    let rustcrypto_keys: Vec<_> = fixtures.iter().map(|f| VerifyingKey::from_sec1_bytes(&f.public).unwrap()).collect();
    measure(&format!("case={label} path=rustcrypto_cached_p256"), || {
        for (fixture, public) in fixtures.iter().zip(&rustcrypto_keys) {
            let signature = Signature::from_slice(black_box(&fixture.fixed_signature)).unwrap();
            black_box(public.verify(black_box(&fixture.message), &signature).unwrap());
        }
        fixtures.len()
    });
    if std::env::var_os("P256_BENCH_ONLY_RUSTCRYPTO").is_some() {
        return;
    }
    let parsed_fixed: Vec<_> = fixtures
        .iter()
        .map(|f| ParsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, &f.public).unwrap())
        .collect();
    let parsed_der: Vec<_> = fixtures
        .iter()
        .map(|f| ParsedPublicKey::new(&ECDSA_P256_SHA256_ASN1, &f.public).unwrap())
        .collect();

    measure(&format!("case={label} path=unparsed_fixed"), || {
        for fixture in fixtures {
            black_box(
                UnparsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, &fixture.public)
                    .verify(
                        black_box(&fixture.message),
                        black_box(&fixture.fixed_signature),
                    )
                    .unwrap(),
            );
        }
        fixtures.len()
    });
    measure(&format!("case={label} path=parsed_fixed"), || {
        for (fixture, public) in fixtures.iter().zip(&parsed_fixed) {
            black_box(
                public
                    .verify_sig(
                        black_box(&fixture.message),
                        black_box(&fixture.fixed_signature),
                    )
                    .unwrap(),
            );
        }
        fixtures.len()
    });
    measure(&format!("case={label} path=parsed_asn1_preencoded"), || {
        for (fixture, public) in fixtures.iter().zip(&parsed_der) {
            black_box(
                public
                    .verify_sig(
                        black_box(&fixture.message),
                        black_box(&fixture.der_signature),
                    )
                    .unwrap(),
            );
        }
        fixtures.len()
    });
    measure(
        &format!("case={label} path=parsed_key_construction"),
        || {
            for fixture in fixtures {
                black_box(
                    ParsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, black_box(&fixture.public))
                        .unwrap(),
                );
            }
            fixtures.len()
        },
    );
}

fn main() {
    println!(
        "aws-lc-rs=1.17.1 p256=0.14.0 std=true precomputed={} fixtures={CASES} message_bytes=32 replicates={REPLICATES} sample_seconds={}",
        cfg!(feature = "precomputed"), SAMPLE_TIME.as_secs()
    );
    run_case("same_key_many_messages", &fixtures(true));
    run_case("distinct_key_distinct_message", &fixtures(false));
}

Cargo.lock

# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4

[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"

[[package]]
name = "aws-lc-p256-verify-benches"
version = "0.1.0"
dependencies = [
 "aws-lc-rs",
 "p256",
 "rand_chacha",
 "rand_core",
]

[[package]]
name = "aws-lc-rs"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad"
dependencies = [
 "aws-lc-sys",
 "untrusted",
 "zeroize",
]

[[package]]
name = "aws-lc-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444"
dependencies = [
 "cc",
 "cmake",
 "dunce",
 "fs_extra",
 "pkg-config",
]

[[package]]
name = "base16ct"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6"

[[package]]
name = "base64ct"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"

[[package]]
name = "block-buffer"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
dependencies = [
 "hybrid-array",
]

[[package]]
name = "cc"
version = "1.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4"
dependencies = [
 "find-msvc-tools",
 "jobserver",
 "libc",
 "shlex",
]

[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"

[[package]]
name = "cmake"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
 "cc",
]

[[package]]
name = "cmov"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"

[[package]]
name = "const-oid"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"

[[package]]
name = "cpubits"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"

[[package]]
name = "cpufeatures"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
dependencies = [
 "libc",
]

[[package]]
name = "crypto-bigint"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271"
dependencies = [
 "cpubits",
 "ctutils",
 "getrandom",
 "hybrid-array",
 "num-traits",
 "rand_core",
 "subtle",
 "zeroize",
]

[[package]]
name = "crypto-common"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [
 "getrandom",
 "hybrid-array",
 "rand_core",
]

[[package]]
name = "ctutils"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
dependencies = [
 "cmov",
 "subtle",
]

[[package]]
name = "der"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a"
dependencies = [
 "const-oid",
 "zeroize",
]

[[package]]
name = "digest"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [
 "block-buffer",
 "const-oid",
 "crypto-common",
 "ctutils",
]

[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"

[[package]]
name = "ecdsa"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0"
dependencies = [
 "der",
 "digest",
 "elliptic-curve",
 "rfc6979",
 "signature",
 "spki",
 "zeroize",
]

[[package]]
name = "elliptic-curve"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65"
dependencies = [
 "base16ct",
 "crypto-bigint",
 "crypto-common",
 "digest",
 "ff",
 "group",
 "hybrid-array",
 "pkcs8",
 "rand_core",
 "sec1",
 "subtle",
 "zeroize",
]

[[package]]
name = "ff"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f"
dependencies = [
 "rand_core",
 "subtle",
]

[[package]]
name = "find-msvc-tools"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"

[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"

[[package]]
name = "getrandom"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
 "cfg-if",
 "libc",
 "r-efi",
 "rand_core",
]

[[package]]
name = "group"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4"
dependencies = [
 "ff",
 "rand_core",
 "subtle",
]

[[package]]
name = "hmac"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
dependencies = [
 "digest",
]

[[package]]
name = "hybrid-array"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
dependencies = [
 "subtle",
 "typenum",
 "zeroize",
]

[[package]]
name = "jobserver"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
dependencies = [
 "getrandom",
 "libc",
]

[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"

[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
 "autocfg",
]

[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"

[[package]]
name = "p256"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a"
dependencies = [
 "ecdsa",
 "elliptic-curve",
 "primefield",
 "primeorder",
 "sha2",
]

[[package]]
name = "pkcs8"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
dependencies = [
 "der",
 "spki",
]

[[package]]
name = "pkg-config"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"

[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
 "zerocopy",
]

[[package]]
name = "primefield"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4"
dependencies = [
 "crypto-bigint",
 "crypto-common",
 "ff",
 "rand_core",
 "subtle",
 "zeroize",
]

[[package]]
name = "primeorder"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06"
dependencies = [
 "elliptic-curve",
 "once_cell",
 "primefield",
 "serdect",
 "wnaf",
]

[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
 "unicode-ident",
]

[[package]]
name = "quote"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"

[[package]]
name = "rand_chacha"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb"
dependencies = [
 "ppv-lite86",
 "rand_core",
]

[[package]]
name = "rand_core"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"

[[package]]
name = "rfc6979"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b"
dependencies = [
 "crypto-bigint",
 "hmac",
]

[[package]]
name = "sec1"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d"
dependencies = [
 "base16ct",
 "ctutils",
 "der",
 "hybrid-array",
 "subtle",
 "zeroize",
]

[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
 "serde_core",
]

[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
 "serde_derive",
]

[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "serdect"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e"
dependencies = [
 "base16ct",
 "serde",
]

[[package]]
name = "sha2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
dependencies = [
 "cfg-if",
 "cpufeatures",
 "digest",
]

[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"

[[package]]
name = "signature"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"
dependencies = [
 "digest",
 "rand_core",
]

[[package]]
name = "spki"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
dependencies = [
 "base64ct",
 "der",
]

[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"

[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"

[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"

[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"

[[package]]
name = "wnaf"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "795ca18b3fdb5e62bf982199278341ddcf7ebf7d32e25e212ad05d496e95f6fa"
dependencies = [
 "ff",
 "group",
 "hybrid-array",
 "primefield",
]

[[package]]
name = "zerocopy"
version = "0.8.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873"
dependencies = [
 "zerocopy-derive",
]

[[package]]
name = "zerocopy-derive"
version = "0.8.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "zeroize"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
Successful native SIMD result

p256-crypto-mb-final.txt

sink=4000
crypto_mb_cpu_features=0xff0f3befff p256_width_mask=0x8
backend=crypto_mb working_set=1_repeated_key active_lanes=8 groups=4000 repetitions=5 median_ns_per_group=86423.16 median_ns_per_active_verify=10802.89 median_verifies_per_sec=92567.78 valid_check=ok invalid_check=ok
backend=openssl working_set=1_repeated_key active_lanes=8 groups=4000 repetitions=5 median_ns_per_group=302238.20 median_ns_per_active_verify=37779.78 median_verifies_per_sec=26469.19 valid_check=ok invalid_check=ok
backend=crypto_mb working_set=8_distinct_keys active_lanes=8 groups=4000 repetitions=5 median_ns_per_group=86444.78 median_ns_per_active_verify=10805.60 median_verifies_per_sec=92544.63 valid_check=ok invalid_check=ok
backend=openssl working_set=8_distinct_keys active_lanes=8 groups=4000 repetitions=5 median_ns_per_group=302375.04 median_ns_per_active_verify=37796.88 median_verifies_per_sec=26457.21 valid_check=ok invalid_check=ok
backend=crypto_mb working_set=1_repeated_key active_lanes=1 groups=4000 repetitions=5 median_ns_per_group=89328.31 median_ns_per_active_verify=89328.31 median_verifies_per_sec=11194.66 valid_check=ok invalid_check=ok
backend=openssl working_set=1_repeated_key active_lanes=1 groups=4000 repetitions=5 median_ns_per_group=37777.35 median_ns_per_active_verify=37777.35 median_verifies_per_sec=26470.89 valid_check=ok invalid_check=ok
Exact temporary adjustment for the exploratory Curve25519 measurement

curve25519-benchmark-only.patch

--- a/cryptography/curve25519/src/curve/avx512.rs
+++ b/cryptography/curve25519/src/curve/avx512.rs
@@ -55,6 +55,8 @@
 #[target_feature(enable = "avx512f")]
 fn mul19(z: __m512i) -> __m512i {
     let result;
+    let _times16: __m512i;
+    let _doubled: __m512i;
     // SAFETY: AVX-512F is enabled. The instructions only read their register input, write
     // their register outputs, preserve flags, and stay within the documented limb bound.
     unsafe {
@@ -64,8 +66,8 @@
             "vpaddq {doubled}, {doubled}, {times16}",
             "vpaddq {result}, {doubled}, {z}",
             z = in(zmm_reg) z,
-            times16 = out(zmm_reg) _,
-            doubled = out(zmm_reg) _,
+            times16 = out(zmm_reg) _times16,
+            doubled = out(zmm_reg) _doubled,
             result = lateout(zmm_reg) result,
             options(pure, nomem, nostack, preserves_flags),
         );
--- a/cryptography/curve25519/benches/batch_verify.rs
+++ b/cryptography/curve25519/benches/batch_verify.rs
@@ -60,9 +60,9 @@
                         |(mut rng, verifier)| {
                             #[allow(clippy::option_if_let_else)]
                             if let Some(rayon) = rayon.as_ref() {
-                                black_box(verifier.verify(&mut rng, rayon))
+                                assert!(black_box(verifier.verify(&mut rng, rayon)), "valid benchmark batch")
                             } else {
-                                black_box(verifier.verify(&mut rng, &Sequential))
+                                assert!(black_box(verifier.verify(&mut rng, &Sequential)), "valid benchmark batch")
                             }
                         },
                         BatchSize::SmallInput,

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      Backlog

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions