Skip to content
Draft
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
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ sphincs-build-sha2:
scarb --profile release build --package sphincs_plus

sphincs-execute:
rm -rf $(TARGET_DIR)/execute/sphincs_plus
scarb --profile release execute \
--package sphincs_plus \
--features blake_hash,sparse_addr \
--print-resource-usage \
--arguments-file packages/sphincs-plus/tests/data/blake2s_simple_128s.json

sphincs-execute-sha2: sphincs-build-sha2
rm -rf $(TARGET_DIR)/execute/sphincs_plus
scarb --profile release execute \
--no-build \
Expand All @@ -67,3 +75,16 @@ sphincs-prove:
sphincs-args:
cd packages/sphincs-plus/scripts && cargo +nightly run --release --example generate_cairo_data \
> ../tests/data/sha2_simple_128s.json

sphincs-args-blake2s:
cd packages/sphincs-plus/scripts && cargo +nightly run --release --no-default-features \
--features "blake2s,sparse_addr,s128,simple" --example generate_cairo_data \
> ../tests/data/blake2s_simple_128s.json

sphincs-test-blake2s:
cd packages/sphincs-plus/scripts && cargo +nightly test --no-default-features \
--features "blake2s,sparse_addr,s128,simple" --lib

sphincs-verify-blake2s:
cd packages/sphincs-plus/scripts && cargo +nightly run --no-default-features \
--features "blake2s,sparse_addr,s128,simple" --example debug_signing
1 change: 1 addition & 0 deletions packages/sphincs-plus/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ cairo_test = "2.11.4"
default = []
blake_hash = []
sparse_addr = []
debug = []
61 changes: 61 additions & 0 deletions packages/sphincs-plus/scripts/Cargo.lock

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

6 changes: 5 additions & 1 deletion packages/sphincs-plus/scripts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,20 @@ optional = true

[dev-dependencies]
pqc_core = {version = "0.3.0", features = ["load"]}
serde_json = "1.0"

# Must enable only one from each of the groups below
# otherwise library will throw a compilation error
[features]
default = ["sha2", "s128", "simple"]

### Hash ###
haraka = []
sha2 = ["sha256"]
shake = ["sha3"]
blake2s = [] # Uses built-in Blake2s implementation (no external crate)

### Address Format ###
sparse_addr = []

