diff --git a/convex-core/src/main/cvx/convex/core/metadata.cvx b/convex-core/src/main/cvx/convex/core/metadata.cvx index ca4ebad1c..b8463ab87 100644 --- a/convex-core/src/main/cvx/convex/core/metadata.cvx +++ b/convex-core/src/main/cvx/convex/core/metadata.cvx @@ -604,6 +604,18 @@ {:code "(hash (encoding :foo))"}] :signature [{:params [value] :return Blob}]}} + + keccak256 + {:doc {:description "Calculates the 32-byte Keccak-256 cryptographic hash of a `blob` or an `address` (which is a specialized type of `blob`). Returns a 32-byte `blob`." + :examples [{:code "(keccak256 0x1234)"}] + :signature [{:params [value] + :return Blob}]}} + + sha256 + {:doc {:description "Calculates the 32-byte SHA-256 cryptographic hash of a `blob` or an `address` (which is a specialized type of `blob`). Returns a 32-byte `blob`." + :examples [{:code "(sha256 0x1234)"}] + :signature [{:params [value] + :return Blob}]}} hash-map {:doc {:description "Constructs a map with the given keys and values. If a key is repeated, the last value will overwrite previous ones." diff --git a/convex-core/src/main/java/convex/core/crypto/Hashing.java b/convex-core/src/main/java/convex/core/crypto/Hashing.java index 05abd52c1..879a97e09 100644 --- a/convex-core/src/main/java/convex/core/crypto/Hashing.java +++ b/convex-core/src/main/java/convex/core/crypto/Hashing.java @@ -79,16 +79,28 @@ public static MessageDigest getSHA256Digest() { } /** - * Computes the SHA3-256 hash of byte data + * Computes the SHA-256 hash of byte data * * @param data Byte array to hash - * @return SHA3-256 Hash value + * @return Hash value */ public static Hash sha256(byte[] data) { MessageDigest md = getSHA256Digest(); byte[] hash = md.digest(data); return Hash.wrap(hash); } + + /** + * Computes the KECCAK-256 hash of byte data + * + * @param data Byte array to hash + * @return Hash value + */ + public static Hash keccak256(byte[] data) { + MessageDigest md = getKeccak256Digest(); + byte[] hash = md.digest(data); + return Hash.wrap(hash); + } /** * Computes the SHA-256 hash of a string diff --git a/convex-core/src/main/java/convex/core/lang/Core.java b/convex-core/src/main/java/convex/core/lang/Core.java index 9730992af..51513964f 100644 --- a/convex-core/src/main/java/convex/core/lang/Core.java +++ b/convex-core/src/main/java/convex/core/lang/Core.java @@ -10,6 +10,7 @@ import convex.core.Constants; import convex.core.ErrorCodes; import convex.core.State; +import convex.core.crypto.Hashing; import convex.core.data.ABlob; import convex.core.data.ACell; import convex.core.data.ACountable; @@ -2061,21 +2062,51 @@ public Context invoke(Context context, ACell[] args) { }); public static final CoreFn HASH = reg(new CoreFn<>(Symbols.HASH) { - @Override public Context invoke(Context context, ACell[] args) { if (args.length != 1) return context.withArityError(exactArityMessage(1, args.length)); ABlob blob=RT.ensureBlob(args[0]); if (blob==null) return context.withCastError(0,args, Types.BLOB); + long juice=Juice.HASH+blob.count()*Juice.HASH_PER_BYTE; + if (!context.checkJuice(juice)) return context.withJuiceError(); Hash result = blob.getContentHash(); - return context.withResult(Juice.HASH, result); + return context.withResult(juice, result); } }); + + public static final CoreFn KECCAK256 = reg(new CoreFn<>(Symbols.KECCAK256) { + @Override + public Context invoke(Context context, ACell[] args) { + if (args.length != 1) return context.withArityError(exactArityMessage(1, args.length)); - public static final CoreFn COUNT = reg(new CoreFn<>(Symbols.COUNT) { - + ABlob blob=RT.ensureBlob(args[0]); + if (blob==null) return context.withCastError(0,args, Types.BLOB); + long juice=Juice.HASH+blob.count()*Juice.HASH_PER_BYTE; + if (!context.checkJuice(juice)) return context.withJuiceError(); + + Hash result = blob.computeHash(Hashing.getKeccak256Digest()); + return context.withResult(juice, result); + } + }); + + public static final CoreFn SHA256 = reg(new CoreFn<>(Symbols.SHA256) { + @Override + public Context invoke(Context context, ACell[] args) { + if (args.length != 1) return context.withArityError(exactArityMessage(1, args.length)); + + ABlob blob=RT.ensureBlob(args[0]); + if (blob==null) return context.withCastError(0,args, Types.BLOB); + long juice=Juice.HASH+blob.count()*Juice.HASH_PER_BYTE; + if (!context.checkJuice(juice)) return context.withJuiceError(); + + Hash result = blob.computeHash(Hashing.getSHA256Digest()); + return context.withResult(juice, result); + } + }); + + public static final CoreFn COUNT = reg(new CoreFn<>(Symbols.COUNT) { @Override public Context invoke(Context context, ACell[] args) { if (args.length != 1) return context.withArityError(exactArityMessage(1, args.length)); @@ -2088,7 +2119,6 @@ public Context invoke(Context context, ACell[] args) { }); public static final CoreFn EMPTY = reg(new CoreFn<>(Symbols.EMPTY) { - @Override public Context invoke(Context context, ACell[] args) { if (args.length != 1) return context.withArityError(exactArityMessage(1, args.length)); diff --git a/convex-core/src/main/java/convex/core/lang/Juice.java b/convex-core/src/main/java/convex/core/lang/Juice.java index de7ad8340..d6e5a9155 100644 --- a/convex-core/src/main/java/convex/core/lang/Juice.java +++ b/convex-core/src/main/java/convex/core/lang/Juice.java @@ -152,11 +152,16 @@ public class Juice { public static final long APPLY = 50; /** - * Juice for a cryptographic hash + * Base juice for a cryptographic hash * * Expensive. */ - public static final long HASH = 10000; + public static final long HASH = 1000; + + /** + * Juice per byte of a cryptographic hash + */ + public static final long HASH_PER_BYTE = 10; /** * Juice for a very cheap operation. O(1), no new cell allocations or non-trivial lookups. diff --git a/convex-core/src/main/java/convex/core/lang/Symbols.java b/convex-core/src/main/java/convex/core/lang/Symbols.java index 4f16acdf7..95efb160d 100644 --- a/convex-core/src/main/java/convex/core/lang/Symbols.java +++ b/convex-core/src/main/java/convex/core/lang/Symbols.java @@ -64,7 +64,10 @@ public class Symbols { public static final Symbol CATCH = intern("catch"); public static final Symbol APPLY = intern("apply"); + public static final Symbol HASH = intern("hash"); + public static final Symbol KECCAK256 = intern("keccak256"); + public static final Symbol SHA256 = intern("sha256"); public static final Symbol QUOTE = intern("quote"); public static final Symbol QUASIQUOTE = intern("quasiquote"); @@ -325,8 +328,6 @@ public class Symbols { public static final Symbol MEMORY_VALUE = intern("memory-value"); public static final Symbol PROTOCOL = intern("protocol"); - - public static Symbol intern(String s) { AString name=Strings.create(s); Symbol sym=Symbol.create(name); diff --git a/convex-core/src/main/java/convex/core/util/Utils.java b/convex-core/src/main/java/convex/core/util/Utils.java index 2aad177b2..13374bd9f 100644 --- a/convex-core/src/main/java/convex/core/util/Utils.java +++ b/convex-core/src/main/java/convex/core/util/Utils.java @@ -722,7 +722,7 @@ public static int toInt(Object v) { public static String readResourceAsString(String path) throws IOException { ClassLoader classLoader = ClassLoader.getSystemClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path)) { - if (inputStream == null) throw new IOException("Resource not found: " + path); + if (inputStream == null) throw new IOException("Resource not found: " + path + " with classloader "+classLoader); try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { return reader.lines().collect(Collectors.joining(System.lineSeparator())); } diff --git a/convex-core/src/test/java/convex/core/lang/CoreTest.java b/convex-core/src/test/java/convex/core/lang/CoreTest.java index 6e5b4aeb7..c3c6d0c9c 100644 --- a/convex-core/src/test/java/convex/core/lang/CoreTest.java +++ b/convex-core/src/test/java/convex/core/lang/CoreTest.java @@ -3391,6 +3391,7 @@ public void testExp() { @Test public void testHash() { assertEquals(Hash.fromHex("a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"),eval("(hash 0x)")); + assertEquals(Hash.fromHex("352B82608DAD6C7AC3DD665BC2666E5D97803CB13F23A1109E2105E93F42C448"),eval("(hash 0xDEADBEEF)")); assertEquals(Hash.NULL_HASH, eval("(hash (encoding nil))")); assertEquals(Hash.TRUE_HASH, eval("(hash (encoding true))")); @@ -3406,6 +3407,38 @@ public void testHash() { assertArityError(step("(hash)")); assertArityError(step("(hash nil nil)")); } + + @Test + public void testKeccak() { + assertEquals(Hash.fromHex("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"),eval("(keccak256 0x)")); + + assertTrue(evalB("(= (keccak256 0x1234) (keccak256 0x1234))")); + assertTrue(evalB("(blob? (keccak256 0x00))")); // Should be a Blob + + assertCastError(step("(keccak256 nil)")); + assertCastError(step("(keccak256 :foo)")); + assertCastError(step("(keccak256 #44)")); // specialised blobs don't implicitly cast to Blob + + assertArityError(step("(keccak256)")); + assertArityError(step("(keccak256 nil nil)")); + } + + @Test + public void testSHA256() { + assertEquals(Hash.fromHex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),eval("(sha256 0x)")); + assertEquals(Hash.fromHex("5F78C33274E43FA9DE5659265C1D917E25C03722DCB0B8D27DB8D5FEAA813953"),eval("(sha256 0xdeadbeef)")); + + assertTrue(evalB("(= (sha256 0x1234) (sha256 0x1234))")); + assertTrue(evalB("(blob? (sha256 0x00))")); // Should be a Blob + + assertCastError(step("(sha256 nil)")); + assertCastError(step("(sha256 :foo)")); + assertCastError(step("(sha256 #44)")); // specialised blobs don't implicitly cast to Blob + + assertArityError(step("(sha256)")); + assertArityError(step("(sha256 nil nil)")); + } + @Test public void testCount() { diff --git a/convex-core/src/test/java/convex/lib/SimpleNFTTest.java b/convex-core/src/test/java/convex/lib/SimpleNFTTest.java index 433669106..925b9da14 100644 --- a/convex-core/src/test/java/convex/lib/SimpleNFTTest.java +++ b/convex-core/src/test/java/convex/lib/SimpleNFTTest.java @@ -42,7 +42,6 @@ private static State createState() { assertNotError(c); } - @SuppressWarnings("unchecked") @Test public void testAssetAPI() { Context ctx=context();