diff --git a/hedera-dependency-versions/build.gradle.kts b/hedera-dependency-versions/build.gradle.kts index cc7021dfc2c3..60dcfcf13aa5 100644 --- a/hedera-dependency-versions/build.gradle.kts +++ b/hedera-dependency-versions/build.gradle.kts @@ -58,10 +58,10 @@ dependencies.constraints { api("com.google.jimfs:jimfs:1.2") { because("com.google.jimfs") } - api("com.google.protobuf:protobuf-java:3.25.4") { + api("com.google.protobuf:protobuf-java:4.28.2") { because("com.google.protobuf") } - api("com.google.protobuf:protobuf-java-util:3.25.4") { + api("com.google.protobuf:protobuf-java-util:4.28.2") { because("com.google.protobuf.util") } api("com.hedera.pbj:pbj-runtime:0.9.2") { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SystemSetup.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SystemSetup.java index 71a8a41cc422..5e0bcbb8f82d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SystemSetup.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SystemSetup.java @@ -136,7 +136,7 @@ public void doPostUpgradeSetup(@NonNull final Dispatch dispatch) { // We update the node details file from the address book that resulted from all pre-upgrade HAPI node changes final var nodeStore = dispatch.handleContext().storeFactory().readableStore(ReadableNodeStore.class); - fileService.updateNodeDetailsAfterFreeze(systemContext, nodeStore); + fileService.updateAddressBookAndNodeDetailsAfterFreeze(systemContext, nodeStore); dispatch.stack().commitFullStack(); // And then we update the system files for fees schedules, throttles, override properties, and override diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/steps/UserTxn.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/steps/UserTxn.java index f4aa5d4db303..a459cdee7c68 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/steps/UserTxn.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/steps/UserTxn.java @@ -118,13 +118,12 @@ public static UserTxn from( } else { type = ORDINARY_TRANSACTION; } - final var isGenesis = lastHandledConsensusTime.equals(Instant.EPOCH); final var config = configProvider.getConfiguration(); final var consensusConfig = config.getConfigData(ConsensusConfig.class); final var blockStreamConfig = config.getConfigData(BlockStreamConfig.class); final var stack = SavepointStackImpl.newRootStack( state, - isGenesis ? Integer.MAX_VALUE : consensusConfig.handleMaxPrecedingRecords(), + type != ORDINARY_TRANSACTION ? Integer.MAX_VALUE : consensusConfig.handleMaxPrecedingRecords(), consensusConfig.handleMaxFollowingRecords(), boundaryStateChangeListener, kvStateChangeListener, diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/SystemSetupTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/SystemSetupTest.java index 5f66a2df666e..daac35601aa8 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/SystemSetupTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/SystemSetupTest.java @@ -177,7 +177,7 @@ void successfulAutoUpdatesAreDispatchedWithFilesAvailable() throws IOException { subject.doPostUpgradeSetup(dispatch); final var filesConfig = config.getConfigData(FilesConfig.class); - verify(fileService).updateNodeDetailsAfterFreeze(any(SystemContext.class), eq(readableNodeStore)); + verify(fileService).updateAddressBookAndNodeDetailsAfterFreeze(any(SystemContext.class), eq(readableNodeStore)); verifyUpdateDispatch(filesConfig.networkProperties(), serializedPropertyOverrides()); verifyUpdateDispatch(filesConfig.hapiPermissions(), serializedPermissionOverrides()); verifyUpdateDispatch(filesConfig.throttleDefinitions(), serializedThrottleOverrides()); @@ -186,7 +186,7 @@ void successfulAutoUpdatesAreDispatchedWithFilesAvailable() throws IOException { } @Test - void onlyNodeDetailsAutoUpdateIsDispatchedWithNoFilesAvailable() { + void onlyAddressBookAndNodeDetailsAutoUpdateIsDispatchedWithNoFilesAvailable() { final var config = HederaTestConfigBuilder.create() .withValue("networkAdmin.upgradeSysFilesLoc", tempDir.toString()) .getOrCreateConfig(); @@ -198,7 +198,7 @@ void onlyNodeDetailsAutoUpdateIsDispatchedWithNoFilesAvailable() { subject.doPostUpgradeSetup(dispatch); - verify(fileService).updateNodeDetailsAfterFreeze(any(SystemContext.class), eq(readableNodeStore)); + verify(fileService).updateAddressBookAndNodeDetailsAfterFreeze(any(SystemContext.class), eq(readableNodeStore)); verify(stack, times(1)).commitFullStack(); final var infoLogs = logCaptor.infoLogs(); @@ -210,7 +210,7 @@ void onlyNodeDetailsAutoUpdateIsDispatchedWithNoFilesAvailable() { } @Test - void onlyNodeDetailsAutoUpdateIsDispatchedWithInvalidFilesAvailable() throws IOException { + void onlyAddressBookAndNodeDetailsAutoUpdateIsDispatchedWithInvalidFilesAvailable() throws IOException { final var config = HederaTestConfigBuilder.create() .withValue("networkAdmin.upgradeSysFilesLoc", tempDir.toString()) .getOrCreateConfig(); @@ -227,7 +227,7 @@ void onlyNodeDetailsAutoUpdateIsDispatchedWithInvalidFilesAvailable() throws IOE subject.doPostUpgradeSetup(dispatch); - verify(fileService).updateNodeDetailsAfterFreeze(any(SystemContext.class), eq(readableNodeStore)); + verify(fileService).updateAddressBookAndNodeDetailsAfterFreeze(any(SystemContext.class), eq(readableNodeStore)); verify(stack, times(1)).commitFullStack(); final var errorLogs = logCaptor.errorLogs(); diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/FileServiceImpl.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/FileServiceImpl.java index 1db47377b974..e978ed668798 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/FileServiceImpl.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/FileServiceImpl.java @@ -73,13 +73,13 @@ public V0490FileSchema fileSchema() { } /** - * Creates the 102 files in the given genesis context. + * Update the 101, 102 files with the nodeStore data. * * @param context the genesis context * @param nodeStore the ReadableNodeStore */ - public void updateNodeDetailsAfterFreeze( + public void updateAddressBookAndNodeDetailsAfterFreeze( @NonNull final SystemContext context, @NonNull final ReadableNodeStore nodeStore) { - fileSchema.updateNodeDetailsAfterFreeze(context, nodeStore); + fileSchema.updateAddressBookAndNodeDetailsAfterFreeze(context, nodeStore); } } diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/V0490FileSchema.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/V0490FileSchema.java index 0ee102bd9dc9..3cc9ce8ac4a6 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/V0490FileSchema.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/V0490FileSchema.java @@ -235,14 +235,17 @@ public Bytes genesisNodeDetails(@NonNull final NetworkInfo networkInfo) { NodeAddressBook.newBuilder().nodeAddress(nodeDetails).build()); } - public void updateNodeDetailsAfterFreeze( + public void updateAddressBookAndNodeDetailsAfterFreeze( @NonNull final SystemContext systemContext, @NonNull final ReadableNodeStore nodeStore) { requireNonNull(systemContext); final var config = systemContext.configuration(); final var filesConfig = config.getConfigData(FilesConfig.class); - // Create the node details for file 102 + // Create the nodeDetails for file 102 dispatchSynthFileUpdate( systemContext, createFileID(filesConfig.nodeDetails(), config), nodeStoreNodeDetails(nodeStore)); + // Create the addressBook for file 101 + dispatchSynthFileUpdate( + systemContext, createFileID(filesConfig.addressBook(), config), nodeStoreAddressBook(nodeStore)); } /** @@ -281,22 +284,36 @@ private Bytes nodeStoreNodeDetails(@NonNull final ReadableNodeStore nodeStore) { .mapToLong(EntityNumber::number) .mapToObj(nodeStore::get) .filter(node -> node != null && !node.deleted()) - .forEach(node -> { - nodeDetails.add(NodeAddress.newBuilder() - .nodeId(node.nodeId()) - .nodeAccountId(node.accountId()) - .nodeCertHash(node.grpcCertificateHash()) - .description(node.description()) - .stake(node.weight()) - .rsaPubKey(readableKey(getPublicKeyFromCertBytes( - node.gossipCaCertificate().toByteArray(), node.nodeId()))) - .serviceEndpoint(node.serviceEndpoint()) - .build()); - }); + .forEach(node -> nodeDetails.add(NodeAddress.newBuilder() + .nodeId(node.nodeId()) + .nodeAccountId(node.accountId()) + .nodeCertHash(node.grpcCertificateHash()) + .description(node.description()) + .stake(node.weight()) + .rsaPubKey(readableKey(getPublicKeyFromCertBytes( + node.gossipCaCertificate().toByteArray(), node.nodeId()))) + .serviceEndpoint(node.serviceEndpoint()) + .build())); return NodeAddressBook.PROTOBUF.toBytes( NodeAddressBook.newBuilder().nodeAddress(nodeDetails).build()); } + private Bytes nodeStoreAddressBook(@NonNull final ReadableNodeStore nodeStore) { + final var nodeAddresses = new ArrayList(); + StreamSupport.stream(Spliterators.spliterator(nodeStore.keys(), nodeStore.sizeOfState(), DISTINCT), false) + .mapToLong(EntityNumber::number) + .mapToObj(nodeStore::get) + .filter(node -> node != null && !node.deleted()) + .forEach(node -> nodeAddresses.add(NodeAddress.newBuilder() + .nodeId(node.nodeId()) + .nodeCertHash(node.grpcCertificateHash()) + .nodeAccountId(node.accountId()) + .serviceEndpoint(node.serviceEndpoint()) + .build())); + return NodeAddressBook.PROTOBUF.toBytes( + NodeAddressBook.newBuilder().nodeAddress(nodeAddresses).build()); + } + // ================================================================================================================ // Creates and loads the initial Fee Schedule into state diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/SystemFileExportsTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/SystemFileExportsTest.java index ea11adb35e7b..5242f737f7f5 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/SystemFileExportsTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/SystemFileExportsTest.java @@ -125,7 +125,33 @@ final Stream syntheticNodeDetailsUpdateHappensAtUpgradeBoundary() { final AtomicReference> gossipCertificates = new AtomicReference<>(); return hapiTest( recordStreamMustIncludePassFrom(selectedItems( - addressBookExportValidator(grpcCertHashes, gossipCertificates), 1, this::isSysFileUpdate)), + nodeDetailsExportValidator(grpcCertHashes, gossipCertificates), 1, this::isSysFileUpdate)), + given(() -> gossipCertificates.set(generateCertificates(CLASSIC_HAPI_TEST_NETWORK_SIZE))), + // This is the genesis transaction + cryptoCreate("firstUser"), + overriding("nodes.updateAccountIdAllowed", "true"), + sourcing(() -> blockingOrder(nOps(CLASSIC_HAPI_TEST_NETWORK_SIZE, i -> nodeUpdate("" + i) + .accountId("0.0." + (i + ACCOUNT_ID_OFFSET)) + .description(DESCRIPTION_PREFIX + i) + .serviceEndpoint(endpointsFor(i)) + .grpcCertificateHash(grpcCertHashes[i]) + .gossipCaCertificate(derEncoded(gossipCertificates.get().get((long) i)))))), + // And now simulate an upgrade boundary + simulatePostUpgradeTransaction(), + cryptoCreate("secondUser").via("addressBookExport")); + } + + @GenesisHapiTest + final Stream syntheticAddressBookUpdateHappensAtUpgradeBoundary() { + final var grpcCertHashes = new byte[][] { + randomUtf8Bytes(48), randomUtf8Bytes(48), randomUtf8Bytes(48), randomUtf8Bytes(48), + }; + final AtomicReference> gossipCertificates = new AtomicReference<>(); + return hapiTest( + recordStreamMustIncludePassFrom(selectedItems( + addressBookExportValidator("files.addressBook", grpcCertHashes, gossipCertificates), + 2, + this::isSysFileUpdate)), given(() -> gossipCertificates.set(generateCertificates(CLASSIC_HAPI_TEST_NETWORK_SIZE))), // This is the genesis transaction cryptoCreate("firstUser"), @@ -151,7 +177,7 @@ final Stream syntheticFeeSchedulesUpdateHappensAtUpgradeBoundary() recordStreamMustIncludePassFrom(selectedItems( sysFileExportValidator( "files.feeSchedules", upgradeFeeSchedules, SystemFileExportsTest::parseFeeSchedule), - 2, + 3, this::isSysFileUpdate)), // This is the genesis transaction sourcingContextual(spec -> overridingTwo( @@ -196,7 +222,7 @@ final Stream syntheticThrottlesUpdateHappensAtUpgradeBoundary() thr "files.throttleDefinitions", upgradeThrottleDefs, SystemFileExportsTest::parseThrottleDefs), - 2, + 3, this::isSysFileUpdate)), // This is the genesis transaction sourcingContextual(spec -> overridingTwo( @@ -237,7 +263,7 @@ final Stream syntheticPropertyOverridesUpdateHappensAtUpgradeBounda "files.networkProperties", upgradePropOverrides, SystemFileExportsTest::parseConfigList), - 2, + 3, this::isSysFileUpdate)), // This is the genesis transaction sourcingContextual(spec -> overriding( @@ -272,7 +298,7 @@ final Stream syntheticPropertyOverridesUpdateCanBeEmptyFile() { "files.networkProperties", ServicesConfigurationList.getDefaultInstance(), SystemFileExportsTest::parseConfigList), - 2, + 3, this::isSysFileUpdate)), // This is the genesis transaction sourcingContextual(spec -> overridingTwo( @@ -313,7 +339,7 @@ final Stream syntheticPermissionOverridesUpdateHappensAtUpgradeBoun "files.hapiPermissions", upgradePermissionOverrides, SystemFileExportsTest::parseConfigList), - 2, + 3, this::isSysFileUpdate)), // This is the genesis transaction sourcingContextual(spec -> overriding( @@ -401,7 +427,7 @@ private static VisibleItemsValidator sysFileExportValidator( }; } - private static VisibleItemsValidator addressBookExportValidator( + private static VisibleItemsValidator nodeDetailsExportValidator( @NonNull final byte[][] grpcCertHashes, @NonNull final AtomicReference> gossipCertificates) { return (spec, records) -> { @@ -448,6 +474,50 @@ private static VisibleItemsValidator addressBookExportValidator( }; } + private static VisibleItemsValidator addressBookExportValidator( + @NonNull final String fileNumProperty, + @NonNull final byte[][] grpcCertHashes, + @NonNull final AtomicReference> gossipCertificates) { + return (spec, records) -> { + final var items = records.get(SELECTED_ITEMS_KEY); + assertNotNull(items, "No post-upgrade txn found"); + final var targetId = + new FileID(0, 0, Long.parseLong(spec.startupProperties().get(fileNumProperty))); + final var updateItem = items.entries().stream() + .filter(item -> item.function() == FileUpdate) + .filter(item -> + toPbj(item.body().getFileUpdate().getFileID()).equals(targetId)) + .findFirst() + .orElse(null); + assertNotNull(updateItem, "No update for " + fileNumProperty + " found in post-upgrade txn"); + final var synthOp = updateItem.body().getFileUpdate(); + final var addressBookId = + new FileID(0, 0, Long.parseLong(spec.startupProperties().get("files.addressBook"))); + assertEquals(addressBookId, toPbj(synthOp.getFileID())); + try { + final var updatedAddressBook = NodeAddressBook.PROTOBUF.parse( + Bytes.wrap(synthOp.getContents().toByteArray())); + for (final var address : updatedAddressBook.nodeAddress()) { + final var actualCertHash = address.nodeCertHash().toByteArray(); + assertArrayEquals( + grpcCertHashes[(int) address.nodeId()], + actualCertHash, + "node" + address.nodeId() + " has wrong cert hash"); + + final var expectedAccountID = AccountID.newBuilder() + .accountNum(address.nodeId() + ACCOUNT_ID_OFFSET) + .build(); + assertEquals(expectedAccountID, address.nodeAccountId()); + + final var expectedServiceEndpoint = endpointsFor((int) address.nodeId()); + assertEquals(expectedServiceEndpoint, address.serviceEndpoint()); + } + } catch (ParseException e) { + Assertions.fail("Update contents was not protobuf " + e.getMessage()); + } + }; + } + private static VisibleItemsValidator validatorFor( @NonNull final AtomicReference> preGenesisContents) { return (spec, records) -> validateSystemFileExports(spec, records, preGenesisContents.get()); diff --git a/platform-sdk/docs/proposals/TSS-Library/TSS-Library-Test-Plan.md b/platform-sdk/docs/proposals/TSS-Library/TSS-Library-Test-Plan.md new file mode 100644 index 000000000000..c43582ccea44 --- /dev/null +++ b/platform-sdk/docs/proposals/TSS-Library/TSS-Library-Test-Plan.md @@ -0,0 +1,286 @@ +# TSS-Library Testing plan + +## Pairings API + +API Implementing Pairings, Groups, and Field Operations + +### Technical Tests + +The aim of these tests is to validate the JNI wrapping is performed correctly +Serialization Test Cases +Deserialization Test Cases +Operations against know results +Exceptions and failure scenarios +Points not in the curve + +### Functionality Tests + +The aim of these tests is to validate the correctness of the operations performed with the Java wrapping of the library. + +#### Test Cases for GroupElements Addition + +Identity element: G \+ 0 \= G +Inverse element: G \+ (-G) \= 0 +Associativity: (G1 \+ G2) \+ G3 \= G1 \+ (G2 \+ G3) +Commutativity: G1 \+ G2 \= G2 \+ G1 + +##### Negation + +Double negation: \-(-G) \= G +Identity element: G \+ (-G) \= 0 + +##### Scalar Multiplication + +Identity element: 1 \* G \= G +Zero element: 0 \* G \= 0 +Distributivity: (a \+ b) \* G \= a \* G \+ b \* G +Associativity: a \* (b \* G) \= (a \* b) \* G +Negation: (-a) \* G \= \-(a \* G) + +##### Point Doubling + +Doubling identity: 2 \* 0 \= 0 +Relation to scalar multiplication: 2 \* G \= G \+ G + +#### Test Cases for FieldElements + +##### Addition + +Identity element: a \+ 0 \= a +Inverse element: a \+ (-a) \= 0 +Associativity: (a \+ b) \+ c \= a \+ (b \+ c) +Commutativity: a \+ b \= b \+ a + +##### Subtraction + +Definition: a \- b \= a \+ (-b) +Identity element: a \- 0 \= a +Inverse element: a \- a \= 0 + +##### Multiplication + +Identity element: a \* 1 \= a +Zero element: a \* 0 \= 0 +Associativity: (a \* b) \* c \= a \* (b \* c) +Commutativity: a \* b \= b \* a +Distributivity: a \* (b \+ c) \= a \* b \+ a \* c +Inverse property: a \* Inverse(a) \= 1 + +#### Test Cases for Pairings + +##### Bilinearity + +Generate random group elements g1, g2, h1, h2. +verify:: e(g1 , g2) \= e(g2 , g1)? +Verify the equation:“a”, “b” member of “Fq” (Finite Field), “P” member of “G₁”, and “Q” member of “G₂”, +\* then e(a×P, b×Q) \= e(ab×P, Q) \= e(P, ab×Q) \= e(P, Q)^(ab) +Scalar values k1 and k2: Verify the equation: e(k1\*g1, g2\*k2) \= e(g1, g2) ^k1\*k2 +Test with different combinations of group elements, including identity elements (point at infinity). + +#### Input selection + +**Test with extreme values** (e.g., very large or small scalars, specific points on the curve). **Ensure all inputs are validated**, e.g., checking if points lie on the curve.**Uniformly distributed**: Generate random points within the group's defined range. +**Edge cases**: Include points near the boundaries of the group's definition. + +### Performance Tests + +Measure the time taken for pairings, group operations, and field operations. → what are the performance parameters to compare against? What do we do with the results? +Compare performance from executing rust code directly and through JNI → it can be done using Criterion crate (​​https://crates.io/crates/criterion) + +## Signatures Library + +Library Producing EC Private-Public Keys and Signatures + +### Technical Tests + +#### Key Generation Test + +##### Randomness + +The aim of these tests is to: detect incorrectly used pseudorandom number generators for exploitable errors and or detecting Key biases. + +* Test N randomly generated keys and check their distribution is normal. → (maybe Apache Commons Math?) (frequency, serial, poker, run, gap) +* Test a single key byte’s distribution → (Chi Square test ?)Measure the entropy of the generated keys. + +#### Signature Tests + +* Test N randomly generated signatures and check their distribution is normal. → (maybe Apache Commons Math?) (frequency, serial, poker, run, gap) +* ensure signatures vary given the same input message and different signing keys (by randomness in the algorithm). → which tool to use to determine how much change is good? + +### Functional Tests + +#### Private Key Generation + +##### Key Generation Error Test + +Attempt to generate keys with invalid parameters and expect proper error handling. + +##### Valid Key Test + +Generate a key pair and verify that the public key is a valid point on the elliptic curve. + +##### Randomness Test + +Generate multiple key pairs and ensure that keys are not repeated and appear random. + +##### Private Key Range Test + +Ensure that the private key is within the valid range (1 to n-1, where n is the order of the curve). + +#### Public Key Derivation + +##### Correctness Test + +For a known private key, check that the derived public key matches the expected value. + +##### Consistency Test + +Derive the public key multiple times from the same private key and verify that the results are consistently the same. + +#### Signature Generation + +##### Signature Validity Test + +Sign a known message and verify the signature using the corresponding public key. + +##### Deterministic Signatures Test + +sign the same message multiple times and ensure the signature is identical. + +##### Signature Error Test + +Try signing with an invalid private key or malformed data to verify that errors are handled gracefully. + +#### Signature Verification + +##### Valid Signature Test + +Verify a correctly generated signature and ensure it is accepted. → should we provide the key externally to the test? + +##### Invalid Signature Test + +Modify a valid signature slightly (e.g., change a single byte) and check that it is rejected. + +##### Edge Case Signature Test + +Test signatures where parameters hit boundary values, such as the low or high ends of their ranges. → should we use max and min in the range of Fp to get these values? + +### Performance Tests + +Measure the max, min avg time taken for key generation, to sign and verify messages. → Do we want to define acceptance parameters? Reports? + +## TSS Library + +### Functional Tests + +#### Genesis and Key Generation + +##### Valid Parameters Test + +Check that the setup accepts valid curve parameters and threshold settings. + +##### Participant Keys Validity Test + +Ensure that each participant's key is correctly checked against invalid inputs. + +#### Shares Generation + +Tests to verify that key shares are distributed and handled securely and correctly. + +* Invalid polynomial commitment +* Different PaticipantDictionary +* Detection of invalid proof + +##### Share protection validation + +Each participant can decrypt their intended parts and no others. + +##### Share reconstruction + +* An aggregated private share has the same length of its original source +* An aggregated private share is different from its original source +* Threshold number of messages can produce a valid public key +* Meny Low entropy keys plus one enough entropy key still produces a valid share +* Public key does not change when rehashing + +##### Graceful Degradation + +Test how the library handles corrupted shared inputs (low or no entropy, same input). + +##### Ciphertext Length Check Test + +Check that each share of share is a valid elgamal ciphertext + +##### Public Key Aggregation Test + +Verify that the aggregated public key correctly represents the combination of individual shares. + +##### Randomness Quality Test + +Under the same conditions, and non-deterministic RNG, different TssMessages are produced for the same input. + +#### Signature Generation Tests + +Validate the functionality and correctness of the signature generation process. + +##### Correct Signature Test + +Check that signatures generated by the threshold number of participants are valid. + +##### Invalid Signature Test + +Ensure that signatures generated with less than the threshold number of participants are invalid. + +##### Fixed length of threshold signature + +Our scheme ensures that the size of a threshold signature is fixed (i.e., not depending on the number of signers) + +##### Verification Accuracy + +Ensure that valid signatures are always accepted and invalid signatures (or tampered messages) are rejected. + +##### Consistency Test + +Sign the same message multiple times with the same subset of participants and verify that signatures are consistent (if the scheme is deterministic). + +##### Boundary Messages + +Test with minimum and maximum length messages, or messages consisting of unusual or special characters. + +#### Signature Verification Tests + +Ensure that the signature verification process is reliable and accurate. + +##### Valid Signature Verification Test + +Verify that valid signatures are accepted by the verification algorithm. + +##### Invalid Signature Verification Test + +Modify a signature slightly and check that it is rejected. + +##### Edge Case Verification Test + +Test signatures with boundary values and special cases, such as zero, near-zero, and maximum possible values. + +##### Key Compromise Simulation Test + +Simulate the compromise of one or more key shares and ensure that the system remains secure if below the threshold. + +### Performance Tests + +JMH performance test at the top level. +generate shares, aggregate signatures, sign and rekey. +Adding the results to the class as a comment. +Add MH results as comments to the class as a comment. + +### Notes + +We prefer to do high level test first and start going down the implementation layers + +## Resources + +* Eth: [https://github.com/ethereum/py\_pairing/blob/master/py\_ecc/bn128/bn128\_curve.py](https://github.com/ethereum/py_pairing/blob/master/py_ecc/bn128/bn128_curve.py) +* Elliptic curve calculator: [http://www.christelbach.com/ECCalculator.aspx](http://www.christelbach.com/ECCalculator.aspx) +* https://csrc.nist.gov/projects/random-bit-generation/ +* [https://github.com/google/paranoid\_crypto/blob/main/docs/randomness\_tests.md](https://github.com/google/paranoid_crypto/blob/main/docs/randomness_tests.md) diff --git a/platform-sdk/docs/proposals/TSS-Library/TSS-Library.md b/platform-sdk/docs/proposals/TSS-Library/TSS-Library.md index 74b95f96c43f..d08ad445ea4e 100644 --- a/platform-sdk/docs/proposals/TSS-Library/TSS-Library.md +++ b/platform-sdk/docs/proposals/TSS-Library/TSS-Library.md @@ -4,726 +4,602 @@ Provide necessary components for signing messages using a TSS scheme. -| Metadata | Entities | -|--------------------|--------------------------------------------------------| -| Designers | Austin, Cody, Edward, Rohit, Maxi,
Platform team | -| Functional Impacts | Platform team. Release engineering. DevOps | -| Related Proposals | TSS-Roster, TSS-Ledger-Id | -| Version | 2 | +| Metadata | Entities | +|:-------------------|:--------------------------------------------------| +| Designers | Austin, Cody, Edward, Rohit, Maxi, Platform team | +| Functional Impacts | Platform team. Release engineering. DevOps | +| Related Proposals | TSS-Roster, TSS-Ledger-Id | +| Version | 2 | ## Purpose and Context -A threshold signature scheme (TSS) aims to enable a threshold number of participants (shareholders) to securely and efficiently generate succinct aggregate signatures. -In our TSS implementation, a static public key should be produced that doesn't change even when the number of participants in the scheme varies, -and it can be used to verify the aggregate of a set of partial signatures produced by a threshold number of private shares. +This document covers the implementation of all necessary components for building a set of generic libraries that provides the operations to sign and verify information using a Threshold Signature Scheme (TSS) and EC Cryptography. -This is important for producing proofs that are easily consumable and verifiable by external entities. +A threshold signature scheme (TSS) aims to enable a threshold number of participants (shareholders) to securely and efficiently generate succinct aggregate signatures to cryptographically sign and verify signatures in the presence of some corruption threshold in a decentralized network. -This proposal covers the implementation of all necessary components to provide the consensus node and future library users with -the functionality to sign and verify blocks using a Threshold Signature Scheme (TSS) and EC Cryptography. +In that scheme, a static public key is produced that doesn't change even when the number of participants in the scheme varies. Such property is important for producing proofs that are easily consumable and verifiable by external entities. -The related proposal, TSS-Ledger-Id, provides an overview of the process and background for TSS and how it impacts the platform’s functionality. +Our implementation will be based on [Groth21](https://eprint.iacr.org/2021/339) . This algorithm generates a public key and a private key for each participant. The private key is distributed as key-splits (or shares of shares), and no single party knows the complete aggregated private key. +Only a threshold number of key-splits can produce a valid signature that the aggregated public key can later verify. To achieve that goal, it uses BLS signatures, Shamir's secret sharing, ElGamal, and Zero-Knowledge Proofs. -This proposal assumes no relation with the platform and defines a generic component that any consumer can integrate. -It only assumes that there exists a channel to connect participants, where the identity of the message sender has been previously validated. - -The process of sending messages through that channel and receiving the responses is outside the scope of this proposal. -Additionally, participants will need access to each other's public key. While the generation of the public/private keys is included in this proposal, -the distribution aspect, the loading, and the in-memory interpretation from each participant are outside the scope of this proposal. +This proposal assumes no relation with the platform and defines a generic component that any consumer can integrate. It only assumes a channel exists to connect participants where the message sender's identity has been previously validated. The process of sending messages through that channel and receiving the responses is also outside the scope of this proposal. +Additionally, participants will need access to each other's public key. While the generation of the public/private keys is included in this proposal, the distribution aspect, the loading, and the in-memory interpretation from each participant are outside the scope of this proposal. The related proposal, TSS-Ledger-Id, provides an overview of the process and background for TSS and how it impacts the platform’s functionality. ### Glossary -- **TSS (Threshold Signature Scheme)**: A cryptographic signing scheme in which a minimum number of parties (reconstruction threshold) must collaborate - to produce an aggregate signature that can be used to sign messages and an aggregate public key that can be used to verify that signature. -- **Groth 21**: Publicly verifiable secret sharing and resharing schemes that enable secure and efficient distribution and management of secret shares, - with many possible use cases supporting applications in distributed key generation and threshold signatures. - Uses Shamir's secret sharing, ElGamal, and Zero-Knowledge Proofs. -- **Distribute key generation**: Aims to solve the problem of getting n parties able to cryptographically sign and verify signatures in the presence of some corruption threshold in a decentralized network. - To do so, this algorithm generates a public key and a private key. The private key is distributed as key-splits (shares) such that no single party knows the private key. -- **Shamir’s Secret Sharing**: In Shamir’s SS, a secret `s` is divided into `n` shares by a dealer, and shares are sent to shareholders secretly. - The secret `s` is shared among `n` shareholders in such a way that: - (a) any party with at least `t` shares can recover the secret, and (b) any party with fewer than `t` shares cannot obtain the secret. +- **BLS (Boneh, Lynn, and Shacham) Signatures**: BLS signatures are succinct and allow aggregation. +- **TSS (Threshold Signature Scheme)**: A cryptographic signing scheme in which a minimum number of parties (reconstruction threshold) must collaborate to produce an aggregate signature that can be used to sign messages and an aggregate public key that can be used to verify that signature. +- **Groth 21**: Publicly verifiable secret sharing and resharing schemes that enable secure and efficient distribution and management of secret shares, with many possible use cases supporting applications in distributed key generation and threshold signatures. As mentioned, it uses Shamir's secret sharing, ElGamal, and Zero-Knowledge Proofs. +- **Shamir’s Secret Sharing**: In Shamir’s SS, a secret `s` is divided into `n` shares by a dealer, and shares are sent to shareholders secretly. The secret `s` is shared among `n` shareholders so that (a) any party with at least `t` shares can recover the secret, and (b) any party with fewer than `t` shares cannot obtain the secret. - **ElGamal**: On a message, the ElGamal signature scheme produces a signature consisting of two elements `(r, s)`, where `r` is a random number, and `s` is computed from the message, a signer's secret key, and `r`. -- **SNARK**: A proof system for proving arbitrary statements (circuits / programs). -- **Zero-Knowledge Proofs**: A proof system where one can prove possession of certain information, e.g., a secret key, without revealing that information or any interaction between the prover and verifier. -- **NIZK**: A non-interactive zero-knowledge proof for an statement. In TSS we use NIZK proofs for encoding the correctness of the secret sharing. -- **EC (Elliptic Curve)**: `Elliptic` is not elliptic in the sense of an `oval circle`. In the field `Fp`, an `EC` is like a non-connected cloud of points where - all points satisfy an equation, and all operations are performed modulo `p`. Some elliptic curves are pairing-friendly. -- **Bilinear Pairings**: These are mathematical functions used in cryptography to map two elements of different groups (in EC, the group is an elliptic curve) to a single value in another group - in a way that preserves specific algebraic properties. -- **Fields**: Mathematical structures where addition, subtraction, multiplication, and division are defined and behave as expected (excluding division by zero). -- **Groups**: Sets equipped with an operation (like addition or multiplication) that satisfies certain conditions (closure, associativity, identity element, and inverses). -- **Share**: Represents a piece of the necessary public/private elements to create signatures. In TSS, - a threshold number of shares is needed to produce an aggregate signature that the ledger public key can later verify. -- **Polynomial Commitment**: A process that enables evaluations of a polynomial at specific points to be verified without revealing the entire polynomial. -- **Participant**: Any party involved in the distributed key generation protocol. -- **Participant Directory**: An address book of participants of the distributed key generation protocol. +- **SNARK**: A proof system for proving arbitrary statements (circuits/programs). +- **Zero-Knowledge Proofs**: ZKProofs are a proof system where one can prove possession of certain information, e.g., a secret key, without revealing that information or any interaction between the prover and verifier. +- **NIZK**: A non-interactive zero-knowledge proof for a statement. In TSS, we use NIZK proofs to encode the correctness of the secret sharing. +- **Elliptic Curve (EC)**: `Elliptic` is not elliptic in the sense of an `oval circle`. In the field `Fp`, an `EC` is like a non-connected cloud of points where all points satisfy an equation, and all operations are performed modulo `p`. Some elliptic curves are pairing-friendly. +- **Bilinear Pairings**: These are mathematical functions used in cryptography to map two elements of different groups of an elliptic curve to a single value in another group in a way that preserves specific algebraic properties. +- **Fields**: Fields are mathematical structures where addition, subtraction, multiplication, and division are defined and behave as expected (excluding division by zero). Finite fields are especially important for EC-cryptography, where all the operations are performed modulo a big prime number. +- **Groups**: Groups are sets equipped with an operation (like addition or multiplication) that satisfies certain conditions (closure, associativity, identity element, and inverses). +- **Share**: Represents a piece of the necessary public/private elements to create signatures. Each share is in itself a valid Pairings-Key. +- **Polynomial Commitment**: It’s a process that enables evaluations of a polynomial at specific points to be verified without revealing the entire polynomial. +- **Participant**: Is any party involved in the distributed key generation protocol. +- **Participant Directory**: An address book of participants of the distributed key generation protocol that holds information of the participant executing the protocol and all the others. ### Goals -- **Usability**: Design user-friendly libraries with a public API that are easy to integrate with other projects, such as consensus node and other library users. -- **EVM support**: Generated signature and public keys should be compatible with EVM precompiled functions - so that signature validation can be done on smart contracts without incurring an excessive gas cost. +- **Usability**: Design user-friendly libraries with a public API that are easy to integrate. +- **EVM support**: Generated signature and public keys should be compatible with EVM precompiled functions so that signature validation can be done on smart contracts without incurring an excessive gas cost. - **Security**: Our produced code should be able to pass internal and external security audits. - **Flexibility**: Minimize the impact of introducing support for other elliptic curves. -- **Independent Release**: When applicable, the new libraries should have the release cycle separate from the platform. - They should be implemented in a way that is easy for both platform and block node to depend on. +- **Independent Release**: When applicable, the new libraries should separate the release cycle from the platform. ### Non-Goals -- Implement support for elliptic curve cryptography in Java. -- Support any system/architecture other than: Windows amd64, Linux amd64 and arm64, and MacOS amd64 and arm64. +- Implement support for elliptic curve arithmetic in Java. +- Support any system/architecture other than Windows amd64, Linux amd64 and arm64, and MacOS amd64 and arm64. - Creation of the building artifacts and plugins for rust code. -- This proposal covers the implementation of a tool similar to ssh-keygen to generate those keys, but the generation, persistence, distribution - and loading of those keys is outside the scope of this proposal. +- This proposal covers the implementation of a tool similar to ssh-keygen to generate those keys. Still, the generation, persistence, distribution, and loading of those keys are outside the scope of this proposal. ## Changes ### Core Behaviors -The proposed TSS solution is based on Groth21. +The proposed TSS solution is based on [Groth21](https://eprint.iacr.org/2021/339). -Groth21 is a non-interactive, publicly verifiable secret-sharing scheme where a dealer can construct a Shamir secret sharing of a field element -and confidentially yet verifiably distribute shares to multiple receivers. -It includes a distributed resharing protocol that preserves the public key but creates a fresh secret sharing of the secret key and hands it to a set of receivers, -which may or may not overlap with the original set of shareholders. +Groth21 is a non-interactive, publicly verifiable secret-sharing scheme where a dealer can construct a Shamir secret sharing of a field element and confidentially yet verifiably distribute shares to multiple receivers. It includes a distributed resharing protocol that preserves the public key but creates a fresh secret sharing of the secret key and hands it to a set of receivers, which may or may not overlap with the original set of shareholders. -#### Overview +### Overview -Participants can hold one or more shares, each of which can be used to sign a message. -The goal is to generate an aggregate signature which is valid if a threshold number of individual signatures are combined. +Participants can hold one or more shares, each of which can be used to sign a message. The goal is to generate an aggregate signature valid if a threshold number of individual signatures are combined. -Each participant brings their own Elliptic Curve (EC) key pair (private and public). They share their public keys with all other participants while securing their private keys. -Before the protocol begins, all participants agree on the cryptographic parameters (type of curve and what group of the pairing will be used for public keys and signatures). -When the protocol is initialized, a participant directory is built. This directory includes the number of participants, each participant’s EC public key, and the shares they own. +Each participant brings their own BLS key private and public pair _(we will call them TssEncryptionKeys as every key created is a valid bls key but serving different purposes)_. +They share their public TssEncryptionKeys with all other participants while securing their private keys. +Before the protocol begins, all participants agree on the cryptographic parameters (type of curve and what pairing group will be used for public keys and signatures). +When the protocol is initialized, a participant directory is built. This directory includes the number of participants, all participant’s public TssEncryptionKeys, and the shares they own. Each participant generates portions of a secret share and distributes them among the other participants using the following process: -1. A random private key is created and mathematically split into a known number of total shares. - * Using Shamir's Secret Sharing and interpolation polynomials. -2. Each portion is encrypted with the share owner's public key, ensuring only the intended recipient can read it. -3. A message is created that includes all encrypted values so that only the intended recipients can decrypt their respective portions of the secret share. +1. A random share is created and mathematically split (Using Shamir's Secret Sharing and interpolation polynomials) into a known number of key-splits (or shares of shares). +2. Each portion is encrypted with the share owner's tss encryption public key, ensuring only the intended recipient can read it. +3. A message includes all encrypted values so only the intended recipients can decrypt their respective portions of the secret share. -(e.g.: for a directory of 10 participants and 10 shares distributing 1 share each with a threshold value of 6, Participant 1 will generate a message out of a random key, -that will contain 10 portions of that key, each one encrypted under each participants' public key) -This setup allows participants to share secret information securely. The message also contains additional information necessary for its validation (Such as a polynomial commitment and a NIZK proof). +(e.g., for a directory of 10 participants and 10 shares distributing 1 share each with a threshold value of 6, `participant1` will generate a message out of a random key that will contain 10 portions of that key, each one encrypted under each participant' public key) This setup allows participants to share secret information securely. The message also contains additional information necessary for validation (Such as a polynomial commitment and a NIZK proof). Upon receiving a threshold number of messages, each participant: -1. Validates the message and decrypts the information encrypted with their public key. +1. Validates the message and decrypts the information encrypted with their tss encryption private key. 2. Aggregates the decrypted information to generate a private key for each owned share. 3. Retrieves a public key for each share in the system to validate signatures. -(e.g.: for a directory of 5 participants and 10 shares with a threshold value of 6 where Participant 1 has 2 shares; -Participant 1 will collect at least 6 valid messages from all participants, take the first and second portions of each message, decrypt and aggregate them respectively, so they become the first and second owned secret share ) +(e.g., for a directory of 5 participants and 10 shares with a threshold value of 6 where `participant1` has 2 shares, `participant1` will collect at least 6 valid messages from all participants, take the first and second portions of each message, decrypt with its tss encryption private key and aggregate them, so they become the first and second owned secret share) -Individual signing can then begin. Participants use the private information of their shares to sign messages. +Individual signing can then begin. Participants use their private shares to sign messages. -An aggregate signature is created when signatures from at least the threshold number of parties are combined. This aggregate signature can be validated using the combined value of the public shares in the directory. +An aggregate signature is created when at least a threshold number of signatures are combined. This aggregate signature can be validated using the combined value of the public shares in the directory. -The process restarts whenever the number of participants or the shares assigned to each change. However, the initially generated group public key remains unchanged to maintain consistency. -New secret information for shares is created using existing data, ensuring the aggregate signature can still be verified with the original group public key. +The process restarts whenever the number of participants or the shares assigned to each change. However, the initially generated group public key remains unchanged to maintain consistency. New secret information for shares is created using existing data, ensuring the aggregate signature can still be verified with the original group public key. -#### Implementation details +### Architecture -Before starting, all participants should agree on a `SignatureSchema` they will use. -The `SignatureSchema` defines the type of Curve and which Group of the Pairing is used for PublicKey Generation. +Although the library is designed for agnostic ally from the client, this section describes its place in hedera ecosystem and how it will interact and be used by the system: -##### Input +![img_5.svg](img_5.svg) -* Participant's persistent EC Private key (Private to each participant) -* Number of participants (Public) -* Number of shares per participant (Public) -* A threshold value -* All participants' persistent EC public keys (Public) -* A predefined `SignatureSchema` (Public / Constant for all the network) +1. **TSS Lib**: Used to create shares, create TSS messages to send to other nodes, assemble shared public keys, and sign messages. +2. **Pairings Signatures Library**: This library provides cryptographic objects (PrivateKey, PublicKey, and Signature) and operations to sign and verify signatures. +3. **Pairings API**: An API definition for the arithmetic operations required to work with specific EC curves and the underlying Groups, Fields, and Pairings concepts. This API minimizes the impact of changing to different curves. +4. **Bilinear Pairings Impl**: Implementing the Bilinear Pairings API that will be loaded at runtime using Java’s service provider (SPI) mechanism. Multiple implementations can be provided to support different curves, and the Java service provider approach facilitates easily changing between implementations and dependencies. +5. **Native Support Library**: Provides a set of generic functions for loading native libraries in different system architectures when packaged in a jar, using a predefined organization so they can be accessed with JNI. +6. **EC-KeyGen:** is a utility module that enables the node operator to generate a bootstrap public/private key pair. -``` -`Share`: An abstract concept having a unique identifier and an owner -|- `PrivateShare`: Represents a share owned by the executor of the scheme. Contains a secret value (EC Private key) used for signing. -|- `PublicShare`: Represents a share in the system. It contains public information that can be used to validate each signatures. -``` +### Module organization and repositories -##### Bootstrap Stage +![img_6.svg](img_6.svg) -Given +1. **hedera-cryptography**: This is a separate repository for hosting cryptography-related libraries. It is necessary to facilitate our build process, which includes Rust libraries. It also provides independent release cycles. +2. **hedera-common-nativesupport**: This gradle module enables loading compiled native libraries to be used with JNI. +3. **plataform.core**: Represents the part of the consensus node that will be interacting directly with the tss library. +4. **hedera-cryptography-tss**: Gradle module for the TSS Library. +5. **hedera-cryptography-signatures**: Gradle module for the Bilinear Pairings Signature Library. +6. **hedera-cryptography-pairings-api**: Gradle module for the Bilinear Pairings API. Minimizes the impact of adding or removing implementations. +7. **hedera-cryptography-altbn128**: Gradle module that will implement the Bilinear Pairings API using alt-bn128 elliptic curve using arkworks library implemented in rust. -A participants directory: +### Using arkworks -``` -e.g: -P # shares ------------------------------ -P₁ 5 P₁_EC_PublicKey -P₂ 2 P₂_EC_PublicKey -P₃ 1 P₃_EC_PublicKey -P₄ 2 P₄_EC_PublicKey - -``` +Given that it is outside the scope of this project the creation and support of elliptic curve arithmetic and the mathematical operations that allow resolving a pairings operation and that there is no library implemented in Java with production-grade security that can help us with this goal, we decided to include the wrapping of arkworks bn254 module and delegate the operations to that library. We will wrap the invocations with a Java abstraction layer that will facilitate switching curves and other parameters when and if necessary. -A threshold value: -`e.g: t = 5` +### Handling of multilanguage modules and native code distribution -First, a `shareId`: `sid` is generated for each share. It's a unique, contiguous starting from 1 identifier for each existent share. It is deterministic because the function we use to generate it is deterministic. -It is necessary to assure they are values that: a) are unique per share, b) non-0, and c) can be used as input for the polynomial (They are from the same field of the selected curve) +The software provided by this approach will require multi-language modules (rust \+ Java). Rust code must be compiled into binary libraries and accessed through JNI. Given the immature state of development, Project Panama is not considered for this proposal, but it might be for future development. -And an ownership map: `ShareId`->`Participant`: +There are two possible ways of loading libraries and accessing them through JNI: -``` -e.g: -sid₁ sid₂ sid₃ sid₄ sid₅ sid₆ sid₇ sid₈ sid₉ sid₁₀ -P₁ P₁ P₁ P₁ P₁ P₂ P₂ P₃ P₄ P₄ -``` - -##### 1. Create TssMessage - -`TssMessage`: A data structure for distributing encrypted shares of a secret among all participants in a way that only the intended participant can see its part of the share. -It includes auxiliary information used to validate its correctness and assemble an aggregate public key, i.e., a commitment to a secret share polynomial and a NIZK proof. +1) Libraries are installed on the system as shared object(SO) libraries and found via the classpath and system library path. +2) Distributed with the application jars, unpacked, and loaded at runtime. -###### Generation of the shares +We want to ensure that dependent software does not require the installation of any additional dependencies other than those distributed in the jars, so we are choosing option 2\. We will provide a library `hedera-common-nativesupport` to help load and use binary libraries and binding with JNI. The low-level details of the build logic for this to work are outside the scope of this proposal. A high overview is mentioned for the benefit of readers. -In the bootstrap process, each participant creates a random EC Private Key `k` out of the Field of the `SignatureScheme` (the secret being shared). +In multilanguage projects, Rust code will be placed in a structure such as: ``` -`k` - A random EC Private Key for the participant -`n` - The number of total shares across all participants -`sidᵢ`- The shareId of the share i -`Xₖ` - A polynomial with certain properties given a specific secret k -`sᵢ` - A point in the polynomial Xₖ for sidᵢ +hedera-multilanguage-project + ├── main + │ ├── java + │ │ └── ** + │ └── rust + │ └── ** + ├── test + │ └── java + │ └── ** + ├── cargo.toml + └── build.gradle.kts ``` -Then, each shareholder will produce `n` (n=total number of shares) values `Xₛ` by evaluating a polynomial Xₖ at each `ShareId`: `sidᵢ` in the ownership map. - -The polynomial `Xₖ` is a polynomial with degree `t-1` (t=threshold) with the form: -`Xₖ = k + a₁x + ...aₜ₋₁xᵗ⁻¹`[ having: `a₁...aₜ₋₁`: random coefficients from `SignatureScheme.publicKeyGroup` and `k`'s EC field element. x is a field element, thus allowing the polynomial to be evaluated for each share id] - -Each `sᵢ = Xₖ(sidᵢ)` constitutes a point on the polynomial. - -Once the `sᵢ` value has been calculated for each `ShareId`: `sidᵢ`, the value: `Cᵢ` will be produced by encrypting the `sᵢ` using the `sidᵢ` owner's public key. - -![img.svg](img.svg) - -The TssMessage will contain all the encrypted values for all shares. - -###### Generation of the Polynomial Commitment +Then, it will be cross-compiled into a set of binary libraries and packaged in the same jar as the Java code for accessing it. -Secret sharing poses a problem for the receiver: did it get a correct share? -The dealer may send a bad share that does not correspond to the dealing, or send so many fake shares to different receivers that the aggregate does not correspond to a real dealing. -We include a Feldman commitment to the polynomial as a mechanism to detect this form of bad dealing. +Rust code will be compiled first, and the build process will create the following folder structure, where binary files will be placed and then distributed. They will be arranged by platform identifier, as returned by `System.getProperty("os.name")` and `System.getProperty("os.arch")`. ``` -g = a point generator of `SignatureScheme.publicKeyGroup` -aₒ...aₜ₋₁ = coefficients of the polynomial being commited to. +/software + ├── darwin + │ ├── amd64 + │ │ └── native_lib.dylib + │ └── arm64 + │ └── native_lib.dylib + ├── linux + │ ├── amd64 + │ │ └── native_lib.so + │ └── arm64 + │ └── native_lib.so + └── windows + └── amd64 + └── native_lib.dll ``` -For each coefficient in the polynomial `Xₖ` `a₍ₒ₎` to `a₍ₜ₋₁₎`, compute a commitment value by calculating: `gᵢ * aᵢ ` (g multiplied by polynomial coefficient `a₍ᵢ₎` ) - -###### Generation of the NIZKs proofs - -Generate a NIZK proof that these commitments and the encrypted shares correspond to a valid sharing of the secret according to the polynomial. - -Given the input: - -* witness: private data for the proof, including the actual share and the randomness used in the encryption process and private shares(sᵢ). - -* statement: The information to prove (sidᵢs,public_keys, polynomial_commitment, encrypted_shares ) - -* rng: source of randomness - -1. Serialize statement -2. Initialize a pseudorandom number generator using the `SHA256` hash of (1) -3. `x` = Random out of the random generator in (2) -4. Generate random values `α`, `ρ` from the `SignatureScheme.field` out of rng -5. `g` = `SignatureScheme.publicKeyGroup.generator` - 5.1 `F = g^ρ` - 5.2 `A = g^α` -6. Calculate `Y` by transforming elements of the statement (public keys, sids) multiplied by `ρ`, then adding `A` -7. Serialize (`x`, `F`, `A`, `Y`) -8. Recompute the pseudorandom number generator seed using the hash of (7) -9. `x'` = Random out of the random generator in (8) -10. `z_r` = `x'` * `witness.randomness` + `ρ` -11. `z_a` = `x'` + `α` + Sum(`sᵢ` * `x`.pow(`sidᵢ`)) - -Return the proof composed of `F`, `A`, `Y`, `z_r`, and `z_a` - -##### Outside of scope - -Using an established channel, each participant will broadcast a single message to be received by all participants -while waiting to receive other participants' messages. This functionality is critical for the protocol to work but needs to be handled outside the library. -Each participant will validate the received message against the commitment and the NIZKs proof. Invalid messages need to be discarded. - -##### Validation of TssMessage - -The validation is produced over the content of the message and does not include the sender's identity, which is assumed to be provided by the external channel. -Each message can be validated against the commitment and the proof by: -* Checking that the encrypted shares correspond to the commitments. -* and, that the commitments are consistent with the public values and the generated proof. - -Given the input: - -* statement: The proven information (sidᵢs, public_keys, polynomial_commitment, encrypted_shares ) -* proof: NIZKs Proofs received in the TssMessage -* generator `g`: a point generator of `SignatureScheme.publicKeyGroup` - -1. Serialize statement -2. Initialize a pseudorandom number generator using the `SHA256` hash of (1) -3. `x` = Random out of the random generator in (2) -4. Serialize (`proof.F`, `proof.A`, `proof.Y`) -5. Initialize a pseudorandom number generator using the `SHA256` hash of (4) -6. `x'` = Random out of the random generator in (5) -7. `lhs` = (`statement.ciphertext_rand` raised to the power of `x'`) multiplied by `proof.F` -8. `rhs` = generator multiplied by `proof.z_r` -9. If `lhs` != `rhs`: Return False -10. `inner` = 0 -11. For each pair (`i`:index ; `k`:commitment value) in `statement.polynomial_commitment`: - - 11.1. `term` = For each `sid` in `statements.sids`: sum (`sid` raised to the power of `i`) multiplied by (`x` raised to the power of `sid`) - - 11.2. `inner` += `k` multiplied by `term` - -12. `lhs` = (`inner` multiplied by `x'`) add `proof.A` - -13. `rhs` = `g` multiplied by `proof.z_a` - -14. If `lhs` != `rhs`: Return False - -15. `inner` = 0 - -16. For each (`ciphertext_value`, `sid`) pair in Zip(`statements.ciphertext_values`;`statements.sids`): - - 16.1. `term` =`ciphertext_value` multiplied by (`x` raised to the power of `sid`) - - 16.2. `inner` =`inner` + `term` +This whole process will be produced both locally and in CI/CD. -17. `lhs` = (`inner` multiplied by `x'`) add `proof.Y` - -18. `rhs` = `g` multiplied by `proof.z_a` - -19. If `lhs` != `rhs`: Return False - -20. `inner` = 0 - -21. For each (`public_key`, `sid`) pair in Zip(`statements.public_keys`;`statements.sids`): - - 21.1. `term` = `public_key` multiplied by (`proof.z_r` * (`x` raised to the power of `sid`)) - - 21.2. `inner` =`inner` + `term` +### Libraries Specifications -22. `rhs` = `inner` + (`g` multiplied by `proof.z_a`) +#### Hedera Cryptography Pairings API -23. If `lhs` != `rhs`: Return False +##### Overview -24. Return True +This API will expose general arithmetic operations to work with elliptic curves and bilinear pairings that implementations must provide. -##### Generating Participant's Private Shares & Ledger Id +##### Public API -Given Participant's persistent EC PrivateKey and precisely `t` number of validated messages (t=threshold) -each participant will decrypt all `Cᵢ` to generate an aggregated value `sᵢ` that will become a `SecretShare(sidᵢ, sᵢ)` for each `ShareId`: `sidᵢ` owned by the participant. +![img_7.svg](img_7.svg) -**Note:** All participants must choose the same set of valid `TssMessages` and have a threshold number of valid messages. +###### *`Curve`* -![img_1.svg](img_1.svg) +An interface that represents the different types of elliptic curves. -Also, we will extract a `PublicShare` for each `ShareId`: `sidᵢ` in the directory from the list of valid messages. -The PublicShare for share `s` is computed by evaluating each polynomial commitment in the common set of messages at `sidᵢ` and then aggregating the results. +###### *`PairingFriendlyCurve`* -At this point, the participant executing the scheme can start signing, sharing signatures, and validating individual signatures produced by other parties in the scheme. +This class provides access to each group (G₁, G₂) enabled for pairings operations and the scalar field associated with the curve. It provides an initialization method to load all necessary dependencies for the library to work. Callers are requested to invoke that method before start using the library. Implementations of this API should decide if they provide one or many curves. -##### Rekey Stage +###### *`Field`* -This Section describes the rekey process executed each time some of the scheme’s parameters change, such as the number of participants or the number of shares assigned to each participant. -This rekeying process may also be conducted for reasons other than parameter changes; to rotate shares, for example. +Represents a finite field used in a pairing-based cryptography scheme. A finite field is a ring of scalar values \[0,1,...r-1, 0, 1, ...\] where all operations are done modulus a prime number P. All operations inside the field produce a value that belongs to it. -The rekeying process is similar to the bootstrap process, but it starts with the previous list of owned private `SecretShare`. -The main difference with the genesis stage is that every participant generates a `TssMessage` out of each previously owned `SecretShare`. +Each scalar element in the field is a `FieldElement`. Supports the following operations: -![img_4.svg](img_4.svg) +- create an element out of its serialized form +- create the zero-element +- create the one-element +- create a random element -Once finished, the list of `SecretShare`s will be updated but the previously generated aggregate public key remains the same. +###### *`FieldElement`* -##### Sign Stage +A scalar value that is a member of `Field`. In an elliptic curve, these values can be used to operate in certain `Group` operations. Supports the following operations: -After genesis or rekeying stages, the library can sign any message. -Using each `SecretShare` owned by the participant, a message can be signed, producing a `TssSignature` +- Addition +- Subtraction +- Power +- Multiplication +- Serialization/Deserialization +- Inversion -Multiple signatures can be aggregated to create an aggregate `TssSignature`. An aggregate `TssSignature` can be validated against the LedgerId (public key) if -`t` (t=threshold) valid signatures are aggregated. If the threshold is met and the signature is valid, the library will respond with true; if not, it will respond with false. +###### *`Group`* -#### Security Considerations +Represents a mathematical group used in a pairing-based cryptography system. A group in this context is a set of elements (curve points) with operations that satisfy the group properties: -As long as an adversary Participant knows fewer than a threshold number of decryption keys, they cannot recover enough information to start forging threshold signatures. -Adversarial participants may learn the shares of the participants whose keys they have compromised, but more is needed to recover the secret. -For security, adversarial parties might choose low or zero entropy values for protocol inputs such as shareIds, but here will still be sufficient entropy in the overall protocol from honest nodes. +* closure +* associativity +* identity +* invertibility -#### Summary diagram +Curves can be defined by more than one group. This class provides methods to obtain elements of the group represented by the instance and some operations that handle multiple points. -The following diagram exposes the steps necessary for bootstrapping and using the library. In orange are all the steps where the library is involved. -The rest of the steps need to happen but are outside the scope of the described library. +- Create a point out of its serialized form +- Create a random point +- (hashToGroup) create a point out of a hashed (sha256?) value of a byte array +- Create the zero point or point at infinity +- Retrieve the group generator point +- Add a list of points and return the aggregate result +- Multiply the generator point with a list of scalars and return each result -![img_2.png](img_2.svg) +###### *`GroupElement`* -### Architecture +Represents an element of `Group`. In an elliptic curve, these are points. +Supports the following operations: -To implement the functionality detailed in the previous section, the following code structure is proposed: - -![img_5.png](img_5.svg) - -1. **TSS Lib**: The consensus node will use the TSS library to create shares, create TSS messages to send to other nodes, - assemble shared public keys (ledgerId), and sign block hashes. -2. **Pairings Signatures Library**: This library provides cryptographic objects (PrivateKey, PublicKey, and Signature) - and operations for the block node and consensus node to sign and verify signatures. The consensus node uses this library indirectly through the TSS Library. -3. **Pairings API**: An API definition for the cryptography primitives - and arithmetic operations required to work with a specific EC curve and the - underlying Groups, Fields, and Pairings. This API minimizes the impact of - changing to different curve implementations. -4. **Bilinear Pairings Impl**: An implementation of the Bilinear Pairings API that will be loaded at runtime using Java’s service provider (SPI) mechanism. - Multiple implementations can be provided to support different types of curves and - the Java service provider approach facilitates easily changing between - implementations and dependencies. -5. **Native Support Lib**: Provides a set of generic functions loading native libraries in different system architectures when packaged in a jar - using a predefined organization so they can be accessed with JNI. -6. **[Arkworks](https://github.com/arkworks-rs)**: A Rust ecosystem for cryptography. Our implementation uses it as the library responsible for elliptic curve cryptography. -7. **EC-Key Utils** is a utility module that enables the node operator to generate a bootstrap public/private key pair. +- addition +- multiplication for a scalar +- serialization -### Module organization and repositories +###### *`BillinearPairing`* -![img_6.png](img_6.svg) -1. **hedera-cryptography**: This is a separate repository for hosting cryptography-related libraries. It is necessary to facilitate our build process, which includes Rust libraries. It also provides independent release cycles between consensus node code and other library users. -2. **hedera-common-nativesupport**: Gradle module that enables loading into memory compiled native libraries so they can be used with JNI. -3. **hedera-cryptography-tss**: Gradle module for the TSS Library. This library's only client is the consensus node, so it will be in the `hedera-services` repository, under a newly created `hedera-cryptography` folder in `hedera-services`. -4. **hedera-cryptography-signatures**: Gradle module for the Bilinear Pairings Signature Library. -5. **hedera-cryptography-pairings-api**: Gradle module for the Bilinear Pairings API. Minimizes the impact of adding or removing implementations. -6. **hedera-cryptography-altbn128**: Gradle module that will implement the Bilinear Pairings API using alt-bn128 elliptic curve. That curve has been chosen due to EVM support. The arkworks rust library will provide the underlying cryptography implementation. The module will include Java and Rust code that will be compiled for all supported system architectures and distributed in a jar with a predefined structure. +Represents a bilinear pairing operation used in cryptographic protocols. A pairing is a map: e : G₁ × G₂ \-\> Gₜ which can satisfy these properties: -### Handling of multilanguage modules and native code distribution +* Bilinearity: `a`, `b` member of `Fq` (Finite Field a.k.a. `Field`), `P` member of `G₁`, and `Q` member of `G₂`, then `e(a×P, b×Q) = e(ab×P, Q) = e(P, ab×Q) = e(P, Q)^(ab)` +* Non-degeneracy: `e != 1` +* Computability: There should be an efficient way to compute `e`. -The software provided by this approach will require multi-language modules (rust + java). Rust code must be compiled into binary libraries and accessed through JNI. -Given the immature state of development, Project Panama is not considered for this proposal; but Project Panama may be considered for future development. +Supports the following operations: -There are two possible ways of loading libraries and accessing them through JNI -1) Libraries are installed on the system as shared object(SO) libraries and found via the classpath and system library path. -2) Distributed with the application jars, unpacked, and loaded at runtime. +- Compare: compares two pairings and returns if they are equal. -We want to ensure that dependent software does not require the installation of any additional dependencies other than those distributed in the jars, so we are choosing option 2. - -In multilanguage projects, Rust code will be compiled first using `Cargo` into a binary library and then packaged in the same jar. -While developing and on a developer’s machine, Rust code will be built for the architecture executing the build and packaged in the library’s jar, accessing the JNI wrapping code. -Once the code is merged to develop, the CI/CD pipeline compiles the Rust code into a binary library for each of the multiple supported platforms, packages it into a jar, and publishes it to Maven. -The dependency from the Maven repo will contain the Java code and the binary library cross-compiled for all supported architectures. - -We will provide a library `hedera-common-nativesupport` that will help load and use native code through JNI. -The low-level details of the build logic for this to work are outside the scope of this proposal. -A high overview is mentioned for the benefit of readers and will be provided by the DevOps Team alongside the RE Team. - -### Libraries Specifications - -#### Hedera Cryptography Pairings API +#### Hedera Cryptography alt-bn128 ##### Overview -This API will expose general arithmetic operations to work with Bilinear Pairings and EC curves that implementations must provide. - -##### Public API - -###### `Curve` - -**Description**: Represents the different types of elliptic curves. Implementations of this API should decide if they provide any, one, or many curves. - -**Link**: [Curve.java](pairings-api%2FCurve.java) - -**Note**: This enum should list all possible types so changes in the implementation don't change the API, but as of now, the number is limited. - -###### `BilinearPairing` - -**Description**: This class provides access to each group (G₁, G₂) for a specific Pairing and the FiniteField associated with the curve. -It provides an initialization method to load all necessary dependencies for the library to work. Callers are requested to invoke that method before start using the library. - -**Link**: [BilinearPairing.java](pairings-api%2FBilinearPairing.java) +Implementation module of the parings API for `alt-bn128` (`bn254`) pairings-friendly curve. That curve has been chosen due to the existence of EVM precompiles that allow this verification in a cheap way for smart contract users. The underlying elliptic curve and fields algebra operations and primitives will be implemented using `arkwrorks` library, accessed through a JNI interface. The module will include Java and Rust code. -###### `Field` - -**Description**: This is a factory interface responsible for creating FieldElement, which are scalars belonging to the field represented by this instance. - -**Link**: [Field.java](pairings-api%2FField.java) - -###### `FieldElement` - -**Description**: An interface representing an element within a field, providing methods for basic arithmetic operations. - -**Link**: [FieldElement.java](pairings-api%2FFieldElement.java) - -###### `Group` - -**Description**: An interface to define methods that obtain elements belonging to the group(s) supported by an implementation. - -**Link**: [Group.java](pairings-api%2FGroup.java) - -###### `GroupElement` +##### Implementation details -**Description**: An interface representing an element within a group, providing methods for basic group operations. +`alt-bn128` curve uses a scalar field based on the prime number: -**Link**: [GroupElement.java](pairings-api%2FGroupElement.java) +* r=21888242871839275222246405745257275088548364400416034343698204186575808495617 +* p=21888242871839275222246405745257275088548364400416034343698204186575808495617 +* q=21888242871839275222246405745257275088696311157297823662689037894645226208583 +* Generator 5\. +* G1 generator: (1, 2\) +* G2 generator: (10857046999023057135944570762232829481370756359578518086990519993285655852781, 11559732032986387107991004021392285783925812861821192530917403151452391805634, 8495653923123431417604973247489272438418190587263600148770280649306958101930, 4082367875863433681332203403145435568316851327593401208105741076214120093531\) -###### `PairingResult` +It is defined by groups `Group1` and `Group2,` identified by an enum value. +This library will not have a public API as it is intended to be used only as a runtime dependency. -**Description**: An interface representing the result of a pairing operation, with methods to compare it to other group elements. +###### *Encoding:* -**Link**: [PairingResult.java](pairings-api%2FPairingResult.java) +`FieldElements` scalars are encoded as 32-byte little-endian numbers. Curve points are encoded as two components (x, y) that differ in size for each curve group. Group 1 `GroupElements` have the form (X, Y) for each 32 little-endian bytes number. Group 2 `GroupElements` have the form (X₁, X₂, Y₁, Y₂) each 32 little-endian bytes numbers. -##### Examples: +The point at infinity in all cases is stored at 32-byte 0 value for each component. -###### Get a Pairing instance for a given curve +##### Hash to curve Note -```java -static { - //This will get an initialized instance of the pairing - BilinearPairing pairing = BilinearPairingService.instanceOf(Curve.ALT_BN128); -} -``` +hashing algorithm used by `pairings-api` does need to be the same as one of the hashing algorithms available to smart contracts, +and likely one that is available as a precompile on the EVM. -###### Work with the field associated to the pairing +###### *Code Organization Structure:* -```java -static { - final Field field = pairing.getField(); - final FieldElement indexElement = field.elementFromLong(10L); - final FieldElement randomElement = field.randomElement(new SecureRandom()); - final FieldElement otherElement = indexElement.power(BigInteger.valueOf(100)); - final byte[] seed = new byte[getSeedSize()]; - random.nextBytes(seed); - final FieldElement anotherElement = field.randomElement(seed); - final FieldElement resultAddition = randomElement.add(anotherElement); - final FieldElement resultMultiplication = randomElement.multiply(anotherElement); -} ``` - -###### Work with the groups associated to the pairing - -```java -static { - final Group group1 = pairing.getGroup1(); - final Group group2 = pairing.getGroup2(); - - final Group g2 = group1.getOppositeGroup(); - final GroupElement g1Generator = group1.getGenerator(); - final GroupElement g2Generator = group2.getGenerator(); - final GroupElement zero = group1.zeroElement(); - - final byte[] serialization = zero.toBytes(); - - final GroupElement element = group1.elementFromBytes(/*Some byte array representing a group element*/); - g1Generator.isSameGroup(g2Generator); //false -} +hedera-cryptography-altbn128 + ├── main + │ ├── java + │ │ └── ** + │ └── rust + │ └── ** + └── test + └── java + └── ** ``` #### Hedera Cryptography Pairings Signatures Library ##### Overview -This module provides cryptography primitives to create EC PublicKeys, EC PrivateKeys, and Signatures. +This provides public keys, private keys, signatures, and operations to produce each other. ##### Public API -###### `SignatureSchema` +![img.png](img.png) -**Link**: [SignatureSchema.java](signature-lib%2FSignatureSchema.java) +###### *`SignatureSchema`* -**Description**: A pairings signature scheme can be implemented with different types of curves and group assignment configurations. -For example, two different configurations might consist of a BLS_12_381 curve using G1 of the pairing to generate public key elements or G2 for the same purpose. +A pairings signature scheme can be implemented with different curves and group assignment configurations. For example, two different configurations might consist of the `ALT_BN_128` curve using short signatures or short public keys (which translates to choosing `Group1` to generate public key elements or `Group2` for the same purpose). This class allows those parameters to be specified internally, so library users don't need to keep track of the specific parameters used to produce the keys or signatures. -###### `PairingPrivateKey` +###### *`PairingsPrivateKey`* -**Link**: [PairingPrivateKey.java](signature-lib%2FPairingPrivateKey.java) +A private key is generated using the pairings API. It is a Random `FieldElement` out of a 32-byte seed. +This class provides the following operations: -**Description**: A private key generated using the pairings API +- Create a public key: The scalar multiplication of the private key and the `generator` `GroupElement` of the `Group` configured in the `SignatureSchema` for public keys. +- Sign a message: It is the scalar multiplication of the private key element and the `hashToGroup` `GroupElement` from the message of the `Group` configured in the `SignatureSchema` for signatures. -###### `PairingPublicKey` +###### *`PairingsPublicKey`* -**Link**: [PairingPublicKey.java](signature-lib%2FPairingPublicKey.java) +A public key is generated using the pairings API. Under the hood is a point in the selected curve (`GroupElement`). +This class provides the following operations: -**Description**: A public key generated using the pairings API +- Verify a signed message against a signature relies on the verification executed by the signature class. -###### `PairingSignature` +###### *`PairingsSignature`* -**Link**: [PairingSignature.java](signature-lib%2FPairingSignature.java) +A signature is generated with the private key that can be verified with the public key. Under the hood, it is also a point in the curve (`GroupElement)`. +This class provides the following operations: -**Description**: A signature generated with the private key that can be verified with the public key +- Verify against a public key and the original signed message: This operation uses the pairings operation and verifies that: -###### `GroupAssignment` +``` +pairing.pairingBetween(signatureElement, publicKeyGroup.getGenerator()); +== +pairing.pairingBetween(messageHashedToGroup, publicKey.keyElement()); +``` -**Link**: [GroupAssignment.java](signature-lib%2FGroupAssignment.java) +###### *`ElGamalUtil`* -**Description**: An enum to clarify which group public keys and signatures are in, for a given SignatureSchema +A utility class that helps to encrypt a decrypt using the ElGamal method. Operations: -##### Implementation Note +- Decrypt a message: +- ElGamal encrypts: -The serialization of the elements in this module adds a byte to represent the combination of Curve type and group assignment. +``` +var r = Field.randomFieldElement(random); +var c1 = this.groupElement().group().generator().mul(&r); +var c2 = this.groupElement().mul(&r).add(this.groupElement().group().generator().generator.mul(&m)); +return { c1, c2 } +``` -##### Examples +##### Serialization Note -###### Generating a SignatureSchema +It will be the general case that consumers of this library don’t need to understand the elements as points on the curve to operate algebraically with them, so it is sufficient for them to interact with an opaque representation. There are two strategies for getting that representation: -```java -SignatureSchema signatureSchema = SignatureSchema.from(Curve.ALT_BN128, GroupAssignament.G1_PUBLIC_KEYS); -``` +1. **Use arkworks-based serialization mechanism** This serialization mechanism uses the encoding defined for the `parings-api` and its ability to produce byte arrays from its components. -###### Generating a Private Key + **Pros:** + - Serialization is guaranteed to be canonical. + - Given that internal consumers don't need to operate with values and an opaque representation is enough, the approach decouples the serialization mechanism + from the pairing internal structure and allows to switch curves with 0 impact on the consumers. + - Simple and reduced effort, low infra support needed. -```java -PairingPrivateKey privateKey = PairingPrivateKey.create(signatureSchema, new SecureRandom()); -``` + **Cons:** + - Coupled to the arkworks serialization mechanism + - There is nothing formal to share the specification of the format. It should be through technical documentation as there is no technical interface we can share. -###### Generating a Public Key +2. **Use protobuf serialization.** This serialization mechanism consists of generating a structure that represents the internal representation of PublicKey, PrivateKey, and Signature in a defined protobuf schema. + We would need to extract the information from the pairings API implementation and produce a protobuf object to expose. -```java -PairingPublicKey publicKey = PairingPublicKey.create(privateKey); -``` + **Pros:** + - Allows to share a schema to know how to interpret the curve + - Any client that can use protobuf can interpret our elements -###### Generating a Signature + **Cons:** + - Consumers needs to be able to operate with protobuf schemas. + - By producing a structure for the internals of the pairings API, switching curves impacts our code. -```java -byte[] message = new byte[]{}; -PairingSignature signature = PairingPrivateKey.sign(message ); -``` +It was decided that keys and signatures would be represented as opaque byte arrays in little-endian form when recorded in the state and published to the block stream. +We will publish a byte layout of the data format for external consumers. +The code that sets up smart contracts for execution will be responsible for parsing the byte array and translating the little-endian data into the big-endian form that is used in the EVM. -###### Verifying a Signature +**_Serialized form of a `PairingsPrivateKey`_** -```java -static{ - signature.verify(publicKey, message); -} -``` +![img_8.svg](img_8.svg) +*_This example corresponds to AltBN128, other curves might have different lengths._ -##### Constraints +**_Serialized form of a `PairingsPublicKey` or `PairingsSignature`_** -This module will not depend on hedera-services artifacts, so it cannot include logging, metrics, configuration, or any other helper module from that repo. - -##### Dependencies +![img_8.svg](img_9.svg) +*_This example corresponds to AltBN128, other curves might have different lengths._ -hedera-cryptography-pairings-api and runtime implementation +Curve ID value `1` is `ALT_BN_128`, other curves implementations might choose a different id for their curves but it is up to the implementation to select the id. +The idea of using the id is to be able to determine if a particular key or signature can be interpreted and it is compatible with the plugged implementation of `pairings-api` #### Hedera Cryptography TSS Library ##### Overview -This library implements the Groth21 TSS-specific primitives. - -##### Constraints - -Once built, this library will depend on artifacts constructed in other repositories. -This library accepts Integer.MAX_VALUE -1 participants. - -##### Public API: +This library implements the Groth21 TSS-specific operations. -###### `TssService` +##### Public Interface -**Description**: This class handles all tss specific operations. +###### *`TssMessage`* -**Link**: [TssService.java](tss%2FTssService.java) +A data structure for distributing encrypted shares of a secret among all participants in a way that only the intended participant can see part of the share. It includes auxiliary information used to validate its correctness and assemble an aggregate public key, i.e., a commitment to a secret share polynomial and a NIZK proof. +Besides their construction, another operation is to retrieve a deterministic byte representation of an instance and retrieve the corresponding instance back from a byte array representation. -###### `TssParticipantDirectory` +###### *`TssShareId`* -**Description**: This class holds all information about the participants in the scheme. Including: participants' EC public keys, public shares, private shares, number of shares. +A deterministic unique, contiguous starting from 1 identifier for each existent share. a) are unique per share, b) non-0, and c) can be used as input for the polynomial (They are from the same field of the selected curve) -**Link**: [TssParticipantDirectory.java](tss%2FTssParticipantDirectory.java) +###### *`TssPrivateShare`* -###### `TssMessage` +Represents a share owned by the executor of the scheme. Contains a secret value used for signing. It is also an PairingsPrivateKey. -**Description**: This class is used in the exchange of secret information between participants. Contains an encrypted message, a polynomial commitment, and a cryptographic proof that can be used to validate this message. +###### *`TssPublicShare`* -**Link**:[TssMessage.java](tss%2FTssMessage.java) +Represents a share in the system. It contains public information that can be used to validate each signature. It is also an PairingsPublicKey. -###### `TssShareId` +###### *`TssShareSignature`* -**Description**: This class represents the unique identification of a share in the system. It contains a FieldElement that can be used as input in the polynomial. +Represents a signature created from a TSSPrivateShare. -**Link**:[TssShareId.java](tss%2FTssShareId.java) +###### *`TssParticipantDirectory`* -###### `TssPrivateShare` +This class contains all the information about the participants in the scheme. This includes participants' `tssEncryptionPublicKeys`, total shares, and owned shares. -**Description**: A record that contains a share ID, and the corresponding private key. +###### *`TssService`* -**Link**:[TssPrivateShare.java](tss%2FTssPrivateShare.java) +A class that implements all the TSS operations allowing: -###### `TssPublicShare` +* Generate TSSMessages out of a random PrivateShare +* Generate TSSMessages out of a list of PrivateShares +* Verify TSSMessages out of a ParticipantDirectory +* Obtain PrivateShares out of TssMessages for each owned share +* Obtain PublicShares out of TssMessages for each share +* Aggregate PublicShares +* Sign Messages +* Verify Signatures +* Aggregate Signatures -**Description**: A record that contains a share ID, and the corresponding public key. +##### Usage -**Link**:[TssPublicShare.java](tss%2FTssPublicShare.java) +###### *Input* -###### `TssShareSignature` +* Participant's persistent `tssDecryptionPrivateKey` (Private to each participant) +* Number of participants (Public) +* Number of shares per participant (Public) +* A threshold value externally provided:`e.g: t = 5` +* All participants' `tssEncryptionPublicKey` +* A predefined `SignatureSchema` (Public / Constant for all the network) -**Description**: Represents a signature created from a TSSPrivateShare. +###### *0\. Bootstrap* -**Link**:[TssShareSignature.java](tss%2FTssShareSignature.java) +Given a participants directory, e.g: -##### Examples: +``` +P # tssEncryptionPublicKey +----------------------------- +P₁ 5 P₁tssEncryptionPublicKey +P₂ 2 P₂tssEncryptionPublicKey +P₃ 1 P₃tssEncryptionPublicKey +P₄ 2 P₄tssEncryptionPublicKey -###### 1. Bootstrapping +``` ```java +//Given: TssService service = new TssService(signatureScheme, new Random()); -PairingPrivateKey persistentParticipantKey = Requirements.loadECPrivateKey(); -List persistentPublicKeys = Requirements.loadECPublicKeys(); +PairingPrivateKey tssEncryptionPrivateKey = loadCurrentParticipantTssEncryptionPrivateKey(); +List tssEncryptionPublicKeys = loadAllParticipantsTssEncryptionPublicKeys(); + +//Then: TssParticipantDirectory participantDirectory = TssParticipantDirectory.createBuilder() - .self(/*Identification, different for each participant*/ 0, persistentParticipantKey) - .withParticipant(/*Identification:*/0, /*Number of Shares*/5, persistentPublicKeys.get(0)) - .withParticipant(/*Identification:*/1, /*Number of Shares*/2, persistentPublicKeys.get(1)) - .withParticipant(/*Identification:*/2, /*Number of Shares*/1, persistentPublicKeys.get(2)) - .withParticipant(/*Identification:*/3, /*Number of Shares*/1, persistentPublicKeys.get(3)) + .self(/*Identification, different for each participant*/ 0, tssEncryptionPrivateKey) + .withParticipant(/*Identification:*/0, /*Number of Shares*/5, tssEncryptionPublicKeys.get(0)) + .withParticipant(/*Identification:*/1, /*Number of Shares*/2, tssEncryptionPublicKeys.get(1)) + .withParticipant(/*Identification:*/2, /*Number of Shares*/1, tssEncryptionPublicKeys.get(2)) + .withParticipant(/*Identification:*/3, /*Number of Shares*/1, tssEncryptionPublicKeys.get(3)) .withThreshold(5) .build(signatureScheme); + +//After: //One can then query the directory int n = participantDirectory.getTotalNumberOfShares(); List privateShares = participantDirectory.getOwnedSharesIds(); List shareIds = participantDirectory.getShareIds(); ``` -###### 1. Create TssMessage +Under the hood, a `shareId`: `sid` is generated for each share. And an ownership map is maintained inside the directory: `ShareId`\-\>`Participant`, e.g: + +``` +sid₁ sid₂ sid₃ sid₄ sid₅ sid₆ sid₇ sid₈ sid₉ sid₁₀ +P₁ P₁ P₁ P₁ P₁ P₂ P₂ P₃ P₄ P₄ +``` + +###### *1\. Create TssMessage* ```java //Creates a TssMessage out of a randomly generated share TssMessage message = service.generateTssMessage(participantDirectory); ``` -###### 2. Validation of TssMessage +**Implementation details** Internally, the process of creating a TssMessage consists of +![img.svg](img.svg) + +a. Generation of the shares: In this operation for the bootstrap process, a random `TssPrivateShare` `k` is created. It is the same process as creating a random PairingsPrivateKey. + +b. After that, the key is split into `n` (n=total number of shares) values `Xₛ` by evaluating a polynomial Xₖ at each `ShareId`: `sidᵢ` in the ownership map. The polynomial `Xₖ` is a polynomial with degree `t-1` (t=threshold) with the form: `Xₖ = k + a₁x + … + aₜ₋₁xᵗ⁻¹`\ +[ having: `a₁,…,aₜ₋₁`: random coefficients from `SignatureScheme.publicKeyGroup` and `k`'s EC field element. +x is a field element, thus allowing the polynomial to be evaluated for each share id\] Each `sᵢ = Xₖ(sidᵢ)` constitutes a point on the polynomial. + +c. Once the `sᵢ` value has been calculated for each `ShareId`: `sidᵢ`, the value: `Cᵢ` will be produced by encrypting the `sᵢ` using the `sidᵢ` owner's `tssEncryptionPublicKey`. +The TssMessage will contain all the encrypted values for all shares. + +d. Generation of the Polynomial Commitment: We include a Feldman commitment to the polynomial to detect forms of bad dealing. +For each coefficient in the polynomial `Xₖ` `a₍ₒ₎` to `a₍ₜ₋₁₎`, compute a commitment value by calculating: `gᵢ * aᵢ` (g multiplied by polynomial coefficient `a₍ᵢ₎` ) + +e. Generation of the NIZKs proofs: Generate a NIZKs proof that these commitments and the encrypted shares correspond to a valid secret sharing according to the polynomial. + +###### *Acting as dealers of `TssMessage`s (outside the scope of the library)* + +Using an established channel, each participant will broadcast a single message to be received by all participants while waiting to receive other participants' messages. This functionality is critical for the protocol to work but needs to be handled outside the library. Each participant will validate the received message against the commitment and the NIZK proof. +Invalid messages need to be discarded. + +###### *Note about serialization / deserialization of `TssMessage`s* + +`TssMessage`s need to be serialized to bytes in a deterministic manner. We will include a protobuf serialization mechanism hidden in the library that will allow instances of `TssMessage` to create a protobuf representation of themselves and serialize to byte arrays, additionally it will allow to construct instances from the byte array representation back to a valid instance. + +###### *2\. Validation of TssMessage* ```java static { - List messages = Requirements.receiveTssMessages(); + //given + List messages = List.of(someTssMessage, someOtherTssMessage); + //then for(TssMessage m : messages){ - service.verifyTssMessage(participantDirectory, m); + if(!service.verifyTssMessage(participantDirectory, m)) + throw new SomeException("There are non valid tssMessages"); } } ``` -###### 3. Processing of TssMessage +The validation is produced over the content of the message and does not include the sender's identity, which is assumed to be provided by the external channel. Each message can be validated against the commitment and the proof by: -```java - Set agreedValidMessages = /*Some previously agreed upon same set of valid messages for all participants*/; - //Get Private Shares - List privateShares = service.decryptPrivateShares(participantDirectory, agreedValidMessages); - //Get Public Shares - List publicShares = service.computePublicShare(participantDirectory, agreedValidMessages); +* Checking that the encrypted shares correspond to the commitments. +* Commitments are consistent with the public values and the generated proof. +###### *3\. Generating Participant's Private Shares & Ledger ID* + +```java +// Given some previously agreed upon same set of valid messages for all participants +Set agreedValidMessages = Set.of(someTssMessage, someOtherTssMessage, etc); +//Get Private Shares +List privateShares = service.decryptPrivateShares(participantDirectory, agreedValidMessages); +//Get Public Shares +List publicShares = service.computePublicShare(participantDirectory, agreedValidMessages); ``` -###### Sign +Given the Participant's `TssEncryptionPrivateKey` and precisely `t` number of validated messages (t=threshold), The participant will decrypt all `Cᵢ` to generate an aggregated value `sᵢ` that will become a `SecretShare(sidᵢ, sᵢ)` for each `ShareId`: `sidᵢ` owned. -```java - byte[] message = Requirements.messageToSign(); - List signatures = service.sign(privateShares, message); +**Note:** All participants must choose the same threshold number of valid `TssMessages`. + +![img_1.svg](img_1.svg) + +Also, we will extract a `PublicShare` for each `ShareId`: `sidᵢ` in the directory from the list of valid messages. The PublicShare for share `s` is computed by evaluating each polynomial commitment in the common set of messages at `sidᵢ` and then aggregating the results. +###### *4\. Sign and aggregate/validate signatures* + +At this point, the participant executing the scheme can start signing, sharing signatures, and validating individual signatures produced by other parties in the scheme. Using each `privateShares` owned by the participant, a message can be signed, producing a `TssShareSignature.` +a. Perform signing: + +```java +//Given +byte[] message = getMessageToSign(); +//Then +List signatures = service.sign(privateShares, message); ``` -###### Aggregate Signatures +Again, it is outside the scope of this library to distribute or collect other participants' signatures. + +Multiple `TssShareSignature` can be aggregated to create an aggregate `PairingSignature`. An aggregate `PairingSignature` can be validated against the LedgerId (public key) if `t` (t=threshold) valid signatures are aggregated. If the threshold is met and the signature is valid, the library will respond with true; if not, it will respond with false. ```java static { - List signatures = Requirements.loadSignatures(); + //Given + List signatures = receiveParticipantsSignatures(); - //Validation of individual signatures + //Then: validation of individual signatures List validSignatures = new ArrayList<>(signatures.size()); for (TssShareSignature signature : signatures) { if (service.verifySignature(participantDirectory, publicShares, signature)) { @@ -736,194 +612,131 @@ static { } ``` -#### Hedera Common Native Support - -##### Overview - -This library provides classes that assist other modules to load native libraries packaged in dependency jars. - -##### Constraints - -This module will not depend on hedera-services artifacts, so it cannot include logging, metrics, configuration, or any other helper module from that repo. - -##### Public API - -###### `OperatingSystem` - -**Description**: An enum class listing all supported OS. +###### *5\. Rekey Stage* -**Link**: [OperatingSystem.java](native%2FOperatingSystem.java) - -###### `Architecture` - -**Description**: An enum class listing all supported architectures. - -**Link**: [Architecture.java](native%2FArchitecture.java) - -###### `LibraryLoader` - -**Description**: Helper class that will load a library for the current O.S/Architecture - -##### Example +The rekey process should be executed each time some of the scheme’s parameters change, such as the number of participants or the number of shares assigned to each participant. This rekeying process may also be conducted for reasons other than parameter changes, such as rotating shares. ```java -static { - try { - LibraryLoader.load("libhedera_bls_jni"); - } catch (IOException e){ - //Handle library loading problems - } -} -``` - -##### Implementation details +//Given +List privateShares = List.of(firstOwnedPrivateShare, secondOwnedPrivateShare, etc); +PairingPrivateKey tssEncryptionPrivateKey = loadCurrentParticipantTssEncryptionPrivateKey(); +List tssEncryptionPublicKeys = loadAllParticipantsTssEncryptionPublicKeys(); +TssParticipantDirectory oldParticipantDirectory = getPreviousParticipantDirectory(); +int newThreshold = someNewThreshold; + +//And: +TssParticipantDirectory newParticipantDirectory = + TssParticipantDirectory.createBuilder() + .self(/*Identification, different for each participant*/ 0, tssEncryptionPrivateKey) + .withParticipant(/*Identification:*/0, /*Number of Shares*/3, tssEncryptionPublicKeys.get(0)) + .withParticipant(/*Identification:*/1, /*Number of Shares*/2, tssEncryptionPublicKeys.get(1)) + .withParticipant(/*Identification:*/2, /*Number of Shares*/1, tssEncryptionPublicKeys.get(2)) + .withParticipant(/*Identification:*/3, /*Number of Shares*/1, tssEncryptionPublicKeys.get(3)) + .withParticipant(/*Identification:*/4, /*Number of Shares*/3, tssEncryptionPublicKeys.get(3)) + .withParticipant(/*Identification:*/5, /*Number of Shares*/2, tssEncryptionPublicKeys.get(3)) + .withThreshold(newThreshold) + .withPreviousThreshold(oldParticipantDirectory.threshold()) + .build(newThreshold); -Loading a library for a system architecture where no description is provided will result in a runtime exception. +//Creates a TssMessage out of a randomly generated share +TssMessage message = service.generateTssMessages(participantDirectory, privateShares); +``` -Under the hood, LibraryLoader will make use of a class similar to: -https://github.com/hashgraph/full-stack-testing/blob/c3fd5602525145be132770116f5bb5a1a1922dea/fullstack-core/fullstack-base-api/src/main/java/com/hedera/fullstack/base/api/resource/ResourceLoader.java +The rekeying process is similar to bootstrap. However, given that the goal is to produce a new set of keys that can still produce signatures that once aggregated, can be validated with the already established PublicKey, it starts with the list of `privateShares` owned. The main difference with the genesis stage is that every participant generates a `TssMessage` from each previously owned `PrivateShare`. -#### Hedera Cryptography alt-bn128 +![img_4.svg](img_4.svg) -##### Overview +Once finished, the list of `public/private shares` will be updated, but the previously generated aggregate public key will remain the same. -Implementation module of the parings API for alt-bn128 pairings friendly curve. -The underlying cryptography primitives will be provided by `arkwrorks` accessed through custom Rust code and Java JNI interface. +##### Security Considerations -###### Code Organization Structure: +If an adversary participant knows fewer than a threshold number of decryption keys, they cannot recover enough information to start forging threshold signatures. Adversarial participants may learn the shares of the participants whose keys they have compromised, but more is needed to recover the secret. Adversarial parties might choose low or zero entropy values for protocol inputs such as shares; however, assuring the right threshold value and share numbers per participant will still guarantee sufficient entropy from honest nodes to maintain security in the overall protocol. -``` -hedera-cryptography-altbn128 - ├── main - │ ├── java - │ │ └── ** - │ └── rust - │ │ └── ** - └── test - └── java - └── ** -``` +#### Hedera Common Native Support -###### Generated resources folder structure +##### Overview -Rust code will be compiled first and the build process will create the following folder structure where binaries files will be placed and then distributed. -They will be arranged by platform identifier, as returned by `System.getProperty("os.name")` and `System.getProperty("os.arch")`. +This library provides classes that assist other modules to load native libraries packaged in dependency jars. -``` -resources/software - ├── LIBRARY_VERSION (Contains the version number or information for the original native library) - ├── darwin - │ ├── amd64 - │ │ ├── native_lib.dylib ** - │ │ └── jni_bindings.dylib - │ └── arm64 - │ │ ├── native_lib.dylib ** - │ │ └── jni_bindings.dylib - ├── linux - │ ├── amd64 - │ │ ├── native_lib.so ** - │ │ └── jni_bindings.so - │ ├── arm - │ │ ├── native_lib.so ** - │ │ └── jni_bindings.so - │ └── arm64 - │ │ ├── native_lib.so ** - │ │ └── jni_bindings.so - └── windows - └── amd64 - ├── native_lib.dll ** - └── jni_bindings.dll +##### Constraints -** NOTE: native_lib is only required if not statically linked to the native JNI binding library -``` +This module will not depend on hedera-services artifacts, so it cannot include logging, metrics, configuration, or any other helper module from that repo. -## Test Plan +##### Public API -Since cryptographic code is often difficult to test due to its complexity and the lack of a test oracle, -we should design our test cases based on the cryptographic properties that these implementations should satisfy. +###### *`OperatingSystem`* -**Properties**: +An enum class listing all supported OS. -Protection of private keys. Our scheme protects the secrecy of the group’s and members’ private keys; otherwise, private keys can generate only one group signature. +###### *`Architecture`* -Unforgeability of group signature. Our scheme ensures that (a) a subset containing t, or more than t, members can work -together to generate a valid group signature, and (b) fewer than t members cannot generate a valid group signature. +An enum class listing all supported architectures. -Fixed length of threshold signature. Our scheme ensures that the size of a threshold signature is fixed (i.e., not depending on the number of signers). +###### *`NativeLibrary`* -Efficiency of verification. Verifying a group signature is based on the group’s public key and may be computed efficiently in an EVM context. +Helper class that will load a library for the current O.S/Architecture -* Generation of Private and Public Key Pairs: Tests the generation of key pairs for correctness by having the implementation under test produce 10 key - pairs. The private key provided is used to compute the public key, Q’. The calculated value Q’ is then compared to the supplied public key, Q. -* Public Key Validation: Generate 12 key pairs for each supported curve, modify some public keys to introduce errors, and verify that we can detect those errors. -* Signature Generation: Ten preconfigured known messages per curve supported are supplied. Generate a signature for each message. The message, public key, and signature components are returned. - The signatures are compared against previous known signatures. -* Signature Verification: For each curve supported, 15 pseudorandom messages, a public key, Q, and a signature component (r, s) are supplied. Modify some of the provided values and verify that signature verification fails. -* Perform a Signature verification via EVM code of a signature produced with the library. -* Some ideas: -* [Cryptographic-Algorithm-Validation-Program](https://csrc.nist.gov/CSRC/media//Projects/Cryptographic-Algorithm-Validation-Program/documents/dss/ECDSAVS.pdf): -* https://csrc.nist.gov/CSRC/media/Events/lightweight-cryptography-workshop-2019/documents/papers/systematic-testing-of-lightweight-crypto-lwc2019.pdf +###### *`SingletonLoader`* -The proposal is to move forward with the implementation while creating a work group to discuss and collect ideas for testing and validating these features. +Helper class that will load a library for the current O.S/Architecture -### Unit Tests +## Test Plan -One of the components that can be unit-tested is native support. SPI loading and failing. JNI wrapping. -Limitations: nativesupport-library tests must be executed in different environments to provide comprehensive code validation. +Refer to: [TSS-Library-Test-Plan.md](./TSS-Library-Test-Plan.md) ### Performance Testing -JMH benchmarks should be provided for signature generation and aggregation. -TssMessage validation, and public key aggregation. +JMH benchmarks should be provided for signature generation and aggregation. TssMessage validation, and public key aggregation. -We should know that variance (min, max, average, median) time it takes to do the following: +We should know the variance (min, max, average, median) time it takes to do the following: -Genesis for a 4, 10, 20, 40, and 100 participants. -Rekeying for a 4, 10, 20, 40, and 100 participants. -Shares Amounts distributed randomly: 200, 500, 1000, 2000, 5000 -we need to test each configuration 100 times to get confident mins, maxes, and a good average and median +Genesis for 4, 10, 20, 40, and 100 participants. Rekeying for 4, 10, 20, 40, and 100 participants. Shares Amounts distributed randomly: 200, 500, 1000, 2000, 5000 we need to test each configuration 100 times to get confident mins, maxes, and a good average and median ## Security Audit -After this proposal is accepted we will invite security team to define the necessary steps for auditing the code. +After this proposal is accepted, we will invite the security team to define the necessary steps for auditing the code. ## Alternatives Considered -- As mentioned before, Java Foreign Function Interface is still immature so it was not considered for this proposal. -- Using JCA through Bouncy Castle implementation was analyzed. BC provides support for EC-Curves, but it does not support Pairings which are required for BLS signatures. - [beacon-chain](https://github.com/harmony-dev/beacon-chain-java/tree/master) implemented the support using bouncy castle + [Milagro](https://incubator.apache.org/projects/milagro.html) - but Milagro project is reported to have little coverage, not audited. Milagro podling has been retired on 2024-03-21. +- As mentioned before, Java Foreign Function Interface is still immature, so it was not considered for this proposal. +- JRA was discarded, given the possibility of eventually moving to JFF. +- Using JSA through Bouncy Castle implementation was analyzed. BC provides support for EC-Curves, but it does not support Pairings which are required for BLS signatures. [beacon-chain](https://github.com/harmony-dev/beacon-chain-java/tree/master) implemented the support using bouncy castle \+ [Milagro](https://incubator.apache.org/projects/milagro.html), but Milagro project is reported to have little coverage and is not audited. Milagro podling has been retired on 2024-03-21. -## Implementation and Delivery Plan by stages +## Delivery Plan by Stages **Stage 1** -* Preconditions: + +Preconditions: + 1. hedera-cryptography repository. 2. Gradle multilanguage module with rust compilation plugin. 3. CI/CD pipelines for building artifacts in hedera-cryptography. + * Define the Test plan. * Define a security plan. -* Implementation of nativesupport library. +* Implementation of native-support library. * Implementation of Pairings API using JNI, arkworks, and alt-bn128. * Implementation of Pairings Signatures library. +* Implementation of EC-key utility. * Implementation of TSS library public interface (TBD: include mock implementation). * Execute Test Plan and validation. -* Execute Security Audits. -* Implementation of EC-key utility. **Stage 2** -* Preconditions: -1. CI/CD pipelines to reference built artifacts in hedera-cryptography from hedera-services. -2. Implementation of the public interface for the TSS library. + +Preconditions: hedera-cryptography CI/CD publishes artifacts and they can be referenced from hedera-services. + +Implementation of the public interface for the TSS library. + * Implementation of TSS library. +* Performance tests and optimizations. * Execute Test Plan and validation. * Execute Security Audits. ## External References -- https://eprint.iacr.org/2021/339 -- https://crypto.stanford.edu/pbc/notes/elliptic -- https://andrea.corbellini.name/2015/05/17/elliptic-curve-cryptography-a-gentle-introduction/ -- https://www.iacr.org/archive/asiacrypt2001/22480516.pdf -- https://hackmd.io/@benjaminion/bls12-381#Motivation -- https://www.johannes-bauer.com/compsci/ecc/ +- [https://eprint.iacr.org/2021/339](https://eprint.iacr.org/2021/339) +- [https://crypto.stanford.edu/pbc/notes/elliptic](https://crypto.stanford.edu/pbc/notes/elliptic) +- [https://andrea.corbellini.name/2015/05/17/elliptic-curve-cryptography-a-gentle-introduction/](https://andrea.corbellini.name/2015/05/17/elliptic-curve-cryptography-a-gentle-introduction/) +- [https://www.iacr.org/archive/asiacrypt2001/22480516.pdf](https://www.iacr.org/archive/asiacrypt2001/22480516.pdf) +- [https://hackmd.io/@benjaminion/bls12-381\#Motivation](https://hackmd.io/@benjaminion/bls12-381#Motivation) +- [https://www.johannes-bauer.com/compsci/ecc/](https://www.johannes-bauer.com/compsci/ecc/) diff --git a/platform-sdk/docs/proposals/TSS-Library/img.png b/platform-sdk/docs/proposals/TSS-Library/img.png new file mode 100644 index 000000000000..210fb8a64eaa Binary files /dev/null and b/platform-sdk/docs/proposals/TSS-Library/img.png differ diff --git a/platform-sdk/docs/proposals/TSS-Library/img_2.svg b/platform-sdk/docs/proposals/TSS-Library/img_2.svg deleted file mode 100644 index cdf78324491f..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/img_2.svg +++ /dev/null @@ -1 +0,0 @@ -isGenesisProtocolYESNOGenesisGenerate TssMessage out of Random ShareDistribute TssMessageRekeying (for each Private Share)Generate TSSMessages out of Private ShareDistribute TssMessageCollect TssMessagesValidate TssMessagestime < definedDuration && #messages < thresholdGenerate Private SharesGenerate Public Sharesfor each ProprietaryShareSign MessageDistribute SignatureCollect SignaturesValidate Signatures#signatures < thresholdAggregate SignaturesPersist Signature \ No newline at end of file diff --git a/platform-sdk/docs/proposals/TSS-Library/img_7.svg b/platform-sdk/docs/proposals/TSS-Library/img_7.svg new file mode 100644 index 000000000000..a2c76f18a29f --- /dev/null +++ b/platform-sdk/docs/proposals/TSS-Library/img_7.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/platform-sdk/docs/proposals/TSS-Library/img_8.svg b/platform-sdk/docs/proposals/TSS-Library/img_8.svg new file mode 100644 index 000000000000..c0e8c8baa22f --- /dev/null +++ b/platform-sdk/docs/proposals/TSS-Library/img_8.svg @@ -0,0 +1,4 @@ + + + +
Scalar Value (Bytes 1- 33)
FieldElement as returned by arkworks
Byte 0
Schema
Representation
b1-b7
CurveID
1
2
3
4
5
6
33
32
31
30
0 = Short Signatures
1 = Short public keys 
...
bytes 1 to 33 (Little Endian Scalar)
\ No newline at end of file diff --git a/platform-sdk/docs/proposals/TSS-Library/img_9.svg b/platform-sdk/docs/proposals/TSS-Library/img_9.svg new file mode 100644 index 000000000000..c277b55775e3 --- /dev/null +++ b/platform-sdk/docs/proposals/TSS-Library/img_9.svg @@ -0,0 +1,4 @@ + + + +
Element (Bytes 1- 65 / 129)
GroupElement as returned by arkworks
Byte 0
Schema
Representation
b1-b7
CurveID
1
2
3
32/64
33/65
65/129
64/128
63/127
62/126
0 = Short Signatures
1 = Short public keys 
...
if the first bit is 0 and this is a signature or 1 and this is a public key
bytes 1 to 33 (Little Endian X coordinate)
bytes 33 to 65 (Little Endian Y coordinate)
if the first bit is 0 and this is a public key or 1 and this is a signature
bytes 1 to 65 (Little Endian X coordinate)
bytes 33 to 65 (Little Endian Y coordinate)
...
\ No newline at end of file diff --git a/platform-sdk/docs/proposals/TSS-Library/native/Architecture.java b/platform-sdk/docs/proposals/TSS-Library/native/Architecture.java deleted file mode 100644 index 8ba70ab33e4c..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/native/Architecture.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - -package com.hedera.common.nativesupport; - -public enum Architecture { - AMD64, - ARM64, - I386; - - public String directoryName() { - return this.name().toLowerCase(); - } - - /** - * Attempts to determine the current architecture based on the system property "os.arch". - * - * @return the current architecture. - * @throws IllegalStateException if the current architecture is not supported. - */ - public static Architecture current() { - final String osArch = System.getProperty("os.arch").toLowerCase(); - if (osArch.contains("amd64") || osArch.contains("x86_64")) { - return AMD64; - } else if (osArch.contains("arm64") || osArch.contains("aarch64")) { - return ARM64; - } else if (osArch.contains("i386")) { - return I386; - } else { - throw new IllegalStateException("Unsupported architecture: " + osArch); - } - } -} \ No newline at end of file diff --git a/platform-sdk/docs/proposals/TSS-Library/native/OperatingSystem.java b/platform-sdk/docs/proposals/TSS-Library/native/OperatingSystem.java deleted file mode 100644 index b4747aa772ee..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/native/OperatingSystem.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - -package com.hedera.common.nativesupport; - -public enum OperatingSystem { - WINDOWS, - LINUX, - DARWIN; - - public String directoryName() { - return this.name().toLowerCase(); - } - - /** - * Attempts to determine the current operating system based on the system property "os.name". - * - * @return the current operating system. - * @throws IllegalStateException if the current operating system is not supported. - */ - public static OperatingSystem current() { - final String osName = System.getProperty("os.name").toLowerCase(); - if (osName.contains("win")) { - return WINDOWS; - } else if (osName.contains("nix") || osName.contains("nux") ) { - return LINUX; - } else if (osName.contains("mac")) { - return DARWIN; - } else { - throw new IllegalStateException("Unsupported operating system: " + osName); - } - } -} \ No newline at end of file diff --git a/platform-sdk/docs/proposals/TSS-Library/pairings-api/BilinearPairing.java b/platform-sdk/docs/proposals/TSS-Library/pairings-api/BilinearPairing.java deleted file mode 100644 index 8d12bf2c56de..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/pairings-api/BilinearPairing.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package com.hedera.cryptography.pairings.api; - -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Represents a bilinear pairing operation used in cryptographic protocols. - * - *

A bilinear pairing is a function that takes two elements from two groups and maps them to an element - * in a third group, satisfying certain properties that are useful in various cryptographic schemes - * such as identity-based encryption, short signatures, and more.

- * - *

This class provides access to each of the groups (G₁, G₂) for a specific Pairing and the FiniteField associated - * with the curves.

- *

- * A pairing is a map: e : G₁ × G₂ -> Gₜ which can satisfy these properties: - *

    - *
  • Bilinearity: “a”, “b” member of “Fq” (Finite Field), “P” member of “G₁”, and “Q” member of “G₂”, - * then e(a×P, b×Q) = e(ab×P, Q) = e(P, ab×Q) = e(P, Q)^(ab) - *
  • Non-degeneracy: e != 1 - *
  • Computability: There should be an efficient way to compute “e”. - *
- * - * @see Group - * @see Field - */ -public interface BilinearPairing { - - /** - * Returns the finite field “Fq” associated with the curves of G₁ and G₂. - * - * @return the field - */ - @NonNull - Field getField(); - - /** - * Returns the G₁ group associated with the pairing. - * - * @return the G₁ group - */ - @NonNull - Group getGroup1(); - - /** - * Returns the G₂ group associated with the pairing. - * - * @return the G₂ group - */ - @NonNull - Group getGroup2(); - - /** - * Returns G₁ if input is G₂, and vice versa. - * - * @param group the group to get the "other group" of - * @return the other group - */ - @NonNull - Group getOtherGroup(@NonNull Group group); - - /** - * Returns a pairing between elements from G₁ and G₂ - *

- * The order of the elements is not important, element1 can be from G₁ and element2 from G₂, or vice versa. - * - * @param element1 one element of the pairing - * @param element2 the other element of the pairing - * @return the PairingResult - */ - @NonNull - PairingResult pairingBetween(@NonNull GroupElement element1, @NonNull GroupElement element2); - - /** - * Compares two pairing results. - *

- * This default implementation uses {@link #comparePairings} under the hood, with the assumption that the actual - * pairing computation was not performed upon construction of the {@link PairingResult} objects. If an - * implementation of {@link PairingResult} is used that actually does perform the pairing computation upon - * construction, this method should be overridden to compare the results directly. - * - * @param result1 the first pairing result - * @param result2 the second pairing result - * @return true if the pairings are equal, otherwise false - */ - default boolean comparePairingResults(@NonNull final PairingResult result1, @NonNull final PairingResult result2) { - return comparePairings( - result1.getInputElement1(), - result1.getInputElement2(), - result2.getInputElement1(), - result2.getInputElement2()); - } - - /** - * Compares two pairings, referred to as A and B. - *

- * The 2 elements of each pairing must be in opposite groups. - *

- * The order of the elements in each pairing is not important. - * - * @param pairingAElement1 the first element of the first pairing - * @param pairingAElement2 the second element of the first pairing - * @param pairingBElement1 the first element of the second pairing - * @param pairingBElement2 the second element of the second pairing - * @return true if the pairings are equal, otherwise false - */ - boolean comparePairings( - @NonNull GroupElement pairingAElement1, - @NonNull GroupElement pairingAElement2, - @NonNull GroupElement pairingBElement1, - @NonNull GroupElement pairingBElement2); -} diff --git a/platform-sdk/docs/proposals/TSS-Library/pairings-api/BilinearPairingProvider.java b/platform-sdk/docs/proposals/TSS-Library/pairings-api/BilinearPairingProvider.java deleted file mode 100644 index ead2d7d00e38..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/pairings-api/BilinearPairingProvider.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package com.hedera.cryptography.pairings.spi; - -import com.hedera.cryptography.pairings.api.BilinearPairing; -import com.hedera.cryptography.pairings.api.Curve; - -/** - * A provider to facilitate fetching of a {@link BilinearPairing} instance - */ -public abstract class BilinearPairingProvider { - - /** - * Atomic boolean so that we don't repeatedly attempt to reload the resource. - */ - private static final AtomicBoolean initialized = new AtomicBoolean(false); - - - /** - * Gets a byte representing which curve is implemented by the provided Bilinear Pairing object - * - * @return a string representing the algorithm - */ - public abstract Curve curve(); - - /** - * Returns a static instance of the bilinear map - * - * @return the bilinear map instance - */ - public abstract BilinearPairing pairing(); - - /** - * Implementations should include here all the stpes necessary to load the library, e.g., - * perform native library loads. - */ - public abstract void doInit() throws IOException; - - /** - * Performs the initialization steps of the library. - */ - public BilinearPairingProvider init() throws IOException{ - if (!initialized.getAndSet(true)) { - loader.reload(); - } - return this; - } - - -} diff --git a/platform-sdk/docs/proposals/TSS-Library/pairings-api/BilinearPairingService.java b/platform-sdk/docs/proposals/TSS-Library/pairings-api/BilinearPairingService.java deleted file mode 100644 index 584dd9f70696..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/pairings-api/BilinearPairingService.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.pairings.spi; - -import com.hedera.cryptography.pairings.api.BilinearPairing; -import com.hedera.cryptography.pairings.api.Curve; -import java.util.NoSuchElementException; -import java.util.ServiceLoader; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Loader for accessing an implementation of the {@link BilinearPairingProvider} SPI. - */ -public final class BilinearPairingService { - - /** - * Atomic boolean so that we don't repeatedly attempt to reload the resource. - */ - private static final AtomicBoolean initialized = new AtomicBoolean(false); - - /** - * Private Constructor since this class should not be instantiated. - */ - private BilinearPairingService() {} - - /** - * Obtain an instance of a {@link BilinearPairing} implementation which provides the given algorithm name/identifier. - * - * @param algorithm the algorithm name for which to locate an implementation. - * @return an instance of the {@link BilinearPairing} implementing the requested {@code algorithm} which was found via - * the {@link ServiceLoader} scan. - */ - public static BilinearPairing instanceOf(final Curve algorithm) { - return findInstance(algorithm).init().pairing(); - } - - /** - * Forces a service loader refresh and causes the {@link ServiceLoader} implementation to evict the internal cache - * and lazily rescan. - */ - public static void refresh() { - loader.reload(); - } - - /** - * Locates a {@link BilinearPairingProvider} instance based on the search criteria provided. - * - * @param algorithm the algorithm value for which to locate an implementation. - * @throws NullPointerException if the {@code algorithm} argument is a {@code null} reference. - * @throws IllegalArgumentException if the {@code algorithm} argument is a blank or empty string. - * @throws NoSuchElementException if no {@link BilinearPairing} implementation was found via the {@link ServiceLoader} - * mechanism. - */ - private static BilinearPairingProvider findInstance(final Curve algorithm) { - - final ServiceLoader serviceLoader = - ServiceLoader.load(BilinearPairingProvider.class, classloader); - - final Iterator iterator = serviceLoader.iterator(); - - while (iterator.hasNext()) { - final BilinearPairingProvider provider : iterator.next(); - if (algorithm == provider.curve()) { - return provider; - } - } - - throw new NoSuchElementException("The requested algorithm was not found."); - } -} diff --git a/platform-sdk/docs/proposals/TSS-Library/pairings-api/Curve.java b/platform-sdk/docs/proposals/TSS-Library/pairings-api/Curve.java deleted file mode 100644 index 3713b35986ab..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/pairings-api/Curve.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.pairings.api; - -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * An enumeration of supported pairing curves. - */ -public enum Curve { - ALT_BN128, - BLS12_381; -} diff --git a/platform-sdk/docs/proposals/TSS-Library/pairings-api/Field.java b/platform-sdk/docs/proposals/TSS-Library/pairings-api/Field.java deleted file mode 100644 index 9051256c7755..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/pairings-api/Field.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package com.hedera.cryptography.pairings.api; - -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Random; - -/** - * Represents a finite field used in a pairing-based cryptography scheme. - *

A finite field, often denoted as 𝔽q, where (q) is a prime power, is a field with a finite number of elements.

- * - *

This is a factory interface, responsible for creating {@link FieldElement} which are scalars belonging to the - * field represented by this instance. - *

- * - * @see FieldElement - */ -public interface Field { - /** - * Creates a random field element - * - * @param random the source of randomness - * @return the random field element - */ - @NonNull - default FieldElement randomElement(@NonNull final Random random) { - final byte[] seed = new byte[getSeedSize()]; - random.nextBytes(seed); - - return randomElement(seed); - } - - /** - * Creates a new field element from a long - * - * @param inputLong the long to use to create the field element - * @return the new field element - */ - @NonNull - FieldElement elementFromLong(long inputLong); - - /** - * Creates a field element from a seed - * - * @param seed a seed to use to generate randomness - * @return the new field element - */ - @NonNull - FieldElement randomElement(@NonNull byte[] seed); - - /** - * Creates a field element from its serialized encoding - * - * @param bytes serialized form - * @return the new field element - */ - @NonNull - FieldElement elementFromBytes(@NonNull byte[] bytes); - - /** - * Gets the size in bytes of an element - * - * @return the size of an element - */ - int getElementSize(); - - /** - * Gets the size in bytes of the seed necessary to generate a new element - * - * @return the size of a seed needed to generate a new element - */ - int getSeedSize(); - - /** - * Get the pairing that this field is used in - * - * @return the pairing - */ - @NonNull - BilinearPairing getPairing(); -} diff --git a/platform-sdk/docs/proposals/TSS-Library/pairings-api/FieldElement.java b/platform-sdk/docs/proposals/TSS-Library/pairings-api/FieldElement.java deleted file mode 100644 index f1630707bae7..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/pairings-api/FieldElement.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package com.hedera.cryptography.pairings.api; - -import edu.umd.cs.findbugs.annotations.NonNull; -import java.math.BigInteger; - -/** - * An interface representing a generic field element - */ -public interface FieldElement { - /** - * Check if the field of another element is the same as this element's field - * - * @param otherElement the other element - * @return true if the fields are the same, otherwise false - */ - default boolean isSameField(@NonNull final FieldElement otherElement) { - return otherElement.getField().equals(getField()); - } - - /** - * Get the size of the field element - * - * @return the size of the field element - */ - default int size() { - return getField().getElementSize(); - } - - /** - * Returns the field the element is in - * - * @return the field - */ - @NonNull - Field getField(); - - /** - * Adds another field element to this one - * - * @param other the other field element - * @return a new field element which is the sum of this element and another - */ - @NonNull - FieldElement add(@NonNull FieldElement other); - - /** - * Subtracts another field element from this one - * - * @param other the other field element - * @return a new field element which is the difference of this element and another - */ - @NonNull - FieldElement subtract(@NonNull FieldElement other); - - /** - * Multiplies another field element with this one - * - * @param other the other field element - * @return a new field element which is the product of this element and another - */ - @NonNull - FieldElement multiply(@NonNull FieldElement other); - - /** - * Takes the field element to the power of an integer - * - * @param exponent the exponent integer - * @return a new field element which is the power - */ - @NonNull - FieldElement power(@NonNull BigInteger exponent); - - /** - * Returns the field element as a BigInteger - * - * @return the field element as a BigInteger - */ - @NonNull - BigInteger toBigInteger(); - - /** - * Returns the byte array representation of the field element - * - * @return the byte array representation of the field element - */ - @NonNull - byte[] toBytes(); -} diff --git a/platform-sdk/docs/proposals/TSS-Library/pairings-api/Group.java b/platform-sdk/docs/proposals/TSS-Library/pairings-api/Group.java deleted file mode 100644 index 3dded96c71c4..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/pairings-api/Group.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package com.hedera.cryptography.pairings.api; - -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Collection; - -/** - * Represents a mathematical group used in a pairing-based cryptography system. - * - *

A group in this context is a set of elements combined with an operation that satisfies - * the group properties: closure, associativity, identity, and invertibility. When the group - * also satisfies the commutativity property, it is referred to as an abelian group.

- * - *

This class provides methods to obtain elements belonging to the group represented by the instance. - * - * @see GroupElement - */ -public interface Group { - /** - * Returns the opposite group of this group - *

- * If this group is G₁, then the opposite group is G₂, and vice versa. - * - * @return the opposite group - */ - @NonNull - default Group getOppositeGroup() { - return getPairing().getOtherGroup(this); - } - - /** - * Returns the pairing associated with this group - * - * @return the pairing associated with this group - */ - @NonNull - BilinearPairing getPairing(); - - /** - * Returns the group's generator - * - * @return the group's generator - */ - @NonNull - GroupElement getGenerator(); - - /** - * Creates a new group element with value 0 - * - * @return the new group element - */ - @NonNull - GroupElement zeroElement(); - - /** - * Creates a group element from a seed - * - * @param seed the seed to generate the element from - * @return the new group element - */ - @NonNull - GroupElement randomElement(@NonNull byte[] seed); - - /** - * Hashes an unbounded length input to a group element - * - * @param input the input to be hashed - * @return the new group element - */ - @NonNull - GroupElement elementFromHash(@NonNull byte[] input); - - /** - * Adds a collection of group elements together - * - * @param elements the collection of elements to add together - * @return a new group element which is the sum the collection of elements - */ - @NonNull - GroupElement batchAdd(@NonNull Collection elements); - - /** - * Creates a group element from its serialized encoding - * - * @param bytes serialized form - * @return the new group element, or null if construction failed - */ - @NonNull - GroupElement elementFromBytes(@NonNull byte[] bytes); - - /** - * Gets the size in bytes of a compressed group element - * - * @return the size of a compressed group element - */ - int getCompressedSize(); - - /** - * Gets the size in bytes of an uncompressed group element - * - * @return the size of an uncompressed group element - */ - int getUncompressedSize(); - - /** - * Gets the size in bytes of the seed necessary to generate a new element - * - * @return the size of a seed needed to generate a new element - */ - int getSeedSize(); -} diff --git a/platform-sdk/docs/proposals/TSS-Library/pairings-api/GroupElement.java b/platform-sdk/docs/proposals/TSS-Library/pairings-api/GroupElement.java deleted file mode 100644 index 61a086a05d5e..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/pairings-api/GroupElement.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package com.hedera.cryptography.pairings.api; - -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Interface representing a cryptographic group element - */ -public interface GroupElement { - /** - * Check if the group of another element is the same as this element's group - * - * @param otherElement the other element - * @return true if the groups are the same, otherwise false - */ - default boolean isSameGroup(@NonNull final GroupElement otherElement) { - return otherElement.getGroup().equals(getGroup()); - } - - /** - * Check if the group of another element is the opposite of this element's group - * - * @param otherElement the other element - * @return true if the groups are the opposite, otherwise false - */ - default boolean isOppositeGroup(@NonNull final GroupElement otherElement) { - return getGroup().getOppositeGroup().equals(otherElement.getGroup()); - } - - /** - * Returns the size of the group element in bytes - * - * @return the size of the group element in bytes - */ - default int size() { - return isCompressed() ? getGroup().getCompressedSize() : getGroup().getUncompressedSize(); - } - - /** - * Returns the group of the element - * - * @return the element's group - */ - @NonNull - Group getGroup(); - - /** - * Multiplies the group element with a field element - * - * @param other the field element - * @return a new group element which is this group element multiplied by the field element - */ - @NonNull - GroupElement multiply(@NonNull FieldElement other); - - /** - * Adds this group element with another - * - * @param other the other group element - * @return a new group element which is the addition of this element and another - */ - @NonNull - GroupElement add(@NonNull GroupElement other); - - /** - * Compresses the group element - * - * @return this object, compressed - */ - @NonNull - GroupElement compress(); - - /** - * Gets whether the group element is compressed - * - * @return true if the element is compressed, otherwise false - */ - boolean isCompressed(); - - /** - * Returns a copy of the group element - * - * @return a copy of the group element - */ - @NonNull - GroupElement copy(); - - /** - * Returns the byte array representation of the group element - * - * @return the byte array representation of the group element - */ - @NonNull - byte[] toBytes(); -} diff --git a/platform-sdk/docs/proposals/TSS-Library/pairings-api/PairingResult.java b/platform-sdk/docs/proposals/TSS-Library/pairings-api/PairingResult.java deleted file mode 100644 index d74fb0ee3338..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/pairings-api/PairingResult.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package com.hedera.cryptography.pairings.api; - -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * This class represents the result of the pairing operation between two elements - */ -public interface PairingResult { - /** - * Get the first input element. This element is in the opposite group of the second input element - * - * @return the first input element - */ - @NonNull - GroupElement getInputElement1(); - - /** - * Get the second input element. This element is in the opposite group of the first input element - * - * @return the second input element - */ - @NonNull - GroupElement getInputElement2(); - - /** - * Get the bytes of the pairing result - *

- * If the implementation so wishes, it might throw an {@link UnsupportedOperationException}. Serializing the - * pairing result is an expensive operation, and may not be supported by all implementations. - * - * @return the bytes of the pairing result - */ - @NonNull - byte[] getPairingBytes(); -} diff --git a/platform-sdk/docs/proposals/TSS-Library/signature-lib/GroupAssignment.java b/platform-sdk/docs/proposals/TSS-Library/signature-lib/GroupAssignment.java deleted file mode 100644 index 8adf7250c743..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/signature-lib/GroupAssignment.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.signaturescheme.api; - -import com.hedera.cryptography.pairings.api.BilinearPairing; -import com.hedera.cryptography.pairings.api.Group; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.function.Function; - -/** - * An enum to clarify which group public keys and signatures are in, for a given - * {@link SignatureSchema SignatureSchema} - */ -public enum GroupAssignment { - /** - * The group for signatures is the first group in the pairing, and the group for public keys is the second group. - */ - GROUP1_FOR_SIGNING(BilinearPairing::getGroup1, BilinearPairing::getGroup2), - /** - * The group for signatures is the second group in the pairing, and the group for public keys is the first group. - */ - GROUP1_FOR_PUBLIC_KEY(BilinearPairing::getGroup2, BilinearPairing::getGroup1); - - private final Function signatureGroup; - private final Function publicKeyGroup; - - /** - * Constructor - * - * @param signatureGroup a function that takes a pairing and returns the group for signatures - * @param publicKeyGroup a function that takes a pairing and returns the group for public keys - */ - GroupAssignment( - final Function signatureGroup, - final Function publicKeyGroup) { - this.signatureGroup = signatureGroup; - this.publicKeyGroup = publicKeyGroup; - } - - /** - * Get the group for signatures for a given pairing - * - * @param pairing the pairing - * @return the group for signatures - */ - @NonNull - public Group getSignatureGroupFor(@NonNull final BilinearPairing pairing) { - return signatureGroup.apply(pairing); - } - - /** - * Get the group for public keys for a given pairing - * - * @param pairing the pairing - * @return the group for public keys - */ - @NonNull - public Group getPublicKeyGroupFor(@NonNull final BilinearPairing pairing) { - return publicKeyGroup.apply(pairing); - } -} diff --git a/platform-sdk/docs/proposals/TSS-Library/signature-lib/PairingPrivateKey.java b/platform-sdk/docs/proposals/TSS-Library/signature-lib/PairingPrivateKey.java deleted file mode 100644 index 372c5a106343..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/signature-lib/PairingPrivateKey.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.signaturescheme.api; - -import com.hedera.cryptography.pairings.api.Field; -import com.hedera.cryptography.pairings.api.FieldElement; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Random; - -/** - * A private key that can be used to sign a message. - * - * @param signatureSchema the signature schema - * @param secretElement the secret element - */ -public record PairingPrivateKey(@NonNull SignatureSchema signatureSchema, @NonNull FieldElement secretElement) { - /** - * Creates a private key out of the CurveType and a random - * - * @param type The implementing curve type - * @param random The environment secureRandom to use - * @return a privateKey for that CurveType - */ - @NonNull - public static PairingPrivateKey create(@NonNull final SignatureSchema type, @NonNull final Random random) { - final Field field = type.getField(); - return new PairingPrivateKey(type, field.randomElement(random)); - } - - /** - * Deserialize a private key from a byte array. - * - * @param bytes the serialized private key, with the first byte representing the curve type - * @return the deserialized private key - */ - public static PairingPrivateKey fromBytes(@NonNull final byte[] bytes) { - final SerializedSignatureSchemaObject schemaObject = SerializedSignatureSchemaObject.fromByteArray(bytes); - - return new PairingPrivateKey( - schemaObject.schema(), schemaObject.schema().getField().elementFromBytes(schemaObject.elementBytes())); - } - - /** - * Sign a message using the private key. - *

- * Signing: - * In order to sign a message “m”, the first step is to map it onto a point in the signature group. - * After this step, the resulting point in the signature group is referred to as “H(m)”. - *

- * The message is signed by computing the signature “σ = [sk]H(m)”, where “[sk]H(m)” represents multiplying the - * hash point by the private key. - * - * @param message the message to sign - * @return the signature, which will be in the group opposite to the group of the public key - */ - @NonNull - public PairingSignature sign(@NonNull final byte[] message) { - return new PairingSignature( - signatureSchema, - signatureSchema.getSignatureGroup().elementFromHash(message).multiply(secretElement)); - } - - /** - * Serialize the private key to a byte array. - *

- * The first byte of the serialized private key represents the curve type. - * - * @return the serialized private key - */ - @NonNull - public byte[] toBytes() { - final int elementSize = secretElement.size(); - final byte[] output = new byte[1 + elementSize]; - - output[0] = signatureSchema.getIdByte(); - System.arraycopy(secretElement.toBytes(), 0, output, 1, elementSize); - - return output; - } -} diff --git a/platform-sdk/docs/proposals/TSS-Library/signature-lib/PairingPublicKey.java b/platform-sdk/docs/proposals/TSS-Library/signature-lib/PairingPublicKey.java deleted file mode 100644 index c422037f7310..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/signature-lib/PairingPublicKey.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.signaturescheme.api; - -import com.hedera.cryptography.pairings.api.GroupElement; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * A public key that can be used to verify a signature. - * - * @param signatureSchema the signature schema - * @param keyElement the public key element - */ -public record PairingPublicKey(@NonNull SignatureSchema signatureSchema, @NonNull GroupElement keyElement) { - /** - * Create a public key from a private key. - * - * @param privateKey the private key - * @return the public key - */ - public static PairingPublicKey create(@NonNull final PairingPrivateKey privateKey) { - return new PairingPublicKey( - privateKey.signatureSchema(), - privateKey.signatureSchema().getPublicKeyGroup().getGenerator().multiply(privateKey.secretElement())); - } - - /** - * Deserialize a public key from a byte array. - * - * @param bytes the serialized public key, with the first byte representing the curve type - * @return the deserialized public key - */ - public static PairingPublicKey fromBytes(@NonNull final byte[] bytes) { - final SerializedSignatureSchemaObject schemaObject = SerializedSignatureSchemaObject.fromByteArray(bytes); - - return new PairingPublicKey( - schemaObject.schema(), - schemaObject.schema().getPublicKeyGroup().elementFromBytes(schemaObject.elementBytes())); - } - - /** - * Serialize the public key to a byte array. - *

- * The first byte of the serialized public key will represent the curve type. - * - * @return the serialized public key - */ - @NonNull - public byte[] toBytes() { - final int elementSize = keyElement.size(); - final byte[] output = new byte[1 + elementSize]; - - output[0] = signatureSchema.getIdByte(); - System.arraycopy(keyElement.toBytes(), 0, output, 1, elementSize); - - return output; - } -} diff --git a/platform-sdk/docs/proposals/TSS-Library/signature-lib/PairingSignature.java b/platform-sdk/docs/proposals/TSS-Library/signature-lib/PairingSignature.java deleted file mode 100644 index b4bfbfe08069..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/signature-lib/PairingSignature.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.signaturescheme.api; - -import com.hedera.cryptography.pairings.api.BilinearPairing; -import com.hedera.cryptography.pairings.api.Group; -import com.hedera.cryptography.pairings.api.GroupElement; -import com.hedera.cryptography.pairings.api.PairingResult; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * A signature that has been produced by a {@link PairingPrivateKey}. - * - * @param signatureSchema the schema of the signature - * @param signatureElement the signature element - */ -public record PairingSignature(@NonNull SignatureSchema signatureSchema, @NonNull GroupElement signatureElement) { - /** - * Deserialize a signature from a byte array. - * - * @param bytes the serialized signature, with the first byte representing the curve type - * @return the deserialized signature - */ - public static PairingSignature fromBytes(@NonNull final byte[] bytes) { - final SerializedSignatureSchemaObject schemaObject = SerializedSignatureSchemaObject.fromByteArray(bytes); - - return new PairingSignature( - schemaObject.schema(), - schemaObject.schema().getSignatureGroup().elementFromBytes(schemaObject.elementBytes())); - } - - /** - * Verify a signed message with the known public key. - *

- * To verify a signature, we need to ensure that the message m was signed with the corresponding private key “sk” - * for the given public key “pk”. - *

- * The signature is considered valid only if the pairing between the generator of the public key group and the - * signature “σ” is equal to the pairing between the public key and the message hashed to the signature key group. - *

- * Mathematically, this verification can be expressed like this: - * e(pk, H(m)) = e([sk]g1, H(m)) = e(g1, H(m))^(sk) = e(g1, [sk]H(m)) = e(g1, σ). - * - * @param publicKey the public key to verify with - * @param message the message that was signed - * @return true if the signature is valid, false otherwise - */ - public boolean verifySignature(@NonNull final PairingPublicKey publicKey, @NonNull final byte[] message) { - final Group publicKeyGroup = publicKey.keyElement().getGroup(); - if (!publicKeyGroup.equals(signatureSchema.getPublicKeyGroup())) { - throw new IllegalArgumentException("The public key group does not match the signature schema."); - } - - final Group signatureGroup = signatureElement.getGroup(); - final BilinearPairing pairing = signatureSchema.getPairing(); - final GroupElement messageHashedToGroup = signatureGroup.elementFromHash(message); - - final PairingResult lhs = pairing.pairingBetween(signatureElement, publicKeyGroup.getGenerator()); - final PairingResult rhs = pairing.pairingBetween(messageHashedToGroup, publicKey.keyElement()); - - return pairing.comparePairingResults(lhs, rhs); - } - - /** - * Serialize the public key to a byte array. - *

- * The first byte of the serialized public key will represent the curve type. - * - * @return the serialized public key - */ - @NonNull - public byte[] toBytes() { - final int elementSize = signatureElement.size(); - final byte[] output = new byte[1 + elementSize]; - - output[0] = signatureSchema.getIdByte(); - System.arraycopy(signatureElement.toBytes(), 0, output, 1, elementSize); - - return output; - } -} diff --git a/platform-sdk/docs/proposals/TSS-Library/signature-lib/SignatureSchema.java b/platform-sdk/docs/proposals/TSS-Library/signature-lib/SignatureSchema.java deleted file mode 100644 index 23bcc5a7c8ed..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/signature-lib/SignatureSchema.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.signaturescheme.api; - -import com.hedera.cryptography.pairings.api.BilinearPairing; -import com.hedera.cryptography.pairings.api.Curve; -import com.hedera.cryptography.pairings.api.Field; -import com.hedera.cryptography.pairings.api.Group; -import com.hedera.cryptography.pairings.spi.BilinearPairingService; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Objects; - -/** - * Represents a threshold signature schema. - * - * @implNote Given that we pack the type of the curve in serialized forms in 1 byte alongside other information - * we can only support a limited amount of curves (128). - *

- */ -public class SignatureSchema { - private final BilinearPairing pairing; - private final GroupAssignment groupAssignment; - private final Curve curve; - - /** - * Returns a signature scheme from its byte representation - *

- * The input byte is expected to be a packed byte, with the first 7 bits representing the curve type, and the last - * bit representing the group assignment. - * - * @param idByte the byte representation of signature schema - * @return the SignatureSchema instance - */ - @NonNull - public static SignatureSchema fromIdByte(final byte idByte) { - final byte curveIdByte = BytePacker.unpackCurveType(idByte); - final GroupAssignment groupAssignment = BytePacker.unpackGroupAssignment(idByte); - final Curve curve = Curve.fromId(curveIdByte); - return create(curve,groupAssignment); - } - - /** - * Returns a signature scheme a curve and a groupAssignment - * - * @param groupAssignment the group assignment - * @param curve the curve - * @return the SignatureSchema instance - */ - @NonNull - public static SignatureSchema create(Curve curve, GroupAssignment groupAssignment) { - return new SignatureSchema(BilinearPairingService.instanceOf(curve), groupAssignment ,curve); - } - - - /** - * Get the group used for public keys - * - * @return the group used for public keys - */ - @NonNull - public Group getPublicKeyGroup() { - return groupAssignment.getPublicKeyGroupFor(pairing); - } - - /** - * Get the group used for signatures - * - * @return the group used for signatures - */ - @NonNull - public Group getSignatureGroup() { - return groupAssignment.getSignatureGroupFor(pairing); - } - - /** - * Get the field used for the curve - * - * @return the field used for the curve - */ - @NonNull - public Field getField() { - return pairing.getField(); - } - - /** - * Get the ID byte representing this schema - * - * @return the ID byte - */ - public byte getIdByte() { - return BytePacker.pack(groupAssignment, curve.getId()); - } - - /** - * Get the bilinear pairing used for this schema - * - * @return the bilinear pairing - */ - @NonNull - public BilinearPairing getPairing() { - return pairing; - } - - /** - * Constructor - * - * @param pairing the pairing - * @param groupAssignment the group assignment - * @param curve the curve - */ - private SignatureSchema( - @NonNull final BilinearPairing pairing, @NonNull final GroupAssignment groupAssignment, final Curve curve) { - this.pairing = Objects.requireNonNull(pairing); - this.groupAssignment = Objects.requireNonNull(groupAssignment); - this.curve = curve; - } - - /** - * Packs and unpacks the curve type and group assignment into a single byte - */ - private static class BytePacker { - private static final int G_ASSIGNMENT_MASK = 0b10000000; // 1 bit for GroupAssignment - private static final int CURVE_MASK = 0b01111111; // 7 bits for curve type - - /** - * Packs the group assignment and curve type into a single byte - * - * @param groupAssignment the group assignment - * @param curveType the curve type - * @return the packed byte - */ - public static byte pack(@NonNull final GroupAssignment groupAssignment, final byte curveType) { - if (curveType < 0) { - throw new IllegalArgumentException("Curve type must be between 0 and 127"); - } - - final int assignmentValue = groupAssignment.ordinal() << 7; - return (byte) (assignmentValue | (curveType & CURVE_MASK)); - } - - /** - * Unpacks the group assignment from a packed byte - * - * @param packedByte the packed byte - * @return the group assignment - */ - public static GroupAssignment unpackGroupAssignment(final byte packedByte) { - final int schemaValue = (packedByte & G_ASSIGNMENT_MASK) >> 7; - return GroupAssignment.values()[schemaValue]; - } - - /** - * Unpacks the curve type from a packed byte - * - * @param packedByte the packed byte - * @return the curve type - */ - public static byte unpackCurveType(final byte packedByte) { - return (byte) (packedByte & CURVE_MASK); - } - } -} diff --git a/platform-sdk/docs/proposals/TSS-Library/tss/TssMessage.java b/platform-sdk/docs/proposals/TSS-Library/tss/TssMessage.java deleted file mode 100644 index 43ef6832ad17..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/tss/TssMessage.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.tss.api; - -import com.hedera.cryptography.signaturescheme.api.PairingPublicKey; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * A message sent as part of either genesis keying, or rekeying. - * - * @param shareId the ID of the share used to generate this message - * @param cipherText contains secrets that are being distributed - * @param commitment a commitment to the polynomial that was used to generate the secrets - * @param proof a proof that the polynomial commitment is valid - */ -public record TssMessage( - @NonNull TssShareId shareId, - @NonNull Object cipherText, - @NonNull Object commitment, - @NonNull Object proof) { - - - /** - * Convert the message to a byte array. - * - * @return the byte array representation of the message - */ - public byte[] toBytes() { - throw new UnsupportedOperationException("Implementation should be provided but not defined in the proposal"); - } -} diff --git a/platform-sdk/docs/proposals/TSS-Library/tss/TssParticipantDirectory.java b/platform-sdk/docs/proposals/TSS-Library/tss/TssParticipantDirectory.java deleted file mode 100644 index c84958e62a52..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/tss/TssParticipantDirectory.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.tss.api; - -import com.hedera.cryptography.signaturescheme.api.PairingPrivateKey; -import com.hedera.cryptography.signaturescheme.api.PairingPublicKey; -import com.hedera.cryptography.signaturescheme.api.SignatureSchema; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -/** - * Represents a directory of participants in a Threshold Signature Scheme (TSS). - * Each participant has associated shares count, public keys. - * Also includes the current participant private key. - */ -public class TssParticipantDirectory { - private final List ownedShareIds; - private final Map ownershipMap; - private final Map publicKeyMap; - private final PairingPrivateKey persistentPairingPrivateKey; - private final int threshold; - - /** - * Constructs a ParticipantDirectory with the specified owned share IDs, ownership map, - * public key map, persistent pairing private key, and threshold. - * - * @param ownedShareIds the list of owned share IDs - * @param ownershipMap the map of share IDs to participant IDs - * @param publicKeyMap the map of participant IDs to public keys - * @param persistentPairingPrivateKey the persistent pairing private key - * @param threshold the threshold value for the TSS - */ - public TssParticipantDirectory(@NonNull final List ownedShareIds, - @NonNull final Map ownershipMap, - @NonNull final Map publicKeyMap, - @NonNull final PairingPrivateKey persistentPairingPrivateKey, - final int threshold) { - this.ownedShareIds = List.copyOf(ownedShareIds); - this.ownershipMap = Map.copyOf(ownershipMap); - this.publicKeyMap = Map.copyOf(publicKeyMap); - this.persistentPairingPrivateKey = persistentPairingPrivateKey; - this.threshold = threshold; - } - - /** - * Returns the list of owned share IDs. - * - * @return the list of owned share IDs - */ - public List getOwnedShareIds() { - return ownedShareIds; - } - - /** - * Returns the persistent pairing private key. - * - * @return the persistent pairing private key - */ - public PairingPrivateKey getPersistentPairingPrivateKey() { - return persistentPairingPrivateKey; - } - - /** - * Returns the threshold value for the TSS. - * - * @return the threshold value - */ - public int getThreshold() { - return threshold; - } - - /** - * Returns a collection of all share IDs. - * - * @return a collection of all share IDs - */ - public Collection getShareIds() { - return ownershipMap.keySet(); - } - - /** - * Returns the totalNumber of shares - * - * @return a collection of all share IDs - */ - public int getTotalNumberOfShares() { - return ownershipMap.size(); - } - - /** - * Returns a list of public keys corresponding to the given list of share IDs. - * - * @param ids the list of share IDs - * @return the list of public keys - */ - public List getPairingPublicKeys(@NonNull final List ids) { - List publicKeys = new ArrayList<>(ids.size()); - for (TssShareId id : ids) { - int key = ownershipMap.get(id); - publicKeys.add(publicKeyMap.get(key)); - } - return publicKeys; - } - - /** - * Creates a new Builder for constructing a ParticipantDirectory. - * - * @return a new Builder instance - */ - public static Builder createBuilder() { - return new Builder(); - } - - /** - * A builder for creating ParticipantDirectory instances. - */ - public static class Builder { - private SelfEntry selfEntry; - private final List participantEntries = new ArrayList<>(); - private int threshold; - - private Builder() {} - - /** - * Sets the self entry for the builder. - * - * @param id the participant ID - * @param key the pairing private key - * @return the builder instance - */ - Builder withSelf(final int id, @NonNull final PairingPrivateKey key) { - selfEntry = new SelfEntry(id, key); - return this; - } - - /** - * Sets the threshold value for the TSS. - * - * @param threshold the threshold value - * @return the builder instance - */ - Builder withThreshold(final int threshold) { - this.threshold = threshold; - return this; - } - - /** - * Adds a participant entry to the builder. - * - * @param participantId the participant ID - * @param numberOfShares the number of shares - * @param publicKey the pairing public key - * @return the builder instance - */ - Builder withParticipant(final int participantId, final int numberOfShares, final @NonNull PairingPublicKey publicKey) { - participantEntries.add(new ParticipantEntry(participantId, numberOfShares, publicKey)); - return this; - } - - /** - * Adds a participant entry with one share to the builder. - * - * @param participantId the participant ID - * @param publicKey the pairing public key - * @return the builder instance - */ - Builder withParticipant(final int participantId, final @NonNull PairingPublicKey publicKey) { - participantEntries.add(new ParticipantEntry(participantId, 1, publicKey)); - return this; - } - - /** - * Adds a participant entry with a generated ID and one share to the builder. - * - * @param publicKey the pairing public key - * @return the builder instance - */ - Builder withParticipant(final @NonNull PairingPublicKey publicKey) { - participantEntries.add(new ParticipantEntry(participantEntries.size(), 1, publicKey)); - return this; - } - - /** - * Builds and returns a ParticipantDirectory instance based on the provided entries and schema. - * - * @param schema the signature schema - * @return the constructed ParticipantDirectory instance - */ - public TssParticipantDirectory build(SignatureSchema schema) { - int totalShares = participantEntries.stream().map(ParticipantEntry::shareCount).reduce(0, Integer::sum); - - List ids = IntStream.range(1, totalShares + 1) - .boxed() - .map(schema.getField()::elementFromLong) - .map(TssShareId::new) - .toList(); - - Iterator elementIterator = ids.iterator(); - Map ownershipMap = new HashMap<>(); - List ownedShareIds = new ArrayList<>(); - - for (ParticipantEntry record : participantEntries) { - for (int i = 0; i < record.shareCount(); i++) { - if (elementIterator.hasNext()) { - TssShareId tssShareId = elementIterator.next(); - ownershipMap.put(tssShareId, record.id()); - if (record.id == selfEntry.id) { - ownedShareIds.add(tssShareId); - } - } - } - } - - Map publicKeyMap = participantEntries.stream() - .collect(Collectors.toMap(ParticipantEntry::id, ParticipantEntry::publicKey)); - - return new TssParticipantDirectory(ownedShareIds, ownershipMap, publicKeyMap, selfEntry.privateKey, threshold); - } - } - - /** - * Represents an entry for the participant itself, containing the ID and private key. - */ - record SelfEntry(int id, PairingPrivateKey privateKey) {} - - /** - * Represents an entry for a participant, containing the ID, share count, and public key. - */ - record ParticipantEntry(int id, int shareCount, PairingPublicKey publicKey) {} -} \ No newline at end of file diff --git a/platform-sdk/docs/proposals/TSS-Library/tss/TssPrivateShare.java b/platform-sdk/docs/proposals/TSS-Library/tss/TssPrivateShare.java deleted file mode 100644 index f0e9eb7997cf..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/tss/TssPrivateShare.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.tss.api; - -import com.hedera.cryptography.signaturescheme.api.PairingPrivateKey; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * A record that contains a share ID, and the corresponding private key. - * - * @param shareId the share ID - * @param privateKey the private key - */ -public record TssPrivateShare(@NonNull TssShareId shareId, @NonNull PairingPrivateKey privateKey) { - -} diff --git a/platform-sdk/docs/proposals/TSS-Library/tss/TssPublicShare.java b/platform-sdk/docs/proposals/TSS-Library/tss/TssPublicShare.java deleted file mode 100644 index 2563b7d3ee7a..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/tss/TssPublicShare.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.tss.api; - -import com.hedera.cryptography.signaturescheme.api.PairingPublicKey; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * A record that contains a share ID, and the corresponding public key. - * - * @param shareId the share ID - * @param publicKey the public key - */ -public record TssPublicShare(@NonNull TssShareId shareId, @NonNull PairingPublicKey publicKey) {} diff --git a/platform-sdk/docs/proposals/TSS-Library/tss/TssService.java b/platform-sdk/docs/proposals/TSS-Library/tss/TssService.java deleted file mode 100644 index cb0066bea115..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/tss/TssService.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.tss.api; - -import com.hedera.cryptography.signaturescheme.api.PairingPrivateKey; -import com.hedera.cryptography.signaturescheme.api.PairingPublicKey; -import com.hedera.cryptography.signaturescheme.api.PairingSignature; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.List; - -/** - * A Threshold Signature Scheme Service. - *

- * Contract of TSS: - *

    - *
  • Generate TSSMessages out of PrivateShares
  • - *
  • Verify TSSMessages out of a ParticipantDirectory
  • - *
  • Obtain PrivateShares out of TssMessages for each owned share
  • - *
  • Aggregate PrivateShares
  • - *
  • Obtain PublicShares out of TssMessages for each share
  • - *
  • Aggregate PublicShares
  • - *
  • sign Messages
  • - *
  • verify Signatures
  • - *
  • Aggregate Signatures
  • - *
- * @implNote an instance of the service would require a source of randomness {@link java.util.Random}, and a{@link com.hedera.cryptography.signaturescheme.api.SignatureSchema} - */ -public interface TssService { - - /** - * Verify that the message is valid. - * - * @param pendingParticipantDirectory the participant directory used to generate the message - * @return true if the message is valid, false otherwise - */ - boolean verifyTssMessage(@NonNull final ParticipantDirectory participantDirectory, @NonNull final TssMessage message); - - /** - * Generate a TSS message for a pendingParticipantDirectory, for each private share owned. - * - * @param pendingParticipantDirectory the pending participant directory that we should generate the message for - * @param privateShare the secret to use for generating new keys - * @return a TSSMessage for each owned share. - */ - @NonNull - List generateTssMessages( - @NonNull final ParticipantDirectory pendingParticipantDirectory, - @NonNull final TssPrivateShare privateShare); - - /** - * Generate a TSS message for a pendingParticipantDirectory, from a random private share. - * - * @param pendingParticipantDirectory the pending participant directory that we should generate the message for - * @param privateShare the secret to use for generating new keys - * @return a TSSMessage produced out of a random share. - */ - @NonNull - TssMessage generateTssMessage(@NonNull final ParticipantDirectory pendingParticipantDirectory); - - /** - * Compute all private shares that belongs to this participant. - * - * @param pendingParticipantDirectory the pending participant directory that we should generate the private share for - * @param validTssMessages the TSS messages to extract the private shares from. They must be previously validated. - * @return the list of private share the current participant owns, or null if there aren't enough shares to meet the threshold. - * the list is sorted by the sharedId - */ - @Nullable - List decryptPrivateShares( - @NonNull final ParticipantDirectory participantDirectory, - @NonNull final List validTssMessages); - - /** - * Aggregate a threshold number of {@link TssPrivateShare}s. - *

- * It is the responsibility of the caller to ensure that the list of private shares meets the required threshold. - * If the threshold is not met, the private key returned by this method will be invalid. - * - * @param privateShares the private shares to aggregate - * @return the aggregate private key - */ - @NonNull - PairingPrivateKey aggregatePrivateShares(@NonNull final List privateShares); - - /** - * Compute all public shares. - * - * @param pendingParticipantDirectory the pending participant directory that we should generate the message for - * @param validTssMessages the TSS messages to extract the private shares from. They must be previously validated. - * @return the list of public shares, or null if there aren't enough messages to meet the threshold - */ - @Nullable - List computePublicShares( - @NonNull final ParticipantDirectory participantDirectory, - @NonNull final List tssMessages) ; - - /** - * Aggregate a threshold number of {@link TssPublicShare}s. - *

- * It is the responsibility of the caller to ensure that the list of public shares meets the required threshold. - * If the threshold is not met, the public key returned by this method will be invalid. - *

- * This method is used for two distinct purposes: - *

    - *
  • Aggregating public shares to produce the Ledger ID
  • - *
  • Aggregating public shares derived from all commitments, to produce the public key for a given share
  • - *
- * - * @param publicShares the public shares to aggregate - * @return the interpolated public key - */ - @NonNull - PairingPublicKey aggregatePublicShares(@NonNull final List publicShares); - - /** - * Sign a message using the private share's key. - *

- * @param privateShare the private share to sign the message with - * @param message the message to sign - * @return the signature, which will be in the group opposite to the group of the public key - */ - @NonNull - TssShareSignature sign(final @NonNull TssPrivateShare privateShare, final @NonNull byte[] message) ; - - /** - * verifies a signature using the participantDirectory and the list of public shares. - *

- * @param participantDirectory the pending share claims the TSS message was created for - * @param privateShare the private share to sign the message with - * @return true if the signature is valid, false otherwise. - */ - boolean verifySignature(@NonNull final ParticipantDirectory participantDirectory, - @NonNull final List publicShares, final @NonNull TssShareSignature signature) ; - - /** - * Aggregate a threshold number of {@link TssShareSignature}s. - *

- * It is the responsibility of the caller to ensure that the list of partial signatures meets the required - * threshold. If the threshold is not met, the signature returned by this method will be invalid. - * - * @param partialSignatures the list of signatures to aggregate - * @return the interpolated signature - */ - @NonNull - PairingSignature aggregateSignatures(@NonNull final List partialSignatures); -} diff --git a/platform-sdk/docs/proposals/TSS-Library/tss/TssShareId.java b/platform-sdk/docs/proposals/TSS-Library/tss/TssShareId.java deleted file mode 100644 index ae8515f6c4dc..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/tss/TssShareId.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.tss.api; - -import com.hedera.cryptography.pairings.api.FieldElement; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * The ID of a TSS share. - * - * @param idElement the field element that represents the share ID - */ -public record TssShareId(@NonNull FieldElement idElement) {} diff --git a/platform-sdk/docs/proposals/TSS-Library/tss/TssShareSignature.java b/platform-sdk/docs/proposals/TSS-Library/tss/TssShareSignature.java deleted file mode 100644 index 396a5022cf50..000000000000 --- a/platform-sdk/docs/proposals/TSS-Library/tss/TssShareSignature.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.cryptography.tss.api; - -import com.hedera.cryptography.signaturescheme.api.PairingSignature; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Represents a signature created for a TSSPrivateShare. - * - * @param shareId the share ID - * @param signature the signature - */ -public record TssShareSignature(@NonNull TssShareId shareId, @NonNull PairingSignature signature) {} diff --git a/platform-sdk/platform-apps/demos/CryptocurrencyDemo/src/main/java/com/swirlds/demo/crypto/CryptocurrencyDemoState.java b/platform-sdk/platform-apps/demos/CryptocurrencyDemo/src/main/java/com/swirlds/demo/crypto/CryptocurrencyDemoState.java index 405644f5a263..d08785a13640 100644 --- a/platform-sdk/platform-apps/demos/CryptocurrencyDemo/src/main/java/com/swirlds/demo/crypto/CryptocurrencyDemoState.java +++ b/platform-sdk/platform-apps/demos/CryptocurrencyDemo/src/main/java/com/swirlds/demo/crypto/CryptocurrencyDemoState.java @@ -33,7 +33,7 @@ import com.swirlds.common.merkle.impl.PartialMerkleLeaf; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.SwirldsPlatform; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; @@ -200,7 +200,7 @@ public synchronized CryptocurrencyDemoState copy() { } @Override - public void handleConsensusRound(final Round round, final PlatformStateAccessor platformState) { + public void handleConsensusRound(final Round round, final PlatformStateModifier platformState) { throwIfImmutable(); round.forEachEventTransaction((event, transaction) -> handleTransaction(event.getCreatorId(), transaction)); } diff --git a/platform-sdk/platform-apps/demos/HelloSwirldDemo/src/main/java/com/swirlds/demo/hello/HelloSwirldDemoState.java b/platform-sdk/platform-apps/demos/HelloSwirldDemo/src/main/java/com/swirlds/demo/hello/HelloSwirldDemoState.java index 13ed08998c01..7d36ec7950cd 100644 --- a/platform-sdk/platform-apps/demos/HelloSwirldDemo/src/main/java/com/swirlds/demo/hello/HelloSwirldDemoState.java +++ b/platform-sdk/platform-apps/demos/HelloSwirldDemo/src/main/java/com/swirlds/demo/hello/HelloSwirldDemoState.java @@ -30,7 +30,7 @@ import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.merkle.MerkleLeaf; import com.swirlds.common.merkle.impl.PartialMerkleLeaf; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.transaction.Transaction; @@ -99,7 +99,7 @@ private HelloSwirldDemoState(final HelloSwirldDemoState sourceState) { } @Override - public synchronized void handleConsensusRound(final Round round, final PlatformStateAccessor platformState) { + public synchronized void handleConsensusRound(final Round round, final PlatformStateModifier platformState) { throwIfImmutable(); round.forEachTransaction(this::handleTransaction); } diff --git a/platform-sdk/platform-apps/demos/StatsDemo/src/main/java/com/swirlds/demo/stats/StatsDemoState.java b/platform-sdk/platform-apps/demos/StatsDemo/src/main/java/com/swirlds/demo/stats/StatsDemoState.java index f966af5f4523..68d018b2c2a3 100644 --- a/platform-sdk/platform-apps/demos/StatsDemo/src/main/java/com/swirlds/demo/stats/StatsDemoState.java +++ b/platform-sdk/platform-apps/demos/StatsDemo/src/main/java/com/swirlds/demo/stats/StatsDemoState.java @@ -30,7 +30,7 @@ import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.merkle.MerkleLeaf; import com.swirlds.common.merkle.impl.PartialMerkleLeaf; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SwirldState; @@ -69,7 +69,7 @@ private StatsDemoState(final StatsDemoState sourceState) { } @Override - public void handleConsensusRound(final Round round, final PlatformStateAccessor platformState) {} + public void handleConsensusRound(final Round round, final PlatformStateModifier platformState) {} /** * {@inheritDoc} diff --git a/platform-sdk/platform-apps/tests/AddressBookTestingTool/src/main/java/com/swirlds/demo/addressbook/AddressBookTestingToolState.java b/platform-sdk/platform-apps/tests/AddressBookTestingTool/src/main/java/com/swirlds/demo/addressbook/AddressBookTestingToolState.java index cc779c3c1d21..ba00acdaab9f 100644 --- a/platform-sdk/platform-apps/tests/AddressBookTestingTool/src/main/java/com/swirlds/demo/addressbook/AddressBookTestingToolState.java +++ b/platform-sdk/platform-apps/tests/AddressBookTestingTool/src/main/java/com/swirlds/demo/addressbook/AddressBookTestingToolState.java @@ -45,6 +45,7 @@ import com.swirlds.common.utility.StackTrace; import com.swirlds.platform.config.AddressBookConfig; import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; @@ -182,7 +183,7 @@ public void init( * {@inheritDoc} */ @Override - public void handleConsensusRound(@NonNull final Round round, @NonNull final PlatformStateAccessor platformState) { + public void handleConsensusRound(@NonNull final Round round, @NonNull final PlatformStateModifier platformState) { Objects.requireNonNull(round, "the round cannot be null"); Objects.requireNonNull(platformState, "the platform state cannot be null"); throwIfImmutable(); diff --git a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java index fe58d93d1a9e..4cbd7b6c7b7b 100644 --- a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java +++ b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java @@ -27,6 +27,7 @@ import com.swirlds.common.merkle.impl.PartialMerkleLeaf; import com.swirlds.common.utility.NonCryptographicHashing; import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; @@ -249,7 +250,7 @@ public void preHandle(@NonNull final Event event) { * Writes the round and its contents to a log on disk */ @Override - public void handleConsensusRound(final @NonNull Round round, final @NonNull PlatformStateAccessor platformState) { + public void handleConsensusRound(final @NonNull Round round, final @NonNull PlatformStateModifier platformState) { Objects.requireNonNull(round); Objects.requireNonNull(platformState); diff --git a/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java b/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java index c70894f84400..b14e90ca0d2b 100644 --- a/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java +++ b/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java @@ -40,7 +40,7 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.common.utility.ByteUtils; import com.swirlds.platform.scratchpad.Scratchpad; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; @@ -172,7 +172,7 @@ public void init( * {@inheritDoc} */ @Override - public void handleConsensusRound(final Round round, final PlatformStateAccessor platformState) { + public void handleConsensusRound(final Round round, final PlatformStateModifier platformState) { throwIfImmutable(); final Iterator eventIterator = round.iterator(); diff --git a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java index 23ac7cff3b37..dea54d5ab330 100644 --- a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java +++ b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java @@ -32,7 +32,7 @@ import com.swirlds.merkledb.MerkleDb; import com.swirlds.merkledb.MerkleDbDataSourceBuilder; import com.swirlds.merkledb.MerkleDbTableConfig; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; @@ -249,7 +249,7 @@ public void init( * {@inheritDoc} */ @Override - public void handleConsensusRound(final Round round, final PlatformStateAccessor platformState) { + public void handleConsensusRound(final Round round, final PlatformStateModifier platformState) { throwIfImmutable(); for (final Iterator eventIt = round.iterator(); eventIt.hasNext(); ) { final ConsensusEvent event = eventIt.next(); diff --git a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolState.java b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolState.java index f0428a539025..0681177a515e 100644 --- a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolState.java +++ b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolState.java @@ -83,7 +83,7 @@ import com.swirlds.merkle.test.fixtures.map.pta.MapKey; import com.swirlds.platform.ParameterProvider; import com.swirlds.platform.Utilities; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; @@ -1071,7 +1071,7 @@ private void handleControlTransaction( * Handle the freeze transaction type. */ private void handleFreezeTransaction( - final TestTransaction testTransaction, final PlatformStateAccessor platformState) { + final TestTransaction testTransaction, final PlatformStateModifier platformState) { final FreezeTransaction freezeTx = testTransaction.getFreezeTransaction(); FreezeTransactionHandler.freeze(freezeTx, platformState); } @@ -1093,7 +1093,7 @@ protected void preHandleTransaction(final Transaction transaction) { } @Override - public synchronized void handleConsensusRound(final Round round, final PlatformStateAccessor platformState) { + public synchronized void handleConsensusRound(final Round round, final PlatformStateModifier platformState) { throwIfImmutable(); if (!initialized.get()) { throw new IllegalStateException("handleConsensusRound() called before init()"); @@ -1125,7 +1125,7 @@ private void updateTransactionCounters() { private void handleConsensusTransaction( final ConsensusEvent event, final ConsensusTransaction trans, - final PlatformStateAccessor platformState, + final PlatformStateModifier platformState, final long roundNum) { if (trans.isSystem()) { return; @@ -1164,7 +1164,7 @@ private void handleTransaction( @NonNull final Instant timeCreated, @NonNull final Instant timestamp, @NonNull final ConsensusTransaction trans, - @NonNull final PlatformStateAccessor platformState) { + @NonNull final PlatformStateModifier platformState) { if (getConfig().isAppendSig()) { try { final TestTransactionWrapper testTransactionWrapper = TestTransactionWrapper.parseFrom( diff --git a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/freeze/FreezeTransactionHandler.java b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/freeze/FreezeTransactionHandler.java index aa4d2fb0d8a4..c00a4e0c7fa7 100644 --- a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/freeze/FreezeTransactionHandler.java +++ b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/freeze/FreezeTransactionHandler.java @@ -17,7 +17,7 @@ package com.swirlds.demo.platform.freeze; import com.swirlds.demo.platform.fs.stresstest.proto.FreezeTransaction; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import java.time.Instant; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -28,7 +28,7 @@ public class FreezeTransactionHandler { private static final Logger logger = LogManager.getLogger(FreezeTransactionHandler.class); private static final Marker LOGM_FREEZE = MarkerManager.getMarker("FREEZE"); - public static boolean freeze(final FreezeTransaction transaction, final PlatformStateAccessor platformState) { + public static boolean freeze(final FreezeTransaction transaction, final PlatformStateModifier platformState) { logger.debug(LOGM_FREEZE, "Handling FreezeTransaction: " + transaction); try { platformState.setFreezeTime(Instant.ofEpochSecond(transaction.getStartTimeEpochSecond())); diff --git a/platform-sdk/platform-apps/tests/StatsSigningTestingTool/src/main/java/com/swirlds/demo/stats/signing/StatsSigningTestingToolState.java b/platform-sdk/platform-apps/tests/StatsSigningTestingTool/src/main/java/com/swirlds/demo/stats/signing/StatsSigningTestingToolState.java index 15620a5eb567..22c71f3f071d 100644 --- a/platform-sdk/platform-apps/tests/StatsSigningTestingTool/src/main/java/com/swirlds/demo/stats/signing/StatsSigningTestingToolState.java +++ b/platform-sdk/platform-apps/tests/StatsSigningTestingTool/src/main/java/com/swirlds/demo/stats/signing/StatsSigningTestingToolState.java @@ -37,7 +37,7 @@ import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.merkle.MerkleLeaf; import com.swirlds.common.merkle.impl.PartialMerkleLeaf; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.events.Event; @@ -129,7 +129,7 @@ public void preHandle(final Event event) { * {@inheritDoc} */ @Override - public void handleConsensusRound(final Round round, final PlatformStateAccessor platformState) { + public void handleConsensusRound(final Round round, final PlatformStateModifier platformState) { throwIfImmutable(); round.forEachTransaction(this::handleTransaction); } diff --git a/platform-sdk/platform-apps/tests/StressTestingTool/src/main/java/com/swirlds/demo/stress/StressTestingToolState.java b/platform-sdk/platform-apps/tests/StressTestingTool/src/main/java/com/swirlds/demo/stress/StressTestingToolState.java index f01443a69713..c4885e40e090 100644 --- a/platform-sdk/platform-apps/tests/StressTestingTool/src/main/java/com/swirlds/demo/stress/StressTestingToolState.java +++ b/platform-sdk/platform-apps/tests/StressTestingTool/src/main/java/com/swirlds/demo/stress/StressTestingToolState.java @@ -31,7 +31,7 @@ import com.swirlds.common.merkle.MerkleLeaf; import com.swirlds.common.merkle.impl.PartialMerkleLeaf; import com.swirlds.common.utility.ByteUtils; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; @@ -99,7 +99,7 @@ public void preHandle(final Event event) { * {@inheritDoc} */ @Override - public void handleConsensusRound(final Round round, final PlatformStateAccessor platformState) { + public void handleConsensusRound(final Round round, final PlatformStateModifier platformState) { throwIfImmutable(); round.forEachTransaction(this::handleTransaction); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java index 572a55e2a720..bd083dabd6b3 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java @@ -29,6 +29,7 @@ import com.swirlds.platform.config.DefaultConfiguration; import com.swirlds.platform.consensus.SyntheticSnapshot; import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.snapshot.DeserializedSignedState; import com.swirlds.platform.state.snapshot.SignedStateFileReader; @@ -75,7 +76,7 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti final DeserializedSignedState deserializedSignedState = SignedStateFileReader.readStateFile(platformContext, statePath, SignedStateFileUtils::readState); try (final ReservedSignedState reservedSignedState = deserializedSignedState.reservedSignedState()) { - final PlatformStateAccessor platformState = + final PlatformStateModifier platformState = reservedSignedState.get().getState().getWritablePlatformState(); platformState.bulkUpdate(v -> { System.out.printf("Replacing platform data %n"); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/DefaultInternalEventValidator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/DefaultInternalEventValidator.java index 5866e745557e..670d6cae6fef 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/DefaultInternalEventValidator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/DefaultInternalEventValidator.java @@ -21,7 +21,13 @@ import static com.swirlds.platform.consensus.ConsensusConstants.ROUND_NEGATIVE_INFINITY; import static com.swirlds.platform.system.events.EventConstants.FIRST_GENERATION; +import com.hedera.hapi.platform.event.EventCore; +import com.hedera.hapi.platform.event.EventDescriptor; +import com.hedera.hapi.platform.event.EventTransaction; +import com.hedera.hapi.platform.event.GossipEvent; import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.crypto.DigestType; +import com.swirlds.common.crypto.SignatureType; import com.swirlds.common.utility.throttle.RateLimitedLogger; import com.swirlds.metrics.api.LongAccumulator; import com.swirlds.platform.config.TransactionConfig; @@ -65,7 +71,8 @@ public class DefaultInternalEventValidator implements InternalEventValidator { private final AncientMode ancientMode; - private final RateLimitedLogger nullUnhashedDataLogger; + private final RateLimitedLogger nullFieldLogger; + private final RateLimitedLogger fieldLengthLogger; private final RateLimitedLogger tooManyTransactionBytesLogger; private final RateLimitedLogger inconsistentSelfParentLogger; private final RateLimitedLogger inconsistentOtherParentLogger; @@ -73,7 +80,8 @@ public class DefaultInternalEventValidator implements InternalEventValidator { private final RateLimitedLogger invalidGenerationLogger; private final RateLimitedLogger invalidBirthRoundLogger; - private final LongAccumulator nullUnhashedDataAccumulator; + private final LongAccumulator nullFieldAccumulator; + private final LongAccumulator fieldLengthAccumulator; private final LongAccumulator tooManyTransactionBytesAccumulator; private final LongAccumulator inconsistentSelfParentAccumulator; private final LongAccumulator inconsistentOtherParentAccumulator; @@ -102,7 +110,8 @@ public DefaultInternalEventValidator( .getConfigData(EventConfig.class) .getAncientMode(); - this.nullUnhashedDataLogger = new RateLimitedLogger(logger, platformContext.getTime(), MINIMUM_LOG_PERIOD); + this.nullFieldLogger = new RateLimitedLogger(logger, platformContext.getTime(), MINIMUM_LOG_PERIOD); + this.fieldLengthLogger = new RateLimitedLogger(logger, platformContext.getTime(), MINIMUM_LOG_PERIOD); this.tooManyTransactionBytesLogger = new RateLimitedLogger(logger, platformContext.getTime(), MINIMUM_LOG_PERIOD); this.inconsistentSelfParentLogger = @@ -113,10 +122,15 @@ public DefaultInternalEventValidator( this.invalidGenerationLogger = new RateLimitedLogger(logger, platformContext.getTime(), MINIMUM_LOG_PERIOD); this.invalidBirthRoundLogger = new RateLimitedLogger(logger, platformContext.getTime(), MINIMUM_LOG_PERIOD); - this.nullUnhashedDataAccumulator = platformContext + this.nullFieldAccumulator = platformContext .getMetrics() - .getOrCreate(new LongAccumulator.Config(PLATFORM_CATEGORY, "eventsWithNullUnhashedData") - .withDescription("Events that had null unhashed data") + .getOrCreate(new LongAccumulator.Config(PLATFORM_CATEGORY, "eventsWithNullFields") + .withDescription("Events that had a null field") + .withUnit("events")); + this.fieldLengthAccumulator = platformContext + .getMetrics() + .getOrCreate(new LongAccumulator.Config(PLATFORM_CATEGORY, "eventsWithInvalidFieldLength") + .withDescription("Events with an invalid field length") .withUnit("events")); this.tooManyTransactionBytesAccumulator = platformContext .getMetrics() @@ -157,16 +171,67 @@ public DefaultInternalEventValidator( * @return true if the required fields of the event are non-null, otherwise false */ private boolean areRequiredFieldsNonNull(@NonNull final PlatformEvent event) { - if (event.getSignature() == null) { - // do not log the event itself, since toString would throw a NullPointerException - nullUnhashedDataLogger.error(EXCEPTION.getMarker(), "Event has null signature"); - nullUnhashedDataAccumulator.update(1); + final GossipEvent gossipEvent = event.getGossipEvent(); + final EventCore eventCore = gossipEvent.eventCore(); + String nullField = null; + if (eventCore == null) { + nullField = "eventCore"; + } else if (eventCore.timeCreated() == null) { + nullField = "timeCreated"; + } else if (eventCore.version() == null) { + nullField = "version"; + } else if (eventCore.parents().stream().anyMatch(Objects::isNull)) { + nullField = "parent"; + } else if (gossipEvent.eventTransaction().stream().anyMatch(DefaultInternalEventValidator::isTransactionNull)) { + nullField = "transaction"; + } + if (nullField != null) { + nullFieldLogger.error(EXCEPTION.getMarker(), "Event has null field '{}' {}", nullField, gossipEvent); + nullFieldAccumulator.update(1); return false; } return true; } + /** + * Checks whether the transaction is null. + * @param transaction the transaction to check + * @return true if the transaction is null, otherwise false + */ + private static boolean isTransactionNull(@Nullable final EventTransaction transaction) { + return transaction == null + || transaction.transaction() == null + || transaction.transaction().value() == null; + } + + /** + * Checks whether the {@link com.hedera.pbj.runtime.io.buffer.Bytes} fields of an event are the expected length. + * + * @param event the event to check + * @return true if the byte fields of the event are the correct length, otherwise false + */ + private boolean areByteFieldsCorrectLength(@NonNull final PlatformEvent event) { + final GossipEvent gossipEvent = event.getGossipEvent(); + final EventCore eventCore = gossipEvent.eventCore(); + if (gossipEvent.signature().length() != SignatureType.RSA.signatureLength()) { + fieldLengthLogger.error(EXCEPTION.getMarker(), "Event signature is the wrong length {}", gossipEvent); + fieldLengthAccumulator.update(1); + return false; + } + if (eventCore.parents().stream() + .map(EventDescriptor::hash) + .anyMatch(hash -> hash.length() != DigestType.SHA_384.digestLength())) { + fieldLengthLogger.error( + EXCEPTION.getMarker(), + "Event parent descriptor has a hash that is the wrong length {}", + gossipEvent); + fieldLengthAccumulator.update(1); + return false; + } + return true; + } + /** * Checks whether the total byte count of all transactions in an event is less than the maximum. * @@ -320,6 +385,7 @@ private boolean isEventBirthRoundValid(@NonNull final PlatformEvent event) { @Nullable public PlatformEvent validateEvent(@NonNull final PlatformEvent event) { if (areRequiredFieldsNonNull(event) + && areByteFieldsCorrectLength(event) && isTransactionByteCountValid(event) && areParentsInternallyConsistent(event) && isEventGenerationValid(event) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java index 42371c32f49f..1e804183db43 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java @@ -37,7 +37,7 @@ import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.metrics.RoundHandlingMetrics; import com.swirlds.platform.state.MerkleRoot; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; @@ -215,12 +215,12 @@ public StateAndRound handleConsensusRound(@NonNull final ConsensusRound consensu } /** - * Populate the {@link PlatformStateAccessor} with all needed data for this round. + * Populate the {@link PlatformStateModifier} with all needed data for this round. * * @param round the consensus round */ private void updatePlatformState(@NonNull final ConsensusRound round) { - final PlatformStateAccessor platformState = + final PlatformStateModifier platformState = swirldStateManager.getConsensusState().getWritablePlatformState(); platformState.bulkUpdate(v -> { v.setRound(round.getRoundNum()); @@ -238,7 +238,7 @@ private void updatePlatformState(@NonNull final ConsensusRound round) { * @throws InterruptedException if this thread is interrupted */ private void updateRunningEventHash(@NonNull final ConsensusRound round) throws InterruptedException { - final PlatformStateAccessor platformState = + final PlatformStateModifier platformState = swirldStateManager.getConsensusState().getWritablePlatformState(); if (writeLegacyRunningEventHash) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java index fc3e2ca68636..76ced4608049 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java @@ -53,6 +53,7 @@ import com.swirlds.platform.recovery.internal.StreamedRound; import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.snapshot.SignedStateFileReader; @@ -380,7 +381,7 @@ private static ReservedSignedState handleNextRound( new DefaultEventHasher().hashEvent(lastEvent); final PlatformStateAccessor newReadablePlatformState = newState.getReadablePlatformState(); - final PlatformStateAccessor newWritablePlatformState = newState.getWritablePlatformState(); + final PlatformStateModifier newWritablePlatformState = newState.getWritablePlatformState(); final PlatformStateAccessor previousReadablePlatformState = previousState.get().getState().getReadablePlatformState(); @@ -478,7 +479,7 @@ static ConsensusEvent getLastEvent(final Round round) { static void applyTransactions( final SwirldState immutableState, final SwirldState mutableState, - final PlatformStateAccessor platformState, + final PlatformStateModifier platformState, final Round round) { mutableState.throwIfImmutable(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/BirthRoundStateMigration.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/BirthRoundStateMigration.java index 7dde25310efc..925a96cb6e25 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/BirthRoundStateMigration.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/BirthRoundStateMigration.java @@ -64,7 +64,7 @@ public static void modifyStateForBirthRoundMigration( } final MerkleRoot state = initialState.getState(); - final PlatformStateAccessor writablePlatformState = state.getWritablePlatformState(); + final PlatformStateModifier writablePlatformState = state.getWritablePlatformState(); final boolean alreadyMigrated = writablePlatformState.getFirstVersionInBirthRoundMode() != null; if (alreadyMigrated) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java index ef0d081fe3bb..55ede92be17c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java @@ -39,7 +39,7 @@ private GenesisStateBuilder() {} */ public static void initGenesisPlatformState( final PlatformContext platformContext, - final PlatformStateAccessor platformState, + final PlatformStateModifier platformState, final AddressBook addressBook, final SoftwareVersion appVersion) { platformState.bulkUpdate(v -> { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleRoot.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleRoot.java index 74244ab4323a..3375518f26fb 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleRoot.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleRoot.java @@ -32,6 +32,12 @@ public interface MerkleRoot extends MerkleInternal { @NonNull SwirldState getSwirldState(); + /** + * This method makes sure that the platform state is initialized. + * If it's already initialized, it does nothing. + */ + void initPlatformState(); + /** * Get readable platform state. * Works on both - mutable and immutable {@link MerkleRoot} and, therefore, this method should be preferred. @@ -48,14 +54,14 @@ public interface MerkleRoot extends MerkleInternal { * @return mutable platform state */ @NonNull - PlatformStateAccessor getWritablePlatformState(); + PlatformStateModifier getWritablePlatformState(); /** * Set the platform state. * * @param platformState the platform state */ - void updatePlatformState(@NonNull final PlatformStateAccessor platformState); + void updatePlatformState(@NonNull final PlatformStateModifier platformState); /** * Generate a string that describes this state. diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleStateLifecycles.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleStateLifecycles.java index d0342ede771e..2b49761540fb 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleStateLifecycles.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleStateLifecycles.java @@ -37,7 +37,7 @@ */ public interface MerkleStateLifecycles { /** - * Called when a {@link MerkleStateRoot} needs to ensure its {@link PlatformStateAccessor} implementation + * Called when a {@link MerkleStateRoot} needs to ensure its {@link PlatformStateModifier} implementation * is initialized. * * @param state the root of the state to be initialized diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleStateRoot.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleStateRoot.java index 039aa89c5b82..948d441ddd88 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleStateRoot.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleStateRoot.java @@ -390,7 +390,7 @@ public MerkleStateRoot copy() { * {@inheritDoc} */ @Override - public void handleConsensusRound(@NonNull final Round round, @NonNull final PlatformStateAccessor platformState) { + public void handleConsensusRound(@NonNull final Round round, @NonNull final PlatformStateModifier platformState) { throwIfImmutable(); lifecycles.onHandleConsensusRound(round, this); } @@ -1014,7 +1014,7 @@ public PlatformStateAccessor getReadablePlatformState() { */ @NonNull @Override - public PlatformStateAccessor getWritablePlatformState() { + public PlatformStateModifier getWritablePlatformState() { if (isImmutable()) { throw new IllegalStateException("Cannot get writable platform state when state is immutable"); } @@ -1022,12 +1022,22 @@ public PlatformStateAccessor getWritablePlatformState() { } /** - * Updates the platform state with the values from the provided instance of {@link PlatformStateAccessor} + * {@inheritDoc} + */ + @Override + public void initPlatformState() { + if (!services.containsKey(PlatformStateService.NAME)) { + platformStateInitChanges = lifecycles.initPlatformState(this); + } + } + + /** + * Updates the platform state with the values from the provided instance of {@link PlatformStateModifier} * * @param accessor a source of values */ @Override - public void updatePlatformState(@NonNull final PlatformStateAccessor accessor) { + public void updatePlatformState(@NonNull final PlatformStateModifier accessor) { writablePlatformStateStore().setAllFrom(accessor); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformState.java index 477df54a4d33..2e5f19b88c86 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformState.java @@ -35,11 +35,11 @@ /** * State managed and used by the platform. - * @deprecated Implementation of {@link PlatformStateAccessor} before moving platform state into State API. This class + * @deprecated Implementation of {@link PlatformStateModifier} before moving platform state into State API. This class * should be moved to the platform test fixtures after migration to 0.54.0. */ @Deprecated(since = "0.54.0", forRemoval = true) -public class PlatformState extends PartialMerkleLeaf implements MerkleLeaf, PlatformStateAccessor { +public class PlatformState extends PartialMerkleLeaf implements MerkleLeaf, PlatformStateModifier { public static final long CLASS_ID = 0x52cef730a11cb6dfL; @@ -560,7 +560,7 @@ public void setLowestJudgeGenerationBeforeBirthRoundMode(final long lowestJudgeG * {@inheritDoc} */ @Override - public void bulkUpdate(@NonNull Consumer updater) { + public void bulkUpdate(@NonNull Consumer updater) { updater.accept(this); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformStateAccessor.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformStateAccessor.java index 38dd0fc1e5d0..07fb374c324a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformStateAccessor.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformStateAccessor.java @@ -24,7 +24,6 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; -import java.util.function.Consumer; /** * This interface represents the platform state and provide access to the state's properties. @@ -43,13 +42,6 @@ public interface PlatformStateAccessor { @NonNull SoftwareVersion getCreationSoftwareVersion(); - /** - * Set the software version of the application that created this state. - * - * @param creationVersion the creation version - */ - void setCreationSoftwareVersion(@NonNull SoftwareVersion creationVersion); - /** * Get the address book. * @return an address book @@ -57,13 +49,6 @@ public interface PlatformStateAccessor { @Nullable AddressBook getAddressBook(); - /** - * Set the address book. - * - * @param addressBook an address book - */ - void setAddressBook(@Nullable AddressBook addressBook); - /** * Get the previous address book. * @return a previous address book @@ -71,20 +56,6 @@ public interface PlatformStateAccessor { @Nullable AddressBook getPreviousAddressBook(); - /** - * Set the previous address book. - * - * @param addressBook an address book - */ - void setPreviousAddressBook(@Nullable AddressBook addressBook); - - /** - * Set the round when this state was generated. - * - * @param round a round number - */ - void setRound(long round); - /** * Get the round when this state was generated. * @@ -100,13 +71,6 @@ public interface PlatformStateAccessor { @Nullable Hash getLegacyRunningEventHash(); - /** - * Set the legacy running event hash. Used by the consensus event stream. - * - * @param legacyRunningEventHash a running hash of events - */ - void setLegacyRunningEventHash(@Nullable Hash legacyRunningEventHash); - /** * Get the consensus timestamp for this state, defined as the timestamp of the first transaction that was applied in * the round that created the state. @@ -116,14 +80,6 @@ public interface PlatformStateAccessor { @Nullable Instant getConsensusTimestamp(); - /** - * Set the consensus timestamp for this state, defined as the timestamp of the first transaction that was applied in - * the round that created the state. - * - * @param consensusTimestamp a consensus timestamp - */ - void setConsensusTimestamp(@NonNull Instant consensusTimestamp); - /** * For the oldest non-ancient round, get the lowest ancient indicator out of all of those round's judges. This is * the ancient threshold at the moment after this state's round reached consensus. All events with an ancient @@ -138,13 +94,6 @@ public interface PlatformStateAccessor { */ long getAncientThreshold(); - /** - * Sets the number of non-ancient rounds. - * - * @param roundsNonAncient the number of non-ancient rounds - */ - void setRoundsNonAncient(int roundsNonAncient); - /** * Gets the number of non-ancient rounds. * @@ -158,11 +107,6 @@ public interface PlatformStateAccessor { @Nullable ConsensusSnapshot getSnapshot(); - /** - * @param snapshot the consensus snapshot for this round - */ - void setSnapshot(@NonNull ConsensusSnapshot snapshot); - /** * Gets the time when the next freeze is scheduled to start. If null then there is no freeze scheduled. * @@ -171,15 +115,6 @@ public interface PlatformStateAccessor { @Nullable Instant getFreezeTime(); - /** - * Sets the instant after which the platform will enter FREEZING status. When consensus timestamp of a signed state - * is after this instant, the platform will stop creating events and accepting transactions. This is used to safely - * shut down the platform for maintenance. - * - * @param freezeTime an Instant in UTC - */ - void setFreezeTime(@Nullable Instant freezeTime); - /** * Gets the last freezeTime based on which the nodes were frozen. If null then there has never been a freeze. * @@ -188,13 +123,6 @@ public interface PlatformStateAccessor { @Nullable Instant getLastFrozenTime(); - /** - * Sets the last freezeTime based on which the nodes were frozen. - * - * @param lastFrozenTime the last freezeTime based on which the nodes were frozen - */ - void setLastFrozenTime(@Nullable Instant lastFrozenTime); - /** * Get the first software version where the birth round migration happened, or null if birth round migration has not * yet happened. @@ -204,14 +132,6 @@ public interface PlatformStateAccessor { @Nullable SoftwareVersion getFirstVersionInBirthRoundMode(); - /** - * Set the first software version where the birth round migration happened. - * - * @param firstVersionInBirthRoundMode the first software version where the birth round migration happened - */ - @NonNull - void setFirstVersionInBirthRoundMode(SoftwareVersion firstVersionInBirthRoundMode); - /** * Get the last round before the birth round mode was enabled, or -1 if birth round mode has not yet been enabled. * @@ -219,13 +139,6 @@ public interface PlatformStateAccessor { */ long getLastRoundBeforeBirthRoundMode(); - /** - * Set the last round before the birth round mode was enabled. - * - * @param lastRoundBeforeBirthRoundMode the last round before the birth round mode was enabled - */ - void setLastRoundBeforeBirthRoundMode(long lastRoundBeforeBirthRoundMode); - /** * Get the lowest judge generation before the birth round mode was enabled, or -1 if birth round mode has not yet * been enabled. @@ -233,18 +146,4 @@ public interface PlatformStateAccessor { * @return the lowest judge generation before the birth round mode was enabled */ long getLowestJudgeGenerationBeforeBirthRoundMode(); - - /** - * Set the lowest judge generation before the birth round mode was enabled. - * - * @param lowestJudgeGenerationBeforeBirthRoundMode the lowest judge generation before the birth round mode was - * enabled - */ - void setLowestJudgeGenerationBeforeBirthRoundMode(long lowestJudgeGenerationBeforeBirthRoundMode); - - /** - * This is a convenience method to update multiple fields in the platform state in a single operation. - * @param updater a consumer that updates the platform state - */ - void bulkUpdate(@NonNull Consumer updater); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformStateModifier.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformStateModifier.java new file mode 100644 index 000000000000..73d983a1496a --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformStateModifier.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state; + +import com.swirlds.common.crypto.Hash; +import com.swirlds.platform.consensus.ConsensusSnapshot; +import com.swirlds.platform.system.SoftwareVersion; +import com.swirlds.platform.system.address.AddressBook; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.time.Instant; +import java.util.function.Consumer; + +/** + * This interface represents the platform state and provide methods for modifying the state. + */ +public interface PlatformStateModifier extends PlatformStateAccessor { + + /** + * Set the software version of the application that created this state. + * + * @param creationVersion the creation version + */ + void setCreationSoftwareVersion(@NonNull SoftwareVersion creationVersion); + + /** + * Set the address book. + * + * @param addressBook an address book + */ + void setAddressBook(@Nullable AddressBook addressBook); + + /** + * Set the previous address book. + * + * @param addressBook an address book + */ + void setPreviousAddressBook(@Nullable AddressBook addressBook); + + /** + * Set the round when this state was generated. + * + * @param round a round number + */ + void setRound(long round); + + /** + * Set the legacy running event hash. Used by the consensus event stream. + * + * @param legacyRunningEventHash a running hash of events + */ + void setLegacyRunningEventHash(@Nullable Hash legacyRunningEventHash); + + /** + * Set the consensus timestamp for this state, defined as the timestamp of the first transaction that was applied in + * the round that created the state. + * + * @param consensusTimestamp a consensus timestamp + */ + void setConsensusTimestamp(@NonNull Instant consensusTimestamp); + + /** + * Sets the number of non-ancient rounds. + * + * @param roundsNonAncient the number of non-ancient rounds + */ + void setRoundsNonAncient(int roundsNonAncient); + + /** + * @param snapshot the consensus snapshot for this round + */ + void setSnapshot(@NonNull ConsensusSnapshot snapshot); + + /** + * Sets the instant after which the platform will enter FREEZING status. When consensus timestamp of a signed state + * is after this instant, the platform will stop creating events and accepting transactions. This is used to safely + * shut down the platform for maintenance. + * + * @param freezeTime an Instant in UTC + */ + void setFreezeTime(@Nullable Instant freezeTime); + + /** + * Sets the last freezeTime based on which the nodes were frozen. + * + * @param lastFrozenTime the last freezeTime based on which the nodes were frozen + */ + void setLastFrozenTime(@Nullable Instant lastFrozenTime); + + /** + * Set the first software version where the birth round migration happened. + * + * @param firstVersionInBirthRoundMode the first software version where the birth round migration happened + */ + void setFirstVersionInBirthRoundMode(SoftwareVersion firstVersionInBirthRoundMode); + + /** + * Set the last round before the birth round mode was enabled. + * + * @param lastRoundBeforeBirthRoundMode the last round before the birth round mode was enabled + */ + void setLastRoundBeforeBirthRoundMode(long lastRoundBeforeBirthRoundMode); + + /** + * Set the lowest judge generation before the birth round mode was enabled. + * + * @param lowestJudgeGenerationBeforeBirthRoundMode the lowest judge generation before the birth round mode was + * enabled + */ + void setLowestJudgeGenerationBeforeBirthRoundMode(long lowestJudgeGenerationBeforeBirthRoundMode); + + /** + * This is a convenience method to update multiple fields in the platform state in a single operation. + * @param updater a consumer that updates the platform state + */ + void bulkUpdate(@NonNull Consumer updater); +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java index d7b589ec798d..3b436ee1cb03 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java @@ -111,6 +111,11 @@ && getSwirldState() instanceof MerkleStateRoot merkleStateRoot) { return this; } + @Override + public void initPlatformState() { + // no initialization required + } + /** * {@inheritDoc} */ @@ -161,17 +166,15 @@ public PlatformState getWritablePlatformState() { /** * Updates the platform state. * - * @param platformStateAccessor the platform state + * @param modifier the platform state */ @Override - public void updatePlatformState(@NonNull final PlatformStateAccessor platformStateAccessor) { - if (platformStateAccessor instanceof PlatformState platformState) { + public void updatePlatformState(@NonNull final PlatformStateModifier modifier) { + if (modifier instanceof PlatformState platformState) { setChild(ChildIndices.PLATFORM_STATE, platformState); } else { throw new UnsupportedOperationException("%s implementation of %s is not supported" - .formatted( - platformStateAccessor.getClass().getSimpleName(), - PlatformStateAccessor.class.getSimpleName())); + .formatted(modifier.getClass().getSimpleName(), PlatformStateModifier.class.getSimpleName())); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java index e0fad3358917..203a9980dc34 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java @@ -118,7 +118,7 @@ public void setInitialState(@NonNull final MerkleRoot state) { /** * Handles the events in a consensus round. Implementations are responsible for invoking - * {@link SwirldState#handleConsensusRound(Round, PlatformStateAccessor)}. + * {@link SwirldState#handleConsensusRound(Round, PlatformStateModifier)}. * * @param round the round to handle */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/PbjConverter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/PbjConverter.java index 2e7137ebed27..c2e8c1c7956c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/PbjConverter.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/PbjConverter.java @@ -29,6 +29,7 @@ import com.swirlds.platform.crypto.SerializableX509Certificate; import com.swirlds.platform.state.MinimumJudgeInfo; import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; @@ -52,7 +53,7 @@ */ public final class PbjConverter { /** - * Converts an instance of {@link PlatformStateAccessor} to PBJ representation (an instance of {@link com.hedera.hapi.platform.state.PlatformState}.) + * Converts an instance of {@link PlatformStateModifier} to PBJ representation (an instance of {@link com.hedera.hapi.platform.state.PlatformState}.) * @param accessor the source of the data * @return the platform state as PBJ object */ @@ -79,7 +80,7 @@ public static com.hedera.hapi.platform.state.PlatformState toPbjPlatformState( } /** - * Converts an instance of {@link PlatformStateAccessor} to PBJ representation (an instance of {@link com.hedera.hapi.platform.state.PlatformState}.) + * Converts an instance of {@link PlatformStateModifier} to PBJ representation (an instance of {@link com.hedera.hapi.platform.state.PlatformState}.) * @param accumulator the source of the data * @return the platform state as PBJ object */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/PlatformStateValueAccumulator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/PlatformStateValueAccumulator.java index a16536de7ec3..5f7a63e28d0c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/PlatformStateValueAccumulator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/PlatformStateValueAccumulator.java @@ -20,7 +20,7 @@ import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.event.AncientMode; import com.swirlds.platform.state.MinimumJudgeInfo; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.AddressBook; import edu.umd.cs.findbugs.annotations.NonNull; @@ -36,7 +36,7 @@ * It's not meant to be used for other purposes. This class tracks the changes to the fields to prevent resetting original * platform state fields to null if they are not updated. */ -public class PlatformStateValueAccumulator implements PlatformStateAccessor { +public class PlatformStateValueAccumulator implements PlatformStateModifier { /** * The address book for this round. @@ -478,7 +478,7 @@ public boolean isLowestJudgeGenerationBeforeBirthRoundModeUpdated() { } @Override - public void bulkUpdate(@NonNull Consumer updater) { + public void bulkUpdate(@NonNull Consumer updater) { updater.accept(this); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/ReadablePlatformStateStore.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/ReadablePlatformStateStore.java index 361847c4e17b..4df3c28e8908 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/ReadablePlatformStateStore.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/ReadablePlatformStateStore.java @@ -24,7 +24,6 @@ import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.hapi.platform.state.PlatformState; -import com.swirlds.base.state.MutabilityException; import com.swirlds.common.crypto.Hash; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.state.PlatformStateAccessor; @@ -35,7 +34,6 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; -import java.util.function.Consumer; import java.util.function.Function; /** @@ -215,118 +213,6 @@ public long getLowestJudgeGenerationBeforeBirthRoundMode() { return stateOrThrow().lowestJudgeGenerationBeforeBirthRoundMode(); } - /** - * {@inheritDoc} - */ - @Override - public void setCreationSoftwareVersion(@NonNull final SoftwareVersion creationVersion) { - throw new MutabilityException("Platform state is read-only"); - } - - /** - * {@inheritDoc} - */ - @Override - public void setAddressBook(@Nullable AddressBook addressBook) { - throw new MutabilityException("Platform state is read-only"); - } - - /** - * {@inheritDoc} - */ - @Override - public void setPreviousAddressBook(@Nullable AddressBook addressBook) { - throw new MutabilityException("Platform state is read-only"); - } - - /** - * {@inheritDoc} - */ - @Override - public void setRound(long round) { - throw new MutabilityException("Platform state is read-only"); - } - - /** - * {@inheritDoc} - */ - @Override - public void setLegacyRunningEventHash(@Nullable Hash legacyRunningEventHash) { - throw new MutabilityException("Platform state is read-only"); - } - - /** - * {@inheritDoc} - */ - @Override - public void setConsensusTimestamp(@NonNull Instant consensusTimestamp) { - throw new MutabilityException("Platform state is read-only"); - } - - /** - * {@inheritDoc} - */ - @Override - public void setRoundsNonAncient(int roundsNonAncient) { - throw new MutabilityException("Platform state is read-only"); - } - - /** - * {@inheritDoc} - */ - @Override - public void setSnapshot(@NonNull ConsensusSnapshot snapshot) { - throw new MutabilityException("Platform state is read-only"); - } - - /** - * {@inheritDoc} - */ - @Override - public void setFreezeTime(@Nullable Instant freezeTime) { - throw new MutabilityException("Platform state is read-only"); - } - - /** - * {@inheritDoc} - */ - @Override - public void setLastFrozenTime(@Nullable Instant lastFrozenTime) { - throw new MutabilityException("Platform state is read-only"); - } - - /** - * {@inheritDoc} - */ - @Override - public void setFirstVersionInBirthRoundMode(SoftwareVersion firstVersionInBirthRoundMode) { - throw new MutabilityException("Platform state is read-only"); - } - - /** - * {@inheritDoc} - */ - @Override - public void setLastRoundBeforeBirthRoundMode(long lastRoundBeforeBirthRoundMode) { - throw new MutabilityException("Platform state is read-only"); - } - - /** - * {@inheritDoc} - */ - @Override - public void setLowestJudgeGenerationBeforeBirthRoundMode(long lowestJudgeGenerationBeforeBirthRoundMode) { - throw new MutabilityException("Platform state is read-only"); - } - - /** - * {@inheritDoc} - */ - @Override - public void bulkUpdate(@NonNull Consumer updater) { - throw new MutabilityException("Platform state is read-only"); - } - private @NonNull PlatformState stateOrThrow() { return requireNonNull(state.get()); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/WritablePlatformStateStore.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/WritablePlatformStateStore.java index 3d0ccd7f88fe..5bc42e25134f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/WritablePlatformStateStore.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/WritablePlatformStateStore.java @@ -27,7 +27,7 @@ import com.hedera.hapi.platform.state.PlatformState; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.crypto.Hash; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.AddressBook; @@ -43,7 +43,7 @@ /** * Extends the read-only platform state store to provide write access to the platform state. */ -public class WritablePlatformStateStore extends ReadablePlatformStateStore { +public class WritablePlatformStateStore extends ReadablePlatformStateStore implements PlatformStateModifier { private final WritableStates writableStates; private final WritableSingletonState state; @@ -75,8 +75,8 @@ public WritablePlatformStateStore(@NonNull final WritableStates writableStates) /** * Overwrite the current platform state with the provided state. */ - public void setAllFrom(@NonNull final PlatformStateAccessor accessor) { - this.update(toPbjPlatformState(accessor)); + public void setAllFrom(@NonNull final PlatformStateModifier modifier) { + this.update(toPbjPlatformState(modifier)); } private void setAllFrom(@NonNull final PlatformStateValueAccumulator accumulator) { @@ -229,7 +229,7 @@ public void setLowestJudgeGenerationBeforeBirthRoundMode(final long lowestJudgeG * {@inheritDoc} */ @Override - public void bulkUpdate(@NonNull final Consumer updater) { + public void bulkUpdate(@NonNull final Consumer updater) { final var accumulator = new PlatformStateValueAccumulator(); updater.accept(accumulator); setAllFrom(accumulator); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java index cc859ccb3260..964243f9f7ae 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java @@ -241,8 +241,7 @@ public boolean isGenesisState() { public void setSigSet(@NonNull final SigSet sigSet) { this.sigSet = Objects.requireNonNull(sigSet); signingWeight = 0; - // init - state.getWritablePlatformState(); + state.initPlatformState(); if (!isGenesisState()) { // Only non-genesis states will have signing weight final AddressBook addressBook = getAddressBook(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/SwirldState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/SwirldState.java index 7c5d973fe18a..c8a00afcecdd 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/SwirldState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/SwirldState.java @@ -18,7 +18,7 @@ import com.swirlds.common.context.PlatformContext; import com.swirlds.common.merkle.MerkleNode; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.transaction.Transaction; @@ -77,7 +77,7 @@ default void preHandle(final Event event) {} * @param round the round to apply * @param platformState the platform state */ - void handleConsensusRound(final Round round, final PlatformStateAccessor platformState); + void handleConsensusRound(final Round round, final PlatformStateModifier platformState); /** * Called by the platform after it has made all its changes to this state for the given round. diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java index 1d4be49cfff3..d3d31fa7e39f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java @@ -26,7 +26,7 @@ import com.swirlds.common.formatting.TextTable; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.state.MerkleRoot; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.state.address.AddressBookInitializer; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.system.SoftwareVersion; @@ -405,7 +405,7 @@ private static RosterEntry toRosterEntry(@NonNull final Address address, @NonNul // Update the address book with the current address book read from config.txt. // Eventually we will not do this, and only transactions will be capable of // modifying the address book. - final PlatformStateAccessor platformState = state.getWritablePlatformState(); + final PlatformStateModifier platformState = state.getWritablePlatformState(); platformState.bulkUpdate(v -> { v.setAddressBook(addressBookInitializer.getCurrentAddressBook().copy()); v.setPreviousAddressBook( diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java index 9786788ee916..5fdda8434044 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java @@ -20,7 +20,7 @@ import static org.mockito.Mockito.when; import com.swirlds.platform.state.MerkleRoot; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.events.EventConstants; import java.util.HashMap; @@ -70,7 +70,7 @@ void getMinGenNonAncientFromSignedState() { LongStream.range(1, 50).collect(HashMap::new, (m, l) -> m.put(l, l * 10), HashMap::putAll); final SignedState signedState = mock(SignedState.class); final MerkleRoot state = mock(MerkleRoot.class); - final PlatformStateAccessor platformState = mock(PlatformStateAccessor.class); + final PlatformStateModifier platformState = mock(PlatformStateModifier.class); when(signedState.getState()).thenReturn(state); when(state.getReadablePlatformState()).thenReturn(platformState); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/InternalEventValidatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/InternalEventValidatorTests.java index a17c91ea1f4c..424502eb2191 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/InternalEventValidatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/InternalEventValidatorTests.java @@ -27,16 +27,28 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.hapi.node.base.Timestamp; +import com.hedera.hapi.platform.event.EventCore; +import com.hedera.hapi.platform.event.EventDescriptor; +import com.hedera.hapi.platform.event.EventTransaction; +import com.hedera.hapi.platform.event.EventTransaction.TransactionOneOfType; +import com.hedera.hapi.platform.event.GossipEvent; +import com.hedera.pbj.runtime.OneOf; import com.swirlds.base.test.fixtures.time.FakeTime; import com.swirlds.base.time.Time; import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.crypto.DigestType; +import com.swirlds.common.crypto.SignatureType; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.Randotron; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.event.PlatformEvent; import com.swirlds.platform.eventhandling.EventConfig_; import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.test.fixtures.event.TestingEventBuilder; +import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicLong; @@ -83,15 +95,127 @@ void setup() { } @Test - @DisplayName("An event with null signature is invalid") - void nullSignatureData() { - final PlatformEvent event = Mockito.spy(new TestingEventBuilder(random).build()); - when(event.getSignature()).thenReturn(null); + @DisplayName("An event with null fields is invalid") + void nullFields() { + final PlatformEvent platformEvent = Mockito.mock(PlatformEvent.class); - assertNull(multinodeValidator.validateEvent(event)); - assertNull(singleNodeValidator.validateEvent(event)); + final Randotron r = Randotron.create(); + final GossipEvent wholeEvent = new TestingEventBuilder(r) + .setSystemTransactionCount(1) + .setAppTransactionCount(2) + .setSelfParent(new TestingEventBuilder(r).build()) + .setOtherParent(new TestingEventBuilder(r).build()) + .build() + .getGossipEvent(); + final GossipEvent noEventCore = GossipEvent.newBuilder() + .eventCore((EventCore) null) + .signature(wholeEvent.signature()) + .eventTransaction(wholeEvent.eventTransaction()) + .build(); + when(platformEvent.getGossipEvent()).thenReturn(noEventCore); + assertNull(multinodeValidator.validateEvent(platformEvent)); + assertNull(singleNodeValidator.validateEvent(platformEvent)); assertEquals(2, exitedIntakePipelineCount.get()); + + final GossipEvent noTimeCreated = GossipEvent.newBuilder() + .eventCore(EventCore.newBuilder() + .timeCreated((Timestamp) null) + .version(wholeEvent.eventCore().version()) + .build()) + .signature(wholeEvent.signature()) + .eventTransaction(wholeEvent.eventTransaction()) + .build(); + when(platformEvent.getGossipEvent()).thenReturn(noTimeCreated); + assertNull(multinodeValidator.validateEvent(platformEvent)); + assertNull(singleNodeValidator.validateEvent(platformEvent)); + assertEquals(4, exitedIntakePipelineCount.get()); + + final GossipEvent noVersion = GossipEvent.newBuilder() + .eventCore(EventCore.newBuilder() + .timeCreated(wholeEvent.eventCore().timeCreated()) + .version((SemanticVersion) null) + .build()) + .signature(wholeEvent.signature()) + .eventTransaction(wholeEvent.eventTransaction()) + .build(); + when(platformEvent.getGossipEvent()).thenReturn(noVersion); + assertNull(multinodeValidator.validateEvent(platformEvent)); + assertNull(singleNodeValidator.validateEvent(platformEvent)); + assertEquals(6, exitedIntakePipelineCount.get()); + + final GossipEvent nullTransaction = GossipEvent.newBuilder() + .eventCore(wholeEvent.eventCore()) + .signature(wholeEvent.signature()) + .eventTransaction( + List.of(new EventTransaction(new OneOf<>(TransactionOneOfType.APPLICATION_TRANSACTION, null)))) + .build(); + when(platformEvent.getGossipEvent()).thenReturn(nullTransaction); + + assertNull(multinodeValidator.validateEvent(platformEvent)); + assertNull(singleNodeValidator.validateEvent(platformEvent)); + assertEquals(8, exitedIntakePipelineCount.get()); + + final ArrayList parents = new ArrayList<>(); + parents.add(null); + final GossipEvent nullParent = GossipEvent.newBuilder() + .eventCore(EventCore.newBuilder() + .timeCreated(wholeEvent.eventCore().timeCreated()) + .version(wholeEvent.eventCore().version()) + .parents(parents) + .build()) + .signature(wholeEvent.signature()) + .eventTransaction(wholeEvent.eventTransaction()) + .build(); + when(platformEvent.getGossipEvent()).thenReturn(nullParent); + assertNull(multinodeValidator.validateEvent(platformEvent)); + assertNull(singleNodeValidator.validateEvent(platformEvent)); + assertEquals(10, exitedIntakePipelineCount.get()); + } + + @Test + @DisplayName("An event with a byte field length that is invalid") + void byteFieldLength() { + final PlatformEvent platformEvent = Mockito.mock(PlatformEvent.class); + final Randotron r = Randotron.create(); + final GossipEvent validEvent = new TestingEventBuilder(r) + .setSystemTransactionCount(1) + .setAppTransactionCount(2) + .setSelfParent(new TestingEventBuilder(r).build()) + .setOtherParent(new TestingEventBuilder(r).build()) + .build() + .getGossipEvent(); + + final GossipEvent shortSignature = GossipEvent.newBuilder() + .eventCore(validEvent.eventCore()) + .signature(validEvent.signature().getBytes(1, SignatureType.RSA.signatureLength() - 2)) + .eventTransaction(validEvent.eventTransaction()) + .build(); + when(platformEvent.getGossipEvent()).thenReturn(shortSignature); + assertNull(multinodeValidator.validateEvent(platformEvent)); + assertNull(singleNodeValidator.validateEvent(platformEvent)); + assertEquals(2, exitedIntakePipelineCount.get()); + + final GossipEvent shortDescriptorHash = GossipEvent.newBuilder() + .eventCore(EventCore.newBuilder() + .timeCreated(validEvent.eventCore().timeCreated()) + .version(validEvent.eventCore().version()) + .parents(EventDescriptor.newBuilder() + .hash(validEvent + .eventCore() + .parents() + .getFirst() + .hash() + .getBytes(1, DigestType.SHA_384.digestLength() - 2)) + .build()) + .build()) + .signature(validEvent.signature()) + .eventTransaction(validEvent.eventTransaction()) + .build(); + when(platformEvent.getGossipEvent()).thenReturn(shortDescriptorHash); + assertNull(multinodeValidator.validateEvent(platformEvent)); + assertNull(singleNodeValidator.validateEvent(platformEvent)); + assertEquals(4, exitedIntakePipelineCount.get()); } @Test diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java index d14a1e74ec91..3a4a01a707fe 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java @@ -25,7 +25,7 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.state.MerkleRoot; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.service.PlatformStateValueAccumulator; import com.swirlds.platform.system.BasicSoftwareVersion; @@ -42,7 +42,7 @@ * A helper class for testing the {@link DefaultTransactionHandler}. */ public class TransactionHandlerTester { - private final PlatformStateAccessor platformState; + private final PlatformStateModifier platformState; private final SwirldStateManager swirldStateManager; private final DefaultTransactionHandler defaultTransactionHandler; private final List submittedActions = new ArrayList<>(); @@ -87,9 +87,9 @@ public DefaultTransactionHandler getTransactionHandler() { } /** - * @return the {@link PlatformStateAccessor} used by this tester + * @return the {@link PlatformStateModifier} used by this tester */ - public PlatformStateAccessor getPlatformState() { + public PlatformStateModifier getPlatformState() { return platformState; } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventRecoveryWorkflowTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventRecoveryWorkflowTests.java index ce8b89c69b2a..4490711621b8 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventRecoveryWorkflowTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventRecoveryWorkflowTests.java @@ -40,7 +40,7 @@ import com.swirlds.platform.event.PlatformEvent; import com.swirlds.platform.recovery.emergencyfile.EmergencyRecoveryFile; import com.swirlds.platform.recovery.internal.StreamedRound; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.events.CesEvent; @@ -107,7 +107,7 @@ void isFreezeStateTest() { @Test @DisplayName("applyTransactions() Test") void applyTransactionsTest() { - final PlatformStateAccessor platformState = mock(PlatformStateAccessor.class); + final PlatformStateModifier platformState = mock(PlatformStateModifier.class); final List events = new ArrayList<>(); for (int i = 0; i < 100; i++) { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/BirthRoundStateMigrationTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/BirthRoundStateMigrationTests.java index d7e993850fb8..0bcf82a9dde8 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/BirthRoundStateMigrationTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/BirthRoundStateMigrationTests.java @@ -113,12 +113,10 @@ void alreadyMigratedTest() { ; final SoftwareVersion newSoftwareVersion = createNextVersion(previousSoftwareVersion); - signedState - .getState() - .getWritablePlatformState() - .setLastRoundBeforeBirthRoundMode(signedState.getRound() - 100); - signedState.getState().getWritablePlatformState().setFirstVersionInBirthRoundMode(previousSoftwareVersion); - signedState.getState().getWritablePlatformState().setLowestJudgeGenerationBeforeBirthRoundMode(100); + PlatformStateModifier writablePlatformState = signedState.getState().getWritablePlatformState(); + writablePlatformState.setLastRoundBeforeBirthRoundMode(signedState.getRound() - 100); + writablePlatformState.setFirstVersionInBirthRoundMode(previousSoftwareVersion); + writablePlatformState.setLowestJudgeGenerationBeforeBirthRoundMode(100); rehashTree(signedState.getState()); final Hash originalHash = signedState.getState().getHash(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/MerkleStateRootTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/MerkleStateRootTest.java index f8e2cd6c5a9a..2378e3c7ed94 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/MerkleStateRootTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/MerkleStateRootTest.java @@ -779,7 +779,7 @@ final class ConsensusRoundTest { @DisplayName("Notifications are sent to onHandleConsensusRound when handleConsensusRound is called") void handleConsensusRoundCallback() { final var round = Mockito.mock(Round.class); - final var platformState = Mockito.mock(PlatformStateAccessor.class); + final var platformState = Mockito.mock(PlatformStateModifier.class); final var state = new MerkleStateRoot(lifecycles, softwareVersionSupplier); state.handleConsensusRound(round, platformState); @@ -797,7 +797,7 @@ void originalLosesConsensusRoundCallbackAfterCopy() { // The original no longer has the listener final var round = Mockito.mock(Round.class); - final var platformState = Mockito.mock(PlatformStateAccessor.class); + final var platformState = Mockito.mock(PlatformStateModifier.class); assertThrows(MutabilityException.class, () -> stateRoot.handleConsensusRound(round, platformState)); // But the copy does @@ -933,7 +933,7 @@ void platformStateIsRegisteredByDefault() { @Test @DisplayName("Test access to the platform state") void testAccessToPlatformStateData() { - PlatformStateAccessor randomPlatformState = randomPlatformState(stateRoot.getWritablePlatformState()); + PlatformStateModifier randomPlatformState = randomPlatformState(stateRoot.getWritablePlatformState()); stateRoot.updatePlatformState(randomPlatformState); ReadableSingletonState readableSingletonState = stateRoot .getReadableStates(PlatformStateService.NAME) @@ -949,12 +949,12 @@ void testAccessToPlatformStateData() { @Test @DisplayName("Test update of the platform state") void testUpdatePlatformStateData() { - PlatformStateAccessor randomPlatformState = randomPlatformState(stateRoot.getWritablePlatformState()); + PlatformStateModifier randomPlatformState = randomPlatformState(stateRoot.getWritablePlatformState()); stateRoot.updatePlatformState(randomPlatformState); WritableStates writableStates = stateRoot.getWritableStates(PlatformStateService.NAME); WritableSingletonState writableSingletonState = writableStates.getSingleton(V0540PlatformStateSchema.PLATFORM_STATE_KEY); - PlatformStateAccessor newPlatformState = randomPlatformState(stateRoot.getWritablePlatformState()); + PlatformStateModifier newPlatformState = randomPlatformState(stateRoot.getWritablePlatformState()); writableSingletonState.put(toPbjPlatformState(newPlatformState)); ((CommittableWritableStates) writableStates).commit(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateTests.java index 4db93d4e6f51..47b3146121b7 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateTests.java @@ -64,7 +64,7 @@ private MerkleStateRoot buildMockState(final Runnable reserveCallback, final Run final MerkleStateRoot state = spy(new MerkleStateRoot( FAKE_MERKLE_STATE_LIFECYCLES, version -> new BasicSoftwareVersion(version.major()))); - final PlatformStateAccessor platformState = new PlatformState(); + final PlatformStateModifier platformState = new PlatformState(); platformState.setAddressBook(mock(AddressBook.class)); when(state.getWritablePlatformState()).thenReturn(platformState); if (reserveCallback != null) { @@ -208,9 +208,8 @@ void noGarbageCollectorTest() { void alternateConstructorReservationsTest() { final MerkleRoot state = spy(new MerkleStateRoot( FAKE_MERKLE_STATE_LIFECYCLES, version -> new BasicSoftwareVersion(version.major()))); - final PlatformStateAccessor platformState = mock(PlatformStateAccessor.class); - // init state first - state.getWritablePlatformState(); + final PlatformStateModifier platformState = mock(PlatformStateModifier.class); + state.initPlatformState(); when(state.getReadablePlatformState()).thenReturn(platformState); when(platformState.getRound()).thenReturn(0L); final SignedState signedState = new SignedState( diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerTests.java index adcd5f56b00b..2a001ba1020f 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerTests.java @@ -123,7 +123,7 @@ private static MerkleRoot newState() { final MerkleStateRoot state = new MerkleStateRoot(FAKE_MERKLE_STATE_LIFECYCLES, version -> new BasicSoftwareVersion(version.major())); - final PlatformStateAccessor platformState = mock(PlatformStateAccessor.class); + final PlatformStateModifier platformState = mock(PlatformStateModifier.class); when(platformState.getCreationSoftwareVersion()).thenReturn(new BasicSoftwareVersion(nextInt(1, 100))); state.updatePlatformState(platformState); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/PbjConverterTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/PbjConverterTest.java index 54dd824eea0d..3bd4386a33fc 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/PbjConverterTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/PbjConverterTest.java @@ -39,7 +39,7 @@ import com.swirlds.platform.crypto.CryptoStatic; import com.swirlds.platform.crypto.SerializableX509Certificate; import com.swirlds.platform.state.MinimumJudgeInfo; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.Address; @@ -68,7 +68,7 @@ void setUp() { @Test void testToPbjPlatformState() { - final PlatformStateAccessor platformState = randomPlatformState(randotron); + final PlatformStateModifier platformState = randomPlatformState(randotron); final com.hedera.hapi.platform.state.PlatformState pbjPlatformState = PbjConverter.toPbjPlatformState(platformState); @@ -467,7 +467,7 @@ void testToPbjPlatformState_acc_updateAll() { assertEquals(toPbjAddressBook(newValue.getAddressBook()), pbjState.addressBook()); } - static PlatformStateAccessor randomPlatformState(Randotron randotron) { + static PlatformStateModifier randomPlatformState(Randotron randotron) { final PlatformStateValueAccumulator platformState = new PlatformStateValueAccumulator(); platformState.setCreationSoftwareVersion(randomSoftwareVersion()); platformState.setRoundsNonAncient(nextInt()); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleTestingToolState.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleTestingToolState.java index 00dfa4048baa..6ab2196681e3 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleTestingToolState.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleTestingToolState.java @@ -22,7 +22,7 @@ import com.swirlds.common.merkle.impl.PartialMerkleLeaf; import com.swirlds.common.utility.NonCryptographicHashing; import com.swirlds.platform.state.MerkleRoot; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.state.State; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SwirldState; @@ -82,7 +82,7 @@ public int getVersion() { * {@inheritDoc} */ @Override - public void handleConsensusRound(@NonNull final Round round, @NonNull final PlatformStateAccessor platformState) { + public void handleConsensusRound(@NonNull final Round round, @NonNull final PlatformStateModifier platformState) { state = NonCryptographicHashing.hash64( state, round.getRoundNum(), diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/BlockingSwirldState.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/BlockingSwirldState.java index 20ccc2648240..b02b3496446f 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/BlockingSwirldState.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/BlockingSwirldState.java @@ -26,7 +26,7 @@ import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.MerkleStateRoot; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SwirldState; @@ -77,7 +77,7 @@ private BlockingSwirldState(final BlockingSwirldState that) { } @Override - public void handleConsensusRound(final Round round, final PlatformStateAccessor platformState) { + public void handleConsensusRound(final Round round, final PlatformStateModifier platformState) { // intentionally does nothing } diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/RandomSignedStateGenerator.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/RandomSignedStateGenerator.java index 1d6b0487ef31..4e0a63edf70c 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/RandomSignedStateGenerator.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/RandomSignedStateGenerator.java @@ -37,7 +37,7 @@ import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.MerkleStateRoot; import com.swirlds.platform.state.MinimumJudgeInfo; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.SoftwareVersion; @@ -186,7 +186,7 @@ public SignedState build() { consensusSnapshotInstance = consensusSnapshot; } - final PlatformStateAccessor platformState = stateInstance.getWritablePlatformState(); + final PlatformStateModifier platformState = stateInstance.getWritablePlatformState(); platformState.bulkUpdate(v -> { v.setSnapshot(consensusSnapshotInstance); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/PlatformStateUtils.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/PlatformStateUtils.java index 634a3c8bd73c..2732a4ca36b7 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/PlatformStateUtils.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/PlatformStateUtils.java @@ -22,7 +22,7 @@ import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.state.MinimumJudgeInfo; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; @@ -38,14 +38,14 @@ private PlatformStateUtils() {} /** * Generate a randomized PlatformState object. Values contained internally may be nonsensical. */ - public static PlatformStateAccessor randomPlatformState(PlatformStateAccessor platformState) { + public static PlatformStateModifier randomPlatformState(PlatformStateModifier platformState) { return randomPlatformState(new Random(), platformState); } /** * Generate a randomized PlatformState object. Values contained internally may be nonsensical. */ - public static PlatformStateAccessor randomPlatformState(final Random random, PlatformStateAccessor platformState) { + public static PlatformStateModifier randomPlatformState(final Random random, PlatformStateModifier platformState) { final AddressBook addressBook = RandomAddressBookBuilder.create(random) .withSize(4) .withWeightDistributionStrategy(WeightDistributionStrategy.BALANCED)