### Security level ###
# Considered equivalent to 128, 192 and 256 bit
Expand Down
58 changes: 41 additions & 17 deletions packages/sphincs-plus/scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,59 @@ This directory contains a modified version of the [pqc_sphincsplus](https://gith

Original implementation by **Mitchell Berry** ([Argyle Software](https://github.com/Argyle-Software/sphincsplus)), licensed under MIT/Apache-2.0.

## Modifications
## Supported Hash Functions

### Big-endian address byte ordering
- **SHA2** (default) - Standard SPHINCS+ with SHA-256
- **Blake2s** - Cairo-optimized variant with sparse address format

The standard SPHINCS+ implementation uses native endianness for address bytes (`to_ne_bytes`/`from_ne_bytes`). This version uses big-endian ordering (`to_be_bytes`/`from_be_bytes`) instead.
## Quick Start

**Why?** Cairo's native integer representation is big-endian. Using big-endian address bytes in the signature allows the Cairo verifier to work with addresses directly without byte-swapping, simplifying the implementation and reducing the number of operations.
```bash
# From the repository root

The changes are in `src/address.rs`:
- `from_ne_bytes` → `from_be_bytes`
- `to_ne_bytes` → `to_be_bytes`
# SHA2 variant
make sphincs-args # Generate test data
make sphincs-execute-sha2 # Verify in Cairo

## Usage
# Blake2s variant
make sphincs-args-blake2s # Generate test data
make sphincs-execute # Verify in Cairo
make sphincs-test-blake2s # Run Rust unit tests
make sphincs-verify-blake2s # Run Rust e2e verification
```

Generate test data for Cairo:
## Modifications

```bash
# From the repository root
make sphincs-args
```
### Big-endian address byte ordering (SHA2)

The standard SPHINCS+ implementation uses native endianness for address bytes. This version uses big-endian ordering for SHA2 mode to match Cairo's native integer representation.

### Sparse address format (Blake2s)

For Blake2s mode, addresses use a sparse format (8 u32 words) matching Cairo's `Address` struct:
- Word 0: layer
- Word 1: hypertree_addr_hi
- Word 2: hypertree_addr_lo
- Word 3: address_type
- Word 4: keypair
- Word 5: tree_height
- Word 6: tree_index
- Word 7: wots_addr (chain_idx * 0x100 + hash_position)

This will:
1. Build the Rust crate with nightly (required for `generic_const_exprs`)
2. Run the `generate_cairo_data` example
3. Output JSON to `tests/data/sha2_simple_128s.json`
### Custom Blake2s implementation

The Blake2s variant uses a custom implementation that matches Cairo's `blake2s_compress`/`blake2s_finalize` builtins, allowing pre-computed state reuse in thash operations.

## Building manually

```bash
cd packages/sphincs-plus/scripts

# SHA2 (default)
cargo +nightly run --release --example generate_cairo_data

# Blake2s
cargo +nightly run --release --no-default-features \
--features "blake2s,sparse_addr,s128,simple" \
--example generate_cairo_data
```
100 changes: 100 additions & 0 deletions packages/sphincs-plus/scripts/examples/debug_blake2s.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! Debug script to print intermediate Blake2s values for comparison with Cairo
//!
//! Run with: cargo +nightly run --no-default-features --features "blake2s,sparse_addr,s128,simple" --example debug_blake2s

use pqc_sphincsplus::blake2s::{blake2s, CairoBlake2sState, BLAKE2S_256_IV};

fn main() {
// Use fixed seeds for reproducibility
let pk_seed: [u8; 16] = [
0xd3, 0x22, 0xf6, 0x17, 0xa9, 0xa6, 0xd9, 0x3f,
0x4a, 0x6b, 0x10, 0xd0, 0x5d, 0x69, 0x7b, 0x42,
];
let sk_seed: [u8; 16] = [
0xd7, 0xb5, 0x0d, 0x8d, 0xb9, 0xd3, 0xea, 0xcb,
0x5c, 0xce, 0x8e, 0x15, 0xf7, 0xba, 0xbc, 0xaa,
];

println!("=== Debug Blake2s Implementation ===\n");

// Print seeds as u32 words (little-endian, as Cairo expects)
println!("pk_seed as LE u32 words:");
for i in 0..4 {
let word = u32::from_le_bytes(pk_seed[i*4..i*4+4].try_into().unwrap());
println!(" [{}]: 0x{:08x}", i, word);
}

println!("\nsk_seed as LE u32 words:");
for i in 0..4 {
let word = u32::from_le_bytes(sk_seed[i*4..i*4+4].try_into().unwrap());
println!(" [{}]: 0x{:08x}", i, word);
}

// Print Blake2s IV
println!("\n=== Blake2s-256 IV (with param block XOR) ===");
for (i, w) in BLAKE2S_256_IV.iter().enumerate() {
println!(" [{}]: 0x{:08x}", i, w);
}

// Test a simple Blake2s hash
println!("\n=== Testing simple Blake2s hash ===");

// Hash 32 zero bytes
let input = [0u8; 32];
println!("Input: 32 zero bytes");

let mut result = [0u8; 32];
blake2s(&mut result, &input, 32);

println!("\nBlake2s output as u32 LE words:");
for i in 0..8 {
let word = u32::from_le_bytes(result[i*4..i*4+4].try_into().unwrap());
println!(" [{}]: 0x{:08x}", i, word);
}

// Test thash structure
println!("\n=== Testing thash_4 structure ===");
println!("Cairo thash_4 does:");
println!("1. Uses pre-computed state_seeded from compress([pk_seed(4) || zeros(12)])");
println!("2. Finalize: [address(8) || data(4) || zeros(4)] = 64 bytes");

// Simulate the structure
let address: [u32; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; // all zeros for test
let data: [u32; 4] = [0x11111111, 0x22222222, 0x33333333, 0x44444444];

println!("\nTest address (8 words): all zeros");
println!("Test data (4 words): 0x11111111, 0x22222222, 0x33333333, 0x44444444");

// Build the blocks as Cairo would see them
let mut block1 = [0u8; 64];
for i in 0..4 {
let word = u32::from_le_bytes(pk_seed[i*4..i*4+4].try_into().unwrap());
block1[i*4..i*4+4].copy_from_slice(&word.to_le_bytes());
}
// Rest is zeros

println!("\nBlock 1 (seed block) as u32 words:");
for i in 0..16 {
let word = u32::from_le_bytes(block1[i*4..i*4+4].try_into().unwrap());
if word != 0 || i < 4 {
println!(" [{}]: 0x{:08x}", i, word);
}
}

let mut block2 = [0u8; 64];
// Address (8 words)
for i in 0..8 {
block2[i*4..i*4+4].copy_from_slice(&address[i].to_le_bytes());
}
// Data (4 words)
for i in 0..4 {
block2[(8+i)*4..(8+i)*4+4].copy_from_slice(&data[i].to_le_bytes());
}
// Rest is zeros (4 words)

println!("\nBlock 2 (addr+data block) as u32 words:");
for i in 0..16 {
let word = u32::from_le_bytes(block2[i*4..i*4+4].try_into().unwrap());
println!(" [{}]: 0x{:08x}", i, word);
}
}
Loading
Loading