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); } }