From 7b098b3cc65a2490da7070007b78425a583c65bc Mon Sep 17 00:00:00 2001 From: nick avlo Date: Mon, 6 Jan 2025 20:52:32 -0500 Subject: [PATCH 01/16] GenericEvent id, added 64-character min & max string length constraint ReqMessage subscriptionId, added 0 < #characters < 65 string length constraint NostrUtil, added hexString validations: - non-null - 128-character string length for Signature/SignatureDeserializer - 64-character string length for all others - (lower)case-sensitive JsonParseTest methods for the above JsonParseTest removed EVENT JSON id --- .../src/main/java/nostr/base/Signature.java | 2 +- .../java/nostr/event/impl/GenericEvent.java | 7 +- .../deserializer/SignatureDeserializer.java | 4 +- .../java/nostr/event/message/ReqMessage.java | 8 +- .../java/nostr/test/base/BaseKeyTest.java | 80 ++++ .../java/nostr/test/base/NostrUtilTest.java | 40 +- .../test/java/nostr/test/event/EventTest.java | 343 +++++++++--------- .../java/nostr/test/json/JsonParseTest.java | 30 +- .../src/main/java/nostr/util/NostrUtil.java | 43 ++- pom.xml | 6 +- 10 files changed, 367 insertions(+), 196 deletions(-) create mode 100644 nostr-java-test/src/test/java/nostr/test/base/BaseKeyTest.java diff --git a/nostr-java-base/src/main/java/nostr/base/Signature.java b/nostr-java-base/src/main/java/nostr/base/Signature.java index 9e6f5c676..3e1ca2d17 100644 --- a/nostr-java-base/src/main/java/nostr/base/Signature.java +++ b/nostr-java-base/src/main/java/nostr/base/Signature.java @@ -31,7 +31,7 @@ public String toString() { public static Signature fromString(String sig) { Signature signature = new Signature(); - signature.setRawData(NostrUtil.hexToBytes(sig)); + signature.setRawData(NostrUtil.hex128ToBytes(sig)); return signature; } } diff --git a/nostr-java-event/src/main/java/nostr/event/impl/GenericEvent.java b/nostr-java-event/src/main/java/nostr/event/impl/GenericEvent.java index 3e73b99bf..ef69b8799 100644 --- a/nostr-java-event/src/main/java/nostr/event/impl/GenericEvent.java +++ b/nostr-java-event/src/main/java/nostr/event/impl/GenericEvent.java @@ -98,7 +98,7 @@ public GenericEvent() { public GenericEvent(@NonNull String id) { this(); - this.id = id; + setId(id); } public GenericEvent(@NonNull PublicKey pubKey, @NonNull Kind kind) { @@ -128,6 +128,11 @@ public GenericEvent(@NonNull PublicKey pubKey, @NonNull Integer kind, @NonNull L updateTagsParents(tags); } + public void setId(String id) { + NostrUtil.validateHexString(id, 64); + this.id = id; + } + @Override public String toBech32() { if (!isSigned()) { diff --git a/nostr-java-event/src/main/java/nostr/event/json/deserializer/SignatureDeserializer.java b/nostr-java-event/src/main/java/nostr/event/json/deserializer/SignatureDeserializer.java index f6917ba2e..3d7e8c08c 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/deserializer/SignatureDeserializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/deserializer/SignatureDeserializer.java @@ -11,14 +11,14 @@ import nostr.util.NostrUtil; public class SignatureDeserializer extends JsonDeserializer { - + @Override public Signature deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { ObjectMapper objectMapper = (ObjectMapper) jsonParser.getCodec(); JsonNode node = objectMapper.readTree(jsonParser); String sigValue = node.asText(); - byte[] rawData = NostrUtil.hexToBytes(sigValue); + byte[] rawData = NostrUtil.hex128ToBytes(sigValue); Signature signature = new Signature(); signature.setRawData(rawData); diff --git a/nostr-java-event/src/main/java/nostr/event/message/ReqMessage.java b/nostr-java-event/src/main/java/nostr/event/message/ReqMessage.java index b750e34f7..17fadf42d 100644 --- a/nostr-java-event/src/main/java/nostr/event/message/ReqMessage.java +++ b/nostr-java-event/src/main/java/nostr/event/message/ReqMessage.java @@ -14,6 +14,7 @@ import nostr.event.impl.Filters; import nostr.event.json.codec.FiltersEncoder; +import java.time.temporal.ValueRange; import java.util.ArrayList; import java.util.List; @@ -32,12 +33,15 @@ public class ReqMessage extends BaseMessage { @JsonProperty private final List filtersList; - public ReqMessage(String subscriptionId, Filters filters) { + public ReqMessage(@NonNull String subscriptionId, Filters filters) { this(subscriptionId, List.of(filters)); } - public ReqMessage(String subscriptionId, List incomingFiltersList) { + public ReqMessage(@NonNull String subscriptionId, List incomingFiltersList) { super(Command.REQ.name()); + if (!ValueRange.of(1, 64).isValidIntValue(subscriptionId.length())) { + throw new IllegalArgumentException(String.format("subscriptionId length must be between 1 and 64 characters but was [%d]", subscriptionId.length())); + } this.subscriptionId = subscriptionId; this.filtersList = new ArrayList<>(); this.filtersList.addAll(incomingFiltersList); diff --git a/nostr-java-test/src/test/java/nostr/test/base/BaseKeyTest.java b/nostr-java-test/src/test/java/nostr/test/base/BaseKeyTest.java new file mode 100644 index 000000000..599e6f300 --- /dev/null +++ b/nostr-java-test/src/test/java/nostr/test/base/BaseKeyTest.java @@ -0,0 +1,80 @@ +package nostr.test.base; + +import nostr.base.PublicKey; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class BaseKeyTest { + public static final String VALID_HEXPUBKEY = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; + public static final String INVALID_HEXPUBKEY_NON_HEX_DIGITS = "XYZdf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; + public static final String INVALID_HEXPUBKEY_LENGTH_TOO_SHORT = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6"; + public static final String INVALID_HEXPUBKEY_LENGTH_TOO_LONG = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f666"; + public static final String VALID_HEXPUBKEY_ALL_ZEROS = "0000000000000000000000000000000000000000000000000000000000000000"; + public static final String VALID_HEXPUBKEY_ALL_FF = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + public static final String INVALID_HEXPUBKEY_HAS_MULTIPLE_UPPERCASE = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + public static final String INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6F"; + + @Test + public void testValidPublicKeyString() { + System.out.println("testValidPublicKeyString"); + assertDoesNotThrow(() -> new PublicKey(VALID_HEXPUBKEY)); + } + + @Test + public void testValidPublicKeyByteArray() { + System.out.println("testValidPublicKeyByteArray"); + assertDoesNotThrow(() -> new PublicKey(VALID_HEXPUBKEY.getBytes(StandardCharsets.UTF_8))); + } + + @Test + public void testInValidNullPublicKeyString() { + System.out.println("testInValidNullPublicKeyString"); + assertThrows(IllegalArgumentException.class, () -> new PublicKey("")); + } + + @Test + public void testInValidPublicKeyNonHexDigits() { + System.out.println("testInValidPublicKeyNonHexDigits"); + assertThrows(IllegalArgumentException.class, () -> new PublicKey(INVALID_HEXPUBKEY_NON_HEX_DIGITS)); + } + + @Test + public void testInValidPublicKeyLengthTooShort() { + System.out.println("testInValidPublicKeyLengthTooShort"); + assertThrows(IllegalArgumentException.class, () -> new PublicKey(INVALID_HEXPUBKEY_LENGTH_TOO_SHORT)); + } + + @Test + public void testInValidPublicKeyLengthTooLong() { + System.out.println("testInValidPublicKeyLengthTooShort"); + assertThrows(IllegalArgumentException.class, () -> new PublicKey(INVALID_HEXPUBKEY_LENGTH_TOO_LONG)); + } + + @Test + public void testValidPublicKeyAllZeros() { + System.out.println("testValidPublicKeyAllZeros"); + assertDoesNotThrow(() -> new PublicKey(VALID_HEXPUBKEY_ALL_ZEROS)); + } + + @Test + public void testValidPublicKeyAllFF() { + System.out.println("testValidPublicKeyAllFF"); + assertDoesNotThrow(() -> new PublicKey(VALID_HEXPUBKEY_ALL_FF)); + } + + @Test + public void testInvalidPublicKeyMultipleUppercase() { + System.out.println("testInvalidPublicKeyMultipleUppercase"); + assertThrows(IllegalArgumentException.class, () -> new PublicKey(INVALID_HEXPUBKEY_HAS_MULTIPLE_UPPERCASE)); + } + + @Test + public void testInvalidPublicKeySingleUppercase() { + System.out.println("testInvalidPublicKeySingleUppercase"); + assertThrows(IllegalArgumentException.class, () -> new PublicKey(INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE)); + } +} diff --git a/nostr-java-test/src/test/java/nostr/test/base/NostrUtilTest.java b/nostr-java-test/src/test/java/nostr/test/base/NostrUtilTest.java index d20bf1631..ffe85c678 100644 --- a/nostr-java-test/src/test/java/nostr/test/base/NostrUtilTest.java +++ b/nostr-java-test/src/test/java/nostr/test/base/NostrUtilTest.java @@ -1,9 +1,31 @@ -package nostr.test.base; - -/** - * - * @author squirrel - */ -public class NostrUtilTest { - -} \ No newline at end of file +package nostr.test.base; + +import lombok.extern.java.Log; +import nostr.util.NostrException; +import nostr.util.NostrUtil; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author squirrel + */ +@Log +public class NostrUtilTest { + /** + * test intended to confirm conversion routines: + * (1) Hex string to byte[], then + * (2) btye[] back to Hex string + * are properly functioning inversions of each other + */ + @Test + public void testHexToBytesHex() throws NostrException { + System.out.println("testBech32HexToBytesToBech32"); + String pubKeyString = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; + assertEquals( + pubKeyString, + NostrUtil.bytesToHex( // (2) + NostrUtil.hexToBytes( // (1) + pubKeyString))); + } +} diff --git a/nostr-java-test/src/test/java/nostr/test/event/EventTest.java b/nostr-java-test/src/test/java/nostr/test/event/EventTest.java index 9d4c4b7a6..bc684f6a7 100644 --- a/nostr-java-test/src/test/java/nostr/test/event/EventTest.java +++ b/nostr-java-test/src/test/java/nostr/test/event/EventTest.java @@ -1,164 +1,179 @@ -package nostr.test.event; - -import lombok.extern.java.Log; -import nostr.base.ElementAttribute; - -import org.junit.jupiter.api.Test; - -import nostr.base.IEncoder; -import nostr.base.PublicKey; -import nostr.base.Relay; -import nostr.crypto.bech32.Bech32; -import nostr.crypto.bech32.Bech32Prefix; -import nostr.event.BaseTag; -import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericMessage; -import nostr.event.impl.GenericTag; -import nostr.event.json.codec.BaseTagEncoder; -import nostr.event.util.Nip05Validator; -import nostr.id.Identity; -import nostr.test.EntityFactory; -import nostr.util.NostrException; -import nostr.util.NostrUtil; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * - * @author squirrel - */ -@Log -public class EventTest { - - public EventTest() { - } - - @Test - public void testCreateTextNoteEvent(){ - log.info("testCreateTextNoteEvent"); - PublicKey publicKey = Identity.generateRandomIdentity().getPublicKey(); - GenericEvent instance = EntityFactory.Events.createTextNoteEvent(publicKey); - instance.update(); - assertNotNull(instance.getId()); - assertNotNull(instance.getCreatedAt()); - assertNull(instance.getSignature()); - final String bech32 = instance.toBech32(); - assertNotNull(bech32); - assertDoesNotThrow(() -> { - assertEquals(Bech32Prefix.NOTE.getCode(), Bech32.decode(bech32).hrp); - }); - } - - @Test - public void testCreateGenericTag() { - log.info("testCreateGenericTag"); - PublicKey publicKey = Identity.generateRandomIdentity().getPublicKey(); - GenericTag genericTag = EntityFactory.Events.createGenericTag(publicKey); - - Relay relay = new Relay("wss://secret.relay.com"); - relay.addNipSupport(1); - relay.addNipSupport(genericTag.getNip()); - var attrs = genericTag.getAttributes(); - for (var a : attrs) { - relay.addNipSupport(a.getNip()); - } - - var encoder = new BaseTagEncoder(genericTag, relay); - var strJsonEvent = encoder.encode(); - - assertDoesNotThrow(() -> { - BaseTag tag = IEncoder.MAPPER.readValue(strJsonEvent, BaseTag.class); - assertEquals(genericTag, tag); - }); - } - - @Test - public void testCreateUnsupportedGenericTagAttribute() { -// try { -// System.out.println("testCreateUnsupportedGenericTagAttribute"); -// PublicKey publicKey = this.identity.getPublicKey(); -// GenericTag genericTag = EntityFactory.Events.createGenericTag(publicKey); -// -// Relay relay = Relay.builder().uri("wss://secret.relay.com").build(); -// relay.addNipSupport(1); -// relay.addNipSupport(genericTag.getNip()); -// -// BaseEventEncoder marshaller = new BaseEventEncoder(genericTag.getParent(), relay); -// var strJsonEvent = marshaller.marshall(); -// -// var jsonValue = new JsonObjectUnmarshaller(strJsonEvent).unmarshall(); -// -// IValue tags = ((ObjectValue) jsonValue).get("tags").get(); -// -// Assertions.assertEquals(2, ((ArrayValue) tags).length()); -// -// IValue tag = ((ArrayValue) tags).get(1).get(); -// -// Assertions.assertTrue(tag instanceof ArrayValue); -// -// IValue code = ((ArrayValue) tag).get(0).get(); -// -// Assertions.assertTrue(code instanceof StringValue); -// -// Assertions.assertEquals("devil", code.getValue()); -// Assertions.assertEquals(1, ((ArrayValue) tag).length()); -// -// } catch (NostrException ex) { -// Assertions.fail(ex); -// } - } - - // TODO - Rewrite the test class after implementing the configuration -/* - @Test - public void testCreateUnsupportedGenericTag() { - System.out.println("testCreateUnsupportedGenericTag"); - //PublicKey publicKey = this.identity.getPublicKey(); - PublicKey publicKey = Identity.getInstance().getPublicKey(); - IEvent event = EntityFactory.Events.createOtsEvent(publicKey); - GenericTag genericTag = EntityFactory.Events.createGenericTag(publicKey, event, 7); - - Relay relay = new Relay("wss://secret.relay.com"); - relay.addNipSupport(0); - - var encoder = new BaseEventEncoder((BaseEvent) genericTag.getParent(), relay); - - RuntimeException thrown = Assertions.assertThrows(RuntimeException.class, - () -> { - encoder.encode(); - }, - "This event is not supported. List of relay supported NIP(s): " + relay.printSupportedNips() - ); - - Assertions.assertNotNull(thrown); - } -*/ - - @Test - public void testNip05Validator() { - System.out.println("testNip05Validator"); - try { - var nip05 = "nostr-java@nostr.band"; - var publicKey = new PublicKey(NostrUtil.hexToBytes(Bech32.fromBech32("npub126klq89p42wk78p4j5ur8wlxmxdqepdh8tez9e4axpd4run5nahsmff27j"))); - - var nip05Validator = Nip05Validator.builder().nip05(nip05).publicKey(publicKey).build(); - - nip05Validator.validate(); - } catch (NostrException ex) { - fail(ex); - } - assertTrue(true); - } - - @Test - public void testAuthMessage() { - System.out.println("testAuthMessage"); - - GenericMessage msg = new GenericMessage("AUTH", 42); - String attr = "challenge-string"; - msg.addAttribute(ElementAttribute.builder().name("challenge").value(attr).build()); - - var muattr = (msg.getAttributes().iterator().next().getValue()).toString(); - assertEquals(attr, muattr); - } -} +package nostr.test.event; + +import lombok.extern.java.Log; +import nostr.base.ElementAttribute; + +import org.junit.jupiter.api.Test; + +import nostr.base.IEncoder; +import nostr.base.PublicKey; +import nostr.base.Relay; +import nostr.crypto.bech32.Bech32; +import nostr.crypto.bech32.Bech32Prefix; +import nostr.event.BaseTag; +import nostr.event.impl.GenericEvent; +import nostr.event.impl.GenericMessage; +import nostr.event.impl.GenericTag; +import nostr.event.json.codec.BaseTagEncoder; +import nostr.event.util.Nip05Validator; +import nostr.id.Identity; +import nostr.test.EntityFactory; +import nostr.util.NostrException; +import nostr.util.NostrUtil; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + * @author squirrel + */ +@Log +public class EventTest { + + public EventTest() { + } + + @Test + public void testCreateTextNoteEvent(){ + log.info("testCreateTextNoteEvent"); + PublicKey publicKey = Identity.generateRandomIdentity().getPublicKey(); + GenericEvent instance = EntityFactory.Events.createTextNoteEvent(publicKey); + instance.update(); + assertNotNull(instance.getId()); + assertNotNull(instance.getCreatedAt()); + assertNull(instance.getSignature()); + final String bech32 = instance.toBech32(); + assertNotNull(bech32); + assertDoesNotThrow(() -> { + assertEquals(Bech32Prefix.NOTE.getCode(), Bech32.decode(bech32).hrp); + }); + } + + @Test + public void testEventIdConstraints() { + log.info("testCreateTextNoteEvent"); + PublicKey publicKey = Identity.generateRandomIdentity().getPublicKey(); + GenericEvent genericEvent = EntityFactory.Events.createTextNoteEvent(publicKey); + String id64chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; + assertDoesNotThrow(() -> genericEvent.setId(id64chars)); + + String id63chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71"; + assertThrows(IllegalArgumentException.class, () -> genericEvent.setId(id63chars)); + + String id65chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71"; + assertThrows(IllegalArgumentException.class, () -> genericEvent.setId(id65chars)); + } + + @Test + public void testCreateGenericTag() { + log.info("testCreateGenericTag"); + PublicKey publicKey = Identity.generateRandomIdentity().getPublicKey(); + GenericTag genericTag = EntityFactory.Events.createGenericTag(publicKey); + + Relay relay = new Relay("wss://secret.relay.com"); + relay.addNipSupport(1); + relay.addNipSupport(genericTag.getNip()); + var attrs = genericTag.getAttributes(); + for (var a : attrs) { + relay.addNipSupport(a.getNip()); + } + + var encoder = new BaseTagEncoder(genericTag, relay); + var strJsonEvent = encoder.encode(); + + assertDoesNotThrow(() -> { + BaseTag tag = IEncoder.MAPPER.readValue(strJsonEvent, BaseTag.class); + assertEquals(genericTag, tag); + }); + } + + @Test + public void testCreateUnsupportedGenericTagAttribute() { +// try { +// System.out.println("testCreateUnsupportedGenericTagAttribute"); +// PublicKey publicKey = this.identity.getPublicKey(); +// GenericTag genericTag = EntityFactory.Events.createGenericTag(publicKey); +// +// Relay relay = Relay.builder().uri("wss://secret.relay.com").build(); +// relay.addNipSupport(1); +// relay.addNipSupport(genericTag.getNip()); +// +// BaseEventEncoder marshaller = new BaseEventEncoder(genericTag.getParent(), relay); +// var strJsonEvent = marshaller.marshall(); +// +// var jsonValue = new JsonObjectUnmarshaller(strJsonEvent).unmarshall(); +// +// IValue tags = ((ObjectValue) jsonValue).get("tags").get(); +// +// Assertions.assertEquals(2, ((ArrayValue) tags).length()); +// +// IValue tag = ((ArrayValue) tags).get(1).get(); +// +// Assertions.assertTrue(tag instanceof ArrayValue); +// +// IValue code = ((ArrayValue) tag).get(0).get(); +// +// Assertions.assertTrue(code instanceof StringValue); +// +// Assertions.assertEquals("devil", code.getValue()); +// Assertions.assertEquals(1, ((ArrayValue) tag).length()); +// +// } catch (NostrException ex) { +// Assertions.fail(ex); +// } + } + + // TODO - Rewrite the test class after implementing the configuration +/* + @Test + public void testCreateUnsupportedGenericTag() { + System.out.println("testCreateUnsupportedGenericTag"); + //PublicKey publicKey = this.identity.getPublicKey(); + PublicKey publicKey = Identity.getInstance().getPublicKey(); + IEvent event = EntityFactory.Events.createOtsEvent(publicKey); + GenericTag genericTag = EntityFactory.Events.createGenericTag(publicKey, event, 7); + + Relay relay = new Relay("wss://secret.relay.com"); + relay.addNipSupport(0); + + var encoder = new BaseEventEncoder((BaseEvent) genericTag.getParent(), relay); + + RuntimeException thrown = Assertions.assertThrows(RuntimeException.class, + () -> { + encoder.encode(); + }, + "This event is not supported. List of relay supported NIP(s): " + relay.printSupportedNips() + ); + + Assertions.assertNotNull(thrown); + } +*/ + + @Test + public void testNip05Validator() { + System.out.println("testNip05Validator"); + try { + var nip05 = "nostr-java@nostr.band"; + var publicKey = new PublicKey(NostrUtil.hexToBytes(Bech32.fromBech32("npub126klq89p42wk78p4j5ur8wlxmxdqepdh8tez9e4axpd4run5nahsmff27j"))); + + var nip05Validator = Nip05Validator.builder().nip05(nip05).publicKey(publicKey).build(); + + nip05Validator.validate(); + } catch (NostrException ex) { + fail(ex); + } + assertTrue(true); + } + + @Test + public void testAuthMessage() { + System.out.println("testAuthMessage"); + + GenericMessage msg = new GenericMessage("AUTH", 42); + String attr = "challenge-string"; + msg.addAttribute(ElementAttribute.builder().name("challenge").value(attr).build()); + + var muattr = (msg.getAttributes().iterator().next().getValue()).toString(); + assertEquals(attr, muattr); + } +} diff --git a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java index 1b39aa879..632b3a453 100644 --- a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java +++ b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java @@ -37,6 +37,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -106,7 +107,6 @@ public void testBaseEventMessageDecoder() { final String parseTarget = "[\"EVENT\"," - + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\"," + "{" + "\"content\":\"直んないわ。まあええか\"," + "\"created_at\":1686199583," @@ -134,7 +134,6 @@ public void testBaseEventMessageMarkerDecoder() { final String json = "[" + "\"EVENT\"," - + "\"temp20230627\"," + "{" + "\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\"," + "\"kind\":1," @@ -448,4 +447,31 @@ public void testReqMessagePopulatedListOfFiltersListDecoder() { ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); assertEquals(expectedReqMessage, decodedReqMessage); } + + @Test + public void testReqMessageSubscriptionIdLength() { + log.info("testReqMessageSubscriptionIdLength"); + String id65Chars = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9ab"; + assertThrows(IllegalArgumentException.class, () -> new ReqMessage(id65Chars, new Filters())); +// assertDoesNotThrow(() -> new ReqMessage(id65Chars, new Filters())); + } + + @Test + public void testReqMessageFilterIdLength() { + log.info("testReqMessageFilterIdLength"); + String id64chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; + String reqJsonId64Chars = + "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id64chars + "\"]}]"; + assertDoesNotThrow(() -> new BaseMessageDecoder().decode(reqJsonId64Chars)); + + String id65chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123"; + String reqJsonId65Chars = + "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id65chars + "\"]}]"; + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId65Chars)); + + String id63chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71"; + String reqJsonId63chars = + "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id63chars + "\"]}]"; + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId63chars)); + } } diff --git a/nostr-java-util/src/main/java/nostr/util/NostrUtil.java b/nostr-java-util/src/main/java/nostr/util/NostrUtil.java index 2fc8ad3d6..c127e73eb 100644 --- a/nostr-java-util/src/main/java/nostr/util/NostrUtil.java +++ b/nostr-java-util/src/main/java/nostr/util/NostrUtil.java @@ -7,6 +7,7 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; +import java.util.Optional; /** * @@ -27,6 +28,16 @@ public static String bytesToHex(byte[] b) { } public static byte[] hexToBytes(String s) { + validateHexString(s, 64); + return hexToBytesConvert(s); + } + + public static byte[] hex128ToBytes(String s) { + validateHexString(s, 128); + return hexToBytesConvert(s); + } + + private static byte[] hexToBytesConvert(String s) { int len = s.length(); byte[] buf = new byte[len / 2]; for (int i = 0; i < len; i += 2) { @@ -35,6 +46,14 @@ public static byte[] hexToBytes(String s) { return buf; } + public static void validateHexString(String hexString, int targetLength) { + Optional.ofNullable(hexString) // non-null enforcement + .filter(s -> s.length() == targetLength) // length enforcement + .filter(s -> s.toLowerCase().equals(s)) // case-sensitivity enforcement, potentially swappable for line below +// .map(String::toLowerCase) // if upper-case leniency is desirable, followed by explicit lower-casing + .orElseThrow(() -> new IllegalArgumentException(String.format("Invalid hex string: [%s], length: [%d], length targetLength: [%d]", hexString, hexString.length(), targetLength))); + } + public static byte[] bytesFromInt(int n) { return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(n).array(); } @@ -87,21 +106,21 @@ public static byte[] createRandomByteArray(int len) { public static String escapeJsonString(String jsonString) { return jsonString.replace("\\", "\\\\") - .replace("\"", "\\\"") - .replace("\b", "\\b") - .replace("\f", "\\f") - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\t", "\\t"); + .replace("\"", "\\\"") + .replace("\b", "\\b") + .replace("\f", "\\f") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t"); } public static String unEscapeJsonString(String jsonString) { return jsonString.replace("\\\\", "\\") - .replace("\\\"", "\"") - .replace("\\b", "\b") - .replace("\\f", "\f") - .replace("\\n", "\n") - .replace("\\r", "\r") - .replace("\\t", "\t"); + .replace("\\\"", "\"") + .replace("\\b", "\b") + .replace("\\f", "\f") + .replace("\\n", "\n") + .replace("\\r", "\r") + .replace("\\t", "\t"); } } diff --git a/pom.xml b/pom.xml index 6b099bf0a..d84f902de 100644 --- a/pom.xml +++ b/pom.xml @@ -70,15 +70,15 @@ 2.8.0 - 1.18.32 + 1.18.34 1.70 1.78 - 2.14.1 + 2.17.2 - 5.9.1 + 5.10.2 3.23.1 From 65316ab677fb920c3aeb039201194890f059d352 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Mon, 6 Jan 2025 20:54:22 -0500 Subject: [PATCH 02/16] pom dependency updates --- nostr-java-util/src/main/java/nostr/util/NostrUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nostr-java-util/src/main/java/nostr/util/NostrUtil.java b/nostr-java-util/src/main/java/nostr/util/NostrUtil.java index c127e73eb..d29a02d00 100644 --- a/nostr-java-util/src/main/java/nostr/util/NostrUtil.java +++ b/nostr-java-util/src/main/java/nostr/util/NostrUtil.java @@ -51,7 +51,7 @@ public static void validateHexString(String hexString, int targetLength) { .filter(s -> s.length() == targetLength) // length enforcement .filter(s -> s.toLowerCase().equals(s)) // case-sensitivity enforcement, potentially swappable for line below // .map(String::toLowerCase) // if upper-case leniency is desirable, followed by explicit lower-casing - .orElseThrow(() -> new IllegalArgumentException(String.format("Invalid hex string: [%s], length: [%d], length targetLength: [%d]", hexString, hexString.length(), targetLength))); + .orElseThrow(() -> new IllegalArgumentException(String.format("Invalid hex string: [%s], length: [%d], target length: [%d]", hexString, hexString.length(), targetLength))); } public static byte[] bytesFromInt(int n) { From 6723e79efb990e9f1eb698128a2f80b4af1537ac Mon Sep 17 00:00:00 2001 From: nick avlo Date: Mon, 6 Jan 2025 23:12:30 -0500 Subject: [PATCH 03/16] updated UUID length & pubkey required value in tests --- .../event/impl/CreateOrUpdateStallEvent.java | 18 +++++++++--------- .../java/nostr/test/event/ApiEventTest.java | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/impl/CreateOrUpdateStallEvent.java b/nostr-java-event/src/main/java/nostr/event/impl/CreateOrUpdateStallEvent.java index ac18ce3e1..752211272 100644 --- a/nostr-java-event/src/main/java/nostr/event/impl/CreateOrUpdateStallEvent.java +++ b/nostr-java-event/src/main/java/nostr/event/impl/CreateOrUpdateStallEvent.java @@ -36,21 +36,21 @@ public static class Stall extends AbstractEventContent @JsonProperty private final String id; - + @JsonProperty private String name; - + @JsonProperty private String description; - + @JsonProperty private String currency; - + @JsonProperty - private Shipping shipping; + private Shipping shipping; public Stall() { - this.id = UUID.randomUUID().toString(); + this.id = UUID.randomUUID().toString().concat(UUID.randomUUID().toString()).substring(0, 64); } @Data @@ -58,13 +58,13 @@ public static class Shipping { @JsonProperty private final String id; - + @JsonProperty private String name; - + @JsonProperty private Float cost; - + @JsonProperty private List countries; diff --git a/nostr-java-test/src/test/java/nostr/test/event/ApiEventTest.java b/nostr-java-test/src/test/java/nostr/test/event/ApiEventTest.java index a146f0d25..26e88c8a1 100644 --- a/nostr-java-test/src/test/java/nostr/test/event/ApiEventTest.java +++ b/nostr-java-test/src/test/java/nostr/test/event/ApiEventTest.java @@ -62,7 +62,7 @@ public class ApiEventTest { public void testNIP01CreateTextNoteEvent() throws NostrException { System.out.println("testNIP01CreateTextNoteEvent"); - PublicKey publicKey = new PublicKey(""); + PublicKey publicKey = new PublicKey(NOSTR_JAVA_PUBKEY); var recipient = NIP01.createPubKeyTag(publicKey); List tags = new ArrayList<>(); tags.add(recipient); From 2bbcf5719f812c5e8c648e7770a75ab1e12e9cf4 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 7 Jan 2025 00:12:57 -0500 Subject: [PATCH 04/16] testDeserializeTag use proper bech32 hex to bytes conversion --- .../src/test/java/nostr/test/json/JsonParseTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java index 632b3a453..c7fe4ac4a 100644 --- a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java +++ b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java @@ -27,6 +27,7 @@ import nostr.event.tag.PriceTag; import nostr.event.tag.PubKeyTag; import nostr.id.Identity; +import nostr.util.NostrUtil; import org.junit.jupiter.api.Test; import java.math.BigDecimal; @@ -122,7 +123,6 @@ public void testBaseEventMessageDecoder() { assertEquals(Command.EVENT.toString(), message.getCommand()); final var event = (GenericEvent) (((EventMessage) message).getEvent()); - assertEquals("npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh", ((EventMessage) message).getSubscriptionId()); assertEquals(1, event.getKind().intValue()); assertEquals(1686199583, event.getCreatedAt().longValue()); assertEquals("fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712", event.getId()); @@ -244,7 +244,8 @@ public void testDeserializeTag() { log.info("testDeserializeTag"); assertDoesNotThrow(() -> { - String npubHex = new PublicKey(Bech32.decode("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9").data).toString(); + String npubHex = new PublicKey(NostrUtil.hexToBytes(Bech32.fromBech32(("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9")))).toString(); + final String jsonString = "[\"p\", \"" + npubHex + "\", \"wss://nostr.java\", \"alice\"]"; var tag = new BaseTagDecoder<>().decode(jsonString); From 5b1a7c34ace3a2a59ee29f6f035742d64540f6f9 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 7 Jan 2025 13:18:23 -0500 Subject: [PATCH 05/16] kind threshold and tests --- .../src/main/java/nostr/event/Kind.java | 5 +++++ .../test/java/nostr/test/json/JsonParseTest.java | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/nostr-java-event/src/main/java/nostr/event/Kind.java b/nostr-java-event/src/main/java/nostr/event/Kind.java index 415d39f3d..fce15b159 100644 --- a/nostr-java-event/src/main/java/nostr/event/Kind.java +++ b/nostr-java-event/src/main/java/nostr/event/Kind.java @@ -5,6 +5,8 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import java.time.temporal.ValueRange; + /** * * @author squirrel @@ -47,6 +49,9 @@ public enum Kind { @JsonCreator public static Kind valueOf(int value) { + if (!ValueRange.of(0, 65535).isValidIntValue(value)) { + throw new IllegalArgumentException(String.format("Kind must be between 0 and 65536 but was [%d]", value)); + } for (Kind k : values()) { if (k.getValue() == value) { return k; diff --git a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java index c7fe4ac4a..259e60f68 100644 --- a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java +++ b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java @@ -475,4 +475,20 @@ public void testReqMessageFilterIdLength() { "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id63chars + "\"]}]"; assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId63chars)); } + + @Test + public void testBaseMessageDecoderKind() { + log.info("testBaseMessageDecoderKind"); + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget(0))); + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget(65535))); + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget(-1))); + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget(65536))); + } + + private static String kindTarget(int kind) { + return "[\"REQ\", " + + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + + "{\"kinds\": [" + kind + "]" + + "}]"; + } } From 1e90cbb63412cef5a3c7c87f57d5ab4ccf975b19 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 7 Jan 2025 13:42:20 -0500 Subject: [PATCH 06/16] replace private method with function<> --- .../test/java/nostr/test/json/JsonParseTest.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java index 259e60f68..1698b53c4 100644 --- a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java +++ b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java @@ -34,6 +34,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.BiPredicate; +import java.util.function.Function; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -479,16 +481,15 @@ public void testReqMessageFilterIdLength() { @Test public void testBaseMessageDecoderKind() { log.info("testBaseMessageDecoderKind"); - assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget(0))); - assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget(65535))); - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget(-1))); - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget(65536))); - } - private static String kindTarget(int kind) { - return "[\"REQ\", " + + Function kindTarget = kind -> "[\"REQ\", " + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + "{\"kinds\": [" + kind + "]" + "}]"; + + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget.apply(0))); + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget.apply(65535))); + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(-1))); + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(65536))); } } From 31161c0aebce5a213c8dc0c483658eddaaa806e6 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 7 Jan 2025 17:34:04 -0500 Subject: [PATCH 07/16] added HexStringValidator --- .../java/nostr/test/json/JsonParseTest.java | 938 ++++++++++-------- .../src/main/java/nostr/util/NostrUtil.java | 179 ++-- .../nostr/util/thread/HexStringValidator.java | 50 + 3 files changed, 633 insertions(+), 534 deletions(-) create mode 100644 nostr-java-util/src/main/java/nostr/util/thread/HexStringValidator.java diff --git a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java index 1698b53c4..8b8082e03 100644 --- a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java +++ b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java @@ -34,7 +34,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.function.BiPredicate; import java.util.function.Function; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -49,447 +48,504 @@ @Log public class JsonParseTest { - @Test - public void testBaseMessageDecoder() { - log.info("testBaseMessageDecoder"); + @Test + public void testBaseMessageDecoder() { + log.info("testBaseMessageDecoder"); - final String parseTarget = - "[\"REQ\", " + - "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + - "{\"kinds\": [1], " + - "\"authors\": [\"f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75\"]," + - "\"#e\": [\"fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712\"]}]"; - - final var message = new BaseMessageDecoder<>().decode(parseTarget); - - assertEquals(Command.REQ.toString(), message.getCommand()); - assertEquals("npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh", ((ReqMessage) message).getSubscriptionId()); - assertEquals(1, ((ReqMessage) message).getFiltersList().size()); - - var filters = ((ReqMessage) message).getFiltersList().get(0); - - assertEquals(1, filters.getKinds().size()); - assertEquals(Kind.TEXT_NOTE, filters.getKinds().get(0)); - - assertEquals(1, filters.getAuthors().size()); - assertEquals("npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh", filters.getAuthors().get(0).toBech32String()); - - assertEquals(1, filters.getReferencedEvents().size()); - assertEquals("fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712", (filters.getReferencedEvents().get(0).getId())); - } - - @Test - public void testBaseReqMessageDecoder() { - log.info("testBaseReqMessageDecoder"); - - final var filtersList = new ArrayList(); - var publicKey = Identity.generateRandomIdentity().getPublicKey(); - filtersList.add(Filters.builder().authors(new ArrayList<>(List.of(publicKey))).kinds(new ArrayList<>(List.of(Kind.CONTACT_LIST, Kind.DELETION))).build()); - filtersList.add(Filters.builder().kinds(new ArrayList<>(List.of(Kind.SET_METADATA, Kind.TEXT_NOTE))).build()); - final var reqMessage = new ReqMessage(publicKey.toString(), filtersList); - - assertDoesNotThrow(() -> { - String jsonMessage = reqMessage.encode(); - - String jsonMsg = jsonMessage.substring(1, jsonMessage.length() - 1); - String[] parts = jsonMsg.split(","); - assertEquals("\"REQ\"", parts[0]); - assertEquals("\"" + publicKey.toString() + "\"", parts[1]); - assertFalse(parts[2].startsWith("[")); - assertFalse(parts[parts.length - 1].endsWith("]")); - - BaseMessage message = new BaseMessageDecoder<>().decode(jsonMessage); - - assertEquals(reqMessage, message); - }); - } - - @Test - public void testBaseEventMessageDecoder() { - log.info("testBaseEventMessageDecoder"); - - final String parseTarget - = "[\"EVENT\"," - + "{" - + "\"content\":\"直んないわ。まあええか\"," - + "\"created_at\":1686199583," - + "\"id\":\"fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712\"," - + "\"kind\":1," - + "\"pubkey\":\"8c59239319637f97e007dad0d681e65ce35b1ace333b629e2d33f9465c132608\"," - + "\"sig\":\"9584afd231c52fcbcec6ce668a2cc4b6dc9b4d9da20510dcb9005c6844679b4844edb7a2e1e0591958b0295241567c774dbf7d39a73932877542de1a5f963f4b\"," - + "\"tags\":[]" - + "}]"; - - final var message = new BaseMessageDecoder<>().decode(parseTarget); - - assertEquals(Command.EVENT.toString(), message.getCommand()); - - final var event = (GenericEvent) (((EventMessage) message).getEvent()); - assertEquals(1, event.getKind().intValue()); - assertEquals(1686199583, event.getCreatedAt().longValue()); - assertEquals("fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712", event.getId()); - } - - @Test - public void testBaseEventMessageMarkerDecoder() { - log.info("testBaseEventMessageMarkerDecoder"); - - final String json = "[" - + "\"EVENT\"," - + "{" - + "\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\"," - + "\"kind\":1," - + "\"pubkey\":\"2bed79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984\"," - + "\"created_at\":1687765220," - + "\"content\":\"手順書が間違ってたら作業者は無理だな\"," - + "\"tags\":[" - + "[\"e\",\"494001ac0c8af2a10f60f23538e5b35d3cdacb8e1cc956fe7a16dfa5cbfc4346\",\"\",\"root\"]," - + "[\"p\",\"2bed79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984\"]" - + "]," - + "\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"" - + "}]"; - - BaseMessage message = new BaseMessageDecoder<>().decode(json); - - final var event = (GenericEvent) (((EventMessage) message).getEvent()); - var tags = event.getTags(); - for (BaseTag t : tags) { - if (t.getCode().equalsIgnoreCase("e")) { - EventTag et = (EventTag) t; - assertEquals(Marker.ROOT, et.getMarker()); - } - } - } - - @Test - public void testGenericTagDecoder() { - log.info("testGenericTagDecoder"); - final String jsonString = "[\"saturn\", \"jetpack\", false]"; - - var tag = new GenericTagDecoder<>().decode(jsonString); - - assertEquals("saturn", tag.getCode()); - assertEquals(2, tag.getAttributes().size()); - assertEquals("jetpack", ((ElementAttribute) (tag.getAttributes().toArray())[0]).getValue()); - assertEquals(false, Boolean.valueOf(((ElementAttribute) (tag.getAttributes().toArray())[1]).getValue().toString())); - } - - @Test - public void testClassifiedListingTagSerializer() { - log.info("testClassifiedListingSerializer"); - final String classifiedListingEventJson = "{" - + "\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\"," - + "\"kind\":30402," - + "\"content\":\"content ipsum\"," - + "\"pubkey\":\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\"," - + "\"created_at\":1687765220," - + "\"tags\":[" - + "[\"p\",\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\"]," - + "[\"title\",\"title ipsum\"]," - + "[\"summary\",\"summary ipsum\"]," - + "[\"published_at\",\"1687765220\"]," - + "[\"location\",\"location ipsum\"]," - + "[\"price\",\"11111\",\"BTC\",\"1\"]]," - + "\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"" - + "}]"; - - GenericEvent event = new GenericEventDecoder<>().decode(classifiedListingEventJson); - EventMessage message = NIP01.createEventMessage(event, "1"); - assertEquals(1, message.getNip()); - String encoded = new BaseEventEncoder<>((BaseEvent) message.getEvent()).encode(); - assertEquals("{\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\",\"kind\":30402,\"content\":\"content ipsum\",\"pubkey\":\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\",\"created_at\":1687765220,\"tags\":[[\"p\",\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\"],[\"title\",\"title ipsum\"],[\"summary\",\"summary ipsum\"],[\"published_at\",\"1687765220\"],[\"location\",\"location ipsum\"],[\"price\",\"11111\",\"BTC\",\"1\"]],\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"}", encoded); - - assertEquals("28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a", event.getId()); - assertEquals(30402, event.getKind()); - assertEquals("content ipsum", event.getContent()); - assertEquals("ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de", event.getPubKey().toString()); - assertEquals(1687765220L, event.getCreatedAt()); - assertEquals("86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546", event.getSignature().toString()); - - assertEquals(new BigDecimal("11111"), event.getTags().stream().filter(baseTag -> - baseTag.getCode().equalsIgnoreCase("price")) - .filter(PriceTag.class::isInstance) - .map(PriceTag.class::cast) - .map(PriceTag::getNumber).findFirst().orElseThrow()); - - assertEquals("BTC", event.getTags().stream().filter(baseTag -> - baseTag.getCode().equalsIgnoreCase("price")) - .filter(PriceTag.class::isInstance) - .map(PriceTag.class::cast) - .map(PriceTag::getCurrency).findFirst().orElseThrow()); - - assertEquals("1", event.getTags().stream().filter(baseTag -> - baseTag.getCode().equalsIgnoreCase("price")) - .filter(PriceTag.class::isInstance) - .map(PriceTag.class::cast) - .map(PriceTag::getFrequency).findFirst().orElseThrow()); - - List genericTags = event.getTags().stream() - .filter(GenericTag.class::isInstance) - .map(GenericTag.class::cast).toList(); - - assertEquals("title ipsum", genericTags.stream() - .filter(tag -> tag.getCode().equalsIgnoreCase("title")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); - - assertEquals("summary ipsum", genericTags.stream() - .filter(tag -> tag.getCode().equalsIgnoreCase("summary")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); - - assertEquals("1687765220", genericTags.stream() - .filter(tag -> tag.getCode().equalsIgnoreCase("published_at")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); - - assertEquals("location ipsum", genericTags.stream() - .filter(tag -> tag.getCode().equalsIgnoreCase("location")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); - } - - @Test - public void testDeserializeTag() { - log.info("testDeserializeTag"); - - assertDoesNotThrow(() -> { - String npubHex = new PublicKey(NostrUtil.hexToBytes(Bech32.fromBech32(("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9")))).toString(); - - final String jsonString = "[\"p\", \"" + npubHex + "\", \"wss://nostr.java\", \"alice\"]"; - var tag = new BaseTagDecoder<>().decode(jsonString); - - assertTrue(tag instanceof PubKeyTag); - - PubKeyTag pTag = (PubKeyTag) tag; - assertEquals("wss://nostr.java", pTag.getMainRelayUrl()); - assertEquals(npubHex, pTag.getPublicKey().toString()); - assertEquals("alice", pTag.getPetName()); - }); - } - - @Test - public void testDeserializeGenericTag() { - log.info("testDeserializeGenericTag"); - assertDoesNotThrow(() -> { - String npubHex = new PublicKey(Bech32.decode("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9").data).toString(); - final String jsonString = "[\"gt\", \"" + npubHex + "\", \"wss://nostr.java\", \"alice\"]"; - var tag = new BaseTagDecoder<>().decode(jsonString); - - assertTrue(tag instanceof GenericTag); - - GenericTag gTag = (GenericTag) tag; - assertEquals("gt", gTag.getCode()); - }); - } - - @Test - public void testFiltersEncoder() { - log.info("testFiltersEncoder"); - - String new_geohash = "2vghde"; - List geohashList = new ArrayList<>(); - geohashList.add(new_geohash); - Filters filters = Filters.builder().genericTagQuery(Map.of("#g", geohashList)).build(); - - FiltersEncoder encoder = new FiltersEncoder(filters); - String jsonMessage = encoder.encode(); - assertEquals("{\"#g\":[\"2vghde\"]}", jsonMessage); - } - - @Test - public void testReqMessageFilterListSerializer() { - log.info("testReqMessageFilterListSerializer"); - - String new_geohash = "2vghde"; - String second_geohash = "3abcde"; - List geohashList = new ArrayList<>(); - geohashList.add(new_geohash); - geohashList.add(second_geohash); - Filters filters = Filters.builder().genericTagQuery(Map.of("#g", geohashList)).build(); - - ReqMessage reqMessage = new ReqMessage("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9", new ArrayList(List.of(filters))); - assertDoesNotThrow(() -> { - String jsonMessage = reqMessage.encode(); - - assertEquals("[\"REQ\",\"npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9\",{\"#g\":[\"2vghde\",\"3abcde\"]}]", jsonMessage); - }); - } - - @Test - public void testReqMessageFiltersDecoder() { - log.info("testReqMessageFiltersDecoder"); - - String geohashKey = "#g"; - String geohashValue = "2vghde"; - String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + geohashKey + "\":[\"" + geohashValue + "\"]}"; - - Filters decodedFilters = new FiltersDecoder<>().decode(reqJsonWithCustomTagQueryFilterToDecode); - - Filters expectedFilters = new Filters(); - List expectedGeohashValueList = List.of(geohashValue); - expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValueList); - - assertEquals(expectedFilters, decodedFilters); - } - - @Test - public void testReqMessageFiltersListDecoder() { - log.info("testReqMessageFiltersListDecoder"); - - String geohashKey = "#g"; - String geohashValue1 = "2vghde"; - String geohashValue2 = "3abcde"; - String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + geohashKey + "\":[\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]}"; - - Filters decodedFilters = new FiltersDecoder<>().decode(reqJsonWithCustomTagQueryFilterToDecode); - - Filters expectedFilters = new Filters(); - List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); - expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); - - assertEquals(expectedFilters, decodedFilters); - } - - @Test - public void testReqMessageDeserializer() { - log.info("testReqMessageDeserializer"); - - String subscriptionId = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9"; - String geohashKey = "#g"; - String geohashValue = "2vghde"; - String reqJsonWithCustomTagQueryFilterToDecode = "[\"REQ\",\"" + subscriptionId + "\",{\"" + geohashKey + "\":[\"" + geohashValue + "\"]}]"; - - ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); - - Filters expectedFilters = new Filters(); - List expectedGeohashValuesList = List.of(geohashValue); - expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); - - ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); - assertEquals(expectedReqMessage, decodedReqMessage); - } - - @Test - public void testReqMessageFilterListDecoder() { - log.info("testReqMessageFilterListDecoder"); - - String subscriptionId = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9"; - String geohashKey = "#g"; - String geohashValue1 = "2vghde"; - String geohashValue2 = "3abcde"; - String reqJsonWithCustomTagQueryFiltersToDecode = "[\"REQ\",\"" + subscriptionId + "\",{\"" + geohashKey + "\":[\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]}]"; - - ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFiltersToDecode); - - Filters expectedFilters = new Filters(); - List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); - expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); - - ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); - assertEquals(expectedReqMessage, decodedReqMessage); - } - - @Test - public void testReqMessagePopulatedFilterDecoder() { - log.info("testReqMessagePopulatedFilterDecoder"); - - String subscriptionId = "npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh"; - String kind = "1"; - String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String geohashKey = "#g"; - String geohashValue1 = "2vghde"; - String geohashValue2 = "3abcde"; - String referencedEventId = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; - String reqJsonWithCustomTagQueryFilterToDecode = - "[\"REQ\", " + - "\"" + subscriptionId + "\", " + - "{\"kinds\": [" + kind + "], " + - "\"authors\": [\"" + author + "\"]," + - "\"" + geohashKey + "\": [\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]," + - "\"#e\": [\"" + referencedEventId + "\"]}]"; - - ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); - - Filters expectedFilters = new Filters(); - expectedFilters.setKinds(List.of(Kind.TEXT_NOTE)); - expectedFilters.setAuthors(List.of(new PublicKey(author))); - expectedFilters.setReferencedEvents(List.of(new GenericEvent(referencedEventId))); - List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); - expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); - - ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); - assertEquals(expectedReqMessage, decodedReqMessage); - } - - @Test - public void testReqMessagePopulatedListOfFiltersListDecoder() { - log.info("testReqMessagePopulatedListOfFiltersListDecoder"); - - String subscriptionId = "npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh"; - String kind = "1"; - String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String geohashKey = "#g"; - String geohashValue1 = "2vghde"; - String geohashValue2 = "3abcde"; - String referencedEventId = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; - String uuidKey = "#d"; - String uuidValue1 = "UUID-1"; - String uuidValue2 = "UUID-2"; - String reqJsonWithCustomTagQueryFilterToDecode = - "[\"REQ\", " + - "\"" + subscriptionId + "\", " + - "{\"kinds\": [" + kind + "], " + - "\"authors\": [\"" + author + "\"]," + - "\"" + geohashKey + "\": [\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]," + - "\"" + uuidKey + "\": [\"" + uuidValue1 + "\",\"" + uuidValue2 + "\"]," + - "\"#e\": [\"" + referencedEventId + "\"]}]"; - - ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); - - Filters expectedFilters = new Filters(); - expectedFilters.setKinds(List.of(Kind.TEXT_NOTE)); - expectedFilters.setAuthors(List.of(new PublicKey(author))); - expectedFilters.setReferencedEvents(List.of(new GenericEvent(referencedEventId))); - List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); - expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); - List expectedIdentityTagValuesList = List.of(uuidValue1, uuidValue2); - expectedFilters.setGenericTagQuery(uuidKey, expectedIdentityTagValuesList); - ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); - assertEquals(expectedReqMessage, decodedReqMessage); - } - - @Test - public void testReqMessageSubscriptionIdLength() { - log.info("testReqMessageSubscriptionIdLength"); - String id65Chars = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9ab"; - assertThrows(IllegalArgumentException.class, () -> new ReqMessage(id65Chars, new Filters())); -// assertDoesNotThrow(() -> new ReqMessage(id65Chars, new Filters())); - } - - @Test - public void testReqMessageFilterIdLength() { - log.info("testReqMessageFilterIdLength"); - String id64chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; - String reqJsonId64Chars = - "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id64chars + "\"]}]"; - assertDoesNotThrow(() -> new BaseMessageDecoder().decode(reqJsonId64Chars)); - - String id65chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123"; - String reqJsonId65Chars = - "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id65chars + "\"]}]"; - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId65Chars)); - - String id63chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71"; - String reqJsonId63chars = - "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id63chars + "\"]}]"; - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId63chars)); - } - - @Test - public void testBaseMessageDecoderKind() { - log.info("testBaseMessageDecoderKind"); - - Function kindTarget = kind -> "[\"REQ\", " + + final String parseTarget = + "[\"REQ\", " + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + - "{\"kinds\": [" + kind + "]" + - "}]"; - - assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget.apply(0))); - assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget.apply(65535))); - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(-1))); - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(65536))); + "{\"kinds\": [1], " + + "\"authors\": [\"f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75\"]," + + "\"#e\": [\"fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712\"]}]"; + + final var message = new BaseMessageDecoder<>().decode(parseTarget); + + assertEquals(Command.REQ.toString(), message.getCommand()); + assertEquals("npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh", ((ReqMessage) message).getSubscriptionId()); + assertEquals(1, ((ReqMessage) message).getFiltersList().size()); + + var filters = ((ReqMessage) message).getFiltersList().get(0); + + assertEquals(1, filters.getKinds().size()); + assertEquals(Kind.TEXT_NOTE, filters.getKinds().get(0)); + + assertEquals(1, filters.getAuthors().size()); + assertEquals("npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh", filters.getAuthors().get(0).toBech32String()); + + assertEquals(1, filters.getReferencedEvents().size()); + assertEquals("fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712", (filters.getReferencedEvents().get(0).getId())); + } + + @Test + public void testBaseReqMessageDecoder() { + log.info("testBaseReqMessageDecoder"); + + final var filtersList = new ArrayList(); + var publicKey = Identity.generateRandomIdentity().getPublicKey(); + filtersList.add(Filters.builder().authors(new ArrayList<>(List.of(publicKey))).kinds(new ArrayList<>(List.of(Kind.CONTACT_LIST, Kind.DELETION))).build()); + filtersList.add(Filters.builder().kinds(new ArrayList<>(List.of(Kind.SET_METADATA, Kind.TEXT_NOTE))).build()); + final var reqMessage = new ReqMessage(publicKey.toString(), filtersList); + + assertDoesNotThrow(() -> { + String jsonMessage = reqMessage.encode(); + + String jsonMsg = jsonMessage.substring(1, jsonMessage.length() - 1); + String[] parts = jsonMsg.split(","); + assertEquals("\"REQ\"", parts[0]); + assertEquals("\"" + publicKey.toString() + "\"", parts[1]); + assertFalse(parts[2].startsWith("[")); + assertFalse(parts[parts.length - 1].endsWith("]")); + + BaseMessage message = new BaseMessageDecoder<>().decode(jsonMessage); + + assertEquals(reqMessage, message); + }); + } + + @Test + public void testBaseEventMessageDecoder() { + log.info("testBaseEventMessageDecoder"); + + final String parseTarget + = "[\"EVENT\"," + + "{" + + "\"content\":\"直んないわ。まあええか\"," + + "\"created_at\":1686199583," + + "\"id\":\"fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712\"," + + "\"kind\":1," + + "\"pubkey\":\"8c59239319637f97e007dad0d681e65ce35b1ace333b629e2d33f9465c132608\"," + + "\"sig\":\"9584afd231c52fcbcec6ce668a2cc4b6dc9b4d9da20510dcb9005c6844679b4844edb7a2e1e0591958b0295241567c774dbf7d39a73932877542de1a5f963f4b\"," + + "\"tags\":[]" + + "}]"; + + final var message = new BaseMessageDecoder<>().decode(parseTarget); + + assertEquals(Command.EVENT.toString(), message.getCommand()); + + final var event = (GenericEvent) (((EventMessage) message).getEvent()); + assertEquals(1, event.getKind().intValue()); + assertEquals(1686199583, event.getCreatedAt().longValue()); + assertEquals("fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712", event.getId()); + } + + @Test + public void testBaseEventMessageMarkerDecoder() { + log.info("testBaseEventMessageMarkerDecoder"); + + final String json = "[" + + "\"EVENT\"," + + "{" + + "\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\"," + + "\"kind\":1," + + "\"pubkey\":\"2bed79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984\"," + + "\"created_at\":1687765220," + + "\"content\":\"手順書が間違ってたら作業者は無理だな\"," + + "\"tags\":[" + + "[\"e\",\"494001ac0c8af2a10f60f23538e5b35d3cdacb8e1cc956fe7a16dfa5cbfc4346\",\"\",\"root\"]," + + "[\"p\",\"2bed79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984\"]" + + "]," + + "\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"" + + "}]"; + + BaseMessage message = new BaseMessageDecoder<>().decode(json); + + final var event = (GenericEvent) (((EventMessage) message).getEvent()); + var tags = event.getTags(); + for (BaseTag t : tags) { + if (t.getCode().equalsIgnoreCase("e")) { + EventTag et = (EventTag) t; + assertEquals(Marker.ROOT, et.getMarker()); + } } + } + + @Test + public void testGenericTagDecoder() { + log.info("testGenericTagDecoder"); + final String jsonString = "[\"saturn\", \"jetpack\", false]"; + + var tag = new GenericTagDecoder<>().decode(jsonString); + + assertEquals("saturn", tag.getCode()); + assertEquals(2, tag.getAttributes().size()); + assertEquals("jetpack", ((ElementAttribute) (tag.getAttributes().toArray())[0]).getValue()); + assertEquals(false, Boolean.valueOf(((ElementAttribute) (tag.getAttributes().toArray())[1]).getValue().toString())); + } + + @Test + public void testClassifiedListingTagSerializer() { + log.info("testClassifiedListingSerializer"); + final String classifiedListingEventJson = "{" + + "\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\"," + + "\"kind\":30402," + + "\"content\":\"content ipsum\"," + + "\"pubkey\":\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\"," + + "\"created_at\":1687765220," + + "\"tags\":[" + + "[\"p\",\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\"]," + + "[\"title\",\"title ipsum\"]," + + "[\"summary\",\"summary ipsum\"]," + + "[\"published_at\",\"1687765220\"]," + + "[\"location\",\"location ipsum\"]," + + "[\"price\",\"11111\",\"BTC\",\"1\"]]," + + "\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"" + + "}]"; + + GenericEvent event = new GenericEventDecoder<>().decode(classifiedListingEventJson); + EventMessage message = NIP01.createEventMessage(event, "1"); + assertEquals(1, message.getNip()); + String encoded = new BaseEventEncoder<>((BaseEvent) message.getEvent()).encode(); + assertEquals("{\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\",\"kind\":30402,\"content\":\"content ipsum\",\"pubkey\":\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\",\"created_at\":1687765220,\"tags\":[[\"p\",\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\"],[\"title\",\"title ipsum\"],[\"summary\",\"summary ipsum\"],[\"published_at\",\"1687765220\"],[\"location\",\"location ipsum\"],[\"price\",\"11111\",\"BTC\",\"1\"]],\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"}", encoded); + + assertEquals("28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a", event.getId()); + assertEquals(30402, event.getKind()); + assertEquals("content ipsum", event.getContent()); + assertEquals("ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de", event.getPubKey().toString()); + assertEquals(1687765220L, event.getCreatedAt()); + assertEquals("86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546", event.getSignature().toString()); + + assertEquals(new BigDecimal("11111"), event.getTags().stream().filter(baseTag -> + baseTag.getCode().equalsIgnoreCase("price")) + .filter(PriceTag.class::isInstance) + .map(PriceTag.class::cast) + .map(PriceTag::getNumber).findFirst().orElseThrow()); + + assertEquals("BTC", event.getTags().stream().filter(baseTag -> + baseTag.getCode().equalsIgnoreCase("price")) + .filter(PriceTag.class::isInstance) + .map(PriceTag.class::cast) + .map(PriceTag::getCurrency).findFirst().orElseThrow()); + + assertEquals("1", event.getTags().stream().filter(baseTag -> + baseTag.getCode().equalsIgnoreCase("price")) + .filter(PriceTag.class::isInstance) + .map(PriceTag.class::cast) + .map(PriceTag::getFrequency).findFirst().orElseThrow()); + + List genericTags = event.getTags().stream() + .filter(GenericTag.class::isInstance) + .map(GenericTag.class::cast).toList(); + + assertEquals("title ipsum", genericTags.stream() + .filter(tag -> tag.getCode().equalsIgnoreCase("title")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); + + assertEquals("summary ipsum", genericTags.stream() + .filter(tag -> tag.getCode().equalsIgnoreCase("summary")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); + + assertEquals("1687765220", genericTags.stream() + .filter(tag -> tag.getCode().equalsIgnoreCase("published_at")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); + + assertEquals("location ipsum", genericTags.stream() + .filter(tag -> tag.getCode().equalsIgnoreCase("location")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); + } + + @Test + public void testDeserializeTag() { + log.info("testDeserializeTag"); + + assertDoesNotThrow(() -> { + String npubHex = new PublicKey(NostrUtil.hexToBytes(Bech32.fromBech32(("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9")))).toString(); + + final String jsonString = "[\"p\", \"" + npubHex + "\", \"wss://nostr.java\", \"alice\"]"; + var tag = new BaseTagDecoder<>().decode(jsonString); + + assertTrue(tag instanceof PubKeyTag); + + PubKeyTag pTag = (PubKeyTag) tag; + assertEquals("wss://nostr.java", pTag.getMainRelayUrl()); + assertEquals(npubHex, pTag.getPublicKey().toString()); + assertEquals("alice", pTag.getPetName()); + }); + } + + @Test + public void testDeserializeGenericTag() { + log.info("testDeserializeGenericTag"); + assertDoesNotThrow(() -> { + String npubHex = new PublicKey(Bech32.decode("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9").data).toString(); + final String jsonString = "[\"gt\", \"" + npubHex + "\", \"wss://nostr.java\", \"alice\"]"; + var tag = new BaseTagDecoder<>().decode(jsonString); + + assertTrue(tag instanceof GenericTag); + + GenericTag gTag = (GenericTag) tag; + assertEquals("gt", gTag.getCode()); + }); + } + + @Test + public void testFiltersEncoder() { + log.info("testFiltersEncoder"); + + String new_geohash = "2vghde"; + List geohashList = new ArrayList<>(); + geohashList.add(new_geohash); + Filters filters = Filters.builder().genericTagQuery(Map.of("#g", geohashList)).build(); + + FiltersEncoder encoder = new FiltersEncoder(filters); + String jsonMessage = encoder.encode(); + assertEquals("{\"#g\":[\"2vghde\"]}", jsonMessage); + } + + @Test + public void testReqMessageFilterListSerializer() { + log.info("testReqMessageFilterListSerializer"); + + String new_geohash = "2vghde"; + String second_geohash = "3abcde"; + List geohashList = new ArrayList<>(); + geohashList.add(new_geohash); + geohashList.add(second_geohash); + Filters filters = Filters.builder().genericTagQuery(Map.of("#g", geohashList)).build(); + + ReqMessage reqMessage = new ReqMessage("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9", new ArrayList(List.of(filters))); + assertDoesNotThrow(() -> { + String jsonMessage = reqMessage.encode(); + + assertEquals("[\"REQ\",\"npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9\",{\"#g\":[\"2vghde\",\"3abcde\"]}]", jsonMessage); + }); + } + + @Test + public void testReqMessageFiltersDecoder() { + log.info("testReqMessageFiltersDecoder"); + + String geohashKey = "#g"; + String geohashValue = "2vghde"; + String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + geohashKey + "\":[\"" + geohashValue + "\"]}"; + + Filters decodedFilters = new FiltersDecoder<>().decode(reqJsonWithCustomTagQueryFilterToDecode); + + Filters expectedFilters = new Filters(); + List expectedGeohashValueList = List.of(geohashValue); + expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValueList); + + assertEquals(expectedFilters, decodedFilters); + } + + @Test + public void testReqMessageFiltersListDecoder() { + log.info("testReqMessageFiltersListDecoder"); + + String geohashKey = "#g"; + String geohashValue1 = "2vghde"; + String geohashValue2 = "3abcde"; + String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + geohashKey + "\":[\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]}"; + + Filters decodedFilters = new FiltersDecoder<>().decode(reqJsonWithCustomTagQueryFilterToDecode); + + Filters expectedFilters = new Filters(); + List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); + expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); + + assertEquals(expectedFilters, decodedFilters); + } + + @Test + public void testReqMessageDeserializer() { + log.info("testReqMessageDeserializer"); + + String subscriptionId = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9"; + String geohashKey = "#g"; + String geohashValue = "2vghde"; + String reqJsonWithCustomTagQueryFilterToDecode = "[\"REQ\",\"" + subscriptionId + "\",{\"" + geohashKey + "\":[\"" + geohashValue + "\"]}]"; + + ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); + + Filters expectedFilters = new Filters(); + List expectedGeohashValuesList = List.of(geohashValue); + expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); + + ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); + assertEquals(expectedReqMessage, decodedReqMessage); + } + + @Test + public void testReqMessageFilterListDecoder() { + log.info("testReqMessageFilterListDecoder"); + + String subscriptionId = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9"; + String geohashKey = "#g"; + String geohashValue1 = "2vghde"; + String geohashValue2 = "3abcde"; + String reqJsonWithCustomTagQueryFiltersToDecode = "[\"REQ\",\"" + subscriptionId + "\",{\"" + geohashKey + "\":[\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]}]"; + + ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFiltersToDecode); + + Filters expectedFilters = new Filters(); + List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); + expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); + + ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); + assertEquals(expectedReqMessage, decodedReqMessage); + } + + @Test + public void testReqMessagePopulatedFilterDecoder() { + log.info("testReqMessagePopulatedFilterDecoder"); + + String subscriptionId = "npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh"; + String kind = "1"; + String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String geohashKey = "#g"; + String geohashValue1 = "2vghde"; + String geohashValue2 = "3abcde"; + String referencedEventId = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; + String reqJsonWithCustomTagQueryFilterToDecode = + "[\"REQ\", " + + "\"" + subscriptionId + "\", " + + "{\"kinds\": [" + kind + "], " + + "\"authors\": [\"" + author + "\"]," + + "\"" + geohashKey + "\": [\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]," + + "\"#e\": [\"" + referencedEventId + "\"]}]"; + + ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); + + Filters expectedFilters = new Filters(); + expectedFilters.setKinds(List.of(Kind.TEXT_NOTE)); + expectedFilters.setAuthors(List.of(new PublicKey(author))); + expectedFilters.setReferencedEvents(List.of(new GenericEvent(referencedEventId))); + List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); + expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); + + ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); + assertEquals(expectedReqMessage, decodedReqMessage); + } + + @Test + public void testReqMessagePopulatedListOfFiltersListDecoder() { + log.info("testReqMessagePopulatedListOfFiltersListDecoder"); + + String subscriptionId = "npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh"; + String kind = "1"; + String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String geohashKey = "#g"; + String geohashValue1 = "2vghde"; + String geohashValue2 = "3abcde"; + String referencedEventId = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; + String uuidKey = "#d"; + String uuidValue1 = "UUID-1"; + String uuidValue2 = "UUID-2"; + String reqJsonWithCustomTagQueryFilterToDecode = + "[\"REQ\", " + + "\"" + subscriptionId + "\", " + + "{\"kinds\": [" + kind + "], " + + "\"authors\": [\"" + author + "\"]," + + "\"" + geohashKey + "\": [\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]," + + "\"" + uuidKey + "\": [\"" + uuidValue1 + "\",\"" + uuidValue2 + "\"]," + + "\"#e\": [\"" + referencedEventId + "\"]}]"; + + ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); + + Filters expectedFilters = new Filters(); + expectedFilters.setKinds(List.of(Kind.TEXT_NOTE)); + expectedFilters.setAuthors(List.of(new PublicKey(author))); + expectedFilters.setReferencedEvents(List.of(new GenericEvent(referencedEventId))); + List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); + expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); + List expectedIdentityTagValuesList = List.of(uuidValue1, uuidValue2); + expectedFilters.setGenericTagQuery(uuidKey, expectedIdentityTagValuesList); + ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); + assertEquals(expectedReqMessage, decodedReqMessage); + } + + @Test + public void testReqMessageSubscriptionIdLength() { + log.info("testReqMessageSubscriptionIdLength"); + String id65Chars = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9ab"; + assertThrows(IllegalArgumentException.class, () -> new ReqMessage(id65Chars, new Filters())); +// assertDoesNotThrow(() -> new ReqMessage(id65Chars, new Filters())); + } + + @Test + public void testReqMessageFilterIdLength() { + log.info("testReqMessageFilterIdLength"); + String id64chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; + String reqJsonId64Chars = + "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id64chars + "\"]}]"; + assertDoesNotThrow(() -> new BaseMessageDecoder().decode(reqJsonId64Chars)); + + String id65chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123"; + String reqJsonId65Chars = + "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id65chars + "\"]}]"; + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId65Chars)); + + String id63chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71"; + String reqJsonId63chars = + "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id63chars + "\"]}]"; + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId63chars)); + } + + @Test + public void testReqMessageDecoderKind() { + log.info("testReqMessageDecoderKind"); + + Function kindTarget = kind -> "[\"REQ\", " + + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + + "{\"kinds\": [" + kind + "]" + + "}]"; + + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget.apply(0))); + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget.apply(65535))); + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(-1))); + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(65536))); + } + + @Test + public void testReqMessageDecoderETag() { + log.info("testReqMessageDecoderETag"); + + String VALID_EVENTID = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; + String VALID_EVENTID_ALL_ZEROS = "0000000000000000000000000000000000000000000000000000000000000000"; + String VALID_EVENTID_ALL_FF = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + String INVALID_EVENTID_NON_HEX_DIGITS = "XYZdf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; + String INVALID_EVENTID_LENGTH_TOO_SHORT = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6"; + String INVALID_EVENTID_LENGTH_TOO_LONG = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f666"; + String INVALID_EVENTID_HAS_MULTIPLE_UPPERCASE = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + String INVALID_EVENTID_HAS_SINGLE_UPPERCASE = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6F"; + + Function eTagTarget = eTag -> "[\"REQ\", " + + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + + "{\"#e\": [\"" + eTag + "\"]}]"; + + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_EVENTID))); + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_EVENTID_ALL_ZEROS))); + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_EVENTID_ALL_FF))); + + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_NON_HEX_DIGITS))).getMessage().contains("has non-hex characters")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_LENGTH_TOO_SHORT))).getMessage().contains("target length")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_LENGTH_TOO_LONG))).getMessage().contains("target length")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_MULTIPLE_UPPERCASE))).getMessage().contains("non-hex characters")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_SINGLE_UPPERCASE))).getMessage().contains("has uppcase characters")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_SINGLE_UPPERCASE))).getMessage().contains("has non-hex characters")); + } + + @Test + public void testReqMessageDecoderPTag() { + log.info("testReqMessageDecoderPTag"); + + String VALID_HEXPUBKEY = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; + String VALID_HEXPUBKEY_ALL_ZEROS = "0000000000000000000000000000000000000000000000000000000000000000"; + String VALID_HEXPUBKEY_ALL_FF = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + String INVALID_HEXPUBKEY_NON_HEX_DIGITS = "XYZdf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; + String INVALID_HEXPUBKEY_LENGTH_TOO_SHORT = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6"; + String INVALID_HEXPUBKEY_LENGTH_TOO_LONG = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f666"; + String INVALID_HEXPUBKEY_HAS_MULTIPLE_UPPERCASE = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + String INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6F"; + + Function eTagTarget = pTag -> "[\"REQ\", " + + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + + "{\"#p\": [\"" + pTag + "\"]}]"; + + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY))); + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY_ALL_ZEROS))); + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY_ALL_FF))); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_NON_HEX_DIGITS))).getMessage().contains("has non-hex characters")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_SHORT))).getMessage().contains("target length")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_LONG))).getMessage().contains("target length")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_MULTIPLE_UPPERCASE))).getMessage().contains("non-hex characters")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE))).getMessage().contains("has uppcase characters")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE))).getMessage().contains("has non-hex characters")); + } } diff --git a/nostr-java-util/src/main/java/nostr/util/NostrUtil.java b/nostr-java-util/src/main/java/nostr/util/NostrUtil.java index d29a02d00..c5228be50 100644 --- a/nostr-java-util/src/main/java/nostr/util/NostrUtil.java +++ b/nostr-java-util/src/main/java/nostr/util/NostrUtil.java @@ -1,5 +1,7 @@ package nostr.util; +import nostr.util.thread.HexStringValidator; + import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -7,7 +9,6 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; -import java.util.Optional; /** * @@ -15,112 +16,104 @@ */ public class NostrUtil { - private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - - public static String bytesToHex(byte[] b) { - char[] hexChars = new char[b.length * 2]; - for (int j = 0; j < b.length; j++) { - int v = b[j] & 0xFF; - hexChars[j * 2] = HEX_ARRAY[v >>> 4]; - hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; - } - return new String(hexChars).toLowerCase(); - } - - public static byte[] hexToBytes(String s) { - validateHexString(s, 64); - return hexToBytesConvert(s); - } - - public static byte[] hex128ToBytes(String s) { - validateHexString(s, 128); - return hexToBytesConvert(s); - } - - private static byte[] hexToBytesConvert(String s) { - int len = s.length(); - byte[] buf = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - buf[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); - } - return buf; - } + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - public static void validateHexString(String hexString, int targetLength) { - Optional.ofNullable(hexString) // non-null enforcement - .filter(s -> s.length() == targetLength) // length enforcement - .filter(s -> s.toLowerCase().equals(s)) // case-sensitivity enforcement, potentially swappable for line below -// .map(String::toLowerCase) // if upper-case leniency is desirable, followed by explicit lower-casing - .orElseThrow(() -> new IllegalArgumentException(String.format("Invalid hex string: [%s], length: [%d], target length: [%d]", hexString, hexString.length(), targetLength))); + public static String bytesToHex(byte[] b) { + char[] hexChars = new char[b.length * 2]; + for (int j = 0; j < b.length; j++) { + int v = b[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; } - - public static byte[] bytesFromInt(int n) { - return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(n).array(); + return new String(hexChars).toLowerCase(); + } + + public static byte[] hexToBytes(String s) { + HexStringValidator.validateHex(s, 64); + return hexToBytesConvert(s); + } + + public static byte[] hex128ToBytes(String s) { + HexStringValidator.validateHex(s, 128); + return hexToBytesConvert(s); + } + + private static byte[] hexToBytesConvert(String s) { + int len = s.length(); + byte[] buf = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + buf[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); } + return buf; + } - public static byte[] bytesFromBigInteger(BigInteger n) { + public static byte[] bytesFromInt(int n) { + return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(n).array(); + } - byte[] b = n.toByteArray(); - - if (b.length == 32) { - return b; - } else if (b.length > 32) { - return Arrays.copyOfRange(b, b.length - 32, b.length); - } else { - byte[] buf = new byte[32]; - System.arraycopy(b, 0, buf, buf.length - b.length, b.length); - return buf; - } - } + public static byte[] bytesFromBigInteger(BigInteger n) { - public static BigInteger bigIntFromBytes(byte[] b) { - return new BigInteger(1, b); - } + byte[] b = n.toByteArray(); - public static byte[] sha256(byte[] b) throws NoSuchAlgorithmException { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - return digest.digest(b); + if (b.length == 32) { + return b; + } else if (b.length > 32) { + return Arrays.copyOfRange(b, b.length - 32, b.length); + } else { + byte[] buf = new byte[32]; + System.arraycopy(b, 0, buf, buf.length - b.length, b.length); + return buf; } + } - public static byte[] xor(byte[] b0, byte[] b1) { - - if (b0.length != b1.length) { - return null; - } + public static BigInteger bigIntFromBytes(byte[] b) { + return new BigInteger(1, b); + } - byte[] ret = new byte[b0.length]; - int i = 0; - for (byte b : b0) { - ret[i] = (byte) (b ^ b1[i]); - i++; - } + public static byte[] sha256(byte[] b) throws NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return digest.digest(b); + } - return ret; - } + public static byte[] xor(byte[] b0, byte[] b1) { - public static byte[] createRandomByteArray(int len) { - byte[] b = new byte[len]; - new SecureRandom().nextBytes(b); - return b; + if (b0.length != b1.length) { + return null; } - public static String escapeJsonString(String jsonString) { - return jsonString.replace("\\", "\\\\") - .replace("\"", "\\\"") - .replace("\b", "\\b") - .replace("\f", "\\f") - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\t", "\\t"); + byte[] ret = new byte[b0.length]; + int i = 0; + for (byte b : b0) { + ret[i] = (byte) (b ^ b1[i]); + i++; } - public static String unEscapeJsonString(String jsonString) { - return jsonString.replace("\\\\", "\\") - .replace("\\\"", "\"") - .replace("\\b", "\b") - .replace("\\f", "\f") - .replace("\\n", "\n") - .replace("\\r", "\r") - .replace("\\t", "\t"); - } + return ret; + } + + public static byte[] createRandomByteArray(int len) { + byte[] b = new byte[len]; + new SecureRandom().nextBytes(b); + return b; + } + + public static String escapeJsonString(String jsonString) { + return jsonString.replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\b", "\\b") + .replace("\f", "\\f") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t"); + } + + public static String unEscapeJsonString(String jsonString) { + return jsonString.replace("\\\\", "\\") + .replace("\\\"", "\"") + .replace("\\b", "\b") + .replace("\\f", "\f") + .replace("\\n", "\n") + .replace("\\r", "\r") + .replace("\\t", "\t"); + } } diff --git a/nostr-java-util/src/main/java/nostr/util/thread/HexStringValidator.java b/nostr-java-util/src/main/java/nostr/util/thread/HexStringValidator.java new file mode 100644 index 000000000..413042eac --- /dev/null +++ b/nostr-java-util/src/main/java/nostr/util/thread/HexStringValidator.java @@ -0,0 +1,50 @@ +package nostr.util.thread; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class HexStringValidator { + private static final String validHexChars = "0123456789ABCDEF"; + + private static BiFunction lengthCheck = (s, targetLength) -> s.length() == targetLength; + private static Function hexCharsCheck = HexStringValidator::checkValidHexChars; + private static Function upperCaseCheck = s -> s.toLowerCase().equals(s); + + public static void validateHex(String hexString, int targetLength) { + List exceptions = new ArrayList<>(); + Optional.ofNullable(hexString) // non-null enforcement + .filter(s -> { + if (!lengthCheck.apply(s, targetLength)) { + return exceptions.add(String.format("Invalid hex string: [%s], length: [%d], target length: [%d]", hexString, hexString.length(), targetLength)); + } + return true; + }) + .filter(s -> { + if (!hexCharsCheck.apply(s)) { + exceptions.add(String.format("Invalid hex string: [%s] has non-hex characters", hexString)); + } + return true; + }) + .filter(s -> { + if (!upperCaseCheck.apply(s)) { + exceptions.add(String.format("Invalid hex string: [%s] has uppcase characters", hexString)); + } + return true; + }); + + if (!exceptions.isEmpty()) { + throw new IllegalArgumentException(exceptions.getFirst()); + } + } + + private static Boolean checkValidHexChars(String aHexString) { + for (char a : aHexString.toCharArray()) { + if (validHexChars.toLowerCase().indexOf(a) < 0) + return false; + } + return true; + } +} From 5c5a83741dc27c054e24817aa7c1406ffa3ac89d Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 7 Jan 2025 17:57:32 -0500 Subject: [PATCH 08/16] test for explicit case error messages, useful for messaging specifics to clients --- .../main/java/nostr/event/impl/GenericEvent.java | 3 ++- .../test/java/nostr/test/json/JsonParseTest.java | 14 ++++++-------- .../java/nostr/util/thread/HexStringValidator.java | 6 +++--- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/impl/GenericEvent.java b/nostr-java-event/src/main/java/nostr/event/impl/GenericEvent.java index ef69b8799..d3e11f90d 100644 --- a/nostr-java-event/src/main/java/nostr/event/impl/GenericEvent.java +++ b/nostr-java-event/src/main/java/nostr/event/impl/GenericEvent.java @@ -26,6 +26,7 @@ import nostr.event.json.deserializer.SignatureDeserializer; import nostr.util.NostrException; import nostr.util.NostrUtil; +import nostr.util.thread.HexStringValidator; import java.beans.Transient; import java.nio.charset.StandardCharsets; @@ -129,7 +130,7 @@ public GenericEvent(@NonNull PublicKey pubKey, @NonNull Integer kind, @NonNull L } public void setId(String id) { - NostrUtil.validateHexString(id, 64); + HexStringValidator.validateHex(id, 64); this.id = id; } diff --git a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java index 8b8082e03..9c1c19f4f 100644 --- a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java +++ b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java @@ -516,9 +516,8 @@ public void testReqMessageDecoderETag() { assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_NON_HEX_DIGITS))).getMessage().contains("has non-hex characters")); assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_LENGTH_TOO_SHORT))).getMessage().contains("target length")); assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_LENGTH_TOO_LONG))).getMessage().contains("target length")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_MULTIPLE_UPPERCASE))).getMessage().contains("non-hex characters")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_MULTIPLE_UPPERCASE))).getMessage().contains("has uppcase characters")); assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_SINGLE_UPPERCASE))).getMessage().contains("has uppcase characters")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_SINGLE_UPPERCASE))).getMessage().contains("has non-hex characters")); } @Test @@ -541,11 +540,10 @@ public void testReqMessageDecoderPTag() { assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY))); assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY_ALL_ZEROS))); assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY_ALL_FF))); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_NON_HEX_DIGITS))).getMessage().contains("has non-hex characters")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_SHORT))).getMessage().contains("target length")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_LONG))).getMessage().contains("target length")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_MULTIPLE_UPPERCASE))).getMessage().contains("non-hex characters")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE))).getMessage().contains("has uppcase characters")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE))).getMessage().contains("has non-hex characters")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_NON_HEX_DIGITS))).getMessage().contains("has non-hex characters")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_SHORT))).getMessage().contains("target length")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_LONG))).getMessage().contains("target length")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_MULTIPLE_UPPERCASE))).getMessage().contains("has uppcase characters")); + assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE))).getMessage().contains("has uppcase characters")); } } diff --git a/nostr-java-util/src/main/java/nostr/util/thread/HexStringValidator.java b/nostr-java-util/src/main/java/nostr/util/thread/HexStringValidator.java index 413042eac..f5a4977c8 100644 --- a/nostr-java-util/src/main/java/nostr/util/thread/HexStringValidator.java +++ b/nostr-java-util/src/main/java/nostr/util/thread/HexStringValidator.java @@ -7,7 +7,7 @@ import java.util.function.Function; public class HexStringValidator { - private static final String validHexChars = "0123456789ABCDEF"; + private static final String validHexChars = "0123456789abcdef"; private static BiFunction lengthCheck = (s, targetLength) -> s.length() == targetLength; private static Function hexCharsCheck = HexStringValidator::checkValidHexChars; @@ -41,8 +41,8 @@ public static void validateHex(String hexString, int targetLength) { } private static Boolean checkValidHexChars(String aHexString) { - for (char a : aHexString.toCharArray()) { - if (validHexChars.toLowerCase().indexOf(a) < 0) + for (char a : aHexString.toLowerCase().toCharArray()) { + if (validHexChars.indexOf(a) < 0) return false; } return true; From abe01c9cce4a92c8b3c2f023b118403fa81d9575 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 7 Jan 2025 19:23:58 -0500 Subject: [PATCH 09/16] added since and until filter validation and tests --- .../src/main/java/nostr/event/Kind.java | 2 +- .../main/java/nostr/event/impl/Filters.java | 15 +++ .../nostr/test/event/KindMappingTest.java | 46 ++++---- .../java/nostr/test/json/JsonParseTest.java | 100 +++++++++++++++--- 4 files changed, 118 insertions(+), 45 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/Kind.java b/nostr-java-event/src/main/java/nostr/event/Kind.java index fce15b159..4b88c6b36 100644 --- a/nostr-java-event/src/main/java/nostr/event/Kind.java +++ b/nostr-java-event/src/main/java/nostr/event/Kind.java @@ -50,7 +50,7 @@ public enum Kind { @JsonCreator public static Kind valueOf(int value) { if (!ValueRange.of(0, 65535).isValidIntValue(value)) { - throw new IllegalArgumentException(String.format("Kind must be between 0 and 65536 but was [%d]", value)); + throw new IllegalArgumentException(String.format("Kind must be between 0 and 65535 but was [%d]", value)); } for (Kind k : values()) { if (k.getValue() == value) { diff --git a/nostr-java-event/src/main/java/nostr/event/impl/Filters.java b/nostr-java-event/src/main/java/nostr/event/impl/Filters.java index 44272f9f3..4e987ab32 100644 --- a/nostr-java-event/src/main/java/nostr/event/impl/Filters.java +++ b/nostr-java-event/src/main/java/nostr/event/impl/Filters.java @@ -10,6 +10,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.NonNull; import lombok.Setter; import nostr.base.PublicKey; import nostr.base.annotation.Key; @@ -66,6 +67,20 @@ public class Filters { @Setter(AccessLevel.NONE) private Map> genericTagQuery; + public void setUntil(@NonNull Long until) { + if (until < 0) { + throw new IllegalArgumentException("'until' filter cannot be negative."); + } + this.until = until; + } + + public void setSince(@NonNull Long since) { + if (since < 0) { + throw new IllegalArgumentException("'since' filter cannot be negative."); + } + this.since = since; + } + @JsonAnyGetter public Map> getGenericTagQuery() { return genericTagQuery; diff --git a/nostr-java-test/src/test/java/nostr/test/event/KindMappingTest.java b/nostr-java-test/src/test/java/nostr/test/event/KindMappingTest.java index 014dfec92..48b1f5283 100644 --- a/nostr-java-test/src/test/java/nostr/test/event/KindMappingTest.java +++ b/nostr-java-test/src/test/java/nostr/test/event/KindMappingTest.java @@ -1,28 +1,18 @@ -package nostr.test.event; - -import nostr.event.Kind; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class KindMappingTest { - @Test - void testKindValueOf() { - assertEquals("1", Kind.valueOf(1).toString()); - } - - @Test - void testKindName() { - assertEquals("text_note", Kind.valueOf(1).getName()); - } - - @Test - void testKindValueOfUndefined() { - assertEquals("-1", Kind.valueOf(9999999).toString()); - } - - @Test - void testKindUndefinedName() { - assertEquals("undefined", Kind.valueOf(9999999).getName()); - } -} \ No newline at end of file +package nostr.test.event; + +import nostr.event.Kind; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class KindMappingTest { + @Test + void testKindValueOf() { + assertEquals("1", Kind.valueOf(1).toString()); + } + + @Test + void testKindName() { + assertEquals("text_note", Kind.valueOf(1).getName()); + } +} diff --git a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java index 9c1c19f4f..3fe6668d2 100644 --- a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java +++ b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java @@ -1,5 +1,6 @@ package nostr.test.json; +import com.fasterxml.jackson.databind.JsonMappingException; import lombok.extern.java.Log; import nostr.api.NIP01; import nostr.base.Command; @@ -454,8 +455,9 @@ public void testReqMessagePopulatedListOfFiltersListDecoder() { public void testReqMessageSubscriptionIdLength() { log.info("testReqMessageSubscriptionIdLength"); String id65Chars = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9ab"; - assertThrows(IllegalArgumentException.class, () -> new ReqMessage(id65Chars, new Filters())); -// assertDoesNotThrow(() -> new ReqMessage(id65Chars, new Filters())); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new ReqMessage(id65Chars, new Filters())) + .getMessage().contains("subscriptionId length must be between 1 and 64 characters but was [65]")); } @Test @@ -469,12 +471,16 @@ public void testReqMessageFilterIdLength() { String id65chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123"; String reqJsonId65Chars = "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id65chars + "\"]}]"; - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId65Chars)); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId65Chars)) + .getMessage().contains("[fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123], length: [65], target length: [64]")); String id63chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71"; String reqJsonId63chars = "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id63chars + "\"]}]"; - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId63chars)); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId63chars)) + .getMessage().contains("[fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71], length: [63], target length: [64]")); } @Test @@ -488,8 +494,13 @@ public void testReqMessageDecoderKind() { assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget.apply(0))); assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget.apply(65535))); - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(-1))); - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(65536))); + + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(-1))) + .getMessage().contains("Kind must be between 0 and 65535 but was [-1]")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(65536))) + .getMessage().contains("Kind must be between 0 and 65535 but was [65536]")); } @Test @@ -513,11 +524,21 @@ public void testReqMessageDecoderETag() { assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_EVENTID_ALL_ZEROS))); assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_EVENTID_ALL_FF))); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_NON_HEX_DIGITS))).getMessage().contains("has non-hex characters")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_LENGTH_TOO_SHORT))).getMessage().contains("target length")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_LENGTH_TOO_LONG))).getMessage().contains("target length")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_MULTIPLE_UPPERCASE))).getMessage().contains("has uppcase characters")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_SINGLE_UPPERCASE))).getMessage().contains("has uppcase characters")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_NON_HEX_DIGITS))) + .getMessage().contains("has non-hex characters")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_LENGTH_TOO_SHORT))) + .getMessage().contains("target length")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_LENGTH_TOO_LONG))) + .getMessage().contains("target length")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_MULTIPLE_UPPERCASE))) + .getMessage().contains("has uppcase characters")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_SINGLE_UPPERCASE))) + .getMessage().contains("has uppcase characters")); } @Test @@ -540,10 +561,57 @@ public void testReqMessageDecoderPTag() { assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY))); assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY_ALL_ZEROS))); assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY_ALL_FF))); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_NON_HEX_DIGITS))).getMessage().contains("has non-hex characters")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_SHORT))).getMessage().contains("target length")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_LONG))).getMessage().contains("target length")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_MULTIPLE_UPPERCASE))).getMessage().contains("has uppcase characters")); - assertTrue(assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE))).getMessage().contains("has uppcase characters")); + + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_NON_HEX_DIGITS))) + .getMessage().contains("has non-hex characters")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_SHORT))) + .getMessage().contains("target length")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_LONG))) + .getMessage().contains("target length")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_MULTIPLE_UPPERCASE))) + .getMessage().contains("has uppcase characters")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE))) + .getMessage().contains("has uppcase characters")); + } + + @Test + public void testReqMessageFilterSince() { + log.info("testReqMessageFilterSince"); + Function sinceTarget = since -> "[\"REQ\", " + + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + + "{\"since\": " + since + "}]"; + + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(sinceTarget.apply(null))) + .getMessage().contains("since is marked non-null but is null")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(sinceTarget.apply("-1"))) + .getMessage().contains("'since' filter cannot be negative")); + assertTrue( + assertThrows(JsonMappingException.class, () -> new BaseMessageDecoder<>().decode(sinceTarget.apply("a"))) + .getMessage().contains("Unrecognized token 'a'")); + } + + @Test + public void testReqMessageFilterUntil() { + log.info("testReqMessageFilterUntil"); + Function untilTarget = until -> "[\"REQ\", " + + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + + "{\"until\": " + until + "}]"; + + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(untilTarget.apply(null))) + .getMessage().contains("until is marked non-null but is null")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(untilTarget.apply("-1"))) + .getMessage().contains("'until' filter cannot be negative")); + assertTrue( + assertThrows(JsonMappingException.class, () -> new BaseMessageDecoder<>().decode(untilTarget.apply("a"))) + .getMessage().contains("Unrecognized token 'a'")); } } From b9acf8b61455dfe1f2bb0796e6a19fdab890fa7d Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 7 Jan 2025 19:40:02 -0500 Subject: [PATCH 10/16] added event signature validity tests --- .../java/nostr/test/event/SignatureTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 nostr-java-test/src/test/java/nostr/test/event/SignatureTest.java diff --git a/nostr-java-test/src/test/java/nostr/test/event/SignatureTest.java b/nostr-java-test/src/test/java/nostr/test/event/SignatureTest.java new file mode 100644 index 000000000..18b016199 --- /dev/null +++ b/nostr-java-test/src/test/java/nostr/test/event/SignatureTest.java @@ -0,0 +1,20 @@ +package nostr.test.event; + +import nostr.base.Signature; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SignatureTest { + @Test + public void testSignatureStringLength() { + assertDoesNotThrow(() -> + Signature.fromString("86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546")); + + assertTrue( + assertThrows(IllegalArgumentException.class, () -> Signature.fromString("86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546a")) + .getMessage().contains("[129], target length: [128]")); + } +} From 21fa27870d4acd6037381189cd405e8960c30aff Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 7 Jan 2025 20:48:12 -0500 Subject: [PATCH 11/16] maintain original indentation --- .../src/main/java/nostr/util/NostrUtil.java | 170 +++++++++--------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/nostr-java-util/src/main/java/nostr/util/NostrUtil.java b/nostr-java-util/src/main/java/nostr/util/NostrUtil.java index c5228be50..855ae1883 100644 --- a/nostr-java-util/src/main/java/nostr/util/NostrUtil.java +++ b/nostr-java-util/src/main/java/nostr/util/NostrUtil.java @@ -16,104 +16,104 @@ */ public class NostrUtil { - private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - - public static String bytesToHex(byte[] b) { - char[] hexChars = new char[b.length * 2]; - for (int j = 0; j < b.length; j++) { - int v = b[j] & 0xFF; - hexChars[j * 2] = HEX_ARRAY[v >>> 4]; - hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + + public static String bytesToHex(byte[] b) { + char[] hexChars = new char[b.length * 2]; + for (int j = 0; j < b.length; j++) { + int v = b[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars).toLowerCase(); } - return new String(hexChars).toLowerCase(); - } - - public static byte[] hexToBytes(String s) { - HexStringValidator.validateHex(s, 64); - return hexToBytesConvert(s); - } - - public static byte[] hex128ToBytes(String s) { - HexStringValidator.validateHex(s, 128); - return hexToBytesConvert(s); - } - - private static byte[] hexToBytesConvert(String s) { - int len = s.length(); - byte[] buf = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - buf[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); + + public static byte[] hexToBytes(String s) { + HexStringValidator.validateHex(s, 64); + return hexToBytesConvert(s); } - return buf; - } - public static byte[] bytesFromInt(int n) { - return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(n).array(); - } + public static byte[] hex128ToBytes(String s) { + HexStringValidator.validateHex(s, 128); + return hexToBytesConvert(s); + } - public static byte[] bytesFromBigInteger(BigInteger n) { + private static byte[] hexToBytesConvert(String s) { + int len = s.length(); + byte[] buf = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + buf[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); + } + return buf; + } - byte[] b = n.toByteArray(); + public static byte[] bytesFromInt(int n) { + return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(n).array(); + } - if (b.length == 32) { - return b; - } else if (b.length > 32) { - return Arrays.copyOfRange(b, b.length - 32, b.length); - } else { - byte[] buf = new byte[32]; - System.arraycopy(b, 0, buf, buf.length - b.length, b.length); - return buf; + public static byte[] bytesFromBigInteger(BigInteger n) { + + byte[] b = n.toByteArray(); + + if (b.length == 32) { + return b; + } else if (b.length > 32) { + return Arrays.copyOfRange(b, b.length - 32, b.length); + } else { + byte[] buf = new byte[32]; + System.arraycopy(b, 0, buf, buf.length - b.length, b.length); + return buf; + } } - } - public static BigInteger bigIntFromBytes(byte[] b) { - return new BigInteger(1, b); - } + public static BigInteger bigIntFromBytes(byte[] b) { + return new BigInteger(1, b); + } - public static byte[] sha256(byte[] b) throws NoSuchAlgorithmException { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - return digest.digest(b); - } + public static byte[] sha256(byte[] b) throws NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return digest.digest(b); + } + + public static byte[] xor(byte[] b0, byte[] b1) { + + if (b0.length != b1.length) { + return null; + } - public static byte[] xor(byte[] b0, byte[] b1) { + byte[] ret = new byte[b0.length]; + int i = 0; + for (byte b : b0) { + ret[i] = (byte) (b ^ b1[i]); + i++; + } - if (b0.length != b1.length) { - return null; + return ret; } - byte[] ret = new byte[b0.length]; - int i = 0; - for (byte b : b0) { - ret[i] = (byte) (b ^ b1[i]); - i++; + public static byte[] createRandomByteArray(int len) { + byte[] b = new byte[len]; + new SecureRandom().nextBytes(b); + return b; } - return ret; - } - - public static byte[] createRandomByteArray(int len) { - byte[] b = new byte[len]; - new SecureRandom().nextBytes(b); - return b; - } - - public static String escapeJsonString(String jsonString) { - return jsonString.replace("\\", "\\\\") - .replace("\"", "\\\"") - .replace("\b", "\\b") - .replace("\f", "\\f") - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\t", "\\t"); - } - - public static String unEscapeJsonString(String jsonString) { - return jsonString.replace("\\\\", "\\") - .replace("\\\"", "\"") - .replace("\\b", "\b") - .replace("\\f", "\f") - .replace("\\n", "\n") - .replace("\\r", "\r") - .replace("\\t", "\t"); - } + public static String escapeJsonString(String jsonString) { + return jsonString.replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\b", "\\b") + .replace("\f", "\\f") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t"); + } + + public static String unEscapeJsonString(String jsonString) { + return jsonString.replace("\\\\", "\\") + .replace("\\\"", "\"") + .replace("\\b", "\b") + .replace("\\f", "\f") + .replace("\\n", "\n") + .replace("\\r", "\r") + .replace("\\t", "\t"); + } } From f3e81a32b5515c0fce2c18b8fccb49bee83d7d79 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 7 Jan 2025 20:49:33 -0500 Subject: [PATCH 12/16] maintain original indentation --- .../java/nostr/test/json/JsonParseTest.java | 1124 ++++++++--------- 1 file changed, 562 insertions(+), 562 deletions(-) diff --git a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java index 3fe6668d2..d0ee584f4 100644 --- a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java +++ b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java @@ -49,569 +49,569 @@ @Log public class JsonParseTest { - @Test - public void testBaseMessageDecoder() { - log.info("testBaseMessageDecoder"); + @Test + public void testBaseMessageDecoder() { + log.info("testBaseMessageDecoder"); - final String parseTarget = - "[\"REQ\", " + + final String parseTarget = + "[\"REQ\", " + + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + + "{\"kinds\": [1], " + + "\"authors\": [\"f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75\"]," + + "\"#e\": [\"fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712\"]}]"; + + final var message = new BaseMessageDecoder<>().decode(parseTarget); + + assertEquals(Command.REQ.toString(), message.getCommand()); + assertEquals("npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh", ((ReqMessage) message).getSubscriptionId()); + assertEquals(1, ((ReqMessage) message).getFiltersList().size()); + + var filters = ((ReqMessage) message).getFiltersList().get(0); + + assertEquals(1, filters.getKinds().size()); + assertEquals(Kind.TEXT_NOTE, filters.getKinds().get(0)); + + assertEquals(1, filters.getAuthors().size()); + assertEquals("npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh", filters.getAuthors().get(0).toBech32String()); + + assertEquals(1, filters.getReferencedEvents().size()); + assertEquals("fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712", (filters.getReferencedEvents().get(0).getId())); + } + + @Test + public void testBaseReqMessageDecoder() { + log.info("testBaseReqMessageDecoder"); + + final var filtersList = new ArrayList(); + var publicKey = Identity.generateRandomIdentity().getPublicKey(); + filtersList.add(Filters.builder().authors(new ArrayList<>(List.of(publicKey))).kinds(new ArrayList<>(List.of(Kind.CONTACT_LIST, Kind.DELETION))).build()); + filtersList.add(Filters.builder().kinds(new ArrayList<>(List.of(Kind.SET_METADATA, Kind.TEXT_NOTE))).build()); + final var reqMessage = new ReqMessage(publicKey.toString(), filtersList); + + assertDoesNotThrow(() -> { + String jsonMessage = reqMessage.encode(); + + String jsonMsg = jsonMessage.substring(1, jsonMessage.length() - 1); + String[] parts = jsonMsg.split(","); + assertEquals("\"REQ\"", parts[0]); + assertEquals("\"" + publicKey.toString() + "\"", parts[1]); + assertFalse(parts[2].startsWith("[")); + assertFalse(parts[parts.length - 1].endsWith("]")); + + BaseMessage message = new BaseMessageDecoder<>().decode(jsonMessage); + + assertEquals(reqMessage, message); + }); + } + + @Test + public void testBaseEventMessageDecoder() { + log.info("testBaseEventMessageDecoder"); + + final String parseTarget + = "[\"EVENT\"," + + "{" + + "\"content\":\"直んないわ。まあええか\"," + + "\"created_at\":1686199583," + + "\"id\":\"fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712\"," + + "\"kind\":1," + + "\"pubkey\":\"8c59239319637f97e007dad0d681e65ce35b1ace333b629e2d33f9465c132608\"," + + "\"sig\":\"9584afd231c52fcbcec6ce668a2cc4b6dc9b4d9da20510dcb9005c6844679b4844edb7a2e1e0591958b0295241567c774dbf7d39a73932877542de1a5f963f4b\"," + + "\"tags\":[]" + + "}]"; + + final var message = new BaseMessageDecoder<>().decode(parseTarget); + + assertEquals(Command.EVENT.toString(), message.getCommand()); + + final var event = (GenericEvent) (((EventMessage) message).getEvent()); + assertEquals(1, event.getKind().intValue()); + assertEquals(1686199583, event.getCreatedAt().longValue()); + assertEquals("fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712", event.getId()); + } + + @Test + public void testBaseEventMessageMarkerDecoder() { + log.info("testBaseEventMessageMarkerDecoder"); + + final String json = "[" + + "\"EVENT\"," + + "{" + + "\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\"," + + "\"kind\":1," + + "\"pubkey\":\"2bed79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984\"," + + "\"created_at\":1687765220," + + "\"content\":\"手順書が間違ってたら作業者は無理だな\"," + + "\"tags\":[" + + "[\"e\",\"494001ac0c8af2a10f60f23538e5b35d3cdacb8e1cc956fe7a16dfa5cbfc4346\",\"\",\"root\"]," + + "[\"p\",\"2bed79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984\"]" + + "]," + + "\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"" + + "}]"; + + BaseMessage message = new BaseMessageDecoder<>().decode(json); + + final var event = (GenericEvent) (((EventMessage) message).getEvent()); + var tags = event.getTags(); + for (BaseTag t : tags) { + if (t.getCode().equalsIgnoreCase("e")) { + EventTag et = (EventTag) t; + assertEquals(Marker.ROOT, et.getMarker()); + } + } + } + + @Test + public void testGenericTagDecoder() { + log.info("testGenericTagDecoder"); + final String jsonString = "[\"saturn\", \"jetpack\", false]"; + + var tag = new GenericTagDecoder<>().decode(jsonString); + + assertEquals("saturn", tag.getCode()); + assertEquals(2, tag.getAttributes().size()); + assertEquals("jetpack", ((ElementAttribute) (tag.getAttributes().toArray())[0]).getValue()); + assertEquals(false, Boolean.valueOf(((ElementAttribute) (tag.getAttributes().toArray())[1]).getValue().toString())); + } + + @Test + public void testClassifiedListingTagSerializer() { + log.info("testClassifiedListingSerializer"); + final String classifiedListingEventJson = "{" + + "\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\"," + + "\"kind\":30402," + + "\"content\":\"content ipsum\"," + + "\"pubkey\":\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\"," + + "\"created_at\":1687765220," + + "\"tags\":[" + + "[\"p\",\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\"]," + + "[\"title\",\"title ipsum\"]," + + "[\"summary\",\"summary ipsum\"]," + + "[\"published_at\",\"1687765220\"]," + + "[\"location\",\"location ipsum\"]," + + "[\"price\",\"11111\",\"BTC\",\"1\"]]," + + "\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"" + + "}]"; + + GenericEvent event = new GenericEventDecoder<>().decode(classifiedListingEventJson); + EventMessage message = NIP01.createEventMessage(event, "1"); + assertEquals(1, message.getNip()); + String encoded = new BaseEventEncoder<>((BaseEvent) message.getEvent()).encode(); + assertEquals("{\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\",\"kind\":30402,\"content\":\"content ipsum\",\"pubkey\":\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\",\"created_at\":1687765220,\"tags\":[[\"p\",\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\"],[\"title\",\"title ipsum\"],[\"summary\",\"summary ipsum\"],[\"published_at\",\"1687765220\"],[\"location\",\"location ipsum\"],[\"price\",\"11111\",\"BTC\",\"1\"]],\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"}", encoded); + + assertEquals("28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a", event.getId()); + assertEquals(30402, event.getKind()); + assertEquals("content ipsum", event.getContent()); + assertEquals("ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de", event.getPubKey().toString()); + assertEquals(1687765220L, event.getCreatedAt()); + assertEquals("86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546", event.getSignature().toString()); + + assertEquals(new BigDecimal("11111"), event.getTags().stream().filter(baseTag -> + baseTag.getCode().equalsIgnoreCase("price")) + .filter(PriceTag.class::isInstance) + .map(PriceTag.class::cast) + .map(PriceTag::getNumber).findFirst().orElseThrow()); + + assertEquals("BTC", event.getTags().stream().filter(baseTag -> + baseTag.getCode().equalsIgnoreCase("price")) + .filter(PriceTag.class::isInstance) + .map(PriceTag.class::cast) + .map(PriceTag::getCurrency).findFirst().orElseThrow()); + + assertEquals("1", event.getTags().stream().filter(baseTag -> + baseTag.getCode().equalsIgnoreCase("price")) + .filter(PriceTag.class::isInstance) + .map(PriceTag.class::cast) + .map(PriceTag::getFrequency).findFirst().orElseThrow()); + + List genericTags = event.getTags().stream() + .filter(GenericTag.class::isInstance) + .map(GenericTag.class::cast).toList(); + + assertEquals("title ipsum", genericTags.stream() + .filter(tag -> tag.getCode().equalsIgnoreCase("title")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); + + assertEquals("summary ipsum", genericTags.stream() + .filter(tag -> tag.getCode().equalsIgnoreCase("summary")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); + + assertEquals("1687765220", genericTags.stream() + .filter(tag -> tag.getCode().equalsIgnoreCase("published_at")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); + + assertEquals("location ipsum", genericTags.stream() + .filter(tag -> tag.getCode().equalsIgnoreCase("location")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); + } + + @Test + public void testDeserializeTag() { + log.info("testDeserializeTag"); + + assertDoesNotThrow(() -> { + String npubHex = new PublicKey(NostrUtil.hexToBytes(Bech32.fromBech32(("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9")))).toString(); + + final String jsonString = "[\"p\", \"" + npubHex + "\", \"wss://nostr.java\", \"alice\"]"; + var tag = new BaseTagDecoder<>().decode(jsonString); + + assertTrue(tag instanceof PubKeyTag); + + PubKeyTag pTag = (PubKeyTag) tag; + assertEquals("wss://nostr.java", pTag.getMainRelayUrl()); + assertEquals(npubHex, pTag.getPublicKey().toString()); + assertEquals("alice", pTag.getPetName()); + }); + } + + @Test + public void testDeserializeGenericTag() { + log.info("testDeserializeGenericTag"); + assertDoesNotThrow(() -> { + String npubHex = new PublicKey(Bech32.decode("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9").data).toString(); + final String jsonString = "[\"gt\", \"" + npubHex + "\", \"wss://nostr.java\", \"alice\"]"; + var tag = new BaseTagDecoder<>().decode(jsonString); + + assertTrue(tag instanceof GenericTag); + + GenericTag gTag = (GenericTag) tag; + assertEquals("gt", gTag.getCode()); + }); + } + + @Test + public void testFiltersEncoder() { + log.info("testFiltersEncoder"); + + String new_geohash = "2vghde"; + List geohashList = new ArrayList<>(); + geohashList.add(new_geohash); + Filters filters = Filters.builder().genericTagQuery(Map.of("#g", geohashList)).build(); + + FiltersEncoder encoder = new FiltersEncoder(filters); + String jsonMessage = encoder.encode(); + assertEquals("{\"#g\":[\"2vghde\"]}", jsonMessage); + } + + @Test + public void testReqMessageFilterListSerializer() { + log.info("testReqMessageFilterListSerializer"); + + String new_geohash = "2vghde"; + String second_geohash = "3abcde"; + List geohashList = new ArrayList<>(); + geohashList.add(new_geohash); + geohashList.add(second_geohash); + Filters filters = Filters.builder().genericTagQuery(Map.of("#g", geohashList)).build(); + + ReqMessage reqMessage = new ReqMessage("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9", new ArrayList(List.of(filters))); + assertDoesNotThrow(() -> { + String jsonMessage = reqMessage.encode(); + + assertEquals("[\"REQ\",\"npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9\",{\"#g\":[\"2vghde\",\"3abcde\"]}]", jsonMessage); + }); + } + + @Test + public void testReqMessageFiltersDecoder() { + log.info("testReqMessageFiltersDecoder"); + + String geohashKey = "#g"; + String geohashValue = "2vghde"; + String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + geohashKey + "\":[\"" + geohashValue + "\"]}"; + + Filters decodedFilters = new FiltersDecoder<>().decode(reqJsonWithCustomTagQueryFilterToDecode); + + Filters expectedFilters = new Filters(); + List expectedGeohashValueList = List.of(geohashValue); + expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValueList); + + assertEquals(expectedFilters, decodedFilters); + } + + @Test + public void testReqMessageFiltersListDecoder() { + log.info("testReqMessageFiltersListDecoder"); + + String geohashKey = "#g"; + String geohashValue1 = "2vghde"; + String geohashValue2 = "3abcde"; + String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + geohashKey + "\":[\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]}"; + + Filters decodedFilters = new FiltersDecoder<>().decode(reqJsonWithCustomTagQueryFilterToDecode); + + Filters expectedFilters = new Filters(); + List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); + expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); + + assertEquals(expectedFilters, decodedFilters); + } + + @Test + public void testReqMessageDeserializer() { + log.info("testReqMessageDeserializer"); + + String subscriptionId = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9"; + String geohashKey = "#g"; + String geohashValue = "2vghde"; + String reqJsonWithCustomTagQueryFilterToDecode = "[\"REQ\",\"" + subscriptionId + "\",{\"" + geohashKey + "\":[\"" + geohashValue + "\"]}]"; + + ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); + + Filters expectedFilters = new Filters(); + List expectedGeohashValuesList = List.of(geohashValue); + expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); + + ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); + assertEquals(expectedReqMessage, decodedReqMessage); + } + + @Test + public void testReqMessageFilterListDecoder() { + log.info("testReqMessageFilterListDecoder"); + + String subscriptionId = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9"; + String geohashKey = "#g"; + String geohashValue1 = "2vghde"; + String geohashValue2 = "3abcde"; + String reqJsonWithCustomTagQueryFiltersToDecode = "[\"REQ\",\"" + subscriptionId + "\",{\"" + geohashKey + "\":[\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]}]"; + + ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFiltersToDecode); + + Filters expectedFilters = new Filters(); + List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); + expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); + + ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); + assertEquals(expectedReqMessage, decodedReqMessage); + } + + @Test + public void testReqMessagePopulatedFilterDecoder() { + log.info("testReqMessagePopulatedFilterDecoder"); + + String subscriptionId = "npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh"; + String kind = "1"; + String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String geohashKey = "#g"; + String geohashValue1 = "2vghde"; + String geohashValue2 = "3abcde"; + String referencedEventId = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; + String reqJsonWithCustomTagQueryFilterToDecode = + "[\"REQ\", " + + "\"" + subscriptionId + "\", " + + "{\"kinds\": [" + kind + "], " + + "\"authors\": [\"" + author + "\"]," + + "\"" + geohashKey + "\": [\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]," + + "\"#e\": [\"" + referencedEventId + "\"]}]"; + + ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); + + Filters expectedFilters = new Filters(); + expectedFilters.setKinds(List.of(Kind.TEXT_NOTE)); + expectedFilters.setAuthors(List.of(new PublicKey(author))); + expectedFilters.setReferencedEvents(List.of(new GenericEvent(referencedEventId))); + List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); + expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); + + ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); + assertEquals(expectedReqMessage, decodedReqMessage); + } + + @Test + public void testReqMessagePopulatedListOfFiltersListDecoder() { + log.info("testReqMessagePopulatedListOfFiltersListDecoder"); + + String subscriptionId = "npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh"; + String kind = "1"; + String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String geohashKey = "#g"; + String geohashValue1 = "2vghde"; + String geohashValue2 = "3abcde"; + String referencedEventId = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; + String uuidKey = "#d"; + String uuidValue1 = "UUID-1"; + String uuidValue2 = "UUID-2"; + String reqJsonWithCustomTagQueryFilterToDecode = + "[\"REQ\", " + + "\"" + subscriptionId + "\", " + + "{\"kinds\": [" + kind + "], " + + "\"authors\": [\"" + author + "\"]," + + "\"" + geohashKey + "\": [\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]," + + "\"" + uuidKey + "\": [\"" + uuidValue1 + "\",\"" + uuidValue2 + "\"]," + + "\"#e\": [\"" + referencedEventId + "\"]}]"; + + ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); + + Filters expectedFilters = new Filters(); + expectedFilters.setKinds(List.of(Kind.TEXT_NOTE)); + expectedFilters.setAuthors(List.of(new PublicKey(author))); + expectedFilters.setReferencedEvents(List.of(new GenericEvent(referencedEventId))); + List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); + expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); + List expectedIdentityTagValuesList = List.of(uuidValue1, uuidValue2); + expectedFilters.setGenericTagQuery(uuidKey, expectedIdentityTagValuesList); + ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); + assertEquals(expectedReqMessage, decodedReqMessage); + } + + @Test + public void testReqMessageSubscriptionIdLength() { + log.info("testReqMessageSubscriptionIdLength"); + String id65Chars = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9ab"; + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new ReqMessage(id65Chars, new Filters())) + .getMessage().contains("subscriptionId length must be between 1 and 64 characters but was [65]")); + } + + @Test + public void testReqMessageFilterIdLength() { + log.info("testReqMessageFilterIdLength"); + String id64chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; + String reqJsonId64Chars = + "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id64chars + "\"]}]"; + assertDoesNotThrow(() -> new BaseMessageDecoder().decode(reqJsonId64Chars)); + + String id65chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123"; + String reqJsonId65Chars = + "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id65chars + "\"]}]"; + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId65Chars)) + .getMessage().contains("[fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123], length: [65], target length: [64]")); + + String id63chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71"; + String reqJsonId63chars = + "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id63chars + "\"]}]"; + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId63chars)) + .getMessage().contains("[fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71], length: [63], target length: [64]")); + } + + @Test + public void testReqMessageDecoderKind() { + log.info("testReqMessageDecoderKind"); + + Function kindTarget = kind -> "[\"REQ\", " + + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + + "{\"kinds\": [" + kind + "]" + + "}]"; + + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget.apply(0))); + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget.apply(65535))); + + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(-1))) + .getMessage().contains("Kind must be between 0 and 65535 but was [-1]")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(65536))) + .getMessage().contains("Kind must be between 0 and 65535 but was [65536]")); + } + + @Test + public void testReqMessageDecoderETag() { + log.info("testReqMessageDecoderETag"); + + String VALID_EVENTID = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; + String VALID_EVENTID_ALL_ZEROS = "0000000000000000000000000000000000000000000000000000000000000000"; + String VALID_EVENTID_ALL_FF = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + String INVALID_EVENTID_NON_HEX_DIGITS = "XYZdf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; + String INVALID_EVENTID_LENGTH_TOO_SHORT = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6"; + String INVALID_EVENTID_LENGTH_TOO_LONG = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f666"; + String INVALID_EVENTID_HAS_MULTIPLE_UPPERCASE = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + String INVALID_EVENTID_HAS_SINGLE_UPPERCASE = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6F"; + + Function eTagTarget = eTag -> "[\"REQ\", " + + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + + "{\"#e\": [\"" + eTag + "\"]}]"; + + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_EVENTID))); + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_EVENTID_ALL_ZEROS))); + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_EVENTID_ALL_FF))); + + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_NON_HEX_DIGITS))) + .getMessage().contains("has non-hex characters")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_LENGTH_TOO_SHORT))) + .getMessage().contains("target length")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_LENGTH_TOO_LONG))) + .getMessage().contains("target length")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_MULTIPLE_UPPERCASE))) + .getMessage().contains("has uppcase characters")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_SINGLE_UPPERCASE))) + .getMessage().contains("has uppcase characters")); + } + + @Test + public void testReqMessageDecoderPTag() { + log.info("testReqMessageDecoderPTag"); + + String VALID_HEXPUBKEY = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; + String VALID_HEXPUBKEY_ALL_ZEROS = "0000000000000000000000000000000000000000000000000000000000000000"; + String VALID_HEXPUBKEY_ALL_FF = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + String INVALID_HEXPUBKEY_NON_HEX_DIGITS = "XYZdf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; + String INVALID_HEXPUBKEY_LENGTH_TOO_SHORT = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6"; + String INVALID_HEXPUBKEY_LENGTH_TOO_LONG = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f666"; + String INVALID_HEXPUBKEY_HAS_MULTIPLE_UPPERCASE = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + String INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6F"; + + Function eTagTarget = pTag -> "[\"REQ\", " + + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + + "{\"#p\": [\"" + pTag + "\"]}]"; + + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY))); + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY_ALL_ZEROS))); + assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY_ALL_FF))); + + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_NON_HEX_DIGITS))) + .getMessage().contains("has non-hex characters")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_SHORT))) + .getMessage().contains("target length")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_LONG))) + .getMessage().contains("target length")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_MULTIPLE_UPPERCASE))) + .getMessage().contains("has uppcase characters")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE))) + .getMessage().contains("has uppcase characters")); + } + + @Test + public void testReqMessageFilterSince() { + log.info("testReqMessageFilterSince"); + Function sinceTarget = since -> "[\"REQ\", " + + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + + "{\"since\": " + since + "}]"; + + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(sinceTarget.apply(null))) + .getMessage().contains("since is marked non-null but is null")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(sinceTarget.apply("-1"))) + .getMessage().contains("'since' filter cannot be negative")); + assertTrue( + assertThrows(JsonMappingException.class, () -> new BaseMessageDecoder<>().decode(sinceTarget.apply("a"))) + .getMessage().contains("Unrecognized token 'a'")); + } + + @Test + public void testReqMessageFilterUntil() { + log.info("testReqMessageFilterUntil"); + Function untilTarget = until -> "[\"REQ\", " + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + - "{\"kinds\": [1], " + - "\"authors\": [\"f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75\"]," + - "\"#e\": [\"fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712\"]}]"; - - final var message = new BaseMessageDecoder<>().decode(parseTarget); - - assertEquals(Command.REQ.toString(), message.getCommand()); - assertEquals("npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh", ((ReqMessage) message).getSubscriptionId()); - assertEquals(1, ((ReqMessage) message).getFiltersList().size()); - - var filters = ((ReqMessage) message).getFiltersList().get(0); - - assertEquals(1, filters.getKinds().size()); - assertEquals(Kind.TEXT_NOTE, filters.getKinds().get(0)); - - assertEquals(1, filters.getAuthors().size()); - assertEquals("npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh", filters.getAuthors().get(0).toBech32String()); - - assertEquals(1, filters.getReferencedEvents().size()); - assertEquals("fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712", (filters.getReferencedEvents().get(0).getId())); - } - - @Test - public void testBaseReqMessageDecoder() { - log.info("testBaseReqMessageDecoder"); - - final var filtersList = new ArrayList(); - var publicKey = Identity.generateRandomIdentity().getPublicKey(); - filtersList.add(Filters.builder().authors(new ArrayList<>(List.of(publicKey))).kinds(new ArrayList<>(List.of(Kind.CONTACT_LIST, Kind.DELETION))).build()); - filtersList.add(Filters.builder().kinds(new ArrayList<>(List.of(Kind.SET_METADATA, Kind.TEXT_NOTE))).build()); - final var reqMessage = new ReqMessage(publicKey.toString(), filtersList); - - assertDoesNotThrow(() -> { - String jsonMessage = reqMessage.encode(); - - String jsonMsg = jsonMessage.substring(1, jsonMessage.length() - 1); - String[] parts = jsonMsg.split(","); - assertEquals("\"REQ\"", parts[0]); - assertEquals("\"" + publicKey.toString() + "\"", parts[1]); - assertFalse(parts[2].startsWith("[")); - assertFalse(parts[parts.length - 1].endsWith("]")); - - BaseMessage message = new BaseMessageDecoder<>().decode(jsonMessage); - - assertEquals(reqMessage, message); - }); - } - - @Test - public void testBaseEventMessageDecoder() { - log.info("testBaseEventMessageDecoder"); - - final String parseTarget - = "[\"EVENT\"," - + "{" - + "\"content\":\"直んないわ。まあええか\"," - + "\"created_at\":1686199583," - + "\"id\":\"fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712\"," - + "\"kind\":1," - + "\"pubkey\":\"8c59239319637f97e007dad0d681e65ce35b1ace333b629e2d33f9465c132608\"," - + "\"sig\":\"9584afd231c52fcbcec6ce668a2cc4b6dc9b4d9da20510dcb9005c6844679b4844edb7a2e1e0591958b0295241567c774dbf7d39a73932877542de1a5f963f4b\"," - + "\"tags\":[]" - + "}]"; - - final var message = new BaseMessageDecoder<>().decode(parseTarget); - - assertEquals(Command.EVENT.toString(), message.getCommand()); - - final var event = (GenericEvent) (((EventMessage) message).getEvent()); - assertEquals(1, event.getKind().intValue()); - assertEquals(1686199583, event.getCreatedAt().longValue()); - assertEquals("fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712", event.getId()); - } - - @Test - public void testBaseEventMessageMarkerDecoder() { - log.info("testBaseEventMessageMarkerDecoder"); - - final String json = "[" - + "\"EVENT\"," - + "{" - + "\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\"," - + "\"kind\":1," - + "\"pubkey\":\"2bed79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984\"," - + "\"created_at\":1687765220," - + "\"content\":\"手順書が間違ってたら作業者は無理だな\"," - + "\"tags\":[" - + "[\"e\",\"494001ac0c8af2a10f60f23538e5b35d3cdacb8e1cc956fe7a16dfa5cbfc4346\",\"\",\"root\"]," - + "[\"p\",\"2bed79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984\"]" - + "]," - + "\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"" - + "}]"; - - BaseMessage message = new BaseMessageDecoder<>().decode(json); - - final var event = (GenericEvent) (((EventMessage) message).getEvent()); - var tags = event.getTags(); - for (BaseTag t : tags) { - if (t.getCode().equalsIgnoreCase("e")) { - EventTag et = (EventTag) t; - assertEquals(Marker.ROOT, et.getMarker()); - } + "{\"until\": " + until + "}]"; + + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(untilTarget.apply(null))) + .getMessage().contains("until is marked non-null but is null")); + assertTrue( + assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(untilTarget.apply("-1"))) + .getMessage().contains("'until' filter cannot be negative")); + assertTrue( + assertThrows(JsonMappingException.class, () -> new BaseMessageDecoder<>().decode(untilTarget.apply("a"))) + .getMessage().contains("Unrecognized token 'a'")); } - } - - @Test - public void testGenericTagDecoder() { - log.info("testGenericTagDecoder"); - final String jsonString = "[\"saturn\", \"jetpack\", false]"; - - var tag = new GenericTagDecoder<>().decode(jsonString); - - assertEquals("saturn", tag.getCode()); - assertEquals(2, tag.getAttributes().size()); - assertEquals("jetpack", ((ElementAttribute) (tag.getAttributes().toArray())[0]).getValue()); - assertEquals(false, Boolean.valueOf(((ElementAttribute) (tag.getAttributes().toArray())[1]).getValue().toString())); - } - - @Test - public void testClassifiedListingTagSerializer() { - log.info("testClassifiedListingSerializer"); - final String classifiedListingEventJson = "{" - + "\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\"," - + "\"kind\":30402," - + "\"content\":\"content ipsum\"," - + "\"pubkey\":\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\"," - + "\"created_at\":1687765220," - + "\"tags\":[" - + "[\"p\",\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\"]," - + "[\"title\",\"title ipsum\"]," - + "[\"summary\",\"summary ipsum\"]," - + "[\"published_at\",\"1687765220\"]," - + "[\"location\",\"location ipsum\"]," - + "[\"price\",\"11111\",\"BTC\",\"1\"]]," - + "\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"" - + "}]"; - - GenericEvent event = new GenericEventDecoder<>().decode(classifiedListingEventJson); - EventMessage message = NIP01.createEventMessage(event, "1"); - assertEquals(1, message.getNip()); - String encoded = new BaseEventEncoder<>((BaseEvent) message.getEvent()).encode(); - assertEquals("{\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\",\"kind\":30402,\"content\":\"content ipsum\",\"pubkey\":\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\",\"created_at\":1687765220,\"tags\":[[\"p\",\"ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de\"],[\"title\",\"title ipsum\"],[\"summary\",\"summary ipsum\"],[\"published_at\",\"1687765220\"],[\"location\",\"location ipsum\"],[\"price\",\"11111\",\"BTC\",\"1\"]],\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"}", encoded); - - assertEquals("28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a", event.getId()); - assertEquals(30402, event.getKind()); - assertEquals("content ipsum", event.getContent()); - assertEquals("ec0762fe78b0f0b763d1324452d973a38bef576d1d76662722d2b8ff948af1de", event.getPubKey().toString()); - assertEquals(1687765220L, event.getCreatedAt()); - assertEquals("86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546", event.getSignature().toString()); - - assertEquals(new BigDecimal("11111"), event.getTags().stream().filter(baseTag -> - baseTag.getCode().equalsIgnoreCase("price")) - .filter(PriceTag.class::isInstance) - .map(PriceTag.class::cast) - .map(PriceTag::getNumber).findFirst().orElseThrow()); - - assertEquals("BTC", event.getTags().stream().filter(baseTag -> - baseTag.getCode().equalsIgnoreCase("price")) - .filter(PriceTag.class::isInstance) - .map(PriceTag.class::cast) - .map(PriceTag::getCurrency).findFirst().orElseThrow()); - - assertEquals("1", event.getTags().stream().filter(baseTag -> - baseTag.getCode().equalsIgnoreCase("price")) - .filter(PriceTag.class::isInstance) - .map(PriceTag.class::cast) - .map(PriceTag::getFrequency).findFirst().orElseThrow()); - - List genericTags = event.getTags().stream() - .filter(GenericTag.class::isInstance) - .map(GenericTag.class::cast).toList(); - - assertEquals("title ipsum", genericTags.stream() - .filter(tag -> tag.getCode().equalsIgnoreCase("title")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); - - assertEquals("summary ipsum", genericTags.stream() - .filter(tag -> tag.getCode().equalsIgnoreCase("summary")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); - - assertEquals("1687765220", genericTags.stream() - .filter(tag -> tag.getCode().equalsIgnoreCase("published_at")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); - - assertEquals("location ipsum", genericTags.stream() - .filter(tag -> tag.getCode().equalsIgnoreCase("location")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); - } - - @Test - public void testDeserializeTag() { - log.info("testDeserializeTag"); - - assertDoesNotThrow(() -> { - String npubHex = new PublicKey(NostrUtil.hexToBytes(Bech32.fromBech32(("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9")))).toString(); - - final String jsonString = "[\"p\", \"" + npubHex + "\", \"wss://nostr.java\", \"alice\"]"; - var tag = new BaseTagDecoder<>().decode(jsonString); - - assertTrue(tag instanceof PubKeyTag); - - PubKeyTag pTag = (PubKeyTag) tag; - assertEquals("wss://nostr.java", pTag.getMainRelayUrl()); - assertEquals(npubHex, pTag.getPublicKey().toString()); - assertEquals("alice", pTag.getPetName()); - }); - } - - @Test - public void testDeserializeGenericTag() { - log.info("testDeserializeGenericTag"); - assertDoesNotThrow(() -> { - String npubHex = new PublicKey(Bech32.decode("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9").data).toString(); - final String jsonString = "[\"gt\", \"" + npubHex + "\", \"wss://nostr.java\", \"alice\"]"; - var tag = new BaseTagDecoder<>().decode(jsonString); - - assertTrue(tag instanceof GenericTag); - - GenericTag gTag = (GenericTag) tag; - assertEquals("gt", gTag.getCode()); - }); - } - - @Test - public void testFiltersEncoder() { - log.info("testFiltersEncoder"); - - String new_geohash = "2vghde"; - List geohashList = new ArrayList<>(); - geohashList.add(new_geohash); - Filters filters = Filters.builder().genericTagQuery(Map.of("#g", geohashList)).build(); - - FiltersEncoder encoder = new FiltersEncoder(filters); - String jsonMessage = encoder.encode(); - assertEquals("{\"#g\":[\"2vghde\"]}", jsonMessage); - } - - @Test - public void testReqMessageFilterListSerializer() { - log.info("testReqMessageFilterListSerializer"); - - String new_geohash = "2vghde"; - String second_geohash = "3abcde"; - List geohashList = new ArrayList<>(); - geohashList.add(new_geohash); - geohashList.add(second_geohash); - Filters filters = Filters.builder().genericTagQuery(Map.of("#g", geohashList)).build(); - - ReqMessage reqMessage = new ReqMessage("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9", new ArrayList(List.of(filters))); - assertDoesNotThrow(() -> { - String jsonMessage = reqMessage.encode(); - - assertEquals("[\"REQ\",\"npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9\",{\"#g\":[\"2vghde\",\"3abcde\"]}]", jsonMessage); - }); - } - - @Test - public void testReqMessageFiltersDecoder() { - log.info("testReqMessageFiltersDecoder"); - - String geohashKey = "#g"; - String geohashValue = "2vghde"; - String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + geohashKey + "\":[\"" + geohashValue + "\"]}"; - - Filters decodedFilters = new FiltersDecoder<>().decode(reqJsonWithCustomTagQueryFilterToDecode); - - Filters expectedFilters = new Filters(); - List expectedGeohashValueList = List.of(geohashValue); - expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValueList); - - assertEquals(expectedFilters, decodedFilters); - } - - @Test - public void testReqMessageFiltersListDecoder() { - log.info("testReqMessageFiltersListDecoder"); - - String geohashKey = "#g"; - String geohashValue1 = "2vghde"; - String geohashValue2 = "3abcde"; - String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + geohashKey + "\":[\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]}"; - - Filters decodedFilters = new FiltersDecoder<>().decode(reqJsonWithCustomTagQueryFilterToDecode); - - Filters expectedFilters = new Filters(); - List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); - expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); - - assertEquals(expectedFilters, decodedFilters); - } - - @Test - public void testReqMessageDeserializer() { - log.info("testReqMessageDeserializer"); - - String subscriptionId = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9"; - String geohashKey = "#g"; - String geohashValue = "2vghde"; - String reqJsonWithCustomTagQueryFilterToDecode = "[\"REQ\",\"" + subscriptionId + "\",{\"" + geohashKey + "\":[\"" + geohashValue + "\"]}]"; - - ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); - - Filters expectedFilters = new Filters(); - List expectedGeohashValuesList = List.of(geohashValue); - expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); - - ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); - assertEquals(expectedReqMessage, decodedReqMessage); - } - - @Test - public void testReqMessageFilterListDecoder() { - log.info("testReqMessageFilterListDecoder"); - - String subscriptionId = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9"; - String geohashKey = "#g"; - String geohashValue1 = "2vghde"; - String geohashValue2 = "3abcde"; - String reqJsonWithCustomTagQueryFiltersToDecode = "[\"REQ\",\"" + subscriptionId + "\",{\"" + geohashKey + "\":[\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]}]"; - - ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFiltersToDecode); - - Filters expectedFilters = new Filters(); - List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); - expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); - - ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); - assertEquals(expectedReqMessage, decodedReqMessage); - } - - @Test - public void testReqMessagePopulatedFilterDecoder() { - log.info("testReqMessagePopulatedFilterDecoder"); - - String subscriptionId = "npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh"; - String kind = "1"; - String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String geohashKey = "#g"; - String geohashValue1 = "2vghde"; - String geohashValue2 = "3abcde"; - String referencedEventId = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; - String reqJsonWithCustomTagQueryFilterToDecode = - "[\"REQ\", " + - "\"" + subscriptionId + "\", " + - "{\"kinds\": [" + kind + "], " + - "\"authors\": [\"" + author + "\"]," + - "\"" + geohashKey + "\": [\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]," + - "\"#e\": [\"" + referencedEventId + "\"]}]"; - - ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); - - Filters expectedFilters = new Filters(); - expectedFilters.setKinds(List.of(Kind.TEXT_NOTE)); - expectedFilters.setAuthors(List.of(new PublicKey(author))); - expectedFilters.setReferencedEvents(List.of(new GenericEvent(referencedEventId))); - List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); - expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); - - ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); - assertEquals(expectedReqMessage, decodedReqMessage); - } - - @Test - public void testReqMessagePopulatedListOfFiltersListDecoder() { - log.info("testReqMessagePopulatedListOfFiltersListDecoder"); - - String subscriptionId = "npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh"; - String kind = "1"; - String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String geohashKey = "#g"; - String geohashValue1 = "2vghde"; - String geohashValue2 = "3abcde"; - String referencedEventId = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; - String uuidKey = "#d"; - String uuidValue1 = "UUID-1"; - String uuidValue2 = "UUID-2"; - String reqJsonWithCustomTagQueryFilterToDecode = - "[\"REQ\", " + - "\"" + subscriptionId + "\", " + - "{\"kinds\": [" + kind + "], " + - "\"authors\": [\"" + author + "\"]," + - "\"" + geohashKey + "\": [\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]," + - "\"" + uuidKey + "\": [\"" + uuidValue1 + "\",\"" + uuidValue2 + "\"]," + - "\"#e\": [\"" + referencedEventId + "\"]}]"; - - ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); - - Filters expectedFilters = new Filters(); - expectedFilters.setKinds(List.of(Kind.TEXT_NOTE)); - expectedFilters.setAuthors(List.of(new PublicKey(author))); - expectedFilters.setReferencedEvents(List.of(new GenericEvent(referencedEventId))); - List expectedGeohashValuesList = List.of(geohashValue1, geohashValue2); - expectedFilters.setGenericTagQuery(geohashKey, expectedGeohashValuesList); - List expectedIdentityTagValuesList = List.of(uuidValue1, uuidValue2); - expectedFilters.setGenericTagQuery(uuidKey, expectedIdentityTagValuesList); - ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, expectedFilters); - assertEquals(expectedReqMessage, decodedReqMessage); - } - - @Test - public void testReqMessageSubscriptionIdLength() { - log.info("testReqMessageSubscriptionIdLength"); - String id65Chars = "npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9ab"; - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new ReqMessage(id65Chars, new Filters())) - .getMessage().contains("subscriptionId length must be between 1 and 64 characters but was [65]")); - } - - @Test - public void testReqMessageFilterIdLength() { - log.info("testReqMessageFilterIdLength"); - String id64chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; - String reqJsonId64Chars = - "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id64chars + "\"]}]"; - assertDoesNotThrow(() -> new BaseMessageDecoder().decode(reqJsonId64Chars)); - - String id65chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123"; - String reqJsonId65Chars = - "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id65chars + "\"]}]"; - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId65Chars)) - .getMessage().contains("[fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123], length: [65], target length: [64]")); - - String id63chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71"; - String reqJsonId63chars = - "[\"REQ\",\"" + "subscriber_id" + "\",{\"ids\":[\"" + id63chars + "\"]}]"; - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder().decode(reqJsonId63chars)) - .getMessage().contains("[fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71], length: [63], target length: [64]")); - } - - @Test - public void testReqMessageDecoderKind() { - log.info("testReqMessageDecoderKind"); - - Function kindTarget = kind -> "[\"REQ\", " + - "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + - "{\"kinds\": [" + kind + "]" + - "}]"; - - assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget.apply(0))); - assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(kindTarget.apply(65535))); - - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(-1))) - .getMessage().contains("Kind must be between 0 and 65535 but was [-1]")); - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(kindTarget.apply(65536))) - .getMessage().contains("Kind must be between 0 and 65535 but was [65536]")); - } - - @Test - public void testReqMessageDecoderETag() { - log.info("testReqMessageDecoderETag"); - - String VALID_EVENTID = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; - String VALID_EVENTID_ALL_ZEROS = "0000000000000000000000000000000000000000000000000000000000000000"; - String VALID_EVENTID_ALL_FF = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; - String INVALID_EVENTID_NON_HEX_DIGITS = "XYZdf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; - String INVALID_EVENTID_LENGTH_TOO_SHORT = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6"; - String INVALID_EVENTID_LENGTH_TOO_LONG = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f666"; - String INVALID_EVENTID_HAS_MULTIPLE_UPPERCASE = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; - String INVALID_EVENTID_HAS_SINGLE_UPPERCASE = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6F"; - - Function eTagTarget = eTag -> "[\"REQ\", " + - "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + - "{\"#e\": [\"" + eTag + "\"]}]"; - - assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_EVENTID))); - assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_EVENTID_ALL_ZEROS))); - assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_EVENTID_ALL_FF))); - - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_NON_HEX_DIGITS))) - .getMessage().contains("has non-hex characters")); - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_LENGTH_TOO_SHORT))) - .getMessage().contains("target length")); - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_LENGTH_TOO_LONG))) - .getMessage().contains("target length")); - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_MULTIPLE_UPPERCASE))) - .getMessage().contains("has uppcase characters")); - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_EVENTID_HAS_SINGLE_UPPERCASE))) - .getMessage().contains("has uppcase characters")); - } - - @Test - public void testReqMessageDecoderPTag() { - log.info("testReqMessageDecoderPTag"); - - String VALID_HEXPUBKEY = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; - String VALID_HEXPUBKEY_ALL_ZEROS = "0000000000000000000000000000000000000000000000000000000000000000"; - String VALID_HEXPUBKEY_ALL_FF = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; - String INVALID_HEXPUBKEY_NON_HEX_DIGITS = "XYZdf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; - String INVALID_HEXPUBKEY_LENGTH_TOO_SHORT = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6"; - String INVALID_HEXPUBKEY_LENGTH_TOO_LONG = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f666"; - String INVALID_HEXPUBKEY_HAS_MULTIPLE_UPPERCASE = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; - String INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6F"; - - Function eTagTarget = pTag -> "[\"REQ\", " + - "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + - "{\"#p\": [\"" + pTag + "\"]}]"; - - assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY))); - assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY_ALL_ZEROS))); - assertDoesNotThrow(() -> new BaseMessageDecoder<>().decode(eTagTarget.apply(VALID_HEXPUBKEY_ALL_FF))); - - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_NON_HEX_DIGITS))) - .getMessage().contains("has non-hex characters")); - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_SHORT))) - .getMessage().contains("target length")); - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_LENGTH_TOO_LONG))) - .getMessage().contains("target length")); - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_MULTIPLE_UPPERCASE))) - .getMessage().contains("has uppcase characters")); - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(eTagTarget.apply(INVALID_HEXPUBKEY_HAS_SINGLE_UPPERCASE))) - .getMessage().contains("has uppcase characters")); - } - - @Test - public void testReqMessageFilterSince() { - log.info("testReqMessageFilterSince"); - Function sinceTarget = since -> "[\"REQ\", " + - "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + - "{\"since\": " + since + "}]"; - - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(sinceTarget.apply(null))) - .getMessage().contains("since is marked non-null but is null")); - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(sinceTarget.apply("-1"))) - .getMessage().contains("'since' filter cannot be negative")); - assertTrue( - assertThrows(JsonMappingException.class, () -> new BaseMessageDecoder<>().decode(sinceTarget.apply("a"))) - .getMessage().contains("Unrecognized token 'a'")); - } - - @Test - public void testReqMessageFilterUntil() { - log.info("testReqMessageFilterUntil"); - Function untilTarget = until -> "[\"REQ\", " + - "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + - "{\"until\": " + until + "}]"; - - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(untilTarget.apply(null))) - .getMessage().contains("until is marked non-null but is null")); - assertTrue( - assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(untilTarget.apply("-1"))) - .getMessage().contains("'until' filter cannot be negative")); - assertTrue( - assertThrows(JsonMappingException.class, () -> new BaseMessageDecoder<>().decode(untilTarget.apply("a"))) - .getMessage().contains("Unrecognized token 'a'")); - } } From 4bf2814b2e073fa8599a5445c507724f2daed7df Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 7 Jan 2025 22:02:24 -0500 Subject: [PATCH 13/16] added error message content match --- .../test/java/nostr/test/event/EventTest.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/nostr-java-test/src/test/java/nostr/test/event/EventTest.java b/nostr-java-test/src/test/java/nostr/test/event/EventTest.java index bc684f6a7..b657d8a82 100644 --- a/nostr-java-test/src/test/java/nostr/test/event/EventTest.java +++ b/nostr-java-test/src/test/java/nostr/test/event/EventTest.java @@ -49,21 +49,6 @@ public void testCreateTextNoteEvent(){ }); } - @Test - public void testEventIdConstraints() { - log.info("testCreateTextNoteEvent"); - PublicKey publicKey = Identity.generateRandomIdentity().getPublicKey(); - GenericEvent genericEvent = EntityFactory.Events.createTextNoteEvent(publicKey); - String id64chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; - assertDoesNotThrow(() -> genericEvent.setId(id64chars)); - - String id63chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71"; - assertThrows(IllegalArgumentException.class, () -> genericEvent.setId(id63chars)); - - String id65chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71"; - assertThrows(IllegalArgumentException.class, () -> genericEvent.setId(id65chars)); - } - @Test public void testCreateGenericTag() { log.info("testCreateGenericTag"); @@ -176,4 +161,23 @@ public void testAuthMessage() { var muattr = (msg.getAttributes().iterator().next().getValue()).toString(); assertEquals(attr, muattr); } + + @Test + public void testEventIdConstraints() { + log.info("testCreateTextNoteEvent"); + PublicKey publicKey = Identity.generateRandomIdentity().getPublicKey(); + GenericEvent genericEvent = EntityFactory.Events.createTextNoteEvent(publicKey); + String id64chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; + assertDoesNotThrow(() -> genericEvent.setId(id64chars)); + + String id63chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71"; + assertTrue( + assertThrows(IllegalArgumentException.class, () -> genericEvent.setId(id63chars)) + .getMessage().contains("[fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a71], length: [63], target length: [64]")); + + String id65chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123"; + assertTrue( + assertThrows(IllegalArgumentException.class, () -> genericEvent.setId(id65chars)) + .getMessage().contains("[fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123], length: [65], target length: [64]"));; + } } From e9b2a8add5c1d84877ace2006f3386b0df5fe611 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 7 Jan 2025 22:44:21 -0500 Subject: [PATCH 14/16] cleanup --- .../java/nostr/test/base/NostrUtilTest.java | 3 +-- .../test/java/nostr/test/event/EventTest.java | 4 +-- .../java/nostr/test/json/JsonParseTest.java | 25 ++++++++++--------- .../nostr/util/thread/HexStringValidator.java | 12 +++++---- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/nostr-java-test/src/test/java/nostr/test/base/NostrUtilTest.java b/nostr-java-test/src/test/java/nostr/test/base/NostrUtilTest.java index ffe85c678..274cfba48 100644 --- a/nostr-java-test/src/test/java/nostr/test/base/NostrUtilTest.java +++ b/nostr-java-test/src/test/java/nostr/test/base/NostrUtilTest.java @@ -1,7 +1,6 @@ package nostr.test.base; import lombok.extern.java.Log; -import nostr.util.NostrException; import nostr.util.NostrUtil; import org.junit.jupiter.api.Test; @@ -19,7 +18,7 @@ public class NostrUtilTest { * are properly functioning inversions of each other */ @Test - public void testHexToBytesHex() throws NostrException { + public void testHexToBytesHex() { System.out.println("testBech32HexToBytesToBech32"); String pubKeyString = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; assertEquals( diff --git a/nostr-java-test/src/test/java/nostr/test/event/EventTest.java b/nostr-java-test/src/test/java/nostr/test/event/EventTest.java index b657d8a82..a65742a23 100644 --- a/nostr-java-test/src/test/java/nostr/test/event/EventTest.java +++ b/nostr-java-test/src/test/java/nostr/test/event/EventTest.java @@ -158,7 +158,7 @@ public void testAuthMessage() { String attr = "challenge-string"; msg.addAttribute(ElementAttribute.builder().name("challenge").value(attr).build()); - var muattr = (msg.getAttributes().iterator().next().getValue()).toString(); + var muattr = (msg.getAttributes().getFirst().getValue()).toString(); assertEquals(attr, muattr); } @@ -178,6 +178,6 @@ public void testEventIdConstraints() { String id65chars = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123"; assertTrue( assertThrows(IllegalArgumentException.class, () -> genericEvent.setId(id65chars)) - .getMessage().contains("[fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123], length: [65], target length: [64]"));; + .getMessage().contains("[fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a7123], length: [65], target length: [64]")); } } diff --git a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java index d0ee584f4..c17855037 100644 --- a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java +++ b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java @@ -40,6 +40,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -66,16 +67,16 @@ public void testBaseMessageDecoder() { assertEquals("npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh", ((ReqMessage) message).getSubscriptionId()); assertEquals(1, ((ReqMessage) message).getFiltersList().size()); - var filters = ((ReqMessage) message).getFiltersList().get(0); + var filters = ((ReqMessage) message).getFiltersList().getFirst(); assertEquals(1, filters.getKinds().size()); - assertEquals(Kind.TEXT_NOTE, filters.getKinds().get(0)); + assertEquals(Kind.TEXT_NOTE, filters.getKinds().getFirst()); assertEquals(1, filters.getAuthors().size()); - assertEquals("npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh", filters.getAuthors().get(0).toBech32String()); + assertEquals("npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh", filters.getAuthors().getFirst().toBech32String()); assertEquals(1, filters.getReferencedEvents().size()); - assertEquals("fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712", (filters.getReferencedEvents().get(0).getId())); + assertEquals("fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712", (filters.getReferencedEvents().getFirst().getId())); } @Test @@ -229,16 +230,16 @@ public void testClassifiedListingTagSerializer() { .map(GenericTag.class::cast).toList(); assertEquals("title ipsum", genericTags.stream() - .filter(tag -> tag.getCode().equalsIgnoreCase("title")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); + .filter(tag -> tag.getCode().equalsIgnoreCase("title")).map(GenericTag::getAttributes).toList().getFirst().getFirst().getValue()); assertEquals("summary ipsum", genericTags.stream() - .filter(tag -> tag.getCode().equalsIgnoreCase("summary")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); + .filter(tag -> tag.getCode().equalsIgnoreCase("summary")).map(GenericTag::getAttributes).toList().getFirst().getFirst().getValue()); assertEquals("1687765220", genericTags.stream() - .filter(tag -> tag.getCode().equalsIgnoreCase("published_at")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); + .filter(tag -> tag.getCode().equalsIgnoreCase("published_at")).map(GenericTag::getAttributes).toList().getFirst().getFirst().getValue()); assertEquals("location ipsum", genericTags.stream() - .filter(tag -> tag.getCode().equalsIgnoreCase("location")).map(GenericTag::getAttributes).toList().get(0).get(0).getValue()); + .filter(tag -> tag.getCode().equalsIgnoreCase("location")).map(GenericTag::getAttributes).toList().getFirst().getFirst().getValue()); } @Test @@ -246,12 +247,12 @@ public void testDeserializeTag() { log.info("testDeserializeTag"); assertDoesNotThrow(() -> { - String npubHex = new PublicKey(NostrUtil.hexToBytes(Bech32.fromBech32(("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9")))).toString(); + String npubHex = new PublicKey(NostrUtil.hexToBytes(Bech32.fromBech32("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9"))).toString(); final String jsonString = "[\"p\", \"" + npubHex + "\", \"wss://nostr.java\", \"alice\"]"; var tag = new BaseTagDecoder<>().decode(jsonString); - assertTrue(tag instanceof PubKeyTag); + assertInstanceOf(PubKeyTag.class, tag); PubKeyTag pTag = (PubKeyTag) tag; assertEquals("wss://nostr.java", pTag.getMainRelayUrl()); @@ -268,7 +269,7 @@ public void testDeserializeGenericTag() { final String jsonString = "[\"gt\", \"" + npubHex + "\", \"wss://nostr.java\", \"alice\"]"; var tag = new BaseTagDecoder<>().decode(jsonString); - assertTrue(tag instanceof GenericTag); + assertInstanceOf(GenericTag.class, tag); GenericTag gTag = (GenericTag) tag; assertEquals("gt", gTag.getCode()); @@ -300,7 +301,7 @@ public void testReqMessageFilterListSerializer() { geohashList.add(second_geohash); Filters filters = Filters.builder().genericTagQuery(Map.of("#g", geohashList)).build(); - ReqMessage reqMessage = new ReqMessage("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9", new ArrayList(List.of(filters))); + ReqMessage reqMessage = new ReqMessage("npub1clk6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9", new ArrayList<>(List.of(filters))); assertDoesNotThrow(() -> { String jsonMessage = reqMessage.encode(); diff --git a/nostr-java-util/src/main/java/nostr/util/thread/HexStringValidator.java b/nostr-java-util/src/main/java/nostr/util/thread/HexStringValidator.java index f5a4977c8..e7da244be 100644 --- a/nostr-java-util/src/main/java/nostr/util/thread/HexStringValidator.java +++ b/nostr-java-util/src/main/java/nostr/util/thread/HexStringValidator.java @@ -1,5 +1,7 @@ package nostr.util.thread; +import lombok.NonNull; + import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -9,13 +11,13 @@ public class HexStringValidator { private static final String validHexChars = "0123456789abcdef"; - private static BiFunction lengthCheck = (s, targetLength) -> s.length() == targetLength; - private static Function hexCharsCheck = HexStringValidator::checkValidHexChars; - private static Function upperCaseCheck = s -> s.toLowerCase().equals(s); + private static final BiFunction lengthCheck = (s, targetLength) -> s.length() == targetLength; + private static final Function hexCharsCheck = HexStringValidator::checkValidHexChars; + private static final Function upperCaseCheck = s -> s.toLowerCase().equals(s); - public static void validateHex(String hexString, int targetLength) { + public static void validateHex(@NonNull String hexString, int targetLength) { List exceptions = new ArrayList<>(); - Optional.ofNullable(hexString) // non-null enforcement + Optional.of(hexString) // non-null enforcement .filter(s -> { if (!lengthCheck.apply(s, targetLength)) { return exceptions.add(String.format("Invalid hex string: [%s], length: [%d], target length: [%d]", hexString, hexString.length(), targetLength)); From 848bae477aa4ff1de11f2ed68da24a6f5a9872b7 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 7 Jan 2025 22:49:44 -0500 Subject: [PATCH 15/16] cleanup --- .../src/test/java/nostr/test/base/NostrUtilTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nostr-java-test/src/test/java/nostr/test/base/NostrUtilTest.java b/nostr-java-test/src/test/java/nostr/test/base/NostrUtilTest.java index 274cfba48..677a3a90f 100644 --- a/nostr-java-test/src/test/java/nostr/test/base/NostrUtilTest.java +++ b/nostr-java-test/src/test/java/nostr/test/base/NostrUtilTest.java @@ -14,12 +14,12 @@ public class NostrUtilTest { /** * test intended to confirm conversion routines: * (1) Hex string to byte[], then - * (2) btye[] back to Hex string + * (2) byte[] back to Hex string * are properly functioning inversions of each other */ @Test public void testHexToBytesHex() { - System.out.println("testBech32HexToBytesToBech32"); + log.info("testHexToBytesHex"); String pubKeyString = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; assertEquals( pubKeyString, From 8ff8bb7eb7cb65be5385adf18092cb28bfd47b0e Mon Sep 17 00:00:00 2001 From: nick avlo Date: Sun, 12 Jan 2025 19:48:17 -0500 Subject: [PATCH 16/16] updated to include NIP-04 66-character accomodation --- .../main/java/nostr/crypto/nip04/EncryptedDirectMessage.java | 2 +- nostr-java-util/src/main/java/nostr/util/NostrUtil.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/nostr-java-crypto/src/main/java/nostr/crypto/nip04/EncryptedDirectMessage.java b/nostr-java-crypto/src/main/java/nostr/crypto/nip04/EncryptedDirectMessage.java index 5703e8284..92206f73b 100644 --- a/nostr-java-crypto/src/main/java/nostr/crypto/nip04/EncryptedDirectMessage.java +++ b/nostr-java-crypto/src/main/java/nostr/crypto/nip04/EncryptedDirectMessage.java @@ -79,7 +79,7 @@ private static SecretKeySpec getSharedSecretKeySpec(byte[] privateKey, byte[] pu private static byte[] getSharedSecret(String privateKeyHex, String publicKeyHex) { SecP256K1Curve curve = new SecP256K1Curve(); - ECPoint pubKeyPt = curve.decodePoint(NostrUtil.hexToBytes("02" + publicKeyHex)); + ECPoint pubKeyPt = curve.decodePoint(NostrUtil.nip04PubKeyHexToBytes("02" + publicKeyHex)); BigInteger tweakVal = new BigInteger(1, NostrUtil.hexToBytes(privateKeyHex)); return pubKeyPt.multiply(tweakVal).getEncoded(true); } diff --git a/nostr-java-util/src/main/java/nostr/util/NostrUtil.java b/nostr-java-util/src/main/java/nostr/util/NostrUtil.java index 855ae1883..60df7514a 100644 --- a/nostr-java-util/src/main/java/nostr/util/NostrUtil.java +++ b/nostr-java-util/src/main/java/nostr/util/NostrUtil.java @@ -38,6 +38,11 @@ public static byte[] hex128ToBytes(String s) { return hexToBytesConvert(s); } + public static byte[] nip04PubKeyHexToBytes(String s) { + HexStringValidator.validateHex(s, 66); + return hexToBytesConvert(s); + } + private static byte[] hexToBytesConvert(String s) { int len = s.length(); byte[] buf = new byte[len / 2];