Skip to content

Commit

Permalink
Hex, PublicKey and EventID classes
Browse files Browse the repository at this point in the history
  • Loading branch information
mmalmi committed Aug 21, 2023
1 parent de72141 commit a7ead39
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/js/components/searchbox/SearchBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ class SearchBox extends Component<Props, State> {
Key.getPubKeyByNip05Address(query).then((pubKey) => {
// if query hasn't changed since we started the request
if (pubKey && query === String(this.props.query || this.inputRef.current.value)) {
this.props.onSelect?.({ key: pubKey });
this.props.onSelect?.({ key: pubKey.toHex() });
}
});
}
Expand Down
6 changes: 4 additions & 2 deletions src/js/nostr/Key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
} from 'nostr-tools';
import { route } from 'preact-router';

import { PublicKey } from '@/utils/Hex.ts';

import localState from '../state/LocalState.ts';
import Helpers from '../utils/Helpers';

Expand Down Expand Up @@ -193,14 +195,14 @@ export default {
console.error(e);
}
},
async getPubKeyByNip05Address(address: string): Promise<string | null> {
async getPubKeyByNip05Address(address: string): Promise<PublicKey | null> {
try {
const [localPart, domain] = address.split('@');
const url = `https://${domain}/.well-known/nostr.json?name=${localPart}`;
const response = await fetch(url);
const json = await response.json();
const names = json.names;
return names[localPart] || null;
return new PublicKey(names[localPart]) || null;
} catch (error) {
console.error(error);
return null;
Expand Down
57 changes: 57 additions & 0 deletions src/js/utils/Hex.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, expect, it } from 'vitest';

import { EventID, PublicKey } from '@/utils/Hex';

describe('PublicKey', () => {
it('should convert npub bech32 to hex', () => {
const bech32 = 'npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk';
const hex = '4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0';
const publicKey = new PublicKey(bech32);
expect(publicKey.toHex()).toEqual(hex);
expect(publicKey.toBech32()).toEqual(bech32);
});

it('should init from hex', () => {
const hex = '4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0';
const publicKey = new PublicKey(hex);
expect(publicKey.toHex()).toEqual(hex);
expect(publicKey.toBech32()).toEqual(
'npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk',
);
});

it('should fail with too long hex', () => {
const hex =
'4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd04523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0';
expect(() => new PublicKey(hex)).toThrow();
});

it('equals(hexStr)', () => {
const hex = '4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0';
const publicKey = new PublicKey(hex);
expect(publicKey.equals(hex)).toEqual(true);
});

it('equals(PublicKey)', () => {
const hex = '4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0';
const publicKey = new PublicKey(hex);
const publicKey2 = new PublicKey(hex);
expect(publicKey.equals(publicKey2)).toEqual(true);
});

it('equals(bech32)', () => {
const bech32 = 'npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk';
const publicKey = new PublicKey(bech32);
expect(publicKey.equals(bech32)).toEqual(true);
});
});

describe('EventID', () => {
it('should convert note id bech32 to hex', () => {
const noteBech32 = 'note1wdyajan9c9d72wanqe2l34lxgdu3q5esglhquusfkg34fqq6462qh4cjd5';
const noteHex = '7349d97665c15be53bb30655f8d7e6437910533047ee0e7209b22354801aae94';
const eventId = new EventID(noteBech32);
expect(eventId.toHex()).toEqual(noteHex);
expect(eventId.toBech32()).toEqual(noteBech32);
});
});
88 changes: 88 additions & 0 deletions src/js/utils/Hex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as bech32 from 'bech32-buffer';

import Helpers from '@/utils/Helpers.tsx';

function bech32ToHex(str: string): string {
try {
const { data } = bech32.decode(str);
const addr = Helpers.arrayToHex(data);
return addr;
} catch (e) {
throw new Error('The provided string is not a valid bech32 address: ' + str);
}
}

export class Hex {
value: string;

constructor(str: string, expectedLength?: number) {
this.validateHex(str, expectedLength);
this.value = str;
}

private validateHex(str: string, expectedLength?: number): void {
if (!/^[0-9a-fA-F]+$/.test(str)) {
throw new Error(`The provided string is not a valid hex value: "${str}"`);
}

if (expectedLength && str.length !== expectedLength) {
throw new Error(
`The provided hex value does not match the expected length of ${expectedLength} characters: ${str}`,
);
}
}

toBech32(prefix: string): string {
if (!prefix) {
throw new Error('prefix is required');
}

const bytesArray = this.value.match(/.{1,2}/g);
const bytes = new Uint8Array(bytesArray!.map((byte) => parseInt(byte, 16)));
return bech32.encode(prefix, bytes);
}

toHex(): string {
return this.value;
}
}

export class EventID extends Hex {
constructor(str: string) {
if (str.startsWith('note')) {
str = bech32ToHex(str);
}
super(str, 64);
}

toBech32(): string {
return super.toBech32('note');
}

equals(other: EventID | string): boolean {
if (typeof other === 'string') {
other = new EventID(other);
}
return this.value === other.value;
}
}

export class PublicKey extends Hex {
constructor(str: string) {
if (str.startsWith('npub')) {
str = bech32ToHex(str);
}
super(str, 64);
}

toBech32(): string {
return super.toBech32('npub');
}

equals(other: PublicKey | string): boolean {
if (typeof other === 'string') {
other = new PublicKey(other);
}
return this.value === other.value;
}
}
4 changes: 2 additions & 2 deletions src/js/views/Note.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { useEffect } from 'preact/hooks';
import { route } from 'preact-router';

import { EventID } from '@/utils/Hex.ts';
import View from '@/views/View.tsx';

import CreateNoteForm from '../components/create/CreateNoteForm';
import EventComponent from '../components/events/EventComponent';
import Key from '../nostr/Key';
import { translate as t } from '../translations/Translation.mjs';

const Note = (props) => {
useEffect(() => {
const nostrBech32Id = Key.toNostrBech32Address(props.id, 'note');
const nostrBech32Id = new EventID(props.id).toBech32();
if (nostrBech32Id && props.id !== nostrBech32Id) {
route(`/${nostrBech32Id}`, true);
return;
Expand Down
32 changes: 15 additions & 17 deletions src/js/views/profile/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import SimpleImageModal from '@/components/modal/Image.tsx';
import { useProfile } from '@/nostr/hooks/useProfile.ts';
import { getEventReplyingTo, isRepost } from '@/nostr/utils.ts';
import useLocalState from '@/state/useLocalState.ts';
import { PublicKey } from '@/utils/Hex.ts';
import ProfileHelmet from '@/views/profile/Helmet.tsx';

import Feed from '../../components/feed/Feed.tsx';
Expand Down Expand Up @@ -60,21 +61,20 @@ function Profile(props) {
}, [profile]);

useEffect(() => {
const pub = props.id;
const npubComputed = Key.toNostrBech32Address(pub, 'npub');
try {
const pub = new PublicKey(props.id);
const npubComputed = pub.toBech32();

if (npubComputed && npubComputed !== pub) {
route(`/${npubComputed}`, true);
return;
}
if (npubComputed !== props.id) {
route(`/${npubComputed}`, true);
return;
}

const hexPubComputed = Key.toNostrHexAddress(pub) || '';
setHexPub(pub.toHex());
setNpub(npubComputed);
} catch (e) {
let nostrAddress = props.id;

if (hexPubComputed) {
setHexPub(hexPubComputed);
setNpub(Key.toNostrBech32Address(hexPubComputed, 'npub') || '');
} else {
let nostrAddress = pub;
if (!nostrAddress.match(/.+@.+\..+/)) {
if (nostrAddress.match(/.+\..+/)) {
nostrAddress = '_@' + nostrAddress;
Expand All @@ -85,11 +85,8 @@ function Profile(props) {

Key.getPubKeyByNip05Address(nostrAddress).then((pubKey) => {
if (pubKey) {
const npubComputed = Key.toNostrBech32Address(pubKey, 'npub');
if (npubComputed && npubComputed !== pubKey) {
setNpub(npubComputed);
setHexPub(pubKey);
}
setNpub(pubKey.toBech32());
setHexPub(pubKey.toHex());
} else {
setNpub(''); // To indicate not found
}
Expand All @@ -99,6 +96,7 @@ function Profile(props) {
setTimeout(() => {
window.prerenderReady = true;
}, 1000);

return () => {
setIsMyProfile(false);
};
Expand Down

0 comments on commit a7ead39

Please sign in to comment.