Contractless-PHP-RPC/src/Crypto/NativeCrypto.php

78 lines
2.6 KiB
PHP
Raw Normal View History

2026-07-25 21:23:28 +00:00
<?php
declare(strict_types=1);
namespace Contractless\Rpc\Crypto;
use Contractless\Rpc\Exception\ContractlessException;
use Contractless\Rpc\Protocol\Binary;
final class NativeCrypto implements CryptoInterface
{
private const ALGORITHM = 'Falcon-padded-512';
/** @var \OQS_SIGNATURE */
private readonly object $falcon;
public function __construct()
{
if (!function_exists('skein_hash')) {
throw new ContractlessException('The PHP Skein extension is not loaded.');
}
if (!function_exists('contractless_shake256')) {
throw new ContractlessException(
'The Contractless liboqs-php SHAKE256 helper is not loaded.',
);
}
if (!class_exists('OQS_SIGNATURE')) {
throw new ContractlessException('The liboqs-php extension is not loaded.');
}
$this->falcon = new \OQS_SIGNATURE(self::ALGORITHM);
}
public function skein256(string $message): string
{
$digest = skein_hash($message, 256);
if (!is_string($digest) || strlen($digest) !== 32) {
throw new ContractlessException('Skein-256 hashing failed.');
}
return $digest;
}
public function sign(string $message, string $privateKey, string $publicKey): string
{
Binary::length($privateKey, 1281, 'Falcon private key');
Binary::length($publicKey, 897, 'Falcon public key');
$adapted = $this->adaptMessage($message, $publicKey);
$signature = '';
$status = $this->falcon->sign($signature, $adapted, $privateKey);
if ($status !== 0 || strlen($signature) !== 666) {
throw new ContractlessException('Falcon-padded-512 signing failed.');
}
return $signature;
}
public function verify(string $message, string $signature, string $publicKey): bool
{
Binary::length($signature, 666, 'Falcon signature');
Binary::length($publicKey, 897, 'Falcon public key');
return $this->falcon->verify(
$this->adaptMessage($message, $publicKey),
$signature,
$publicKey,
) === 0;
}
private function adaptMessage(string $message, string $publicKey): string
{
$publicKeyHash = contractless_shake256($publicKey, 64);
if (!is_string($publicKeyHash) || strlen($publicKeyHash) !== 64) {
throw new ContractlessException('SHAKE256 public-key hashing failed.');
}
// fn-dsa uses DOMAIN_NONE and HASH_ID_RAW before the original message.
return $publicKeyHash . "\0\0" . $message;
}
}