Something went wrong. Try again.
A barebones implementation of an atproto PDS in PHP and Slim Framework 4.
pds php atproto
Something went wrong. Try again.
2.9 kB · 103 lines
PHP
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104<?php
declare(strict_types=1);
namespace App\Infrastructure\Crypto;
use App\Domain\Crypto\DidKeyEncoder;use App\Domain\Crypto\Keypair;use Elliptic\EC;use Elliptic\EC\KeyPair as EcKeyPair;use InvalidArgumentException;
/** * secp256k1 (k256) {@see Keypair} backed by simplito/elliptic-php. * * Signatures are returned in atproto's required "compact low-S" form: * the 32-byte big-endian r and s concatenated (64 bytes total), with * s normalised to the lower half of the curve order. */final class Secp256k1Keypair implements Keypair{ public const CURVE = 'k256';
private static ?EC $ec = null;
public function __construct(private readonly EcKeyPair $key) { }
public static function ec(): EC { if (self::$ec === null) { self::$ec = new EC('secp256k1'); } return self::$ec; }
public function sign(string $message): string { $hash = hash('sha256', $message, true); $sig = $this->key->sign(bin2hex($hash), ['canonical' => true]);
$r = self::pad32((string) $sig->r->toString(16)); $s = self::pad32((string) $sig->s->toString(16));
$bin = hex2bin($r . $s); if ($bin === false) { throw new \RuntimeException('Failed to encode signature'); } return $bin; }
public function verify(string $message, string $signature): bool { if (strlen($signature) !== 64) { return false; }
$hash = hash('sha256', $message, true); $r = bin2hex(substr($signature, 0, 32)); $s = bin2hex(substr($signature, 32, 32));
return (bool) $this->key->verify(bin2hex($hash), ['r' => $r, 's' => $s]); }
public function getPublicKeyBytes(): string { // elliptic-php has no type stubs; getPublic() returns mixed. $bin = hex2bin((string) $this->key->getPublic(true, 'hex')); if ($bin === false || strlen($bin) !== 33) { throw new \RuntimeException('Failed to encode compressed public key'); } return $bin; }
public function export(): string { $hex = self::pad32((string) $this->key->getPrivate('hex')); $bin = hex2bin($hex); if ($bin === false || strlen($bin) !== 32) { throw new \RuntimeException('Failed to encode private key'); } return $bin; }
public function getDidKey(): string { return DidKeyEncoder::encodeSecp256k1($this->getPublicKeyBytes()); }
public function getCurveName(): string { return self::CURVE; }
private static function pad32(string $hex): string { if (strlen($hex) > 64) { throw new InvalidArgumentException('hex too long for 32-byte field'); } return str_pad($hex, 64, '0', STR_PAD_LEFT); }}