# objectstore.crypto

> A module of objectstore's API.

`objectstore.crypto` — SHA-256, HMAC-SHA256 and hex, in pure Mojo.

AWS SigV4 is built entirely out of these two primitives, and nothing in the
Mojo ecosystem provides either: hashes.mojo covers the *non-cryptographic*
hashes Iceberg needs for partition transforms (murmur3, xxhash, CRC32), and no
conda-resolvable package offers SHA-2. Rather than pull OpenSSL in through a
second FFI shim — libcurl already links it, but its EVP surface is a moving
target across conda builds — this module implements FIPS 180-4 and RFC 2104
directly.

SigV4 hashes the *whole* payload of every signed PUT, so this is not a
negligible cost: a purely scalar compression function caps a multipart upload
at the speed of the hash. The block loop therefore has three backends, chosen
at compile time by `_BACKEND`:

* `armv8-crypto` — the ARMv8 SHA-2 extension (`sha256h`/`sha256h2`/`sha256su0`
  /`sha256su1`), which every Apple Silicon and every server aarch64 part has.
* `x86-sha-ni`   — Intel SHA extensions (`sha256rnds2`/`sha256msg1`
  /`sha256msg2`), present on AMD Zen and on Intel from Goldmont/Ice Lake
  client onwards, but *not* on Ice Lake / Sapphire Rapids Xeons.
* `scalar`       — portable FIPS 180-4, fully unrolled with a 16-word rolling
  schedule so the whole working set stays in registers.

The choice is a `comptime` query of the *target's* CPU features, and `mojo`
compiles for the host CPU unless told otherwise, so on a normal build (pixi
source dependency, `pixi run`, CI) the compile-time answer is the run-time
truth. A binary deliberately cross-built with `--target-cpu` for a CPU it will
not run on is the one case that would fault, and that is the same contract as
every other `-march=native` build.

If a general-purpose home ever appears, this belongs in hashes.mojo.

## Structs

### `Sha256`

```mojo
struct Sha256
```

Streaming FIPS 180-4 SHA-256.

Streaming rather than one-shot because a SigV4 `PUT` has to hash the whole
payload, which for a data file is larger than anything worth copying into
one contiguous buffer just to hash it.

Implements: `Copyable`, `Movable`

#### `__init__`

```mojo
def __init__(out self)
```

#### `update`

```mojo
def update(mut self, data: Span[UInt8])
```

#### `digest`

```mojo
def digest(self) -> List[UInt8]
```

The digest of everything fed so far. Does not consume `self`, so a caller can keep hashing afterwards.

## Functions

### `sha256_backend`

```mojo
def sha256_backend() -> String
```

Which compression backend this build is using: `armv8-crypto`, `armv8-crypto (asm)`, `x86-sha-ni` or `scalar`. Benchmarks and tests print it so a CI log says which path it actually exercised.

### `sha256_target_features`

```mojo
def sha256_target_features() -> String
```

The CPU features this build was compiled against, as the backend choice saw them. A `scalar` backend on hardware that plainly has the extension means `mojo` did not resolve the host CPU — this is the line that says so.

### `sha256_scalar`

```mojo
def sha256_scalar(data: Span[UInt8]) -> List[UInt8]
```

SHA-256 forced through the portable backend, whatever `_BACKEND` is.

Exists so the test suite can cross-check the hardware path against the
reference implementation on the very machine that runs it — the two are
entirely separate code, and the FIPS vectors alone would not catch a
schedule bug that only shows up past the first block.

### `sha256`

```mojo
def sha256(data: Span[UInt8]) -> List[UInt8]
```

### `sha256_hex`

```mojo
def sha256_hex(data: Span[UInt8]) -> String
```

```mojo
def sha256_hex(s: String) -> String
```

### `hmac_sha256`

```mojo
def hmac_sha256(key: Span[UInt8], msg: Span[UInt8]) -> List[UInt8]
```

RFC 2104 HMAC with SHA-256. Keys longer than the 64-byte block are hashed first; shorter keys are zero-padded.

```mojo
def hmac_sha256(key: Span[UInt8], msg: String) -> List[UInt8]
```

### `hmac_sha256_hex`

```mojo
def hmac_sha256_hex(key: Span[UInt8], msg: String) -> String
```

### `to_hex`

```mojo
def to_hex(data: Span[UInt8]) -> String
```

Lowercase hex — the case SigV4's canonical request requires.

### `from_hex`

```mojo
def from_hex(s: String) -> List[UInt8]
```

## Aliases

### `SHA256_DIGEST_SIZE`

```mojo
comptime SHA256_DIGEST_SIZE = 32
```

### `SHA256_BLOCK_SIZE`

```mojo
comptime SHA256_BLOCK_SIZE = 64
```

### `AT_HWCAP`

```mojo
comptime AT_HWCAP = 16
```

### `HWCAP_SHA2`

```mojo
comptime HWCAP_SHA2 = 64
```
