From d05ecaaf69a3dab09e6a66991a727e93330a0c8f Mon Sep 17 00:00:00 2001 From: Sebastix Date: Mon, 11 Dec 2023 14:18:18 +0100 Subject: [PATCH 1/6] init Nip19 --- src/Nip19/Nip19.php | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/Nip19/Nip19.php diff --git a/src/Nip19/Nip19.php b/src/Nip19/Nip19.php new file mode 100644 index 0000000..03b066b --- /dev/null +++ b/src/Nip19/Nip19.php @@ -0,0 +1,50 @@ + 90) { + throw new \Exception('Bech32 string cannot exceed 90 characters in length'); + } + if ($length < 8) { + throw new \Exception('Bech32 string is too short'); + } + } + + /** + * @throws \Exception + */ + public function encode(string $value) + { + $prefix = ''; + switch($prefix) { + case 'npub': + break; + case 'nsec': + break; + case 'note': + break; + default: + throw new \Exception('Unexpected value'); + } + } + +} From b24f4dde80731098af29b2523f436107e93b813c Mon Sep 17 00:00:00 2001 From: Sebastix Date: Fri, 25 Oct 2024 12:17:38 +0200 Subject: [PATCH 2/6] NIP-19 work in progress --- src/Examples/bech32-encoded-entities.php | 52 ++++++++++ src/Nip19/Nip19.php | 50 ---------- src/Nip19/Nip19Helper.php | 121 +++++++++++++++++++++++ src/Nip19/TLVEnum.php | 16 +++ 4 files changed, 189 insertions(+), 50 deletions(-) create mode 100644 src/Examples/bech32-encoded-entities.php delete mode 100644 src/Nip19/Nip19.php create mode 100644 src/Nip19/Nip19Helper.php create mode 100644 src/Nip19/TLVEnum.php diff --git a/src/Examples/bech32-encoded-entities.php b/src/Examples/bech32-encoded-entities.php new file mode 100644 index 0000000..3aa848c --- /dev/null +++ b/src/Examples/bech32-encoded-entities.php @@ -0,0 +1,52 @@ +encodeNote($id); + // Expected result: + // note1g0asggj90s06mmrgckk3sdu2hvkxymtt0pmep9e73zxsnx8kem2qulye77 + print $note . PHP_EOL; + + // Encode a profile pubkey or npub, this already works. + $key = new Key(); + $pubkey = '06639a386c9c1014217622ccbcf40908c4f1a0c33e23f8d6d68f4abf655f8f71'; + // Alternative: $npub = $key->convertPublicKeyToBech32($pubkey); + $npub = $nip19->encodeNpub($pubkey); + // Expected result: + // npub1qe3e5wrvnsgpggtkytxteaqfprz0rgxr8c3l34kk3a9t7e2l3acslezefe + print $npub . PHP_EOL; + + // Using the more generic encode method + $note1 = $nip19->encode($id, 'note'); + print $note1 . PHP_EOL; + + // TODO: + // Encode to nevent with TLV data + + // Encode it bech32 encoded nevent ID, + // $nevent = $nip19->encodeEvent($id); + // Expected result: + // nevent1qqsy87cyyfzhc8ada35vttgcx79tktrzd44hsausjulg3rgfnrmva4qey0p0j + // print $nevent . PHP_EOL; + + // Encode to nprofile with TLV data + + // Encode to naddr with TLV data + + // Decode a bech32 encoded entity to an event ID. + $nevent = ''; + +} catch (Exception $e) { + print $e->getMessage() . PHP_EOL; +} diff --git a/src/Nip19/Nip19.php b/src/Nip19/Nip19.php deleted file mode 100644 index 03b066b..0000000 --- a/src/Nip19/Nip19.php +++ /dev/null @@ -1,50 +0,0 @@ - 90) { - throw new \Exception('Bech32 string cannot exceed 90 characters in length'); - } - if ($length < 8) { - throw new \Exception('Bech32 string is too short'); - } - } - - /** - * @throws \Exception - */ - public function encode(string $value) - { - $prefix = ''; - switch($prefix) { - case 'npub': - break; - case 'nsec': - break; - case 'note': - break; - default: - throw new \Exception('Unexpected value'); - } - } - -} diff --git a/src/Nip19/Nip19Helper.php b/src/Nip19/Nip19Helper.php new file mode 100644 index 0000000..2833996 --- /dev/null +++ b/src/Nip19/Nip19Helper.php @@ -0,0 +1,121 @@ + 90) { + throw new \Exception('Bech32 string cannot exceed 90 characters in length'); + } + if ($length < 8) { + throw new \Exception('Bech32 string is too short'); + } + + // switch ($prefix) { + // case 'npub': + // break; + // case 'nsec': + // break; + // case 'note': + // break; + // default: + // throw new \Exception('Unexpected value'); + // } + } + + public function encode(string $value, string $prefix): string + { + return $this->convertToBech32($value, $prefix); + } + + public function encodeNote(string $event_hex): string + { + return $this->convertToBech32($event_hex, 'note'); + } + + public function encodeEvent(string $event_hex): string + { + $hexInBin = hex2bin($event_hex); // Convert hex formatted string to binary string. + if (strlen($hexInBin) !== 32) { + throw new \Exception(sprintf('This is an invalid ID: %s', $event_hex)); + } + // todo process TLV + return $this->convertToBech32($event_hex, 'nevent'); + } + + public function encodeProfile(string $profile_hex): string + { + // todo + return ''; + } + + public function encodeAddr(string $event_hex): string + { + // todo + return ''; + } + + /** + * @param string $pubkey + * @return string + */ + public function encodeNpub(string $pubkey): string + { + $key = new Key(); + return $key->convertPublicKeyToBech32($pubkey); + } + + /** + * @param string $seckey + * @return string + */ + public function encodeNsec(string $seckey): string + { + $key = new Key(); + return $key->convertPrivateKeyToBech32($seckey); + } + + private function convertToBech32(string $key, string $prefix): string + { + $str = ''; + + $dec = []; + $split = str_split($key, 2); + foreach ($split as $item) { + $dec[] = hexdec($item); + } + $bytes = convertBits($dec, count($dec), 8, 5); + $str = encode($prefix, $bytes); + + return $str; + } + + private function readTLVEntry($value) {} + + private function writeTLVEntry($value, string $type) {} +} diff --git a/src/Nip19/TLVEnum.php b/src/Nip19/TLVEnum.php new file mode 100644 index 0000000..069f159 --- /dev/null +++ b/src/Nip19/TLVEnum.php @@ -0,0 +1,16 @@ + Date: Tue, 29 Oct 2024 12:32:06 +0100 Subject: [PATCH 3/6] NIP-19 work in progress --- src/Examples/bech32-encoded-entities.php | 22 +++++- src/Message/RequestMessage.php | 1 - src/Nip19/Nip19Helper.php | 94 +++++++++++++++++++++--- src/Nip19/TLVEnum.php | 8 +- 4 files changed, 106 insertions(+), 19 deletions(-) diff --git a/src/Examples/bech32-encoded-entities.php b/src/Examples/bech32-encoded-entities.php index 3aa848c..d67d92e 100644 --- a/src/Examples/bech32-encoded-entities.php +++ b/src/Examples/bech32-encoded-entities.php @@ -34,19 +34,35 @@ // TODO: // Encode to nevent with TLV data - // Encode it bech32 encoded nevent ID, - // $nevent = $nip19->encodeEvent($id); + $nevent = $nip19->encodeEvent($id, ['wss://nostr.sebastix.dev'], $pubkey, 1); // Expected result: // nevent1qqsy87cyyfzhc8ada35vttgcx79tktrzd44hsausjulg3rgfnrmva4qey0p0j // print $nevent . PHP_EOL; - // Encode to nprofile with TLV data + // Encode to pubkey profile with TLV data + $pubkey = '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d'; + $relays = ['wss://r.x.com', 'wss://djbas.sadkb.com']; + $nprofile = $nip19->encodeProfile($pubkey, $relays); + // Expected result with TLV items: + // - pubkey: 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d + // - relay: wss://r.x.com + // - relay: wss://djbas.sadkb.com + // TODO // Encode to naddr with TLV data + // TODO // Decode a bech32 encoded entity to an event ID. $nevent = ''; + // TODO + // Decode to event with TLV data + $profile_id = 'nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p'; + // Expected result with TLV items: + // - pubkey: 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d + // - relay: wss://r.x.com + // - relay: wss://djbas.sadkb.com + } catch (Exception $e) { print $e->getMessage() . PHP_EOL; } diff --git a/src/Message/RequestMessage.php b/src/Message/RequestMessage.php index a5d48fa..e78970a 100644 --- a/src/Message/RequestMessage.php +++ b/src/Message/RequestMessage.php @@ -5,7 +5,6 @@ namespace swentel\nostr\Message; use swentel\nostr\MessageInterface; -use swentel\nostr\Filter; class RequestMessage implements MessageInterface { diff --git a/src/Nip19/Nip19Helper.php b/src/Nip19/Nip19Helper.php index 2833996..e204f94 100644 --- a/src/Nip19/Nip19Helper.php +++ b/src/Nip19/Nip19Helper.php @@ -6,6 +6,7 @@ use BitWasp\Bech32\Exception\Bech32Exception; use swentel\nostr\Key\Key; +use swentel\nostr\Nip19\TLVEnum; use function BitWasp\Bech32\convertBits; use function BitWasp\Bech32\encode; @@ -24,7 +25,9 @@ class Nip19Helper */ protected $prefix; - public function __construct() {} + public function __construct() + { + } public function decode(string $bech32string) { @@ -58,23 +61,61 @@ public function encodeNote(string $event_hex): string return $this->convertToBech32($event_hex, 'note'); } - public function encodeEvent(string $event_hex): string + /** + * @param string $event_hex + * @param array $relays + * @param string $author + * @param int $kind + * @return string + * @throws \Exception + */ + public function encodeEvent(string $event_hex, array $relays = [], string $author = '', int $kind = null): string { - $hexInBin = hex2bin($event_hex); // Convert hex formatted string to binary string. - if (strlen($hexInBin) !== 32) { - throw new \Exception(sprintf('This is an invalid ID: %s', $event_hex)); + $data = ''; + $prefix = 'nevent'; + $event_hex_in_bin = hex2bin($event_hex); // Convert hex formatted pubkey string to binary string. + if (strlen($event_hex_in_bin) !== 32) { + throw new \Exception(sprintf('This is an invalid event ID: %s', $event_hex)); + } + // TODO: process TLV entries + $tlvEntry = $this->writeTLVEntry(TLVEnum::Special, $event_hex_in_bin); + // Optional + if (!(empty($relays))) { + foreach ($relays as $relay) { + // Encode as ascii. + //$relay = implode('', unpack('C*', $relay)); + // Alternative which requires the icon PHP extension installed on the host machine. + // $relay = iconv('UTF-8', 'ASCII', $relay); + // decode ascii relay string + $tlvEntry .= $this->writeTLVEntry(TLVEnum::Relay, urlencode($relay)); + } + } + // Optional + if (!(empty($author))) { + if (strlen(hex2bin($author)) !== 32) { + throw new \Exception(sprintf('This is an invalid author ID: %s', $event_hex)); + } + // Convert hex formatted pubkey to 32-bit binary value. + $tlvEntry .= $this->writeTLVEntry(TLVEnum::Author, hex2bin($author)); } - // todo process TLV - return $this->convertToBech32($event_hex, 'nevent'); + // Optional + if ($kind !== null) { + // Convert kint int to unsigned integer, big-endian. + $v = pack('N', $kind); + $tlvEntry .= $this->writeTLVEntry(TLVEnum::Kind, $v); + } + $data = $tlvEntry; + + return $this->encodeBech32($data, $prefix); } - public function encodeProfile(string $profile_hex): string + public function encodeProfile(string $pubkey, array $relays = []): string { // todo return ''; } - public function encodeAddr(string $event_hex): string + public function encodeAddr(string $event_hex, int $kind, string $DTag, array $relays = []): string { // todo return ''; @@ -100,6 +141,19 @@ public function encodeNsec(string $seckey): string return $key->convertPrivateKeyToBech32($seckey); } + public function encodeBech32(string $value, string $prefix): string + { + // TODO + $bytes = []; + return encode($prefix, $bytes); + } + + /** + * @param string $key + * @param string $prefix + * @return string + * @throws Bech32Exception + */ private function convertToBech32(string $key, string $prefix): string { $str = ''; @@ -115,7 +169,25 @@ private function convertToBech32(string $key, string $prefix): string return $str; } - private function readTLVEntry($value) {} + private function readTLVEntry(string $data, TLVEnum $type) + { + } - private function writeTLVEntry($value, string $type) {} + /** + * @param \swentel\nostr\Nip19\TLVEnum $type + * @param string $value + * Binary string. + * @return string + */ + private function writeTLVEntry(TLVEnum $type, string $value) + { + // TODO + return $value; + } + + private function encodeTLV(Object $TLV): array + { + + return []; + } } diff --git a/src/Nip19/TLVEnum.php b/src/Nip19/TLVEnum.php index 069f159..4f02345 100644 --- a/src/Nip19/TLVEnum.php +++ b/src/Nip19/TLVEnum.php @@ -9,8 +9,8 @@ */ enum TLVEnum: int { - case TLVDefault = 0; - case TLVRelay = 1; - case TLVAuthor = 2; - case TLVKind = 3; + case Special = 0; + case Relay = 1; + case Author = 2; + case Kind = 3; } \ No newline at end of file From 58f9aaced0fc825fafc1fc5439b7bb6981b9749d Mon Sep 17 00:00:00 2001 From: Sebastix Date: Thu, 21 Nov 2024 21:44:29 +0100 Subject: [PATCH 4/6] wip encoding TLV identifiers --- src/Examples/bech32-encoded-entities.php | 23 ++-- src/Nip19/Nip19Helper.php | 145 +++++++++++++++++++---- 2 files changed, 133 insertions(+), 35 deletions(-) diff --git a/src/Examples/bech32-encoded-entities.php b/src/Examples/bech32-encoded-entities.php index d67d92e..60b441b 100644 --- a/src/Examples/bech32-encoded-entities.php +++ b/src/Examples/bech32-encoded-entities.php @@ -10,7 +10,7 @@ try { $nip19 = new Nip19Helper(); // Helper. $id = '43fb0422457c1fadec68c5ad18378abb2c626d6b787790973e888d0998f6ced4'; // This is an event hex id. - //$id = 'fb0422457c1fadec68c5ad18378abb2c626d6b787790973e888d0998f6ce'; // Invalid ID. + //$id = 'fb0422457c1fadec68c5ad18378abb2c626d6b787790973e888d0998f6ce'; // This is an invalid ID. // Encode it to a bech32 encoded note ID. $note = $nip19->encodeNote($id); @@ -21,28 +21,29 @@ // Encode a profile pubkey or npub, this already works. $key = new Key(); $pubkey = '06639a386c9c1014217622ccbcf40908c4f1a0c33e23f8d6d68f4abf655f8f71'; - // Alternative: $npub = $key->convertPublicKeyToBech32($pubkey); + // Alternative way: $npub = $key->convertPublicKeyToBech32($pubkey); $npub = $nip19->encodeNpub($pubkey); // Expected result: // npub1qe3e5wrvnsgpggtkytxteaqfprz0rgxr8c3l34kk3a9t7e2l3acslezefe print $npub . PHP_EOL; - // Using the more generic encode method + // Alternative: using the more generic encode method with the encode() method. $note1 = $nip19->encode($id, 'note'); - print $note1 . PHP_EOL; + //print $note1 . PHP_EOL; - // TODO: + // TODO // Encode to nevent with TLV data - + $pubkey = 'npub1qe3e5wrvnsgpggtkytxteaqfprz0rgxr8c3l34kk3a9t7e2l3acslezefe'; // This npub will be converted to a hex formatted pubkey. $nevent = $nip19->encodeEvent($id, ['wss://nostr.sebastix.dev'], $pubkey, 1); // Expected result: - // nevent1qqsy87cyyfzhc8ada35vttgcx79tktrzd44hsausjulg3rgfnrmva4qey0p0j - // print $nevent . PHP_EOL; + // nevent1qqsy87cyyfzhc8ada35vttgcx79tktrzd44hsausjulg3rgfnrmva4qey0p0js + print $nevent . PHP_EOL; + // TODO // Encode to pubkey profile with TLV data $pubkey = '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d'; $relays = ['wss://r.x.com', 'wss://djbas.sadkb.com']; - $nprofile = $nip19->encodeProfile($pubkey, $relays); + //$nprofile = $nip19->encodeProfile($pubkey, $relays); // Expected result with TLV items: // - pubkey: 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d // - relay: wss://r.x.com @@ -52,11 +53,11 @@ // Encode to naddr with TLV data // TODO - // Decode a bech32 encoded entity to an event ID. + // Decode a bech32 encoded event entity to an event ID. $nevent = ''; // TODO - // Decode to event with TLV data + // Decode a bech32 encoded profile entity with TLV data $profile_id = 'nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p'; // Expected result with TLV items: // - pubkey: 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d diff --git a/src/Nip19/Nip19Helper.php b/src/Nip19/Nip19Helper.php index e204f94..c2ac0db 100644 --- a/src/Nip19/Nip19Helper.php +++ b/src/Nip19/Nip19Helper.php @@ -9,6 +9,7 @@ use swentel\nostr\Nip19\TLVEnum; use function BitWasp\Bech32\convertBits; +use function BitWasp\Bech32\decode; use function BitWasp\Bech32\encode; /** @@ -25,10 +26,6 @@ class Nip19Helper */ protected $prefix; - public function __construct() - { - } - public function decode(string $bech32string) { $length = strlen($bech32string); @@ -65,20 +62,15 @@ public function encodeNote(string $event_hex): string * @param string $event_hex * @param array $relays * @param string $author - * @param int $kind + * @param int|null $kind * @return string - * @throws \Exception */ public function encodeEvent(string $event_hex, array $relays = [], string $author = '', int $kind = null): string { $data = ''; $prefix = 'nevent'; - $event_hex_in_bin = hex2bin($event_hex); // Convert hex formatted pubkey string to binary string. - if (strlen($event_hex_in_bin) !== 32) { - throw new \Exception(sprintf('This is an invalid event ID: %s', $event_hex)); - } // TODO: process TLV entries - $tlvEntry = $this->writeTLVEntry(TLVEnum::Special, $event_hex_in_bin); + $tlvEntry = $this->writeTLVEntry($prefix, TLVEnum::Special, $event_hex); // Optional if (!(empty($relays))) { foreach ($relays as $relay) { @@ -87,22 +79,24 @@ public function encodeEvent(string $event_hex, array $relays = [], string $autho // Alternative which requires the icon PHP extension installed on the host machine. // $relay = iconv('UTF-8', 'ASCII', $relay); // decode ascii relay string - $tlvEntry .= $this->writeTLVEntry(TLVEnum::Relay, urlencode($relay)); + $tlvEntry .= $this->writeTLVEntry($prefix, TLVEnum::Relay, urlencode($relay)); } } // Optional if (!(empty($author))) { + if (str_starts_with($author, 'npub') === true) { + $author = $this->convertToHex($author); + } if (strlen(hex2bin($author)) !== 32) { - throw new \Exception(sprintf('This is an invalid author ID: %s', $event_hex)); + throw new \RuntimeException(sprintf('This is an invalid author ID: %s', $event_hex)); } // Convert hex formatted pubkey to 32-bit binary value. - $tlvEntry .= $this->writeTLVEntry(TLVEnum::Author, hex2bin($author)); + $tlvEntry .= $this->writeTLVEntry($prefix, TLVEnum::Author, $author); } // Optional if ($kind !== null) { // Convert kint int to unsigned integer, big-endian. - $v = pack('N', $kind); - $tlvEntry .= $this->writeTLVEntry(TLVEnum::Kind, $v); + $tlvEntry .= $this->writeTLVEntry($prefix, TLVEnum::Kind, $kind); } $data = $tlvEntry; @@ -144,7 +138,7 @@ public function encodeNsec(string $seckey): string public function encodeBech32(string $value, string $prefix): string { // TODO - $bytes = []; + $bytes = [$value]; return encode($prefix, $bytes); } @@ -169,25 +163,128 @@ private function convertToBech32(string $key, string $prefix): string return $str; } - private function readTLVEntry(string $data, TLVEnum $type) + /** + * Convert a bech32 encoded string to hex string. + * + * @param string $key + * + * @return string + */ + private function convertToHex(string $key): string { + $str = ''; + try { + $decoded = decode($key); + $data = $decoded[1]; + $bytes = convertBits($data, count($data), 5, 8, false); + foreach ($bytes as $item) { + $str .= str_pad(dechex($item), 2, '0', STR_PAD_LEFT); + } + } catch (Bech32Exception $e) { + throw new \RuntimeException($e->getMessage()); + } + + return $str; } + private function readTLVEntry(string $data, TLVEnum $type): string {} + /** + * https://nips.nostr.com/19#shareable-identifiers-with-extra-metadata + * + * @param string $prefix * @param \swentel\nostr\Nip19\TLVEnum $type - * @param string $value - * Binary string. + * @param string|int $value * @return string */ - private function writeTLVEntry(TLVEnum $type, string $value) + private function writeTLVEntry(string $prefix, TLVEnum $type, string|int $value): string { - // TODO - return $value; + $buf = ''; + try { + if ($prefix === 'nevent' && $type->name === 'Special') { + $event_hex_in_bin = hex2bin($value); + if (strlen($event_hex_in_bin) !== 32) { + throw new \RuntimeException(sprintf('This is an invalid event ID: %s', $value)); + } + // TODO Return the 32 bytes of the event id. + $byte_array = unpack('C*', $event_hex_in_bin); + $uint32 = $this->uInt32($value, null); + $buf .= $uint32; + //print $event_hex_in_bin; + } + if ($prefix === 'nevent' && $type->name === 'Author') { + // TODO Return the 32 bytes of the pubkey of the event + $buf .= $this->uInt32($value, null); + } + if ($prefix === 'nevent' && $type->name === 'Relay') { + // TODO encoded as ascii + $buf .= $value; + } + if ($prefix === 'nevent' && $type->name === 'Kind') { + // TODO Return the 32-bit unsigned integer of the kind, big-endian + $buf .= $this->uInt32($value, true); + } + + if ($prefix === 'profile') { + + } + if ($prefix === 'naddr') { + + } + + } catch (Bech32Exception $e) { + throw new \RuntimeException($e->getMessage()); + } + return $buf; } private function encodeTLV(Object $TLV): array { - + // TODO return []; } + + /** + * @param $i + * @return mixed|string + */ + private static function uInt8($i) + { + return is_int($i) ? pack("C", $i) : unpack("C", $i)[1]; + } + + /** + * @param $i + * @param $endianness + * @return mixed + */ + private static function uInt16($i, $endianness = false) + { + $f = is_int($i) ? "pack" : "unpack"; + + if ($endianness === true) { // big-endian + $i = $f("n", $i); + } elseif ($endianness === false) { // little-endian + $i = $f("v", $i); + } elseif ($endianness === null) { // machine byte order + $i = $f("S", $i); + } + + return is_array($i) ? $i[1] : $i; + } + + private static function uInt32($i, $endianness = false) + { + $f = is_int($i) ? "pack" : "unpack"; + + if ($endianness === true) { // big-endian + $i = $f("N", $i); + } elseif ($endianness === false) { // little-endian + $i = $f("V", $i); + } elseif ($endianness === null) { // machine byte order + $i = $f("L", $i); + } + + return is_array($i) ? $i[1] : $i; + } } From 663f8d0054f03cb05f33f628c001a4be0b7790fe Mon Sep 17 00:00:00 2001 From: Sebastix Date: Fri, 22 Nov 2024 08:33:47 +0100 Subject: [PATCH 5/6] wip encoding TLV identifiers --- src/Examples/bech32-encoded-entities.php | 51 ++++++++--- src/Nip19/Nip19Helper.php | 106 ++++++++++++++++------- 2 files changed, 112 insertions(+), 45 deletions(-) diff --git a/src/Examples/bech32-encoded-entities.php b/src/Examples/bech32-encoded-entities.php index 60b441b..ee2ad06 100644 --- a/src/Examples/bech32-encoded-entities.php +++ b/src/Examples/bech32-encoded-entities.php @@ -7,40 +7,61 @@ use swentel\nostr\Key\Key; use swentel\nostr\Nip19\Nip19Helper; +/** + * Example snippet where we encode key ands ids into bech32 formatted entities. + */ + try { - $nip19 = new Nip19Helper(); // Helper. - $id = '43fb0422457c1fadec68c5ad18378abb2c626d6b787790973e888d0998f6ced4'; // This is an event hex id. + $nip19 = new Nip19Helper(); // The helper. + $event_id = '43fb0422457c1fadec68c5ad18378abb2c626d6b787790973e888d0998f6ced4'; // This is an event hex id. //$id = 'fb0422457c1fadec68c5ad18378abb2c626d6b787790973e888d0998f6ce'; // This is an invalid ID. // Encode it to a bech32 encoded note ID. - $note = $nip19->encodeNote($id); + $note = $nip19->encodeNote($event_id); // Expected result: // note1g0asggj90s06mmrgckk3sdu2hvkxymtt0pmep9e73zxsnx8kem2qulye77 print $note . PHP_EOL; + // Alternative: using the more generic encode method with the encode() method. + $note1 = $nip19->encode($event_id, 'note'); + //print $note1 . PHP_EOL; // Encode a profile pubkey or npub, this already works. - $key = new Key(); $pubkey = '06639a386c9c1014217622ccbcf40908c4f1a0c33e23f8d6d68f4abf655f8f71'; - // Alternative way: $npub = $key->convertPublicKeyToBech32($pubkey); + // Alternative way: + // $key = new Key(); + // $npub = $key->convertPublicKeyToBech32($pubkey); $npub = $nip19->encodeNpub($pubkey); // Expected result: // npub1qe3e5wrvnsgpggtkytxteaqfprz0rgxr8c3l34kk3a9t7e2l3acslezefe print $npub . PHP_EOL; - // Alternative: using the more generic encode method with the encode() method. - $note1 = $nip19->encode($id, 'note'); - //print $note1 . PHP_EOL; - // TODO // Encode to nevent with TLV data + $nevent_1 = $nip19->encodeEvent($event_id); + $nevent_11 = $nip19->encode($event_id, 'nevent'); + // Expected result, checked with nak: + // $ ./nak encode nevent 43fb0422457c1fadec68c5ad18378abb2c626d6b787790973e888d0998f6ced4 + // nevent1qqsy87cyyfzhc8ada35vttgcx79tktrzd44hsausjulg3rgfnrmva4qey0p0j + print $nevent_1 . PHP_EOL; + + // TODO + $nevent_2 = $nip19->encodeEvent($event_id, [], $pubkey, 1); + // Expected result, checked with nak: + // $ ./nak encode nevent --author 06639a386c9c1014217622ccbcf40908c4f1a0c33e23f8d6d68f4abf655f8f71 43fb0422457c1fadec68c5ad18378abb2c626d6b787790973e888d0998f6ced4 + // nevent1qqsy87cyyfzhc8ada35vttgcx79tktrzd44hsausjulg3rgfnrmva4qzyqrx8x3cdjwpq9ppwc3ve085pyyvfudqcvlz87xk668540m9t78hz5s5hp9 + print $nevent_2 . PHP_EOL; + + // TODO $pubkey = 'npub1qe3e5wrvnsgpggtkytxteaqfprz0rgxr8c3l34kk3a9t7e2l3acslezefe'; // This npub will be converted to a hex formatted pubkey. - $nevent = $nip19->encodeEvent($id, ['wss://nostr.sebastix.dev'], $pubkey, 1); - // Expected result: - // nevent1qqsy87cyyfzhc8ada35vttgcx79tktrzd44hsausjulg3rgfnrmva4qey0p0js - print $nevent . PHP_EOL; + $relays = ['wss://nostr.sebastix.dev']; + //$nevent_3 = $nip19->encodeEvent($event_id, $relays, $pubkey, 1); + // Expected result, checked with nak: + // $ ./nak encode nevent --author 06639a386c9c1014217622ccbcf40908c4f1a0c33e23f8d6d68f4abf655f8f71 --relay wss://nostr.sebastix.dev 43fb0422457c1fadec68c5ad18378abb2c626d6b787790973e888d0998f6ced4 + // nevent1qqsy87cyyfzhc8ada35vttgcx79tktrzd44hsausjulg3rgfnrmva4qprpmhxue69uhkummnw3ezuum9vfshxarf0qhxgetkqgsqvcu68pkfcyq5y9mz9n9u7sys33835rpnuglc6mtg7j4lv40c7ugdggh4t + //print $nevent_3 . PHP_EOL; // TODO - // Encode to pubkey profile with TLV data + // Encode to nprofile with TLV data $pubkey = '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d'; $relays = ['wss://r.x.com', 'wss://djbas.sadkb.com']; //$nprofile = $nip19->encodeProfile($pubkey, $relays); @@ -48,6 +69,8 @@ // - pubkey: 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d // - relay: wss://r.x.com // - relay: wss://djbas.sadkb.com + // $ ./nak encode nprofile --relay wss://r.x.com --relay wss://djbas.sadkb.com 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d + // nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p // TODO // Encode to naddr with TLV data diff --git a/src/Nip19/Nip19Helper.php b/src/Nip19/Nip19Helper.php index c2ac0db..2f89ce6 100644 --- a/src/Nip19/Nip19Helper.php +++ b/src/Nip19/Nip19Helper.php @@ -7,7 +7,6 @@ use BitWasp\Bech32\Exception\Bech32Exception; use swentel\nostr\Key\Key; use swentel\nostr\Nip19\TLVEnum; - use function BitWasp\Bech32\convertBits; use function BitWasp\Bech32\decode; use function BitWasp\Bech32\encode; @@ -15,9 +14,14 @@ /** * NIP-19 bech32-encoded entities * - * Example reference: https://github.com/nbd-wtf/go-nostr/blob/master/nip19/nip19.go + * Example reference Go library: https://github.com/nbd-wtf/go-nostr/blob/master/nip19/nip19.go + * Example reference Javascript library: + * Example reference Python library: * * https://github.com/Bit-Wasp/bech32/blob/master/src/bech32.php + * + * Other helpfull resources + * https://www.geeksforgeeks.org/how-to-convert-byte-array-to-string-in-php/ */ class Nip19Helper { @@ -74,12 +78,7 @@ public function encodeEvent(string $event_hex, array $relays = [], string $autho // Optional if (!(empty($relays))) { foreach ($relays as $relay) { - // Encode as ascii. - //$relay = implode('', unpack('C*', $relay)); - // Alternative which requires the icon PHP extension installed on the host machine. - // $relay = iconv('UTF-8', 'ASCII', $relay); - // decode ascii relay string - $tlvEntry .= $this->writeTLVEntry($prefix, TLVEnum::Relay, urlencode($relay)); + array_push($tlvEntry, ...$this->writeTLVEntry($prefix, TLVEnum::Relay, $relay)); } } // Optional @@ -90,14 +89,14 @@ public function encodeEvent(string $event_hex, array $relays = [], string $autho if (strlen(hex2bin($author)) !== 32) { throw new \RuntimeException(sprintf('This is an invalid author ID: %s', $event_hex)); } - // Convert hex formatted pubkey to 32-bit binary value. - $tlvEntry .= $this->writeTLVEntry($prefix, TLVEnum::Author, $author); - } - // Optional - if ($kind !== null) { - // Convert kint int to unsigned integer, big-endian. - $tlvEntry .= $this->writeTLVEntry($prefix, TLVEnum::Kind, $kind); + array_push($tlvEntry, ...$this->writeTLVEntry($prefix, TLVEnum::Author, $author)); + //$tlvEntry = array_merge($tlvEntry, $this->writeTLVEntry($prefix, TLVEnum::Author, $author)); } +// // Optional +// if ($kind !== null) { +// // Convert kind int to unsigned integer, big-endian. +// array_push($tlvEntry, ...$this->writeTLVEntry($prefix, TLVEnum::Kind, $kind)); +// } $data = $tlvEntry; return $this->encodeBech32($data, $prefix); @@ -135,10 +134,8 @@ public function encodeNsec(string $seckey): string return $key->convertPrivateKeyToBech32($seckey); } - public function encodeBech32(string $value, string $prefix): string + public function encodeBech32(array $bytes, string $prefix): string { - // TODO - $bytes = [$value]; return encode($prefix, $bytes); } @@ -152,11 +149,17 @@ private function convertToBech32(string $key, string $prefix): string { $str = ''; + /** @var array $dec */ + // This is our bits array with decimal formatted values. $dec = []; + /** @var array $split */ + // Split string into data chucks with a max length of 2 chars each chunk. This will create the byte array. $split = str_split($key, 2); foreach ($split as $item) { + // Loop over the byte array and convert each chuck from a hex formatted value into a decimal formatted chunks. $dec[] = hexdec($item); } + // Convert the bits array to a bytes array. $bytes = convertBits($dec, count($dec), 8, 5); $str = encode($prefix, $bytes); @@ -195,34 +198,47 @@ private function readTLVEntry(string $data, TLVEnum $type): string {} * @param string $prefix * @param \swentel\nostr\Nip19\TLVEnum $type * @param string|int $value - * @return string + * @return array */ - private function writeTLVEntry(string $prefix, TLVEnum $type, string|int $value): string + private function writeTLVEntry(string $prefix, TLVEnum $type, string|int $value): array { - $buf = ''; + $buf = []; try { if ($prefix === 'nevent' && $type->name === 'Special') { + // TODO Return the 32 bytes of the event id. + + // Convert hexadecimal string to its binary representation. $event_hex_in_bin = hex2bin($value); if (strlen($event_hex_in_bin) !== 32) { throw new \RuntimeException(sprintf('This is an invalid event ID: %s', $value)); } - // TODO Return the 32 bytes of the event id. - $byte_array = unpack('C*', $event_hex_in_bin); - $uint32 = $this->uInt32($value, null); - $buf .= $uint32; - //print $event_hex_in_bin; + +// // Convert to ... ? +// $uint32 = $this->uInt32($value, null); +// // Bytes or bits (?) array with decimal formatted chunks +// $byte_array = unpack('C*', $value); +// // Some from byte array to string methods: +// $strFromByteArray1 = implode(array_map("chr", $byte_array)); +// $strFromByteArray2 = pack('C*', ...$byte_array); +// $strFromByteArray3 = ''; +// foreach ($byte_array as $byte) { +// $strFromByteArray3 .= chr($byte); +// } + + $buf = $this->convertToBytes($value); } if ($prefix === 'nevent' && $type->name === 'Author') { // TODO Return the 32 bytes of the pubkey of the event - $buf .= $this->uInt32($value, null); + $buf = $this->convertToBytes($value); } if ($prefix === 'nevent' && $type->name === 'Relay') { - // TODO encoded as ascii - $buf .= $value; + // TODO + $relay = urlencode($value); + //$buf = $this->convertToBytes($relay); } if ($prefix === 'nevent' && $type->name === 'Kind') { // TODO Return the 32-bit unsigned integer of the kind, big-endian - $buf .= $this->uInt32($value, true); + //$buf = $this->uInt32($value, true); } if ($prefix === 'profile') { @@ -231,10 +247,12 @@ private function writeTLVEntry(string $prefix, TLVEnum $type, string|int $value) if ($prefix === 'naddr') { } - } catch (Bech32Exception $e) { throw new \RuntimeException($e->getMessage()); } + if (empty($buf)) { + throw new \RuntimeException('$buf is empty'); + } return $buf; } @@ -244,6 +262,27 @@ private function encodeTLV(Object $TLV): array return []; } + /** + * @param string $str + * @return array + * @throws Bech32Exception + */ + private function convertToBytes(string $str): array + { + /** @var array $dec */ + // This will our bits array with decimal formatted values. + $dec = []; + /** @var array $split */ + // Split string into data chucks with a max length of 2 chars each chunk. This will create the byte array. + $split = str_split($str, 2); + foreach ($split as $item) { + // Loop over the byte array and convert each chuck from a hex formatted value into a decimal formatted chunks so we get our bits array. + $dec[] = hexdec($item); + } + // Convert bits to bytes. + return convertBits($dec, count($dec), 8, 5); + } + /** * @param $i * @return mixed|string @@ -273,6 +312,11 @@ private static function uInt16($i, $endianness = false) return is_array($i) ? $i[1] : $i; } + /** + * @param $i + * @param $endianness + * @return mixed + */ private static function uInt32($i, $endianness = false) { $f = is_int($i) ? "pack" : "unpack"; From 6e509b6a34fd70d7960c30cb4a445313fd71e7fd Mon Sep 17 00:00:00 2001 From: Sebastix Date: Sat, 23 Nov 2024 21:55:46 +0100 Subject: [PATCH 6/6] change reference --- src/Nip19/Nip19Helper.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Nip19/Nip19Helper.php b/src/Nip19/Nip19Helper.php index 2f89ce6..e7e0cb4 100644 --- a/src/Nip19/Nip19Helper.php +++ b/src/Nip19/Nip19Helper.php @@ -15,8 +15,7 @@ * NIP-19 bech32-encoded entities * * Example reference Go library: https://github.com/nbd-wtf/go-nostr/blob/master/nip19/nip19.go - * Example reference Javascript library: - * Example reference Python library: + * Example reference Javascript library: https://github.com/nbd-wtf/nostr-tools/blob/master/nip19.ts * * https://github.com/Bit-Wasp/bech32/blob/master/src/bech32.php *