Initial Contractless PHP crypto modules
This commit is contained in:
commit
40bdc3dd79
|
|
@ -0,0 +1,5 @@
|
|||
/native/
|
||||
/vendor/
|
||||
/interop/fixtures/
|
||||
/interop/target/
|
||||
/interop/*.exe
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
# Contractless PHP Crypto Modules
|
||||
|
||||
These native PHP modules provide the cryptography required by
|
||||
`php-contractless-rpc`:
|
||||
|
||||
- Skein-256 hashing through PHP-Skein-Hash
|
||||
- Contractless-compatible Falcon-512 signing and verification through liboqs
|
||||
|
||||
Contractless uses fixed 666-byte Falcon signatures and a specific FN-DSA raw
|
||||
message format. The included liboqs-php patch adds the SHAKE256 operation needed
|
||||
to reproduce that format exactly.
|
||||
|
||||
## Supported Environment
|
||||
|
||||
The automated installation currently supports 64-bit Debian and Ubuntu systems.
|
||||
This includes Ubuntu running through WSL.
|
||||
|
||||
Native modules are tied to the operating system, CPU architecture, and PHP
|
||||
extension API. They should normally be compiled on the machine where they will
|
||||
run.
|
||||
|
||||
## Build Requirements
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y \
|
||||
php-cli php-dev \
|
||||
swig cmake ninja-build \
|
||||
build-essential git pkg-config libssl-dev
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
chmod +x build-linux.sh install-linux.sh
|
||||
./build-linux.sh
|
||||
```
|
||||
|
||||
This downloads and compiles:
|
||||
|
||||
- A minimal liboqs build containing `Falcon-padded-512`
|
||||
- The patched liboqs-php extension
|
||||
- The PHP-Skein-Hash extension
|
||||
|
||||
Building does not install or enable the modules.
|
||||
|
||||
## Install
|
||||
|
||||
After a successful build:
|
||||
|
||||
```bash
|
||||
sudo ./install-linux.sh
|
||||
```
|
||||
|
||||
The installer copies both modules into the active PHP extension directory,
|
||||
creates `contractless-crypto.ini`, enables it through `phpenmod`, and verifies
|
||||
that PHP can see both modules.
|
||||
|
||||
Confirm the installation manually:
|
||||
|
||||
```bash
|
||||
php -m | grep -E '^(skein|oqsphp)$'
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```text
|
||||
oqsphp
|
||||
skein
|
||||
```
|
||||
|
||||
Apache or PHP-FPM may need to be restarted:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart apache2
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart php8.3-fpm
|
||||
```
|
||||
|
||||
Use the PHP version installed on the server in the service name.
|
||||
|
||||
## Compatibility Test
|
||||
|
||||
The compatibility test proves that PHP and Contractless Rust produce matching
|
||||
Skein hashes and mutually valid Falcon signatures:
|
||||
|
||||
```bash
|
||||
cd interop
|
||||
cargo run --release
|
||||
cd ..
|
||||
php tests/native.php
|
||||
```
|
||||
|
||||
The generated private key is a disposable test fixture. Never use it as an
|
||||
application wallet.
|
||||
|
||||
## Using The Modules
|
||||
|
||||
Applications normally use these modules through `php-contractless-rpc` rather
|
||||
than calling them directly.
|
||||
|
||||
Direct Skein hashing:
|
||||
|
||||
```php
|
||||
$digest = skein_hash($data, 256);
|
||||
```
|
||||
|
||||
Direct Contractless Falcon handling requires the public-key SHAKE256 prefix and
|
||||
raw-message marker. `php-contractless-rpc` applies that format automatically,
|
||||
so application developers should use its `NativeCrypto` class.
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
third_party="$root/native/third-party"
|
||||
mkdir -p "$third_party"
|
||||
|
||||
if [[ ! -d "$third_party/liboqs/.git" ]]; then
|
||||
git clone --depth 1 https://github.com/open-quantum-safe/liboqs.git \
|
||||
"$third_party/liboqs"
|
||||
fi
|
||||
|
||||
cmake -S "$third_party/liboqs" -B "$third_party/liboqs/build" -GNinja \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DOQS_BUILD_ONLY_LIB=ON \
|
||||
-DOQS_MINIMAL_BUILD="SIG_falcon_padded_512"
|
||||
ninja -C "$third_party/liboqs/build"
|
||||
|
||||
if [[ ! -d "$third_party/liboqs-php/.git" ]]; then
|
||||
git clone --depth 1 https://github.com/Muzosh/liboqs-php.git \
|
||||
"$third_party/liboqs-php"
|
||||
fi
|
||||
|
||||
(
|
||||
cd "$third_party/liboqs-php"
|
||||
if git apply --check "$root/oqsphp-contractless.patch"; then
|
||||
git apply "$root/oqsphp-contractless.patch"
|
||||
elif ! git apply --reverse --check "$root/oqsphp-contractless.patch"; then
|
||||
echo "The Contractless liboqs-php patch cannot be applied cleanly." >&2
|
||||
exit 1
|
||||
fi
|
||||
LIBOQS_ROOT="$third_party/liboqs" bash build.sh
|
||||
)
|
||||
|
||||
if [[ ! -d "$third_party/php-skein/.git" ]]; then
|
||||
git clone --depth 1 https://github.com/jedisct1/PHP-Skein-Hash.git \
|
||||
"$third_party/php-skein"
|
||||
fi
|
||||
|
||||
(
|
||||
cd "$third_party/php-skein"
|
||||
phpize
|
||||
./configure
|
||||
make
|
||||
)
|
||||
|
||||
cat <<EOF
|
||||
|
||||
Native extensions built:
|
||||
$third_party/liboqs-php/build/oqsphp.so
|
||||
$third_party/php-skein/modules/skein.so
|
||||
|
||||
Add both absolute paths to php.ini:
|
||||
extension=$third_party/liboqs-php/build/oqsphp.so
|
||||
extension=$third_party/php-skein/modules/skein.so
|
||||
EOF
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
oqs_module="$root/native/third-party/liboqs-php/build/oqsphp.so"
|
||||
skein_module="$root/native/third-party/php-skein/modules/skein.so"
|
||||
|
||||
if [[ ! -f "$oqs_module" || ! -f "$skein_module" ]]; then
|
||||
echo "The modules have not been built. Run ./build-linux.sh first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
extension_dir="$(php-config --extension-dir)"
|
||||
php_version="$(php -r 'echo PHP_MAJOR_VERSION . \".\" . PHP_MINOR_VERSION;')"
|
||||
ini_dir="/etc/php/$php_version/mods-available"
|
||||
|
||||
install -m 0755 "$oqs_module" "$extension_dir/oqsphp.so"
|
||||
install -m 0755 "$skein_module" "$extension_dir/skein.so"
|
||||
mkdir -p "$ini_dir"
|
||||
printf '%s\n' 'extension=skein.so' 'extension=oqsphp.so' \
|
||||
> "$ini_dir/contractless-crypto.ini"
|
||||
|
||||
if command -v phpenmod >/dev/null 2>&1; then
|
||||
phpenmod contractless-crypto
|
||||
fi
|
||||
|
||||
php -m | grep -E '^(skein|oqsphp)$'
|
||||
echo "Contractless PHP crypto modules installed and enabled for PHP $php_version."
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "contractless-crypto-interop"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"fn-dsa",
|
||||
"rand",
|
||||
"skein",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fn-dsa"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afb39d94be3f4538ccfdf23dc69a89904c9b802331b1975538fcc251973593f9"
|
||||
dependencies = [
|
||||
"fn-dsa-comm",
|
||||
"fn-dsa-kgen",
|
||||
"fn-dsa-sign",
|
||||
"fn-dsa-vrfy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fn-dsa-comm"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28104bba227c79bff8ffe34dff7c5a5631fd790dfd4d4f0a84b01952ef7c6f46"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fn-dsa-kgen"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8a04fe82799cc1550d32778b415648d1962a7b4b4860ff5f2e6ca32494fc9aa"
|
||||
dependencies = [
|
||||
"fn-dsa-comm",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fn-dsa-sign"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ffb7d9c94e56de7e64019849b3b6110b1495690da2e9e1decef8f1744b3bab4"
|
||||
dependencies = [
|
||||
"fn-dsa-comm",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fn-dsa-vrfy"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc96b904b213160c8804cc2642ccd15cb968318a64648fc5ca3edab9153cda2b"
|
||||
dependencies = [
|
||||
"fn-dsa-comm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[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.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "skein"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96a90a220ab98dbbfeabeae7c558a79f37839c10b9ef55c77082a741a463cab7"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"threefish",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "threefish"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a693d0c8cf16973fac5a93fbe47b8c6452e7097d4fcac49f3d7a18e39c76e62e"
|
||||
|
||||
[[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 = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "contractless-crypto-interop"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
fn-dsa = "0.3.0"
|
||||
rand = "0.8"
|
||||
skein = "0.1.0"
|
||||
digest = "0.10"
|
||||
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Contractless PHP Crypto Interoperability
|
||||
|
||||
This harness verifies that the native libraries packaged by
|
||||
`contractless-php-crypto` produce data compatible with Contractless.
|
||||
|
||||
## Results
|
||||
|
||||
- The C implementation wrapped by `jedisct1/PHP-Skein-Hash` produces the
|
||||
same 32-byte Skein-256 output as the Rust `skein` crate used by
|
||||
Contractless.
|
||||
- Unmodified `Falcon-padded-512` message handling is not compatible with
|
||||
Contractless `fn-dsa 0.3.0`.
|
||||
- `Falcon-padded-512` keys and fixed signatures use the same Contractless
|
||||
sizes: 897-byte public keys, 1,281-byte private keys, and 666-byte
|
||||
signatures.
|
||||
- Both signing implementations are compatible when liboqs receives the
|
||||
message framing used by Contractless:
|
||||
|
||||
```text
|
||||
SHAKE256(public_key, 64 bytes) || 0x00 || 0x00 || original_message
|
||||
```
|
||||
|
||||
The two zero bytes represent raw-message mode and an empty domain context.
|
||||
|
||||
With that adapter:
|
||||
|
||||
- Contractless Rust signatures verify through liboqs.
|
||||
- Liboqs signatures verify through Contractless Rust.
|
||||
|
||||
## Required liboqs algorithm
|
||||
|
||||
Use `Falcon-padded-512`. Do not use variable-length `Falcon-512`.
|
||||
|
||||
## Regression requirement
|
||||
|
||||
Run these tests whenever Contractless changes its `fn-dsa`, `skein`, or
|
||||
signature-framing dependencies. Matching key and signature lengths alone
|
||||
does not establish compatibility.
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
#include <oqs/oqs.h>
|
||||
#include <oqs/sha3.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static uint8_t *read_file(const char *path, size_t *length) {
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (file == NULL) {
|
||||
fprintf(stderr, "Could not open %s\n", path);
|
||||
exit(1);
|
||||
}
|
||||
fseek(file, 0, SEEK_END);
|
||||
long size = ftell(file);
|
||||
rewind(file);
|
||||
if (size < 0) {
|
||||
fprintf(stderr, "Could not determine size of %s\n", path);
|
||||
exit(1);
|
||||
}
|
||||
uint8_t *bytes = malloc((size_t)size);
|
||||
if (bytes == NULL || fread(bytes, 1, (size_t)size, file) != (size_t)size) {
|
||||
fprintf(stderr, "Could not read %s\n", path);
|
||||
exit(1);
|
||||
}
|
||||
fclose(file);
|
||||
*length = (size_t)size;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static void write_file(const char *path, const uint8_t *bytes, size_t length) {
|
||||
FILE *file = fopen(path, "wb");
|
||||
if (file == NULL || fwrite(bytes, 1, length, file) != length) {
|
||||
fprintf(stderr, "Could not write %s\n", path);
|
||||
exit(1);
|
||||
}
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
OQS_SIG *sig = OQS_SIG_new(OQS_SIG_alg_falcon_padded_512);
|
||||
if (sig == NULL) {
|
||||
fprintf(stderr, "Falcon-padded-512 is unavailable\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t public_key_len;
|
||||
size_t private_key_len;
|
||||
size_t message_len;
|
||||
size_t rust_signature_len;
|
||||
uint8_t *public_key = read_file("fixtures/public-key.bin", &public_key_len);
|
||||
uint8_t *private_key = read_file("fixtures/private-key.bin", &private_key_len);
|
||||
uint8_t *message = read_file("fixtures/message.bin", &message_len);
|
||||
uint8_t *rust_signature =
|
||||
read_file("fixtures/rust-signature.bin", &rust_signature_len);
|
||||
size_t adapted_message_len = 64 + 2 + message_len;
|
||||
uint8_t *adapted_message = malloc(adapted_message_len);
|
||||
OQS_SHA3_shake256(adapted_message, 64, public_key, public_key_len);
|
||||
adapted_message[64] = 0;
|
||||
adapted_message[65] = 0;
|
||||
for (size_t i = 0; i < message_len; i++) {
|
||||
adapted_message[66 + i] = message[i];
|
||||
}
|
||||
|
||||
printf("liboqs public_key_bytes=%zu private_key_bytes=%zu signature_bytes=%zu\n",
|
||||
sig->length_public_key, sig->length_secret_key, sig->length_signature);
|
||||
|
||||
if (public_key_len != sig->length_public_key ||
|
||||
private_key_len != sig->length_secret_key ||
|
||||
rust_signature_len != sig->length_signature) {
|
||||
fprintf(stderr, "Contractless and liboqs object sizes differ\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
OQS_STATUS rust_valid = OQS_SIG_verify(
|
||||
sig, adapted_message, adapted_message_len,
|
||||
rust_signature, rust_signature_len, public_key
|
||||
);
|
||||
printf("Rust signature verified in liboqs: %s\n",
|
||||
rust_valid == OQS_SUCCESS ? "true" : "false");
|
||||
|
||||
uint8_t *oqs_signature = malloc(sig->length_signature);
|
||||
size_t oqs_signature_len = sig->length_signature;
|
||||
OQS_STATUS signed_ok = OQS_SIG_sign(
|
||||
sig, oqs_signature, &oqs_signature_len,
|
||||
adapted_message, adapted_message_len, private_key
|
||||
);
|
||||
if (signed_ok != OQS_SUCCESS) {
|
||||
fprintf(stderr, "liboqs signing failed\n");
|
||||
return 1;
|
||||
}
|
||||
printf("liboqs produced signature_bytes=%zu\n", oqs_signature_len);
|
||||
write_file("fixtures/oqs-signature.bin", oqs_signature, oqs_signature_len);
|
||||
|
||||
free(oqs_signature);
|
||||
free(adapted_message);
|
||||
free(rust_signature);
|
||||
free(message);
|
||||
free(private_key);
|
||||
free(public_key);
|
||||
OQS_SIG_free(sig);
|
||||
|
||||
return rust_valid == OQS_SUCCESS ? 0 : 2;
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#include "skein.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static uint8_t *read_file(const char *path, size_t *length) {
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (file == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
fseek(file, 0, SEEK_END);
|
||||
long size = ftell(file);
|
||||
rewind(file);
|
||||
uint8_t *bytes = malloc((size_t)size);
|
||||
if (size < 0 || bytes == NULL ||
|
||||
fread(bytes, 1, (size_t)size, file) != (size_t)size) {
|
||||
return NULL;
|
||||
}
|
||||
fclose(file);
|
||||
*length = (size_t)size;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
size_t message_len;
|
||||
size_t rust_hash_len;
|
||||
uint8_t *message = read_file("fixtures/message.bin", &message_len);
|
||||
uint8_t *rust_hash = read_file("fixtures/rust-skein256.bin", &rust_hash_len);
|
||||
if (message == NULL || rust_hash == NULL || rust_hash_len != 32) {
|
||||
fprintf(stderr, "Could not read Skein fixtures\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t extension_hash[32];
|
||||
Skein_256_Ctxt_t context;
|
||||
Skein_256_Init(&context, 256);
|
||||
Skein_256_Update(&context, message, message_len);
|
||||
Skein_256_Final(&context, extension_hash);
|
||||
|
||||
int equal = 1;
|
||||
for (size_t i = 0; i < sizeof(extension_hash); i++) {
|
||||
if (extension_hash[i] != rust_hash[i]) {
|
||||
equal = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
printf("PHP-Skein-Hash output matches Contractless Rust: %s\n",
|
||||
equal ? "true" : "false");
|
||||
|
||||
free(rust_hash);
|
||||
free(message);
|
||||
return equal ? 0 : 2;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
use digest::Digest;
|
||||
use fn_dsa::{
|
||||
signature_size, sign_key_size, vrfy_key_size, KeyPairGenerator,
|
||||
KeyPairGeneratorStandard, SigningKey, SigningKeyStandard, VerifyingKey,
|
||||
VerifyingKeyStandard, DOMAIN_NONE, FN_DSA_LOGN_512, HASH_ID_RAW,
|
||||
};
|
||||
use rand::rngs::OsRng;
|
||||
use skein::Skein256;
|
||||
use digest::consts::U32;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
const MESSAGE: &[u8] = b"Contractless FN-DSA interoperability\x00binary\xffmessage";
|
||||
|
||||
fn main() {
|
||||
let fixture_dir = Path::new("fixtures");
|
||||
fs::create_dir_all(fixture_dir).expect("create fixture directory");
|
||||
|
||||
let private_key_path = fixture_dir.join("private-key.bin");
|
||||
let public_key_path = fixture_dir.join("public-key.bin");
|
||||
let message_path = fixture_dir.join("message.bin");
|
||||
let rust_signature_path = fixture_dir.join("rust-signature.bin");
|
||||
let oqs_signature_path = fixture_dir.join("oqs-signature.bin");
|
||||
|
||||
if !private_key_path.exists() {
|
||||
let mut generator = KeyPairGeneratorStandard::default();
|
||||
let mut private_key = vec![0u8; sign_key_size(FN_DSA_LOGN_512)];
|
||||
let mut public_key = vec![0u8; vrfy_key_size(FN_DSA_LOGN_512)];
|
||||
generator.keygen(
|
||||
FN_DSA_LOGN_512,
|
||||
&mut OsRng,
|
||||
&mut private_key,
|
||||
&mut public_key,
|
||||
);
|
||||
fs::write(&private_key_path, private_key).expect("write private key");
|
||||
fs::write(&public_key_path, public_key).expect("write public key");
|
||||
fs::write(&message_path, MESSAGE).expect("write message");
|
||||
}
|
||||
|
||||
let private_key = fs::read(&private_key_path).expect("read private key");
|
||||
let public_key = fs::read(&public_key_path).expect("read public key");
|
||||
let message = fs::read(&message_path).expect("read message");
|
||||
|
||||
let mut signing_key =
|
||||
SigningKeyStandard::decode(&private_key).expect("decode Contractless private key");
|
||||
let mut rust_signature = vec![0u8; signature_size(FN_DSA_LOGN_512)];
|
||||
signing_key.sign(
|
||||
&mut OsRng,
|
||||
&DOMAIN_NONE,
|
||||
&HASH_ID_RAW,
|
||||
&message,
|
||||
&mut rust_signature,
|
||||
);
|
||||
fs::write(&rust_signature_path, &rust_signature).expect("write Rust signature");
|
||||
|
||||
let verifying_key =
|
||||
VerifyingKeyStandard::decode(&public_key).expect("decode Contractless public key");
|
||||
assert!(
|
||||
verifying_key.verify(
|
||||
&rust_signature,
|
||||
&DOMAIN_NONE,
|
||||
&HASH_ID_RAW,
|
||||
&message
|
||||
),
|
||||
"Rust signature did not verify in Rust"
|
||||
);
|
||||
|
||||
let mut hasher = Skein256::<U32>::new();
|
||||
hasher.update(&message);
|
||||
let hash = hasher.finalize();
|
||||
fs::write(fixture_dir.join("rust-skein256.bin"), hash).expect("write Skein hash");
|
||||
|
||||
println!("Rust FN-DSA signature verified in Rust.");
|
||||
println!("public_key_bytes={}", public_key.len());
|
||||
println!("private_key_bytes={}", private_key.len());
|
||||
println!("signature_bytes={}", rust_signature.len());
|
||||
|
||||
if oqs_signature_path.exists() {
|
||||
let oqs_signature = fs::read(&oqs_signature_path).expect("read liboqs signature");
|
||||
let valid = verifying_key.verify(
|
||||
&oqs_signature,
|
||||
&DOMAIN_NONE,
|
||||
&HASH_ID_RAW,
|
||||
&message,
|
||||
);
|
||||
println!("liboqs signature verified in Rust: {valid}");
|
||||
if !valid {
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
--- a/oqsphp.i
|
||||
+++ b/oqsphp.i
|
||||
@@ -25,6 +25,7 @@
|
||||
%module(directors="1") oqsphp
|
||||
%{
|
||||
#include "oqs/oqs.h"
|
||||
+#include "oqs/sha3.h"
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <exception>
|
||||
@@ -39,6 +40,20 @@
|
||||
%feature("director");
|
||||
%feature("php:type", "1");
|
||||
|
||||
+%inline %{
|
||||
+std::string contractless_shake256(const std::string &input, size_t output_length) {
|
||||
+ std::string output;
|
||||
+ output.resize(output_length);
|
||||
+ OQS_SHA3_shake256(
|
||||
+ reinterpret_cast<uint8_t *>(output.data()),
|
||||
+ output_length,
|
||||
+ reinterpret_cast<const uint8_t *>(input.data()),
|
||||
+ input.length()
|
||||
+ );
|
||||
+ return output;
|
||||
+}
|
||||
+%}
|
||||
+
|
||||
%inline %{
|
||||
class CustomException : public std::exception {
|
||||
public:
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$fixtures = dirname(__DIR__) . '/interop/fixtures';
|
||||
$message = file_get_contents("$fixtures/message.bin");
|
||||
$publicKey = file_get_contents("$fixtures/public-key.bin");
|
||||
$privateKey = file_get_contents("$fixtures/private-key.bin");
|
||||
$rustSignature = file_get_contents("$fixtures/rust-signature.bin");
|
||||
$rustSkein = file_get_contents("$fixtures/rust-skein256.bin");
|
||||
|
||||
if (in_array(false, [$message, $publicKey, $privateKey, $rustSignature, $rustSkein], true)) {
|
||||
throw new RuntimeException(
|
||||
'Interop fixtures are missing. Run cargo run --release inside interop first.',
|
||||
);
|
||||
}
|
||||
if (!function_exists('skein_hash') || !function_exists('contractless_shake256')) {
|
||||
throw new RuntimeException('The Contractless PHP crypto modules are not loaded.');
|
||||
}
|
||||
if (!class_exists('OQS_SIGNATURE')) {
|
||||
throw new RuntimeException('The liboqs-php module is not loaded.');
|
||||
}
|
||||
|
||||
$phpSkein = skein_hash($message, 256);
|
||||
if (!is_string($phpSkein) || !hash_equals($rustSkein, $phpSkein)) {
|
||||
throw new RuntimeException('PHP Skein-256 does not match Contractless Rust.');
|
||||
}
|
||||
|
||||
$falcon = new OQS_SIGNATURE('Falcon-padded-512');
|
||||
$adapted = contractless_shake256($publicKey, 64) . "\0\0" . $message;
|
||||
if ($falcon->verify($adapted, $rustSignature, $publicKey) !== 0) {
|
||||
throw new RuntimeException('PHP/liboqs did not verify the Contractless Rust signature.');
|
||||
}
|
||||
|
||||
$phpSignature = '';
|
||||
if ($falcon->sign($phpSignature, $adapted, $privateKey) !== 0
|
||||
|| strlen($phpSignature) !== 666
|
||||
|| $falcon->verify($adapted, $phpSignature, $publicKey) !== 0
|
||||
) {
|
||||
throw new RuntimeException('PHP/liboqs Falcon signing failed.');
|
||||
}
|
||||
|
||||
echo "Native Skein and Falcon interoperability tests passed.\n";
|
||||
Loading…
Reference in New Issue