From c05214b123e2aff7eecb69b8a832220f46fc9fc3 Mon Sep 17 00:00:00 2001 From: jimmyshi <417711026@qq.com> Date: Tue, 22 Dec 2020 14:27:21 +0800 Subject: [PATCH 1/8] (UAProof): Support to generate and verify UAProof (#113) * Add Fabric verify UAProof * modify log obv --- .../account/ExtendedIdentityFactory.java | 26 ++++++++++++ .../webank/wecross/account/FabricAccount.java | 12 +++++- .../wecross/account/FabricAccountFactory.java | 9 ++++- .../account/X509ExtendedSigningIdentity.java | 40 +++++++++++++++++++ .../wecross/stub/fabric/FabricDriver.java | 36 +++++++++++++++-- .../fabric/FabricDriverSignVerifyTest.java | 28 +++++++++++++ 6 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/webank/wecross/account/ExtendedIdentityFactory.java create mode 100644 src/main/java/com/webank/wecross/account/X509ExtendedSigningIdentity.java create mode 100644 src/test/java/com/webank/wecross/stub/fabric/FabricDriverSignVerifyTest.java diff --git a/src/main/java/com/webank/wecross/account/ExtendedIdentityFactory.java b/src/main/java/com/webank/wecross/account/ExtendedIdentityFactory.java new file mode 100644 index 0000000..5a2ba92 --- /dev/null +++ b/src/main/java/com/webank/wecross/account/ExtendedIdentityFactory.java @@ -0,0 +1,26 @@ +package com.webank.wecross.account; + +import org.hyperledger.fabric.sdk.Enrollment; +import org.hyperledger.fabric.sdk.User; +import org.hyperledger.fabric.sdk.identity.IdemixEnrollment; +import org.hyperledger.fabric.sdk.identity.IdemixSigningIdentity; +import org.hyperledger.fabric.sdk.identity.SigningIdentity; +import org.hyperledger.fabric.sdk.security.CryptoSuite; + +public class ExtendedIdentityFactory { + + public static SigningIdentity getSigningIdentity(CryptoSuite cryptoSuite, User user) { + Enrollment enrollment = user.getEnrollment(); + + try { + if (enrollment instanceof IdemixEnrollment) { // Need Idemix signer for this. + return new IdemixSigningIdentity((IdemixEnrollment) enrollment); + } else { // for now all others are x509 + return new X509ExtendedSigningIdentity(cryptoSuite, user); + } + + } catch (Exception e) { + throw new IllegalStateException(e.getMessage(), e); + } + } +} diff --git a/src/main/java/com/webank/wecross/account/FabricAccount.java b/src/main/java/com/webank/wecross/account/FabricAccount.java index 89ce536..a726b9e 100644 --- a/src/main/java/com/webank/wecross/account/FabricAccount.java +++ b/src/main/java/com/webank/wecross/account/FabricAccount.java @@ -3,7 +3,8 @@ import com.webank.wecross.common.FabricType; import com.webank.wecross.stub.Account; import org.hyperledger.fabric.sdk.User; -import org.hyperledger.fabric.sdk.identity.IdentityFactory; +import org.hyperledger.fabric.sdk.exception.CryptoException; +import org.hyperledger.fabric.sdk.exception.InvalidArgumentException; import org.hyperledger.fabric.sdk.identity.SigningIdentity; import org.hyperledger.fabric.sdk.security.CryptoSuite; @@ -19,13 +20,20 @@ public FabricAccount(User user) throws Exception { // ECDSA secp256r1 this.signer = - IdentityFactory.getSigningIdentity(CryptoSuite.Factory.getCryptoSuite(), user); + ExtendedIdentityFactory.getSigningIdentity( + CryptoSuite.Factory.getCryptoSuite(), user); } public byte[] sign(byte[] message) throws Exception { return signer.sign(message); } + // Only in fabric stub + public boolean verifySign(byte[] message, byte[] sig) + throws CryptoException, InvalidArgumentException { + return signer.verifySignature(message, sig); + } + @Override public String getName() { return user.getName(); diff --git a/src/main/java/com/webank/wecross/account/FabricAccountFactory.java b/src/main/java/com/webank/wecross/account/FabricAccountFactory.java index 9c86b80..38e7b9a 100644 --- a/src/main/java/com/webank/wecross/account/FabricAccountFactory.java +++ b/src/main/java/com/webank/wecross/account/FabricAccountFactory.java @@ -139,7 +139,14 @@ public String getMspId() { } public static Enrollment buildEnrollment(String pubKey, String secKey) throws Exception { - PrivateKey privateKey = buildPemPrivateKey(secKey); + PrivateKey privateKey; + + // support to build with empty secKey + if (secKey == null || secKey.length() == 0) { + privateKey = null; + } else { + privateKey = buildPemPrivateKey(secKey); + } return new Enrollment() { @Override diff --git a/src/main/java/com/webank/wecross/account/X509ExtendedSigningIdentity.java b/src/main/java/com/webank/wecross/account/X509ExtendedSigningIdentity.java new file mode 100644 index 0000000..45eb289 --- /dev/null +++ b/src/main/java/com/webank/wecross/account/X509ExtendedSigningIdentity.java @@ -0,0 +1,40 @@ +package com.webank.wecross.account; + +import java.io.ByteArrayInputStream; +import java.security.Signature; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import org.hyperledger.fabric.sdk.User; +import org.hyperledger.fabric.sdk.exception.CryptoException; +import org.hyperledger.fabric.sdk.identity.X509SigningIdentity; +import org.hyperledger.fabric.sdk.security.CryptoSuite; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class X509ExtendedSigningIdentity extends X509SigningIdentity { + private static Logger logger = LoggerFactory.getLogger(X509ExtendedSigningIdentity.class); + + public X509ExtendedSigningIdentity(CryptoSuite cryptoSuite, User user) { + super(cryptoSuite, user); + } + + @Override + public boolean verifySignature(byte[] msg, byte[] sig) throws CryptoException { + try { + ByteArrayInputStream bis = + new ByteArrayInputStream( + this.createSerializedIdentity().getIdBytes().toByteArray()); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate certificate = (X509Certificate) cf.generateCertificate(bis); + + Signature signer = Signature.getInstance(certificate.getSigAlgName()); + signer.initVerify(certificate); + signer.update(msg); + return signer.verify(sig); + + } catch (Exception e) { + logger.error("verifySignature exception: ", e); + return false; + } + } +} diff --git a/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java b/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java index b2c73ae..3fc7a18 100644 --- a/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java +++ b/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java @@ -4,6 +4,8 @@ import static com.webank.wecross.utils.FabricUtils.longToBytes; import com.google.protobuf.ByteString; +import com.webank.wecross.account.FabricAccount; +import com.webank.wecross.account.FabricAccountFactory; import com.webank.wecross.common.FabricType; import com.webank.wecross.stub.*; import com.webank.wecross.stub.fabric.FabricCustomCommand.InstallChaincodeRequest; @@ -754,14 +756,40 @@ public void asyncCustomCommand( @Override public byte[] accountSign(Account account, byte[] message) { - // TODO: implememt this - return new byte[0]; + if (!(account instanceof FabricAccount)) { + throw new UnsupportedOperationException( + "Not FabricAccount, account name: " + account.getClass().getName()); + } + + try { + byte[] signBytes = ((FabricAccount) account).sign(message); + logger.debug( + "accountSign: {}, message: {}, signBytes: {}", + account.getName(), + message.toString(), + Arrays.toString(signBytes)); + return signBytes; + } catch (Exception e) { + logger.error("accountSign exception: ", e); + return null; + } } @Override public boolean accountVerify(String identity, byte[] signBytes, byte[] message) { - // TODO: implememt this - return true; + FabricAccount fabricAccount = + FabricAccountFactory.build("temp-to-verify-" + identity, "", identity, null); + try { + logger.debug( + "accountVerify: {}, signBytes:{}, message: {} ", + identity, + Arrays.toString(signBytes), + message); + return fabricAccount.verifySign(message, signBytes); + } catch (Exception e) { + logger.error("accountVerify exception: ", e); + return false; + } } private void handleInstallCommand( diff --git a/src/test/java/com/webank/wecross/stub/fabric/FabricDriverSignVerifyTest.java b/src/test/java/com/webank/wecross/stub/fabric/FabricDriverSignVerifyTest.java new file mode 100644 index 0000000..0452a91 --- /dev/null +++ b/src/test/java/com/webank/wecross/stub/fabric/FabricDriverSignVerifyTest.java @@ -0,0 +1,28 @@ +package com.webank.wecross.stub.fabric; + +import com.webank.wecross.account.FabricAccount; +import java.nio.charset.StandardCharsets; +import org.junit.Assert; +import org.junit.Test; + +public class FabricDriverSignVerifyTest { + private FabricDriver driver; + private FabricAccount account; + + public FabricDriverSignVerifyTest() { + FabricStubFactory stubFactory = new FabricStubFactory(); + driver = (FabricDriver) stubFactory.newDriver(); + account = + (FabricAccount) + stubFactory.newAccount("fabric_user1", "classpath:accounts/fabric_user1/"); + } + + @Test + public void allTest() { + byte[] message = "verify good".getBytes(StandardCharsets.UTF_8); + byte[] signBytes = driver.accountSign(account, message); + boolean res = driver.accountVerify(account.getIdentity(), signBytes, message); + + Assert.assertTrue(res); + } +} From cb2858418de3a1182043fc1165c8fe2b9e0257f0 Mon Sep 17 00:00:00 2001 From: jimmyshi <417711026@qq.com> Date: Thu, 24 Dec 2020 19:41:41 +0800 Subject: [PATCH 2/8] (block verify): Add verify block endorser logic (#114) * Add verify block endorser logic (cherry picked from commit 7323a4304401f588ae1c4b59f5eed665987a85e9) * optimize code * ignore system transaction --- .../wecross/stub/fabric/FabricBlock.java | 113 ++++++++++++++++++ .../wecross/stub/fabric/FabricDriver.java | 17 +++ .../wecross/stub/fabric/FabricBlockTest.java | 48 ++++++++ 3 files changed, 178 insertions(+) create mode 100644 src/test/java/com/webank/wecross/stub/fabric/FabricBlockTest.java diff --git a/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java b/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java index a5b0396..fe9bd16 100644 --- a/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java +++ b/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java @@ -2,8 +2,14 @@ import com.google.protobuf.ByteString; import com.webank.wecross.stub.BlockHeader; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.security.Signature; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; import java.util.Set; import org.apache.commons.codec.binary.Hex; @@ -11,6 +17,8 @@ import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERSequenceGenerator; import org.hyperledger.fabric.protos.common.Common; +import org.hyperledger.fabric.protos.msp.Identities; +import org.hyperledger.fabric.protos.peer.FabricProposalResponse; import org.hyperledger.fabric.protos.peer.FabricTransaction; import org.hyperledger.fabric.sdk.exception.InvalidArgumentException; import org.hyperledger.fabric.sdk.security.CryptoSuite; @@ -99,6 +107,10 @@ public Set parseValidTxIDListFromDataAndFilter() { return validList; } + public Header getHeader() { + return header; + } + public static class Header { private Common.BlockHeader header; @@ -197,4 +209,105 @@ public static String calculateBlockHash(Common.Block block) { public String toString() { return block.toString(); } + + public boolean verify(Collection endorsers) { + try { + byte[] txFilter = metaData.getTransactionFilter(); + + if (block.getData().getDataList().size() != txFilter.length) { + throw new Exception( + "Illegal block format. block data(tx) number: " + + block.getData().getDataList().size() + + " tx filter size: " + + txFilter.length); + } + + for (int i = 0; i < block.getData().getDataCount(); i++) { + // a tx + ByteString envelopeBytes = block.getData().getData(i); + + if (txFilter[i] != FabricTransaction.TxValidationCode.VALID_VALUE) { + // jump illegal tx + continue; + } + + Common.Envelope envelope = Common.Envelope.parseFrom(envelopeBytes); + Common.Payload payload = Common.Payload.parseFrom(envelope.getPayload()); + + Common.ChannelHeader channelHeader = + Common.ChannelHeader.parseFrom(payload.getHeader().getChannelHeader()); + String txID = channelHeader.getTxId(); + if (txID == null || txID.length() == 0) { + // ignore system tx + continue; + } + + FabricTransaction.Transaction transaction = + FabricTransaction.Transaction.parseFrom(payload.getData()); + for (FabricTransaction.TransactionAction action : transaction.getActionsList()) { + // an action of tx + FabricTransaction.ChaincodeActionPayload chaincodeActionPayload = + FabricTransaction.ChaincodeActionPayload.parseFrom(action.getPayload()); + FabricTransaction.ChaincodeEndorsedAction chaincodeEndorsedAction = + chaincodeActionPayload.getAction(); + + for (FabricProposalResponse.Endorsement endorsement : + chaincodeEndorsedAction.getEndorsementsList()) { + // a endorsement of tx + Identities.SerializedIdentity endorser = + Identities.SerializedIdentity.parseFrom(endorsement.getEndorser()); + ByteString plainText = + chaincodeEndorsedAction + .getProposalResponsePayload() + .concat(endorsement.getEndorser()); + + ByteString endorserCertifcate = endorser.getIdBytes(); + byte[] signBytes = endorsement.getSignature().toByteArray(); + byte[] data = plainText.toByteArray(); + + // verify endorser signature + if (!verifyEndorsement(endorserCertifcate, signBytes, data)) { + return false; + } + + // verify endorser + /* + if (endorsers.contains(endorserCertifcate)) { + + } + */ + } + } + } + return true; + } catch (Exception e) { + logger.error("verify block failed: ", e); + return false; + } + } + + private boolean verifyEndorsement(ByteString identity, byte[] signBytes, byte[] data) { + try { + ByteArrayInputStream bis = new ByteArrayInputStream(identity.toByteArray()); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate certificate = (X509Certificate) cf.generateCertificate(bis); + + Signature signer = Signature.getInstance(certificate.getSigAlgName()); + signer.initVerify(certificate); + signer.update(data); + boolean ok = signer.verify(signBytes); + + logger.debug( + "verifyEndorsement: {}, identity: {}, signBytes:{}, data: {} ", + ok, + identity.toStringUtf8(), + Arrays.toString(signBytes), + data); + + return ok; + } catch (Exception e) { + logger.error("verifyEndorsement exception: ", e); + return false; + } + } } diff --git a/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java b/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java index 3fc7a18..bf597e0 100644 --- a/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java +++ b/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java @@ -399,6 +399,23 @@ public void asyncGetBlock( List transactionsHashes = new ArrayList<>(); try { fabricBlock = FabricBlock.encode(response.getData()); + if (!fabricBlock.verify(null)) { + /* + logger.warn( + "block {} verify failed: {}", + fabricBlock.getHeader().getNumber(), + java.util.Base64.getEncoder() + .encodeToString(response.getData())); + */ + callback.onResponse( + new Exception( + "block " + + fabricBlock.getHeader().getNumber() + + " verify failed"), + null); + return; + } + if (!onlyHeader) { transactionsHashes = new ArrayList<>(fabricBlock.getValidTxs()); } diff --git a/src/test/java/com/webank/wecross/stub/fabric/FabricBlockTest.java b/src/test/java/com/webank/wecross/stub/fabric/FabricBlockTest.java new file mode 100644 index 0000000..4a1549c --- /dev/null +++ b/src/test/java/com/webank/wecross/stub/fabric/FabricBlockTest.java @@ -0,0 +1,48 @@ +package com.webank.wecross.stub.fabric; + +import java.util.Base64; +import org.junit.Assert; +import org.junit.Test; + +public class FabricBlockTest { + + public static final byte[] systemBlockBytes = + Base64.getDecoder() + .decode( + "CkYIBRIg/AJVTB5Jo6PsxVf280oGYALnDeJU+3hB26V9NnMwl6YaIPxr0BLzo+ytoJJLflPDyJQe+Iqqh3uqBZQIYyOM/q8QEvsiCvgiCqwiCsAHCmgIAxABGgsIg8SM/wUQ8MSGQSIJbXljaGFubmVsKkAzNjFmY2ZhZWI0MzhmZGEzZmMzMDBkMTYwODg0ZjBlYmI4YjYxMjcxNWRjZDlhNzVjYzg3YWRlZGM0OWJhZWEwOggSBhIEbHNjYxLTBgq2BgoHT3JnMU1TUBKqBi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlDS2pDQ0FkQ2dBd0lCQWdJUkFJNXBWT0FtNGFjcGNXaHphZnZzZHJnd0NnWUlLb1pJemowRUF3SXdjekVMCk1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFZOaGJpQkcKY21GdVkybHpZMjh4R1RBWEJnTlZCQW9URUc5eVp6RXVaWGhoYlhCc1pTNWpiMjB4SERBYUJnTlZCQU1URTJOaApMbTl5WnpFdVpYaGhiWEJzWlM1amIyMHdIaGNOTWpBeE1qSXpNVEEwTmpBd1doY05NekF4TWpJeE1UQTBOakF3CldqQnJNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU4KVTJGdUlFWnlZVzVqYVhOamJ6RU9NQXdHQTFVRUN4TUZZV1J0YVc0eEh6QWRCZ05WQkFNTUZrRmtiV2x1UUc5eQpaekV1WlhoaGJYQnNaUzVqYjIwd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFUVHNvYkJjMEdFCk5OTXNrV2dLbXcxOWROeTgraStUMW9CNDVnWkJpckZPVEZLRWd1b1JMNnZLZ3pPbVZmYTBNSE4ybHVWcXhKMHIKUzNRcW9wU25VeTRNbzAwd1N6QU9CZ05WSFE4QkFmOEVCQU1DQjRBd0RBWURWUjBUQVFIL0JBSXdBREFyQmdOVgpIU01FSkRBaWdDRGU0aHZaTThVQndadFBnUFpRRDJIKzRFb1pqclF1cTVkTkxqM2UxNWErWmpBS0JnZ3Foa2pPClBRUURBZ05JQURCRkFpRUF0MTRuUmhqL1Frb1dZMlJldkphcit6ZEJncFJabGY0Ly9DWjB3T2Nlc2p3Q0lHVlQKUjFmTFpJM3dZRVNiQTd5dUxpckVaQkRpNWw4MUpOckRRc2VFYTUzcAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tChIY4cqX4AzJh1WdX76HMLv3R4vmt7/IKQpCEuYaCuMaCtMGCrYGCgdPcmcxTVNQEqoGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLakNDQWRDZ0F3SUJBZ0lSQUk1cFZPQW00YWNwY1doemFmdnNkcmd3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpFdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekV1WlhoaGJYQnNaUzVqYjIwd0hoY05NakF4TWpJek1UQTBOakF3V2hjTk16QXhNakl4TVRBME5qQXcKV2pCck1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFT01Bd0dBMVVFQ3hNRllXUnRhVzR4SHpBZEJnTlZCQU1NRmtGa2JXbHVRRzl5Clp6RXVaWGhoYlhCc1pTNWpiMjB3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVRUc29iQmMwR0UKTk5Nc2tXZ0ttdzE5ZE55OCtpK1Qxb0I0NWdaQmlyRk9URktFZ3VvUkw2dktnek9tVmZhME1ITjJsdVZxeEowcgpTM1Fxb3BTblV5NE1vMDB3U3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3REFZRFZSMFRBUUgvQkFJd0FEQXJCZ05WCkhTTUVKREFpZ0NEZTRodlpNOFVCd1p0UGdQWlFEMkgrNEVvWmpyUXVxNWROTGozZTE1YStaakFLQmdncWhrak8KUFFRREFnTklBREJGQWlFQXQxNG5SaGovUWtvV1kyUmV2SmFyK3pkQmdwUlpsZjQvL0NaMHdPY2VzandDSUdWVApSMWZMWkkzd1lFU2JBN3l1TGlyRVpCRGk1bDgxSk5yRFFzZUVhNTNwCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KEhjhypfgDMmHVZ1fvocwu/dHi+a3v8gpCkISihQKngEKmwEKmAEIARIGEgRsc2NjGosBCgZkZXBsb3kKCW15Y2hhbm5lbAorCikIARIWCgljaGFpbmNvZGUSBHNhY2MaAzEuMBoNCgRpbml0CgFhCgIxMApJEhwSGggBEgwSCggBEgIIABICCAESCBIGCAESAggCGgsSCQoHT3JnMU1TUBoNEgsKB09yZzFNU1AQARoNEgsKB09yZzJNU1AQARLmEgrjBAog+EPl5AfioADe7ZozqMYyhHjJi9uMD6q9B6XG/DYKzpASvgQKqwISlQIKBGxzY2MSjAIKBgoEc2FjYxqBAgoEc2FjYxr4AQoEc2FjYxIDMS4wGgRlc2NjIgR2c2NjKkkSHBIaCAESDBIKCAESAggAEgIIARIIEgYIARICCAIaCxIJCgdPcmcxTVNQGg0SCwoHT3JnMU1TUBABGg0SCwoHT3JnMk1TUBABMkQKIFnQ0n2UiE5U1qq3QZhUkz1G1LWR/8EUe5c6W+Hy63FbEiBMlrXzgKrIbP3ccg2QO0PIDsT8usMh8EY3rlXlr0HfVTog4QBbK2OH4/YoYQlJtwUO8h8OdJO/mqFk6FmFft6pH3tCLBIMEgoIARICCAASAggBGg0SCwoHT3JnMU1TUBABGg0SCwoHT3JnMk1TUBABEhEKBHNhY2MSCRoHCgFhGgIxMBr+AQjIARr4AQoEc2FjYxIDMS4wGgRlc2NjIgR2c2NjKkkSHBIaCAESDBIKCAESAggAEgIIARIIEgYIARICCAIaCxIJCgdPcmcxTVNQGg0SCwoHT3JnMU1TUBABGg0SCwoHT3JnMk1TUBABMkQKIFnQ0n2UiE5U1qq3QZhUkz1G1LWR/8EUe5c6W+Hy63FbEiBMlrXzgKrIbP3ccg2QO0PIDsT8usMh8EY3rlXlr0HfVTog4QBbK2OH4/YoYQlJtwUO8h8OdJO/mqFk6FmFft6pH3tCLBIMEgoIARICCAASAggBGg0SCwoHT3JnMU1TUBABGg0SCwoHT3JnMk1TUBABIg0SBGxzY2MaBTEuNC40Ev0GCrIGCgdPcmcyTVNQEqYGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNKekNDQWM2Z0F3SUJBZ0lRZk5MZ1pnb05SWmQrTUIyNjBkWmZ2ekFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NaTVsZUdGdGNHeGxMbU52YlRBZUZ3MHlNREV5TWpNeE1EUTJNREJhRncwek1ERXlNakV4TURRMk1EQmEKTUdveEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVEwd0N3WURWUVFMRXdSd1pXVnlNUjh3SFFZRFZRUURFeFp3WldWeU1DNXZjbWN5CkxtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVGeENpYlFaM1ZhYXoKTWhsMCtHWXEwdWxISG1OZllUVUhtMWxwUFBXK0JZcXRkMHltb1NsS1NoRjZCSmczbE8vTGZVeTBGd3RGRzlmQQp5MG1FbENZczBLTk5NRXN3RGdZRFZSMFBBUUgvQkFRREFnZUFNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqCkJDUXdJb0FnSWZkMlR2eDE3UUZNQmdpUnpJNFMxUURtYU1PVzNjRWprY1JUZUZyakZYZ3dDZ1lJS29aSXpqMEUKQXdJRFJ3QXdSQUlnUm5uaUh3WG8zSzY2YzRldFk0NmFJeEFzTUVERXB6S0NJSnVyalpKSWJ0Z0NJQVYvMjFSYgpEWGRINFNlS00wQnlmUXphYzJhekJXVkJOZXF1a3kyeVUyankKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQoSRjBEAiBlQtNg6834s7zf1dILDMLwjFSoiBmserjHtOijWR0MrgIgEZRizs6ZRkyxVpyh6GdYcyXSN0JXSY3eRnkqMnZxn9IS/QYKsgYKB09yZzFNU1ASpgYtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ0p6Q0NBYzZnQXdJQkFnSVFaQlorUG9yV3RGL2JWSmQ0TXVnN1FEQUtCZ2dxaGtqT1BRUURBakJ6TVFzdwpDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTlUyRnVJRVp5CllXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NUzVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFQXhNVFkyRXUKYjNKbk1TNWxlR0Z0Y0d4bExtTnZiVEFlRncweU1ERXlNak14TURRMk1EQmFGdzB6TURFeU1qRXhNRFEyTURCYQpNR294Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhFdzFUCllXNGdSbkpoYm1OcGMyTnZNUTB3Q3dZRFZRUUxFd1J3WldWeU1SOHdIUVlEVlFRREV4WndaV1Z5TUM1dmNtY3gKTG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRWJKbllxRytTWFNQQwpCeWJ5bEg2eDZjTnB3YTI3ZWFpWEdYNno3bjRqaGdJTWo2M1N3Q3FaZTE1YnlXS1BLNGNmNHJyM2xJejIxOVl6ClZRNU4yZTd3QktOTk1Fc3dEZ1lEVlIwUEFRSC9CQVFEQWdlQU1Bd0dBMVVkRXdFQi93UUNNQUF3S3dZRFZSMGoKQkNRd0lvQWczdUliMlRQRkFjR2JUNEQyVUE5aC91QktHWTYwTHF1WFRTNDkzdGVXdm1Zd0NnWUlLb1pJemowRQpBd0lEUndBd1JBSWdDWTREVlJ1Z2RkT2lpOWdKcUhDdVowdGxDQm5hYlhZUVN1Vml1YlR0dVhVQ0lBMjNoMFdaCkVnaSt2OHY4TlRpc3ZDYWdJN2xlODE5dmwvM2hFS1ZZWG5rMgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tChJGMEQCIBCUEarqSHqyb4OkXcvFbc7sDHR2WWDBs9NDYWM3zka0AiBVcc8u66K7u49nPa+OOIzM77g+u7bXg/EpowlvJUveuxJHMEUCIQCm+Oe/IcaKtZMVGyVEY+6gtS4YGZ0aZInbpS2IlujLmAIgAwx9fK5ipWI8hnUBDDig1NJtQFqh+VGjXCok2XsxxoQazQcKmwcKBAoCCAISkgcKxgYKqQYKCk9yZGVyZXJNU1ASmgYtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ0hUQ0NBY1NnQXdJQkFnSVFCSXFHUnBobG5ScUNzbUF2Q0lVVjFEQUtCZ2dxaGtqT1BRUURBakJwTVFzdwpDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTlUyRnVJRVp5CllXNWphWE5qYnpFVU1CSUdBMVVFQ2hNTFpYaGhiWEJzWlM1amIyMHhGekFWQmdOVkJBTVREbU5oTG1WNFlXMXcKYkdVdVkyOXRNQjRYRFRJd01USXlNekV3TkRZd01Gb1hEVE13TVRJeU1URXdORFl3TUZvd2FqRUxNQWtHQTFVRQpCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCR2NtRnVZMmx6ClkyOHhFREFPQmdOVkJBc1RCMjl5WkdWeVpYSXhIREFhQmdOVkJBTVRFMjl5WkdWeVpYSXVaWGhoYlhCc1pTNWoKYjIwd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFUREt4K2M3STFOR0VtdlNuUEdUUWpZR3dXaApLUzhIVjRoTFI3Qy8rMnBVZlJJdGU4WnhxbC9kam94b0IrS0J6N0dpd1ByNjBzQ1I3QnVQZWVIVitSNmtvMDB3ClN6QU9CZ05WSFE4QkFmOEVCQU1DQjRBd0RBWURWUjBUQVFIL0JBSXdBREFyQmdOVkhTTUVKREFpZ0NEVVI1cC8KWitLb0FsNCs1TnlIcWUwT2JjVCtaYWdaTHU0U3FsSEY5amhPYlRBS0JnZ3Foa2pPUFFRREFnTkhBREJFQWlBYgp4VC90akRWbzErdURoek5mb0xXaUttRFFXOW9rMUoyUnB6ZlhiUHcrYkFJZ2FCSTF4QWRQRUFtcmZac2gxMkRtCjVjQjdvdi9ZVU5aR0h1UkJ4MWZsd2FjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tChIYjp4IfdUNJbiZytbo1jNcB+QDTx+jhbTQEkcwRQIhANyNsvnLTepSAoGnHgvRtgtTYcl+cxseOcB0MqKY3v1tAiBRq6lxWAp/LVxdLNHP0xjcyPrmXOMrXbuDxUyNB6vLZQoECgIIAgoBAAoACiIKICcBZgr/1NCRyZxP1cqXq+H/6w+b4AaKZEvYRmB8Tu39"); + + public static final byte[] blockBytes = + Base64.getDecoder() + .decode( + "CkYIAhIgqRUgCxiSIqW1CV6Y/nZJNqOSSTGHBr+k8zNvwxbHXo4aIBfdZchdFnBFOOQfcUIgc5HmNCbP7S+9hDP3KIVnlIU7Es6gAQrKoAEK/Z8BCuAGChUIARoGCOD/j/8FIglteWNoYW5uZWwSxgYKqQYKCk9yZGVyZXJNU1ASmgYtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ0hUQ0NBY1NnQXdJQkFnSVFhVExwVXJDeFlxNm14Nml2ZXdCVVpUQUtCZ2dxaGtqT1BRUURBakJwTVFzdwpDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTlUyRnVJRVp5CllXNWphWE5qYnpFVU1CSUdBMVVFQ2hNTFpYaGhiWEJzWlM1amIyMHhGekFWQmdOVkJBTVREbU5oTG1WNFlXMXcKYkdVdVkyOXRNQjRYRFRJd01USXlOREF5TXpZd01Gb1hEVE13TVRJeU1qQXlNell3TUZvd2FqRUxNQWtHQTFVRQpCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCR2NtRnVZMmx6ClkyOHhFREFPQmdOVkJBc1RCMjl5WkdWeVpYSXhIREFhQmdOVkJBTVRFMjl5WkdWeVpYSXVaWGhoYlhCc1pTNWoKYjIwd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFRS0lMR2xPNVZMTmRyZS9nZ0NyZ2ltTlRpTApKRWxOalZGbVRXd2U2b3JqU1hlOFJUc3YreG8vSFZjd1d5QnVJcFVhVGN4Q3JmVG9MSTdManIxdUJCZW1vMDB3ClN6QU9CZ05WSFE4QkFmOEVCQU1DQjRBd0RBWURWUjBUQVFIL0JBSXdBREFyQmdOVkhTTUVKREFpZ0NCcWZLWTkKcXJTUUpRVUdjR1U0WnVQZVhRZG1wTWFwZzJ1VUZ3TXNtODgrbFRBS0JnZ3Foa2pPUFFRREFnTkhBREJFQWlBawpBSWRtWDF4TkpycEhhVzFyWkltaDVXa2RidXhpRmdQZmYxcFRMMFVHOFFJZ0RGZjRzK0RpSlpBU2xFMGRxcnI1CjN4aXhacDlDWU5KUnZobDl3Z0Q5WUNjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tChIYV8Zd3/rc9esZeuPNVLBQdoJ2tgkHJa39EpaZAQqqiAEIAxKkiAESuiwKB09yZGVyZXISriwS2ykKCk9yZGVyZXJPcmcSzCkaoSgKA01TUBKZKBKOKBKLKAoKT3JkZXJlck1TUBLHBi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlDUFRDQ0FlU2dBd0lCQWdJUkFJcnNyU1owNCszbDVFYjhKYmZqSTN3d0NnWUlLb1pJemowRUF3SXdhVEVMCk1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFZOaGJpQkcKY21GdVkybHpZMjh4RkRBU0JnTlZCQW9UQzJWNFlXMXdiR1V1WTI5dE1SY3dGUVlEVlFRREV3NWpZUzVsZUdGdApjR3hsTG1OdmJUQWVGdzB5TURFeU1qUXdNak0yTURCYUZ3MHpNREV5TWpJd01qTTJNREJhTUdreEN6QUpCZ05WCkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSEV3MVRZVzRnUm5KaGJtTnAKYzJOdk1SUXdFZ1lEVlFRS0V3dGxlR0Z0Y0d4bExtTnZiVEVYTUJVR0ExVUVBeE1PWTJFdVpYaGhiWEJzWlM1agpiMjB3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVQwS0VhT3IzZ3R0NzBLaXpUM2xnL0dUQnZOCjEwT3dkK01rV0g5bUw4SmZZbXk5b2FEYlBaaFZBNGxCVm91YVZaVnZ3SUlBeWJ1OGlHdnVodUl0ZWZzSm8yMHcKYXpBT0JnTlZIUThCQWY4RUJBTUNBYVl3SFFZRFZSMGxCQll3RkFZSUt3WUJCUVVIQXdJR0NDc0dBUVVGQndNQgpNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdLUVlEVlIwT0JDSUVJR3A4cGoycXRKQWxCUVp3WlRobTQ5NWRCMmFrCnhxbURhNVFYQXl5Ynp6NlZNQW9HQ0NxR1NNNDlCQU1DQTBjQU1FUUNJRGN6YXVJSGJYaDdHeXcxWXkxLytFZDEKZHFYZ2UzbVBqM3JkdURWR1NucUFBaUI4b0t0Y0VSditsV3dPVzg0YnlwcC83akVVQ3NnQ1B3S3lQM0ZCU0dJMwo2Zz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KQg4KBFNIQTISBlNIQTI1NkrLBi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlDUWpDQ0FlbWdBd0lCQWdJUWJxd0kzb0I5UzZUQy9QQXZMR3pFN2pBS0JnZ3Foa2pPUFFRREFqQnNNUXN3CkNRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNCTUtRMkZzYVdadmNtNXBZVEVXTUJRR0ExVUVCeE1OVTJGdUlFWnkKWVc1amFYTmpiekVVTUJJR0ExVUVDaE1MWlhoaGJYQnNaUzVqYjIweEdqQVlCZ05WQkFNVEVYUnNjMk5oTG1WNApZVzF3YkdVdVkyOXRNQjRYRFRJd01USXlOREF5TXpZd01Gb1hEVE13TVRJeU1qQXlNell3TUZvd2JERUxNQWtHCkExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCR2NtRnUKWTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJvd0dBWURWUVFERXhGMGJITmpZUzVsZUdGdApjR3hsTG1OdmJUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJFaGVSVm9TblhkNUJSSzdGTm50CmNhL0IzMXVRckY3RVdCcWd0T2VRSnk3V2tjaitUSGkrTzM4aWtTV1RjK0c1a2ZVLzYvcVV6Vjh0ZUhPTFgrMHcKOGl1amJUQnJNQTRHQTFVZER3RUIvd1FFQXdJQnBqQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBZ1lJS3dZQgpCUVVIQXdFd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBcEJnTlZIUTRFSWdRZ1Jldm51NHZhQTNTaHpsTWxSMlpyCmpGSnRDODk3Wmw0MVd3SjM1V3F6TTlzd0NnWUlLb1pJemowRUF3SURSd0F3UkFJZ0xzQzAveDliT1FWTEF1QUkKUE02VUJPTTRoaW1yM2RkTEw4c2U4S2xoVHVJQ0lGcUNidnE0ekJTNDJQU0FwWFFXS3NnMk5rOTdUeEtKNlUvZQp4RFYwTjcyUwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tClrUGggBEtIGCscGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNQVENDQWVTZ0F3SUJBZ0lSQUlyc3JTWjA0KzNsNUViOEpiZmpJM3d3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHlNREV5TWpRd01qTTJNREJhRncwek1ERXlNakl3TWpNMk1EQmFNR2t4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJRd0VnWURWUVFLRXd0bGVHRnRjR3hsTG1OdmJURVhNQlVHQTFVRUF4TU9ZMkV1WlhoaGJYQnNaUzVqCmIyMHdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVDBLRWFPcjNndHQ3MEtpelQzbGcvR1RCdk4KMTBPd2QrTWtXSDltTDhKZllteTlvYURiUFpoVkE0bEJWb3VhVlpWdndJSUF5YnU4aUd2dWh1SXRlZnNKbzIwdwphekFPQmdOVkhROEJBZjhFQkFNQ0FhWXdIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0lHQ0NzR0FRVUZCd01CCk1BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0tRWURWUjBPQkNJRUlHcDhwajJxdEpBbEJRWndaVGhtNDk1ZEIyYWsKeHFtRGE1UVhBeXlieno2Vk1Bb0dDQ3FHU000OUJBTUNBMGNBTUVRQ0lEY3phdUlIYlhoN0d5dzFZeTEvK0VkMQpkcVhnZTNtUGozcmR1RFZHU25xQUFpQjhvS3RjRVJ2K2xXd09XODRieXBwLzdqRVVDc2dDUHdLeVAzRkJTR0kzCjZnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQoSBmNsaWVudBrQBgrHBi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlDUFRDQ0FlU2dBd0lCQWdJUkFJcnNyU1owNCszbDVFYjhKYmZqSTN3d0NnWUlLb1pJemowRUF3SXdhVEVMCk1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFZOaGJpQkcKY21GdVkybHpZMjh4RkRBU0JnTlZCQW9UQzJWNFlXMXdiR1V1WTI5dE1SY3dGUVlEVlFRREV3NWpZUzVsZUdGdApjR3hsTG1OdmJUQWVGdzB5TURFeU1qUXdNak0yTURCYUZ3MHpNREV5TWpJd01qTTJNREJhTUdreEN6QUpCZ05WCkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSEV3MVRZVzRnUm5KaGJtTnAKYzJOdk1SUXdFZ1lEVlFRS0V3dGxlR0Z0Y0d4bExtTnZiVEVYTUJVR0ExVUVBeE1PWTJFdVpYaGhiWEJzWlM1agpiMjB3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVQwS0VhT3IzZ3R0NzBLaXpUM2xnL0dUQnZOCjEwT3dkK01rV0g5bUw4SmZZbXk5b2FEYlBaaFZBNGxCVm91YVZaVnZ3SUlBeWJ1OGlHdnVodUl0ZWZzSm8yMHcKYXpBT0JnTlZIUThCQWY4RUJBTUNBYVl3SFFZRFZSMGxCQll3RkFZSUt3WUJCUVVIQXdJR0NDc0dBUVVGQndNQgpNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdLUVlEVlIwT0JDSUVJR3A4cGoycXRKQWxCUVp3WlRobTQ5NWRCMmFrCnhxbURhNVFYQXl5Ynp6NlZNQW9HQ0NxR1NNNDlCQU1DQTBjQU1FUUNJRGN6YXVJSGJYaDdHeXcxWXkxLytFZDEKZHFYZ2UzbVBqM3JkdURWR1NucUFBaUI4b0t0Y0VSditsV3dPVzg0YnlwcC83akVVQ3NnQ1B3S3lQM0ZCU0dJMwo2Zz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KEgRwZWVyItEGCscGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNQVENDQWVTZ0F3SUJBZ0lSQUlyc3JTWjA0KzNsNUViOEpiZmpJM3d3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHlNREV5TWpRd01qTTJNREJhRncwek1ERXlNakl3TWpNMk1EQmFNR2t4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJRd0VnWURWUVFLRXd0bGVHRnRjR3hsTG1OdmJURVhNQlVHQTFVRUF4TU9ZMkV1WlhoaGJYQnNaUzVqCmIyMHdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVDBLRWFPcjNndHQ3MEtpelQzbGcvR1RCdk4KMTBPd2QrTWtXSDltTDhKZllteTlvYURiUFpoVkE0bEJWb3VhVlpWdndJSUF5YnU4aUd2dWh1SXRlZnNKbzIwdwphekFPQmdOVkhROEJBZjhFQkFNQ0FhWXdIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0lHQ0NzR0FRVUZCd01CCk1BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0tRWURWUjBPQkNJRUlHcDhwajJxdEpBbEJRWndaVGhtNDk1ZEIyYWsKeHFtRGE1UVhBeXlieno2Vk1Bb0dDQ3FHU000OUJBTUNBMGNBTUVRQ0lEY3phdUlIYlhoN0d5dzFZeTEvK0VkMQpkcVhnZTNtUGozcmR1RFZHU25xQUFpQjhvS3RjRVJ2K2xXd09XODRieXBwLzdqRVVDc2dDUHdLeVAzRkJTR0kzCjZnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQoSBWFkbWluKtMGCscGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNQVENDQWVTZ0F3SUJBZ0lSQUlyc3JTWjA0KzNsNUViOEpiZmpJM3d3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHlNREV5TWpRd01qTTJNREJhRncwek1ERXlNakl3TWpNMk1EQmFNR2t4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJRd0VnWURWUVFLRXd0bGVHRnRjR3hsTG1OdmJURVhNQlVHQTFVRUF4TU9ZMkV1WlhoaGJYQnNaUzVqCmIyMHdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVDBLRWFPcjNndHQ3MEtpelQzbGcvR1RCdk4KMTBPd2QrTWtXSDltTDhKZllteTlvYURiUFpoVkE0bEJWb3VhVlpWdndJSUF5YnU4aUd2dWh1SXRlZnNKbzIwdwphekFPQmdOVkhROEJBZjhFQkFNQ0FhWXdIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0lHQ0NzR0FRVUZCd01CCk1BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0tRWURWUjBPQkNJRUlHcDhwajJxdEpBbEJRWndaVGhtNDk1ZEIyYWsKeHFtRGE1UVhBeXlieno2Vk1Bb0dDQ3FHU000OUJBTUNBMGNBTUVRQ0lEY3phdUlIYlhoN0d5dzFZeTEvK0VkMQpkcVhnZTNtUGozcmR1RFZHU25xQUFpQjhvS3RjRVJ2K2xXd09XODRieXBwLzdqRVVDc2dDUHdLeVAzRkJTR0kzCjZnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQoSB29yZGVyZXIaBkFkbWlucyIzCgdSZWFkZXJzEigSHggBEhoSCBIGCAESAggAGg4SDAoKT3JkZXJlck1TUBoGQWRtaW5zIjMKB1dyaXRlcnMSKBIeCAESGhIIEgYIARICCAAaDhIMCgpPcmRlcmVyTVNQGgZBZG1pbnMiNAoGQWRtaW5zEioSIAgBEhwSCBIGCAESAggAGhASDgoKT3JkZXJlck1TUBABGgZBZG1pbnMqBkFkbWlucxofChNDaGFubmVsUmVzdHJpY3Rpb25zEggaBkFkbWlucxomCgxDYXBhYmlsaXRpZXMSFhIMCgoKBlYxXzRfMhIAGgZBZG1pbnMaIQoNQ29uc2Vuc3VzVHlwZRIQEgYKBHNvbG8aBkFkbWlucxoiCglCYXRjaFNpemUSFRILCAoQgIDAMRiAgCAaBkFkbWlucxoeCgxCYXRjaFRpbWVvdXQSDhIECgIycxoGQWRtaW5zIiIKB1JlYWRlcnMSFxINCAMSCQoHUmVhZGVycxoGQWRtaW5zIiIKB1dyaXRlcnMSFxINCAMSCQoHV3JpdGVycxoGQWRtaW5zIiIKBkFkbWlucxIYEg4IAxIKCgZBZG1pbnMQAhoGQWRtaW5zIioKD0Jsb2NrVmFsaWRhdGlvbhIXEg0IAxIJCgdXcml0ZXJzGgZBZG1pbnMqBkFkbWlucxL6WAoLQXBwbGljYXRpb24S6lgIARLXKwoHT3JnMk1TUBLLKwgBGrIpCgNNU1ASqikSnykSnCkKB09yZzJNU1AS3wYtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ1VUQ0NBZmVnQXdJQkFnSVFRR0ZpUU1ZeXhPU0wreTAyOFBEK0hEQUtCZ2dxaGtqT1BRUURBakJ6TVFzdwpDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTlUyRnVJRVp5CllXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFQXhNVFkyRXUKYjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEFlRncweU1ERXlNalF3TWpNMk1EQmFGdzB6TURFeU1qSXdNak0yTURCYQpNSE14Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhFdzFUCllXNGdSbkpoYm1OcGMyTnZNUmt3RndZRFZRUUtFeEJ2Y21jeUxtVjRZVzF3YkdVdVkyOXRNUnd3R2dZRFZRUUQKRXhOallTNXZjbWN5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRQpYQ1l0bGt6a0FaK0xCQmNxbnFlVUM0ZDhhUjgxVG5aRXU1Ulh4TCt3V3kwQmg1b0NGeDJmMzZtalVKekVSaU53Cm1wN0cwaE1kWU1vNHY3blhnNFROb0tOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkSlFRV01CUUcKQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWREZ1FpQkNBSQp1TXdlWUFzM2pNaUg4M3R0bFhOOFNPb2VVU3VQUlBpN2k0V1hUbDFJcHpBS0JnZ3Foa2pPUFFRREFnTklBREJGCkFpRUFvTzF1R1dveU00aXMraUJjSlpSOVRlZDhxdkpHMjFtRUU2UC91RGwxdU5ZQ0lEbmZaSzNHWFovOE9IRDkKbkd4ZEhYTSt2VEM1bjhoamZuNHF4a2JPNm1EbQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCkIOCgRTSEEyEgZTSEEyNTZK5wYtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ1ZqQ0NBZjJnQXdJQkFnSVFBUlRDUXBwWnl6UXU0OGplTnU0Z3N6QUtCZ2dxaGtqT1BRUURBakIyTVFzdwpDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTlUyRnVJRVp5CllXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFZk1CMEdBMVVFQXhNV2RHeHoKWTJFdWIzSm5NaTVsZUdGdGNHeGxMbU52YlRBZUZ3MHlNREV5TWpRd01qTTJNREJhRncwek1ERXlNakl3TWpNMgpNREJhTUhZeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFICkV3MVRZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3lMbVY0WVcxd2JHVXVZMjl0TVI4d0hRWUQKVlFRREV4WjBiSE5qWVM1dmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowRApBUWNEUWdBRXNxZlNhZGwvRSttSTU0RGpqeW1ZaloxTlZ4eU90L0JMd1I1Nk1LMk15TVZuMU0zQ011OTM1ejZICnU3N0ZxTkJhRGZaR0lVVGZ4NE16dEFyN3B0Z01jcU50TUdzd0RnWURWUjBQQVFIL0JBUURBZ0dtTUIwR0ExVWQKSlFRV01CUUdDQ3NHQVFVRkJ3TUNCZ2dyQmdFRkJRY0RBVEFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQ2tHQTFVZApEZ1FpQkNBbWlsTmt1MVlNZVJ4dkFXTEN5N2NWOHpQakZFTXlwb1VLZndTWmdmUi82VEFLQmdncWhrak9QUVFECkFnTkhBREJFQWlCbytYZEJCR2RHMGdLZ2ludGQ0UW5GN3NodEJJMVRaQ083TjZSZk1mODczZ0lnV2Fwc3lhR1kKRlExOHZHUm50S3hyQXRJaE5CdzhpOFR0S2dzeEp5RHVqOEU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KWrQbCAES6gYK3wYtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ1VUQ0NBZmVnQXdJQkFnSVFRR0ZpUU1ZeXhPU0wreTAyOFBEK0hEQUtCZ2dxaGtqT1BRUURBakJ6TVFzdwpDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTlUyRnVJRVp5CllXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFQXhNVFkyRXUKYjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEFlRncweU1ERXlNalF3TWpNMk1EQmFGdzB6TURFeU1qSXdNak0yTURCYQpNSE14Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhFdzFUCllXNGdSbkpoYm1OcGMyTnZNUmt3RndZRFZRUUtFeEJ2Y21jeUxtVjRZVzF3YkdVdVkyOXRNUnd3R2dZRFZRUUQKRXhOallTNXZjbWN5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRQpYQ1l0bGt6a0FaK0xCQmNxbnFlVUM0ZDhhUjgxVG5aRXU1Ulh4TCt3V3kwQmg1b0NGeDJmMzZtalVKekVSaU53Cm1wN0cwaE1kWU1vNHY3blhnNFROb0tOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkSlFRV01CUUcKQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWREZ1FpQkNBSQp1TXdlWUFzM2pNaUg4M3R0bFhOOFNPb2VVU3VQUlBpN2k0V1hUbDFJcHpBS0JnZ3Foa2pPUFFRREFnTklBREJGCkFpRUFvTzF1R1dveU00aXMraUJjSlpSOVRlZDhxdkpHMjFtRUU2UC91RGwxdU5ZQ0lEbmZaSzNHWFovOE9IRDkKbkd4ZEhYTSt2VEM1bjhoamZuNHF4a2JPNm1EbQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tChIGY2xpZW50GugGCt8GLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVVENDQWZlZ0F3SUJBZ0lRUUdGaVFNWXl4T1NMK3kwMjhQRCtIREFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NaTVsZUdGdGNHeGxMbU52YlRBZUZ3MHlNREV5TWpRd01qTTJNREJhRncwek1ERXlNakl3TWpNMk1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3lMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeUxtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKWENZdGxremtBWitMQkJjcW5xZVVDNGQ4YVI4MVRuWkV1NVJYeEwrd1d5MEJoNW9DRngyZjM2bWpVSnpFUmlOdwptcDdHMGhNZFlNbzR2N25YZzRUTm9LTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQUkKdU13ZVlBczNqTWlIODN0dGxYTjhTT29lVVN1UFJQaTdpNFdYVGwxSXB6QUtCZ2dxaGtqT1BRUURBZ05JQURCRgpBaUVBb08xdUdXb3lNNGlzK2lCY0paUjlUZWQ4cXZKRzIxbUVFNlAvdURsMXVOWUNJRG5mWkszR1haLzhPSEQ5Cm5HeGRIWE0rdlRDNW44aGpmbjRxeGtiTzZtRG0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQoSBHBlZXIi6QYK3wYtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ1VUQ0NBZmVnQXdJQkFnSVFRR0ZpUU1ZeXhPU0wreTAyOFBEK0hEQUtCZ2dxaGtqT1BRUURBakJ6TVFzdwpDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTlUyRnVJRVp5CllXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFQXhNVFkyRXUKYjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEFlRncweU1ERXlNalF3TWpNMk1EQmFGdzB6TURFeU1qSXdNak0yTURCYQpNSE14Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhFdzFUCllXNGdSbkpoYm1OcGMyTnZNUmt3RndZRFZRUUtFeEJ2Y21jeUxtVjRZVzF3YkdVdVkyOXRNUnd3R2dZRFZRUUQKRXhOallTNXZjbWN5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRQpYQ1l0bGt6a0FaK0xCQmNxbnFlVUM0ZDhhUjgxVG5aRXU1Ulh4TCt3V3kwQmg1b0NGeDJmMzZtalVKekVSaU53Cm1wN0cwaE1kWU1vNHY3blhnNFROb0tOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkSlFRV01CUUcKQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWREZ1FpQkNBSQp1TXdlWUFzM2pNaUg4M3R0bFhOOFNPb2VVU3VQUlBpN2k0V1hUbDFJcHpBS0JnZ3Foa2pPUFFRREFnTklBREJGCkFpRUFvTzF1R1dveU00aXMraUJjSlpSOVRlZDhxdkpHMjFtRUU2UC91RGwxdU5ZQ0lEbmZaSzNHWFovOE9IRDkKbkd4ZEhYTSt2VEM1bjhoamZuNHF4a2JPNm1EbQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tChIFYWRtaW4q6wYK3wYtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ1VUQ0NBZmVnQXdJQkFnSVFRR0ZpUU1ZeXhPU0wreTAyOFBEK0hEQUtCZ2dxaGtqT1BRUURBakJ6TVFzdwpDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTlUyRnVJRVp5CllXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFQXhNVFkyRXUKYjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEFlRncweU1ERXlNalF3TWpNMk1EQmFGdzB6TURFeU1qSXdNak0yTURCYQpNSE14Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhFdzFUCllXNGdSbkpoYm1OcGMyTnZNUmt3RndZRFZRUUtFeEJ2Y21jeUxtVjRZVzF3YkdVdVkyOXRNUnd3R2dZRFZRUUQKRXhOallTNXZjbWN5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRQpYQ1l0bGt6a0FaK0xCQmNxbnFlVUM0ZDhhUjgxVG5aRXU1Ulh4TCt3V3kwQmg1b0NGeDJmMzZtalVKekVSaU53Cm1wN0cwaE1kWU1vNHY3blhnNFROb0tOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkSlFRV01CUUcKQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWREZ1FpQkNBSQp1TXdlWUFzM2pNaUg4M3R0bFhOOFNPb2VVU3VQUlBpN2k0V1hUbDFJcHpBS0JnZ3Foa2pPUFFRREFnTklBREJGCkFpRUFvTzF1R1dveU00aXMraUJjSlpSOVRlZDhxdkpHMjFtRUU2UC91RGwxdU5ZQ0lEbmZaSzNHWFovOE9IRDkKbkd4ZEhYTSt2VEM1bjhoamZuNHF4a2JPNm1EbQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tChIHb3JkZXJlchoGQWRtaW5zGjYKC0FuY2hvclBlZXJzEicSHQobChZwZWVyMC5vcmcyLmV4YW1wbGUuY29tENtGGgZBZG1pbnMiMQoGQWRtaW5zEicSHQgBEhkSCBIGCAESAggAGg0SCwoHT3JnMk1TUBABGgZBZG1pbnMiWAoHUmVhZGVycxJNEkMIARI/EhASDggBEgIIABICCAESAggCGg0SCwoHT3JnMk1TUBABGg0SCwoHT3JnMk1TUBADGg0SCwoHT3JnMk1TUBACGgZBZG1pbnMiRQoHV3JpdGVycxI6EjAIARIsEgwSCggBEgIIABICCAEaDRILCgdPcmcyTVNQEAEaDRILCgdPcmcyTVNQEAIaBkFkbWlucyoGQWRtaW5zEu8rCgdPcmcxTVNQEuMrCAEayikKA01TUBLCKRK3KRK0KQoHT3JnMU1TUBLjBi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlDVWpDQ0FmaWdBd0lCQWdJUkFORU9rK1JtUWl2L0tERGJodk1uVnNzd0NnWUlLb1pJemowRUF3SXdjekVMCk1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFZOaGJpQkcKY21GdVkybHpZMjh4R1RBWEJnTlZCQW9URUc5eVp6RXVaWGhoYlhCc1pTNWpiMjB4SERBYUJnTlZCQU1URTJOaApMbTl5WnpFdVpYaGhiWEJzWlM1amIyMHdIaGNOTWpBeE1qSTBNREl6TmpBd1doY05NekF4TWpJeU1ESXpOakF3CldqQnpNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU4KVTJGdUlFWnlZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRQpBeE1UWTJFdWIzSm5NUzVsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBCkJGWE1FZFl6Um9qcC92NWdpMDZ5NDNlVEpHYjBWcWtSVXlud3JiS0N3OGgvR3gxMUNtRW9TaVBpVGNXWGMxWVYKRUV5ZjFCbnpUSkFrckFVOTlXSmdDMHFqYlRCck1BNEdBMVVkRHdFQi93UUVBd0lCcGpBZEJnTlZIU1VFRmpBVQpCZ2dyQmdFRkJRY0RBZ1lJS3dZQkJRVUhBd0V3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFwQmdOVkhRNEVJZ1FnCkNiWXdvSHNKS2VoeGdSUUlTT0lkaWVQTHBLM1JoK3JBbHIxT1FYcUZOcWd3Q2dZSUtvWkl6ajBFQXdJRFNBQXcKUlFJaEFPSUtlRHBFUTVuWWNNanloQnpmZ1NPRU0rMkp1WEx2Mm51cTlvSElDK0xLQWlCNUlvUW1DMEViSkpoWQpqczFKZ3hVclZiYU8vV2ZXRW1CM25wb0IrcndxYmc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCkIOCgRTSEEyEgZTSEEyNTZK6wYtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ1dEQ0NBZjZnQXdJQkFnSVJBTWNWTHdWWVZaci9hUU9NV0JJTlIxUXdDZ1lJS29aSXpqMEVBd0l3ZGpFTApNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdUQ2tOaGJHbG1iM0p1YVdFeEZqQVVCZ05WQkFjVERWTmhiaUJHCmNtRnVZMmx6WTI4eEdUQVhCZ05WQkFvVEVHOXlaekV1WlhoaGJYQnNaUzVqYjIweEh6QWRCZ05WQkFNVEZuUnMKYzJOaExtOXlaekV1WlhoaGJYQnNaUzVqYjIwd0hoY05NakF4TWpJME1ESXpOakF3V2hjTk16QXhNakl5TURJegpOakF3V2pCMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFCkJ4TU5VMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NUzVsZUdGdGNHeGxMbU52YlRFZk1CMEcKQTFVRUF4TVdkR3h6WTJFdWIzSm5NUzVsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OQpBd0VIQTBJQUJHdFJUVG5BM0VSNTBNZFVweERwYjRIalpCTXY0aFpjSmN2R0dTeExVSllxUjZmSkQ3VnRTSXR1CkNtT1BGbEhTejZuWUQzWEo5U0RxalN5NFMwanUybGlqYlRCck1BNEdBMVVkRHdFQi93UUVBd0lCcGpBZEJnTlYKSFNVRUZqQVVCZ2dyQmdFRkJRY0RBZ1lJS3dZQkJRVUhBd0V3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFwQmdOVgpIUTRFSWdRZzlSUnViLzFXcWZtRG1lbHlQYy9TaURlV2RRU0Z5c0hubTkvYU1WcktIK1F3Q2dZSUtvWkl6ajBFCkF3SURTQUF3UlFJaEFJN1BCZFZ6RmIzOHJoSXhxQmRsZTdQTVZaaWkvV3NMS0ZIektTWGFmc0VRQWlCMnJoS1gKOFliQnZFYUllWkdWUVBYSFc2SlBzNkpVMmNRMTRUQXQwbytxd1E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tClrEGwgBEu4GCuMGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU5FT2srUm1RaXYvS0REYmh2TW5Wc3N3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpFdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekV1WlhoaGJYQnNaUzVqYjIwd0hoY05NakF4TWpJME1ESXpOakF3V2hjTk16QXhNakl5TURJek5qQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NUzVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1TNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkZYTUVkWXpSb2pwL3Y1Z2kwNnk0M2VUSkdiMFZxa1JVeW53cmJLQ3c4aC9HeDExQ21Fb1NpUGlUY1dYYzFZVgpFRXlmMUJuelRKQWtyQVU5OVdKZ0MwcWpiVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKQ2JZd29Ic0pLZWh4Z1JRSVNPSWRpZVBMcEszUmgrckFscjFPUVhxRk5xZ3dDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQU9JS2VEcEVRNW5ZY01qeWhCemZnU09FTSsySnVYTHYybnVxOW9ISUMrTEtBaUI1SW9RbUMwRWJKSmhZCmpzMUpneFVyVmJhTy9XZldFbUIzbnBvQityd3FiZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KEgZjbGllbnQa7AYK4wYtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ1VqQ0NBZmlnQXdJQkFnSVJBTkVPaytSbVFpdi9LRERiaHZNblZzc3dDZ1lJS29aSXpqMEVBd0l3Y3pFTApNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdUQ2tOaGJHbG1iM0p1YVdFeEZqQVVCZ05WQkFjVERWTmhiaUJHCmNtRnVZMmx6WTI4eEdUQVhCZ05WQkFvVEVHOXlaekV1WlhoaGJYQnNaUzVqYjIweEhEQWFCZ05WQkFNVEUyTmgKTG05eVp6RXVaWGhoYlhCc1pTNWpiMjB3SGhjTk1qQXhNakkwTURJek5qQXdXaGNOTXpBeE1qSXlNREl6TmpBdwpXakJ6TVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNCTUtRMkZzYVdadmNtNXBZVEVXTUJRR0ExVUVCeE1OClUyRnVJRVp5WVc1amFYTmpiekVaTUJjR0ExVUVDaE1RYjNKbk1TNWxlR0Z0Y0d4bExtTnZiVEVjTUJvR0ExVUUKQXhNVFkyRXViM0puTVM1bGVHRnRjR3hsTG1OdmJUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQQpCRlhNRWRZelJvanAvdjVnaTA2eTQzZVRKR2IwVnFrUlV5bndyYktDdzhoL0d4MTFDbUVvU2lQaVRjV1hjMVlWCkVFeWYxQm56VEpBa3JBVTk5V0pnQzBxamJUQnJNQTRHQTFVZER3RUIvd1FFQXdJQnBqQWRCZ05WSFNVRUZqQVUKQmdnckJnRUZCUWNEQWdZSUt3WUJCUVVIQXdFd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBcEJnTlZIUTRFSWdRZwpDYll3b0hzSktlaHhnUlFJU09JZGllUExwSzNSaCtyQWxyMU9RWHFGTnFnd0NnWUlLb1pJemowRUF3SURTQUF3ClJRSWhBT0lLZURwRVE1blljTWp5aEJ6ZmdTT0VNKzJKdVhMdjJudXE5b0hJQytMS0FpQjVJb1FtQzBFYkpKaFkKanMxSmd4VXJWYmFPL1dmV0VtQjNucG9CK3J3cWJnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQoSBHBlZXIi7QYK4wYtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ1VqQ0NBZmlnQXdJQkFnSVJBTkVPaytSbVFpdi9LRERiaHZNblZzc3dDZ1lJS29aSXpqMEVBd0l3Y3pFTApNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdUQ2tOaGJHbG1iM0p1YVdFeEZqQVVCZ05WQkFjVERWTmhiaUJHCmNtRnVZMmx6WTI4eEdUQVhCZ05WQkFvVEVHOXlaekV1WlhoaGJYQnNaUzVqYjIweEhEQWFCZ05WQkFNVEUyTmgKTG05eVp6RXVaWGhoYlhCc1pTNWpiMjB3SGhjTk1qQXhNakkwTURJek5qQXdXaGNOTXpBeE1qSXlNREl6TmpBdwpXakJ6TVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNCTUtRMkZzYVdadmNtNXBZVEVXTUJRR0ExVUVCeE1OClUyRnVJRVp5WVc1amFYTmpiekVaTUJjR0ExVUVDaE1RYjNKbk1TNWxlR0Z0Y0d4bExtTnZiVEVjTUJvR0ExVUUKQXhNVFkyRXViM0puTVM1bGVHRnRjR3hsTG1OdmJUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQQpCRlhNRWRZelJvanAvdjVnaTA2eTQzZVRKR2IwVnFrUlV5bndyYktDdzhoL0d4MTFDbUVvU2lQaVRjV1hjMVlWCkVFeWYxQm56VEpBa3JBVTk5V0pnQzBxamJUQnJNQTRHQTFVZER3RUIvd1FFQXdJQnBqQWRCZ05WSFNVRUZqQVUKQmdnckJnRUZCUWNEQWdZSUt3WUJCUVVIQXdFd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBcEJnTlZIUTRFSWdRZwpDYll3b0hzSktlaHhnUlFJU09JZGllUExwSzNSaCtyQWxyMU9RWHFGTnFnd0NnWUlLb1pJemowRUF3SURTQUF3ClJRSWhBT0lLZURwRVE1blljTWp5aEJ6ZmdTT0VNKzJKdVhMdjJudXE5b0hJQytMS0FpQjVJb1FtQzBFYkpKaFkKanMxSmd4VXJWYmFPL1dmV0VtQjNucG9CK3J3cWJnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQoSBWFkbWluKu8GCuMGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU5FT2srUm1RaXYvS0REYmh2TW5Wc3N3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpFdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekV1WlhoaGJYQnNaUzVqYjIwd0hoY05NakF4TWpJME1ESXpOakF3V2hjTk16QXhNakl5TURJek5qQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NUzVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1TNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkZYTUVkWXpSb2pwL3Y1Z2kwNnk0M2VUSkdiMFZxa1JVeW53cmJLQ3c4aC9HeDExQ21Fb1NpUGlUY1dYYzFZVgpFRXlmMUJuelRKQWtyQVU5OVdKZ0MwcWpiVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKQ2JZd29Ic0pLZWh4Z1JRSVNPSWRpZVBMcEszUmgrckFscjFPUVhxRk5xZ3dDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQU9JS2VEcEVRNW5ZY01qeWhCemZnU09FTSsySnVYTHYybnVxOW9ISUMrTEtBaUI1SW9RbUMwRWJKSmhZCmpzMUpneFVyVmJhTy9XZldFbUIzbnBvQityd3FiZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KEgdvcmRlcmVyGgZBZG1pbnMaNgoLQW5jaG9yUGVlcnMSJxIdChsKFnBlZXIwLm9yZzEuZXhhbXBsZS5jb20QizcaBkFkbWlucyIxCgZBZG1pbnMSJxIdCAESGRIIEgYIARICCAAaDRILCgdPcmcxTVNQEAEaBkFkbWlucyJYCgdSZWFkZXJzEk0SQwgBEj8SEBIOCAESAggAEgIIARICCAIaDRILCgdPcmcxTVNQEAEaDRILCgdPcmcxTVNQEAMaDRILCgdPcmcxTVNQEAIaBkFkbWlucyJFCgdXcml0ZXJzEjoSMAgBEiwSDBIKCAESAggAEgIIARoNEgsKB09yZzFNU1AQARoNEgsKB09yZzFNU1AQAhoGQWRtaW5zKgZBZG1pbnMaJgoMQ2FwYWJpbGl0aWVzEhYSDAoKCgZWMV80XzISABoGQWRtaW5zIiIKB1JlYWRlcnMSFxINCAMSCQoHUmVhZGVycxoGQWRtaW5zIiIKB1dyaXRlcnMSFxINCAMSCQoHV3JpdGVycxoGQWRtaW5zIiIKBkFkbWlucxIYEg4IAxIKCgZBZG1pbnMQAhoGQWRtaW5zKgZBZG1pbnMaSQoQT3JkZXJlckFkZHJlc3NlcxI1EhoKGG9yZGVyZXIuZXhhbXBsZS5jb206NzA1MBoXL0NoYW5uZWwvT3JkZXJlci9BZG1pbnMaJgoMQ2FwYWJpbGl0aWVzEhYSDAoKCgZWMV80XzMSABoGQWRtaW5zGiYKEEhhc2hpbmdBbGdvcml0aG0SEhIICgZTSEEyNTYaBkFkbWlucxotChlCbG9ja0RhdGFIYXNoaW5nU3RydWN0dXJlEhASBgj/////DxoGQWRtaW5zGioKCkNvbnNvcnRpdW0SHBISChBTYW1wbGVDb25zb3J0aXVtGgZBZG1pbnMiIgoGQWRtaW5zEhgSDggDEgoKBkFkbWlucxACGgZBZG1pbnMiIgoHUmVhZGVycxIXEg0IAxIJCgdSZWFkZXJzGgZBZG1pbnMiIgoHV3JpdGVycxIXEg0IAxIJCgdXcml0ZXJzGgZBZG1pbnMqBkFkbWlucxLlEAqaEArtBgoVCAIaBgjg/4//BSIJbXljaGFubmVsEtMGCrYGCgdPcmcyTVNQEqoGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLRENDQWMrZ0F3SUJBZ0lRV0pDdVV5bmhoQTNyMTJvRkxEOTVkekFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NaTVsZUdGdGNHeGxMbU52YlRBZUZ3MHlNREV5TWpRd01qTTJNREJhRncwek1ERXlNakl3TWpNMk1EQmEKTUdzeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVE0d0RBWURWUVFMRXdWaFpHMXBiakVmTUIwR0ExVUVBd3dXUVdSdGFXNUFiM0puCk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCUEQ2eEFMRVRYZjUKNVZpQXFmNkFnUWUvMzUwREl5ZS9qbkU1K0o1eTNaWUM1Z1luL3FyUjVwWmVEa09Mc3BEdUVqTExWMy8xcUxvMwpaYWFJcVNWNlFiYWpUVEJMTUE0R0ExVWREd0VCL3dRRUF3SUhnREFNQmdOVkhSTUJBZjhFQWpBQU1Dc0dBMVVkCkl3UWtNQ0tBSUFpNHpCNWdDemVNeUlmemUyMlZjM3hJNmg1Uks0OUUrTHVMaFpkT1hVaW5NQW9HQ0NxR1NNNDkKQkFNQ0EwY0FNRVFDSUdGTjdrYXZCN1NNWmZ6YmxmRkYrS0kvVUJlT3NBVkRiL3lZYmlEWHRPQ2dBaUFES2MrbgpaeGt5eXBxU1ZqNzhXOGRKd2JSREErd3pCZTVrOE9HRzl2UjY1dz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KEhg4yV4UQkA8lW8aD7KtOBRKoqmsIYICsgESpwkKggIKCW15Y2hhbm5lbBJXElUKC0FwcGxpY2F0aW9uEkYIARI6CgdPcmcyTVNQEi8aBwoDTVNQEgAiCwoHV3JpdGVycxIAIgoKBkFkbWlucxIAIgsKB1JlYWRlcnMSACoGQWRtaW5zGpsBEpgBCgtBcHBsaWNhdGlvbhKIAQgBEnwKB09yZzJNU1AScQgBGgcKA01TUBIAGjYKC0FuY2hvclBlZXJzEicSHQobChZwZWVyMC5vcmcyLmV4YW1wbGUuY29tENtGGgZBZG1pbnMiCgoGQWRtaW5zEgAiCwoHUmVhZGVycxIAIgsKB1dyaXRlcnMSACoGQWRtaW5zKgZBZG1pbnMSnwcK0wYKtgYKB09yZzJNU1ASqgYtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ0tEQ0NBYytnQXdJQkFnSVFXSkN1VXluaGhBM3IxMm9GTEQ5NWR6QUtCZ2dxaGtqT1BRUURBakJ6TVFzdwpDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTlUyRnVJRVp5CllXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFQXhNVFkyRXUKYjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEFlRncweU1ERXlNalF3TWpNMk1EQmFGdzB6TURFeU1qSXdNak0yTURCYQpNR3N4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhFdzFUCllXNGdSbkpoYm1OcGMyTnZNUTR3REFZRFZRUUxFd1ZoWkcxcGJqRWZNQjBHQTFVRUF3d1dRV1J0YVc1QWIzSm4KTWk1bGVHRnRjR3hsTG1OdmJUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJQRDZ4QUxFVFhmNQo1VmlBcWY2QWdRZS8zNTBESXllL2puRTUrSjV5M1pZQzVnWW4vcXJSNXBaZURrT0xzcER1RWpMTFYzLzFxTG8zClphYUlxU1Y2UWJhalRUQkxNQTRHQTFVZER3RUIvd1FFQXdJSGdEQU1CZ05WSFJNQkFmOEVBakFBTUNzR0ExVWQKSXdRa01DS0FJQWk0ekI1Z0N6ZU15SWZ6ZTIyVmMzeEk2aDVSSzQ5RStMdUxoWmRPWFVpbk1Bb0dDQ3FHU000OQpCQU1DQTBjQU1FUUNJR0ZON2thdkI3U01aZnpibGZGRitLSS9VQmVPc0FWRGIveVliaURYdE9DZ0FpQURLYytuClp4a3l5cHFTVmo3OFc4ZEp3YlJEQSt3ekJlNWs4T0dHOXZSNjV3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQoSGI32VqPpyWO3ni3BsBYRrBZJYbuYFnondBJHMEUCIQDZ0X9mPpXNn7qlxtZ+AkOkWjDvHt4W6KkVzRtdiPuY3AIgfPVbskDCC1a+aP5rDsbdS/oISZCrN+u/B+6XBBjS6W0SRjBEAiBiETe3vd6P4cdD7GCSvtn4jpF+x4lCjYsmgozQwI7/ggIgVHU2YfK/+sbCCM4B+GCqc4S7W52aRFgdvCm7yZ9Hwg0SRzBFAiEAzLE//naPPDgQIGqbZYB+zrKehnvYkWjztIPI+dalWswCIE++a4rK8qyq9WK5Pa2DO9ai52KVv1J9FEQ3x5QeJ1DlGs0HCpsHCgQKAggCEpIHCsYGCqkGCgpPcmRlcmVyTVNQEpoGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNIVENDQWNTZ0F3SUJBZ0lRYVRMcFVyQ3hZcTZteDZpdmV3QlVaVEFLQmdncWhrak9QUVFEQWpCcE1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVVNQklHQTFVRUNoTUxaWGhoYlhCc1pTNWpiMjB4RnpBVkJnTlZCQU1URG1OaExtVjRZVzF3CmJHVXVZMjl0TUI0WERUSXdNVEl5TkRBeU16WXdNRm9YRFRNd01USXlNakF5TXpZd01Gb3dhakVMTUFrR0ExVUUKQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFZOaGJpQkdjbUZ1WTJsegpZMjh4RURBT0JnTlZCQXNUQjI5eVpHVnlaWEl4SERBYUJnTlZCQU1URTI5eVpHVnlaWEl1WlhoaGJYQnNaUzVqCmIyMHdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBUUtJTEdsTzVWTE5kcmUvZ2dDcmdpbU5UaUwKSkVsTmpWRm1UV3dlNm9yalNYZThSVHN2K3hvL0hWY3dXeUJ1SXBVYVRjeENyZlRvTEk3TGpyMXVCQmVtbzAwdwpTekFPQmdOVkhROEJBZjhFQkFNQ0I0QXdEQVlEVlIwVEFRSC9CQUl3QURBckJnTlZIU01FSkRBaWdDQnFmS1k5CnFyU1FKUVVHY0dVNFp1UGVYUWRtcE1hcGcydVVGd01zbTg4K2xUQUtCZ2dxaGtqT1BRUURBZ05IQURCRUFpQWsKQUlkbVgxeE5KcnBIYVcxclpJbWg1V2tkYnV4aUZnUGZmMXBUTDBVRzhRSWdERmY0cytEaUpaQVNsRTBkcXJyNQozeGl4WnA5Q1lOSlJ2aGw5d2dEOVlDYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQoSGGEmyym3OZsko+PQdQZpgdlruZ9XBckoKBJHMEUCIQCzGkCYrYGwNY3iqxmxBeTB2FO/M2utuLnhQHCyNTZd0gIgQi37bOff6VJuf36aYX6IWDgtjrwxHgPhBeZFANBgkBAKBAoCCAIKAQAKAAoiCiBfiLYUB7FJpIQTQz9GcMRlMeXEqP69wzmpU2/4cWpVng=="); + + @Test + public void encodeSystemTest() throws Exception { + FabricBlock block = FabricBlock.encode(systemBlockBytes); + block.getValidTxs(); + Assert.assertTrue(true); // if no throw + } + + @Test + public void verifySystemTest() throws Exception { + FabricBlock block = FabricBlock.encode(systemBlockBytes); + boolean ok = block.verify(null); + + Assert.assertTrue(ok); + } + + @Test + public void encodeTest() throws Exception { + FabricBlock block = FabricBlock.encode(blockBytes); + block.getValidTxs(); + Assert.assertTrue(true); // if no throw + } + + @Test + public void verifyTest() throws Exception { + FabricBlock block = FabricBlock.encode(blockBytes); + boolean ok = block.verify(null); + + Assert.assertTrue(ok); + } +} From 93d65715972e3178107b5d8c4f7243f6efef8e90 Mon Sep 17 00:00:00 2001 From: jimmyshi <417711026@qq.com> Date: Mon, 28 Dec 2020 11:30:09 +0800 Subject: [PATCH 3/8] (block verify): Verify orderer signature (#115) * Verify orderer signature * format code --- .../wecross/stub/fabric/FabricBlock.java | 74 ++++++++++++++++--- .../wecross/stub/fabric/FabricDriver.java | 2 +- .../wecross/stub/fabric/FabricBlockTest.java | 4 +- 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java b/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java index fe9bd16..335272f 100644 --- a/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java +++ b/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java @@ -182,6 +182,11 @@ public byte[] getTransactionFilter() { return metadata.getMetadata(Common.BlockMetadataIndex.TRANSACTIONS_FILTER_VALUE) .toByteArray(); } + + public Common.Metadata getBlockSignatures() throws Exception { + return Common.Metadata.parseFrom( + metadata.getMetadata(Common.BlockMetadataIndex.SIGNATURES_VALUE)); + } } public static String calculateBlockHash(Common.Block block) { @@ -210,7 +215,54 @@ public String toString() { return block.toString(); } - public boolean verify(Collection endorsers) { + public boolean verify(Collection endorsers, Collection orderers) { + if (!verifyBlockCreator(orderers)) { + logger.warn("Verify creator in block {} failed.", header.getNumber()); + return false; + } + + if (!verifyTransactions(endorsers)) { + logger.warn("Verify transaction in block {} failed.", header.getNumber()); + return false; + } + + return true; + } + + public boolean verifyBlockCreator(Collection orderers) { + try { + Common.Metadata metadata = metaData.getBlockSignatures(); + ByteString signData = metadata.getValue(); + + // It seems that this metadata has no relation with block number or hash + + for (Common.MetadataSignature metadataSignature : metadata.getSignaturesList()) { + byte[] signBytes = metadataSignature.getSignature().toByteArray(); + ByteString ordererCertifcate = + Common.SignatureHeader.parseFrom(metadataSignature.getSignatureHeader()) + .getCreator(); + + // TODO: verify orderers + /* + if (orderers.contains(ordererCertifcate)) { + + } + */ + + if (verifySignature(ordererCertifcate, signBytes, signData.toByteArray())) { + return false; + } + } + return true; + + } catch (Exception e) { + logger.warn("Verify block creator exception: ", e); + return false; + } + } + + // Verify every transaction's endorsement + public boolean verifyTransactions(Collection endorsers) { try { byte[] txFilter = metaData.getTransactionFilter(); @@ -265,28 +317,28 @@ public boolean verify(Collection endorsers) { byte[] signBytes = endorsement.getSignature().toByteArray(); byte[] data = plainText.toByteArray(); - // verify endorser signature - if (!verifyEndorsement(endorserCertifcate, signBytes, data)) { - return false; - } - - // verify endorser + // TODO: verify endorser /* if (endorsers.contains(endorserCertifcate)) { } */ + + // verify endorser signature + if (!verifySignature(endorserCertifcate, signBytes, data)) { + return false; + } } } } return true; } catch (Exception e) { - logger.error("verify block failed: ", e); + logger.warn("verify block txs failed: ", e); return false; } } - private boolean verifyEndorsement(ByteString identity, byte[] signBytes, byte[] data) { + private boolean verifySignature(ByteString identity, byte[] signBytes, byte[] data) { try { ByteArrayInputStream bis = new ByteArrayInputStream(identity.toByteArray()); CertificateFactory cf = CertificateFactory.getInstance("X.509"); @@ -298,7 +350,7 @@ private boolean verifyEndorsement(ByteString identity, byte[] signBytes, byte[] boolean ok = signer.verify(signBytes); logger.debug( - "verifyEndorsement: {}, identity: {}, signBytes:{}, data: {} ", + "verifySignature: {}, identity: {}, signBytes:{}, data: {} ", ok, identity.toStringUtf8(), Arrays.toString(signBytes), @@ -306,7 +358,7 @@ private boolean verifyEndorsement(ByteString identity, byte[] signBytes, byte[] return ok; } catch (Exception e) { - logger.error("verifyEndorsement exception: ", e); + logger.error("verifySignature exception: ", e); return false; } } diff --git a/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java b/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java index bf597e0..9e897db 100644 --- a/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java +++ b/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java @@ -399,7 +399,7 @@ public void asyncGetBlock( List transactionsHashes = new ArrayList<>(); try { fabricBlock = FabricBlock.encode(response.getData()); - if (!fabricBlock.verify(null)) { + if (!fabricBlock.verify(null, null)) { /* logger.warn( "block {} verify failed: {}", diff --git a/src/test/java/com/webank/wecross/stub/fabric/FabricBlockTest.java b/src/test/java/com/webank/wecross/stub/fabric/FabricBlockTest.java index 4a1549c..2df76fe 100644 --- a/src/test/java/com/webank/wecross/stub/fabric/FabricBlockTest.java +++ b/src/test/java/com/webank/wecross/stub/fabric/FabricBlockTest.java @@ -26,7 +26,7 @@ public void encodeSystemTest() throws Exception { @Test public void verifySystemTest() throws Exception { FabricBlock block = FabricBlock.encode(systemBlockBytes); - boolean ok = block.verify(null); + boolean ok = block.verify(null, null); Assert.assertTrue(ok); } @@ -41,7 +41,7 @@ public void encodeTest() throws Exception { @Test public void verifyTest() throws Exception { FabricBlock block = FabricBlock.encode(blockBytes); - boolean ok = block.verify(null); + boolean ok = block.verify(null, null); Assert.assertTrue(ok); } From a3c9ecc1c182d7055787ff31e000484fad7a12fe Mon Sep 17 00:00:00 2001 From: jimmyshi <417711026@qq.com> Date: Tue, 29 Dec 2020 20:56:53 +0800 Subject: [PATCH 4/8] disable verify orderer signature logic (#116) (cherry picked from commit 2e8f528e9378ad484e9b61daacd21585880b59c6) --- .../wecross/stub/fabric/FabricBlock.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java b/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java index 335272f..bab33cd 100644 --- a/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java +++ b/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java @@ -232,15 +232,19 @@ public boolean verify(Collection endorsers, Collection orderers) public boolean verifyBlockCreator(Collection orderers) { try { Common.Metadata metadata = metaData.getBlockSignatures(); - ByteString signData = metadata.getValue(); - - // It seems that this metadata has no relation with block number or hash for (Common.MetadataSignature metadataSignature : metadata.getSignaturesList()) { + byte[] signBytes = metadataSignature.getSignature().toByteArray(); - ByteString ordererCertifcate = - Common.SignatureHeader.parseFrom(metadataSignature.getSignatureHeader()) - .getCreator(); + Common.SignatureHeader header = + Common.SignatureHeader.parseFrom(metadataSignature.getSignatureHeader()); + Identities.SerializedIdentity serializedIdentity = + Identities.SerializedIdentity.parseFrom(header.getCreator()); + + ByteString plainText = + metadata.getValue() + .concat(metadataSignature.getSignatureHeader()) + .concat(block.getHeader().toByteString()); // TODO: verify orderers /* @@ -249,9 +253,12 @@ public boolean verifyBlockCreator(Collection orderers) { } */ - if (verifySignature(ordererCertifcate, signBytes, signData.toByteArray())) { + // TODO: support to verify orderer signature according with config block + /* + if (!verifySignature(serializedIdentity.getIdBytes(), signBytes, plainText.toByteArray())) { return false; } + //*/ } return true; @@ -358,7 +365,7 @@ private boolean verifySignature(ByteString identity, byte[] signBytes, byte[] da return ok; } catch (Exception e) { - logger.error("verifySignature exception: ", e); + logger.error("verifySignature in block exception: ", e); return false; } } From 58973d01f5b81b87d13712778ab5a83bbf45851c Mon Sep 17 00:00:00 2001 From: Kyon <32325790+kyonRay@users.noreply.github.com> Date: Fri, 26 Feb 2021 09:59:49 +0800 Subject: [PATCH 5/8] (Verifier): move fabric verifier logic from router. (#127) * (Verifier): move fabric verifier logic from router. * (UT): fix ut bug. * (Verifier): perf the form of util function. --- .../com/webank/wecross/common/FabricType.java | 1 + .../wecross/stub/fabric/FabricBlock.java | 41 +++++++++++++--- .../wecross/stub/fabric/FabricDriver.java | 2 +- .../com/webank/wecross/utils/FabricUtils.java | 49 +++++++++++++++++++ 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/webank/wecross/common/FabricType.java b/src/main/java/com/webank/wecross/common/FabricType.java index ea15fa6..8277a71 100644 --- a/src/main/java/com/webank/wecross/common/FabricType.java +++ b/src/main/java/com/webank/wecross/common/FabricType.java @@ -2,6 +2,7 @@ public class FabricType { public static final String STUB_NAME = "Fabric1.4"; + public static final String FABRIC_VERIFIER = "VERIFIER"; public static final class Account { public static final String FABRIC_ACCOUNT = STUB_NAME; diff --git a/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java b/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java index 09a71fb..9b5117a 100644 --- a/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java +++ b/src/main/java/com/webank/wecross/stub/fabric/FabricBlock.java @@ -7,6 +7,7 @@ import com.webank.wecross.exception.WeCrossException; import com.webank.wecross.stub.BlockHeader; import com.webank.wecross.stub.ObjectMapperFactory; +import com.webank.wecross.utils.FabricUtils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -24,6 +25,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.regex.Pattern; import org.apache.commons.codec.binary.Hex; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.DEROctetString; @@ -233,16 +235,29 @@ public boolean verify(String blockVerifierString) { getMapperInVerifierString(blockVerifierString, "ordererCA"); Map endorserCAMap = getMapperInVerifierString(blockVerifierString, "endorserCA"); - if (ordererCAMap == null && endorserCAMap == null) { + if (ordererCAMap == null + || endorserCAMap == null + || endorserCAMap.size() == 0 + || ordererCAMap.size() == 0) { logger.error( "Did not full config Fabric Block Verifier, will skip block verification on what field didn't config."); return false; } - if (ordererCAMap != null && !verifyBlockCreator(ordererCAMap)) { + // CAMap: => + ordererCAMap = FabricUtils.readFileInMap(ordererCAMap); + endorserCAMap = FabricUtils.readFileInMap(endorserCAMap); + if (logger.isDebugEnabled()) { + ObjectMapper objectMapper = new ObjectMapper(); + logger.debug( + "ordererCA:{}, endorserCA:{}", + objectMapper.writeValueAsString(ordererCAMap), + objectMapper.writeValueAsString(endorserCAMap)); + } + if (!verifyBlockCreator(ordererCAMap)) { logger.warn("Verify creator in block {} failed.", header.getNumber()); return false; } - if (endorserCAMap != null && !verifyTransactions(endorserCAMap)) { + if (!verifyTransactions(endorserCAMap)) { logger.warn("Verify transaction in block {} failed.", header.getNumber()); return false; } @@ -253,6 +268,9 @@ public boolean verify(String blockVerifierString) { e.getMessage(), e.getCause()); return false; + } catch (Exception e) { + logger.error("Verify block error, error: ", e); + return false; } return true; } @@ -284,7 +302,7 @@ public boolean verifyBlockCreator(Map ordererCAs) { String mspId = serializedIdentity.getMspid(); if (ordererCAs.containsKey(mspId) && checkCert( - ordererCAs.get(mspId).getBytes(), + ordererCAs.get(mspId), serializedIdentity.getIdBytes().toByteArray())) { if (!verifySignature( serializedIdentity.getIdBytes(), signBytes, plainText.toByteArray())) { @@ -383,7 +401,7 @@ public boolean verifyTransactions(Map endorserCAs) { // verify endorser certificate if (endorserCAs.containsKey(mspId) && checkCert( - endorserCAs.get(mspId).getBytes(), + endorserCAs.get(mspId), endorserCertificate.toByteArray())) { if (!verifySignature( endorserCertificate, signBytes, plainText.toByteArray())) { @@ -433,13 +451,20 @@ private boolean verifySignature(ByteString identity, byte[] signBytes, byte[] da } } - private boolean checkCert(byte[] caCert, byte[] cert) { - ByteArrayInputStream CAByteStream = new ByteArrayInputStream(caCert); + private boolean checkCert(String caCert, byte[] cert) throws WeCrossException { + if (!Pattern.compile(FabricUtils.CERT_PATTERN, Pattern.MULTILINE) + .matcher(caCert) + .matches()) { + throw new WeCrossException( + WeCrossException.ErrorCode.UNEXPECTED_CONFIG, + "Fabric cert pattern matches error, please check."); + } + ByteArrayInputStream caByteStream = new ByteArrayInputStream(caCert.getBytes()); ByteArrayInputStream certByteStrean = new ByteArrayInputStream(cert); CertificateFactory cf = null; try { cf = CertificateFactory.getInstance("X.509"); - X509Certificate caCertificate = (X509Certificate) cf.generateCertificate(CAByteStream); + X509Certificate caCertificate = (X509Certificate) cf.generateCertificate(caByteStream); X509Certificate certificate = (X509Certificate) cf.generateCertificate(certByteStrean); PublicKey caKey = caCertificate.getPublicKey(); certificate.verify(caKey); diff --git a/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java b/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java index f7cddd3..c269e36 100644 --- a/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java +++ b/src/main/java/com/webank/wecross/stub/fabric/FabricDriver.java @@ -387,7 +387,7 @@ public void asyncGetBlock( Request request = new Request(); request.setType(FabricType.ConnectionMessage.FABRIC_GET_BLOCK); request.setData(numberBytes); - String blockVerifierString = connection.getProperties().get("VERIFIER"); + String blockVerifierString = connection.getProperties().get(FabricType.FABRIC_VERIFIER); connection.asyncSend( request, response -> { diff --git a/src/main/java/com/webank/wecross/utils/FabricUtils.java b/src/main/java/com/webank/wecross/utils/FabricUtils.java index 187c996..ac56f8f 100644 --- a/src/main/java/com/webank/wecross/utils/FabricUtils.java +++ b/src/main/java/com/webank/wecross/utils/FabricUtils.java @@ -1,6 +1,7 @@ package com.webank.wecross.utils; import com.moandjiezana.toml.Toml; +import com.webank.wecross.exception.WeCrossException; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileWriter; @@ -11,7 +12,9 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Base64; +import java.util.HashMap; import java.util.Map; +import java.util.regex.Pattern; import org.hyperledger.fabric.sdk.ChaincodeEndorsementPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,6 +22,15 @@ public class FabricUtils { + public static final String CERT_PATTERN = + "-+BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+" + + // Header + "([A-Za-z0-9+/=\\r\\n]+)" + + // Base64 text + "-+END\\s+.*CERTIFICATE[^-]*-+\n"; // Footer + + private static final Map fileContentMap = new HashMap<>(); + public static byte[] longToBytes(long number) { BigInteger bigInteger = BigInteger.valueOf(number); @@ -68,6 +80,43 @@ public static String readFileContent(String fileName) throws Exception { } } + public static Map readFileInMap(Map map) + throws WeCrossException { + if (map == null) return null; + // map: key => filePath + // resultMap: key => fileContent + Map resultMap = new HashMap<>(); + for (Map.Entry entry : map.entrySet()) { + if (Pattern.compile(FabricUtils.CERT_PATTERN, Pattern.MULTILINE) + .matcher(entry.getValue()) + .matches()) { + resultMap.put(entry.getKey(), entry.getValue()); + continue; + } + if (!fileIsExists(entry.getValue())) { + String errorMessage = "File: " + entry.getValue() + " is not exists"; + throw new WeCrossException(WeCrossException.ErrorCode.DIR_NOT_EXISTS, errorMessage); + } + String fileContent; + try { + // fileContentMap cache file content + if (fileContentMap.containsKey(entry.getValue()) + && fileContentMap.get(entry.getValue()) != null) { + fileContent = fileContentMap.get(entry.getValue()); + } else { + fileContent = readFileContent(entry.getValue()); + fileContentMap.put(entry.getValue(), fileContent); + } + resultMap.put(entry.getKey(), fileContent); + } catch (Exception e) { + throw new WeCrossException( + WeCrossException.ErrorCode.DIR_NOT_EXISTS, + "Read Cert fail: " + entry.getKey() + entry.getValue()); + } + } + return resultMap; + } + public static Toml readToml(String fileName) throws Exception { return new Toml().read(readFileContent(fileName)); } From 6c7d470e7a9e7506ff189500d7e0ea68fbbabc8a Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Tue, 16 Mar 2021 15:05:56 +0800 Subject: [PATCH 6/8] Update version --- build.gradle | 2 +- release_note.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 80e0192..1a26949 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ apply plugin: 'java' apply plugin: 'jacoco' apply plugin: 'com.github.johnrengelman.shadow' group 'com.webank.wecross' -version '1.1.0' +version '1.1.1' sourceCompatibility = 1.8 diff --git a/release_note.txt b/release_note.txt index 795460f..56130fb 100644 --- a/release_note.txt +++ b/release_note.txt @@ -1 +1 @@ -v1.1.0 +v1.1.1 From 642355f58bb06f63a4fc8580c19089adfbadb012 Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Mon, 29 Mar 2021 14:36:43 +0800 Subject: [PATCH 7/8] Update changelog --- Changelog.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index bbb7810..bbb836d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,11 @@ +### v1.1.1 + +(2021-04-02) + +**更改** + +* 优化区块头验证代码结构 + ### v1.1.0 (2021-02-02) @@ -22,7 +30,7 @@ * 发交易新增交易号UUID用于去重 * 不允许call事务中的资源 * 优化部分接口命名与定义 - + ### v1.0.0-rc4 (2020-08-05) From 8c5f0fd62f85c1c63b16a127497bb10952a0568b Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Mon, 29 Mar 2021 16:11:26 +0800 Subject: [PATCH 8/8] fix ci problem --- .../wecross/stub/fabric/FabricConnectionFactoryTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/webank/wecross/stub/fabric/FabricConnectionFactoryTest.java b/src/test/java/com/webank/wecross/stub/fabric/FabricConnectionFactoryTest.java index 27cb0dd..338f339 100644 --- a/src/test/java/com/webank/wecross/stub/fabric/FabricConnectionFactoryTest.java +++ b/src/test/java/com/webank/wecross/stub/fabric/FabricConnectionFactoryTest.java @@ -14,7 +14,8 @@ public void buildTest() throws Exception { fabricConnection.start(); List resourceInfoList = fabricConnection.getResources(); - Assert.assertTrue(resourceInfoList.size() > 1); + System.out.println(resourceInfoList.toString()); + Assert.assertTrue(resourceInfoList.size() > 0); } @Test