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 +- .../com/jd/blockchain/provider/NamedProvider.java | 20 + .../java/com/jd/blockchain/provider/Provider.java | 11 + .../jd/blockchain/provider/ProviderException.java | 11 + .../jd/blockchain/provider/ProviderManager.java | 247 ++++++ .../bftsmart/BftsmartClientIncomingConfig.java | 2 +- .../bftsmart/BftsmartClientIncomingSettings.java | 4 +- .../bftsmart/BftsmartCommitBlockSettings.java | 2 +- .../bftsmart/BftsmartConsensusSettings.java | 2 +- .../bftsmart/BftsmartConsensusSettingsBuilder.java | 2 +- .../consensus/bftsmart/BftsmartNodeConfig.java | 2 +- .../consensus/bftsmart/BftsmartNodeSettings.java | 4 +- .../bftsmart/client/BftsmartClientConfig.java | 2 +- .../client/BftsmartClientIdentification.java | 6 +- .../client/BftsmartConsensusClientFactory.java | 2 + .../bftsmart/client/BftsmartMessageService.java | 20 +- .../bftsmart/service/BftsmartNodeServer.java | 392 ++++++++- .../consensus/bftsmart/proxyClientTest.java | 2 +- .../blockchain/consensus/ClientIdentification.java | 4 +- .../consensus/ClientIdentifications.java | 2 +- .../consensus/ClientIncomingSettings.java | 2 +- .../jd/blockchain/consensus/ConsensusSettings.java | 2 +- .../com/jd/blockchain/consensus/NodeSettings.java | 4 +- .../blockchain/consensus/action/ActionRequest.java | 2 +- .../consensus/action/ActionResponse.java | 2 +- .../consensus/client/ClientSettings.java | 2 +- .../mq/MsgQueueConsensusSettingsBuilder.java | 2 +- .../consensus/mq/client/MsgQueueClientFactory.java | 2 +- .../mq/client/MsgQueueClientIdentification.java | 2 +- .../consensus/mq/config/MsgQueueClientConfig.java | 2 +- .../mq/config/MsgQueueClientIncomingConfig.java | 2 +- .../mq/config/MsgQueueConsensusConfig.java | 2 +- .../consensus/mq/config/MsgQueueNodeConfig.java | 2 +- .../mq/server/MsgQueueConsensusManageService.java | 2 +- .../mq/settings/MsgQueueBlockSettings.java | 2 +- .../settings/MsgQueueClientIncomingSettings.java | 4 +- .../mq/settings/MsgQueueConsensusSettings.java | 2 +- .../mq/settings/MsgQueueNetworkSettings.java | 2 +- .../mq/settings/MsgQueueNodeSettings.java | 2 +- source/contract/contract-compile/pom.xml | 11 +- .../com/jd/blockchain/contract/AssetContract1.java | 55 +- .../com/jd/blockchain/contract/AssetContract2.java | 5 +- .../com/jd/blockchain/contract/AssetContract3.java | 4 +- .../com/jd/blockchain/contract/AssetContract4.java | 10 +- .../com/jd/blockchain/contract/AssetContract5.java | 4 +- .../com/jd/blockchain/contract/ContractEngine.java | 10 +- .../contract/ContractServiceProvider.java | 2 +- .../blockchain/contract/jvm/JavaContractCode.java | 17 +- .../java/com/jd/blockchain/CheckImportsMojo.java | 2 +- .../java/com/jd/blockchain/ContractDeployMojo.java | 60 +- .../contract/model/ContractAppLifecycleAwire.java | 2 +- .../contract/model/ContractDeployExeUtil.java | 18 +- .../contract/model/ContractEventContext.java | 16 +- .../contract/model/ContractRuntimeAwire.java | 3 +- .../blockchain/contract/model/ErrorCodeEnum.java | 12 +- .../jd/blockchain/contract/model/EventHandle.java | 2 +- .../contract/model/EventProcessingAwire.java | 16 +- source/crypto/crypto-adv/pom.xml | 45 +- .../java/com/jd/blockchain/crypto/ecvrf/VRF.java | 5 +- .../com/jd/blockchain/crypto/mpc/EqualVerify.java | 20 +- .../com/jd/blockchain/crypto/mpc/IntCompare.java | 62 +- .../com/jd/blockchain/crypto/mpc/MultiSum.java | 14 +- .../blockchain/crypto/paillier/PaillierUtils.java | 9 +- .../com/jd/blockchain/crypto/ecvrf/VRFTest.java | 3 +- source/crypto/crypto-classic/pom.xml | 20 + .../service/classic/AESEncryptionFunction.java | 216 +++++ .../service/classic/ClassicCryptoService.java | 62 ++ .../service/classic/ECDSASignatureFunction.java | 67 ++ .../service/classic}/ED25519SignatureFunction.java | 71 +- .../service/classic/JVMSecureRandomFunction.java | 54 ++ .../service/classic}/RIPEMD160HashFunction.java | 20 +- .../crypto/service/classic/RSACryptoFunction.java} | 34 +- .../service/classic}/SHA256HashFunction.java | 21 +- .../crypto/utils/classic/ECDSAUtils.java | 10 + .../blockchain/crypto/utils/classic/RSAUtils.java | 10 + .../com.jd.blockchain.crypto.CryptoService | 1 + .../asymmetric/AsymmtricCryptographyImplTest.java | 953 +++++++++++++++++++++ .../crypto/hash/HashCryptographyImplTest.java | 334 ++++++++ .../symmetric/SymmetricCryptographyImplTest.java | 471 ++++++++++ .../com/jd/blockchain/crypto/AddressEncoding.java | 5 +- .../com/jd/blockchain/crypto/AddressVersion.java | 11 + .../crypto/{base => }/BaseCryptoBytes.java | 16 +- .../com/jd/blockchain/crypto/BaseCryptoKey.java | 46 + .../com/jd/blockchain/crypto/CryptoAlgorithm.java | 174 ++-- .../crypto/CryptoAlgorithmDefinition.java | 118 +++ .../jd/blockchain/crypto/CryptoAlgorithmType.java | 48 +- .../jd/blockchain/crypto/CryptoAlgorithm_Enum.java | 150 ++++ .../java/com/jd/blockchain/crypto/CryptoBytes.java | 6 +- .../com/jd/blockchain/crypto/CryptoFactory.java | 30 +- .../blockchain/crypto/CryptoKeyPairGenerator.java | 2 - .../com/jd/blockchain/crypto/CryptoKeyType.java | 8 +- .../com/jd/blockchain/crypto/CryptoService.java | 9 + .../blockchain/crypto/CryptoServiceProviders.java | 213 +++++ .../java/com/jd/blockchain/crypto/CryptoUtils.java | 124 +-- .../crypto/{asymmetric => }/PrivKey.java | 12 +- .../blockchain/crypto/{asymmetric => }/PubKey.java | 15 +- .../com/jd/blockchain/crypto/RandomFunction.java | 7 + .../com/jd/blockchain/crypto/RandomGenerator.java | 9 + .../crypto/{symmetric => }/SymmetricKey.java | 17 +- .../crypto/asymmetric/AsymmetricCiphertext.java | 6 +- .../crypto/asymmetric/AsymmetricCryptography.java | 166 ++-- .../asymmetric/AsymmetricEncryptionFunction.java | 2 + .../crypto/asymmetric/CryptoKeyPair.java | 3 + .../crypto/asymmetric/SignatureDigest.java | 6 +- .../crypto/asymmetric/SignatureFunction.java | 2 + .../jd/blockchain/crypto/base/BaseCryptoKey.java | 62 -- .../blockchain/crypto/hash/HashCryptography.java | 100 +-- .../com/jd/blockchain/crypto/hash/HashDigest.java | 4 +- .../crypto/impl/AsymmtricCryptographyImpl.java | 218 ----- .../blockchain/crypto/impl/CryptoFactoryImpl.java | 30 - .../crypto/impl/HashCryptographyImpl.java | 84 -- .../crypto/impl/SymmetricCryptographyImpl.java | 101 --- .../symmetric/AESSymmetricEncryptionFunction.java | 142 --- .../asymmetric/JNIED25519SignatureFunction.java | 140 --- .../impl/jni/hash/JNIRIPEMD160HashFunction.java | 53 -- .../impl/jni/hash/JNISHA256HashFunction.java | 53 -- .../impl/sm/asymmetric/SM2CryptoFunction.java | 192 ----- .../symmetric/SM4SymmetricEncryptionFunction.java | 145 ---- .../jniutils/asymmetric/JNIED25519Utils.java | 37 - .../crypto/jniutils/hash/JNIMBSHA256Utils.java | 24 - .../crypto/jniutils/hash/JNIRIPEMD160Utils.java | 30 - .../crypto/jniutils/hash/JNISHA256Utils.java | 30 - .../serialize/ByteArrayObjectDeserializer.java} | 41 +- .../serialize/ByteArrayObjectSerializer.java | 62 ++ .../crypto/symmetric/SymmetricCiphertext.java | 4 +- .../crypto/symmetric/SymmetricCryptography.java | 69 +- .../symmetric/SymmetricEncryptionFunction.java | 6 +- .../asymmetric/AsymmtricCryptographyImplTest.java | 946 -------------------- .../crypto/hash/HashCryptographyImplTest.java | 333 ------- .../crypto/jniutils/JNIED25519UtilsTest.java | 123 --- .../crypto/jniutils/JNIMBSHA256UtilsTest.java | 111 --- .../crypto/jniutils/JNIRIPEMD160UtilsTest.java | 41 - .../crypto/jniutils/JNISHA256UtilsTest.java | 41 - .../performance/MyAsymmetricEncryptionTest.java | 55 -- .../blockchain/crypto/performance/MyHashTest.java | 62 -- .../crypto/performance/MySignatureTest.java | 83 -- .../performance/MySymmetricEncryptionTest.java | 91 -- .../jd/blockchain/crypto/smutils/SM3UtilsTest.java | 33 - .../jd/blockchain/crypto/smutils/SM4UtilsTest.java | 66 -- .../symmetric/SymmetricCryptographyImplTest.java | 471 ---------- source/crypto/crypto-impl/pom.xml | 20 + .../crypto/impl/AsymmtricCryptographyImpl.java | 220 +++++ .../blockchain/crypto/impl/CryptoFactoryImpl.java | 30 + .../crypto/impl/HashCryptographyImpl.java | 84 ++ .../crypto/impl/SymmetricCryptographyImpl.java | 101 +++ .../performance/MyAsymmetricEncryptionTest.java | 55 ++ .../blockchain/crypto/performance/MyHashTest.java | 62 ++ .../crypto/performance/MySignatureTest.java | 83 ++ .../performance/MySymmetricEncryptionTest.java | 92 ++ source/crypto/crypto-sm/pom.xml | 20 + .../crypto/service/sm/SM2CryptoFunction.java | 212 +++++ .../crypto/service/sm}/SM3HashFunction.java | 29 +- .../crypto/service/sm/SM4EncryptionFunction.java | 148 ++++ .../crypto/service/sm/SMCryptoService.java | 47 + .../jd/blockchain/crypto/utils/sm}/SM2Utils.java | 90 +- .../jd/blockchain/crypto/utils/sm}/SM3Utils.java | 5 +- .../jd/blockchain/crypto/utils/sm}/SM4Utils.java | 22 +- .../com.jd.blockchain.crypto.CryptoService | 1 + .../jd/blockchain/crypto/smutils/SM2UtilsTest.java | 128 ++- .../jd/blockchain/crypto/smutils/SM3UtilsTest.java | 71 ++ .../jd/blockchain/crypto/smutils/SM4UtilsTest.java | 137 +++ source/crypto/pom.xml | 7 +- .../com/jd/blockchain/boot/peer/PeerBooter.java | 2 +- .../jd/blockchain/gateway/GatewayServerBooter.java | 4 +- .../gateway/web/BlockBrowserController.java | 2 +- .../gateway/web/GatewayWebServerConfigurer.java | 21 +- .../src/main/resources/application-gw.properties | 1 - source/ledger/ledger-core/pom.xml | 14 + .../ledger/core/AccountAccessPolicy.java | 2 +- .../com/jd/blockchain/ledger/core/AccountSet.java | 2 +- .../com/jd/blockchain/ledger/core/BaseAccount.java | 2 +- .../jd/blockchain/ledger/core/ContractAccount.java | 2 +- .../blockchain/ledger/core/ContractAccountSet.java | 2 +- .../com/jd/blockchain/ledger/core/DataAccount.java | 18 +- .../jd/blockchain/ledger/core/DataAccountSet.java | 2 +- .../blockchain/ledger/core/LedgerInitDecision.java | 2 +- .../ledger/core/LedgerInitPermission.java | 2 +- .../jd/blockchain/ledger/core/LedgerMetadata.java | 2 +- .../jd/blockchain/ledger/core/LedgerSetting.java | 2 +- .../ledger/core/ParticipantCertData.java | 2 +- .../com/jd/blockchain/ledger/core/UserAccount.java | 2 +- .../jd/blockchain/ledger/core/UserAccountSet.java | 2 +- .../ledger/core/impl/LedgerTransactionData.java | 1 - .../ledger/core/impl/OpeningAccessPolicy.java | 2 +- .../com/jd/blockchain/ledger/AccountSetTest.java | 3 +- .../com/jd/blockchain/ledger/BaseAccountTest.java | 3 +- .../jd/blockchain/ledger/LedgerAccountTest.java | 8 +- .../blockchain/ledger/LedgerAdminAccountTest.java | 3 +- .../jd/blockchain/ledger/LedgerBlockImplTest.java | 35 +- .../com/jd/blockchain/ledger/LedgerEditerTest.java | 33 +- .../blockchain/ledger/LedgerInitOperationTest.java | 3 +- .../blockchain/ledger/LedgerInitSettingTest.java | 3 +- .../jd/blockchain/ledger/LedgerManagerTest.java | 13 +- .../jd/blockchain/ledger/LedgerMetaDataTest.java | 18 +- .../com/jd/blockchain/ledger/LedgerTestUtils.java | 9 +- .../ledger/LedgerTransactionDataTest.java | 43 +- .../jd/blockchain/ledger/MerkleDataSetTest.java | 9 +- .../com/jd/blockchain/ledger/MerkleTreeTest.java | 16 +- .../ledger/TransactionStagedSnapshotTest.java | 25 +- source/ledger/ledger-model/pom.xml | 10 + .../com/jd/blockchain/ledger/AccountHeader.java | 4 +- .../java/com/jd/blockchain/ledger/BlockBody.java | 2 +- .../jd/blockchain/ledger/BlockchainIdentity.java | 4 +- .../blockchain/ledger/BlockchainIdentityData.java | 2 +- .../blockchain/ledger/BlockchainKeyGenerator.java | 3 - .../jd/blockchain/ledger/BlockchainKeyPair.java | 4 +- .../java/com/jd/blockchain/ledger/BytesValue.java | 2 +- .../ledger/ContractCodeDeployOperation.java | 2 +- .../ledger/ContractEventSendOperation.java | 2 +- .../com/jd/blockchain/ledger/CryptoSetting.java | 2 +- .../ledger/DataAccountKVSetOperation.java | 2 +- .../ledger/DataAccountRegisterOperation.java | 2 +- .../java/com/jd/blockchain/ledger/DataType.java | 2 +- .../com/jd/blockchain/ledger/DigitalSignature.java | 2 +- .../jd/blockchain/ledger/DigitalSignatureBody.java | 4 +- .../com/jd/blockchain/ledger/EndpointRequest.java | 2 +- .../java/com/jd/blockchain/ledger/HashObject.java | 2 +- .../java/com/jd/blockchain/ledger/LedgerBlock.java | 2 +- .../jd/blockchain/ledger/LedgerDataSnapshot.java | 2 +- .../jd/blockchain/ledger/LedgerInitOperation.java | 2 +- .../jd/blockchain/ledger/LedgerInitSetting.java | 2 +- .../jd/blockchain/ledger/LedgerTransaction.java | 2 +- .../java/com/jd/blockchain/ledger/NodeRequest.java | 2 +- .../java/com/jd/blockchain/ledger/Operation.java | 2 +- .../com/jd/blockchain/ledger/ParticipantNode.java | 4 +- .../java/com/jd/blockchain/ledger/Transaction.java | 2 +- .../jd/blockchain/ledger/TransactionContent.java | 2 +- .../blockchain/ledger/TransactionContentBody.java | 2 +- .../jd/blockchain/ledger/TransactionRequest.java | 2 +- .../jd/blockchain/ledger/TransactionResponse.java | 2 +- .../com/jd/blockchain/ledger/TransactionState.java | 2 +- .../java/com/jd/blockchain/ledger/UserInfo.java | 4 +- .../blockchain/ledger/UserRegisterOperation.java | 2 +- .../ledger/data/ConsensusParticipantData.java | 2 +- .../blockchain/ledger/data/CryptoKeyEncoding.java | 6 +- .../ledger/data/DigitalSignatureBlob.java | 2 +- .../com/jd/blockchain/ledger/data/PreparedTx.java | 3 +- .../blockchain/ledger/data/TxRequestBuilder.java | 4 +- .../ledger/data/AddressEncodingTest.java | 2 +- .../data/ContractCodeDeployOpTemplateTest.java | 2 +- .../data/DataAccountRegisterOpTemplateTest.java | 2 +- .../ledger/data/DigitalSignatureBlobTest.java | 2 +- .../ledger/data/TxRequestMessageTest.java | 2 +- .../ledger/data/UserRegisterOpTemplateTest.java | 2 +- source/ledger/ledger-rpc/pom.xml | 24 +- .../serializes/ByteArrayObjectJsonSerializer.java | 120 --- .../web/serializes/ByteArrayObjectUtil.java | 41 - .../peer/web/PeerWebServerConfigurer.java | 21 +- source/pom.xml | 2 +- .../com/jd/blockchain/runtime/AbstractModule.java | 3 +- .../blockchain/runtime/modular/ModularFactory.java | 2 +- .../runtime/modular/MuduleClassLoader.java | 1 - source/sdk/sdk-base/pom.xml | 9 +- .../converters/HashDigestsResponseConverter.java | 2 +- .../blockchain/sdk/client/ClientOperationUtil.java | 273 ------ .../sdk/client/GatewayServiceFactory.java | 44 +- source/sdk/sdk-mq/pom.xml | 45 + source/sdk/sdk-samples/pom.xml | 7 - .../blockchain/sdk/samples/SDKDemo_Contract.java | 4 +- .../sdk/samples/SDKDemo_DataAccount.java | 10 +- .../blockchain/sdk/samples/SDKDemo_InsertData.java | 4 +- .../jd/blockchain/sdk/samples/SDKDemo_Params.java | 4 +- .../sdk/samples/SDKDemo_RegisterTest.java | 4 +- .../sdk/samples/SDKDemo_RegisterUser.java | 4 +- .../jd/blockchain/sdk/samples/SDKDemo_User.java | 9 +- .../test/SDK_GateWay_BatchInsertData_Test_.java | 77 +- .../sdk/test/SDK_GateWay_ContractDeploy_Test_.java | 102 --- .../sdk/test/SDK_GateWay_ContractExec_Test_.java | 104 --- .../sdk/test/SDK_GateWay_DataAccount_Test_.java | 75 +- .../sdk/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 +- .../com/jd/blockchain/intgr/IntegrationTest.java | 4 +- .../blockchain/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 +- .../test/com/jd/blockchain/intgr/perf/Utils.java | 2 +- .../com/jd/blockchain/intgr/IntegrationBase.java | 21 - .../jd/blockchain/intgr/IntegrationBaseTest.java | 2 +- .../com/jd/blockchain/intgr/IntegrationTest2.java | 2 +- .../blockchain/intgr/IntegrationTest4Bftsmart.java | 4 +- .../jd/blockchain/intgr/IntegrationTest4MQ.java | 6 +- .../blockchain/intgr/IntegrationTestAll4Redis.java | 4 +- .../intgr/IntegrationTestDataAccount.java | 4 +- .../intgr/batch/bftsmart/BftsmartLedgerInit.java | 4 +- .../intgr/initializer/LedgerInitSettingTest.java | 2 +- .../intgr/initializer/LedgerInitializeTest.java | 4 +- .../initializer/LedgerInitializeWeb4Nodes.java | 4 +- .../LedgerInitializeWeb4SingleStepsTest.java | 4 +- .../intgr/ledger/LedgerBlockGeneratingTest.java | 2 +- .../capability/service/SettingsInit.java | 4 +- .../tools/initializer/LedgerInitCommand.java | 4 +- .../tools/initializer/LedgerInitProcess.java | 2 +- .../tools/initializer/LedgerInitProperties.java | 2 +- .../web/LedgerInitializeWebController.java | 4 +- .../jd/blockchain/tools/keygen/KeyGenCommand.java | 4 +- source/tools/tools-package/pom.xml | 45 + .../com/jd/blockchain/utils/io/BytesUtils.java | 130 ++- .../com/jd/blockchain/utils/io/NumberMask.java | 7 - .../com/jd/blockchain/utils/security/AESUtils.java | 18 +- .../my/utils/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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

N*} 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ⅆckR82obKC2gJ#~^BJIkIwXWj8v$CjxrP<#@KF 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

  • 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 A}| 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
    YrfhWGOn8(!fPbaT$+v8J=Zd% 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 z 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Ⓢd)u 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ÿ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 B2txn> literal 0 KcmV+b0RR6000031 diff --git a/docs/readme.zip b/docs/readme.zip index 5ad632fe21067c355e7d8973ca150d5b98ef0280..7484f74221d824d1d85001a9e0effaf885495056 100644 GIT binary patch delta 75942 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 Dd5Vt& 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) { @@ -51,8 +69,18 @@ public class ECDSASignatureFunction implements SignatureFunction { } @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);