Skip to content

Notes on naddr decoder (NIP-19) #74

@nusapuksic

Description

@nusapuksic

This is about #68

When trying to build a decoder, one of the first snags I got tangled in was the length of the naddr, so I ended up using this, to work around the length restrictions.

public function decodeBech32(string $bech32): array
    {
        $charset = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';

        // Find the separator (1)
        $pos = strrpos($bech32, '1');
        if ($pos === false) {
            throw new Exception('Invalid Bech32 string');
        }

        // Extract human-readable part (HRP)
        $hrp = substr($bech32, 0, $pos);

        // Extract data part
        $data_part = substr($bech32, $pos + 1);
        $data = [];
        for ($i = 0; $i < strlen($data_part); $i++) {
            $data[] = strpos($charset, $data_part[$i]);
            if ($data[$i] === false) {
                throw new Exception('Invalid character in Bech32 string');
            }
        }

        return [$hrp, $data];
    }

I used the following to muddle through the TLV...

  public function decodeAndParseNostrBech32(string $bech32): array
  {
      list($hrp, $data) = $this->decodeBech32($bech32);
      // Convert 5-bit data to 8-bit data
      $decodedData = $this->convertBits($data, 5, 8);

      if ($hrp == 'npub') {
          return [$hrp, $binaryData];
      }

      // Step 2: Parse the binary data into TLV format
      $tlvData = $this->parseTLV($binaryData);

      return [$hrp, $tlvData];
  }


public function parseTLV(array $binaryData): array
  {
      $parsedTLVs = [];
      $offset = 0;

      while ($offset < count($binaryData)) {
          if ($offset + 1 >= count($binaryData)) {
              throw new Exception("Incomplete TLV data");
          }

          // Read the Type (T) and Length (L)
          $type = $binaryData[$offset];
          $length = $binaryData[$offset + 1];
          $offset += 2;

          // Ensure we have enough data for the value
          if ($offset + $length > count($binaryData)) {
              break;
          } else {
              // Extract the Value (V)
              $value = array_slice($binaryData, $offset, $length);
          }

          $offset += $length;

          // Add the TLV to the parsed array
          $parsedTLVs[] = [
              'type' => $type,
              'length' => $length,
              'value' => $value,
          ];
      }

      return $parsedTLVs;
  }

And this insanity, to get the TLVs into data:

// naddr - a nostr replaceable event coordinate
list($hrp, $tlv) = $bech32Decoder->decodeAndParseNostrBech32($naddr);

if ($hrp !== 'naddr') {
  throw new \Exception('Not a naddr scheme link');
}
$typeSlug = 0; // slug
$typeRelays = 1; // relays
$typeAuthor = 2; // author
$typeKind = 3; // kind
$relays = [];
foreach ($tlv as $item) {
    if ($item['type'] === $typeSlug) {
        $slug = implode('', array_map('chr', $item['value']));
    }
    if ($item['type'] === $typeRelays) {
        $relays[] = implode('', array_map('chr', $item['value']));
    }
    if ($item['type'] === $typeAuthor) {
        $str = '';
        foreach ($item['value'] as $byte) {
            $str .= str_pad(dechex($byte), 2, '0', STR_PAD_LEFT);
        }
        $author = $str;
    }
    if ($item['type'] === $typeKind) {
        // big-endian integer
        $intValue = 0;
        foreach ($item['value'] as $byte) {
            $intValue = ($intValue << 8) | $byte;
        }
        $kind = (string) $intValue;
    }

}

I'm just dumping this on you, but maybe you can get something out of it. Have fun 😄

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions