From d7c1e752c87e99dcd5bd85f44fc664ae83f51d48 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Thu, 28 Mar 2019 19:12:23 +0800 Subject: [PATCH] rebase crypto-spi; --- README.md | 565 ++++++----- README.zip | Bin 0 -> 360457 bytes docs/readme.zip | Bin 285635 -> 360457 bytes source/base/pom.xml | 8 + .../{base/data => consts}/TypeCodes.java | 4 +- .../jd/blockchain/provider/NamedProvider.java | 20 + .../com/jd/blockchain/provider/Provider.java | 11 + .../provider/ProviderException.java | 11 + .../blockchain/provider/ProviderManager.java | 247 +++++ .../BftsmartClientIncomingConfig.java | 2 +- .../BftsmartClientIncomingSettings.java | 4 +- .../bftsmart/BftsmartCommitBlockSettings.java | 2 +- .../bftsmart/BftsmartConsensusSettings.java | 2 +- .../BftsmartConsensusSettingsBuilder.java | 2 +- .../bftsmart/BftsmartNodeConfig.java | 2 +- .../bftsmart/BftsmartNodeSettings.java | 4 +- .../bftsmart/client/BftsmartClientConfig.java | 2 +- .../client/BftsmartClientIdentification.java | 6 +- .../BftsmartConsensusClientFactory.java | 2 + .../client/BftsmartMessageService.java | 20 +- .../bftsmart/service/BftsmartNodeServer.java | 392 ++++++- .../consensus/bftsmart/proxyClientTest.java | 2 +- .../consensus/ClientIdentification.java | 4 +- .../consensus/ClientIdentifications.java | 2 +- .../consensus/ClientIncomingSettings.java | 2 +- .../consensus/ConsensusSettings.java | 2 +- .../jd/blockchain/consensus/NodeSettings.java | 4 +- .../consensus/action/ActionRequest.java | 2 +- .../consensus/action/ActionResponse.java | 2 +- .../consensus/client/ClientSettings.java | 2 +- .../mq/MsgQueueConsensusSettingsBuilder.java | 2 +- .../mq/client/MsgQueueClientFactory.java | 2 +- .../client/MsgQueueClientIdentification.java | 2 +- .../mq/config/MsgQueueClientConfig.java | 2 +- .../config/MsgQueueClientIncomingConfig.java | 2 +- .../mq/config/MsgQueueConsensusConfig.java | 2 +- .../mq/config/MsgQueueNodeConfig.java | 2 +- .../MsgQueueConsensusManageService.java | 2 +- .../mq/settings/MsgQueueBlockSettings.java | 2 +- .../MsgQueueClientIncomingSettings.java | 4 +- .../settings/MsgQueueConsensusSettings.java | 2 +- .../mq/settings/MsgQueueNetworkSettings.java | 2 +- .../mq/settings/MsgQueueNodeSettings.java | 2 +- source/contract/contract-compile/pom.xml | 11 +- .../blockchain/contract/AssetContract1.java | 55 +- .../blockchain/contract/AssetContract2.java | 5 +- .../blockchain/contract/AssetContract3.java | 4 +- .../blockchain/contract/AssetContract4.java | 10 +- .../blockchain/contract/AssetContract5.java | 4 +- .../blockchain/contract/ContractEngine.java | 10 +- .../contract/ContractServiceProvider.java | 2 +- .../contract/jvm/JavaContractCode.java | 17 +- .../com/jd/blockchain/CheckImportsMojo.java | 2 +- .../com/jd/blockchain/ContractDeployMojo.java | 60 +- .../model/ContractAppLifecycleAwire.java | 2 +- .../contract/model/ContractDeployExeUtil.java | 18 +- .../contract/model/ContractEventContext.java | 16 +- .../contract/model/ContractRuntimeAwire.java | 3 +- .../contract/model/ErrorCodeEnum.java | 12 +- .../contract/model/EventHandle.java | 2 +- .../contract/model/EventProcessingAwire.java | 16 +- source/crypto/crypto-adv/pom.xml | 45 +- .../com/jd/blockchain/crypto/ecvrf/VRF.java | 5 +- .../jd/blockchain/crypto/mpc/EqualVerify.java | 20 +- .../jd/blockchain/crypto/mpc/IntCompare.java | 62 +- .../jd/blockchain/crypto/mpc/MultiSum.java | 14 +- .../crypto/paillier/PaillierUtils.java | 9 +- .../jd/blockchain/crypto/ecvrf/VRFTest.java | 3 +- source/crypto/crypto-classic/pom.xml | 20 + .../classic/AESEncryptionFunction.java | 216 ++++ .../service/classic/ClassicCryptoService.java | 62 ++ .../classic/ECDSASignatureFunction.java | 67 ++ .../classic}/ED25519SignatureFunction.java | 71 +- .../classic/JVMSecureRandomFunction.java | 54 + .../classic}/RIPEMD160HashFunction.java | 20 +- .../service/classic/RSACryptoFunction.java} | 34 +- .../service/classic}/SHA256HashFunction.java | 21 +- .../crypto/utils/classic/ECDSAUtils.java | 10 + .../crypto/utils/classic/RSAUtils.java | 10 + .../com.jd.blockchain.crypto.CryptoService | 1 + .../AsymmtricCryptographyImplTest.java | 953 ++++++++++++++++++ .../crypto/hash/HashCryptographyImplTest.java | 334 ++++++ .../SymmetricCryptographyImplTest.java | 471 +++++++++ .../jd/blockchain/crypto/AddressEncoding.java | 5 +- .../jd/blockchain/crypto/AddressVersion.java | 11 + .../crypto/{base => }/BaseCryptoBytes.java | 16 +- .../jd/blockchain/crypto/BaseCryptoKey.java | 46 + .../jd/blockchain/crypto/CryptoAlgorithm.java | 174 ++-- .../crypto/CryptoAlgorithmDefinition.java | 118 +++ .../crypto/CryptoAlgorithmType.java | 48 +- .../crypto/CryptoAlgorithm_Enum.java | 150 +++ .../com/jd/blockchain/crypto/CryptoBytes.java | 6 +- .../jd/blockchain/crypto/CryptoFactory.java | 30 +- .../crypto/CryptoKeyPairGenerator.java | 2 - .../jd/blockchain/crypto/CryptoKeyType.java | 8 +- .../jd/blockchain/crypto/CryptoService.java | 9 + .../crypto/CryptoServiceProviders.java | 213 ++++ .../com/jd/blockchain/crypto/CryptoUtils.java | 124 ++- .../crypto/{asymmetric => }/PrivKey.java | 12 +- .../crypto/{asymmetric => }/PubKey.java | 15 +- .../jd/blockchain/crypto/RandomFunction.java | 7 + .../jd/blockchain/crypto/RandomGenerator.java | 9 + .../crypto/{symmetric => }/SymmetricKey.java | 17 +- .../asymmetric/AsymmetricCiphertext.java | 6 +- .../asymmetric/AsymmetricCryptography.java | 166 +-- .../AsymmetricEncryptionFunction.java | 2 + .../crypto/asymmetric/CryptoKeyPair.java | 3 + .../crypto/asymmetric/SignatureDigest.java | 6 +- .../crypto/asymmetric/SignatureFunction.java | 2 + .../blockchain/crypto/base/BaseCryptoKey.java | 62 -- .../crypto/hash/HashCryptography.java | 100 +- .../jd/blockchain/crypto/hash/HashDigest.java | 4 +- .../impl/AsymmtricCryptographyImpl.java | 218 ---- .../crypto/impl/CryptoFactoryImpl.java | 30 - .../crypto/impl/HashCryptographyImpl.java | 84 -- .../impl/SymmetricCryptographyImpl.java | 101 -- .../AESSymmetricEncryptionFunction.java | 142 --- .../JNIED25519SignatureFunction.java | 140 --- .../jni/hash/JNIRIPEMD160HashFunction.java | 53 - .../impl/jni/hash/JNISHA256HashFunction.java | 53 - .../impl/sm/asymmetric/SM2CryptoFunction.java | 192 ---- .../SM4SymmetricEncryptionFunction.java | 145 --- .../jniutils/asymmetric/JNIED25519Utils.java | 37 - .../jniutils/hash/JNIMBSHA256Utils.java | 24 - .../jniutils/hash/JNIRIPEMD160Utils.java | 30 - .../crypto/jniutils/hash/JNISHA256Utils.java | 30 - .../ByteArrayObjectDeserializer.java} | 41 +- .../serialize/ByteArrayObjectSerializer.java | 62 ++ .../crypto/symmetric/SymmetricCiphertext.java | 4 +- .../symmetric/SymmetricCryptography.java | 69 +- .../SymmetricEncryptionFunction.java | 6 +- .../AsymmtricCryptographyImplTest.java | 946 ----------------- .../crypto/hash/HashCryptographyImplTest.java | 333 ------ .../crypto/jniutils/JNIED25519UtilsTest.java | 123 --- .../crypto/jniutils/JNIMBSHA256UtilsTest.java | 111 -- .../jniutils/JNIRIPEMD160UtilsTest.java | 41 - .../crypto/jniutils/JNISHA256UtilsTest.java | 41 - .../MyAsymmetricEncryptionTest.java | 55 - .../crypto/performance/MyHashTest.java | 62 -- .../crypto/performance/MySignatureTest.java | 83 -- .../MySymmetricEncryptionTest.java | 91 -- .../crypto/smutils/SM3UtilsTest.java | 33 - .../crypto/smutils/SM4UtilsTest.java | 66 -- .../SymmetricCryptographyImplTest.java | 471 --------- source/crypto/crypto-impl/pom.xml | 20 + .../impl/AsymmtricCryptographyImpl.java | 220 ++++ .../crypto/impl/CryptoFactoryImpl.java | 30 + .../crypto/impl/HashCryptographyImpl.java | 84 ++ .../impl/SymmetricCryptographyImpl.java | 101 ++ .../MyAsymmetricEncryptionTest.java | 55 + .../crypto/performance/MyHashTest.java | 62 ++ .../crypto/performance/MySignatureTest.java | 83 ++ .../MySymmetricEncryptionTest.java | 92 ++ source/crypto/crypto-sm/pom.xml | 20 + .../crypto/service/sm/SM2CryptoFunction.java | 212 ++++ .../crypto/service/sm}/SM3HashFunction.java | 29 +- .../service/sm/SM4EncryptionFunction.java | 148 +++ .../crypto/service/sm/SMCryptoService.java | 47 + .../blockchain/crypto/utils/sm}/SM2Utils.java | 90 +- .../blockchain/crypto/utils/sm}/SM3Utils.java | 5 +- .../blockchain/crypto/utils/sm}/SM4Utils.java | 22 +- .../com.jd.blockchain.crypto.CryptoService | 1 + .../crypto/smutils/SM2UtilsTest.java | 128 ++- .../crypto/smutils/SM3UtilsTest.java | 71 ++ .../crypto/smutils/SM4UtilsTest.java | 137 +++ source/crypto/pom.xml | 7 +- .../jd/blockchain/boot/peer/PeerBooter.java | 2 +- .../gateway/GatewayServerBooter.java | 4 +- .../gateway/web/BlockBrowserController.java | 2 +- .../web/GatewayWebServerConfigurer.java | 21 +- .../main/resources/application-gw.properties | 1 - source/ledger/ledger-core/pom.xml | 14 + .../ledger/core/AccountAccessPolicy.java | 2 +- .../jd/blockchain/ledger/core/AccountSet.java | 2 +- .../blockchain/ledger/core/BaseAccount.java | 2 +- .../ledger/core/ContractAccount.java | 2 +- .../ledger/core/ContractAccountSet.java | 2 +- .../blockchain/ledger/core/DataAccount.java | 18 +- .../ledger/core/DataAccountSet.java | 2 +- .../ledger/core/LedgerInitDecision.java | 2 +- .../ledger/core/LedgerInitPermission.java | 2 +- .../ledger/core/LedgerMetadata.java | 2 +- .../blockchain/ledger/core/LedgerSetting.java | 2 +- .../ledger/core/ParticipantCertData.java | 2 +- .../blockchain/ledger/core/UserAccount.java | 2 +- .../ledger/core/UserAccountSet.java | 2 +- .../core/impl/LedgerTransactionData.java | 1 - .../ledger/core/impl/OpeningAccessPolicy.java | 2 +- .../jd/blockchain/ledger/AccountSetTest.java | 3 +- .../jd/blockchain/ledger/BaseAccountTest.java | 3 +- .../blockchain/ledger/LedgerAccountTest.java | 8 +- .../ledger/LedgerAdminAccountTest.java | 3 +- .../ledger/LedgerBlockImplTest.java | 35 +- .../blockchain/ledger/LedgerEditerTest.java | 33 +- .../ledger/LedgerInitOperationTest.java | 3 +- .../ledger/LedgerInitSettingTest.java | 3 +- .../blockchain/ledger/LedgerManagerTest.java | 13 +- .../blockchain/ledger/LedgerMetaDataTest.java | 18 +- .../jd/blockchain/ledger/LedgerTestUtils.java | 9 +- .../ledger/LedgerTransactionDataTest.java | 43 +- .../blockchain/ledger/MerkleDataSetTest.java | 9 +- .../jd/blockchain/ledger/MerkleTreeTest.java | 16 +- .../ledger/TransactionStagedSnapshotTest.java | 25 +- source/ledger/ledger-model/pom.xml | 10 + .../jd/blockchain/ledger/AccountHeader.java | 4 +- .../com/jd/blockchain/ledger/BlockBody.java | 2 +- .../blockchain/ledger/BlockchainIdentity.java | 4 +- .../ledger/BlockchainIdentityData.java | 2 +- .../ledger/BlockchainKeyGenerator.java | 3 - .../blockchain/ledger/BlockchainKeyPair.java | 4 +- .../com/jd/blockchain/ledger/BytesValue.java | 2 +- .../ledger/ContractCodeDeployOperation.java | 2 +- .../ledger/ContractEventSendOperation.java | 2 +- .../jd/blockchain/ledger/CryptoSetting.java | 2 +- .../ledger/DataAccountKVSetOperation.java | 2 +- .../ledger/DataAccountRegisterOperation.java | 2 +- .../com/jd/blockchain/ledger/DataType.java | 2 +- .../blockchain/ledger/DigitalSignature.java | 2 +- .../ledger/DigitalSignatureBody.java | 4 +- .../jd/blockchain/ledger/EndpointRequest.java | 2 +- .../com/jd/blockchain/ledger/HashObject.java | 2 +- .../com/jd/blockchain/ledger/LedgerBlock.java | 2 +- .../blockchain/ledger/LedgerDataSnapshot.java | 2 +- .../ledger/LedgerInitOperation.java | 2 +- .../blockchain/ledger/LedgerInitSetting.java | 2 +- .../blockchain/ledger/LedgerTransaction.java | 2 +- .../com/jd/blockchain/ledger/NodeRequest.java | 2 +- .../com/jd/blockchain/ledger/Operation.java | 2 +- .../jd/blockchain/ledger/ParticipantNode.java | 4 +- .../com/jd/blockchain/ledger/Transaction.java | 2 +- .../blockchain/ledger/TransactionContent.java | 2 +- .../ledger/TransactionContentBody.java | 2 +- .../blockchain/ledger/TransactionRequest.java | 2 +- .../ledger/TransactionResponse.java | 2 +- .../blockchain/ledger/TransactionState.java | 2 +- .../com/jd/blockchain/ledger/UserInfo.java | 4 +- .../ledger/UserRegisterOperation.java | 2 +- .../ledger/data/ConsensusParticipantData.java | 2 +- .../ledger/data/CryptoKeyEncoding.java | 6 +- .../ledger/data/DigitalSignatureBlob.java | 2 +- .../jd/blockchain/ledger/data/PreparedTx.java | 3 +- .../ledger/data/TxRequestBuilder.java | 4 +- .../ledger/data/AddressEncodingTest.java | 2 +- .../ContractCodeDeployOpTemplateTest.java | 2 +- .../DataAccountRegisterOpTemplateTest.java | 2 +- .../ledger/data/DigitalSignatureBlobTest.java | 2 +- .../ledger/data/TxRequestMessageTest.java | 2 +- .../data/UserRegisterOpTemplateTest.java | 2 +- source/ledger/ledger-rpc/pom.xml | 24 +- .../ByteArrayObjectJsonSerializer.java | 120 --- .../web/serializes/ByteArrayObjectUtil.java | 41 - .../peer/web/PeerWebServerConfigurer.java | 21 +- source/pom.xml | 2 +- .../jd/blockchain/runtime/AbstractModule.java | 3 +- .../runtime/modular/ModularFactory.java | 2 +- .../runtime/modular/MuduleClassLoader.java | 1 - source/sdk/sdk-base/pom.xml | 9 +- .../HashDigestsResponseConverter.java | 2 +- .../sdk/client/ClientOperationUtil.java | 273 ----- .../sdk/client/GatewayServiceFactory.java | 44 +- source/sdk/sdk-mq/pom.xml | 45 + source/sdk/sdk-samples/pom.xml | 7 - .../sdk/samples/SDKDemo_Contract.java | 4 +- .../sdk/samples/SDKDemo_DataAccount.java | 10 +- .../sdk/samples/SDKDemo_InsertData.java | 4 +- .../sdk/samples/SDKDemo_Params.java | 4 +- .../sdk/samples/SDKDemo_RegisterTest.java | 4 +- .../sdk/samples/SDKDemo_RegisterUser.java | 4 +- .../blockchain/sdk/samples/SDKDemo_User.java | 9 +- .../SDK_GateWay_BatchInsertData_Test_.java | 77 +- .../SDK_GateWay_ContractDeploy_Test_.java | 102 -- .../test/SDK_GateWay_ContractExec_Test_.java | 104 -- .../test/SDK_GateWay_DataAccount_Test_.java | 75 +- .../test/SDK_GateWay_InsertData_Test_.java | 75 +- .../sdk/test/SDK_GateWay_KeyPair_Para.java | 4 +- .../sdk/test/SDK_GateWay_Query_Test_.java | 269 +++-- .../sdk/test/SDK_GateWay_User_Test_.java | 95 +- source/test/test-integration/pom.xml | 9 +- .../jd/blockchain/intgr/IntegrationTest.java | 4 +- .../intgr/consensus/ConsensusTest.java | 2 +- .../intgr/perf/GlobalPerformanceTest.java | 2 +- .../intgr/perf/LedgerInitializeTest.java | 4 +- .../intgr/perf/LedgerInitializeWebTest.java | 4 +- .../intgr/perf/LedgerPerformanceTest.java | 2 +- .../com/jd/blockchain/intgr/perf/Utils.java | 2 +- .../jd/blockchain/intgr/IntegrationBase.java | 21 - .../blockchain/intgr/IntegrationBaseTest.java | 2 +- .../jd/blockchain/intgr/IntegrationTest2.java | 2 +- .../intgr/IntegrationTest4Bftsmart.java | 4 +- .../blockchain/intgr/IntegrationTest4MQ.java | 6 +- .../intgr/IntegrationTestAll4Redis.java | 4 +- .../intgr/IntegrationTestDataAccount.java | 4 +- .../batch/bftsmart/BftsmartLedgerInit.java | 4 +- .../initializer/LedgerInitSettingTest.java | 2 +- .../initializer/LedgerInitializeTest.java | 4 +- .../LedgerInitializeWeb4Nodes.java | 4 +- .../LedgerInitializeWeb4SingleStepsTest.java | 4 +- .../ledger/LedgerBlockGeneratingTest.java | 2 +- .../capability/service/SettingsInit.java | 4 +- .../tools/initializer/LedgerInitCommand.java | 4 +- .../tools/initializer/LedgerInitProcess.java | 2 +- .../initializer/LedgerInitProperties.java | 2 +- .../web/LedgerInitializeWebController.java | 4 +- .../tools/keygen/KeyGenCommand.java | 4 +- source/tools/tools-package/pom.xml | 45 + .../jd/blockchain/utils/io/BytesUtils.java | 130 ++- .../jd/blockchain/utils/io/NumberMask.java | 7 - .../blockchain/utils/security/AESUtils.java | 18 +- .../http/agent/HttpServiceAgentTest.java | 35 +- 309 files changed, 7208 insertions(+), 6065 deletions(-) create mode 100644 README.zip rename source/base/src/main/java/com/jd/blockchain/{base/data => consts}/TypeCodes.java (97%) create mode 100644 source/base/src/main/java/com/jd/blockchain/provider/NamedProvider.java create mode 100644 source/base/src/main/java/com/jd/blockchain/provider/Provider.java create mode 100644 source/base/src/main/java/com/jd/blockchain/provider/ProviderException.java create mode 100644 source/base/src/main/java/com/jd/blockchain/provider/ProviderManager.java create mode 100644 source/crypto/crypto-classic/pom.xml create mode 100644 source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/AESEncryptionFunction.java create mode 100644 source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/ClassicCryptoService.java create mode 100644 source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/ECDSASignatureFunction.java rename source/crypto/{crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/asymmetric => crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic}/ED25519SignatureFunction.java (65%) create mode 100644 source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/JVMSecureRandomFunction.java rename source/crypto/{crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/hash => crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic}/RIPEMD160HashFunction.java (66%) rename source/crypto/{crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/asymmetric/ECDSASignatureFunction.java => crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/RSACryptoFunction.java} (56%) rename source/crypto/{crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/hash => crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic}/SHA256HashFunction.java (67%) create mode 100644 source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/utils/classic/ECDSAUtils.java create mode 100644 source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/utils/classic/RSAUtils.java create mode 100644 source/crypto/crypto-classic/src/main/resources/META-INF/services/com.jd.blockchain.crypto.CryptoService create mode 100644 source/crypto/crypto-classic/src/test/java/test/com/jd/blockchain/crypto/asymmetric/AsymmtricCryptographyImplTest.java create mode 100644 source/crypto/crypto-classic/src/test/java/test/com/jd/blockchain/crypto/hash/HashCryptographyImplTest.java create mode 100644 source/crypto/crypto-classic/src/test/java/test/com/jd/blockchain/crypto/symmetric/SymmetricCryptographyImplTest.java rename source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/{base => }/BaseCryptoBytes.java (71%) create mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/BaseCryptoKey.java create mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithmDefinition.java create mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithm_Enum.java create mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoService.java create mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoServiceProviders.java rename source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/{asymmetric => }/PrivKey.java (54%) rename source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/{asymmetric => }/PubKey.java (53%) create mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/RandomFunction.java create mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/RandomGenerator.java rename source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/{symmetric => }/SymmetricKey.java (60%) delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/base/BaseCryptoKey.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/AsymmtricCryptographyImpl.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/CryptoFactoryImpl.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/HashCryptographyImpl.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/SymmetricCryptographyImpl.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/symmetric/AESSymmetricEncryptionFunction.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/jni/asymmetric/JNIED25519SignatureFunction.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/jni/hash/JNIRIPEMD160HashFunction.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/jni/hash/JNISHA256HashFunction.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/sm/asymmetric/SM2CryptoFunction.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/sm/symmetric/SM4SymmetricEncryptionFunction.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/asymmetric/JNIED25519Utils.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/hash/JNIMBSHA256Utils.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/hash/JNIRIPEMD160Utils.java delete mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/hash/JNISHA256Utils.java rename source/{ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/serializes/ByteArrayObjectJsonDeserializer.java => crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/serialize/ByteArrayObjectDeserializer.java} (63%) create mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/serialize/ByteArrayObjectSerializer.java delete mode 100644 source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/asymmetric/AsymmtricCryptographyImplTest.java delete mode 100644 source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/hash/HashCryptographyImplTest.java delete mode 100644 source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNIED25519UtilsTest.java delete mode 100644 source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNIMBSHA256UtilsTest.java delete mode 100644 source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNIRIPEMD160UtilsTest.java delete mode 100644 source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNISHA256UtilsTest.java delete mode 100644 source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MyAsymmetricEncryptionTest.java delete mode 100644 source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MyHashTest.java delete mode 100644 source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MySignatureTest.java delete mode 100644 source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MySymmetricEncryptionTest.java delete mode 100644 source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/smutils/SM3UtilsTest.java delete mode 100644 source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/smutils/SM4UtilsTest.java delete mode 100644 source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/symmetric/SymmetricCryptographyImplTest.java create mode 100644 source/crypto/crypto-impl/pom.xml create mode 100644 source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/AsymmtricCryptographyImpl.java create mode 100644 source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/CryptoFactoryImpl.java create mode 100644 source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/HashCryptographyImpl.java create mode 100644 source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/SymmetricCryptographyImpl.java create mode 100644 source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MyAsymmetricEncryptionTest.java create mode 100644 source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MyHashTest.java create mode 100644 source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MySignatureTest.java create mode 100644 source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MySymmetricEncryptionTest.java create mode 100644 source/crypto/crypto-sm/pom.xml create mode 100644 source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SM2CryptoFunction.java rename source/crypto/{crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/sm/hash => crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm}/SM3HashFunction.java (66%) create mode 100644 source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SM4EncryptionFunction.java create mode 100644 source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SMCryptoService.java rename source/crypto/{crypto-framework/src/main/java/com/jd/blockchain/crypto/smutils/asymmetric => crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm}/SM2Utils.java (76%) rename source/crypto/{crypto-framework/src/main/java/com/jd/blockchain/crypto/smutils/hash => crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm}/SM3Utils.java (89%) rename source/crypto/{crypto-framework/src/main/java/com/jd/blockchain/crypto/smutils/symmetric => crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm}/SM4Utils.java (91%) create mode 100644 source/crypto/crypto-sm/src/main/resources/META-INF/services/com.jd.blockchain.crypto.CryptoService rename source/crypto/{crypto-framework => crypto-sm}/src/test/java/test/com/jd/blockchain/crypto/smutils/SM2UtilsTest.java (50%) create mode 100644 source/crypto/crypto-sm/src/test/java/test/com/jd/blockchain/crypto/smutils/SM3UtilsTest.java create mode 100644 source/crypto/crypto-sm/src/test/java/test/com/jd/blockchain/crypto/smutils/SM4UtilsTest.java delete mode 100644 source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/serializes/ByteArrayObjectJsonSerializer.java delete mode 100644 source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/serializes/ByteArrayObjectUtil.java delete mode 100644 source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/ClientOperationUtil.java create mode 100644 source/sdk/sdk-mq/pom.xml delete mode 100644 source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_ContractDeploy_Test_.java delete mode 100644 source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_ContractExec_Test_.java create mode 100644 source/tools/tools-package/pom.xml diff --git a/README.md b/README.md index 47382e47..461582a1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [TOC] -#JD区块链 0.8.3-SNAPSHOT +#JD区块链 0.5.0-SNAPSHOT ------------------------------------------------------------------------ ### 版本修订历史 @@ -354,266 +354,325 @@ JD区块链的核心对象包括: ## 五、编程接口 ### 1. 服务连接 + // 区块链共识域; + String realm = "SUPPLY_CHAIN_ALLIANCE"; + // 节点地址列表; + String[] peerIPs = { "192.168.10.10", "192.168.10.11", "192.168.10.12", "192.168.10.13" }; + // 客户端的认证账户; + String clientAddress = "kkjsafieweqEkadsfaslkdslkae998232jojf=="; + String privKey = "safefsd32q34vdsvs"; + // 创建服务代理; + BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); + +### 2. 账户注册 + // 创建服务代理; + BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); + + // 在本地定义注册账号的 TX; + String sponsorAddress = "kFGeafiafEeqEkadsfaslkdslkae99ds66jf=="; + String sponsorPrivKey = "privKkjwlkejflkjdsfoiajfij329323=="; + TransactionTemplate txTemp = service.newTransaction(sponsorAddress); + + //-------------------------------------- + // 区块链秘钥生成器;用于在客户端生成账户的公私钥和地址; + BlockchainKeyGenerator generator = BlockchainKeyGenerator.getInstance(); + BlockchainKeyPair bcKey1 = generator.generate(KeyType.ED25519); + BlockchainKeyPair bcKey2 = generator.generate(KeyType.ED25519); + + String exchangeContractScript = "function(){}"; + // 注册账户; + txTemp.registerAccount() + .register(bcKey1, AccountStateType.MAP, exchangeContractScript) + .register(bcKey2, AccountStateType.OBJECT, null); + //-------------------------------------- + // TX 准备就绪; + PreparedTransaction prepTx = txTemp.prepare(); -```java - //创建服务代理 - public static BlockchainKeyPair CLIENT_CERT = BlockchainKeyGenerator.getInstance().generate(); - final String GATEWAY_IP = "127.0.0.1"; - final int GATEWAY_PORT = 80; - final boolean SECURE = false; - GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IP, GATEWAY_PORT, SECURE, - CLIENT_CERT); - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); -``` - - -### 2. 用户注册 - - -```java - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); - // 在本地定义注册账号的 TX; - TransactionTemplate txTemp = service.newTransaction(ledgerHash); - SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); - CryptoKeyPair cryptoKeyPair = signatureFunction.generateKeyPair(); - BlockchainKeyPair user = new BlockchainKeyPair(cryptoKeyPair.getPubKey(), cryptoKeyPair.getPrivKey()); - - txTemp.users().register(user.getIdentity()); - - // TX 准备就绪; - PreparedTransaction prepTx = txTemp.prepare(); - // 使用私钥进行签名; - CryptoKeyPair keyPair = getSponsorKey(); - prepTx.sign(keyPair); - - // 提交交易; - prepTx.commit(); -``` - - -### 3. 数据账户注册 - - -```java - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); - // 在本地定义注册账号的 TX; - TransactionTemplate txTemp = service.newTransaction(ledgerHash); - SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); - CryptoKeyPair cryptoKeyPair = signatureFunction.generateKeyPair(); - BlockchainKeyPair dataAccount = new BlockchainKeyPair(cryptoKeyPair.getPubKey(), cryptoKeyPair.getPrivKey()); - - txTemp.dataAccounts().register(dataAccount.getIdentity()); - - // TX 准备就绪; - PreparedTransaction prepTx = txTemp.prepare(); - // 使用私钥进行签名; - CryptoKeyPair keyPair = getSponsorKey(); - prepTx.sign(keyPair); - - // 提交交易; - prepTx.commit(); -``` - -### 4. 写入数据 - -```java - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); - - HashDigest ledgerHash = getLedgerHash(); - // 在本地定义注册账号的 TX; - TransactionTemplate txTemp = service.newTransaction(ledgerHash); - - // -------------------------------------- - // 将商品信息写入到指定的账户中; - // 对象将被序列化为 JSON 形式存储,并基于 JSON 结构建立查询索引; - String commodityDataAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; - Commodity commodity1 = new Commodity(); - txTemp.dataAccount(commodityDataAccount).set("ASSET_CODE", commodity1.getCode().getBytes(), -1); - - // TX 准备就绪; - PreparedTransaction prepTx = txTemp.prepare(); - - String txHash = ByteArray.toBase64(prepTx.getHash().toBytes()); - // 使用私钥进行签名; - CryptoKeyPair keyPair = getSponsorKey(); - prepTx.sign(keyPair); - - // 提交交易; - prepTx.commit(); -``` - - -### 5. 查询数据 - -> 注:详细的查询可参考模块sdk-samples中SDK_GateWay_Query_Test_相关测试用例 - -```java - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); - - // 查询区块信息; - // 区块高度; - long ledgerNumber = service.getLedger(LEDGER_HASH).getLatestBlockHeight(); - // 最新区块; - LedgerBlock latestBlock = service.getBlock(LEDGER_HASH, ledgerNumber); - // 区块中的交易的数量; - long txCount = service.getTransactionCount(LEDGER_HASH, latestBlock.getHash()); - // 获取交易列表; - LedgerTransaction[] txList = service.getTransactions(LEDGER_HASH, ledgerNumber, 0, 100); - // 遍历交易列表 - for (LedgerTransaction ledgerTransaction : txList) { - TransactionContent txContent = ledgerTransaction.getTransactionContent(); - Operation[] operations = txContent.getOperations(); - if (operations != null && operations.length > 0) { - for (Operation operation : operations) { - operation = ClientOperationUtil.read(operation); - // 操作类型:数据账户注册操作 - if (operation instanceof DataAccountRegisterOperation) { - DataAccountRegisterOperation daro = (DataAccountRegisterOperation) operation; - BlockchainIdentity blockchainIdentity = daro.getAccountID(); - } - // 操作类型:用户注册操作 - else if (operation instanceof UserRegisterOperation) { - UserRegisterOperation uro = (UserRegisterOperation) operation; - BlockchainIdentity blockchainIdentity = uro.getUserID(); - } - // 操作类型:账本注册操作 - else if (operation instanceof LedgerInitOperation) { - - LedgerInitOperation ledgerInitOperation = (LedgerInitOperation)operation; - LedgerInitSetting ledgerInitSetting = ledgerInitOperation.getInitSetting(); - - ParticipantNode[] participantNodes = ledgerInitSetting.getConsensusParticipants(); - } - // 操作类型:合约发布操作 - else if (operation instanceof ContractCodeDeployOperation) { - ContractCodeDeployOperation ccdo = (ContractCodeDeployOperation) operation; - BlockchainIdentity blockchainIdentity = ccdo.getContractID(); - } - // 操作类型:合约执行操作 - else if (operation instanceof ContractEventSendOperation) { - ContractEventSendOperation ceso = (ContractEventSendOperation) operation; - } - // 操作类型:KV存储操作 - else if (operation instanceof DataAccountKVSetOperation) { - DataAccountKVSetOperation.KVWriteEntry[] kvWriteEntries = - ((DataAccountKVSetOperation) operation).getWriteSet(); - if (kvWriteEntries != null && kvWriteEntries.length > 0) { - for (DataAccountKVSetOperation.KVWriteEntry kvWriteEntry : kvWriteEntries) { - BytesValue bytesValue = kvWriteEntry.getValue(); - DataType dataType = bytesValue.getType(); - Object showVal = ClientOperationUtil.readValueByBytesValue(bytesValue); - System.out.println("writeSet.key=" + kvWriteEntry.getKey()); - System.out.println("writeSet.value=" + showVal); - System.out.println("writeSet.type=" + dataType); - System.out.println("writeSet.version=" + kvWriteEntry.getExpectedVersion()); - } - } - } - } - } - } - - // 根据交易的 hash 获得交易;注:客户端生成 PrepareTransaction 时得到交易hash; - HashDigest txHash = txList[0].getTransactionContent().getHash(); - Transaction tx = service.getTransactionByContentHash(LEDGER_HASH, txHash); - // 获取数据; - String commerceAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; - String[] objKeys = new String[] { "x001", "x002" }; - KVDataEntry[] kvData = service.getDataEntries(LEDGER_HASH, commerceAccount, objKeys); - - long payloadVersion = kvData[0].getVersion(); - - // 获取数据账户下所有的KV列表 - KVDataEntry[] kvData = service.getDataEntries(ledgerHash, commerceAccount, 0, 100); - if (kvData != null && kvData.length > 0) { - for (KVDataEntry kvDatum : kvData) { - System.out.println("kvData.key=" + kvDatum.getKey()); - System.out.println("kvData.version=" + kvDatum.getVersion()); - System.out.println("kvData.type=" + kvDatum.getType()); - System.out.println("kvData.value=" + kvDatum.getValue()); - } - } -``` - - -### 6. 合约发布 - - -```java - - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); - - // 在本地定义TX模板 - TransactionTemplate txTemp = service.newTransaction(ledgerHash); - - // 合约内容读取 - byte[] contractBytes = FileUtils.readBytes(new File(CONTRACT_FILE)); - - // 生成用户 - BlockchainIdentityData blockchainIdentity = new BlockchainIdentityData(getSponsorKey().getPubKey()); - - // 发布合约 - txTemp.contracts().deploy(blockchainIdentity, contractBytes); - - // TX 准备就绪; - PreparedTransaction prepTx = txTemp.prepare(); - - // 使用私钥进行签名; - CryptoKeyPair keyPair = getSponsorKey(); - - prepTx.sign(keyPair); - - // 提交交易; - TransactionResponse transactionResponse = prepTx.commit(); - - assertTrue(transactionResponse.isSuccess()); - - // 打印合约地址 - System.out.println(blockchainIdentity.getAddress().toBase58()); - -``` - -### 7. 合约执行 - -```java - - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); - - // 在本地定义TX模板 - TransactionTemplate txTemp = service.newTransaction(ledgerHash); - - // 合约地址 - String contractAddressBase58 = ""; - - // Event - String event = ""; + // 使用私钥进行签名; + prepTx.sign(sponsorAddress, sponsorPrivKey); - // args(注意参数的格式) - byte[] args = "20##30##abc".getBytes(); + // 提交交易; + prepTx.commit(); +### 3. 权限设置 - // 提交合约执行代码 - txTemp.contractEvents().send(contractAddressBase58, event, args); + // 创建服务代理; + BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); + + // 在本地定义注册账号的 TX; + String sponsorAddress = "kFGeafiafEeqEkadsfaslkdslkae99ds66jf=="; + String sponsorPrivKey = "privKkjwlkejflkjdsfoiajfij329323=="; + TransactionTemplate txTemp = service.newTransaction(sponsorAddress); + + //-------------------------------------- + // 配置账户的权限; + String walletAccount = "Kjfe8832hfa9jjjJJDkshrFjksjdlkfj93F=="; + String user1 = "MMMEy902jkjjJJDkshreGeasdfassdfajjf=="; + String user2 = "Kjfe8832hfa9jjjJJDkshrFjksjdlkfj93F=="; + // 配置: + // “状态数据的写入权限”的阈值为 100; + // 需要 user1、user2 两个账户的联合签名才能写入; + // 当前账户仅用于表示一个业务钱包,禁止自身的写入权限,只能由业务角色的账户才能操作; + txTemp.configPrivilege(walletAccount) + .setThreshhold(PrivilegeType.STATE_WRITE, 100) + .enable(PrivilegeType.STATE_WRITE, user1, 50) + .enable(PrivilegeType.STATE_WRITE, user2, 50) + .disable(PrivilegeType.STATE_WRITE, walletAccount); + //-------------------------------------- + + // TX 准备就绪; + PreparedTransaction prepTx = txTemp.prepare(); + + // 使用私钥进行签名; + prepTx.sign(sponsorAddress, sponsorPrivKey); + + // 提交交易; + prepTx.commit(); - // TX 准备就绪; - PreparedTransaction prepTx = txTemp.prepare(); +### 4. 写入数据 - // 生成私钥并使用私钥进行签名; - CryptoKeyPair keyPair = getSponsorKey(); + // 创建服务代理; + BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); + + // 在本地定义注册账号的 TX; + String sponsorAddress = "kFGeafiafEeqEkadsfaslkdslkae99ds66jf=="; + String sponsorPrivKey = "privKkjwlkejflkjdsfoiajfij329323=="; + TransactionTemplate txTemp = service.newTransaction(sponsorAddress); + + // -------------------------------------- + // 将商品信息写入到指定的账户中; + // 对象将被序列化为 JSON 形式存储,并基于 JSON 结构建立查询索引; + String commodityDataAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; + Commodity commodity1 = new Commodity(); + Commodity commodity2 = new Commodity(); + txTemp.updateObjects(commodityDataAccount) + .insert(commodity1.getCode(), commodity1) + .update(commodity2.getCode(), commodity2, true); + + // 在钱包账户以 KEY “RMB-ASSET” 表示一种数字资产,通过在一个 TX + // 对两个账户的同一个资产数值分别增加和减少,实现转账的功能; + String walletAccount1 = "MMMEy902jkjjJJDkshreGeasdfassdfajjf=="; + String walletAccount2 = "Kjfe8832hfa9jjjJJDkshrFjksjdlkfj93F=="; + txTemp.updateMap(walletAccount1).decreaseInt("RMB-ASSET", 1000); + txTemp.updateMap(walletAccount2).increaseInt("RMB-ASSET", 1000); + // -------------------------------------- + + // TX 准备就绪; + PreparedTransaction prepTx = txTemp.prepare(); + String txHash = prepTx.getHash(); + + // 使用私钥进行签名; + prepTx.sign(sponsorAddress, sponsorPrivKey); + + // 提交交易; + prepTx.commit(); + +### 5. 查询数据 + // 创建服务代理; + BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); + + // 查询区块信息; + // 区块高度; + long ledgerNumber = service.getLedgerNumber(); + // 最新区块; + Block latestBlock = service.getBlock(ledgerNumber); + // 区块中的交易的数量; + int txCount = latestBlock.getTxCount(); + // 获取交易列表; + Transaction[] txList = service.getTransactions(ledgerNumber, 0, 100); + + // 根据交易的 hash 获得交易;注:客户端生成 PrepareTransaction 时得到交易hash; + String txHash = "iikjeqke98321rjoijsdfa"; + Transaction tx = service.getTransaction(txHash); + + // 获取数据; + String commerceAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; + Set objKeys = ArrayUtils.asSet(new String[] { "x001", "x002" }); + + PayloadMap payloadData = service.getPayload(commerceAccount, objKeys); + + AccountStateType payloadType = payloadData.getPayloadType(); + long payloadVersion = payloadData.getPayloadVersion(); + + boolean exist = service.containPayload(commerceAccount, "x003"); + + // 按条件查询; + // 1、从保存会员信息的账户地址查询; + String condition = "female = true AND age > 18 AND address.city = 'beijing'"; + String memberInfoAccountAddress = "kkf2io39823jfIjfiIRWKQj30203fx=="; + PayloadMap memberInfo = service.queryObject(memberInfoAccountAddress, condition); + + // 2、从保存会员信息的账户地址查询; + Map memberInfoWithAccounts = service.queryObject(condition); + +### 6. 合约调用 + // 创建服务代理; + BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); + + // 发起交易; + String sponsorAddress = "kFGeafiafEeqEkadsfaslkdslkae99ds66jf=="; + String sponsorPrivKey = "privKkjwlkejflkjdsfoiajfij329323=="; + TransactionTemplate txTemp = service.newTransaction(sponsorAddress); + + // -------------------------------------- + // 一个贸易账户,贸易结算后的利润将通过一个合约账户来执行利润分配; + // 合约账户被设置为通用的账户,不具备对贸易结算账户的直接权限; + // 只有当前交易发起人具备对贸易账户的直接权限,当交易发起人对交易进行签名之后,权限被间接传递给合约账户; + String commerceAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; + // 处理利润分成的通用业务逻辑的合约账户; + String profitDistributionContract = "AAdfe4346fHhefe34fwf343kaeER4678RT=="; + + //收益人账户; + String receiptorAccount1 = "MMMEy902jkjjJJDkshreGeasdfassdfajjf=="; + String receiptorAccount2 = "Kjfe8832hfa9jjjJJDkshrFjksjdlkfj93F=="; + //资产编码; + String assetKey = "RMB-ASSET"; + //此次待分配利润; + long profit = 1000000; + + //备注信息; + Remark remark = new Remark(); + String remarkJSON = SerializeUtils.serializeToJSON(remark); + + // 合约代码的参数表; + String[] args = { commerceAccount, assetKey, profit+"", receiptorAccount1, receiptorAccount2, remarkJSON }; + + // 调用合约代码的分配操作; + txTemp.executeScript(commerceAccount) + .invoke("DISTRIBUTE", args); + // -------------------------------------- - prepTx.sign(keyPair); + // TX 准备就绪; + PreparedTransaction prepTx = txTemp.prepare(); + String txHash = prepTx.getHash(); - // 提交交易; - TransactionResponse transactionResponse = prepTx.commit(); + // 使用私钥进行签名; + prepTx.sign(sponsorAddress, sponsorPrivKey); - assertTrue(transactionResponse.isSuccess()); + // 提交交易; + prepTx.commit(); -``` \ No newline at end of file +### 7. 事件监听 + // 创建服务代理; + BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); + + //监听账户变动; + String walletAccount = "MMMEy902jkjjJJDkshreGeasdfassdfajjf=="; + service.addBlockchainEventListener(BlockchainEventType.PAYLOAD_UPDATED.CODE, null, walletAccount, new BlockchainEventListener() { + @Override + public void onEvent(BlockchainEventMessage eventMessage, BlockchainEventHandle eventHandle) { + //钱包余额; + PayloadMap balancePayload = service.getPayload(walletAccount, "RMB-ASSET"); + Long balance =(Long) balancePayload.get("RMB-ASSET"); + if (balance != null) { + //notify balance change; + }else{ + //wallet is empty and isn't listened any more; + eventHandle.cancel(); + } + } + }); + + //销毁服务代理; + service.dispose(); + +### 8. 合约开发 + /** + * 示例:一个“资产管理”智能合约的实现; + * + * 注: 1、实现 EventProcessingAwire 接口以便合约实例在运行时可以从上下文获得合约生命周期事件的通知; 2、实现 + * AssetContract 接口定义的合约方法; + * + * @author huanghaiquan + * + */ + public class AssetContractImpl implements EventProcessingAwire, AssetContract { + // 资产管理账户的地址; + private static final String ASSET_ADDRESS = "2njZBNbFQcmKd385DxVejwSjy4driRzf9Pk"; + // 保存资产总数的键; + private static final String KEY_TOTAL = "TOTAL"; + // 合约事件上下文; + private ContractEventContext eventContext; + + /** + * ------------------- 定义可以由外部用户通过提交“交易”触发的调用方法 ------------------ + */ + + @Override + public void issue(long amount, String assetHolderAddress) { + checkAllOwnersAgreementPermission(); + + // 新发行的资产数量; + if (amount < 0) { + throw new ContractError("The amount is negative!"); + } + if (amount == 0) { + return; + } + + // 校验持有者账户的有效性; + BlockchainAccount holderAccount = eventContext.getLedger().getAccount(currentLedgerHash(), assetHolderAddress); + if (holderAccount == null) { + throw new ContractError("The holder is not exist!"); + } + + // 查询当前值; + Set keys = new HashSet<>(); + keys.add(KEY_TOTAL); + keys.add(assetHolderAddress); + StateMap currStates = eventContext.getLedger().getStates(currentLedgerHash(), ASSET_ADDRESS, keys); + + // 计算资产的发行总数; + StateEntry currTotal = currStates.get(KEY_TOTAL); + StateEntry newTotal = currTotal.newLong(currTotal.longValue() + amount); + + // 分配到持有者账户; + StateEntry holderAmount = currStates.get(assetHolderAddress); + StateEntry newHodlerAmount = holderAmount.newLong(holderAmount.longValue() + amount); + + // 把数据的更改写入到账本; + SimpleStateMap newStates = new SimpleStateMap(currStates.getAccount(), currStates.getAccountVersion(), + currStates.getStateVersion()); + newStates.setValue(newTotal); + newStates.setValue(newHodlerAmount); + + eventContext.getLedger().updateState(ASSET_ADDRESS).setStates(currStates); + } + + @Override + public void transfer(String fromAddress, String toAddress, long amount) { + if (amount < 0) { + throw new ContractError("The amount is negative!"); + } + if (amount == 0) { + return; + } + + //校验“转出账户”是否已签名; + checkSignerPermission(fromAddress); + + // 查询现有的余额; + Set keys = new HashSet<>(); + keys.add(fromAddress); + keys.add(toAddress); + StateMap origBalances = eventContext.getLedger().getStates(currentLedgerHash(), ASSET_ADDRESS, keys); + StateEntry fromBalance = origBalances.get(fromAddress); + StateEntry toBalance = origBalances.get(toAddress); + + //检查是否余额不足; + if ((fromBalance.longValue() - amount) < 0) { + throw new ContractError("Insufficient balance!"); + } + + // 把数据的更改写入到账本; + SimpleStateMap newBalances = new SimpleStateMap(origBalances.getAccount(), origBalances.getAccountVersion(), + origBalances.getStateVersion()); + StateEntry newFromBalance = fromBalance.newLong(fromBalance.longValue() - amount); + StateEntry newToBalance = toBalance.newLong(toBalance.longValue() + amount); + newBalances.setValue(newFromBalance); + newBalances.setValue(newToBalance); + + eventContext.getLedger().updateState(ASSET_ADDRESS).setStates(newBalances); + } + } \ No newline at end of file diff --git a/README.zip b/README.zip new file mode 100644 index 0000000000000000000000000000000000000000..7484f74221d824d1d85001a9e0effaf885495056 GIT binary patch literal 360457 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 z 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%0D1w{;(` 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

vb^o_8;k#4dhG zjwv78sZrxbxaf)Kjd#=Nm1SOS*hciG`(4mS6XFf#FcbwRjOfnX+Hs5@k9Fs!d58v0XJCPqN z)~vr~ivklM-XvxlFcdgEZTQ9R!s%dT`{kZcg+G|8dK7z9vdGs!UU(}_l1gnl{N1nJ zp<20(K#N(A`-oW4k&#8Mg5ybN(=!(#`x>+4-lP|=(n9KmPr1tn9X}|{0x{KCq__2s?|)_}^DY|T zdAahU*&ds8$t8Ezn`-r=@wKQOc4zg2sirh@7nsjiYa`tj^uW@6ei)`&=Ak~jOvI;0 z_SD1G#ZC|E$6OBXZ(Aj8*~NjB47!DbqGYc!H@^xysWFhQXp5uRdCKL?j9bT4?RwJImwLg={55&o;LF zbx$U7KXt2FFQ;39;?!2tacjz=Su>dib&=|i1~ya#{+`_7-Tvv(Jsde*ic}5VdEY{##6%;C#>nNt6wE8$J+q=d_c$hVqEW`xf6mp-PZ7RYgqdW~DyEG2RgiiVuH zSJV~yRD$zpPyv-EpBk8LV$vUWQ*f_?9yniZwo$*B4XNU}IzB zTl^1ZJ`3DB$rh8L$U?Yz;ZOJunRm2P*}3HGIr}qK>Auz-d|8f>gK9jGFx})kHCb6% zH6bC7nGn+3+K4eA%@_R{zrT!)Ryw-N=9t-J=JN>TBmJl7-jZv~8mDIFDzR%K(p{0g zvCO)5*?T%ozo*K8{Ea873z$@6GQax)%lRy7mc=uhghDAS229DAmlEc(ww8qjW@>>e zd)YZVyfiiYqdv5gQ>et9bv*)MeWBYiVp5+?e&v{X_7;=OMfHU_y<$|7iGZQ44_dQ! z6NPX2R#}xUzGm?V^cR@U)9}&mrW^JLDNlAz=T#wdcD;-2i;DZXs1Fl>Wv~QxV@R!3 zB*mDg1#p>dPk)XOQ+4^G8Mfu5$%xh69Wf(!aksnAW0X8~F3}IFa4`Cy0hQsv`Xh3h zzSh=*G+P;%bG3anrM>-&7yU`EIaUZ=+9_$(tIc4;7-ZtAqpxpYToXwe8lf<|u%!ch zGLxYZ)y#hLCY4nz`q!1=l5Lk9TRP`25&o7b!bpj!^3B>EosA9)OY&j+Q=l)mNd8!#76Z9#ys3TyXaHX*xlWRE3MMdaIbjcXK4dXL= z+IXKyLz}tPP$|FlCzHON zXv`mEVYE;Arq$9_IVznplV>3E@e$0pr#%)TDleRDGS_sO6Zg>T6t4L4oy}Z^aHit$;~-&fjbw!vi+}XtWMbaz zJ$-=U(uqs-7r0R>Xo`Rjv%f`T4$_$Q^?I>>YQ zU}euTZLxCx5vAdnIAqQJAzy25Hhs1}Xt>J$7}B=)EMSP-&JPl+L&AQrq>}dHt2wRt z%%XEV8Wt~&Vb8Fr6k!XS>Qb-~hPW9jW$~~7BkZlCqTJfJVHigQ5h(%bE)kIKZb?Bp zrMtUFkPxLC>24W12LZ9{E_ z>jW-^!&PI7Nth7x`w}8hmZdPUD^%{XGrD zZ{)IHm_|@XG63G>7w-P>XX>BPW%Zz`!}hhO(K&W7DA6qH-*trG>o>Xbbp(?;A5zVZ z%59soH^oz%Z}n8UIJ>w6Fp2f>Z^&EQ*z{+3u7+!VjELA@dj1zlsbV2W>RBN&{r?7^ zoB$pZ9q>mJm-yO=Hslc~sv9pt!BbmLQtlW$=zSdF4G4-RHkI+-{f@W48PPiVCUZhJ zeUK`DgqZ3gBnpitv9IjFZB_y-o-)W6UpAEE#z}K0Bk(yfovB%VN@EkRv#2kHwtD;J zei@d+@?>2V6;@iNCWDLeHd1j-tt>^;o8#fPCxnLoaj|ZwmV{%k?=H@yPZiS9@?r|V znHfkUWLe!nM5}_NH4wR~hM>tmZ}>V&`ZLja_;c2gt=6)VlHHw1QUU*|u zg9m$a{z4)mkk`;8M8O6;h^Vn0XxOles_#>ss3NF!cGFXm z&kk%_!(Zr{e!^#O#>QeyN#3ASUTb}!dlo5cRX}u$O;lL4l-w{*`$BNDq(`&uyLu$; zO(egRZcbO%*K3DpQkxG(Fok*N*+8RN?yiQQQrlT&vweLx^aLK{u=G+Du&_ow{o9xq z^Bet19>cx8+DniCxP9>|hyQhF`0u9Hz1S?_asAf(Fbh2V8Q z!;I zUWuB=A_KwB4?@G1?#cK#)}5-5hm8YTD7(D_+otfO%ovCKnyQNl)&!o!?ka4@Soa1q zM)mpcI=6^=)gyIIKI8If(pk*G+Or+iRaHVVj@+}07q{y(qxsW61d8UMzTLUBu+Kpm zo^Zm`&Ve*Uj^1|nDXA|Ryr;}ZLQXFkaz1?#o?Br(S)!glv{7AWtKjB#p7WHHKfbX2 znIkr$DcAsQ##MUz|3wI7O7~T1$Sy{ou3Dw=Tn-<9GSSD09@n^G{KtWK@NNG$Z`>fk z=h@118uPKQ-0~fHIXriAFCsBzd zqpqy5v$7pX*K^Wj4U1_i?h$`nhkc$Z*CijZSTjT>`99#VtB)lg>b?yXC>&-vLX1kGvxbg?nrS$A`I`muHCo&U0PfcoSuk3SC!vV)gW z|BF<9NlcGhzbOUs)6Fy+eubs3JNY?6ys8 z^)#-4w_vb2Ax=cOs{d@{M}6_sVZgy>0U3BPW8@&)KQkec?Nt6xPD$p2Q7#tY$uDUD zRV)O3ED3{P@!&JxV3!2A|{FnF6C77u?l_` z63Bcy_UkOaNz?C9$KV5Y9AFFZdI#hpe!HKskhAnGfiodLqCiIDL)8&>cfon*7Pg8T($0X&0CG55S zU0JL9+LBK-x-NtwyF(>q{IcMK{Pex~8E&?p?#+q4#$B;Bf35yj1$_Pz23DK8w};+L z8F0FNV)bv}EPq!adU1{N1(tey}r$=ESzpazQUTc8umIIez zcMNK$6PRTA4oB?F=df*c7^ZLN9o$I(VxXPcyu8K_1yX!FiP;Uv*;q@lCJm?`@#Mps z@S)aqEA}Q>-g6>Cs3Q)r0h0HR~uh(ewTn;~^ zn9?kyn9oc*R~S`fvvL$BZ<`ZoV5#ceBE2T~Jv9Rbo(b*VN^JTO7f8C{d_)=JzVm@V zeKH9iz}+Vy__Q|QGD`W%8Dd^HG}(bdcXp4ta`F6TDus;tr{6{8#$Ghf2)TDhl5WWD zlH33H+%zl-oG>yEV3HSMshTym2w?cb*xDI;#3mgDa+uJ3b5~hbR+jPMQmZnPVO;?H zUMgL{V`*&R-pLmMwAZMnpHL(>Ga`t zLBA0Kdvs33`!rqgYV0r+o%Pg=Pg!<`YX4JQW_mi8CLlpgTz;)*OSChxi>zYWEvn8Y zVbMEr(>KJTjD)l~sF20Fs^Dpr+(f$7@7-~nq{z5h1M#GXe0loHGv|QsH1JxvQqSXmZfLuY!K`Dm5vOOU z8&D3v#L>FdBS7{L&{hwa2ETgX=Pwf@3lOrCix;_ewm!A>O7ti+^i5t)?T1+3-e>wR z3DgBtr_YrU(KBJ8)6P2`jQr^S7Dj%6z0}}xHhyQX;~AybD6DdLO1jbCf5*B3-?}31 ziyZqBiE*9;574c)Ijzk;kt>f96sD)&`mDk?R=13S{^-V;1UK-^^>Wm+^j3Il`GDU; zxc_kdhSabsTuuC(F8O$p|00q7_pKrXn{yZGL+JLap;djO^bt(e8HD+*&r%(*K)@evqvhKwL1SZ+3ce z;Z(2NCVe5{UV>;Dt;Kdemv+TGplyS=@2gj|o?{*O zU+&ci?iIvC15r)S&&(i)JZ?Qv&kWWyv#ZM@0(J8(8n{&4`Lcw0Ha0ewU#zy!*$&7o$sS*fOo#NZL_6^zKZ+f(Cnda6Ng<7 zJ8rex9ti@XFY&Mf6c}FjejEB{6?d;xd;C>YUyFQsa?)@=6`Fv|H`)haBYdrDUN{t{8U4KPkwD}vo!WDAlG??`CY_nc4G1O zfNWUg6u0hvJ%J*rD%7LT-p>R!gM+3!bi${twsFXio*$%SBRqV~RLUxav>uehY zpfSA1%=C?LEyH~3{xRCi1-7jz0f@$J)0`8UKfEd2gsL9E2+Hmk8{7^Sm)m@x_-F!& zjeD*O-0o1F%xdGoMFQci(sRRd;g2Uj0|pP@KGkvAO1^n!Wm-1X-l$%-k!rqwiqUgo zHmpvQ5LKgJcCsCfB&lD=xCBeE@#nAx0i{iDm>#vo~Ei9JK{{ zjxc8tDej7$${l zbL-#)o8?NaCHTx#_pICN@?nTlf37Wf+Kbt0;44-1uxb!QT4a8k| zQ5Y$51Nm|DNz^oI*rRAxy_zSBO3!aAITUvP6}nj4EaJ&k6R!Z-lW*>WCp6L2@T5b0 z9rdEZq_^_K`+wXnZIyb#<<+*_y*yw)$Vs8{D8Cu51*kxA%``h4#8NRiwM68_s#Rfg zO5pa@re356it^E~a1O`L;9Yo0Dp!h#;(pwX=f)tfPd)kIw)C0Ao=&BT(CeJb9K1j6 zH3b#{;L)!$#6^CcCfM*P@k-1#B}t5bU?wX)_DaGT_-mAc>}0*U$>VGhA(;1+-yOzz zr!aiA{ge}ID^JAggvhI+xL+HXYwBd-NNAaGkLPlJ}CVG-1K3hGn%Pl{)v>tV&em@T91t_Q$J;{0?8&rVC9f9tz}X8TSh+_c@8rjs z;|N3#+cF9-Urh5-D?VS{x7pzF=eS_B%i#aHJ66|dm##56$u)U@H=B?K*J*(`_ATqw z9L!t;Nw>Ch-ji+d^Dy(jSxx%(f}FtqrGtK;S)xmGVylx*|1(C@LYOa&)6mmeyGl3f z`vO|iO|Q$`-@7bi0RTumCjNCtn+W9pZ|M#`T3$&>2?@@h9f$;?qfkjG&0C`>M>XA> zDlea}vl9>(4oURdnF8wZ0)esVqBeYgC&W`r(wR0*|HQw0xhVW zhl1H*R6O(kIEmm1OD&Hwe1sFZH|lRnR<#FU~MgXQaQcXk}|=5I)zYEi!K zQ%my`3Q_F|MTpP~FD_tCQ6s;t^9H85$Nt>puPbgOnI+DGj*35i{75h0wp(*;^oYGz zRKXpZu;!ajQbNcIx%aqvzZ^9tVVGn5r4u@D8y2y@<~xGP#T&-93D^VCiM=Vq%)@ z0&&4?!SgkwL^}!~4n>kYoL0nNuBdk-b|FG&fi)=bxJ4}Kl_(aegr)OLCJh{)YzQ-EXn&?m3f{UT*+G%#p5)r^0C{%pGhqU>!H zvg9^@g$dCF5bOIhtB-wY=^#fX0**C!-N2)6Pp+V4UuX}%)YDPSG8>N00HlZ}s&-n= zVd6n7{^f-78Me&}E$%lPg)A~FE=1e!R6BIl+RbnBZx8o{mxJUpMvh{c(vs3-D(5d< z_olamFneT>bt!bKlg}pG6STL=#|bV1TV<=W^d=h?xJ&k7kp?#2afwgpYcbT}aT!xv z%e9c%ty)ZAU+rk9_d)`46vXv(4ZW^T2X5ha-yZA`k3Xw_L41B)>#fvfy&69{x70Ug zJZBAoYh27I5($0}MgpKx1d6_W$dBGIQl{UU!SW2r`02N3wC@(Z6)SlXb`PgZ`S|!YviX2MVo0DRR2nI)o;C|%i?L64Gz{@=@}HXndS4YT z^9Zh>T6(*6a}4ftmUdQ#E_2YzNRS`A>OVU#E{}T2kapHSW}|cea+?A-?0)e`is^yu z+K|pzPGK61mV3T))1=0)2JAJHcLxiE7|z!XR@UE@nTC?>(PGdvPU_&JPF(wOLD+`- zFD7s8PQDJ@c@MnXtZhT9T!5W^5Ewi)8t;-p^yr4NLR+P41uuyMip}`ES+oD~4hJr| z7R+UTFk!9|ZLWB~X&969Dv!}q0>b3n%Y{p>=d1YDWXJ1O#PZnw@>mPw=G!NuWt0!x zc0&Cjhj!JmXvjb;Q6yxbIJM&ibhZt)HHOht|B&BCqk>!P%RZ({F-z0i`Ma#9CRnm? zTVAESa;9*rxJ~#}ox@P9?Bl$Gf~V6iVAj~~b*+DO1`_pH%7-Y&;=Z#hHKQBN{|$TX#KZAx@2o-w3UfLQXp^qPu+MF_}uFNvC7%@PiOO zjP9(~R$&Jopz`yb)-gFPoaH2y9)EVGq=&J&ol9H;4B+H>I-X0W6vBO|TOBfyAh28R zuQ*bKKlpg{RSb^#SV(KrUiG)lySV;Ct@4{@xd-}ftC%xw`C7!pm;MKEZrzpFX@mEz zo3zNs#LbCTr_`qq8v_M@#~lM9a*p#I1&%O#WyM0V8?eK8$EnEl{{Ae|?;y{iS*~;g z?^f#C^=L*DG`AN{LY14l(qE43D`G5Grt_MdGyO#Hm~LusU?2x7jI-k=7KFYS)BJ<;iCH?y^dT@%y7ts?TxV zvqr;xy?TWpEb7#x9pPn1O;6%#Z%*bjvnKwsLat-b4-c!#0sZ=^Jd@tTL)NQRr@`%a z#2LpANl&6T-H0rl?yh_cQ@(CgNtrSCHyxYaP)A%Sc^e(qbG5I@n{$w^1udb#6UwV2 z6s{nw=Cd5^0gElVq{E=TaS%BdWE1;$fQ;phwuaJ+A3S>6(x*o;nt@0_4U{UZ%dv^6 z*4Y8OzOH!d+2^<_*7>l<$859u#K=P39d1k0=4`Ck{|Yh?3pFCp|C;zffuatfDk8WZ zfY4yCPHVnC2~MX96jcUF?;frTnUe{)LkS0oZc7f<@iK=mQaaKw3X!?np7%WT%aC>AChusg9MBUBA-uM&vQ6%^1j0_iX`YG|F$x?{ z`|%FiWlp=AxBYT0jp2UzV@bNk!7=>pJ`RRfZUI8dkOsZaBA8whc!5H<-77cqpEMw%!?ztL+^m=aNEz zd-blu=@n(5=o9qq!{SD<`#5uV{&eA1?(0Zl*2%rM`3MjfFmBj$#Q-%t+kRtx=t*3> zOn0LTBf*M7akY&7RKQ4asP*lk!7|q1Qq`NSZpGr*-pf|!MikZCn#PpGP%rlz_`<~V z7HrfSU%8w&W#`OxO-H6(C}rpU`YJqF6jTuf}UD(-tG@?*sxim~YT}^l>4_&h_^UujS7Nr+Ky^*6n>8 zX#pBw-j#Z7WR|I_v*$`_`-9<^cXr$BKT`rma?^O6Y*aJ1xP|!P4uh2fJ~fY^eyaM7 z)Tvv6!|D@FtzL^QLxQOn<HZwr}ZR=B82gxK*f zOL$IMi=b?0D9$G5dlQI$xHzOuhp4=HGkV6O*R~3i(zi|dsgz8x% z7*Sd;!)<0zH?iVwui*0V0L*8r zOpK3#T0C=m#N0gS!fe7`3QelljPhQoyptpem=gWi#qWm9e7zditw3x8mXkjM)fL+q zEWq9@H_$1x1?)hq@nfrqp2f_!4AET>aJ^cKfO*%N4?j0Y2fpU~`n~L8>LF2E+&g*F zf}1Y{So7WV3-J15v#6z(e6M+mC<#uy?)Qls{_|0t6<++E=4L3PtwVMOg~)W5a}Q4N zgr;5!>6PU1>b_S_op-$`nw8FQI73z7+UFMGhmXo(vXEAGU$uCZ-@L4w)t^7tZt_~> zeYjL%^EIW)-45KVc|N%0i~P*hnHSpszQTy|cb)_BW|}=5bP$TQaugz;+TYSCq4Lno zc&Fc6pi)GO0)D^*{2?i48Z=yvC&|9 zd@_|$L#|S#76+%(@l!HIq@HLWLxth5`K?7vC_I%Nszp`NgFb zis5lLH~1G43cThPaT;$NpdEblO5p3PIX7hD2}Sen!by?PkIZyy7eUj9-Zf?MZaPbw zm~muUIdX4I6Sd2l;Vtg#xfF{wOS>)IT}W8_KB6B}HEsF*j+o+{LnFuh4D6Vz)6tRG z?v5w87QlND;iZv<44Hq>91gZ=1Moo`R*PL;Mi`&%arYDzmRDHnn!k{zexexM=l6ZmGwpF_?B5Y7Ly$O6b0og!elNPB z3*%o|HaxXhy!;j@a%8A#=#di1-0apoHGKDUK6$Eg&_zwUd+RclHNh0R7{HwTg=3wp zmdMTMp()y?44w(qRQ-ok*Vk|T;J#@I~I zYB6N%@BF684pe*~Qm}I?l&-mRg}HZ2obE8ixAZ{N6?Kd5ZoUWDkX-hzyuZu0t)-=f z*7H)&CZiT_rp78xF2=`vb09^GW46|A*ya!I0~DuNnHoMNH3#sy?M}R6Mm|<|*@7f` z6^{hQK&)<9w-u0NQIBRH0-RW~F30CLw{JNL$E{v>*^Xa}41eL{xcNa~Xxq{&JoHHR z5*sy1RMix)^LQ!v27lOTW?gFV)0>$wVXT=_!dLDJP3y6*Q>yZ$r}KCB5>;On=%+Vm zrpaIw&6FWye^#Sd8FyyUA)@_iFz#kMlCV4;81=IHWS|%KwEE@(o?h2lUR>M74vg%* zPaP|mZn9c&8k{`NJD)Qr-u#;1!t)c9PtNLNdhfNN!kM+kY?qdQBhs=n8mpOhxrKg} zQ8WMLgO0U%kU3il_aV8os&vDR)z5N#YhvvfJu_ zM3|@egQ2aV>)aOtW$*U*#;OAr?wz?HW$rYu)AanClnvK8xXDS7{N+Qy{g1h63vQvN zmE5P?ef>Rhc4#lqu1Fp#da0IeB4?H|Po=ZM{ zy}hC5ekAcU5)MKGiD0&!ot?2~NNU!qQs!o6b`v46EkZRLWXQ6Jo(SimP3xzvKgcy$ETq!%N+IL@H_b4O)8dcVGEz4N8uUgQLHok!S+t&!xGOl>&EtoNV zdU~o%-}8zP1we^SfhtMZy?B}gdfMFj_5raD;0fp6k13SQ0E3Sm+>@Yck?T3<+n@FpNHx0aX5~MTA7}SN{Zhoc&)PfaxRs$xr+TqX zg`2Eu%-p=0-Q{9iy}0Gd-)5Sa&s$cZRjPZx6}QMdd>$TYSJjr1DiAh!YF8I{+uXqd zAIZYcN9DJQ4izjR6n$MYh#{u4o%HkqzD2NX_`b|W&or*m8jZxo!(nZLE@b%UR~xJ+ zy*8;{o_`4}Sr*X8j~^+mtgPs$rvd4e<9tGmKo(w|zAn8$S$F|8>N-HWO!^Ns+Zf3B zD6ZV&VA&WT;(AyxAio?<{(%%BWmVC(Z)Dq^US*ygXR4~H2@zp|#Vh?%z-$o+rnsQN zLkW_i2t!&fV(Odfb`UDr&=StBkI(@T!y4+o9YUaWM339hti;`-YkKgFhPfbhCUFqQc*&ofCRD?YSn!@zXf|Fo&xPb;FvSm}_Nw-@J8c$gqg~{C%#SuKq>erVM`D zq&r{bZ>djF6j)wTVjWT-=_f1F>6?d4@u+sP(v5po1QiMG^;Cn3$n6_$y09n%TWVCA zF!YE&p@=oh%phcs>vJ;)J39*#1H)A-%KKY&k;(uA3xKHj@E{qstUGd2TfJW4bX$gF zqh9m5&aQcSK+ZP{4#kUX)9!b-y^NxC(FLal7mmF)xa2Qe{Z3t-&x4^y#!-nqvPV&{qC#6)xZSHjyk~or9~qe(|>Y;o;8SUe{8+ z>8F0O2B~FF5fXL?$7@w_Wv0p` z#oI{|US6BhY>dAxNU_v~6?B(xt1vD%3aJg*8y{rzjNHcgV{qpSI)Z8&Yt&Pcdd+2t zZr_1F%ecB065pOg`H^|KMaf97&zgNJdX};fRoOx1jl=sK@aEZwO$qMV5$@UDmLZwv z3dlLV9L$Db^vcWq`Agep&0X2pVacL5MS)?Y{N9gJk3Nr4g(iBj35{5Wn3zQ|>){P8 zDKtP_>t;wa2Ajb$n>KQ|G2xhPoYtou^cCOyKBsha8|-KA}$L4ZUbWxAj=sJ-%!eEH9aBYjw*UvKjdn z@$<4JzW(r7w`8s&!}Bnf5V?8TmyeoA7aVXpY|rh%=V6uVxx5^LVjKCcplZ;el1E#s zsfP0N-+35D3F_VL%4~xynV40nr zeMj0pY7Ig0YcT7zk1FHc#;%0lc3||@^?x@`inkQ)!3+Ab`jviT+GH#D=rL#=*KzN> zeS<&AyLE}y4|X=2Hl=CSzGPacstFcyuX*hewX0;Ub-Qq|4jb z*cgsC1IiRE!#i`tD0J`q;q(Pc={kwBYsN4CAe0!~k+E9pvjf;>n)c>)OgXWUz%B^o zqpY=c$@==b>qK66zsFk`=XM8kgxmdga%KO`PMmq%HMKrCi&v_G(OIr3uClM`lZ*rq zM^-dQj5{?DtJOdQHe(W1KDAe8_q`j(^6L}Y9pF_G^NteVgWtdkl0{DzFgbf4S}fnF z5=DA8lq`E0zNW9zZu*gliyT-D26OM8_H0(P8eKLm8;utzo?~EOTyvNYSoX3{-?e^y z_(zhnqa~+lA2c7f_3P-5Cr_jwCnY6Cn2RPXEG!(@;@6b>ODAy=LEiyHUVAi}d8i*k zDix<+u$_kRn4>oO2g{#~LB*m5#S!FSi|iM`mrbo_3lKj;sp9!{^=RA-wYh6Jizt?C zPUgF=(BJ>{4fggyI@_Bann(L3fxVAUcyO_tcPEPRjEu4bFyz#clAN_xK(p9un+$p6T#vGY4B8YCRJec55Dx?mpqdu+(CRwVUU38 zev&5Fcz3K2nYPym_0p22_s*H;qCVe?VHWfVE(*yYaTc1<8jAcM{Z9dZe03x!9aCQiO+owl>dpNV<=)kMBr zq_LazJvf$n?eNDCn>g*r+szFVZ=u}A0r?pR^HFd~PNod4hzMlyRh{OQX*i1i%PFsY zC&T9Rs-~=f5XIM=6sFlI!2?q#Y%FRrA!wlI1iU^MW!vYJOh>t8WeS2B75Oux17LRZ zf#*yVrif(D$$w+)n}@w`(AoxREj}#HPn9y0d-v|=&oHv|Aj*+&Bsja<`;Hg?d{oxX zP;yFnU&|+nhaeWz&bPDA2Z1N@cPdR%{oe#j^j9cmKZGv~!uNv1nCbU3Jo(2Q(4To`%cwv+UdNR{yfXwuJtgITR-;&iZ@r10BJ9_M_l(_o zf`Lt2B3TEh&}GC#A=z)tOw8|E)cEcb8Ym163y8yJ{q&z(LQ>t))Y)2`g=W9s@HX9QE2NVB1_JL>UU3y9VlX{y! z76x!8y)o3!{k>c=W%^q@P1mZtnQq#btUpJ~=JfhryZZSEP96A%?3_r;}G_Mem@{&ktD$51kxNu9xS?W{EBvF_2unJ)#X!LPIOZ9g3x zO*;CaPEbViV*nTtKT=iPzTExcm?V`&`^!F!y)4*F4e7mbN)T?!nI?{*p7hr)p*2X{ zzP+HV^jw3C$;2zo-ot4NfnBJnZQAWyT|J0l@y;lGhwco2`9rk7^LJhb6vt>uVnB{pk$is2hcZ} zomIEa7Q1qq_{Wm#-AIAtuMM4Lfmm^jd0Bz|YIbK&cWsy`*0)D<`(>iY7Dvp5Sh2Tb zkl^66u+N|WsZ;~t5W@f0?ErcK7_j90J36>|%NIcyVJw)<+i3*{!yh(@C`HzgXMa?< z|E~(ytDNQ-7<`FvP5zudhnPUq^EZ_!{+@ZRNxByqbc-?$!!lw`iknfc+GZxd6h&%W z<-WH~s_ZUQOn@ z0M(|lKe@H!7pi(-2!j>z4T7OG&nof?+<_`uE;XQ)59g|-B`WBZS zCW9BPl3SswK&Vxfwl{gW63 z00i+UkRq^qQc;Y&V4(kN!!~caBn23--`BPMBbXMJeWdBlBV}dU{C)Hf2b}uw?BISB z!Iyf){ke!%oY~lX&`gA$^cZ8@Y6X0sUQj19(KAwLTYQYdCPq096OgP-_~99~lYw1S zQi7wl`eQ)1-X1SAYPqrE)-*ZukjX>JS)>S7w7SgjLw~4{VpEi5X5124fKkEVV^nRGFI3gF zzMsa2ru|#FFg7j_C}YQX3idc$QaRway3C)h*Fk)zOMyGUN8wc%3-e-~-Tdo2kFfU! z`(d#r$Z#T>ZZGrr-zMvGAd0`j6go9}Z3iOSX8fm@uNS@u@e@S=HmN!gRUo-1Pd;|v zYAlS3!bFT4;a8~q@9Yso`{jnScbY;Pi}RIhZVV4GdOik`F|CC%UaA^c91v_@pKa>< z`LsD<*54+Xse?BemQ4)8Sa~MtrlTBt0fSiV9u4yB`g*F((b`4RHdasBrr%AK>skzU zN0DQmQ_dJNfxdtLo){Rtx@zpQUplFivCGxYQF4?zl%MKlZe?$8Py3>mx*dYl>rz3; z`1gmgF=ml}8iSqh@yG~RycbqDdPKRv=2fu$R`7B1M9w2jviP4Jq4oQ1>f8l8LxGyL zb-Pt{7@nX1t#TdTfjBINK8>=HmD>;zow4cGhk2(Ym3Tv1+J=Gt4I6`ZWW{LjZi8vf;mli=_#n;m9(kK3d_K% zkf>T#0-nt!S~8j<6#nhiacpetnA~2T-p2S|;Neb>;iyr*>L5{z5F=i!Y3)435uPe#(SOP@(2Yq$caXd(o-K z*27p}s?f`j^lZa27*(wjXl-L-w$}9-K_uLEb2OVUbTueyGEy`GrL>SK2 zAWATFf_AF59YGf@h)|LoUNd{*9scrCH-vfc-;D|VYhd156M;ShBU`tmIOJQA23=Kw ziJCYZn9pBj3FKzkDtOdbM*M3@2W?hnm68>btij7C-tvu_iIywfIDb_+K@1Bv5C?nn zE19XYVQ2!SsaTaf-7{$+Cq=DpqLOr~6bn*=S9ZhX_UjqhDaehkIXxPknxrE(%EHhNLjt_RML-Fd<@K{?&(6-C$A8`_CYk^LhF(8dcB=Y3T2v8vV$)mqzj^*R{q$ErwWo6b;Oeu7( zzByoEo=mj}2{Fi&U}Ra&sZlRBEsgl`^r#P1E(pthB4jNP%>$7)4 zj*~*y?-m1Epe;E$;!D1#ir3q@QF&l?Z-~Y5D&FB8Cy!!^5f(=jnUD_D-KPb!xciPC zHLu&1t(np*!}wxigzYzJiMJP|ps={}lF)6NfQH5uD-~0P=n%Br7AU7#0@BXj4ZHr) zHZTAaxR+=>vg+$w1C>#6xh91Wjz^EoGIZ7@!0ceU%Z2zk0(c`PIFP@y-jK@ag&Vtu#*hM z|3j#u03+3J@=Ao+H!MpQ<9~*M)G}V_AKlz(w0DEe`c1ndNmt#Qam{U?;wGK_5l2+3K-r!K6(!4$*`TPAKPn?mnP$^iqL%9E} z4ZFWysUQ6_{tP7wKSdqnIJ8b*$ItEY#oY~3%jEW>-GTm&2KiC>bPSRr3=W^#kNJRq zP(SeD{eJoQ$w1c}Dp`yK<%kQiOWeRCa$gDu$X#9X0J(5x<(7a+e=xx-5!Suw%6jwG zDYJx+RxHG7T3X3RYZigwl%iO&#$RNlL4Utc_TjJlOmA8*mwdvf#B5nk*=h408Cf7G z&K=oBtixC3v$)WBoteqgT_Zy_WJ(=w^M__8-~D&_{@bOTXugL&%eaHKFTM2vxWFc6 zBY%5^uIC((qwnbKraf3p5@BdiwH<6~o+J4s#W zHEDEvGLYv7Ecy~iAavB^mlR4C3b&0SZ<~qAYzI^aahz`f9Y$P#gys`^*{y=%PUc2~ z5v$>%I_qVNf!jBCPEm?@0D^BZHV$SqW6??YU1bLpuw-N56rJCWOjb)ANq9%4{>}j$ z)vS^d>)?)`mMo{vpP#bRQaVFrvUqw8)MJ)E;fSrm2Z|a&O%oh1Qf2Ng{lS4&MOS=IpU}rr& zuX@!PYUF|F@gM*lYUhUY52ImzsH*&IvRDv`m4fWx9-Ty3U4gAasA<_BYYG1M7$TrR zUH8Q__&wQ&1<=_vi%$e;Wd&&$iIHpWnCSW8e!-##iBVvgA>#sLGcw3F49>2OzWrKH zrP8xnR#x^|Xo-pVj{)Uiy_FOUuGl1R|KcR0N7?}kq_%AR|I}+If|i1x$NwfuEDR8V zq3@_u2L{i8t@@^!9w+P@8@4EgJ5Z1;%JP*K)U{|!$V#7kQzn&iJ@B9p7#y$4U9eST z8~*SA_9rcF%bj=>i^*J)| zI~`W&hEbFCn>>uesmqpTM&faG_PKx4cs5>xxo2u#qr!mybm3xxftQJc#57M9?e9N1 zll_HoRaMx3|2~S%>#{lUeDGpa55fHx?*saCU0(3M0I)HyoL)-?(MT?{8aHK9Zj+dq zhzItt{5um49xw2*NenEc?+okOG~)qJ_7PHUBd=Z{G;F61(;$YqIzrW?Da!S)p;-zx z;MFPv+Yoi`--b~Iw9%g=V1e+-x{v|=A_={ss9apvq9=%Q6=yI42hzwgCLa{3a2E*Z z)JXj+6TB&prrNw`HAY*JKD%`Nn>t?RCU$=X7mekW^SW%~*_eF3A;dptnrt0G%_lKL`vLHttgW-)d@lH2P^0r9P>dHVp97Ox3ZgvtN91b9s2^d{e;* zU4}pgGK6$%^u8uwUv`G$p8O0Y?^|m1X$JGzVaZw|oGT9OjGgyy=*Q;uxq$NCbu=RM zhQfY5wE;{?1d)>$h@4brHwWxMB~5qwqV{geQXUXton*JMOh&NhYinz#@y|!Q{RyuK zhE*>OL=Wi$ykYb9kUj^_saE`{KpFv zDKP<~Y0sM{LkSE#Df7fA5V9^l;C?H>Uh}cn!9pY0WlQPT-D|#QqcR$zN=ez4VWI#u zEMTlCeO&h6@DbIX%4MTx(jD3Q+Q;^FOY%z8o8xgG?~h1;s{g~+RX|16ZfzKb5`_T- zq#LBAyHirSL20BrBt)bUknRrY?v`fg?(URs_=ivL{jPrZziXYvTFhe3ob$eW@8@~; ze$GJumdUF|t4Qt8sN!0DI@YAePV2X>xrA4e6H1t%I#yH4HpA@B8Z(0`MJBYjB%jyz zXbzaJdX5AJ?&+5GkM;F++l{_B8;EFe4z zfvf>akOl_1gf_I3d4w;x}8 z%NUo-4F~^oF~fiW*?Ji*WOS>=tF;QJrsPV5c5eYOF<-ay@{}lu{&tv}*RFT$8Of6B zFj#u~B(w!WQ`Wf`uR^5bSzd1prZ9ALbnGJ=0PLHt$BTRG9Cp{@jFZ34Xh^D63hN1e ze*#&;lALI-zw*ER)5mC+IFnC&_B!A0YPF1aSBsiLd=7@;A5#rd4_EAub39OFt#->8 zJ?L=DB`*Z0V+jPx5dVbO0E)L|vU5(0?h0?-fb30_m6eaX`0t0Ga3Rcm^dZK2<;7;i zCdlcu;F7bpzFx`gtFEe~L>9_-{VkElrEK06lPJs|poJ~lUfvt~*9N6iyirk!W-^~D zF?*y?aWTvCyHDZ8pu93MemL_)dT-xNvkQKpa6YtkmwWgM5eJi-L97vq+rt^+iz5~3 z&l>rGAO3*`Ue2Ibt*|J3mv4uNU`8JbHLH6S<#@@*$H$Y9jejh)K2m|6fq^O}#l7kB zE^5t@ZL!xiBL+t=Sl>rRZL3&SL<;*M_dw(xcjT*SKF5POD&70z7{j?5oBf%%mLw~W zlZ4|8!aE7+5@dA1??Wk&ycgtss$0=_Ux)%XrY#&-{nN)02 zTmc#djD#^XQM`;&)BRbol!k`J&}ss%3)d{)mKb8)5Qn|JegtBw%aRv-fdERx8R?Wk z6}uP7;=}!gH{3o;id00c52BX{CWNwl9i-R)dnzWu1%2{va(iv?Hk144>5JcDz(2L6 zt1An<_36O0r#bXh{s-6R#nJzIfLBHhenmx-shA5qWPu_v`L?r^WeqP~DIk zx?b8v5K+hF!5+MPzCXJT&wuGP2PtYydhPy>1r%YO&hbQ{5bUkgw89*lm5tnRrFo}wR?nsXd9-n0JlLp^fsQU^uJPAW2(;39O`#F~;8i18$QR>#jIMh7xQ{HXI{ZSdb zH3@%D1fy`%JNJFs)}mE9E}B~qk4+rxnhZ&?E(mEP<89n^(#J162j_p7D>JATSHU~= zpY*g`z?(J}?9kg2O9CFeb*t6KI zWP{I?EslSL94Sz7EuJJMlN%8M(WsN@cVRgUJAvmQ)pN4G?|U`Ze`R}f8SDsF|0uwN zgvaZu^eO5yqad8o)xmsgIZgtU&)M!Icq~`Z8+p;7>~gA$kD|EYSaRsBr(vkZK6+b! zzRn>D?qlr9h&-sfRKF`cF1nO50D_6HZPT$Se$htID+fmQq#%BdETl9Fpnt=@Vetz$1=SDQaYJaTOUJEL$IF{HmAFg(JWY=8{m|f%Jeqsxniuy%yptUpgI1s5AGbFY#enzC z2WeD`B6o|Mb;`a#-$!luT@ZW9AtryiFgin_nSDWdVX}hh{ z=zitDT}IIhiP#%eR@F9CcZh^t+pk`X=D$A*)ni(AWQjXr^tioD%^ytWrJ+$NK&T7x zfv9Jw2OP?gX@g&O7LnIW1=V5;xmp&m9u3;HlJDIeP`3l*ZjaxMbD7;A6;T@Mor|jY zhSmBo8;Xd@i(Iz|g8a$7MxS}O*y-vob>V)PSD0_&u~Vzt9lZ>%Ms_W{@>;KCYHSw9 z43;eYB-FX4h{Q#^wfFmu`=<-!MZ@DK&flL97z$@NE4bQh0T?mS)aA6@28f5M6VyBnlq%98tzUM9&gKeYK)8yqR28rqfnfPiqFFH zntY;XL2d;I^l@I^9eOh4s}>*FuTl87Cx(Vl*b(gR?s9AN+XB(VAcE;LXVlZ5b9Jxo zCwNvbW2&hasyCFCcRO%@h*rS}hE!ecIWnFwoZqSVW}!?xB`z;a4SS7)r&sQC;H?`b zT#n%N4G}LFQoQ1~^?L|H3wX9rtp=$ZKUX}Mh29wKA8}n>TWFBl6+jc?Jhwf9 zf^QBYjxsX-b1tK@XB8H{37*BjtxY&tKR?3Wk1i*wf(^9Y*E7lvfmJEgAcv5fC|t{$ zG;LD5`J~1+aR^%^GW>Z2hPh+tXSD= z;7VBLb&Y-y6RA^R&E75IbMR(JleXI14?c2_U0l5+nHEu6x%&Lu}N3n6wBpnu@+_=|S{mOe%Y`)eI)M=-oeB;R)63 zhTyc-oY%Thk8bV+nT>$6xAA60Ul;EVoLW`RoTo8VwT25VP#_4Gb>7((X|^rf6AfX1 znt~h@CD|FKM=Kr2$6yAhium=%|7yGVk!+62P~ex!mu(^Z0nRR;EoccfTtDpfx+YDV zbpc)|9TY`kh0<4T(2DzNDO=ZKJZGZu^fllZkZXR?YH9NlSVy~r0yOCVIlNVZ;XgNd zE6O9=Wd?K1kBeKtDLz`J!~fk7)<=uIk6fKtdK4#=?G*d>grW3;R!6Qi)4K^KY_k(i zu^~#<+A>=~iWLB$FC9T>ph{U;+03&kAc@<}>|)g?9!V*wtcC{n&O}FC2fNl>#hkSy zrv+5S>r9%_C{-O(LnHTPA1on-Cs{Q$YJ5ila`x*(D!38bkNrw*%f)eDA0MAG>t!KE zXexX|FE&B)L7tOf2;oo5ug}5_mXYD%dQl~e&_onJzBe578UR$1U3YSeSoT$L6{9yd609tZ z7aPzOJvt50UDl&iGqba^T^K_V$i6&cs$CxT%b)3ukCb?%0}G zHtyzV(#+ht7k9~yHZX}=Yg$YCQSo5Dxu;NYN=4T=Gi*VfiDy={TQW-$qSiqEA4hm1G1 ze$3pj&*DY#sCSf9RM;4vOlNH8Gg@;D4DuOt8UnPV8Fc9>pdjMF03#K_sVv#0^RH9bZoG%=Rpm-p%e=s+Pl z>&zJjsmgXRTGArl8itjdiYp?k6!@OvZ!Zc~Q|fTpyV#U1AACM3pXu0n(*Kyn1_4T~ z_bCsLhCI_|l+7-hk+$RH_`fZEGzy+uCH`-d^+V&yeqaVjKVT@n7Es5J^ZF zI5U)g@OrL*UiWqdi*zMEI+{YiD+EI^y*-kG(F;Yh+VYtuNMctILdphv2W+;pVjQ{O zWSn^joy`G_A3)_W0z4JT)s)W*c9BVQ2_cBwV$3!QOl&13nRP z{_apD!V$C?O6*vap1jELt5y0@;04feaj*$E%MH9xh{qfYr1>4#!^mJA0!pu7C7y{euPE-5U#ZoB5dF&Fr2>^sH{@q(nDE zl%k3lnxbzuMB7?u_&lsb1i#^Im4ymacTWUmn}=$AGn_Yzpund)Xq~3NN}&*3f_K&LiZ9y0ZRgBh9LxE4yIX=5sw(v|b9<^{6dsjYk@>VDiiaQ{&pn ze3cqW1b#kkkF@wlJDP|QD18+Xqw6sp5064X`zXdCLL1!um6V*9mp9X!1)EQrDlUoJ zsj3fxkHox>UQe%NBoJ`Kj=SootC)xa7E%Y@)G8OAT)$e^q2^U)oE~)EAEO#Hb=jU; zEaaR|rY=8bK#d?aaD3#@eSwqjh7VsMusFk%t)zB6xmBy{qN6lh&DKzQ!6MWQ4y^ll z+tg>#kA0nMwD69xXOo2lC@)?K_&&p>*cb-- z#4d#$NPoCEh}xg64(d67cWwp{aN{eRb?geOLJ(1aAiBhRJKA5ekj%PMlkQNds0_dl z;e>P!_t&rR1LmtRzn!pmC#)Z;l`BfMYlX9S^n#dj-e#Xl(V5IgZoVdYqkI^{e$EQ# z(NH=_$C^xr?HuW+p1*FWy8xleIh~$bob9=W`U(b312E+X2m#02%Rm0nrvw>#L6RsS zu3t$@Yi^$?41-3cyZ*2VcU|7v83@|SgH%@SsC4{agMSL^hUH9J22b5+v#L?vGw-J1~0RB5bf(0lRe7hUn+2g^NDYBF@oZ z^uKn380a{@H<}uHQ4mb1ndd~&9*gDxN*J)jV&*Ts@H7y)e)9|=&J@;1x*La-ErCwn zFNtM!Mq@dOJl;j`(VfOG2&F=sav2(OwU(jpa6DA+z(lcV3F{r38fJQje@owa9FSr4 zTT}7}fSbBe&xqn*58V_()++7H;MX60R(hl@zRa4M0Dj~NLd5O+X>|kO`2>Ds4ocJ8 z{yT*y1|f(D!%vBsE8K#qNZX#@0PY0@RPcGRE=wPCN$;Vc@^CpsvRUVCK}?s*B)CO7 z0R!~tVmC6odXbZ~RB*0GEBHH4Q(Qt)WM02f-Td5eqWhsxQfEd7>-err_}6fR3X=OT zS*Njn-T^Vl0(Dy};0>3k?Fket9fJ2e>nytrW^ zBj8b9+8S@CXz}QTr5$=oZ0A=`HYzG0@%;hnmB4Qu1rsm(JFQ6u=2P~C%%egLp@+Fw zk9~O}dEn(Hwfq3t>dz8r=Ec15-lcv;nC8dod@rlQW^w55(a=>9V&d6&cQ`t@fU{#G zoaS1~JnBnr-IDG0YVZDxtO%sy37w z#hmwF#YB;>b$+F%YI(Ko$p`ft&JQ8b;w90;H}jRua^IYSqssI=g{!OeVSdG>@?%`` zo&@9m6_-1Jt{pj(xR{GV7nyOmBgA#`bPt8}DQKq_QW|Rh5~KPlLyZIABV=rtOvehE z-epXcxAUK-mGi0B&1RHqEjgj#1(MuU>v;T*yJsG19Qnb zDsjSMCn0$<8pao||FnnmA-__$xGev_+|f!{@x9HtlS)-_rae!1KW%B=OskCOcc?&NE~y z!c%|B9{vs4?Za@wend~x`Pok6Zj05yhcwr;0u^t8l}$MG9F>qH-gYV^@hr7Ch8trA z$@5$0UYf_-xQ9gm1U%SvBm9z9OHO_dXz-1hK}ST^HTSWxDU_cCTpg>DwKNyYpG`mU zas*U;)u7w((3S>CaN}bBRu*euueY%-QW6r4`YDh)7BA`ykLar#Y8OdaldTDp?z$2L z>O62CW0`i$Hitdf`n3*@7vYg3@;1X`B;Y?rLWZd3e;pG43*dZ#wh9)uvr|fDmOtPO zuAoDsVC`~^5OWJqEaJja&){!IG(Z(icsB426w9 zTCH2A@3G!4eEKs4Uq7lCg+wE`zh}818bDW`yMi)d98cr;!H~{g5Qgxz*B>~gMKo?2 zfh+dD3O>o*4(9kCwK$5{AOP+&9?$H}U^)#73y|MBGA6m;4BYc#Uz5!A+IVVB?Af(i zCN_W3SU3Ycycglu(@nLXgD4OXjmG$s$KU5C^J!^rbkIniR!exUZy5FmOb(-4PgyX1 z@CU~~tm1q`Fg|tc-9HG>(lLl_;O^gRKVT! zSp-Cd_EwmV8jO9D7o05td8wf->H4a1;hw_&z00{k6_4!V(?MG;>%QWHyqFI<#dUAF zasDI=Ll|CPU65;y#p+lI^N+x2&k(5Q_oJPVT{>oO@))(Z0Ty*suk=3(OAq-`oiPaw z5-Lst_Z;hk33zAX4{KJDIo&Qx9xe8KmrF359=FbujzamF&tFngm;L1l3L_Q{ANeTQ zRSW^rqYnpLe_Lq<5|N(vJM=E?A?em^$C&b4Wje|9g&k_g*)5bAcrczfhmxQCz~=t_ zVrjgfuj5XT6h{hb^A;G24^Pyvg%-xTG^~mp`#T#!)+Wj^G=3Zjl8u@EoO05QSTA^- zOJZG?zx&wkj8V}Lt=(NL#@yXpND+ay?x0uRqELV~mi-71ROCYOrEbj_`k{p>w)7!u z9iEc1NJol83X*#C-i=JBGW=oy>Tli7O=|NF*O@5HKCfl1Ne0eiY>;+NADr3g6c)?M zvP4Qv$KjXVwtuiKST;y}_UVAaTfi|-M(li9?#h5;^50Js{fdc90iAelOTvWr{bvgw zX=jU}iQ|da#(4=il~eF^!^B7G_e)1k`M#R@)j~KBdx(7iC!zE*`qpSpXkQZdWW1S~ znPHpnGZo)3i`lAx!I&Du@SUBV$KJ!T_m>RboXpHee{^9mVfLJqk&zMJS#lA{P$`sZ z2zS-HXa90GzDZ=&^59q&tgRVb`;4tf2Dd_Z7^50W7ei=u;{)fbB(f6r-y+)|(TgQV zW+POx)F_3Y=#b!C16wG1@4S2Y{rk?(J7nuSUkC_s56YN4`bN8+#$-di3#@sZ?`qj} zU`*^CIcF9WNI&-DaM>**udlEDFZ^J9<%no#q$uQ)&0#&H6FKCy=q)3krm%eoiH1ba zaQJ4;uTHB(i^0R?s-c`@HZ!S^xO8IMl!YS|His%bIE5nR`iJGT?uVB)a9t95Lci=Q z%k{1kgic)|p4)%=xon?s?6K9p{|1itx-a#TVCjm)P6Hes_-VaKIo z^W(^9XP+Y}_94WkLPcPF@AGKZb%Y37vx$OIfKV?Ote>4?pScTwg@v8CV#(6!48|tS zK1D(Y1kgfCBT0&Or|(~%_wk9F@!TXL)b_fZ(7X3dJS_KoS99?N=$GL9L5gf_n?24Py-Xu8#dCtCGEt#j}<76$B5-|Bx zE~3IR+i_ldJBt0B?R3rB{7cQnm5!mG@1Y*$Gxd{hR?RY)0?1CKqtVd zU|?ZY13ganXW6C2^{Nrto1h+6+V45j{eIbaB#Al*8nTxYC>Nr6Sw-@zI~qMtW60!Y z;Jq?u6ld7hSpmFgHFkQwzXp$vGze?6;5-BHYbz&Y)KLq<1`iEriWg`KkrV!rnD#UOQDsuCVW1(292@(de{LnW@&KRvK2)6RNy>*M@ zb}C|>92@I~2+4xfu(C2Hn|i8!+<@|3doIJ<%OjN~KdQxuq$K=+HruB_Jsk+wjO7=# z2mM{kzXBI#R2gQe>zvlj=1UK>IWCt_BV>nTNBiZvpQ?gu*if^>j$;Cm)Mv z$!T+%$Og*rv3)m$E$_oU2$y~=U_wg(fMdNjB)RqbQ*PHN*Oywo>7a=>I2F49F!0C~ zy$!J~p{S%{_H$vZ5U`SkY@{O@?SE1)CII&#LF!pJgWKkJ-Ky~(W1XU4=^TzHe_kFc zTFJDz?GaQCD?bajaFyz1!P~IM>&s~+9!`ETYN5bHEsBX*zm}Iq|C+HcXz2yEThr4TQ08QP5A{Nj9DyF7h9AwL^jYf3K;3(Kv>; z2OhQ#%Yg8Rg0j~S<$&K;E$k@(3xi`V{gZv|;fOt_+k_SD=h!kvNZ}7;5K`6;wB0Lv ztEAr<3Wn$GDAJW?CI%sUefk8bJ*e9)sI9BhU^Ab33+rP(T_!qb>BNDIZFj?BsNMMm zcT!=8oSmJ0<66QGGCnF8HvXRs)(aNScy=AEI9hO})Tpa7zlw?uDl=~u-w{qE zGca!w1~=5OtcnLpZ+EF0Bi`y`lnaAl#gB;Ogl|I|AX4Tv!*i*0~cPWIt`|0SqqvM!9Q&E|J#v&kNIPYT=eM)}3~79BM+u@K8;}w^%INAwn;(i5;_c&CK80Si z`9kuI8xlLNn_UzCUV-mVE&+UuSR_k_$D3c1~nP1?i!R6I`1#n(P1`sYva+*PR@>OwwdZuKY02=WYO16@J{-I z6W2eyfdx-uX<=fX+H0H$mopnoQl&iYu8>mc^|Gli%}WDtG~J40J@+h&!7d)MUobXvHu;YXxfDL)S$1xr$F8XT;f~#4Xj}BZKgJEA z{k6t-D~mfD#S&3{fzKFQL(eaV@J`g*9%j2o6N6M+BQHr z1C9xIb9Sa@4QIy_gOy*V2h0~a*eyp7Tt=G)bQhW|^5{e_Mm?@vW?|hK;N;k z&ur)x@89Q1$G#HGmP?_3G)o2t2b0Mg#UiwaJ_-W6qAjX_)ng$dxDg6R!`R8^TJK<0 zW0!(A-XeX-ZwZw12y}FU^%VlEp>-hFxlk$yMWMpJ?A0C9BN$18o z>`wGtpY0Z+G4TpA2~s>-m(IB%8pWnoi+B3jEMzr510nDQV)6TT(@luVTECeyZ4Un? zFtsI@t2r|1w1RtON+{>K5!w46GWE-4|FsnK{e=r5_-6f1b2ff>9M&yQ5EuoC{gCl@ zPPcq~eCS%qIyyS^&O1Is0gfKkvRfjh>)&cQj0j*BQkS)Mb?lhoa&~;yJlk6G zUq6csASx=Vw~=63_ZmVxAKB5t92Inb7?1tdP;?8*Nh0DS219c5lE>;4=YN*4#~)i? zKlYq5S}pLH@qIWisF}BXe0*=XX$s--yEq|dnc^Zmh5P%{TVd%56G;d?nsn>sL%LVx zP8?s-{vFF>wmmLB10w{5pJ`mFKoi{#lR5os((arFQH zL>iDlGfi9ix1%%_U;tR|g2YY4b?sVp&~cmF^T)5y`tKJX7X0YKSyeag`}xAR7DDao+8KmXnRW0L8(BlJu$K$zh=H2r%55%xFfEnMrJ_X4pI?dkYY%Y%0d|HQIIei; zzt9BpCD{~l$RL5Xc&eS3XHwFr3(N$`|Fet;WSNf^U>6PS=f7U4!1!_9czm0tYT&RA zmwUn99b53#wreQC;En4;m5j@u4DfeY$by{A#${%U<<}-*{V?sp^6MWydxloAzFMCg zt$XNFWM~hTZJ=QwppL>&eNZ34%cTQzyXt+C_|q!%dwo*FEa|e{+`|3W*6F;xndWU= zd=1zm?qA>=A{|dAsiF@l-0KUWugb*!9~Vgk!iQ$^66s3H__-(`MlV07Tgl|6%Xg)- z;oRpZ2l5ZJ-lOo{y~ik76RMOEww=96qU=)dvC2NOT;iC5#jn9s+RH@rspE`#8zxU$#*wR?!%;@_0g1u&oL354eIzChJVwA1JBG^#+ zTw;5*7b7N#A67#j4gj5dG7b*#eKK7jNcg~U6yl~`9}RjMuYA2P94-QjV4X6%}=Nr_a5kX#pPQUu3e?DsPoLW@0}dC>b1Gqk}1wY z$0kdm07Q^u=^FThdg82@(G}sZ-=P3WpIXY>?qxj=nvC7(KLnu+E5hsWt=^TABG?-O z%3W_1X54u-=6rDZ1~U#rRjZu&_n>~g^Y3Hm3NexTTWZ1gC%sffc`4LXIqfBV>se8i z_FZdab!WH9u6}v%ceV!DtG@c)KfGiG-Y%>(vU%SP$eJ!v z1-%iT^;l90K>OHzeUxZR{%NRcdeEt4D8m<@8Lwj{h1on1zDz(5%$UE_YLwxPfvWp- zz17zAK}|N!8>f%~tr|tVblJKI@^-e|lble;(V%MN3_hNZR>7n>Qrl;Tp}V5>vY1pG zAtFTFqfgFUXM_YzE33Y33;Mx6{I~VKA1b&$khJ&y( zuz2T)C8j;?VsuuBYNx5)MpJc!&fPlaHB_0uFu|R9GkxsWK&)p0Wj4ygvQ;6w3rE*% z7USDmAvyS?>g3pK)hMrPPv%L{_k$8DJ!tEL4c=3KacHe7!%g8j~v>0qmp|NNzz=~Y=@K8wfU3?Jt&qeZ`xf#gt|ZAz0snQTQj@g z{neF6!uws6^${Prf=VhgbHm;iiR;w@zAwtHY@n@h;18@uDIK;%mcKSdK$Y~zc}Wgc zHFb*0dO?PAt(mEQt79IcCgIaM4NvPPe5sbUHCa+`zmZ6P8x~eQ9#Ll>hdsoap!~Kl{g)4gksa+usq`KRZ7mB9wDl7f8iF zexN@T_g;aaqVD!<;Jtd+2_o?rA|akr#o{$FH2!cON^?_vnYhzugjO9~F=u-{n{yLC z?gMXpN^FBX6(gU%a}o562vyVM=)R6+25bqZAweDE$6zrNCy*cNGD%64tE_UsQOISx zZM|vg%D)g>!wLR_9dRjLGbb-F<$~>k98A%PNBMnt6W-VTH*#TOB!vDepboQI%37PvrXa zJ>n1LDaX{y3mhL-Ih9JFo;l6&(9+YgJFydv6%jnb)s^qDS$7SvFwc#TaOZ=H-WFEE1X54wMc5#^% zq0(a$3_5Nl(M7!CY$K1kF)!mF;Uo*dG4WVraxx3eco4npc_xSujMRG5=_3qfTg8AJ zg7Vvz%qM>2KsPx6hC3QuN9@{@cyl=TBkXWF{`$oR=*nzaXbVAc)|!D1-BC z;AwdBs%o)C)-5R`1(GHvqB&MS9_hBvaSeFt^<)`)7}NJ9CmqWSBaw0&!y>fjBY zYI#j%yX6aO_uePscc?a!KFho4Cz9WTy>2v)1IqZzPq z<_pHic!k{K-{;L*#5||b_yf+rYzNe*uPL7KDU~2vdf%s7&W;8l<73n?njTCw#=$5u^hziSLsoiPDq2`>+=I zeM!6vv^MD$CYK0UCt?h1FCK3C<*&nLS$*+7%%n=-635uo(}F7*b!e#2#SlQ&q5zOr z&5E*UiU1T4a&Yl_L7{lok-ZO$&)dl}algl0M?2O&aHalM>hWcGoBI>Gxgx;p;ND*6 z@`dB=3%ONc@$h19f~MK$CElH%8f5I1`K?%$Ct^*{0^VzM(i786k&_jFzXo)+7SYki zpBL>1Wn;2i!*TENSh%Y%n_pOks*Fr?uydyLjSO5>ioSZFr`K!kl6Jg}jb3~0kzB>F zm{=;piSP@c|8>|q0+_xfy#rYXFSo9Du1qKR&X}{6e_Mvotr>rT4Ouy5?*TRp%~BFm z^|B{4%926qo9%D#kfcL4BZqf*Ip2q8lau>CwZFM>9tyOGBNGZ!RVB=5<@t6%`vjb) zTt84Hh5N-PbLl0SrtzYirQ-K5@Ttx2&O+Yv<+c4%Shtxmi``Vb2TOvU`VC-Hp8K0K z)W)rSZt*vIpHrF^W?)e}wfclWB49*+bbm~%V$LGqo0)6z#6VFXQ0C6E;`-bU75LiU z`4PjnNy2EvJHkRAFx0`QODskPvD%F^gjCxo3)qN zfD=h*Brw5>bbD{llVh0EGiKV*A#(opU_Una{AK7l^RPXpyH;-qAv))Jkeh?|KgimE z^inH|W{8-G9c zGW~0kY0-n%IJ6@RX!&DCSgW-@Le3XoGov{QurS)EZCA`3A{?|=fdUJf#=AVFdLJ-z z?1()sT#OD;f>+>Hzjk1BCTa8T6=#&nn#IgYnPeqRGN^T6a?+g3P#O8i5glF==mDUB7jd(#( zMoyY!#&L!OLEZ6N{Q0_r>_y;#YoW&IVI~oKd{264Ty8Q;wjcaK$3#ne(;xVcFDo$e zW{tbYj2h-!=eL4V9@E_iG=6$<=O{j@>m8E>z)--3dPjZ03#EFE$K6}bCu;b8m zjJ_u@s#d}nlH5y4-Wy5~Hy`S8bAY_wX>-+05ZNB`T&`R|a3u~Ge)JOqa%ujT^^5i3 z8=nV_-P5qPsb`s;M8{u0C;;Wr0ut}3f_<<$L0_XYOsvwP0>&BRp+rI+zO##)LAq5P{R zLj&$$K14`gP|OQa_KrpgcUN<^N5hjFdUquZN10{V)&gh_`6lX5A%k&;4IriuxZiHnBc58)@p zJCa6xir}Q09;!}z{{kn`I7Cxdd|u9wDe#(t5{ zX=Jk0Q8%IJ+<51=6EmYZvl_a+=V8yN^eP-RxSJ2Z>rn5@n9a7{(@~}d7>lqFlVlV- zB~i#ISrc%p`=5w#<*1^`x3wjni?nl!u17}-qF;X=g~l{BVNCRtIv)OtIglt&u^+NA zIB%YiynVo4Jq#fTc{}~+=-X(rU&j(n%+jJxZ>FO#OM{LsSL0eM1m(%PBF5O__I{As z+|>YaUM#+Hh{()AI|@cf45p|3u0t|3>7CqY>TmmeTU(ccULyR!kSm8LJn~Jvm{>cJ z2$Q<-XDdZ`F?qzCY{dMtH6`YWn|Tc+DWWAD;E#z>)*@0@=$|C|83NgL=WrpS-B_e{ zoK!xCc3)niLq)DN_qa6Bj9M9?MH4|^84t+PQC^>g_f4We|JlGO5?E>qa+No%To<$-Pd1-%p4vIK z*^DH6a#mdj_5lBsiqhevX5V+-%VAN2_;**)b<_z1_a&h{xF-y{la7bOF*>0 z_JimZO{s6y!qn~fVF=Y^Sm(}*>K>va<6H7(4(P%>Y_eU08n!;3WP&mpX@b(d^ze~z1$6lAp3TB%rnYoKR0?F zQN-16biA~DOi4*m$jp}grF{2HLrQML!1b#|g2W9%=QthibHNWR^BWe?V=R`_Qxov^ zlT@`{3@`0fq`8l0cq+D@3jeuPEOQX|d>l629e zgD<|xovn!4CV~D`Y)kKf^aN;ybxQKeQVhjh??)S7-;s_%8G}%)-xJfrX|$Jxkx-3< zMq6@<>h5(bz+^mh<;qpCRl3GQwQrfES_EyLqKAj1aULzyRbU1Pxc<13S!Fm^J*Y^w z1Jy%mv5-k3DvNw1p(3t`o#R)i1sZ5bqgLp--`}eG<<_Kc!=H`P`VN!$C5aK}mg#Cb zaK%9H+%U*G)HPP(Ht;5RWaGcUk`R-ZQ|w~lN@h{6uD6U?Y}(ja>8=#2IW=&K>nbJ? z5az%S@GqFa?^CRS`*9YgBuMfm4B@C8p{%`92+3HM2xVYPhVM4v+F9Qw^RPwbajU~;B zBS6CFE{$iV&$OgB-@JJa%}d=;J}TZo*9Ogt*)t^R7^c4E9a+s=#j&O{S ziao42uB5Iix_iQ&U%|Mzc`xF_8V&!Y3TU6YCZWkTxjuXBc#>Y4MjH7;UttQO1E+6` z>FeA!hoNx+#C(=4IVsig)Kj*$<2IO|ON6_z#Pg_{zhA$xEJs(F35N1i5>|9|rS1yd zrn<#|*F^#TgR{c}g4hjNUc7L3UM8?zW!QQE>n?)dRD9dc{=Udm!5bT z4~r#+>R%H)_b`B*5Q+Id=i0N1fF@P9v1(jx&7QNlVrHAo7}qi}%!CwBYwwIQpKCBs zYePH4k$Om-`xYmtxF&^ZX*N8pfQWlr(12oW4ClMVc~?Igm{Z}8me96`g4-y=x}b@>+ZMc@?< zkH41M$7|D+?Tn^m+TxG~;K&n<`9U_&XyNJzSZ`rqTA%OYnmm5B0V^v?=CHS#R4+Dz zm3vn8T;r@0q5O;BRBWt{3Z}*jnO~*I2JR|6yDs-PybR?m3!^k%pmT{7lS>s=?9QvPEU|+N{ zA=n#1#GL&`zPBCBFOrLi1`_<9`J?oG-uiRK1SF@tNXBq3ihQPepTe{{=0_pN!szxi zV7BXXiTv8`!L8tsI_^s+3jp8kcq@5h!x$M#joGMKq;U6f+jyjop8JxM#gqNT`nOi- z+Oh4m>74ywSe}cMHA>HaZZ|0)+;@4t()5`tZ7vy^Nc+*8jBr%g(kAJIJ>zD%qJKM? zg2`pR${}q3oik2jIjEwuer++itsUda>oJIT5@IkwTd&33Vy1Le4jfu<6R5#itq~RhdQG$uD&EI^DKPI zf^y(it|w!?o9fWtLWLCCDT4b8GIn-0fA)R6BNpKsWa50=iIL%B zUk3Q#8@*a$uUCVh_`dYeT+~$zMLG7{#c#fmZ&v(2uF>-{v?|IxBuiJY=KDqyG?xa~rJA&}}X{C9|FOUGhKN%F27iIK;)e?Z@<=ix$yfplI3U%6I zQ@yuGt9Q=I9x>{7WcZoX)2llYllwsL-c|uVJ+(?o&Ds|dye`T6gD?fhu%2$Q47_%$ zZ?Eq@)Z3%_KWz|${U9|?A>7o!9!tcct~+AH1E$gnF12oZT^HRv5tGyMP|EGi$fxER zP9SB?qsDNy5gcIk>YL@9Zglf%>-s9^I$MYP2jEL5=mep9jEvon^V!TAQ59Gkm(Hw% z>&F=>BbC~(tb_q1fxQNd;T*08m`ZP6XR&4tXHGC0Y|sH~@y35Q~~ zxoU_Ib(-x`vFCbom|9<0m(v~E7DPwfYZ*w@b)%VQ|N1f0Fmb>qkn zvi)-|u|*HWd`N8F)}n<;jo%X9y|JR-6CEtzDQL&PUpP3uYr?ZYm)XND0um^I2)0C- z{0rS{8K~ZoMfqSJXZ;_0Ulmo?(sYXj3AT|S!QI)x-Ccsagamh&;O_43uEE`dy99T4 z2<~uqj{X0a^Nss_A1-4(Fa~?A-m9ytYR>9eO5wqAay6lyLzWZUMeD|)8aPCI|!MEPmPjlPg;Sc~6Rc+pC829emf&^4DpR=mf zYkD+LD!^T*Ejs`?N-YgX-lhGoQE0yTTl0787XYhV_ zb7h*t%IeF11e+JoJbfm5UqaO?eDZGa7Z7~@(Tt{RQE$pY*HEb@o6{Pyzr5_P7^_u_8Wov`xMaM<)Ma(^@ke*7J@ zKuUX8S2vp{u9YG+L}UK7XR6`(n%^U^+P$CaB)E?Cx(WiKLcH_blSiwn@ULR{F9#HY zm|R7%cQ~eHd8dpyed~-PuFzLDye9q5As*@0rmnlo+Z~kCpyCh7CfUYjngd_#Q6Q?o zEw6^Nc}=N{igJOGDl`HE4gICTYn~3FSSVW+!UT!cKS{{)HsJd#!NJp^elIf_0;0f8 zQ6Td6ROJ->G9_bm(Y?jd8Y;gEwcotD*+I8BtMNX-UEk)(Q)RP?CH>&@$=DH4gp=s6 z44J0U`fj318k74pvpmTcW4c>4zO?$~=K3t+i2iL%^X?mb^o|$*k4jqd-*o|{OJc;x zl&?3NN-2-4G7c_M4H?7|R?{(n2LH8=j#v<0*l*GPKp8`-9LPG*p6<_Z+gg_nGY1os zmunna^o1ytT_lLhN6cL#3IfBk_58aHk3}RdMa;LVT)-EfAU68}FuXUdzG`f+G)P_st~9Z6yy z)uhySwhlx+GgcDQnv&+7=yQ49!&EHMPY0@>9n0}|3D<|9ekQKOf1`hZ{>%I5NCDf} z;x##i#l<=GG5&<9YJh4PIH3^J5Y2gwz4B8TOJ-ty*pKD1*Qh%d?qH{_M&V3HGEjnU z%lj!WtESZ77^WNg)d1b}L^}Az1wKOoI^+(@tf$A~oJ6a+yfdMg*Pn?H{*_?)vz(#G zk%N?|r18@xHG?s5PvW(;B)*mL7Fab$i9j-bun5B^;-#g8UH3K!1#AXBb2P*DsC~;# z@Z^p8MsPSc9TN}Gh4vhM&F(r7rWSVzvr`m`Z{gRlTvvEnAerW$lXTG_`J%;oK zo4d0@gguno!&AblcYiPUK2BRHpEWpl$l{x2cGI+?GACZ_U_y4ir`_HR?N~HA$L5NU z$H?Dj0U!`n0EpjG7MIBrP*SHy?>9H}&{kR$DGjudWq;?~e zi#>X@H>@&GNj6AKyc1qLXzwqw9?hOoCze(6(y1Vc(>N8`IX#@HTAX!dFAM~HU@dce z+DvYIGOv_`2cuum2Z%F3(RS=zdR*L()AK80QukN0$Q1kXGGBhE*~d*tPHhGO zK5l+C3`Qjy?XUzqA6}O$^+iyjj$c}-DQqd7KLX*J>RvnyJbC;hsLeMsQ$H;p*Y-8g zn#RI`L#%{@vc$?sNN&g95nCF9_jHw-ZYVD3TuHAYfAkI4{?c+?EvFGrdPVhaw1uIa zQCmvlU~FGNM3%|%osGvG*4rBd&Z9j)G%)X7z|S3+jpXAnB1OawyoYGTUh$!%cVZ=! zxp_x$f;|FXC1s^j5)Y^{(1(05|Ano+_=n-7cfDOvoDpP^h-{)OBTZl4zDA3<+N&Zi zWMrrcyy|ZF<{48OIRR`aq7LFVq#p2&-Q<-%jnco%xcRtPmO0IobefW4uDEM1(teT1 zj7=BpeeUhzlBg3FQitM=@MX{Ia-mCDP(f5&BV}pqOky+SCpH&3Glof%!PT*<*uL2J z&uu@D^~Dfu9s!B(f<2#f!Jbds@FcZ}kJSC+hjxfq7Im7+r1#Ne!V!0edB+6(ur_C{ zykHHUj(_bHz1n%UN;9<`ga5dQZ{kBBbOXuqBpAh@Lu@ zqkB#Cbxj~brI=9$s*V{SEUl1&i(tP<@e3=C!l8H#oDEVoq6$p%&?-#{`mt*S{S*_N z;#|2HGSoIFHIVf*vDt|>(DS?3)H3er+M)5Cmr!864n^Fi;^$Z_Ta5jfX)%;F{9Yn| zN?wrWBN*}X!1kjUvHyxczCm<9Q)yDnfof!F+ZQ2&syYHwIu25QJAQ`8Bq{`bHk%5tGJ;2k{Hz+Ts@uOr`;P z1O$Z%iQi*=UlwL3COSm{cwfe-wezF+w>#{s=4pF&ZJ+)ELyA!ixN3J|k)F=X7nI?S|)X1~AGclr~e7r$l1? zzbdfF$d=Lzd3cu>(LBaC)>71|bjQ}!=4J`?#a&M)EEI8OL03-pl34eHbHX2c=6(Sa zp(#x|gD*)vwC#MSdS8MW=)dZg+q@elT>n*kvl$!?k@;%@@js$O!31^*JpZuwys)s( zJN85MEgMpoYLP;!UGq*|uuU@h@bN7{p&x;sU_f-C=9W%5bB($Zq_>P1|7kA@1HNer zTUX@G-Yhl40B8Z38`umiM8N8xjqdM=)F|BhEBa74OPv0WbsthLatBiU@RV{>4NfQ1zY{(Dnfs8goyA(p}IR|!1U&Pf9D;c%YD7w ze%1JGY@Q!OKqIEPK8lDZhEmXcem>D=_y@;O>#aRk6dQ3#EQtl;ZX}e%pzne11Luxu zl^OkrBZ4=v0F*#-2$bxh^EAdo>7C|9(td6+Rsz{>UaHJ|RL#EUMlQk!^Eq z>0`_bZu7%gcMx1-@IWKX=4Afs!_ubvRbxN+#ksv|1dr%O^pjz&_(m-w*A(p>b5Ji) zn|csw>{C0-c?2xBD5?KMo0XNSE`^28M>V3O7*tVVIgZ{0bkVblkBY?+FE0K&?UOgr z&aSZKdD{0YJMr_)s4E|M1@bLiOlIUU{C1`oUbI;;?vG7t-82(Yws{H0p7F;!hKKLZ ze3r)}1npWT>w^iU;nnNcDlER^LTM#~vP^}W!zA0^+zkwZ$|E=~CP}xRBUax=!8GObZd}%nf)cooiUk>x%}zUDKMElI7Sc zLZp%kxyn_wBoL+f`uU-Qdw|~ehlaRhn$Aa=DHTm-ObX0o4b*QHyalR}aOWzJTN+(* zyZGI6wO)#0;%?umge)`T@^JZt)nZ0B!6bBQfVslK{Gz0R3Tyb_wXbo#;oIdb6oe3y zpIPl*E=u$ep$KoalXA4w>Z|8P)o`@+YYj!_vwG?F$VzG{+cjk{nVup4R96XT_fVnT~gm5XZ=plmVZj+ZH45&W*%A%~s7hoLQegki}#|+FkzS|HH z%KiCul3Z>Q_ILBwqJY~3tKFr_kD}UzouP^9oDL{NKob8b`T~O(8DNP1F=JtV^$U^+ zq5J&GdO>h7I*KvgzGZb4IW>ZAVMJNCd8)={0nr#-L)x4oPRNkI)s~7O65O~~e;K>qLHi>HD+VC! z+1Ba_3G*K1-Lu^on9EOJ?PD*YtEEuag(uNky`fe*e-(|EO_z(g;m1il+Qw$r6cGdx zs$7rzMV0ap-oy?zC)tnLpC(^Qq@n2G+0ycE=hE|ZThKXftJL7ZL1~&10`FF*AR!&o z)c3C#oJcCCp<$XRUn}L9J1X1UTJJNvYo(hQRd)pmFBD=XHNKDK3-MUO3{>pySB%kl z!mjg9=g;Coqby~eX8T1usPt6u%L}@Pm>y$UxEIsu4JP}gGZciz*Tb{mwV*sbJ-MZt zIvs>D|C4;=LvbFp5Zvo}G!SV^@X4?dRUp#^m!xoB_qywK!%8p^Uz0+p@lTS}et8?e zdGVfe2y%;9GRoUVDg~|Lh;SvOmSoR^aXf{XnEuDyL1*0$Hc|Q(P4om+8VX5SM_9O@ z=UMz2Y57JR17on8oLTvD4D~ZpJG-3uxI5^ykN1rlLG0hL;mc+8E;%vwo+|kQ$Bu9| zYBR%6td_}(ZF>v^pwCb~q9wJhQ*e%&XppY9+d*A_p?KFsfUjN~NRNe{FD$4*bD@dG zhJgZ*fU3C+gT)uUeLG#k%!x@6Rj7ii;9zOc5XGfteQ_&$Tm1MGzcel{!#nm~t)3-T zPF^$%EaL(3?p__ffET}wN2AKMWLd` z&>m?8XY8Zc1iFlN?9doRgrLH4&}09ttKGhsgnUmJQ{*jR9vriwSOoBH=5fIyW;> z(0xK}F><)<5T_kKmldJ1E^5ilsQ)2&Ur(~5pZpjeA2x z^L5Bv_&YfP%g3Ngxg2B^j~?jSV-Z~%**eA3h`=owXLW0Z|AdRDP(M!#S5UUV2q#S7 z%(>IV;;)NUdJev4z7aclN4(75l|vJ(hN0P8%`OQ=@g^-|Ac?ha;hd(#bTDw5Ny0vF zVrcRn-Ig=;X{y8!UbdEXyHzBQ)TAIRT2ÿH1RE#)Y@N=9#ZNfsS-#uJk)g7ORa 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 literal 0 HcmV?d00001 diff --git a/docs/readme.zip b/docs/readme.zip index 5ad632fe21067c355e7d8973ca150d5b98ef0280..7484f74221d824d1d85001a9e0effaf885495056 100644 GIT binary patch delta 75942 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 OLTyF{hNdVMAP)d}ijM>U diff --git a/source/base/pom.xml b/source/base/pom.xml index 68a2cf0e..9a67837e 100644 --- a/source/base/pom.xml +++ b/source/base/pom.xml @@ -8,4 +8,12 @@ 0.9.0-SNAPSHOT base + + + + org.slf4j + slf4j-api + + + \ No newline at end of file diff --git a/source/base/src/main/java/com/jd/blockchain/base/data/TypeCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/TypeCodes.java similarity index 97% rename from source/base/src/main/java/com/jd/blockchain/base/data/TypeCodes.java rename to source/base/src/main/java/com/jd/blockchain/consts/TypeCodes.java index b2e28bd9..3289a18f 100644 --- a/source/base/src/main/java/com/jd/blockchain/base/data/TypeCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/TypeCodes.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.base.data; +package com.jd.blockchain.consts; /** * @author huanghaiquan @@ -83,7 +83,7 @@ public interface TypeCodes { public static final int ENUM_TYPE = 0xB20; - public static final int ENUM_TYPE_CRYPTO_ALGORITHM = 0xB21; + public static final int CRYPTO_ALGORITHM = 0xB21; public static final int ENUM_TYPE_TRANSACTION_STATE = 0xB22; diff --git a/source/base/src/main/java/com/jd/blockchain/provider/NamedProvider.java b/source/base/src/main/java/com/jd/blockchain/provider/NamedProvider.java new file mode 100644 index 00000000..a1e08b27 --- /dev/null +++ b/source/base/src/main/java/com/jd/blockchain/provider/NamedProvider.java @@ -0,0 +1,20 @@ +package com.jd.blockchain.provider; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mark a class as a named service provider; + * + * @author huanghaiquan + * + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface NamedProvider { + + String value() default ""; + +} diff --git a/source/base/src/main/java/com/jd/blockchain/provider/Provider.java b/source/base/src/main/java/com/jd/blockchain/provider/Provider.java new file mode 100644 index 00000000..6843a1a6 --- /dev/null +++ b/source/base/src/main/java/com/jd/blockchain/provider/Provider.java @@ -0,0 +1,11 @@ +package com.jd.blockchain.provider; + +public interface Provider { + + String getShortName(); + + String getFullName(); + + S getService(); + +} diff --git a/source/base/src/main/java/com/jd/blockchain/provider/ProviderException.java b/source/base/src/main/java/com/jd/blockchain/provider/ProviderException.java new file mode 100644 index 00000000..68840e59 --- /dev/null +++ b/source/base/src/main/java/com/jd/blockchain/provider/ProviderException.java @@ -0,0 +1,11 @@ +package com.jd.blockchain.provider; + +public class ProviderException extends RuntimeException { + + private static final long serialVersionUID = 6422628637835262811L; + + public ProviderException(String message) { + super(message); + } + +} diff --git a/source/base/src/main/java/com/jd/blockchain/provider/ProviderManager.java b/source/base/src/main/java/com/jd/blockchain/provider/ProviderManager.java new file mode 100644 index 00000000..af6fabdc --- /dev/null +++ b/source/base/src/main/java/com/jd/blockchain/provider/ProviderManager.java @@ -0,0 +1,247 @@ +package com.jd.blockchain.provider; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The ProviderManager manages all serivce providers in the system. + *

+ * + * The service is represented by an interface, and the provider is + * implementation class of the service. + *

+ * + * One service can have multiple providers in the system. + *

+ * + * A provider must have a name, and implementor can use the annotation + * {@link NamedProvider} to specify a short name, otherwise the system defaults + * to the full name of the implementation class. + * + * + * @author huanghaiquan + * + */ +public final class ProviderManager { + + private final Logger LOGGER = LoggerFactory.getLogger(ProviderManager.class); + + private final Object mutex = new Object(); + + private ConcurrentHashMap, NamedProviders> serviceProviders = new ConcurrentHashMap<>(); + + /** + * 返回指定提供者的服务; + * + * @param serviceClazz + * @param providerName + * @return + */ + public S getService(Class serviceClazz, String providerName) { + NamedProviders providers = getServiceProvider(serviceClazz); + return providers.getService(providerName); + } + + public Collection> getAllProviders(Class serviceClazz) { + NamedProviders providers = getServiceProvider(serviceClazz); + return providers.getProviders(); + } + + public S installProvider(Class serviceClazz, String providerFullName) { + NamedProviders providers = getServiceProvider(serviceClazz); + return providers.install(providerFullName); + } + + public S installProvider(Class service, String providerFullName, ClassLoader classLoader) { + NamedProviders providers = getServiceProvider(service); + return providers.install(providerFullName, classLoader); + } + + public void installAllProviders(Class serviceClazz, ClassLoader classLoader) { + NamedProviders providers = getServiceProvider(serviceClazz); + providers.installAll(classLoader); + } + + @SuppressWarnings("unchecked") + private NamedProviders getServiceProvider(Class serviceClazz) { + NamedProviders providers = (NamedProviders) serviceProviders.get(serviceClazz); + if (providers == null) { + synchronized (mutex) { + providers = (NamedProviders) serviceProviders.get(serviceClazz); + if (providers == null) { + providers = new NamedProviders(serviceClazz); + serviceProviders.put(serviceClazz, providers); + } + } + } + return providers; + } + + /** + * @author huanghaiquan + * + * @param + * Type of Service + */ + private class NamedProviders { + + private Class serviceClazz; + + private Map> namedProviders = new LinkedHashMap<>(); + + private AccessControlContext acc; + + public NamedProviders(Class serviceClazz) { + this.serviceClazz = serviceClazz; + this.acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; + installAll(); + } + + public void installAll(ClassLoader classLoader) { + ServiceLoader sl = ServiceLoader.load(serviceClazz, classLoader); + installAll(sl); + } + + public void installAll() { + // 默认采用线程上下文的类加载器;避免直接采用系统的类加载器: ClassLoader.getSystemClassLoader() ; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + installAll(classLoader); + } + + private synchronized void installAll(ServiceLoader sl) { + for (S provider : sl) { + install(provider); + } + } + + /** + * 安装指定的服务提供者;
+ * + * 如果同名的服务提供者已经存在(包括ShortName和FullName任意之一存在),则返回 false;
+ * + * 如果同名的服务提供者不存在,则返回 false; + * + * @param service + * 提供者的服务实现; + * @return + */ + private synchronized boolean install(S service) { + String fullName = service.getClass().getName(); + if (namedProviders.containsKey(fullName)) { + LOGGER.warn(String.format("The provider[%s] already exists.", fullName)); + return false; + } + String shortName = null; + NamedProvider annoNP = service.getClass().getAnnotation(NamedProvider.class); + if (annoNP != null && annoNP.value() != null) { + String n = annoNP.value().trim(); + if (n.length() > 0) { + shortName = n; + } + } + if (shortName != null && namedProviders.containsKey(shortName)) { + return false; + } + ProviderInfo provider = new ProviderInfo<>(shortName, fullName, service); + if (shortName != null) { + namedProviders.put(shortName, provider); + } + namedProviders.put(fullName, provider); + return true; + } + + public S install(String providerFullName) { + return install(providerFullName, null); + } + + public S install(String providerFullName, ClassLoader classLoader) { + // 默认采用线程上下文的类加载器;避免直接采用系统的类加载器: ClassLoader.getSystemClassLoader() ; + ClassLoader cl = (classLoader == null) ? Thread.currentThread().getContextClassLoader() : classLoader; + S p = null; + if (acc == null) { + p = instantiate(providerFullName, cl); + } else { + PrivilegedAction action = new PrivilegedAction() { + public S run() { + return instantiate(providerFullName, cl); + } + }; + p = AccessController.doPrivileged(action, acc); + } + if (!install(p)) { + throw new ProviderException( + "[" + serviceClazz.getName() + "] Provider " + providerFullName + " already exist!"); + } + return p; + } + + public Collection> getProviders() { + return namedProviders.values(); + } + + public S getService(String name) { + Provider pd = namedProviders.get(name); + return pd == null ? null : pd.getService(); + } + + private S instantiate(String className, ClassLoader classLoader) { + Class c = null; + try { + c = Class.forName(className, false, classLoader); + } catch (ClassNotFoundException x) { + throw new ProviderException("[" + serviceClazz.getName() + "] Provider " + className + " not found"); + } + if (!serviceClazz.isAssignableFrom(c)) { + throw new ProviderException( + "[" + serviceClazz.getName() + "] Provider " + className + " not a subtype"); + } + try { + S provider = serviceClazz.cast(c.newInstance()); + return provider; + } catch (Throwable e) { + throw new ProviderException("[" + serviceClazz.getName() + "] Provider " + className + + " could not be instantiated! --" + e.getMessage()); + } + } + } + + private static class ProviderInfo implements Provider { + + private final String shortName; + + private final String fullName; + + private final S service; + + public ProviderInfo(String shortName, String fullName, S service) { + this.shortName = shortName; + this.fullName = fullName; + this.service = service; + } + + @Override + public String getShortName() { + return shortName; + } + + @Override + public String getFullName() { + return fullName; + } + + @Override + public S getService() { + return service; + } + + } +} diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartClientIncomingConfig.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartClientIncomingConfig.java index d8e7c115..afa21aeb 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartClientIncomingConfig.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartClientIncomingConfig.java @@ -1,6 +1,6 @@ package com.jd.blockchain.consensus.bftsmart; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; public class BftsmartClientIncomingConfig implements BftsmartClientIncomingSettings { diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartClientIncomingSettings.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartClientIncomingSettings.java index 9a1548b6..354180a8 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartClientIncomingSettings.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartClientIncomingSettings.java @@ -1,10 +1,10 @@ package com.jd.blockchain.consensus.bftsmart; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; import com.jd.blockchain.consensus.ClientIncomingSettings; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.consts.TypeCodes; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.utils.ValueType; @DataContract(code = TypeCodes.CONSENSUS_BFTSMART_CLI_INCOMING_SETTINGS) diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartCommitBlockSettings.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartCommitBlockSettings.java index 749fdaed..e99ea811 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartCommitBlockSettings.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartCommitBlockSettings.java @@ -1,8 +1,8 @@ package com.jd.blockchain.consensus.bftsmart; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.ValueType; diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettings.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettings.java index ae72383e..8b6fa451 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettings.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettings.java @@ -1,9 +1,9 @@ package com.jd.blockchain.consensus.bftsmart; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; import com.jd.blockchain.consensus.ConsensusSettings; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.Property; import com.jd.blockchain.utils.ValueType; import com.jd.blockchain.utils.serialize.binary.BinarySerializeUtils; diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettingsBuilder.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettingsBuilder.java index 6c25f0ac..1311a3a2 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettingsBuilder.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettingsBuilder.java @@ -14,7 +14,7 @@ import org.springframework.core.io.ClassPathResource; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.consensus.ConsensusSettingsBuilder; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; public class BftsmartConsensusSettingsBuilder implements ConsensusSettingsBuilder { diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartNodeConfig.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartNodeConfig.java index 005e05c5..fd45b144 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartNodeConfig.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartNodeConfig.java @@ -2,7 +2,7 @@ package com.jd.blockchain.consensus.bftsmart; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.AddressEncoding; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.utils.net.NetworkAddress; public class BftsmartNodeConfig implements BftsmartNodeSettings { diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartNodeSettings.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartNodeSettings.java index 50a5e1a1..e1595626 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartNodeSettings.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartNodeSettings.java @@ -1,10 +1,10 @@ package com.jd.blockchain.consensus.bftsmart; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; import com.jd.blockchain.consensus.NodeSettings; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.consts.TypeCodes; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.utils.ValueType; import com.jd.blockchain.utils.net.NetworkAddress; diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartClientConfig.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartClientConfig.java index 6bb9e238..89dfe1e4 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartClientConfig.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartClientConfig.java @@ -2,7 +2,7 @@ package com.jd.blockchain.consensus.bftsmart.client; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.consensus.bftsmart.BftsmartClientIncomingSettings; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; public class BftsmartClientConfig implements BftsmartClientSettings { diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartClientIdentification.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartClientIdentification.java index 3b046e1f..7ce425a3 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartClientIdentification.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartClientIdentification.java @@ -2,12 +2,8 @@ package com.jd.blockchain.consensus.bftsmart.client; import com.jd.blockchain.consensus.ClientIdentification; import com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider; -import com.jd.blockchain.crypto.CryptoUtils; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; -import com.jd.blockchain.crypto.asymmetric.SignatureFunction; - -import java.util.Arrays; public class BftsmartClientIdentification implements ClientIdentification { diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartConsensusClientFactory.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartConsensusClientFactory.java index 51afe4d2..b0855c7a 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartConsensusClientFactory.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartConsensusClientFactory.java @@ -12,6 +12,8 @@ import com.jd.blockchain.consensus.client.ClientFactory; import com.jd.blockchain.consensus.client.ClientSettings; import com.jd.blockchain.consensus.client.ConsensusClient; import com.jd.blockchain.crypto.CryptoUtils; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.*; import com.jd.blockchain.utils.http.agent.HttpServiceAgent; import com.jd.blockchain.utils.http.agent.ServiceEndpoint; diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartMessageService.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartMessageService.java index 53a5f0a8..a80375a4 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartMessageService.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartMessageService.java @@ -27,16 +27,16 @@ public class BftsmartMessageService implements MessageService { AsynchServiceProxy asynchServiceProxy = null; try { asynchServiceProxy = asyncPeerProxyPool.borrowObject(); -// //0: Transaction msg, 1: Commitblock msg -// byte[] msgType = BytesUtils.toBytes(0); -// byte[] wrapMsg = new byte[message.length + 4]; -// System.arraycopy(message, 0, wrapMsg, 4, message.length); -// System.arraycopy(msgType, 0, wrapMsg, 0, 4); -// -// System.out.printf("BftsmartMessageService invokeOrdered time = %s, id = %s threadId = %s \r\n", -// System.currentTimeMillis(), asynchServiceProxy.getProcessId(), Thread.currentThread().getId()); - - byte[] result = asynchServiceProxy.invokeOrdered(message); + //0: Transaction msg, 1: Commitblock msg + byte[] msgType = BytesUtils.toBytes(0); + byte[] wrapMsg = new byte[message.length + 4]; + System.arraycopy(message, 0, wrapMsg, 4, message.length); + System.arraycopy(msgType, 0, wrapMsg, 0, 4); + + System.out.printf("BftsmartMessageService invokeOrdered time = %s, id = %s threadId = %s \r\n", + System.currentTimeMillis(), asynchServiceProxy.getProcessId(), Thread.currentThread().getId()); + + byte[] result = asynchServiceProxy.invokeOrdered(wrapMsg); asyncFuture.complete(result); } catch (Exception e) { diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/service/BftsmartNodeServer.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/service/BftsmartNodeServer.java index f25c8d61..84f6aab2 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/service/BftsmartNodeServer.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/service/BftsmartNodeServer.java @@ -1,15 +1,27 @@ package com.jd.blockchain.consensus.bftsmart.service; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + import bftsmart.tom.*; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.consensus.ConsensusManageService; import com.jd.blockchain.consensus.NodeSettings; +import com.jd.blockchain.consensus.bftsmart.BftsmartCommitBlockSettings; import com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider; import com.jd.blockchain.consensus.bftsmart.BftsmartConsensusSettings; import com.jd.blockchain.consensus.bftsmart.BftsmartNodeSettings; @@ -19,12 +31,28 @@ import com.jd.blockchain.consensus.service.NodeServer; import com.jd.blockchain.consensus.service.ServerSettings; import com.jd.blockchain.consensus.service.StateHandle; import com.jd.blockchain.consensus.service.StateMachineReplicate; +import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoUtils; +import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeyPair; +import com.jd.blockchain.ledger.TransactionContent; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.TransactionRespHandle; +import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.data.TxContentBlob; +import com.jd.blockchain.ledger.data.TxRequestMessage; import com.jd.blockchain.utils.PropertiesUtils; +import com.jd.blockchain.utils.codec.Base58Utils; import com.jd.blockchain.utils.concurrent.AsyncFuture; +import com.jd.blockchain.utils.concurrent.CompletableAsyncFuture; import com.jd.blockchain.utils.io.BytesUtils; +import com.jd.blockchain.utils.serialize.binary.BinarySerializeUtils; + import bftsmart.reconfiguration.util.HostsConfig; import bftsmart.reconfiguration.util.TOMConfiguration; +import bftsmart.reconfiguration.views.MemoryBasedViewStorage; import bftsmart.tom.core.messages.TOMMessage; import bftsmart.tom.server.defaultservices.DefaultRecoverable; @@ -34,9 +62,15 @@ public class BftsmartNodeServer extends DefaultRecoverable implements NodeServer private List stateHandles = new CopyOnWriteArrayList<>(); +// private List batchConsensusListeners = new LinkedList<>(); + +// private Map> replyContextMessages = new ConcurrentHashMap<>(); + // TODO 暂不处理队列溢出问题 private ExecutorService notifyReplyExecutors = Executors.newSingleThreadExecutor(); +// private ExecutorService sendCommitExecutors = Executors.newFixedThreadPool(2); + private volatile Status status = Status.STOPPED; private final Object mutex = new Object(); @@ -67,6 +101,28 @@ public class BftsmartNodeServer extends DefaultRecoverable implements NodeServer private int serverId; +// private volatile String batchId = null; +// +//// private List> replyMessages ; +// +// private boolean leader_has_makedicision = false; +// +// private boolean commit_block_condition = false; +// +// private final AtomicLong txIndex = new AtomicLong(); +// +// private final AtomicLong blockIndex = new AtomicLong(); +// +// private static final AtomicInteger incrementNum = new AtomicInteger(); +// +//// private final BlockingQueue txRequestQueue = new LinkedBlockingQueue(); +// +// private final ExecutorService queueExecutor = Executors.newSingleThreadExecutor(); +// +// private final ScheduledExecutorService timerEexecutorService = new ScheduledThreadPoolExecutor(10); +// private ServiceProxy peerProxy; + + public BftsmartNodeServer() { } @@ -83,6 +139,22 @@ public class BftsmartNodeServer extends DefaultRecoverable implements NodeServer initConfig(serverId, systemConfig, hostsConfig); } + //aim to send commit block message +// protected void createProxyClient() { +// BftsmartTopology topologyCopy = (BftsmartTopology) topology.copyOf(); +// +// MemoryBasedViewStorage viewStorage = new MemoryBasedViewStorage(topologyCopy.getView()); +// +// byte[] bytes = BinarySerializeUtils.serialize(tomConfig); +// +// TOMConfiguration decodeTomConfig = BinarySerializeUtils.deserialize(bytes); +// +// decodeTomConfig.setProcessId(0); +// +// peerProxy = new ServiceProxy(decodeTomConfig, viewStorage, null, null); +// +// } + protected int findServerId() { int serverId = 0; @@ -190,12 +262,214 @@ public class BftsmartNodeServer extends DefaultRecoverable implements NodeServer return appExecuteBatch(commands, msgCtxs, fromConsensus, null); } +// private boolean checkLeaderId(MessageContext[] msgCtxs) { +// boolean result = false; +// +// for (int i = 0; i < msgCtxs.length - 1; i++) { +// if (msgCtxs[i].getLeader() != msgCtxs[i+1].getLeader()) +// return result; +// } +// +// result = true; +// +// return result; +// } +// +// //普通消息处理 +// private void normalMsgProcess(ReplyContextMessage replyContextMsg, byte[] msg, String realmName, String batchId) { +// AsyncFuture replyMsg = messageHandle.processOrdered(replyContextMsg.getMessageContext().getOperationId(), msg, realmName, batchId); +// replyContextMessages.put(replyContextMsg, replyMsg); +// } +// +// //结块消息处理 +// private void commitMsgProcess(ReplyContextMessage replyContextMsg, String realmName, String batchId) { +// try{ +// //receive messages before commitblock message, then execute commit +// if (replyContextMessages.size() != 0) { +// messageHandle.completeBatch(realmName, batchId); +// messageHandle.commitBatch(realmName, batchId); +// } +// +// // commit block msg need response too +// CompletableAsyncFuture asyncFuture = new CompletableAsyncFuture<>(); +// TransactionResponse transactionRespHandle = new TransactionRespHandle(newBlockCommitRequest(), +// TransactionState.SUCCESS, TransactionState.SUCCESS); +// +// asyncFuture.complete(BinaryEncodingUtils.encode(transactionRespHandle, TransactionResponse.class)); +// replyContextMessages.put(replyContextMsg, asyncFuture); +// }catch (Exception e){ +// LOGGER.error("Error occurred on commit batch transactions, so the new block is canceled! --" + e.getMessage(), e); +// messageHandle.rollbackBatch(realmName, batchId, -1); +// }finally{ +// this.batchId = null; +// } +// } + +// private void sendCommitMessage() { +// +// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(realmName)); +// +// BlockchainKeyPair userKeyPeer = BlockchainKeyGenerator.getInstance().generate(); +// +// TxContentBlob txContentBlob = new TxContentBlob(ledgerHash); +// +// byte[] reqBytes = BinaryEncodingUtils.encode(txContentBlob, TransactionContent.class); +// +// HashDigest reqHash = CryptoUtils.hash(CryptoAlgorithm.SHA256).hash(reqBytes); +// +// txContentBlob.setHash(reqHash); +// +// TxRequestMessage transactionRequest = new TxRequestMessage(txContentBlob); +// +// byte[] msg = BinaryEncodingUtils.encode(transactionRequest, TransactionRequest.class); +// +// byte[] type = BytesUtils.toBytes(1); +// +// byte[] wrapMsg = new byte[msg.length + 4]; +// +// System.arraycopy(type, 0, wrapMsg, 0, 4); +// System.arraycopy(msg, 0, wrapMsg, 4, msg.length); +// +// peerProxy.invokeOrdered(wrapMsg); +// +// LOGGER.info("Send commit block msg success!"); +// } + +// private TransactionRequest newBlockCommitRequest() { +// +// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(realmName)); +// +// TxContentBlob txContentBlob = new TxContentBlob(ledgerHash); +// +// byte[] reqBytes = BinaryEncodingUtils.encode(txContentBlob, TransactionContent.class); +// +// HashDigest reqHash = CryptoUtils.hash(CryptoAlgorithm.SHA256).hash(reqBytes); +// +// txContentBlob.setHash(reqHash); +// +// TxRequestMessage transactionRequest = new TxRequestMessage(txContentBlob); +// +// return transactionRequest; +// } + +// private void checkConsensusFinish() { +// BftsmartCommitBlockSettings commitBlockSettings = ((BftsmartServerSettings)serverSettings).getConsensusSettings().getCommitBlockSettings(); +// int txSize = commitBlockSettings.getTxSizePerBlock(); +// long maxDelay = commitBlockSettings.getMaxDelayMilliSecondsPerBlock(); +// +// long currIndex = txIndex.incrementAndGet(); +// if (currIndex == txSize) { +// txIndex.set(0); +// this.blockIndex.getAndIncrement(); +// sendCommitExecutors.execute(()-> { +// sendCommitMessage(); +// }); +// } else if (currIndex == 1) { +//// System.out.printf("checkConsensusFinish schedule blockIndex = %s \r\n", this.blockIndex.get()); +// timerEexecutorService.schedule(timeTask(this.blockIndex.get()), maxDelay, TimeUnit.MILLISECONDS); +// } +// +// return; +// } + +// @Override +// public byte[][] appExecuteBatch(byte[][] commands, MessageContext[] msgCtxs, boolean fromConsensus, List replyList) { +// +// if (!checkLeaderId(msgCtxs)) { +// throw new IllegalArgumentException(); +// } +// +// boolean isLeader = (msgCtxs[0].getLeader() == getId()); +// +// if (isLeader) { +// for (int i = 0; i < commands.length; i++) { +// byte[] wrapMsg = commands[i]; +// byte[] type = new byte[4]; +// byte[] msg= new byte[wrapMsg.length - 4]; +// +// System.arraycopy(wrapMsg, 0, type, 0, 4); +// System.arraycopy(wrapMsg, 4, msg, 0, wrapMsg.length - 4); +// +// MessageContext messageContext = msgCtxs[i]; +// ReplyContextMessage replyContextMessage = replyList.get(i); +// replyContextMessage.setMessageContext(messageContext); +// +// if (batchId == null) { +// batchId = messageHandle.beginBatch(realmName); +// } +// +// int msgType = BytesUtils.readInt(new ByteArrayInputStream(type)); +// +// if (msgType == 0) { +// +// //only leader do it +// checkConsensusFinish(); +// //normal message process +// normalMsgProcess(replyContextMessage, msg, realmName, batchId); +// } +// if (!leader_has_makedicision) { +// if (msgType == 1) { +// LOGGER.error("Error occurred on appExecuteBatch msg process, leader confilicting error!"); +// } +// +// if (commit_block_condition) { +// leader_has_makedicision = true; +// +//// sendCommitExecutors.execute(() -> { +// commit_block_condition = false; +// LOGGER.info("Txcount execute commit block!"); +// sendCommitMessage(); +//// }); +// +// } +// } else if (msgType == 1) { +// //commit block message +// commitMsgProcess(replyContextMessage, realmName, batchId); +// leader_has_makedicision = false; +// sendReplyMessage(); +// } +// } +// } else { +// for (int i = 0; i < commands.length; i++) { +// byte[] wrapMsg = commands[i]; +// byte[] type = new byte[4]; +// byte[] msg= new byte[wrapMsg.length - 4]; +// +// System.arraycopy(wrapMsg, 0, type, 0, 4); +// System.arraycopy(wrapMsg, 4, msg, 0, wrapMsg.length - 4); +// +// MessageContext messageContext = msgCtxs[i]; +// ReplyContextMessage replyContextMessage = replyList.get(i); +// replyContextMessage.setMessageContext(messageContext); +// +// if (batchId == null) { +// batchId = messageHandle.beginBatch(realmName); +// } +// +// int msgType = BytesUtils.readInt(new ByteArrayInputStream(type)); +// +// if (msgType == 0) { +// //normal message +// normalMsgProcess(replyContextMessage, msg, realmName, batchId); +// } else if (msgType == 1) { +// // commit block message +// commitMsgProcess(replyContextMessage, realmName, batchId); +// sendReplyMessage(); +// } +// } +// } +// +// return null; +// } + @Override public byte[][] appExecuteBatch(byte[][] commands, MessageContext[] msgCtxs, boolean fromConsensus, List replyList) { if (replyList == null || replyList.size() == 0) { throw new IllegalArgumentException(); } + + // todo 此部分需要重新改造 /** * 默认BFTSmart接口提供的commands是一个或多个共识结果的顺序集合 @@ -227,6 +501,52 @@ public class BftsmartNodeServer extends DefaultRecoverable implements NodeServer if (!manageConsensusCmds.isEmpty()) { blockAndReply(manageConsensusCmds, manageReplyMsgs); } + + +//// if (!checkLeaderId(msgCtxs)) { +//// throw new IllegalArgumentException(); +//// } +// +// if (replyList == null || replyList.size() == 0) { +// throw new IllegalArgumentException(); +// } +// +// for (int i = 0; i < commands.length; i++) { +// byte[] wrapMsg = commands[i]; +// byte[] type = new byte[4]; +// byte[] msg= new byte[wrapMsg.length - 4]; +// // batch messages, maybe in different consensus instance, leader also maybe different +// boolean isLeader = (msgCtxs[i].getLeader() == getId()); +// +// System.arraycopy(wrapMsg, 0, type, 0, 4); +// System.arraycopy(wrapMsg, 4, msg, 0, wrapMsg.length - 4); +// +// MessageContext messageContext = msgCtxs[i]; +// ReplyContextMessage replyContextMessage = replyList.get(i); +// replyContextMessage.setMessageContext(messageContext); +// +// if (batchId == null) { +// batchId = messageHandle.beginBatch(realmName); +// } +// +// int msgType = BytesUtils.readInt(new ByteArrayInputStream(type)); +// +// if (msgType == 0) { +// +// //only leader do it +// if (isLeader) { +// checkConsensusFinish(); +// } +// //normal message process +// normalMsgProcess(replyContextMessage, msg, realmName, batchId); +// } +// else if (msgType == 1) { +// //commit block message +// commitMsgProcess(replyContextMessage, realmName, batchId); +// sendReplyMessage(); +// } +// } + return null; } @@ -274,6 +594,46 @@ public class BftsmartNodeServer extends DefaultRecoverable implements NodeServer }); } +// private void sendReplyMessage() { +// for (ReplyContextMessage msg: replyContextMessages.keySet()) { +// byte[] reply = replyContextMessages.get(msg).get(); +// msg.setReply(reply); +// TOMMessage request = msg.getTomMessage(); +// ReplyContext replyContext = msg.getReplyContext(); +// request.reply = new TOMMessage(replyContext.getId(), request.getSession(), request.getSequence(), +// request.getOperationId(), msg.getReply(), replyContext.getCurrentViewId(), +// request.getReqType()); +// +// if (replyContext.getNumRepliers() > 0) { +// bftsmart.tom.util.Logger.println("(ServiceReplica.receiveMessages) sending reply to " +// + request.getSender() + " with sequence number " + request.getSequence() +// + " and operation ID " + request.getOperationId() + " via ReplyManager"); +// replyContext.getRepMan().send(request); +// } else { +// bftsmart.tom.util.Logger.println("(ServiceReplica.receiveMessages) sending reply to " +// + request.getSender() + " with sequence number " + request.getSequence() +// + " and operation ID " + request.getOperationId()); +// replyContext.getReplier().manageReply(request, msg.getMessageContext()); +// // cs.send(new int[]{request.getSender()}, request.reply); +// } +// } +// replyContextMessages.clear(); +// } + +// private Runnable timeTask(final long currBlockIndex) { +// Runnable task = () -> { +// boolean isAdd = this.blockIndex.compareAndSet(currBlockIndex, currBlockIndex + 1); +// if (isAdd) { +// LOGGER.info("TimerTask execute commit block! "); +// this.txIndex.set(0); +// timerEexecutorService.execute(()-> { +// sendCommitMessage(); +// }); +// } +// }; +// return task; +// } + //notice public byte[] getSnapshot() { LOGGER.debug("------- GetSnapshot...[replica.id=" + this.getId() + "]"); @@ -283,6 +643,9 @@ public class BftsmartNodeServer extends DefaultRecoverable implements NodeServer for (StateHandle stateHandle : stateHandles) { // TODO: 测试代码; return stateHandle.takeSnapshot(); + + // byte[] state = stateHandle.takeSnapshot(); + // BytesEncoding.writeInNormal(state, out); } return out.toByteArray(); } @@ -349,7 +712,34 @@ public class BftsmartNodeServer extends DefaultRecoverable implements NodeServer } } } - + +// private static class ActionRequestExtend { +// +// +// ReplyContextMessage replyContextMessage; +// +// private byte[] message; +// +// private ActionRequest actionRequest; +// +// public ActionRequestExtend(byte[] message) { +// this.message = message; +// actionRequest = BinaryEncodingUtils.decode(message); +// } +// +// public byte[] getMessage() { +// return message; +// } +// +// public ReplyContextMessage getReplyContextMessage() { +// return replyContextMessage; +// } +// +// public ActionRequest getActionRequest() { +// return actionRequest; +// } +// } + enum Status { STARTING, diff --git a/source/consensus/consensus-bftsmart/src/test/java/test/com/jd/blockchain/consensus/bftsmart/proxyClientTest.java b/source/consensus/consensus-bftsmart/src/test/java/test/com/jd/blockchain/consensus/bftsmart/proxyClientTest.java index 10462343..5c6d0d52 100644 --- a/source/consensus/consensus-bftsmart/src/test/java/test/com/jd/blockchain/consensus/bftsmart/proxyClientTest.java +++ b/source/consensus/consensus-bftsmart/src/test/java/test/com/jd/blockchain/consensus/bftsmart/proxyClientTest.java @@ -8,7 +8,7 @@ import com.jd.blockchain.consensus.bftsmart.client.BftsmartMessageService; import com.jd.blockchain.consensus.bftsmart.service.BftsmartNodeServer; import com.jd.blockchain.consensus.bftsmart.service.BftsmartServerSettingConfig; import com.jd.blockchain.consensus.service.ServerSettings; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeyPair; import com.jd.blockchain.utils.PropertiesUtils; diff --git a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ClientIdentification.java b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ClientIdentification.java index 73198cb5..4f7c34d7 100644 --- a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ClientIdentification.java +++ b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ClientIdentification.java @@ -1,9 +1,9 @@ package com.jd.blockchain.consensus; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.consts.TypeCodes; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.utils.ValueType; diff --git a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ClientIdentifications.java b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ClientIdentifications.java index 0b620074..8d23135a 100644 --- a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ClientIdentifications.java +++ b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ClientIdentifications.java @@ -8,9 +8,9 @@ */ package com.jd.blockchain.consensus; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; /** * diff --git a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ClientIncomingSettings.java b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ClientIncomingSettings.java index d0644443..3585c613 100644 --- a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ClientIncomingSettings.java +++ b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ClientIncomingSettings.java @@ -1,8 +1,8 @@ package com.jd.blockchain.consensus; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.ValueType; /** diff --git a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ConsensusSettings.java b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ConsensusSettings.java index a2d99ad3..b7849625 100644 --- a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ConsensusSettings.java +++ b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ConsensusSettings.java @@ -1,8 +1,8 @@ package com.jd.blockchain.consensus; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; /** * 共识网络的配置参数; diff --git a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/NodeSettings.java b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/NodeSettings.java index 31ae9a36..a0c7ee66 100644 --- a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/NodeSettings.java +++ b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/NodeSettings.java @@ -1,9 +1,9 @@ package com.jd.blockchain.consensus; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.consts.TypeCodes; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.utils.ValueType; /** diff --git a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/action/ActionRequest.java b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/action/ActionRequest.java index b88fc0f5..694d7326 100644 --- a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/action/ActionRequest.java +++ b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/action/ActionRequest.java @@ -1,8 +1,8 @@ package com.jd.blockchain.consensus.action; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.ValueType; @DataContract(code= TypeCodes.CONSENSUS_ACTION_REQUEST) diff --git a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/action/ActionResponse.java b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/action/ActionResponse.java index 8e388aa2..1ba34d01 100644 --- a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/action/ActionResponse.java +++ b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/action/ActionResponse.java @@ -1,8 +1,8 @@ package com.jd.blockchain.consensus.action; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.ValueType; @DataContract(code= TypeCodes.CONSENSUS_ACTION_RESPONSE) diff --git a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/client/ClientSettings.java b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/client/ClientSettings.java index 5551e18a..9c4fa89f 100644 --- a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/client/ClientSettings.java +++ b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/client/ClientSettings.java @@ -1,7 +1,7 @@ package com.jd.blockchain.consensus.client; import com.jd.blockchain.consensus.ConsensusSettings; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; /** * 共识客户端的配置参数; diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java index 025ffcef..839be6e0 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java @@ -20,7 +20,7 @@ import com.jd.blockchain.consensus.mq.settings.MsgQueueConsensusSettings; import com.jd.blockchain.consensus.mq.settings.MsgQueueNetworkSettings; import com.jd.blockchain.consensus.mq.settings.MsgQueueNodeSettings; import com.jd.blockchain.crypto.AddressEncoding; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.PropertiesUtils; diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/client/MsgQueueClientFactory.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/client/MsgQueueClientFactory.java index 2158f223..63028e85 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/client/MsgQueueClientFactory.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/client/MsgQueueClientFactory.java @@ -20,8 +20,8 @@ import com.jd.blockchain.consensus.mq.settings.MsgQueueClientIncomingSettings; import com.jd.blockchain.consensus.mq.settings.MsgQueueClientSettings; import com.jd.blockchain.consensus.mq.settings.MsgQueueConsensusSettings; import com.jd.blockchain.crypto.CryptoUtils; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/client/MsgQueueClientIdentification.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/client/MsgQueueClientIdentification.java index b34b70b3..e0184606 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/client/MsgQueueClientIdentification.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/client/MsgQueueClientIdentification.java @@ -11,7 +11,7 @@ package com.jd.blockchain.consensus.mq.client; import com.jd.blockchain.consensus.ClientIdentification; import com.jd.blockchain.consensus.mq.MsgQueueConsensusProvider; import com.jd.blockchain.crypto.CryptoUtils; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueClientConfig.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueClientConfig.java index 5fec7a69..78f9f7c3 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueClientConfig.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueClientConfig.java @@ -11,7 +11,7 @@ package com.jd.blockchain.consensus.mq.config; import com.jd.blockchain.consensus.mq.settings.MsgQueueClientSettings; import com.jd.blockchain.consensus.mq.settings.MsgQueueConsensusSettings; import com.jd.blockchain.consensus.mq.settings.MsgQueueNetworkSettings; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; /** * diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueClientIncomingConfig.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueClientIncomingConfig.java index 8e947f26..967c03de 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueClientIncomingConfig.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueClientIncomingConfig.java @@ -13,7 +13,7 @@ import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.consensus.mq.MsgQueueConsensusProvider; import com.jd.blockchain.consensus.mq.settings.MsgQueueClientIncomingSettings; import com.jd.blockchain.consensus.mq.settings.MsgQueueConsensusSettings; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import java.lang.reflect.Method; diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueConsensusConfig.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueConsensusConfig.java index 526ac603..9ece2285 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueConsensusConfig.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueConsensusConfig.java @@ -11,7 +11,7 @@ package com.jd.blockchain.consensus.mq.config; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.consensus.NodeSettings; import com.jd.blockchain.consensus.mq.settings.*; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import java.lang.reflect.Method; import java.lang.reflect.Proxy; diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueNodeConfig.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueNodeConfig.java index c7cc03d7..9e9506ed 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueNodeConfig.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/config/MsgQueueNodeConfig.java @@ -9,7 +9,7 @@ package com.jd.blockchain.consensus.mq.config; import com.jd.blockchain.consensus.mq.settings.MsgQueueNodeSettings; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; /** * peer节点IP diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/server/MsgQueueConsensusManageService.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/server/MsgQueueConsensusManageService.java index d33c462e..1c6dc677 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/server/MsgQueueConsensusManageService.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/server/MsgQueueConsensusManageService.java @@ -17,7 +17,7 @@ import com.jd.blockchain.consensus.mq.config.MsgQueueConsensusConfig; import com.jd.blockchain.consensus.mq.settings.MsgQueueClientIncomingSettings; import com.jd.blockchain.consensus.mq.settings.MsgQueueConsensusSettings; import com.jd.blockchain.crypto.CryptoUtils; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; import java.lang.reflect.Proxy; diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueBlockSettings.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueBlockSettings.java index 95ba2000..e2edaa7b 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueBlockSettings.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueBlockSettings.java @@ -8,9 +8,9 @@ */ package com.jd.blockchain.consensus.mq.settings; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.ValueType; /** diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueClientIncomingSettings.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueClientIncomingSettings.java index 1eeffa91..e0cb03d1 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueClientIncomingSettings.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueClientIncomingSettings.java @@ -8,12 +8,12 @@ */ package com.jd.blockchain.consensus.mq.settings; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; import com.jd.blockchain.consensus.ClientIncomingSettings; import com.jd.blockchain.consensus.ConsensusSettings; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.consts.TypeCodes; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.utils.ValueType; /** diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueConsensusSettings.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueConsensusSettings.java index d24c5422..d4e9e648 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueConsensusSettings.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueConsensusSettings.java @@ -8,11 +8,11 @@ */ package com.jd.blockchain.consensus.mq.settings; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.consensus.mq.config.MsgQueueBlockConfig; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.Property; import com.jd.blockchain.utils.ValueType; diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueNetworkSettings.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueNetworkSettings.java index d512c32b..fea1690e 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueNetworkSettings.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueNetworkSettings.java @@ -8,9 +8,9 @@ */ package com.jd.blockchain.consensus.mq.settings; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.ValueType; /** diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueNodeSettings.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueNodeSettings.java index 784a1708..acfeb22b 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueNodeSettings.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/settings/MsgQueueNodeSettings.java @@ -8,9 +8,9 @@ */ package com.jd.blockchain.consensus.mq.settings; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.consensus.NodeSettings; +import com.jd.blockchain.consts.TypeCodes; /** * diff --git a/source/contract/contract-compile/pom.xml b/source/contract/contract-compile/pom.xml index e6c67b88..67eec97b 100644 --- a/source/contract/contract-compile/pom.xml +++ b/source/contract/contract-compile/pom.xml @@ -5,7 +5,7 @@ contract com.jd.blockchain - 0.8.3.RELEASE + 0.8.2.RELEASE 4.0.0 @@ -100,15 +100,6 @@ - - - org.apache.maven.plugins - maven-deploy-plugin - 2.8.2 - - true - - diff --git a/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract1.java b/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract1.java index 8fae2439..2926a757 100644 --- a/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract1.java +++ b/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract1.java @@ -2,7 +2,7 @@ package com.jd.blockchain.contract; import com.jd.blockchain.contract.model.*; import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.*; import com.jd.blockchain.utils.BaseConstant; @@ -14,23 +14,23 @@ import java.util.Map; import java.util.Set; /** - * mock the smart contract; + * 模拟用智能合约; */ @Contract public class AssetContract1 implements EventProcessingAwire { // private static final Logger LOGGER = LoggerFactory.getLogger(AssetContract1.class); - // the address of asset manager; + // 资产管理账户的地址; // private static String ASSET_ADDRESS = "2njZBNbFQcmKd385DxVejwSjy4driRzf9Pk"; private static String ASSET_ADDRESS = ""; - //account address; + //账户地址; private static final String ACCOUNT_ADDRESS = "accountAddress"; String contractAddress = "2njZBNbFQcmKd385DxVejwSjy4driRzf9Pk"; String userPubKeyVal = "this is user's pubKey"; - // the key of save asset; + // 保存资产总数的键; private static final String KEY_TOTAL = "TOTAL"; - // contractEvent context; + // 合约事件上下文; private ContractEventContext eventContext; private Object eventContextObj; private byte[] eventContextBytes; @@ -59,7 +59,7 @@ public class AssetContract1 implements EventProcessingAwire { } /** - * issue-asset; + * 发行资产; * @param contractEventContext * @throws Exception */ @@ -83,6 +83,7 @@ public class AssetContract1 implements EventProcessingAwire { // checkAllOwnersAgreementPermission(); + // 新发行的资产数量;在传递过程中都改为字符串,需要反转; // long amount = BytesUtils.toLong(args[0]); if (amount < 0) { @@ -92,77 +93,83 @@ public class AssetContract1 implements EventProcessingAwire { return; } + // 校验持有者账户的有效性; // BlockchainAccount holderAccount = eventContext.getLedger().getAccount(currentLedgerHash(), assetHolderAddress); // if (holderAccount == null) { // throw new ContractError("The holder is not exist!"); // } + // 查询当前值; HashDigest hashDigest = eventContext.getCurrentLedgerHash(); + //赋值;mock的对象直接赋值无效; // eventContext.getLedger().dataAccount(ACCOUNT_ADDRESS).set(KEY_TOTAL,"total new dataAccount".getBytes(),2); // KVDataEntry[] kvEntries = eventContext.getLedger().getDataEntries(hashDigest, ASSET_ADDRESS, KEY_TOTAL,assetHolderAddress); // assert ByteArray.toHex("total new dataAccount".getBytes()).equals(kvEntries[0].getValue()) // && ByteArray.toHex("abc new dataAccount".getBytes()).equals(kvEntries[1].getValue()) : -// "getDataEntries() test,expect!=actual;"; +// "getDataEntries() test,期望值!=设定值;"; KVDataEntry[] kvEntries = eventContext.getLedger().getDataEntries(hashDigest, ASSET_ADDRESS, KEY_TOTAL,assetHolderAddress,"ledgerHash"); //,"latestBlockHash" + //当前mock设定值为:TOTAL="total value,dataAccount";abc="abc value,dataAccount"; assert ByteArray.toHex("total value,dataAccount".getBytes()).equals(kvEntries[0].getValue()) && ByteArray.toHex("abc value,dataAccount".getBytes()).equals(kvEntries[1].getValue()) : - "getDataEntries() test,expect=actual;"; + "getDataEntries() test,期望值=设定值;"; - //get the latest block; + //高度只是一个模拟,看结果是否与期望相同;//get the latest block; LedgerBlock ledgerBlock = eventContext.getLedger().getBlock(hashDigest, eventContext.getLedger().getLedger(hashDigest).getLatestBlockHeight()); // assert "zhaogw".equals(new String(ledgerBlock.getLedgerHash().getRawDigest())) && // "lisi".equals(new String(ledgerBlock.getPreviousHash().getRawDigest())) : -// "getBlock(hash,long) test,expect!=actual;"; +// "getBlock(hash,long) test,期望值!=设定值;"; assert ByteArray.toHex(eventContext.getCurrentLedgerHash().getRawDigest()).equals(kvEntries[2].getValue()) && ledgerBlock.getPreviousHash().toBase58().equals(previousBlockHash) : - "getPreviousHash() test,expect!=acutal;"; + "getPreviousHash() test,期望值!=设定值;"; //模拟:根据hash来获得区块; LedgerBlock ledgerBlock1 = eventContext.getLedger().getBlock(hashDigest,ledgerBlock.getHash()); assert eventContext.getLedger().getTransactionCount(hashDigest,1) == 2 : - "getTransactionCount(),expect!=acutal"; + "getTransactionCount(),期望值!=设定值"; // assert "zhaogw".equals(new String(ledgerBlock1.getLedgerHash().getRawDigest())) && // "lisi".equals(new String(ledgerBlock1.getPreviousHash().getRawDigest())) : -// "getBlock(hash,blockHash) test,expect!=acutal;"; +// "getBlock(hash,blockHash) test,期望值!=设定值;"; assert ByteArray.toHex(eventContext.getCurrentLedgerHash().getRawDigest()).equals(kvEntries[2].getValue()) && ledgerBlock1.getPreviousHash().toBase58().equals(previousBlockHash) : - "getBlock(hash,blockHash) test,expect!=acutal;"; + "getBlock(hash,blockHash) test,期望值!=设定值;"; assert ASSET_ADDRESS.equals(eventContext.getLedger().getDataAccount(hashDigest,ASSET_ADDRESS).getAddress()) : - "getDataAccount(hash,address), expect!=acutal"; + "getDataAccount(hash,address), 期望值!=设定值"; + //mock user()等;内部赋值,验证外部是否能够得到; PubKey pubKey = new PubKey(CryptoAlgorithm.ED25519, pubKeyVal.getBytes()); BlockchainIdentity contractID = new BlockchainIdentityData(pubKey); // assert contractID == contractEventContext.getLedger().dataAccounts().register(contractID).getAccountID() : -// "dataAccounts(),expect!=acutal"; +// "dataAccounts(),期望值!=设定值"; contractEventContext.getLedger().dataAccounts().register(contractID); contractEventContext.getLedger().dataAccount(contractID.getAddress()). set(KEY_TOTAL,"hello".getBytes(),-1).getOperation(); assert userAddress.equals(eventContext.getLedger().getUser(hashDigest,userAddress).getAddress()) : - "getUser(hash,address), expect!=acutal"; + "getUser(hash,address), 期望值!=设定值"; assert contractAddress.equals(eventContext.getLedger().getContract(hashDigest,contractAddress).getAddress()) : - "getContract(hash,address), expect!=acutal"; + "getContract(hash,address), 期望值!=设定值"; PubKey userPubKey = new PubKey(CryptoAlgorithm.ED25519, userPubKeyVal.getBytes()); BlockchainIdentity userBlockId = new BlockchainIdentityData(userPubKey); contractEventContext.getLedger().users().register(userBlockId); // txRootHash + //此方法未实现;需要相关人员进一步完善; // eventContext.getLedger().getTransactions(hashDigest,ledgerBlock1.getHash(),0,10); HashDigest txHashDigest = new HashDigest(Base58Utils.decode(txHash)); LedgerTransaction ledgerTransactions = eventContext.getLedger().getTransactionByContentHash(hashDigest,txHashDigest); - assert ledgerTransactions != null : "getTransactionByContentHash(hashDigest,txHashDigest),expect!=acutal"; + assert ledgerTransactions != null : "getTransactionByContentHash(hashDigest,txHashDigest),期望值!=设定值"; System.out.println("issue(),over."); } @@ -185,6 +192,9 @@ public class AssetContract1 implements EventProcessingAwire { System.out.println("transfer(),over."); } + /** + * 只有全部的合约拥有者同意才能通过校验; + */ private void checkAllOwnersAgreementPermission() { Set owners = eventContext.getContracOwners(); Set requestors = eventContext.getTxSigners(); @@ -204,6 +214,11 @@ public class AssetContract1 implements EventProcessingAwire { } } + /** + * 校验指定的账户是否签署了当前交易; + * + * @param address + */ private void checkSignerPermission(String address) { Set requestors = eventContext.getTxSigners(); for (BlockchainIdentity r : requestors) { diff --git a/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract2.java b/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract2.java index 247e36e8..44c3bb4c 100644 --- a/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract2.java +++ b/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract2.java @@ -6,7 +6,7 @@ import com.jd.blockchain.ledger.KVDataEntry; import com.jd.blockchain.utils.BaseConstant; /** - * mock the smart contract; + * 模拟用智能合约; */ @Contract public class AssetContract2 implements EventProcessingAwire { @@ -31,11 +31,12 @@ public class AssetContract2 implements EventProcessingAwire { HashDigest hashDigest = eventContext.getCurrentLedgerHash(); KVDataEntry[] kvEntries = eventContext.getLedger().getDataEntries(hashDigest, contractDataAddress, KEY_TOTAL,LEDGER_HASH); //,"latestBlockHash" + //当前mock设定值为:TOTAL="total value,dataAccount";abc="abc value,dataAccount"; // // assert ByteArray.toHex("total value,dataAccount".getBytes()).equals(kvEntries[0].getValue()) // && ByteArray.toHex("abc value,dataAccount".getBytes()).equals(kvEntries[1].getValue()) : -// "getDataEntries() test,expect=actual;"; +// "getDataEntries() test,期望值=设定值;"; System.out.println("in dataSet,KEY_TOTAL="+new String(kvEntries[0].getValue().toString())); System.out.println("in dataSet,LEDGER_HASH="+new String(kvEntries[1].getValue().toString())); } diff --git a/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract3.java b/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract3.java index acae4716..d6c8b190 100644 --- a/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract3.java +++ b/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract3.java @@ -4,8 +4,8 @@ import com.jd.blockchain.contract.model.*; import com.jd.blockchain.utils.BaseConstant; /** - * mock the smart contract; - * Do only the simplest addition operation. + * 模拟用智能合约; + * 只做最简单的加法运算; */ @Contract public class AssetContract3 implements EventProcessingAwire { diff --git a/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract4.java b/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract4.java index 0b2766b0..0bb265bb 100644 --- a/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract4.java +++ b/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract4.java @@ -8,9 +8,8 @@ import com.jd.blockchain.utils.BaseConstant; import com.jd.blockchain.utils.io.ByteArray; /** - * mock the smart contract; - * The test takes data from the chain and compares it with the expected value; - * the value of param1Val should be consistent with that of Integration Test; + * 模拟用智能合约; + * 测试从链中取数据,然后对比是否与预定值一致;param1Val 的值要与IntegrationTest中保持一致; */ @Contract public class AssetContract4 implements EventProcessingAwire { @@ -32,6 +31,7 @@ public class AssetContract4 implements EventProcessingAwire { ",contractDataAddress= "+contractDataAddress); BlockchainKeyPair dataAccount = BlockchainKeyGenerator.getInstance().generate(); + //TODO:register牵扯到账本中的事务处理,需要优化; // contractEventContext.getLedger().dataAccounts().register(dataAccount.getIdentity()); // contractEventContext.getLedger().dataAccount(dataAccount.getAddress()). // set(param1,param1Val.getBytes(),-1).getOperation(); @@ -55,9 +55,9 @@ public class AssetContract4 implements EventProcessingAwire { KVDataEntry[] kvEntries = eventContext.getLedger().getDataEntries(eventContext.getCurrentLedgerHash(), contractDataAddress, param1); if (ByteArray.toHex(param1Val.getBytes()).equals(kvEntries[0].getValue())){ - System.out.println("getDataEntries() test,expect==actual;"); + System.out.println("getDataEntries() test,期望值==设定值;"); } else { - System.out.println("getDataEntries() test,expect==actual;"); + System.out.println("getDataEntries() test,期望值!=设定值;"); } } diff --git a/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract5.java b/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract5.java index bf3d8def..de6fe3cc 100644 --- a/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract5.java +++ b/source/contract/contract-compile/src/main/java/com/jd/blockchain/contract/AssetContract5.java @@ -4,8 +4,8 @@ import com.jd.blockchain.contract.model.*; import com.jd.blockchain.utils.BaseConstant; /** - * mock the smart contract; - * Do only the simplest addition operation. + * 模拟用智能合约; + * 只做最简单的加法运算; */ @Contract public class AssetContract5 implements EventProcessingAwire { diff --git a/source/contract/contract-framework/src/main/java/com/jd/blockchain/contract/ContractEngine.java b/source/contract/contract-framework/src/main/java/com/jd/blockchain/contract/ContractEngine.java index fe6f6a7a..09f7b416 100644 --- a/source/contract/contract-framework/src/main/java/com/jd/blockchain/contract/ContractEngine.java +++ b/source/contract/contract-framework/src/main/java/com/jd/blockchain/contract/ContractEngine.java @@ -1,7 +1,7 @@ package com.jd.blockchain.contract; /** - * contract engine. + * 合约引擎; * * @author huanghaiquan * @@ -9,9 +9,9 @@ package com.jd.blockchain.contract; public interface ContractEngine { /** - * Returns the contract code for the specified address;
+ * 返回指定地址的合约代码;
* - * If not, return null; + * 如果不存在,则返回 null; * * @param address * @return @@ -19,9 +19,9 @@ public interface ContractEngine { ContractCode getContract(String address, long version); /** - * Load contract code;
+ * 装入合约代码;
* - * If it already exists, it returns the existing instance directly. + * 如果已经存在,则直接返回已有实例; * * @param address * @param code diff --git a/source/contract/contract-framework/src/main/java/com/jd/blockchain/contract/ContractServiceProvider.java b/source/contract/contract-framework/src/main/java/com/jd/blockchain/contract/ContractServiceProvider.java index 1cc94b5e..ae8a0d00 100644 --- a/source/contract/contract-framework/src/main/java/com/jd/blockchain/contract/ContractServiceProvider.java +++ b/source/contract/contract-framework/src/main/java/com/jd/blockchain/contract/ContractServiceProvider.java @@ -5,7 +5,7 @@ public interface ContractServiceProvider { String getName(); /** - * Return the contract code execution engine instance; + * 返回合约代码执行引擎实例; * * @return */ diff --git a/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/JavaContractCode.java b/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/JavaContractCode.java index 282f06c6..147b6a26 100644 --- a/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/JavaContractCode.java +++ b/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/JavaContractCode.java @@ -52,32 +52,31 @@ public class JavaContractCode implements ContractCode { } class ContractThread implements Runnable{ - @Override public void run(){ LOGGER.info("ContractThread execute()."); try { - //Perform pretreatment; + //执行预处理; long startTime = System.currentTimeMillis(); String contractClassName = codeModule.getMainClass(); Class myClass = codeModule.loadClass(contractClassName); - Object contractMainClassObj = myClass.newInstance(); + Object contractMainClassObj = myClass.newInstance();//合约主类生成的类实例; Method beforeMth_ = myClass.getMethod("beforeEvent",codeModule.loadClass(ContractEventContext.class.getName())); ReflectionUtils.invokeMethod(beforeMth_,contractMainClassObj,contractEventContext); - LOGGER.info("beforeEvent,spend time:"+(System.currentTimeMillis()-startTime)); + LOGGER.info("beforeEvent,耗时:"+(System.currentTimeMillis()-startTime)); Method eventMethod = this.getMethodByAnno(contractMainClassObj,contractEventContext.getEvent()); startTime = System.currentTimeMillis(); ReflectionUtils.invokeMethod(eventMethod,contractMainClassObj,contractEventContext); - LOGGER.info("execute contract,spend time:"+(System.currentTimeMillis()-startTime)); + LOGGER.info("合约执行,耗时:"+(System.currentTimeMillis()-startTime)); Method mth2 = myClass.getMethod("postEvent"); startTime = System.currentTimeMillis(); ReflectionUtils.invokeMethod(mth2,contractMainClassObj); - LOGGER.info("postEvent,spend time:"+(System.currentTimeMillis()-startTime)); + LOGGER.info("postEvent,耗时:"+(System.currentTimeMillis()-startTime)); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (Exception e) { @@ -85,7 +84,7 @@ public class JavaContractCode implements ContractCode { } } - //get the relation between the methods and annotations + //得到当前类中相关方法和注解对应关系; Method getMethodByAnno(Object classObj, String eventName){ Class c = classObj.getClass(); Class contractEventClass = null; @@ -100,9 +99,9 @@ public class JavaContractCode implements ContractCode { for(int i = 0;i - 4.0.0 - + + 4.0.0 + com.jd.blockchain crypto - 0.9.0-SNAPSHOT + 0.9.0-SNAPSHOT - crypto-adv + crypto-adv + + com.jd.blockchain + crypto-sm + ${project.version} + org.bouncycastle bcprov-jdk15on @@ -48,28 +55,14 @@ org.mockito mockito-core - 1.10.19 test - - com.jd.blockchain - crypto-framework - ${project.version} - compile - - - + + com.jd.blockchain + crypto-framework + ${project.version} + compile + - - - - org.apache.maven.plugins - maven-deploy-plugin - 2.8.2 - - true - - - - + \ No newline at end of file diff --git a/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/ecvrf/VRF.java b/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/ecvrf/VRF.java index 0fd713ef..45e05083 100644 --- a/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/ecvrf/VRF.java +++ b/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/ecvrf/VRF.java @@ -1,6 +1,7 @@ package com.jd.blockchain.crypto.ecvrf; +import com.jd.blockchain.crypto.CryptoException; import com.sun.jna.Library; import com.sun.jna.Native; @@ -27,7 +28,9 @@ public class VRF { } // unsupported OS - else throw new IllegalArgumentException("The VRF implementation is not supported in this Operation System!"); + else { + throw new CryptoException("The VRF implementation is not supported in this Operation System!"); + } path = Objects.requireNonNull(VRF.class.getClassLoader().getResource(lib)).getPath(); return path; diff --git a/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/mpc/EqualVerify.java b/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/mpc/EqualVerify.java index 7d3dfc61..68393c8c 100644 --- a/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/mpc/EqualVerify.java +++ b/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/mpc/EqualVerify.java @@ -1,5 +1,6 @@ package com.jd.blockchain.crypto.mpc; +import com.jd.blockchain.crypto.CryptoException; import com.jd.blockchain.crypto.elgamal.ElGamalUtils; import com.jd.blockchain.utils.io.BytesUtils; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; @@ -56,8 +57,9 @@ public class EqualVerify { public static byte[] responder(int responderInput, byte[] sponsorOutput, byte[] responderEPubKeyBytes, byte[] responderEPrivKeyBytes) { - if (sponsorOutput.length != ELEMENTLENGTH) - throw new IllegalArgumentException("The sponsorOutput' length is not 64!"); + if (sponsorOutput.length != ELEMENTLENGTH) { + throw new CryptoException("The sponsorOutput' length is not 64!"); + } BigInteger responderBigInt = BigInteger.valueOf(responderInput); BigInteger responderEPubKey = new BigInteger(1,responderEPubKeyBytes); @@ -72,8 +74,9 @@ public class EqualVerify { public static boolean sponsorCheck(int sponsorInput, byte[] responderOutput, byte[] sponsorEPrivKeyBytes){ - if (responderOutput.length != 2 * ELEMENTLENGTH) - throw new IllegalArgumentException("The responderOutput's length is not 128!"); + if (responderOutput.length != 2 * ELEMENTLENGTH) { + throw new CryptoException("The responderOutput's length is not 128!"); + } byte[] responderCipherBytes = new byte[ELEMENTLENGTH]; byte[] dhValueBytes = new byte[ELEMENTLENGTH]; @@ -99,9 +102,12 @@ public class EqualVerify { private static byte[] bigIntegerTo64Bytes(BigInteger b){ byte[] tmp = b.toByteArray(); byte[] result = new byte[64]; - if (tmp.length > result.length) - System.arraycopy(tmp, tmp.length-result.length, result, 0, result.length); - else System.arraycopy(tmp,0,result,result.length-tmp.length,tmp.length); + if (tmp.length > result.length) { + System.arraycopy(tmp, tmp.length - result.length, result, 0, result.length); + } + else { + System.arraycopy(tmp,0,result,result.length-tmp.length,tmp.length); + } return result; } } diff --git a/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/mpc/IntCompare.java b/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/mpc/IntCompare.java index 68afd2e5..f6ff71c6 100644 --- a/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/mpc/IntCompare.java +++ b/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/mpc/IntCompare.java @@ -1,5 +1,6 @@ package com.jd.blockchain.crypto.mpc; +import com.jd.blockchain.crypto.CryptoException; import com.jd.blockchain.crypto.elgamal.ElGamalUtils; import com.jd.blockchain.utils.io.BytesUtils; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; @@ -67,13 +68,15 @@ public class IntCompare { public static byte[][] responder(int responderInt, byte[][] cipherArray, byte[] pubKeyBytes){ - if (cipherArray.length != 2 * INTLENGTH) - throw new IllegalArgumentException("The cipherArray has wrong format!"); + if (cipherArray.length != 2 * INTLENGTH) { + throw new CryptoException("The cipherArray has wrong format!"); + } int i,j; for (i = 0; i < cipherArray.length; i++){ - if(cipherArray[i].length != CIPHERLENGTH) - throw new IllegalArgumentException("The cipherArray has wrong format!"); + if(cipherArray[i].length != CIPHERLENGTH) { + throw new CryptoException("The cipherArray has wrong format!"); + } } String[] responderStrSet = encoding(responderInt, false); @@ -131,16 +134,18 @@ public class IntCompare { public static int sponsorOutput(byte[][] aggregatedCipherArray, byte[] privKeyBytes){ - if (aggregatedCipherArray.length != INTLENGTH) - throw new IllegalArgumentException("The aggregatedCipherArray has wrong format!"); + if (aggregatedCipherArray.length != INTLENGTH) { + throw new CryptoException("The aggregatedCipherArray has wrong format!"); + } int i; byte[] plaintext; for (i = 0; i < aggregatedCipherArray.length; i++){ - if(aggregatedCipherArray[i].length != CIPHERLENGTH) - throw new IllegalArgumentException("The aggregatedCipherArray has wrong format!"); + if(aggregatedCipherArray[i].length != CIPHERLENGTH) { + throw new CryptoException("The aggregatedCipherArray has wrong format!"); + } plaintext = ElGamalUtils.decrypt(aggregatedCipherArray[i], privKeyBytes); @@ -186,8 +191,9 @@ public class IntCompare { private static String to32BinaryString(int integer) { - if (integer < 0) - throw new IllegalArgumentException("integer must be non-negative!"); + if (integer < 0) { + throw new CryptoException("integer must be non-negative!"); + } int i; String str = Integer.toBinaryString(integer); @@ -204,16 +210,19 @@ public class IntCompare { * @return the next pseudorandom, uniformly distributed {@code int} * value between min (inclusive) and max (inclusive) * from this random number generator's sequence - * @throws IllegalArgumentException if min is not non-negative, + * @throws CryptoException if min is not non-negative, * max is not positive, or min is bigger than max */ private static int randInt(int min, int max) { - if (min < 0) - throw new IllegalArgumentException("min must be non-negative!"); - if (max <= 0) - throw new IllegalArgumentException("max must be positive!"); - if (min > max) - throw new IllegalArgumentException("min must not be greater than max"); + if (min < 0) { + throw new CryptoException("min must be non-negative!"); + } + if (max <= 0) { + throw new CryptoException("max must be positive!"); + } + if (min > max) { + throw new CryptoException("min must not be greater than max"); + } Random random = new Random(); return random.nextInt(max) % (max - min + 1) + min; @@ -244,23 +253,28 @@ public class IntCompare { private static byte[] bigIntegerTo64Bytes(BigInteger b){ byte[] tmp = b.toByteArray(); byte[] result = new byte[64]; - if (tmp.length > result.length) - System.arraycopy(tmp, tmp.length-result.length, result, 0, result.length); - else System.arraycopy(tmp,0,result,result.length-tmp.length,tmp.length); + if (tmp.length > result.length) { + System.arraycopy(tmp, tmp.length - result.length, result, 0, result.length); + } + else { + System.arraycopy(tmp,0,result,result.length-tmp.length,tmp.length); + } return result; } private static BigInteger getLeftBigIntegerFrom128Bytes(byte[] byteArray){ - if (byteArray.length != 128) - throw new IllegalArgumentException("The byteArray's length must be 128!"); + if (byteArray.length != 128) { + throw new CryptoException("The byteArray's length must be 128!"); + } byte[] tmp = new byte[64]; System.arraycopy(byteArray, 0, tmp, 0, tmp.length); return new BigInteger(1, tmp); } private static BigInteger getRightBigIntegerFrom128Bytes(byte[] byteArray){ - if (byteArray.length != 128) - throw new IllegalArgumentException("The byteArray's length must be 128!"); + if (byteArray.length != 128) { + throw new CryptoException("The byteArray's length must be 128!"); + } byte[] tmp = new byte[64]; System.arraycopy(byteArray, 64, tmp, 0, tmp.length); return new BigInteger(1, tmp); diff --git a/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/mpc/MultiSum.java b/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/mpc/MultiSum.java index 4a5dcc9c..cb0316d3 100644 --- a/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/mpc/MultiSum.java +++ b/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/mpc/MultiSum.java @@ -1,11 +1,6 @@ package com.jd.blockchain.crypto.mpc; -import com.jd.blockchain.crypto.paillier.KeyPair; -import com.jd.blockchain.crypto.paillier.PaillierUtils; -import com.jd.blockchain.crypto.paillier.PublicKey; -import com.jd.blockchain.crypto.smutils.asymmetric.SM2Utils; -import com.jd.blockchain.crypto.smutils.hash.SM3Utils; -import com.jd.blockchain.utils.io.BytesUtils; +import java.math.BigInteger; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.CipherParameters; @@ -17,7 +12,12 @@ import org.bouncycastle.math.ec.ECCurve; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.util.encoders.Hex; -import java.math.BigInteger; +import com.jd.blockchain.crypto.paillier.KeyPair; +import com.jd.blockchain.crypto.paillier.PaillierUtils; +import com.jd.blockchain.crypto.paillier.PublicKey; +import com.jd.blockchain.crypto.utils.sm.SM2Utils; +import com.jd.blockchain.crypto.utils.sm.SM3Utils; +import com.jd.blockchain.utils.io.BytesUtils; public class MultiSum { diff --git a/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/paillier/PaillierUtils.java b/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/paillier/PaillierUtils.java index ac14f22b..4c199487 100644 --- a/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/paillier/PaillierUtils.java +++ b/source/crypto/crypto-adv/src/main/java/com/jd/blockchain/crypto/paillier/PaillierUtils.java @@ -11,9 +11,12 @@ public class PaillierUtils { public static byte[] BigIntegerToLBytes(BigInteger b, int l){ byte[] tmp = b.toByteArray(); byte[] result = new byte[l]; - if (tmp.length > result.length) - System.arraycopy(tmp, tmp.length-result.length, result, 0, result.length); - else System.arraycopy(tmp,0,result,result.length-tmp.length,tmp.length); + if (tmp.length > result.length) { + System.arraycopy(tmp, tmp.length - result.length, result, 0, result.length); + } + else { + System.arraycopy(tmp,0,result,result.length-tmp.length,tmp.length); + } return result; } diff --git a/source/crypto/crypto-adv/src/test/java/test/com/jd/blockchain/crypto/ecvrf/VRFTest.java b/source/crypto/crypto-adv/src/test/java/test/com/jd/blockchain/crypto/ecvrf/VRFTest.java index 5c109651..2ee66422 100644 --- a/source/crypto/crypto-adv/src/test/java/test/com/jd/blockchain/crypto/ecvrf/VRFTest.java +++ b/source/crypto/crypto-adv/src/test/java/test/com/jd/blockchain/crypto/ecvrf/VRFTest.java @@ -1,5 +1,6 @@ package test.com.jd.blockchain.crypto.ecvrf; +import com.jd.blockchain.crypto.CryptoException; import com.jd.blockchain.crypto.ecvrf.VRF; import org.junit.Test; @@ -66,7 +67,7 @@ public class VRFTest { } else { assertNotNull(actualEx); - Class expectedException = IllegalArgumentException.class; + Class expectedException = CryptoException.class; assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); } } diff --git a/source/crypto/crypto-classic/pom.xml b/source/crypto/crypto-classic/pom.xml new file mode 100644 index 00000000..bd59aeb8 --- /dev/null +++ b/source/crypto/crypto-classic/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + + com.jd.blockchain + crypto + 0.9.0-SNAPSHOT + + crypto-classic + + + + com.jd.blockchain + crypto-framework + ${project.version} + + + + \ No newline at end of file diff --git a/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/AESEncryptionFunction.java b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/AESEncryptionFunction.java new file mode 100644 index 00000000..5a4a1539 --- /dev/null +++ b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/AESEncryptionFunction.java @@ -0,0 +1,216 @@ +package com.jd.blockchain.crypto.service.classic; + +import static com.jd.blockchain.crypto.BaseCryptoKey.KEY_TYPE_BYTES; +import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_CODE_SIZE; +import static com.jd.blockchain.crypto.CryptoKeyType.SYMMETRIC_KEY; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import com.jd.blockchain.crypto.*; +import com.jd.blockchain.crypto.SymmetricKey; +import com.jd.blockchain.crypto.symmetric.SymmetricCiphertext; +import com.jd.blockchain.crypto.symmetric.SymmetricEncryptionFunction; +import com.jd.blockchain.utils.security.AESUtils; + +public class AESEncryptionFunction implements SymmetricEncryptionFunction { + + public static final CryptoAlgorithm AES = ClassicCryptoService.AES_ALGORITHM; + + private static final int KEY_SIZE = 128 / 8; + private static final int BLOCK_SIZE = 128 / 8; + + private static final int PLAINTEXT_BUFFER_LENGTH = 256; + private static final int CIPHERTEXT_BUFFER_LENGTH = 256 + 16 + 2; + + private static final int SYMMETRICKEY_LENGTH = ALGORYTHM_CODE_SIZE + KEY_TYPE_BYTES + KEY_SIZE; + + AESEncryptionFunction() { + } + + @Override + public Ciphertext encrypt(SymmetricKey key, byte[] data) { + + byte[] rawKeyBytes = key.getRawKeyBytes(); + + // 验证原始密钥长度为128比特,即16字节 + if (rawKeyBytes.length != KEY_SIZE) { + throw new CryptoException("This key has wrong format!"); + } + + // 验证密钥数据的算法标识对应AES算法 + if (key.getAlgorithm().code() != AES.code()) { + throw new CryptoException("The is not AES symmetric key!"); + } + + // 调用底层AES128算法并计算密文数据 + return new SymmetricCiphertext(AES, AESUtils.encrypt(data, rawKeyBytes)); + } + + @Override + public void encrypt(SymmetricKey key, InputStream in, OutputStream out) { + // 读输入流得到明文,加密,密文数据写入输出流 + try { + // TODO: 错误地使用 available 方法; + + byte[] buffBytes = new byte[PLAINTEXT_BUFFER_LENGTH]; + + // The final byte of plaintextWithPadding represents the length of padding in the first 256 bytes, + // and the padded value in hexadecimal + byte[] plaintextWithPadding = new byte[buffBytes.length + 1]; + + byte padding; + + int len; + int i; + + while((len=in.read(buffBytes)) > 0){ + padding = (byte) (PLAINTEXT_BUFFER_LENGTH - len); + i = len; + while (i < plaintextWithPadding.length){ + plaintextWithPadding[i] = padding; + i++; + } + out.write(encrypt(key,plaintextWithPadding).toBytes()); + } +// int size = in.available(); +// if (size < 1){ +// throw new CryptoException("The input is null!"); +// } +// +// byte[] aesData = new byte[size]; +// +// if (in.read(aesData) != -1) { +// out.write(encrypt(key, aesData).); +// } +// +// in.close(); +// out.close(); + + } catch (IOException e) { + throw new CryptoException(e.getMessage(), e); + } + } + + @Override + public byte[] decrypt(SymmetricKey key, Ciphertext ciphertext) { + byte[] rawKeyBytes = key.getRawKeyBytes(); + byte[] rawCiphertextBytes = ciphertext.getRawCiphertext(); + + // 验证原始密钥长度为128比特,即16字节 + if (rawKeyBytes.length != KEY_SIZE) { + throw new CryptoException("This key has wrong format!"); + } + + // 验证密钥数据的算法标识对应AES算法 + if (key.getAlgorithm().code() != AES.code()) { + throw new CryptoException("The is not AES symmetric key!"); + } + + // 验证原始密文长度为分组长度的整数倍 + if (rawCiphertextBytes.length % BLOCK_SIZE != 0) { + throw new CryptoException("This ciphertext has wrong format!"); + } + + // 验证密文数据算法标识对应AES算法 + if (ciphertext.getAlgorithm().code() != AES.code()) { + throw new CryptoException("This is not AES ciphertext!"); + } + + // 调用底层AES128算法解密,得到明文 + return AESUtils.decrypt(rawCiphertextBytes, rawKeyBytes); + } + + @Override + public void decrypt(SymmetricKey key, InputStream in, OutputStream out) { + // 读输入流得到密文数据,解密,明文写入输出流 + try { + byte[] buffBytes = new byte[CIPHERTEXT_BUFFER_LENGTH]; + byte[] plaintextWithPadding = new byte[PLAINTEXT_BUFFER_LENGTH + 1]; + + byte padding; + byte[] plaintext; + + int len,i; + while ((len = in.read(buffBytes)) > 0){ + if (len != CIPHERTEXT_BUFFER_LENGTH){ + throw new CryptoException("inputStream's length is wrong!"); + } + if (!supportCiphertext(buffBytes)) { + throw new CryptoException("InputStream is not valid AES ciphertext!"); + } + + plaintextWithPadding = decrypt(key,resolveCiphertext(buffBytes)); + + if (plaintextWithPadding.length != (PLAINTEXT_BUFFER_LENGTH +1)){ + throw new CryptoException("The decrypted plaintext is valid"); + } + + + padding = plaintextWithPadding[PLAINTEXT_BUFFER_LENGTH]; + i = PLAINTEXT_BUFFER_LENGTH; + + + while ((PLAINTEXT_BUFFER_LENGTH - padding) < i){ + + i--; + } + plaintext = new byte[PLAINTEXT_BUFFER_LENGTH - padding]; + System.arraycopy(plaintextWithPadding,0,plaintext,0,plaintext.length); + out.write(plaintext); + } + +// // TODO: 错误地使用 available 方法; +// byte[] aesData = new byte[in.available()]; +// in.read(aesData); +// in.close(); +// +// if (!supportCiphertext(aesData)) { +// throw new CryptoException("InputStream is not valid AES ciphertext!"); +// } +// +// out.write(decrypt(key, resolveCiphertext(aesData))); +// out.close(); + } catch (IOException e) { + throw new CryptoException(e.getMessage(), e); + } + } + + @Override + public boolean supportSymmetricKey(byte[] symmetricKeyBytes) { + // 验证输入字节数组长度=算法标识长度+密钥类型长度+密钥长度,字节数组的算法标识对应AES算法且密钥密钥类型是对称密钥 + return symmetricKeyBytes.length == SYMMETRICKEY_LENGTH && CryptoAlgorithm.match(AES, symmetricKeyBytes) + && symmetricKeyBytes[ALGORYTHM_CODE_SIZE] == SYMMETRIC_KEY.CODE; + } + + @Override + public SymmetricKey resolveSymmetricKey(byte[] symmetricKeyBytes) { + // 由框架调用 support 方法检查有效性,在此不做重复检查; + return new SymmetricKey(symmetricKeyBytes); + } + + @Override + public boolean supportCiphertext(byte[] ciphertextBytes) { + // 验证(输入字节数组长度-算法标识长度)是分组长度的整数倍,字节数组的算法标识对应AES算法 + return (ciphertextBytes.length - ALGORYTHM_CODE_SIZE) % BLOCK_SIZE == 0 + && CryptoAlgorithm.match(AES, ciphertextBytes); + } + + @Override + public SymmetricCiphertext resolveCiphertext(byte[] ciphertextBytes) { + // 由框架调用 support 方法检查有效性,在此不做重复检查; + return new SymmetricCiphertext(ciphertextBytes); + } + + @Override + public CryptoAlgorithm getAlgorithm() { + return AES; + } + + @Override + public CryptoKey generateSymmetricKey() { + // 根据对应的标识和原始密钥生成相应的密钥数据 + return new SymmetricKey(AES, AESUtils.generateKey128_Bytes()); + } +} diff --git a/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/ClassicCryptoService.java b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/ClassicCryptoService.java new file mode 100644 index 00000000..d807fd7c --- /dev/null +++ b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/ClassicCryptoService.java @@ -0,0 +1,62 @@ +package com.jd.blockchain.crypto.service.classic; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoAlgorithmDefinition; +import com.jd.blockchain.crypto.CryptoFunction; +import com.jd.blockchain.crypto.CryptoService; +import com.jd.blockchain.provider.NamedProvider; + +@NamedProvider("CLASSIC") +public class ClassicCryptoService implements CryptoService { + + public static final CryptoAlgorithm ED25519_ALGORITHM = CryptoAlgorithmDefinition.defineSignature("ED25519", + false, (byte) 21); + public static final CryptoAlgorithm ECDSA_ALGORITHM = CryptoAlgorithmDefinition.defineSignature("ECDSA", + false, (byte) 22); + + public static final CryptoAlgorithm RSA_ALGORITHM = CryptoAlgorithmDefinition.defineSignature("RSA", + true, (byte) 23); + + public static final CryptoAlgorithm SHA256_ALGORITHM = CryptoAlgorithmDefinition.defineHash("SHA256", + (byte) 24); + + public static final CryptoAlgorithm RIPEMD160_ALGORITHM = CryptoAlgorithmDefinition.defineHash("RIPEMD160", + (byte) 25); + + public static final CryptoAlgorithm AES_ALGORITHM = CryptoAlgorithmDefinition.defineSymmetricEncryption("AES", + (byte) 26); + + public static final CryptoAlgorithm JVM_SECURE_RANDOM_ALGORITHM = CryptoAlgorithmDefinition + .defineRandom("JVM-SECURE-RANDOM", (byte) 27); + + public static final AESEncryptionFunction AES = new AESEncryptionFunction(); + + public static final ED25519SignatureFunction ED25519 = new ED25519SignatureFunction(); + + public static final RIPEMD160HashFunction RIPEMD160 = new RIPEMD160HashFunction(); + + public static final SHA256HashFunction SHA256 = new SHA256HashFunction(); + + public static final JVMSecureRandomFunction JVM_SECURE_RANDOM = new JVMSecureRandomFunction(); + + // public static final ECDSASignatureFunction ECDSA = new + // ECDSASignatureFunction(); + + private static final Collection FUNCTIONS; + + static { + List funcs = Arrays.asList(AES, ED25519, RIPEMD160, SHA256, JVM_SECURE_RANDOM); + FUNCTIONS = Collections.unmodifiableList(funcs); + } + + @Override + public Collection getFunctions() { + return FUNCTIONS; + } + +} diff --git a/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/ECDSASignatureFunction.java b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/ECDSASignatureFunction.java new file mode 100644 index 00000000..11ef1217 --- /dev/null +++ b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/ECDSASignatureFunction.java @@ -0,0 +1,67 @@ +package com.jd.blockchain.crypto.service.classic; + +import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.crypto.asymmetric.*; + +public class ECDSASignatureFunction implements SignatureFunction { + + ECDSASignatureFunction() { + } + + @Override + public SignatureDigest sign(PrivKey privKey, byte[] data) { + return null; + } + + @Override + public boolean verify(SignatureDigest digest, PubKey pubKey, byte[] data) { + return false; + } + + @Override + public byte[] retrievePubKeyBytes(byte[] privKeyBytes) { + return new byte[0]; + } + + @Override + public boolean supportPrivKey(byte[] privKeyBytes) { + return false; + } + + @Override + public PrivKey resolvePrivKey(byte[] privKeyBytes) { + return null; + } + + @Override + public boolean supportPubKey(byte[] pubKeyBytes) { + return false; + } + + @Override + public PubKey resolvePubKey(byte[] pubKeyBytes) { + return null; + } + + @Override + public boolean supportDigest(byte[] digestBytes) { + return false; + } + + @Override + public SignatureDigest resolveDigest(byte[] digestBytes) { + return null; + } + + @Override + public CryptoAlgorithm getAlgorithm() { + return ClassicCryptoService.ECDSA_ALGORITHM; + } + + @Override + public CryptoKeyPair generateKeyPair() { + return null; + } +} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/asymmetric/ED25519SignatureFunction.java b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/ED25519SignatureFunction.java similarity index 65% rename from source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/asymmetric/ED25519SignatureFunction.java rename to source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/ED25519SignatureFunction.java index 519f3c88..1d60c2e8 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/asymmetric/ED25519SignatureFunction.java +++ b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/ED25519SignatureFunction.java @@ -1,7 +1,19 @@ -package com.jd.blockchain.crypto.impl.def.asymmetric; +package com.jd.blockchain.crypto.service.classic; + +import static com.jd.blockchain.crypto.BaseCryptoKey.KEY_TYPE_BYTES; +import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_CODE_SIZE; +import static com.jd.blockchain.crypto.CryptoKeyType.PRIV_KEY; +import static com.jd.blockchain.crypto.CryptoKeyType.PUB_KEY; + +import java.security.KeyPair; import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.*; +import com.jd.blockchain.crypto.CryptoException; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; +import com.jd.blockchain.crypto.asymmetric.SignatureDigest; +import com.jd.blockchain.crypto.asymmetric.SignatureFunction; import com.jd.blockchain.utils.security.Ed25519Utils; import net.i2p.crypto.eddsa.EdDSAPrivateKey; @@ -11,25 +23,19 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; -import java.security.KeyPair; - -import static com.jd.blockchain.crypto.CryptoAlgorithm.ED25519; -import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_BYTES; -import static com.jd.blockchain.crypto.CryptoKeyType.PRIV_KEY; -import static com.jd.blockchain.crypto.CryptoKeyType.PUB_KEY; -import static com.jd.blockchain.crypto.base.BaseCryptoKey.KEY_TYPE_BYTES; - public class ED25519SignatureFunction implements SignatureFunction { + private static final CryptoAlgorithm ED25519 = ClassicCryptoService.ED25519_ALGORITHM; + private static final int PUBKEY_SIZE = 32; private static final int PRIVKEY_SIZE = 32; - private static final int DIGEST_SIZE = 64; + private static final int SIGNATUREDIGEST_SIZE = 64; - private static final int PUBKEY_LENGTH = ALGORYTHM_BYTES + KEY_TYPE_BYTES + PUBKEY_SIZE; - private static final int PRIVKEY_LENGTH = ALGORYTHM_BYTES + KEY_TYPE_BYTES + PRIVKEY_SIZE; - private static final int SIGNATUREDIGEST_LENGTH = ALGORYTHM_BYTES + DIGEST_SIZE; + private static final int PUBKEY_LENGTH = ALGORYTHM_CODE_SIZE + KEY_TYPE_BYTES + PUBKEY_SIZE; + private static final int PRIVKEY_LENGTH = ALGORYTHM_CODE_SIZE + KEY_TYPE_BYTES + PRIVKEY_SIZE; + private static final int SIGNATUREDIGEST_LENGTH = ALGORYTHM_CODE_SIZE + SIGNATUREDIGEST_SIZE; - public ED25519SignatureFunction() { + ED25519SignatureFunction() { } @Override @@ -38,12 +44,14 @@ public class ED25519SignatureFunction implements SignatureFunction { byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); // 验证原始私钥长度为256比特,即32字节 - if (rawPrivKeyBytes.length != PRIVKEY_SIZE) - throw new IllegalArgumentException("This key has wrong format!"); + if (rawPrivKeyBytes.length != PRIVKEY_SIZE) { + throw new CryptoException("This key has wrong format!"); + } // 验证密钥数据的算法标识对应ED25519签名算法 - if (privKey.getAlgorithm() != ED25519) - throw new IllegalArgumentException("This key is not ED25519 private key!"); + if (privKey.getAlgorithm().code() != ED25519.code()) { + throw new CryptoException("This key is not ED25519 private key!"); + } // 调用ED25519签名算法计算签名结果 return new SignatureDigest(ED25519, Ed25519Utils.sign_512(data, rawPrivKeyBytes)); @@ -56,16 +64,19 @@ public class ED25519SignatureFunction implements SignatureFunction { byte[] rawDigestBytes = digest.getRawDigest(); // 验证原始公钥长度为256比特,即32字节 - if (rawPubKeyBytes.length != PUBKEY_SIZE) - throw new IllegalArgumentException("This key has wrong format!"); + if (rawPubKeyBytes.length != PUBKEY_SIZE) { + throw new CryptoException("This key has wrong format!"); + } // 验证密钥数据的算法标识对应ED25519签名算法 - if (pubKey.getAlgorithm() != ED25519) - throw new IllegalArgumentException("This key is not ED25519 public key!"); + if (pubKey.getAlgorithm().code() != ED25519.code()) { + throw new CryptoException("This key is not ED25519 public key!"); + } // 验证密文数据的算法标识对应ED25519签名算法,并且原始摘要长度为64字节 - if (digest.getAlgorithm() != ED25519 || rawDigestBytes.length != DIGEST_SIZE) - throw new IllegalArgumentException("This is not ED25519 signature digest!"); + if (digest.getAlgorithm().code() != ED25519.code() || rawDigestBytes.length != SIGNATUREDIGEST_SIZE) { + throw new CryptoException("This is not ED25519 signature digest!"); + } // 调用ED25519验签算法验证签名结果 return Ed25519Utils.verify(data, rawPubKeyBytes, rawDigestBytes); @@ -78,13 +89,14 @@ public class ED25519SignatureFunction implements SignatureFunction { EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512); EdDSAPrivateKeySpec privateKeySpec = new EdDSAPrivateKeySpec(rawPrivKeyBytes, spec); byte[] rawPubKeyBytes = privateKeySpec.getA().toByteArray(); - return new PubKey(ED25519,rawPubKeyBytes).toBytes(); + return new PubKey(ED25519, rawPubKeyBytes).toBytes(); } @Override public boolean supportPrivKey(byte[] privKeyBytes) { // 验证输入字节数组长度=算法标识长度+密钥类型长度+密钥长度,密钥数据的算法标识对应ED25519签名算法,并且密钥类型是私钥 - return privKeyBytes.length == PRIVKEY_LENGTH && privKeyBytes[0] == ED25519.CODE && privKeyBytes[1] == PRIV_KEY.CODE; + return privKeyBytes.length == PRIVKEY_LENGTH && CryptoAlgorithm.match(ED25519, privKeyBytes) + && privKeyBytes[ALGORYTHM_CODE_SIZE] == PRIV_KEY.CODE; } @Override @@ -96,7 +108,8 @@ public class ED25519SignatureFunction implements SignatureFunction { @Override public boolean supportPubKey(byte[] pubKeyBytes) { // 验证输入字节数组长度=算法标识长度+密钥类型长度+密钥长度,密钥数据的算法标识对应ED25519签名算法,并且密钥类型是公钥 - return pubKeyBytes.length == PUBKEY_LENGTH && pubKeyBytes[0] == ED25519.CODE && pubKeyBytes[1] == PUB_KEY.CODE; + return pubKeyBytes.length == PUBKEY_LENGTH && CryptoAlgorithm.match(ED25519, pubKeyBytes) + && pubKeyBytes[ALGORYTHM_CODE_SIZE] == PUB_KEY.CODE; } @@ -109,7 +122,7 @@ public class ED25519SignatureFunction implements SignatureFunction { @Override public boolean supportDigest(byte[] digestBytes) { // 验证输入字节数组长度=算法标识长度+摘要长度,字节数组的算法标识对应ED25519算法 - return digestBytes.length == SIGNATUREDIGEST_LENGTH && digestBytes[0] == ED25519.CODE; + return digestBytes.length == SIGNATUREDIGEST_LENGTH && CryptoAlgorithm.match(ED25519, digestBytes); } @Override diff --git a/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/JVMSecureRandomFunction.java b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/JVMSecureRandomFunction.java new file mode 100644 index 00000000..e32b4813 --- /dev/null +++ b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/JVMSecureRandomFunction.java @@ -0,0 +1,54 @@ +package com.jd.blockchain.crypto.service.classic; + +import java.security.SecureRandom; + +import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.RandomFunction; +import com.jd.blockchain.crypto.RandomGenerator; + +public class JVMSecureRandomFunction implements RandomFunction { + + private static final CryptoAlgorithm JVM_SECURE_RANDOM = ClassicCryptoService.JVM_SECURE_RANDOM_ALGORITHM; + + + JVMSecureRandomFunction() { + } + + @Override + public CryptoAlgorithm getAlgorithm() { + return JVM_SECURE_RANDOM; + } + + @Override + public RandomGenerator generate(byte[] seed) { + return new SecureRandomGenerator(seed); + } + + + private static class SecureRandomGenerator implements RandomGenerator{ + + private SecureRandom sr; + + public SecureRandomGenerator(byte[] seed) { + if (seed == null || seed.length == 0) { + // 随机; + sr = new SecureRandom(); + } else { + sr = new SecureRandom(seed); + } + } + + @Override + public byte[] nextBytes(int size) { + byte[] randomBytes = new byte[size]; + sr.nextBytes(randomBytes); + return randomBytes; + } + + @Override + public void nextBytes(byte[] buffer) { + sr.nextBytes(buffer); + } + + } +} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/hash/RIPEMD160HashFunction.java b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/RIPEMD160HashFunction.java similarity index 66% rename from source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/hash/RIPEMD160HashFunction.java rename to source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/RIPEMD160HashFunction.java index 7933021b..f7ffba03 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/hash/RIPEMD160HashFunction.java +++ b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/RIPEMD160HashFunction.java @@ -1,20 +1,25 @@ -package com.jd.blockchain.crypto.impl.def.hash; +package com.jd.blockchain.crypto.service.classic; -import static com.jd.blockchain.crypto.CryptoAlgorithm.RIPEMD160; -import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_BYTES; +import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_CODE_SIZE; import java.util.Arrays; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoException; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.crypto.hash.HashFunction; import com.jd.blockchain.utils.security.RipeMD160Utils; public class RIPEMD160HashFunction implements HashFunction { + private static final CryptoAlgorithm RIPEMD160 = ClassicCryptoService.RIPEMD160_ALGORITHM; + private static final int DIGEST_BYTES = 160 / 8; - private static final int DIGEST_LENGTH = ALGORYTHM_BYTES + DIGEST_BYTES; + private static final int DIGEST_LENGTH = ALGORYTHM_CODE_SIZE + DIGEST_BYTES; + + RIPEMD160HashFunction() { + } @Override public CryptoAlgorithm getAlgorithm() { @@ -23,6 +28,11 @@ public class RIPEMD160HashFunction implements HashFunction { @Override public HashDigest hash(byte[] data) { + + if (data == null) { + throw new CryptoException("The input is null!"); + } + byte[] digestBytes = RipeMD160Utils.hash(data); return new HashDigest(RIPEMD160, digestBytes); } @@ -36,7 +46,7 @@ public class RIPEMD160HashFunction implements HashFunction { @Override public boolean supportHashDigest(byte[] digestBytes) { // 验证输入字节数组长度=算法标识长度+摘要长度,以及算法标识; - return RIPEMD160.CODE == digestBytes[0] && DIGEST_LENGTH == digestBytes.length; + return DIGEST_LENGTH == digestBytes.length && CryptoAlgorithm.match(RIPEMD160, digestBytes); } @Override diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/asymmetric/ECDSASignatureFunction.java b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/RSACryptoFunction.java similarity index 56% rename from source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/asymmetric/ECDSASignatureFunction.java rename to source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/RSACryptoFunction.java index d17ecf3c..d921a398 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/asymmetric/ECDSASignatureFunction.java +++ b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/RSACryptoFunction.java @@ -1,9 +1,27 @@ -package com.jd.blockchain.crypto.impl.def.asymmetric; +package com.jd.blockchain.crypto.service.classic; +import com.jd.blockchain.crypto.Ciphertext; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.*; -public class ECDSASignatureFunction implements SignatureFunction { +/** + * @author zhanglin33 + * @title: RSACryptoFunction + * @description: Interfaces for RSA crypto functions, including key generation, encryption, signature, and so on + * @date 2019-03-25, 17:28 + */ +public class RSACryptoFunction implements AsymmetricEncryptionFunction, SignatureFunction { + @Override + public Ciphertext encrypt(PubKey pubKey, byte[] data) { + return null; + } + + @Override + public byte[] decrypt(PrivKey privKey, Ciphertext ciphertext) { + return new byte[0]; + } @Override public SignatureDigest sign(PrivKey privKey, byte[] data) { @@ -50,9 +68,19 @@ public class ECDSASignatureFunction implements SignatureFunction { return null; } + @Override + public boolean supportCiphertext(byte[] ciphertextBytes) { + return false; + } + + @Override + public AsymmetricCiphertext resolveCiphertext(byte[] ciphertextBytes) { + return null; + } + @Override public CryptoAlgorithm getAlgorithm() { - return CryptoAlgorithm.ECDSA; + return null; } @Override diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/hash/SHA256HashFunction.java b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/SHA256HashFunction.java similarity index 67% rename from source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/hash/SHA256HashFunction.java rename to source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/SHA256HashFunction.java index b7aebeff..e210bd27 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/hash/SHA256HashFunction.java +++ b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/service/classic/SHA256HashFunction.java @@ -1,20 +1,25 @@ -package com.jd.blockchain.crypto.impl.def.hash; +package com.jd.blockchain.crypto.service.classic; -import static com.jd.blockchain.crypto.CryptoAlgorithm.SHA256; -import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_BYTES; +import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_CODE_SIZE; import java.util.Arrays; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoException; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.crypto.hash.HashFunction; import com.jd.blockchain.utils.security.ShaUtils; public class SHA256HashFunction implements HashFunction { + private static final CryptoAlgorithm SHA256 = ClassicCryptoService.SHA256_ALGORITHM; + private static final int DIGEST_BYTES = 256 / 8; - private static final int DIGEST_LENGTH = ALGORYTHM_BYTES + DIGEST_BYTES; + private static final int DIGEST_LENGTH = ALGORYTHM_CODE_SIZE + DIGEST_BYTES; + + SHA256HashFunction() { + } @Override public CryptoAlgorithm getAlgorithm() { @@ -23,6 +28,11 @@ public class SHA256HashFunction implements HashFunction { @Override public HashDigest hash(byte[] data) { + + if (data == null) { + throw new CryptoException("The input is null!"); + } + byte[] digestBytes = ShaUtils.hash_256(data); return new HashDigest(SHA256, digestBytes); } @@ -36,7 +46,7 @@ public class SHA256HashFunction implements HashFunction { @Override public boolean supportHashDigest(byte[] digestBytes) { // 验证输入字节数组长度=算法标识长度+摘要长度,以及算法标识; - return SHA256.CODE == digestBytes[0] && DIGEST_LENGTH == digestBytes.length; + return DIGEST_LENGTH == digestBytes.length && CryptoAlgorithm.match(SHA256, digestBytes); } @Override @@ -46,4 +56,3 @@ public class SHA256HashFunction implements HashFunction { } } - diff --git a/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/utils/classic/ECDSAUtils.java b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/utils/classic/ECDSAUtils.java new file mode 100644 index 00000000..8ef85fe0 --- /dev/null +++ b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/utils/classic/ECDSAUtils.java @@ -0,0 +1,10 @@ +package com.jd.blockchain.crypto.utils.classic; + +/** + * @author zhanglin33 + * @title: ECDSAUtils + * @description: ECDSA signature algorithm + * @date 2019-03-25, 17:21 + */ +public class ECDSAUtils { +} diff --git a/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/utils/classic/RSAUtils.java b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/utils/classic/RSAUtils.java new file mode 100644 index 00000000..7724de86 --- /dev/null +++ b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/utils/classic/RSAUtils.java @@ -0,0 +1,10 @@ +package com.jd.blockchain.crypto.utils.classic; + +/** + * @author zhanglin33 + * @title: RSAUtils + * @description: RSA encryption and signature algorithms + * @date 2019-03-25, 17:20 + */ +public class RSAUtils { +} diff --git a/source/crypto/crypto-classic/src/main/resources/META-INF/services/com.jd.blockchain.crypto.CryptoService b/source/crypto/crypto-classic/src/main/resources/META-INF/services/com.jd.blockchain.crypto.CryptoService new file mode 100644 index 00000000..75d86390 --- /dev/null +++ b/source/crypto/crypto-classic/src/main/resources/META-INF/services/com.jd.blockchain.crypto.CryptoService @@ -0,0 +1 @@ +com.jd.blockchain.crypto.service.classic.ClassicCryptoService \ No newline at end of file diff --git a/source/crypto/crypto-classic/src/test/java/test/com/jd/blockchain/crypto/asymmetric/AsymmtricCryptographyImplTest.java b/source/crypto/crypto-classic/src/test/java/test/com/jd/blockchain/crypto/asymmetric/AsymmtricCryptographyImplTest.java new file mode 100644 index 00000000..091938cd --- /dev/null +++ b/source/crypto/crypto-classic/src/test/java/test/com/jd/blockchain/crypto/asymmetric/AsymmtricCryptographyImplTest.java @@ -0,0 +1,953 @@ +//package test.com.jd.blockchain.crypto.asymmetric; +// +//import static com.jd.blockchain.crypto.CryptoKeyType.PRIV_KEY; +//import static com.jd.blockchain.crypto.CryptoKeyType.PUB_KEY; +//import static org.junit.Assert.assertArrayEquals; +//import static org.junit.Assert.assertEquals; +//import static org.junit.Assert.assertNotNull; +//import static org.junit.Assert.assertNull; +//import static org.junit.Assert.assertTrue; +// +//import java.util.Random; +// +//import org.junit.Test; +// +//import com.jd.blockchain.crypto.Ciphertext; +//import com.jd.blockchain.crypto.CryptoAlgorithm; +//import com.jd.blockchain.crypto.CryptoException; +//import com.jd.blockchain.crypto.CryptoKeyType; +//import com.jd.blockchain.crypto.PrivKey; +//import com.jd.blockchain.crypto.PubKey; +//import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; +//import com.jd.blockchain.crypto.asymmetric.AsymmetricEncryptionFunction; +//import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; +//import com.jd.blockchain.crypto.asymmetric.SignatureDigest; +//import com.jd.blockchain.crypto.asymmetric.SignatureFunction; +//import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; +//import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; +//import com.jd.blockchain.utils.io.BytesUtils; +// +//public class AsymmtricCryptographyImplTest { +// +// @Test +// public void testGenerateKeyPair() { +// +// //test ED25519 +// CryptoAlgorithm algorithm = ClassicCryptoService.ED25519.getAlgorithm(); +// CryptoKeyPair keyPair = ClassicCryptoService.ED25519.generateKeyPair(); +// +// PubKey pubKey = keyPair.getPubKey(); +// PrivKey privKey = keyPair.getPrivKey(); +// +// assertNotNull(pubKey); +// assertNotNull(privKey); +// +// assertEquals(ClassicCryptoService.ED25519.getAlgorithm().code(),pubKey.getAlgorithm().code()); +// assertEquals(ClassicCryptoService.ED25519.getAlgorithm().code(),privKey.getAlgorithm().code()); +// +// assertEquals(32,pubKey.getRawKeyBytes().length); +// assertEquals(32,privKey.getRawKeyBytes().length); +// +// byte[] pubKeyBytes = pubKey.toBytes(); +// byte[] privKeyBytes = privKey.toBytes(); +// +// assertEquals(32+1+1,pubKeyBytes.length); +// assertEquals(32+1+1,privKeyBytes.length); +// +// byte[] algorithmBytes = CryptoAlgorithm.toBytes(ClassicCryptoService.ED25519.getAlgorithm()); +// assertEquals(algorithmBytes[0],pubKeyBytes[0]); +// assertEquals(algorithmBytes[1],pubKeyBytes[1]); +// assertEquals(algorithmBytes[0],privKeyBytes[0]); +// assertEquals(algorithmBytes[1],privKeyBytes[1]); +// +// assertEquals(pubKey.getKeyType().CODE,pubKeyBytes[CryptoAlgorithm.CODE_SIZE]); +// assertEquals(privKey.getKeyType().CODE,privKeyBytes[CryptoAlgorithm.CODE_SIZE]); +// +// +// //test SM2 +// algorithm = CryptoAlgorithm.SM2; +// keyPair = asymmetricCrypto.generateKeyPair(algorithm); +// +// assertNotNull(keyPair); +// +// pubKey = keyPair.getPubKey(); +// privKey = keyPair.getPrivKey(); +// +// assertNotNull(pubKey); +// assertNotNull(privKey); +// +// assertEquals(algorithm,pubKey.getAlgorithm()); +// assertEquals(algorithm,privKey.getAlgorithm()); +// +// assertEquals(65,pubKey.getRawKeyBytes().length); +// assertEquals(32,privKey.getRawKeyBytes().length); +// +// pubKeyBytes = pubKey.toBytes(); +// privKeyBytes = privKey.toBytes(); +// +// assertEquals(32+1+1,privKeyBytes.length); +// assertEquals(65+1+1,pubKeyBytes.length); +// +// assertEquals(CryptoAlgorithm.SM2.CODE,pubKeyBytes[0]); +// assertEquals(CryptoAlgorithm.SM2.CODE,privKeyBytes[0]); +// assertEquals(CryptoAlgorithm.SM2, CryptoAlgorithm.valueOf(pubKey.getAlgorithm().CODE)); +// assertEquals(CryptoAlgorithm.SM2, CryptoAlgorithm.valueOf(privKey.getAlgorithm().CODE)); +// +// assertEquals(pubKey.getKeyType().CODE,pubKeyBytes[1]); +// assertEquals(privKey.getKeyType().CODE,privKeyBytes[1]); +// +// +// //test JNIED25519 +// algorithm = CryptoAlgorithm.JNIED25519; +// keyPair = asymmetricCrypto.generateKeyPair(algorithm); +// +// assertNotNull(keyPair); +// +// pubKey = keyPair.getPubKey(); +// privKey = keyPair.getPrivKey(); +// +// assertNotNull(pubKey); +// assertNotNull(privKey); +// +// assertEquals(algorithm,pubKey.getAlgorithm()); +// assertEquals(algorithm,privKey.getAlgorithm()); +// +// assertEquals(32,pubKey.getRawKeyBytes().length); +// assertEquals(32,privKey.getRawKeyBytes().length); +// +// pubKeyBytes = pubKey.toBytes(); +// privKeyBytes = privKey.toBytes(); +// +// assertEquals(32+1+1,pubKeyBytes.length); +// assertEquals(32+1+1,privKeyBytes.length); +// +// assertEquals(CryptoAlgorithm.JNIED25519.CODE,pubKeyBytes[0]); +// assertEquals(CryptoAlgorithm.JNIED25519.CODE,privKeyBytes[0]); +// assertEquals(CryptoAlgorithm.JNIED25519, CryptoAlgorithm.valueOf(pubKey.getAlgorithm().CODE)); +// assertEquals(CryptoAlgorithm.JNIED25519, CryptoAlgorithm.valueOf(privKey.getAlgorithm().CODE)); +// +// assertEquals(pubKey.getKeyType().CODE,pubKeyBytes[1]); +// assertEquals(privKey.getKeyType().CODE,privKeyBytes[1]); +// } +// +// @Test +// public void testGetSignatureFunction() { +// +// AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); +// Random random = new Random(); +// +// +// //test ED25519 +// CryptoAlgorithm algorithm = CryptoAlgorithm.ED25519; +// +// // 测试256字节的消息进行签名 +// byte[] data = new byte[256]; +// random.nextBytes(data); +// verifyGetSignatureFunction(asymmetricCrypto,algorithm,data,32,32,64,null); +// +// //错误的算法标识 +// verifyGetSignatureFunction(asymmetricCrypto,CryptoAlgorithm.AES,data,32,32,64,IllegalArgumentException.class); +// +// data = null; +// verifyGetSignatureFunction(asymmetricCrypto,algorithm,data,32,32,64,NullPointerException.class); +// +// +// //test SM2 +// algorithm = CryptoAlgorithm.SM2; +// +// // 测试256字节的消息进行签名 +// data = new byte[256]; +// random.nextBytes(data); +// verifyGetSignatureFunction(asymmetricCrypto,algorithm,data,65,32,64,null); +// +// //错误的算法标识 +// verifyGetSignatureFunction(asymmetricCrypto,CryptoAlgorithm.AES,data,65,32,64,IllegalArgumentException.class); +// +// data = null; +// verifyGetSignatureFunction(asymmetricCrypto,algorithm,data,65,32,64,NullPointerException.class); +// +// +// //test JNNIED25519 +// algorithm = CryptoAlgorithm.JNIED25519; +// +// // 测试256字节的消息进行签名 +// data = new byte[256]; +// random.nextBytes(data); +// verifyGetSignatureFunction(asymmetricCrypto,algorithm,data,32,32,64,null); +// +// //错误的算法标识 +// verifyGetSignatureFunction(asymmetricCrypto,CryptoAlgorithm.AES,data,32,32,64,IllegalArgumentException.class); +// +// data = null; +// verifyGetSignatureFunction(asymmetricCrypto,algorithm,data,32,32,64,IllegalArgumentException.class); +// } +// +// private void verifyGetSignatureFunction(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, byte[] data, +// int expectedPubKeyLength, int expectedPrivKeyLength, +// int expectedSignatureDigestLength, Class expectedException){ +// +// //初始化一个异常 +// Exception actualEx = null; +// +// try { +// SignatureFunction sf = asymmetricCrypto.getSignatureFunction(algorithm); +// +// assertNotNull(sf); +// +// CryptoKeyPair keyPair = sf.generateKeyPair(); +// PubKey pubKey = keyPair.getPubKey(); +// PrivKey privKey = keyPair.getPrivKey(); +// byte[] rawPubKeyBytes = pubKey.getRawKeyBytes(); +// byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); +// byte[] pubKeyBytes = pubKey.toBytes(); +// byte[] privKeyBytes = privKey.toBytes(); +// +// assertEquals(algorithm, pubKey.getAlgorithm()); +// assertEquals(algorithm, privKey.getAlgorithm()); +// assertEquals(expectedPubKeyLength,rawPubKeyBytes.length); +// assertEquals(expectedPrivKeyLength,rawPrivKeyBytes.length); +// +// assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{CryptoKeyType.PUB_KEY.CODE},rawPubKeyBytes), pubKeyBytes); +// assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{CryptoKeyType.PRIV_KEY.CODE},rawPrivKeyBytes), privKeyBytes); +// +// SignatureDigest signatureDigest = sf.sign(privKey,data); +// byte[] rawDigest = signatureDigest.getRawDigest(); +// +// assertEquals(algorithm,signatureDigest.getAlgorithm()); +// assertEquals(expectedSignatureDigestLength,rawDigest.length); +// byte[] signatureDigestBytes = signatureDigest.toBytes(); +// assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},rawDigest),signatureDigestBytes); +// +// assertTrue(signatureDigest.equals(signatureDigest)); +// assertEquals(signatureDigest.hashCode(),signatureDigest.hashCode()); +// +// assertTrue(sf.verify(signatureDigest,pubKey,data)); +// +// assertTrue(sf.supportPubKey(pubKeyBytes)); +// assertTrue(sf.supportPrivKey(privKeyBytes)); +// assertTrue(sf.supportDigest(signatureDigestBytes)); +// +// assertEquals(pubKey,sf.resolvePubKey(pubKeyBytes)); +// assertEquals(privKey,sf.resolvePrivKey(privKeyBytes)); +// assertEquals(signatureDigest,sf.resolveDigest(signatureDigestBytes)); +// +// assertEquals(algorithm,sf.getAlgorithm()); +// +// } catch (Exception e){ +// actualEx = e; +// } +// +// if (expectedException == null) { +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testVerify() { +// +// AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); +// Random randomData = new Random(); +// +// //test ED25519 +// CryptoAlgorithm algorithm = CryptoAlgorithm.ED25519; +// +// // 测试256字节的消息进行签名 +// byte[] data = new byte[256]; +// randomData.nextBytes(data); +// SignatureFunction sf = asymmetricCrypto.getSignatureFunction(algorithm); +// CryptoKeyPair keyPair = sf.generateKeyPair(); +// byte[] pubKeyBytes = keyPair.getPubKey().toBytes(); +// +// byte[] signatureDigestBytes = sf.sign(keyPair.getPrivKey(),data).toBytes(); +// verifyVerify(asymmetricCrypto,true,data,pubKeyBytes,signatureDigestBytes,null); +// +// //签名数据末尾两个字节丢失情况下,抛出异常 +// byte[] truncatedSignatureDigestBytes = new byte[signatureDigestBytes.length-2]; +// System.arraycopy(signatureDigestBytes,0,truncatedSignatureDigestBytes,0,truncatedSignatureDigestBytes.length); +// verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,truncatedSignatureDigestBytes,IllegalArgumentException.class); +// +// byte[] signatureDigestBytesWithWrongAlgCode = signatureDigestBytes; +// signatureDigestBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,signatureDigestBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// signatureDigestBytes = null; +// verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,signatureDigestBytes,NullPointerException.class); +// +// +// //test SM2 +// algorithm = CryptoAlgorithm.SM2; +// +// // 测试256字节的消息进行签名 +// data = new byte[256]; +// randomData.nextBytes(data); +// sf = asymmetricCrypto.getSignatureFunction(algorithm); +// keyPair = sf.generateKeyPair(); +// pubKeyBytes = keyPair.getPubKey().toBytes(); +// +// signatureDigestBytes = sf.sign(keyPair.getPrivKey(),data).toBytes(); +// verifyVerify(asymmetricCrypto,true,data,pubKeyBytes,signatureDigestBytes,null); +// +// //签名数据末尾两个字节丢失情况下,抛出异常 +// truncatedSignatureDigestBytes = new byte[signatureDigestBytes.length-2]; +// System.arraycopy(signatureDigestBytes,0,truncatedSignatureDigestBytes,0,truncatedSignatureDigestBytes.length); +// verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,truncatedSignatureDigestBytes,IllegalArgumentException.class); +// +// signatureDigestBytesWithWrongAlgCode = signatureDigestBytes; +// signatureDigestBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,signatureDigestBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// signatureDigestBytes = null; +// verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,signatureDigestBytes,NullPointerException.class); +// +// //test JNIED25519 +// algorithm = CryptoAlgorithm.JNIED25519; +// +// // 测试256字节的消息进行签名 +// data = new byte[256]; +// randomData.nextBytes(data); +// sf = asymmetricCrypto.getSignatureFunction(algorithm); +// keyPair = sf.generateKeyPair(); +// pubKeyBytes = keyPair.getPubKey().toBytes(); +// +// signatureDigestBytes = sf.sign(keyPair.getPrivKey(),data).toBytes(); +// verifyVerify(asymmetricCrypto,true,data,pubKeyBytes,signatureDigestBytes,null); +// +// //签名数据末尾两个字节丢失情况下,抛出异常 +// truncatedSignatureDigestBytes = new byte[signatureDigestBytes.length-2]; +// System.arraycopy(signatureDigestBytes,0,truncatedSignatureDigestBytes,0,truncatedSignatureDigestBytes.length); +// verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,truncatedSignatureDigestBytes,IllegalArgumentException.class); +// +// signatureDigestBytesWithWrongAlgCode = signatureDigestBytes; +// signatureDigestBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,signatureDigestBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// signatureDigestBytes = null; +// verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,signatureDigestBytes,NullPointerException.class); +// } +// +// private void verifyVerify(AsymmetricCryptography asymmetricCrypto,boolean expectedResult,byte[] data, +// byte[] pubKeyBytes, byte[] signatureDigestBytes, Class expectedException){ +// +// //初始化一个异常 +// Exception actualEx = null; +// boolean pass = false; +// +// try { +// +// pass = asymmetricCrypto.verify(signatureDigestBytes,pubKeyBytes,data); +// +// } +// catch (Exception e){ +// actualEx = e; +// } +// +// assertEquals(expectedResult, pass); +// +// if (expectedException == null) { +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testGetAsymmetricEncryptionFunction() { +// +// AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); +// Random random = new Random(); +// +// +// //test SM2 +// CryptoAlgorithm algorithm = CryptoAlgorithm.SM2; +// +// //Case 1: SM2Encryption with 16 bytes data +// byte[] data = new byte[16]; +// random.nextBytes(data); +// verifyGetAsymmetricEncryptionFunction(asymmetricCrypto, algorithm,65,32,65+16+32,data,null); +// +// //Case 2: SM2Encryption with 256 bytes data +// data = new byte[256]; +// random.nextBytes(data); +// verifyGetAsymmetricEncryptionFunction(asymmetricCrypto, algorithm,65,32,65+256+32,data,null); +// +// //Case 3: SM2Encryption with 1 bytes data +// data = new byte[3]; +// random.nextBytes(data); +// verifyGetAsymmetricEncryptionFunction(asymmetricCrypto, algorithm,65,32,65+3+32,data,null); +// +// //Case 4: SM2Encryption with wrong algorithm +// verifyGetAsymmetricEncryptionFunction(asymmetricCrypto,CryptoAlgorithm.AES,65,32,65+3+32,data,IllegalArgumentException.class); +// +// //Case 5: SM2Encryption with null data +// data = null; +// verifyGetAsymmetricEncryptionFunction(asymmetricCrypto,algorithm,65,32,65+32,data,NullPointerException.class); +// } +// +// private void verifyGetAsymmetricEncryptionFunction(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, +// int expectedPubKeyLength, int expectedPrivKeyLength, +// int expectedCiphertextLength, byte[] data, Class expectedException){ +// +// //初始化一个异常 +// Exception actualEx = null; +// +// try { +// AsymmetricEncryptionFunction aef = asymmetricCrypto.getAsymmetricEncryptionFunction(algorithm); +// //验证获取的算法实例非空 +// assertNotNull(aef); +// +// CryptoKeyPair keyPair = aef.generateKeyPair(); +// PubKey pubKey = keyPair.getPubKey(); +// PrivKey privKey = keyPair.getPrivKey(); +// byte[] rawPubKeyBytes = pubKey.getRawKeyBytes(); +// byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); +// byte[] pubKeyBytes = pubKey.toBytes(); +// byte[] privKeyBytes = privKey.toBytes(); +// +// assertEquals(algorithm, pubKey.getAlgorithm()); +// assertEquals(algorithm, privKey.getAlgorithm()); +// assertEquals(expectedPubKeyLength,rawPubKeyBytes.length); +// assertEquals(expectedPrivKeyLength,rawPrivKeyBytes.length); +// +// assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{CryptoKeyType.PUB_KEY.CODE},rawPubKeyBytes), pubKeyBytes); +// assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{CryptoKeyType.PRIV_KEY.CODE},rawPrivKeyBytes), privKeyBytes); +// +// Ciphertext ciphertext = aef.encrypt(pubKey,data); +// byte[] rawCiphertextBytes = ciphertext.getRawCiphertext(); +// +// assertEquals(algorithm,ciphertext.getAlgorithm()); +// assertEquals(expectedCiphertextLength,rawCiphertextBytes.length); +// byte[] ciphertextBytes = ciphertext.toBytes(); +// assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},rawCiphertextBytes),ciphertextBytes); +// +// assertArrayEquals(data,aef.decrypt(privKey,ciphertext)); +// +// assertTrue(aef.supportPubKey(pubKeyBytes)); +// assertTrue(aef.supportPrivKey(privKeyBytes)); +// assertTrue(aef.supportCiphertext(ciphertextBytes)); +// +// assertEquals(pubKey,aef.resolvePubKey(pubKeyBytes)); +// assertEquals(privKey,aef.resolvePrivKey(privKeyBytes)); +// assertEquals(ciphertext,aef.resolveCiphertext(ciphertextBytes)); +// +// assertEquals(algorithm,aef.getAlgorithm()); +// +// +// }catch (Exception e){ +// actualEx = e; +// } +// +// if(expectedException == null){ +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testDecrypt() { +// +// AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); +// Random random = new Random(); +// +// byte[] data = new byte[16]; +// random.nextBytes(data); +// +// //test SM2 +// CryptoAlgorithm algorithm = CryptoAlgorithm.SM2; +// AsymmetricEncryptionFunction aef = asymmetricCrypto.getAsymmetricEncryptionFunction(algorithm); +// CryptoKeyPair keyPair = aef.generateKeyPair(); +// PubKey pubKey = keyPair.getPubKey(); +// PrivKey privKey = keyPair.getPrivKey(); +// byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); +// Ciphertext ciphertext = aef.encrypt(pubKey,data); +// byte[] ciphertextBytes = ciphertext.toBytes(); +// +// verifyDecrypt(asymmetricCrypto, algorithm, rawPrivKeyBytes, data, ciphertextBytes, null); +// +// //密钥的算法标识与密文的算法标识不一致情况 +// verifyDecrypt(asymmetricCrypto, CryptoAlgorithm.AES, rawPrivKeyBytes, data, ciphertextBytes, IllegalArgumentException.class); +// +// //密文末尾两个字节丢失情况下,抛出异常 +// byte[] truncatedCiphertextBytes = new byte[ciphertextBytes.length-2]; +// System.arraycopy(ciphertextBytes,0,truncatedCiphertextBytes,0,truncatedCiphertextBytes.length); +// verifyDecrypt(asymmetricCrypto, algorithm, rawPrivKeyBytes, data, truncatedCiphertextBytes, com.jd.blockchain.crypto.CryptoException.class); +// +// byte[] ciphertextBytesWithWrongAlgCode = ciphertextBytes; +// ciphertextBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyDecrypt(asymmetricCrypto,algorithm,rawPrivKeyBytes,data,ciphertextBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// ciphertextBytes = null; +// verifyDecrypt(asymmetricCrypto,algorithm,rawPrivKeyBytes,data,ciphertextBytes,NullPointerException.class); +// } +// +// private void verifyDecrypt(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, +// byte[] key, byte[] data, byte[] ciphertextBytes, Class expectedException){ +// Exception actualEx = null; +// +// try { +// PrivKey privKey = new PrivKey(algorithm,key); +// +// byte[] plaintext = asymmetricCrypto.decrypt(privKey.toBytes(), ciphertextBytes); +// +// //解密后的明文与初始的明文一致 +// assertArrayEquals(data,plaintext); +// } +// catch (Exception e){ +// actualEx = e; +// } +// +// if (expectedException == null) { +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testResolveCiphertext() { +// +// +// AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); +// Random random = new Random(); +// +// byte[] data = new byte[16]; +// random.nextBytes(data); +// +// //test SM2 +// CryptoAlgorithm algorithm = CryptoAlgorithm.SM2; +// AsymmetricEncryptionFunction aef = asymmetricCrypto.getAsymmetricEncryptionFunction(algorithm); +// CryptoKeyPair keyPair = aef.generateKeyPair(); +// PubKey pubKey = keyPair.getPubKey(); +// PrivKey privKey = keyPair.getPrivKey(); +// byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); +// Ciphertext ciphertext = aef.encrypt(pubKey,data); +// byte[] ciphertextBytes = ciphertext.toBytes(); +// +// verifyResolveCiphertext(asymmetricCrypto, algorithm, ciphertextBytes, null); +// +// +// //密文末尾两个字节丢失情况下,抛出异常 +// byte[] truncatedCiphertextBytes = new byte[ciphertextBytes.length-2]; +// System.arraycopy(ciphertextBytes,0,truncatedCiphertextBytes,0,truncatedCiphertextBytes.length); +// verifyDecrypt(asymmetricCrypto, algorithm, rawPrivKeyBytes, data, truncatedCiphertextBytes, CryptoException.class); +// +// byte[] ciphertextBytesWithWrongAlgCode = ciphertextBytes; +// ciphertextBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyResolveCiphertext(asymmetricCrypto,algorithm,ciphertextBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// ciphertextBytes = null; +// verifyResolveCiphertext(asymmetricCrypto,algorithm,ciphertextBytes,NullPointerException.class); +// } +// +// private void verifyResolveCiphertext(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, byte[] ciphertextBytes, +// Class expectedException){ +// Exception actualEx = null; +// +// try { +// +// Ciphertext ciphertext = asymmetricCrypto.resolveCiphertext(ciphertextBytes); +// +// assertNotNull(ciphertext); +// +// assertEquals(algorithm, ciphertext.getAlgorithm()); +// +// assertArrayEquals(ciphertextBytes, ciphertext.toBytes()); +// } +// catch (Exception e){ +// actualEx = e; +// } +// +// if (expectedException == null) { +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testTryResolveCiphertext() { +// } +// +// @Test +// public void testResolveSignatureDigest() { +// +// AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); +// Random randomData = new Random(); +// +// //test ED25519 +// CryptoAlgorithm algorithm = CryptoAlgorithm.ED25519; +// +// // 测试256字节的消息进行签名 +// byte[] data = new byte[256]; +// randomData.nextBytes(data); +// SignatureFunction sf = asymmetricCrypto.getSignatureFunction(algorithm); +// CryptoKeyPair keyPair = sf.generateKeyPair(); +// +// byte[] signatureDigestBytes = sf.sign(keyPair.getPrivKey(),data).toBytes(); +// verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,signatureDigestBytes,null); +// +// //签名数据末尾两个字节丢失情况下,抛出异常 +// byte[] truncatedSignatureDigestBytes = new byte[signatureDigestBytes.length-2]; +// System.arraycopy(signatureDigestBytes,0,truncatedSignatureDigestBytes,0,truncatedSignatureDigestBytes.length); +// verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,truncatedSignatureDigestBytes,IllegalArgumentException.class); +// +// signatureDigestBytes = null; +// verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,signatureDigestBytes,NullPointerException.class); +// +// +// //test SM2 +// algorithm = CryptoAlgorithm.SM2; +// +// // 测试256字节的消息进行签名 +// data = new byte[256]; +// randomData.nextBytes(data); +// sf = asymmetricCrypto.getSignatureFunction(algorithm); +// keyPair = sf.generateKeyPair(); +// +// signatureDigestBytes = sf.sign(keyPair.getPrivKey(),data).toBytes(); +// verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,signatureDigestBytes,null); +// +// //签名数据末尾两个字节丢失情况下,抛出异常 +// truncatedSignatureDigestBytes = new byte[signatureDigestBytes.length-2]; +// System.arraycopy(signatureDigestBytes,0,truncatedSignatureDigestBytes,0,truncatedSignatureDigestBytes.length); +// verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,truncatedSignatureDigestBytes,IllegalArgumentException.class); +// +// signatureDigestBytes = null; +// verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,signatureDigestBytes,NullPointerException.class); +// +// //test JNIED25519 +// algorithm = CryptoAlgorithm.JNIED25519; +// +// // 测试256字节的消息进行签名 +// data = new byte[256]; +// randomData.nextBytes(data); +// sf = asymmetricCrypto.getSignatureFunction(algorithm); +// keyPair = sf.generateKeyPair(); +// +// signatureDigestBytes = sf.sign(keyPair.getPrivKey(),data).toBytes(); +// verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,signatureDigestBytes,null); +// +// //签名数据末尾两个字节丢失情况下,抛出异常 +// truncatedSignatureDigestBytes = new byte[signatureDigestBytes.length-2]; +// System.arraycopy(signatureDigestBytes,0,truncatedSignatureDigestBytes,0,truncatedSignatureDigestBytes.length); +// verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,truncatedSignatureDigestBytes,IllegalArgumentException.class); +// +// signatureDigestBytes = null; +// verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,signatureDigestBytes,NullPointerException.class); +// } +// +// private void verifyResolveSignatureDigest(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, +// int expectedSignatureDigestLength, +// byte[] signatureDigestBytes, Class expectedException){ +// +// //初始化一个异常 +// Exception actualEx = null; +// +// try { +// +// SignatureDigest signatureDigest = asymmetricCrypto.resolveSignatureDigest(signatureDigestBytes); +// +// assertNotNull(signatureDigest); +// +// assertEquals(algorithm,signatureDigest.getAlgorithm()); +// +// assertEquals(expectedSignatureDigestLength,signatureDigest.getRawDigest().length); +// +// assertArrayEquals(signatureDigestBytes,signatureDigest.toBytes()); +// +// } +// catch (Exception e){ +// actualEx = e; +// } +// +// if (expectedException == null) { +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testTryResolveSignatureDigest() { +// } +// +// @Test +// public void testRetrievePubKeyBytes() { +// +// AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); +// +// //test ED25519 +// CryptoAlgorithm algorithm = CryptoAlgorithm.ED25519; +// +// CryptoKeyPair keyPair = asymmetricCrypto.generateKeyPair(algorithm); +// +// byte[] expectedPrivKeyBytes = keyPair.getPrivKey().toBytes(); +// byte[] expectedPubKeyBytes = keyPair.getPubKey().toBytes(); +// +// byte[] pubKeyBytes = asymmetricCrypto.retrievePubKeyBytes(expectedPrivKeyBytes); +// +// assertArrayEquals(expectedPubKeyBytes,pubKeyBytes); +// +// +// //test SM2 +// algorithm = CryptoAlgorithm.SM2; +// +// keyPair = asymmetricCrypto.generateKeyPair(algorithm); +// +// expectedPrivKeyBytes = keyPair.getPrivKey().toBytes(); +// expectedPubKeyBytes = keyPair.getPubKey().toBytes(); +// +// pubKeyBytes = asymmetricCrypto.retrievePubKeyBytes(expectedPrivKeyBytes); +// +// assertArrayEquals(expectedPubKeyBytes,pubKeyBytes); +// +// +// //test JNIED25519 +// algorithm = CryptoAlgorithm.JNIED25519; +// +// keyPair = asymmetricCrypto.generateKeyPair(algorithm); +// +// expectedPrivKeyBytes = keyPair.getPrivKey().toBytes(); +// expectedPubKeyBytes = keyPair.getPubKey().toBytes(); +// +// pubKeyBytes = asymmetricCrypto.retrievePubKeyBytes(expectedPrivKeyBytes); +// +// assertArrayEquals(expectedPubKeyBytes,pubKeyBytes); +// +// } +// +// +// @Test +// public void testResolvePubKey() { +// +// AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); +// +// //test ED25519 +// CryptoAlgorithm algorithm = CryptoAlgorithm.ED25519; +// +// CryptoKeyPair keyPair = asymmetricCrypto.generateKeyPair(algorithm); +// +// byte[] pubKeyBytes = keyPair.getPubKey().toBytes(); +// verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytes,null); +// +// byte[] truncatedPubKeyBytes = new byte[pubKeyBytes.length-2]; +// System.arraycopy(pubKeyBytes,0,truncatedPubKeyBytes,0,truncatedPubKeyBytes.length); +// verifyResolvePubKey(asymmetricCrypto,algorithm,32,truncatedPubKeyBytes,IllegalArgumentException.class); +// +// byte[] pubKeyBytesWithWrongAlgCode = pubKeyBytes; +// pubKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// byte[] pubKeyBytesWithWrongKeyType= pubKeyBytes; +// pubKeyBytesWithWrongKeyType[1] = PRIV_KEY.CODE; +// verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytesWithWrongKeyType,IllegalArgumentException.class); +// +// pubKeyBytes = null; +// verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytes,NullPointerException.class); +// +// +// //test SM2 +// algorithm = CryptoAlgorithm.SM2; +// +// keyPair = asymmetricCrypto.generateKeyPair(algorithm); +// +// pubKeyBytes = keyPair.getPubKey().toBytes(); +// verifyResolvePubKey(asymmetricCrypto,algorithm,65,pubKeyBytes,null); +// +// truncatedPubKeyBytes = new byte[pubKeyBytes.length-2]; +// System.arraycopy(pubKeyBytes,0,truncatedPubKeyBytes,0,truncatedPubKeyBytes.length); +// verifyResolvePubKey(asymmetricCrypto,algorithm,65,truncatedPubKeyBytes,IllegalArgumentException.class); +// +// pubKeyBytesWithWrongAlgCode = pubKeyBytes; +// pubKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyResolvePubKey(asymmetricCrypto,algorithm,65,pubKeyBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// pubKeyBytesWithWrongKeyType= pubKeyBytes; +// pubKeyBytesWithWrongKeyType[1] = PRIV_KEY.CODE; +// verifyResolvePubKey(asymmetricCrypto,algorithm,65,pubKeyBytesWithWrongKeyType,IllegalArgumentException.class); +// +// pubKeyBytes = null; +// verifyResolvePubKey(asymmetricCrypto,algorithm,65,pubKeyBytes,NullPointerException.class); +// +// //test JNIED25519 +// algorithm = CryptoAlgorithm.JNIED25519; +// +// keyPair = asymmetricCrypto.generateKeyPair(algorithm); +// +// pubKeyBytes = keyPair.getPubKey().toBytes(); +// verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytes,null); +// +// truncatedPubKeyBytes = new byte[pubKeyBytes.length-2]; +// System.arraycopy(pubKeyBytes,0,truncatedPubKeyBytes,0,truncatedPubKeyBytes.length); +// verifyResolvePubKey(asymmetricCrypto,algorithm,32,truncatedPubKeyBytes,IllegalArgumentException.class); +// +// pubKeyBytesWithWrongAlgCode = pubKeyBytes; +// pubKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// pubKeyBytesWithWrongKeyType= pubKeyBytes; +// pubKeyBytesWithWrongKeyType[1] = PRIV_KEY.CODE; +// verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytesWithWrongKeyType,IllegalArgumentException.class); +// +// pubKeyBytes = null; +// verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytes,NullPointerException.class); +// } +// +// private void verifyResolvePubKey(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, +// int expectedPubKeyLength, byte[] pubKeyBytes,Class expectedException){ +// +// Exception actualEx = null; +// +// try { +// PubKey pubKey = asymmetricCrypto.resolvePubKey(pubKeyBytes); +// +// assertNotNull(pubKey); +// +// assertEquals(algorithm, pubKey.getAlgorithm()); +// +// assertEquals(expectedPubKeyLength, pubKey.getRawKeyBytes().length); +// +// assertArrayEquals(pubKeyBytes, pubKey.toBytes()); +// +// } +// catch (Exception e){ +// actualEx = e; +// } +// +// if (expectedException == null) { +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testTryResolvePubKey() { +// } +// +// @Test +// public void testResolvePrivKey() { +// +// AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); +// +// //test ED25519 +// CryptoAlgorithm algorithm = CryptoAlgorithm.ED25519; +// +// CryptoKeyPair keyPair = asymmetricCrypto.generateKeyPair(algorithm); +// +// byte[] privKeyBytes = keyPair.getPrivKey().toBytes(); +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytes,null); +// +// byte[] truncatedPrivKeyBytes = new byte[privKeyBytes.length-2]; +// System.arraycopy(privKeyBytes,0,truncatedPrivKeyBytes,0,truncatedPrivKeyBytes.length); +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,truncatedPrivKeyBytes,IllegalArgumentException.class); +// +// byte[] privKeyBytesWithWrongAlgCode = privKeyBytes; +// privKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// byte[] privKeyBytesWithWrongKeyType = privKeyBytes; +// privKeyBytesWithWrongKeyType[1] = PUB_KEY.CODE; +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytesWithWrongKeyType,IllegalArgumentException.class); +// +// privKeyBytes = null; +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytes,NullPointerException.class); +// +// +// //test SM2 +// algorithm = CryptoAlgorithm.SM2; +// +// keyPair = asymmetricCrypto.generateKeyPair(algorithm); +// +// privKeyBytes = keyPair.getPrivKey().toBytes(); +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytes,null); +// +// truncatedPrivKeyBytes = new byte[privKeyBytes.length-2]; +// System.arraycopy(privKeyBytes,0,truncatedPrivKeyBytes,0,truncatedPrivKeyBytes.length); +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,truncatedPrivKeyBytes,IllegalArgumentException.class); +// +// privKeyBytesWithWrongAlgCode = privKeyBytes; +// privKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// privKeyBytesWithWrongKeyType = privKeyBytes; +// privKeyBytesWithWrongKeyType[1] = PUB_KEY.CODE; +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytesWithWrongKeyType,IllegalArgumentException.class); +// +// privKeyBytes = null; +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytes,NullPointerException.class); +// +// //test JNIED25519 +// algorithm = CryptoAlgorithm.JNIED25519; +// +// keyPair = asymmetricCrypto.generateKeyPair(algorithm); +// +// privKeyBytes = keyPair.getPrivKey().toBytes(); +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytes,null); +// +// truncatedPrivKeyBytes = new byte[privKeyBytes.length-2]; +// System.arraycopy(privKeyBytes,0,truncatedPrivKeyBytes,0,truncatedPrivKeyBytes.length); +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,truncatedPrivKeyBytes,IllegalArgumentException.class); +// +// privKeyBytesWithWrongAlgCode = privKeyBytes; +// privKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// privKeyBytesWithWrongKeyType = privKeyBytes; +// privKeyBytesWithWrongKeyType[1] = PUB_KEY.CODE; +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytesWithWrongKeyType,IllegalArgumentException.class); +// +// privKeyBytes = null; +// verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytes,NullPointerException.class); +// } +// +// private void verifyResolvePrivKey(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, +// int expectedPrivKeyLength, byte[] privKeyBytes,Class expectedException){ +// +// Exception actualEx = null; +// +// try { +// PrivKey privKey = asymmetricCrypto.resolvePrivKey(privKeyBytes); +// +// assertNotNull(privKey); +// +// assertEquals(algorithm, privKey.getAlgorithm()); +// +// assertEquals(expectedPrivKeyLength, privKey.getRawKeyBytes().length); +// +// assertArrayEquals(privKeyBytes, privKey.toBytes()); +// +// } +// catch (Exception e){ +// actualEx = e; +// } +// +// if (expectedException == null) { +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testTryResolvePrivKey() { +// } +//} \ No newline at end of file diff --git a/source/crypto/crypto-classic/src/test/java/test/com/jd/blockchain/crypto/hash/HashCryptographyImplTest.java b/source/crypto/crypto-classic/src/test/java/test/com/jd/blockchain/crypto/hash/HashCryptographyImplTest.java new file mode 100644 index 00000000..cf7ec9b3 --- /dev/null +++ b/source/crypto/crypto-classic/src/test/java/test/com/jd/blockchain/crypto/hash/HashCryptographyImplTest.java @@ -0,0 +1,334 @@ +//package test.com.jd.blockchain.crypto.hash; +// +//import static org.junit.Assert.*; +// +//import java.util.Random; +// +//import com.jd.blockchain.crypto.smutils.hash.SM3Utils; +//import com.jd.blockchain.utils.io.BytesUtils; +//import com.jd.blockchain.utils.security.RipeMD160Utils; +//import com.jd.blockchain.utils.security.ShaUtils; +// +//import org.junit.Test; +// +//import com.jd.blockchain.crypto.CryptoAlgorithm; +//import com.jd.blockchain.crypto.CryptoUtils; +//import com.jd.blockchain.crypto.hash.HashCryptography; +//import com.jd.blockchain.crypto.hash.HashDigest; +//import com.jd.blockchain.crypto.hash.HashFunction; +//import com.jd.blockchain.crypto.impl.HashCryptographyImpl; +// +//public class HashCryptographyImplTest { +// +// @Test +// public void testGetFunction() { +// HashCryptography hashCrypto = CryptoUtils.hashCrypto(); +// Random rand = new Random(); +// // test SHA256 +// CryptoAlgorithm algorithm = CryptoAlgorithm.SHA256; +// byte[] data = new byte[256]; +// rand.nextBytes(data); +// verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); +// +// data = new byte[0]; +// verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); +// +// data = new byte[1056]; +// rand.nextBytes(data); +// verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); +// +// data = null; +// verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,NullPointerException.class); +// +// +// // test RIPEMD160 +// algorithm = CryptoAlgorithm.RIPEMD160; +// data=new byte[256]; +// rand.nextBytes(data); +// verifyGetFunction(hashCrypto, algorithm, data, 160 / 8,null); +// +// data = new byte[0]; +// verifyGetFunction(hashCrypto, algorithm, data, 160/ 8,null); +// +// data = new byte[1056]; +// rand.nextBytes(data); +// verifyGetFunction(hashCrypto, algorithm, data, 160 / 8,null); +// +// data = null; +// verifyGetFunction(hashCrypto, algorithm, data, 160 / 8,NullPointerException.class); +// +// // test SM3 +// algorithm = CryptoAlgorithm.SM3; +// data = new byte[256]; +// rand.nextBytes(data); +// verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); +// +// data = new byte[0]; +// verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); +// +// data = new byte[1056]; +// rand.nextBytes(data); +// verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); +// +// data = null; +// verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,NullPointerException.class); +// +// // test AES +// data = new byte[0]; +// algorithm = CryptoAlgorithm.AES; +// verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,IllegalArgumentException.class); +// +// // test JNISHA256 +// algorithm = CryptoAlgorithm.JNISHA256; +// data = new byte[256]; +// rand.nextBytes(data); +// verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); +// +// data = new byte[0]; +// verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); +// +// data = new byte[1056]; +// rand.nextBytes(data); +// verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); +// +// data = null; +// verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,IllegalArgumentException.class); +// +// // test JNIRIPEMD160 +// algorithm = CryptoAlgorithm.JNIRIPEMD160; +// data=new byte[256]; +// rand.nextBytes(data); +// verifyGetFunction(hashCrypto, algorithm, data, 160 / 8,null); +// +// data = new byte[0]; +// verifyGetFunction(hashCrypto, algorithm, data, 160/ 8,null); +// +// data = new byte[1056]; +// rand.nextBytes(data); +// verifyGetFunction(hashCrypto, algorithm, data, 160 / 8,null); +// +// data = null; +// verifyGetFunction(hashCrypto, algorithm, data, 160 / 8,IllegalArgumentException.class); +// } +// +// private void verifyGetFunction(HashCryptography hashCrypto, CryptoAlgorithm algorithm, byte[] data, +// int expectedRawBytes,Class expectedException) { +// Exception actualEx = null; +// try { +// HashFunction hf = hashCrypto.getFunction(algorithm); +// assertNotNull(hf); +// +// HashDigest hd = hf.hash(data); +// +// assertEquals(algorithm, hd.getAlgorithm()); +// +// assertEquals(expectedRawBytes, hd.getRawDigest().length); +// +// // verify encoding; +// byte[] encodedHash = hd.toBytes(); +// assertEquals(expectedRawBytes + 1, encodedHash.length); +// +// +// assertEquals(algorithm.CODE, encodedHash[0]); +// +// //verify equals +// assertEquals(true, hd.equals(hf.hash(data))); +// +// //verify verify +// assertTrue( hf.verify(hd, data)); +// +// } catch (Exception e) { +// actualEx = e; +// } +// +// if(expectedException==null){ +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testVerifyHashDigestByteArray() { +// HashCryptography hashCrypto = CryptoUtils.hashCrypto(); +// //test SHA256 +// byte[] data=new byte[256]; +// Random rand = new Random(); +// rand.nextBytes(data); +// CryptoAlgorithm algorithm=CryptoAlgorithm.SHA256; +// verifyHashDigestByteArray(hashCrypto,algorithm,data,null); +// data=null; +// verifyHashDigestByteArray(hashCrypto,algorithm,data,NullPointerException.class); +// +// //test RIPEMD160 +// algorithm=CryptoAlgorithm.RIPEMD160; +// data=new byte[896]; +// rand.nextBytes(data); +// verifyHashDigestByteArray(hashCrypto,algorithm,data,null); +// data=null; +// verifyHashDigestByteArray(hashCrypto,algorithm,data,NullPointerException.class); +// +// //test SM3 +// algorithm=CryptoAlgorithm.SM3; +// data=new byte[896]; +// rand.nextBytes(data); +// verifyHashDigestByteArray(hashCrypto,algorithm,data,null); +// data=null; +// verifyHashDigestByteArray(hashCrypto,algorithm,data,NullPointerException.class); +// +// +// //test AES +// algorithm=CryptoAlgorithm.AES; +// data=new byte[277]; +// rand.nextBytes(data); +// verifyHashDigestByteArray(hashCrypto,algorithm,data,IllegalArgumentException.class); +// +// //test JNISHA256 +// data=new byte[256]; +// rand = new Random(); +// rand.nextBytes(data); +// algorithm=CryptoAlgorithm.JNISHA256; +// verifyHashDigestByteArray(hashCrypto,algorithm,data,null); +// data=null; +// verifyHashDigestByteArray(hashCrypto,algorithm,data,IllegalArgumentException.class); +// +// //test JNIRIPEMD160 +// algorithm=CryptoAlgorithm.JNIRIPEMD160; +// data=new byte[896]; +// rand.nextBytes(data); +// verifyHashDigestByteArray(hashCrypto,algorithm,data,null); +// data=null; +// verifyHashDigestByteArray(hashCrypto,algorithm,data,IllegalArgumentException.class); +// } +// +// private void verifyHashDigestByteArray(HashCryptography hashCrypto,CryptoAlgorithm algorithm,byte[] data,Class expectedException){ +// Exception actualEx=null; +// try { +// HashFunction hf = hashCrypto.getFunction(algorithm); +// assertNotNull(hf); +// HashDigest hd = hf.hash(data); +// hashCrypto.verify(hd,data); +// }catch (Exception e) +// { +// actualEx=e; +// } +// if (expectedException==null) +// { +// assertNull(actualEx); +// } +// else{ +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testResolveHashDigest() { +// Random rand = new Random(); +// HashCryptography hashCrypto = CryptoUtils.hashCrypto(); +// +// //test SHA256 +// CryptoAlgorithm algorithm = CryptoAlgorithm.SHA256; +// byte[] data = new byte[256]; +// rand.nextBytes(data); +// byte[] hashDigestBytes = hashCrypto.getFunction(algorithm).hash(data).toBytes(); +// verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,32+1,null); +// +// byte[] truncatedHashDigestBytes = new byte[hashDigestBytes.length-2]; +// System.arraycopy(hashDigestBytes,0,truncatedHashDigestBytes,0,truncatedHashDigestBytes.length); +// verifyResolveHashDigest(algorithm, hashCrypto,truncatedHashDigestBytes,32+1,IllegalArgumentException.class); +// +// hashDigestBytes = null; +// verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,32+1,NullPointerException.class); +// +// +// //test RIPEMD160 +// algorithm = CryptoAlgorithm.RIPEMD160; +// data = new byte[256]; +// rand.nextBytes(data); +// hashDigestBytes = hashCrypto.getFunction(algorithm).hash(data).toBytes(); +// verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,20+1,null); +// +// truncatedHashDigestBytes = new byte[hashDigestBytes.length-2]; +// System.arraycopy(hashDigestBytes,0,truncatedHashDigestBytes,0,truncatedHashDigestBytes.length); +// verifyResolveHashDigest(algorithm, hashCrypto,truncatedHashDigestBytes,20+1,IllegalArgumentException.class); +// +// hashDigestBytes = null; +// verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,20+1,NullPointerException.class); +// +// +// //test SM3 +// algorithm = CryptoAlgorithm.SM3; +// data = new byte[256]; +// rand.nextBytes(data); +// hashDigestBytes = hashCrypto.getFunction(algorithm).hash(data).toBytes(); +// verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,32+1,null); +// +// truncatedHashDigestBytes = new byte[hashDigestBytes.length-2]; +// System.arraycopy(hashDigestBytes,0,truncatedHashDigestBytes,0,truncatedHashDigestBytes.length); +// verifyResolveHashDigest(algorithm, hashCrypto,truncatedHashDigestBytes,32+1,IllegalArgumentException.class); +// +// hashDigestBytes = null; +// verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,32+1,NullPointerException.class); +// +// +// //test JNISHA256 +// algorithm = CryptoAlgorithm.JNISHA256; +// data = new byte[256]; +// rand.nextBytes(data); +// hashDigestBytes = hashCrypto.getFunction(algorithm).hash(data).toBytes(); +// verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,32+1,null); +// +// truncatedHashDigestBytes = new byte[hashDigestBytes.length-2]; +// System.arraycopy(hashDigestBytes,0,truncatedHashDigestBytes,0,truncatedHashDigestBytes.length); +// verifyResolveHashDigest(algorithm, hashCrypto,truncatedHashDigestBytes,32+1,IllegalArgumentException.class); +// +// hashDigestBytes = null; +// verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,32+1,NullPointerException.class); +// +// //test JNIRIPEMD160 +// algorithm = CryptoAlgorithm.JNIRIPEMD160; +// data = new byte[256]; +// rand.nextBytes(data); +// hashDigestBytes = hashCrypto.getFunction(algorithm).hash(data).toBytes(); +// verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,20+1,null); +// +// truncatedHashDigestBytes = new byte[hashDigestBytes.length-2]; +// System.arraycopy(hashDigestBytes,0,truncatedHashDigestBytes,0,truncatedHashDigestBytes.length); +// verifyResolveHashDigest(algorithm, hashCrypto,truncatedHashDigestBytes,20+1,IllegalArgumentException.class); +// +// hashDigestBytes = null; +// verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,20+1,NullPointerException.class); +// } +// +// private void verifyResolveHashDigest(CryptoAlgorithm algorithm,HashCryptography +// hashCrypto,byte[] hashDigestBytes,int expectedLength,ClassexpectedException){ +// +// Exception actualEx=null; +// +// try { +// +// HashDigest hashDigest=hashCrypto.resolveHashDigest(hashDigestBytes); +// assertNotNull(hashDigest); +// assertEquals(algorithm,hashDigest.getAlgorithm()); +// byte[] algBytes = new byte[1]; +// algBytes[0] = algorithm.CODE; +// assertArrayEquals(hashDigestBytes,BytesUtils.concat(algBytes,hashDigest.getRawDigest())); +// assertEquals(expectedLength,hashDigestBytes.length); +// +// }catch (Exception e) +// { +// actualEx = e; +// } +// if (expectedException==null) +// { +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// } diff --git a/source/crypto/crypto-classic/src/test/java/test/com/jd/blockchain/crypto/symmetric/SymmetricCryptographyImplTest.java b/source/crypto/crypto-classic/src/test/java/test/com/jd/blockchain/crypto/symmetric/SymmetricCryptographyImplTest.java new file mode 100644 index 00000000..efcdd3d8 --- /dev/null +++ b/source/crypto/crypto-classic/src/test/java/test/com/jd/blockchain/crypto/symmetric/SymmetricCryptographyImplTest.java @@ -0,0 +1,471 @@ +//package test.com.jd.blockchain.crypto.symmetric; +// +//import com.jd.blockchain.crypto.Ciphertext; +//import com.jd.blockchain.crypto.CryptoAlgorithm; +//import com.jd.blockchain.crypto.SymmetricKey; +//import com.jd.blockchain.crypto.impl.SymmetricCryptographyImpl; +//import com.jd.blockchain.crypto.symmetric.SymmetricCryptography; +//import com.jd.blockchain.crypto.symmetric.SymmetricEncryptionFunction; +//import com.jd.blockchain.utils.io.BytesUtils; +// +//import org.junit.Test; +// +//import java.io.ByteArrayInputStream; +//import java.io.ByteArrayOutputStream; +//import java.io.InputStream; +//import java.io.OutputStream; +//import java.util.Random; +// +//import static com.jd.blockchain.crypto.CryptoKeyType.PRIV_KEY; +//import static com.jd.blockchain.crypto.CryptoKeyType.SYMMETRIC_KEY; +//import static org.junit.Assert.*; +// +//public class SymmetricCryptographyImplTest { +// +// @Test +// public void testGenerateKey() { +// +// SymmetricCryptography symmetricCrypto = new SymmetricCryptographyImpl(); +// +// //test AES +// CryptoAlgorithm algorithm = CryptoAlgorithm.AES; +// verifyGenerateKey(symmetricCrypto,algorithm); +// +// //test SM4 +// algorithm = CryptoAlgorithm.SM4; +// verifyGenerateKey(symmetricCrypto,algorithm); +// } +// +// private void verifyGenerateKey(SymmetricCryptography symmetricCrypto, CryptoAlgorithm algorithm){ +// +// SymmetricKey symmetricKey= symmetricCrypto.generateKey(algorithm); +// +// assertNotNull(symmetricKey); +// assertEquals(algorithm, symmetricKey.getAlgorithm()); +// assertEquals(128/8,symmetricKey.getRawKeyBytes().length); +// +// byte[] symmetricKeyBytes = symmetricKey.toBytes(); +// //判断密钥数据长度=算法标识长度+密钥掩码长度+原始密钥长度 +// assertEquals(1 + 1 + 128 / 8, symmetricKeyBytes.length); +// +// assertEquals(algorithm.CODE,symmetricKeyBytes[0]); +// assertEquals(algorithm,CryptoAlgorithm.valueOf(symmetricKeyBytes[0])); +// } +// +// @Test +// public void testGetSymmetricEncryptionFunction() { +// +// SymmetricCryptography symmetricCrypto = new SymmetricCryptographyImpl(); +// Random random = new Random(); +// +// +// //test AES +// CryptoAlgorithm algorithm = CryptoAlgorithm.AES; +// +// //Case 1: AES with 16 bytes data +// //刚好一个分组长度,随机生成明文数据 +// byte[] data = new byte[16]; +// random.nextBytes(data); +// verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 2*16, null); +// +// //Case 2: AES with 33 bytes data +// //明文长度大于两倍分组长度,生成的密文是三倍分组长度 +// data = new byte[33]; +// random.nextBytes(data); +// verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 3*16,null); +// +// //Case 3: AES with 3 bytes data +// //明文长度小于分组长度,生成的密文是一倍分组长度 +// data = new byte[3]; +// random.nextBytes(data); +// verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 16,null); +// +// //Case 4: AES with 0 bytes data +// //明文长度小于分组长度,生成的密文是一倍分组长度 +// data = new byte[0]; +// random.nextBytes(data); +// verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 16,null); +// +// //Case 5 AES with null +// //明文为空,可以捕获到异常异常 +// data = null; +// verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 16,IllegalArgumentException.class); +// +// +// //test ED25519 +// algorithm = CryptoAlgorithm.ED25519; +// data = new byte[16]; +// random.nextBytes(data); +// verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 16,IllegalArgumentException.class); +// +// +// //test SM4 +// algorithm = CryptoAlgorithm.SM4; +// +// //Case 1: SM4 with 16 bytes data +// data = new byte[16]; +// random.nextBytes(data); +// //密文长度 = IV长度 + 真实密文长度 +// verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 3*16, null); +// +// //Case 2: SM4 with 33 bytes data +// data = new byte[33]; +// random.nextBytes(data); +// verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 4*16,null); +// +// //Case 3: SM4 with 3 bytes data +// data = new byte[3]; +// random.nextBytes(data); +// verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 2*16,null); +// +// //Case 4: SM4 with 0 bytes data +// data = new byte[0]; +// random.nextBytes(data); +// verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 2*16,null); +// +// //Case 5 SM4 with null +// data = null; +// verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 16,IllegalArgumentException.class); +// } +// +// //不同明文输入下,用来简化加解密过程的method +// private void verifyGetSymmetricEncryptionFunction(SymmetricCryptography symmetricCrypto, CryptoAlgorithm algorithm, +// byte[] data, int expectedCiphertextLength, Class expectedException){ +// +// //初始化一个异常 +// Exception actualEx = null; +// +// try { +// SymmetricEncryptionFunction sef = symmetricCrypto.getSymmetricEncryptionFunction(algorithm); +// //验证获取的算法实例非空 +// assertNotNull(sef); +// +// SymmetricKey symmetricKey = (SymmetricKey) sef.generateSymmetricKey(); +// +// //验证SymmetricKey的getAlgorithm方法 +// assertEquals(algorithm, symmetricKey.getAlgorithm()); +// //验证SymmetricKey的getRawKeyBytes方法 +// assertEquals(16, symmetricKey.getRawKeyBytes().length); +// //验证SymmetricKey的toBytes方法 +// assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{SYMMETRIC_KEY.CODE},symmetricKey.getRawKeyBytes()), symmetricKey.toBytes()); +// +// +// Ciphertext ciphertext = sef.encrypt(symmetricKey,data); +// +// //Ciphertext中算法标识与入参算法一致 +// assertEquals(algorithm, ciphertext.getAlgorithm()); +// //验证原始密文长度与预期长度一致 +// assertEquals(expectedCiphertextLength, ciphertext.getRawCiphertext().length); +// //验证密文数据长度=算法标识长度+预期长度 +// byte[] ciphertextBytes = ciphertext.toBytes(); +// assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},ciphertext.getRawCiphertext()), ciphertextBytes); +// +// +// //验证equal +// assertTrue(ciphertext.equals(ciphertext)); +// assertEquals(ciphertext.hashCode(),ciphertext.hashCode()); +// +// //验证SymmetricEncryptionFunction的decrypt +// assertArrayEquals(data, sef.decrypt(symmetricKey,ciphertext)); +// +// //测试SymmetricEncryptionFunction的输入输出流的加解密方法 +// InputStream inPlaintext = new ByteArrayInputStream(data); +// //16字节的明文输入,将会产生32字节的密文 +// OutputStream outCiphertext = new ByteArrayOutputStream(ciphertext.toBytes().length); +// InputStream inCiphertext = new ByteArrayInputStream(ciphertext.toBytes()); +// OutputStream outPlaintext = new ByteArrayOutputStream(data.length); +// sef.encrypt(symmetricKey, inPlaintext, outCiphertext); +// sef.decrypt(symmetricKey, inCiphertext, outPlaintext); +// +// //验证SymmetricEncryptionFunction的supportCiphertext方法 +// assertTrue(sef.supportCiphertext(ciphertextBytes)); +// +// //验证SymmetricEncryptionFunction的resolveCiphertext方法 +// assertEquals(ciphertext, sef.resolveCiphertext(ciphertextBytes)); +// +// //验证SymmetricEncryptionFunction的supportSymmetricKey方法 +// assertTrue(sef.supportSymmetricKey(symmetricKey.toBytes())); +// +// //验证SymmetricEncryptionFunction的resolveSymmetricKey方法 +// assertEquals(symmetricKey, sef.resolveSymmetricKey(symmetricKey.toBytes())); +// +// //验证SymmetricEncryptionFunction的getAlgorithm +// assertEquals(algorithm, sef.getAlgorithm()); +// +// } catch (Exception e){ +// actualEx = e; +// } +// +// if (expectedException == null) { +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testDecrypt() { +// +// SymmetricCryptography symmetricCrypto = new SymmetricCryptographyImpl(); +// Random randomData = new Random(); +// Random randomKey = new Random(); +// +// +// //test AES +// CryptoAlgorithm algorithm = CryptoAlgorithm.AES; +// SymmetricEncryptionFunction sef = symmetricCrypto.getSymmetricEncryptionFunction(algorithm); +// +// byte[] data = new byte[16]; +// randomData.nextBytes(data); +// byte[] key = new byte[16]; +// randomKey.nextBytes(key); +// +// SymmetricKey symmetricKey = new SymmetricKey(algorithm, key); +// byte[] ciphertextBytes = sef.encrypt(symmetricKey,data).toBytes(); +// +// verifyDecrypt(symmetricCrypto, algorithm, key, data, ciphertextBytes, null); +// +// //密钥的算法标识与密文的算法标识不一致情况 +// verifyDecrypt(symmetricCrypto, CryptoAlgorithm.SM4, key, data, ciphertextBytes, IllegalArgumentException.class); +// +// //密文末尾两个字节丢失情况下,抛出异常 +// byte[] truncatedCiphertextBytes = new byte[ciphertextBytes.length-2]; +// System.arraycopy(ciphertextBytes,0,truncatedCiphertextBytes,0,truncatedCiphertextBytes.length); +// verifyDecrypt(symmetricCrypto, algorithm, key, data, truncatedCiphertextBytes, IllegalArgumentException.class); +// +// byte[] ciphertextBytesWithWrongAlgCode = ciphertextBytes; +// ciphertextBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyDecrypt(symmetricCrypto,algorithm,key,data,ciphertextBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// ciphertextBytes = null; +// verifyDecrypt(symmetricCrypto,algorithm,key,data,ciphertextBytes,NullPointerException.class); +// +// +// //test SM4 +// algorithm = CryptoAlgorithm.SM4; +// sef = symmetricCrypto.getSymmetricEncryptionFunction(algorithm); +// symmetricKey = new SymmetricKey(algorithm, key); +// ciphertextBytes = sef.encrypt(symmetricKey,data).toBytes(); +// +// verifyDecrypt(symmetricCrypto, algorithm, key, data, ciphertextBytes, null); +// +// //密钥的算法标识与密文的算法标识不一致情况 +// verifyDecrypt(symmetricCrypto, CryptoAlgorithm.AES, key, data, ciphertextBytes, IllegalArgumentException.class); +// +// //密文末尾两个字节丢失情况下,抛出异常 +// truncatedCiphertextBytes = new byte[ciphertextBytes.length-2]; +// System.arraycopy(ciphertextBytes,0,truncatedCiphertextBytes,0,truncatedCiphertextBytes.length); +// verifyDecrypt(symmetricCrypto, algorithm, key, data, truncatedCiphertextBytes, IllegalArgumentException.class); +// +// ciphertextBytesWithWrongAlgCode = ciphertextBytes; +// ciphertextBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyDecrypt(symmetricCrypto,algorithm,key,data,ciphertextBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// ciphertextBytes = null; +// verifyDecrypt(symmetricCrypto,algorithm,key,data,ciphertextBytes,NullPointerException.class); +// } +// +// private void verifyDecrypt(SymmetricCryptography symmetricCrypto, CryptoAlgorithm algorithm, +// byte[] key, byte[] data, byte[] ciphertextBytes, Class expectedException) { +// +// Exception actualEx = null; +// +// try { +// SymmetricKey symmetricKey = new SymmetricKey(algorithm,key); +// +// byte[] plaintext = symmetricCrypto.decrypt(symmetricKey.toBytes(), ciphertextBytes); +// +// //解密后的明文与初始的明文一致 +// assertArrayEquals(data,plaintext); +// } +// catch (Exception e){ +// actualEx = e; +// } +// +// if (expectedException == null) { +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testResolveCiphertext() { +// +// SymmetricCryptography symmetricCrypto = new SymmetricCryptographyImpl(); +// Random randomData = new Random(); +// Random randomKey = new Random(); +// +// //test AES +// CryptoAlgorithm algorithm = CryptoAlgorithm.AES; +// SymmetricEncryptionFunction sef = symmetricCrypto.getSymmetricEncryptionFunction(algorithm); +// +// byte[] data = new byte[16]; +// randomData.nextBytes(data); +// byte[] key = new byte[16]; +// randomKey.nextBytes(key); +// +// SymmetricKey symmetricKey = new SymmetricKey(algorithm, key); +// byte[] ciphertextBytes = sef.encrypt(symmetricKey,data).toBytes(); +// verifyResolveCiphertext(symmetricCrypto, algorithm, ciphertextBytes, null); +// +// byte[] truncatedCiphertextBytes = new byte[ciphertextBytes.length-2]; +// System.arraycopy(ciphertextBytes,0,truncatedCiphertextBytes,0,truncatedCiphertextBytes.length); +// verifyResolveCiphertext(symmetricCrypto,algorithm,truncatedCiphertextBytes,IllegalArgumentException.class); +// +// byte[] ciphertextBytesWithWrongAlgCode = ciphertextBytes; +// ciphertextBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyResolveCiphertext(symmetricCrypto,algorithm,ciphertextBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// ciphertextBytes = null; +// verifyResolveCiphertext(symmetricCrypto,algorithm,ciphertextBytes,NullPointerException.class); +// +// +// //test SM4 +// algorithm = CryptoAlgorithm.SM4; +// sef = symmetricCrypto.getSymmetricEncryptionFunction(algorithm); +// +// symmetricKey = new SymmetricKey(algorithm, key); +// ciphertextBytes = sef.encrypt(symmetricKey,data).toBytes(); +// +// verifyResolveCiphertext(symmetricCrypto, algorithm, ciphertextBytes, null); +// +// truncatedCiphertextBytes = new byte[ciphertextBytes.length-2]; +// System.arraycopy(ciphertextBytes,0,truncatedCiphertextBytes,0,truncatedCiphertextBytes.length); +// verifyResolveCiphertext(symmetricCrypto,algorithm,truncatedCiphertextBytes,IllegalArgumentException.class); +// +// ciphertextBytesWithWrongAlgCode = ciphertextBytes; +// ciphertextBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyResolveCiphertext(symmetricCrypto,algorithm,ciphertextBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// ciphertextBytes = null; +// verifyResolveCiphertext(symmetricCrypto,algorithm,ciphertextBytes,NullPointerException.class); +// } +// +// private void verifyResolveCiphertext(SymmetricCryptography symmetricCrypto, CryptoAlgorithm algorithm, byte[] ciphertextBytes, +// Class expectedException) { +// +// Exception actualEx = null; +// +// try { +// Ciphertext ciphertext = symmetricCrypto.resolveCiphertext(ciphertextBytes); +// +// assertNotNull(ciphertext); +// +// assertEquals(algorithm, ciphertext.getAlgorithm()); +// +// assertEquals(0, ciphertext.getRawCiphertext().length % 16); +// +// assertArrayEquals(ciphertextBytes, ciphertext.toBytes()); +// } +// catch (Exception e){ +// actualEx = e; +// } +// +// if (expectedException == null) { +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testTryResolveCiphertext() { +// } +// +// +// +// @Test +// public void testResolveSymmetricKey() { +// +// SymmetricCryptography symmetricCrypto = new SymmetricCryptographyImpl(); +// +// //test AES +// CryptoAlgorithm algorithm = CryptoAlgorithm.AES; +// +// Random randomKey = new Random(); +// byte[] key = new byte[16]; +// randomKey.nextBytes(key); +// +// byte[] symmetricKeyBytes = BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{SYMMETRIC_KEY.CODE},key); +// verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytes,null); +// +// byte[] truncatedSymmetricKeyBytes = new byte[symmetricKeyBytes.length-2]; +// System.arraycopy(symmetricKeyBytes,0,truncatedSymmetricKeyBytes,0,truncatedSymmetricKeyBytes.length); +// verifyResolveSymmetricKey(symmetricCrypto,algorithm,truncatedSymmetricKeyBytes,IllegalArgumentException.class); +// +// byte[] symmetricKeyBytesWithWrongAlgCode = symmetricKeyBytes; +// symmetricKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// byte[] symmetricKeyBytesWithWrongKeyType= symmetricKeyBytes; +// System.arraycopy(symmetricKeyBytes,0,symmetricKeyBytesWithWrongKeyType,0,symmetricKeyBytesWithWrongKeyType.length); +// symmetricKeyBytesWithWrongKeyType[1] = PRIV_KEY.CODE; +// verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytesWithWrongKeyType,IllegalArgumentException.class); +// +// symmetricKeyBytes = null; +// verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytes,NullPointerException.class); +// +// +// //test SM4 +// algorithm = CryptoAlgorithm.SM4; +// symmetricKeyBytes = BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{SYMMETRIC_KEY.CODE},key); +// +// verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytes,null); +// +// truncatedSymmetricKeyBytes = new byte[symmetricKeyBytes.length-2]; +// System.arraycopy(symmetricKeyBytes,0,truncatedSymmetricKeyBytes,0,truncatedSymmetricKeyBytes.length); +// verifyResolveSymmetricKey(symmetricCrypto,algorithm,truncatedSymmetricKeyBytes,IllegalArgumentException.class); +// +// symmetricKeyBytesWithWrongAlgCode = symmetricKeyBytes; +// symmetricKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; +// verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytesWithWrongAlgCode,IllegalArgumentException.class); +// +// symmetricKeyBytesWithWrongKeyType= symmetricKeyBytes; +// System.arraycopy(symmetricKeyBytes,0,symmetricKeyBytesWithWrongKeyType,0,symmetricKeyBytesWithWrongKeyType.length); +// symmetricKeyBytesWithWrongKeyType[1] = PRIV_KEY.CODE; +// verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytesWithWrongKeyType,IllegalArgumentException.class); +// +// symmetricKeyBytes = null; +// verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytes,NullPointerException.class); +// } +// +// private void verifyResolveSymmetricKey(SymmetricCryptography symmetricCrypto, CryptoAlgorithm algorithm, byte[] symmetricKeyBytes, +// Class expectedException) { +// +// Exception actualEx = null; +// +// try { +// SymmetricKey symmetricKey = symmetricCrypto.resolveSymmetricKey(symmetricKeyBytes); +// +// assertNotNull(symmetricKey); +// +// assertEquals(algorithm, symmetricKey.getAlgorithm()); +// +// assertEquals(16, symmetricKey.getRawKeyBytes().length); +// +// assertArrayEquals(symmetricKeyBytes, symmetricKey.toBytes()); +// } +// catch (Exception e){ +// actualEx = e; +// } +// +// if (expectedException == null) { +// assertNull(actualEx); +// } +// else { +// assertNotNull(actualEx); +// assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); +// } +// } +// +// @Test +// public void testTryResolveSymmetricKey() { +// } +//} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/AddressEncoding.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/AddressEncoding.java index bc4a08a8..77db928b 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/AddressEncoding.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/AddressEncoding.java @@ -5,7 +5,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.io.BytesEncoding; import com.jd.blockchain.utils.io.BytesUtils; @@ -56,10 +55,10 @@ public class AddressEncoding { public static Bytes generateAddress(PubKey pubKey) { byte[] h1Bytes = ShaUtils.hash_256(pubKey.getRawKeyBytes()); byte[] h2Bytes = RipeMD160Utils.hash(h1Bytes); - byte[] xBytes = BytesUtils.concat(new byte[]{AddressVersion.V1.CODE, pubKey.getAlgorithm().CODE}, h2Bytes); + byte[] xBytes = BytesUtils.concat(new byte[] { AddressVersion.V1.CODE}, CryptoAlgorithm.toBytes(pubKey.getAlgorithm()), h2Bytes); byte[] checksum = Arrays.copyOf(ShaUtils.hash_256(ShaUtils.hash_256(xBytes)), 4); byte[] addressBytes = BytesUtils.concat(xBytes, checksum); - + return new Bytes(addressBytes); } diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/AddressVersion.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/AddressVersion.java index dff8877b..5e741abc 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/AddressVersion.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/AddressVersion.java @@ -1,9 +1,20 @@ package com.jd.blockchain.crypto; +/** + * The version of Blockchain Address generation rule;
+ * + * + * + * @author huanghaiquan + * + */ public enum AddressVersion { V1((byte) 0x91); + // Note: Implementor can only add new enum items, cann't remove or modify + // existing enum items; + public final byte CODE; AddressVersion(byte code) { diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/base/BaseCryptoBytes.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/BaseCryptoBytes.java similarity index 71% rename from source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/base/BaseCryptoBytes.java rename to source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/BaseCryptoBytes.java index 3fc2ac70..6ddfc545 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/base/BaseCryptoBytes.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/BaseCryptoBytes.java @@ -1,9 +1,7 @@ -package com.jd.blockchain.crypto.base; +package com.jd.blockchain.crypto; import java.util.Arrays; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.CryptoBytes; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.io.BytesSlice; import com.jd.blockchain.utils.io.BytesUtils; @@ -25,17 +23,22 @@ public abstract class BaseCryptoBytes extends Bytes implements CryptoBytes { super(cryptoBytes); CryptoAlgorithm algorithm = decodeAlgorithm(cryptoBytes); if (!support(algorithm)) { - throw new IllegalArgumentException("Not supported algorithm[" + algorithm.toString() + "]!"); + throw new CryptoException("Not supported algorithm[" + algorithm.toString() + "]!"); } this.algorithm = algorithm; } static byte[] encodeBytes(CryptoAlgorithm algorithm, byte[] rawCryptoBytes) { - return BytesUtils.concat(new byte[] { algorithm.CODE }, rawCryptoBytes); + return BytesUtils.concat(CryptoAlgorithm.toBytes(algorithm), rawCryptoBytes); } static CryptoAlgorithm decodeAlgorithm(byte[] cryptoBytes) { - return CryptoAlgorithm.valueOf(cryptoBytes[0]); + short algorithmCode = BytesUtils.toShort(cryptoBytes, 0); + CryptoAlgorithm algorithm = CryptoServiceProviders.getAlgorithm(algorithmCode); + if (algorithm == null) { + throw new CryptoException("The algorithm with code[" + algorithmCode + "] is not supported!"); + } + return algorithm; } protected abstract boolean support(CryptoAlgorithm algorithm); @@ -44,6 +47,7 @@ public abstract class BaseCryptoBytes extends Bytes implements CryptoBytes { return Arrays.copyOfRange(cryptoBytes, 1, cryptoBytes.length); } + @Override public CryptoAlgorithm getAlgorithm() { // return resolveAlgorithm(encodedBytes); return algorithm; diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/BaseCryptoKey.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/BaseCryptoKey.java new file mode 100644 index 00000000..db95dbd4 --- /dev/null +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/BaseCryptoKey.java @@ -0,0 +1,46 @@ +package com.jd.blockchain.crypto; + +import java.io.Serializable; + +import com.jd.blockchain.utils.io.BytesSlice; +import com.jd.blockchain.utils.io.BytesUtils; + +public abstract class BaseCryptoKey extends BaseCryptoBytes implements CryptoKey, Serializable { + + public static final int KEY_TYPE_BYTES = 1; + private static final long serialVersionUID = 4543074827807908363L; + +// public BaseCryptoKey() { +// super(); +// } + + protected BaseCryptoKey(CryptoAlgorithm algorithm, byte[] rawKeyBytes, CryptoKeyType keyType) { + super(algorithm, encodeKeyBytes(rawKeyBytes, keyType)); + } + + public BaseCryptoKey(byte[] cryptoBytes) { + super(cryptoBytes); + CryptoKeyType keyType = decodeKeyType(getRawCryptoBytes()); + if (getKeyType() != keyType) { + throw new CryptoException("CryptoKey doesn't support keyType[" + keyType + "]!"); + } + } + + private static byte[] encodeKeyBytes(byte[] rawKeyBytes, CryptoKeyType keyType) { + return BytesUtils.concat(new byte[] { keyType.CODE }, rawKeyBytes); + } + + private static CryptoKeyType decodeKeyType(BytesSlice cryptoBytes) { + return CryptoKeyType.valueOf(cryptoBytes.getByte()); + } + + @Override + protected boolean support(CryptoAlgorithm algorithm) { + return CryptoAlgorithm.hasAsymmetricKey(algorithm) || CryptoAlgorithm.hasSymmetricKey(algorithm); + } + + @Override + public byte[] getRawKeyBytes() { + return getRawCryptoBytes().getBytesCopy(1); + } +} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithm.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithm.java index 33a9c562..5837590b 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithm.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithm.java @@ -1,80 +1,110 @@ package com.jd.blockchain.crypto; -import com.jd.blockchain.base.data.TypeCodes; -import com.jd.blockchain.binaryproto.EnumContract; -import com.jd.blockchain.binaryproto.EnumField; +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.ValueType; +import com.jd.blockchain.utils.io.BytesUtils; +@DataContract(code = TypeCodes.CRYPTO_ALGORITHM) +public interface CryptoAlgorithm { -@EnumContract(code= TypeCodes.ENUM_TYPE_CRYPTO_ALGORITHM) -public enum CryptoAlgorithm { - - SHA256(CryptoAlgorithmType.HASH, (byte) 0x01, false, false), - - RIPEMD160(CryptoAlgorithmType.HASH, (byte) 0x02, false, false), - - SM3(CryptoAlgorithmType.HASH, (byte) 0x03, false, false), - - JNISHA256(CryptoAlgorithmType.HASH, (byte) 0x04, false, false), - - JNIRIPEMD160(CryptoAlgorithmType.HASH, (byte) 0x05, false, false), + /** + * 随机数算法标识; + */ + static final int RANDOM_ALGORITHM = 0x1000; - // 非对称签名/加密算法; + /** + * 哈希数算法标识; + */ + static final int HASH_ALGORITHM = 0x2000; /** - * RSA 签名算法;可签名,可加密; + * 签名算法标识; */ - RSA(CryptoAlgorithmType.ASYMMETRIC, (byte) 0x01, true, true), + static final int SIGNATURE_ALGORITHM = 0x4000; /** - * ED25519 签名算法;只用于签名,没有加密特性; + * 加密算法标识; */ - ED25519(CryptoAlgorithmType.ASYMMETRIC, (byte) 0x02, true, false), + static final int ENCRYPTION_ALGORITHM = 0x8000; /** - * ECDSA 签名算法;只用于签名,没有加密特性; + * 扩展密码算法标识;
+ * 表示除了 + * {@link #RANDOM_ALGORITHM}、{@link #HASH_ALGORITHM}、{@link #SIGNATURE_ALGORITHM}、{@link #ENCRYPTION_ALGORITHM} + * 之外的其它非标准分类的密码算法,诸如加法同态算法、多方求和算法等; */ - ECDSA(CryptoAlgorithmType.ASYMMETRIC, (byte) 0x03, true, false), + static final int EXT_ALGORITHM = 0x0000; /** - * 国密 SM2 算法;可签名,可加密; + * 非对称密钥标识; */ - SM2(CryptoAlgorithmType.ASYMMETRIC, (byte) 0x04, true, true), + static final int ASYMMETRIC_KEY = 0x0100; /** - * JNIED25519 签名算法;只用于签名,没有加密特性; + * 对称密钥标识; */ - JNIED25519(CryptoAlgorithmType.ASYMMETRIC, (byte) 0x05, true, false), + static final int SYMMETRIC_KEY = 0x0200; - // 对称加密; /** - * AES 算法;可加密; + * 算法编码的字节长度;等同于 {@link #toBytes(CryptoAlgorithm)} 返回的字节数组的长度; */ - AES(CryptoAlgorithmType.SYMMETRIC, (byte) 0x01, false, true), + static final int CODE_SIZE = 2; - SM4(CryptoAlgorithmType.SYMMETRIC, (byte) 0x02, false, true), + /** + * 密码算法的唯一编码; + *

+ * 长度16位,高4位标识算法类型(包括: {@link #RANDOM_ALGORITHM}, {@link #HASH_ALGORITHM}, + * {@link #SIGNATURE_ALGORITHM}, {@link #ENCRYPTION_ALGORITHM}, + * {@link #EXT_ALGORITHM}) 5 种); 接下来4位标识密钥类型(包括:{@link #SYMMETRIC_KEY}, + * {@link #ASYMMETRIC_KEY}); 最后8位是算法唯一ID; + */ + @DataField(primitiveType = ValueType.INT16, order = 0) + short code(); - // 随机性; /** - * 随机数算法,待定; + * 算法名称; + *

+ * + * 实现者应该遵循“英文字符大写”的命名规范,并确保唯一性;
+ * 例如,sha256 和 SHA256 将被视为相同的名称; + * + * @return */ - JAVA_SECURE(CryptoAlgorithmType.RANDOM, (byte) 0x01, false, false); + String name(); /** - * 密码算法的代号;
- * 注:只占16位; + * + * @return */ - @EnumField(type= ValueType.INT8) - public final byte CODE; + static byte[] toBytes(CryptoAlgorithm algorithm) { + return BytesUtils.toBytes(algorithm.code()); + } - private final boolean signable; + static short resolveCode(byte[] algorithmBytes) { + return BytesUtils.toShort(algorithmBytes, 0); + } - private final boolean encryptable; + static short resolveCode(byte[] algorithmBytes, int offset) { + return BytesUtils.toShort(algorithmBytes, offset); + } + + static boolean match(CryptoAlgorithm algorithm, byte[] algorithmBytes) { + return algorithm.code() == BytesUtils.toShort(algorithmBytes, 0); + } - private CryptoAlgorithm(byte algType, byte algId, boolean signable, boolean encryptable) { - this.CODE = (byte) (algType | algId); - this.signable = signable; - this.encryptable = encryptable; + static boolean match(CryptoAlgorithm algorithm, byte[] algorithmBytes, int offset) { + return algorithm.code() == BytesUtils.toShort(algorithmBytes, offset); + } + + /** + * 是否属于随机数算法; + * + * @return + */ + static boolean isRandomAlgorithm(CryptoAlgorithm algorithm) { + return RANDOM_ALGORITHM == (algorithm.code() & RANDOM_ALGORITHM); } /** @@ -82,69 +112,73 @@ public enum CryptoAlgorithm { * * @return */ - public boolean isHash() { - return (CODE & CryptoAlgorithmType.HASH) == CryptoAlgorithmType.HASH; + static boolean isHashAlgorithm(CryptoAlgorithm algorithm) { + return HASH_ALGORITHM == (algorithm.code() & HASH_ALGORITHM); } /** - * 是否属于非对称密码算法; + * 是否属于签名算法; * * @return */ - public boolean isAsymmetric() { - return (CODE & CryptoAlgorithmType.ASYMMETRIC) == CryptoAlgorithmType.ASYMMETRIC; + static boolean isSignatureAlgorithm(CryptoAlgorithm algorithm) { + return SIGNATURE_ALGORITHM == (algorithm.code() & SIGNATURE_ALGORITHM); } /** - * 是否属于对称密码算法; + * 是否属于加密算法; * * @return */ - public boolean isSymmetric() { - return (CODE & CryptoAlgorithmType.SYMMETRIC) == CryptoAlgorithmType.SYMMETRIC; + static boolean isEncryptionAlgorithm(CryptoAlgorithm algorithm) { + return ENCRYPTION_ALGORITHM == (algorithm.code() & ENCRYPTION_ALGORITHM); } /** - * 是否属于随机数算法; + * 是否属于扩展密码算法; * * @return */ - public boolean isRandom() { - return (CODE & CryptoAlgorithmType.RANDOM) == CryptoAlgorithmType.RANDOM; + static boolean isExtAlgorithm(CryptoAlgorithm algorithm) { + return EXT_ALGORITHM == (algorithm.code() & 0xF000); } /** - * 是否支持签名操作; + * 算法是否包含非对称密钥; * * @return */ - public boolean isSignable() { - return signable; + static boolean hasAsymmetricKey(CryptoAlgorithm algorithm) { + return ASYMMETRIC_KEY == (algorithm.code() & ASYMMETRIC_KEY); } /** - * 是否支持加密操作; + * 算法是否包含对称密钥; * * @return */ - public boolean isEncryptable() { - return encryptable; + static boolean hasSymmetricKey(CryptoAlgorithm algorithm) { + return SYMMETRIC_KEY == (algorithm.code() & SYMMETRIC_KEY); } /** - * 返回指定编码对应的枚举实例;
+ * 是否属于对称加密算法; * - * 如果不存在,则返回 null; + * @param algorithm + * @return + */ + static boolean isSymmetricEncryptionAlgorithm(CryptoAlgorithm algorithm) { + return isEncryptionAlgorithm(algorithm) && hasSymmetricKey(algorithm); + } + + /** + * 是否属于非对称加密算法; * - * @param code + * @param algorithm * @return */ - public static CryptoAlgorithm valueOf(byte code) { - for (CryptoAlgorithm alg : CryptoAlgorithm.values()) { - if (alg.CODE == code) { - return alg; - } - } - throw new IllegalArgumentException("CryptoAlgorithm doesn't support enum code[" + code + "]!"); + static boolean isAsymmetricEncryptionAlgorithm(CryptoAlgorithm algorithm) { + return isEncryptionAlgorithm(algorithm) && hasAsymmetricKey(algorithm); } + } diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithmDefinition.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithmDefinition.java new file mode 100644 index 00000000..642a27d0 --- /dev/null +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithmDefinition.java @@ -0,0 +1,118 @@ +package com.jd.blockchain.crypto; + +public final class CryptoAlgorithmDefinition implements CryptoAlgorithm { + + private short code; + + private String name; + + CryptoAlgorithmDefinition(String name, short code) { + this.code = code; + this.name = name; + } + + @Override + public short code() { + return this.code; + } + + @Override + public String name() { + return name; + } + + @Override + public String toString() { + return name + "[" + code + "]"; + } + + /** + * 声明一项哈希算法; + * + * @param name + * 算法名称; + * @param uid + * 算法ID;需要在同类算法中保持唯一性; + * @return + */ + public static CryptoAlgorithm defineHash(String name, byte uid) { + short code = (short) (HASH_ALGORITHM | (uid & 0x00FF)); + return new CryptoAlgorithmDefinition(name, code); + } + + /** + * 声明一项非对称密码算法; + * + * @param name + * 算法名称; + * @param uid + * 算法ID;需要在同类算法中保持唯一性; + * @return + */ + public static CryptoAlgorithm defineSignature(String name, boolean encryptable, byte uid) { + short code; + if (encryptable) { + code = (short) (SIGNATURE_ALGORITHM | ENCRYPTION_ALGORITHM | ASYMMETRIC_KEY | (uid & 0x00FF)); + } else { + code = (short) (SIGNATURE_ALGORITHM | ASYMMETRIC_KEY | (uid & 0x00FF)); + } + return new CryptoAlgorithmDefinition(name, code); + } + + /** + * 声明一项非对称加密算法; + * + * @param name + * 算法名称; + * @param uid + * 算法ID;需要在同类算法中保持唯一性; + * @return + */ + public static CryptoAlgorithm defineAsymmetricEncryption(String name, byte uid) { + short code = (short) (ENCRYPTION_ALGORITHM | ASYMMETRIC_KEY | (uid & 0x00FF)); + return new CryptoAlgorithmDefinition(name, code); + } + + /** + * 声明一项对称密码算法; + * + * @param name + * 算法名称; + * @param uid + * 算法ID;需要在同类算法中保持唯一性; + * @return + */ + public static CryptoAlgorithm defineSymmetricEncryption(String name, byte uid) { + short code = (short) (ENCRYPTION_ALGORITHM | SYMMETRIC_KEY | (uid & 0x00FF)); + return new CryptoAlgorithmDefinition(name, code); + } + + /** + * 声明一项随机数算法; + * + * @param name + * 算法名称; + * @param uid + * 算法ID;需要在同类算法中保持唯一性; + * @return + */ + public static CryptoAlgorithm defineRandom(String name, byte uid) { + short code = (short) (RANDOM_ALGORITHM | (uid & 0x00FF)); + return new CryptoAlgorithmDefinition(name, code); + } + + /** + * 声明一项扩展的密码算法; + * + * @param name + * 算法名称; + * @param uid + * 算法ID;需要在同类算法中保持唯一性; + * @return + */ + public static CryptoAlgorithm definExt(String name, byte uid) { + short code = (short) (EXT_ALGORITHM | (uid & 0x00FF)); + return new CryptoAlgorithmDefinition(name, code); + } + +} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithmType.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithmType.java index 10e38f77..35cb66ec 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithmType.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithmType.java @@ -1,24 +1,24 @@ -package com.jd.blockchain.crypto; - -public class CryptoAlgorithmType { - /** - * Hash 类算法的掩码; - */ - public static final byte HASH = 0x10; - - /** - * 非对称加密类算法的掩码; - */ - public static final byte ASYMMETRIC = 0x20; - - /** - * 对称加密类算法的掩码; - */ - public static final byte SYMMETRIC = 0x30; - - /** - * 随机数类算法的掩码; - */ - public static final byte RANDOM = 0x40; - -} +//package com.jd.blockchain.crypto; +// +//public class CryptoAlgorithmType { +// /** +// * Hash 类算法的掩码; +// */ +// public static final byte HASH = 0x10; +// +// /** +// * 非对称加密类算法的掩码; +// */ +// public static final byte ASYMMETRIC = 0x20; +// +// /** +// * 对称加密类算法的掩码; +// */ +// public static final byte SYMMETRIC = 0x30; +// +// /** +// * 随机数类算法的掩码; +// */ +// public static final byte RANDOM = 0x40; +// +//} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithm_Enum.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithm_Enum.java new file mode 100644 index 00000000..c9209b03 --- /dev/null +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoAlgorithm_Enum.java @@ -0,0 +1,150 @@ +//package com.jd.blockchain.crypto; +// +//import com.jd.blockchain.base.data.TypeCodes; +//import com.jd.blockchain.binaryproto.EnumContract; +//import com.jd.blockchain.binaryproto.EnumField; +//import com.jd.blockchain.utils.ValueType; +// +// +//@EnumContract(code= TypeCodes.CRYPTO_ALGORITHM) +//public enum CryptoAlgorithm_Enum { +// +// SHA256(CryptoAlgorithmType.HASH, (byte) 0x01, false, false), +// +// RIPEMD160(CryptoAlgorithmType.HASH, (byte) 0x02, false, false), +// +// SM3(CryptoAlgorithmType.HASH, (byte) 0x03, false, false), +// +// JNISHA256(CryptoAlgorithmType.HASH, (byte) 0x04, false, false), +// +// JNIRIPEMD160(CryptoAlgorithmType.HASH, (byte) 0x05, false, false), +// +// // 非对称签名/加密算法; +// +// /** +// * RSA 签名算法;可签名,可加密; +// */ +// RSA(CryptoAlgorithmType.ASYMMETRIC, (byte) 0x01, true, true), +// +// /** +// * ED25519 签名算法;只用于签名,没有加密特性; +// */ +// ED25519(CryptoAlgorithmType.ASYMMETRIC, (byte) 0x02, true, false), +// +// /** +// * ECDSA 签名算法;只用于签名,没有加密特性; +// */ +// ECDSA(CryptoAlgorithmType.ASYMMETRIC, (byte) 0x03, true, false), +// +// /** +// * 国密 SM2 算法;可签名,可加密; +// */ +// SM2(CryptoAlgorithmType.ASYMMETRIC, (byte) 0x04, true, true), +// +// /** +// * JNIED25519 签名算法;只用于签名,没有加密特性; +// */ +// JNIED25519(CryptoAlgorithmType.ASYMMETRIC, (byte) 0x05, true, false), +// +// // 对称加密; +// /** +// * AES 算法;可加密; +// */ +// AES(CryptoAlgorithmType.SYMMETRIC, (byte) 0x01, false, true), +// +// SM4(CryptoAlgorithmType.SYMMETRIC, (byte) 0x02, false, true), +// +// // 随机性; +// /** +// * 随机数算法,待定; +// */ +// JAVA_SECURE(CryptoAlgorithmType.RANDOM, (byte) 0x01, false, false); +// +// /** +// * 密码算法的代号;
+// * 注:只占16位; +// */ +// @EnumField(type= ValueType.INT8) +// public final byte CODE; +// +// private final boolean signable; +// +// private final boolean encryptable; +// +// private CryptoAlgorithm_Enum(byte algType, byte algId, boolean signable, boolean encryptable) { +// this.CODE = (byte) (algType | algId); +// this.signable = signable; +// this.encryptable = encryptable; +// } +// +// /** +// * 是否属于摘要算法; +// * +// * @return +// */ +// public boolean isHash() { +// return (CODE & CryptoAlgorithmType.HASH) == CryptoAlgorithmType.HASH; +// } +// +// /** +// * 是否属于非对称密码算法; +// * +// * @return +// */ +// public boolean isAsymmetric() { +// return (CODE & CryptoAlgorithmType.ASYMMETRIC) == CryptoAlgorithmType.ASYMMETRIC; +// } +// +// /** +// * 是否属于对称密码算法; +// * +// * @return +// */ +// public boolean isSymmetric() { +// return (CODE & CryptoAlgorithmType.SYMMETRIC) == CryptoAlgorithmType.SYMMETRIC; +// } +// +// /** +// * 是否属于随机数算法; +// * +// * @return +// */ +// public boolean isRandom() { +// return (CODE & CryptoAlgorithmType.RANDOM) == CryptoAlgorithmType.RANDOM; +// } +// +// /** +// * 是否支持签名操作; +// * +// * @return +// */ +// public boolean isSignable() { +// return signable; +// } +// +// /** +// * 是否支持加密操作; +// * +// * @return +// */ +// public boolean isEncryptable() { +// return encryptable; +// } +// +// /** +// * 返回指定编码对应的枚举实例;
+// * +// * 如果不存在,则返回 null; +// * +// * @param code +// * @return +// */ +// public static CryptoAlgorithm_Enum valueOf(byte code) { +// for (CryptoAlgorithm_Enum alg : CryptoAlgorithm_Enum.values()) { +// if (alg.CODE == code) { +// return alg; +// } +// } +// throw new IllegalArgumentException("CryptoAlgorithm doesn't support enum code[" + code + "]!"); +// } +//} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoBytes.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoBytes.java index 2da141ef..8950a1ee 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoBytes.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoBytes.java @@ -13,7 +13,7 @@ public interface CryptoBytes extends BytesSerializable { /** * 算法标识符的长度; */ - int ALGORYTHM_BYTES = 1; + int ALGORYTHM_CODE_SIZE = CryptoAlgorithm.CODE_SIZE; /** * 算法; @@ -25,10 +25,12 @@ public interface CryptoBytes extends BytesSerializable { /** * 返回编码后的摘要信息;
* - * 这是算法标识 {@link #getAlgorithm()} 与原始的摘要数据 {@link #getRawDigest()} + * 这是算法标识 {@link #getAlgorithm()} 与原始的摘要数据 * 按照特定的编码方式合并后的结果; */ @Override byte[] toBytes(); + + } diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoFactory.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoFactory.java index 5cf15bbf..0eeaaae5 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoFactory.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoFactory.java @@ -1,15 +1,15 @@ -package com.jd.blockchain.crypto; - -import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; -import com.jd.blockchain.crypto.hash.HashCryptography; -import com.jd.blockchain.crypto.symmetric.SymmetricCryptography; - -public interface CryptoFactory { - - HashCryptography hashCryptography(); - - AsymmetricCryptography asymmetricCryptography(); - - SymmetricCryptography symmetricCryptography(); - -} +//package com.jd.blockchain.crypto; +// +//import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; +//import com.jd.blockchain.crypto.hash.HashCryptography; +//import com.jd.blockchain.crypto.symmetric.SymmetricCryptography; +// +//public interface CryptoFactory { +// +// HashCryptography hashCryptography(); +// +// AsymmetricCryptography asymmetricCryptography(); +// +// SymmetricCryptography symmetricCryptography(); +// +//} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoKeyPairGenerator.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoKeyPairGenerator.java index 2e3c9da9..d2591cc2 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoKeyPairGenerator.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoKeyPairGenerator.java @@ -9,6 +9,4 @@ public interface CryptoKeyPairGenerator { */ CryptoKeyPair generateKeyPair(); - - } diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoKeyType.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoKeyType.java index 7c94d2df..61dcbe28 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoKeyType.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoKeyType.java @@ -3,17 +3,17 @@ package com.jd.blockchain.crypto; public enum CryptoKeyType { /** - * 非对称密码算法的公钥 + * 非对称密钥的公钥 */ PUB_KEY((byte)0x01), /** - * 非对称密码算法的私钥; + * 非对称密钥的私钥; */ PRIV_KEY((byte)0x02), /** - * 对称密码算法的密钥; + * 对称密钥; */ SYMMETRIC_KEY((byte)0x03); @@ -29,7 +29,7 @@ public enum CryptoKeyType { return alg; } } - throw new IllegalArgumentException("CryptoKeyType doesn't support enum code[" + code + "]!"); + throw new CryptoException("CryptoKeyType doesn't support enum code[" + code + "]!"); } public byte getCODE() { diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoService.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoService.java new file mode 100644 index 00000000..4a835a83 --- /dev/null +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoService.java @@ -0,0 +1,9 @@ +package com.jd.blockchain.crypto; + +import java.util.Collection; + +public interface CryptoService { + + Collection getFunctions(); + +} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoServiceProviders.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoServiceProviders.java new file mode 100644 index 00000000..1a185cf9 --- /dev/null +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoServiceProviders.java @@ -0,0 +1,213 @@ +package com.jd.blockchain.crypto; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.jd.blockchain.crypto.asymmetric.AsymmetricEncryptionFunction; +import com.jd.blockchain.crypto.asymmetric.SignatureFunction; +import com.jd.blockchain.crypto.hash.HashFunction; +import com.jd.blockchain.crypto.symmetric.SymmetricEncryptionFunction; +import com.jd.blockchain.provider.Provider; +import com.jd.blockchain.provider.ProviderManager; + +/** + * 密码服务提供者的管理器; + * + * @author huanghaiquan + * + */ +public final class CryptoServiceProviders { + + private static Logger LOGGER = LoggerFactory.getLogger(CryptoServiceProviders.class); + + private static Map functions = new ConcurrentHashMap<>(); + + private static Map algorithms = new ConcurrentHashMap<>(); + + private static Map names = new ConcurrentHashMap<>(); + + private static ProviderManager pm = new ProviderManager(); + + static { + loadDefaultProviders(); + } + + private static void loadDefaultProviders() { + ClassLoader cl = CryptoServiceProviders.class.getClassLoader(); + pm.installAllProviders(CryptoService.class, cl); + + Iterable> providers = pm.getAllProviders(CryptoService.class); + for (Provider provider : providers) { + register(provider); + } + } + + private static void register(Provider provider) { + for (CryptoFunction cryptoFunction : provider.getService().getFunctions()) { + + String name = cryptoFunction.getAlgorithm().name().toUpperCase(); + short code = cryptoFunction.getAlgorithm().code(); + CryptoAlgorithm algorithm = new CryptoAlgorithmDefinition(name, code); + if (CryptoAlgorithm.isRandomAlgorithm(algorithm) && !(cryptoFunction instanceof RandomFunction)) { + LOGGER.error(String.format( + "The random algorithm \"%s\" declared by provider[%s] does not implement the interface \"%s\"!", + algorithm.toString(), provider.getFullName(), RandomFunction.class.getName())); + continue; + } + if (CryptoAlgorithm.isAsymmetricEncryptionAlgorithm(algorithm) + && !(cryptoFunction instanceof AsymmetricEncryptionFunction)) { + LOGGER.error(String.format( + "The asymmetric encryption algorithm \"%s\" declared by the provider[%s] does not implement the interface \"%s\"!", + algorithm.toString(), provider.getFullName(), AsymmetricEncryptionFunction.class.getName())); + continue; + } + if (CryptoAlgorithm.isSignatureAlgorithm(algorithm) && !(cryptoFunction instanceof SignatureFunction)) { + LOGGER.error(String.format( + "The signature algorithm \"%s\" declared by the provider[%s] does not implement the interface \"%s\"!", + algorithm.toString(), provider.getFullName(), SignatureFunction.class.getName())); + continue; + } + if (CryptoAlgorithm.isSymmetricEncryptionAlgorithm(algorithm) + && !(cryptoFunction instanceof SymmetricEncryptionFunction)) { + LOGGER.error(String.format( + "The symmetric encryption algorithm \"%s\" declared by the provider[%s] does not implement the interface \"%s\"!", + algorithm.toString(), provider.getFullName(), SymmetricEncryptionFunction.class.getName())); + continue; + } + if (CryptoAlgorithm.isHashAlgorithm(algorithm) && !(cryptoFunction instanceof HashFunction)) { + LOGGER.error(String.format( + "The hash encryption algorithm \"%s\" declared by the provider[%s] does not implement the interface \"%s\"!", + algorithm.toString(), provider.getFullName(), HashFunction.class.getName())); + continue; + } + if (CryptoAlgorithm.isExtAlgorithm(algorithm) && (cryptoFunction instanceof RandomFunction + || cryptoFunction instanceof AsymmetricEncryptionFunction + || cryptoFunction instanceof SignatureFunction + || cryptoFunction instanceof SymmetricEncryptionFunction + || cryptoFunction instanceof HashFunction)) { + LOGGER.error(String.format( + "The ext algorithm \"%s\" declared by the provider[%s] can not implement the standard algorithm interface!", + algorithm.toString(), provider.getFullName())); + continue; + } + + if (functions.containsKey(algorithm.code()) || names.containsKey(algorithm.name())) { + LOGGER.error(String.format("The algorithm \"%s\" declared by the provider[%s] already exists!", + algorithm.toString(), provider.getFullName())); + continue; + } + + functions.put(algorithm.code(), cryptoFunction); + algorithms.put(algorithm.code(), algorithm); + names.put(algorithm.name(), algorithm.code()); + } + } + + private CryptoServiceProviders() { + } + + public static Collection getAllAlgorithms() { + return algorithms.values(); + } + + /** + * 返回指定编码的算法;
+ * 如果不存在,则返回 null; + * + * @param code + * @return + */ + public static CryptoAlgorithm getAlgorithm(short code) { + return algorithms.get(code); + } + + public static CryptoAlgorithm getAlgorithm(String name) { + Short code = names.get(name.toUpperCase()); + return code == null ? null : algorithms.get(code); + } + + public static RandomFunction getRandomFunction(CryptoAlgorithm algorithm) { + if (!CryptoAlgorithm.isRandomAlgorithm(algorithm)) { + throw new CryptoException("The specified algorithm " + algorithm.name() + "[" + algorithm.code() + + "] is not a random function!"); + } + CryptoFunction func = functions.get(algorithm.code()); + if (func == null) { + throw new CryptoException( + "Algorithm " + algorithm.name() + "[" + algorithm.code() + "] has no service provider!"); + } + + return (RandomFunction) func; + } + + public static HashFunction getHashFunction(CryptoAlgorithm algorithm) { + if (!CryptoAlgorithm.isHashAlgorithm(algorithm)) { + throw new CryptoException("The specified algorithm " + algorithm.name() + "[" + algorithm.code() + + "] is not a hash function!"); + } + CryptoFunction func = functions.get(algorithm.code()); + if (func == null) { + throw new CryptoException( + "Algorithm " + algorithm.name() + "[" + algorithm.code() + "] has no service provider!"); + } + + return (HashFunction) func; + } + + public static AsymmetricEncryptionFunction getAsymmetricEncryptionFunction(CryptoAlgorithm algorithm) { + if (!CryptoAlgorithm.isAsymmetricEncryptionAlgorithm(algorithm)) { + throw new CryptoException("The specified algorithm " + algorithm.name() + "[" + algorithm.code() + + "] is not a asymmetric encryption function!"); + } + CryptoFunction func = functions.get(algorithm.code()); + if (func == null) { + throw new CryptoException( + "Algorithm " + algorithm.name() + "[" + algorithm.code() + "] has no service provider!"); + } + + return (AsymmetricEncryptionFunction) func; + } + + public static SignatureFunction getSignatureFunction(CryptoAlgorithm algorithm) { + if (!CryptoAlgorithm.isSignatureAlgorithm(algorithm)) { + throw new CryptoException("The specified algorithm " + algorithm.name() + "[" + algorithm.code() + + "] is not a signature function!"); + } + CryptoFunction func = functions.get(algorithm.code()); + if (func == null) { + throw new CryptoException( + "Algorithm " + algorithm.name() + "[" + algorithm.code() + "] has no service provider!"); + } + + return (SignatureFunction) func; + } + + public static SymmetricEncryptionFunction getSymmetricEncryptionFunction(CryptoAlgorithm algorithm) { + if (!CryptoAlgorithm.isSymmetricEncryptionAlgorithm(algorithm)) { + throw new CryptoException("The specified algorithm " + algorithm.name() + "[" + algorithm.code() + + "] is not a symmetric encryption function!"); + } + CryptoFunction func = functions.get(algorithm.code()); + if (func == null) { + throw new CryptoException( + "Algorithm " + algorithm.name() + "[" + algorithm.code() + "] has no service provider!"); + } + + return (SymmetricEncryptionFunction) func; + } + + public static CryptoFunction getCryptoFunction(CryptoAlgorithm algorithm) { + CryptoFunction func = functions.get(algorithm.code()); + if (func == null) { + throw new CryptoException( + "Algorithm " + algorithm.name() + "[" + algorithm.code() + "] has no service provider!"); + } + + return func; + } + +} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoUtils.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoUtils.java index 4493a681..5bea9f61 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoUtils.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/CryptoUtils.java @@ -1,51 +1,73 @@ -package com.jd.blockchain.crypto; - -import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; -import com.jd.blockchain.crypto.asymmetric.AsymmetricEncryptionFunction; -import com.jd.blockchain.crypto.asymmetric.SignatureFunction; -import com.jd.blockchain.crypto.hash.HashCryptography; -import com.jd.blockchain.crypto.hash.HashFunction; -import com.jd.blockchain.crypto.impl.CryptoFactoryImpl; -import com.jd.blockchain.crypto.symmetric.SymmetricCryptography; -import com.jd.blockchain.crypto.symmetric.SymmetricEncryptionFunction; - -public class CryptoUtils { - - private static CryptoFactory CRYPTO_FACTORY = new CryptoFactoryImpl(); - - public static CryptoFactory crypto() { - return CRYPTO_FACTORY; - } - - - public static HashCryptography hashCrypto() { - return crypto().hashCryptography(); - } - - public static HashFunction hash(CryptoAlgorithm alg) { - return hashCrypto().getFunction(alg); - } - - - public static AsymmetricCryptography asymmCrypto() { - return crypto().asymmetricCryptography(); - } - - public static SignatureFunction sign(CryptoAlgorithm alg) { - return asymmCrypto().getSignatureFunction(alg); - } - - public static AsymmetricEncryptionFunction asymmEncrypt(CryptoAlgorithm alg) { - return asymmCrypto().getAsymmetricEncryptionFunction(alg); - } - - - public static SymmetricCryptography symmCrypto() { - return crypto().symmetricCryptography(); - } - - public static SymmetricEncryptionFunction symmEncrypt(CryptoAlgorithm alg) { - return symmCrypto().getSymmetricEncryptionFunction(alg); - } - -} +//package com.jd.blockchain.crypto; +// +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; +//import com.jd.blockchain.crypto.asymmetric.AsymmetricEncryptionFunction; +//import com.jd.blockchain.crypto.asymmetric.SignatureFunction; +//import com.jd.blockchain.crypto.hash.HashCryptography; +//import com.jd.blockchain.crypto.hash.HashFunction; +//import com.jd.blockchain.crypto.symmetric.SymmetricCryptography; +//import com.jd.blockchain.crypto.symmetric.SymmetricEncryptionFunction; +// +//public class CryptoUtils { +// +// private static Logger LOGGER = LoggerFactory.getLogger(CryptoUtils.class); +// +// private static final Object MUTEX = new Object(); +// +// private static final String STD = "com.jd.blockchain.crypto.impl.CryptoFactoryImpl"; +// +// private static volatile CryptoFactory STD_FACTORY; +// +// public static CryptoFactory crypto() { +// if (STD_FACTORY == null) { +// synchronized (MUTEX) { +// if (STD_FACTORY == null) { +// try { +// Class stdFactoryClass = Class.forName(STD); +// STD_FACTORY = (CryptoFactory) stdFactoryClass.newInstance(); +// } catch (ClassNotFoundException e) { +// LOGGER.error("STD crypto provider is not found!", e); +// throw new CryptoException("STD crypto provider is not found!", e); +// } catch (InstantiationException | IllegalAccessException e) { +// LOGGER.error("Fail to init STD crypto provider!", e); +// throw new CryptoException("Fail to init STD crypto provider!", e); +// } +// } +// return STD_FACTORY; +// } +// } +// return STD_FACTORY; +// } +// +// public static HashCryptography hashCrypto() { +// return crypto().hashCryptography(); +// } +// +// public static HashFunction hash(CryptoAlgorithm alg) { +// return hashCrypto().getFunction(alg); +// } +// +// public static AsymmetricCryptography asymmCrypto() { +// return crypto().asymmetricCryptography(); +// } +// +// public static SignatureFunction sign(CryptoAlgorithm alg) { +// return asymmCrypto().getSignatureFunction(alg); +// } +// +// public static AsymmetricEncryptionFunction asymmEncrypt(CryptoAlgorithm alg) { +// return asymmCrypto().getAsymmetricEncryptionFunction(alg); +// } +// +// public static SymmetricCryptography symmCrypto() { +// return crypto().symmetricCryptography(); +// } +// +// public static SymmetricEncryptionFunction symmEncrypt(CryptoAlgorithm alg) { +// return symmCrypto().getSymmetricEncryptionFunction(alg); +// } +// +//} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/PrivKey.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/PrivKey.java similarity index 54% rename from source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/PrivKey.java rename to source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/PrivKey.java index b756b9f2..96c622f3 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/PrivKey.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/PrivKey.java @@ -1,8 +1,4 @@ -package com.jd.blockchain.crypto.asymmetric; - -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.CryptoKeyType; -import com.jd.blockchain.crypto.base.BaseCryptoKey; +package com.jd.blockchain.crypto; /** * 私钥; @@ -20,9 +16,9 @@ public class PrivKey extends BaseCryptoKey { public PrivKey(byte[] cryptoBytes) { super(cryptoBytes); } - + @Override - protected boolean support(CryptoKeyType keyType) { - return CryptoKeyType.PRIV_KEY == keyType; + public CryptoKeyType getKeyType() { + return CryptoKeyType.PRIV_KEY; } } \ No newline at end of file diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/PubKey.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/PubKey.java similarity index 53% rename from source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/PubKey.java rename to source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/PubKey.java index 328b0751..ed228888 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/PubKey.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/PubKey.java @@ -1,8 +1,4 @@ -package com.jd.blockchain.crypto.asymmetric; - -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.CryptoKeyType; -import com.jd.blockchain.crypto.base.BaseCryptoKey; +package com.jd.blockchain.crypto; /** * 公钥; @@ -17,16 +13,13 @@ public class PubKey extends BaseCryptoKey { public PubKey(CryptoAlgorithm algorithm, byte[] rawCryptoBytes) { super(algorithm, rawCryptoBytes, CryptoKeyType.PUB_KEY); } - public PubKey() { - super(); - } public PubKey(byte[] cryptoBytes) { super(cryptoBytes); } - + @Override - protected boolean support(CryptoKeyType keyType) { - return CryptoKeyType.PUB_KEY == keyType; + public CryptoKeyType getKeyType() { + return CryptoKeyType.PUB_KEY; } } \ No newline at end of file diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/RandomFunction.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/RandomFunction.java new file mode 100644 index 00000000..a4aa748c --- /dev/null +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/RandomFunction.java @@ -0,0 +1,7 @@ +package com.jd.blockchain.crypto; + +public interface RandomFunction extends CryptoFunction { + + RandomGenerator generate(byte[] seed); + +} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/RandomGenerator.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/RandomGenerator.java new file mode 100644 index 00000000..92421570 --- /dev/null +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/RandomGenerator.java @@ -0,0 +1,9 @@ +package com.jd.blockchain.crypto; + +public interface RandomGenerator { + + byte[] nextBytes(int size); + + void nextBytes(byte[] buffer); + +} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricKey.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/SymmetricKey.java similarity index 60% rename from source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricKey.java rename to source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/SymmetricKey.java index c226dfd3..5dc9f1f4 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricKey.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/SymmetricKey.java @@ -1,11 +1,13 @@ -package com.jd.blockchain.crypto.symmetric; +package com.jd.blockchain.crypto; import static com.jd.blockchain.crypto.CryptoKeyType.SYMMETRIC_KEY; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.CryptoKeyType; -import com.jd.blockchain.crypto.base.BaseCryptoKey; - +/** + * 单密钥; + * + * @author huanghaiquan + * + */ public class SymmetricKey extends BaseCryptoKey { private static final long serialVersionUID = 5055547663903904933L; @@ -17,11 +19,6 @@ public class SymmetricKey extends BaseCryptoKey { public SymmetricKey(byte[] keyBytes) { super(keyBytes); } - - @Override - protected boolean support(CryptoKeyType keyType) { - return SYMMETRIC_KEY == keyType; - } @Override public CryptoKeyType getKeyType() { diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/AsymmetricCiphertext.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/AsymmetricCiphertext.java index f28d3d83..b6c1a2cf 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/AsymmetricCiphertext.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/AsymmetricCiphertext.java @@ -1,8 +1,8 @@ package com.jd.blockchain.crypto.asymmetric; +import com.jd.blockchain.crypto.BaseCryptoBytes; import com.jd.blockchain.crypto.Ciphertext; import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.base.BaseCryptoBytes; public class AsymmetricCiphertext extends BaseCryptoBytes implements Ciphertext { @@ -13,10 +13,10 @@ public class AsymmetricCiphertext extends BaseCryptoBytes implements Ciphertext public AsymmetricCiphertext(byte[] cryptoBytes) { super(cryptoBytes); } - + @Override protected boolean support(CryptoAlgorithm algorithm) { - return algorithm.isAsymmetric() && algorithm.isEncryptable(); + return CryptoAlgorithm.isAsymmetricEncryptionAlgorithm(algorithm); } @Override diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/AsymmetricCryptography.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/AsymmetricCryptography.java index 2ca708f1..21b855e1 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/AsymmetricCryptography.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/AsymmetricCryptography.java @@ -1,82 +1,84 @@ -package com.jd.blockchain.crypto.asymmetric; - -import com.jd.blockchain.crypto.Ciphertext; -import com.jd.blockchain.crypto.CryptoAlgorithm; - -public interface AsymmetricCryptography { - - /** - * 生成密钥对; - * - * @param algorithm - * @return - */ - CryptoKeyPair generateKeyPair(CryptoAlgorithm algorithm); - - /** - * 获取签名方法; - * - * @param algorithm - * @return - */ - SignatureFunction getSignatureFunction(CryptoAlgorithm algorithm); - - /** - * 校验签名摘要和数据是否一致; - * - * @param digestBytes 签名摘要数据 - * @param pubKeyBytes 公钥数据 - * @param data 被签名数据 - * @return - */ - boolean verify(byte[] digestBytes, byte[] pubKeyBytes, byte[] data); - - /** - * 获取非对称加密方法; - * - * @param algorithm - * @return - */ - AsymmetricEncryptionFunction getAsymmetricEncryptionFunction(CryptoAlgorithm algorithm); - - /** - * 解密; - * - * @param privKeyBytes - * @param ciphertextBytes - * @return - */ - byte[] decrypt(byte[] privKeyBytes, byte[] ciphertextBytes); - - - Ciphertext resolveCiphertext(byte[] ciphertextBytes); - - Ciphertext tryResolveCiphertext(byte[] ciphertextBytes); - - /** - * @param digestBytes 待解析签名摘要 - * @return - */ - SignatureDigest resolveSignatureDigest(byte[] digestBytes); - - SignatureDigest tryResolveSignatureDigest(byte[] digestBytes); - - /** - * 由私钥恢复公钥; - * - * @param privKeyBytes 包含算法标识、密钥掩码和私钥的字节数组 - * @return 包含算法标识、密钥掩码和公钥的字节数组 - */ - byte[] retrievePubKeyBytes(byte[] privKeyBytes); - - byte[] tryRetrievePubKeyBytes(byte[] privKeyBytes); - - PubKey resolvePubKey(byte[] pubKeyBytes); - - PubKey tryResolvePubKey(byte[] pubKeyBytes); - - PrivKey resolvePrivKey(byte[] privKeyBytes); - - PrivKey tryResolvePrivKey(byte[] privKeyBytes); - -} +//package com.jd.blockchain.crypto.asymmetric; +// +//import com.jd.blockchain.crypto.Ciphertext; +//import com.jd.blockchain.crypto.CryptoAlgorithm; +//import com.jd.blockchain.crypto.PrivKey; +//import com.jd.blockchain.crypto.PubKey; +// +//public interface AsymmetricCryptography { +// +// /** +// * 生成密钥对; +// * +// * @param algorithm +// * @return +// */ +// CryptoKeyPair generateKeyPair(CryptoAlgorithm algorithm); +// +// /** +// * 获取签名方法; +// * +// * @param algorithm +// * @return +// */ +// SignatureFunction getSignatureFunction(CryptoAlgorithm algorithm); +// +// /** +// * 校验签名摘要和数据是否一致; +// * +// * @param digestBytes 签名摘要数据 +// * @param pubKeyBytes 公钥数据 +// * @param data 被签名数据 +// * @return +// */ +// boolean verify(byte[] digestBytes, byte[] pubKeyBytes, byte[] data); +// +// /** +// * 获取非对称加密方法; +// * +// * @param algorithm +// * @return +// */ +// AsymmetricEncryptionFunction getAsymmetricEncryptionFunction(CryptoAlgorithm algorithm); +// +// /** +// * 解密; +// * +// * @param privKeyBytes +// * @param ciphertextBytes +// * @return +// */ +// byte[] decrypt(byte[] privKeyBytes, byte[] ciphertextBytes); +// +// +// Ciphertext resolveCiphertext(byte[] ciphertextBytes); +// +// Ciphertext tryResolveCiphertext(byte[] ciphertextBytes); +// +// /** +// * @param digestBytes 待解析签名摘要 +// * @return +// */ +// SignatureDigest resolveSignatureDigest(byte[] digestBytes); +// +// SignatureDigest tryResolveSignatureDigest(byte[] digestBytes); +// +// /** +// * 由私钥恢复公钥; +// * +// * @param privKeyBytes 包含算法标识、密钥掩码和私钥的字节数组 +// * @return 包含算法标识、密钥掩码和公钥的字节数组 +// */ +// byte[] retrievePubKeyBytes(byte[] privKeyBytes); +// +// byte[] tryRetrievePubKeyBytes(byte[] privKeyBytes); +// +// PubKey resolvePubKey(byte[] pubKeyBytes); +// +// PubKey tryResolvePubKey(byte[] pubKeyBytes); +// +// PrivKey resolvePrivKey(byte[] privKeyBytes); +// +// PrivKey tryResolvePrivKey(byte[] privKeyBytes); +// +//} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/AsymmetricEncryptionFunction.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/AsymmetricEncryptionFunction.java index 93216fe3..04d8d525 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/AsymmetricEncryptionFunction.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/AsymmetricEncryptionFunction.java @@ -3,6 +3,8 @@ package com.jd.blockchain.crypto.asymmetric; import com.jd.blockchain.crypto.Ciphertext; import com.jd.blockchain.crypto.CryptoFunction; import com.jd.blockchain.crypto.CryptoKeyPairGenerator; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; public interface AsymmetricEncryptionFunction extends CryptoKeyPairGenerator, CryptoFunction { diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/CryptoKeyPair.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/CryptoKeyPair.java index 1dd023b2..df2e1017 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/CryptoKeyPair.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/CryptoKeyPair.java @@ -1,5 +1,8 @@ package com.jd.blockchain.crypto.asymmetric; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; + public class CryptoKeyPair { private PubKey pubKey; diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/SignatureDigest.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/SignatureDigest.java index 9e6e17de..342d8c61 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/SignatureDigest.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/SignatureDigest.java @@ -1,8 +1,8 @@ package com.jd.blockchain.crypto.asymmetric; +import com.jd.blockchain.crypto.BaseCryptoBytes; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoDigest; -import com.jd.blockchain.crypto.base.BaseCryptoBytes; public class SignatureDigest extends BaseCryptoBytes implements CryptoDigest { public SignatureDigest() { @@ -16,10 +16,10 @@ public class SignatureDigest extends BaseCryptoBytes implements CryptoDigest { public SignatureDigest(byte[] cryptoBytes) { super(cryptoBytes); } - + @Override protected boolean support(CryptoAlgorithm algorithm) { - return algorithm.isAsymmetric() && algorithm.isSignable(); + return CryptoAlgorithm.isSignatureAlgorithm(algorithm); } /** diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/SignatureFunction.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/SignatureFunction.java index 2f534a15..355f6df7 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/SignatureFunction.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/asymmetric/SignatureFunction.java @@ -2,6 +2,8 @@ package com.jd.blockchain.crypto.asymmetric; import com.jd.blockchain.crypto.CryptoFunction; import com.jd.blockchain.crypto.CryptoKeyPairGenerator; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; public interface SignatureFunction extends CryptoKeyPairGenerator, CryptoFunction { diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/base/BaseCryptoKey.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/base/BaseCryptoKey.java deleted file mode 100644 index de6eeabf..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/base/BaseCryptoKey.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.jd.blockchain.crypto.base; - -import java.io.Serializable; - -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.CryptoException; -import com.jd.blockchain.crypto.CryptoKey; -import com.jd.blockchain.crypto.CryptoKeyType; -import com.jd.blockchain.utils.io.BytesSlice; -import com.jd.blockchain.utils.io.BytesUtils; - -public abstract class BaseCryptoKey extends BaseCryptoBytes implements CryptoKey,Serializable { - - public static final int KEY_TYPE_BYTES = 1; - private static final long serialVersionUID = 4543074827807908363L; - - private CryptoKeyType keyType; - - public BaseCryptoKey() { - super(); - } - - public BaseCryptoKey(CryptoAlgorithm algorithm, byte[] rawCryptoBytes, CryptoKeyType keyType) { - super(algorithm, encodeKeyBytes(rawCryptoBytes, keyType)); - this.keyType = keyType; - } - - public BaseCryptoKey(byte[] cryptoBytes) { - super(cryptoBytes); - CryptoKeyType keyType = decodeKeyType(getRawCryptoBytes()); - if (!support(keyType)) { - throw new CryptoException("CryptoKey doesn't support keyType[" + keyType + "]!"); - } - this.keyType = keyType; - } - - @Override - public CryptoKeyType getKeyType() { - return keyType; - } - - private static byte[] encodeKeyBytes(byte[] rawCryptoBytes, CryptoKeyType keyType ) { - return BytesUtils.concat(new byte[] {keyType.CODE }, rawCryptoBytes); - } - - private static CryptoKeyType decodeKeyType(BytesSlice cryptoBytes) { - return CryptoKeyType.valueOf(cryptoBytes.getByte()); - } - - protected abstract boolean support(CryptoKeyType keyType); - - @Override - protected boolean support(CryptoAlgorithm algorithm) { - return algorithm.isSymmetric() || algorithm.isAsymmetric(); - } - - - @Override - public byte[] getRawKeyBytes() { - return getRawCryptoBytes().getBytesCopy(1); - } -} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/hash/HashCryptography.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/hash/HashCryptography.java index 52c8240d..85107ffc 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/hash/HashCryptography.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/hash/HashCryptography.java @@ -1,50 +1,50 @@ -package com.jd.blockchain.crypto.hash; - -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.CryptoException; - -public interface HashCryptography { - - /** - * return HashFunction instance of the specified hash alg; - * - * - * if alg out of hash alg,then throws {@link IllegalArgumentException} - * - * @param algorithm - * @return - */ - HashFunction getFunction(CryptoAlgorithm algorithm); - - /** - * 校验 hash 摘要与指定的数据是否匹配; - * - * @param digestBytes - * @param data - * @return - */ - boolean verify(byte[] digestBytes, byte[] data); - - boolean verify(HashDigest digest, byte[] data); - - /** - * 解析指定的 hash 摘要;
- * - * 如果不符合哈希摘要的编码格式,则引发 {@link CryptoException} 异常; - * - * @param digestBytes - * @return - */ - HashDigest resolveHashDigest(byte[] digestBytes); - - /** - * 解析指定的 hash 摘要;
- * - * 如果不符合哈希摘要的编码格式,则返回 null; - * - * @param digestBytes - * @return - */ - HashDigest tryResolveHashDigest(byte[] digestBytes); - -} +//package com.jd.blockchain.crypto.hash; +// +//import com.jd.blockchain.crypto.CryptoAlgorithm; +//import com.jd.blockchain.crypto.CryptoException; +// +//public interface HashCryptography { +// +// /** +// * return HashFunction instance of the specified hash alg; +// * +// * +// * if alg out of hash alg,then throws {@link IllegalArgumentException} +// * +// * @param algorithm +// * @return +// */ +// HashFunction getFunction(CryptoAlgorithm algorithm); +// +// /** +// * 校验 hash 摘要与指定的数据是否匹配; +// * +// * @param digestBytes +// * @param data +// * @return +// */ +// boolean verify(byte[] digestBytes, byte[] data); +// +// boolean verify(HashDigest digest, byte[] data); +// +// /** +// * 解析指定的 hash 摘要;
+// * +// * 如果不符合哈希摘要的编码格式,则引发 {@link CryptoException} 异常; +// * +// * @param digestBytes +// * @return +// */ +// HashDigest resolveHashDigest(byte[] digestBytes); +// +// /** +// * 解析指定的 hash 摘要;
+// * +// * 如果不符合哈希摘要的编码格式,则返回 null; +// * +// * @param digestBytes +// * @return +// */ +// HashDigest tryResolveHashDigest(byte[] digestBytes); +// +//} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/hash/HashDigest.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/hash/HashDigest.java index e19ff3fb..99cf37bb 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/hash/HashDigest.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/hash/HashDigest.java @@ -2,9 +2,9 @@ package com.jd.blockchain.crypto.hash; import java.io.Serializable; +import com.jd.blockchain.crypto.BaseCryptoBytes; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoDigest; -import com.jd.blockchain.crypto.base.BaseCryptoBytes; public class HashDigest extends BaseCryptoBytes implements CryptoDigest,Serializable { @@ -24,7 +24,7 @@ public class HashDigest extends BaseCryptoBytes implements CryptoDigest,Serializ @Override protected boolean support(CryptoAlgorithm algorithm) { - return algorithm.isHash(); + return CryptoAlgorithm.isHashAlgorithm(algorithm); } @Override diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/AsymmtricCryptographyImpl.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/AsymmtricCryptographyImpl.java deleted file mode 100644 index 1b55da0f..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/AsymmtricCryptographyImpl.java +++ /dev/null @@ -1,218 +0,0 @@ -package com.jd.blockchain.crypto.impl; - -import com.jd.blockchain.crypto.Ciphertext; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.*; -import com.jd.blockchain.crypto.impl.def.asymmetric.ED25519SignatureFunction; -import com.jd.blockchain.crypto.impl.jni.asymmetric.JNIED25519SignatureFunction; -import com.jd.blockchain.crypto.impl.sm.asymmetric.SM2CryptoFunction; - -public class AsymmtricCryptographyImpl implements AsymmetricCryptography { - - private static final SignatureFunction ED25519_SIGF = new ED25519SignatureFunction(); - - private static final SignatureFunction SM2_SIGF = new SM2CryptoFunction(); - - private static final SignatureFunction JNIED25519_SIGF = new JNIED25519SignatureFunction(); - - private static final AsymmetricEncryptionFunction SM2_ENCF = new SM2CryptoFunction(); - - /** - * 封装了非对称密码算法对应的密钥生成算法 - */ - @Override - public CryptoKeyPair generateKeyPair(CryptoAlgorithm algorithm) { - - //判断算法是签名算法还是非对称加密算法,并根据算法生成密钥对,否则抛出异常 - if (algorithm.isSignable() && algorithm.isAsymmetric()){ - return getSignatureFunction(algorithm).generateKeyPair(); - } - else if (algorithm.isEncryptable() && algorithm.isAsymmetric()){ - return getAsymmetricEncryptionFunction(algorithm).generateKeyPair(); - } - else throw new IllegalArgumentException("The specified algorithm is not signature or asymmetric encryption algorithm!"); - } - - @Override - public SignatureFunction getSignatureFunction(CryptoAlgorithm algorithm) { - //遍历签名算法,如果满足,则返回实例 - switch (algorithm) { - case ED25519: - return ED25519_SIGF; - case SM2: - return SM2_SIGF; - case JNIED25519: - return JNIED25519_SIGF; - default: - break; - } - throw new IllegalArgumentException("The specified algorithm is not signature algorithm!"); - } - - @Override - public boolean verify(byte[] digestBytes, byte[] pubKeyBytes, byte[] data) { - - //得到SignatureDigest类型的签名摘要,并得到算法标识 - SignatureDigest signatureDigest = resolveSignatureDigest(digestBytes); - CryptoAlgorithm algorithm = signatureDigest.getAlgorithm(); - PubKey pubKey = resolvePubKey(pubKeyBytes); - - //验证两个输入中算法标识一致,否则抛出异常 - if (algorithm != signatureDigest.getAlgorithm()) - throw new IllegalArgumentException("Digest's algorithm and key's are not matching!"); - - //根据算法标识,调用对应算法实例来验证签名摘要 - return getSignatureFunction(algorithm).verify(signatureDigest,pubKey,data); - } - - @Override - public AsymmetricEncryptionFunction getAsymmetricEncryptionFunction(CryptoAlgorithm algorithm) { - //遍历非对称加密算法,如果满足,则返回实例 - switch (algorithm) { - case SM2: - return SM2_ENCF; - default: - break; - } - throw new IllegalArgumentException("The specified algorithm is not asymmetric encryption algorithm!"); - } - - @Override - public byte[] decrypt(byte[] privKeyBytes, byte[] ciphertextBytes) { - - //分别得到PrivKey和Ciphertext类型的密钥和密文,以及privKey对应的算法 - PrivKey privKey = resolvePrivKey(privKeyBytes); - Ciphertext ciphertext = resolveCiphertext(ciphertextBytes); - CryptoAlgorithm algorithm = privKey.getAlgorithm(); - - //验证两个输入中算法标识一致,否则抛出异常 - if (algorithm != ciphertext.getAlgorithm()) - throw new IllegalArgumentException("Ciphertext's algorithm and key's are not matching!"); - - //根据算法标识,调用对应算法实例来计算返回明文 - return getAsymmetricEncryptionFunction(algorithm).decrypt(privKey,ciphertext); - } - - @Override - public Ciphertext resolveCiphertext(byte[] ciphertextBytes) { - Ciphertext ciphertext = tryResolveCiphertext(ciphertextBytes); - if (ciphertext == null) - throw new IllegalArgumentException("This ciphertextBytes cannot be resolved!"); - else return ciphertext; - } - - @Override - public Ciphertext tryResolveCiphertext(byte[] ciphertextBytes) { - //遍历非对称加密算法,如果满足,则返回解析结果 - if (SM2_ENCF.supportCiphertext(ciphertextBytes)){ - return SM2_ENCF.resolveCiphertext(ciphertextBytes); - } - //否则返回null - return null; - } - - @Override - public SignatureDigest resolveSignatureDigest(byte[] digestBytes) { - SignatureDigest signatureDigest = tryResolveSignatureDigest(digestBytes); - if (signatureDigest == null) - throw new IllegalArgumentException("This digestBytes cannot be resolved!"); - else return signatureDigest; - } - - @Override - public SignatureDigest tryResolveSignatureDigest(byte[] digestBytes) { - //遍历签名算法,如果满足,则返回解析结果 - if (ED25519_SIGF.supportDigest(digestBytes)){ - return ED25519_SIGF.resolveDigest(digestBytes); - } - if (SM2_SIGF.supportDigest(digestBytes)){ - return SM2_SIGF.resolveDigest(digestBytes); - } - if (JNIED25519_SIGF.supportDigest(digestBytes)){ - return JNIED25519_SIGF.resolveDigest(digestBytes); - } - //否则返回null - return null; - } - - @Override - public byte[] retrievePubKeyBytes(byte[] privKeyBytes) { - byte[] pubKeyBytes = tryRetrievePubKeyBytes(privKeyBytes); - if (pubKeyBytes == null) - throw new IllegalArgumentException("The specified algorithm in privKeyBytes is not signature or asymmetric encryption algorithm!"); - else return pubKeyBytes; - } - - @Override - public byte[] tryRetrievePubKeyBytes(byte[] privKeyBytes) { - //解析私钥获得算法标识 - CryptoAlgorithm algorithm = resolvePrivKey(privKeyBytes).getAlgorithm(); - - //判断算法是签名算法还是非对称加密算法,并根据算法生成密钥对,否则抛出异常 - if (algorithm.isSignable() && algorithm.isAsymmetric()){ - return getSignatureFunction(algorithm).retrievePubKeyBytes(privKeyBytes); - } - else if (algorithm.isEncryptable() && algorithm.isAsymmetric()){ - return getAsymmetricEncryptionFunction(algorithm).retrievePubKeyBytes(privKeyBytes); - } - //否则返回null - return null; - } - - @Override - public PubKey resolvePubKey(byte[] pubKeyBytes) { - PubKey pubKey = tryResolvePubKey(pubKeyBytes); - if (pubKey == null) - throw new IllegalArgumentException("This pubKeyBytes cannot be resolved!"); - else return pubKey; - - } - - @Override - public PubKey tryResolvePubKey(byte[] pubKeyBytes) { - //遍历签名算法,如果满足,则返回解析结果 - if (ED25519_SIGF.supportPubKey(pubKeyBytes)){ - return ED25519_SIGF.resolvePubKey(pubKeyBytes); - } - if (SM2_SIGF.supportPubKey(pubKeyBytes)){ - return SM2_SIGF.resolvePubKey(pubKeyBytes); - } - if (JNIED25519_SIGF.supportPubKey(pubKeyBytes)){ - return JNIED25519_SIGF.resolvePubKey(pubKeyBytes); - } - //遍历非对称加密算法,如果满足,则返回解析结果 - if (SM2_ENCF.supportPubKey(pubKeyBytes)){ - return SM2_ENCF.resolvePubKey(pubKeyBytes); - } - //否则返回null - return null; - } - - @Override - public PrivKey resolvePrivKey(byte[] privKeyBytes) { - PrivKey privKey = tryResolvePrivKey(privKeyBytes); - if (privKey == null) - throw new IllegalArgumentException("This privKeyBytes cannot be resolved!"); - else return privKey; - } - - @Override - public PrivKey tryResolvePrivKey(byte[] privKeyBytes) { - //遍历签名算法,如果满足,则返回解析结果 - if (ED25519_SIGF.supportPrivKey(privKeyBytes)){ - return ED25519_SIGF.resolvePrivKey(privKeyBytes); - } - if (SM2_SIGF.supportPrivKey(privKeyBytes)){ - return SM2_SIGF.resolvePrivKey(privKeyBytes); - } - if (JNIED25519_SIGF.supportPrivKey(privKeyBytes)){ - return JNIED25519_SIGF.resolvePrivKey(privKeyBytes); - } - //遍历非对称加密算法,如果满足,则返回解析结果 - if (SM2_ENCF.supportPrivKey(privKeyBytes)){ - return SM2_ENCF.resolvePrivKey(privKeyBytes); - } - //否则返回null - return null; - } -} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/CryptoFactoryImpl.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/CryptoFactoryImpl.java deleted file mode 100644 index 90a6d719..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/CryptoFactoryImpl.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.jd.blockchain.crypto.impl; - -import com.jd.blockchain.crypto.CryptoFactory; -import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; -import com.jd.blockchain.crypto.hash.HashCryptography; -import com.jd.blockchain.crypto.symmetric.SymmetricCryptography; - -public class CryptoFactoryImpl implements CryptoFactory { - - //Field; - private static HashCryptography hashCryptography = new HashCryptographyImpl(); - private static AsymmetricCryptography asymmetricCryptography = new AsymmtricCryptographyImpl(); - private static SymmetricCryptography symmetricCryptography = new SymmetricCryptographyImpl(); - - @Override - public HashCryptography hashCryptography() { - return hashCryptography; - } - - @Override - public AsymmetricCryptography asymmetricCryptography() { - return asymmetricCryptography; - } - - @Override - public SymmetricCryptography symmetricCryptography() { - return symmetricCryptography; - } - -} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/HashCryptographyImpl.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/HashCryptographyImpl.java deleted file mode 100644 index ae1772c5..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/HashCryptographyImpl.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.jd.blockchain.crypto.impl; - -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.hash.HashCryptography; -import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.hash.HashFunction; -import com.jd.blockchain.crypto.impl.def.hash.RIPEMD160HashFunction; -import com.jd.blockchain.crypto.impl.def.hash.SHA256HashFunction; -import com.jd.blockchain.crypto.impl.jni.hash.JNIRIPEMD160HashFunction; -import com.jd.blockchain.crypto.impl.jni.hash.JNISHA256HashFunction; -import com.jd.blockchain.crypto.impl.sm.hash.SM3HashFunction; - -public class HashCryptographyImpl implements HashCryptography { - - private static final HashFunction SHA256_FUNC = new SHA256HashFunction(); - private static final HashFunction RIPEMD160_FUNC = new RIPEMD160HashFunction(); - private static final HashFunction SM3_FUNC = new SM3HashFunction(); - - private static final HashFunction JNISHA256_FUNC = new JNISHA256HashFunction(); - private static final HashFunction JNIRIPEMD160_FUNC = new JNIRIPEMD160HashFunction(); - - @Override - public HashFunction getFunction(CryptoAlgorithm algorithm) { - - // 遍历哈希算法,如果满足,则返回实例 - switch (algorithm) { - case SHA256: - return SHA256_FUNC; - case RIPEMD160: - return RIPEMD160_FUNC; - case SM3: - return SM3_FUNC; - case JNISHA256: - return JNISHA256_FUNC; - case JNIRIPEMD160: - return JNIRIPEMD160_FUNC; - default: - break; - } - throw new IllegalArgumentException("The specified algorithm is not hash algorithm!"); - } - - @Override - public boolean verify(byte[] digestBytes, byte[] data) { - HashDigest hashDigest = resolveHashDigest(digestBytes); - return verify(hashDigest,data); - } - - @Override - public boolean verify(HashDigest digest, byte[] data) { - CryptoAlgorithm algorithm = digest.getAlgorithm(); - return getFunction(algorithm).verify(digest, data); - } - - @Override - public HashDigest resolveHashDigest(byte[] digestBytes) { - HashDigest hashDigest = tryResolveHashDigest(digestBytes); - if (hashDigest == null) - throw new IllegalArgumentException("This digestBytes cannot be resolved!"); - else return hashDigest; - } - - @Override - public HashDigest tryResolveHashDigest(byte[] digestBytes) { - //遍历哈希函数,如果满足,则返回解析结果 - if (SHA256_FUNC.supportHashDigest(digestBytes)) { - return SHA256_FUNC.resolveHashDigest(digestBytes); - } - if (RIPEMD160_FUNC.supportHashDigest(digestBytes)) { - return RIPEMD160_FUNC.resolveHashDigest(digestBytes); - } - if (SM3_FUNC.supportHashDigest(digestBytes)) { - return SM3_FUNC.resolveHashDigest(digestBytes); - } - if (JNISHA256_FUNC.supportHashDigest(digestBytes)) { - return JNISHA256_FUNC.resolveHashDigest(digestBytes); - } - if (JNIRIPEMD160_FUNC.supportHashDigest(digestBytes)) { - return JNIRIPEMD160_FUNC.resolveHashDigest(digestBytes); - } - //否则返回null - return null; - } -} \ No newline at end of file diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/SymmetricCryptographyImpl.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/SymmetricCryptographyImpl.java deleted file mode 100644 index ce787511..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/SymmetricCryptographyImpl.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.jd.blockchain.crypto.impl; - -import com.jd.blockchain.crypto.Ciphertext; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.impl.def.symmetric.AESSymmetricEncryptionFunction; -import com.jd.blockchain.crypto.impl.sm.symmetric.SM4SymmetricEncryptionFunction; -import com.jd.blockchain.crypto.symmetric.SymmetricCryptography; -import com.jd.blockchain.crypto.symmetric.SymmetricEncryptionFunction; -import com.jd.blockchain.crypto.symmetric.SymmetricKey; - -public class SymmetricCryptographyImpl implements SymmetricCryptography { - - private static final SymmetricEncryptionFunction AES_ENCF = new AESSymmetricEncryptionFunction(); - private static final SymmetricEncryptionFunction SM4_ENCF = new SM4SymmetricEncryptionFunction(); - - /** - * 封装了对称密码算法对应的密钥生成算法 - */ - @Override - public SymmetricKey generateKey(CryptoAlgorithm algorithm) { - - //验证算法标识是对称加密算法,并根据算法生成对称密钥,否则抛出异常 - if (algorithm.isEncryptable() && algorithm.isSymmetric() ){ - return (SymmetricKey) getSymmetricEncryptionFunction(algorithm).generateSymmetricKey(); - } - else throw new IllegalArgumentException("The specified algorithm is not symmetric encryption algorithm!"); - } - - @Override - public SymmetricEncryptionFunction getSymmetricEncryptionFunction(CryptoAlgorithm algorithm) { - - // 遍历对称加密算法,如果满足,则返回实例 - switch (algorithm) { - case AES: - return AES_ENCF; - case SM4: - return SM4_ENCF; - default: - break; - } - throw new IllegalArgumentException("The specified algorithm is not symmetric encryption algorithm!"); - } - - @Override - public byte[] decrypt(byte[] symmetricKeyBytes, byte[] ciphertextBytes) { - - //分别得到SymmetricKey和Ciphertext类型的密钥和密文,以及symmetricKey对应的算法 - SymmetricKey symmetricKey = resolveSymmetricKey(symmetricKeyBytes); - Ciphertext ciphertext = resolveCiphertext(ciphertextBytes); - CryptoAlgorithm algorithm = symmetricKey.getAlgorithm(); - - //验证两个输入中算法标识一致,否则抛出异常 - if (algorithm != ciphertext.getAlgorithm()) - throw new IllegalArgumentException("Ciphertext's algorithm and key's are not matching!"); - - //根据算法标识,调用对应算法实例来计算返回明文 - return getSymmetricEncryptionFunction(algorithm).decrypt(symmetricKey,ciphertext); - } - - @Override - public Ciphertext resolveCiphertext(byte[] ciphertextBytes) { - Ciphertext ciphertext = tryResolveCiphertext(ciphertextBytes); - if (ciphertext == null) - throw new IllegalArgumentException("This ciphertextBytes cannot be resolved!"); - else return ciphertext; - } - - @Override - public Ciphertext tryResolveCiphertext(byte[] ciphertextBytes) { - //遍历对称加密算法,如果满足,则返回解析结果 - if (AES_ENCF.supportCiphertext(ciphertextBytes)) { - return AES_ENCF.resolveCiphertext(ciphertextBytes); - } - if (SM4_ENCF.supportCiphertext(ciphertextBytes)) { - return SM4_ENCF.resolveCiphertext(ciphertextBytes); - } - //否则返回null - return null; - } - - @Override - public SymmetricKey resolveSymmetricKey(byte[] symmetricKeyBytes) { - SymmetricKey symmetricKey = tryResolveSymmetricKey(symmetricKeyBytes); - if (symmetricKey == null) - throw new IllegalArgumentException("This symmetricKeyBytes cannot be resolved!"); - else return symmetricKey; - } - - @Override - public SymmetricKey tryResolveSymmetricKey(byte[] symmetricKeyBytes) { - //遍历对称加密算法,如果满足,则返回解析结果 - if(AES_ENCF.supportSymmetricKey(symmetricKeyBytes)) { - return AES_ENCF.resolveSymmetricKey(symmetricKeyBytes); - } - if(SM4_ENCF.supportSymmetricKey(symmetricKeyBytes)) { - return SM4_ENCF.resolveSymmetricKey(symmetricKeyBytes); - } - //否则返回null - return null; - } -} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/symmetric/AESSymmetricEncryptionFunction.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/symmetric/AESSymmetricEncryptionFunction.java deleted file mode 100644 index 64b7cf82..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/def/symmetric/AESSymmetricEncryptionFunction.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.jd.blockchain.crypto.impl.def.symmetric; - -import com.jd.blockchain.crypto.Ciphertext; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.CryptoKey; -import com.jd.blockchain.crypto.symmetric.SymmetricCiphertext; -import com.jd.blockchain.crypto.symmetric.SymmetricEncryptionFunction; -import com.jd.blockchain.crypto.symmetric.SymmetricKey; -import com.jd.blockchain.utils.security.AESUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import static com.jd.blockchain.crypto.CryptoAlgorithm.AES; -import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_BYTES; -import static com.jd.blockchain.crypto.CryptoKeyType.SYMMETRIC_KEY; -import static com.jd.blockchain.crypto.base.BaseCryptoKey.KEY_TYPE_BYTES; - -public class AESSymmetricEncryptionFunction implements SymmetricEncryptionFunction { - - private static final int KEY_SIZE = 16; - private static final int BLOCK_SIZE = 16; - - private static final int SYMMETRICKEY_LENGTH = ALGORYTHM_BYTES + KEY_TYPE_BYTES + KEY_SIZE; - - public AESSymmetricEncryptionFunction() { - } - - @Override - public Ciphertext encrypt(SymmetricKey key, byte[] data) { - - byte[] rawKeyBytes = key.getRawKeyBytes(); - - // 验证原始密钥长度为128比特,即16字节 - if (rawKeyBytes.length != KEY_SIZE) - throw new IllegalArgumentException("This key has wrong format!"); - - // 验证密钥数据的算法标识对应AES算法 - if (key.getAlgorithm() != AES) - throw new IllegalArgumentException("The is not AES symmetric key!"); - - // 调用底层AES128算法并计算密文数据 - return new SymmetricCiphertext(AES, AESUtils.encrypt(data, rawKeyBytes)); - } - - @Override - public void encrypt(SymmetricKey key, InputStream in, OutputStream out) { - - // 读输入流得到明文,加密,密文数据写入输出流 - try { - byte[] aesData = new byte[in.available()]; - in.read(aesData); - in.close(); - - out.write(encrypt(key, aesData).toBytes()); - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public byte[] decrypt(SymmetricKey key, Ciphertext ciphertext) { - - byte[] rawKeyBytes = key.getRawKeyBytes(); - byte[] rawCiphertextBytes = ciphertext.getRawCiphertext(); - - // 验证原始密钥长度为128比特,即16字节 - if (rawKeyBytes.length != KEY_SIZE) - throw new IllegalArgumentException("This key has wrong format!"); - - // 验证密钥数据的算法标识对应AES算法 - if (key.getAlgorithm().CODE != AES.CODE) - throw new IllegalArgumentException("The is not AES symmetric key!"); - - // 验证原始密文长度为分组长度的整数倍 - if (rawCiphertextBytes.length % BLOCK_SIZE != 0) - throw new IllegalArgumentException("This ciphertext has wrong format!"); - - // 验证密文数据算法标识对应AES算法 - if (ciphertext.getAlgorithm() != AES) - throw new IllegalArgumentException("This is not AES ciphertext!"); - - // 调用底层AES128算法解密,得到明文 - return AESUtils.decrypt(rawCiphertextBytes, rawKeyBytes); - } - - @Override - public void decrypt(SymmetricKey key, InputStream in, OutputStream out) { - - // 读输入流得到密文数据,解密,明文写入输出流 - try { - byte[] aesData = new byte[in.available()]; - in.read(aesData); - in.close(); - - if (!supportCiphertext(aesData)) - throw new IllegalArgumentException("InputStream is not valid AES ciphertext!"); - - out.write(decrypt(key, resolveCiphertext(aesData))); - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public boolean supportSymmetricKey(byte[] symmetricKeyBytes) { - //验证输入字节数组长度=算法标识长度+密钥类型长度+密钥长度,字节数组的算法标识对应AES算法且密钥密钥类型是对称密钥 - return symmetricKeyBytes.length == SYMMETRICKEY_LENGTH && symmetricKeyBytes[0] == AES.CODE && symmetricKeyBytes[1] == SYMMETRIC_KEY.CODE; - } - - @Override - public SymmetricKey resolveSymmetricKey(byte[] symmetricKeyBytes) { - // 由框架调用 support 方法检查有效性,在此不做重复检查; - return new SymmetricKey(symmetricKeyBytes); - } - - @Override - public boolean supportCiphertext(byte[] ciphertextBytes) { - // 验证(输入字节数组长度-算法标识长度)是分组长度的整数倍,字节数组的算法标识对应AES算法 - return (ciphertextBytes.length - ALGORYTHM_BYTES) % BLOCK_SIZE == 0 && ciphertextBytes[0] == AES.CODE; - } - - @Override - public SymmetricCiphertext resolveCiphertext(byte[] ciphertextBytes) { - // 由框架调用 support 方法检查有效性,在此不做重复检查; - return new SymmetricCiphertext(ciphertextBytes); - } - - @Override - public CryptoAlgorithm getAlgorithm() { - return AES; - } - - @Override - public CryptoKey generateSymmetricKey() { - // 根据对应的标识和原始密钥生成相应的密钥数据 - return new SymmetricKey(AES, AESUtils.generateKey128_Bytes()); - } -} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/jni/asymmetric/JNIED25519SignatureFunction.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/jni/asymmetric/JNIED25519SignatureFunction.java deleted file mode 100644 index c03f8e93..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/jni/asymmetric/JNIED25519SignatureFunction.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.jd.blockchain.crypto.impl.jni.asymmetric; - -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.*; -import com.jd.blockchain.crypto.jniutils.asymmetric.JNIED25519Utils; -import com.jd.blockchain.utils.io.BytesUtils; - -import static com.jd.blockchain.crypto.CryptoAlgorithm.JNIED25519; -import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_BYTES; -import static com.jd.blockchain.crypto.CryptoKeyType.PRIV_KEY; -import static com.jd.blockchain.crypto.CryptoKeyType.PUB_KEY; -import static com.jd.blockchain.crypto.base.BaseCryptoKey.KEY_TYPE_BYTES; - -public class JNIED25519SignatureFunction implements SignatureFunction { - - private static final int PUBKEY_SIZE = 32; - private static final int PRIVKEY_SIZE = 32; - private static final int DIGEST_SIZE = 64; - - private static final int PUBKEY_LENGTH = ALGORYTHM_BYTES + KEY_TYPE_BYTES + PUBKEY_SIZE; - private static final int PRIVKEY_LENGTH = ALGORYTHM_BYTES + KEY_TYPE_BYTES + PRIVKEY_SIZE; - private static final int SIGNATUREDIGEST_LENGTH = ALGORYTHM_BYTES + DIGEST_SIZE; - - public JNIED25519SignatureFunction() { - } - - @Override - public SignatureDigest sign(PrivKey privKey, byte[] data) { - - if (data == null) - throw new IllegalArgumentException("This data is null!"); - - JNIED25519Utils ed25519 = new JNIED25519Utils(); - - byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); - byte[] rawPubKeyBytes = ed25519.getPubKey(rawPrivKeyBytes); - - // 验证原始私钥长度为256比特,即32字节 - if (rawPrivKeyBytes.length != PRIVKEY_SIZE) - throw new IllegalArgumentException("This key has wrong format!"); - - // 验证密钥数据的算法标识对应JNIED25519签名算法 - if (privKey.getAlgorithm() != JNIED25519) - throw new IllegalArgumentException("This key is not ED25519 private key!"); - - // 调用JNIED25519签名算法计算签名结果 - return new SignatureDigest(JNIED25519, ed25519.sign(data, rawPrivKeyBytes, rawPubKeyBytes)); - } - - @Override - public boolean verify(SignatureDigest digest, PubKey pubKey, byte[] data) { - - JNIED25519Utils ed25519 = new JNIED25519Utils(); - - byte[] rawPubKeyBytes = pubKey.getRawKeyBytes(); - byte[] rawDigestBytes = digest.getRawDigest(); - - // 验证原始公钥长度为256比特,即32字节 - if (rawPubKeyBytes.length != PUBKEY_SIZE) - throw new IllegalArgumentException("This key has wrong format!"); - - // 验证密钥数据的算法标识对应JNIED25519签名算法 - if (pubKey.getAlgorithm() != JNIED25519) - throw new IllegalArgumentException("This key is not ED25519 public key!"); - - // 验证密文数据的算法标识对应JNIED25519签名算法,并且原始摘要长度为64字节 - if (digest.getAlgorithm() != JNIED25519 || rawDigestBytes.length != DIGEST_SIZE) - throw new IllegalArgumentException("This is not ED25519 signature digest!"); - - // 调用JNIED25519验签算法验证签名结果 - return ed25519.verify(data, rawPubKeyBytes, rawDigestBytes); - } - - @Override - public byte[] retrievePubKeyBytes(byte[] privKeyBytes) { - - JNIED25519Utils ed25519 = new JNIED25519Utils(); - byte[] rawPrivKeyBytes = resolvePrivKey(privKeyBytes).getRawKeyBytes(); - byte[] rawPubKeyBytes = ed25519.getPubKey(rawPrivKeyBytes); - return new PubKey(JNIED25519,rawPubKeyBytes).toBytes(); - } - - @Override - public boolean supportPrivKey(byte[] privKeyBytes) { - // 验证输入字节数组长度=算法标识长度+密钥类型长度+密钥长度,密钥数据的算法标识对应JNIED25519签名算法,并且密钥类型是私钥 - return privKeyBytes.length == PRIVKEY_LENGTH - && privKeyBytes[0] == JNIED25519.CODE && privKeyBytes[1] == PRIV_KEY.CODE; - } - - @Override - public PrivKey resolvePrivKey(byte[] privKeyBytes) { - // 由框架调用 support 方法检查有效性,在此不做重复检查; - return new PrivKey(privKeyBytes); - } - - @Override - public boolean supportPubKey(byte[] pubKeyBytes) { - // 验证输入字节数组长度=算法标识长度+密钥类型长度+密钥长度,密钥数据的算法标识对应JNIED25519签名算法,并且密钥类型是公钥 - return pubKeyBytes.length == PUBKEY_LENGTH && - pubKeyBytes[0] == JNIED25519.CODE && pubKeyBytes[1] == PUB_KEY.CODE; - - } - - @Override - public PubKey resolvePubKey(byte[] pubKeyBytes) { - // 由框架调用 support 方法检查有效性,在此不做重复检查; - return new PubKey(pubKeyBytes); - } - - @Override - public boolean supportDigest(byte[] digestBytes) { - // 验证输入字节数组长度=算法标识长度+摘要长度,字节数组的算法标识对应JNIED25519算法 - return digestBytes.length == SIGNATUREDIGEST_LENGTH && digestBytes[0] == JNIED25519.CODE; - } - - @Override - public SignatureDigest resolveDigest(byte[] digestBytes) { - // 由框架调用 support 方法检查有效性,在此不做重复检查; - return new SignatureDigest(digestBytes); - } - - @Override - public CryptoAlgorithm getAlgorithm() { - return JNIED25519; - } - - @Override - public CryptoKeyPair generateKeyPair() { - - JNIED25519Utils ed25519 = new JNIED25519Utils(); - byte[] rawPrivKeyBytes = new byte[PRIVKEY_SIZE]; - byte[] rawPubKeyBytes = new byte[PUBKEY_SIZE]; - - // 调用JNIED25519算法的密钥生成算法生成公私钥对 - ed25519.generateKeyPair(rawPrivKeyBytes, rawPubKeyBytes); - - return new CryptoKeyPair(new PubKey(JNIED25519, rawPubKeyBytes), new PrivKey(JNIED25519, rawPrivKeyBytes)); - - } -} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/jni/hash/JNIRIPEMD160HashFunction.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/jni/hash/JNIRIPEMD160HashFunction.java deleted file mode 100644 index 72d0777e..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/jni/hash/JNIRIPEMD160HashFunction.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.jd.blockchain.crypto.impl.jni.hash; - -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.hash.HashFunction; -import com.jd.blockchain.crypto.jniutils.hash.JNIRIPEMD160Utils; - -import java.util.Arrays; - -import static com.jd.blockchain.crypto.CryptoAlgorithm.JNIRIPEMD160; -import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_BYTES; - -public class JNIRIPEMD160HashFunction implements HashFunction { - - private static final int DIGEST_BYTES = 160 / 8; - - private static final int DIGEST_LENGTH = ALGORYTHM_BYTES + DIGEST_BYTES; - - @Override - public CryptoAlgorithm getAlgorithm() { - return JNIRIPEMD160; - } - - @Override - public HashDigest hash(byte[] data) { - - if (data == null) - throw new IllegalArgumentException("This data is null!"); - - JNIRIPEMD160Utils ripemd160 = new JNIRIPEMD160Utils(); - byte[] digestBytes = ripemd160.hash(data); - return new HashDigest(JNIRIPEMD160, digestBytes); - } - - @Override - public boolean verify(HashDigest digest, byte[] data) { - HashDigest hashDigest = hash(data); - return Arrays.equals(hashDigest.toBytes(), digest.toBytes()); - } - - @Override - public boolean supportHashDigest(byte[] digestBytes) { - // 验证输入字节数组长度=算法标识长度+摘要长度,字节数组的算法标识对应JNIRIPEMD160算法 - return digestBytes.length == DIGEST_LENGTH && JNIRIPEMD160.CODE == digestBytes[0]; - } - - @Override - public HashDigest resolveHashDigest(byte[] digestBytes) { - // 由框架调用 support 方法检查有效性,在此不做重复检查; - return new HashDigest(digestBytes); - } -} - diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/jni/hash/JNISHA256HashFunction.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/jni/hash/JNISHA256HashFunction.java deleted file mode 100644 index bb6e84f0..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/jni/hash/JNISHA256HashFunction.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.jd.blockchain.crypto.impl.jni.hash; - -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.hash.HashFunction; -import com.jd.blockchain.crypto.jniutils.hash.JNISHA256Utils; - -import java.util.Arrays; - -import static com.jd.blockchain.crypto.CryptoAlgorithm.JNISHA256; -import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_BYTES; - -public class JNISHA256HashFunction implements HashFunction { - - private static final int DIGEST_BYTES = 256/8; - - private static final int DIGEST_LENGTH = ALGORYTHM_BYTES + DIGEST_BYTES; - - @Override - public CryptoAlgorithm getAlgorithm() { - return JNISHA256; - } - - @Override - public HashDigest hash(byte[] data) { - - if (data == null) - throw new IllegalArgumentException("This data is null!"); - - JNISHA256Utils sha256 = new JNISHA256Utils(); - byte[] digestBytes = sha256.hash(data); - return new HashDigest(JNISHA256,digestBytes); - } - - @Override - public boolean verify(HashDigest digest, byte[] data) { - HashDigest hashDigest=hash(data); - return Arrays.equals(hashDigest.toBytes(),digest.toBytes()); - } - - @Override - public boolean supportHashDigest(byte[] digestBytes) { - // 验证输入字节数组长度=算法标识长度+摘要长度,字节数组的算法标识对应JNISHA256算法 - return digestBytes.length == DIGEST_LENGTH && JNISHA256.CODE == digestBytes[0]; - } - - @Override - public HashDigest resolveHashDigest(byte[] hashDigestBytes) { - // 由框架调用 support 方法检查有效性,在此不做重复检查; - return new HashDigest(hashDigestBytes); - } - -} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/sm/asymmetric/SM2CryptoFunction.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/sm/asymmetric/SM2CryptoFunction.java deleted file mode 100644 index 3e9097f5..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/sm/asymmetric/SM2CryptoFunction.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.jd.blockchain.crypto.impl.sm.asymmetric; - -import com.jd.blockchain.crypto.Ciphertext; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.*; -import com.jd.blockchain.crypto.smutils.asymmetric.SM2Utils; -import org.bouncycastle.crypto.AsymmetricCipherKeyPair; -import org.bouncycastle.crypto.params.ECPrivateKeyParameters; -import org.bouncycastle.crypto.params.ECPublicKeyParameters; - -import static com.jd.blockchain.crypto.CryptoAlgorithm.SM2; -import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_BYTES; -import static com.jd.blockchain.crypto.CryptoKeyType.PRIV_KEY; -import static com.jd.blockchain.crypto.CryptoKeyType.PUB_KEY; -import static com.jd.blockchain.crypto.base.BaseCryptoKey.KEY_TYPE_BYTES; - -public class SM2CryptoFunction implements AsymmetricEncryptionFunction, SignatureFunction { - - private static final int ECPOINT_SIZE = 65; - private static final int PRIVKEY_SIZE = 32; - private static final int SIGNATUREDIGEST_SIZE = 64; - private static final int HASHDIGEST_SIZE = 32; - - private static final int PUBKEY_LENGTH = ALGORYTHM_BYTES + KEY_TYPE_BYTES + ECPOINT_SIZE; - private static final int PRIVKEY_LENGTH = ALGORYTHM_BYTES + KEY_TYPE_BYTES + PRIVKEY_SIZE; - private static final int SIGNATUREDIGEST_LENGTH = ALGORYTHM_BYTES + SIGNATUREDIGEST_SIZE; - - @Override - public Ciphertext encrypt(PubKey pubKey, byte[] data) { - - byte[] rawPubKeyBytes = pubKey.getRawKeyBytes(); - - // 验证原始公钥长度为65字节 - if (rawPubKeyBytes.length != ECPOINT_SIZE) - throw new IllegalArgumentException("This key has wrong format!"); - - // 验证密钥数据的算法标识对应SM2算法 - if (pubKey.getAlgorithm() != SM2) - throw new IllegalArgumentException("The is not sm2 public key!"); - - // 调用SM2加密算法计算密文 - return new AsymmetricCiphertext(SM2, SM2Utils.encrypt(data, rawPubKeyBytes)); - } - - @Override - public byte[] decrypt(PrivKey privKey, Ciphertext ciphertext) { - - byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); - byte[] rawCiphertextBytes = ciphertext.getRawCiphertext(); - - // 验证原始私钥长度为32字节 - if (rawPrivKeyBytes.length != PRIVKEY_SIZE) - throw new IllegalArgumentException("This key has wrong format!"); - - // 验证密钥数据的算法标识对应SM2算法 - if (privKey.getAlgorithm() != SM2) - throw new IllegalArgumentException("This key is not SM2 private key!"); - - // 验证密文数据的算法标识对应SM2签名算法,并且原始摘要长度为64字节 - if (ciphertext.getAlgorithm() != SM2 || rawCiphertextBytes.length < ECPOINT_SIZE + HASHDIGEST_SIZE) - throw new IllegalArgumentException("This is not SM2 ciphertext!"); - - // 调用SM2解密算法得到明文结果 - return SM2Utils.decrypt(rawCiphertextBytes,rawPrivKeyBytes); - } - - @Override - public byte[] retrievePubKeyBytes(byte[] privKeyBytes) { - - byte[] rawPrivKeyBytes = resolvePrivKey(privKeyBytes).getRawKeyBytes(); - byte[] rawPubKeyBytes = SM2Utils.retrievePublicKey(rawPrivKeyBytes); - return new PubKey(SM2,rawPubKeyBytes).toBytes(); - } - - @Override - public boolean supportPrivKey(byte[] privKeyBytes) { - // 验证输入字节数组长度=算法标识长度+密钥类型长度+密钥长度,密钥数据的算法标识对应SM2算法,并且密钥类型是私钥 - return privKeyBytes.length == PRIVKEY_LENGTH && privKeyBytes[0] == SM2.CODE && privKeyBytes[1] == PRIV_KEY.CODE; - } - - @Override - public PrivKey resolvePrivKey(byte[] privKeyBytes) { - // 由框架调用 support 方法检查有效性,在此不做重复检查; - return new PrivKey(privKeyBytes); - } - - @Override - public boolean supportPubKey(byte[] pubKeyBytes) { - // 验证输入字节数组长度=算法标识长度+密钥类型长度+椭圆曲线点长度,密钥数据的算法标识对应SM2算法,并且密钥类型是公钥 - return pubKeyBytes.length == PUBKEY_LENGTH && pubKeyBytes[0] == SM2.CODE && pubKeyBytes[1] == PUB_KEY.CODE; - } - - @Override - public PubKey resolvePubKey(byte[] pubKeyBytes) { - // 由框架调用 support 方法检查有效性,在此不做重复检查; - return new PubKey(pubKeyBytes); - } - - @Override - public boolean supportCiphertext(byte[] ciphertextBytes) { - // 验证输入字节数组长度>=算法标识长度+椭圆曲线点长度+哈希长度,字节数组的算法标识对应SM2算法 - return ciphertextBytes.length >= ALGORYTHM_BYTES + ECPOINT_SIZE + HASHDIGEST_SIZE && ciphertextBytes[0] == SM2.CODE; - } - - @Override - public AsymmetricCiphertext resolveCiphertext(byte[] ciphertextBytes) { - // 由框架调用 support 方法检查有效性,在此不做重复检查; - return new AsymmetricCiphertext(ciphertextBytes); - } - - @Override - public SignatureDigest sign(PrivKey privKey, byte[] data) { - - byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); - - // 验证原始私钥长度为256比特,即32字节 - if (rawPrivKeyBytes.length != PRIVKEY_SIZE) - throw new IllegalArgumentException("This key has wrong format!"); - - // 验证密钥数据的算法标识对应SM2签名算法 - if (privKey.getAlgorithm() != SM2) - throw new IllegalArgumentException("This key is not SM2 private key!"); - - // 调用SM2签名算法计算签名结果 - return new SignatureDigest(SM2, SM2Utils.sign(data,rawPrivKeyBytes)); - } - - @Override - public boolean verify(SignatureDigest digest, PubKey pubKey, byte[] data) { - - byte[] rawPubKeyBytes = pubKey.getRawKeyBytes(); - byte[] rawDigestBytes = digest.getRawDigest(); - - // 验证原始公钥长度为520比特,即65字节 - if (rawPubKeyBytes.length != ECPOINT_SIZE) - throw new IllegalArgumentException("This key has wrong format!"); - - // 验证密钥数据的算法标识对应SM2签名算法 - if (pubKey.getAlgorithm() != SM2) - throw new IllegalArgumentException("This key is not SM2 public key!"); - - // 验证签名数据的算法标识对应SM2签名算法,并且原始签名长度为64字节 - if (digest.getAlgorithm() != SM2 || rawDigestBytes.length != SIGNATUREDIGEST_SIZE) - throw new IllegalArgumentException("This is not SM2 signature digest!"); - - // 调用SM2验签算法验证签名结果 - return SM2Utils.verify(data, rawPubKeyBytes, rawDigestBytes); - } - - @Override - public boolean supportDigest(byte[] digestBytes) { - // 验证输入字节数组长度=算法标识长度+签名长度,字节数组的算法标识对应SM2算法 - return digestBytes.length == SIGNATUREDIGEST_LENGTH && digestBytes[0] == SM2.CODE; - } - - @Override - public SignatureDigest resolveDigest(byte[] digestBytes) { - // 由框架调用 support 方法检查有效性,在此不做重复检查; - return new SignatureDigest(digestBytes); - } - - @Override - public CryptoAlgorithm getAlgorithm() { - return SM2; - } - - @Override - public CryptoKeyPair generateKeyPair() { - - // 调用SM2算法的密钥生成算法生成公私钥对priKey和pubKey,返回密钥对 - AsymmetricCipherKeyPair keyPair = SM2Utils.generateKeyPair(); - ECPrivateKeyParameters ecPriv = (ECPrivateKeyParameters) keyPair.getPrivate(); - ECPublicKeyParameters ecPub = (ECPublicKeyParameters) keyPair.getPublic(); - - byte[] privKeyBytesD = ecPriv.getD().toByteArray(); - byte[] privKeyBytes = new byte[PRIVKEY_SIZE]; - if (privKeyBytesD.length > PRIVKEY_SIZE) - System.arraycopy(privKeyBytesD,privKeyBytesD.length-PRIVKEY_SIZE,privKeyBytes,0,PRIVKEY_SIZE); - else System.arraycopy(privKeyBytesD,0,privKeyBytes,PRIVKEY_SIZE-privKeyBytesD.length,privKeyBytesD.length); - -// byte[] pubKeyBytesX = ecPub.getQ().getAffineXCoord().getEncoded(); -// byte[] pubKeyBytesY = ecPub.getQ().getAffineYCoord().getEncoded(); -// byte[] pubKeyBytes = new byte[ECPOINT_SIZE]; -// System.arraycopy(Hex.decode("04"),0,pubKeyBytes,0,1); -// System.arraycopy(pubKeyBytesX,0,pubKeyBytes,1,32); -// System.arraycopy(pubKeyBytesY,0,pubKeyBytes,1+32,32); - byte[] pubKeyBytes = ecPub.getQ().getEncoded(false); - - return new CryptoKeyPair(new PubKey(SM2,pubKeyBytes),new PrivKey(SM2,privKeyBytes)); - } -} - diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/sm/symmetric/SM4SymmetricEncryptionFunction.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/sm/symmetric/SM4SymmetricEncryptionFunction.java deleted file mode 100644 index 3bba6efa..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/sm/symmetric/SM4SymmetricEncryptionFunction.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.jd.blockchain.crypto.impl.sm.symmetric; - -import com.jd.blockchain.crypto.Ciphertext; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.CryptoKey; -import com.jd.blockchain.crypto.smutils.symmetric.SM4Utils; -import com.jd.blockchain.crypto.symmetric.SymmetricCiphertext; -import com.jd.blockchain.crypto.symmetric.SymmetricEncryptionFunction; -import com.jd.blockchain.crypto.symmetric.SymmetricKey; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import static com.jd.blockchain.crypto.CryptoAlgorithm.SM4; -import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_BYTES; -import static com.jd.blockchain.crypto.CryptoKeyType.SYMMETRIC_KEY; -import static com.jd.blockchain.crypto.base.BaseCryptoKey.KEY_TYPE_BYTES; - -public class SM4SymmetricEncryptionFunction implements SymmetricEncryptionFunction { - - private static final int KEY_SIZE = 16; - private static final int BLOCK_SIZE = 16; - - private static final int SYMMETRICKEY_LENGTH = ALGORYTHM_BYTES + KEY_TYPE_BYTES + KEY_SIZE; - - @Override - public Ciphertext encrypt(SymmetricKey key, byte[] data) { - - byte[] rawKeyBytes = key.getRawKeyBytes(); - - // 验证原始密钥长度为128比特,即16字节 - if (rawKeyBytes.length != KEY_SIZE) - throw new IllegalArgumentException("This key has wrong format!"); - - // 验证密钥数据的算法标识对应SM4算法 - if (key.getAlgorithm() != SM4) - throw new IllegalArgumentException("The is not SM4 symmetric key!"); - - // 调用底层SM4算法并计算密文数据 - return new SymmetricCiphertext(SM4, SM4Utils.encrypt(data, rawKeyBytes)); - } - - @Override - public void encrypt(SymmetricKey key, InputStream in, OutputStream out) { - - // 读输入流得到明文,加密,密文数据写入输出流 - try { - byte[] sm4Data = new byte[in.available()]; - in.read(sm4Data); - in.close(); - - out.write(encrypt(key, sm4Data).toBytes()); - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public byte[] decrypt(SymmetricKey key, Ciphertext ciphertext) { - - byte[] rawKeyBytes = key.getRawKeyBytes(); - byte[] rawCiphertextBytes = ciphertext.getRawCiphertext(); - - // 验证原始密钥长度为128比特,即16字节 - if (rawKeyBytes.length != KEY_SIZE) - throw new IllegalArgumentException("This key has wrong format!"); - - // 验证密钥数据的算法标识对应SM4算法 - if (key.getAlgorithm().CODE != SM4.CODE) - throw new IllegalArgumentException("The is not SM4 symmetric key!"); - - // 验证原始密文长度为分组长度的整数倍 - if (rawCiphertextBytes.length % BLOCK_SIZE != 0) - throw new IllegalArgumentException("This ciphertext has wrong format!"); - - // 验证密文数据算法标识对应SM4算法 - if (ciphertext.getAlgorithm() != SM4) - throw new IllegalArgumentException("This is not SM4 ciphertext!"); - - // 调用底层SM4算法解密,得到明文 - try { - return SM4Utils.decrypt(rawCiphertextBytes, rawKeyBytes); - } catch (Exception e) { - e.printStackTrace(); - } - - throw new IllegalArgumentException("Decrypting process fails!"); - } - - @Override - public void decrypt(SymmetricKey key, InputStream in, OutputStream out) { - - // 读输入流得到密文数据,解密,明文写入输出流 - try { - byte[] sm4Data = new byte[in.available()]; - in.read(sm4Data); - in.close(); - - if (!supportCiphertext(sm4Data)) - throw new IllegalArgumentException("InputStream is not valid SM4 ciphertext!"); - - out.write(decrypt(key, resolveCiphertext(sm4Data))); - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public boolean supportSymmetricKey(byte[] symmetricKeyBytes) { - //验证输入字节数组长度=算法标识长度+密钥类型长度+密钥长度,字节数组的算法标识对应SM4算法且密钥密钥类型是对称密钥 - return symmetricKeyBytes.length == SYMMETRICKEY_LENGTH && symmetricKeyBytes[0] == SM4.CODE && symmetricKeyBytes[1] == SYMMETRIC_KEY.CODE; - } - - @Override - public SymmetricKey resolveSymmetricKey(byte[] symmetricKeyBytes) { - // 由框架调用 support 方法检查有效性,在此不做重复检查; - return new SymmetricKey(symmetricKeyBytes); - } - - @Override - public boolean supportCiphertext(byte[] ciphertextBytes) { - // 验证(输入字节数组长度-算法标识长度)是分组长度的整数倍,字节数组的算法标识对应SM4算法 - return (ciphertextBytes.length - ALGORYTHM_BYTES) % BLOCK_SIZE == 0 && ciphertextBytes[0] == SM4.CODE; - } - - @Override - public SymmetricCiphertext resolveCiphertext(byte[] ciphertextBytes) { - // 由框架调用 support 方法检查有效性,在此不做重复检查; - return new SymmetricCiphertext(ciphertextBytes); - } - - @Override - public CryptoAlgorithm getAlgorithm() { - return SM4; - } - - @Override - public CryptoKey generateSymmetricKey() { - // 根据对应的标识和原始密钥生成相应的密钥数据 - return new SymmetricKey(SM4, SM4Utils.generateKey()); - } -} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/asymmetric/JNIED25519Utils.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/asymmetric/JNIED25519Utils.java deleted file mode 100644 index e1b4bb3b..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/asymmetric/JNIED25519Utils.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.jd.blockchain.crypto.jniutils.asymmetric; - - -import com.jd.blockchain.crypto.jniutils.hash.JNISHA256Utils; - -import java.util.Objects; - -public class JNIED25519Utils { - - /* load c library */ - static { - //differentiate OS - String osName = System.getProperty("os.name").toLowerCase(); - String path=""; - // Windows OS - if (osName.startsWith("windows")){ - path = Objects.requireNonNull(JNIED25519Utils.class.getClassLoader().getResource("com/jd/blockchain/crypto/jniutils/asymmetric/c_ed25519.dll")).getPath(); - } - // Linux OS - else if (osName.contains("linux")){ - path = Objects.requireNonNull(JNIED25519Utils.class.getClassLoader().getResource("com/jd/blockchain/crypto/jniutils/asymmetric/libc_ed25519.so")).getPath(); - } - // Mac OS - else if (osName.contains("mac")){ - path = Objects.requireNonNull(JNISHA256Utils.class.getClassLoader().getResource("com/jd/blockchain/crypto/jniutils/asymmetric/libc_ed25519.jnilib")).getPath(); - } - - System.load(path); - } - - /* define java native method */ - public native void generateKeyPair(byte[] privKey, byte[] pubKey); - public native byte[] getPubKey(byte[] privKey); - public native byte[] sign(byte[] msg, byte[] privKey, byte[] pubKey); - public native boolean verify(byte[] msg, byte[] pubKey, byte[] signature); - } - diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/hash/JNIMBSHA256Utils.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/hash/JNIMBSHA256Utils.java deleted file mode 100644 index 5518117d..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/hash/JNIMBSHA256Utils.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.jd.blockchain.crypto.jniutils.hash; - -import java.util.Objects; - -public class JNIMBSHA256Utils { - /* load c library */ - static{ - //differentiate OS - String osName = System.getProperty("os.name").toLowerCase(); - String pathOfSo; - String pathOfSo2; - if (osName.contains("linux")){ - pathOfSo = Objects.requireNonNull(JNIMBSHA256Utils.class.getClassLoader().getResource("com/jd/blockchain/crypto/jniutils/hash/libc_mbsha256.so")).getPath(); - pathOfSo2 = Objects.requireNonNull(JNIMBSHA256Utils.class.getClassLoader().getResource("com/jd/blockchain/crypto/jniutils/hash/libisal_crypto.so.2")).getPath(); - } - else throw new IllegalArgumentException("The JNIMBSHA256 implementation is not supported in this Operation System!"); - - System.load(pathOfSo2); - System.load(pathOfSo); - } - - /* define java native method */ - public native byte[][] multiBufferHash(byte[][] multiMsgs); -} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/hash/JNIRIPEMD160Utils.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/hash/JNIRIPEMD160Utils.java deleted file mode 100644 index d9e0ac7a..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/hash/JNIRIPEMD160Utils.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.jd.blockchain.crypto.jniutils.hash; - -import java.util.Objects; - -public class JNIRIPEMD160Utils { - - /* load c library */ - static { - //differentiate OS - String osName = System.getProperty("os.name").toLowerCase(); - String path=""; - // Windows OS - if (osName.startsWith("windows")){ - path = Objects.requireNonNull(JNIRIPEMD160Utils.class.getClassLoader().getResource("com/jd/blockchain/crypto/jniutils/hash/c_ripemd160.dll")).getPath(); - } - // Linux OS - else if (osName.contains("linux")){ - path = Objects.requireNonNull(JNIRIPEMD160Utils.class.getClassLoader().getResource("com/jd/blockchain/crypto/jniutils/hash/libc_ripemd160.so")).getPath(); - } - // Mac OS - else if (osName.contains("mac")){ - path = Objects.requireNonNull(JNISHA256Utils.class.getClassLoader().getResource("com/jd/blockchain/crypto/jniutils/hash/libc_ripemd160.jnilib")).getPath(); - } - - System.load(path); - } - - /* define java native method */ - public native byte[] hash(byte[] msg); -} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/hash/JNISHA256Utils.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/hash/JNISHA256Utils.java deleted file mode 100644 index a26ef912..00000000 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/jniutils/hash/JNISHA256Utils.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.jd.blockchain.crypto.jniutils.hash; - -import java.util.Objects; - -public class JNISHA256Utils { - - /* load c library */ - static { - //differentiate OS - String osName = System.getProperty("os.name").toLowerCase(); - String path=""; - // Windows OS - if (osName.startsWith("windows")){ - path = Objects.requireNonNull(JNISHA256Utils.class.getClassLoader().getResource("com/jd/blockchain/crypto/jniutils/hash/c_sha256.dll")).getPath(); - } - // Linux OS - else if (osName.contains("linux")){ - path = Objects.requireNonNull(JNISHA256Utils.class.getClassLoader().getResource("com/jd/blockchain/crypto/jniutils/hash/libc_sha256.so")).getPath(); - } - // Mac OS - else if (osName.contains("mac")){ - path = Objects.requireNonNull(JNISHA256Utils.class.getClassLoader().getResource("com/jd/blockchain/crypto/jniutils/hash/libc_sha256.jnilib")).getPath(); - } - - System.load(path); - } - - /* define java native method */ - public native byte[] hash(byte[] msg); -} diff --git a/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/serializes/ByteArrayObjectJsonDeserializer.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/serialize/ByteArrayObjectDeserializer.java similarity index 63% rename from source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/serializes/ByteArrayObjectJsonDeserializer.java rename to source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/serialize/ByteArrayObjectDeserializer.java index dc8cb119..dcbaf0e8 100644 --- a/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/serializes/ByteArrayObjectJsonDeserializer.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/serialize/ByteArrayObjectDeserializer.java @@ -1,10 +1,10 @@ -package com.jd.blockchain.web.serializes; +package com.jd.blockchain.crypto.serialize; import com.alibaba.fastjson.parser.DefaultJSONParser; import com.alibaba.fastjson.parser.JSONToken; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.utils.Bytes; @@ -15,22 +15,22 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Type; import java.util.Map; -public class ByteArrayObjectJsonDeserializer extends JavaBeanDeserializer { +public class ByteArrayObjectDeserializer extends JavaBeanDeserializer { - private ByteArrayObjectJsonDeserializer(Class clazz) { + private ByteArrayObjectDeserializer(Class clazz) { super(ParserConfig.global, clazz); } - public static ByteArrayObjectJsonDeserializer getInstance(Class clazz) { - return new ByteArrayObjectJsonDeserializer(clazz); + public static ByteArrayObjectDeserializer getInstance(Class clazz) { + return new ByteArrayObjectDeserializer(clazz); } @SuppressWarnings("unchecked") @Override public T deserialze(DefaultJSONParser parser, Type type, Object fieldName) { if (type instanceof Class && clazz.isAssignableFrom((Class) type)) { - String parseText = parser.parseObject(String.class); - byte[] hashBytes = Base58Utils.decode(parseText); + String base58Str = parser.parseObject(String.class); + byte[] hashBytes = Base58Utils.decode(base58Str); if (clazz == HashDigest.class) { return (T) new HashDigest(hashBytes); } else if (clazz == PubKey.class) { @@ -42,29 +42,6 @@ public class ByteArrayObjectJsonDeserializer extends JavaBeanDeserializer { } else if (clazz == BytesSlice.class) { return (T) new BytesSlice(hashBytes); } - -// else if (clazz == BytesValue.class) { -// ByteArrayObjectJsonSerializer.BytesValueJson valueJson = JSON.parseObject(parseText, ByteArrayObjectJsonSerializer.BytesValueJson.class); -// DataType dataType = valueJson.getType(); -// Object dataVal = valueJson.getValue(); -// byte[] bytes = null; -// switch (dataType) { -// case BYTES: -// bytes = ByteArray.fromHex((String) dataVal); -// break; -// case TEXT: -// bytes = ((String) dataVal).getBytes(); -// break; -// case INT64: -// bytes = BytesUtils.toBytes((Long) dataVal); -// break; -// case JSON: -// bytes = ((String) dataVal).getBytes(); -// break; -// } -// BytesValue bytesValue = new BytesValueImpl(dataType, bytes); -// return (T) bytesValue; -// } } return (T) parser.parse(fieldName); } @@ -77,7 +54,7 @@ public class ByteArrayObjectJsonDeserializer extends JavaBeanDeserializer { for (Map.Entry entry : map.entrySet()) { Object value = entry.getValue(); if (value instanceof String) { - byte[] hashBytes = Base58Utils.decode((String) value); + byte[] hashBytes = Base58Utils.decode((String)value); if (clazz == HashDigest.class) { return new HashDigest(hashBytes); } else if (clazz == PubKey.class) { diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/serialize/ByteArrayObjectSerializer.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/serialize/ByteArrayObjectSerializer.java new file mode 100644 index 00000000..cab05ffa --- /dev/null +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/serialize/ByteArrayObjectSerializer.java @@ -0,0 +1,62 @@ +package com.jd.blockchain.crypto.serialize; + +import com.alibaba.fastjson.serializer.JSONSerializer; +import com.alibaba.fastjson.serializer.ObjectSerializer; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.crypto.asymmetric.SignatureDigest; +import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.utils.Bytes; +import com.jd.blockchain.utils.io.BytesSlice; + +import java.io.IOException; +import java.lang.reflect.Type; + +public class ByteArrayObjectSerializer implements ObjectSerializer { + + private Class clazz; + + private ByteArrayObjectSerializer(Class clazz) { + this.clazz = clazz; + } + + public static ByteArrayObjectSerializer getInstance(Class clazz) { + return new ByteArrayObjectSerializer(clazz); + } + + @Override + public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) { + if (object.getClass() != clazz) { + serializer.writeNull(); + return; + } + if (object instanceof HashDigest) { + serializer.write(new HashDigestJson(((HashDigest) object).toBase58())); + } else if (object instanceof PubKey) { + serializer.write(new HashDigestJson(((PubKey) object).toBase58())); + } else if (object instanceof SignatureDigest) { + serializer.write(new HashDigestJson(((SignatureDigest) object).toBase58())); + } else if (object instanceof Bytes) { + serializer.write(new HashDigestJson(((Bytes) object).toBase58())); + } else if (object instanceof BytesSlice) { + byte[] bytes = ((BytesSlice) object).toBytes(); + serializer.write(new HashDigestJson(new String(bytes))); + } + } + + private static class HashDigestJson { + + String value; + + public HashDigestJson(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricCiphertext.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricCiphertext.java index 39878e38..b5b3834a 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricCiphertext.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricCiphertext.java @@ -1,8 +1,8 @@ package com.jd.blockchain.crypto.symmetric; +import com.jd.blockchain.crypto.BaseCryptoBytes; import com.jd.blockchain.crypto.Ciphertext; import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.base.BaseCryptoBytes; public class SymmetricCiphertext extends BaseCryptoBytes implements Ciphertext { @@ -23,7 +23,7 @@ public class SymmetricCiphertext extends BaseCryptoBytes implements Ciphertext { @Override protected boolean support(CryptoAlgorithm algorithm) { - return algorithm.isSymmetric(); + return CryptoAlgorithm.isEncryptionAlgorithm(algorithm) && CryptoAlgorithm.hasSymmetricKey(algorithm); } @Override diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricCryptography.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricCryptography.java index 4de371a2..89f587c8 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricCryptography.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricCryptography.java @@ -1,34 +1,35 @@ -package com.jd.blockchain.crypto.symmetric; - -import com.jd.blockchain.crypto.Ciphertext; -import com.jd.blockchain.crypto.CryptoAlgorithm; - -public interface SymmetricCryptography { - - /** - * 生成秘钥对; - * - * @param algorithm - * @return - */ - SymmetricKey generateKey(CryptoAlgorithm algorithm); - - /** - * 获取签名方法; - * - * @param algorithm - * @return - */ - SymmetricEncryptionFunction getSymmetricEncryptionFunction(CryptoAlgorithm algorithm); - - byte[] decrypt(byte[] symmetricKeyBytes,byte[] ciphertextBytes); - - Ciphertext resolveCiphertext(byte[] ciphertextBytes); - - Ciphertext tryResolveCiphertext(byte[] ciphertextBytes); - - SymmetricKey resolveSymmetricKey(byte[] symmetricKeyBytes); - - SymmetricKey tryResolveSymmetricKey(byte[] symmetricKeyBytes); - -} +//package com.jd.blockchain.crypto.symmetric; +// +//import com.jd.blockchain.crypto.Ciphertext; +//import com.jd.blockchain.crypto.CryptoAlgorithm; +//import com.jd.blockchain.crypto.SymmetricKey; +// +//public interface SymmetricCryptography { +// +// /** +// * 生成密钥对; +// * +// * @param algorithm +// * @return +// */ +// SymmetricKey generateKey(CryptoAlgorithm algorithm); +// +// /** +// * 获取签名方法; +// * +// * @param algorithm +// * @return +// */ +// SymmetricEncryptionFunction getSymmetricEncryptionFunction(CryptoAlgorithm algorithm); +// +// byte[] decrypt(byte[] symmetricKeyBytes,byte[] ciphertextBytes); +// +// Ciphertext resolveCiphertext(byte[] ciphertextBytes); +// +// Ciphertext tryResolveCiphertext(byte[] ciphertextBytes); +// +// SymmetricKey resolveSymmetricKey(byte[] symmetricKeyBytes); +// +// SymmetricKey tryResolveSymmetricKey(byte[] symmetricKeyBytes); +// +//} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricEncryptionFunction.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricEncryptionFunction.java index 5e97958c..856975ae 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricEncryptionFunction.java +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/symmetric/SymmetricEncryptionFunction.java @@ -6,6 +6,7 @@ import java.io.OutputStream; import com.jd.blockchain.crypto.Ciphertext; import com.jd.blockchain.crypto.CryptoFunction; import com.jd.blockchain.crypto.CryptoSymmetricKeyGenerator; +import com.jd.blockchain.crypto.SymmetricKey; public interface SymmetricEncryptionFunction extends CryptoSymmetricKeyGenerator, CryptoFunction { @@ -37,7 +38,9 @@ public interface SymmetricEncryptionFunction extends CryptoSymmetricKeyGenerator byte[] decrypt(SymmetricKey key, Ciphertext ciphertext); /** - * 解密密文的输入流,把明文写入输出流; + * 解密密文的输入流,把明文写入输出流;
+ * + * 注:实现者不应在方法内部关闭参数指定的输入输出流; * * @param key 密钥; * @param in 密文的输入流; @@ -45,7 +48,6 @@ public interface SymmetricEncryptionFunction extends CryptoSymmetricKeyGenerator */ void decrypt(SymmetricKey key, InputStream in, OutputStream out); - /** * 校验对称密钥格式是否满足要求; * diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/asymmetric/AsymmtricCryptographyImplTest.java b/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/asymmetric/AsymmtricCryptographyImplTest.java deleted file mode 100644 index eee63899..00000000 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/asymmetric/AsymmtricCryptographyImplTest.java +++ /dev/null @@ -1,946 +0,0 @@ -package test.com.jd.blockchain.crypto.asymmetric; - -import com.jd.blockchain.crypto.Ciphertext; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.CryptoException; -import com.jd.blockchain.crypto.CryptoKeyType; -import com.jd.blockchain.crypto.asymmetric.*; -import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; -import com.jd.blockchain.utils.io.BytesUtils; - -import org.junit.Test; - -import java.util.Random; - -import static com.jd.blockchain.crypto.CryptoKeyType.PRIV_KEY; -import static com.jd.blockchain.crypto.CryptoKeyType.PUB_KEY; -import static org.junit.Assert.*; -import static org.junit.Assert.assertNotNull; - -public class AsymmtricCryptographyImplTest { - - @Test - public void testGenerateKeyPair() { - - AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); - - //test ED25519 - CryptoAlgorithm algorithm = CryptoAlgorithm.ED25519; - CryptoKeyPair keyPair = asymmetricCrypto.generateKeyPair(algorithm); - - assertNotNull(keyPair); - - PubKey pubKey = keyPair.getPubKey(); - PrivKey privKey = keyPair.getPrivKey(); - - assertNotNull(pubKey); - assertNotNull(privKey); - - assertEquals(algorithm,pubKey.getAlgorithm()); - assertEquals(algorithm,privKey.getAlgorithm()); - - assertEquals(32,pubKey.getRawKeyBytes().length); - assertEquals(32,privKey.getRawKeyBytes().length); - - byte[] pubKeyBytes = pubKey.toBytes(); - byte[] privKeyBytes = privKey.toBytes(); - - assertEquals(32+1+1,pubKeyBytes.length); - assertEquals(32+1+1,privKeyBytes.length); - - assertEquals(CryptoAlgorithm.ED25519.CODE,pubKeyBytes[0]); - assertEquals(CryptoAlgorithm.ED25519.CODE,privKeyBytes[0]); - assertEquals(CryptoAlgorithm.ED25519, CryptoAlgorithm.valueOf(pubKey.getAlgorithm().CODE)); - assertEquals(CryptoAlgorithm.ED25519, CryptoAlgorithm.valueOf(privKey.getAlgorithm().CODE)); - - assertEquals(pubKey.getKeyType().CODE,pubKeyBytes[1]); - assertEquals(privKey.getKeyType().CODE,privKeyBytes[1]); - - - //test SM2 - algorithm = CryptoAlgorithm.SM2; - keyPair = asymmetricCrypto.generateKeyPair(algorithm); - - assertNotNull(keyPair); - - pubKey = keyPair.getPubKey(); - privKey = keyPair.getPrivKey(); - - assertNotNull(pubKey); - assertNotNull(privKey); - - assertEquals(algorithm,pubKey.getAlgorithm()); - assertEquals(algorithm,privKey.getAlgorithm()); - - assertEquals(65,pubKey.getRawKeyBytes().length); - assertEquals(32,privKey.getRawKeyBytes().length); - - pubKeyBytes = pubKey.toBytes(); - privKeyBytes = privKey.toBytes(); - - assertEquals(32+1+1,privKeyBytes.length); - assertEquals(65+1+1,pubKeyBytes.length); - - assertEquals(CryptoAlgorithm.SM2.CODE,pubKeyBytes[0]); - assertEquals(CryptoAlgorithm.SM2.CODE,privKeyBytes[0]); - assertEquals(CryptoAlgorithm.SM2, CryptoAlgorithm.valueOf(pubKey.getAlgorithm().CODE)); - assertEquals(CryptoAlgorithm.SM2, CryptoAlgorithm.valueOf(privKey.getAlgorithm().CODE)); - - assertEquals(pubKey.getKeyType().CODE,pubKeyBytes[1]); - assertEquals(privKey.getKeyType().CODE,privKeyBytes[1]); - - - //test JNIED25519 - algorithm = CryptoAlgorithm.JNIED25519; - keyPair = asymmetricCrypto.generateKeyPair(algorithm); - - assertNotNull(keyPair); - - pubKey = keyPair.getPubKey(); - privKey = keyPair.getPrivKey(); - - assertNotNull(pubKey); - assertNotNull(privKey); - - assertEquals(algorithm,pubKey.getAlgorithm()); - assertEquals(algorithm,privKey.getAlgorithm()); - - assertEquals(32,pubKey.getRawKeyBytes().length); - assertEquals(32,privKey.getRawKeyBytes().length); - - pubKeyBytes = pubKey.toBytes(); - privKeyBytes = privKey.toBytes(); - - assertEquals(32+1+1,pubKeyBytes.length); - assertEquals(32+1+1,privKeyBytes.length); - - assertEquals(CryptoAlgorithm.JNIED25519.CODE,pubKeyBytes[0]); - assertEquals(CryptoAlgorithm.JNIED25519.CODE,privKeyBytes[0]); - assertEquals(CryptoAlgorithm.JNIED25519, CryptoAlgorithm.valueOf(pubKey.getAlgorithm().CODE)); - assertEquals(CryptoAlgorithm.JNIED25519, CryptoAlgorithm.valueOf(privKey.getAlgorithm().CODE)); - - assertEquals(pubKey.getKeyType().CODE,pubKeyBytes[1]); - assertEquals(privKey.getKeyType().CODE,privKeyBytes[1]); - } - - @Test - public void testGetSignatureFunction() { - - AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); - Random random = new Random(); - - - //test ED25519 - CryptoAlgorithm algorithm = CryptoAlgorithm.ED25519; - - // 测试256字节的消息进行签名 - byte[] data = new byte[256]; - random.nextBytes(data); - verifyGetSignatureFunction(asymmetricCrypto,algorithm,data,32,32,64,null); - - //错误的算法标识 - verifyGetSignatureFunction(asymmetricCrypto,CryptoAlgorithm.AES,data,32,32,64,IllegalArgumentException.class); - - data = null; - verifyGetSignatureFunction(asymmetricCrypto,algorithm,data,32,32,64,NullPointerException.class); - - - //test SM2 - algorithm = CryptoAlgorithm.SM2; - - // 测试256字节的消息进行签名 - data = new byte[256]; - random.nextBytes(data); - verifyGetSignatureFunction(asymmetricCrypto,algorithm,data,65,32,64,null); - - //错误的算法标识 - verifyGetSignatureFunction(asymmetricCrypto,CryptoAlgorithm.AES,data,65,32,64,IllegalArgumentException.class); - - data = null; - verifyGetSignatureFunction(asymmetricCrypto,algorithm,data,65,32,64,NullPointerException.class); - - - //test JNNIED25519 - algorithm = CryptoAlgorithm.JNIED25519; - - // 测试256字节的消息进行签名 - data = new byte[256]; - random.nextBytes(data); - verifyGetSignatureFunction(asymmetricCrypto,algorithm,data,32,32,64,null); - - //错误的算法标识 - verifyGetSignatureFunction(asymmetricCrypto,CryptoAlgorithm.AES,data,32,32,64,IllegalArgumentException.class); - - data = null; - verifyGetSignatureFunction(asymmetricCrypto,algorithm,data,32,32,64,IllegalArgumentException.class); - } - - private void verifyGetSignatureFunction(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, byte[] data, - int expectedPubKeyLength, int expectedPrivKeyLength, - int expectedSignatureDigestLength, Class expectedException){ - - //初始化一个异常 - Exception actualEx = null; - - try { - SignatureFunction sf = asymmetricCrypto.getSignatureFunction(algorithm); - - assertNotNull(sf); - - CryptoKeyPair keyPair = sf.generateKeyPair(); - PubKey pubKey = keyPair.getPubKey(); - PrivKey privKey = keyPair.getPrivKey(); - byte[] rawPubKeyBytes = pubKey.getRawKeyBytes(); - byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); - byte[] pubKeyBytes = pubKey.toBytes(); - byte[] privKeyBytes = privKey.toBytes(); - - assertEquals(algorithm, pubKey.getAlgorithm()); - assertEquals(algorithm, privKey.getAlgorithm()); - assertEquals(expectedPubKeyLength,rawPubKeyBytes.length); - assertEquals(expectedPrivKeyLength,rawPrivKeyBytes.length); - - assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{CryptoKeyType.PUB_KEY.CODE},rawPubKeyBytes), pubKeyBytes); - assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{CryptoKeyType.PRIV_KEY.CODE},rawPrivKeyBytes), privKeyBytes); - - SignatureDigest signatureDigest = sf.sign(privKey,data); - byte[] rawDigest = signatureDigest.getRawDigest(); - - assertEquals(algorithm,signatureDigest.getAlgorithm()); - assertEquals(expectedSignatureDigestLength,rawDigest.length); - byte[] signatureDigestBytes = signatureDigest.toBytes(); - assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},rawDigest),signatureDigestBytes); - - assertTrue(signatureDigest.equals(signatureDigest)); - assertEquals(signatureDigest.hashCode(),signatureDigest.hashCode()); - - assertTrue(sf.verify(signatureDigest,pubKey,data)); - - assertTrue(sf.supportPubKey(pubKeyBytes)); - assertTrue(sf.supportPrivKey(privKeyBytes)); - assertTrue(sf.supportDigest(signatureDigestBytes)); - - assertEquals(pubKey,sf.resolvePubKey(pubKeyBytes)); - assertEquals(privKey,sf.resolvePrivKey(privKeyBytes)); - assertEquals(signatureDigest,sf.resolveDigest(signatureDigestBytes)); - - assertEquals(algorithm,sf.getAlgorithm()); - - } catch (Exception e){ - actualEx = e; - } - - if (expectedException == null) { - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testVerify() { - - AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); - Random randomData = new Random(); - - //test ED25519 - CryptoAlgorithm algorithm = CryptoAlgorithm.ED25519; - - // 测试256字节的消息进行签名 - byte[] data = new byte[256]; - randomData.nextBytes(data); - SignatureFunction sf = asymmetricCrypto.getSignatureFunction(algorithm); - CryptoKeyPair keyPair = sf.generateKeyPair(); - byte[] pubKeyBytes = keyPair.getPubKey().toBytes(); - - byte[] signatureDigestBytes = sf.sign(keyPair.getPrivKey(),data).toBytes(); - verifyVerify(asymmetricCrypto,true,data,pubKeyBytes,signatureDigestBytes,null); - - //签名数据末尾两个字节丢失情况下,抛出异常 - byte[] truncatedSignatureDigestBytes = new byte[signatureDigestBytes.length-2]; - System.arraycopy(signatureDigestBytes,0,truncatedSignatureDigestBytes,0,truncatedSignatureDigestBytes.length); - verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,truncatedSignatureDigestBytes,IllegalArgumentException.class); - - byte[] signatureDigestBytesWithWrongAlgCode = signatureDigestBytes; - signatureDigestBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,signatureDigestBytesWithWrongAlgCode,IllegalArgumentException.class); - - signatureDigestBytes = null; - verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,signatureDigestBytes,NullPointerException.class); - - - //test SM2 - algorithm = CryptoAlgorithm.SM2; - - // 测试256字节的消息进行签名 - data = new byte[256]; - randomData.nextBytes(data); - sf = asymmetricCrypto.getSignatureFunction(algorithm); - keyPair = sf.generateKeyPair(); - pubKeyBytes = keyPair.getPubKey().toBytes(); - - signatureDigestBytes = sf.sign(keyPair.getPrivKey(),data).toBytes(); - verifyVerify(asymmetricCrypto,true,data,pubKeyBytes,signatureDigestBytes,null); - - //签名数据末尾两个字节丢失情况下,抛出异常 - truncatedSignatureDigestBytes = new byte[signatureDigestBytes.length-2]; - System.arraycopy(signatureDigestBytes,0,truncatedSignatureDigestBytes,0,truncatedSignatureDigestBytes.length); - verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,truncatedSignatureDigestBytes,IllegalArgumentException.class); - - signatureDigestBytesWithWrongAlgCode = signatureDigestBytes; - signatureDigestBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,signatureDigestBytesWithWrongAlgCode,IllegalArgumentException.class); - - signatureDigestBytes = null; - verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,signatureDigestBytes,NullPointerException.class); - - //test JNIED25519 - algorithm = CryptoAlgorithm.JNIED25519; - - // 测试256字节的消息进行签名 - data = new byte[256]; - randomData.nextBytes(data); - sf = asymmetricCrypto.getSignatureFunction(algorithm); - keyPair = sf.generateKeyPair(); - pubKeyBytes = keyPair.getPubKey().toBytes(); - - signatureDigestBytes = sf.sign(keyPair.getPrivKey(),data).toBytes(); - verifyVerify(asymmetricCrypto,true,data,pubKeyBytes,signatureDigestBytes,null); - - //签名数据末尾两个字节丢失情况下,抛出异常 - truncatedSignatureDigestBytes = new byte[signatureDigestBytes.length-2]; - System.arraycopy(signatureDigestBytes,0,truncatedSignatureDigestBytes,0,truncatedSignatureDigestBytes.length); - verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,truncatedSignatureDigestBytes,IllegalArgumentException.class); - - signatureDigestBytesWithWrongAlgCode = signatureDigestBytes; - signatureDigestBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,signatureDigestBytesWithWrongAlgCode,IllegalArgumentException.class); - - signatureDigestBytes = null; - verifyVerify(asymmetricCrypto,false,data,pubKeyBytes,signatureDigestBytes,NullPointerException.class); - } - - private void verifyVerify(AsymmetricCryptography asymmetricCrypto,boolean expectedResult,byte[] data, - byte[] pubKeyBytes, byte[] signatureDigestBytes, Class expectedException){ - - //初始化一个异常 - Exception actualEx = null; - boolean pass = false; - - try { - - pass = asymmetricCrypto.verify(signatureDigestBytes,pubKeyBytes,data); - - } - catch (Exception e){ - actualEx = e; - } - - assertEquals(expectedResult, pass); - - if (expectedException == null) { - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testGetAsymmetricEncryptionFunction() { - - AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); - Random random = new Random(); - - - //test SM2 - CryptoAlgorithm algorithm = CryptoAlgorithm.SM2; - - //Case 1: SM2Encryption with 16 bytes data - byte[] data = new byte[16]; - random.nextBytes(data); - verifyGetAsymmetricEncryptionFunction(asymmetricCrypto, algorithm,65,32,65+16+32,data,null); - - //Case 2: SM2Encryption with 256 bytes data - data = new byte[256]; - random.nextBytes(data); - verifyGetAsymmetricEncryptionFunction(asymmetricCrypto, algorithm,65,32,65+256+32,data,null); - - //Case 3: SM2Encryption with 1 bytes data - data = new byte[3]; - random.nextBytes(data); - verifyGetAsymmetricEncryptionFunction(asymmetricCrypto, algorithm,65,32,65+3+32,data,null); - - //Case 4: SM2Encryption with wrong algorithm - verifyGetAsymmetricEncryptionFunction(asymmetricCrypto,CryptoAlgorithm.AES,65,32,65+3+32,data,IllegalArgumentException.class); - - //Case 5: SM2Encryption with null data - data = null; - verifyGetAsymmetricEncryptionFunction(asymmetricCrypto,algorithm,65,32,65+32,data,NullPointerException.class); - } - - private void verifyGetAsymmetricEncryptionFunction(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, - int expectedPubKeyLength, int expectedPrivKeyLength, - int expectedCiphertextLength, byte[] data, Class expectedException){ - - //初始化一个异常 - Exception actualEx = null; - - try { - AsymmetricEncryptionFunction aef = asymmetricCrypto.getAsymmetricEncryptionFunction(algorithm); - //验证获取的算法实例非空 - assertNotNull(aef); - - CryptoKeyPair keyPair = aef.generateKeyPair(); - PubKey pubKey = keyPair.getPubKey(); - PrivKey privKey = keyPair.getPrivKey(); - byte[] rawPubKeyBytes = pubKey.getRawKeyBytes(); - byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); - byte[] pubKeyBytes = pubKey.toBytes(); - byte[] privKeyBytes = privKey.toBytes(); - - assertEquals(algorithm, pubKey.getAlgorithm()); - assertEquals(algorithm, privKey.getAlgorithm()); - assertEquals(expectedPubKeyLength,rawPubKeyBytes.length); - assertEquals(expectedPrivKeyLength,rawPrivKeyBytes.length); - - assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{CryptoKeyType.PUB_KEY.CODE},rawPubKeyBytes), pubKeyBytes); - assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{CryptoKeyType.PRIV_KEY.CODE},rawPrivKeyBytes), privKeyBytes); - - Ciphertext ciphertext = aef.encrypt(pubKey,data); - byte[] rawCiphertextBytes = ciphertext.getRawCiphertext(); - - assertEquals(algorithm,ciphertext.getAlgorithm()); - assertEquals(expectedCiphertextLength,rawCiphertextBytes.length); - byte[] ciphertextBytes = ciphertext.toBytes(); - assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},rawCiphertextBytes),ciphertextBytes); - - assertArrayEquals(data,aef.decrypt(privKey,ciphertext)); - - assertTrue(aef.supportPubKey(pubKeyBytes)); - assertTrue(aef.supportPrivKey(privKeyBytes)); - assertTrue(aef.supportCiphertext(ciphertextBytes)); - - assertEquals(pubKey,aef.resolvePubKey(pubKeyBytes)); - assertEquals(privKey,aef.resolvePrivKey(privKeyBytes)); - assertEquals(ciphertext,aef.resolveCiphertext(ciphertextBytes)); - - assertEquals(algorithm,aef.getAlgorithm()); - - - }catch (Exception e){ - actualEx = e; - } - - if(expectedException == null){ - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testDecrypt() { - - AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); - Random random = new Random(); - - byte[] data = new byte[16]; - random.nextBytes(data); - - //test SM2 - CryptoAlgorithm algorithm = CryptoAlgorithm.SM2; - AsymmetricEncryptionFunction aef = asymmetricCrypto.getAsymmetricEncryptionFunction(algorithm); - CryptoKeyPair keyPair = aef.generateKeyPair(); - PubKey pubKey = keyPair.getPubKey(); - PrivKey privKey = keyPair.getPrivKey(); - byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); - Ciphertext ciphertext = aef.encrypt(pubKey,data); - byte[] ciphertextBytes = ciphertext.toBytes(); - - verifyDecrypt(asymmetricCrypto, algorithm, rawPrivKeyBytes, data, ciphertextBytes, null); - - //密钥的算法标识与密文的算法标识不一致情况 - verifyDecrypt(asymmetricCrypto, CryptoAlgorithm.AES, rawPrivKeyBytes, data, ciphertextBytes, IllegalArgumentException.class); - - //密文末尾两个字节丢失情况下,抛出异常 - byte[] truncatedCiphertextBytes = new byte[ciphertextBytes.length-2]; - System.arraycopy(ciphertextBytes,0,truncatedCiphertextBytes,0,truncatedCiphertextBytes.length); - verifyDecrypt(asymmetricCrypto, algorithm, rawPrivKeyBytes, data, truncatedCiphertextBytes, com.jd.blockchain.crypto.CryptoException.class); - - byte[] ciphertextBytesWithWrongAlgCode = ciphertextBytes; - ciphertextBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyDecrypt(asymmetricCrypto,algorithm,rawPrivKeyBytes,data,ciphertextBytesWithWrongAlgCode,IllegalArgumentException.class); - - ciphertextBytes = null; - verifyDecrypt(asymmetricCrypto,algorithm,rawPrivKeyBytes,data,ciphertextBytes,NullPointerException.class); - } - - private void verifyDecrypt(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, - byte[] key, byte[] data, byte[] ciphertextBytes, Class expectedException){ - Exception actualEx = null; - - try { - PrivKey privKey = new PrivKey(algorithm,key); - - byte[] plaintext = asymmetricCrypto.decrypt(privKey.toBytes(), ciphertextBytes); - - //解密后的明文与初始的明文一致 - assertArrayEquals(data,plaintext); - } - catch (Exception e){ - actualEx = e; - } - - if (expectedException == null) { - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testResolveCiphertext() { - - - AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); - Random random = new Random(); - - byte[] data = new byte[16]; - random.nextBytes(data); - - //test SM2 - CryptoAlgorithm algorithm = CryptoAlgorithm.SM2; - AsymmetricEncryptionFunction aef = asymmetricCrypto.getAsymmetricEncryptionFunction(algorithm); - CryptoKeyPair keyPair = aef.generateKeyPair(); - PubKey pubKey = keyPair.getPubKey(); - PrivKey privKey = keyPair.getPrivKey(); - byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); - Ciphertext ciphertext = aef.encrypt(pubKey,data); - byte[] ciphertextBytes = ciphertext.toBytes(); - - verifyResolveCiphertext(asymmetricCrypto, algorithm, ciphertextBytes, null); - - - //密文末尾两个字节丢失情况下,抛出异常 - byte[] truncatedCiphertextBytes = new byte[ciphertextBytes.length-2]; - System.arraycopy(ciphertextBytes,0,truncatedCiphertextBytes,0,truncatedCiphertextBytes.length); - verifyDecrypt(asymmetricCrypto, algorithm, rawPrivKeyBytes, data, truncatedCiphertextBytes, CryptoException.class); - - byte[] ciphertextBytesWithWrongAlgCode = ciphertextBytes; - ciphertextBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyResolveCiphertext(asymmetricCrypto,algorithm,ciphertextBytesWithWrongAlgCode,IllegalArgumentException.class); - - ciphertextBytes = null; - verifyResolveCiphertext(asymmetricCrypto,algorithm,ciphertextBytes,NullPointerException.class); - } - - private void verifyResolveCiphertext(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, byte[] ciphertextBytes, - Class expectedException){ - Exception actualEx = null; - - try { - - Ciphertext ciphertext = asymmetricCrypto.resolveCiphertext(ciphertextBytes); - - assertNotNull(ciphertext); - - assertEquals(algorithm, ciphertext.getAlgorithm()); - - assertArrayEquals(ciphertextBytes, ciphertext.toBytes()); - } - catch (Exception e){ - actualEx = e; - } - - if (expectedException == null) { - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testTryResolveCiphertext() { - } - - @Test - public void testResolveSignatureDigest() { - - AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); - Random randomData = new Random(); - - //test ED25519 - CryptoAlgorithm algorithm = CryptoAlgorithm.ED25519; - - // 测试256字节的消息进行签名 - byte[] data = new byte[256]; - randomData.nextBytes(data); - SignatureFunction sf = asymmetricCrypto.getSignatureFunction(algorithm); - CryptoKeyPair keyPair = sf.generateKeyPair(); - - byte[] signatureDigestBytes = sf.sign(keyPair.getPrivKey(),data).toBytes(); - verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,signatureDigestBytes,null); - - //签名数据末尾两个字节丢失情况下,抛出异常 - byte[] truncatedSignatureDigestBytes = new byte[signatureDigestBytes.length-2]; - System.arraycopy(signatureDigestBytes,0,truncatedSignatureDigestBytes,0,truncatedSignatureDigestBytes.length); - verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,truncatedSignatureDigestBytes,IllegalArgumentException.class); - - signatureDigestBytes = null; - verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,signatureDigestBytes,NullPointerException.class); - - - //test SM2 - algorithm = CryptoAlgorithm.SM2; - - // 测试256字节的消息进行签名 - data = new byte[256]; - randomData.nextBytes(data); - sf = asymmetricCrypto.getSignatureFunction(algorithm); - keyPair = sf.generateKeyPair(); - - signatureDigestBytes = sf.sign(keyPair.getPrivKey(),data).toBytes(); - verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,signatureDigestBytes,null); - - //签名数据末尾两个字节丢失情况下,抛出异常 - truncatedSignatureDigestBytes = new byte[signatureDigestBytes.length-2]; - System.arraycopy(signatureDigestBytes,0,truncatedSignatureDigestBytes,0,truncatedSignatureDigestBytes.length); - verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,truncatedSignatureDigestBytes,IllegalArgumentException.class); - - signatureDigestBytes = null; - verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,signatureDigestBytes,NullPointerException.class); - - //test JNIED25519 - algorithm = CryptoAlgorithm.JNIED25519; - - // 测试256字节的消息进行签名 - data = new byte[256]; - randomData.nextBytes(data); - sf = asymmetricCrypto.getSignatureFunction(algorithm); - keyPair = sf.generateKeyPair(); - - signatureDigestBytes = sf.sign(keyPair.getPrivKey(),data).toBytes(); - verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,signatureDigestBytes,null); - - //签名数据末尾两个字节丢失情况下,抛出异常 - truncatedSignatureDigestBytes = new byte[signatureDigestBytes.length-2]; - System.arraycopy(signatureDigestBytes,0,truncatedSignatureDigestBytes,0,truncatedSignatureDigestBytes.length); - verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,truncatedSignatureDigestBytes,IllegalArgumentException.class); - - signatureDigestBytes = null; - verifyResolveSignatureDigest(asymmetricCrypto,algorithm,64,signatureDigestBytes,NullPointerException.class); - } - - private void verifyResolveSignatureDigest(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, - int expectedSignatureDigestLength, - byte[] signatureDigestBytes, Class expectedException){ - - //初始化一个异常 - Exception actualEx = null; - - try { - - SignatureDigest signatureDigest = asymmetricCrypto.resolveSignatureDigest(signatureDigestBytes); - - assertNotNull(signatureDigest); - - assertEquals(algorithm,signatureDigest.getAlgorithm()); - - assertEquals(expectedSignatureDigestLength,signatureDigest.getRawDigest().length); - - assertArrayEquals(signatureDigestBytes,signatureDigest.toBytes()); - - } - catch (Exception e){ - actualEx = e; - } - - if (expectedException == null) { - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testTryResolveSignatureDigest() { - } - - @Test - public void testRetrievePubKeyBytes() { - - AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); - - //test ED25519 - CryptoAlgorithm algorithm = CryptoAlgorithm.ED25519; - - CryptoKeyPair keyPair = asymmetricCrypto.generateKeyPair(algorithm); - - byte[] expectedPrivKeyBytes = keyPair.getPrivKey().toBytes(); - byte[] expectedPubKeyBytes = keyPair.getPubKey().toBytes(); - - byte[] pubKeyBytes = asymmetricCrypto.retrievePubKeyBytes(expectedPrivKeyBytes); - - assertArrayEquals(expectedPubKeyBytes,pubKeyBytes); - - - //test SM2 - algorithm = CryptoAlgorithm.SM2; - - keyPair = asymmetricCrypto.generateKeyPair(algorithm); - - expectedPrivKeyBytes = keyPair.getPrivKey().toBytes(); - expectedPubKeyBytes = keyPair.getPubKey().toBytes(); - - pubKeyBytes = asymmetricCrypto.retrievePubKeyBytes(expectedPrivKeyBytes); - - assertArrayEquals(expectedPubKeyBytes,pubKeyBytes); - - - //test JNIED25519 - algorithm = CryptoAlgorithm.JNIED25519; - - keyPair = asymmetricCrypto.generateKeyPair(algorithm); - - expectedPrivKeyBytes = keyPair.getPrivKey().toBytes(); - expectedPubKeyBytes = keyPair.getPubKey().toBytes(); - - pubKeyBytes = asymmetricCrypto.retrievePubKeyBytes(expectedPrivKeyBytes); - - assertArrayEquals(expectedPubKeyBytes,pubKeyBytes); - - } - - - @Test - public void testResolvePubKey() { - - AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); - - //test ED25519 - CryptoAlgorithm algorithm = CryptoAlgorithm.ED25519; - - CryptoKeyPair keyPair = asymmetricCrypto.generateKeyPair(algorithm); - - byte[] pubKeyBytes = keyPair.getPubKey().toBytes(); - verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytes,null); - - byte[] truncatedPubKeyBytes = new byte[pubKeyBytes.length-2]; - System.arraycopy(pubKeyBytes,0,truncatedPubKeyBytes,0,truncatedPubKeyBytes.length); - verifyResolvePubKey(asymmetricCrypto,algorithm,32,truncatedPubKeyBytes,IllegalArgumentException.class); - - byte[] pubKeyBytesWithWrongAlgCode = pubKeyBytes; - pubKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytesWithWrongAlgCode,IllegalArgumentException.class); - - byte[] pubKeyBytesWithWrongKeyType= pubKeyBytes; - pubKeyBytesWithWrongKeyType[1] = PRIV_KEY.CODE; - verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytesWithWrongKeyType,IllegalArgumentException.class); - - pubKeyBytes = null; - verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytes,NullPointerException.class); - - - //test SM2 - algorithm = CryptoAlgorithm.SM2; - - keyPair = asymmetricCrypto.generateKeyPair(algorithm); - - pubKeyBytes = keyPair.getPubKey().toBytes(); - verifyResolvePubKey(asymmetricCrypto,algorithm,65,pubKeyBytes,null); - - truncatedPubKeyBytes = new byte[pubKeyBytes.length-2]; - System.arraycopy(pubKeyBytes,0,truncatedPubKeyBytes,0,truncatedPubKeyBytes.length); - verifyResolvePubKey(asymmetricCrypto,algorithm,65,truncatedPubKeyBytes,IllegalArgumentException.class); - - pubKeyBytesWithWrongAlgCode = pubKeyBytes; - pubKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyResolvePubKey(asymmetricCrypto,algorithm,65,pubKeyBytesWithWrongAlgCode,IllegalArgumentException.class); - - pubKeyBytesWithWrongKeyType= pubKeyBytes; - pubKeyBytesWithWrongKeyType[1] = PRIV_KEY.CODE; - verifyResolvePubKey(asymmetricCrypto,algorithm,65,pubKeyBytesWithWrongKeyType,IllegalArgumentException.class); - - pubKeyBytes = null; - verifyResolvePubKey(asymmetricCrypto,algorithm,65,pubKeyBytes,NullPointerException.class); - - //test JNIED25519 - algorithm = CryptoAlgorithm.JNIED25519; - - keyPair = asymmetricCrypto.generateKeyPair(algorithm); - - pubKeyBytes = keyPair.getPubKey().toBytes(); - verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytes,null); - - truncatedPubKeyBytes = new byte[pubKeyBytes.length-2]; - System.arraycopy(pubKeyBytes,0,truncatedPubKeyBytes,0,truncatedPubKeyBytes.length); - verifyResolvePubKey(asymmetricCrypto,algorithm,32,truncatedPubKeyBytes,IllegalArgumentException.class); - - pubKeyBytesWithWrongAlgCode = pubKeyBytes; - pubKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytesWithWrongAlgCode,IllegalArgumentException.class); - - pubKeyBytesWithWrongKeyType= pubKeyBytes; - pubKeyBytesWithWrongKeyType[1] = PRIV_KEY.CODE; - verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytesWithWrongKeyType,IllegalArgumentException.class); - - pubKeyBytes = null; - verifyResolvePubKey(asymmetricCrypto,algorithm,32,pubKeyBytes,NullPointerException.class); - } - - private void verifyResolvePubKey(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, - int expectedPubKeyLength, byte[] pubKeyBytes,Class expectedException){ - - Exception actualEx = null; - - try { - PubKey pubKey = asymmetricCrypto.resolvePubKey(pubKeyBytes); - - assertNotNull(pubKey); - - assertEquals(algorithm, pubKey.getAlgorithm()); - - assertEquals(expectedPubKeyLength, pubKey.getRawKeyBytes().length); - - assertArrayEquals(pubKeyBytes, pubKey.toBytes()); - - } - catch (Exception e){ - actualEx = e; - } - - if (expectedException == null) { - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testTryResolvePubKey() { - } - - @Test - public void testResolvePrivKey() { - - AsymmetricCryptography asymmetricCrypto = new AsymmtricCryptographyImpl(); - - //test ED25519 - CryptoAlgorithm algorithm = CryptoAlgorithm.ED25519; - - CryptoKeyPair keyPair = asymmetricCrypto.generateKeyPair(algorithm); - - byte[] privKeyBytes = keyPair.getPrivKey().toBytes(); - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytes,null); - - byte[] truncatedPrivKeyBytes = new byte[privKeyBytes.length-2]; - System.arraycopy(privKeyBytes,0,truncatedPrivKeyBytes,0,truncatedPrivKeyBytes.length); - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,truncatedPrivKeyBytes,IllegalArgumentException.class); - - byte[] privKeyBytesWithWrongAlgCode = privKeyBytes; - privKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytesWithWrongAlgCode,IllegalArgumentException.class); - - byte[] privKeyBytesWithWrongKeyType = privKeyBytes; - privKeyBytesWithWrongKeyType[1] = PUB_KEY.CODE; - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytesWithWrongKeyType,IllegalArgumentException.class); - - privKeyBytes = null; - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytes,NullPointerException.class); - - - //test SM2 - algorithm = CryptoAlgorithm.SM2; - - keyPair = asymmetricCrypto.generateKeyPair(algorithm); - - privKeyBytes = keyPair.getPrivKey().toBytes(); - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytes,null); - - truncatedPrivKeyBytes = new byte[privKeyBytes.length-2]; - System.arraycopy(privKeyBytes,0,truncatedPrivKeyBytes,0,truncatedPrivKeyBytes.length); - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,truncatedPrivKeyBytes,IllegalArgumentException.class); - - privKeyBytesWithWrongAlgCode = privKeyBytes; - privKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytesWithWrongAlgCode,IllegalArgumentException.class); - - privKeyBytesWithWrongKeyType = privKeyBytes; - privKeyBytesWithWrongKeyType[1] = PUB_KEY.CODE; - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytesWithWrongKeyType,IllegalArgumentException.class); - - privKeyBytes = null; - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytes,NullPointerException.class); - - //test JNIED25519 - algorithm = CryptoAlgorithm.JNIED25519; - - keyPair = asymmetricCrypto.generateKeyPair(algorithm); - - privKeyBytes = keyPair.getPrivKey().toBytes(); - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytes,null); - - truncatedPrivKeyBytes = new byte[privKeyBytes.length-2]; - System.arraycopy(privKeyBytes,0,truncatedPrivKeyBytes,0,truncatedPrivKeyBytes.length); - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,truncatedPrivKeyBytes,IllegalArgumentException.class); - - privKeyBytesWithWrongAlgCode = privKeyBytes; - privKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytesWithWrongAlgCode,IllegalArgumentException.class); - - privKeyBytesWithWrongKeyType = privKeyBytes; - privKeyBytesWithWrongKeyType[1] = PUB_KEY.CODE; - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytesWithWrongKeyType,IllegalArgumentException.class); - - privKeyBytes = null; - verifyResolvePrivKey(asymmetricCrypto,algorithm,32,privKeyBytes,NullPointerException.class); - } - - private void verifyResolvePrivKey(AsymmetricCryptography asymmetricCrypto, CryptoAlgorithm algorithm, - int expectedPrivKeyLength, byte[] privKeyBytes,Class expectedException){ - - Exception actualEx = null; - - try { - PrivKey privKey = asymmetricCrypto.resolvePrivKey(privKeyBytes); - - assertNotNull(privKey); - - assertEquals(algorithm, privKey.getAlgorithm()); - - assertEquals(expectedPrivKeyLength, privKey.getRawKeyBytes().length); - - assertArrayEquals(privKeyBytes, privKey.toBytes()); - - } - catch (Exception e){ - actualEx = e; - } - - if (expectedException == null) { - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testTryResolvePrivKey() { - } -} \ No newline at end of file diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/hash/HashCryptographyImplTest.java b/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/hash/HashCryptographyImplTest.java deleted file mode 100644 index 513cb577..00000000 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/hash/HashCryptographyImplTest.java +++ /dev/null @@ -1,333 +0,0 @@ -package test.com.jd.blockchain.crypto.hash; - -import static org.junit.Assert.*; - -import java.util.Random; - -import com.jd.blockchain.crypto.smutils.hash.SM3Utils; -import com.jd.blockchain.utils.io.BytesUtils; -import com.jd.blockchain.utils.security.RipeMD160Utils; -import com.jd.blockchain.utils.security.ShaUtils; - -import org.junit.Test; - -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.hash.HashCryptography; -import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.hash.HashFunction; -import com.jd.blockchain.crypto.impl.HashCryptographyImpl; - -public class HashCryptographyImplTest { - - @Test - public void testGetFunction() { - HashCryptography hashCrypto = new HashCryptographyImpl(); - Random rand = new Random(); - // test SHA256 - CryptoAlgorithm algorithm = CryptoAlgorithm.SHA256; - byte[] data = new byte[256]; - rand.nextBytes(data); - verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); - - data = new byte[0]; - verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); - - data = new byte[1056]; - rand.nextBytes(data); - verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); - - data = null; - verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,NullPointerException.class); - - - // test RIPEMD160 - algorithm = CryptoAlgorithm.RIPEMD160; - data=new byte[256]; - rand.nextBytes(data); - verifyGetFunction(hashCrypto, algorithm, data, 160 / 8,null); - - data = new byte[0]; - verifyGetFunction(hashCrypto, algorithm, data, 160/ 8,null); - - data = new byte[1056]; - rand.nextBytes(data); - verifyGetFunction(hashCrypto, algorithm, data, 160 / 8,null); - - data = null; - verifyGetFunction(hashCrypto, algorithm, data, 160 / 8,NullPointerException.class); - - // test SM3 - algorithm = CryptoAlgorithm.SM3; - data = new byte[256]; - rand.nextBytes(data); - verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); - - data = new byte[0]; - verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); - - data = new byte[1056]; - rand.nextBytes(data); - verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); - - data = null; - verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,NullPointerException.class); - - // test AES - data = new byte[0]; - algorithm = CryptoAlgorithm.AES; - verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,IllegalArgumentException.class); - - // test JNISHA256 - algorithm = CryptoAlgorithm.JNISHA256; - data = new byte[256]; - rand.nextBytes(data); - verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); - - data = new byte[0]; - verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); - - data = new byte[1056]; - rand.nextBytes(data); - verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,null); - - data = null; - verifyGetFunction(hashCrypto, algorithm, data, 256 / 8,IllegalArgumentException.class); - - // test JNIRIPEMD160 - algorithm = CryptoAlgorithm.JNIRIPEMD160; - data=new byte[256]; - rand.nextBytes(data); - verifyGetFunction(hashCrypto, algorithm, data, 160 / 8,null); - - data = new byte[0]; - verifyGetFunction(hashCrypto, algorithm, data, 160/ 8,null); - - data = new byte[1056]; - rand.nextBytes(data); - verifyGetFunction(hashCrypto, algorithm, data, 160 / 8,null); - - data = null; - verifyGetFunction(hashCrypto, algorithm, data, 160 / 8,IllegalArgumentException.class); - } - - private void verifyGetFunction(HashCryptography hashCrypto, CryptoAlgorithm algorithm, byte[] data, - int expectedRawBytes,Class expectedException) { - Exception actualEx = null; - try { - HashFunction hf = hashCrypto.getFunction(algorithm); - assertNotNull(hf); - - HashDigest hd = hf.hash(data); - - assertEquals(algorithm, hd.getAlgorithm()); - - assertEquals(expectedRawBytes, hd.getRawDigest().length); - - // verify encoding; - byte[] encodedHash = hd.toBytes(); - assertEquals(expectedRawBytes + 1, encodedHash.length); - - - assertEquals(algorithm.CODE, encodedHash[0]); - - //verify equals - assertEquals(true, hd.equals(hf.hash(data))); - - //verify verify - assertTrue( hf.verify(hd, data)); - - } catch (Exception e) { - actualEx = e; - } - - if(expectedException==null){ - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testVerifyHashDigestByteArray() { - HashCryptography hashCrypto = new HashCryptographyImpl(); - //test SHA256 - byte[] data=new byte[256]; - Random rand = new Random(); - rand.nextBytes(data); - CryptoAlgorithm algorithm=CryptoAlgorithm.SHA256; - verifyHashDigestByteArray(hashCrypto,algorithm,data,null); - data=null; - verifyHashDigestByteArray(hashCrypto,algorithm,data,NullPointerException.class); - - //test RIPEMD160 - algorithm=CryptoAlgorithm.RIPEMD160; - data=new byte[896]; - rand.nextBytes(data); - verifyHashDigestByteArray(hashCrypto,algorithm,data,null); - data=null; - verifyHashDigestByteArray(hashCrypto,algorithm,data,NullPointerException.class); - - //test SM3 - algorithm=CryptoAlgorithm.SM3; - data=new byte[896]; - rand.nextBytes(data); - verifyHashDigestByteArray(hashCrypto,algorithm,data,null); - data=null; - verifyHashDigestByteArray(hashCrypto,algorithm,data,NullPointerException.class); - - - //test AES - algorithm=CryptoAlgorithm.AES; - data=new byte[277]; - rand.nextBytes(data); - verifyHashDigestByteArray(hashCrypto,algorithm,data,IllegalArgumentException.class); - - //test JNISHA256 - data=new byte[256]; - rand = new Random(); - rand.nextBytes(data); - algorithm=CryptoAlgorithm.JNISHA256; - verifyHashDigestByteArray(hashCrypto,algorithm,data,null); - data=null; - verifyHashDigestByteArray(hashCrypto,algorithm,data,IllegalArgumentException.class); - - //test JNIRIPEMD160 - algorithm=CryptoAlgorithm.JNIRIPEMD160; - data=new byte[896]; - rand.nextBytes(data); - verifyHashDigestByteArray(hashCrypto,algorithm,data,null); - data=null; - verifyHashDigestByteArray(hashCrypto,algorithm,data,IllegalArgumentException.class); - } - - private void verifyHashDigestByteArray(HashCryptography hashCrypto,CryptoAlgorithm algorithm,byte[] data,Class expectedException){ - Exception actualEx=null; - try { - HashFunction hf = hashCrypto.getFunction(algorithm); - assertNotNull(hf); - HashDigest hd = hf.hash(data); - hashCrypto.verify(hd,data); - }catch (Exception e) - { - actualEx=e; - } - if (expectedException==null) - { - assertNull(actualEx); - } - else{ - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testResolveHashDigest() { - Random rand = new Random(); - HashCryptography hashCrypto = new HashCryptographyImpl(); - - //test SHA256 - CryptoAlgorithm algorithm = CryptoAlgorithm.SHA256; - byte[] data = new byte[256]; - rand.nextBytes(data); - byte[] hashDigestBytes = hashCrypto.getFunction(algorithm).hash(data).toBytes(); - verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,32+1,null); - - byte[] truncatedHashDigestBytes = new byte[hashDigestBytes.length-2]; - System.arraycopy(hashDigestBytes,0,truncatedHashDigestBytes,0,truncatedHashDigestBytes.length); - verifyResolveHashDigest(algorithm, hashCrypto,truncatedHashDigestBytes,32+1,IllegalArgumentException.class); - - hashDigestBytes = null; - verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,32+1,NullPointerException.class); - - - //test RIPEMD160 - algorithm = CryptoAlgorithm.RIPEMD160; - data = new byte[256]; - rand.nextBytes(data); - hashDigestBytes = hashCrypto.getFunction(algorithm).hash(data).toBytes(); - verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,20+1,null); - - truncatedHashDigestBytes = new byte[hashDigestBytes.length-2]; - System.arraycopy(hashDigestBytes,0,truncatedHashDigestBytes,0,truncatedHashDigestBytes.length); - verifyResolveHashDigest(algorithm, hashCrypto,truncatedHashDigestBytes,20+1,IllegalArgumentException.class); - - hashDigestBytes = null; - verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,20+1,NullPointerException.class); - - - //test SM3 - algorithm = CryptoAlgorithm.SM3; - data = new byte[256]; - rand.nextBytes(data); - hashDigestBytes = hashCrypto.getFunction(algorithm).hash(data).toBytes(); - verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,32+1,null); - - truncatedHashDigestBytes = new byte[hashDigestBytes.length-2]; - System.arraycopy(hashDigestBytes,0,truncatedHashDigestBytes,0,truncatedHashDigestBytes.length); - verifyResolveHashDigest(algorithm, hashCrypto,truncatedHashDigestBytes,32+1,IllegalArgumentException.class); - - hashDigestBytes = null; - verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,32+1,NullPointerException.class); - - - //test JNISHA256 - algorithm = CryptoAlgorithm.JNISHA256; - data = new byte[256]; - rand.nextBytes(data); - hashDigestBytes = hashCrypto.getFunction(algorithm).hash(data).toBytes(); - verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,32+1,null); - - truncatedHashDigestBytes = new byte[hashDigestBytes.length-2]; - System.arraycopy(hashDigestBytes,0,truncatedHashDigestBytes,0,truncatedHashDigestBytes.length); - verifyResolveHashDigest(algorithm, hashCrypto,truncatedHashDigestBytes,32+1,IllegalArgumentException.class); - - hashDigestBytes = null; - verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,32+1,NullPointerException.class); - - //test JNIRIPEMD160 - algorithm = CryptoAlgorithm.JNIRIPEMD160; - data = new byte[256]; - rand.nextBytes(data); - hashDigestBytes = hashCrypto.getFunction(algorithm).hash(data).toBytes(); - verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,20+1,null); - - truncatedHashDigestBytes = new byte[hashDigestBytes.length-2]; - System.arraycopy(hashDigestBytes,0,truncatedHashDigestBytes,0,truncatedHashDigestBytes.length); - verifyResolveHashDigest(algorithm, hashCrypto,truncatedHashDigestBytes,20+1,IllegalArgumentException.class); - - hashDigestBytes = null; - verifyResolveHashDigest(algorithm, hashCrypto,hashDigestBytes,20+1,NullPointerException.class); - } - - private void verifyResolveHashDigest(CryptoAlgorithm algorithm,HashCryptography - hashCrypto,byte[] hashDigestBytes,int expectedLength,ClassexpectedException){ - - Exception actualEx=null; - - try { - - HashDigest hashDigest=hashCrypto.resolveHashDigest(hashDigestBytes); - assertNotNull(hashDigest); - assertEquals(algorithm,hashDigest.getAlgorithm()); - byte[] algBytes = new byte[1]; - algBytes[0] = algorithm.CODE; - assertArrayEquals(hashDigestBytes,BytesUtils.concat(algBytes,hashDigest.getRawDigest())); - assertEquals(expectedLength,hashDigestBytes.length); - - }catch (Exception e) - { - actualEx = e; - } - if (expectedException==null) - { - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - } diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNIED25519UtilsTest.java b/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNIED25519UtilsTest.java deleted file mode 100644 index 8cbce2bb..00000000 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNIED25519UtilsTest.java +++ /dev/null @@ -1,123 +0,0 @@ -package test.com.jd.blockchain.crypto.jniutils; - -import com.jd.blockchain.crypto.jniutils.asymmetric.JNIED25519Utils; - - -public class JNIED25519UtilsTest { - - /* Program entry function */ - public static void main(String args[]) { - - byte[] msg = "abc".getBytes(); - int i; - int j; - int count = 10000; - - long startTS; - long elapsedTS; - - byte[] privKey = new byte[32]; - byte[] pubKey = new byte[32]; - byte[] signature; - - - JNIED25519Utils ed25519 = new JNIED25519Utils(); - - System.out.println("=================== Key Generation test ==================="); - ed25519.generateKeyPair(privKey,pubKey); - System.out.println("Private Key: "); - for(i = 0; i < privKey.length; i++) { - System.out.print(privKey[i] + " "); - if((i+1)%8 == 0) - System.out.println(); - } - System.out.println(); - System.out.println("Public Key: "); - for(i = 0; i < pubKey.length; i++) { - System.out.print(pubKey[i] + " "); - if((i+1)%8 == 0) - System.out.println(); - } - System.out.println(); - - System.out.println("=================== Public Key Retrieval test ==================="); - byte[] pk; - pk = ed25519.getPubKey(privKey); - System.out.println("Retrieved Public Key: "); - for(i = 0; i < pk.length; i++) { - System.out.print(pk[i] + " "); - if((i+1)%8 == 0) - System.out.println(); - } - System.out.println(); - - System.out.println("=================== Signing test ==================="); - signature = ed25519.sign(msg,privKey,pubKey); - System.out.println("Signature: "); - for(i = 0; i < signature.length; i++) { - System.out.print(signature[i] + " "); - if((i+1)%8 == 0) - System.out.println(); - } - System.out.println(); - - System.out.println("=================== Verifying test ==================="); - if (ed25519.verify(msg,pubKey,signature)) - System.out.println("valid signature"); - else System.out.println("invalid signature"); - - System.out.println("=================== Do ED25519 Key Pair Generation Test ==================="); - - - for (j = 0; j < 5; j++) { - System.out.println("------------- round[" + j + "] --------------"); - startTS = System.currentTimeMillis(); - for (i = 0; i < count; i++) { - ed25519.generateKeyPair(privKey,pubKey); - } - elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("ED25519 Key Pair Generation: Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - System.out.println(); - - System.out.println("=================== Do ED25519 Public Key Retrieval Test ==================="); - for (j = 0; j < 5; j++) { - System.out.println("------------- round[" + j + "] --------------"); - startTS = System.currentTimeMillis(); - for (i = 0; i < count; i++) { - ed25519.getPubKey(privKey); - } - elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("ED25519 Public Key Retrieval: Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - System.out.println(); - - System.out.println("=================== Do ED25519 Signing Test ==================="); - for (j = 0; j < 5; j++) { - System.out.println("------------- round[" + j + "] --------------"); - startTS = System.currentTimeMillis(); - for (i = 0; i < count; i++) { - ed25519.sign(msg,privKey,pubKey); - } - elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("ED25519 Signing: Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - System.out.println(); - - System.out.println("=================== Do ED25519 Verifying Test ==================="); - for (j = 0; j < 5; j++) { - System.out.println("------------- round[" + j + "] --------------"); - startTS = System.currentTimeMillis(); - for (i = 0; i < count; i++) { - ed25519.verify(msg,pubKey,signature); - } - elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("ED25519 Verifying: Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - System.out.println(); - } -} diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNIMBSHA256UtilsTest.java b/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNIMBSHA256UtilsTest.java deleted file mode 100644 index e5b5ef2f..00000000 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNIMBSHA256UtilsTest.java +++ /dev/null @@ -1,111 +0,0 @@ -package test.com.jd.blockchain.crypto.jniutils; - -import com.jd.blockchain.crypto.jniutils.hash.JNIMBSHA256Utils; - -public class JNIMBSHA256UtilsTest { - /* Program entry function */ - public static void main(String args[]) { - - String osName = System.getProperty("os.name").toLowerCase(); - - if (! osName.contains("linux")) { - return ; - } - - byte[] array1 = "abc".getBytes(); - byte[] array2 = "abcd".getBytes(); - byte[] array3 = "abcde".getBytes(); - byte[] array4 = "abcdef".getBytes(); - - byte[][] arrays = {array1,array2,array3,array4}; - JNIMBSHA256Utils mbsha256 = new JNIMBSHA256Utils(); - byte[][] results = mbsha256.multiBufferHash(arrays); - - System.out.println("JAVA to C : "); - for (int i = 0; i < arrays.length; i++) { - for (int j = 0; j < arrays[i].length; j++) { - System.out.print(arrays[i][j] + " "); - } - System.out.println(); - } - - System.out.println(); - - System.out.println("C to JAVA : "); - for (int i = 0; i < results.length; i++) { - for (int j = 0; j < results[i].length; j++) { - System.out.print(results[i][j] + " "); - } - System.out.println(); - } - - System.out.println(); - - String str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; - byte[] array = str.getBytes(); - - - int count = 1000000; - - - byte[][] arraysx4 = {array,array,array,array}; - byte[][] arraysx8 = {array,array,array,array,array,array,array,array}; - byte[][] arraysx16 = {array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array}; - byte[][] arraysx32 = {array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array,array}; - - - System.out.println("=================== do MBSHA256 hash test in x4==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - mbsha256.multiBufferHash(arraysx4); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("SHA256 hashing Count=%s; Elapsed Times=%s; KBPS=%.2f; Total KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS, (count * 1000.00D) / elapsedTS*4)); - } - System.out.println(); - System.out.println(); - - System.out.println("=================== do MBSHA256 hash test in x8==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - mbsha256.multiBufferHash(arraysx8); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("SHA256 hashing Count=%s; Elapsed Times=%s; KBPS=%.2f; Total KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS, (count * 1000.00D) / elapsedTS*8)); - } - System.out.println(); - System.out.println(); - - System.out.println("=================== do MBSHA256 hash test in x16==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - mbsha256.multiBufferHash(arraysx16); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("SHA256 hashing Count=%s; Elapsed Times=%s; KBPS=%.2f; Total KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS, (count * 1000.00D) / elapsedTS*16)); - } - System.out.println(); - System.out.println(); - - System.out.println("=================== do MBSHA256 hash test in x32==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - mbsha256.multiBufferHash(arraysx32); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("SHA256 hashing Count=%s; Elapsed Times=%s; KBPS=%.2f; Total KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS, (count * 1000.00D) / elapsedTS*32)); - } - } -} diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNIRIPEMD160UtilsTest.java b/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNIRIPEMD160UtilsTest.java deleted file mode 100644 index 47bb7bda..00000000 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNIRIPEMD160UtilsTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package test.com.jd.blockchain.crypto.jniutils; - -import com.jd.blockchain.crypto.jniutils.hash.JNIRIPEMD160Utils; - -public class JNIRIPEMD160UtilsTest { - - /* Program entry function */ - public static void main(String args[]) { - byte[] array1 = "abc".getBytes(); - byte[] array2; - JNIRIPEMD160Utils ripemd160 = new JNIRIPEMD160Utils(); - array2 = ripemd160.hash(array1); - - System.out.print("JAVA to C : "); - for (byte anArray1 : array1) { - System.out.print(anArray1 + " "); - } - System.out.println(); - System.out.print("C to JAVA : "); - for (byte anArray2 : array2) { - System.out.print(anArray2 + " "); - } - System.out.println(); - - String str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; - byte[] array = str.getBytes(); - int count = 1000000; - - System.out.println("=================== do RIPEMD160 hash test ==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - ripemd160.hash(array); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("RIPEMD160 hashing Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - } -} \ No newline at end of file diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNISHA256UtilsTest.java b/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNISHA256UtilsTest.java deleted file mode 100644 index 10e040f8..00000000 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/jniutils/JNISHA256UtilsTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package test.com.jd.blockchain.crypto.jniutils; - -import com.jd.blockchain.crypto.jniutils.hash.JNISHA256Utils; - -public class JNISHA256UtilsTest { - - /* Program entry function */ - public static void main(String args[]) { - byte[] array1 = "abc".getBytes(); - byte[] array2; - JNISHA256Utils sha256 = new JNISHA256Utils(); - array2 = sha256.hash(array1); - System.out.print("JAVA to C : "); - for (byte anArray1 : array1) { - System.out.print(anArray1 + " "); - } - System.out.println(); - System.out.print("C to JAVA : "); - for (byte anArray2 : array2) { - System.out.print(anArray2 + " "); - } - System.out.println(); - - - String str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; - byte[] array = str.getBytes(); - int count = 1000000; - - System.out.println("=================== do SHA256 hash test ==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - sha256.hash(array); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("SHA256 hashing Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - } -} diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MyAsymmetricEncryptionTest.java b/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MyAsymmetricEncryptionTest.java deleted file mode 100644 index df33e94c..00000000 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MyAsymmetricEncryptionTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package test.com.jd.blockchain.crypto.performance; - -import com.jd.blockchain.crypto.Ciphertext; -import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; -import com.jd.blockchain.crypto.impl.sm.asymmetric.SM2CryptoFunction; -import org.bouncycastle.util.encoders.Hex; - -public class MyAsymmetricEncryptionTest { - - public static void main(String[] args) { - - String string1K = "0123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210"; - String string1M = ""; - for (int i = 0; i < 1024 ; i++) - { - string1M = string1M + string1K; - } - - byte[] data1K = Hex.decode(string1K); - byte[] data1M = Hex.decode(string1M); - int count = 10000; - - SM2CryptoFunction sm2 = new SM2CryptoFunction(); - CryptoKeyPair keyPairSM2 = sm2.generateKeyPair(); - PrivKey privKeySM2 = keyPairSM2.getPrivKey(); - PubKey pubKeySM2 = keyPairSM2.getPubKey(); - - System.out.println("=================== do SM2 encrypt test ==================="); - Ciphertext ciphertextSM2 = null; - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - ciphertextSM2 = sm2.encrypt(pubKeySM2,data1K); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("SM2 Encrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - - System.out.println("=================== do SM2 decrypt test ==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - sm2.decrypt(privKeySM2,ciphertextSM2); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("SM2 Decrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - } -} diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MyHashTest.java b/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MyHashTest.java deleted file mode 100644 index 0c2733b0..00000000 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MyHashTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package test.com.jd.blockchain.crypto.performance; - -import com.jd.blockchain.crypto.impl.def.hash.RIPEMD160HashFunction; -import com.jd.blockchain.crypto.impl.def.hash.SHA256HashFunction; -import com.jd.blockchain.crypto.impl.sm.hash.SM3HashFunction; - -import java.util.Random; - -public class MyHashTest { - - public static void main(String[] args) { - - Random rand = new Random(); - byte[] data1K = new byte[1024]; - rand.nextBytes(data1K); - int count = 1000000; - - SHA256HashFunction sha256hf = new SHA256HashFunction(); - - System.out.println("=================== do SHA256 hash test ==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - sha256hf.hash(data1K); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("SHA256 hashing Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - - RIPEMD160HashFunction ripemd160hf = new RIPEMD160HashFunction(); - - System.out.println("=================== do RIPEMD160 hash test ==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - ripemd160hf.hash(data1K); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("RIPEMD160 hashing Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - - SM3HashFunction sm3hf = new SM3HashFunction(); - - System.out.println("=================== do SM3 hash test ==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - sm3hf.hash(data1K); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("SM3 hashing Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - - } -} - diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MySignatureTest.java b/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MySignatureTest.java deleted file mode 100644 index 189d3011..00000000 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MySignatureTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package test.com.jd.blockchain.crypto.performance; - -import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; -import com.jd.blockchain.crypto.asymmetric.SignatureDigest; -import com.jd.blockchain.crypto.impl.def.asymmetric.ED25519SignatureFunction; -import com.jd.blockchain.crypto.impl.sm.asymmetric.SM2CryptoFunction; - -import java.util.Random; - -public class MySignatureTest { - - public static void main(String[] args) { - - Random rand = new Random(); - byte[] data = new byte[64]; - rand.nextBytes(data); - int count = 10000; - - ED25519SignatureFunction ed25519sf = new ED25519SignatureFunction(); - CryptoKeyPair keyPairED25519 = ed25519sf.generateKeyPair(); - PrivKey privKeyED25519 = keyPairED25519.getPrivKey(); - PubKey pubKeyED25519 = keyPairED25519.getPubKey(); - - System.out.println("=================== do ED25519 sign test ==================="); - SignatureDigest signatureDigestED25519 = null; - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - signatureDigestED25519 = ed25519sf.sign(privKeyED25519,data); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("ED25519 Signing Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - - System.out.println("=================== do ED25519 verify test ==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - ed25519sf.verify(signatureDigestED25519,pubKeyED25519,data); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("ED25519 Verifying Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - - SM2CryptoFunction sm2 = new SM2CryptoFunction(); - CryptoKeyPair keyPairSM2 = sm2.generateKeyPair(); - PrivKey privKeySM2 = keyPairSM2.getPrivKey(); - PubKey pubKeySM2 = keyPairSM2.getPubKey(); - - - System.out.println("=================== do SM2 sign test ==================="); - SignatureDigest signatureDigestSM2 = null; - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - signatureDigestSM2 = sm2.sign(privKeySM2,data); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("SM2 Signing Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - - System.out.println("=================== do SM2 verify test ==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - sm2.verify(signatureDigestSM2,pubKeySM2,data); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("SM2 Verifying Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - - } -} diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MySymmetricEncryptionTest.java b/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MySymmetricEncryptionTest.java deleted file mode 100644 index 82ea11c0..00000000 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/performance/MySymmetricEncryptionTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package test.com.jd.blockchain.crypto.performance; - -import com.jd.blockchain.crypto.Ciphertext; -import com.jd.blockchain.crypto.impl.def.symmetric.AESSymmetricEncryptionFunction; -import com.jd.blockchain.crypto.impl.sm.symmetric.SM4SymmetricEncryptionFunction; -import com.jd.blockchain.crypto.symmetric.SymmetricKey; -import org.bouncycastle.util.encoders.Hex; - -public class MySymmetricEncryptionTest { - - public static void main(String[] args) { - - String string1K = "0123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210"; - -// String string1M = ""; -// for (int i = 0; i < 1024 ; i++) -// { -// string1M = string1M + string1K; -// } - - byte[] data1K = Hex.decode(string1K); -// byte[] data1M = Hex.decode(string1M); - - int count = 100000; - - - AESSymmetricEncryptionFunction aes = new AESSymmetricEncryptionFunction(); - SymmetricKey keyAES = (SymmetricKey) aes.generateSymmetricKey(); - Ciphertext ciphertext1KAES = null; - Ciphertext ciphertext1MAES = null; - - System.out.println("=================== do AES encrypt test ==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - ciphertext1KAES = aes.encrypt(keyAES,data1K); -// ciphertext1MAES = aes.encrypt(keyAES,data1M); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("AES Encrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - - - - System.out.println("=================== do AES decrypt test ==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - aes.decrypt(keyAES,ciphertext1KAES); -// aes.decrypt(keyAES,ciphertext1MAES); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("AES Decrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - - SM4SymmetricEncryptionFunction sm4 = new SM4SymmetricEncryptionFunction(); - SymmetricKey keySM4 = (SymmetricKey) sm4.generateSymmetricKey(); - Ciphertext ciphertext1KSM4 = null; - Ciphertext ciphertext1MSM4 = null; - - System.out.println("=================== do SM4 encrypt test ==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - ciphertext1KSM4 = sm4.encrypt(keySM4,data1K); -// ciphertext1MSM4 =sm4.encrypt(keySM4,data1M); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("SM4 Encrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - - System.out.println("=================== do SM4 decrypt test ==================="); - for (int r = 0; r < 5; r++) { - System.out.println("------------- round[" + r + "] --------------"); - long startTS = System.currentTimeMillis(); - for (int i = 0; i < count; i++) { - sm4.decrypt(keySM4,ciphertext1KSM4); -// sm4.decrypt(keySM4,ciphertext1MSM4); - } - long elapsedTS = System.currentTimeMillis() - startTS; - System.out.println(String.format("SM4 Decrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, - (count * 1000.00D) / elapsedTS)); - } - } -} diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/smutils/SM3UtilsTest.java b/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/smutils/SM3UtilsTest.java deleted file mode 100644 index 5d2ff04d..00000000 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/smutils/SM3UtilsTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package test.com.jd.blockchain.crypto.smutils; - -import com.jd.blockchain.crypto.smutils.hash.SM3Utils; -import org.bouncycastle.util.encoders.Hex; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class SM3UtilsTest { - - private static final int SM3DIGEST_LENGTH = 32; - - @Test - public void testHash() { - - String testString1 = "abc"; - String testString2 = "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"; - String expectedResult1="66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0" ; - String expectedResult2="debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732"; - - byte[] testString1Bytes = testString1.getBytes(); - byte[] testString2Bytes = testString2.getBytes(); - byte[] hash1 = SM3Utils.hash(testString1Bytes); - byte[] hash2 = SM3Utils.hash(testString2Bytes); - byte[] expectedResult1Bytes = expectedResult1.getBytes(); - byte[] expectedResult2Bytes = expectedResult2.getBytes(); - assertEquals(hash1.length, SM3DIGEST_LENGTH); - assertEquals(hash2.length, SM3DIGEST_LENGTH); - assertArrayEquals(hash1, Hex.decode(expectedResult1Bytes)); - assertArrayEquals(hash2, Hex.decode(expectedResult2Bytes)); - } - -} \ No newline at end of file diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/smutils/SM4UtilsTest.java b/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/smutils/SM4UtilsTest.java deleted file mode 100644 index 2137eec4..00000000 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/smutils/SM4UtilsTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package test.com.jd.blockchain.crypto.smutils; - -import com.jd.blockchain.crypto.smutils.symmetric.SM4Utils; -import org.bouncycastle.util.encoders.Hex; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class SM4UtilsTest { - - private static final int KEY_SIZE = 16; - private static final int BLOCK_SIZE = 16; - - @Test - public void testGenerateKey() { - byte[] key = SM4Utils.generateKey(); - assertEquals(KEY_SIZE,key.length); - } - - @Test - public void testEncrypt() { - - String plaintext = "0123456789abcdeffedcba9876543210"; - String key = "0123456789abcdeffedcba9876543210"; - String iv = "00000000000000000000000000000000"; - String expectedCiphertextIn2ndBlock = "681edf34d206965e86b3e94f536e4246"; - - byte[] plaintextBytes = Hex.decode(plaintext); - byte[] keyBytes = Hex.decode(key); - byte[] ivBytes = Hex.decode(iv); - byte[] expectedCiphertextIn2ndBlockBytes = Hex.decode(expectedCiphertextIn2ndBlock); - - - byte[] ciphertextbytes = SM4Utils.encrypt(plaintextBytes,keyBytes,ivBytes); - - assertEquals(BLOCK_SIZE*3,ciphertextbytes.length); - - byte[] ciphertextIn1stBlockBytes = new byte[BLOCK_SIZE]; - System.arraycopy(ciphertextbytes,0,ciphertextIn1stBlockBytes,0,BLOCK_SIZE); - assertArrayEquals(ivBytes,ciphertextIn1stBlockBytes); - - byte[] ciphertextIn2ndBlockBytes = new byte[BLOCK_SIZE]; - System.arraycopy(ciphertextbytes,BLOCK_SIZE,ciphertextIn2ndBlockBytes,0,BLOCK_SIZE); - assertArrayEquals(expectedCiphertextIn2ndBlockBytes,ciphertextIn2ndBlockBytes); - - - } - - @Test - public void testDecrypt() { - - String plaintext = "0123456789abcdeffedcba987654321000112233445566778899"; - String key = "0123456789abcdeffedcba9876543210"; - String iv = "0123456789abcdeffedcba9876543210"; - - byte[] plaintextBytes = Hex.decode(plaintext); - byte[] keyBytes = Hex.decode(key); - byte[] ivBytes = Hex.decode(iv); - - - byte[] ciphertext = SM4Utils.encrypt(plaintextBytes,keyBytes,ivBytes); - byte[] decryptedData = SM4Utils.decrypt(ciphertext,keyBytes); - assertArrayEquals(plaintextBytes,decryptedData); - - } -} \ No newline at end of file diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/symmetric/SymmetricCryptographyImplTest.java b/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/symmetric/SymmetricCryptographyImplTest.java deleted file mode 100644 index 37a3ceaf..00000000 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/symmetric/SymmetricCryptographyImplTest.java +++ /dev/null @@ -1,471 +0,0 @@ -package test.com.jd.blockchain.crypto.symmetric; - -import com.jd.blockchain.crypto.Ciphertext; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.impl.SymmetricCryptographyImpl; -import com.jd.blockchain.crypto.symmetric.SymmetricCryptography; -import com.jd.blockchain.crypto.symmetric.SymmetricEncryptionFunction; -import com.jd.blockchain.crypto.symmetric.SymmetricKey; -import com.jd.blockchain.utils.io.BytesUtils; - -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Random; - -import static com.jd.blockchain.crypto.CryptoKeyType.PRIV_KEY; -import static com.jd.blockchain.crypto.CryptoKeyType.SYMMETRIC_KEY; -import static org.junit.Assert.*; - -public class SymmetricCryptographyImplTest { - - @Test - public void testGenerateKey() { - - SymmetricCryptography symmetricCrypto = new SymmetricCryptographyImpl(); - - //test AES - CryptoAlgorithm algorithm = CryptoAlgorithm.AES; - verifyGenerateKey(symmetricCrypto,algorithm); - - //test SM4 - algorithm = CryptoAlgorithm.SM4; - verifyGenerateKey(symmetricCrypto,algorithm); - } - - private void verifyGenerateKey(SymmetricCryptography symmetricCrypto, CryptoAlgorithm algorithm){ - - SymmetricKey symmetricKey= symmetricCrypto.generateKey(algorithm); - - assertNotNull(symmetricKey); - assertEquals(algorithm, symmetricKey.getAlgorithm()); - assertEquals(128/8,symmetricKey.getRawKeyBytes().length); - - byte[] symmetricKeyBytes = symmetricKey.toBytes(); - //判断密钥数据长度=算法标识长度+密钥掩码长度+原始密钥长度 - assertEquals(1 + 1 + 128 / 8, symmetricKeyBytes.length); - - assertEquals(algorithm.CODE,symmetricKeyBytes[0]); - assertEquals(algorithm,CryptoAlgorithm.valueOf(symmetricKeyBytes[0])); - } - - @Test - public void testGetSymmetricEncryptionFunction() { - - SymmetricCryptography symmetricCrypto = new SymmetricCryptographyImpl(); - Random random = new Random(); - - - //test AES - CryptoAlgorithm algorithm = CryptoAlgorithm.AES; - - //Case 1: AES with 16 bytes data - //刚好一个分组长度,随机生成明文数据 - byte[] data = new byte[16]; - random.nextBytes(data); - verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 2*16, null); - - //Case 2: AES with 33 bytes data - //明文长度大于两倍分组长度,生成的密文是三倍分组长度 - data = new byte[33]; - random.nextBytes(data); - verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 3*16,null); - - //Case 3: AES with 3 bytes data - //明文长度小于分组长度,生成的密文是一倍分组长度 - data = new byte[3]; - random.nextBytes(data); - verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 16,null); - - //Case 4: AES with 0 bytes data - //明文长度小于分组长度,生成的密文是一倍分组长度 - data = new byte[0]; - random.nextBytes(data); - verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 16,null); - - //Case 5 AES with null - //明文为空,可以捕获到异常异常 - data = null; - verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 16,IllegalArgumentException.class); - - - //test ED25519 - algorithm = CryptoAlgorithm.ED25519; - data = new byte[16]; - random.nextBytes(data); - verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 16,IllegalArgumentException.class); - - - //test SM4 - algorithm = CryptoAlgorithm.SM4; - - //Case 1: SM4 with 16 bytes data - data = new byte[16]; - random.nextBytes(data); - //密文长度 = IV长度 + 真实密文长度 - verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 3*16, null); - - //Case 2: SM4 with 33 bytes data - data = new byte[33]; - random.nextBytes(data); - verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 4*16,null); - - //Case 3: SM4 with 3 bytes data - data = new byte[3]; - random.nextBytes(data); - verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 2*16,null); - - //Case 4: SM4 with 0 bytes data - data = new byte[0]; - random.nextBytes(data); - verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 2*16,null); - - //Case 5 SM4 with null - data = null; - verifyGetSymmetricEncryptionFunction(symmetricCrypto, algorithm, data, 16,IllegalArgumentException.class); - } - - //不同明文输入下,用来简化加解密过程的method - private void verifyGetSymmetricEncryptionFunction(SymmetricCryptography symmetricCrypto, CryptoAlgorithm algorithm, - byte[] data, int expectedCiphertextLength, Class expectedException){ - - //初始化一个异常 - Exception actualEx = null; - - try { - SymmetricEncryptionFunction sef = symmetricCrypto.getSymmetricEncryptionFunction(algorithm); - //验证获取的算法实例非空 - assertNotNull(sef); - - SymmetricKey symmetricKey = (SymmetricKey) sef.generateSymmetricKey(); - - //验证SymmetricKey的getAlgorithm方法 - assertEquals(algorithm, symmetricKey.getAlgorithm()); - //验证SymmetricKey的getRawKeyBytes方法 - assertEquals(16, symmetricKey.getRawKeyBytes().length); - //验证SymmetricKey的toBytes方法 - assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{SYMMETRIC_KEY.CODE},symmetricKey.getRawKeyBytes()), symmetricKey.toBytes()); - - - Ciphertext ciphertext = sef.encrypt(symmetricKey,data); - - //Ciphertext中算法标识与入参算法一致 - assertEquals(algorithm, ciphertext.getAlgorithm()); - //验证原始密文长度与预期长度一致 - assertEquals(expectedCiphertextLength, ciphertext.getRawCiphertext().length); - //验证密文数据长度=算法标识长度+预期长度 - byte[] ciphertextBytes = ciphertext.toBytes(); - assertArrayEquals(BytesUtils.concat(new byte[]{algorithm.CODE},ciphertext.getRawCiphertext()), ciphertextBytes); - - - //验证equal - assertTrue(ciphertext.equals(ciphertext)); - assertEquals(ciphertext.hashCode(),ciphertext.hashCode()); - - //验证SymmetricEncryptionFunction的decrypt - assertArrayEquals(data, sef.decrypt(symmetricKey,ciphertext)); - - //测试SymmetricEncryptionFunction的输入输出流的加解密方法 - InputStream inPlaintext = new ByteArrayInputStream(data); - //16字节的明文输入,将会产生32字节的密文 - OutputStream outCiphertext = new ByteArrayOutputStream(ciphertext.toBytes().length); - InputStream inCiphertext = new ByteArrayInputStream(ciphertext.toBytes()); - OutputStream outPlaintext = new ByteArrayOutputStream(data.length); - sef.encrypt(symmetricKey, inPlaintext, outCiphertext); - sef.decrypt(symmetricKey, inCiphertext, outPlaintext); - - //验证SymmetricEncryptionFunction的supportCiphertext方法 - assertTrue(sef.supportCiphertext(ciphertextBytes)); - - //验证SymmetricEncryptionFunction的resolveCiphertext方法 - assertEquals(ciphertext, sef.resolveCiphertext(ciphertextBytes)); - - //验证SymmetricEncryptionFunction的supportSymmetricKey方法 - assertTrue(sef.supportSymmetricKey(symmetricKey.toBytes())); - - //验证SymmetricEncryptionFunction的resolveSymmetricKey方法 - assertEquals(symmetricKey, sef.resolveSymmetricKey(symmetricKey.toBytes())); - - //验证SymmetricEncryptionFunction的getAlgorithm - assertEquals(algorithm, sef.getAlgorithm()); - - } catch (Exception e){ - actualEx = e; - } - - if (expectedException == null) { - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testDecrypt() { - - SymmetricCryptography symmetricCrypto = new SymmetricCryptographyImpl(); - Random randomData = new Random(); - Random randomKey = new Random(); - - - //test AES - CryptoAlgorithm algorithm = CryptoAlgorithm.AES; - SymmetricEncryptionFunction sef = symmetricCrypto.getSymmetricEncryptionFunction(algorithm); - - byte[] data = new byte[16]; - randomData.nextBytes(data); - byte[] key = new byte[16]; - randomKey.nextBytes(key); - - SymmetricKey symmetricKey = new SymmetricKey(algorithm, key); - byte[] ciphertextBytes = sef.encrypt(symmetricKey,data).toBytes(); - - verifyDecrypt(symmetricCrypto, algorithm, key, data, ciphertextBytes, null); - - //密钥的算法标识与密文的算法标识不一致情况 - verifyDecrypt(symmetricCrypto, CryptoAlgorithm.SM4, key, data, ciphertextBytes, IllegalArgumentException.class); - - //密文末尾两个字节丢失情况下,抛出异常 - byte[] truncatedCiphertextBytes = new byte[ciphertextBytes.length-2]; - System.arraycopy(ciphertextBytes,0,truncatedCiphertextBytes,0,truncatedCiphertextBytes.length); - verifyDecrypt(symmetricCrypto, algorithm, key, data, truncatedCiphertextBytes, IllegalArgumentException.class); - - byte[] ciphertextBytesWithWrongAlgCode = ciphertextBytes; - ciphertextBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyDecrypt(symmetricCrypto,algorithm,key,data,ciphertextBytesWithWrongAlgCode,IllegalArgumentException.class); - - ciphertextBytes = null; - verifyDecrypt(symmetricCrypto,algorithm,key,data,ciphertextBytes,NullPointerException.class); - - - //test SM4 - algorithm = CryptoAlgorithm.SM4; - sef = symmetricCrypto.getSymmetricEncryptionFunction(algorithm); - symmetricKey = new SymmetricKey(algorithm, key); - ciphertextBytes = sef.encrypt(symmetricKey,data).toBytes(); - - verifyDecrypt(symmetricCrypto, algorithm, key, data, ciphertextBytes, null); - - //密钥的算法标识与密文的算法标识不一致情况 - verifyDecrypt(symmetricCrypto, CryptoAlgorithm.AES, key, data, ciphertextBytes, IllegalArgumentException.class); - - //密文末尾两个字节丢失情况下,抛出异常 - truncatedCiphertextBytes = new byte[ciphertextBytes.length-2]; - System.arraycopy(ciphertextBytes,0,truncatedCiphertextBytes,0,truncatedCiphertextBytes.length); - verifyDecrypt(symmetricCrypto, algorithm, key, data, truncatedCiphertextBytes, IllegalArgumentException.class); - - ciphertextBytesWithWrongAlgCode = ciphertextBytes; - ciphertextBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyDecrypt(symmetricCrypto,algorithm,key,data,ciphertextBytesWithWrongAlgCode,IllegalArgumentException.class); - - ciphertextBytes = null; - verifyDecrypt(symmetricCrypto,algorithm,key,data,ciphertextBytes,NullPointerException.class); - } - - private void verifyDecrypt(SymmetricCryptography symmetricCrypto, CryptoAlgorithm algorithm, - byte[] key, byte[] data, byte[] ciphertextBytes, Class expectedException) { - - Exception actualEx = null; - - try { - SymmetricKey symmetricKey = new SymmetricKey(algorithm,key); - - byte[] plaintext = symmetricCrypto.decrypt(symmetricKey.toBytes(), ciphertextBytes); - - //解密后的明文与初始的明文一致 - assertArrayEquals(data,plaintext); - } - catch (Exception e){ - actualEx = e; - } - - if (expectedException == null) { - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testResolveCiphertext() { - - SymmetricCryptography symmetricCrypto = new SymmetricCryptographyImpl(); - Random randomData = new Random(); - Random randomKey = new Random(); - - //test AES - CryptoAlgorithm algorithm = CryptoAlgorithm.AES; - SymmetricEncryptionFunction sef = symmetricCrypto.getSymmetricEncryptionFunction(algorithm); - - byte[] data = new byte[16]; - randomData.nextBytes(data); - byte[] key = new byte[16]; - randomKey.nextBytes(key); - - SymmetricKey symmetricKey = new SymmetricKey(algorithm, key); - byte[] ciphertextBytes = sef.encrypt(symmetricKey,data).toBytes(); - verifyResolveCiphertext(symmetricCrypto, algorithm, ciphertextBytes, null); - - byte[] truncatedCiphertextBytes = new byte[ciphertextBytes.length-2]; - System.arraycopy(ciphertextBytes,0,truncatedCiphertextBytes,0,truncatedCiphertextBytes.length); - verifyResolveCiphertext(symmetricCrypto,algorithm,truncatedCiphertextBytes,IllegalArgumentException.class); - - byte[] ciphertextBytesWithWrongAlgCode = ciphertextBytes; - ciphertextBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyResolveCiphertext(symmetricCrypto,algorithm,ciphertextBytesWithWrongAlgCode,IllegalArgumentException.class); - - ciphertextBytes = null; - verifyResolveCiphertext(symmetricCrypto,algorithm,ciphertextBytes,NullPointerException.class); - - - //test SM4 - algorithm = CryptoAlgorithm.SM4; - sef = symmetricCrypto.getSymmetricEncryptionFunction(algorithm); - - symmetricKey = new SymmetricKey(algorithm, key); - ciphertextBytes = sef.encrypt(symmetricKey,data).toBytes(); - - verifyResolveCiphertext(symmetricCrypto, algorithm, ciphertextBytes, null); - - truncatedCiphertextBytes = new byte[ciphertextBytes.length-2]; - System.arraycopy(ciphertextBytes,0,truncatedCiphertextBytes,0,truncatedCiphertextBytes.length); - verifyResolveCiphertext(symmetricCrypto,algorithm,truncatedCiphertextBytes,IllegalArgumentException.class); - - ciphertextBytesWithWrongAlgCode = ciphertextBytes; - ciphertextBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyResolveCiphertext(symmetricCrypto,algorithm,ciphertextBytesWithWrongAlgCode,IllegalArgumentException.class); - - ciphertextBytes = null; - verifyResolveCiphertext(symmetricCrypto,algorithm,ciphertextBytes,NullPointerException.class); - } - - private void verifyResolveCiphertext(SymmetricCryptography symmetricCrypto, CryptoAlgorithm algorithm, byte[] ciphertextBytes, - Class expectedException) { - - Exception actualEx = null; - - try { - Ciphertext ciphertext = symmetricCrypto.resolveCiphertext(ciphertextBytes); - - assertNotNull(ciphertext); - - assertEquals(algorithm, ciphertext.getAlgorithm()); - - assertEquals(0, ciphertext.getRawCiphertext().length % 16); - - assertArrayEquals(ciphertextBytes, ciphertext.toBytes()); - } - catch (Exception e){ - actualEx = e; - } - - if (expectedException == null) { - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testTryResolveCiphertext() { - } - - - - @Test - public void testResolveSymmetricKey() { - - SymmetricCryptography symmetricCrypto = new SymmetricCryptographyImpl(); - - //test AES - CryptoAlgorithm algorithm = CryptoAlgorithm.AES; - - Random randomKey = new Random(); - byte[] key = new byte[16]; - randomKey.nextBytes(key); - - byte[] symmetricKeyBytes = BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{SYMMETRIC_KEY.CODE},key); - verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytes,null); - - byte[] truncatedSymmetricKeyBytes = new byte[symmetricKeyBytes.length-2]; - System.arraycopy(symmetricKeyBytes,0,truncatedSymmetricKeyBytes,0,truncatedSymmetricKeyBytes.length); - verifyResolveSymmetricKey(symmetricCrypto,algorithm,truncatedSymmetricKeyBytes,IllegalArgumentException.class); - - byte[] symmetricKeyBytesWithWrongAlgCode = symmetricKeyBytes; - symmetricKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytesWithWrongAlgCode,IllegalArgumentException.class); - - byte[] symmetricKeyBytesWithWrongKeyType= symmetricKeyBytes; - System.arraycopy(symmetricKeyBytes,0,symmetricKeyBytesWithWrongKeyType,0,symmetricKeyBytesWithWrongKeyType.length); - symmetricKeyBytesWithWrongKeyType[1] = PRIV_KEY.CODE; - verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytesWithWrongKeyType,IllegalArgumentException.class); - - symmetricKeyBytes = null; - verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytes,NullPointerException.class); - - - //test SM4 - algorithm = CryptoAlgorithm.SM4; - symmetricKeyBytes = BytesUtils.concat(new byte[]{algorithm.CODE},new byte[]{SYMMETRIC_KEY.CODE},key); - - verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytes,null); - - truncatedSymmetricKeyBytes = new byte[symmetricKeyBytes.length-2]; - System.arraycopy(symmetricKeyBytes,0,truncatedSymmetricKeyBytes,0,truncatedSymmetricKeyBytes.length); - verifyResolveSymmetricKey(symmetricCrypto,algorithm,truncatedSymmetricKeyBytes,IllegalArgumentException.class); - - symmetricKeyBytesWithWrongAlgCode = symmetricKeyBytes; - symmetricKeyBytesWithWrongAlgCode[0] = CryptoAlgorithm.SHA256.CODE; - verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytesWithWrongAlgCode,IllegalArgumentException.class); - - symmetricKeyBytesWithWrongKeyType= symmetricKeyBytes; - System.arraycopy(symmetricKeyBytes,0,symmetricKeyBytesWithWrongKeyType,0,symmetricKeyBytesWithWrongKeyType.length); - symmetricKeyBytesWithWrongKeyType[1] = PRIV_KEY.CODE; - verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytesWithWrongKeyType,IllegalArgumentException.class); - - symmetricKeyBytes = null; - verifyResolveSymmetricKey(symmetricCrypto,algorithm,symmetricKeyBytes,NullPointerException.class); - } - - private void verifyResolveSymmetricKey(SymmetricCryptography symmetricCrypto, CryptoAlgorithm algorithm, byte[] symmetricKeyBytes, - Class expectedException) { - - Exception actualEx = null; - - try { - SymmetricKey symmetricKey = symmetricCrypto.resolveSymmetricKey(symmetricKeyBytes); - - assertNotNull(symmetricKey); - - assertEquals(algorithm, symmetricKey.getAlgorithm()); - - assertEquals(16, symmetricKey.getRawKeyBytes().length); - - assertArrayEquals(symmetricKeyBytes, symmetricKey.toBytes()); - } - catch (Exception e){ - actualEx = e; - } - - if (expectedException == null) { - assertNull(actualEx); - } - else { - assertNotNull(actualEx); - assertTrue(expectedException.isAssignableFrom(actualEx.getClass())); - } - } - - @Test - public void testTryResolveSymmetricKey() { - } -} diff --git a/source/crypto/crypto-impl/pom.xml b/source/crypto/crypto-impl/pom.xml new file mode 100644 index 00000000..cb07d6c5 --- /dev/null +++ b/source/crypto/crypto-impl/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + + com.jd.blockchain + crypto + 0.9.0-SNAPSHOT + + crypto-impl + + + + com.jd.blockchain + crypto-framework + ${project.version} + + + + \ No newline at end of file diff --git a/source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/AsymmtricCryptographyImpl.java b/source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/AsymmtricCryptographyImpl.java new file mode 100644 index 00000000..41b38408 --- /dev/null +++ b/source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/AsymmtricCryptographyImpl.java @@ -0,0 +1,220 @@ +//package com.jd.blockchain.crypto.impl; +// +//import com.jd.blockchain.crypto.Ciphertext; +//import com.jd.blockchain.crypto.CryptoAlgorithm; +//import com.jd.blockchain.crypto.PrivKey; +//import com.jd.blockchain.crypto.PubKey; +//import com.jd.blockchain.crypto.asymmetric.*; +//import com.jd.blockchain.crypto.impl.jni.asymmetric.JNIED25519SignatureFunction; +//import com.jd.blockchain.crypto.impl.sm.asymmetric.SM2CryptoFunction; +//import com.jd.blockchain.crypto.service.classic.ED25519SignatureFunction; +// +//public class AsymmtricCryptographyImpl implements AsymmetricCryptography { +// +// private static final SignatureFunction ED25519_SIGF = new ED25519SignatureFunction(); +// +// private static final SignatureFunction SM2_SIGF = new SM2CryptoFunction(); +// +// private static final SignatureFunction JNIED25519_SIGF = new JNIED25519SignatureFunction(); +// +// private static final AsymmetricEncryptionFunction SM2_ENCF = new SM2CryptoFunction(); +// +// /** +// * 封装了非对称密码算法对应的密钥生成算法 +// */ +// @Override +// public CryptoKeyPair generateKeyPair(CryptoAlgorithm algorithm) { +// +// //判断算法是签名算法还是非对称加密算法,并根据算法生成密钥对,否则抛出异常 +// if (algorithm.isSignable() && algorithm.hasAsymmetricKey()){ +// return getSignatureFunction(algorithm).generateKeyPair(); +// } +// else if (algorithm.isEncryptable() && algorithm.hasAsymmetricKey()){ +// return getAsymmetricEncryptionFunction(algorithm).generateKeyPair(); +// } +// else throw new IllegalArgumentException("The specified algorithm is not signature or asymmetric encryption algorithm!"); +// } +// +// @Override +// public SignatureFunction getSignatureFunction(CryptoAlgorithm algorithm) { +// //遍历签名算法,如果满足,则返回实例 +// switch (algorithm) { +// case ED25519: +// return ED25519_SIGF; +// case SM2: +// return SM2_SIGF; +// case JNIED25519: +// return JNIED25519_SIGF; +// default: +// break; +// } +// throw new IllegalArgumentException("The specified algorithm is not signature algorithm!"); +// } +// +// @Override +// public boolean verify(byte[] digestBytes, byte[] pubKeyBytes, byte[] data) { +// +// //得到SignatureDigest类型的签名摘要,并得到算法标识 +// SignatureDigest signatureDigest = resolveSignatureDigest(digestBytes); +// CryptoAlgorithm algorithm = signatureDigest.getAlgorithm(); +// PubKey pubKey = resolvePubKey(pubKeyBytes); +// +// //验证两个输入中算法标识一致,否则抛出异常 +// if (algorithm != signatureDigest.getAlgorithm()) +// throw new IllegalArgumentException("Digest's algorithm and key's are not matching!"); +// +// //根据算法标识,调用对应算法实例来验证签名摘要 +// return getSignatureFunction(algorithm).verify(signatureDigest,pubKey,data); +// } +// +// @Override +// public AsymmetricEncryptionFunction getAsymmetricEncryptionFunction(CryptoAlgorithm algorithm) { +// //遍历非对称加密算法,如果满足,则返回实例 +// switch (algorithm) { +// case SM2: +// return SM2_ENCF; +// default: +// break; +// } +// throw new IllegalArgumentException("The specified algorithm is not asymmetric encryption algorithm!"); +// } +// +// @Override +// public byte[] decrypt(byte[] privKeyBytes, byte[] ciphertextBytes) { +// +// //分别得到PrivKey和Ciphertext类型的密钥和密文,以及privKey对应的算法 +// PrivKey privKey = resolvePrivKey(privKeyBytes); +// Ciphertext ciphertext = resolveCiphertext(ciphertextBytes); +// CryptoAlgorithm algorithm = privKey.getAlgorithm(); +// +// //验证两个输入中算法标识一致,否则抛出异常 +// if (algorithm != ciphertext.getAlgorithm()) +// throw new IllegalArgumentException("Ciphertext's algorithm and key's are not matching!"); +// +// //根据算法标识,调用对应算法实例来计算返回明文 +// return getAsymmetricEncryptionFunction(algorithm).decrypt(privKey,ciphertext); +// } +// +// @Override +// public Ciphertext resolveCiphertext(byte[] ciphertextBytes) { +// Ciphertext ciphertext = tryResolveCiphertext(ciphertextBytes); +// if (ciphertext == null) +// throw new IllegalArgumentException("This ciphertextBytes cannot be resolved!"); +// else return ciphertext; +// } +// +// @Override +// public Ciphertext tryResolveCiphertext(byte[] ciphertextBytes) { +// //遍历非对称加密算法,如果满足,则返回解析结果 +// if (SM2_ENCF.supportCiphertext(ciphertextBytes)){ +// return SM2_ENCF.resolveCiphertext(ciphertextBytes); +// } +// //否则返回null +// return null; +// } +// +// @Override +// public SignatureDigest resolveSignatureDigest(byte[] digestBytes) { +// SignatureDigest signatureDigest = tryResolveSignatureDigest(digestBytes); +// if (signatureDigest == null) +// throw new IllegalArgumentException("This digestBytes cannot be resolved!"); +// else return signatureDigest; +// } +// +// @Override +// public SignatureDigest tryResolveSignatureDigest(byte[] digestBytes) { +// //遍历签名算法,如果满足,则返回解析结果 +// if (ED25519_SIGF.supportDigest(digestBytes)){ +// return ED25519_SIGF.resolveDigest(digestBytes); +// } +// if (SM2_SIGF.supportDigest(digestBytes)){ +// return SM2_SIGF.resolveDigest(digestBytes); +// } +// if (JNIED25519_SIGF.supportDigest(digestBytes)){ +// return JNIED25519_SIGF.resolveDigest(digestBytes); +// } +// //否则返回null +// return null; +// } +// +// @Override +// public byte[] retrievePubKeyBytes(byte[] privKeyBytes) { +// byte[] pubKeyBytes = tryRetrievePubKeyBytes(privKeyBytes); +// if (pubKeyBytes == null) +// throw new IllegalArgumentException("The specified algorithm in privKeyBytes is not signature or asymmetric encryption algorithm!"); +// else return pubKeyBytes; +// } +// +// @Override +// public byte[] tryRetrievePubKeyBytes(byte[] privKeyBytes) { +// //解析私钥获得算法标识 +// CryptoAlgorithm algorithm = resolvePrivKey(privKeyBytes).getAlgorithm(); +// +// //判断算法是签名算法还是非对称加密算法,并根据算法生成密钥对,否则抛出异常 +// if (algorithm.isSignable() && algorithm.hasAsymmetricKey()){ +// return getSignatureFunction(algorithm).retrievePubKeyBytes(privKeyBytes); +// } +// else if (algorithm.isEncryptable() && algorithm.hasAsymmetricKey()){ +// return getAsymmetricEncryptionFunction(algorithm).retrievePubKeyBytes(privKeyBytes); +// } +// //否则返回null +// return null; +// } +// +// @Override +// public PubKey resolvePubKey(byte[] pubKeyBytes) { +// PubKey pubKey = tryResolvePubKey(pubKeyBytes); +// if (pubKey == null) +// throw new IllegalArgumentException("This pubKeyBytes cannot be resolved!"); +// else return pubKey; +// +// } +// +// @Override +// public PubKey tryResolvePubKey(byte[] pubKeyBytes) { +// //遍历签名算法,如果满足,则返回解析结果 +// if (ED25519_SIGF.supportPubKey(pubKeyBytes)){ +// return ED25519_SIGF.resolvePubKey(pubKeyBytes); +// } +// if (SM2_SIGF.supportPubKey(pubKeyBytes)){ +// return SM2_SIGF.resolvePubKey(pubKeyBytes); +// } +// if (JNIED25519_SIGF.supportPubKey(pubKeyBytes)){ +// return JNIED25519_SIGF.resolvePubKey(pubKeyBytes); +// } +// //遍历非对称加密算法,如果满足,则返回解析结果 +// if (SM2_ENCF.supportPubKey(pubKeyBytes)){ +// return SM2_ENCF.resolvePubKey(pubKeyBytes); +// } +// //否则返回null +// return null; +// } +// +// @Override +// public PrivKey resolvePrivKey(byte[] privKeyBytes) { +// PrivKey privKey = tryResolvePrivKey(privKeyBytes); +// if (privKey == null) +// throw new IllegalArgumentException("This privKeyBytes cannot be resolved!"); +// else return privKey; +// } +// +// @Override +// public PrivKey tryResolvePrivKey(byte[] privKeyBytes) { +// //遍历签名算法,如果满足,则返回解析结果 +// if (ED25519_SIGF.supportPrivKey(privKeyBytes)){ +// return ED25519_SIGF.resolvePrivKey(privKeyBytes); +// } +// if (SM2_SIGF.supportPrivKey(privKeyBytes)){ +// return SM2_SIGF.resolvePrivKey(privKeyBytes); +// } +// if (JNIED25519_SIGF.supportPrivKey(privKeyBytes)){ +// return JNIED25519_SIGF.resolvePrivKey(privKeyBytes); +// } +// //遍历非对称加密算法,如果满足,则返回解析结果 +// if (SM2_ENCF.supportPrivKey(privKeyBytes)){ +// return SM2_ENCF.resolvePrivKey(privKeyBytes); +// } +// //否则返回null +// return null; +// } +//} diff --git a/source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/CryptoFactoryImpl.java b/source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/CryptoFactoryImpl.java new file mode 100644 index 00000000..ffd35e66 --- /dev/null +++ b/source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/CryptoFactoryImpl.java @@ -0,0 +1,30 @@ +//package com.jd.blockchain.crypto.impl; +// +//import com.jd.blockchain.crypto.CryptoFactory; +//import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; +//import com.jd.blockchain.crypto.hash.HashCryptography; +//import com.jd.blockchain.crypto.symmetric.SymmetricCryptography; +// +//public class CryptoFactoryImpl implements CryptoFactory { +// +// //Field; +// private static HashCryptography hashCryptography = new HashCryptographyImpl(); +// private static AsymmetricCryptography asymmetricCryptography = new AsymmtricCryptographyImpl(); +// private static SymmetricCryptography symmetricCryptography = new SymmetricCryptographyImpl(); +// +// @Override +// public HashCryptography hashCryptography() { +// return hashCryptography; +// } +// +// @Override +// public AsymmetricCryptography asymmetricCryptography() { +// return asymmetricCryptography; +// } +// +// @Override +// public SymmetricCryptography symmetricCryptography() { +// return symmetricCryptography; +// } +// +//} diff --git a/source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/HashCryptographyImpl.java b/source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/HashCryptographyImpl.java new file mode 100644 index 00000000..59a1defc --- /dev/null +++ b/source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/HashCryptographyImpl.java @@ -0,0 +1,84 @@ +//package com.jd.blockchain.crypto.impl; +// +//import com.jd.blockchain.crypto.CryptoAlgorithm; +//import com.jd.blockchain.crypto.hash.HashCryptography; +//import com.jd.blockchain.crypto.hash.HashDigest; +//import com.jd.blockchain.crypto.hash.HashFunction; +//import com.jd.blockchain.crypto.impl.jni.hash.JNIRIPEMD160HashFunction; +//import com.jd.blockchain.crypto.impl.jni.hash.JNISHA256HashFunction; +//import com.jd.blockchain.crypto.impl.sm.hash.SM3HashFunction; +//import com.jd.blockchain.crypto.service.classic.RIPEMD160HashFunction; +//import com.jd.blockchain.crypto.service.classic.SHA256HashFunction; +// +//public class HashCryptographyImpl implements HashCryptography { +// +// private static final HashFunction SHA256_FUNC = new SHA256HashFunction(); +// private static final HashFunction RIPEMD160_FUNC = new RIPEMD160HashFunction(); +// private static final HashFunction SM3_FUNC = new SM3HashFunction(); +// +// private static final HashFunction JNISHA256_FUNC = new JNISHA256HashFunction(); +// private static final HashFunction JNIRIPEMD160_FUNC = new JNIRIPEMD160HashFunction(); +// +// @Override +// public HashFunction getFunction(CryptoAlgorithm algorithm) { +// +// // 遍历哈希算法,如果满足,则返回实例 +// switch (algorithm) { +// case SHA256: +// return SHA256_FUNC; +// case RIPEMD160: +// return RIPEMD160_FUNC; +// case SM3: +// return SM3_FUNC; +// case JNISHA256: +// return JNISHA256_FUNC; +// case JNIRIPEMD160: +// return JNIRIPEMD160_FUNC; +// default: +// break; +// } +// throw new IllegalArgumentException("The specified algorithm is not hash algorithm!"); +// } +// +// @Override +// public boolean verify(byte[] digestBytes, byte[] data) { +// HashDigest hashDigest = resolveHashDigest(digestBytes); +// return verify(hashDigest,data); +// } +// +// @Override +// public boolean verify(HashDigest digest, byte[] data) { +// CryptoAlgorithm algorithm = digest.getAlgorithm(); +// return getFunction(algorithm).verify(digest, data); +// } +// +// @Override +// public HashDigest resolveHashDigest(byte[] digestBytes) { +// HashDigest hashDigest = tryResolveHashDigest(digestBytes); +// if (hashDigest == null) +// throw new IllegalArgumentException("This digestBytes cannot be resolved!"); +// else return hashDigest; +// } +// +// @Override +// public HashDigest tryResolveHashDigest(byte[] digestBytes) { +// //遍历哈希函数,如果满足,则返回解析结果 +// if (SHA256_FUNC.supportHashDigest(digestBytes)) { +// return SHA256_FUNC.resolveHashDigest(digestBytes); +// } +// if (RIPEMD160_FUNC.supportHashDigest(digestBytes)) { +// return RIPEMD160_FUNC.resolveHashDigest(digestBytes); +// } +// if (SM3_FUNC.supportHashDigest(digestBytes)) { +// return SM3_FUNC.resolveHashDigest(digestBytes); +// } +// if (JNISHA256_FUNC.supportHashDigest(digestBytes)) { +// return JNISHA256_FUNC.resolveHashDigest(digestBytes); +// } +// if (JNIRIPEMD160_FUNC.supportHashDigest(digestBytes)) { +// return JNIRIPEMD160_FUNC.resolveHashDigest(digestBytes); +// } +// //否则返回null +// return null; +// } +//} \ No newline at end of file diff --git a/source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/SymmetricCryptographyImpl.java b/source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/SymmetricCryptographyImpl.java new file mode 100644 index 00000000..115855d0 --- /dev/null +++ b/source/crypto/crypto-impl/src/main/java/com/jd/blockchain/crypto/impl/SymmetricCryptographyImpl.java @@ -0,0 +1,101 @@ +//package com.jd.blockchain.crypto.impl; +// +//import com.jd.blockchain.crypto.Ciphertext; +//import com.jd.blockchain.crypto.CryptoAlgorithm; +//import com.jd.blockchain.crypto.SingleKey; +//import com.jd.blockchain.crypto.impl.sm.symmetric.SM4SymmetricEncryptionFunction; +//import com.jd.blockchain.crypto.service.classic.AESSymmetricEncryptionFunction; +//import com.jd.blockchain.crypto.symmetric.SymmetricCryptography; +//import com.jd.blockchain.crypto.symmetric.SymmetricEncryptionFunction; +// +//public class SymmetricCryptographyImpl implements SymmetricCryptography { +// +// private static final SymmetricEncryptionFunction AES_ENCF = new AESSymmetricEncryptionFunction(); +// private static final SymmetricEncryptionFunction SM4_ENCF = new SM4SymmetricEncryptionFunction(); +// +// /** +// * 封装了对称密码算法对应的密钥生成算法 +// */ +// @Override +// public SingleKey generateKey(CryptoAlgorithm algorithm) { +// +// //验证算法标识是对称加密算法,并根据算法生成对称密钥,否则抛出异常 +// if (algorithm.isEncryptable() && algorithm.isSymmetric() ){ +// return (SingleKey) getSymmetricEncryptionFunction(algorithm).generateSymmetricKey(); +// } +// else throw new IllegalArgumentException("The specified algorithm is not symmetric encryption algorithm!"); +// } +// +// @Override +// public SymmetricEncryptionFunction getSymmetricEncryptionFunction(CryptoAlgorithm algorithm) { +// +// // 遍历对称加密算法,如果满足,则返回实例 +// switch (algorithm) { +// case AES: +// return AES_ENCF; +// case SM4: +// return SM4_ENCF; +// default: +// break; +// } +// throw new IllegalArgumentException("The specified algorithm is not symmetric encryption algorithm!"); +// } +// +// @Override +// public byte[] decrypt(byte[] symmetricKeyBytes, byte[] ciphertextBytes) { +// +// //分别得到SymmetricKey和Ciphertext类型的密钥和密文,以及symmetricKey对应的算法 +// SingleKey symmetricKey = resolveSymmetricKey(symmetricKeyBytes); +// Ciphertext ciphertext = resolveCiphertext(ciphertextBytes); +// CryptoAlgorithm algorithm = symmetricKey.getAlgorithm(); +// +// //验证两个输入中算法标识一致,否则抛出异常 +// if (algorithm != ciphertext.getAlgorithm()) +// throw new IllegalArgumentException("Ciphertext's algorithm and key's are not matching!"); +// +// //根据算法标识,调用对应算法实例来计算返回明文 +// return getSymmetricEncryptionFunction(algorithm).decrypt(symmetricKey,ciphertext); +// } +// +// @Override +// public Ciphertext resolveCiphertext(byte[] ciphertextBytes) { +// Ciphertext ciphertext = tryResolveCiphertext(ciphertextBytes); +// if (ciphertext == null) +// throw new IllegalArgumentException("This ciphertextBytes cannot be resolved!"); +// else return ciphertext; +// } +// +// @Override +// public Ciphertext tryResolveCiphertext(byte[] ciphertextBytes) { +// //遍历对称加密算法,如果满足,则返回解析结果 +// if (AES_ENCF.supportCiphertext(ciphertextBytes)) { +// return AES_ENCF.resolveCiphertext(ciphertextBytes); +// } +// if (SM4_ENCF.supportCiphertext(ciphertextBytes)) { +// return SM4_ENCF.resolveCiphertext(ciphertextBytes); +// } +// //否则返回null +// return null; +// } +// +// @Override +// public SingleKey resolveSymmetricKey(byte[] symmetricKeyBytes) { +// SingleKey symmetricKey = tryResolveSymmetricKey(symmetricKeyBytes); +// if (symmetricKey == null) +// throw new IllegalArgumentException("This symmetricKeyBytes cannot be resolved!"); +// else return symmetricKey; +// } +// +// @Override +// public SingleKey tryResolveSymmetricKey(byte[] symmetricKeyBytes) { +// //遍历对称加密算法,如果满足,则返回解析结果 +// if(AES_ENCF.supportSymmetricKey(symmetricKeyBytes)) { +// return AES_ENCF.resolveSymmetricKey(symmetricKeyBytes); +// } +// if(SM4_ENCF.supportSymmetricKey(symmetricKeyBytes)) { +// return SM4_ENCF.resolveSymmetricKey(symmetricKeyBytes); +// } +// //否则返回null +// return null; +// } +//} diff --git a/source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MyAsymmetricEncryptionTest.java b/source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MyAsymmetricEncryptionTest.java new file mode 100644 index 00000000..87ff496f --- /dev/null +++ b/source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MyAsymmetricEncryptionTest.java @@ -0,0 +1,55 @@ +//package test.com.jd.blockchain.crypto.performance; +// +//import com.jd.blockchain.crypto.Ciphertext; +//import com.jd.blockchain.crypto.PrivKey; +//import com.jd.blockchain.crypto.PubKey; +//import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; +//import com.jd.blockchain.crypto.impl.sm.asymmetric.SM2CryptoFunction; +//import org.bouncycastle.util.encoders.Hex; +// +//public class MyAsymmetricEncryptionTest { +// +// public static void main(String[] args) { +// +// String string1K = "0123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210"; +// String string1M = ""; +// for (int i = 0; i < 1024 ; i++) +// { +// string1M = string1M + string1K; +// } +// +// byte[] data1K = Hex.decode(string1K); +// byte[] data1M = Hex.decode(string1M); +// int count = 10000; +// +// SM2CryptoFunction sm2 = new SM2CryptoFunction(); +// CryptoKeyPair keyPairSM2 = sm2.generateKeyPair(); +// PrivKey privKeySM2 = keyPairSM2.getPrivKey(); +// PubKey pubKeySM2 = keyPairSM2.getPubKey(); +// +// System.out.println("=================== do SM2 encrypt test ==================="); +// Ciphertext ciphertextSM2 = null; +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// ciphertextSM2 = sm2.encrypt(pubKeySM2,data1K); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM2 Encrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// System.out.println("=================== do SM2 decrypt test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// sm2.decrypt(privKeySM2,ciphertextSM2); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM2 Decrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// } +//} diff --git a/source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MyHashTest.java b/source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MyHashTest.java new file mode 100644 index 00000000..dfa922fc --- /dev/null +++ b/source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MyHashTest.java @@ -0,0 +1,62 @@ +//package test.com.jd.blockchain.crypto.performance; +// +//import com.jd.blockchain.crypto.impl.sm.hash.SM3HashFunction; +//import com.jd.blockchain.crypto.service.classic.RIPEMD160HashFunction; +//import com.jd.blockchain.crypto.service.classic.SHA256HashFunction; +// +//import java.util.Random; +// +//public class MyHashTest { +// +// public static void main(String[] args) { +// +// Random rand = new Random(); +// byte[] data1K = new byte[1024]; +// rand.nextBytes(data1K); +// int count = 1000000; +// +// SHA256HashFunction sha256hf = new SHA256HashFunction(); +// +// System.out.println("=================== do SHA256 hash test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// sha256hf.hash(data1K); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SHA256 hashing Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// RIPEMD160HashFunction ripemd160hf = new RIPEMD160HashFunction(); +// +// System.out.println("=================== do RIPEMD160 hash test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// ripemd160hf.hash(data1K); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("RIPEMD160 hashing Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// SM3HashFunction sm3hf = new SM3HashFunction(); +// +// System.out.println("=================== do SM3 hash test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// sm3hf.hash(data1K); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM3 hashing Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// } +//} +// diff --git a/source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MySignatureTest.java b/source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MySignatureTest.java new file mode 100644 index 00000000..684ecdb6 --- /dev/null +++ b/source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MySignatureTest.java @@ -0,0 +1,83 @@ +//package test.com.jd.blockchain.crypto.performance; +// +//import com.jd.blockchain.crypto.PrivKey; +//import com.jd.blockchain.crypto.PubKey; +//import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; +//import com.jd.blockchain.crypto.asymmetric.SignatureDigest; +//import com.jd.blockchain.crypto.impl.sm.asymmetric.SM2CryptoFunction; +//import com.jd.blockchain.crypto.service.classic.ED25519SignatureFunction; +// +//import java.util.Random; +// +//public class MySignatureTest { +// +// public static void main(String[] args) { +// +// Random rand = new Random(); +// byte[] data = new byte[64]; +// rand.nextBytes(data); +// int count = 10000; +// +// ED25519SignatureFunction ed25519sf = new ED25519SignatureFunction(); +// CryptoKeyPair keyPairED25519 = ed25519sf.generateKeyPair(); +// PrivKey privKeyED25519 = keyPairED25519.getPrivKey(); +// PubKey pubKeyED25519 = keyPairED25519.getPubKey(); +// +// System.out.println("=================== do ED25519 sign test ==================="); +// SignatureDigest signatureDigestED25519 = null; +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// signatureDigestED25519 = ed25519sf.sign(privKeyED25519,data); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("ED25519 Signing Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// System.out.println("=================== do ED25519 verify test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// ed25519sf.verify(signatureDigestED25519,pubKeyED25519,data); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("ED25519 Verifying Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// SM2CryptoFunction sm2 = new SM2CryptoFunction(); +// CryptoKeyPair keyPairSM2 = sm2.generateKeyPair(); +// PrivKey privKeySM2 = keyPairSM2.getPrivKey(); +// PubKey pubKeySM2 = keyPairSM2.getPubKey(); +// +// +// System.out.println("=================== do SM2 sign test ==================="); +// SignatureDigest signatureDigestSM2 = null; +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// signatureDigestSM2 = sm2.sign(privKeySM2,data); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM2 Signing Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// System.out.println("=================== do SM2 verify test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// sm2.verify(signatureDigestSM2,pubKeySM2,data); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM2 Verifying Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// } +//} diff --git a/source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MySymmetricEncryptionTest.java b/source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MySymmetricEncryptionTest.java new file mode 100644 index 00000000..fbab4b13 --- /dev/null +++ b/source/crypto/crypto-impl/src/test/java/test/com/jd/blockchain/crypto/performance/MySymmetricEncryptionTest.java @@ -0,0 +1,92 @@ +//package test.com.jd.blockchain.crypto.performance; +// +//import com.jd.blockchain.crypto.Ciphertext; +//import com.jd.blockchain.crypto.SingleKey; +//import com.jd.blockchain.crypto.impl.sm.symmetric.SM4SymmetricEncryptionFunction; +//import com.jd.blockchain.crypto.service.classic.AESSymmetricEncryptionFunction; +// +//import org.bouncycastle.util.encoders.Hex; +// +//public class MySymmetricEncryptionTest { +// +// public static void main(String[] args) { +// +// String string1K = "0123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210"; +// +//// String string1M = ""; +//// for (int i = 0; i < 1024 ; i++) +//// { +//// string1M = string1M + string1K; +//// } +// +// byte[] data1K = Hex.decode(string1K); +//// byte[] data1M = Hex.decode(string1M); +// +// int count = 100000; +// +// +// AESSymmetricEncryptionFunction aes = new AESSymmetricEncryptionFunction(); +// SingleKey keyAES = (SingleKey) aes.generateSymmetricKey(); +// Ciphertext ciphertext1KAES = null; +// Ciphertext ciphertext1MAES = null; +// +// System.out.println("=================== do AES encrypt test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// ciphertext1KAES = aes.encrypt(keyAES,data1K); +//// ciphertext1MAES = aes.encrypt(keyAES,data1M); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("AES Encrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// +// +// System.out.println("=================== do AES decrypt test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// aes.decrypt(keyAES,ciphertext1KAES); +//// aes.decrypt(keyAES,ciphertext1MAES); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("AES Decrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// SM4SymmetricEncryptionFunction sm4 = new SM4SymmetricEncryptionFunction(); +// SingleKey keySM4 = (SingleKey) sm4.generateSymmetricKey(); +// Ciphertext ciphertext1KSM4 = null; +// Ciphertext ciphertext1MSM4 = null; +// +// System.out.println("=================== do SM4 encrypt test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// ciphertext1KSM4 = sm4.encrypt(keySM4,data1K); +//// ciphertext1MSM4 =sm4.encrypt(keySM4,data1M); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM4 Encrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// System.out.println("=================== do SM4 decrypt test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// sm4.decrypt(keySM4,ciphertext1KSM4); +//// sm4.decrypt(keySM4,ciphertext1MSM4); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM4 Decrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// } +//} diff --git a/source/crypto/crypto-sm/pom.xml b/source/crypto/crypto-sm/pom.xml new file mode 100644 index 00000000..607c7360 --- /dev/null +++ b/source/crypto/crypto-sm/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + + com.jd.blockchain + crypto + 0.9.0-SNAPSHOT + + crypto-sm + + + + com.jd.blockchain + crypto-framework + ${project.version} + + + + \ No newline at end of file diff --git a/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SM2CryptoFunction.java b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SM2CryptoFunction.java new file mode 100644 index 00000000..2c2c1fb2 --- /dev/null +++ b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SM2CryptoFunction.java @@ -0,0 +1,212 @@ +package com.jd.blockchain.crypto.service.sm; + +import static com.jd.blockchain.crypto.BaseCryptoKey.KEY_TYPE_BYTES; +import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_CODE_SIZE; +import static com.jd.blockchain.crypto.CryptoKeyType.PRIV_KEY; +import static com.jd.blockchain.crypto.CryptoKeyType.PUB_KEY; + +import com.jd.blockchain.crypto.*; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; + +import com.jd.blockchain.crypto.asymmetric.AsymmetricCiphertext; +import com.jd.blockchain.crypto.asymmetric.AsymmetricEncryptionFunction; +import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; +import com.jd.blockchain.crypto.asymmetric.SignatureDigest; +import com.jd.blockchain.crypto.asymmetric.SignatureFunction; +import com.jd.blockchain.crypto.utils.sm.SM2Utils; + +public class SM2CryptoFunction implements AsymmetricEncryptionFunction, SignatureFunction { + + private static final CryptoAlgorithm SM2 = SMCryptoService.SM2_ALGORITHM; + + private static final int ECPOINT_SIZE = 65; + private static final int PRIVKEY_SIZE = 32; + private static final int SIGNATUREDIGEST_SIZE = 64; + private static final int HASHDIGEST_SIZE = 32; + + private static final int PUBKEY_LENGTH = ALGORYTHM_CODE_SIZE + KEY_TYPE_BYTES + ECPOINT_SIZE; + private static final int PRIVKEY_LENGTH = ALGORYTHM_CODE_SIZE + KEY_TYPE_BYTES + PRIVKEY_SIZE; + private static final int SIGNATUREDIGEST_LENGTH = ALGORYTHM_CODE_SIZE + SIGNATUREDIGEST_SIZE; + + SM2CryptoFunction() { + } + + @Override + public Ciphertext encrypt(PubKey pubKey, byte[] data) { + + byte[] rawPubKeyBytes = pubKey.getRawKeyBytes(); + + // 验证原始公钥长度为65字节 + if (rawPubKeyBytes.length != ECPOINT_SIZE) { + throw new CryptoException("This key has wrong format!"); + } + + // 验证密钥数据的算法标识对应SM2算法 + if (pubKey.getAlgorithm().code() != SM2.code()) { + throw new CryptoException("The is not sm2 public key!"); + } + + // 调用SM2加密算法计算密文 + return new AsymmetricCiphertext(SM2, SM2Utils.encrypt(data, rawPubKeyBytes)); + } + + @Override + public byte[] decrypt(PrivKey privKey, Ciphertext ciphertext) { + + byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); + byte[] rawCiphertextBytes = ciphertext.getRawCiphertext(); + + // 验证原始私钥长度为32字节 + if (rawPrivKeyBytes.length != PRIVKEY_SIZE) { + throw new CryptoException("This key has wrong format!"); + } + + // 验证密钥数据的算法标识对应SM2算法 + if (privKey.getAlgorithm().code() != SM2.code()) { + throw new CryptoException("This key is not SM2 private key!"); + } + + // 验证密文数据的算法标识对应SM2签名算法,并且原始摘要长度为64字节 + if (ciphertext.getAlgorithm().code() != SM2.code() + || rawCiphertextBytes.length < ECPOINT_SIZE + HASHDIGEST_SIZE) { + throw new CryptoException("This is not SM2 ciphertext!"); + } + + // 调用SM2解密算法得到明文结果 + return SM2Utils.decrypt(rawCiphertextBytes, rawPrivKeyBytes); + } + + @Override + public byte[] retrievePubKeyBytes(byte[] privKeyBytes) { + + byte[] rawPrivKeyBytes = resolvePrivKey(privKeyBytes).getRawKeyBytes(); + byte[] rawPubKeyBytes = SM2Utils.retrievePublicKey(rawPrivKeyBytes); + return new PubKey(SM2, rawPubKeyBytes).toBytes(); + } + + @Override + public boolean supportPrivKey(byte[] privKeyBytes) { + // 验证输入字节数组长度=算法标识长度+密钥类型长度+密钥长度,密钥数据的算法标识对应SM2算法,并且密钥类型是私钥 + return privKeyBytes.length == PRIVKEY_LENGTH && CryptoAlgorithm.match(SM2, privKeyBytes) + && privKeyBytes[ALGORYTHM_CODE_SIZE] == PRIV_KEY.CODE; + } + + @Override + public PrivKey resolvePrivKey(byte[] privKeyBytes) { + // 由框架调用 support 方法检查有效性,在此不做重复检查; + return new PrivKey(privKeyBytes); + } + + @Override + public boolean supportPubKey(byte[] pubKeyBytes) { + // 验证输入字节数组长度=算法标识长度+密钥类型长度+椭圆曲线点长度,密钥数据的算法标识对应SM2算法,并且密钥类型是公钥 + return pubKeyBytes.length == PUBKEY_LENGTH && CryptoAlgorithm.match(SM2, pubKeyBytes) + && pubKeyBytes[ALGORYTHM_CODE_SIZE] == PUB_KEY.CODE; + } + + @Override + public PubKey resolvePubKey(byte[] pubKeyBytes) { + // 由框架调用 support 方法检查有效性,在此不做重复检查; + return new PubKey(pubKeyBytes); + } + + @Override + public boolean supportCiphertext(byte[] ciphertextBytes) { + // 验证输入字节数组长度>=算法标识长度+椭圆曲线点长度+哈希长度,字节数组的算法标识对应SM2算法 + return ciphertextBytes.length >= ALGORYTHM_CODE_SIZE + ECPOINT_SIZE + HASHDIGEST_SIZE + && CryptoAlgorithm.match(SM2, ciphertextBytes); + } + + @Override + public AsymmetricCiphertext resolveCiphertext(byte[] ciphertextBytes) { + // 由框架调用 support 方法检查有效性,在此不做重复检查; + return new AsymmetricCiphertext(ciphertextBytes); + } + + @Override + public SignatureDigest sign(PrivKey privKey, byte[] data) { + + byte[] rawPrivKeyBytes = privKey.getRawKeyBytes(); + + // 验证原始私钥长度为256比特,即32字节 + if (rawPrivKeyBytes.length != PRIVKEY_SIZE) { + throw new CryptoException("This key has wrong format!"); + } + + // 验证密钥数据的算法标识对应SM2签名算法 + if (privKey.getAlgorithm().code() != SM2.code()) { + throw new CryptoException("This key is not SM2 private key!"); + } + + // 调用SM2签名算法计算签名结果 + return new SignatureDigest(SM2, SM2Utils.sign(data, rawPrivKeyBytes)); + } + + @Override + public boolean verify(SignatureDigest digest, PubKey pubKey, byte[] data) { + + byte[] rawPubKeyBytes = pubKey.getRawKeyBytes(); + byte[] rawDigestBytes = digest.getRawDigest(); + + // 验证原始公钥长度为520比特,即65字节 + if (rawPubKeyBytes.length != ECPOINT_SIZE) { + throw new CryptoException("This key has wrong format!"); + } + + // 验证密钥数据的算法标识对应SM2签名算法 + if (pubKey.getAlgorithm().code() != SM2.code()) { + throw new CryptoException("This key is not SM2 public key!"); + } + + // 验证签名数据的算法标识对应SM2签名算法,并且原始签名长度为64字节 + if (digest.getAlgorithm().code() != SM2.code() || rawDigestBytes.length != SIGNATUREDIGEST_SIZE) { + throw new CryptoException("This is not SM2 signature digest!"); + } + + // 调用SM2验签算法验证签名结果 + return SM2Utils.verify(data, rawPubKeyBytes, rawDigestBytes); + } + + @Override + public boolean supportDigest(byte[] digestBytes) { + // 验证输入字节数组长度=算法标识长度+签名长度,字节数组的算法标识对应SM2算法 + return digestBytes.length == SIGNATUREDIGEST_LENGTH && CryptoAlgorithm.match(SM2, digestBytes); + } + + @Override + public SignatureDigest resolveDigest(byte[] digestBytes) { + // 由框架调用 support 方法检查有效性,在此不做重复检查; + return new SignatureDigest(digestBytes); + } + + @Override + public CryptoAlgorithm getAlgorithm() { + return SM2; + } + + @Override + public CryptoKeyPair generateKeyPair() { + + // 调用SM2算法的密钥生成算法生成公私钥对priKey和pubKey,返回密钥对 + AsymmetricCipherKeyPair keyPair = SM2Utils.generateKeyPair(); + ECPrivateKeyParameters ecPriv = (ECPrivateKeyParameters) keyPair.getPrivate(); + ECPublicKeyParameters ecPub = (ECPublicKeyParameters) keyPair.getPublic(); + + byte[] privKeyBytesD = ecPriv.getD().toByteArray(); + byte[] privKeyBytes = new byte[PRIVKEY_SIZE]; + if (privKeyBytesD.length > PRIVKEY_SIZE) { + System.arraycopy(privKeyBytesD, privKeyBytesD.length - PRIVKEY_SIZE, + privKeyBytes, 0, PRIVKEY_SIZE); + } + else { + System.arraycopy(privKeyBytesD, 0, + privKeyBytes, PRIVKEY_SIZE - privKeyBytesD.length, privKeyBytesD.length); + } + + byte[] pubKeyBytes = ecPub.getQ().getEncoded(false); + + return new CryptoKeyPair(new PubKey(SM2, pubKeyBytes), new PrivKey(SM2, privKeyBytes)); + } +} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/sm/hash/SM3HashFunction.java b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SM3HashFunction.java similarity index 66% rename from source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/sm/hash/SM3HashFunction.java rename to source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SM3HashFunction.java index 98f9a487..b79a0f07 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/impl/sm/hash/SM3HashFunction.java +++ b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SM3HashFunction.java @@ -1,20 +1,24 @@ -package com.jd.blockchain.crypto.impl.sm.hash; +package com.jd.blockchain.crypto.service.sm; + +import java.util.Arrays; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoBytes; +import com.jd.blockchain.crypto.CryptoException; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.crypto.hash.HashFunction; -import com.jd.blockchain.crypto.smutils.hash.SM3Utils; - -import java.util.Arrays; - -import static com.jd.blockchain.crypto.CryptoAlgorithm.SM3; +import com.jd.blockchain.crypto.utils.sm.SM3Utils; public class SM3HashFunction implements HashFunction { - private static final int DIGEST_BYTES = 256/8; + private static final CryptoAlgorithm SM3 = SMCryptoService.SM3_ALGORITHM; - private static final int DIGEST_LENGTH = CryptoBytes.ALGORYTHM_BYTES + DIGEST_BYTES; + private static final int DIGEST_BYTES = 256 / 8; + + private static final int DIGEST_LENGTH = CryptoBytes.ALGORYTHM_CODE_SIZE + DIGEST_BYTES; + + SM3HashFunction() { + } @Override public CryptoAlgorithm getAlgorithm() { @@ -23,8 +27,13 @@ public class SM3HashFunction implements HashFunction { @Override public HashDigest hash(byte[] data) { + + if (data == null) { + throw new CryptoException("The input is null!"); + } + byte[] digestBytes = SM3Utils.hash(data); - return new HashDigest(SM3,digestBytes); + return new HashDigest(SM3, digestBytes); } @Override @@ -36,7 +45,7 @@ public class SM3HashFunction implements HashFunction { @Override public boolean supportHashDigest(byte[] digestBytes) { // 验证输入字节数组长度=算法标识长度+摘要长度,以及算法标识; - return SM3.CODE == digestBytes[0] && DIGEST_LENGTH == digestBytes.length; + return CryptoAlgorithm.match(SM3, digestBytes) && DIGEST_LENGTH == digestBytes.length; } @Override diff --git a/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SM4EncryptionFunction.java b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SM4EncryptionFunction.java new file mode 100644 index 00000000..d4766af5 --- /dev/null +++ b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SM4EncryptionFunction.java @@ -0,0 +1,148 @@ +package com.jd.blockchain.crypto.service.sm; + +import static com.jd.blockchain.crypto.BaseCryptoKey.KEY_TYPE_BYTES; +import static com.jd.blockchain.crypto.CryptoBytes.ALGORYTHM_CODE_SIZE; +import static com.jd.blockchain.crypto.CryptoKeyType.SYMMETRIC_KEY; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import com.jd.blockchain.crypto.*; +import com.jd.blockchain.crypto.symmetric.SymmetricCiphertext; +import com.jd.blockchain.crypto.symmetric.SymmetricEncryptionFunction; +import com.jd.blockchain.crypto.utils.sm.SM4Utils; + +public class SM4EncryptionFunction implements SymmetricEncryptionFunction { + + private static final CryptoAlgorithm SM4 = SMCryptoService.SM4_ALGORITHM; + + private static final int KEY_SIZE = 128 / 8; + private static final int BLOCK_SIZE = 128 / 8; + + private static final int SYMMETRICKEY_LENGTH = ALGORYTHM_CODE_SIZE + KEY_TYPE_BYTES + KEY_SIZE; + + SM4EncryptionFunction() { + } + + @Override + public Ciphertext encrypt(SymmetricKey key, byte[] data) { + + byte[] rawKeyBytes = key.getRawKeyBytes(); + + // 验证原始密钥长度为128比特,即16字节 + if (rawKeyBytes.length != KEY_SIZE) { + throw new CryptoException("This key has wrong format!"); + } + + // 验证密钥数据的算法标识对应SM4算法 + if (key.getAlgorithm().code() != SM4.code()) { + throw new CryptoException("The is not SM4 symmetric key!"); + } + + // 调用底层SM4算法并计算密文数据 + return new SymmetricCiphertext(SM4, SM4Utils.encrypt(data, rawKeyBytes)); + } + + @Override + public void encrypt(SymmetricKey key, InputStream in, OutputStream out) { + + // 读输入流得到明文,加密,密文数据写入输出流 + try { + byte[] sm4Data = new byte[in.available()]; + in.read(sm4Data); + in.close(); + + out.write(encrypt(key, sm4Data).toBytes()); + out.close(); + } catch (IOException e) { + throw new CryptoException(e.getMessage(), e); + } + } + + @Override + public byte[] decrypt(SymmetricKey key, Ciphertext ciphertext) { + + byte[] rawKeyBytes = key.getRawKeyBytes(); + byte[] rawCiphertextBytes = ciphertext.getRawCiphertext(); + + // 验证原始密钥长度为128比特,即16字节 + if (rawKeyBytes.length != KEY_SIZE) { + throw new CryptoException("This key has wrong format!"); + } + + // 验证密钥数据的算法标识对应SM4算法 + if (key.getAlgorithm().code() != SM4.code()) { + throw new CryptoException("The is not SM4 symmetric key!"); + } + + // 验证原始密文长度为分组长度的整数倍 + if (rawCiphertextBytes.length % BLOCK_SIZE != 0) { + throw new CryptoException("This ciphertext has wrong format!"); + } + + // 验证密文数据算法标识对应SM4算法 + if (ciphertext.getAlgorithm().code() != SM4.code()) { + throw new CryptoException("This is not SM4 ciphertext!"); + } + + // 调用底层SM4算法解密,得到明文 + return SM4Utils.decrypt(rawCiphertextBytes, rawKeyBytes); + } + + @Override + public void decrypt(SymmetricKey key, InputStream in, OutputStream out) { + // 读输入流得到密文数据,解密,明文写入输出流 + try { + byte[] sm4Data = new byte[in.available()]; + in.read(sm4Data); + in.close(); + + if (!supportCiphertext(sm4Data)) { + throw new CryptoException("InputStream is not valid SM4 ciphertext!"); + } + + out.write(decrypt(key, resolveCiphertext(sm4Data))); + out.close(); + } catch (IOException e) { + throw new CryptoException(e.getMessage(), e); + } + } + + @Override + public boolean supportSymmetricKey(byte[] symmetricKeyBytes) { + // 验证输入字节数组长度=算法标识长度+密钥类型长度+密钥长度,字节数组的算法标识对应SM4算法且密钥密钥类型是对称密钥 + return symmetricKeyBytes.length == SYMMETRICKEY_LENGTH && CryptoAlgorithm.match(SM4, symmetricKeyBytes) + && symmetricKeyBytes[ALGORYTHM_CODE_SIZE] == SYMMETRIC_KEY.CODE; + } + + @Override + public SymmetricKey resolveSymmetricKey(byte[] symmetricKeyBytes) { + // 由框架调用 support 方法检查有效性,在此不做重复检查; + return new SymmetricKey(symmetricKeyBytes); + } + + @Override + public boolean supportCiphertext(byte[] ciphertextBytes) { + // 验证(输入字节数组长度-算法标识长度)是分组长度的整数倍,字节数组的算法标识对应SM4算法 + return (ciphertextBytes.length - ALGORYTHM_CODE_SIZE) % BLOCK_SIZE == 0 + && CryptoAlgorithm.match(SM4, ciphertextBytes); + } + + @Override + public SymmetricCiphertext resolveCiphertext(byte[] ciphertextBytes) { + // 由框架调用 support 方法检查有效性,在此不做重复检查; + return new SymmetricCiphertext(ciphertextBytes); + } + + @Override + public CryptoAlgorithm getAlgorithm() { + return SM4; + } + + @Override + public CryptoKey generateSymmetricKey() { + // 根据对应的标识和原始密钥生成相应的密钥数据 + return new SymmetricKey(SM4, SM4Utils.generateKey()); + } +} diff --git a/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SMCryptoService.java b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SMCryptoService.java new file mode 100644 index 00000000..a4579d27 --- /dev/null +++ b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/service/sm/SMCryptoService.java @@ -0,0 +1,47 @@ +package com.jd.blockchain.crypto.service.sm; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoAlgorithmDefinition; +import com.jd.blockchain.crypto.CryptoFunction; +import com.jd.blockchain.crypto.CryptoService; +import com.jd.blockchain.provider.NamedProvider; + +/** + * 国密软实现; + * + * @author huanghaiquan + * + */ +@NamedProvider("SM-SOFTWARE") +public class SMCryptoService implements CryptoService { + + public static final CryptoAlgorithm SM2_ALGORITHM = CryptoAlgorithmDefinition.defineSignature("SM2", + true, (byte) 2); + + public static final CryptoAlgorithm SM3_ALGORITHM = CryptoAlgorithmDefinition.defineHash("SM3", (byte) 3); + + public static final CryptoAlgorithm SM4_ALGORITHM = CryptoAlgorithmDefinition.defineSymmetricEncryption("SM4", + (byte) 4); + + public static final SM2CryptoFunction SM2 = new SM2CryptoFunction(); + public static final SM3HashFunction SM3 = new SM3HashFunction(); + public static final SM4EncryptionFunction SM4 = new SM4EncryptionFunction(); + + private static final Collection FUNCTIONS; + + static { + List funcs = Arrays.asList(SM2, SM3, SM4); + FUNCTIONS = Collections.unmodifiableList(funcs); + } + + @Override + public Collection getFunctions() { + return FUNCTIONS; + } + +} diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/smutils/asymmetric/SM2Utils.java b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm/SM2Utils.java similarity index 76% rename from source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/smutils/asymmetric/SM2Utils.java rename to source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm/SM2Utils.java index 7be18edf..2e16d553 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/smutils/asymmetric/SM2Utils.java +++ b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm/SM2Utils.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.crypto.smutils.asymmetric; +package com.jd.blockchain.crypto.utils.sm; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1Integer; @@ -31,18 +31,18 @@ public class SM2Utils { // The length of sm3 output is 32 bytes private static final int SM3DIGEST_LENGTH = 32; - private static final BigInteger SM2_ECC_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16); - private static final BigInteger SM2_ECC_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16); - private static final BigInteger SM2_ECC_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16); - private static final BigInteger SM2_ECC_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16); - private static final BigInteger SM2_ECC_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16); - private static final BigInteger SM2_ECC_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16); + private static final BigInteger SM2_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16); + private static final BigInteger SM2_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16); + private static final BigInteger SM2_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16); + private static final BigInteger SM2_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16); + private static final BigInteger SM2_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16); + private static final BigInteger SM2_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16); // To get the curve from the equation y^2=x^3+ax+b according the coefficient a and b, // with the big prime p, and obtain the generator g and the domain's parameters - private static final ECCurve curve = new ECCurve.Fp(SM2_ECC_P, SM2_ECC_A, SM2_ECC_B); - private static final ECPoint g = curve.createPoint(SM2_ECC_GX, SM2_ECC_GY); - private static final ECDomainParameters domainParams = new ECDomainParameters(curve, g, SM2_ECC_N); + private static final ECCurve CURVE = new ECCurve.Fp(SM2_P, SM2_A, SM2_B); + private static final ECPoint G = CURVE.createPoint(SM2_GX, SM2_GY); + private static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G, SM2_N); //-----------------Key Pair Generation Algorithm----------------- @@ -60,7 +60,7 @@ public class SM2Utils { public static AsymmetricCipherKeyPair generateKeyPair(SecureRandom random){ - ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParams,random); + ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(DOMAIN_PARAMS,random); ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); // To generate the key pair @@ -71,7 +71,7 @@ public class SM2Utils { public static byte[] retrievePublicKey(byte[] privateKey) { ECMultiplier createBasePointMultiplier = new FixedPointCombMultiplier(); - ECPoint publicKeyPoint = createBasePointMultiplier.multiply(domainParams.getG(), new BigInteger(1,privateKey)).normalize(); + ECPoint publicKeyPoint = createBasePointMultiplier.multiply(DOMAIN_PARAMS.getG(), new BigInteger(1,privateKey)).normalize(); return publicKeyPoint.getEncoded(false); } @@ -89,7 +89,7 @@ public class SM2Utils { public static byte[] sign(byte[] data, byte[] privateKey){ SecureRandom random = new SecureRandom(); - ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(new BigInteger(1,privateKey),domainParams); + ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(new BigInteger(1,privateKey), DOMAIN_PARAMS); CipherParameters param = new ParametersWithRandom(privKey,random); return sign(data,param); @@ -97,13 +97,13 @@ public class SM2Utils { public static byte[] sign(byte[] data, byte[] privateKey, SecureRandom random, String ID){ - ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(new BigInteger(1,privateKey),domainParams); + ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(new BigInteger(1,privateKey), DOMAIN_PARAMS); CipherParameters param = new ParametersWithID(new ParametersWithRandom(privKey,random),ID.getBytes()); return sign(data,param); } - private static byte[] sign(byte[] data, CipherParameters param){ + public static byte[] sign(byte[] data, CipherParameters param){ SM2Signer signer = new SM2Signer(); @@ -142,7 +142,7 @@ public class SM2Utils { public static boolean verify(byte[] data, byte[] publicKey, byte[] signature){ ECPoint pubKeyPoint = resolvePubKeyBytes(publicKey); - ECPublicKeyParameters pubKey = new ECPublicKeyParameters(pubKeyPoint,domainParams); + ECPublicKeyParameters pubKey = new ECPublicKeyParameters(pubKeyPoint, DOMAIN_PARAMS); return verify(data,pubKey,signature); } @@ -150,13 +150,13 @@ public class SM2Utils { public static boolean verify(byte[] data, byte[] publicKey, byte[] signature, String ID){ ECPoint pubKeyPoint = resolvePubKeyBytes(publicKey); - ECPublicKeyParameters pubKey = new ECPublicKeyParameters(pubKeyPoint,domainParams); + ECPublicKeyParameters pubKey = new ECPublicKeyParameters(pubKeyPoint, DOMAIN_PARAMS); ParametersWithID param = new ParametersWithID(pubKey,ID.getBytes()); return verify(data,param,signature); } - private static boolean verify(byte[] data, CipherParameters param, byte[] signature){ + public static boolean verify(byte[] data, CipherParameters param, byte[] signature){ SM2Signer verifier = new SM2Signer(); @@ -199,21 +199,35 @@ public class SM2Utils { public static byte[] encrypt(byte[] plainBytes, byte[] publicKey){ SecureRandom random = new SecureRandom(); + return encrypt(plainBytes,publicKey,random); } public static byte[] encrypt(byte[] plainBytes, byte[] publicKey, SecureRandom random){ ECPoint pubKeyPoint = resolvePubKeyBytes(publicKey); - ECPublicKeyParameters pubKey = new ECPublicKeyParameters(pubKeyPoint,domainParams); + ECPublicKeyParameters pubKey = new ECPublicKeyParameters(pubKeyPoint, DOMAIN_PARAMS); + ParametersWithRandom param = new ParametersWithRandom(pubKey,random); + + return encrypt(plainBytes,param); + } + + public static byte[] encrypt(byte[] plainBytes, ECPublicKeyParameters pubKey){ + + SecureRandom random = new SecureRandom(); ParametersWithRandom param = new ParametersWithRandom(pubKey,random); + return encrypt(plainBytes,param); + } + + public static byte[] encrypt(byte[] plainBytes, CipherParameters param){ + SM2Engine encryptor = new SM2Engine(); // To prepare parameters encryptor.init(true,param); - // To generate the twisted ciphertext c1c2c3. + // To generate the twisted ciphertext c1c2c3. // The latest standard specification indicates that the correct ordering is c1c3c2 byte[] c1c2c3 = new byte[0]; try { @@ -240,19 +254,23 @@ public class SM2Utils { */ public static byte[] decrypt(byte[] cipherBytes, byte[] privateKey){ + ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(new BigInteger(1,privateKey), DOMAIN_PARAMS); - // To get c1c2c3 from ciphertext whose ordering is c1c3c2 - byte[] c1c2c3 = new byte[cipherBytes.length]; - System.arraycopy(cipherBytes,0,c1c2c3,0,POINT_SIZE); - System.arraycopy(cipherBytes,POINT_SIZE,c1c2c3,c1c2c3.length-SM3DIGEST_LENGTH, SM3DIGEST_LENGTH); - System.arraycopy(cipherBytes,SM3DIGEST_LENGTH + POINT_SIZE,c1c2c3,POINT_SIZE,c1c2c3.length-SM3DIGEST_LENGTH-POINT_SIZE); + return decrypt(cipherBytes,privKey); + } - ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(new BigInteger(1,privateKey),domainParams); + public static byte[] decrypt(byte[] cipherBytes, CipherParameters param){ SM2Engine decryptor = new SM2Engine(); // To prepare parameters - decryptor.init(false,privKey); + decryptor.init(false,param); + + // To get c1c2c3 from ciphertext whose ordering is c1c3c2 + byte[] c1c2c3 = new byte[cipherBytes.length]; + System.arraycopy(cipherBytes,0,c1c2c3,0,POINT_SIZE); + System.arraycopy(cipherBytes,POINT_SIZE,c1c2c3,c1c2c3.length-SM3DIGEST_LENGTH, SM3DIGEST_LENGTH); + System.arraycopy(cipherBytes,SM3DIGEST_LENGTH + POINT_SIZE,c1c2c3,POINT_SIZE,c1c2c3.length-SM3DIGEST_LENGTH-POINT_SIZE); // To output the plaintext try { @@ -262,23 +280,29 @@ public class SM2Utils { } } + + // To convert BigInteger to byte[] whose length is 32 private static byte[] BigIntegerTo32Bytes(BigInteger b){ byte[] tmp = b.toByteArray(); byte[] result = new byte[32]; - if (tmp.length > result.length) - System.arraycopy(tmp, tmp.length-result.length, result, 0, result.length); - else System.arraycopy(tmp,0,result,result.length-tmp.length,tmp.length); + if (tmp.length > result.length) { + System.arraycopy(tmp, tmp.length - result.length, result, 0, result.length); + } + else { + System.arraycopy(tmp,0,result,result.length-tmp.length,tmp.length); + } return result; } // To retrieve the public key point from publicKey in byte array mode private static ECPoint resolvePubKeyBytes(byte[] publicKey){ - return curve.decodePoint(publicKey); + return CURVE.decodePoint(publicKey); } - public static ECCurve getCurve(){return curve;} - public static ECDomainParameters getDomainParams(){return domainParams;} + public static ECCurve getCurve(){return CURVE;} + + public static ECDomainParameters getDomainParams(){return DOMAIN_PARAMS;} } diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/smutils/hash/SM3Utils.java b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm/SM3Utils.java similarity index 89% rename from source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/smutils/hash/SM3Utils.java rename to source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm/SM3Utils.java index 788247f3..6272a990 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/smutils/hash/SM3Utils.java +++ b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm/SM3Utils.java @@ -1,10 +1,9 @@ -package com.jd.blockchain.crypto.smutils.hash; +package com.jd.blockchain.crypto.utils.sm; import org.bouncycastle.crypto.digests.SM3Digest; public class SM3Utils { - // The length of sm3 output is 32 bytes private static final int SM3DIGEST_LENGTH = 32; @@ -18,8 +17,6 @@ public class SM3Utils { sm3digest.doFinal(result, 0); return result; - } - } diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/smutils/symmetric/SM4Utils.java b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm/SM4Utils.java similarity index 91% rename from source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/smutils/symmetric/SM4Utils.java rename to source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm/SM4Utils.java index b1855a19..63295f49 100644 --- a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/smutils/symmetric/SM4Utils.java +++ b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm/SM4Utils.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.crypto.smutils.symmetric; +package com.jd.blockchain.crypto.utils.sm; import com.jd.blockchain.crypto.CryptoException; import org.bouncycastle.crypto.CipherKeyGenerator; @@ -15,7 +15,7 @@ import java.security.SecureRandom; public class SM4Utils { // SM4 supports 128-bit secret key - private static final int KEY_Length = 128; + private static final int KEY_LENGTH = 128; // One block contains 16 bytes private static final int BLOCK_SIZE = 16; // Initial vector's size is 16 bytes @@ -33,7 +33,7 @@ public class SM4Utils { // To provide secure randomness and key length as input // to prepare generate private key - keyGenerator.init(new KeyGenerationParameters(new SecureRandom(),KEY_Length)); + keyGenerator.init(new KeyGenerationParameters(new SecureRandom(), KEY_LENGTH)); // To generate key return keyGenerator.generateKey(); @@ -53,7 +53,7 @@ public class SM4Utils { // To ensure that plaintext is not null if (plainBytes == null) { - throw new IllegalArgumentException("plaintext is null!"); + throw new CryptoException("plaintext is null!"); } // To get the value padded into input @@ -72,8 +72,9 @@ public class SM4Utils { encryptor.init(true,new ParametersWithIV(new KeyParameter(secretKey),iv)); byte[] output = new byte[plainBytesWithPadding.length + IV_SIZE]; // To encrypt the input_p in CBC mode - for(int i = 0 ; i < plainBytesWithPadding.length/BLOCK_SIZE; i++) - encryptor.processBlock(plainBytesWithPadding, i * BLOCK_SIZE, output, (i+1) * BLOCK_SIZE); + for(int i = 0 ; i < plainBytesWithPadding.length/BLOCK_SIZE; i++) { + encryptor.processBlock(plainBytesWithPadding, i * BLOCK_SIZE, output, (i + 1) * BLOCK_SIZE); + } // The IV locates on the first block of ciphertext System.arraycopy(iv,0,output,0,BLOCK_SIZE); @@ -100,13 +101,13 @@ public class SM4Utils { // To ensure that the ciphertext is not null if (cipherBytes == null) { - throw new IllegalArgumentException("ciphertext is null!"); + throw new CryptoException("ciphertext is null!"); } // To ensure that the ciphertext's length is integral multiples of 16 bytes if ( cipherBytes.length % BLOCK_SIZE != 0 ) { - throw new IllegalArgumentException("ciphertext's length is wrong!"); + throw new CryptoException("ciphertext's length is wrong!"); } byte[] iv = new byte[IV_SIZE]; @@ -117,8 +118,9 @@ public class SM4Utils { decryptor.init(false,new ParametersWithIV(new KeyParameter(secretKey),iv)); byte[] outputWithPadding = new byte[cipherBytes.length-BLOCK_SIZE]; // To decrypt the input in CBC mode - for(int i = 1 ; i < cipherBytes.length/BLOCK_SIZE ; i++) - decryptor.processBlock(cipherBytes, i * BLOCK_SIZE, outputWithPadding, (i-1) * BLOCK_SIZE); + for(int i = 1 ; i < cipherBytes.length/BLOCK_SIZE ; i++) { + decryptor.processBlock(cipherBytes, i * BLOCK_SIZE, outputWithPadding, (i - 1) * BLOCK_SIZE); + } int p = outputWithPadding[outputWithPadding.length-1]; // To ensure that the padding of output_p is valid diff --git a/source/crypto/crypto-sm/src/main/resources/META-INF/services/com.jd.blockchain.crypto.CryptoService b/source/crypto/crypto-sm/src/main/resources/META-INF/services/com.jd.blockchain.crypto.CryptoService new file mode 100644 index 00000000..e89073b2 --- /dev/null +++ b/source/crypto/crypto-sm/src/main/resources/META-INF/services/com.jd.blockchain.crypto.CryptoService @@ -0,0 +1 @@ +com.jd.blockchain.crypto.service.sm.SMCryptoService \ No newline at end of file diff --git a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/smutils/SM2UtilsTest.java b/source/crypto/crypto-sm/src/test/java/test/com/jd/blockchain/crypto/smutils/SM2UtilsTest.java similarity index 50% rename from source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/smutils/SM2UtilsTest.java rename to source/crypto/crypto-sm/src/test/java/test/com/jd/blockchain/crypto/smutils/SM2UtilsTest.java index b10f294b..dec8f2f3 100644 --- a/source/crypto/crypto-framework/src/test/java/test/com/jd/blockchain/crypto/smutils/SM2UtilsTest.java +++ b/source/crypto/crypto-sm/src/test/java/test/com/jd/blockchain/crypto/smutils/SM2UtilsTest.java @@ -1,6 +1,13 @@ package test.com.jd.blockchain.crypto.smutils; -import com.jd.blockchain.crypto.smutils.asymmetric.SM2Utils; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.jd.blockchain.utils.security.Ed25519Utils; +import net.i2p.crypto.eddsa.EdDSAPrivateKey; +import net.i2p.crypto.eddsa.EdDSAPublicKey; +import net.i2p.crypto.eddsa.KeyPairGenerator; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; @@ -8,7 +15,11 @@ import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.test.TestRandomBigInteger; import org.junit.Test; -import static org.junit.Assert.*; +import com.jd.blockchain.crypto.utils.sm.SM2Utils; + +import java.security.KeyPair; +import java.util.Arrays; +import java.util.Random; public class SM2UtilsTest { @@ -114,4 +125,117 @@ public class SM2UtilsTest { byte[] plaintext = SM2Utils.decrypt(ciphertext,privKeyBytes); assertArrayEquals(expectedMessage.getBytes(),plaintext); } +// +// @Test +// public void encryptingPerformace(){ +// +// byte[] data = new byte[1000]; +// Random random = new Random(); +// random.nextBytes(data); +// +// int count = 10000; +// +// byte[] ciphertext = null; +// +// AsymmetricCipherKeyPair keyPair = SM2Utils.generateKeyPair(); +// ECPublicKeyParameters ecPub = (ECPublicKeyParameters) keyPair.getPublic(); +// ECPrivateKeyParameters ecPriv = (ECPrivateKeyParameters) keyPair.getPrivate(); +// +// System.out.println("=================== do SM2 encrypt test ==================="); +// +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// ciphertext = SM2Utils.encrypt(data,ecPub); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM2 Encrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// System.out.println("=================== do SM2 decrypt test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// SM2Utils.decrypt(ciphertext,ecPriv); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM2 Decrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// } +// +// +// @Test +// public void signingPerformace(){ +// +// byte[] data = new byte[1000]; +// Random random = new Random(); +// random.nextBytes(data); +// +// int count = 10000; +// +// byte[] sm2Digest = null; +// byte[] ed25519Digest = null; +// +// AsymmetricCipherKeyPair keyPair = SM2Utils.generateKeyPair(); +// ECPublicKeyParameters ecPub = (ECPublicKeyParameters) keyPair.getPublic(); +// ECPrivateKeyParameters ecPriv = (ECPrivateKeyParameters) keyPair.getPrivate(); +// +// System.out.println("=================== do SM2 sign test ==================="); +// +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// sm2Digest = SM2Utils.sign(data,ecPriv); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM2 Signing Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// System.out.println("=================== do SM2 verify test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// SM2Utils.verify(data,ecPub,sm2Digest); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM2 Verifying Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// KeyPairGenerator keyPairGenerator = new KeyPairGenerator(); +// KeyPair ed25519KeyPair = keyPairGenerator.generateKeyPair(); +// EdDSAPrivateKey privKey = (EdDSAPrivateKey) ed25519KeyPair.getPrivate(); +// EdDSAPublicKey pubKey = (EdDSAPublicKey) ed25519KeyPair.getPublic(); +// +// System.out.println("=================== do ED25519 sign test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// ed25519Digest = Ed25519Utils.sign_512(data,privKey.getSeed()); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("ED25519 Signing Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// System.out.println("=================== do ED25519 verify test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// Ed25519Utils.verify(data,pubKey.getAbyte(),ed25519Digest); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("ED25519 Verifying Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// } } \ No newline at end of file diff --git a/source/crypto/crypto-sm/src/test/java/test/com/jd/blockchain/crypto/smutils/SM3UtilsTest.java b/source/crypto/crypto-sm/src/test/java/test/com/jd/blockchain/crypto/smutils/SM3UtilsTest.java new file mode 100644 index 00000000..8b155edf --- /dev/null +++ b/source/crypto/crypto-sm/src/test/java/test/com/jd/blockchain/crypto/smutils/SM3UtilsTest.java @@ -0,0 +1,71 @@ +package test.com.jd.blockchain.crypto.smutils; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import com.jd.blockchain.utils.security.ShaUtils; +import org.bouncycastle.util.encoders.Hex; +import org.junit.Test; + +import com.jd.blockchain.crypto.utils.sm.SM3Utils; + +import java.util.Random; + +public class SM3UtilsTest { + + private static final int SM3DIGEST_LENGTH = 32; + + @Test + public void testHash() { + + String testString1 = "abc"; + String testString2 = "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"; + String expectedResult1="66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0" ; + String expectedResult2="debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732"; + + byte[] testString1Bytes = testString1.getBytes(); + byte[] testString2Bytes = testString2.getBytes(); + byte[] hash1 = SM3Utils.hash(testString1Bytes); + byte[] hash2 = SM3Utils.hash(testString2Bytes); + byte[] expectedResult1Bytes = expectedResult1.getBytes(); + byte[] expectedResult2Bytes = expectedResult2.getBytes(); + assertEquals(hash1.length, SM3DIGEST_LENGTH); + assertEquals(hash2.length, SM3DIGEST_LENGTH); + assertArrayEquals(hash1, Hex.decode(expectedResult1Bytes)); + assertArrayEquals(hash2, Hex.decode(expectedResult2Bytes)); + } + +// @Test +// public void hashingPerformance() { +// +// byte[] data = new byte[1000]; +// Random random = new Random(); +// random.nextBytes(data); +// +// int count = 1000000; +// +// System.out.println("=================== do SM3 hash test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// SM3Utils.hash(data); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM3 hashing Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// System.out.println("=================== do SHA256 hash test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// ShaUtils.hash_256(data); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SHA256 hashing Count=%s; Elapsed Times=%s; TPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// } +} \ No newline at end of file diff --git a/source/crypto/crypto-sm/src/test/java/test/com/jd/blockchain/crypto/smutils/SM4UtilsTest.java b/source/crypto/crypto-sm/src/test/java/test/com/jd/blockchain/crypto/smutils/SM4UtilsTest.java new file mode 100644 index 00000000..d37238c6 --- /dev/null +++ b/source/crypto/crypto-sm/src/test/java/test/com/jd/blockchain/crypto/smutils/SM4UtilsTest.java @@ -0,0 +1,137 @@ +package test.com.jd.blockchain.crypto.smutils; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import com.jd.blockchain.utils.security.AESUtils; +import org.bouncycastle.util.encoders.Hex; +import org.junit.Test; + +import com.jd.blockchain.crypto.utils.sm.SM4Utils; + +import java.util.Random; + +public class SM4UtilsTest { + + private static final int KEY_SIZE = 16; + private static final int BLOCK_SIZE = 16; + + @Test + public void testGenerateKey() { + byte[] key = SM4Utils.generateKey(); + assertEquals(KEY_SIZE,key.length); + } + + @Test + public void testEncrypt() { + + String plaintext = "0123456789abcdeffedcba9876543210"; + String key = "0123456789abcdeffedcba9876543210"; + String iv = "00000000000000000000000000000000"; + String expectedCiphertextIn2ndBlock = "681edf34d206965e86b3e94f536e4246"; + + byte[] plaintextBytes = Hex.decode(plaintext); + byte[] keyBytes = Hex.decode(key); + byte[] ivBytes = Hex.decode(iv); + byte[] expectedCiphertextIn2ndBlockBytes = Hex.decode(expectedCiphertextIn2ndBlock); + + + byte[] ciphertextbytes = SM4Utils.encrypt(plaintextBytes,keyBytes,ivBytes); + + assertEquals(BLOCK_SIZE*3,ciphertextbytes.length); + + byte[] ciphertextIn1stBlockBytes = new byte[BLOCK_SIZE]; + System.arraycopy(ciphertextbytes,0,ciphertextIn1stBlockBytes,0,BLOCK_SIZE); + assertArrayEquals(ivBytes,ciphertextIn1stBlockBytes); + + byte[] ciphertextIn2ndBlockBytes = new byte[BLOCK_SIZE]; + System.arraycopy(ciphertextbytes,BLOCK_SIZE,ciphertextIn2ndBlockBytes,0,BLOCK_SIZE); + assertArrayEquals(expectedCiphertextIn2ndBlockBytes,ciphertextIn2ndBlockBytes); + + + } + + @Test + public void testDecrypt() { + + String plaintext = "0123456789abcdeffedcba987654321000112233445566778899"; + String key = "0123456789abcdeffedcba9876543210"; + String iv = "0123456789abcdeffedcba9876543210"; + + byte[] plaintextBytes = Hex.decode(plaintext); + byte[] keyBytes = Hex.decode(key); + byte[] ivBytes = Hex.decode(iv); + + + byte[] ciphertext = SM4Utils.encrypt(plaintextBytes,keyBytes,ivBytes); + byte[] decryptedData = SM4Utils.decrypt(ciphertext,keyBytes); + assertArrayEquals(plaintextBytes,decryptedData); + + } + +// @Test +// public void encryptingPerformance() { +// +// byte[] data = new byte[1000]; +// Random random = new Random(); +// random.nextBytes(data); +// +// byte[] sm4Ciphertext = null; +// byte[] aesCiphertext = null; +// +// int count = 100000; +// +// byte[] sm4Key = SM4Utils.generateKey(); +// +// System.out.println("=================== do SM4 encrypt test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// sm4Ciphertext = SM4Utils.encrypt(data, sm4Key); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM4 Encrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// System.out.println("=================== do SM4 decrypt test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// SM4Utils.decrypt(sm4Ciphertext, sm4Key); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("SM4 Decrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// byte[] aesKey = AESUtils.generateKey128_Bytes(); +// +// System.out.println("=================== do AES encrypt test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// aesCiphertext = AESUtils.encrypt(data, aesKey); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("AES Encrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// +// +// System.out.println("=================== do AES decrypt test ==================="); +// for (int r = 0; r < 5; r++) { +// System.out.println("------------- round[" + r + "] --------------"); +// long startTS = System.currentTimeMillis(); +// for (int i = 0; i < count; i++) { +// AESUtils.decrypt(aesCiphertext, aesKey); +// } +// long elapsedTS = System.currentTimeMillis() - startTS; +// System.out.println(String.format("AES Decrypting Count=%s; Elapsed Times=%s; KBPS=%.2f", count, elapsedTS, +// (count * 1000.00D) / elapsedTS)); +// } +// } +} \ No newline at end of file diff --git a/source/crypto/pom.xml b/source/crypto/pom.xml index 807125a1..d0ee654b 100644 --- a/source/crypto/pom.xml +++ b/source/crypto/pom.xml @@ -9,10 +9,13 @@ crypto pom - + crypto-framework + crypto-classic + crypto-sm + crypto-jni-clib crypto-adv - + \ No newline at end of file diff --git a/source/deployment/deployment-peer/src/main/java/com/jd/blockchain/boot/peer/PeerBooter.java b/source/deployment/deployment-peer/src/main/java/com/jd/blockchain/boot/peer/PeerBooter.java index d04ada1a..1bde8881 100644 --- a/source/deployment/deployment-peer/src/main/java/com/jd/blockchain/boot/peer/PeerBooter.java +++ b/source/deployment/deployment-peer/src/main/java/com/jd/blockchain/boot/peer/PeerBooter.java @@ -14,7 +14,7 @@ import com.jd.blockchain.runtime.boot.HomeBooter; import com.jd.blockchain.runtime.boot.HomeContext; /** - * Peer starter; + * Peer 启动器; * * @author huanghaiquan * diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/GatewayServerBooter.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/GatewayServerBooter.java index 34b2df7b..accd6542 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/GatewayServerBooter.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/GatewayServerBooter.java @@ -11,9 +11,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.io.ClassPathResource; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.ArgumentSet; import com.jd.blockchain.utils.BaseConstant; diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java index 1c5b1fcb..ae697531 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java @@ -1,7 +1,7 @@ package com.jd.blockchain.gateway.web; import com.jd.blockchain.crypto.AddressEncoding; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.gateway.PeerService; import com.jd.blockchain.gateway.service.DataRetrievalService; diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java index ed43d2a7..249bf42f 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java @@ -2,7 +2,7 @@ package com.jd.blockchain.gateway.web; import java.util.List; -import com.jd.blockchain.web.serializes.ByteArrayObjectUtil; +import com.jd.blockchain.utils.io.BytesSlice; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.http.converter.HttpMessageConverter; @@ -11,6 +11,12 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.crypto.asymmetric.SignatureDigest; +import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.crypto.serialize.ByteArrayObjectDeserializer; +import com.jd.blockchain.crypto.serialize.ByteArrayObjectSerializer; +import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.io.ByteArray; import com.jd.blockchain.utils.serialize.json.JSONSerializeUtils; import com.jd.blockchain.utils.web.model.JsonWebResponseMessageConverter; @@ -24,6 +30,13 @@ import com.jd.blockchain.web.converters.HashDigestInputConverter; @Configuration public class GatewayWebServerConfigurer implements WebMvcConfigurer { + private static final Class[] BYTEARRAY_JSON_SERIALIZE_CLASS = new Class[] { + HashDigest.class, + PubKey.class, + SignatureDigest.class, + Bytes.class, + BytesSlice.class}; + static { JSONSerializeUtils.disableCircularReferenceDetect(); JSONSerializeUtils.configStringSerializer(ByteArray.class); @@ -66,6 +79,10 @@ public class GatewayWebServerConfigurer implements WebMvcConfigurer { } private void initByteArrayJsonSerialize() { - ByteArrayObjectUtil.init(); + for (Class byteArrayClass : BYTEARRAY_JSON_SERIALIZE_CLASS) { + JSONSerializeUtils.configSerialization(byteArrayClass, + ByteArrayObjectSerializer.getInstance(byteArrayClass), + ByteArrayObjectDeserializer.getInstance(byteArrayClass)); + } } } diff --git a/source/gateway/src/main/resources/application-gw.properties b/source/gateway/src/main/resources/application-gw.properties index b87537f3..e69de29b 100644 --- a/source/gateway/src/main/resources/application-gw.properties +++ b/source/gateway/src/main/resources/application-gw.properties @@ -1 +0,0 @@ -spring.mvc.favicon.enabled=false \ No newline at end of file diff --git a/source/ledger/ledger-core/pom.xml b/source/ledger/ledger-core/pom.xml index f25f8678..65055dd4 100644 --- a/source/ledger/ledger-core/pom.xml +++ b/source/ledger/ledger-core/pom.xml @@ -49,6 +49,20 @@ org.springframework spring-context + + + com.jd.blockchain + crypto-classic + ${project.version} + test + + + com.jd.blockchain + crypto-sm + ${project.version} + test + + diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountAccessPolicy.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountAccessPolicy.java index eb2763d9..2a0202c4 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountAccessPolicy.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountAccessPolicy.java @@ -1,7 +1,7 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.utils.Bytes; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java index 511a939f..5fef84aa 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java @@ -8,7 +8,7 @@ import com.jd.blockchain.binaryproto.DConstructor; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.binaryproto.FieldSetter; import com.jd.blockchain.crypto.AddressEncoding; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.CryptoSetting; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java index 5417dc2a..45ec6ec7 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.BlockchainIdentity; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java index bdabb6dc..877949ff 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.utils.Bytes; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java index 612de4b8..8263e0f6 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.CryptoSetting; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java index 2b2271ae..34a2eb43 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java @@ -1,17 +1,18 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.binaryproto.BinaryEncodingUtils; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.KVDataEntry; import com.jd.blockchain.ledger.KVDataObject; import com.jd.blockchain.utils.Bytes; -import com.jd.blockchain.utils.QueryUtil; import com.jd.blockchain.utils.ValueType; +import com.jd.blockchain.utils.serialize.binary.BinarySerializeUtils; public class DataAccount implements AccountHeader, MerkleProvable { + private BaseAccount baseAccount; public DataAccount(BaseAccount accBase) { @@ -118,12 +119,19 @@ public class DataAccount implements AccountHeader, MerkleProvable { */ public KVDataEntry[] getDataEntries(int fromIndex, int count) { + if (getDataEntriesTotalCount() == 0 || count == 0) { return null; } - int pages[] = QueryUtil.calFromIndexAndCount(fromIndex,count,(int)getDataEntriesTotalCount()); - fromIndex = pages[0]; - count = pages[1]; + + if (count == -1 || count > getDataEntriesTotalCount()) { + fromIndex = 0; + count = (int)getDataEntriesTotalCount(); + } + + if (fromIndex < 0 || fromIndex > getDataEntriesTotalCount() - 1) { + fromIndex = 0; + } KVDataEntry[] kvDataEntries = new KVDataEntry[count]; byte[] value; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java index 8d92eb5a..5d318b92 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.CryptoSetting; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitDecision.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitDecision.java index 8d3f35b2..b0ad19a1 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitDecision.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitDecision.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitPermission.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitPermission.java index 46319be8..70a0f554 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitPermission.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitPermission.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.ledger.LedgerInitOperation; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerMetadata.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerMetadata.java index 3655e7b0..5a114533 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerMetadata.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerMetadata.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSetting.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSetting.java index 67c503c9..eba16465 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSetting.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSetting.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java index 62b649ce..96d30caf 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.ParticipantNode; /** diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccount.java index 6db9fcee..4094c4e0 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccount.java @@ -1,7 +1,7 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.CryptoUtils; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.UserInfo; import com.jd.blockchain.utils.Bytes; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java index 08364fe7..0cf02380 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.CryptoSetting; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionData.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionData.java index a76dd68b..3f5e38b2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionData.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionData.java @@ -57,7 +57,6 @@ public class LedgerTransactionData implements LedgerTransaction { this.endpointSignatures = txReq.getEndpointSignatures(); this.nodeSignatures = txReq.getNodeSignatures(); this.executionState = execState; - this.hash = txReq.getHash(); } @Override diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/OpeningAccessPolicy.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/OpeningAccessPolicy.java index bc97dd02..3af84a5a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/OpeningAccessPolicy.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/OpeningAccessPolicy.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger.core.impl; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.core.AccountAccessPolicy; import com.jd.blockchain.utils.Bytes; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/AccountSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/AccountSetTest.java index bd15285b..84fe1441 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/AccountSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/AccountSetTest.java @@ -8,6 +8,7 @@ import org.junit.Test; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeyPair; import com.jd.blockchain.ledger.core.AccountSet; @@ -26,7 +27,7 @@ public class AccountSetTest { CryptoConfig cryptoConf = new CryptoConfig(); cryptoConf.setAutoVerifyHash(true); - cryptoConf.setHashAlgorithm(CryptoAlgorithm.SHA256); + cryptoConf.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); String keyPrefix = ""; AccountSet accset = new AccountSet(cryptoConf,keyPrefix, storage, storage, accessPolicy); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/BaseAccountTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/BaseAccountTest.java index acdeff44..7365be93 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/BaseAccountTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/BaseAccountTest.java @@ -7,6 +7,7 @@ import org.junit.Test; import org.springframework.util.StringUtils; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeyPair; import com.jd.blockchain.ledger.core.BaseAccount; @@ -30,7 +31,7 @@ public class BaseAccountTest { CryptoConfig cryptoConf = new CryptoConfig(); cryptoConf.setAutoVerifyHash(true); - cryptoConf.setHashAlgorithm(CryptoAlgorithm.SHA256); + cryptoConf.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); OpeningAccessPolicy accPlc = new OpeningAccessPolicy(); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAccountTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAccountTest.java index 11681165..aac4dd23 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAccountTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAccountTest.java @@ -3,8 +3,10 @@ package test.com.jd.blockchain.ledger; import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; +import com.jd.blockchain.crypto.service.sm.SMCryptoService; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.UserInfo; @@ -44,8 +46,8 @@ public class LedgerAccountTest { @Test public void testSerialize_AccountHeader() { String address = "xxxxxxxxxxxx"; - PubKey pubKey = new PubKey(CryptoAlgorithm.SM2, rawDigestBytes); - HashDigest hashDigest = new HashDigest(CryptoAlgorithm.SHA256, rawDigestBytes); + PubKey pubKey = new PubKey(SMCryptoService.SM2_ALGORITHM, rawDigestBytes); + HashDigest hashDigest = new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, rawDigestBytes); AccountSet.AccountHeaderData accountHeaderData = new AccountSet.AccountHeaderData(Bytes.fromString(address), pubKey, hashDigest); //encode and decode diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java index bad75327..29423fee 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java @@ -14,6 +14,7 @@ import org.junit.Test; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeyPair; import com.jd.blockchain.ledger.ParticipantNode; @@ -57,7 +58,7 @@ public class LedgerAdminAccountTest { CryptoConfig cryptoSetting = new CryptoConfig(); cryptoSetting.setAutoVerifyHash(true); - cryptoSetting.setHashAlgorithm(CryptoAlgorithm.SHA256); + cryptoSetting.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); initSetting.setCryptoSetting(cryptoSetting); byte[] ledgerSeed = new byte[16]; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerBlockImplTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerBlockImplTest.java index d00bcd05..69bef9a3 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerBlockImplTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerBlockImplTest.java @@ -8,18 +8,19 @@ */ package test.com.jd.blockchain.ledger; +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerDataSnapshot; import com.jd.blockchain.ledger.core.impl.LedgerBlockData; import com.jd.blockchain.ledger.core.impl.TransactionStagedSnapshot; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; /** * @@ -37,17 +38,17 @@ public class LedgerBlockImplTest { DataContractRegistry.register(LedgerBlock.class); DataContractRegistry.register(LedgerDataSnapshot.class); long height = 9999L; - HashDigest ledgerHash = new HashDigest(CryptoAlgorithm.SHA256, "zhangsan".getBytes()); - HashDigest previousHash = new HashDigest(CryptoAlgorithm.SHA256, "lisi".getBytes()); + HashDigest ledgerHash = new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "zhangsan".getBytes()); + HashDigest previousHash = new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "lisi".getBytes()); data = new LedgerBlockData(height, ledgerHash, previousHash); - data.setHash(new HashDigest(CryptoAlgorithm.SHA256, "wangwu".getBytes())); - data.setTransactionSetHash(new HashDigest(CryptoAlgorithm.SHA256, "zhaoliu".getBytes())); + data.setHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "wangwu".getBytes())); + data.setTransactionSetHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "zhaoliu".getBytes())); // 设置LedgerDataSnapshot相关属性 - data.setAdminAccountHash(new HashDigest(CryptoAlgorithm.SHA256, "jd1".getBytes())); - data.setDataAccountSetHash(new HashDigest(CryptoAlgorithm.SHA256, "jd2".getBytes())); - data.setUserAccountSetHash(new HashDigest(CryptoAlgorithm.SHA256, "jd3".getBytes())); - data.setContractAccountSetHash(new HashDigest(CryptoAlgorithm.SHA256, "jd4".getBytes())); + data.setAdminAccountHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "jd1".getBytes())); + data.setDataAccountSetHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "jd2".getBytes())); + data.setUserAccountSetHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "jd3".getBytes())); + data.setContractAccountSetHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "jd4".getBytes())); } @@ -88,10 +89,10 @@ public class LedgerBlockImplTest { public void testSerialize_LedgerDataSnapshot() throws Exception { TransactionStagedSnapshot transactionStagedSnapshot = new TransactionStagedSnapshot(); - HashDigest admin = new HashDigest(CryptoAlgorithm.SHA256, "alice".getBytes()); - HashDigest contract = new HashDigest(CryptoAlgorithm.SHA256, "bob".getBytes()); - HashDigest data = new HashDigest(CryptoAlgorithm.SHA256, "jerry".getBytes()); - HashDigest user = new HashDigest(CryptoAlgorithm.SHA256, "tom".getBytes()); + HashDigest admin = new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "alice".getBytes()); + HashDigest contract = new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "bob".getBytes()); + HashDigest data = new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "jerry".getBytes()); + HashDigest user = new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "tom".getBytes()); transactionStagedSnapshot.setAdminAccountHash(admin); transactionStagedSnapshot.setContractAccountSetHash(contract); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerEditerTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerEditerTest.java index f7b94a22..db8647d6 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerEditerTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerEditerTest.java @@ -1,7 +1,9 @@ package test.com.jd.blockchain.ledger; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.ledger.core.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import org.junit.Test; @@ -12,7 +14,19 @@ import com.jd.blockchain.crypto.CryptoUtils; import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; -import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; +import com.jd.blockchain.ledger.BlockchainKeyPair; +import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInitSetting; +import com.jd.blockchain.ledger.LedgerTransaction; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.core.CryptoConfig; +import com.jd.blockchain.ledger.core.DataAccount; +import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerEditor; +import com.jd.blockchain.ledger.core.LedgerTransactionContext; +import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.impl.LedgerTransactionalEditor; import com.jd.blockchain.ledger.data.ConsensusParticipantData; import com.jd.blockchain.ledger.data.LedgerInitSettingData; @@ -21,8 +35,6 @@ import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.io.BytesUtils; import com.jd.blockchain.utils.net.NetworkAddress; -import static org.junit.Assert.*; - public class LedgerEditerTest { static { @@ -32,8 +44,9 @@ public class LedgerEditerTest { } String ledgerKeyPrefix = "LDG://"; - AsymmetricCryptography asymmetricCryptography = new AsymmtricCryptographyImpl(); - SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); + AsymmetricCryptography asymmetricCryptography = CryptoUtils.asymmCrypto(); + SignatureFunction signatureFunction = asymmetricCryptography + .getSignatureFunction(ClassicCryptoService.ED25519_ALGORITHM); // 存储; MemoryKVStorage storage = new MemoryKVStorage(); @@ -100,7 +113,7 @@ public class LedgerEditerTest { private LedgerInitSetting createLedgerInitSetting() { CryptoConfig defCryptoSetting = new CryptoConfig(); defCryptoSetting.setAutoVerifyHash(true); - defCryptoSetting.setHashAlgorithm(CryptoAlgorithm.SHA256); + defCryptoSetting.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); LedgerInitSettingData initSetting = new LedgerInitSettingData(); @@ -110,7 +123,7 @@ public class LedgerEditerTest { parties[0] = new ConsensusParticipantData(); parties[0].setId(0); parties[0].setName("John"); - CryptoKeyPair kp0 = CryptoUtils.sign(CryptoAlgorithm.ED25519).generateKeyPair(); + CryptoKeyPair kp0 = CryptoUtils.sign(ClassicCryptoService.ED25519_ALGORITHM).generateKeyPair(); parties[0].setPubKey(kp0.getPubKey()); parties[0].setAddress(AddressEncoding.generateAddress(kp0.getPubKey()).toBase58()); parties[0].setHostAddress(new NetworkAddress("192.168.1.6", 9000)); @@ -118,7 +131,7 @@ public class LedgerEditerTest { parties[1] = new ConsensusParticipantData(); parties[1].setId(1); parties[1].setName("John"); - CryptoKeyPair kp1 = CryptoUtils.sign(CryptoAlgorithm.ED25519).generateKeyPair(); + CryptoKeyPair kp1 = CryptoUtils.sign(ClassicCryptoService.ED25519_ALGORITHM).generateKeyPair(); parties[1].setPubKey(kp1.getPubKey()); parties[1].setAddress(AddressEncoding.generateAddress(kp1.getPubKey()).toBase58()); parties[1].setHostAddress(new NetworkAddress("192.168.1.7", 9000)); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitOperationTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitOperationTest.java index 2a1a363f..9a10158c 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitOperationTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitOperationTest.java @@ -4,6 +4,7 @@ import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeyPair; import com.jd.blockchain.ledger.LedgerInitOperation; @@ -45,7 +46,7 @@ public class LedgerInitOperationTest { CryptoConfig cryptoConfig = new CryptoConfig(); cryptoConfig.setAutoVerifyHash(true); - cryptoConfig.setHashAlgorithm(CryptoAlgorithm.SHA256); + cryptoConfig.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); ledgerInitSettingData.setConsensusSettings(new Bytes(csSysSettingBytes)); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitSettingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitSettingTest.java index d171e421..a4a56abe 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitSettingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitSettingTest.java @@ -4,6 +4,7 @@ import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeyPair; import com.jd.blockchain.ledger.LedgerInitOperation; @@ -43,7 +44,7 @@ public class LedgerInitSettingTest { CryptoConfig cryptoConfig = new CryptoConfig(); cryptoConfig.setAutoVerifyHash(true); - cryptoConfig.setHashAlgorithm(CryptoAlgorithm.SHA256); + cryptoConfig.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); ledgerInitSettingData.setConsensusSettings(new Bytes(csSysSettingBytes)); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java index 8ed3e587..102c3319 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java @@ -20,6 +20,7 @@ import com.jd.blockchain.crypto.CryptoUtils; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.BlockBody; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeyPair; @@ -60,7 +61,7 @@ public class LedgerManagerTest { DataContractRegistry.register(BlockBody.class); } - private static SignatureFunction signatureFunction = CryptoUtils.sign(CryptoAlgorithm.ED25519); + private static SignatureFunction signatureFunction = CryptoUtils.sign(ClassicCryptoService.ED25519_ALGORITHM); @Test public void testLedgerInit() { @@ -172,7 +173,7 @@ public class LedgerManagerTest { private LedgerInitSetting createLedgerInitSetting() { CryptoConfig defCryptoSetting = new CryptoConfig(); defCryptoSetting.setAutoVerifyHash(true); - defCryptoSetting.setHashAlgorithm(CryptoAlgorithm.SHA256); + defCryptoSetting.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); LedgerInitSettingData initSetting = new LedgerInitSettingData(); @@ -182,7 +183,7 @@ public class LedgerManagerTest { parties[0] = new ConsensusParticipantData(); parties[0].setId(0); parties[0].setName("John"); - CryptoKeyPair kp0 = CryptoUtils.sign(CryptoAlgorithm.ED25519).generateKeyPair(); + CryptoKeyPair kp0 = CryptoUtils.sign(ClassicCryptoService.ED25519_ALGORITHM).generateKeyPair(); parties[0].setPubKey(kp0.getPubKey()); parties[0].setAddress(AddressEncoding.generateAddress(kp0.getPubKey()).toBase58()); parties[0].setHostAddress(new NetworkAddress("127.0.0.1", 9000)); @@ -190,7 +191,7 @@ public class LedgerManagerTest { parties[1] = new ConsensusParticipantData(); parties[1].setId(1); parties[1].setName("Mary"); - CryptoKeyPair kp1 = CryptoUtils.sign(CryptoAlgorithm.ED25519).generateKeyPair(); + CryptoKeyPair kp1 = CryptoUtils.sign(ClassicCryptoService.ED25519_ALGORITHM).generateKeyPair(); parties[1].setPubKey(kp1.getPubKey()); parties[1].setAddress(AddressEncoding.generateAddress(kp1.getPubKey()).toBase58()); parties[1].setHostAddress(new NetworkAddress("127.0.0.1", 9010)); @@ -198,7 +199,7 @@ public class LedgerManagerTest { parties[2] = new ConsensusParticipantData(); parties[2].setId(2); parties[2].setName("Jerry"); - CryptoKeyPair kp2 = CryptoUtils.sign(CryptoAlgorithm.ED25519).generateKeyPair(); + CryptoKeyPair kp2 = CryptoUtils.sign(ClassicCryptoService.ED25519_ALGORITHM).generateKeyPair(); parties[2].setPubKey(kp2.getPubKey()); parties[2].setAddress(AddressEncoding.generateAddress(kp2.getPubKey()).toBase58()); parties[2].setHostAddress(new NetworkAddress("127.0.0.1", 9020)); @@ -206,7 +207,7 @@ public class LedgerManagerTest { parties[3] = new ConsensusParticipantData(); parties[3].setId(3); parties[3].setName("Tom"); - CryptoKeyPair kp3 = CryptoUtils.sign(CryptoAlgorithm.ED25519).generateKeyPair(); + CryptoKeyPair kp3 = CryptoUtils.sign(ClassicCryptoService.ED25519_ALGORITHM).generateKeyPair(); parties[3].setPubKey(kp3.getPubKey()); parties[3].setAddress(AddressEncoding.generateAddress(kp3.getPubKey()).toBase58()); parties[3].setHostAddress(new NetworkAddress("127.0.0.1", 9030)); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java index 4785ab13..e2ce75ad 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java @@ -13,11 +13,11 @@ import org.junit.Test; import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.AddressEncoding; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.CryptoSetting; +import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerAdminAccount; import com.jd.blockchain.ledger.core.LedgerConfiguration; @@ -54,11 +54,11 @@ public class LedgerMetaDataTest { // prepare work // ConsensusConfig consensusConfig = new ConsensusConfig(); - // consensusConfig.setValue(settingValue); + // consensusConfig.setValue(settingValue);ClassicCryptoService.ED25519_ALGORITHM CryptoConfig cryptoConfig = new CryptoConfig(); cryptoConfig.setAutoVerifyHash(true); - cryptoConfig.setHashAlgorithm(CryptoAlgorithm.SHA256); + cryptoConfig.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); LedgerConfiguration ledgerConfiguration = new LedgerConfiguration(consensusProvider, new Bytes(consensusSettingBytes), cryptoConfig); @@ -67,7 +67,7 @@ public class LedgerMetaDataTest { ledgerMetadata.setSeed(seed); ledgerMetadata.setSetting(ledgerConfiguration); - HashDigest hashDigest = new HashDigest(CryptoAlgorithm.SHA256, rawDigestBytes); + HashDigest hashDigest = new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, rawDigestBytes); ledgerMetadata.setParticipantsHash(hashDigest); // encode and decode @@ -95,7 +95,7 @@ public class LedgerMetaDataTest { CryptoConfig cryptoConfig = new CryptoConfig(); cryptoConfig.setAutoVerifyHash(true); - cryptoConfig.setHashAlgorithm(CryptoAlgorithm.SHA256); + cryptoConfig.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); LedgerConfiguration ledgerConfiguration = new LedgerConfiguration(consensusProvider, new Bytes(csSettingsBytes), cryptoConfig); byte[] encodeBytes = BinaryEncodingUtils.encode(ledgerConfiguration, LedgerSetting.class); @@ -134,7 +134,7 @@ public class LedgerMetaDataTest { // LedgerCodes.METADATA_LEDGER_SETTING_CRYPTO CryptoConfig cryptoConfig = new CryptoConfig(); cryptoConfig.setAutoVerifyHash(true); - cryptoConfig.setHashAlgorithm(CryptoAlgorithm.SHA256); + cryptoConfig.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); byte[] encodeBytes = BinaryEncodingUtils.encode(cryptoConfig, CryptoSetting.class); CryptoSetting deCryptoConfig = BinaryEncodingUtils.decode(encodeBytes); @@ -150,7 +150,7 @@ public class LedgerMetaDataTest { // prepare work int id = 1; // String address = "xxxxxxxxxxxxxx"; - PubKey pubKey = new PubKey(CryptoAlgorithm.ED25519, rawDigestBytes); + PubKey pubKey = new PubKey(ClassicCryptoService.ED25519_ALGORITHM, rawDigestBytes); // ParticipantInfo info = new ParticipantCertData.ParticipantInfoData(1, "yyy"); // SignatureDigest signature = new SignatureDigest(CryptoAlgorithm.SM2, // rawDigestBytes); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTestUtils.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTestUtils.java index d9c80841..d78ebed3 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTestUtils.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTestUtils.java @@ -4,10 +4,11 @@ import java.util.Random; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoUtils; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.impl.TransactionStagedSnapshot; @@ -22,7 +23,7 @@ public class LedgerTestUtils { public static TransactionRequest createTxRequest(HashDigest ledgerHash) { - return createTxRequest(ledgerHash, CryptoUtils.sign(CryptoAlgorithm.ED25519)); + return createTxRequest(ledgerHash, CryptoUtils.sign(ClassicCryptoService.ED25519_ALGORITHM)); } public static TransactionRequest createTxRequest(HashDigest ledgerHash, SignatureFunction signatureFunction) { @@ -68,14 +69,14 @@ public class LedgerTestUtils { public static HashDigest generateRandomHash() { byte[] data = new byte[64]; rand.nextBytes(data); - return CryptoUtils.hash(CryptoAlgorithm.SHA256).hash(data); + return CryptoUtils.hash(ClassicCryptoService.SHA256_ALGORITHM).hash(data); } public static CryptoSetting createDefaultCryptoSetting() { CryptoConfig cryptoSetting = new CryptoConfig(); cryptoSetting.setAutoVerifyHash(true); - cryptoSetting.setHashAlgorithm(CryptoAlgorithm.SHA256); + cryptoSetting.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); return cryptoSetting; } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTransactionDataTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTransactionDataTest.java index d44e08d9..e91688a1 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTransactionDataTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTransactionDataTest.java @@ -19,9 +19,10 @@ import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoUtils; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeyPair; import com.jd.blockchain.ledger.DataAccountKVSetOperation; @@ -69,11 +70,11 @@ public class LedgerTransactionDataTest { long blockHeight = 9986L; data = new LedgerTransactionData(blockHeight, txRequestMessage, TransactionState.SUCCESS, initTransactionStagedSnapshot()); - HashDigest hash = new HashDigest(CryptoAlgorithm.SHA256, "zhangsan".getBytes()); - HashDigest adminAccountHash = new HashDigest(CryptoAlgorithm.SHA256, "lisi".getBytes()); - HashDigest userAccountSetHash = new HashDigest(CryptoAlgorithm.SHA256, "wangwu".getBytes()); - HashDigest dataAccountSetHash = new HashDigest(CryptoAlgorithm.SHA256, "zhaoliu".getBytes()); - HashDigest contractAccountSetHash = new HashDigest(CryptoAlgorithm.SHA256, "sunqi".getBytes()); + HashDigest hash = new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "zhangsan".getBytes()); + HashDigest adminAccountHash = new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "lisi".getBytes()); + HashDigest userAccountSetHash = new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "wangwu".getBytes()); + HashDigest dataAccountSetHash = new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "zhaoliu".getBytes()); + HashDigest contractAccountSetHash = new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "sunqi".getBytes()); data.setHash(hash); // data.setBlockHeight(blockHeight); @@ -213,30 +214,30 @@ public class LedgerTransactionDataTest { private TransactionStagedSnapshot initTransactionStagedSnapshot() { TransactionStagedSnapshot transactionStagedSnapshot = new TransactionStagedSnapshot(); - transactionStagedSnapshot.setAdminAccountHash(new HashDigest(CryptoAlgorithm.SHA256, "zhangsan".getBytes())); - transactionStagedSnapshot.setContractAccountSetHash(new HashDigest(CryptoAlgorithm.SHA256, "lisi".getBytes())); - transactionStagedSnapshot.setDataAccountSetHash(new HashDigest(CryptoAlgorithm.SHA256, "wangwu".getBytes())); - transactionStagedSnapshot.setUserAccountSetHash(new HashDigest(CryptoAlgorithm.SHA256, "zhaoliu".getBytes())); + transactionStagedSnapshot.setAdminAccountHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "zhangsan".getBytes())); + transactionStagedSnapshot.setContractAccountSetHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "lisi".getBytes())); + transactionStagedSnapshot.setDataAccountSetHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "wangwu".getBytes())); + transactionStagedSnapshot.setUserAccountSetHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "zhaoliu".getBytes())); return transactionStagedSnapshot; } private TxRequestMessage initTxRequestMessage() throws Exception { TxRequestMessage txRequestMessage = new TxRequestMessage(initTransactionContent()); - SignatureDigest digest1 = new SignatureDigest(CryptoAlgorithm.ED25519, "zhangsan".getBytes()); - SignatureDigest digest2 = new SignatureDigest(CryptoAlgorithm.ED25519, "lisi".getBytes()); - DigitalSignatureBlob endPoint1 = new DigitalSignatureBlob(new PubKey(CryptoAlgorithm.ED25519, "jd1.com".getBytes()) + SignatureDigest digest1 = new SignatureDigest(ClassicCryptoService.ED25519_ALGORITHM, "zhangsan".getBytes()); + SignatureDigest digest2 = new SignatureDigest(ClassicCryptoService.ED25519_ALGORITHM, "lisi".getBytes()); + DigitalSignatureBlob endPoint1 = new DigitalSignatureBlob(new PubKey(ClassicCryptoService.ED25519_ALGORITHM, "jd1.com".getBytes()) , digest1); - DigitalSignatureBlob endPoint2 = new DigitalSignatureBlob(new PubKey(CryptoAlgorithm.ED25519, "jd2.com".getBytes()) + DigitalSignatureBlob endPoint2 = new DigitalSignatureBlob(new PubKey(ClassicCryptoService.ED25519_ALGORITHM, "jd2.com".getBytes()) , digest2); txRequestMessage.addEndpointSignatures(endPoint1); txRequestMessage.addEndpointSignatures(endPoint2); - SignatureDigest digest3 = new SignatureDigest(CryptoAlgorithm.ED25519, "wangwu".getBytes()); - SignatureDigest digest4 = new SignatureDigest(CryptoAlgorithm.ED25519, "zhaoliu".getBytes()); - DigitalSignatureBlob node1 = new DigitalSignatureBlob(new PubKey(CryptoAlgorithm.ED25519, "jd3.com".getBytes()) + SignatureDigest digest3 = new SignatureDigest(ClassicCryptoService.ED25519_ALGORITHM, "wangwu".getBytes()); + SignatureDigest digest4 = new SignatureDigest(ClassicCryptoService.ED25519_ALGORITHM, "zhaoliu".getBytes()); + DigitalSignatureBlob node1 = new DigitalSignatureBlob(new PubKey(ClassicCryptoService.ED25519_ALGORITHM, "jd3.com".getBytes()) , digest3); - DigitalSignatureBlob node2 = new DigitalSignatureBlob(new PubKey(CryptoAlgorithm.ED25519, "jd4.com".getBytes()) + DigitalSignatureBlob node2 = new DigitalSignatureBlob(new PubKey(ClassicCryptoService.ED25519_ALGORITHM, "jd4.com".getBytes()) , digest4); txRequestMessage.addNodeSignatures(node1); txRequestMessage.addNodeSignatures(node2); @@ -246,11 +247,11 @@ public class LedgerTransactionDataTest { private TransactionContent initTransactionContent() throws Exception{ TxContentBlob contentBlob = null; - BlockchainKeyPair id = BlockchainKeyGenerator.getInstance().generate(CryptoAlgorithm.ED25519); - HashDigest ledgerHash = CryptoUtils.hash(CryptoAlgorithm.SHA256).hash(UUID.randomUUID().toString().getBytes("UTF-8")); + BlockchainKeyPair id = BlockchainKeyGenerator.getInstance().generate(ClassicCryptoService.ED25519_ALGORITHM); + HashDigest ledgerHash = CryptoUtils.hash(ClassicCryptoService.SHA256_ALGORITHM).hash(UUID.randomUUID().toString().getBytes("UTF-8")); BlockchainOperationFactory opFactory = new BlockchainOperationFactory(); contentBlob = new TxContentBlob(ledgerHash); - contentBlob.setHash(new HashDigest(CryptoAlgorithm.SHA256, "jd.com".getBytes())); + contentBlob.setHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "jd.com".getBytes())); // contentBlob.setSubjectAccount(id.getAddress()); // contentBlob.setSequenceNumber(1); DataAccountKVSetOperation kvsetOP = opFactory.dataAccount(id.getAddress()).set("Name", ByteArray.fromString("AAA", "UTF-8"), -1).getOperation(); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java index 6a93df07..232fc9a2 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java @@ -13,10 +13,9 @@ import java.util.Random; import java.util.Set; import org.junit.Test; -import org.springframework.util.StringUtils; -import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.MerkleDataSet; import com.jd.blockchain.ledger.core.MerkleProof; @@ -34,7 +33,7 @@ public class MerkleDataSetTest { public void testStorageIncreasement() { String keyPrefix = ""; CryptoConfig cryptoConfig = new CryptoConfig(); - cryptoConfig.setHashAlgorithm(CryptoAlgorithm.SHA256); + cryptoConfig.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); cryptoConfig.setAutoVerifyHash(true); MemoryKVStorage storage = new MemoryKVStorage(); @@ -118,7 +117,7 @@ public class MerkleDataSetTest { Random rand = new Random(); CryptoConfig cryptoConfig = new CryptoConfig(); - cryptoConfig.setHashAlgorithm(CryptoAlgorithm.SHA256); + cryptoConfig.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); cryptoConfig.setAutoVerifyHash(true); MemoryKVStorage storage = new MemoryKVStorage(); @@ -283,7 +282,7 @@ public class MerkleDataSetTest { Random rand = new Random(); CryptoConfig cryptoConfig = new CryptoConfig(); - cryptoConfig.setHashAlgorithm(CryptoAlgorithm.SHA256); + cryptoConfig.setHashAlgorithm(ClassicCryptoService.SHA256_ALGORITHM); cryptoConfig.setAutoVerifyHash(true); MemoryKVStorage storage = new MemoryKVStorage(); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleTreeTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleTreeTest.java index 7a3b9140..8ccd2195 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleTreeTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleTreeTest.java @@ -14,8 +14,8 @@ import java.util.TreeMap; import org.junit.Test; import org.mockito.Mockito; -import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.core.MerkleDataNode; import com.jd.blockchain.ledger.core.MerkleNode; @@ -34,7 +34,7 @@ public class MerkleTreeTest { Random rand = new Random(); CryptoSetting setting = Mockito.mock(CryptoSetting.class); - when(setting.getHashAlgorithm()).thenReturn(CryptoAlgorithm.SHA256); + when(setting.getHashAlgorithm()).thenReturn(ClassicCryptoService.SHA256_ALGORITHM); when(setting.getAutoVerifyHash()).thenReturn(true); // 测试从空的树开始,顺序增加数据节点; @@ -85,7 +85,7 @@ public class MerkleTreeTest { @Test public void testSequenceInsert_OneCommit() { CryptoSetting setting = Mockito.mock(CryptoSetting.class); - when(setting.getHashAlgorithm()).thenReturn(CryptoAlgorithm.SHA256); + when(setting.getHashAlgorithm()).thenReturn(ClassicCryptoService.SHA256_ALGORITHM); when(setting.getAutoVerifyHash()).thenReturn(true); // 测试从空的树开始,顺序增加数据节点; @@ -139,7 +139,7 @@ public class MerkleTreeTest { @Test public void testSequenceInsert_MultiCommit() { CryptoSetting setting = Mockito.mock(CryptoSetting.class); - when(setting.getHashAlgorithm()).thenReturn(CryptoAlgorithm.SHA256); + when(setting.getHashAlgorithm()).thenReturn(ClassicCryptoService.SHA256_ALGORITHM); when(setting.getAutoVerifyHash()).thenReturn(true); // 测试从空的树开始,顺序增加数据节点; @@ -319,7 +319,7 @@ public class MerkleTreeTest { @Test public void testRandomInsert_MultiCommit() { CryptoSetting setting = Mockito.mock(CryptoSetting.class); - when(setting.getHashAlgorithm()).thenReturn(CryptoAlgorithm.SHA256); + when(setting.getHashAlgorithm()).thenReturn(ClassicCryptoService.SHA256_ALGORITHM); when(setting.getAutoVerifyHash()).thenReturn(true); // 保存所有写入的数据节点的 SN-Hash 映射表; @@ -409,7 +409,7 @@ public class MerkleTreeTest { @Test public void testDataModify() { CryptoSetting setting = Mockito.mock(CryptoSetting.class); - when(setting.getHashAlgorithm()).thenReturn(CryptoAlgorithm.SHA256); + when(setting.getHashAlgorithm()).thenReturn(ClassicCryptoService.SHA256_ALGORITHM); when(setting.getAutoVerifyHash()).thenReturn(true); // 保存所有写入的数据节点的 SN-Hash 映射表; @@ -492,7 +492,7 @@ public class MerkleTreeTest { @Test public void testDataVersionModify() { CryptoSetting setting = Mockito.mock(CryptoSetting.class); - when(setting.getHashAlgorithm()).thenReturn(CryptoAlgorithm.SHA256); + when(setting.getHashAlgorithm()).thenReturn(ClassicCryptoService.SHA256_ALGORITHM); when(setting.getAutoVerifyHash()).thenReturn(true); // 保存所有写入的数据节点的 SN-Hash 映射表; @@ -559,7 +559,7 @@ public class MerkleTreeTest { @Test public void testMerkleReload() { CryptoSetting setting = Mockito.mock(CryptoSetting.class); - when(setting.getHashAlgorithm()).thenReturn(CryptoAlgorithm.SHA256); + when(setting.getHashAlgorithm()).thenReturn(ClassicCryptoService.SHA256_ALGORITHM); when(setting.getAutoVerifyHash()).thenReturn(true); // 保存所有写入的数据节点的 SN-Hash 映射表; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionStagedSnapshotTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionStagedSnapshotTest.java index a38c7d0b..e819751c 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionStagedSnapshotTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionStagedSnapshotTest.java @@ -8,22 +8,17 @@ */ package test.com.jd.blockchain.ledger; +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.PubKey; -import com.jd.blockchain.crypto.base.BaseCryptoKey; import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.ledger.ContractEventSendOperation; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.LedgerDataSnapshot; -import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.core.impl.TransactionStagedSnapshot; -import com.jd.blockchain.ledger.data.ContractEventSendOpTemplate; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; /** * @@ -40,10 +35,10 @@ public class TransactionStagedSnapshotTest { public void initTransactionStagedSnapshot() { DataContractRegistry.register(LedgerDataSnapshot.class); data = new TransactionStagedSnapshot(); - data.setAdminAccountHash(new HashDigest(CryptoAlgorithm.SHA256, "zhangsan".getBytes())); - data.setContractAccountSetHash(new HashDigest(CryptoAlgorithm.SHA256, "lisi".getBytes())); - data.setDataAccountSetHash(new HashDigest(CryptoAlgorithm.SHA256, "wangwu".getBytes())); - data.setUserAccountSetHash(new HashDigest(CryptoAlgorithm.SHA256, "zhaoliu".getBytes())); + data.setAdminAccountHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "zhangsan".getBytes())); + data.setContractAccountSetHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "lisi".getBytes())); + data.setDataAccountSetHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "wangwu".getBytes())); + data.setUserAccountSetHash(new HashDigest(ClassicCryptoService.SHA256_ALGORITHM, "zhaoliu".getBytes())); } @Test diff --git a/source/ledger/ledger-model/pom.xml b/source/ledger/ledger-model/pom.xml index d70eb20c..0c19fb1e 100644 --- a/source/ledger/ledger-model/pom.xml +++ b/source/ledger/ledger-model/pom.xml @@ -21,6 +21,14 @@ crypto-framework ${project.version} + + + com.jd.blockchain + crypto-impl + ${project.version} + test + + com.jd.blockchain utils-common @@ -31,6 +39,8 @@ utils-web ${project.version} + + diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AccountHeader.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AccountHeader.java index d382295d..fa77557c 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AccountHeader.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AccountHeader.java @@ -1,9 +1,9 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.consts.TypeCodes; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockBody.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockBody.java index 54a6011e..73d8496c 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockBody.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockBody.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainIdentity.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainIdentity.java index 4b54cd3c..ea061bcf 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainIdentity.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainIdentity.java @@ -1,9 +1,9 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.consts.TypeCodes; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainIdentityData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainIdentityData.java index 212b49e3..66cd5766 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainIdentityData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainIdentityData.java @@ -16,7 +16,7 @@ import com.jd.blockchain.binaryproto.FieldSetter; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.data.CryptoKeyEncoding; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.io.ByteArray; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainKeyGenerator.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainKeyGenerator.java index cdf97c92..03fce3d1 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainKeyGenerator.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainKeyGenerator.java @@ -2,10 +2,7 @@ package com.jd.blockchain.ledger; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoUtils; -import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.SignatureFunction; -import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; /** * 区块链密钥生成器; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainKeyPair.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainKeyPair.java index 2a2a7c9a..a988d2b3 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainKeyPair.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockchainKeyPair.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.utils.Bytes; /** diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValue.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValue.java index c2fec5fc..d54b29b0 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValue.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValue.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.ValueType; import com.jd.blockchain.utils.io.BytesSlice; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ContractCodeDeployOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ContractCodeDeployOperation.java index 48ab6294..7d82a7c8 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ContractCodeDeployOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ContractCodeDeployOperation.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.ValueType; @DataContract(code= TypeCodes.TX_OP_CONTRACT_DEPLOY) diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ContractEventSendOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ContractEventSendOperation.java index dce287b8..38c6b90d 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ContractEventSendOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ContractEventSendOperation.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/CryptoSetting.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/CryptoSetting.java index d00518e9..4dae891b 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/CryptoSetting.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/CryptoSetting.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataAccountKVSetOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataAccountKVSetOperation.java index 868bbd0f..a892aba9 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataAccountKVSetOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataAccountKVSetOperation.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataAccountRegisterOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataAccountRegisterOperation.java index d79ae611..c938d352 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataAccountRegisterOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataAccountRegisterOperation.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; @DataContract(code= TypeCodes.TX_OP_DATA_ACC_REG) public interface DataAccountRegisterOperation extends Operation { diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataType.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataType.java index 5bbd832d..278937d0 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataType.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataType.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.EnumContract; import com.jd.blockchain.binaryproto.EnumField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.ValueType; /** diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DigitalSignature.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DigitalSignature.java index 9a4ea62a..1a4ce413 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DigitalSignature.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DigitalSignature.java @@ -1,7 +1,7 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.consts.TypeCodes; /** * 数字签名; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DigitalSignatureBody.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DigitalSignatureBody.java index 1b3f26ac..4182bed3 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DigitalSignatureBody.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DigitalSignatureBody.java @@ -1,9 +1,9 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.consts.TypeCodes; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/EndpointRequest.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/EndpointRequest.java index f0a31867..0af6f231 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/EndpointRequest.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/EndpointRequest.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/HashObject.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/HashObject.java index 1e9fb23c..8027bb03 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/HashObject.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/HashObject.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.hash.HashDigest; /** diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerBlock.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerBlock.java index e2c3eecc..c2e1e48a 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerBlock.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerBlock.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerDataSnapshot.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerDataSnapshot.java index 27265b6f..969feb78 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerDataSnapshot.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerDataSnapshot.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitOperation.java index 9b6a98a6..c14e79c1 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitOperation.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; @DataContract(code= TypeCodes.TX_OP_LEDGER_INIT) public interface LedgerInitOperation extends Operation{ diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitSetting.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitSetting.java index 548ec34f..7d36dd30 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitSetting.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitSetting.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerTransaction.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerTransaction.java index 45bc493c..ef7cb4d3 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerTransaction.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerTransaction.java @@ -1,7 +1,7 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.consts.TypeCodes; /** * 账本的事务; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/NodeRequest.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/NodeRequest.java index 574f1fcf..2742d4c9 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/NodeRequest.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/NodeRequest.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.hash.HashDigest; @DataContract(code = TypeCodes.REQUEST_NODE) diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Operation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Operation.java index 87b8f97b..cbc7ffcb 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Operation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Operation.java @@ -1,7 +1,7 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.consts.TypeCodes; @DataContract(code= TypeCodes.TX_OP) public interface Operation { diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java index 6cc9924d..91b63af1 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java @@ -1,9 +1,9 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.consts.TypeCodes; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.utils.ValueType; /** diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Transaction.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Transaction.java index a286797e..38dd7978 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Transaction.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Transaction.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.utils.ValueType; import com.jd.blockchain.utils.io.ByteArray; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionContent.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionContent.java index 136ce6e0..1d63bacc 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionContent.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionContent.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionContentBody.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionContentBody.java index 39a27256..2fc029db 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionContentBody.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionContentBody.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionRequest.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionRequest.java index 7bb56dcb..db2c39f4 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionRequest.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionRequest.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionResponse.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionResponse.java index db7e5125..e6b16a32 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionResponse.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionResponse.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.utils.ValueType; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java index d19bcff2..ed756109 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.EnumContract; import com.jd.blockchain.binaryproto.EnumField; +import com.jd.blockchain.consts.TypeCodes; import com.jd.blockchain.utils.ValueType; /** diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserInfo.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserInfo.java index 5510cdb0..eea581ff 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserInfo.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserInfo.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.consts.TypeCodes; +import com.jd.blockchain.crypto.PubKey; @DataContract(code= TypeCodes.USER) public interface UserInfo extends AccountHeader { diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRegisterOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRegisterOperation.java index ec0f7372..f565b190 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRegisterOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRegisterOperation.java @@ -1,8 +1,8 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.base.data.TypeCodes; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.TypeCodes; @DataContract(code= TypeCodes.TX_OP_USER_REG) public interface UserRegisterOperation extends Operation { diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/ConsensusParticipantData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/ConsensusParticipantData.java index 73049d3a..e31d6447 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/ConsensusParticipantData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/ConsensusParticipantData.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger.data; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.utils.net.NetworkAddress; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/CryptoKeyEncoding.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/CryptoKeyEncoding.java index fcf22a26..15cb3649 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/CryptoKeyEncoding.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/CryptoKeyEncoding.java @@ -7,8 +7,8 @@ import java.io.OutputStream; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoKey; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.MagicNumber; import com.jd.blockchain.utils.io.ByteArray; import com.jd.blockchain.utils.io.BytesEncoding; @@ -34,7 +34,7 @@ public class CryptoKeyEncoding { } out.write(magicNum); - out.write(key.getAlgorithm().CODE); + out.write(key.getAlgorithm().code()); int size = 2;// 已经写入 2 字节; size += BytesEncoding.write(key.getRawKeyBytes(), NumberMask.SHORT, out); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/DigitalSignatureBlob.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/DigitalSignatureBlob.java index 47fb64b3..86e78819 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/DigitalSignatureBlob.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/DigitalSignatureBlob.java @@ -1,7 +1,7 @@ package com.jd.blockchain.ledger.data; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.DigitalSignature; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/PreparedTx.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/PreparedTx.java index 68faa219..7e798102 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/PreparedTx.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/PreparedTx.java @@ -2,12 +2,11 @@ package com.jd.blockchain.ledger.data; import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.crypto.CryptoUtils; +import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.impl.def.asymmetric.ED25519SignatureFunction; import com.jd.blockchain.ledger.DigitalSignature; import com.jd.blockchain.ledger.PreparedTransaction; import com.jd.blockchain.ledger.TransactionContent; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/TxRequestBuilder.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/TxRequestBuilder.java index ee8232b7..8d2577a1 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/TxRequestBuilder.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/data/TxRequestBuilder.java @@ -6,9 +6,9 @@ import java.util.List; import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoUtils; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.DigitalSignature; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/AddressEncodingTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/AddressEncodingTest.java index 60003c9b..e2b8a828 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/AddressEncodingTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/AddressEncodingTest.java @@ -3,7 +3,7 @@ package test.com.jd.blockchain.ledger.data; import java.util.Random; import com.jd.blockchain.crypto.AddressEncoding; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeyPair; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractCodeDeployOpTemplateTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractCodeDeployOpTemplateTest.java index 801e744d..ca6e28d3 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractCodeDeployOpTemplateTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractCodeDeployOpTemplateTest.java @@ -11,7 +11,7 @@ package test.com.jd.blockchain.ledger.data; import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.data.ContractCodeDeployOpTemplate; import com.jd.blockchain.utils.io.BytesUtils; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DataAccountRegisterOpTemplateTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DataAccountRegisterOpTemplateTest.java index f31fe98c..27b62443 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DataAccountRegisterOpTemplateTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DataAccountRegisterOpTemplateTest.java @@ -11,7 +11,7 @@ package test.com.jd.blockchain.ledger.data; import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.data.DataAccountRegisterOpTemplate; import com.jd.blockchain.utils.io.ByteArray; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DigitalSignatureBlobTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DigitalSignatureBlobTest.java index 27f6d733..b75c4149 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DigitalSignatureBlobTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DigitalSignatureBlobTest.java @@ -16,7 +16,7 @@ import org.junit.Test; import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.ledger.DigitalSignature; import com.jd.blockchain.ledger.DigitalSignatureBody; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/TxRequestMessageTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/TxRequestMessageTest.java index 6649c6b8..a361cfa3 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/TxRequestMessageTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/TxRequestMessageTest.java @@ -19,7 +19,7 @@ import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoUtils; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.BlockchainKeyGenerator; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/UserRegisterOpTemplateTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/UserRegisterOpTemplateTest.java index c5e717f4..ce31ce45 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/UserRegisterOpTemplateTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/UserRegisterOpTemplateTest.java @@ -11,7 +11,7 @@ package test.com.jd.blockchain.ledger.data; import com.jd.blockchain.binaryproto.BinaryEncodingUtils; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.data.ContractEventSendOpTemplate; import com.jd.blockchain.ledger.data.DataAccountRegisterOpTemplate; diff --git a/source/ledger/ledger-rpc/pom.xml b/source/ledger/ledger-rpc/pom.xml index d46aafd3..3dd190a8 100644 --- a/source/ledger/ledger-rpc/pom.xml +++ b/source/ledger/ledger-rpc/pom.xml @@ -36,16 +36,16 @@ - - - - org.apache.maven.plugins - maven-deploy-plugin - 2.8.2 - - false - - - - + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + true + + + + \ No newline at end of file diff --git a/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/serializes/ByteArrayObjectJsonSerializer.java b/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/serializes/ByteArrayObjectJsonSerializer.java deleted file mode 100644 index 4aba18a3..00000000 --- a/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/serializes/ByteArrayObjectJsonSerializer.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.jd.blockchain.web.serializes; - -import com.alibaba.fastjson.serializer.JSONSerializer; -import com.alibaba.fastjson.serializer.ObjectSerializer; -import com.jd.blockchain.crypto.asymmetric.PubKey; -import com.jd.blockchain.crypto.asymmetric.SignatureDigest; -import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.ledger.DataType; -import com.jd.blockchain.utils.Bytes; -import com.jd.blockchain.utils.codec.Base58Utils; -import com.jd.blockchain.utils.io.BytesSlice; - -import java.lang.reflect.Type; - -public class ByteArrayObjectJsonSerializer implements ObjectSerializer { - - private Class clazz; - - private ByteArrayObjectJsonSerializer(Class clazz) { - this.clazz = clazz; - } - - public static ByteArrayObjectJsonSerializer getInstance(Class clazz) { - return new ByteArrayObjectJsonSerializer(clazz); - } - - @Override - public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) { - if (object.getClass() != clazz) { - serializer.writeNull(); - return; - } - if (object instanceof HashDigest) { - serializer.write(new HashDigestJson(((HashDigest) object).toBase58())); - } else if (object instanceof PubKey) { - serializer.write(new HashDigestJson(((PubKey) object).toBase58())); - } else if (object instanceof SignatureDigest) { - serializer.write(new HashDigestJson(((SignatureDigest) object).toBase58())); - } else if (object instanceof Bytes) { - serializer.write(new HashDigestJson(((Bytes) object).toBase58())); - } else if (object instanceof BytesSlice) { - serializer.write(Base58Utils.encode(((BytesSlice) object).toBytes())); - } - -// else if (object instanceof BytesValue) { -// DataType dataType = ((BytesValue) object).getType(); -// BytesSlice bytesValue = ((BytesValue) object).getValue(); -// Object realVal; -// switch (dataType) { -// case NIL: -// realVal = null; -// break; -// case TEXT: -// realVal = bytesValue.getString(); -// break; -// case BYTES: -// realVal = ByteArray.toHex(bytesValue.toBytes()); -// break; -// case INT32: -// realVal = bytesValue.getInt(); -// break; -// case INT64: -// realVal = bytesValue.getLong(); -// break; -// case JSON: -// realVal = bytesValue.getString(); -// break; -// default: -// realVal = ByteArray.toHex(bytesValue.toBytes()); -// break; -// } -// serializer.write(new BytesValueJson(dataType, realVal)); -// } - } - - private static class HashDigestJson { - - String value; - - public HashDigestJson(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - } - - public static class BytesValueJson { - - public BytesValueJson(DataType type, Object value) { - this.type = type; - this.value = value; - } - - DataType type; - - Object value; - - public DataType getType() { - return type; - } - - public void setType(DataType type) { - this.type = type; - } - - public Object getValue() { - return value; - } - - public void setValue(Object value) { - this.value = value; - } - } -} diff --git a/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/serializes/ByteArrayObjectUtil.java b/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/serializes/ByteArrayObjectUtil.java deleted file mode 100644 index 2623cfe5..00000000 --- a/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/serializes/ByteArrayObjectUtil.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright: Copyright 2016-2020 JD.COM All Right Reserved - * FileName: com.jd.blockchain.web.serializes.ByteArrayObjectUtil - * Author: shaozhuguang - * Department: Y事业部 - * Date: 2019/3/27 上午11:23 - * Description: - */ -package com.jd.blockchain.web.serializes; - -import com.jd.blockchain.crypto.asymmetric.PubKey; -import com.jd.blockchain.crypto.asymmetric.SignatureDigest; -import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.utils.Bytes; -import com.jd.blockchain.utils.io.BytesSlice; -import com.jd.blockchain.utils.serialize.json.JSONSerializeUtils; - -/** - * - * @author shaozhuguang - * @create 2019/3/27 - * @since 1.0.0 - */ - -public class ByteArrayObjectUtil { - - public static final Class[] BYTEARRAY_JSON_SERIALIZE_CLASS = new Class[] { - HashDigest.class, - PubKey.class, - SignatureDigest.class, - Bytes.class, - BytesSlice.class}; - - public static void init() { - for (Class byteArrayClass : BYTEARRAY_JSON_SERIALIZE_CLASS) { - JSONSerializeUtils.configSerialization(byteArrayClass, - ByteArrayObjectJsonSerializer.getInstance(byteArrayClass), - ByteArrayObjectJsonDeserializer.getInstance(byteArrayClass)); - } - } -} \ No newline at end of file diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/PeerWebServerConfigurer.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/PeerWebServerConfigurer.java index f0d52ac5..9ae76451 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/PeerWebServerConfigurer.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/PeerWebServerConfigurer.java @@ -2,16 +2,22 @@ package com.jd.blockchain.peer.web; import java.util.List; +import com.jd.blockchain.utils.io.BytesSlice; import com.jd.blockchain.web.converters.BinaryMessageConverter; import com.jd.blockchain.web.converters.HashDigestInputConverter; -import com.jd.blockchain.web.serializes.ByteArrayObjectUtil; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.crypto.asymmetric.SignatureDigest; +import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.crypto.serialize.ByteArrayObjectDeserializer; +import com.jd.blockchain.crypto.serialize.ByteArrayObjectSerializer; +import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.io.ByteArray; import com.jd.blockchain.utils.serialize.json.JSONSerializeUtils; import com.jd.blockchain.utils.web.model.JsonWebResponseMessageConverter; @@ -19,6 +25,13 @@ import com.jd.blockchain.utils.web.model.JsonWebResponseMessageConverter; @Configuration public class PeerWebServerConfigurer implements WebMvcConfigurer { + private static final Class[] BYTEARRAY_JSON_SERIALIZE_CLASS = new Class[] { + HashDigest.class, + PubKey.class, + SignatureDigest.class, + Bytes.class, + BytesSlice.class}; + static { JSONSerializeUtils.disableCircularReferenceDetect(); JSONSerializeUtils.configStringSerializer(ByteArray.class); @@ -46,6 +59,10 @@ public class PeerWebServerConfigurer implements WebMvcConfigurer { } private void initByteArrayJsonSerialize() { - ByteArrayObjectUtil.init(); + for (Class byteArrayClass : BYTEARRAY_JSON_SERIALIZE_CLASS) { + JSONSerializeUtils.configSerialization(byteArrayClass, + ByteArrayObjectSerializer.getInstance(byteArrayClass), + ByteArrayObjectDeserializer.getInstance(byteArrayClass)); + } } } diff --git a/source/pom.xml b/source/pom.xml index 2e510d96..37592211 100644 --- a/source/pom.xml +++ b/source/pom.xml @@ -41,7 +41,7 @@ 0.8.1-SNAPSHOT 0.0.8.RELEASE - 0.6.6.RELEASE + 0.6.4.RELEASE diff --git a/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/AbstractModule.java b/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/AbstractModule.java index 234b73fe..fbd2df3f 100644 --- a/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/AbstractModule.java +++ b/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/AbstractModule.java @@ -22,8 +22,7 @@ public abstract class AbstractModule implements Module { throw new IllegalStateException(e.getMessage(), e); } } - - @Override + public InputStream loadResourceAsStream(String name) { return getModuleClassLoader().getResourceAsStream(name); } diff --git a/source/runtime/runtime-modular/src/main/java/com/jd/blockchain/runtime/modular/ModularFactory.java b/source/runtime/runtime-modular/src/main/java/com/jd/blockchain/runtime/modular/ModularFactory.java index e38b3dd1..b9615482 100644 --- a/source/runtime/runtime-modular/src/main/java/com/jd/blockchain/runtime/modular/ModularFactory.java +++ b/source/runtime/runtime-modular/src/main/java/com/jd/blockchain/runtime/modular/ModularFactory.java @@ -3,7 +3,7 @@ package com.jd.blockchain.runtime.modular; public class ModularFactory { /** - * start system; + * 启动系统; */ public static void startSystem(String runtimeDir, boolean productMode, ClassLoader libClassLoader,String mainClassName, ClassLoader systemClassLoader, String[] args) { diff --git a/source/runtime/runtime-modular/src/main/java/com/jd/blockchain/runtime/modular/MuduleClassLoader.java b/source/runtime/runtime-modular/src/main/java/com/jd/blockchain/runtime/modular/MuduleClassLoader.java index ad202462..732ba9a8 100644 --- a/source/runtime/runtime-modular/src/main/java/com/jd/blockchain/runtime/modular/MuduleClassLoader.java +++ b/source/runtime/runtime-modular/src/main/java/com/jd/blockchain/runtime/modular/MuduleClassLoader.java @@ -11,7 +11,6 @@ public class MuduleClassLoader extends URLClassLoader { } - @Override public Class loadClass(String name) throws ClassNotFoundException{ if (name.equals("com.jd.blockchain.contract.model.ContractEventContext") ){ diff --git a/source/sdk/sdk-base/pom.xml b/source/sdk/sdk-base/pom.xml index 4b629000..4179db3a 100644 --- a/source/sdk/sdk-base/pom.xml +++ b/source/sdk/sdk-base/pom.xml @@ -9,15 +9,14 @@ sdk-base - + com.jd.blockchain - ledger-rpc + ledger-model ${project.version} diff --git a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/HashDigestsResponseConverter.java b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/HashDigestsResponseConverter.java index 9c766e5e..85aefe33 100644 --- a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/HashDigestsResponseConverter.java +++ b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/HashDigestsResponseConverter.java @@ -4,7 +4,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.jd.blockchain.binaryproto.BinaryEncodingUtils; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.data.TxResponseMessage; diff --git a/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/ClientOperationUtil.java b/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/ClientOperationUtil.java deleted file mode 100644 index 2d75f985..00000000 --- a/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/ClientOperationUtil.java +++ /dev/null @@ -1,273 +0,0 @@ -/** - * Copyright: Copyright 2016-2020 JD.COM All Right Reserved - * FileName: com.jd.blockchain.sdk.client.ClientOperationUtil - * Author: shaozhuguang - * Department: Y事业部 - * Date: 2019/3/27 下午4:12 - * Description: - */ -package com.jd.blockchain.sdk.client; - -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.PubKey; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.ledger.data.*; -import com.jd.blockchain.utils.Bytes; -import com.jd.blockchain.utils.codec.Base58Utils; -import com.jd.blockchain.utils.codec.HexUtils; -import com.jd.blockchain.utils.io.BytesSlice; -import org.apache.commons.codec.binary.Base64; - -import java.lang.reflect.Field; - -/** - * - * @author shaozhuguang - * @create 2019/3/27 - * @since 1.0.0 - */ - -public class ClientOperationUtil { - - public static Operation read(Operation operation) { - - try { - // Class - Class clazz = operation.getClass(); - Field field = clazz.getSuperclass().getDeclaredField("h"); - field.setAccessible(true); - Object object = field.get(operation); - if (object instanceof JSONObject) { - JSONObject jsonObject = (JSONObject) object; - if (jsonObject.containsKey("accountID")) { - return convertDataAccountRegisterOperation(jsonObject); - } else if (jsonObject.containsKey("userID")) { - return convertUserRegisterOperation(jsonObject); - } else if (jsonObject.containsKey("contractID")) { - return convertContractCodeDeployOperation(jsonObject); - } else if (jsonObject.containsKey("writeSet")) { - return convertDataAccountKVSetOperation(jsonObject); - } else if (jsonObject.containsKey("initSetting")) { - return convertLedgerInitOperation(jsonObject); - } else if (jsonObject.containsKey("contractAddress")) { - return convertContractEventSendOperation(jsonObject); - } - } - } catch (Exception e) { - throw new RuntimeException(e); - } - - return null; - } - - public static Object readValueByBytesValue(BytesValue bytesValue) { - DataType dataType = bytesValue.getType(); - BytesSlice saveVal = bytesValue.getValue(); - Object showVal; - switch (dataType) { - case BYTES: - // return hex - showVal = HexUtils.encode(saveVal.getBytesCopy()); - break; - case TEXT: - case JSON: - showVal = saveVal.getString(); - break; - case INT64: - showVal = saveVal.getLong(); - break; - default: - showVal = HexUtils.encode(saveVal.getBytesCopy()); - break; - } - return showVal; - } - - public static DataAccountRegisterOperation convertDataAccountRegisterOperation(JSONObject jsonObject) { - JSONObject account = jsonObject.getJSONObject("accountID"); - return new DataAccountRegisterOpTemplate(blockchainIdentity(account)); - } - - public static DataAccountKVSetOperation convertDataAccountKVSetOperation(JSONObject jsonObject) { - // 写入集合处理 - JSONArray writeSetObj = jsonObject.getJSONArray("writeSet"); - JSONObject accountAddrObj = jsonObject.getJSONObject("accountAddress"); - String addressBase58 = accountAddrObj.getString("value"); - Bytes address = Bytes.fromBase58(addressBase58); - - DataAccountKVSetOpTemplate kvOperation = new DataAccountKVSetOpTemplate(address); - for (int i = 0; i + + + + sdk + com.jd.blockchain + 0.9.0-SNAPSHOT + + 4.0.0 + + sdk-mq + + + UTF-8 + 1.8 + 1.8 + + + + + junit + junit + 4.11 + test + + + com.jd.blockchain + sdk-base + ${project.version} + + + com.lmax + disruptor + + + io.nats + jnats + + + + + + + diff --git a/source/sdk/sdk-samples/pom.xml b/source/sdk/sdk-samples/pom.xml index 8c47ed65..bc85d375 100644 --- a/source/sdk/sdk-samples/pom.xml +++ b/source/sdk/sdk-samples/pom.xml @@ -24,13 +24,6 @@ tools-initializer ${project.version} - - - com.jd.blockchain - ledger-rpc - ${project.version} - - diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Contract.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Contract.java index 4f097128..d79764eb 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Contract.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Contract.java @@ -1,11 +1,11 @@ package com.jd.blockchain.sdk.samples; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoUtils; import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeyPair; import com.jd.blockchain.ledger.PreparedTransaction; @@ -26,7 +26,7 @@ public class SDKDemo_Contract { public static BlockchainKeyPair CLIENT_CERT = BlockchainKeyGenerator.getInstance().generate(CryptoAlgorithm.ED25519); - public static AsymmetricCryptography asymmetricCryptography = new AsymmtricCryptographyImpl(); + public static AsymmetricCryptography asymmetricCryptography = CryptoUtils.asymmCrypto(); /** * 演示合约执行的过程; diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_DataAccount.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_DataAccount.java index 24fe6a56..fdcee7c2 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_DataAccount.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_DataAccount.java @@ -1,13 +1,15 @@ package com.jd.blockchain.sdk.samples; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoUtils; import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.ledger.data.CryptoKeyEncoding; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeyPair; +import com.jd.blockchain.ledger.PreparedTransaction; +import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.sdk.BlockchainTransactionService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.utils.net.NetworkAddress; @@ -16,7 +18,7 @@ public class SDKDemo_DataAccount { public static BlockchainKeyPair CLIENT_CERT = BlockchainKeyGenerator.getInstance().generate(CryptoAlgorithm.ED25519); - public static AsymmetricCryptography asymmetricCryptography = new AsymmtricCryptographyImpl(); + public static AsymmetricCryptography asymmetricCryptography = CryptoUtils.asymmCrypto(); /** * 生成一个区块链用户,并注册到区块链; diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_InsertData.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_InsertData.java index 485b1d5d..be38f91b 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_InsertData.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_InsertData.java @@ -1,11 +1,11 @@ package com.jd.blockchain.sdk.samples; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoUtils; import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeyPair; import com.jd.blockchain.ledger.PreparedTransaction; @@ -25,7 +25,7 @@ public class SDKDemo_InsertData { public static BlockchainKeyPair CLIENT_CERT = BlockchainKeyGenerator.getInstance().generate(CryptoAlgorithm.ED25519); - public static AsymmetricCryptography asymmetricCryptography = new AsymmtricCryptographyImpl(); + public static AsymmetricCryptography asymmetricCryptography = CryptoUtils.asymmCrypto(); /** * 演示数据写入的调用过程; diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Params.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Params.java index 59c6bcfc..491abef8 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Params.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Params.java @@ -8,8 +8,8 @@ */ package com.jd.blockchain.sdk.samples; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.tools.keygen.KeyGenCommand; /** diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_RegisterTest.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_RegisterTest.java index 3701e6fa..5148f62b 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_RegisterTest.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_RegisterTest.java @@ -9,9 +9,9 @@ package com.jd.blockchain.sdk.samples; import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.*; import com.jd.blockchain.sdk.BlockchainService; diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_RegisterUser.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_RegisterUser.java index fe5c18bc..859163df 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_RegisterUser.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_RegisterUser.java @@ -9,9 +9,9 @@ package com.jd.blockchain.sdk.samples; import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.*; import com.jd.blockchain.sdk.BlockchainService; diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_User.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_User.java index e6bb4e81..59c76c68 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_User.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_User.java @@ -1,12 +1,15 @@ package com.jd.blockchain.sdk.samples; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoUtils; import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; -import com.jd.blockchain.ledger.*; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeyPair; +import com.jd.blockchain.ledger.PreparedTransaction; +import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.sdk.BlockchainTransactionService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.utils.net.NetworkAddress; @@ -15,7 +18,7 @@ public class SDKDemo_User { public static BlockchainKeyPair CLIENT_CERT = BlockchainKeyGenerator.getInstance().generate(); - public static AsymmetricCryptography asymmetricCryptography = new AsymmtricCryptographyImpl(); + public static AsymmetricCryptography asymmetricCryptography = CryptoUtils.asymmCrypto(); /** * 生成一个区块链用户,并注册到区块链; diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_BatchInsertData_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_BatchInsertData_Test_.java index 933cf1ff..ee851828 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_BatchInsertData_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_BatchInsertData_Test_.java @@ -8,26 +8,34 @@ */ package test.com.jd.blockchain.sdk.test; +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoUtils; import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; -import com.jd.blockchain.ledger.*; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeyPair; +import com.jd.blockchain.ledger.EndpointRequest; +import com.jd.blockchain.ledger.NodeRequest; +import com.jd.blockchain.ledger.PreparedTransaction; +import com.jd.blockchain.ledger.TransactionContent; +import com.jd.blockchain.ledger.TransactionContentBody; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.TransactionResponse; +import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.data.TxResponseMessage; -import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.BlockchainTransactionService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.utils.codec.Base58Utils; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - /** * 插入数据测试 * @author shaozhuguang @@ -37,6 +45,8 @@ import static org.junit.Assert.assertTrue; public class SDK_GateWay_BatchInsertData_Test_ { + String ledgerHash = ""; + private BlockchainKeyPair CLIENT_CERT = null; private String GATEWAY_IPADDR = null; @@ -45,9 +55,9 @@ public class SDK_GateWay_BatchInsertData_Test_ { private boolean SECURE; - private BlockchainService service; + private BlockchainTransactionService service; - private AsymmetricCryptography asymmetricCryptography = new AsymmtricCryptographyImpl(); + private AsymmetricCryptography asymmetricCryptography = CryptoUtils.asymmCrypto(); @Before public void init() { @@ -55,15 +65,22 @@ public class SDK_GateWay_BatchInsertData_Test_ { GATEWAY_IPADDR = "127.0.0.1"; GATEWAY_PORT = 8000; SECURE = false; - GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect( - GATEWAY_IPADDR, GATEWAY_PORT, SECURE, CLIENT_CERT); + GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IPADDR, GATEWAY_PORT, SECURE, + CLIENT_CERT); service = serviceFactory.getBlockchainService(); + + DataContractRegistry.register(TransactionContent.class); + DataContractRegistry.register(TransactionContentBody.class); + DataContractRegistry.register(TransactionRequest.class); + DataContractRegistry.register(NodeRequest.class); + DataContractRegistry.register(EndpointRequest.class); + DataContractRegistry.register(TransactionResponse.class); } @Test public void batchInsertData_Test() { - HashDigest ledgerHash = service.getLedgerHashs()[0]; - // 在本地定义TX模板 + HashDigest ledgerHash = getLedgerHash(); + // 在本地定义注册账号的 TX; TransactionTemplate txTemp = service.newTransaction(ledgerHash); // -------------------------------------- @@ -77,7 +94,6 @@ public class SDK_GateWay_BatchInsertData_Test_ { String key2 = "jd_key2"; byte[] val2 = "www.jd.com".getBytes(); - // 版本号根据实际情况进行调整 txTemp.dataAccount(dataAccount).set(key1, val1, -1); txTemp.dataAccount(dataAccount).set(key2, val2, -1); @@ -91,11 +107,38 @@ public class SDK_GateWay_BatchInsertData_Test_ { // 提交交易; TransactionResponse transactionResponse = prepTx.commit(); - assertTrue(transactionResponse.isSuccess()); + // 期望返回结果 + TransactionResponse expectResp = initResponse(); + + System.out.println("---------- assert start ----------"); + assertEquals(expectResp.isSuccess(), transactionResponse.isSuccess()); + assertEquals(expectResp.getExecutionState(), transactionResponse.getExecutionState()); + assertEquals(expectResp.getContentHash(), transactionResponse.getContentHash()); + assertEquals(expectResp.getBlockHeight(), transactionResponse.getBlockHeight()); + assertEquals(expectResp.getBlockHash(), transactionResponse.getBlockHash()); + System.out.println("---------- assert OK ----------"); } + private HashDigest getLedgerHash() { + byte[] hashBytes = Base58Utils.decode(ledgerHash); + return new HashDigest(hashBytes); + } + + private CryptoKeyPair getSponsorKey() { SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); return signatureFunction.generateKeyPair(); } + + private TransactionResponse initResponse() { + HashDigest contentHash = new HashDigest(CryptoAlgorithm.SHA256, "contentHash".getBytes()); + HashDigest blockHash = new HashDigest(CryptoAlgorithm.SHA256, "blockHash".getBytes()); + long blockHeight = 9998L; + + TxResponseMessage resp = new TxResponseMessage(contentHash); + resp.setBlockHash(blockHash); + resp.setBlockHeight(blockHeight); + resp.setExecutionState(TransactionState.SUCCESS); + return resp; + } } \ No newline at end of file diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_ContractDeploy_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_ContractDeploy_Test_.java deleted file mode 100644 index 41b30fdd..00000000 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_ContractDeploy_Test_.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright: Copyright 2016-2020 JD.COM All Right Reserved - * FileName: test.com.jd.blockchain.sdk.test.SDK_GateWay_InsertData_Test - * Author: shaozhuguang - * Department: 区块链研发部 - * Date: 2018/9/4 上午11:06 - * Description: 插入数据测试 - */ -package test.com.jd.blockchain.sdk.test; - -import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; -import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.SignatureFunction; -import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.ledger.data.TxResponseMessage; -import com.jd.blockchain.sdk.BlockchainService; -import com.jd.blockchain.sdk.BlockchainTransactionService; -import com.jd.blockchain.sdk.client.GatewayServiceFactory; -import com.jd.blockchain.utils.io.FileUtils; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * 插入数据测试 - * @author shaozhuguang - * @create 2018/9/4 - * @since 1.0.0 - */ - -public class SDK_GateWay_ContractDeploy_Test_ { - - private BlockchainKeyPair CLIENT_CERT = null; - - private String GATEWAY_IPADDR = null; - - private int GATEWAY_PORT; - - private boolean SECURE; - - private BlockchainService service; - - private String CONTRACT_FILE = null; - - private AsymmetricCryptography asymmetricCryptography = new AsymmtricCryptographyImpl(); - - @Before - public void init() { - CLIENT_CERT = BlockchainKeyGenerator.getInstance().generate(CryptoAlgorithm.ED25519); - GATEWAY_IPADDR = "127.0.0.1"; - GATEWAY_PORT = 8000; - SECURE = false; - GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IPADDR, GATEWAY_PORT, SECURE, - CLIENT_CERT); - service = serviceFactory.getBlockchainService(); - } - - @Test - public void contractDeploy_Test() { - HashDigest ledgerHash = service.getLedgerHashs()[0]; - // 在本地定义TX模板 - TransactionTemplate txTemp = service.newTransaction(ledgerHash); - - // 合约内容读取 - byte[] contractBytes = FileUtils.readBytes(new File(CONTRACT_FILE)); - - // 生成用户 - BlockchainIdentityData blockchainIdentity = new BlockchainIdentityData(getSponsorKey().getPubKey()); - - // 发布合约 - txTemp.contracts().deploy(blockchainIdentity, contractBytes); - - // TX 准备就绪; - PreparedTransaction prepTx = txTemp.prepare(); - - // 使用私钥进行签名; - CryptoKeyPair keyPair = getSponsorKey(); - - prepTx.sign(keyPair); - - // 提交交易; - TransactionResponse transactionResponse = prepTx.commit(); - - assertTrue(transactionResponse.isSuccess()); - - // 打印合约地址 - System.out.println(blockchainIdentity.getAddress().toBase58()); - } - - private CryptoKeyPair getSponsorKey() { - SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); - return signatureFunction.generateKeyPair(); - } -} \ No newline at end of file diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_ContractExec_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_ContractExec_Test_.java deleted file mode 100644 index ab6b8df3..00000000 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_ContractExec_Test_.java +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright: Copyright 2016-2020 JD.COM All Right Reserved - * FileName: test.com.jd.blockchain.sdk.test.SDK_GateWay_InsertData_Test - * Author: shaozhuguang - * Department: 区块链研发部 - * Date: 2018/9/4 上午11:06 - * Description: 插入数据测试 - */ -package test.com.jd.blockchain.sdk.test; - -import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.crypto.CryptoAlgorithm; -import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; -import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.SignatureFunction; -import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.ledger.data.TxResponseMessage; -import com.jd.blockchain.sdk.BlockchainService; -import com.jd.blockchain.sdk.BlockchainTransactionService; -import com.jd.blockchain.sdk.client.GatewayServiceFactory; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * 插入数据测试 - * @author shaozhuguang - * @create 2018/9/4 - * @since 1.0.0 - */ - -public class SDK_GateWay_ContractExec_Test_ { - - private BlockchainKeyPair CLIENT_CERT = null; - - private String GATEWAY_IPADDR = null; - - private int GATEWAY_PORT; - - private boolean SECURE; - - private BlockchainService service; - - private AsymmetricCryptography asymmetricCryptography = new AsymmtricCryptographyImpl(); - - @Before - public void init() { - CLIENT_CERT = BlockchainKeyGenerator.getInstance().generate(CryptoAlgorithm.ED25519); - GATEWAY_IPADDR = "127.0.0.1"; - GATEWAY_PORT = 8000; - SECURE = false; - GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect( - GATEWAY_IPADDR, GATEWAY_PORT, SECURE, CLIENT_CERT); - service = serviceFactory.getBlockchainService(); - } - - @Test - public void contractExec_Test() { - HashDigest ledgerHash = getLedgerHash(); - // 在本地定义TX模板 - TransactionTemplate txTemp = service.newTransaction(ledgerHash); - - // 合约地址 - String contractAddressBase58 = ""; - - // Event - String event = ""; - - // args(注意参数的格式) - byte[] args = "20##30##abc".getBytes(); - - - // 提交合约执行代码 - txTemp.contractEvents().send(contractAddressBase58, event, args); - - // TX 准备就绪; - PreparedTransaction prepTx = txTemp.prepare(); - - // 生成私钥并使用私钥进行签名; - CryptoKeyPair keyPair = getSponsorKey(); - - prepTx.sign(keyPair); - - // 提交交易; - TransactionResponse transactionResponse = prepTx.commit(); - - assertTrue(transactionResponse.isSuccess()); - } - - private HashDigest getLedgerHash() { - HashDigest ledgerHash = new HashDigest(CryptoAlgorithm.SHA256, "jd-gateway".getBytes()); - return ledgerHash; - } - - - private CryptoKeyPair getSponsorKey() { - SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); - return signatureFunction.generateKeyPair(); - } -} \ No newline at end of file diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_DataAccount_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_DataAccount_Test_.java index 3be8eb27..b203d2e4 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_DataAccount_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_DataAccount_Test_.java @@ -8,23 +8,30 @@ */ package test.com.jd.blockchain.sdk.test; +import org.junit.Before; +import org.junit.Test; + import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoUtils; import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; -import com.jd.blockchain.ledger.*; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeyPair; +import com.jd.blockchain.ledger.EndpointRequest; +import com.jd.blockchain.ledger.NodeRequest; +import com.jd.blockchain.ledger.PreparedTransaction; +import com.jd.blockchain.ledger.TransactionContent; +import com.jd.blockchain.ledger.TransactionContentBody; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.TransactionResponse; +import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.data.TxResponseMessage; import com.jd.blockchain.sdk.BlockchainService; -import com.jd.blockchain.sdk.BlockchainTransactionService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; /** * 插入数据测试 @@ -45,7 +52,7 @@ public class SDK_GateWay_DataAccount_Test_ { private BlockchainService service; - private AsymmetricCryptography asymmetricCryptography = new AsymmtricCryptographyImpl(); + private AsymmetricCryptography asymmetricCryptography = CryptoUtils.asymmCrypto(); @Before public void init() { @@ -56,21 +63,33 @@ public class SDK_GateWay_DataAccount_Test_ { GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IPADDR, GATEWAY_PORT, SECURE, CLIENT_CERT); service = serviceFactory.getBlockchainService(); + + DataContractRegistry.register(TransactionContent.class); + DataContractRegistry.register(TransactionContentBody.class); + DataContractRegistry.register(TransactionRequest.class); + DataContractRegistry.register(NodeRequest.class); + DataContractRegistry.register(EndpointRequest.class); + DataContractRegistry.register(TransactionResponse.class); } @Test public void registerDataAccount_Test() { +// HashDigest ledgerHash = getLedgerHash(); HashDigest[] ledgerHashs = service.getLedgerHashs(); - // 在本地定义TX模板 + // 在本地定义注册账号的 TX; TransactionTemplate txTemp = service.newTransaction(ledgerHashs[0]); +// SignatureFunction signatureFunction = getSignatureFunction(); +// +// CryptoKeyPair cryptoKeyPair = signatureFunction.generateKeyPair(); + //existed signer CryptoKeyPair keyPair = new BlockchainKeyPair(SDK_GateWay_KeyPair_Para.pubKey1, SDK_GateWay_KeyPair_Para.privkey1); - BlockchainKeyPair dataAccount = BlockchainKeyGenerator.getInstance().generate(); + BlockchainKeyPair dataAcount = BlockchainKeyGenerator.getInstance().generate(); // 注册 - txTemp.dataAccounts().register(dataAccount.getIdentity()); + txTemp.dataAccounts().register(dataAcount.getIdentity()); // TX 准备就绪; PreparedTransaction prepTx = txTemp.prepare(); @@ -80,10 +99,40 @@ public class SDK_GateWay_DataAccount_Test_ { // 提交交易; TransactionResponse transactionResponse = prepTx.commit(); - assertTrue(transactionResponse.isSuccess()); +// // 期望返回结果 +// TransactionResponse expectResp = initResponse(); +// +// System.out.println("---------- assert start ----------"); +// assertEquals(expectResp.isSuccess(), transactionResponse.isSuccess()); +// assertEquals(expectResp.getExecutionState(), transactionResponse.getExecutionState()); +// assertEquals(expectResp.getContentHash(), transactionResponse.getContentHash()); +// assertEquals(expectResp.getBlockHeight(), transactionResponse.getBlockHeight()); +// assertEquals(expectResp.getBlockHash(), transactionResponse.getBlockHash()); +// System.out.println("---------- assert OK ----------"); + } + + private HashDigest getLedgerHash() { + HashDigest ledgerHash = new HashDigest(CryptoAlgorithm.SHA256, "jd-gateway".getBytes()); + return ledgerHash; } private SignatureFunction getSignatureFunction() { return asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); } + + private CryptoKeyPair getSponsorKey() { + return getSignatureFunction().generateKeyPair(); + } + + private TransactionResponse initResponse() { + HashDigest contentHash = new HashDigest(CryptoAlgorithm.SHA256, "contentHash".getBytes()); + HashDigest blockHash = new HashDigest(CryptoAlgorithm.SHA256, "blockHash".getBytes()); + long blockHeight = 9998L; + + TxResponseMessage resp = new TxResponseMessage(contentHash); + resp.setBlockHash(blockHash); + resp.setBlockHeight(blockHeight); + resp.setExecutionState(TransactionState.SUCCESS); + return resp; + } } \ No newline at end of file diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_InsertData_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_InsertData_Test_.java index 668b540c..578c8143 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_InsertData_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_InsertData_Test_.java @@ -8,27 +8,32 @@ */ package test.com.jd.blockchain.sdk.test; +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoUtils; import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; import com.jd.blockchain.crypto.asymmetric.SignatureFunction; import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; -import com.jd.blockchain.ledger.*; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeyPair; +import com.jd.blockchain.ledger.EndpointRequest; +import com.jd.blockchain.ledger.NodeRequest; +import com.jd.blockchain.ledger.PreparedTransaction; +import com.jd.blockchain.ledger.TransactionContent; +import com.jd.blockchain.ledger.TransactionContentBody; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.TransactionResponse; +import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.data.TxResponseMessage; -import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.BlockchainTransactionService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; -import com.jd.blockchain.sdk.samples.SDKDemo_InsertData; -import com.jd.blockchain.utils.io.ByteArray; -import com.jd.blockchain.utils.net.NetworkAddress; - -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; /** * 插入数据测试 @@ -47,9 +52,9 @@ public class SDK_GateWay_InsertData_Test_ { private boolean SECURE; - private BlockchainService service; + private BlockchainTransactionService service; - private AsymmetricCryptography asymmetricCryptography = new AsymmtricCryptographyImpl(); + private AsymmetricCryptography asymmetricCryptography = CryptoUtils.asymmCrypto(); @Before public void init() { @@ -60,12 +65,19 @@ public class SDK_GateWay_InsertData_Test_ { GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IPADDR, GATEWAY_PORT, SECURE, CLIENT_CERT); service = serviceFactory.getBlockchainService(); + + DataContractRegistry.register(TransactionContent.class); + DataContractRegistry.register(TransactionContentBody.class); + DataContractRegistry.register(TransactionRequest.class); + DataContractRegistry.register(NodeRequest.class); + DataContractRegistry.register(EndpointRequest.class); + DataContractRegistry.register(TransactionResponse.class); } @Test public void insertData_Test() { - HashDigest ledgerHash = service.getLedgerHashs()[0]; - // 在本地定义TX模板 + HashDigest ledgerHash = getLedgerHash(); + // 在本地定义注册账号的 TX; TransactionTemplate txTemp = service.newTransaction(ledgerHash); // -------------------------------------- @@ -74,7 +86,6 @@ public class SDK_GateWay_InsertData_Test_ { String dataAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; String dataKey = "jd_code"; - byte[] dataVal = "www.jd.com".getBytes(); txTemp.dataAccount(dataAccount).set(dataKey, dataVal, -1); @@ -88,11 +99,39 @@ public class SDK_GateWay_InsertData_Test_ { // 提交交易; TransactionResponse transactionResponse = prepTx.commit(); - assertTrue(transactionResponse.isSuccess()); + + // 期望返回结果 + TransactionResponse expectResp = initResponse(); + + System.out.println("---------- assert start ----------"); + assertEquals(expectResp.isSuccess(), transactionResponse.isSuccess()); + assertEquals(expectResp.getExecutionState(), transactionResponse.getExecutionState()); + assertEquals(expectResp.getContentHash(), transactionResponse.getContentHash()); + assertEquals(expectResp.getBlockHeight(), transactionResponse.getBlockHeight()); + assertEquals(expectResp.getBlockHash(), transactionResponse.getBlockHash()); + System.out.println("---------- assert OK ----------"); } + private HashDigest getLedgerHash() { + HashDigest ledgerHash = new HashDigest(CryptoAlgorithm.SHA256, "jd-gateway".getBytes()); + return ledgerHash; + } + + private CryptoKeyPair getSponsorKey() { SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); return signatureFunction.generateKeyPair(); } + + private TransactionResponse initResponse() { + HashDigest contentHash = new HashDigest(CryptoAlgorithm.SHA256, "contentHash".getBytes()); + HashDigest blockHash = new HashDigest(CryptoAlgorithm.SHA256, "blockHash".getBytes()); + long blockHeight = 9998L; + + TxResponseMessage resp = new TxResponseMessage(contentHash); + resp.setBlockHash(blockHash); + resp.setBlockHeight(blockHeight); + resp.setExecutionState(TransactionState.SUCCESS); + return resp; + } } \ No newline at end of file diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_KeyPair_Para.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_KeyPair_Para.java index 94dddf05..916a3460 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_KeyPair_Para.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_KeyPair_Para.java @@ -1,7 +1,7 @@ package test.com.jd.blockchain.sdk.test; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.tools.keygen.KeyGenCommand; /** diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Query_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Query_Test_.java index f68ed9ae..d3782d98 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Query_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Query_Test_.java @@ -8,19 +8,41 @@ */ package test.com.jd.blockchain.sdk.test; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.sdk.client.ClientOperationUtil; -import org.apache.commons.codec.binary.Hex; import org.junit.Before; import org.junit.Test; +import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoUtils; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; +import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; +import com.jd.blockchain.crypto.asymmetric.SignatureDigest; +import com.jd.blockchain.crypto.asymmetric.SignatureFunction; import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.crypto.impl.AsymmtricCryptographyImpl; +import com.jd.blockchain.crypto.serialize.ByteArrayObjectDeserializer; +import com.jd.blockchain.crypto.serialize.ByteArrayObjectSerializer; +import com.jd.blockchain.ledger.AccountHeader; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeyPair; +import com.jd.blockchain.ledger.DigitalSignature; +import com.jd.blockchain.ledger.EndpointRequest; +import com.jd.blockchain.ledger.KVDataEntry; +import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInfo; +import com.jd.blockchain.ledger.LedgerTransaction; +import com.jd.blockchain.ledger.NodeRequest; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.Transaction; +import com.jd.blockchain.ledger.TransactionContent; +import com.jd.blockchain.ledger.TransactionContentBody; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.TransactionResponse; +import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.data.TxResponseMessage; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; - +import com.jd.blockchain.utils.serialize.json.JSONSerializeUtils; /** * 插入数据测试 @@ -31,6 +53,16 @@ import com.jd.blockchain.sdk.client.GatewayServiceFactory; public class SDK_GateWay_Query_Test_ { + private static Class[] byteArrayClasss = new Class[]{HashDigest.class, PubKey.class, SignatureDigest.class}; + + static { + for (Class byteArrayClass : byteArrayClasss) { + JSONSerializeUtils.configSerialization(byteArrayClass, + ByteArrayObjectSerializer.getInstance(byteArrayClass), + ByteArrayObjectDeserializer.getInstance(byteArrayClass)); + } + } + private BlockchainKeyPair CLIENT_CERT = null; private String GATEWAY_IPADDR = null; @@ -41,169 +73,82 @@ public class SDK_GateWay_Query_Test_ { private BlockchainService service; + private AsymmetricCryptography asymmetricCryptography = CryptoUtils.asymmCrypto(); + @Before public void init() { CLIENT_CERT = BlockchainKeyGenerator.getInstance().generate(CryptoAlgorithm.ED25519); GATEWAY_IPADDR = "127.0.0.1"; - GATEWAY_PORT = 8081; + GATEWAY_PORT = 11000; SECURE = false; GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IPADDR, GATEWAY_PORT, SECURE, CLIENT_CERT); service = serviceFactory.getBlockchainService(); + + DataContractRegistry.register(TransactionContent.class); + DataContractRegistry.register(TransactionContentBody.class); + DataContractRegistry.register(TransactionRequest.class); + DataContractRegistry.register(NodeRequest.class); + DataContractRegistry.register(EndpointRequest.class); + DataContractRegistry.register(TransactionResponse.class); } @Test public void query_Test() { - // Get First Ledger - HashDigest ledgerHash = service.getLedgerHashs()[0]; - System.out.println("ledgerHash=" + ledgerHash.toBase58()); + HashDigest ledgerHash = service.getLedgerHashs()[0];; +// ParserConfig.global.setAutoTypeSupport(true); - // Show Ledger Info LedgerInfo ledgerInfo = service.getLedger(ledgerHash); - - // Get highest block height - long latestBlockHeight = ledgerInfo.getLatestBlockHeight(); - - // Get highest block hash - HashDigest latestBlockHash = ledgerInfo.getLatestBlockHash(); - - System.out.println("latestBlockHeight=" + latestBlockHeight); - - System.out.println("latestBlockHash=" + latestBlockHash.toBase58()); - - System.out.println("LedgerHash=" + ledgerInfo.getHash().toBase58()); - - // Get newest block - LedgerBlock latestBlock = service.getBlock(ledgerHash, latestBlockHeight); - + long ledgerNumber = ledgerInfo.getLatestBlockHeight(); + System.out.println(ledgerNumber); + HashDigest hashDigest = ledgerInfo.getHash(); + System.out.println(hashDigest); +// 最新区块; + LedgerBlock latestBlock = service.getBlock(ledgerHash, ledgerNumber); System.out.println("latestBlock.Hash=" + latestBlock.getHash()); - - // Get total contract size - long count = service.getContractCount(ledgerHash, latestBlockHeight); - + long count = service.getContractCount(ledgerHash, 3L); System.out.println("contractCount=" + count); - - count = service.getContractCount(ledgerHash, latestBlockHash); - + count = service.getContractCount(ledgerHash, hashDigest); System.out.println("contractCount=" + count); + AccountHeader contract = service.getContract(ledgerHash, "12345678"); + System.out.println(contract); - if (count != 0) { - AccountHeader[] accountHeaders = service.getContractAccounts(ledgerHash, 0, (int)count); - for (AccountHeader accountHeader : accountHeaders) { - String contractAddress = accountHeader.getAddress().toBase58(); - System.out.println("Contract address = " + contractAddress); - // Get one contract by contract address - AccountHeader contract = service.getContract(ledgerHash, contractAddress); - } - } - - // Get other block info - LedgerBlock block = service.getBlock(ledgerHash, latestBlockHeight - 1); + LedgerBlock block = service.getBlock(ledgerHash, hashDigest); System.out.println("block.Hash=" + block.getHash()); - // Get Total DataAccount Size - count = service.getDataAccountCount(ledgerHash, latestBlockHeight); - + count = service.getDataAccountCount(ledgerHash, 123456); System.out.println("dataAccountCount=" + count); - - count = service.getDataAccountCount(ledgerHash, latestBlockHash); - + count = service.getDataAccountCount(ledgerHash, hashDigest); System.out.println("dataAccountCount=" + count); - String queryDataAccountAddress = null; - - if (count > 0) { - AccountHeader[] accountHeaders = service.getDataAccounts(ledgerHash, 0, (int)count); - for (AccountHeader accountHeader : accountHeaders) { - String dataAccountAddress = accountHeader.getAddress().toBase58(); - System.out.println("DataAccount address = " + dataAccountAddress); - // Get one Data Account by address - AccountHeader dataAccount = service.getDataAccount(ledgerHash, dataAccountAddress); - queryDataAccountAddress = dataAccountAddress; - } - } + AccountHeader dataAccount = service.getDataAccount(ledgerHash, "1245633"); + System.out.println(dataAccount.getAddress()); - // Get total transaction size - count = service.getTransactionCount(ledgerHash, latestBlockHash); + count = service.getTransactionCount(ledgerHash, hashDigest); System.out.println("transactionCount=" + count); - - count = service.getTransactionCount(ledgerHash, latestBlockHeight); + count = service.getTransactionCount(ledgerHash, 12456); System.out.println("transactionCount=" + count); - // Get transaction list - LedgerTransaction[] txList = service.getTransactions(ledgerHash, 0, 0, 100); + LedgerTransaction[] txList = service.getTransactions(ledgerHash, ledgerNumber, 0, 100); for (LedgerTransaction ledgerTransaction : txList) { - System.out.println("transaction.executionState=" + ledgerTransaction.getExecutionState()); -// System.out.println("transaction.hash=" + ledgerTransaction.getHash().toBase58()); - TransactionContent txContent = ledgerTransaction.getTransactionContent(); - System.out.println("transactionContent.hash=" + txContent.getHash().toBase58()); - Operation[] operations = txContent.getOperations(); - if (operations != null && operations.length > 0) { - for (Operation operation : operations) { - operation = ClientOperationUtil.read(operation); - if (operation instanceof DataAccountRegisterOperation) { - DataAccountRegisterOperation daro = (DataAccountRegisterOperation) operation; - BlockchainIdentity blockchainIdentity = daro.getAccountID(); - System.out.println("register account = " + blockchainIdentity.getAddress().toBase58()); - } else if (operation instanceof UserRegisterOperation) { - UserRegisterOperation uro = (UserRegisterOperation) operation; - BlockchainIdentity blockchainIdentity = uro.getUserID(); - System.out.println("register user = " + blockchainIdentity.getAddress().toBase58()); - } else if (operation instanceof LedgerInitOperation) { - - LedgerInitOperation ledgerInitOperation = (LedgerInitOperation)operation; - LedgerInitSetting ledgerInitSetting = ledgerInitOperation.getInitSetting(); - - System.out.println(Hex.encodeHexString(ledgerInitSetting.getLedgerSeed())); - System.out.println(ledgerInitSetting.getConsensusProvider()); - System.out.println(ledgerInitSetting.getConsensusSettings().toBase58()); - - ParticipantNode[] participantNodes = ledgerInitSetting.getConsensusParticipants(); - if (participantNodes != null && participantNodes.length > 0) { - for (ParticipantNode participantNode : participantNodes) { - System.out.println("participantNode.id=" + participantNode.getId()); - System.out.println("participantNode.name=" + participantNode.getName()); - System.out.println("participantNode.address=" + participantNode.getAddress()); - System.out.println("participantNode.pubKey=" + participantNode.getPubKey().toBase58()); - } - } - - } else if (operation instanceof ContractCodeDeployOperation) { - ContractCodeDeployOperation ccdo = (ContractCodeDeployOperation) operation; - BlockchainIdentity blockchainIdentity = ccdo.getContractID(); - System.out.println("deploy contract = " + blockchainIdentity.getAddress()); - } else if (operation instanceof ContractEventSendOperation) { - ContractEventSendOperation ceso = (ContractEventSendOperation) operation; - System.out.println("event = " + ceso.getEvent()); - System.out.println("execute contract address = " + ceso.getContractAddress().toBase58()); - } else if (operation instanceof DataAccountKVSetOperation) { - DataAccountKVSetOperation.KVWriteEntry[] kvWriteEntries = - ((DataAccountKVSetOperation) operation).getWriteSet(); - if (kvWriteEntries != null && kvWriteEntries.length > 0) { - for (DataAccountKVSetOperation.KVWriteEntry kvWriteEntry : kvWriteEntries) { - System.out.println("writeSet.key=" + kvWriteEntry.getKey()); - BytesValue bytesValue = kvWriteEntry.getValue(); - DataType dataType = bytesValue.getType(); - Object showVal = ClientOperationUtil.readValueByBytesValue(bytesValue); - System.out.println("writeSet.value=" + showVal); - System.out.println("writeSet.type=" + dataType); - System.out.println("writeSet.version=" + kvWriteEntry.getExpectedVersion()); - } - } - } - } - } + System.out.println("ledgerTransaction.Hash=" + ledgerTransaction.getHash()); } - // Get txs by block height - txList = service.getTransactions(ledgerHash, latestBlockHash, 0, 100); + txList = service.getTransactions(ledgerHash, hashDigest, 0, 100); for (LedgerTransaction ledgerTransaction : txList) { System.out.println("ledgerTransaction.Hash=" + ledgerTransaction.getHash()); } + Transaction tx = service.getTransactionByContentHash(ledgerHash, hashDigest); + DigitalSignature[] signatures = tx.getEndpointSignatures(); + for (DigitalSignature signature : signatures) { + System.out.println(signature.getDigest().getAlgorithm()); + } + System.out.println("transaction.blockHeight=" + tx.getBlockHeight()); + System.out.println("transaction.executionState=" + tx.getExecutionState()); + - // Get total ParticipantNode array ParticipantNode[] participants = service.getConsensusParticipants(ledgerHash); for (ParticipantNode participant : participants) { System.out.println("participant.name=" + participant.getName()); @@ -214,27 +159,47 @@ public class SDK_GateWay_Query_Test_ { System.out.println("participant.getRawKeyBytes=" + participant.getPubKey().getRawKeyBytes()); System.out.println("participant.algorithm=" + participant.getPubKey().getAlgorithm()); } + + String commerceAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; + String[] objKeys = new String[] { "x001", "x002" }; + KVDataEntry[] kvData = service.getDataEntries(ledgerHash, commerceAccount, objKeys); + for (KVDataEntry kvDatum : kvData) { + System.out.println("kvData.key=" + kvDatum.getKey()); + System.out.println("kvData.version=" + kvDatum.getVersion()); + System.out.println("kvData.value=" + kvDatum.getValue()); + } - // Get total kvs - KVDataEntry[] kvData = service.getDataEntries(ledgerHash, queryDataAccountAddress, 0, 100); - if (kvData != null && kvData.length > 0) { - for (KVDataEntry kvDatum : kvData) { - System.out.println("kvData.key=" + kvDatum.getKey()); - System.out.println("kvData.version=" + kvDatum.getVersion()); - System.out.println("kvData.type=" + kvDatum.getType()); - System.out.println("kvData.value=" + kvDatum.getValue()); - - // Get one kvData by key - KVDataEntry[] kvDataEntries = service.getDataEntries(ledgerHash, - queryDataAccountAddress, kvDatum.getKey()); - - for (KVDataEntry kv : kvDataEntries) { - System.out.println("kv.key=" + kv.getKey()); - System.out.println("kv.version=" + kv.getVersion()); - System.out.println("kv.type=" + kv.getType()); - System.out.println("kv.value=" + kv.getValue()); - } - } + HashDigest[] hashs = service.getLedgerHashs(); + for (HashDigest hash : hashs) { + System.out.println("hash.toBase58=" + hash.toBase58()); } } + + private HashDigest getLedgerHash() { + HashDigest ledgerHash = new HashDigest(CryptoAlgorithm.SHA256, "jd-gateway".getBytes()); + return ledgerHash; + } + + private SignatureFunction getSignatureFunction() { + return asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); + } + + private BlockchainKeyPair getSponsorKey() { + SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); + CryptoKeyPair cryptoKeyPair = signatureFunction.generateKeyPair(); + BlockchainKeyPair blockchainKeyPair = new BlockchainKeyPair(cryptoKeyPair.getPubKey(), cryptoKeyPair.getPrivKey()); + return blockchainKeyPair; + } + + private TransactionResponse initResponse() { + HashDigest contentHash = new HashDigest(CryptoAlgorithm.SHA256, "contentHash".getBytes()); + HashDigest blockHash = new HashDigest(CryptoAlgorithm.SHA256, "blockHash".getBytes()); + long blockHeight = 9998L; + + TxResponseMessage resp = new TxResponseMessage(contentHash); + resp.setBlockHash(blockHash); + resp.setBlockHeight(blockHeight); + resp.setExecutionState(TransactionState.SUCCESS); + return resp; + } } \ No newline at end of file diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_User_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_User_Test_.java index 2a571f85..74892d75 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_User_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_User_Test_.java @@ -8,16 +8,34 @@ */ package test.com.jd.blockchain.sdk.test; -import com.jd.blockchain.crypto.asymmetric.*; -import com.jd.blockchain.crypto.hash.HashDigest; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.sdk.BlockchainService; -import com.jd.blockchain.sdk.client.GatewayServiceFactory; +import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.assertTrue; +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoUtils; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.crypto.asymmetric.AsymmetricCryptography; +import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; +import com.jd.blockchain.crypto.asymmetric.SignatureFunction; +import com.jd.blockchain.crypto.hash.HashDigest; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeyPair; +import com.jd.blockchain.ledger.EndpointRequest; +import com.jd.blockchain.ledger.NodeRequest; +import com.jd.blockchain.ledger.PreparedTransaction; +import com.jd.blockchain.ledger.TransactionContent; +import com.jd.blockchain.ledger.TransactionContentBody; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.TransactionResponse; +import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.TransactionTemplate; +import com.jd.blockchain.ledger.data.TxResponseMessage; +import com.jd.blockchain.sdk.BlockchainService; +import com.jd.blockchain.sdk.client.GatewayServiceFactory; /** * 插入数据测试 @@ -28,8 +46,13 @@ import static org.junit.Assert.assertTrue; public class SDK_GateWay_User_Test_ { - private PrivKey privKey; +// public static final String PASSWORD = SDK_GateWay_KeyPair_Para.PASSWORD; +// +// public static final String[] PUB_KEYS = SDK_GateWay_KeyPair_Para.PUB_KEYS; +// +// public static final String[] PRIV_KEYS = SDK_GateWay_KeyPair_Para.PRIV_KEYS; + private PrivKey privKey; private PubKey pubKey; private BlockchainKeyPair CLIENT_CERT = null; @@ -42,9 +65,21 @@ public class SDK_GateWay_User_Test_ { private BlockchainService service; + private AsymmetricCryptography asymmetricCryptography = CryptoUtils.asymmCrypto(); + @Before public void init() { +// PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); +// PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); +// PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); +// PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); +// +// PubKey pubKey0 = KeyGenCommand.decodePubKey(PUB_KEYS[0]); +// PubKey pubKey1 = KeyGenCommand.decodePubKey(PUB_KEYS[1]); +// PubKey pubKey2 = KeyGenCommand.decodePubKey(PUB_KEYS[2]); +// PubKey pubKey3 = KeyGenCommand.decodePubKey(PUB_KEYS[3]); + privKey = SDK_GateWay_KeyPair_Para.privkey1; pubKey = SDK_GateWay_KeyPair_Para.pubKey1; @@ -52,15 +87,22 @@ public class SDK_GateWay_User_Test_ { GATEWAY_IPADDR = "127.0.0.1"; GATEWAY_PORT = 8081; SECURE = false; - GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect( - GATEWAY_IPADDR, GATEWAY_PORT, SECURE, CLIENT_CERT); + GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IPADDR, GATEWAY_PORT, SECURE, + CLIENT_CERT); service = serviceFactory.getBlockchainService(); + + DataContractRegistry.register(TransactionContent.class); + DataContractRegistry.register(TransactionContentBody.class); + DataContractRegistry.register(TransactionRequest.class); + DataContractRegistry.register(NodeRequest.class); + DataContractRegistry.register(EndpointRequest.class); + DataContractRegistry.register(TransactionResponse.class); } @Test public void registerUser_Test() { HashDigest[] ledgerHashs = service.getLedgerHashs(); - // 在本地定义TX模板 + // 在本地定义注册账号的 TX; TransactionTemplate txTemp = service.newTransaction(ledgerHashs[0]); //existed signer @@ -80,5 +122,38 @@ public class SDK_GateWay_User_Test_ { // 提交交易; TransactionResponse transactionResponse = prepTx.commit(); assertTrue(transactionResponse.isSuccess()); + + // 期望返回结果 +// TransactionResponse expectResp = initResponse(); +// +// System.out.println("---------- assert start ----------"); +// assertEquals(expectResp.isSuccess(), transactionResponse.isSuccess()); +// assertEquals(expectResp.getExecutionState(), transactionResponse.getExecutionState()); +// assertEquals(expectResp.getContentHash(), transactionResponse.getContentHash()); +// assertEquals(expectResp.getBlockHeight(), transactionResponse.getBlockHeight()); +// assertEquals(expectResp.getBlockHash(), transactionResponse.getBlockHash()); +// System.out.println("---------- assert OK ----------"); + } + +// private HashDigest getLedgerHash() { +// byte[] hashBytes = Base58Utils.decode(ledgerHashBase58); +// return new HashDigest(hashBytes); +// } + + private CryptoKeyPair getSponsorKey() { + SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); + return signatureFunction.generateKeyPair(); + } + + private TransactionResponse initResponse() { + HashDigest contentHash = new HashDigest(CryptoAlgorithm.SHA256, "contentHash".getBytes()); + HashDigest blockHash = new HashDigest(CryptoAlgorithm.SHA256, "blockHash".getBytes()); + long blockHeight = 9998L; + + TxResponseMessage resp = new TxResponseMessage(contentHash); + resp.setBlockHash(blockHash); + resp.setBlockHeight(blockHeight); + resp.setExecutionState(TransactionState.SUCCESS); + return resp; } } \ No newline at end of file diff --git a/source/test/test-integration/pom.xml b/source/test/test-integration/pom.xml index 26906981..dc29dca3 100644 --- a/source/test/test-integration/pom.xml +++ b/source/test/test-integration/pom.xml @@ -48,8 +48,15 @@ io.nats jnats - 2.2.0 + + + com.jd.blockchain + crypto-impl + ${project.version} + test + + diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java index 49bbd6b5..63e9bad2 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java @@ -16,9 +16,9 @@ import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; import com.jd.blockchain.ledger.AccountHeader; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java index f625fb08..3478478a 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java @@ -14,8 +14,8 @@ import org.springframework.core.io.ClassPathResource; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; +import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java index a58acae7..fa7ca816 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java @@ -16,8 +16,8 @@ import org.springframework.core.io.ClassPathResource; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; +import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java index 8c8018b0..ffff2beb 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java @@ -14,9 +14,9 @@ import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.LedgerBlock; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java index 647f9024..d8edf300 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java @@ -17,8 +17,8 @@ import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoUtils; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.LedgerBlock; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java index 81bfdf43..4d9eb364 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java @@ -5,8 +5,8 @@ import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.LedgerDataSet; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java index c54680df..0913c094 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java @@ -11,8 +11,8 @@ import org.springframework.core.io.ClassPathResource; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.CryptoSetting; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java index 6b8476f3..c50462c1 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java @@ -158,28 +158,7 @@ public class IntegrationBase { return kvResponse; } - public static void testSDK_InsertData_morePage(CryptoKeyPair adminKey, HashDigest ledgerHash, BlockchainService blockchainService, - Bytes dataAccount) { - // 在本地定义注册账号的 TX; - TransactionTemplate txTemp = blockchainService.newTransaction(ledgerHash); - - // -------------------------------------- - // 将商品信息写入到指定的账户中; - // 对象将被序列化为 JSON 形式存储,并基于 JSON 结构建立查询索引; - for(int i=0;i<12;i++){ - String dataKey = "jingdong" + System.currentTimeMillis() + new Random().nextInt(100000); - byte[] dataVal = "www.jd.com".getBytes(); - txTemp.dataAccount(dataAccount).set(dataKey, dataVal, -1); - } - - // TX 准备就绪; - PreparedTransaction prepTx = txTemp.prepare(); - // 使用私钥进行签名; - prepTx.sign(adminKey); - // 提交交易; - prepTx.commit(); - } public static void validKeyPair(IntegrationBase.KeyPairResponse keyPairResponse, LedgerRepository ledgerRepository, KeyPairType keyPairType) { TransactionResponse txResp = keyPairResponse.txResp; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBaseTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBaseTest.java index 3161c06e..79fd67df 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBaseTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBaseTest.java @@ -3,8 +3,8 @@ package test.com.jd.blockchain.intgr; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; +import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; import com.jd.blockchain.ledger.LedgerBlock; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest2.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest2.java index c097cfa6..22bc3324 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest2.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest2.java @@ -18,8 +18,8 @@ import org.springframework.core.io.ClassPathResource; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; +import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; import com.jd.blockchain.ledger.BlockchainKeyGenerator; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java index 79b9e189..d2cba7a5 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java @@ -1,8 +1,8 @@ package test.com.jd.blockchain.intgr; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.gateway.GatewayConfigProperties; import com.jd.blockchain.ledger.BlockchainKeyPair; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java index aae9912d..33f38eea 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java @@ -1,8 +1,8 @@ package test.com.jd.blockchain.intgr; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; import com.jd.blockchain.ledger.*; @@ -130,8 +130,6 @@ public class IntegrationTest4MQ { BlockchainKeyPair da = dataAccountResponse.keyPair; IntegrationBase.KvResponse kvResponse = IntegrationBase.testSDK_InsertData(adminKey, ledgerHash, blockchainService, da.getAddress()); validKvWrite(kvResponse, ledgerRepository, blockchainService); - //more page - testSDK_InsertData_morePage(adminKey, ledgerHash, blockchainService, da.getAddress()); } } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java index 35d9ea09..bb0493ad 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java @@ -16,9 +16,9 @@ import org.springframework.core.io.ClassPathResource; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; import com.jd.blockchain.ledger.BlockchainKeyGenerator; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java index 24d58608..5e1a284e 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java @@ -16,9 +16,9 @@ import com.alibaba.fastjson.JSON; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; import com.jd.blockchain.ledger.BlockchainKeyGenerator; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartLedgerInit.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartLedgerInit.java index bc1506c4..f2f5d3a8 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartLedgerInit.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartLedgerInit.java @@ -8,9 +8,9 @@ */ package test.com.jd.blockchain.intgr.batch.bftsmart; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.gateway.GatewayConfigProperties; import com.jd.blockchain.ledger.BlockchainKeyPair; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitSettingTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitSettingTest.java index b2cc5198..243be4bc 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitSettingTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitSettingTest.java @@ -8,7 +8,7 @@ import java.io.InputStream; import org.junit.Test; import org.springframework.core.io.ClassPathResource; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.LedgerInitProperties.ConsensusParticipantConfig; import com.jd.blockchain.tools.keygen.KeyGenCommand; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java index 9bbb5c9e..a34d4311 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java @@ -19,9 +19,9 @@ import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.LedgerBlock; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java index 7c6ca543..a61a3342 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java @@ -3,8 +3,8 @@ package test.com.jd.blockchain.intgr.initializer; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AddressEncoding; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.*; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java index 64e419b2..89712905 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java @@ -23,8 +23,8 @@ import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoUtils; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.LedgerBlock; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java index bd20870a..7251b97a 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java @@ -16,8 +16,8 @@ import org.springframework.core.io.ClassPathResource; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; +import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeyPair; diff --git a/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java b/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java index 33820bc1..22c316d4 100644 --- a/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java +++ b/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java @@ -13,9 +13,9 @@ import com.jd.blockchain.capability.settings.CapabilitySettings; import com.jd.blockchain.consensus.action.ActionResponse; import com.jd.blockchain.consensus.bftsmart.BftsmartConsensusSettings; import com.jd.blockchain.consensus.bftsmart.BftsmartNodeSettings; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.ContractCodeDeployOperation; import com.jd.blockchain.ledger.ContractEventSendOperation; diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java index 4d307b86..d7ddf1b1 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java @@ -15,8 +15,8 @@ import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AddressEncoding; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.tools.initializer.LedgerBindingConfig.BindingConfig; diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProcess.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProcess.java index c610bfad..5f2fcf2a 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProcess.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProcess.java @@ -3,7 +3,7 @@ package com.jd.blockchain.tools.initializer; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.consensus.service.ConsensusServiceProvider; -import com.jd.blockchain.crypto.asymmetric.PrivKey; +import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.CryptoSetting; diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java index dd6270f5..805315f9 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java @@ -9,7 +9,7 @@ import java.util.List; import java.util.Properties; import com.jd.blockchain.crypto.AddressEncoding; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.codec.HexUtils; diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java index 28c21cb8..6c7ddcaa 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java @@ -21,8 +21,8 @@ import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoUtils; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.SignatureDigest; import com.jd.blockchain.crypto.hash.HashDigest; import com.jd.blockchain.ledger.BlockchainIdentity; diff --git a/source/tools/tools-keygen/src/main/java/com/jd/blockchain/tools/keygen/KeyGenCommand.java b/source/tools/tools-keygen/src/main/java/com/jd/blockchain/tools/keygen/KeyGenCommand.java index 4b9f135d..81f2db5c 100644 --- a/source/tools/tools-keygen/src/main/java/com/jd/blockchain/tools/keygen/KeyGenCommand.java +++ b/source/tools/tools-keygen/src/main/java/com/jd/blockchain/tools/keygen/KeyGenCommand.java @@ -10,9 +10,9 @@ import javax.crypto.SecretKey; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoUtils; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.asymmetric.CryptoKeyPair; -import com.jd.blockchain.crypto.asymmetric.PrivKey; -import com.jd.blockchain.crypto.asymmetric.PubKey; import com.jd.blockchain.utils.ArgumentSet; import com.jd.blockchain.utils.ConsoleUtils; import com.jd.blockchain.utils.ArgumentSet.ArgEntry; diff --git a/source/tools/tools-package/pom.xml b/source/tools/tools-package/pom.xml new file mode 100644 index 00000000..76dc72a4 --- /dev/null +++ b/source/tools/tools-package/pom.xml @@ -0,0 +1,45 @@ + + + + + tools + com.jd.blockchain + 0.9.0-SNAPSHOT + + 4.0.0 + + tools-package + + tools-package + + + UTF-8 + 1.8 + 1.8 + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + make-assembly + package + + single + + + jdchain + + src/main/resources/assemble/assemble.xml + + + + + + + + diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/io/BytesUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/io/BytesUtils.java index bc740a3c..fc08418e 100644 --- a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/io/BytesUtils.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/io/BytesUtils.java @@ -166,6 +166,8 @@ public class BytesUtils { /** * 将 int 值转为4字节的二进制数组; + *

+ * 以“高位在前”的方式转换,即:数值的高位保存在数组地址的低位; * * @param value * 要转换的int整数; @@ -183,23 +185,69 @@ public class BytesUtils { return 4; } -// public static int toBytes(int value, OutputStream out) { -// try { -// out.write((value >>> 24) & 0x00FF); -// out.write((value >>> 16) & 0x00FF); -// out.write((value >>> 8) & 0x00FF); -// out.write(value & 0x00FF); -// return 4; -// } catch (IOException e) { -// throw new RuntimeIOException(e.getMessage(), e); -// } -// } + /** + * 将 int 值转为4字节的二进制数组; + *

+ * 以“高位在后”的方式转换,即:数值的高位保存在数组地址的高位; + * + * @param value + * 要转换的int整数; + * @param bytes + * 要保存转换结果的二进制数组;转换结果将从高位至低位的顺序写入数组从 offset 指定位置开始的4个元素; + * @param offset + * 写入转换结果的起始位置; + * @return 返回写入的长度; + */ + public static int toBytesInReverse(int value, byte[] bytes, int offset) { + bytes[offset] = (byte) (value & 0x00FF); + bytes[offset + 1] = (byte) ((value >>> 8) & 0x00FF); + bytes[offset + 2] = (byte) ((value >>> 16) & 0x00FF); + bytes[offset + 3] = (byte) ((value >>> 24) & 0x00FF); + return 4; + } + + + /** + * 将 int 值转为4字节的二进制数组; + *

+ * 以“高位在后”的方式转换,即:数值的高位保存在数组地址的高位; + * + * @param value + * 要转换的int整数; + * @param bytes + * 要保存转换结果的二进制数组;转换结果将从高位至低位的顺序写入数组从 offset 指定位置开始的4个元素; + * @param offset + * 写入转换结果的起始位置; + * @param len 写入长度;必须大于 0 ,小于等于 4; + * @return 返回写入的长度; + */ + public static int toBytesInReverse(int value, byte[] bytes, int offset, int len) { + int i = 0; + int l = len > 4 ? 4 : len; + for (; i < l; i++) { + bytes[offset + i] = (byte) ((value >>> (8*i)) & 0x00FF); + } + + return i; + } + + + // public static int toBytes(int value, OutputStream out) { + // try { + // out.write((value >>> 24) & 0x00FF); + // out.write((value >>> 16) & 0x00FF); + // out.write((value >>> 8) & 0x00FF); + // out.write(value & 0x00FF); + // return 4; + // } catch (IOException e) { + // throw new RuntimeIOException(e.getMessage(), e); + // } + // } public static void toBytes(short value, byte[] bytes, int offset) { bytes[offset] = (byte) ((value >>> 8) & 0x00FF); bytes[offset + 1] = (byte) (value & 0x00FF); } - public static void toBytes(char value, byte[] bytes, int offset) { bytes[offset] = (byte) ((value >>> 8) & 0x00FF); @@ -229,21 +277,21 @@ public class BytesUtils { return 8; } -// public static int toBytes(long value, OutputStream out) { -// try { -// out.write((int) ((value >>> 56) & 0x00FF)); -// out.write((int) ((value >>> 48) & 0x00FF)); -// out.write((int) ((value >>> 40) & 0x00FF)); -// out.write((int) ((value >>> 32) & 0x00FF)); -// out.write((int) ((value >>> 24) & 0x00FF)); -// out.write((int) ((value >>> 16) & 0x00FF)); -// out.write((int) ((value >>> 8) & 0x00FF)); -// out.write((int) (value & 0x00FF)); -// return 8; -// } catch (IOException e) { -// throw new RuntimeIOException(e.getMessage(), e); -// } -// } + // public static int toBytes(long value, OutputStream out) { + // try { + // out.write((int) ((value >>> 56) & 0x00FF)); + // out.write((int) ((value >>> 48) & 0x00FF)); + // out.write((int) ((value >>> 40) & 0x00FF)); + // out.write((int) ((value >>> 32) & 0x00FF)); + // out.write((int) ((value >>> 24) & 0x00FF)); + // out.write((int) ((value >>> 16) & 0x00FF)); + // out.write((int) ((value >>> 8) & 0x00FF)); + // out.write((int) (value & 0x00FF)); + // return 8; + // } catch (IOException e) { + // throw new RuntimeIOException(e.getMessage(), e); + // } + // } public static byte[] toBytes(String str) { return toBytes(str, DEFAULT_CHARSET); @@ -261,11 +309,11 @@ public class BytesUtils { public static String toString(byte[] bytes) { return toString(bytes, DEFAULT_CHARSET); } - + public static String toString(byte[] bytes, int offset) { return toString(bytes, offset, bytes.length - offset, DEFAULT_CHARSET); } - + public static String toString(byte[] bytes, int offset, int len) { return toString(bytes, offset, len, DEFAULT_CHARSET); } @@ -273,7 +321,7 @@ public class BytesUtils { public static String toString(byte[] bytes, String charset) { return toString(bytes, 0, bytes.length, charset); } - + public static String toString(byte[] bytes, int offset, int len, String charset) { try { if (bytes == null) { @@ -321,17 +369,14 @@ public class BytesUtils { return value; } - - + public static char toChar(byte[] bytes, int offset) { char value = 0; value = (char) ((value | (bytes[offset] & 0xFF)) << 8); value = (char) (value | (bytes[offset + 1] & 0xFF)); - + return value; } - - /** * 按从高位到低位的顺序将指定二进制数组从 offset 参数指定的位置开始的 4 个字节转换为 int 整数; @@ -447,6 +492,17 @@ public class BytesUtils { * @return int */ public static int readInt(InputStream in) { +// try { +// byte[] buf = new byte[4]; +// if (in.read(buf) < 4) { +// throw new IllegalDataException("No enough data to read as integer from the specified input stream!"); + // specified input stream!"); +// } +// return toInt(buf); +// } catch (IOException e) { +// throw new RuntimeIOException(e.getMessage(), e); +// } + try { int value = 0; for (int i = 0; i < 4; i++) { @@ -465,7 +521,7 @@ public class BytesUtils { // } catch (IOException e) { // throw new RuntimeIOException(e.getMessage(), e); // } - + try { out.write((value >>> 24) & 0x00FF); out.write((value >>> 16) & 0x00FF); @@ -515,7 +571,7 @@ public class BytesUtils { // } catch (IOException e) { // throw new RuntimeIOException(e.getMessage(), e); // } - + try { out.write((int) ((value >>> 56) & 0x00FF)); out.write((int) ((value >>> 48) & 0x00FF)); diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/io/NumberMask.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/io/NumberMask.java index a77c5e7d..e13154d0 100644 --- a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/io/NumberMask.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/io/NumberMask.java @@ -139,13 +139,6 @@ public enum NumberMask { * @return */ public int getBoundarySize(int headerLength) { -// if (headerLength < 1) { -// throw new IllegalArgumentException("Header length is less than one!"); -// } -// if (headerLength > MAX_HEADER_LENGTH) { -// throw new IllegalArgumentException( -// "Header length is great than MAX_HEADER_LENGTH[" + MAX_HEADER_LENGTH + "]!"); -// } return boundarySizes[headerLength - 1]; } diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/security/AESUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/security/AESUtils.java index ec1e2563..35408b67 100644 --- a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/security/AESUtils.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/security/AESUtils.java @@ -21,9 +21,9 @@ import com.jd.blockchain.utils.codec.HexUtils; public class AESUtils { /** - * 用指定的种子生成 128 位的秘钥;
+ * 用指定的种子生成 128 位的密钥;
* - * 如果指定的种子为空(null 或长度为 0 ),则生成随机的秘钥; + * 如果指定的种子为空(null 或长度为 0 ),则生成随机的密钥; * * @param seed * 种子; @@ -35,7 +35,7 @@ public class AESUtils { } /** - * 用指定的种子生成 128 位的秘钥; + * 用指定的种子生成 128 位的密钥; * * @param seed * 种子; @@ -47,7 +47,7 @@ public class AESUtils { } /** - * 用指定的种子生成 128 位的秘钥; + * 用指定的种子生成 128 位的密钥; * * @param seed * 种子; 不允许为空; @@ -57,7 +57,7 @@ public class AESUtils { if (seed == null || seed.length == 0) { throw new IllegalArgumentException("Empty seed!"); } - // 注:AES 算法只支持 128 位,不支持 192, 256 位的秘钥加密; + // 注:AES 算法只支持 128 位,不支持 192, 256 位的密钥加密; byte[] hashBytes = ShaUtils.hash_128(seed); return new SecretKeySpec(hashBytes, "AES"); @@ -67,7 +67,7 @@ public class AESUtils { } /** - * 生成 128 位的随机秘钥; + * 生成 128 位的随机密钥; * * @return */ @@ -77,7 +77,7 @@ public class AESUtils { } /** - * 生成以 16 进制编码的 128 位的随机秘钥; + * 生成以 16 进制编码的 128 位的随机密钥; * * @return */ @@ -92,11 +92,11 @@ public class AESUtils { } /** - * 用指定的 16 进制的AES秘钥进行加密; + * 用指定的 16 进制的AES密钥进行加密; * * @param content * @param key - * 16进制编码的 AES 秘钥; + * 16进制编码的 AES 密钥; * @return */ public static byte[] encrypt(byte[] content, String key) { diff --git a/source/utils/utils-http/src/test/java/test/my/utils/http/agent/HttpServiceAgentTest.java b/source/utils/utils-http/src/test/java/test/my/utils/http/agent/HttpServiceAgentTest.java index 2a1cf757..3bed4a46 100644 --- a/source/utils/utils-http/src/test/java/test/my/utils/http/agent/HttpServiceAgentTest.java +++ b/source/utils/utils-http/src/test/java/test/my/utils/http/agent/HttpServiceAgentTest.java @@ -23,6 +23,8 @@ import com.jd.blockchain.utils.http.agent.AuthorizationAlgs; import com.jd.blockchain.utils.http.agent.AuthorizationHeader; import com.jd.blockchain.utils.http.agent.HttpServiceAgent; import com.jd.blockchain.utils.http.agent.ServiceEndpoint; +import com.jd.blockchain.utils.io.BytesUtils; +import com.jd.blockchain.utils.security.ShaUtils; import com.jd.blockchain.utils.serialize.binary.BinarySerializeUtils; import com.jd.blockchain.utils.web.server.WebServer; @@ -30,7 +32,7 @@ public class HttpServiceAgentTest { private static final String host = "127.0.0.1"; - private static final int port = 10809; +// private static final int port = 10809; private static final String SENDER_NAME = "upush_test"; @@ -48,13 +50,22 @@ public class HttpServiceAgentTest { server.stop(); } } + + private int getRandomPort() { + byte[] nanoTime = BytesUtils.toBytes(System.nanoTime()); + byte[] hash = ShaUtils.hash_256(nanoTime); + return hash[0]; + } - private void prepareEnvironment(String contextPath, HttpServlet servlet, String servletMapping) { - int port = 10809; + private int prepareEnvironment(String contextPath, HttpServlet servlet, String servletMapping) { + //随机化端口,避免测试用例的端口冲突 + int port = 11000 + getRandomPort(); server = new WebServer(host, port); server.registServlet("test-servlet", servlet, servletMapping); server.setContextPath(contextPath); server.start(); + + return port; } @Test @@ -66,7 +77,7 @@ public class HttpServiceAgentTest { HttpRequestCollector servlet = new HttpRequestCollector(expectedResponseText); // 准备环境; - prepareEnvironment(contextPath, servlet, servicePath); + int port = prepareEnvironment(contextPath, servlet, servicePath); ServiceEndpoint endpoint = new ServiceEndpoint(host, port, false, contextPath); AuthorizationHeader authorization = new AuthorizationHeader(AuthorizationAlgs.DEFAULT, SENDER_NAME, SECRET_KEY); @@ -169,7 +180,7 @@ public class HttpServiceAgentTest { HttpRequestCollector servlet = new HttpRequestCollector(expectedResponseText); // 准备环境; - prepareEnvironment(contextPath, servlet, servicePath); + int port = prepareEnvironment(contextPath, servlet, servicePath); ServiceEndpoint setting = new ServiceEndpoint(host, port, false, contextPath); AuthorizationHeader authSetting = new AuthorizationHeader(AuthorizationAlgs.DEFAULT, SENDER_NAME, SECRET_KEY); HttpTestService testService = HttpServiceAgent.createService(HttpTestService.class, setting, authSetting); @@ -229,7 +240,7 @@ public class HttpServiceAgentTest { HttpRequestCollector servlet = new HttpRequestCollector(expectedResponseText); // 准备环境; - prepareEnvironment(contextPath, servlet, servicePath); + int port = prepareEnvironment(contextPath, servlet, servicePath); ServiceEndpoint setting = new ServiceEndpoint(host, port, false, contextPath); AuthorizationHeader authSetting = new AuthorizationHeader(AuthorizationAlgs.DEFAULT, SENDER_NAME, SECRET_KEY); @@ -267,7 +278,7 @@ public class HttpServiceAgentTest { HttpRequestCollector servlet = new HttpRequestCollector(expectedResponseText); // 准备环境; - prepareEnvironment(contextPath, servlet, servicePath); + int port = prepareEnvironment(contextPath, servlet, servicePath); ServiceEndpoint setting = new ServiceEndpoint(host, port, false, contextPath); AuthorizationHeader authSetting = new AuthorizationHeader(AuthorizationAlgs.DEFAULT, SENDER_NAME, SECRET_KEY); @@ -332,7 +343,7 @@ public class HttpServiceAgentTest { HttpRequestCollector servlet = new HttpRequestCollector(expectedResponseText); // 准备环境; - prepareEnvironment(contextPath, servlet, servicePath); + int port = prepareEnvironment(contextPath, servlet, servicePath); ServiceEndpoint setting = new ServiceEndpoint(host, port, false, contextPath); AuthorizationHeader authSetting = new AuthorizationHeader(AuthorizationAlgs.DEFAULT, SENDER_NAME, SECRET_KEY); @@ -373,7 +384,7 @@ public class HttpServiceAgentTest { HttpRequestCollector servlet = new HttpRequestCollector(expectedResponseText); // 准备环境; - prepareEnvironment(contextPath, servlet, servicePath); + int port = prepareEnvironment(contextPath, servlet, servicePath); ServiceEndpoint setting = new ServiceEndpoint(host, port, false, contextPath); AuthorizationHeader authSetting = new AuthorizationHeader(AuthorizationAlgs.DEFAULT, SENDER_NAME, SECRET_KEY); @@ -414,7 +425,7 @@ public class HttpServiceAgentTest { HttpRequestCollector servlet = new HttpRequestCollector(expectedResponseText); // 准备环境; - prepareEnvironment(contextPath, servlet, servicePath); + int port = prepareEnvironment(contextPath, servlet, servicePath); ServiceEndpoint setting = new ServiceEndpoint(host, port, false, contextPath); AuthorizationHeader authSetting = new AuthorizationHeader(AuthorizationAlgs.DEFAULT, SENDER_NAME, SECRET_KEY); @@ -453,7 +464,7 @@ public class HttpServiceAgentTest { HttpRequestCollector servlet = new HttpRequestCollector(expectedResponseBytes); // 准备环境; - prepareEnvironment(contextPath, servlet, servicePath); + int port = prepareEnvironment(contextPath, servlet, servicePath); ServiceEndpoint setting = new ServiceEndpoint(host, port, false, contextPath); AuthorizationHeader authSetting = new AuthorizationHeader(AuthorizationAlgs.DEFAULT, SENDER_NAME, SECRET_KEY); @@ -506,7 +517,7 @@ public class HttpServiceAgentTest { HttpRequestCollector servlet = new HttpRequestCollector(expectedResponseBytes); // 准备环境; - prepareEnvironment(contextPath, servlet, servicePath); + int port = prepareEnvironment(contextPath, servlet, servicePath); ServiceEndpoint setting = new ServiceEndpoint(host, port, false, contextPath); AuthorizationHeader authSetting = new AuthorizationHeader(AuthorizationAlgs.DEFAULT, SENDER_NAME, SECRET_KEY);