From 770ac313034cc89b23cdf83245bc332bf96ab08d Mon Sep 17 00:00:00 2001 From: neodiX Date: Mon, 30 Sep 2024 22:09:27 +0400 Subject: [PATCH 1/3] upgrade version to 0.7.0 --- README.md | 8 +++-- address/README.md | 4 +-- address/pom.xml | 2 +- bitstring/README.md | 4 +-- bitstring/pom.xml | 2 +- cell/README.md | 78 ++++++++++++++++++++-------------------- cell/pom.xml | 2 +- emulator/README.md | 4 +-- emulator/pom.xml | 10 +++--- fift/README.md | 16 ++++----- fift/pom.xml | 2 +- func/README.md | 4 +-- func/pom.xml | 2 +- liteclient/README.md | 4 +-- liteclient/pom.xml | 2 +- mnemonic/README.md | 4 +-- mnemonic/pom.xml | 2 +- pom.xml | 2 +- smartcontract/README.md | 10 +++--- smartcontract/pom.xml | 2 +- tl/pom.xml | 4 +-- tonconnect/README.md | 8 +++-- tonconnect/pom.xml | 2 +- tonlib/README.md | 7 ++-- tonlib/pom.xml | 2 +- tweetnacl-java-8/pom.xml | 2 +- utils/README.md | 4 +-- utils/pom.xml | 2 +- 28 files changed, 101 insertions(+), 94 deletions(-) diff --git a/README.md b/README.md index a7180229..164d763d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ found [here](https://github.com/ton-blockchain/ton/actions). io.github.neodix42 smartcontract - 0.6.0 + 0.7.0 ``` @@ -33,7 +33,7 @@ found [here](https://github.com/ton-blockchain/ton/actions). io.github.neodix42 ton4j - 0.6.0 + 0.7.0 ``` @@ -73,7 +73,9 @@ You can use each submodule individually. Click the module below to get more deta * ✅ HashMap, HashMapE, PfxHashMap, PfxHashMapE, HashMapAug, HashMapAugE serialization / deserialization ## Support ton4j development -If you want to speed up ton4j development and thus change its priority in my backlog, you are welcome to donate some toncoins: + +If you want to speed up ton4j development and thus change its priority in my backlog, you are welcome to donate some +toncoins: ```UQBguBMWc_wUA8pJjC-A9JbTJFzb7lbFbbkiFYajA33-U9YU``` diff --git a/address/README.md b/address/README.md index 61d62b84..42922f6a 100644 --- a/address/README.md +++ b/address/README.md @@ -6,7 +6,7 @@ io.github.neodix42 address - 0.6.0 + 0.7.0 ``` @@ -17,7 +17,7 @@ io.github.neodix42.ton4j address - 0.6.0 + 0.7.0 ``` diff --git a/address/pom.xml b/address/pom.xml index 0d5106de..7402fac8 100644 --- a/address/pom.xml +++ b/address/pom.xml @@ -6,7 +6,7 @@ io.github.neodix42 top - 0.6.0 + 0.7.0 4.0.0 diff --git a/bitstring/README.md b/bitstring/README.md index 6236cb9d..aa569b4b 100644 --- a/bitstring/README.md +++ b/bitstring/README.md @@ -6,7 +6,7 @@ io.github.neodix42 bitstring - 0.6.0 + 0.7.0 ``` @@ -16,7 +16,7 @@ io.github.neodix42.ton4j bitstring - 0.6.0 + 0.7.0 ``` diff --git a/bitstring/pom.xml b/bitstring/pom.xml index 037878cc..3fcaabd8 100644 --- a/bitstring/pom.xml +++ b/bitstring/pom.xml @@ -6,7 +6,7 @@ io.github.neodix42 top - 0.6.0 + 0.7.0 4.0.0 diff --git a/cell/README.md b/cell/README.md index 78df4504..224a85a0 100644 --- a/cell/README.md +++ b/cell/README.md @@ -6,7 +6,7 @@ io.github.neodix42 cell - 0.6.0 + 0.7.0 ``` @@ -16,52 +16,52 @@ io.github.neodix42.ton4j cell - 0.6.0 + 0.7.0 ``` ## Cell Serialization to BoC ```java -Cell c1 = CellBuilder.beginCell().storeUint((long) Math.pow(2, 25), 26).endCell(); - -Cell c2 = CellBuilder.beginCell() - .storeUint((long) Math.pow(2, 37), 38) - .storeRef(c1) - .endCell(); - -Cell c3 = CellBuilder.beginCell().storeUint((long) Math.pow(2, 41), 42).endCell(); -Cell c4 = CellBuilder.beginCell().storeUint((long) Math.pow(2, 42), 44).endCell(); -Cell c5 = CellBuilder.beginCell() - .storeAddress(Address.parseFriendlyAddress("0QAljlSWOKaYCuXTx2OCr9P08y40SC2vw3UeM1hYnI3gDY7I")) - .storeString("HELLO") - .storeRef(c2) - .endCell(); - -log.info("c1 {}", c1.bits); -log.info("c2 {}", c2.bits); -log.info("c2:\n{}", c2.print()); -log.info("c3 {}", c3.bits); -log.info("c4 {}", c4.bits); -log.info("c5 {}", c5.bits); -log.info("c5:\n{}", c5.print()); - -byte[] serializedCell5 = c5.toBocNew(); +Cell c1=CellBuilder.beginCell().storeUint((long)Math.pow(2,25),26).endCell(); + + Cell c2=CellBuilder.beginCell() + .storeUint((long)Math.pow(2,37),38) + .storeRef(c1) + .endCell(); + + Cell c3=CellBuilder.beginCell().storeUint((long)Math.pow(2,41),42).endCell(); + Cell c4=CellBuilder.beginCell().storeUint((long)Math.pow(2,42),44).endCell(); + Cell c5=CellBuilder.beginCell() + .storeAddress(Address.parseFriendlyAddress("0QAljlSWOKaYCuXTx2OCr9P08y40SC2vw3UeM1hYnI3gDY7I")) + .storeString("HELLO") + .storeRef(c2) + .endCell(); + + log.info("c1 {}",c1.bits); + log.info("c2 {}",c2.bits); + log.info("c2:\n{}",c2.print()); + log.info("c3 {}",c3.bits); + log.info("c4 {}",c4.bits); + log.info("c5 {}",c5.bits); + log.info("c5:\n{}",c5.print()); + + byte[]serializedCell5=c5.toBocNew(); // output -c1 8000002_ -c2 8000000002_ -c2: -x{8000000002_} - x{8000002_} - -c3 80000000002_ -c4 40000000000 -c5 8004B1CA92C714D3015CBA78EC7055FA7E9E65C68905B5F86EA3C66B0B1391BC01A908A98989F_ -c5: -x{8004B1CA92C714D3015CBA78EC7055FA7E9E65C68905B5F86EA3C66B0B1391BC01A908A98989F_} - x{8000000002_} - x{8000002_} + c1 8000002_ + c2 8000000002_ + c2: + x{8000000002_} + x{8000002_} + + c3 80000000002_ + c4 40000000000 + c5 8004B1CA92C714D3015CBA78EC7055FA7E9E65C68905B5F86EA3C66B0B1391BC01A908A98989F_ + c5: + x{8004B1CA92C714D3015CBA78EC7055FA7E9E65C68905B5F86EA3C66B0B1391BC01A908A98989F_} + x{8000000002_} + x{8000002_} ``` ## Cell Deserialization from BoC diff --git a/cell/pom.xml b/cell/pom.xml index d7c0a4d6..425d59e4 100644 --- a/cell/pom.xml +++ b/cell/pom.xml @@ -6,7 +6,7 @@ io.github.neodix42 top - 0.6.0 + 0.7.0 4.0.0 diff --git a/emulator/README.md b/emulator/README.md index 70620aaf..46d3662b 100644 --- a/emulator/README.md +++ b/emulator/README.md @@ -8,7 +8,7 @@ Java Emulator wrapper uses JNA to access methods in native emulator shared libra io.github.neodix42 emulator - 0.6.0 + 0.7.0 ``` @@ -18,7 +18,7 @@ Java Emulator wrapper uses JNA to access methods in native emulator shared libra io.github.neodix42.ton4j emulator - 0.6.0 + 0.7.0 ``` diff --git a/emulator/pom.xml b/emulator/pom.xml index 1e49aca5..e4d4535e 100644 --- a/emulator/pom.xml +++ b/emulator/pom.xml @@ -6,7 +6,7 @@ io.github.neodix42 top - 0.6.0 + 0.7.0 emulator @@ -88,25 +88,25 @@ io.github.neodix42 tonlib - 0.6.0 + 0.7.0 test io.github.neodix42 liteclient - 0.6.0 + 0.7.0 test io.github.neodix42 smartcontract - 0.6.0 + 0.7.0 test io.github.neodix42 cell - 0.6.0 + 0.7.0 compile diff --git a/fift/README.md b/fift/README.md index 2e9c53c9..a5f7e6bc 100644 --- a/fift/README.md +++ b/fift/README.md @@ -8,7 +8,7 @@ Java Lite-client wrapper uses JNA to access methods in native lite-client binary io.github.neodix42 fift - 0.6.0 + 0.7.0 ``` @@ -18,21 +18,21 @@ Java Lite-client wrapper uses JNA to access methods in native lite-client binary io.github.neodix42.ton4j fift - 0.6.0 + 0.7.0 ``` ## Usage ```java -URL resource = TestFiftRunner.class.getResource("/test.fift"); -File fiftFile = Paths.get(resource.toURI()).toFile(); -String absolutePath = fiftFile.getAbsolutePath(); +URL resource=TestFiftRunner.class.getResource("/test.fift"); + File fiftFile=Paths.get(resource.toURI()).toFile(); + String absolutePath=fiftFile.getAbsolutePath(); -FiftRunner fiftRunner = FiftRunner.builder().build(); + FiftRunner fiftRunner=FiftRunner.builder().build(); -String result = fiftRunner.run(fiftFile.getParent(), "-s", absolutePath); -log.info("output: {}", result); + String result=fiftRunner.run(fiftFile.getParent(),"-s",absolutePath); + log.info("output: {}",result); ``` More examples in [TestFiftRunner](../fift/src/test/java/org/ton/java/fift/TestFiftRunner.java) class. diff --git a/fift/pom.xml b/fift/pom.xml index 2d39c0bf..eba266c5 100644 --- a/fift/pom.xml +++ b/fift/pom.xml @@ -6,7 +6,7 @@ io.github.neodix42 top - 0.6.0 + 0.7.0 fift diff --git a/func/README.md b/func/README.md index 82cdc0bb..6b2cd027 100644 --- a/func/README.md +++ b/func/README.md @@ -8,7 +8,7 @@ Java Lite-client wrapper uses JNA to access methods in native lite-client binary io.github.neodix42 func - 0.6.0 + 0.7.0 ``` @@ -18,7 +18,7 @@ Java Lite-client wrapper uses JNA to access methods in native lite-client binary io.github.neodix42.ton4j func - 0.6.0 + 0.7.0 ``` diff --git a/func/pom.xml b/func/pom.xml index ae0dd361..9649513b 100644 --- a/func/pom.xml +++ b/func/pom.xml @@ -6,7 +6,7 @@ io.github.neodix42 top - 0.6.0 + 0.7.0 func diff --git a/liteclient/README.md b/liteclient/README.md index db18939d..db62de02 100644 --- a/liteclient/README.md +++ b/liteclient/README.md @@ -8,7 +8,7 @@ Java Lite-client wrapper uses JNA to access methods in native lite-client binary io.github.neodix42 lite-client - 0.6.0 + 0.7.0 ``` @@ -18,7 +18,7 @@ Java Lite-client wrapper uses JNA to access methods in native lite-client binary io.github.neodix42.ton4j lite-client - 0.6.0 + 0.7.0 ``` diff --git a/liteclient/pom.xml b/liteclient/pom.xml index e12a5046..61c8ca25 100644 --- a/liteclient/pom.xml +++ b/liteclient/pom.xml @@ -6,7 +6,7 @@ io.github.neodix42 top - 0.6.0 + 0.7.0 liteclient diff --git a/mnemonic/README.md b/mnemonic/README.md index 471d8a7b..5c9c0032 100644 --- a/mnemonic/README.md +++ b/mnemonic/README.md @@ -6,7 +6,7 @@ io.github.neodix42 mnemonic - 0.6.0 + 0.7.0 ``` @@ -16,7 +16,7 @@ io.github.neodix42.ton4j mnemonic - 0.6.0 + 0.7.0 ``` diff --git a/mnemonic/pom.xml b/mnemonic/pom.xml index ef33964f..b8f28933 100644 --- a/mnemonic/pom.xml +++ b/mnemonic/pom.xml @@ -5,7 +5,7 @@ top io.github.neodix42 - 0.6.0 + 0.7.0 4.0.0 diff --git a/pom.xml b/pom.xml index 1e159fb7..d3a61b1b 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ io.github.neodix42 top pom - 0.6.0 + 0.7.0 bitstring diff --git a/smartcontract/README.md b/smartcontract/README.md index 73a563d1..3672a23b 100644 --- a/smartcontract/README.md +++ b/smartcontract/README.md @@ -6,7 +6,7 @@ io.github.neodix42 smartcontract - 0.6.0 + 0.7.0 ``` @@ -16,7 +16,7 @@ io.github.neodix42.ton4j smartcontract - 0.6.0 + 0.7.0 ``` @@ -35,9 +35,11 @@ Currently, following wallet versions and revisions are supported: * v3R2 [(see usage example)](./src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV3R2Short.java) * v4R2 - subscription, plugins [(see usage example)](plugin-example.md) * v5R1 - extensions [(see usage example)](./src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV5.java) -* Lockup - restricted [(see usage example)](./src/test/java/org/ton/java/smartcontract/integrationtests/TestLockupWallet.java) +* Lockup - + restricted [(see usage example)](./src/test/java/org/ton/java/smartcontract/integrationtests/TestLockupWallet.java) * Highload [(see usage example)](./src/test/java/org/ton/java/smartcontract/integrationtests/TestHighloadWalletV2.java) -* Highload V3 [(see usage example)](./src/test/java/org/ton/java/smartcontract/integrationtests/TestHighloadWalletV3.java) +* Highload + V3 [(see usage example)](./src/test/java/org/ton/java/smartcontract/integrationtests/TestHighloadWalletV3.java) * Dns [(see usage example)](dns-example.md) * Jetton [(see usage example)](jetton-example.md) * NFT [(see usage example)](nft-example.md) diff --git a/smartcontract/pom.xml b/smartcontract/pom.xml index 238f4d40..0afb1c37 100644 --- a/smartcontract/pom.xml +++ b/smartcontract/pom.xml @@ -6,7 +6,7 @@ io.github.neodix42 top - 0.6.0 + 0.7.0 4.0.0 diff --git a/tl/pom.xml b/tl/pom.xml index 9d59d589..fa110204 100644 --- a/tl/pom.xml +++ b/tl/pom.xml @@ -5,7 +5,7 @@ top io.github.neodix42 - 0.6.0 + 0.7.0 4.0.0 @@ -47,7 +47,7 @@ io.github.neodix42 cell - 0.6.0 + 0.7.0 compile diff --git a/tonconnect/README.md b/tonconnect/README.md index 2050610b..65c1b4d4 100644 --- a/tonconnect/README.md +++ b/tonconnect/README.md @@ -6,7 +6,7 @@ io.github.neodix42 tonconnect - 0.6.0 + 0.7.0 ``` @@ -16,12 +16,14 @@ io.github.neodix42.ton4j tonconnect - 0.6.0 + 0.7.0 ``` ## Description -Please follow the [official documentation](https://docs.ton.org/develop/dapps/ton-connect/sign#how-does-it-work) for more details. + +Please follow the [official documentation](https://docs.ton.org/develop/dapps/ton-connect/sign#how-does-it-work) for +more details. ## Usage diff --git a/tonconnect/pom.xml b/tonconnect/pom.xml index 3d065117..e14571ef 100644 --- a/tonconnect/pom.xml +++ b/tonconnect/pom.xml @@ -6,7 +6,7 @@ io.github.neodix42 top - 0.6.0 + 0.7.0 tonconnect diff --git a/tonlib/README.md b/tonlib/README.md index 5480c7ca..d9366c33 100644 --- a/tonlib/README.md +++ b/tonlib/README.md @@ -2,7 +2,8 @@ Java Tonlib library uses JNA to access methods in native Tonlib shared library. -Since this is Java Tonlib wrapper around the native binary, you have to specify path to the library, see the example below. +Since this is Java Tonlib wrapper around the native binary, you have to specify path to the library, see the example +below. You can get the latest tonlib library by: @@ -15,7 +16,7 @@ You can get the latest tonlib library by: io.github.neodix42 tonlib - 0.6.0 + 0.7.0 ``` @@ -25,7 +26,7 @@ You can get the latest tonlib library by: io.github.neodix42.ton4j tonlib - 0.6.0 + 0.7.0 ``` diff --git a/tonlib/pom.xml b/tonlib/pom.xml index c508a403..aa926cbb 100644 --- a/tonlib/pom.xml +++ b/tonlib/pom.xml @@ -6,7 +6,7 @@ io.github.neodix42 top - 0.6.0 + 0.7.0 4.0.0 diff --git a/tweetnacl-java-8/pom.xml b/tweetnacl-java-8/pom.xml index 81f84769..93b5ea17 100644 --- a/tweetnacl-java-8/pom.xml +++ b/tweetnacl-java-8/pom.xml @@ -4,7 +4,7 @@ io.github.neodix42 top - 0.6.0 + 0.7.0 tweetnacl-java-8 diff --git a/utils/README.md b/utils/README.md index a12316f3..844b3f5b 100644 --- a/utils/README.md +++ b/utils/README.md @@ -6,7 +6,7 @@ io.github.neodix42 utils - 0.6.0 + 0.7.0 ``` @@ -16,7 +16,7 @@ io.github.neodix42.ton4j utils - 0.6.0 + 0.7.0 ``` diff --git a/utils/pom.xml b/utils/pom.xml index 75a4a9ad..3e70c512 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -6,7 +6,7 @@ io.github.neodix42 top - 0.6.0 + 0.7.0 4.0.0 From a462c91e39472902e5dcf7cf10bc32b36fa4d709 Mon Sep 17 00:00:00 2001 From: Nikita Duginets Date: Mon, 14 Oct 2024 10:56:26 +0300 Subject: [PATCH 2/3] implemented proof builder, fix hash map building fix level mask calculation --- .../src/main/java/org/ton/java/cell/Cell.java | 20 +- .../java/org/ton/java/cell/CellBuilder.java | 1 + .../java/org/ton/java/cell/TonHashMap.java | 203 ++++++++++++++++-- 3 files changed, 196 insertions(+), 28 deletions(-) diff --git a/cell/src/main/java/org/ton/java/cell/Cell.java b/cell/src/main/java/org/ton/java/cell/Cell.java index 53ed7de1..76006c13 100644 --- a/cell/src/main/java/org/ton/java/cell/Cell.java +++ b/cell/src/main/java/org/ton/java/cell/Cell.java @@ -568,7 +568,15 @@ static BocFlags parseBocFlags(byte data) { * @return String */ public String print(String indent) { - StringBuilder s = new StringBuilder(indent + "x{" + bits.toHex() + "}\n"); + String t = "x"; + if (this.type == CellType.MERKLE_PROOF) { + t = "p"; + } else if (this.type == CellType.MERKLE_UPDATE) { + t = "u"; + } else if (this.type == CellType.PRUNED_BRANCH) { + t = "P"; + } + StringBuilder s = new StringBuilder(indent + t + "{" + bits.toHex() + "}\n"); if (nonNull(refs) && !refs.isEmpty()) { for (Cell i : refs) { if (nonNull(i)) { @@ -580,15 +588,7 @@ public String print(String indent) { } public String print() { - StringBuilder s = new StringBuilder("x{" + bits.toHex() + "}\n"); - if (nonNull(refs) && !refs.isEmpty()) { - for (Cell i : refs) { - if (nonNull(i)) { - s.append(i.print(" ")); - } - } - } - return s.toString(); + return print(""); } /** Saves BoC to file */ diff --git a/cell/src/main/java/org/ton/java/cell/CellBuilder.java b/cell/src/main/java/org/ton/java/cell/CellBuilder.java index 76ba8a1a..5f5fc5a5 100644 --- a/cell/src/main/java/org/ton/java/cell/CellBuilder.java +++ b/cell/src/main/java/org/ton/java/cell/CellBuilder.java @@ -38,6 +38,7 @@ public static CellBuilder beginCell(int bitSize) { * Converts a builder into an ordinary cell. */ public Cell endCell() { + cell.levelMask = cell.resolveMask(); if (cell.getHashes().length == 0) { cell.calculateHashes(); } diff --git a/cell/src/main/java/org/ton/java/cell/TonHashMap.java b/cell/src/main/java/org/ton/java/cell/TonHashMap.java index 8db01ae6..9e63a398 100644 --- a/cell/src/main/java/org/ton/java/cell/TonHashMap.java +++ b/cell/src/main/java/org/ton/java/cell/TonHashMap.java @@ -152,6 +152,49 @@ PatriciaTreeNode flatten(PatriciaTreeNode node, int m) { } } + + private boolean isSame(String label) { + if (label.isEmpty() || label.length() == 1) { + return true; + } + for (int i = 1; i < label.length(); i++) { + if (label.charAt(i) != label.charAt(0)) { + return false; + } + } + return true; + } + + private int detectLabelType(String label, int keyLength) { + int type = 0; + // hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m; + // first bit for 0, then n + 1 for Unary ~n and next n is n * Bit + // 0 + int bestLength = 1 + label.length() + 1 + label.length(); + + // hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; + // first 1 + 1 for 10, then log2 bits for n and next n is n * Bit + // 1 + int labelLongLength = 1 + 1 + (int) Math.ceil(log2(keyLength + 1)) + label.length(); + + boolean isSame = isSame(label); + // hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m; + // 2 + int labelSameLength = 1 + 1 + 1 + (int) Math.ceil(log2(keyLength + 1)); + + if (labelLongLength < bestLength) { + type = 1; + bestLength = labelLongLength; + } + if (isSame) { + if (labelSameLength < bestLength) { + type = 2; + } + } + return type; + } + + /** * Serialize HashMap label * hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m; @@ -163,15 +206,10 @@ PatriciaTreeNode flatten(PatriciaTreeNode node, int m) { * @param builder Cell to which label will be serialized */ void serialize_label(String label, int m, CellBuilder builder) { - int n = label.length(); - if (label.isEmpty()) { - builder.storeBit(false); //hml_short$0 - builder.storeBit(false); //Unary 0 - return; - } - + int t = detectLabelType(label, m); int sizeOfM = BigInteger.valueOf(m).bitLength(); - if (n < sizeOfM) { + if (t == 0) { + int n = label.length(); builder.storeBit(false); // hml_short for (int i = 0; i < n; i++) { builder.storeBit(true); // Unary n @@ -180,22 +218,20 @@ void serialize_label(String label, int m, CellBuilder builder) { for (Character c : label.toCharArray()) { builder.storeBit(c == '1'); } - return; - } - boolean isSame = (label.equals(Utils.repeat("0", label.length())) || label.equals(Utils.repeat("10", label.length()))); - - if (isSame) { - builder.storeBit(true); - builder.storeBit(true); //hml_same - builder.storeBit(label.charAt(0) == '1'); - builder.storeUint(label.length(), sizeOfM); - } else { + } else if (t == 1) { builder.storeBit(true); builder.storeBit(false); //hml_long builder.storeUint(label.length(), sizeOfM); for (Character c : label.toCharArray()) { builder.storeBit(c == '1'); } + } else if (t == 2) { + builder.storeBit(true); + builder.storeBit(true); //hml_same + builder.storeBit(label.charAt(0) == '1'); + builder.storeUint(label.length(), sizeOfM); + } else { + throw new IllegalStateException("Unknown label type: " + t); } } @@ -325,4 +361,135 @@ public Object getValueByIndex(long index) { } throw new Error("value not found at index " + index); } + + public Cell buildMerkleProof( + Object key, + Function keySerializer, + Function valueSerializer + ) { + Cell dictCell = CellBuilder.beginCell().storeDictInLine( + this.serialize(keySerializer, valueSerializer) + ).endCell(); + + Cell cell = generateMerkleProof( + "", + CellSlice.beginParse(dictCell), + keySize, + padString(keySerializer.apply(key).toBitString(), keySize, '0') + ); + + return convertToMerkleProof(cell); + } + + private int readUnaryLength(CellSlice slice) { + int res = 0; + while (slice.loadBit()) { + res++; + } + return res; + } + + private Cell generateMerkleProof( + String prefix, + CellSlice slice, + int n, + String key + ) { + Cell originalCell = CellBuilder.beginCell().storeSlice(slice).endCell(); + + int lb0 = slice.loadBit() ? 1 : 0; + int prefixLength; + StringBuilder pp = new StringBuilder(prefix); + + if (lb0 == 0) { + // Short label detected + prefixLength = readUnaryLength(slice); + for (int i = 0; i < prefixLength; i++) { + pp.append(slice.loadBit() ? '1' : '0'); + } + } else { + int lb1 = slice.loadBit() ? 1 : 0; + if (lb1 == 0) { + // Long label detected + prefixLength = slice.loadUint((int) Math.ceil(Math.log(n + 1) / Math.log(2))).intValue(); + for (int i = 0; i < prefixLength; i++) { + pp.append(slice.loadBit() ? '1' : '0'); + } + } else { + // Same label detected + char bit = slice.loadBit() ? '1' : '0'; + prefixLength = slice.loadUint((int) (Math.ceil(Math.log(n + 1) / Math.log(2)))).intValue(); + for (int i = 0; i < prefixLength; i++) { + pp.append(bit); + } + } + } + + if (n - prefixLength == 0) { + return originalCell; + } else { + CellSlice sl = CellSlice.beginParse(originalCell); + Cell left = sl.loadRef(); + Cell right = sl.loadRef(); + // NOTE: Left and right branches implicitly contain prefixes '0' and '1' + + if (left.getCellType() == CellType.ORDINARY) { + left = (pp.toString() + '0').equals(key.substring(0, pp.length() + 1)) + ? generateMerkleProof(pp.toString() + '0', CellSlice.beginParse(left), n - prefixLength - 1, key) + : convertToPrunedBranch(left); + } + + if (right.getCellType() == CellType.ORDINARY) { + right = (pp.toString() + '1').equals(key.substring(0, pp.length() + 1)) + ? generateMerkleProof(pp.toString() + '1', CellSlice.beginParse(right), n - prefixLength - 1, key) + : convertToPrunedBranch(right); + } + + return CellBuilder.beginCell() + .storeSlice(sl) + .storeRef(left) + .storeRef(right) + .endCell(); + } + } + + private Cell endExoticCell(CellBuilder builder, CellType type) { + Cell c = builder.endCell(); + Cell exotic = new Cell(c.getBits(), c.getBitLength(), c.getRefs(), true, type); + exotic.calculateHashes(); + return exotic; + } + + private Cell convertToMerkleProof(Cell c) { + return endExoticCell( + CellBuilder.beginCell() + .storeUint(3, 8) + .storeBytes(c.getHash(0)) + .storeUint(c.getDepthLevels()[0], 16) + .storeRef(c), + CellType.MERKLE_PROOF + ); + } + + private Cell convertToPrunedBranch(Cell c) { + return endExoticCell( + CellBuilder.beginCell() + .storeUint(1, 8) + .storeUint(1, 8) + .storeBytes(c.getHash(0)) + .storeUint(c.getDepthLevels()[0], 16), + CellType.PRUNED_BRANCH + ); + } + + private String padString(String original, int requiredSize, char padChar) { + if (original.length() >= requiredSize) { + return original; // No padding needed + } + + return String.valueOf(padChar) + .repeat(Math.max(0, requiredSize - original.length() + 1)) + + original; + } + } \ No newline at end of file From f41445c279bd20e99009a891a25423d34838a6ef Mon Sep 17 00:00:00 2001 From: neodiX Date: Mon, 14 Oct 2024 20:55:12 +0400 Subject: [PATCH 3/3] review; use StringUtils.repeat() instead of String.valueOf().repeat; Thanks to https://github.com/glcanvas --- .../java/org/ton/java/cell/TonHashMap.java | 872 +++++++++--------- 1 file changed, 425 insertions(+), 447 deletions(-) diff --git a/cell/src/main/java/org/ton/java/cell/TonHashMap.java b/cell/src/main/java/org/ton/java/cell/TonHashMap.java index 9e63a398..ac085933 100644 --- a/cell/src/main/java/org/ton/java/cell/TonHashMap.java +++ b/cell/src/main/java/org/ton/java/cell/TonHashMap.java @@ -1,495 +1,473 @@ package org.ton.java.cell; -import org.ton.java.bitstring.BitString; -import org.ton.java.utils.Utils; - import java.math.BigInteger; import java.util.*; import java.util.function.Function; +import org.apache.commons.lang3.StringUtils; +import org.ton.java.bitstring.BitString; -/** - * Ordinary Hashmap (Patricia Tree), with fixed length keys. - */ +/** Ordinary Hashmap (Patricia Tree), with fixed length keys. */ public class TonHashMap { - public HashMap elements; - int keySize; - int maxMembers; - - /** - * TonHashMap with the fixed length keys. TonHashMap cannot be empty. - * If you plan to store empty Hashmap consider using TonHashMapE. - *

- * TonHashMap consists of two subsequent refs - * Notice, all keys should be of the same size. If you have keys of different size - align them first. - * Duplicates are not allowed. - * - * @param keySize key size in bits - * @param maxMembers max number of hashmap entries - */ - public TonHashMap(int keySize, int maxMembers) { - elements = new LinkedHashMap<>(); - this.keySize = keySize; - this.maxMembers = maxMembers; + public HashMap elements; + int keySize; + int maxMembers; + + /** + * TonHashMap with the fixed length keys. TonHashMap cannot be empty. If you plan to store empty + * Hashmap consider using TonHashMapE. + * + *

TonHashMap consists of two subsequent refs Notice, all keys should be of the same size. If + * you have keys of different size - align them first. Duplicates are not allowed. + * + * @param keySize key size in bits + * @param maxMembers max number of hashmap entries + */ + public TonHashMap(int keySize, int maxMembers) { + elements = new LinkedHashMap<>(); + this.keySize = keySize; + this.maxMembers = maxMembers; + } + + /** + * HashMap with the fixed length keys. TonHashMap cannot be empty. If you plan to store empty + * Hashmap consider using TonHashMapE. + * + *

Notice, all keys should be of the same size. If you have keys of different size - align + * them. Duplicates are not allowed. + * + * @param keySize key size in bits + */ + public TonHashMap(int keySize) { + elements = new LinkedHashMap<>(); + this.keySize = keySize; + this.maxMembers = 10000; + } + + public List deserializeEdge(CellSlice edge, int keySize, final BitString key) { + List nodes = new ArrayList<>(); + BitString l = deserializeLabel(edge, keySize - key.toBitString().length()); + key.writeBitString(l); + if (key.toBitString().length() == keySize) { + Cell value = CellBuilder.beginCell().storeSlice(edge).endCell(); + nodes.add(new Node(key, value)); + return nodes; } - /** - * HashMap with the fixed length keys. - * TonHashMap cannot be empty. If you plan to store empty Hashmap consider using TonHashMapE. - *

- * Notice, all keys should be of the same size. If you have keys of different size - align them. - * Duplicates are not allowed. - * - * @param keySize key size in bits - */ - public TonHashMap(int keySize) { - elements = new LinkedHashMap<>(); - this.keySize = keySize; - this.maxMembers = 10000; + for (int j = 0; j < edge.refs.size(); j++) { + CellSlice forkEdge = CellSlice.beginParse(edge.refs.get(j)); + BitString forkKey = key.clone(); + forkKey.writeBit(j != 0); + nodes.addAll(deserializeEdge(forkEdge, keySize, forkKey)); } - - public List deserializeEdge(CellSlice edge, int keySize, final BitString key) { - List nodes = new ArrayList<>(); - BitString l = deserializeLabel(edge, keySize - key.toBitString().length()); - key.writeBitString(l); - if (key.toBitString().length() == keySize) { - Cell value = CellBuilder.beginCell().storeSlice(edge).endCell(); - nodes.add(new Node(key, value)); - return nodes; - } - - for (int j = 0; j < edge.refs.size(); j++) { - CellSlice forkEdge = CellSlice.beginParse(edge.refs.get(j)); - BitString forkKey = key.clone(); - forkKey.writeBit(j != 0); - nodes.addAll(deserializeEdge(forkEdge, keySize, forkKey)); - } - return nodes; - + return nodes; + } + + /** Loads HashMap and parses keys and values HashMap X Y; */ + void deserialize( + CellSlice c, Function keyParser, Function valueParser) { + List nodes = deserializeEdge(c, keySize, new BitString(keySize)); + for (Node node : nodes) { + elements.put(keyParser.apply(node.key), valueParser.apply(node.value)); } - - /** - * Loads HashMap and parses keys and values - * HashMap X Y; - */ - void deserialize(CellSlice c, Function keyParser, Function valueParser) { - List nodes = deserializeEdge(c, keySize, new BitString(keySize)); - for (Node node : nodes) { - elements.put(keyParser.apply(node.key), valueParser.apply(node.value)); - } + } + + /** + * Read the keys in array and return binary tree in the form of Patrcia Tree Node + * + * @param nodes list which contains nodes + * @return tree node + */ + PatriciaTreeNode splitTree(List nodes) { + if (nodes.size() == 1) { + return new PatriciaTreeNode("", 0, nodes.get(0), null, null); } - /** - * Read the keys in array and return binary tree in the form of Patrcia Tree Node - * - * @param nodes list which contains nodes - * @return tree node - */ - PatriciaTreeNode splitTree(List nodes) { - if (nodes.size() == 1) { - return new PatriciaTreeNode("", 0, nodes.get(0), null, null); - } - - List left = new ArrayList<>(); - List right = new ArrayList<>(); - - for (Node node : nodes) { - boolean lr = node.key.readBit(); + List left = new ArrayList<>(); + List right = new ArrayList<>(); - if (lr) { - right.add(node); - } else { - left.add(node); - } - } + for (Node node : nodes) { + boolean lr = node.key.readBit(); - PatriciaTreeNode leftNode = left.size() > 1 - ? splitTree(left) - : left.isEmpty() - ? null - : new PatriciaTreeNode("", 0, left.get(0), null, null); - PatriciaTreeNode rightNode = right.size() > 1 - ? splitTree(right) - : right.isEmpty() - ? null - : new PatriciaTreeNode("", 0, right.get(0), null, null); - - return new PatriciaTreeNode("", keySize, null, leftNode, rightNode); + if (lr) { + right.add(node); + } else { + left.add(node); + } } - /** - * Flatten binary tree (by cutting empty branches) if possible - * - * @param node tree node - * @param m maximal possible length of prefix - * @return flattened tree node - */ - PatriciaTreeNode flatten(PatriciaTreeNode node, int m) { - if (node == null) { - return null; - } - - if (node.maxPrefixLength == 0) { - node.maxPrefixLength = m; - } - - if (node.leafNode != null) { - return node; - } - - PatriciaTreeNode left = node.left; - PatriciaTreeNode right = node.right; - - if (left == null) { - return flatten(new PatriciaTreeNode(node.prefix + "1", m, null, right.left, right.right), m); - } else if (right == null) { - return flatten(new PatriciaTreeNode(node.prefix + "0", m, null, left.left, left.right), m); - } else { - node.maxPrefixLength = m; - node.left = flatten(left, m - node.prefix.length() - 1); - node.right = flatten(right, m - node.prefix.length() - 1); - return node; - } + PatriciaTreeNode leftNode = + left.size() > 1 + ? splitTree(left) + : left.isEmpty() ? null : new PatriciaTreeNode("", 0, left.get(0), null, null); + PatriciaTreeNode rightNode = + right.size() > 1 + ? splitTree(right) + : right.isEmpty() ? null : new PatriciaTreeNode("", 0, right.get(0), null, null); + + return new PatriciaTreeNode("", keySize, null, leftNode, rightNode); + } + + /** + * Flatten binary tree (by cutting empty branches) if possible + * + * @param node tree node + * @param m maximal possible length of prefix + * @return flattened tree node + */ + PatriciaTreeNode flatten(PatriciaTreeNode node, int m) { + if (node == null) { + return null; } - - private boolean isSame(String label) { - if (label.isEmpty() || label.length() == 1) { - return true; - } - for (int i = 1; i < label.length(); i++) { - if (label.charAt(i) != label.charAt(0)) { - return false; - } - } - return true; + if (node.maxPrefixLength == 0) { + node.maxPrefixLength = m; } - private int detectLabelType(String label, int keyLength) { - int type = 0; - // hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m; - // first bit for 0, then n + 1 for Unary ~n and next n is n * Bit - // 0 - int bestLength = 1 + label.length() + 1 + label.length(); - - // hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; - // first 1 + 1 for 10, then log2 bits for n and next n is n * Bit - // 1 - int labelLongLength = 1 + 1 + (int) Math.ceil(log2(keyLength + 1)) + label.length(); - - boolean isSame = isSame(label); - // hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m; - // 2 - int labelSameLength = 1 + 1 + 1 + (int) Math.ceil(log2(keyLength + 1)); - - if (labelLongLength < bestLength) { - type = 1; - bestLength = labelLongLength; - } - if (isSame) { - if (labelSameLength < bestLength) { - type = 2; - } - } - return type; + if (node.leafNode != null) { + return node; } - - /** - * Serialize HashMap label - * hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m; - * hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; - * hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m; - * - * @param label String label string of zeroes and ones "010101" - * @param m int maximal possible length of the label - * @param builder Cell to which label will be serialized - */ - void serialize_label(String label, int m, CellBuilder builder) { - int t = detectLabelType(label, m); - int sizeOfM = BigInteger.valueOf(m).bitLength(); - if (t == 0) { - int n = label.length(); - builder.storeBit(false); // hml_short - for (int i = 0; i < n; i++) { - builder.storeBit(true); // Unary n - } - builder.storeBit(false); // Unary 0 - for (Character c : label.toCharArray()) { - builder.storeBit(c == '1'); - } - } else if (t == 1) { - builder.storeBit(true); - builder.storeBit(false); //hml_long - builder.storeUint(label.length(), sizeOfM); - for (Character c : label.toCharArray()) { - builder.storeBit(c == '1'); - } - } else if (t == 2) { - builder.storeBit(true); - builder.storeBit(true); //hml_same - builder.storeBit(label.charAt(0) == '1'); - builder.storeUint(label.length(), sizeOfM); - } else { - throw new IllegalStateException("Unknown label type: " + t); - } + PatriciaTreeNode left = node.left; + PatriciaTreeNode right = node.right; + + if (left == null) { + return flatten(new PatriciaTreeNode(node.prefix + "1", m, null, right.left, right.right), m); + } else if (right == null) { + return flatten(new PatriciaTreeNode(node.prefix + "0", m, null, left.left, left.right), m); + } else { + node.maxPrefixLength = m; + node.left = flatten(left, m - node.prefix.length() - 1); + node.right = flatten(right, m - node.prefix.length() - 1); + return node; } + } - /** - * Serialize HashMap edge - * hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n) - * {n = (~m) + l} node:(HashmapNode m X) = Hashmap n X; - * - * @param node tree node which contains [label as "0" and "1" string, maximal possible size of label, leaf or left fork, right fork] - * @param builder Cell to which edge will be serialized - */ - void serialize_edge(PatriciaTreeNode node, CellBuilder builder) { - if (node == null) { - return; - } - if (node.leafNode != null) { // contains leaf - BitString bs = node.leafNode.key.readBits(node.leafNode.key.getUsedBits()); - node.prefix = bs.toBitString(); - serialize_label(node.prefix, node.maxPrefixLength, builder); - builder.storeCell(node.leafNode.value); - } else { // contains fork - serialize_label(node.prefix, node.maxPrefixLength, builder); - CellBuilder leftCell = CellBuilder.beginCell(); - serialize_edge(node.left, leftCell); - CellBuilder rightCell = CellBuilder.beginCell(); - serialize_edge(node.right, rightCell); - builder.storeRef(leftCell.endCell()); - builder.storeRef(rightCell.endCell()); - } + private boolean isSame(String label) { + if (label.isEmpty() || label.length() == 1) { + return true; } - - public Cell serialize(Function keyParser, Function valueParser) { - List nodes = new ArrayList<>(); - for (Map.Entry entry : elements.entrySet()) { - BitString key = keyParser.apply(entry.getKey()); - Cell value = valueParser.apply(entry.getValue()); - nodes.add(new Node(key, value)); - } - - if (nodes.isEmpty()) { - throw new Error("TonHashMap does not support empty dict. Consider using TonHashMapE"); - } - - PatriciaTreeNode root = flatten(splitTree(nodes), keySize); - CellBuilder b = CellBuilder.beginCell(); - serialize_edge(root, b); - - return b.endCell(); + for (int i = 1; i < label.length(); i++) { + if (label.charAt(i) != label.charAt(0)) { + return false; + } } - - /** - * Deserialize label - * - * @param edge cell - * @param m length at most possible bits of n (key) - */ - public BitString deserializeLabel(CellSlice edge, int m) { - if (!edge.loadBit()) { - // hml_short$0 {m:#} {n:#} len:(Unary ~n) s:(n * Bit) = HmLabel ~n m; - return deserializeLabelShort(edge); - } - if (!edge.loadBit()) { - // hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; - return deserializeLabelLong(edge, m); - } - // hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m; - return deserializeLabelSame(edge, m); + return true; + } + + private int detectLabelType(String label, int keyLength) { + int type = 0; + // hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m; + // first bit for 0, then n + 1 for Unary ~n and next n is n * Bit + // 0 + int bestLength = 1 + label.length() + 1 + label.length(); + + // hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; + // first 1 + 1 for 10, then log2 bits for n and next n is n * Bit + // 1 + int labelLongLength = 1 + 1 + (int) Math.ceil(log2(keyLength + 1)) + label.length(); + + boolean isSame = isSame(label); + // hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m; + // 2 + int labelSameLength = 1 + 1 + 1 + (int) Math.ceil(log2(keyLength + 1)); + + if (labelLongLength < bestLength) { + type = 1; + bestLength = labelLongLength; } - - private BitString deserializeLabelShort(CellSlice edge) { - int length = edge.bits.getBitString().indexOf("0"); - edge.skipBits(length + 1); - return edge.loadBits(length); + if (isSame) { + if (labelSameLength < bestLength) { + type = 2; + } } - - private BitString deserializeLabelLong(CellSlice edge, int m) { - BigInteger length = edge.loadUint((int) Math.ceil(log2((m + 1)))); - return edge.loadBits(length.intValue()); + return type; + } + + /** + * Serialize HashMap label hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel + * ~n m; hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; hml_same$11 {m:#} v:Bit n:(#<= m) + * = HmLabel ~n m; + * + * @param label String label string of zeroes and ones "010101" + * @param m int maximal possible length of the label + * @param builder Cell to which label will be serialized + */ + void serialize_label(String label, int m, CellBuilder builder) { + int t = detectLabelType(label, m); + int sizeOfM = BigInteger.valueOf(m).bitLength(); + if (t == 0) { + int n = label.length(); + builder.storeBit(false); // hml_short + for (int i = 0; i < n; i++) { + builder.storeBit(true); // Unary n + } + builder.storeBit(false); // Unary 0 + for (Character c : label.toCharArray()) { + builder.storeBit(c == '1'); + } + } else if (t == 1) { + builder.storeBit(true); + builder.storeBit(false); // hml_long + builder.storeUint(label.length(), sizeOfM); + for (Character c : label.toCharArray()) { + builder.storeBit(c == '1'); + } + } else if (t == 2) { + builder.storeBit(true); + builder.storeBit(true); // hml_same + builder.storeBit(label.charAt(0) == '1'); + builder.storeUint(label.length(), sizeOfM); + } else { + throw new IllegalStateException("Unknown label type: " + t); } - - private BitString deserializeLabelSame(CellSlice edge, int m) { - boolean v = edge.loadBit(); - BigInteger length = edge.loadUint((int) Math.ceil(log2((m + 1)))); - BitString r = new BitString(length.intValue()); - for (int i = 0; i < length.intValue(); i++) { - r.writeBit(v); - } - return r; + } + + /** + * Serialize HashMap edge hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n) {n = (~m) + l} + * node:(HashmapNode m X) = Hashmap n X; + * + * @param node tree node which contains [label as "0" and "1" string, maximal possible size of + * label, leaf or left fork, right fork] + * @param builder Cell to which edge will be serialized + */ + void serialize_edge(PatriciaTreeNode node, CellBuilder builder) { + if (node == null) { + return; } - - private static double log2(int n) { - return (Math.log(n) / Math.log(2)); + if (node.leafNode != null) { // contains leaf + BitString bs = node.leafNode.key.readBits(node.leafNode.key.getUsedBits()); + node.prefix = bs.toBitString(); + serialize_label(node.prefix, node.maxPrefixLength, builder); + builder.storeCell(node.leafNode.value); + } else { // contains fork + serialize_label(node.prefix, node.maxPrefixLength, builder); + CellBuilder leftCell = CellBuilder.beginCell(); + serialize_edge(node.left, leftCell); + CellBuilder rightCell = CellBuilder.beginCell(); + serialize_edge(node.right, rightCell); + builder.storeRef(leftCell.endCell()); + builder.storeRef(rightCell.endCell()); } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("("); - for (Map.Entry entry : elements.entrySet()) { - String s = String.format("[%s,%s],", entry.getKey(), entry.getValue()); - sb.append(s); - } - if (!elements.isEmpty()) { - sb.setLength(sb.length() - 1); - } - sb.append(")"); - return sb.toString(); + } + + public Cell serialize(Function keyParser, Function valueParser) { + List nodes = new ArrayList<>(); + for (Map.Entry entry : elements.entrySet()) { + BitString key = keyParser.apply(entry.getKey()); + Cell value = valueParser.apply(entry.getValue()); + nodes.add(new Node(key, value)); } - - public Object getKeyByIndex(long index) { - long i = 0; - for (Map.Entry entry : elements.entrySet()) { - if (i++ == index) { - return entry.getKey(); - } - } - throw new Error("key not found at index " + index); + if (nodes.isEmpty()) { + throw new Error("TonHashMap does not support empty dict. Consider using TonHashMapE"); } - public Object getValueByIndex(long index) { - long i = 0; - for (Map.Entry entry : elements.entrySet()) { - if (i++ == index) { - return entry.getValue(); - } - } - throw new Error("value not found at index " + index); + PatriciaTreeNode root = flatten(splitTree(nodes), keySize); + CellBuilder b = CellBuilder.beginCell(); + serialize_edge(root, b); + + return b.endCell(); + } + + /** + * Deserialize label + * + * @param edge cell + * @param m length at most possible bits of n (key) + */ + public BitString deserializeLabel(CellSlice edge, int m) { + if (!edge.loadBit()) { + // hml_short$0 {m:#} {n:#} len:(Unary ~n) s:(n * Bit) = HmLabel ~n m; + return deserializeLabelShort(edge); } - - public Cell buildMerkleProof( - Object key, - Function keySerializer, - Function valueSerializer - ) { - Cell dictCell = CellBuilder.beginCell().storeDictInLine( - this.serialize(keySerializer, valueSerializer) - ).endCell(); - - Cell cell = generateMerkleProof( - "", - CellSlice.beginParse(dictCell), - keySize, - padString(keySerializer.apply(key).toBitString(), keySize, '0') - ); - - return convertToMerkleProof(cell); + if (!edge.loadBit()) { + // hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; + return deserializeLabelLong(edge, m); } - - private int readUnaryLength(CellSlice slice) { - int res = 0; - while (slice.loadBit()) { - res++; - } - return res; + // hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m; + return deserializeLabelSame(edge, m); + } + + private BitString deserializeLabelShort(CellSlice edge) { + int length = edge.bits.getBitString().indexOf("0"); + edge.skipBits(length + 1); + return edge.loadBits(length); + } + + private BitString deserializeLabelLong(CellSlice edge, int m) { + BigInteger length = edge.loadUint((int) Math.ceil(log2((m + 1)))); + return edge.loadBits(length.intValue()); + } + + private BitString deserializeLabelSame(CellSlice edge, int m) { + boolean v = edge.loadBit(); + BigInteger length = edge.loadUint((int) Math.ceil(log2((m + 1)))); + BitString r = new BitString(length.intValue()); + for (int i = 0; i < length.intValue(); i++) { + r.writeBit(v); } - - private Cell generateMerkleProof( - String prefix, - CellSlice slice, - int n, - String key - ) { - Cell originalCell = CellBuilder.beginCell().storeSlice(slice).endCell(); - - int lb0 = slice.loadBit() ? 1 : 0; - int prefixLength; - StringBuilder pp = new StringBuilder(prefix); - - if (lb0 == 0) { - // Short label detected - prefixLength = readUnaryLength(slice); - for (int i = 0; i < prefixLength; i++) { - pp.append(slice.loadBit() ? '1' : '0'); - } - } else { - int lb1 = slice.loadBit() ? 1 : 0; - if (lb1 == 0) { - // Long label detected - prefixLength = slice.loadUint((int) Math.ceil(Math.log(n + 1) / Math.log(2))).intValue(); - for (int i = 0; i < prefixLength; i++) { - pp.append(slice.loadBit() ? '1' : '0'); - } - } else { - // Same label detected - char bit = slice.loadBit() ? '1' : '0'; - prefixLength = slice.loadUint((int) (Math.ceil(Math.log(n + 1) / Math.log(2)))).intValue(); - for (int i = 0; i < prefixLength; i++) { - pp.append(bit); - } - } - } - - if (n - prefixLength == 0) { - return originalCell; - } else { - CellSlice sl = CellSlice.beginParse(originalCell); - Cell left = sl.loadRef(); - Cell right = sl.loadRef(); - // NOTE: Left and right branches implicitly contain prefixes '0' and '1' - - if (left.getCellType() == CellType.ORDINARY) { - left = (pp.toString() + '0').equals(key.substring(0, pp.length() + 1)) - ? generateMerkleProof(pp.toString() + '0', CellSlice.beginParse(left), n - prefixLength - 1, key) - : convertToPrunedBranch(left); - } - - if (right.getCellType() == CellType.ORDINARY) { - right = (pp.toString() + '1').equals(key.substring(0, pp.length() + 1)) - ? generateMerkleProof(pp.toString() + '1', CellSlice.beginParse(right), n - prefixLength - 1, key) - : convertToPrunedBranch(right); - } - - return CellBuilder.beginCell() - .storeSlice(sl) - .storeRef(left) - .storeRef(right) - .endCell(); - } + return r; + } + + private static double log2(int n) { + return (Math.log(n) / Math.log(2)); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (Map.Entry entry : elements.entrySet()) { + String s = String.format("[%s,%s],", entry.getKey(), entry.getValue()); + sb.append(s); } - - private Cell endExoticCell(CellBuilder builder, CellType type) { - Cell c = builder.endCell(); - Cell exotic = new Cell(c.getBits(), c.getBitLength(), c.getRefs(), true, type); - exotic.calculateHashes(); - return exotic; + if (!elements.isEmpty()) { + sb.setLength(sb.length() - 1); } - - private Cell convertToMerkleProof(Cell c) { - return endExoticCell( - CellBuilder.beginCell() - .storeUint(3, 8) - .storeBytes(c.getHash(0)) - .storeUint(c.getDepthLevels()[0], 16) - .storeRef(c), - CellType.MERKLE_PROOF - ); + sb.append(")"); + return sb.toString(); + } + + public Object getKeyByIndex(long index) { + long i = 0; + for (Map.Entry entry : elements.entrySet()) { + if (i++ == index) { + return entry.getKey(); + } } - - private Cell convertToPrunedBranch(Cell c) { - return endExoticCell( - CellBuilder.beginCell() - .storeUint(1, 8) - .storeUint(1, 8) - .storeBytes(c.getHash(0)) - .storeUint(c.getDepthLevels()[0], 16), - CellType.PRUNED_BRANCH - ); + throw new Error("key not found at index " + index); + } + + public Object getValueByIndex(long index) { + long i = 0; + for (Map.Entry entry : elements.entrySet()) { + if (i++ == index) { + return entry.getValue(); + } } - - private String padString(String original, int requiredSize, char padChar) { - if (original.length() >= requiredSize) { - return original; // No padding needed + throw new Error("value not found at index " + index); + } + + public Cell buildMerkleProof( + Object key, + Function keySerializer, + Function valueSerializer) { + Cell dictCell = + CellBuilder.beginCell() + .storeDictInLine(this.serialize(keySerializer, valueSerializer)) + .endCell(); + + Cell cell = + generateMerkleProof( + "", + CellSlice.beginParse(dictCell), + keySize, + padString(keySerializer.apply(key).toBitString(), keySize, '0')); + + return convertToMerkleProof(cell); + } + + private int readUnaryLength(CellSlice slice) { + int res = 0; + while (slice.loadBit()) { + res++; + } + return res; + } + + private Cell generateMerkleProof(String prefix, CellSlice slice, int n, String key) { + Cell originalCell = CellBuilder.beginCell().storeSlice(slice).endCell(); + + int lb0 = slice.loadBit() ? 1 : 0; + int prefixLength; + StringBuilder pp = new StringBuilder(prefix); + + if (lb0 == 0) { + // Short label detected + prefixLength = readUnaryLength(slice); + for (int i = 0; i < prefixLength; i++) { + pp.append(slice.loadBit() ? '1' : '0'); + } + } else { + int lb1 = slice.loadBit() ? 1 : 0; + if (lb1 == 0) { + // Long label detected + prefixLength = slice.loadUint((int) Math.ceil(Math.log(n + 1) / Math.log(2))).intValue(); + for (int i = 0; i < prefixLength; i++) { + pp.append(slice.loadBit() ? '1' : '0'); + } + } else { + // Same label detected + char bit = slice.loadBit() ? '1' : '0'; + prefixLength = slice.loadUint((int) (Math.ceil(Math.log(n + 1) / Math.log(2)))).intValue(); + for (int i = 0; i < prefixLength; i++) { + pp.append(bit); } + } + } - return String.valueOf(padChar) - .repeat(Math.max(0, requiredSize - original.length() + 1)) + - original; + if (n - prefixLength == 0) { + return originalCell; + } else { + CellSlice sl = CellSlice.beginParse(originalCell); + Cell left = sl.loadRef(); + Cell right = sl.loadRef(); + // NOTE: Left and right branches implicitly contain prefixes '0' and '1' + + if (left.getCellType() == CellType.ORDINARY) { + left = + (pp.toString() + '0').equals(key.substring(0, pp.length() + 1)) + ? generateMerkleProof( + pp.toString() + '0', CellSlice.beginParse(left), n - prefixLength - 1, key) + : convertToPrunedBranch(left); + } + + if (right.getCellType() == CellType.ORDINARY) { + right = + (pp.toString() + '1').equals(key.substring(0, pp.length() + 1)) + ? generateMerkleProof( + pp.toString() + '1', CellSlice.beginParse(right), n - prefixLength - 1, key) + : convertToPrunedBranch(right); + } + + return CellBuilder.beginCell().storeSlice(sl).storeRef(left).storeRef(right).endCell(); + } + } + + private Cell endExoticCell(CellBuilder builder, CellType type) { + Cell c = builder.endCell(); + Cell exotic = new Cell(c.getBits(), c.getBitLength(), c.getRefs(), true, type); + exotic.calculateHashes(); + return exotic; + } + + private Cell convertToMerkleProof(Cell c) { + return endExoticCell( + CellBuilder.beginCell() + .storeUint(3, 8) + .storeBytes(c.getHash(0)) + .storeUint(c.getDepthLevels()[0], 16) + .storeRef(c), + CellType.MERKLE_PROOF); + } + + private Cell convertToPrunedBranch(Cell c) { + return endExoticCell( + CellBuilder.beginCell() + .storeUint(1, 8) + .storeUint(1, 8) + .storeBytes(c.getHash(0)) + .storeUint(c.getDepthLevels()[0], 16), + CellType.PRUNED_BRANCH); + } + + private String padString(String original, int requiredSize, char padChar) { + if (original.length() >= requiredSize) { + return original; // No padding needed } -} \ No newline at end of file + return StringUtils.repeat(padChar, Math.max(0, requiredSize - original.length() + 1)) + + original; + } +}