Skip to content
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 21 additions & 22 deletions src/crypto/SHA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,54 @@
#include "crypto/ByteSlice.h"
#include "crypto/CryptoError.h"
#include "crypto/Curve25519.h"
#include "rust/RustBridge.h"
#include "util/NonCopyable.h"
#include <Tracy.hpp>
#include <sodium.h>
#include <vector>

namespace stellar
{

// Plain SHA256
// Plain SHA256, routed through the Rust bridge.
uint256
sha256(ByteSlice const& bin)
{
ZoneScoped;
uint256 out;
if (crypto_hash_sha256(out.data(), bin.data(), bin.size()) != 0)
{
throw CryptoError("error from crypto_hash_sha256");
}
rust_bridge::compute_sha256(bin.data(), bin.size(), out.data());
Comment thread
dmkozh marked this conversation as resolved.
return out;
}

Hash
subSha256(ByteSlice const& seed, uint64_t counter)
{
ZoneScoped;
SHA256 sha;
sha.add(seed);
sha.add(xdr::xdr_to_opaque(counter));
return sha.finish();
}

SHA256::SHA256()
class SHA256::Impl
{
public:
Impl() : rust_impl(rust_bridge::new_rust_sha256())
{
}
rust::Box<rust_bridge::RustSha256> rust_impl;
};

SHA256::SHA256() : mImpl(std::make_unique<SHA256::Impl>())
{
reset();
}

SHA256::~SHA256() = default;

void
SHA256::reset()
{
if (crypto_hash_sha256_init(&mState) != 0)
{
throw CryptoError("error from crypto_hash_sha256_init");
}
mImpl->rust_impl->reset();
mFinished = false;
}

Expand All @@ -58,26 +65,18 @@ SHA256::add(ByteSlice const& bin)
{
throw std::runtime_error("adding bytes to finished SHA256");
}
if (crypto_hash_sha256_update(&mState, bin.data(), bin.size()) != 0)
{
throw CryptoError("error from crypto_hash_sha256_update");
}
mImpl->rust_impl->update(bin.data(), bin.size());
}

uint256
SHA256::finish()
{
uint256 out;
static_assert(sizeof(out) == crypto_hash_sha256_BYTES,
"unexpected crypto_hash_sha256_BYTES");
if (mFinished)
{
throw std::runtime_error("finishing already-finished SHA256");
}
if (crypto_hash_sha256_final(&mState, out.data()) != 0)
{
throw CryptoError("error from crypto_hash_sha256_final");
}
uint256 out;
mImpl->rust_impl->finalize(out.data());
mFinished = true;
return out;
}
Expand Down
7 changes: 5 additions & 2 deletions src/crypto/SHA.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

#include "crypto/ByteSlice.h"
#include "crypto/XDRHasher.h"
#include "sodium/crypto_hash_sha256.h"
#include "xdr/Stellar-types.h"
#include <memory>

Expand All @@ -21,13 +20,17 @@ uint256 sha256(ByteSlice const& bin);
Hash subSha256(ByteSlice const& seed, uint64_t counter);

// SHA256 in incremental mode, for large inputs.
// Backed by the Rust sha2 bridge and pimpl'd so this widely-included header
// need not pull in the generated rust bridge.
class SHA256
{
crypto_hash_sha256_state mState;
class Impl;
std::unique_ptr<Impl> mImpl;
bool mFinished{false};

public:
SHA256();
~SHA256();
void reset();
void add(ByteSlice const& bin);
uint256 finish();
Expand Down
57 changes: 43 additions & 14 deletions src/crypto/test/CryptoTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,39 +122,68 @@ TEST_CASE("XDRSHA256 is identical to byte SHA256", "[crypto]")

TEST_CASE("SHA256 bytes bench", "[!hide][sha-bytes-bench]")
{
shortHash::initialize();
autocheck::rng().seed(11111);
std::vector<LedgerEntry> entries;
for (size_t i = 0; i < 1000; ++i)
size_t const entryCount = 1000;
size_t const iterationCount = 10;
size_t const totalCount = entryCount * iterationCount;

std::vector<xdr::opaque_vec<>> entriesXdr;
for (size_t i = 0; i < entryCount; ++i)
{
entries.emplace_back(LedgerTestUtils::generateValidLedgerEntry(1000));
entriesXdr.emplace_back(xdr::xdr_to_opaque(
LedgerTestUtils::generateValidLedgerEntryWithExclusions(
{LedgerEntryType::CONFIG_SETTING}, 1000)));
}
for (size_t i = 0; i < 10000; ++i)

auto startTime = std::chrono::steady_clock::now();
int64_t shaDuration = 0;
for (size_t i = 0; i < iterationCount; ++i)
{
for (auto const& e : entries)
for (auto const& e : entriesXdr)
{
auto opaque = xdr::xdr_to_opaque(e);
sha256(opaque);
sha256(e);
}
}
auto totalDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - startTime)
.count();
std::cout << "SHA256 bytes bench total duration: " << totalDuration * 1e-9
<< " s, "
<< "average duration per entry: "
<< static_cast<double>(totalDuration) / totalCount * 1e-6 << " ms"
<< std::endl;
}

