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-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-event/src/main/java/nostr/event/Kind.java b/nostr-java-event/src/main/java/nostr/event/Kind.java index 415d39f3d..4b88c6b36 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 65535 but was [%d]", value)); + } for (Kind k : values()) { if (k.getValue() == value) { return k; 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-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-event/src/main/java/nostr/event/impl/GenericEvent.java b/nostr-java-event/src/main/java/nostr/event/impl/GenericEvent.java index 3e73b99bf..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; @@ -98,7 +99,7 @@ public GenericEvent() { public GenericEvent(@NonNull String id) { this(); - this.id = id; + setId(id); } public GenericEvent(@NonNull PublicKey pubKey, @NonNull Kind kind) { @@ -128,6 +129,11 @@ public GenericEvent(@NonNull PublicKey pubKey, @NonNull Integer kind, @NonNull L updateTagsParents(tags); } + public void setId(String id) { + HexStringValidator.validateHex(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..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 @@ -1,9 +1,30 @@ -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.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) byte[] back to Hex string + * are properly functioning inversions of each other + */ + @Test + public void testHexToBytesHex() { + log.info("testHexToBytesHex"); + String pubKeyString = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; + assertEquals( + pubKeyString, + NostrUtil.bytesToHex( // (2) + NostrUtil.hexToBytes( // (1) + pubKeyString))); + } +} 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); 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..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 @@ -1,164 +1,183 @@ -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 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().getFirst().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]")); + } +} 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/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]")); + } +} 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..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 @@ -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; @@ -27,16 +28,20 @@ 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; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.Function; 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; /** @@ -62,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 @@ -106,7 +111,6 @@ public void testBaseEventMessageDecoder() { final String parseTarget = "[\"EVENT\"," - + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\"," + "{" + "\"content\":\"直んないわ。まあええか\"," + "\"created_at\":1686199583," @@ -122,7 +126,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()); @@ -134,7 +137,6 @@ public void testBaseEventMessageMarkerDecoder() { final String json = "[" + "\"EVENT\"," - + "\"temp20230627\"," + "{" + "\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\"," + "\"kind\":1," @@ -228,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 @@ -245,11 +247,12 @@ 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); - assertTrue(tag instanceof PubKeyTag); + assertInstanceOf(PubKeyTag.class, tag); PubKeyTag pTag = (PubKeyTag) tag; assertEquals("wss://nostr.java", pTag.getMainRelayUrl()); @@ -266,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()); @@ -298,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(); @@ -448,4 +451,168 @@ public void testReqMessagePopulatedListOfFiltersListDecoder() { 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'")); + } } 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..60df7514a 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; @@ -27,6 +29,21 @@ public static String bytesToHex(byte[] b) { } 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); + } + + 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]; for (int i = 0; i < len; i += 2) { @@ -87,21 +104,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/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..e7da244be --- /dev/null +++ b/nostr-java-util/src/main/java/nostr/util/thread/HexStringValidator.java @@ -0,0 +1,52 @@ +package nostr.util.thread; + +import lombok.NonNull; + +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 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(@NonNull String hexString, int targetLength) { + List exceptions = new ArrayList<>(); + 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)); + } + 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.toLowerCase().toCharArray()) { + if (validHexChars.indexOf(a) < 0) + return false; + } + return true; + } +} 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