TEST_CASE("SHA256 XDR bench", "[!hide][sha-xdr-bench]")
{
shortHash::initialize();
autocheck::rng().seed(11111);
size_t const entryCount = 1000;
size_t const iterationCount = 10;
size_t const totalCount = entryCount * iterationCount;

std::vector<LedgerEntry> entries;
for (size_t i = 0; i < 1000; ++i)
for (size_t i = 0; i < entryCount; ++i)
{
entries.emplace_back(LedgerTestUtils::generateValidLedgerEntry(1000));
entries.emplace_back(
LedgerTestUtils::generateValidLedgerEntryWithExclusions(
{LedgerEntryType::CONFIG_SETTING}, 1000));
}
for (size_t i = 0; i < 10000; ++i)

auto startTime = std::chrono::steady_clock::now();
int64_t shaDuration = 0;
for (size_t i = 0; i < iterationCount; ++i)
{
for (auto const& e : entries)
{
xdrSha256(e);
}
}
auto totalDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - startTime)
.count();
std::cout << "XDR SHA256 bench total duration: " << totalDuration * 1e-9
<< " s, "
<< "average duration per entry: "
<< static_cast<double>(totalDuration) / totalCount * 1e-6 << " ms"
<< std::endl;
}

static std::map<std::string, std::string> blake2TestVectors = {
Expand Down
1 change: 1 addition & 0 deletions src/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ crate-type = ["staticlib"]
log = "=0.4.19"
cxx = "=1.0.97"
base64 = "=0.13.1"
sha2 = "=0.10.9"
rustc-simple-version = "=0.1.0"
# NB: this must match the same rand version used by soroban (but the tooling
# will complain if it does not match)
Expand Down
10 changes: 10 additions & 0 deletions src/rust/src/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,15 @@ pub(crate) mod rust_bridge {
// be called from a background thread. Never panics: any failure is
// reported via NtpProbeResult::succeeded == false.
fn query_ntp_offset(server: &CxxString, timeout_seconds: u64) -> NtpProbeResult;
// SHA-256 via RustCrypto's sha2, writing the 32-byte digest to `out`.
unsafe fn compute_sha256(data: *const u8, data_len: usize, out: *mut u8);
// Incremental SHA-256, for streaming hashing without materializing a
// contiguous input buffer.
type RustSha256;
fn new_rust_sha256() -> Box<RustSha256>;
unsafe fn update(self: &mut RustSha256, data: *const u8, len: usize);
unsafe fn finalize(self: &mut RustSha256, out: *mut u8);
fn reset(self: &mut RustSha256);
fn check_sensible_soroban_config_for_protocol(core_max_proto: u32);

// Ed25519 signature verification using dalek library.
Expand Down Expand Up @@ -482,6 +491,7 @@ use crate::i128::*;
use crate::log::*;
use crate::ntp::*;
use crate::quorum_checker::*;
use crate::sha256::*;
use crate::soroban_fuzz::*;
use crate::soroban_invoke::*;
use crate::soroban_module_cache::*;
Expand Down
1 change: 1 addition & 0 deletions src/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ mod i128;
mod log;
mod ntp;
mod quorum_checker;
mod sha256;
mod soroban_invoke;
mod soroban_module_cache;
mod soroban_test_wasm;
Expand Down
58 changes: 58 additions & 0 deletions src/rust/src/sha256.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2026 Stellar Development Foundation and contributors. Licensed
// under the Apache License, Version 2.0. See the COPYING file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0

// SHA-256 implemented via the RustCrypto `sha2` crate, exposed to C++ over the
// cxx bridge.

use sha2::{Digest, Sha256};

// Computes SHA-256 of `data_len` bytes at `data` and writes the 32-byte digest
// to `out`. `out` must point to at least 32 writable bytes.
//
// # Safety
// `data`/`data_len` must describe a valid readable region (or len 0), and `out`
// must point to 32 writable bytes.
pub(crate) unsafe fn compute_sha256(data: *const u8, data_len: usize, out: *mut u8) {
let input: &[u8] = if data_len == 0 {
&[]
} else {
std::slice::from_raw_parts(data, data_len)
};
let digest = Sha256::digest(input);
std::ptr::copy_nonoverlapping(digest.as_ptr(), out, 32);
}

// Incremental SHA-256 state, exposed to C++ as an opaque cxx type so that
// callers can stream bytes in without first materializing a contiguous buffer.
pub(crate) struct RustSha256 {
hasher: Sha256,
}

pub(crate) fn new_rust_sha256() -> Box<RustSha256> {
Box::new(RustSha256 {
hasher: Sha256::new(),
})
}

impl RustSha256 {
// # Safety: `data`/`len` must describe a valid readable region (or len 0).
pub(crate) unsafe fn update(&mut self, data: *const u8, len: usize) {
let input: &[u8] = if len == 0 {
&[]
} else {
std::slice::from_raw_parts(data, len)
};
self.hasher.update(input);
}

// # Safety: `out` must point to 32 writable bytes. Resets the hasher.
pub(crate) unsafe fn finalize(&mut self, out: *mut u8) {
let digest = self.hasher.finalize_reset();
std::ptr::copy_nonoverlapping(digest.as_ptr(), out, 32);
}

pub(crate) fn reset(&mut self) {
self.hasher.reset();
}
}
Loading