From 2acd74dbfda3364aa3a44e287e58d12806214ca3 Mon Sep 17 00:00:00 2001 From: Toporin Date: Mon, 4 May 2020 20:44:12 +0200 Subject: [PATCH 1/6] Patch for https://github.com/Toporin/SatochipApplet/issues/4 Fixes Toporin/SatochipApplet#4 (missing casting during compilation) --- src/org/satochip/applet/CardEdge.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/satochip/applet/CardEdge.java b/src/org/satochip/applet/CardEdge.java index e1fbf8e..af02cac 100644 --- a/src/org/satochip/applet/CardEdge.java +++ b/src/org/satochip/applet/CardEdge.java @@ -1503,7 +1503,7 @@ private void getBIP32ExtendedKey(APDU apdu, byte[] buffer){ byte bip32_depth = buffer[ISO7816.OFFSET_P1]; if ((bip32_depth < 0) || (bip32_depth > MAX_BIP32_DEPTH) ) ISOException.throwIt(SW_INCORRECT_P1); - if (bytesLeft < 4*bip32_depth) + if (bytesLeft < (short)(4*bip32_depth)) ISOException.throwIt(SW_INVALID_PARAMETER); // P2 option flags @@ -1819,7 +1819,7 @@ else if (bytesLeft==(short)4){ offset+=4; byte altcoinSize= buffer[offset]; offset++; - if (bytesLeft!=(5+altcoinSize)) + if (bytesLeft!=(short)(5+altcoinSize)) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); recvBuffer[0]= (byte) (altcoinSize+17); Util.arrayCopyNonAtomic(buffer, offset, recvBuffer, (short)1, (short)altcoinSize); From 1ca1f468b7c5a089c81eb7acb6bc52709b09759d Mon Sep 17 00:00:00 2001 From: Toporin Date: Mon, 4 May 2020 21:22:44 +0200 Subject: [PATCH 2/6] Patch for https://github.com/Toporin/SatochipApplet/issues/6 Fixes Toporin/SatochipApplet#6 (non-standard APDU response that violates ISO7816) --- src/org/satochip/applet/CardEdge.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/satochip/applet/CardEdge.java b/src/org/satochip/applet/CardEdge.java index af02cac..ee88b79 100644 --- a/src/org/satochip/applet/CardEdge.java +++ b/src/org/satochip/applet/CardEdge.java @@ -820,7 +820,7 @@ private void ImportKey(APDU apdu, byte[] buffer) { bytesLeft--; short key_size = Util.getShort(buffer, dataOffset); if (key_size != LENGTH_EC_FP_256) - ISOException.throwIt(key_size); + ISOException.throwIt(SW_INVALID_PARAMETER ); dataOffset += (short) 2; // Skip Key Size bytesLeft -= (short) 2; dataOffset += (short) 6; // Skip ACL (deprecated) From 65636a614cd38b8b664144dc5329d133aeb6ba4a Mon Sep 17 00:00:00 2001 From: Toporin Date: Wed, 6 May 2020 21:11:37 +0200 Subject: [PATCH 3/6] Satochip applet v0.10-0.2: support for native sha512 --- src/org/satochip/applet/CardEdge.java | 3 ++- src/org/satochip/applet/HmacSha512.java | 33 +++++++++++++++++++------ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/org/satochip/applet/CardEdge.java b/src/org/satochip/applet/CardEdge.java index ee88b79..6ca93d2 100644 --- a/src/org/satochip/applet/CardEdge.java +++ b/src/org/satochip/applet/CardEdge.java @@ -95,6 +95,7 @@ public class CardEdge extends javacard.framework.Applet implements ExtendedLengt // 0.8-0.1: add APDUs to reset the seed/eckey/2FA. 2FA required to sign tx/msg and reset seed/eckey/2FA. 2FA can only be disabled when all privkeys are cleared. // 0.9-0.1: Message signing for altcoin. // 0.10-0.1: add method SignTransactionHash() + // 0.10-0.2: support for native sha512 private final static byte PROTOCOL_MAJOR_VERSION = (byte) 0; private final static byte PROTOCOL_MINOR_VERSION = (byte) 10; private final static byte APPLET_MAJOR_VERSION = (byte) 0; @@ -667,7 +668,7 @@ private void setup(APDU apdu, byte[] buffer) { aes128= Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, false); // HD wallet - Sha512.init(); + //Sha512.init(); HmacSha512.init(tmpBuffer); //EccComputation.init(tmpBuffer); //debug diff --git a/src/org/satochip/applet/HmacSha512.java b/src/org/satochip/applet/HmacSha512.java index 4d8942a..30e5c83 100644 --- a/src/org/satochip/applet/HmacSha512.java +++ b/src/org/satochip/applet/HmacSha512.java @@ -23,6 +23,8 @@ import javacard.framework.ISOException; import javacard.framework.JCSystem; import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.MessageDigest; // very limited Hmac-SHA512 implementation public class HmacSha512 { @@ -33,9 +35,20 @@ public class HmacSha512 { private static final short SW_UNSUPPORTED_MSGSIZE = (short) 0x9c0F; private static byte[] data; + private static MessageDigest sha512; + private static boolean nativeSha512= false; public static void init(byte[] tmp){ data= tmp; + + try { + sha512 = MessageDigest.getInstance(MessageDigest.ALG_SHA_512, false); + nativeSha512= true; + } catch (CryptoException e) { + ISOException.throwIt((short)0x9C05);// debug + nativeSha512= false; + Sha512.init(); + } } public static short computeHmacSha512(byte[] key, short key_offset, short key_length, @@ -46,7 +59,7 @@ public static short computeHmacSha512(byte[] key, short key_offset, short key_le ISOException.throwIt(SW_UNSUPPORTED_KEYSIZE); // don't accept keys bigger than block size } if (message_length>HASHSIZE || message_length<0){ - ISOException.throwIt(SW_UNSUPPORTED_MSGSIZE); // don't accept messsage bigger than block size (should be sufficient for BIP32) + ISOException.throwIt(SW_UNSUPPORTED_MSGSIZE); // don't accept message bigger than block size (should be sufficient for BIP32) } // compute inner hash @@ -55,9 +68,12 @@ public static short computeHmacSha512(byte[] key, short key_offset, short key_le } Util.arrayFillNonAtomic(data, key_length, (short)(BLOCKSIZE-key_length), (byte)0x36); Util.arrayCopyNonAtomic(message, message_offset, data, BLOCKSIZE, message_length); - //Sha512.reset(); - //Sha512.doFinal(data, (short)0, (short)(BLOCKSIZE+message_length), data, BLOCKSIZE); // copy hash result to data buffer! - Sha512.resetUpdateDoFinal(data, (short)0, (short)(BLOCKSIZE+message_length), data, BLOCKSIZE); // copy hash result to data buffer! + if (nativeSha512){ + sha512.reset(); + sha512.doFinal(data, (short)0, (short)(BLOCKSIZE+message_length), data, BLOCKSIZE); // copy hash result to data buffer! + } else{ + Sha512.resetUpdateDoFinal(data, (short)0, (short)(BLOCKSIZE+message_length), data, BLOCKSIZE); // copy hash result to data buffer! + } // compute outer hash for (short i=0; i Date: Thu, 7 May 2020 23:16:37 +0200 Subject: [PATCH 4/6] Satochip applet v0.10-0.3: ECkey recovery optimisation: support ALG_EC_SVDP_DH_PLAIN_XY Work In Progress! * Supports both native sha512 and java implementation for older cards. * Supports pubkey recovery using keyAgreement with ALG_EC_SVDP_DH_PLAIN_XY an ALG_EC_SVDP_DH_PLAIN for older cards. This version should be protocol-compatible with previous v0.10 releases. --- src/org/satochip/applet/CardEdge.java | 138 +++++++++++++++++------- src/org/satochip/applet/HmacSha512.java | 2 +- 2 files changed, 98 insertions(+), 42 deletions(-) diff --git a/src/org/satochip/applet/CardEdge.java b/src/org/satochip/applet/CardEdge.java index 6ca93d2..c74db79 100644 --- a/src/org/satochip/applet/CardEdge.java +++ b/src/org/satochip/applet/CardEdge.java @@ -59,6 +59,7 @@ import javacard.security.ECPrivateKey; import javacard.security.ECPublicKey; //import javacard.security.HMACKey; +import javacard.security.CryptoException; import javacard.security.Key; import javacard.security.KeyAgreement; import javacard.security.KeyBuilder; @@ -96,6 +97,7 @@ public class CardEdge extends javacard.framework.Applet implements ExtendedLengt // 0.9-0.1: Message signing for altcoin. // 0.10-0.1: add method SignTransactionHash() // 0.10-0.2: support for native sha512 + // 0.10-0.3: ECkey recovery optimisation: support ALG_EC_SVDP_DH_PLAIN_XY private final static byte PROTOCOL_MAJOR_VERSION = (byte) 0; private final static byte PROTOCOL_MINOR_VERSION = (byte) 10; private final static byte APPLET_MAJOR_VERSION = (byte) 0; @@ -234,6 +236,7 @@ public class CardEdge extends javacard.framework.Applet implements ExtendedLengt // JC API 2.2.2 does not define these constants: private final static byte ALG_ECDSA_SHA_256= (byte) 33; private final static byte ALG_EC_SVDP_DH_PLAIN= (byte) 3; //https://javacard.kenai.com/javadocs/connected/javacard/security/KeyAgreement.html#ALG_EC_SVDP_DH_PLAIN + private final static byte ALG_EC_SVDP_DH_PLAIN_XY= (byte) 6; //https://docs.oracle.com/javacard/3.0.5/api/javacard/security/KeyAgreement.html#ALG_EC_SVDP_DH_PLAIN_XY private final static short LENGTH_EC_FP_256= (short) 256; /**************************************** @@ -264,6 +267,7 @@ public class CardEdge extends javacard.framework.Applet implements ExtendedLengt // shared cryptographic objects private RandomData randomData; private KeyAgreement keyAgreement; + private boolean isKeyAgreementXY= false; private Signature sigECDSA; private Cipher aes128; @@ -663,7 +667,14 @@ private void setup(APDU apdu, byte[] buffer) { } // shared cryptographic objects - keyAgreement = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN, false); + try { + keyAgreement = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN_XY, false); + isKeyAgreementXY= true; + } catch (CryptoException e) { + ISOException.throwIt((short)0x9C05);// debug: ensure that we use ALG_EC_SVDP_DH_PLAIN_XY + keyAgreement = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN, false); + isKeyAgreementXY= false; + } sigECDSA= Signature.getInstance(ALG_ECDSA_SHA_256, false); aes128= Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, false); @@ -857,7 +868,12 @@ private void ImportKey(APDU apdu, byte[] buffer) { tmpkey.setS(buffer, dataOffset, blob_size); // compute the corresponding partial public key... keyAgreement.init(tmpkey); - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, (short)0); // compute x coordinate of public key as k*G + if (isKeyAgreementXY){ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, tmpBuffer, (short)0); //pubkey in uncompressed form + Util.arrayCopy(tmpBuffer, (short)1, recvBuffer, (short)0, (short)32); + }else{ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, (short)0); // compute x coordinate of public key as k*G + } // hmac of 64-bytes msg: (pubkey-x | 32bytes (0x10^key_nb)-padding) Util.arrayFillNonAtomic(recvBuffer, (short)32, (short)32, (byte)(0x10^key_nb)); HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64); @@ -907,8 +923,13 @@ private void ResetKey(APDU apdu, byte[] buffer) { // compute the corresponding partial public key... keyAgreement.init((ECPrivateKey)key); - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, (short)0); // compute x coordinate of public key as k*G - // hmac of 64-bytes msg: (pubkey-x | 32bytes (0x20^key_nb)-padding) + if (isKeyAgreementXY){ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, tmpBuffer, (short)0); //pubkey in uncompressed form + Util.arrayCopy(tmpBuffer, (short)1, recvBuffer, (short)0, (short)32); + }else{ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, (short)0); // compute x coordinate of public key as k*G + } + // hmac of 64-bytes msg: (pubkey-x | 32bytes (0x20^key_nb)-padding) Util.arrayFillNonAtomic(recvBuffer, (short)32, (short)32, (byte) (0x20^key_nb)); HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64); if (Util.arrayCompare(buffer, ISO7816.OFFSET_CDATA, recvBuffer, (short)64, (short)20)!=0) @@ -958,7 +979,12 @@ private void getPublicKeyFromPrivate(APDU apdu, byte[] buffer) { // compute the corresponding partial public key... keyAgreement.init((ECPrivateKey)key); - short coordx_size = keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)2); // compute x coordinate of public key as k*G + short coordx_size=(short)32; + if (isKeyAgreementXY){ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)1); //pubkey in uncompressed form + }else{ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)2); // compute x coordinate of public key as k*G + } Util.setShort(buffer, (short)0, coordx_size); // sign fixed message @@ -1292,8 +1318,15 @@ private void importBIP32Seed(APDU apdu, byte[] buffer){ // compute the partial authentikey public key... keyAgreement.init(bip32_authentikey); - authentikey_pubkey[0]=0x00; // 0x00 means coordy is not set (yet), otherwise 0x04 - short coordx_size = keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, authentikey_pubkey, (short)1); // compute x coordinate of public key as k*G + short coordx_size= (short)32; + if (isKeyAgreementXY){ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, authentikey_pubkey, (short)0); //pubkey in uncompressed form + Util.arrayCopy(tmpBuffer, (short)1, buffer, (short)2, (short)32); + }else{ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, authentikey_pubkey, (short)1); // compute x coordinate of public key as k*G + authentikey_pubkey[0]=0x00; // 0x00 means coordy is not set (yet), otherwise 0x04 + } + Util.setShort(buffer, (short)0, coordx_size); Util.arrayCopyNonAtomic(authentikey_pubkey, (short)1, buffer, (short)2, coordx_size); // self signed public key @@ -1393,8 +1426,13 @@ private void getBIP32AuthentiKey(APDU apdu, byte[] buffer){ ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED); // compute the partial authentikey public key... - keyAgreement.init(bip32_authentikey); - short coordx_size = keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)2); // compute x coordinate of public key as k*G + keyAgreement.init(bip32_authentikey); + short coordx_size= (short)32; + if (isKeyAgreementXY){ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)1); //pubkey in uncompressed form + }else{ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)2); // compute x coordinate of public key as k*G + } Util.setShort(buffer, (short)0, coordx_size); // self signed public key sigECDSA.init(bip32_authentikey, Signature.MODE_SIGN); @@ -1473,7 +1511,7 @@ private void setBIP32AuthentikeyPubkey(APDU apdu, byte[] buffer){ * The function computes the Bip32 extended key derived from the master key and returns the * x-coordinate of the public key signed by the authentikey. * Extended key is stored in the chip in a temporary EC key, along with corresponding ACL - * Extended key and chaincode is also cached as a Bip32 object is secure memory + * Extended key and chaincode is also cached as a Bip32 object in secure memory * * ins: 0x6D * p1: depth of the extended key (master is depth 0, m/i is depht 1). Max depth is 10 @@ -1552,42 +1590,55 @@ private void getBIP32ExtendedKey(APDU apdu, byte[] buffer){ // compute coord x from privkey bip32_extendedkey.setS(recvBuffer, BIP32_OFFSET_PARENT_KEY, BIP32_KEY_SIZE); keyAgreement.init(bip32_extendedkey); - short coordx_size= keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, BIP32_OFFSET_PUBX); - // compute compbyte from coord y if necessary (slow!) - if (compbyte==0x04 && (opts & 0x40)!=0x40){ - // coord y= square root of X^3+7 mod p => 2 solutions! - EccComputation.SqrtRootOpt(recvBuffer, BIP32_OFFSET_PUBX, recvBuffer, BIP32_OFFSET_PUBY); - recvBuffer[BIP32_OFFSET_PUB]=0x04; - // sign a dummy message - sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN); - short sigsize=sigECDSA.sign(recvBuffer, (short)0, (short)32, buffer, BIP32_OFFSET_SIG); - // verify sig with pubkey (x,y) & recover compression byte - bip32_pubkey.setW(recvBuffer, BIP32_OFFSET_PUB, (short)(2*BIP32_KEY_SIZE+1)) ; - sigECDSA.init(bip32_pubkey, Signature.MODE_VERIFY); - boolean verify= sigECDSA.verify(recvBuffer, (short)0, (short)32, buffer, BIP32_OFFSET_SIG, sigsize); + // keyAgreement.generateSecret() recovers X and Y coordinates + if (isKeyAgreementXY){ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, BIP32_OFFSET_PUB); //pubkey in uncompressed form boolean parity= ((recvBuffer[(short)(BIP32_OFFSET_PUBY+31)]&0x01)==0); - compbyte= (verify^parity)?(byte)0x03:(byte)0x02; + compbyte= (parity)?(byte)0x02:(byte)0x03; //ORIGINAL: compbyte= (parity)?(byte)0x03:(byte)0x02; // save compbyte in parent's object for future use if (parent_base==Bip32ObjectManager.NULL_OFFSET) bip32_master_compbyte= compbyte; else - bip32_om.setByte(parent_base, (short)(BIP32_OBJECT_SIZE-1), compbyte);//bip32_mem.setByte(parent_base, (short)(BIP32_OBJECT_SIZE-1), compbyte);//debugOM - } - // compute compbyte from coord y externally (faster!) - if (compbyte==0x04 && (opts & 0x40)==0x40){ - // exit the for loop prematurely - // the data returned is related to the parent with non-hardened child - // we can then compute the coord-y externally - // save hash of parent path (or 000...0 if masterkey) - if (parent_base==Bip32ObjectManager.NULL_OFFSET){ - Util.arrayFillNonAtomic(buffer, (short)0, (short)32, (byte)0); - }else{ - sha256.reset(); - sha256.doFinal(recvBuffer, BIP32_OFFSET_PATH, (short)(4*(i-1)), buffer, (byte)0); - } - exit_early=(short)0x8000; - break; + bip32_om.setByte(parent_base, (short)(BIP32_OBJECT_SIZE-1), compbyte); + // keyAgreement.generateSecret() recovers X coordinate only + } else{ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, BIP32_OFFSET_PUBX); // compute x coordinate of public key as k*G + // compute compbyte from coord y internally (slow!) + if (compbyte==0x04 && (opts & 0x40)!=0x40){ + // coord y= square root of X^3+7 mod p => 2 solutions! + EccComputation.SqrtRootOpt(recvBuffer, BIP32_OFFSET_PUBX, recvBuffer, BIP32_OFFSET_PUBY); + recvBuffer[BIP32_OFFSET_PUB]=0x04; + // sign a dummy message + sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN); + short sigsize=sigECDSA.sign(recvBuffer, (short)0, (short)32, buffer, BIP32_OFFSET_SIG); + // verify sig with pubkey (x,y) & recover compression byte + bip32_pubkey.setW(recvBuffer, BIP32_OFFSET_PUB, (short)(2*BIP32_KEY_SIZE+1)) ; + sigECDSA.init(bip32_pubkey, Signature.MODE_VERIFY); + boolean verify= sigECDSA.verify(recvBuffer, (short)0, (short)32, buffer, BIP32_OFFSET_SIG, sigsize); + boolean parity= ((recvBuffer[(short)(BIP32_OFFSET_PUBY+31)]&0x01)==0); + compbyte= (verify^parity)?(byte)0x03:(byte)0x02; + // save compbyte in parent's object for future use + if (parent_base==Bip32ObjectManager.NULL_OFFSET) + bip32_master_compbyte= compbyte; + else + bip32_om.setByte(parent_base, (short)(BIP32_OBJECT_SIZE-1), compbyte);//bip32_mem.setByte(parent_base, (short)(BIP32_OBJECT_SIZE-1), compbyte);//debugOM + } + // compute compbyte from coord y externally (faster!) + if (compbyte==0x04 && (opts & 0x40)==0x40){ + // exit the for loop prematurely + // the data returned is related to the parent with non-hardened child + // we can then compute the coord-y externally + // save hash of parent path (or 000...0 if masterkey) + if (parent_base==Bip32ObjectManager.NULL_OFFSET){ + Util.arrayFillNonAtomic(buffer, (short)0, (short)32, (byte)0); + }else{ + sha256.reset(); + sha256.doFinal(recvBuffer, BIP32_OFFSET_PATH, (short)(4*(i-1)), buffer, (byte)0); + } + exit_early=(short)0x8000; + break; + } } // compute HMAC of compressed pubkey + index @@ -1660,7 +1711,12 @@ private void getBIP32ExtendedKey(APDU apdu, byte[] buffer){ // compute the corresponding partial public key... keyAgreement.init(bip32_extendedkey); - short coordx_size = keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)34); // compute x coordinate of public key as k*G + short coordx_size= (short)32; + if (isKeyAgreementXY){ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)33); //pubkey in uncompressed form + }else{ + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)34); // compute x coordinate of public key as k*G + } Util.setShort(buffer, BIP32_KEY_SIZE, (short)(coordx_size^exit_early)); //exit_early flag signals we want to compute pubkey externaly // self-sign coordx diff --git a/src/org/satochip/applet/HmacSha512.java b/src/org/satochip/applet/HmacSha512.java index 30e5c83..ab17f86 100644 --- a/src/org/satochip/applet/HmacSha512.java +++ b/src/org/satochip/applet/HmacSha512.java @@ -45,7 +45,7 @@ public static void init(byte[] tmp){ sha512 = MessageDigest.getInstance(MessageDigest.ALG_SHA_512, false); nativeSha512= true; } catch (CryptoException e) { - ISOException.throwIt((short)0x9C05);// debug + ISOException.throwIt((short)0x9C05);// debug: ensure that we use native sha512 nativeSha512= false; Sha512.init(); } From 07c0038cb87ba036529aae886ec7ab086569f070 Mon Sep 17 00:00:00 2001 From: Toporin Date: Sun, 10 May 2020 15:20:42 +0200 Subject: [PATCH 5/6] Satochip applet v0.10-0.4: Cleanup: optimised code only, removed legacy sha512 implementation and slow pubkey recovery * Supports only native sha512 (removed java implementation for older cards) * Supports pubkey recovery using keyAgreement with ALG_EC_SVDP_DH_PLAIN_XY (removed ALG_EC_SVDP_DH_PLAIN for older cards) This results in faster, simpler and cleaner code... This version should be protocol-compatible with previous v0.10 releases. --- src/org/satochip/applet/CardEdge.java | 239 +----- src/org/satochip/applet/EccComputation.java | 241 ------ src/org/satochip/applet/HmacSha512.java | 24 +- src/org/satochip/applet/Sha512.java | 267 ------- src/org/satochip/applet/Sha512.javap | 839 -------------------- src/org/satochip/applet/mcpp.exe | Bin 201728 -> 0 bytes src/org/satochip/applet/sha512_fast.javap | 634 --------------- 7 files changed, 41 insertions(+), 2203 deletions(-) delete mode 100644 src/org/satochip/applet/EccComputation.java delete mode 100644 src/org/satochip/applet/Sha512.java delete mode 100644 src/org/satochip/applet/Sha512.javap delete mode 100644 src/org/satochip/applet/mcpp.exe delete mode 100644 src/org/satochip/applet/sha512_fast.javap diff --git a/src/org/satochip/applet/CardEdge.java b/src/org/satochip/applet/CardEdge.java index c74db79..e3df5ed 100644 --- a/src/org/satochip/applet/CardEdge.java +++ b/src/org/satochip/applet/CardEdge.java @@ -98,10 +98,11 @@ public class CardEdge extends javacard.framework.Applet implements ExtendedLengt // 0.10-0.1: add method SignTransactionHash() // 0.10-0.2: support for native sha512 // 0.10-0.3: ECkey recovery optimisation: support ALG_EC_SVDP_DH_PLAIN_XY + // 0.10-0.4: Cleanup: optimised code only, removed legacy sha512 implementation and slow pubkey recovery private final static byte PROTOCOL_MAJOR_VERSION = (byte) 0; private final static byte PROTOCOL_MINOR_VERSION = (byte) 10; private final static byte APPLET_MAJOR_VERSION = (byte) 0; - private final static byte APPLET_MINOR_VERSION = (byte) 1; + private final static byte APPLET_MINOR_VERSION = (byte) 4; // Maximum number of keys handled by the Cardlet private final static byte MAX_NUM_KEYS = (byte) 16; @@ -267,7 +268,6 @@ public class CardEdge extends javacard.framework.Applet implements ExtendedLengt // shared cryptographic objects private RandomData randomData; private KeyAgreement keyAgreement; - private boolean isKeyAgreementXY= false; private Signature sigECDSA; private Cipher aes128; @@ -669,20 +669,15 @@ private void setup(APDU apdu, byte[] buffer) { // shared cryptographic objects try { keyAgreement = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN_XY, false); - isKeyAgreementXY= true; } catch (CryptoException e) { - ISOException.throwIt((short)0x9C05);// debug: ensure that we use ALG_EC_SVDP_DH_PLAIN_XY - keyAgreement = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN, false); - isKeyAgreementXY= false; + ISOException.throwIt(SW_UNSUPPORTED_FEATURE);// unsupported feature => use a more recent card! } sigECDSA= Signature.getInstance(ALG_ECDSA_SHA_256, false); aes128= Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, false); // HD wallet - //Sha512.init(); HmacSha512.init(tmpBuffer); - //EccComputation.init(tmpBuffer); //debug - + // bip32 material bip32_seeded= false; bip32_master_compbyte=0x04; @@ -868,13 +863,9 @@ private void ImportKey(APDU apdu, byte[] buffer) { tmpkey.setS(buffer, dataOffset, blob_size); // compute the corresponding partial public key... keyAgreement.init(tmpkey); - if (isKeyAgreementXY){ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, tmpBuffer, (short)0); //pubkey in uncompressed form - Util.arrayCopy(tmpBuffer, (short)1, recvBuffer, (short)0, (short)32); - }else{ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, (short)0); // compute x coordinate of public key as k*G - } - // hmac of 64-bytes msg: (pubkey-x | 32bytes (0x10^key_nb)-padding) + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, tmpBuffer, (short)0); //pubkey in uncompressed form + Util.arrayCopy(tmpBuffer, (short)1, recvBuffer, (short)0, (short)32); + // hmac of 64-bytes msg: (pubkey-x | 32bytes (0x10^key_nb)-padding) Util.arrayFillNonAtomic(recvBuffer, (short)32, (short)32, (byte)(0x10^key_nb)); HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64); if (Util.arrayCompare(buffer, (short)(dataOffset+blob_size), recvBuffer, (short)64, (short)20)!=0) @@ -923,12 +914,8 @@ private void ResetKey(APDU apdu, byte[] buffer) { // compute the corresponding partial public key... keyAgreement.init((ECPrivateKey)key); - if (isKeyAgreementXY){ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, tmpBuffer, (short)0); //pubkey in uncompressed form - Util.arrayCopy(tmpBuffer, (short)1, recvBuffer, (short)0, (short)32); - }else{ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, (short)0); // compute x coordinate of public key as k*G - } + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, tmpBuffer, (short)0); //pubkey in uncompressed form + Util.arrayCopy(tmpBuffer, (short)1, recvBuffer, (short)0, (short)32); // hmac of 64-bytes msg: (pubkey-x | 32bytes (0x20^key_nb)-padding) Util.arrayFillNonAtomic(recvBuffer, (short)32, (short)32, (byte) (0x20^key_nb)); HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64); @@ -980,12 +967,8 @@ private void getPublicKeyFromPrivate(APDU apdu, byte[] buffer) { // compute the corresponding partial public key... keyAgreement.init((ECPrivateKey)key); short coordx_size=(short)32; - if (isKeyAgreementXY){ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)1); //pubkey in uncompressed form - }else{ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)2); // compute x coordinate of public key as k*G - } - Util.setShort(buffer, (short)0, coordx_size); + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)1); //pubkey in uncompressed form + Util.setShort(buffer, (short)0, coordx_size); // sign fixed message sigECDSA.init(key, Signature.MODE_SIGN); @@ -1319,15 +1302,8 @@ private void importBIP32Seed(APDU apdu, byte[] buffer){ // compute the partial authentikey public key... keyAgreement.init(bip32_authentikey); short coordx_size= (short)32; - if (isKeyAgreementXY){ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, authentikey_pubkey, (short)0); //pubkey in uncompressed form - Util.arrayCopy(tmpBuffer, (short)1, buffer, (short)2, (short)32); - }else{ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, authentikey_pubkey, (short)1); // compute x coordinate of public key as k*G - authentikey_pubkey[0]=0x00; // 0x00 means coordy is not set (yet), otherwise 0x04 - } - - Util.setShort(buffer, (short)0, coordx_size); + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, authentikey_pubkey, (short)0); //pubkey in uncompressed form + Util.setShort(buffer, (short)0, coordx_size); Util.arrayCopyNonAtomic(authentikey_pubkey, (short)1, buffer, (short)2, coordx_size); // self signed public key sigECDSA.init(bip32_authentikey, Signature.MODE_SIGN); @@ -1428,12 +1404,8 @@ private void getBIP32AuthentiKey(APDU apdu, byte[] buffer){ // compute the partial authentikey public key... keyAgreement.init(bip32_authentikey); short coordx_size= (short)32; - if (isKeyAgreementXY){ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)1); //pubkey in uncompressed form - }else{ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)2); // compute x coordinate of public key as k*G - } - Util.setShort(buffer, (short)0, coordx_size); + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)1); //pubkey in uncompressed form + Util.setShort(buffer, (short)0, coordx_size); // self signed public key sigECDSA.init(bip32_authentikey, Signature.MODE_SIGN); short sign_size= sigECDSA.sign(buffer, (short)0, (short)(coordx_size+2), buffer, (short)(coordx_size+4)); @@ -1447,6 +1419,9 @@ private void getBIP32AuthentiKey(APDU apdu, byte[] buffer){ } /** + * DEPRECATED - Not necessary anymore when recovering the pubkey with ALG_EC_SVDP_DH_PLAIN_XY + * A minimalist API is maintained for backward compatibility. + * * This function allows to compute the authentikey pubkey externally and * store it in the secure memory cache for future use. * This allows to speed up computation during derivation of non-hardened child. @@ -1463,42 +1438,6 @@ private void setBIP32AuthentikeyPubkey(APDU apdu, byte[] buffer){ if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); - // check whether the seed is initialized - if (!bip32_seeded) - ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED); - - // input - short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (bytesLeft != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); - - short offset= (short) ISO7816.OFFSET_CDATA; - short coordx_size= Util.getShort(buffer, offset); - offset+=2; - offset+=coordx_size; - short sig_size= Util.getShort(buffer, offset); - offset+=2; - short offset_sig=offset; - offset+=sig_size; - short coordy_size= Util.getShort(buffer, offset); - offset+=2; - // copy pubkey coordy - Util.arrayCopyNonAtomic(buffer, offset, recvBuffer, (short)(1+coordx_size), coordy_size); - offset+=coordy_size; - // copy pubkey coordx from trusted source - recvBuffer[0]=0x04; - Util.arrayCopyNonAtomic(authentikey_pubkey, (short)1, recvBuffer, (short)(1), BIP32_KEY_SIZE); - - // verify that authentikey signature is valid - bip32_pubkey.setW(recvBuffer, (short)(0), (short)(1+coordx_size+coordy_size)); - sigECDSA.init(bip32_pubkey, Signature.MODE_VERIFY); - boolean verify= sigECDSA.verify(buffer, (short)ISO7816.OFFSET_CDATA, (short)(2+coordx_size), buffer, offset_sig, sig_size); - if (!verify) - ISOException.throwIt(SW_SIGNATURE_INVALID); - // copy coordy to secure memory - authentikey_pubkey[0]=0x04; - Util.arrayCopyNonAtomic(recvBuffer, (short)(1+BIP32_KEY_SIZE), authentikey_pubkey, (short)(1+BIP32_KEY_SIZE), BIP32_KEY_SIZE); - short pos=0; Util.setShort(buffer, pos, bip32_om.nb_elem_free); // number of slot available pos += (short) 2; @@ -1562,7 +1501,6 @@ private void getBIP32ExtendedKey(APDU apdu, byte[] buffer){ short parent_base=Bip32ObjectManager.NULL_OFFSET; // iterate on indexes provided - short exit_early=0x0000; for (byte i=1; i<=bip32_depth; i++){ //compute SHA of the extended key address up to depth i (only the last bytes are actually used) @@ -1592,55 +1530,15 @@ private void getBIP32ExtendedKey(APDU apdu, byte[] buffer){ keyAgreement.init(bip32_extendedkey); // keyAgreement.generateSecret() recovers X and Y coordinates - if (isKeyAgreementXY){ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, BIP32_OFFSET_PUB); //pubkey in uncompressed form - boolean parity= ((recvBuffer[(short)(BIP32_OFFSET_PUBY+31)]&0x01)==0); - compbyte= (parity)?(byte)0x02:(byte)0x03; //ORIGINAL: compbyte= (parity)?(byte)0x03:(byte)0x02; - // save compbyte in parent's object for future use - if (parent_base==Bip32ObjectManager.NULL_OFFSET) - bip32_master_compbyte= compbyte; - else - bip32_om.setByte(parent_base, (short)(BIP32_OBJECT_SIZE-1), compbyte); - // keyAgreement.generateSecret() recovers X coordinate only - } else{ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, BIP32_OFFSET_PUBX); // compute x coordinate of public key as k*G - // compute compbyte from coord y internally (slow!) - if (compbyte==0x04 && (opts & 0x40)!=0x40){ - // coord y= square root of X^3+7 mod p => 2 solutions! - EccComputation.SqrtRootOpt(recvBuffer, BIP32_OFFSET_PUBX, recvBuffer, BIP32_OFFSET_PUBY); - recvBuffer[BIP32_OFFSET_PUB]=0x04; - // sign a dummy message - sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN); - short sigsize=sigECDSA.sign(recvBuffer, (short)0, (short)32, buffer, BIP32_OFFSET_SIG); - // verify sig with pubkey (x,y) & recover compression byte - bip32_pubkey.setW(recvBuffer, BIP32_OFFSET_PUB, (short)(2*BIP32_KEY_SIZE+1)) ; - sigECDSA.init(bip32_pubkey, Signature.MODE_VERIFY); - boolean verify= sigECDSA.verify(recvBuffer, (short)0, (short)32, buffer, BIP32_OFFSET_SIG, sigsize); - boolean parity= ((recvBuffer[(short)(BIP32_OFFSET_PUBY+31)]&0x01)==0); - compbyte= (verify^parity)?(byte)0x03:(byte)0x02; - // save compbyte in parent's object for future use - if (parent_base==Bip32ObjectManager.NULL_OFFSET) - bip32_master_compbyte= compbyte; - else - bip32_om.setByte(parent_base, (short)(BIP32_OBJECT_SIZE-1), compbyte);//bip32_mem.setByte(parent_base, (short)(BIP32_OBJECT_SIZE-1), compbyte);//debugOM - } - // compute compbyte from coord y externally (faster!) - if (compbyte==0x04 && (opts & 0x40)==0x40){ - // exit the for loop prematurely - // the data returned is related to the parent with non-hardened child - // we can then compute the coord-y externally - // save hash of parent path (or 000...0 if masterkey) - if (parent_base==Bip32ObjectManager.NULL_OFFSET){ - Util.arrayFillNonAtomic(buffer, (short)0, (short)32, (byte)0); - }else{ - sha256.reset(); - sha256.doFinal(recvBuffer, BIP32_OFFSET_PATH, (short)(4*(i-1)), buffer, (byte)0); - } - exit_early=(short)0x8000; - break; - } - } - + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, BIP32_OFFSET_PUB); //pubkey in uncompressed form + boolean parity= ((recvBuffer[(short)(BIP32_OFFSET_PUBY+31)]&0x01)==0); + compbyte= (parity)?(byte)0x02:(byte)0x03; + // save compbyte in parent's object for future use + if (parent_base==Bip32ObjectManager.NULL_OFFSET) + bip32_master_compbyte= compbyte; + else + bip32_om.setByte(parent_base, (short)(BIP32_OBJECT_SIZE-1), compbyte); + // compute HMAC of compressed pubkey + index recvBuffer[BIP32_OFFSET_PUB]= compbyte; Util.arrayCopyNonAtomic(recvBuffer, (short)(BIP32_OFFSET_PATH+4*(i-1)), recvBuffer, BIP32_OFFSET_PUBY, (short)4); @@ -1702,22 +1600,15 @@ private void getBIP32ExtendedKey(APDU apdu, byte[] buffer){ // instantiate elliptic curve with last extended key + copy ACL bip32_extendedkey.setS(recvBuffer, BIP32_OFFSET_PARENT_KEY, BIP32_KEY_SIZE); - if (exit_early==0x0000){ - // save chaincode to buffer, otherwise buffer already contains hash of path - Util.arrayCopyNonAtomic(recvBuffer, (short)BIP32_OFFSET_PARENT_CHAINCODE, buffer, (short)0, BIP32_KEY_SIZE); - } - // clear recvBuffer + // save chaincode to buffer then clear recvBuffer + Util.arrayCopyNonAtomic(recvBuffer, (short)BIP32_OFFSET_PARENT_CHAINCODE, buffer, (short)0, BIP32_KEY_SIZE); Util.arrayFillNonAtomic(recvBuffer, BIP32_OFFSET_PARENT_CHAINCODE, BIP32_OFFSET_END, (byte)0); // compute the corresponding partial public key... keyAgreement.init(bip32_extendedkey); short coordx_size= (short)32; - if (isKeyAgreementXY){ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)33); //pubkey in uncompressed form - }else{ - keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)34); // compute x coordinate of public key as k*G - } - Util.setShort(buffer, BIP32_KEY_SIZE, (short)(coordx_size^exit_early)); //exit_early flag signals we want to compute pubkey externaly + keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)33); //pubkey in uncompressed form + Util.setShort(buffer, BIP32_KEY_SIZE, (short)(coordx_size)); // self-sign coordx sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN); @@ -1737,6 +1628,9 @@ private void getBIP32ExtendedKey(APDU apdu, byte[] buffer){ }// end of getBip32ExtendedKey() /** + * DEPRECATED - Not necessary anymore when recovering the pubkey with ALG_EC_SVDP_DH_PLAIN_XY + * A minimalist API is maintained for backward compatibility. + * * This function allows to compute an extended pubkey externally and * store it in the secure BIP32 memory cache for future use. * This allows to speed up computation during derivation of non-hardened child. @@ -1753,70 +1647,7 @@ private void setBIP32ExtendedPubkey(APDU apdu, byte[] buffer){ if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); - // check whether the seed is initialized - if (!bip32_seeded) - ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED); - - // input - short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (bytesLeft != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);//ISOException.throwIt(bytesLeft);// - - short offset= (short) ISO7816.OFFSET_CDATA; - offset+=32; - short coordx_size= Util.getShort(buffer, offset); - // check that the correct flag is set on msb of coordx_size - if ((coordx_size & (short)0x8000) == (short)0x8000) - coordx_size= (short)(coordx_size & 0x7fff); - else - ISOException.throwIt(SW_OPERATION_NOT_ALLOWED); - offset+=2; - //copy pubkey - recvBuffer[0]=0x04; - Util.arrayCopyNonAtomic(buffer, offset, recvBuffer, (short)1, coordx_size); - offset+=coordx_size; - short sig_size= Util.getShort(buffer, offset); - offset+=2; - short offset_sig=offset; - offset+=sig_size; - short authsig_size= Util.getShort(buffer, offset); - offset+=2; - short offset_authsig=offset; - offset+=authsig_size; - short coordy_size= Util.getShort(buffer, offset); - offset+=2; - // copy pubkey - Util.arrayCopyNonAtomic(buffer, offset, recvBuffer, (short)(1+coordx_size), coordy_size); - offset+=coordy_size; - - // verify that authentikey signature is valid - if (authentikey_pubkey[0]!=(byte)0x04) - ISOException.throwIt(SW_BIP32_UNINITIALIZED_AUTHENTIKEY_PUBKEY); - bip32_pubkey.setW(authentikey_pubkey, (short)(0), (short)(1+2*BIP32_KEY_SIZE)); - sigECDSA.init(bip32_pubkey, Signature.MODE_VERIFY); - boolean verify= sigECDSA.verify(buffer, (short)ISO7816.OFFSET_CDATA, (short)(32+2+coordx_size+2+sig_size), buffer, offset_authsig, authsig_size); - if (!verify) - ISOException.throwIt(SW_SIGNATURE_INVALID); - // verify that coordy is legit - bip32_pubkey.setW(recvBuffer, (short)(0), (short)(1+coordx_size+coordy_size)); - sigECDSA.init(bip32_pubkey, Signature.MODE_VERIFY); - verify= sigECDSA.verify(buffer, (short)ISO7816.OFFSET_CDATA, (short)(32+2+coordx_size), buffer, offset_sig, sig_size); - if (!verify) - ISOException.throwIt(SW_SIGNATURE_INVALID); - // save compression byte from coordy - // if hash is 00...00, compbyte relates to master key - byte compbyte= (byte)( (recvBuffer[(short)(1+coordx_size+coordy_size-1)]&0x01)+0x02); - if (Util.arrayCompare(buffer, (short)(ISO7816.OFFSET_CDATA+32-BIP32_ANTICOLLISION_LENGTH), bip32_om.empty, (short)0, BIP32_ANTICOLLISION_LENGTH)==0){ - bip32_master_compbyte=compbyte; - } - else{ - short base=bip32_om.getBaseAddress(buffer, (short)(ISO7816.OFFSET_CDATA+32-BIP32_ANTICOLLISION_LENGTH)); - if (base==Bip32ObjectManager.NULL_OFFSET) - ISOException.throwIt(SW_OBJECT_NOT_FOUND); - bip32_om.setByte(base, (short)(BIP32_OBJECT_SIZE-1), compbyte); - } - - short pos=0; + short pos=0; Util.setShort(buffer, pos, bip32_om.nb_elem_free); // number of slot available pos += (short) 2; Util.setShort(buffer, pos, bip32_om.nb_elem_used); // number of slot used diff --git a/src/org/satochip/applet/EccComputation.java b/src/org/satochip/applet/EccComputation.java deleted file mode 100644 index cddafcf..0000000 --- a/src/org/satochip/applet/EccComputation.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * SatoChip Bitcoin Hardware Wallet based on javacard - * (c) 2015 by Toporin - 16DMCk4WUaHofchAhpMaQS4UPm4urcy2dN - * Sources available on https://github.com/Toporin - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ -package org.satochip.applet; - -import javacard.framework.Util; -import javacard.security.KeyBuilder; -import javacard.security.RSAPublicKey; -import javacard.security.RandomData; -import javacardx.crypto.Cipher; - -public class EccComputation { - - private static final short EXP2=0; - private static final short EXP3=1; - private static final short EXP4=2; - private static final short EXP6=3; - private static final short EXP8=4; - private static final short EXP12=5; - private static final short EXP16=6; - private static final short EXP24=7; - private static final short EXP32=8; - private static final short EXP48=9; - private static final short EXP96=10; - private static final short DECR=11; - - // Q decomposition: divide by a factor of 96 (2^5*3) or decrement then repeat - private final static byte[] QDECOMP={ - EXP4, DECR, EXP2, DECR, EXP32, DECR, EXP2, DECR, - EXP2, DECR, EXP2, DECR, EXP2, DECR, EXP2, DECR, - EXP2, DECR, EXP2, DECR, EXP2, DECR, EXP2, DECR, - EXP2, DECR, EXP2, DECR, EXP2, DECR, EXP2, DECR, - EXP2, DECR, EXP2, DECR, EXP2, DECR, EXP2, DECR, - EXP2, DECR, EXP2, DECR, EXP2, DECR, EXP2, DECR, - EXP4, DECR, EXP6, EXP3, DECR, EXP6, DECR, EXP96, - DECR, EXP6, EXP3, DECR, EXP2, DECR, EXP2, DECR, - EXP2, DECR, EXP4, DECR, EXP48, DECR, EXP6, DECR, - EXP8, DECR, EXP2, DECR, EXP2, DECR, EXP2, DECR, - EXP2, DECR, EXP4, DECR, EXP6, DECR, EXP16, DECR, - EXP6, DECR, EXP2, DECR, EXP2, DECR, EXP4, DECR, - EXP6, EXP3, EXP3, DECR, EXP24, DECR, EXP2, DECR, - EXP4, DECR, EXP6, EXP3, DECR, EXP6, DECR, EXP16, - DECR, EXP48, DECR, EXP6, EXP3, EXP3, DECR, EXP2, - DECR, EXP2, DECR, EXP2, DECR, EXP4, DECR, EXP6, - EXP3, DECR, EXP6, EXP3, DECR, EXP12, DECR, EXP2, - DECR, EXP4, DECR, EXP24, EXP3, DECR, EXP2, DECR, - EXP8, DECR, EXP8, DECR, EXP8, DECR, EXP2, DECR, - EXP2, DECR, EXP2, DECR, EXP32, EXP8, DECR, EXP6, - DECR, EXP96, DECR, EXP12, EXP3, DECR, EXP12, DECR, - EXP4, DECR, EXP6, DECR, EXP6, DECR, EXP6, EXP3, - EXP3, DECR, EXP6, DECR, EXP48, DECR, EXP12, EXP3, - EXP3, EXP3, DECR, EXP4, DECR, EXP6, EXP3, EXP3, - DECR, EXP32, EXP8, DECR, EXP6, DECR, EXP2, DECR, - EXP2, DECR, EXP2, DECR, EXP16, DECR, EXP6, DECR, - EXP12, DECR, EXP2, DECR, EXP2, DECR, EXP2, DECR, - EXP4, DECR, EXP12, DECR - }; - - private final static byte[] EXP={ - (byte)2, (byte)3, (byte)4, (byte)6, - (byte)8, (byte)12, (byte)16, (byte)24, - (byte)32, (byte)48, (byte)96}; - - private final static short[] EXPOFF={ - (short)48, (short)64, (short)72, (short)80, - (short)84, (short)88, (short)90, (short)92, - (short)93, (short)94, (short)95}; - - private final static byte[] SECP256K1_P = - {(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, - (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, - (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, - (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFE, (byte)0xFF,(byte)0xFF,(byte)0xFC,(byte)0x2F}; - - // q= (p+1)/4 -// private final static byte[] Q= -// {(byte)0x3f,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff, -// (byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff, -// (byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff, -// (byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xbf,(byte)0xff,(byte)0xff,(byte)0x0c}; - - private static byte[] tmpBuffer; - private static final short REG0=(short)0; - private static final short REG1=(short)128; - private static final short REG2=(short)160; - private static final short REG3=(short)192; - private static final short REG4=(short)224; - //private static final short REG5=(short)256; - private static final short OPLENGTH=(short)32; - - private static RSAPublicKey rsa_PublicKey = null; - private static Cipher m_encryptCipherRSA = null; - public static final byte ALG_RSA_NOPAD = 12; - - public static void init(byte[] tmp){ - tmpBuffer=tmp; - - // Allocate objects if not allocated yet - if (rsa_PublicKey == null) { - rsa_PublicKey = (RSAPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC,KeyBuilder.LENGTH_RSA_1024,false); - } - if (m_encryptCipherRSA == null) { - m_encryptCipherRSA = Cipher.getInstance(ALG_RSA_NOPAD, false); - } - - // set SECP256K1_P*2^96 - Util.arrayFillNonAtomic(tmpBuffer, REG0, (short)128, (byte)0); - Util.arrayCopyNonAtomic(SECP256K1_P, (short)0, tmpBuffer, REG0, (short)SECP256K1_P.length); - rsa_PublicKey.setModulus(tmpBuffer, REG0, (short)128); - } - - - // uses REG3 & REG4 - public static void SqrtRootOpt(byte[] src, short srcOff, byte[] dst, short dstOff){ //, short ctrstop){ - // y^2 = z = x^3+7 (mod p) - // y= sqrt(z)= z^((p+1)/4) (mod p) - - // z= x^3+7 - ModularExp32b(src, srcOff, tmpBuffer, REG4, EXP3); - Util.arrayFillNonAtomic(tmpBuffer, REG2, OPLENGTH, (byte)0); - tmpBuffer[(short)(REG2+31)]=(byte)7; //LSB - if(Biginteger.add_carry(tmpBuffer, REG4, tmpBuffer, REG2, OPLENGTH)){ - Biginteger.subtract(tmpBuffer, REG4, SECP256K1_P, (short)0, OPLENGTH); - } - - Util.arrayFillNonAtomic(tmpBuffer, REG3, OPLENGTH, (byte)0); - tmpBuffer[(short)(REG3+31)]=(byte)1; //LSB - - for(short i=0; i use a more recent card! } } @@ -68,12 +64,8 @@ public static short computeHmacSha512(byte[] key, short key_offset, short key_le } Util.arrayFillNonAtomic(data, key_length, (short)(BLOCKSIZE-key_length), (byte)0x36); Util.arrayCopyNonAtomic(message, message_offset, data, BLOCKSIZE, message_length); - if (nativeSha512){ - sha512.reset(); - sha512.doFinal(data, (short)0, (short)(BLOCKSIZE+message_length), data, BLOCKSIZE); // copy hash result to data buffer! - } else{ - Sha512.resetUpdateDoFinal(data, (short)0, (short)(BLOCKSIZE+message_length), data, BLOCKSIZE); // copy hash result to data buffer! - } + sha512.reset(); + sha512.doFinal(data, (short)0, (short)(BLOCKSIZE+message_length), data, BLOCKSIZE); // copy hash result to data buffer! // compute outer hash for (short i=0; i>15)&1); posy--; posx--; addx=hashState[posx]; addy=h_short[posy]; hashState[posx] = (short)(addx+addy+akku); akku= (short)(( ((addx&addy)|((addx|addy) & ~hashState[posx])) >>15)&1); posy--; posx--; addx=hashState[posx]; addy=h_short[posy]; hashState[posx] = (short)(addx+addy+akku); akku= (short)(( ((addx&addy)|((addx|addy) & ~hashState[posx])) >>15)&1); posy--; posx--; addx=hashState[posx]; addy=h_short[posy]; hashState[posx] = (short)(addx+addy+akku) ; - } - - - short remainingBytes= (short)(inLength-(short)128); - Util.arrayCopyNonAtomic(inBuff, inOffset, buffer, bufferOff, remainingBytes); - bufferLeft-=remainingBytes; - bufferOff+=remainingBytes; - - - - - buffer[bufferOff]=(byte)0x80; - bufferLeft--; - bufferOff++; - Util.arrayFillNonAtomic(buffer, bufferOff, bufferLeft, (byte)0x00); - - - buffer[(short)(buffer.length-2)]=(byte)(((short)(8*inLength)>>8)&0xff); - buffer[(short)(buffer.length-1)]=(byte)((8*inLength) &0xff); - - - for (short i=0; i<32; i++){ - h_short[i]=hashState[i]; - } - CompressionFunction(h_short, (short)0, buffer, (short)0); - - for (short i=0; i<32; i+=4){ - akku = 0; posy = (short)((i)+3); posx = (short)((i)+3); addx=hashState[posx]; addy=h_short[posy]; hashState[posx] = (short)(addx+addy+akku); akku= (short)(( ((addx&addy)|((addx|addy) & ~hashState[posx])) >>15)&1); posy--; posx--; addx=hashState[posx]; addy=h_short[posy]; hashState[posx] = (short)(addx+addy+akku); akku= (short)(( ((addx&addy)|((addx|addy) & ~hashState[posx])) >>15)&1); posy--; posx--; addx=hashState[posx]; addy=h_short[posy]; hashState[posx] = (short)(addx+addy+akku); akku= (short)(( ((addx&addy)|((addx|addy) & ~hashState[posx])) >>15)&1); posy--; posx--; addx=hashState[posx]; addy=h_short[posy]; hashState[posx] = (short)(addx+addy+akku) ; - } - - - for (short i=0; i<32; i++){ - outBuff[outOffset]=(byte)((hashState[i]>>8)&0xff); - outOffset++; - outBuff[outOffset]=(byte)(hashState[i]&0xff); - outOffset++; - } - - return (short)64; - } - - public static void CompressionFunction(short[] state, short stateOff, byte[] msgBlock, short msgOff){ - - - short off1, off2, off3; - short regA0, regA1, regA2, regA3; - short regB0, regB1, regB2, regB3; - short tmpA0, tmpA1, tmpA2, tmpA3; - - - - for (short dstOff=0; dstOff<64; dstOff++){ w_short[dstOff]= Util.getShort(msgBlock, (short)((msgOff)+2*dstOff)); } ; - - short hOff=0, wOff=0; - for (short round=0; round<80; round++){ - - - off1=(short)(((short)(wOff+56))%64); off2=(short)(((short)(wOff+36))%64); off3=(short)(((short)(wOff+4))%64); tmpA0 = w_short[off1]; tmpA1 = w_short[(short)(off1+1)]; tmpA2 = w_short[(short)(off1+2)]; tmpA3 = w_short[(short)(off1+3)]; regA0 = (short) ( ( (tmpA3 >>>3) & (short)8191 | ((short)(tmpA2 <<(13))) ) ^ ( (tmpA1 >>>13) & (short)7 | ((short)(tmpA0 <<(3))) ) ^ ((tmpA0 >>>6) & (short)1023) ); regA1 = (short) ( ( (tmpA0 >>>3) & (short)8191 | ((short)(tmpA3 <<(13))) ) ^ ( (tmpA2 >>>13) & (short)7 | ((short)(tmpA1 <<(3))) ) ^ ( (tmpA1 >>>6) & (short)1023 | ((short)(tmpA0 <<(10))) ) ); regA2 = (short) ( ( (tmpA1 >>>3) & (short)8191 | ((short)(tmpA0 <<(13))) ) ^ ( (tmpA3 >>>13) & (short)7 | ((short)(tmpA2 <<(3))) ) ^ ( (tmpA2 >>>6) & (short)1023 | ((short)(tmpA1 <<(10))) ) ); regA3 = (short) ( ( (tmpA2 >>>3) & (short)8191 | ((short)(tmpA1 <<(13))) ) ^ ( (tmpA0 >>>13) & (short)7 | ((short)(tmpA3 <<(3))) ) ^ ( (tmpA3 >>>6) & (short)1023 | ((short)(tmpA2 <<(10))) ) ) ; regB0 = w_short[off2]; regB1 = w_short[(short)(off2+1)]; regB2 =w_short[(short)(off2+2)]; regB3 =w_short[(short)(off2+3)]; tmpA0 = regA3 ; regA3 = (short)((regA3 )+(regB3 )); tmpA2 = (short)(( ((tmpA0 &(regB3 ))|((tmpA0 |(regB3 )) & ~(regA3 ))) >>15)&1); tmpA0 =regA2 ; regA2 = (short)((regA2 )+(regB2 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB2 ))|((tmpA0 |(regB2 )) & ~(regA2 ))) >>15)&1); tmpA0 =regA1 ; regA1 = (short)((regA1 )+(regB1 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB1 ))|((tmpA0 |(regB1 )) & ~(regA1 ))) >>15)&1); regA0 = (short)((regA0 )+(regB0 )+tmpA2 ) ; tmpA0 = w_short[off3]; tmpA1 = w_short[(short)(off3+1)]; tmpA2 = w_short[(short)(off3+2)]; tmpA3 = w_short[(short)(off3+3)]; regB0 = (short) ( ( (tmpA0 >>>1) & (short)32767 | ((short)(tmpA3 <<(15))) ) ^ ( (tmpA0 >>>8) & (short)255 | ((short)(tmpA3 <<(8))) ) ^ ((tmpA0 >>>7) & (short)511) ); regB1 = (short) ( ( (tmpA1 >>>1) & (short)32767 | ((short)(tmpA0 <<(15))) ) ^ ( (tmpA1 >>>8) & (short)255 | ((short)(tmpA0 <<(8))) ) ^ ( (tmpA1 >>>7) & (short)511 | ((short)(tmpA0 <<(9))) ) ); regB2 = (short) ( ( (tmpA2 >>>1) & (short)32767 | ((short)(tmpA1 <<(15))) ) ^ ( (tmpA2 >>>8) & (short)255 | ((short)(tmpA1 <<(8))) ) ^ ( (tmpA2 >>>7) & (short)511 | ((short)(tmpA1 <<(9))) ) ); regB3 = (short) ( ( (tmpA3 >>>1) & (short)32767 | ((short)(tmpA2 <<(15))) ) ^ ( (tmpA3 >>>8) & (short)255 | ((short)(tmpA2 <<(8))) ) ^ ( (tmpA3 >>>7) & (short)511 | ((short)(tmpA2 <<(9))) ) ) ; tmpA0 = regA3 ; regA3 = (short)((regA3 )+(regB3 )); tmpA2 = (short)(( ((tmpA0 &(regB3 ))|((tmpA0 |(regB3 )) & ~(regA3 ))) >>15)&1); tmpA0 =regA2 ; regA2 = (short)((regA2 )+(regB2 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB2 ))|((tmpA0 |(regB2 )) & ~(regA2 ))) >>15)&1); tmpA0 =regA1 ; regA1 = (short)((regA1 )+(regB1 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB1 ))|((tmpA0 |(regB1 )) & ~(regA1 ))) >>15)&1); regA0 = (short)((regA0 )+(regB0 )+tmpA2 ) ; regB0 = w_short[wOff]; regB1 = w_short[(short)(wOff+1)]; regB2 =w_short[(short)(wOff+2)]; regB3 =w_short[(short)(wOff+3)]; tmpA0 = regA3 ; regA3 = (short)((regA3 )+(regB3 )); tmpA2 = (short)(( ((tmpA0 &(regB3 ))|((tmpA0 |(regB3 )) & ~(regA3 ))) >>15)&1); tmpA0 =regA2 ; regA2 = (short)((regA2 )+(regB2 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB2 ))|((tmpA0 |(regB2 )) & ~(regA2 ))) >>15)&1); tmpA0 =regA1 ; regA1 = (short)((regA1 )+(regB1 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB1 ))|((tmpA0 |(regB1 )) & ~(regA1 ))) >>15)&1); regA0 = (short)((regA0 )+(regB0 )+tmpA2 ) ; regB0 = w_short[wOff]; regB1 = w_short[(short)(wOff+1)]; regB2 = w_short[(short)(wOff+2)]; regB3 = w_short[(short)(wOff+3)]; w_short[wOff]= regA0 ; w_short[(short)(wOff+1)]= regA1 ; w_short[(short)(wOff+2)]= regA2 ; w_short[(short)(wOff+3)]= regA3 ; wOff=(short)(((short)(wOff+4))%64) ; - - - off1= (short)(((short)(hOff+28))%32); - off2= (short)(4*(round)); - tmpA0 =state[(short)((off1)+3)]; tmpA1 =K_SHORT[(short)((off2)+3)]; regA3 = (short)(tmpA0 +tmpA1 ); tmpA2 = (short)(( ((tmpA0 &tmpA1 )|((tmpA0 |tmpA1 ) & ~regA3 )) >>15)&1); tmpA0 =state[(short)((off1)+2)]; tmpA1 =K_SHORT[(short)((off2)+2)]; regA2 = (short)(tmpA0 +tmpA1 +tmpA2 ); tmpA2 = (short)(( ((tmpA0 &tmpA1 )|((tmpA0 |tmpA1 ) & ~regA2 )) >>15)&1); tmpA0 =state[(short)((off1)+1)]; tmpA1 =K_SHORT[(short)((off2)+1)]; regA1 = (short)(tmpA0 +tmpA1 +tmpA2 ); tmpA2 = (short)(( ((tmpA0 &tmpA1 )|((tmpA0 |tmpA1 ) & ~regA1 )) >>15)&1); regA0 = (short)(state[(off1)]+K_SHORT[(off2)]+tmpA2 ); ; - - - tmpA0 = regA3 ; regA3 = (short)((regA3 )+(regB3 )); tmpA2 = (short)(( ((tmpA0 &(regB3 ))|((tmpA0 |(regB3 )) & ~(regA3 ))) >>15)&1); tmpA0 =regA2 ; regA2 = (short)((regA2 )+(regB2 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB2 ))|((tmpA0 |(regB2 )) & ~(regA2 ))) >>15)&1); tmpA0 =regA1 ; regA1 = (short)((regA1 )+(regB1 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB1 ))|((tmpA0 |(regB1 )) & ~(regA1 ))) >>15)&1); regA0 = (short)((regA0 )+(regB0 )+tmpA2 ) ; - - - off1=(short)(((short)(hOff+16))%32); - off2=(short)(((short)(hOff+20))%32); - off3=(short)(((short)(hOff+24))%32); - tmpA0 = state[off1]; regB0 = (short) ((tmpA0 & state[off2]) ^ ((~tmpA0 ) & state[off3])); tmpA0 = state[(short)(off1+1)]; regB1 = (short) ((tmpA0 & state[(short)(off2+1)]) ^ ((~tmpA0 ) & state[(short)(off3+1)])); tmpA0 = state[(short)(off1+2)]; regB2 = (short) ((tmpA0 & state[(short)(off2+2)]) ^ ((~tmpA0 ) & state[(short)(off3+2)])); tmpA0 = state[(short)(off1+3)]; regB3 = (short) ((tmpA0 & state[(short)(off2+3)]) ^ ((~tmpA0 ) & state[(short)(off3+3)])); ; - - - tmpA0 = regA3 ; regA3 = (short)((regA3 )+(regB3 )); tmpA2 = (short)(( ((tmpA0 &(regB3 ))|((tmpA0 |(regB3 )) & ~(regA3 ))) >>15)&1); tmpA0 =regA2 ; regA2 = (short)((regA2 )+(regB2 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB2 ))|((tmpA0 |(regB2 )) & ~(regA2 ))) >>15)&1); tmpA0 =regA1 ; regA1 = (short)((regA1 )+(regB1 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB1 ))|((tmpA0 |(regB1 )) & ~(regA1 ))) >>15)&1); regA0 = (short)((regA0 )+(regB0 )+tmpA2 ) ; - - - off1=(short)(((short)(hOff+16))%32); - tmpA0 = state[off1]; tmpA1 = state[(short)(off1+1)]; tmpA2 = state[(short)(off1+2)]; tmpA3 = state[(short)(off1+3)]; regB0 = (short) ( ( (tmpA0 >>>14) & (short)3 | ((short)(tmpA3 <<(2))) ) ^ ( (tmpA3 >>>2) & (short)16383 | ((short)(tmpA2 <<(14))) ) ^ ( (tmpA2 >>>9) & (short)127 | ((short)(tmpA1 <<(7))) ) ); regB1 = (short) ( ( (tmpA1 >>>14) & (short)3 | ((short)(tmpA0 <<(2))) ) ^ ( (tmpA0 >>>2) & (short)16383 | ((short)(tmpA3 <<(14))) ) ^ ( (tmpA3 >>>9) & (short)127 | ((short)(tmpA2 <<(7))) ) ); regB2 = (short) ( ( (tmpA2 >>>14) & (short)3 | ((short)(tmpA1 <<(2))) ) ^ ( (tmpA1 >>>2) & (short)16383 | ((short)(tmpA0 <<(14))) ) ^ ( (tmpA0 >>>9) & (short)127 | ((short)(tmpA3 <<(7))) ) ); regB3 = (short) ( ( (tmpA3 >>>14) & (short)3 | ((short)(tmpA2 <<(2))) ) ^ ( (tmpA2 >>>2) & (short)16383 | ((short)(tmpA1 <<(14))) ) ^ ( (tmpA1 >>>9) & (short)127 | ((short)(tmpA0 <<(7))) ) ) ; - - - tmpA0 = regA3 ; regA3 = (short)((regA3 )+(regB3 )); tmpA2 = (short)(( ((tmpA0 &(regB3 ))|((tmpA0 |(regB3 )) & ~(regA3 ))) >>15)&1); tmpA0 =regA2 ; regA2 = (short)((regA2 )+(regB2 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB2 ))|((tmpA0 |(regB2 )) & ~(regA2 ))) >>15)&1); tmpA0 =regA1 ; regA1 = (short)((regA1 )+(regB1 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB1 ))|((tmpA0 |(regB1 )) & ~(regA1 ))) >>15)&1); regA0 = (short)((regA0 )+(regB0 )+tmpA2 ) ; - - - off1= (short)(((short)(hOff+12))%32); - tmpA0 =(short)((off1)+3); tmpA1 =state[tmpA0 ]; state[tmpA0 ] = (short)(tmpA1 +regA3 ); tmpA2 = (short)(( ((tmpA1 ®A3 )|((tmpA1 |regA3 ) & ~state[tmpA0 ])) >>15)&1); tmpA0 =(short)((off1)+2); tmpA1 =state[tmpA0 ]; state[tmpA0 ] = (short)(tmpA1 +regA2 +tmpA2 ); tmpA2 = (short)(( ((tmpA1 ®A2 )|((tmpA1 |regA2 ) & ~state[tmpA0 ])) >>15)&1); tmpA0 =(short)((off1)+1); tmpA1 =state[tmpA0 ]; state[tmpA0 ] = (short)(tmpA1 +regA1 +tmpA2 ); tmpA2 = (short)(( ((tmpA1 ®A1 )|((tmpA1 |regA1 ) & ~state[tmpA0 ])) >>15)&1); tmpA0 =(short)(off1); tmpA1 =state[tmpA0 ]; state[tmpA0 ] = (short)(tmpA1 +regA0 +tmpA2 ); - - - - off1= (short)(((short)(hOff+4))%32); - off2= (short)(((short)(hOff+8))%32); - tmpA0 =state[hOff]; tmpA1 =state[off1]; tmpA2 =state[off2]; regB0 = (short) ((tmpA0 & tmpA1 ) ^ (tmpA0 & tmpA2 ) ^ (tmpA1 & tmpA2 )); tmpA0 =state[(short)(hOff+1)]; tmpA1 =state[(short)(off1+1)]; tmpA2 =state[(short)(off2+1)]; regB1 = (short) ((tmpA0 & tmpA1 ) ^ (tmpA0 & tmpA2 ) ^ (tmpA1 & tmpA2 )); tmpA0 =state[(short)(hOff+2)]; tmpA1 =state[(short)(off1+2)]; tmpA2 =state[(short)(off2+2)]; regB2 = (short) ((tmpA0 & tmpA1 ) ^ (tmpA0 & tmpA2 ) ^ (tmpA1 & tmpA2 )); tmpA0 =state[(short)(hOff+3)]; tmpA1 =state[(short)(off1+3)]; tmpA2 =state[(short)(off2+3)]; regB3 = (short) ((tmpA0 & tmpA1 ) ^ (tmpA0 & tmpA2 ) ^ (tmpA1 & tmpA2 )); - - - tmpA0 = regA3 ; regA3 = (short)((regA3 )+(regB3 )); tmpA2 = (short)(( ((tmpA0 &(regB3 ))|((tmpA0 |(regB3 )) & ~(regA3 ))) >>15)&1); tmpA0 =regA2 ; regA2 = (short)((regA2 )+(regB2 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB2 ))|((tmpA0 |(regB2 )) & ~(regA2 ))) >>15)&1); tmpA0 =regA1 ; regA1 = (short)((regA1 )+(regB1 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB1 ))|((tmpA0 |(regB1 )) & ~(regA1 ))) >>15)&1); regA0 = (short)((regA0 )+(regB0 )+tmpA2 ) ; - - - tmpA0 = state[hOff]; tmpA1 = state[(short)(hOff+1)]; tmpA2 = state[(short)(hOff+2)]; tmpA3 = state[(short)(hOff+3)]; regB0 = (short) ( ( (tmpA3 >>>12) & (short)15 | ((short)(tmpA2 <<(4))) ) ^ ( (tmpA2 >>>2) & (short)16383 | ((short)(tmpA1 <<(14))) ) ^ ( (tmpA2 >>>7) & (short)511 | ((short)(tmpA1 <<(9))) ) ); regB1 = (short) ( ( (tmpA0 >>>12) & (short)15 | ((short)(tmpA3 <<(4))) ) ^ ( (tmpA3 >>>2) & (short)16383 | ((short)(tmpA2 <<(14))) ) ^ ( (tmpA3 >>>7) & (short)511 | ((short)(tmpA2 <<(9))) ) ); regB2 = (short) ( ( (tmpA1 >>>12) & (short)15 | ((short)(tmpA0 <<(4))) ) ^ ( (tmpA0 >>>2) & (short)16383 | ((short)(tmpA3 <<(14))) ) ^ ( (tmpA0 >>>7) & (short)511 | ((short)(tmpA3 <<(9))) ) ); regB3 = (short) ( ( (tmpA2 >>>12) & (short)15 | ((short)(tmpA1 <<(4))) ) ^ ( (tmpA1 >>>2) & (short)16383 | ((short)(tmpA0 <<(14))) ) ^ ( (tmpA1 >>>7) & (short)511 | ((short)(tmpA0 <<(9))) ) ) ; - - - tmpA0 = regA3 ; regA3 = (short)((regA3 )+(regB3 )); tmpA2 = (short)(( ((tmpA0 &(regB3 ))|((tmpA0 |(regB3 )) & ~(regA3 ))) >>15)&1); tmpA0 =regA2 ; regA2 = (short)((regA2 )+(regB2 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB2 ))|((tmpA0 |(regB2 )) & ~(regA2 ))) >>15)&1); tmpA0 =regA1 ; regA1 = (short)((regA1 )+(regB1 )+tmpA2 ); tmpA2 = (short)(( ((tmpA0 &(regB1 ))|((tmpA0 |(regB1 )) & ~(regA1 ))) >>15)&1); regA0 = (short)((regA0 )+(regB0 )+tmpA2 ) ; - - - state[(short)(((short)(hOff+28))%32)]= regA0; - state[(short)(((short)(hOff+29))%32)]= regA1; - state[(short)(((short)(hOff+30))%32)]= regA2; - state[(short)(((short)(hOff+31))%32)]= regA3; - - - hOff= (short)(((short)(32+hOff-4))%32); - - } - } -} diff --git a/src/org/satochip/applet/Sha512.javap b/src/org/satochip/applet/Sha512.javap deleted file mode 100644 index aab25f2..0000000 --- a/src/org/satochip/applet/Sha512.javap +++ /dev/null @@ -1,839 +0,0 @@ -package org.satochip.applet; - -import javacard.framework.JCSystem; -import javacard.framework.Util; - -// build the java file using a C preprocessor such as mcpp (http://mcpp.sourceforge.net/) -// build command: mcpp Sha512.javap Sha512.java -P - -#define add_carry(x, offsetx, y, offsety)\ - akku = 0;\ - posy = (short)((offsety)+3);\ - posx = (short)((offsetx)+3);\ - addx=x[posx]; addy=y[posy];\ - x[posx] = (short)(addx+addy+akku);\ - akku= (short)(( ((addx&addy)|((addx|addy) & ~x[posx])) >>15)&1);\ - posy--; posx--;\ - addx=x[posx]; addy=y[posy];\ - x[posx] = (short)(addx+addy+akku);\ - akku= (short)(( ((addx&addy)|((addx|addy) & ~x[posx])) >>15)&1);\ - posy--; posx--;\ - addx=x[posx]; addy=y[posy];\ - x[posx] = (short)(addx+addy+akku);\ - akku= (short)(( ((addx&addy)|((addx|addy) & ~x[posx])) >>15)&1);\ - posy--; posx--;\ - addx=x[posx]; addy=y[posy];\ - x[posx] = (short)(addx+addy+akku) - -#define Rotnb(src, srcOffset, dst, dstOffset, rightShifts, mask)\ - leftShifts = (short)(16-rightShifts);\ - dst[dstOffset]= (short) (((src[srcOffset]>>rightShifts)&mask) | (src[(short)(srcOffset+3)]<>rightShifts)&mask) | (src[srcOffset]<>rightShifts)&mask) | (src[(short)(srcOffset+1)]<>rightShifts)&mask) | (src[(short)(srcOffset+2)]<>rightShifts)&mask) | (src[(short)(srcOffset+3)]<>rightShifts)&mask) | (src[srcOffset]<>rightShifts)&mask) | (src[(short)(srcOffset+1)]<>rightShifts)&mask) | (src[(short)(srcOffset+2)]<>rightShifts)&mask) | (src[(short)(srcOffset+3)]<>rightShifts)&mask) | (src[srcOffset]<>rightShifts)&mask) | (src[(short)(srcOffset+1)]<>rightShifts)&mask) | (src[(short)(srcOffset+2)]<>rightShifts)&mask) | (src[(short)(srcOffset+3)]<>rightShifts)&mask) | (src[srcOffset]<>rightShifts)&mask) | (src[(short)(srcOffset+1)]<>rightShifts)&mask) | (src[(short)(srcOffset+2)]<>rightShifts)&mask);\ - dst[(short)(dstOffset+1)]= (short) (((src[(short)(srcOffset+1)]>>rightShifts)&mask) | (src[srcOffset]<>rightShifts)&mask) | (src[(short)(srcOffset+1)]<>rightShifts)&mask) | (src[(short)(srcOffset+2)]<>8)&0xff); - dataSize[7]=(byte)((8*inLength) &0xff); - add_carry_byte(dataSize, MSGSIZE, dataSize, CHUNKSIZE, (short)4); - - // perform function compression on complete 1024 blocks - while (inLength>=bufferLeft){ - - // fulfill buffer - //System.arraycopy(inBuff, inOffset, buffer, bufferOff, bufferLeft); - Util.arrayCopyNonAtomic(inBuff, inOffset, buffer, bufferOff, bufferLeft); - inOffset+=bufferLeft; - inLength-=bufferLeft; - bufferLeft=128; - bufferOff=0; - - // apply compression function - for (short i=0; i<32; i++){ - h_short[i]=hashState[i]; - } - CompressionFunction(h_short, (short)0, buffer, (short)0); - - //add result back in hashState - for (short i=0; i<32; i+=4){ - add_carry(hashState, (short)i, h_short, (short)i); - } - - } - // at this point, bufferLeft>inLength - - // save remaining msg in buffer - Util.arrayCopyNonAtomic(inBuff, inOffset, buffer, bufferOff, inLength); - inOffset+=inLength; - bufferLeft-=inLength; - bufferOff+=inLength; - } - */ - - /* - public static short doFinal(byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset){ - - // for additions - short akku,posy,posx,addx,addy; - - // perform update first - update(inBuff, inOffset, inLength); - - // padd remaining bytes in the buffer - buffer[bufferOff]=(byte)0x80; - bufferLeft--; - bufferOff++; - Util.arrayFillNonAtomic(buffer, bufferOff, bufferLeft, (byte)0x00); - - if (bufferLeft<16){ // needs an additional block - // apply compression function - for (short i=0; i<32; i++){ - h_short[i]=hashState[i]; - } - CompressionFunction(h_short, (short)0, buffer, (short)0); - //add result back in hashState - for (short i=0; i<32; i+=4){ - add_carry(hashState, (short)i, h_short, (short)i); - } - // reset buffer - bufferOff=0; - bufferLeft=128; - Util.arrayFillNonAtomic(buffer, bufferOff, bufferLeft, (byte)0x00); - } - // message size (in bits) - Util.arrayCopyNonAtomic(dataSize, MSGSIZE, buffer, (short)(buffer.length-4), (short)4); - - // apply compression function on last block - for (short i=0; i<32; i++){ - h_short[i]=hashState[i]; - } - CompressionFunction(h_short, (short)0, buffer, (short)0); - //add result back in hashState - for (short i=0; i<32; i+=4){ - add_carry(hashState, (short)i, h_short, (short)i); - } - - // copy final state back and reset - for (short i=0; i<32; i++){ - outBuff[outOffset]=(byte)((hashState[i]>>8)&0xff); - outOffset++; - outBuff[outOffset]=(byte)(hashState[i]&0xff); - outOffset++; - } - reset(); - - return (short)64; - } - */ - - // simplified method to hash exactly 2 blocks, i.e. >128 bytes and <=240 bytes, as required for Bip32 - public static short resetUpdateDoFinal(byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset){ - - // variable declaration for inline additions - short akku,posy,posx,addx,addy; - - /* Reset */ - bufferOff=0; - bufferLeft=128; - - /* Update */ - - // perform function compression on first (complete) 1024 blocks - // fulfil buffer - Util.arrayCopyNonAtomic(inBuff, inOffset, buffer, bufferOff, bufferLeft); - inOffset+=bufferLeft; - bufferLeft=128; - bufferOff=0; - - // apply compression function - for (short i=0; i<32; i++){ - hashState[i]= H_INIT_SHORT[i]; - h_short[i]=hashState[i]; - } - CompressionFunction(h_short, (short)0, buffer, (short)0); - //add result back in hashState - for (short i=0; i<32; i+=4){ - add_carry(hashState, i, h_short, i); - } - - // save remaining msg in buffer - short remainingBytes= (short)(inLength-(short)128); - Util.arrayCopyNonAtomic(inBuff, inOffset, buffer, bufferOff, remainingBytes); - bufferLeft-=remainingBytes; - bufferOff+=remainingBytes; - - /* DoFinal */ - - // pad remaining bytes in the buffer - buffer[bufferOff]=(byte)0x80; - bufferLeft--; - bufferOff++; - Util.arrayFillNonAtomic(buffer, bufferOff, bufferLeft, (byte)0x00); - - // message size (in bits) - buffer[(short)(buffer.length-2)]=(byte)(((short)(8*inLength)>>8)&0xff); - buffer[(short)(buffer.length-1)]=(byte)((8*inLength) &0xff); - - // apply compression function on last block - for (short i=0; i<32; i++){ - h_short[i]=hashState[i]; - } - CompressionFunction(h_short, (short)0, buffer, (short)0); - //add result back in hashState - for (short i=0; i<32; i+=4){ - add_carry(hashState, i, h_short, i); - } - - // copy final state back - for (short i=0; i<32; i++){ - outBuff[outOffset]=(byte)((hashState[i]>>8)&0xff); - outOffset++; - outBuff[outOffset]=(byte)(hashState[i]&0xff); - outOffset++; - } - - return (short)64; - } - - public static void CompressionFunction(short[] state, short stateOff, byte[] msgBlock, short msgOff){ - - // temporary value for inline method - short akku,posy,posx,addx,addy; // used in add_carry - short off1, off2, off3; - short leftShifts; // used in E0, E1, Sig0, Sig1 - - // stateOff => 32 short state (512 bits) - // msgBlock => 128 byte data (1024 bits) - InitW(msgBlock, msgOff); - - short hOff=0, wOff=0; - for (short round=0; round<80; round++){ - - // update W and get wCurrent - //wOff=updateW(w_short, wOff, tmp, REG1); - updateW(w_short, wOff, tmp, REG1); - - // get K - getK(round, tmp, REG2); - - // reg1= K+wCurrent - add_carry(tmp, REG1, tmp, REG2); - - // reg1= K+wCurrent+h - off1= (short)(((short)(hOff+28))%32); - add_carry(tmp, REG1, state, off1); - - // reg2= Ch(e,f,g) - off1=(short)(((short)(hOff+16))%32); - off2=(short)(((short)(hOff+20))%32); - off3=(short)(((short)(hOff+24))%32); - Ch(state, off1, state, off2, state, off3, tmp, REG2); - - // reg1= K+wCurrent+h+Ch(e,f,g) - add_carry(tmp, REG1, tmp, REG2); - - // reg2= E1(e) - off1=(short)(((short)(hOff+16))%32); - E1_opt(state, off1, tmp, REG2); - - // reg1= K+wCurrent+Ch(e,f,g)+E1 - add_carry(tmp, REG1, tmp, REG2); - - // d= d+reg1 - off1= (short)(((short)(hOff+12))%32); - add_carry(state, off1, tmp, REG1); - - // reg2= Maj(a,b,c) - //Maj(state, hOff, state, (short)(((short)(hOff+4))%32), state, (short)(((short)(hOff+8))%32), tmp, REG2); - off1= (short)(((short)(hOff+4))%32); - off2= (short)(((short)(hOff+8))%32); - Maj(state, hOff, state, off1, state, off2, tmp, REG2); - - // reg1= reg1+Maj(a,b,c) - add_carry(tmp, REG1, tmp, REG2); - - // reg2= E0(a) - E0_opt(state, hOff, tmp, REG2); - - // reg1= reg1+E0(a) - add_carry(tmp, REG1, tmp, REG2); - - //update state(h) with reg1 - state[(short)(((short)(hOff+28))%32)]= tmp[8]; - state[(short)(((short)(hOff+29))%32)]= tmp[9]; - state[(short)(((short)(hOff+30))%32)]= tmp[10]; - state[(short)(((short)(hOff+31))%32)]= tmp[11]; - - // update offset - hOff= (short)(((short)(32+hOff-4))%32); - - }// end for - } - - /* - public static void getK(short round, short[] dst, short dstOff){ - dst[dstOff]=K_SHORT[(short)(4*round)]; - dst[(short)(dstOff+1)]=K_SHORT[(short)(4*round+1)]; - dst[(short)(dstOff+2)]=K_SHORT[(short)(4*round+2)]; - dst[(short)(dstOff+3)]=K_SHORT[(short)(4*round+3)]; - } - */ - - /* - public static boolean add_carry_byte(byte[] x, short offsetx, byte[] y, short offsety, short size){ - short digit_mask = 0xff; - short digit_len = 8; - short akku = 0; - short j = (short)(offsetx+size-1); - for(short i = (short)(offsety+size-1); i >= offsety; i--, j--) { - akku = (short)(akku + (x[j] & digit_mask) + (y[i] & digit_mask)); - - x[j] = (byte)(akku & digit_mask); - akku = (short)((akku >>> digit_len) & digit_mask); - } - return akku != 0; - } - */ - - /** - * ROTATION on short[4] - * */ - /* - public static void Rotnb(short[] src, short srcOffset, short[] dst, short dstOffset, short rightShifts){ - //0>rightShifts)&mask) | (src[(short)(srcOffset+3)]<>rightShifts)&mask) | (src[srcOffset]<>rightShifts)&mask) | (src[(short)(srcOffset+1)]<>rightShifts)&mask) | (src[(short)(srcOffset+2)]<>rightShifts)&mask) | (src[(short)(srcOffset+3)]<>rightShifts)&mask) | (src[srcOffset]<>rightShifts)&mask) | (src[(short)(srcOffset+1)]<>rightShifts)&mask) | (src[(short)(srcOffset+2)]<>rightShifts)&mask) | (src[(short)(srcOffset+3)]<>rightShifts)&mask) | (src[srcOffset]<>rightShifts)&mask) | (src[(short)(srcOffset+1)]<>rightShifts)&mask) | (src[(short)(srcOffset+2)]<>rightShifts)&mask) | (src[(short)(srcOffset+3)]<>rightShifts)&mask) | (src[srcOffset]<>rightShifts)&mask) | (src[(short)(srcOffset+1)]<>rightShifts)&mask) | (src[(short)(srcOffset+2)]<>rightShifts)&mask); - dst[(short)(dstOffset+1)]= (short) (((src[(short)(srcOffset+1)]>>rightShifts)&mask) | (src[srcOffset]<>rightShifts)&mask) | (src[(short)(srcOffset+1)]<>rightShifts)&mask) | (src[(short)(srcOffset+2)]<&NMiw3eZyO{d^Rj?^yI)K+D*nZ5%#yqRXCE!!_Y$hyAK+XHkU!|} zlEmF>6L>hd9-Sau>?;R3@2}D@Zo6dFUAHH0H;mOB5cd57d^g}b@2?sNxkP6(8V&*@ zt$4l?8Gd+LzSg{A|Nrg(2QUyb`%-eNP$k!tg& z28WYpG#eK!F;cCbUHs0HUex&;JjSl$_aI|A?ozFlyNvsn8md@+RPSozllRx_CzW?D zF**u6krZmJ!QBk}u0>O33GdP+$C!CC_(~mn$YW$;Lv@+(V67PmM9rALPOYCxNJ6Rl zK_js)ej5wou_|ZnZT`mA#69u5m>3z1D(i7c6v$Ew37?U?&WsJ##pCRoXSl5N&ow9( z7||v5gL)LZ(nti_RqL6%UE5y zjF?3?XQ7*kJL3CLZG1mIR4?mj^UE-#7IH9*WX*^!$6E=5;o!rscCT|#@GaaC1&Ylo z-K@%DWDGUqVh;)tpBw_*l@P^IwW5~om6~Nf5J)peI$cv~mhe}{qH49FPIxQ6xmRNrMDLtThLa7q`gG}L(e;f?p= z;nz@nK_Q_@1Rbc({E{xcnpL+}E+{0@@K}&nrv@I=k3S=is}~d!Pl<`m{Hj&I(@)Kk z5WwAdas&F1C{(|Vqs&6Gf${jQsfB&Sd@`m}Jjn?i;ocHFBjS3G1Hopjw@%Pr8$UsO zyYv@Q^9h`g+(&RneD|igKOG)4BSV?emzcO9Hu%VOOh*G3gE7oW!9!k9=Xt!3`s$j=h~TgRaM+;ftySiw zz-?Q|3L-;lfsq6x14C%U`d)kn>NCq&pf1j&_$(x-&*=g-h@;^9aKM(^fYsyyHec@R z)LR!54qrMvkg6Xr66b(Og1=ho+jxpCubXH?TYs74vizxfzmfD>UTa$pS45b=Gb6RI zQ#j*{)WZ23F^99&IT+H?oh1oOk+>EK_Ziv+2uL8BHlhuij zr0UhQ#1ylP#Wsi4Ca+n-+tguCT5i;P|1Q;7N04)RvPk_ER!jDIv{i zWdRYfeh^cMRKtErJzlFYd-(m~VQIB6`_IDAGBk-`M`3^x5`Wpv(o^}FoRC_`qD7BG z+JNT^ZXnP4Qwup#Mq+`3>A7YZQQpjtl=AH2cjK!9P-PCFH#!PsU{Jp`O9N+PtSuUW!MqPk9`^wkcRb#si{%7uaYjl}Jmv>;r}SPx{tOc?v1c1@0@i&Ae^CyLE* z4Y7QLwd!7xhXLf6Ekp`sIQtcJBvs#QB&MpjQE!eB z^k4#9!}5cLcL{mhV|IONp$u)JI<=74F%loM)c}Q5YOpAAVcu^Dt#bRPAk_5Cu9+{x zYu#d%LV{_Qaq9G1OVqC{;hxcKcRt4LL)p$cf}sY;Mm7W*jSEAk!1fq|?7%;Uhx0^? z$d`&1W~mbHpL+D2*D<`%h{Dx%s;EE+72}{T6?l4qhpIw$IM_HIgl|TA%`!a>zE7lj zsv!^rubX4Nnbv7))yfZNB(ih<2Uwt=nz*2h~FuTS!BQDJmKe%Ai_-q;$CVv_6~( zF)kf&vs%AJ5^RalkYA-CPlx+c_5If-D)6~DaTc1X)9n`#!Zv?)0OJui;$Wh%s#SOC z)Lp%xa`ms-|NB6IN-qMH?lz!mVJGS~zK^=u##Xj*rKmYpT&L>AaPY$vX3OdtkKjVJ zFY_49!qEPxI%?YoLX`y&;e3$xccmo@h6UG&8u$Qlq)ui@fO0yhn)+h|Hml^`Laja3`7(jMh}$O)DTXiocG=vG8*U z+{-#{L@|(sVAHR~i!+u(>W;q<&;1xmhnd%+GBeUYl9@kkYpCQ!Xue7qJ8`bAC3vW2 zcnmRozVvT%q#yW@;So(u{&x{)ZL}pmr%0HV)T)7B6I38;NDJkSr``wfkDo*O(6!XX z?iH-8S)YCcsged5bWbFo^@q zkbv?Qu#9?P>_&$4kYv9IA+3IksjE~qMOP(8!XLs?O&$x$_y%m_1!mbccAR5fu*Rkr zaLynq$}BE=U(KQfVHCfH*a24VQ(q zX~bFenKm15;P~83DbN;jKo)oxV>WOc4FY~LvTD}Hu<5wXK4vG=S_?m+Ha>kc7 z;9v>SdQv-%l4=!gm}Fr#(qCV8AL_1!#4Xq2K!@Ca!x#f zhU3+k#HoM6bPzlO?L0NU)=h16Qwy^JG{R!RhOY~dG+aH&DM+eGmc}=@WhSkbnyiOy z12nM+7N6L*=QRgVCe`Cflo3^03q<;(YT`ISmX((r>#tM4pvj9lj8o2^IGZyZrzEo+ z(^;|)jW>+op~6MzN@Hs>5Ij`EUZo?)t+L>uGT!594ZdFKnb)0;9XBJ#(~(}?_3i|l z@uddGuMQ&f66ShyUaU7AgNpA>hmV<&W2x-DX?)fnTfU~=j2#R9{E4L(RU5-I8cXqo zxyL4S-&%>AExfrMH`{n~6K;0!=0@D?=FN4u*~^=SxY^H}`M5d2o7uQ&Eg{~ z+#KUg2sbBqfSUy>$`6VK1Elt8R-uG{HyzPJ6WWpl!JLV zkd~Y0$}t_TDr4Fsh1tKryJ%ODxZ17aa;lhP!ya8FKm-@2n*apy$L?)rDHwol)0(mVIBir- zVx5JoS*~V1mpPs3Hn3UO%$&9ApLI_4RxA2{o9-`6ny9L?tC*|P;x}QK)j~UQDy>f8 ziW)y8b9^lyR@zlcdaBu>NM0uA=H{K zqu(jDkj^_Jfvh5|5)eEGoUB9}B=&(_bQsxQg5+q&6wR)!_2*R^|B9;_*OR!O#q~p6 zFXK9bs|VLtaBatR09O{55g7Z=6Y!4xz)mgD-#&g72F8sK_yd6nfg(vQ&RlP-I=|X@ z09P%pcW@a6AN1$>pa+tkU(U&C6nMAZFumHi^ulVRZAP`xZ~^)^v)cGCe18|$O-TO@ zo;TtCQQR9vWB+-IykkGGQ|)pE{OKzy99J~HsK_Yt%hL(y<8IXb46a|`dYN-@YPF}<|`qHs}GtyTa=_fTR6c@r(_y*xBc$Qt? zA!N=B+w|xN`xv7 zq-=2BQLdr|n+}UBQuGkapmW6kaJKktCfCOIjObio-qDTq0# zZfqUxy?s3=e4Vuv(uB~%T+FBr01Pa3^y5pdfvj*QN#~6qj9X3#Qq|q`vUd(=-z>nl zwz(2lQIx5udK!kY_8O!X(v%!d`kD0?%nF>t;JQuU*1*zw0Jqkhyg67I=U{bu4n{}U zRKOxb%-kPG?p$8F)kzxS0g*{t89(&`&%OE?{3{#Zgi?acKXRWaI!tr0Xdq@{Vj4SV zJJsMsSdUd;kPM-nK)5P%cEYF`R+G+|{Sx$}c8&e^N|vR+g;V0S{^4W^>p2X2BMT3o zgQZ{tlrX&Wu-KpG3_je0*-|=k0)`~)$f_FOx{KCh%8cJVRg+d>)dyKP=}JB7GlE}v z5@pxHOLRPSm9b`>Jd{Zm9DTKnI87BXXf zbt-X>wvNzkq#D*5$J_|0S3u`#g@wdBR2Y>yJs7W+Ffrm{dclYE;c65_A6yu0tK$j{ zf_i{$4ASi^zRc94bD`a}b!*nltyQmmLBO-^mRMgpI#*CZymRd-Q)kvIW+ppqW z4$Y3vr(p|+(n01gO3u{XYThX#{x|T6W1z(U4LQ$XrR;TJxDUX06YHo`M}HwGr_z}2 zcG*@MSZZaBsO(uEDmw+D%4CkAnC&h&7~*q(x^VzqGn?eon{HJ2Wz$%&;bSP2I>%s8O31r#7K$z)Uz4Y-9ID`{dm`wuOuWEb?xu%?$VZ-{X zsBh!DTvrFBNqH7ZXIfA_bXp_7-^Ww4iFfHH4)?}`{CpRwj|cQUS67mvI|O1==%ZGh z2sYcf~D~ZrYDF z**3qWD{_|>%FRI}fM+s2$P`rxEb?ODq47bngjanJga%zQNhcx20M*nZG1J5%b?Pw4 z5#r~xJfB4#*XFt3A^bb@<{s&$F4SN)@-ukIzwso;b);K;l9}OCuBFP2^@=Hl=y{bJ z%}4SKYp&@`Kgci#YU-d+OR%AtHOm-Ql`UJe#7HjEY+I|5Y(@ihs^xRicXR}S1bW&1 z4y_mj?dDQAthYIN5=(O41y}^aU@auqJN`q+)d>|jS4Wqu^gs>C&_v_i%=V>8%vNeS zv^yKf#KlP=TsM)wS zIV;%sEfKE7w>-P?r)YWbqGUjf*6a~H#l-&lUV3DhiDbL30u~l;XdU)p{poK4EqLV{ z$ZiVJN&9KvYlJ|(3-QHur4ia59_usM7@hG$Fw)^ne+zX2s-k33_6TSs`&<2c4p-Uw zFG`AOho%!m(f4Vtida7njb`^St8EfIcAE5!qy8nyJ5mdeAylO@`B@bQMCfKh6ePOA zn7id}jlUs*maw}WzL%Dx2ar&9zhvQxPlQBEC^vTTXZCj7S)$8yNwf4N_8qQ7EH<4I zvxPbc{&g2lI1@){iK=)5`lhu>*=HOU{h(TRxwW2)T504+6my-8tKdF|w&nyIj$#d1 z7ZYC=^Go4&-~a=fn5Ozq>S?NQeS9B*dI`3o46OA@3%*g-N`bD9Agg1%`w}0~WQK_V zo>i62h0*&soy)utzd7jg1zppq>6e}#%|Rs82{ecGr_xvX1&#xg=!F7%*`S$r4opoD zITrOg3YP*D{yE&eL|q0q7VLd>5n+q<sQH`^v=8}DZOF0(C_MH+;(oRH=Ab0wM(^&8eD z@?;i*)u0wrr_M>bu54eF?><)<9x&NBgb5^*n!o}#G0JOD(6mE@a8z?2vV zkpil8K(a1(<}X2+-ejEIeO9^q1f(^MAr5`AvfHQDb~qGQN#WWvAvsg1yb@K&N^hTv zotDLGy`8-YEG>Ld74CZki*9{-(Y!K&fC}6%9ZsE^ki@LDPmKINM975u!0u}NAAl<@ zpMwog33`N`5;OIP+9P$gO=@jK#S5a0fePzZ3B(#>7~!=2z0T#x14`t?MH?3m!tDNi zpung<#~yrd!da9)&dR^9i@CzV9KQYPYGeAsYU4jfeOFfL^kO`7{VU~=A48}|7TUih zq8hW(0Vf=WOZgOv42q)^&~j=40!lK!LgzW5(~{UMJwgS8Afj{(6gU91Vg(Rdm8;cK z`AsWGDglV`up<^8(}H>9N$KqU`Aq=rXeD`5sy<*OPys{DVas(&3^Q~=@)~(EFHfZO zw;SuhD*|r6Ug<~t#imd$E!=NSvj)_f zVF!bGQn4D5(J_C!w-UL9Z~f}u+jKq#73N2bc`Za>AwosOT^Yq4BE$UX4LJ9WXUKT; z5hkZQ%=iU{UXmGUOK_eP8%oCrl_fg9nf27h{lCS~-}h%02VJ@Ll%5I-y1#Ry!!p<*T{@~h4Mr7w(9Sa;G-Caj zbX)(7N;XFZ8JCm`>Bw^iIQ~Pw3Zr6re(DE{lH!R4oO+Hv`%i*DHSY}=UL?Vs**Vh0 zL%iOlIq)tJB|>A=o|(S|s}kd&ZvFuT#GhYBWzT{)w1C8f9?$4|<8UUYJDT60fR=|1 z$S{WZ436gSbuAXGKJ5{@0$mGo3e1V*<4~da#rdzyU&YqerLSb^&)cQ1c1mBMmH`}e zTrCGg?0v##ookh7uzIv`b$-60e~y8_kly71*1JV|B7|uJI9uFiX9G;r3z1pgcr;lg zKQPu@0b*DnM8tqqVIiah?o|fdmKTNSj0wz$2OHe{2 z6j+k8A@yUCg75|g(uk%EAUuDa7ZI|IkQ}nGA~srm|otDgNr0(2DAkB2fXg%y&j4;c(l zFEtV~QuUQ!3d+Lu<#K=F#`;R}nVgFo@h@m&4ZId#PD*JK?t}{w8u^(h`Chvr_3XLS z42NpfS9Q9Gi9rtKpbQpzqgLlV0!Gn~zRa>vML`5A>^V{ztAil{=+AO!ahe$`cSr=D z8wM5j1Gf=|h@>Z3uzm(0q(K=W_8wyluEwvaO1bPE!&5?)Li3js!|#seSP%pDuBoL!^MIbJ*OWSdMv0un zC(V*tN>3V1Q1tEf)#94Tf zk`Tr$*6v824t^(YtnU*US(x244)gu`KG~Pz;=dG}jrh)TSRz>V8-U!GWj_%+WU6Gb zY-=gZegdV%!*@GKwW@?0lW6bvurX^cXu7Jb4O79~j+L`OzOxG}A<}hp$a|?)gx#LN zY(ZlC5kNax$S8gch+#;S6JdkJNo=G+-N*azgba6lIZz-?IL#ijn+=y&$tEZ^+bzJK z1@LBIFKcV#3-m_pM zoCk0LBK`xR;}kT>$6W3Ym{C7GdIyHCO7@-+udwB$Ee^Xg#I1H)vGUonrSF|LroNhb^S#vb z-gK?a@qGGG19@z zMW|~;U-pjCmpvbXvpr^bPddEUjO>kqo}T{Y6qJAGROLG`W}IW3!2$=_b}7%uh2c zVAh0c%wTk5o zeQomL3nJBBs*xRTuy5>1ldUs@4aE>pFy%(i<`G-0Af#t)71fu~l%twhaA35MQjgp#a5xII+lDO?$O-i>8@5>BbfMmb7-B?J!??pxq+^|k zu?9zUf^1yDF(6Zo0qaTGvoWGISB2!Y=Bkh}T5GjcNTdzouDXQ~y!V5<@+{S^o~+dSr|~+%%#I zM}OpZ$k88Tb!fX8+n$c?FvB})<87dSm+q|?gniEbbo2*@$T3bF{SgkFZhvkyBU{sv zZDwp63?XhmwaGWz1mbs$=9@+|;i{0lCR`OVMiY4|B;Gj+bavliL04PkF@>uw#$bvK zIc))Pmop5q@YvWUt0x)H@!d$J;WGM2r7;Gti@GpHv!q4s*cu|Jn=`Z*AYcw-(?zma0kti$#nh>H<{!3iHHMywtFkgM&4{L zx$$K&o%d??=a}Q>fW9UVN*8W696YqC0(d}Y_{;P)?fEL! zC*wOtbfi3WkWW3q#$TfWyUoN5*IgR#LU#2t*;EX#HCn$Ct!JM`=1imq8}!!9(Ceo( zv_Mt8%>HCv_6f*tJ&*(UviMCvPdxNHo=s=PUR!p;F9Sxg1YKqJQmEQ7OOWcZzw_`@UcP~jA*@l?UTlX;LzOa{-J zTn_#%=J?rO-Wtbc?ZX)WP2?gPJ#y1LAI>Ov0u~5!CB;9*B>Jazvy&5SEX>xGPo{}D zSc=N6$vUNAT}a=ITTd_J{dnYphTyehXifJyb+|zDXQrtWiB;0&HM2+cav1$U+nsDX zg6I%g9RZzngW`3)n*}Y{a4neJj2-h)kCp>f;eki4rpGUHfF#}|pN;bQiT>iH1C0D? zA$b#g2Akm?x2TtQ5(r4@KRXF;ETmgd_p2Q1=lnTcb5?t`*DJp#p{0&R{j98G(E#hQ zzThBfn%TsTW53tDR%Q-vYc1~90B?$Vt!s3?Dai}me273|fgX>2GEaW?pwn??*;E6< zm&nGGm5J4(=+gMt5xk=Li2G*YF&jv|9jc3mkVCayLd5Eg?k2n{Ge#Ce-a3s@gcwog zMKNSROr5f%U?{r)qm&agbj&*h?2I@;O=tF_l-b13)Z1Q!#_{GLKc@tr!kRbg;16u` zyYd#<<~IXh8aGqjMf^F0xCZjbAq58H=u8ZOr0=)itQ*Mw2+7&U-Dgu1PO!k$tiY33 z0V97k^eI(c;MV?PLd>#dYTvSft7o}x`QQu`fYd!`eh zUet09YR!I?u35&e&@bP>vRwY`ky-yDJ?kGAd@9NZksj=H-FbVpBe;VNJl80`7bBq8 z{I-B1F@*3RG;%pd&C&3Q9|3CteY28&Rj$)=3!%;2^H8hzJTOM?LS(R)tXJ!5UwaE| z$weO}?xE`_vvBrKQ|r^aMAMcLMUKw)C$Rla5T3+_7G-c@PtiXe-%m<$H&4RVG&l=~ zDs9?~<`BkLB$L|g6O-BK3TlF%X+B!*ItK}vb8Wv)1~TUpD3!xw%) zE7p1HTC`(v4+;n#UE3o0+n7HjTuI4;y>VCGT5Vhmek-%D@7{*JdAQc%Hw6vmAJJwB zN|VL^bJxv;GN1mq*V3Uwv z>M091(p>QrH<^4TCH+qgE@5mxq2%WBTS4BC1V*qCC)S9ZQsX51Z92Owv}2XM2i``+ zxju)6aJK{{>0Gnx2{t~BA2b8h(I?r~^juqC(s{-wSBX}6MxGbsp5LsWPfgZQRj9N) zf9nYsRq{qvB8Zm^4M0Kpd)?xeIxt&F&7qq z5M<>7@YG=AFNu`MAcCC$5JlIZ5kM4k_w;%T9%GA&6TCvl zyn6U~g00Ch3m!16n=viz1QUz?<`E8zcNoIHs>C8Je9UAGm^D{@g&9Yx-0jpN)@nZx_K%!*(Q)_+D3Ji|G zfsnd_BUpJu1qgxTnToH%JI_qXPdYL`x7!p05mncdY`QfRO1me)wo?ZJm?y!kGQPvTL>O{je&*!sF~JbB+-k;>39p^8w=NH<$WA zJpQ_PH;|H}nNmKA_=`cH9H5cZVaMyOz+!FS4bn$TrF^lM_;o^m_3#U2VV%I3TPivB zZIG|yI)|2)A8Okn7CCP*hD)URhw~vu2o?;E!%~f}A{P1E1*D?=bIIYyp!f{y^JA2c zC|ONaslm03-r0W>jE?ZJ5P^-MF?|GJ8VxxaF=4LXZ(utE87|96T3IK7C_!gCbyvFh zh4VW^kYMQot4RCPkyh-%uT#B3WZ3b8qgBJL*b;dIsR;bunyPQT9s4sQt*Pv1keiaA zHTCvfENbHkw{(V&RfSvE7s}o({CK(!9NvZT>a0_zP)j4>#VybgAz$ z)&NbN!>b+fjXK2<(ji6-? zSyDzKmc(w*13VV=w}8Puq#z5~&Mfy!mSxFD5sz*p#%K2-i*>(5et#Z@Q$Enx6rt_h ziQ0cMy(9p4RuWMt+6=om$B5@QGl^68>4i2+GNKF%@^z{M^bGdpV4q<1AT|lU+z&dD z#V3`WIZOq$pCDHvi!+tDO1goW9j7h)!^szjGFdliJuumCtO9-RW{s?RH=kpTd>8W~ z+?x5$SU+K7eLw16pF=t#>`F+0P`+ug>!JOP#7x(sgwK{8fYn5HMz8|TC$l@Sf_6m$ zN3QAa!{pSb?D&(F&N$r&=8aLC+h%y8~eE@oT>(rkACFl{Zu$EG*fZ0KxEm0NG zk97ahjbWWh_El==+G^FTF*9jNBl-vOrLRTgMO$A^Y@iX00_J?DmT|rpLtKWA1MY=d zpY?APN8Jz0mm+3y7=~ulW0k~n$qe|&U^3(rB0^~x~g z*eZ~4E2g5ZD01VGrd9jP2))UrSj?jPMDOfG0F~sdq07 zHh|qx1km~$aRhHQf5q@?^x>w{%LMir8J<-1iF5K{kL)Xs?1z$T2FQ>`Ja|j@f{T|> zZCJ{zlVWc{Xykek**871KXq!1UhqtOFoh=x!E=l?Vh2KxAQdKUCuy2;44s3IqgB;^ z!ebPN_Z6e7h*p%CMf=IMc#=l&iYz`>g}X$QMfOxhI`c)@d+0Jp)s_l6yb_{L%e0B) zu{D*e$cw{ku)#sk>>!a))fm}ti4ppt+#B1SE+hM*2Qu-7o=%I8TVxfEozaZ9 zG$(f!3W-f|%m6KOu(AA8ErL(0eB_h1I<-JQ$-)T$O!9=?jY6`z>LN!DHo#W|?p{0;#5#QEtdO57`%n`OC5uI%G}%z)ansU!6&g3@g&Ak;bso=vdR= zI~*VrH#w{}N1(aDED>lBLN`5Mip5*N$m-(`IAaT=xN!oRrszys(BRZ(Ft9{P3es^$?R8Ifj-PXlw7w zmBSm50OrKtrai#ZibN5(6)y2XOaR8neL{EumBBv2V}yBzPcX7A(w54epT=i>+oyw1 z(cmy+I1A~Ctw}jD?>2n3j7KVJ{tjwxp}fQW*LaU<4)34jJuM>KKgRnKeg7cuC+Pb- zcwemVKaKm;!V_Ww6lU?F5d6|yQkeZaSQ(L+djtc=kr7iXzZ{@0CqYWkC4NJ60yAkW z!yxkkof-8QJm!^1_&a`Ne;{>r(wmPsl*;6}4qf0>($2#tL&c0E>?A8A&+vM8N-bV{ zNNv=|S(g)1gPLnq66Zrh3X8b~SCpEEB}kFVC-kvwJ+&%wEt6oG(nH9jNA{y0Osfjk zA^!Au0TOoU1m8LciLPVkoaeO_nlozD6sGbE!nU9J!*dFd+3$?u}v8ic|3&)~ufqG(~g^jF*WKw1B7$xE5e zeT-W4ViT4vV-3s!R^2E+ub0Y(|A@-4*;&gh@Gj%fX;Waz{hn;{1u40fO9E`1m!Sf? zKA?tVl!ELOHmXHRJ8*QJGg8@<^drFNZosf=rk3=0zIqBO!aQHH4y1bERp^JsDu)c| zQO+(wS%=8e;V!ww);N?jla~3!_6V)&rR1q z?N6`%mWKsC{3x^^jKlZ%V!wAnagporfW5h~Zx8V7o?X%f>C!J!>qOx9@B!8b#;==& z&nfHA2h*h#3^y#PK+bVVU=b#B+-fF4+5=XZ9jIH@Y)PSH7^aA^V z(2G)N7%U#)_+vi5Uq2t=__ZZ|umL6)z`=GMh>HREC$G|+G(NZm6I%j0CHR#K^xfIP zh6;UGuGc{Dv?SQz*J*)Z!~4>;K~EA!Sf8 zIDWoW}bD#E2MeLS-76r&fI#%NyW5XBQR( zF=Mwen;%4=127UGCV`j6-?6`T&)iv=Cztgv;y=vxY8H3K!i=N;RpZXU8ikqCZEXJ%5Wj`%tD_7m%z%em@0+?I6xWp9zGfp$s zux*_S7Teg&*K{-JpI`PyO-McpOTReWoBX2Ff^6cWn1YLJy3P*lbkz8PJZiLT5y-`= zaT`3_o!L?-;Y7|$>_X(g!Sqt-RKMO_)=}eg>S!0#>WwtO706*0V%0FDd_6=DhO`g& zDw^R|te2UW?}Si@_l3m#pnTteW##50JM%m*>}mB|H-j0|nVSfI6F*aLk4x*DgZz2M zno6^gH&`&={g^D6@4%PtH9AWhCa*KldEBDa#|RlFcj4@gXr(^8!yNtsO0L3X*isP1 z@rNHq?o2|G7rU`_hZKn|GVoMY=v4(Q?N`8#1bPs>T>V5f zPH^PUaG*FGO3XnI-+3Jy>Qb*7@4SKKDzQfbRpH}F#8ZT-A}6?bdrUe51LhcElBu5d z^3HUqj`7K?Zx+Q@Ebz`7&{?T&cwnkt1jAzX0kC9rl!EPw<<3#1VkNQ zImP)K{Omu16;I4@y*hvel5{wf{d6I)FmedtM}YHWe^oe;gqypiDiWf|ES(-Jry~L| z(#0m`=su0ZeGZK0IWS%_qDpqL0-FhjldwYdu5pHrs&h3w)H=9zXV{U`~6-0oQ(3!DvTJ?ONOS!Rb3Z-VLvHA><2ZEQ@&x5ZP zA+M4CH3&IEXQtSl!G_CFRcv70nK)?=!MNdmy8iYOo3!%x62I7lV{eB|=9q+)KMraM zV6LzpLcY|fuh13Lk7SY;FnJRufu+v~J5u<4m{}q7>@c#1rbZywI1LyDH}`{w1nzJ@ zDBv`>BLkoZfjib;6$=n<+D8Jngoeu&qS4H4!yV}dDY$S;1%NxBB#Ldg0UFO5S8-s+_ZV&ko_B&dMfUqbBe@3m zUxrJ^~D!=CpN$${d2Mdrk z%3azhU5x@<2ooId&;@ol1-`)ol(V{kfW$%B?iARr3v71^+|2@{xl&s|my@NpIoWA6 z;rTWvJL9HwMfW!dxk@Whd(!e|+y?G=(5zVhN znW7nk8!8T$Lo87yJ67DuE%&j#@hwO2;->a7~ojLFj-zvcZ&H zOd>=#0AR;=#FI7P?(O)trXZ^5A?%wLy5;o793Ju z;qhwYFs?_}R~ui$Rj{Gjn2+mjT;IUef$P6<&D>aRT#f5fxK`qN8W-z7f^R$CA=P(3 zrX6$sFjC9+I$9nU01*Y3I=>u23^zo`SW`+@Xkd5^rtIZ1&o3P&Ey|(?F?)hVWGC%_ z8LQAo0>N1LXUa!85@_hDY7V4q4hJIpvb*I`;GN?j+0-ir-u(oVh9(DSAcQk{=o*4R zh8x@qy#xW4UBwcx2VMn*ywi=XPjJp;Z_yIC7@7(2j502;#-%N`>_?^SC@KH?JjxGR zbQl=W3{<+%3{<+S5IB&kPF*8eyX0LCT~vqjFomW%#7QI00Rnc`GbL-AY)NV(Z(;0V z1qidy&mxrAD)_?j+k6=&&R)P_-P+{7fDkVNl7-8u!t8zuEZ4oX3C$5^_FBakf@ zfLJStRa6co`&gPuvS+3Lf$1W`r&ENJbF;59ITn~FBsI?=sVvs;IO@eR>M)q~sAqTU zO2~G$sAijEDL1xht<{2o-6@57V0T2F5nZC-u)Wvry|oO&HkA$O7#qDe*TfF1P;u0k zfKd#^qJVyYjL5`TsqCRdANm~Gt*3vg81=(Kl+^mNEfpW z7z?|W1QuHCrC)6R8M*F*$@uTILbLF`Ry}c^gK>chU5l59p68VG(Dj%4OJfoVPZEFUu#J_rz8=^O&+E- zg|WO2-TK}y;GJObiN>~eWC>0|0A(Z=-buGqLSLebg z(}InVpy)CpIQgCbQrKhyOy{okjyQEL`PtM$zDLJM*4i&>OD@sSrrs@2Lh7L0QcvoX zH!&A0tT#_NXY4;sP#3h+3wXVo- zth-X@xvv%Sg{kIp4_NAln8|{APk&Zfr@pM8Szm8v{#y08owr1@+e7xxSVc3X3tYDL zS<3_P*EYk6os+|mPpLlGsUa@t@(q!f;y60{*ZX*1WTH%dwRQ-0 z@1Z;0Uw~6=sFPjV!%-0T9wmLs8UwX80}cY5@aAWOe-Ni?VU+_LCOvG7WrkqWFPT~c}hX>sh@NMzBl<(($h1`GgMUg*qxyzPy0(z*qxA&DWwp>d{Ig0SifjRsDZ)(ni<|slH`4@c?2> z8XPx=y+zZPxX6^Vrs{+O(1m!lsl(>;Rrk}ugh&~YU3%|P{eYZ$Tgs~4dV;6!pA@whb6%c$r~3^vJD5ZDk`r} zKyhJ0KdM)M5VL@B=F7)JHr`>D(5wJ4FDPNDby)DXtq%zw=z66GYFS$wzfB8r#2Df6 zTu|G#Nm5X#h2BqW@|8MYIhmGkQ%nj!GZOft+C5-mJ3JPC)^-EZ$PT#1GV0U6 zq^QDCQxH-yg=hySemnme1BVpPj&;h#6m$l1Ky+)Uv@-)JPGiQ{*HP^!%H(Lj%1opJ z=VeN-e4NsSE7CQn3a+QhbhH$2L}&gG^CPiiQK{U;`Z{VV<&IB%ytCa+SV-b8tj|eY zhF%=5ik+yRhG2@EDk;SoR@l9WWhM#6?6$h4$?kNN(+oK=PX?MZTkR%7bg9_TGdcb` zLI{g$8|#nrbs53T4m2tT*-ynFgT3x|?9!_Rt<&<*!-I>!E-I>gf3j&E(^()5K-%%~ zc-qbv?x9XM^S|~wnMr3fJAf`fgm+{Hc#vRzB>_VDG6cGfpW_WuIU{Xc{J53v8Kbg& ztn6}K865T-pC@*JmEMsU+IJ4?EtMU;(rwv8rM~)}G)EIcHQM(Kj(ya==L*1|{vmm! zzYL;o6cEwh-W+AXDBvu!F5Wf@L==d35yY=xs)+vL5O(w!A!Hi)8j9jPD26q58S@Z< zU%wZTN3sV1ig5OT~I_bTtIoX78qO%ZBri&T4_iPX%Ja*@YooxE;hxQrP9AC9Bh~d}C72`J(@rGy zXb%2WFQ@WjB-#xKQ@=n1{OH-;l_)hr4f?4D^%e${$vuT#&T)9Q>TIJz(jvGg;Tj>2w1KZnvwE1*iT4WXtSBL)l& zbW~U9J8{X#e23mu`*1x-=Rw*z58~z&Z}bLD4P{js-#52@g8d$z^&hbpa6O4iEwb>b z-z`aJl5mmwb(mmP$oamt@zS>-XthXGw@ndqJNsjRBb|rz;g}C%%3&`w1RLOj0nj)( z*AM$@1Ap-O+`4!H18q`!|KEQIsmuPqy65B_QDOfVph7a(F>CX=+P9k) zB80#Y#q0n$b4VADzXq<06A$r9G^3vSKbWxssvY}XZ{{jwzCSC~$M*vOG?*e_P&6z~ z3~c%qYT>c6q61WGy#R$Nk+l+|*lxfy2no&M)ZgBFpti6P9Kea*mp3Cu)rk{QM!$Z= zekseeQ7>g-QP>QQ_>#?+%iXrrqjzJ@iD@0)H&94Q3^x7|5`~JCM?|aC+Y`W8SmRn9 ze2RyifjtrYRNXAWPjm34WYhYcA$|~0Cy2;? zp7q6WHnKN0I2k)>af&iu(lI5Ov^N-|7F4<*JcK|L;DWQd5b6i}xF$b};3$wO$&h1_ zLt*z70L`elfI%x0{+rFh?;IX1khl`a{>J|lP*Bxuo&X5~2fEQ`Ho;ea!n_K}B+hSA6Pf0md#V?^W+ z_V4!sMvnO$3J86=`=Gsvz#*XZ!N#wUo=8MdkB$S^FCG!Q5HYqJWf5r*1nAJpB)Ar6 zZkH8u48iKkVNM*v57p+y<^wpo^}ZvJAhliU-HK$f#$Oiu#9Ve>LK%m;PCaVZHK5MD zgRF%q%Ye2&5uP<_ps=51G8h|dVZEaMwiXkVm3B2h)zvtVVhv2;9j61#>r+Q=mx|%n zsYsj;!j`Dzka&(tv7d`p1j5IDb)Bxc+dpc9P!pH#)Op%&^2g=8f;m>Q-U?P%{m^MhOVsswVptU`GjC^qe(B03GurAHj@&d}L zL;U#V7_}T(EVbwX=H9_~Q5=1sx&PTU(g(Fsmy?z-^XAe(&MZ9JJq)HczMMMl*^N>v zPZ5oHfg2!?>NcJF&>&pxR4B5g2#f?%P|-~ehm5^0s~TJ_!vUK@KUdE0Ud)g2{J8VGCB6Y}cByz145w{3>6ixf_f0v3mOH*HW3UTUzy|=V5WIO#2B@+zr&RR857r>r++3eG$ zUG^2pGwe7K98N4Uri-)u{p=C>yfdn<;EDzHLAUzc3J!i7Hj-}+w{3#IKEiuZy!%q8 zDuNB|5G(K(ImVNI4|ZF5uyGb2F#Ph^BhV(tUU_pttaa66a5ufugCm;ZhdO-H3z)$= zXGv$!9Q-zLMitVp2XD7}pR`Lb`ceMyn_SGsG*3CHy}FK9PNJ5UI@Rr@w@57osN(?M zC*q_YP+z%uL|3p4S**)_^iei($Ac;R9*7csbC8KxIk*YKWXiq?EFR4HNwuLCqbi_* z*2+GyR`#jOm=xK!2@XLUJ$$D0jH5U0+q@F%yhtEi7|*^8>_QcgHAiSArnE2KTmV!K zNQDF0H!&XS-#$0GR@HfW($7vylBNnlJUpx!ZNoW4V9<8!eMbxNnwzNFD$72Q;jQ<* zis1kL_QfF|deACJX|=w+!vHO$WL!#qQ3DZKAAm4baZ{zMF*h)zBftuZv51(AC}MS)~G)EVfhN&d~l%^ga?TY z*sgqE0Bn%#KVceW%{Rksr!UUZiGkgYXUNdO6xfFy6fy_s%HEAqte0<(-iPN@_sok| z+w7B-pEhj(y8HW6C#(71t9=N11lL2~?gP`b)v13HWfI7ivOQ;S*mw`NjPvJB43%nvtC_ z>tOm+g?oaj7Ce-czThERL%UP;yRk9r>yo@fAl_ldcBEt5oeK7Fa=k~M?2$u72I_A_ zuo^7M;31mMQUfjD?b6rykaU4~;jvO_XqlTuY+>myKNJAkGIvF*tcUA}$s#sUw{ z*#Uewt7M=a$$%E@u9HJ(Sc>4_?(D@Reqblq0Q(WWWb_5THt~y{-6V}~!s`~m*iL8L z@DM3rY%JWNzeVZrwp9JLf?y+YWk$A0ueRXr3~7W6+jVSZf49mL@blKHSZjUE3~z<_ zh3rL_L0M7>yWAkHHdvP<>R7tmfD$;G!|w7_)GS?YNMaIbSJA%M<72PCWmk?Tn{?&Q z6X`e$rej;O=b_`!K2%%3c)-yoRAJi~Gu3wT4tbiP_S9Z=Rys zD_pi$6xG|5;tq)i8~+VIu@k)<>jja$(kV35h1tVi2+6jn+ISTk3|QY+pSd3eV|xYA zy~4yNFzL1fu61O!y*tl2!a_FYjoe3t<8Ea`mF%M`So0;lj|#6Ly&C#NpZE<{z}@{o z1T?2%-8Ap0BOIF!XO>Oyrsd%8yc{3Z~Wr-UxY9Htyk zo^D)@mdp%4*?oO;kUtC7e9>&;4fanh19%|rv>a6qVV~A7#}H+ZfhK4oyr8*B>F|b} zF1UIl`f}30tT(ehLz9-AW|FcT0QoGtVG4 z{fE^?E50AV_YZztZ5+h)M_m7f>$-jVdKBNh-1X)aZlG3=>z08(qR%!K%@P?7d96}` zyjE$zna8Ywe@MJP45(rFUeuF%w;%z}81Bb>F?v|CVG>@RymTeBHrLBA)omNrxZ!KY z3U#P>s_UAo%2bGLxR(MCTX8`e(=RhKrhEhWcQeqfC2p>2PoP5>wn{)Db+i%=>=o z6hf0MR_dI*S-^KWp(4II6&9p9)-!m!e91)43(aGNy>&8KF_+GVWiwkW6552B#Z9fX zYR~&N-LzzuXIf#;v@iC)4^n6m+1CP*3E;6;-l+l!?TaT3SLFZpO){*H=OqLWEuQp# zYH(WeBM_D%Jo?>EAfZc$dcWy zFXH(MHaG+_z(PHKmM2%JOEjpm32<+!ey=A{f={k<1l1X=p@biPd^p30x8@#5L+jS> z@iOlopXB}XjmUPAA1CmUJte_ct_Qe`$H~^|<7BZYsgBP_ee|nB1NWvQ)YI1nx4a|0 z*nQpOh1ovb=1ttiO$@HHPx&|i5bPIo10C{^9)lmF5IT~Jma|V8ntdAnGVrR}hua>& z`xioZ#T)g;H?7YCE$1T^oNs4dfnuUW`>Yy49$vD^!JB~BFX~Md;CgXti;FFps1Pr< zICo<_!f8YuppMP41DhB$pexN@KuIa@=C(@pS$v#W9XvwRaAK9+PnoEjgn8F+H+wgN z(44`pg;R%XP;@@wIJ!i&R12%dCKoA&S&dDf&PXCqL3aV|QFR310M6Wc=vfZ0MdJ_C{WAYy$pCkyJ^JFsKNR6PjGM0Hvyd*4a~a7_ z2s<&V3jWAUKFyN|%@Prn_NXtR3bpz!Jml$TSntDaoqS4 zbJ45W*o06v3rjwapHG3xNFSa_!4!CO;(J-xtFp7DFwh2T2?2Grkgx7CoD)xnRa*qK z>BY@A=~cihF70>==M9BqYf>^Ggv%bYpHfk1aJgdvvL7;)-txm-A>aZe4PE$i)+eE2 zki{YPT&5hZFGAVc_;G>OjqiaFJQGIVIS^dI}Zdf74iv4P#m& zuTg~flUV22UgBeKKcJSk_edxLx*v@Xj_61Ty}+6Cy`Z7~2XrU6u)KI2?{T({9Ev@K zzie4rrsAIjN!)n{5UuqX(A#UbEkO)8wu^1=A;_3903(4qRdA2|@$L>tk?}TUL+pHqI6K3erUsa){VA2NcybB)|sl}v>cASxIh`FJyUm{1;BdI5nvy(|Mzgq@gJLv;AK+~7TNG@3P4u9e9BlYC z?yx>yfY2ay^HUIuv~~tKGlkg4Gke#+BM?I?0|+ta#N2K92YaL?7YP z8*E(5O3*W?M;*HpY~YSqgkNFk@YSZj4)DwAc>}ykQV2&N$9iCwc|_Miq~F_$F3_BS zEP}P^b&d-Om&5R2+16WXL%}-bs^5OI@_Guy211lkZk z2zD85=fbw?NxfZIg_p2H$aIP_D^H$_`5a+_Q3&4#&VkMr;@k!#7Vy1dop3A67MCVo z@-1G9&N}t80yKneC3+#8akh89O*BKZ?Z-KSw)KbPy{}6~rm5|3@E(pY9`)A-Hf@FA za(44mh|$Xkyz|u?*eXDSm#5_8m@r_EyQP?{`oFLOGacSHXbW3nZ==NCb{A^E9y{o02ChMf;nX0h)KPojQfU1K zq^alc(KSNVK~;U?IqYl3bqTIjxSW_{>MtN5IbK$gt-H`0)LgypBF7-$Fho~Y+dC2B zTiKjPy*6F?08M#$GYg;(>A;Z|Zg~>WI()AkV4NQQZ;UF^sh=+h`4T*`q+}M=>e!NOL*MVzvbF}@2=pVukMM%&g8gJ`x00q|G z{Z+o#2q-74L#FQ~!fB1SjtOY4U6Y5Os3r-mOYO)VNs^GCCGM=SPJRA0Vg;sUAhX=> zai?@q_1~m3jxLETT{B=AM+jSaRV)FZcb>FhhUmyD|d+XC% zuTW=!Y`0=;)5i$lq)ko7QNxCV{G5i_V6hfF9+3D17i_DT#Oc0G7#k%9*UZIJh+mDo z(>DL4?qsu}l^%`r%0cQY{Su>U%*F-L993-Qinj^>O96j21N%e8ZgMKFiVQx2gYXau zKxaoKmcS+60y|+Q&`~-l)29EExA%dMv#Rp{C;wm?l1>VV1SmB052Un|02K-$kdy`s zv^ohCQ7hmIIu=xf89->DaT4itI&NLt6<1b?t;#C8tcz0FA}w2+qV;RQ;?|$_YryJ0 z!^Aae)WAmidw+7ware`i%;uFR6&ZEM0fR2+0FwCJA+4J88 z+c|U42ukVUp4~qtLb9))Jlh`0@%b8`pYn-R{2%_|#;HR8$^%N^evRLU%EGu!Xt$Iz z#DXC|!PX#xwFq~l-s$`2@6pm6aA(P7wq7rf7zUZ~+Ijx`Up4Oi%VlD0m})-Tot*A{ z?mBTuOlxqM%?o}VCJGsr$s(Du_SA&OxX{e&3iK{_SO#bEhXl~UUbUh=F|L=_Zn4@Y zk>y@TWSJ^!7ee@1kW8WP+soGc(rErmrz+#j00Tew2aSnuS2)(&GiDde*YO~8Jj^?* zOe*8smOpWlG6NntVrWjbdjF2JySQTd`%kOYR({BuPMLPIUVgyUi+S@Cg2M`DiGB?j z4P>|8!>S@eq(w*@qvQOU-8s?QGhv44h$R%;6xI`n=?YbPzwwTTv>;(hIAOYg&>|GBlXY=bK6_I_$av2yCFV&lcR?L--> zbZ0eT=SRHD8EUga(>5BDa1+puaxgxA;ZvXhb{)$+oP`CGLwUNyF-oDooqyS(dM>9&SprnGc%-21)t4J9LA0 zg7M*y4$Q1}{cMMKhh`A_=y!m|y)VfWoBfu*oi8@6HgS{M^o-Fp7;?jUcVb9FM8`W# zIY_eIB%{W8pw-kVA`d00@z=Y1cSVqqxa~^svofj(EV5-*5X7=~IRFOkk9*(xhCdRD z-c!~LyKXz!{XSEhS~XZTlZBh+2YaX_QaWsrFG-BXSaRnX{z8Yw0kYlaTo)e7M5UBj zR^4O?HTm&bdc|*5UyaT?*#(S)Oe(jD#NKVbiwt&`=trq1n!`)p$|uC7YL+swo?w`? zvGl&(V1;oZ0$Z*5lH%@ND09%A5YPSekA3NnXIT<>eRYq_d(jFb{aqM)@Fv=Tewc;$ z(EqZ9cppDS(ZICObWw(QH$9+l<=HEHdZQMK!hdqj+)Mjg-WI zIUR)6Q01>?tw&>dj(1DknZg8we7JjHxy}&7x}WTNH`uvP)G>cn1x;aG*fzdI5mURS zQ7uY~3=;K4NE{&&LSM`-P0QjCBfF31ZT={i^nSUjwBD1%dbbZHysTvXd1CD;FI>wn zYAe`Guxd z+ZRdUJ9(lEWnE~-_J+7H@+K?Nx&D=V!HHPk(*`FAzww?Ln8^D+L4BN=VUWTgTKkWS zR_=p~we4qFYI(5el_gv0erxAM4^Vt034PJzd1NgQ>X&x9+Ar(e>?8DXg})lU%QXm5 zZkYMuK0our8;_Eijklk<%sb6xrc^xk_dtx1sO>IL(>~-DVo&$laO^c#N)tWvi?}Id z#P~Ht#@#kecN-PjUD#tIMgB0p2J~GpgZ6U_1 zhr0?97U1c@9kI+To6%IH=ojFdI&wcpzcm8)Lw6=J+|~jMN=Q7h88~{qj&3C$)yrNF?-2q$+$Z}rDexg$Mddmf;7K1*bh3*d_6;vp?X*~9PnetYBsKCOJ-^u6}TMSO1I zS@&4Gzp~&V{4Eckc;Kq*$+uD4{<(MLAk7%;U(R$^gPdbxJz;2_>0hDM*_OR6%XswJ zNgsl-zR!qO_RQs|QMmuoC9jJ~Y6FCm13g=DIX!VYUFlx4W#SFbxR|8Y9bdTy zMVO6fQ&zh-9d5LziOf=lc|2p*X;7Ao>EjdYs8D_}bW=F%-z&;olg+6II@)7h5%SqX zw%@`d6)u7B$x>!J=awGuv9w5pEwvEc9!e8&(#OI$>|+HKOIkIP+TJj-In5Pgmt{Mf zvn!jk@yZ1=0*A)30RkhNIh*e?$G!h+xTG%?&eO3~W(({VDH*U6&s!p6ETj$L_IO4q zT5YZeW^z`7XS87VBZ1D(vN*g$r1kUug@Q{95-z>pmsk1q~#J=^(g$J|>Z>i@eYq({A6q4mzSkdn@*ib6@)n<`whN8W z4;Z4rL}&UJ5-=4NwkR;OrCCw^`WBn*9-h`KT8SS}ZZ@YE>@8OVDsTU}gd80Kz3qyd zRCkEk-otMkS6EY2RoqyZ>3!sllLk7ACEkiRPUN+`aa{gdTTAoziE#-8sqI>jpBB!b zoj6}FJ<*e6ee>NDdWu;*g;XWhSH~;b4asGF)rM~)C$M9}0PU5Gcg#(SF7FX6CN=8~ zY!%Lu&YnrPCxZ8M33&gFF(eOHV|RIP`;hjzh-NOKz}_~w+_8iD+)l2L2ttM}deF(> zEpvG&95+zMGM?$zi}4dVJ#lNQXntL{>ep$0T{>GxIww9F8}sHfpz>SIJJ9{tXKT$1 z&`Xt>+!9L2ZqfJiFL#?JBJTa+N);6~e?eJGVFDiCpHM}E^cCIS4}WPLHitsb4{ueh zRZ>dCR*kJ+S~fXU=M%@)$IJa*#`?Z2^s80#cD;ng*pwlV+{)@B0w>`7vAaJa>9T5$ zRGSfulP;DmSh{#hGIG{r4bsjT)!N1WgcDU>E*Q88Yy=^*@w=MUE`_x|)Ayn#wlj~7 zASZj55e59H?R-BnQnwsTA!C``ZDc8>lmB?gAeQTo69Gr)Q#G%1nuQ}y$#3VrWQRh= zPKzHw;h^)>H+|6t1+|=Rg$apkG`;t*yExO3%?OAvG;Nk|l0>@&M6=$)gZZ!oR1DWu zcfwxlq)ymd-xdU}>)(O|NNeG$rYv``Cn%dU@BW*5m{|^|LmWF6R!Ll~SVQ)dLsYH8 zaOpClv8SxkB?zg8Kiy+}pAaJa|Fj7DZIn>MUnrT&?6$d|j8-s@cq?{g<{NayCz4@T z#6Vgv<<`|sv}O-){1JCiynx@n=ee_j&)azZ6k$a^s+4J}s+!7Gm5Yz!?%H3F?+Wq_ z@_Q=3lJFrf=pBjImdMH{2Kz_;c=+(@VJYfa8V~U@*gwWM-GXlGi7U^4CoEGO{ybyp zvSWy{r05J?xOGVhunYs;UkIbQ<2}qACgNIm0yF)OD=MdNt>NzBX}z+_e42$nyG1W7 z$T$DI9prN-o8RopPJ3XN8kXtHjZ^WOa@P`A5ryV9c8)VQkc%DA_&{eBYAvwdSo706 zSq0VV{Cu$);nC@w^(0S;*8h!LhRMqT0ZSWqP`UNFirnT}9kb=?#s6@17~qU$P(D0Y z2_U;F@RFrEzAFyGO0-$0Bq1&*v}=4?L6M%b&B?>;#6}H@75S$F@x()V%W1H>yv-|A zUtN9U?I4^5VWq&;3JuoAXV-$*9RGbx;33!W;MW|9~IqiPET!b4nZFNJZ-w zz21HHn(~|_29^rw@=guv)hebS1CbsI&-&xNv!c~1zIzd^wrp5ycICFXAvpMq-`5lr z%;4@ZwrVuj%kvGXy3|NjeI&Uyn^RCx!%k%%kSC8wX3Hor%vwI|94q|8vKDHNt@=3quB7V_tqCGa(xW~d!~N}NwYb9W9Sq@BLJr0khL^&Mi&56 zp$?c9Z-ObQS0NR*5`i`nRi- zIei}u1P&WrY!QG8EyYHm`_XcHgdYh&g}U4p9{^A<08nMe6HWB71ZM*PV&iU)_cxX^-6Zv-Zd* z`28K9QjM3%&pE%1u7v66?DveNmmpK`bhJ#944c?g|Kjl`Ogmj{B@Wo~C7JtWEnD?Hf}NL-NiN;OUK?%^z2?VkxnaT+WyI*1n{z=2TBxy%krL|AlZ}lCWXyJ*tz4?uHwaTpT+* zD|u>OJ8(1*S`~4!QB{L&EFqEnKWP#;#t$R?(2Dg$@rjZ~V;5AHzKD$`A}BE~Tp|dI zDTgj<%Z8Pn?3d*gUrqw?itqjF9D~kZOoWB1UG{R&6g!xO9fS+0u;w3RqZ#=H8_iq) zigLUw{+dLm{}qYcoxqMVU@>=ZVV?KO?EkqV0UvRdx8tu!?S1;MNMumQR>z0-Rh5Vq zgK^8RGr=$5N2H8zoq0AQd&-(74S|)Q@futz^D}t%kx23sJMY!tXTUR8$(Y#-3Y`FL zv3U>B_C)U&ON2P%?hp&t*Upr8n^2(I6d>`5MNF;n{+T?%nF}V0^xCUl;(h`?Equ1| z(R_4v4%ic15pJ$wC@(6SJ#fR4+qSXE`M#-+*yCw8S8}%c^3$22`@Bo2@(6pJnUW=S z&O~L_YED3tb^?E7FKYRMx~^y4AdtTB6P-Im7eL2ogohQYROUZp}3z z_G(L2L|p-s>?v0T;LBMtB@Lp#T>xKOiLS&UiMK<)(Sa8d4YWi+rZGjZUDI>6Q+akJ zDj|Bs4A24Wf9kR|o7jANU$DBOFi+AG27gBH5U+9YuX-TDY?oojLf*@uO0~PSHRYse z1GgUc)Rf1zJPThm$Gj7oSd9yQP~YO9!PI{E5)G^aW*hsfgTSJSqfiOtY$a;^pn9jT z{C~gxum3-)9}WzQs-XX)>OTV+_J73nKRTuVBalH;Pmc!uUw|_c=cdBOPtfq*Y6gxN z0&sKkIJ1mR)mWjbP{F2K0|ETEUjm8sD!y5=2SX$4DXni)9WF;!fWIoK&9S~Y%v_$T zV|{Wg;Uu8u(!BKiMCthpOa`v65Z2KoOyOi``*Uh?*8VVd>}S2VY=f%HiUW0%GW%2|GmeBY&f{fmd%M8PHKEYhK;t|MO|7L{= zIlO-?7x9c12?@@|&F^z^d69=`Gf9JJCRVLT{o&1 z_4yy6Bov5DmW+PQTe4Vsvi!#lbT#zvqxQschyD-Sllm;1-J-7R?Y!kU+nC#Z_;qyu zo@^z2V59zjk^=n>9h~Vp6fD)T{;vyo{d>um=^V~l0bMy$bukn_P<^{=(&HV;YVQ%3 zxA1JhAhaZsbi`TK{QH-!4vc6{HgL$5*hyW9wd(1oFI8M!0iXTuuH2TyBVoo3Q!={7 zCmvPC8~lv5-dtCA{&F`{9W#-_YrhG_5Nh1X{zNjIsdTk)3ex0XAx#FC-jX_xakrBm z+s?P<7O`+ZNa&8;cu$fi?maKGlU*Mc8wcUgynkVGl+q><3FgOORl7JBrS>h}&JWiC zD1X?(=O0sfN4D@vYAfsx@;13)LrFK_r?;h}c*3NFIJ)|~ykjXr_D}que`BOd^Pqb+3;=cd8RZ+J$a{k(iCkIMJ~8@I8a_#>Am%2H5^w3VyoGu$7airV|*D<6@?zZ zf(MdWYcPsl!HhIDsQiH zsdaVp*(gKm7(4zQG@b~g>To=06HE)N>f$wNLyPHzk5Q#9PmwNNp>7D`T8l*0&I z9Y7QZgC#ZmbG2%KupKJkNU$X0n3^EMPMoQ&tcDT0Y&mRSS)CI3T@k|h7g)F>dx&77 zt5aAn*xl60)4bm`x+cDI4%vFfR*AffrRciR8#f3#QXf4{w-ZNC6^k4#?MRckIA*)( zMgSQpUFC2{wB+~*0j7B7zOK=6u6wB;Nn* z)LIG#fBed%v&5AlFIMaR%n7Wgia3~lxe4u+O*;mI6|07JWblwK?A0F%P2oLzsl~6f zN6sv?M~>lh2cLbsf0ExwS(Q||NFMDkRO8zGz`xvtit%-`jD~D)0n*8we~`n4XoiGtO*j<)!28NBx%85AcdFfhCpbAmhB8;$$$_04JhElsQ+d50RX zI@S+WfY8_|$NE0ci!nH!SMEw9QA$&11Z<8JX7kFG&%8$UdV+WGY%k;cpt#X z!MYhO9YK5vR6xsvJ=+QDT#6$pu4DcGDJ$7?)L1jZrH`#v@ExZY*Ill{hMa3?5~D)5 z7I9$dG!o=x|jY7ZFr9lhJljpCQ>sH4$yxD%nck%=Fi)8r|x|D=ZoveWPk`%-= z%BmXiUZ~fwa1f1Kh5OtMgNKUKe@@j1EK{uSfH1_Lh_=@q-LUeIu-`wjm4nE-cnIks zI4%FQs+NY%k$I7Iy7A)^VIc-$?q+D*34P;~huodt7Qm}4`5u4qTfiBsU7Q;`Ad5S1 z95gU?Z$GY(*=mEG2dRxiG^g!S3`_L>ip~Rj+7E*dBI^6ungAJZ)&({~mh`7zvO@Be zARVT#x^W3PNNZ;_Rh|DPFaF){hnQi1qPf88(pcZG_2f$x?hZ)nhl~myDxrej$4>}g zX>)JL<{pBJLpJw@Z0?m8-g-nv0_X_0iGPtKM%(wv4hF|6o%S+p5v|k1z)-|Ej5cw zO~kxEW65* z4Ga*7!X%pMF+^*PHD%6D!7KKKTnfr1Mdof%!%B|0a;!0IsLQvS4B?Qm%~&5+d1dsR zqUCQKix$c=D(&Mb0g?=u1n^fHdxz6qQ3(X*h){6=R_GAx`(rrhWhurmI@=iYGlA9D z$f$tSyB$(YOLEf6xnO(qq#n}Nn`x_Uu>QdyatxveS+sximhTDGJJ%&F ze2^;6)Ez1AI5*3Rt0KIxKDcPgd^HSrc}rZz{B+8frSurkg(@&ji2SKoUwCf>T|s!F zPjGzJMfhu6{?iWW&rH1sOj&>?+c90SV^;ucJ@+d@QZXs~ zos;S*+*U-gGY!2?_o-~ToGXt>4X=X-kE)3`x~u_hJC{LRz&&$Sc0`&xOx(=`;n-?QsZXrDZ);aE7^#u!Lqh}K7V%Coa zS+NavPI5~sTew5H;CHwWI%on$<8iOTZ`Vqkk^jb?(PuL(zi`j$u>_-IC?QK*S^?{= zo^a!dYeCuM4TFQ1tudUhnDDui326*(1 z{e&&Bu(MOl=qaUm%Uz=UL*c}OIY>KvT%GC(N=|yS!qSY)jvJYUR5=!B+{kR5_v&1x zw;6iscm~JSCpL7-l{7)SWSXy8DIUiQ{a22eP_eRbwG5uQad*P|X05fm5cR(5p4eTK zY*CJl_5a35tmC~y=EXn|>lcSjEoR>U8JoBhqUUBxq6S>@zTB)qtn=QcmQLQH8N9VW zvmMJAKw#A$aX%AmAIxAUx+u1J7iYx8mdSBI&7Sf_WU)c1@m_^(GnoZQI`lUz;?GoG zl^!mKeyuNqKy(AImJ}PP8hSWWHVYFBjOJ2q;JzF+zU-qP8E9U|^CXj*j2k}0Y<+MQS^?77k{fHb?{ zUs8v+g}7qgc81{-mF7Na}1)e9tYx2Y3nEp(k&S;NlK zTX?$3!c>jrf57M;qQ6laHuKu-4;!mP+^Y-9m-lNpo)+DAi9mWZ{mR>&FC5D{&h-43 zN^cR0&g|ao@5uU@=V!2LKlDKA`@+TU z8AdQtSmd4&BC%m~&xki*CEPP3piBKMFE|QAtWsukr>G4PZOJDn)9ZzNNZu}-9~Zs# zC$&vX?FQ1`=}xBmuGoWnpxtaTnVd_?)xIs`w2!{0y|zCcIAv!Vk9#|h->Qo8Pg&CO zbiPd<64EdIKE3ONOsz$IUQxJsHGk@7XexSb$?1jZuCXVo;Wa_i+3rO0A5sQ$@64=i z!%cI~p|#9iKSSfB3~XB?`8HR%`4hnPz&vvuINTU}@N2|!dY*GIk6H(EoB8p2jui}~ zZD>oK>RK^J2?djh5||WXE)sGV@s^)PJJ+}X4hKaFHw1eQR@Fncx#ZSEb$F>_{h?qj zj1(5XQOfqIDNAzYWx8WvlghF!xs2~ifx>d{JI5To`J#{$+V%NUEoeS#BlpvtN<(~H znNd=$mW=$L!LWdTo5#LSl2SC|zV7%09S0eo!*P)G&eLqPgWd`0@;k|F(v1UnIj{xc zKpX8W4-#n&$$wUfgaiBWu5dOEK701J2^02Tnyf6e6*|*xV+O(&Zx}3fgLBrT!8xLL zRo*ve76A>#j^uSf*$`(a-7|DCI`W>Ow&ZbqUz)7W|5hoA+lGqN)N$(|LD1-FO8gvO z{?Ntx*_rZU1C9MZFWv=VOXsD!y=Hq8lX7v&pR1juvgLR}+faW6w}7#dfI_`D!mRfG zlP`xr%gBfCtRGJb@P{Qi%gyXgj`$e1%~E0?Z$Q!TSv?5Ctv8Kjbf9OvE$Js_5=8P9 zQv$_Y)uNP7lbT3&M4$5y6H1RN!yXB93b+S% zqYfgBpT#=wx6C3-pKmHncrghV1v3i1cKp&KM$%-#egP+|rdeX_bgV1!xJdRs00nF3 z0I1rs4Vt*_da<;J2R-fZXL7iFF$a4$l}-A zBNKf7j^~pi9g&al{P%pW=XnvI=lP5g_ijGV{kc7IIq(0@^YM`ONUSVYUJ;AMDyw40 z92@1q|C<)8KCUKsd4u2DHwbsZ(>GDq`}my3=be0>;&Zfn7Angs%PWux-%5Ml$>*bL zm-{O(kMNIg&VlN=yu6Gbr(a2r+&tL%$RS1-KTBFuaMd(wZ;#Ekx5p!|DDDZZFdROu zv$q{uK5(kjWN*8f!o2NWXm9%Eg_g)4QxNfovr8)r_>6rxy%VnC z;ldBk-x;tMrtfZBizqVI6C2Pkn~JUag_g8ad$!gB*+|(#wn4ArJ}O^4@D`q2C>~}2Lx0xm&dA9MGB^ODCUKw$tQs!7Zjyxl2y9#<^i5<~F(#6L7%>=q> zIW5($*2#s%h1mw_`(P&F*2`q-G*X^yEHo8Pa~cWZ(Do+2@_%HRounMxxs=84>~!0D zHCtY3xWExBxb$mH^6blt8pV+-=!+$x5MR5{9_!o6ThNB%vJG=3;l_l8bB7b@dz$6% zn5}JBGrq+Ny6P=`PfHNi0@ETT+VJh*4Ll1{UsE%Af-%+VL=w^j+4-|Ec5QNMK3=_e z;vTXUtu`919v%AjenU6Gs>C)Dc<0S?gEXlCXN930Gq4EZH#0+4g%8u0fZ}5UHgb4s&O_&~)Bz{i$>azIaC;d@u09zAJXzUGZy1l~>8Z2CV zrUN%vK}Ljzz95P!4nC=5{?!>S*a^kP6S}(FuNwP;FGK4>nb;(EI{wMGj4xSRUYRlD z&7iubt$VGVQN~Zw=~_KFOq2nUp`=xlz6Tp9)K~{>oOJhdwri%d<$B$2s6K#dfPIrU zbG6?!VY3yfhrue3Z$kK1$V-+*A>Gz2 z+)Z9f86f+SR~X9nSwEmxm`>fk4}8=i2$YZ)3~mW9oQMM2TxJNszYp7$EMqMb(kO5 zI*3=;4kX&Im%07CuWQ`TfgHjIN;_<8=# z)IXq-0#|z|;}1+@wB8}TN@O5Z;7h_DWQWjhqk6kwL`Z!rtWu($tc(~6|r&o8^dwN)U-ZU zm+3lr1Ah87d={&U_QOqiq(eRt1&uQ^f)(o&D1u#l=CQgU)4Kj;kp&D#yu#UoRa(U8 zl}=riCOSV%nhn_>a#i5g;c#v1Q${9}ErKp>+t zrMo{^N$kEYJYhNKvZdAn?Yhpvw}~kHl==(<=jd8#CxXA2Xd{R7a3f*fv~UM`F?Y%P zN}@8SBvOUKuWJQ^2&^{a=4x*@_iUT)tm%kc!{@hrZmaHy+*RAbUIOP+N8a`maTf;# zw+Fd9eoQ_WSi<^@iMihO^)4%G28A5--A$`H8ncPU6b}U80g02&vRf59m{}POy}n## zOp?nN0|mgF;*67(D~3y>P}%v^z|&dD)qJ0Wj>kOnrZXH~%batzy`- znA|3)xE`P^5T(aJDR=D*a@9yD<_>P474EY^a}Bu9q`Eb}n}vp(C=TaVEUA&T(!&gD zfZ3-_=qEtH$`H7Z3b=i2<-on8^x-Si^cVVo`hGdZ+JcTNtstdm6BA9CGTNX1UlR)1=vHAIeTaN(#0~6)VSZ zGsQ@+Z6^^dgIm?Bljq`R&B9I9M=PQuwBkF+cNyhEpx|gy;6J%Q3ZyhG`03ZGnKTz@ zcK3{xuU*Z=My1OMGA=ZzO(DitMSGUM&V)dHD>lUCMIMxHyB_3==zxJK!(a9<8K(hD zRM)A<7HGg=m6D0@_-k0KFD@d{Hrz~ZPEczJ`iL4JtLo8U&x;hziJvhIgo$dmLE^>~ z7&ws{WfY{wd;N@#Pm+E)w*KqFeZPzBa51ZSSRtm-rmWrfC=c7Qn68En_8;Z+J6tv# zQ!7$fg4V%=Oyc|1O-4kqs93_ucsr~-lTn@RX%6|200G&oUFBK}J%*9VROap>L;hXt z?gB1)TxaW7j^Q%2d_a~4;9=_!u4#^LZg8!@{4M0(T1jj+3`eIlF4-0|$aWVZ>b{#% z+sb}>oq>;$EjB11E!vnIefmYrMO^+1TEB~1wy%H1%7RA-P_9qyy8ubKsEy2Ap9+TA zsgJqxV;tRP+mKj zW*;pVxJWOV= z_+_kLKRhxN2KN<0;SvyGfZM5{EJrNM3!fzx#(u-n>h&G3E5BHMLbNb;duKdgV2sla z4qVfEL}*{QPhmQ6&AG`_*rYaK4w|uIU(rAB`#o(Go+=liFkC;w1XTkxP;* z3wPNj%Y(nX*py;W-7auaE9odbkHGxclfr;Yzn7VOxDS@4A^?=>`?wta=^1lSTeS+P4 zryl{kaJ_259&*I+-O$Ni6+`%?Ech6Hj+Qpn7&Gh4wE18XS!=^$KuxVJ8$rV*e75v zgumMA$qfq6h$LkL3ubD^0yiJ`%3Zyaid?!Fj}Wd6W%;dIA!I1!Tf3DZlnqxIdLem> zwjtJwC)}cznEPLWt(5#uerT=wh8>DJ=X&UTOj>8l^X*jO2iLdO*3-#5?+ZqvKD`qo z7(6PvzF(-y_Gh~)X+0*VNcOylzmWPvSHD#SWUUySN;ZgKaa77S2<53cA(MO3ktO{R z9Uo&5s3lXu&V)^}+g_ltH2-2!U0Qom90YO=n@4fxrP*&7Hh1rL#TEVS7E0{qu-O>U zHiSM1cEjD?JwfeG6!Zp7u2n1D~?kuu<55IEh^NGl~V*a zeofqM*{q0oNf8JWTL}m zX#5_hLxeTGFRi1rWR;hEw;=)TW_n%E352-UVL&zjsU-L3%9Cv@%hC_OjUD zst&F4TCzN`uEJot$t$_sf@gP)!}*R>u#Xtcf6%Sk;s6NLN=B12S0T~%_ldwu80$_e zX+)#(#LV9?Nu{)8imf@E7)HPCHGS5dTekVL%uedYy{%tT8gQ!4>}ML$P5C=b>Hho! zBWxtfV>u~qAc)y(bHhI4qxp_q>t|C|#+3PP^X|m~S3C^LvMu{5FuS6Ee`X~X8UPe@ zWedf9^JqL*4zUy$axNs(d63nEXJg~WWVU#jD%aJId*%HyU!CE*XKzF7!8%27xyZLZ zc4(f7rT6jU)`T+bDa(lt)&s=4)Y4>5Z_6?`z5S&CM6?R8g$^CNic$|Z1@jbzoIg&AJI-Ov2#+Rq(pFCQio!F zErw<4ZY0%Xm7!+(u;oR!G-sBC$j}N;lIUl;Dtll1Z}xWgzttcFQ#;=km%f)=r&<&0 zq=T|2bd8tarpcfaMl@k$dl@Tfv6~(BiTy;o5bk%o!%FcX zAQzgnx6%~J^krdY)eaG;9S4-fLBZNDFRweg62p`FUtc&sj8x|C#Q3E6x&bwloJ6uO z|Ar=$R0jYTdNGpUV;0t?gk!GF67oDK4 zGT@eBA?iNoT(;*7OmDLFKTsjILe+d%k6m8btv&?VJeC)pBb)r+N<1pQ1~j3l(h*HA zHs~;#8k+P+85gEMn9xF>CW0EgudFF8_Gv@zs{9oqyin|*^0}u5!!MS&7fOT}*+QY_Abaj8X_a7foQz{ET*V39j{M*wzA_+b- z_$=a6!KZw7#}vP9Ww>W6FE6jm9^OIx&-nZ+pFuu8f0G`mVOcPm1dAbd!^lk(U6~BG zXC+s$`){q4^9DQ9Yoh{-QPXO8sxUJO=jcjj&4Mc3m*7l5|LkZGWXmZAG+IT8cVUBi zakTZRS%A4y4u5vnXKN!2o~W)pgnWR%T#LUrVts2-{_|b_sTpj2Ycd|n_|e1?5*G!D zHTd^wCnO{pVkuG$gf1@)&A%Pe0aPRidP9w=R;Jmc3*;+By=kM&p9FPoC$+7P2?=>a8OT7c7W%c>-Y!wOpj>(|u7fgq0Zw|6DHDTTyv zPsVtM$|9qj3Pi0M&aeCq2;`dbk|o~JwinOrh+I0qBl35AdiW&xT*K!~=EpYTR`MGu zE00!GRvi=l|Mu_L`arMZPpU5@Mbn6*oP^M^~!~{h|vR2$Lgppptcxafza%3t95r9 zMlX*B`!Sleth8C$2eO^ws*>yf4!{52Pn?yEqnyyo-jRnu)9|jVy)4W(sUm6GGPTj` zwU!?s1+)7AR@~Xn{f<|KlZTVcG&GZ9Pu2Eo;F1UJ(r`;_PnphP_yFb*rY?}H#6#%b zy;s?a&|vqn@M67GA{jU*(MPQ=^okEyXqD2(y|-lbR+qDj$h*AfOsrJNg*T+@jC$*?w8C5&IB@b1LEVhy)X zO~PWx$(6)VNo^~_G@L+qjE0qw#uaF(k=G+*{S^Wfu)wEEI;?2jl4E^&`z4DE+U;5S zv-Y%A#1^S|MUqX>As855Iiu=;lsG^mio1-O{N^gvtCPAaA>VJWQl8lBUA*?T%>1C39nxsp%i4PuT_qiD279UIzjs>+(n9p%sRTY<1akcwOE& zctCFltLBo+s{a$BB-e2Cm1QKn(3KCH(Cz)qO$N~5bXOpv6}sA$&;?S7n%}|(+@N9Qc5rVb zi7C!V`lSgunLwj-Jzy$K_iI>$`87SupGnPwRdTLm-D_0$oM!MjNQ!tEE-MRSUjq>m zO*AvW@5Zp5Rrv=fAThVthlv}4KEOJ*h;@AKn2bH-uT|3Kl5&Q^w8sxv(!G^i^_xFS zDHkfGKhwSxrmW94SkmuBGxlKXdZMM+fcwIv94U7VsB;Z)1W9DGW>coNMo3~L%J4$8 z%1&4&78lDpCoBs$-bw+}ovqxkwcZd+0mc~Xq43efl9I0d0ila0Q+08yW4xu`wRrk zy;?tNd0GE+TmmSnrTZIzk9L_}%hm}hdMjykyU~(mKPt?HqJm3P4KAbOl-*lKnRR>-Ue(6(41 zJ&v%=at-kVgDOP63yp(S(qOAKK9%|%3kt2yFCNFsu}}i%4n1%_^$biMTS;FA{QWv5 z<9cOeLeoeZ-tpDVvdI((`ok)vrdS1U>UEu@vY-(>Mq=No6_?WgTVY5;nUoYfM5-J$TTPz1$}x+BzaZh zHr2PXPCnwt-eJ`Nmh(-kf}2MC^fzc>==R>?tV%L=8%+9X-6SAhk~BFzW3(0SBhL>u zPgR~y$(d+&mUo(3y0d+&_WIs@u*9pekR}U32#dS5kKPPsnw;XgvpXUgK9BHulF!0( zIwGxn>RLJ?pW*W)pBK-Ax8_rEen;eZJ{$P#=X3hvj>xH`&G31O&j!*y%I6Sik0b6g ze7?vh(rABW}rMClkU!0{s)EK%+s(viF;c3M}>*Q-DFQff%u;kCX8>wli8s0&ZG9Umay%w z64tq{{WbmcZC2XO^@}ZV<4z0QwB)}PxOtQPJaoW*9&Vpd;3JRN&$bEsd361s75G@w z>-u?ooBccyJ*>CwE9_^-cKg}6oD*r>Z@$ld_O_SlZQpkL8EPrl+x|!F=fJrA9K60l zfy2Z0bLiSiy?HzAXJl@b-bOWMY(e6@|~uMH#@Q=U|u#m!fQ8}fC&9d1m7YqNNZDxG@>vRFp-;k-OLI1 zm)N6@-)8MK`@WnGK#wJEC&biXdso|Bml}X;lQ8NY^OaXpHDfWf-H(_0fSSe%FG?Qo zm056QdOUtdpw@keBE9E+Y0RR-Ov8Vc5t+7t=>nJSFp>7ighB>?0~Y2idYj<-0t*UR z@F=v~Yc+_bhJ2J^bb{NFYK}{lM}8SL@QHgr_E2~ zrM~dm-<4tWBk2=k$dS|N-FPe+N8@yTn{5wQ@MC&OHt4p~O}B{?u!TntWk9chMJ!@y zBrJC5RYnS0r4J{Id7K>|=I-tR2HgtNxmXLK6n|5cn(aILQF+=?A+_Z!jOr{YPn)5N zBqzF^1tA5yhth&ABrLcBGw9HIFKAe}P~h{?xj9m0&gSe?Hc2DEIYf2eyzWn1>%^DK z^6F%gR31~3WX)yXYbJgO_jwhJGgL5Y&W+wYJA|vbNN5=f7a5F5Z`kpBHQu zYtDbpc~N;^<|_ZNJw>_L@>C7x7j|IgeVGniK{=q56w{Ke9c6)K;S?Zow!2a&Vyacn zp?94GWmizO!cVr#WgPofe(^F{Ta{GB`wf#Iy6c%?G9sJyu>3kGQ zc$-B$zSe%8*knK3AGMzyJM3rYKKt3Nyc|lkBA(;EJtQui<-qM4hiuT;vT9k>3mn-% zF~y@E!bK+na{nbg)hOM26Lt*sBNzo<)I3wYO z(I1=?Z995UXT1ax>7Mn|QnM4Iq_s<<;%>h>#=G}Nyu88G<+qY9yK?=l&&|wTTe~T< zYGbx@qe$vG*_9jNWmm0BM2?-5J-c!p{;*x{?5d5n%5E%hN7njC>Ky}>B+hj9nRMU0QvVB;MVA0rG&+LZUsL{hUvloUl#_-|8ovWS@ZNd>EoRG~N5(FsGqW zX^j+m-f<0vLV;ew@fV?RZ6cWYb9`EmtQim~*eb%n;!~@aOzpVdIPUMr#hbDXq9i&_ z*ZcBst1Dm&m4KsZj#tFweAWKfMcWF@b4Puc0+u;hO`9%sv0>1FqL7Rsi?pgZeI#~2 zF_S*$kluPPn$<{6kYN^=)dtLELK?5cTtTM=2a%FGoRMx*dr*5Sb0D3tbS+93b?Lly z;`seOHS}7ZpVd@Ebf2dovSz-!0AbakOoJRv_F8I7G$q3@;5TE8VIvit!&`?*AIR^k zytza}^9Ymm$r)fKJR4l=zr#0|Qhghr#l`VSEThFf!n~@U#?^x>u!p3le%E|9JE8kZ z5~~$|hKtW>^KdT>VkmNQ?)FyqEQ!6H-HE2hTQm*#HL3NO0(#h&M@{v~Sv{%86)=ru zeV?9L&btx`D#&Rs+J2V`ZY+iIYihw-wO|{U)2f9!_;Xx$V!bkHB#*O9HKhY+nKmiY zhh3(`!+K#o5(+^lTAQ{jKaTeU0e z`~l%(Jh4yZ>8j*={qpdt=!4l1Wk}h6ZcV6jBX!dClSj%CUdGFKo7VJbRmPb%6yS=+6*LE8gB+I(m%&IcIkgA5I%?E%Re)|`|$WFv|D*~jm6_y}$$ z#t2Lt1{+NvaiR^sKaO43m5!s8AN4i%wWeLoacXA*mU4A6(7sU3n{EnE-h%kP2)h3$H+ytP4j)^-G0pC4v1R$Q0& zMJprf%CKYuipWV)tunF}_Wa8k`?cLlRepeG_?Z1g>wjp8S!MUw_>Ey}i%t_i+DJTP zbJPUT++lABD*a+0wr8#f%dB!p+xHhA| zEu%0gxb&=ovpGFw5cRQ{&GnB`U)l3`%4)n_O(|V31+p}Br z5c^JF!*3Kheh%h(IR)+Mt5xoETmlgUOUdnt7FngpNZ~TGin%B#uEphzeP?s675CaX zsZ(7JS%O&BNu>i9Tw0tUXasF<2-?1r3c7MzR1bbRLbRA~HBOH`*}sRFd69P5wgxNa zO|d6)3hR9-vW^)dw=caF0zGy++*#+^-QYUqK%v_{6+F33L7j!GY z=zr9*>N}HrjBh_<)*|9wtLc@a*_={zdAH%$N{hx8%dlm;GWY9S7tEz!y*PDB)&k`O zqa-%duc8MHlgb%z2kJ7U99FJTr*ohtR%w0{Nzm#a1dm+daqlYtgXNGJbq;sEQI>8l z^rpMcp*@I))p7%y;*oz`VOg@)D_28IZd!bu`wLgPo*16!BvZFH>jfVsJ1D?3^IW}W zWvx0#iMid=I&39!W1e2Ry_2jF(EwoO2Cz!5^dmN7-E+{Y*z&kSj(|#HSpBq4b$ORO zP*PF)Y1I%(o?GmkcZzGab=g{D2g3Z^pXjtM7aq%Q5xiYpi9;oDJb3?+<;VKKOTPsy z0Be{n%FTVlu4kG4ZM>$R)^{X%O*r1%RJurWPmP2cg5S$kxY_T!L=Y`*LA2bv%J2ic z%$k?oq7c_OEE}issmfo6P)o!j*0)iOVS&r#9)c)zdEd5DIdj1zLkC-v*|Lf7@M>ng zWOMpvo+(GS_i2|S*RNOPg^kuMABBTNU+6GMK(|iwht97sF#^1b_C<9r7V6G+D#^T_ z#4u^S_oZj73c=#4rL|T%@*Zq)@f)>8TJ!jtWgcgoghxm$eKJn|^auxiOjIN9Zcc0k zlYj*8!-Iy8E_Pap?>AX}cpZB!)`)NwtEoZ0GVZ$ zTdWZQ+levw1jgX>;sDYuZAvE&^5B*;l3(dEBZf8Jsc6er;u1ts%)o>Te+Xv8m64Ud zb2^OO{EvQTTcqHmE*d7UPsd*LC%CoiTfs5TzaHyBZA zD0{AUBP!LUW4*e(KmFF<{-NFy|MTTP*n8nL|BM&TeZ+Am>ZVOv@(TlRP=8~(ZIje# zvHpJWWw7cIut!_30X^W);s*wUtt~WXu&R!4?cZ48dD`_7Wby5Og2e=&-{;#2&i|Hm z9bj(tV1+P5B|KBBiu^R*Gd4dfx9+8y!KwpRE8Dyjof?B7V`~~AKmvUi~dM{5BreSaio8a>8 zfJu6MHB?du`C<60nU=vB{lb1ijaMIYDVd6d7{@v-Z?;rpAv@9=U>8Li19_}#`mt5%ex2E zYwX*eQCERbI zmYV?DVgWM!BVsMuWE2odav6_E7G&rNqTy0|!n*=D~JVdf=LEg)?;az>){Yv=nMwGVZp%)(of> z%xF0Q2ravPA3%I^RvnDBp8&D!>6L=jF}ckJ}YeR&s0S z_}!d2m9iF3A9B$Z-4j`jn99gswD3Hv#Gdli1cTZ5bC3hQ=$pIWUkF*!5nlpJe|kq+O7Yi2xxG(w{I{FH0jO`!Sapgs-bMrD`{5_HI@htW`!V_a z5XZziF+{sTani`fP7oW6~Ok7c$D@eG~m z*Dv&9qtOd#v^N9D9G4e096~?P`-(0ctk|NQt>3%zRJ(t0jbAJaz&6_6Se&>T)%fHnkmrgEeqNP6;@iXGIvMD05*~{u;s6(g0fje7o&- zchN_hlT$Cj-)F5C-nPFz5}0DOHrhHZU}nfyygo4zS;IA4>Mu}?dt1nE*3q)hyPRW& zmlw|G=`E6w$RG0b)GU+$md7sX6dO8Yi-BOP3Som%OZlj)dC}El^}IQFYTyj81B#uON|B%A5 z-fRL3=LB9~%91#G)}j)gacAgM3i~C$l;7rnun5cc<1}va3TZADqg}e4c5F5l*$@JB zCJCbv?5&f>Zl_z3LWdQN4lHp{h}`T7w6f-2Vr3yPo#_yI0zIb^Rrs~uN3DoLS9;9- zDH60(q@uytZ+-|Q&gQ^qPV}a!6-!8IZq2Z%KKp((Mo8w+r zkd}iCh(+hxx$o10!kWxEEVg!txNHd$AQ5rtSwM9w2@=Cl76yUlV#50B(#<9ZnAqjc z2-;ni!mT8?cyM*Q^)LA@i*N8>Ro5ZEeAT6As&b^p-r}kQCR=uL-cIX4V{x4C42qQi z{C!sRWkJ>D$;DRnA`LTUKDHsB1d^t>_o1mJV$WCyo9Yf`d(Y4o6WRobRW8gJ1v3^R zs5H!Q`z;f|su5SStV_1N%J{)h{(1~iErmtU%lZc^l zSiWpgR5-rSkX$-3m3EWZo+HFQN9;1sEEVUZrnw1FS)ipRx2Anv*O}~C%M}MMS+1}- z(lNPKlSbcp3-bzFMDJs!g{jOU9cQ1KbZ-@-y|3#e!5xyCntu{p{dNzC#&X}!E+a4o zU{+oYl+7{36@aZ|oGJ?sp1;#wxpCzD8Q){PLUI1Q3KnqI-RDT$IUJLrdK-{p4GKZT zUCL$!_gvhMWO5bU%NfQ2ct2CV{0g5_O#gUn+RJRv3&oO9n zF`2#15*!p;jtR4VTdU;6_x?90meBHSPV=$L%LVehF<#S8>l@`y`0eWgVNi6fBOI#t>1U=1r!frX;H4Y>jbcVtq-Y z##OTz*^)-3+7la<#P^i^qDvB#h>RpC6Z2uK#+)&K!x7K?Et;QxyQ>EThpp;FVY=UC z7P6%z@pHO{mo32Sj`7OLOB|OA*MzWBebA|L@5-k&I7ozDuNH?<6_(2nFH83{Vn{6H z_D*Dft{l3~-3TIN$tM%mSNL3Do#u2b{sU{x1+#P1qQ}hACO2x%rF7sK?A#+5V0Et? zPTtj!*rZ{>SgYP#z}8?Yq6Tp)N9XqGv~;|jSGHAzt6CMRzjT|#7~Fak@aR}oxkVs- zFZpAB@!mE6B`8!ndNCuwtExAv9}pYI{C2{=PhJ2Wy<=5-M5Br)GVdu>D!pW#Y{9*+ z2Cry|8oODV(a4#@3!U?-!}jng)hkyrqC3EAIdHAZG|0hrZ<}DC&aE|FG7lh#c+T(c zP%h+*vM-CE^9!BFotbTro*dTfq6Xzoy@|cMLX29WCSLRJmNjd!;^6Ed=#@{u+lIA8 zlRocpO%f^|(8I3bin#ZjwYYMtl4NW3ov9t;TlzfxE@)8LuGcDB8Freeoy`mt>TAx{BRhmh z>lGW%>))^J5zjBkNo*9K z-UV`7!jg@I!4EANiZ%IBgArko@y||5PD4|*3^P%G3t}*q=B_~|qvpzCynwqk@*?Ju zf?j&qX}+(%RUEV)1x*46dDuppIzPokfTchw%oVnc>?BiY-B`@_0XV8z=c?JN7*@MX zt-=HEu(eF*Qs)&>!zLt8=ri|DnQu5bf;ZexXyV|cDDd2!Sv1UCVE8UNQ7qW`S0rM} zs4fIRI)7KI2QtHGzbz;;$!g)^rw`~i?v1n!L~}=8OS4B$@ys$ zz>kMg`Z=|_LO1zVr{Hs95hoO(EbVpOi?+m$kOy1yoQXfWbBrfQBXKNs!Pn*Q(H_pM z2ar1A7<47}19*z?k8*=F0#WLGUkYBHobMlA@h=@76PU;O@{W1NXv|=ha)>tjoEC-! z6Zzxa4jfq}jN|W_U;*tBjBNgJKlT`z*#6bfGUfM@z1lC1nBk3i9HS%-Y*&|hCJpq} zmmEhE(%-Qrcvt@`d#@}D7M4Ilfr0g(gcj!P{1irMD=0VINOmL_`h7EZ&hXi`d93aG zl$q^X@M`7mPB@w40cmMLdGXw0q%$rX;H(6mh{3z+SS`NU&k zjxUBeuwwP({Q{sCKA6}Zrbw5hIPAUIPjPi(cbMWmB`M0hiW*nO{KUR6#bqTa@L%n= z;i0{^c99&Z30)ZCGZ@(DrtpDvz(4Md{+pqsyUyzq>hgiG=tE8*#{k^pt-qgi(apY8Uz%U*u#MKSZ)k zHa7Ek;GFHEXHRZFr)BMc96O04+TY^ZUs$Y;dB>XrlW4FUA-+f)qB|iTz^OnS!&mQF z^mCG$lo!TICOq@K%lmYXCijH*`F|!!&qQ@{Q8*1xQC)#dt=k9WmyLUYSYg~GxjjaxGJEB09h5d zUGF`t7jf^9>22ULQHdxHa;Vh|QVlmGRkb`-c`1t5H~b$ ziu_`fF)S*}YXTT=bLMc4J~v(G9lB4W*&OS?kSQ(u=MvI-sLT5<(Q)rLN@Qpn>yMGn z`?}?tAL}pETjce_hi#KLpEtKY*Sc<*Gy4%vu zuVy3eBn%#VzqQa6;GyGfh-{-}vHov@2rN1nC^zCdXkLh7Fv$KqO;pyfa$LDxt**;| zN+sYbz6+zM)R{&NI60n6md%2X%Pxi0ztM$OjAl~4PiwB`vn&ksQa@5mn^?EFw@K*j z&cB*#&M~&f@bceY>d#84*J&?j@pO7AgP5ChR1<|YVWU$wO8dw80(|5QmS<ed}kx^C?N47fs49b>LGWTU4#xEBEiDQl&-4!ERLUET(m zJI2%bq=?*KUTCB}NMIT(;Mi7;t-ng|YVwnX(}Nw!q=qa}L-NKh)fFx%l9~k?AsLnr zUrP#6q~V)*u({tk98cV)eE;I|g;$jtOJ(!jHbJTB-skC>A(RBJyWZCJRjzDEjT6AKFAgb}y9IYr@?U+^U|E?MlfqsyQCAyq4u#YFp-Xl9ElyS2q|2YLIkNE3>yXBAo73aRbxx#C6;Qy} zMoRE*@GJQ7-Bj=%DsVYp)tWk&uf&qDo^)8xRa5HuJ_&uXbh5#_^)*dOpAC5ZWn%-p zyHtKzSpH{J{%&BLSQeH)CoG>^Z_Jl>(2rY7%0J0F&Xq5=Ax`EH%HHv+;~)Pi=z~0( z7T%k=?YfHsMFt#D4$)rkYYJNuRx+eM+#XhO zV^{^8?W8_@nS?MF|K->QSKy}zD_oekC(PayX6O90%kB%{Z)LswMoHenI}?3jw*1VX zV>77YYD$GjSQUly6YIld&xXlZ6W#d`tX>YL=+OHg^`Ry)XSfs==Z9-cdEtFrhwAQw zif!3>QD>B^v;FI#PSzsZjJN=qWn!&nTLpYGR4}|+V4DK}%T~TR?|rKEw_iqqft6%b z2Ycq&U0OKPp^VZs%=me1k;XRB5v>#aiLK$!H=-X!f}CY@Yrdg*oMCQBN3+Y>xS6y> zXGhjxrv>FoNQK<;3^==O$l4q}}O4KP>HNPVWMnm=K z!trkEngXlRuUwfMPq!hj=p13Duc=HlTJ~{#taf`Rh9#_GIoQ)a5m|GV)mSk{nON@* z;X+2Y2Ch0}D#t@>DiTfBUYt1p#28lKlR9zD#W_o`$&l(J%x^{< z=22^{*J~_7rCpIBj!~B6htkhh;6n`?3U;27D*r`E`gyP+5~3_=vi`;8jM@)s4;v;s zvrL1^!))-W0TULIr_$xAfckrhU#ns8Y$Qs|HQ>)m=0l_Tta?+mNksm3b{#^kJ_mQWQM;r-XHD|hbqxcb*j_* z(>AJ+m}Y*sqy(6?o#uJRK5me7Wa5+HP+Bh}5XBxygiuu9%6mav#rn%k7 z-xxPimmi^q!fNAMFo0)MO}{QNrjX?(I0{>qI@Wth37dL*Cf4!5`+F*RN7OYDm6M9V z9S*ldt(^GN^K;BTJ!9YAlOHkY<)iHU-AYq|ge&ynfLAK^1viASTyF1>Bxq6T2K@1_ z8(0g?#h3>5rQ7>^7XxzrX=si?+8XYC2VVi+m0O`x)4ZkppfswoQ3=PH@hv4+j=x1d zG`bszGxhiK6RuHz5!=y5f$f-m!3o*9jTE|&QTN`R7_s7t((CU~_E>4zev1s>RDWGe z^m2SLKk2sRe3zv@3-FPcU_afm;XBzG`S6|lC3s64_yD3VIhBnb?%b8zXXdZeJb2nXew>r)+gBeO=&_g zOW`U9FBAR?HYHdw-~d@uj^_KMVipG%%iut&3iQWtg>y*^hv6@hZ#D^tG?@-k>@qVp zMAzdnTP_k1Ihxsyb~|nifUYi{guLR}e+mIU%KSWXzBY%#)Cq28y}i*mjZOA*ATFV- zcFf;6NqC7niTXQMN|-EXcy+o6*jJtS8x?KR?E-|QZaD<7XmVNlO5A7Yq;v^hQo8n` z-mukx>Uq_#)2}T`HRU>g&3OyIT9(w@%3nJn&XWyl3X(Px;+^DDdI#SSdW9kbJF(n%)l9j$Ew#0U9vu_QeNj=J6SwYc)Y7(j z{U#O)(-TPsf*S138P+L)`B8Bo=IHC#yAyDOhv)W39>4>!f!M0$AgsWR^ej@i!aXsE51$wK{BJ(MqYHGl+2s@lNi-omVO)HpY=(Kn2;yuRK`35`IGY6EzfpU6 z(w2puQ*bOg!7vTUQE<}1cf)CW2Y){Ij!d%e@%0rY5`8LdT~!lH|0Df%ZE-$$kI+|K z!dy&VoiiKE%oejpFk1wg)+)b({l3OD%iGlF>X zm&RY}$DeRyd`mz1xeMINZwTVa?&lwVup{F4XS}y(d>s)FH2V*+zK3{)y`oP;JTVjK z)5ES$>#a}Y>Qk)m<9w4z{c?w_^g`caeG**fwf7yY(s=4G-&cGk14q9V!K^NP$WH{T z=!ZrLz1|O{JU_IF(4~IpTgmBZ4Bq=fp#UQYZgnvra#SMdv162*>gu&-)L{o=sJ=m>v^#9 zYI(5ojwu{dm|mz+Bcsyj@#i?;NOJZ0&-wcM*p^+P6PWX~N4L{MvCTW34JJojeAh7e zxN(tXayKK+-GpT$H}T+?z*gvenWdKs!S}A~h+NT2Gr3;0^zVOshYo#}d{6Ni;`1v$ zmwt?Lrrc3F_0H1ZrqUo;1?6lc-$Q)9uKaR9Q}X9L(i8)Zcn|oA`p{}{R9!X}5`j1D zripi19H-uEJ;zW20g=|o{E#wIG#q#{*)_i=QIc*VU0w~V2}brhIZF{meCMr9;}|&p?CzWvKJOly+>_D zg+IoHz*6kjvLw~28@eIDZ7D_=WS@c={aP403Nyiq4<8sTgU`tS2%qb<00)=CQTB$F zW4eEX8(nA9prZI=43VBOW_@t7go|p)) z2VCf(DdW`pviwP;5I0-%C3kiYS1`Knu5A^6uIrM+7js|@3oy-0IS4L5 zWH`x5Ih>U9dcS9Qx@tdoimU3*{rK7rS6atX7``_BAI@hn#YaKvd2)baHy!bd)3T!` zA*&@p8jGLHqb30W@>mqjr1uEhI87$)eChl$Eqbjcc@lAG<+>j-A zfdByl1c{0bDjKK>3z(1>f)dym*$^QFTf{Ze8sojZv;>mgtmfvj6`$HxTd7jC)!IJw zX%(>wCWIz{FTDAJVrx*8Sr#P-CJThz|M$$@yGh8>r_b;6JfD9)pUu5@=FH5QGc#w- zoH=vmQ;TI4W!;06)ea7dEKE?&jG_bcp(o?x342;pIex3R|@VW;1^__LeB@CNP|WXW6<$Ih%o@AX79)|CXDdV!?FM}Bn@x$`h51Je;IYT z!Cbs3Iu6p*B0CZ<_>1I(4%R`pV;)b1qq&;nQ>5$ssYrqNO#qa};05%@o*KPijEnUn z@#*izKV;wBPTW@9Imj5^*5)U%Wk|Du$%6f)hz8(5{)KR{{22LTUB}^fq&tG`%zXbMQ*sav|Xy0(s8dLO>);+_% zfl-|*W2Oz&hA!=hkQZnmEeq#$&%c*f7=GHn2@lSTW2or#gmeF$%|84R{ZO!)DitfO zcY`n>#*(G(GwNR47{0L2NW$H1-K!^{QLiMLKwBIgco#f99m5ZM#bl2k2h}JD$d^W% zj&W4t)?}eHLs#C?on3MYOF`R~(qEeeOGyjM=6f`9bsl+F=hJNQ;j5)_sJJ8;OB?37vlaW)h=w7A(NCE= zuaxscRwq^tUW7}HZ122{xbl{mJmgZDSmxX_rfFg`HXq7c*sm6F^PAZ1Q%2`nS`gO4 z9x;Qa8bNlW95?$6$uap9s{;E8p{8{(ST^bEl%<>u*+d2bwaYlFmLCTlY6Ry+qCyKA z;%5Lfs8B+G>{ZYiCd6pNwL~$y^DKvyKFDaw9A~_~#fHNL5X22+FqqxJGk`Y964Ose)vCYge zJttZog}@=x@3zVgoX~(fib2QaLmIk7%ce0+2iqZawRG)5QSedS7>zjv*_^-}gG8Tn z?V^4xJ-b3f1m*uOLR%^Sza#%0@H7%=*q%)dvZc1B(BiI4S_*z2x1G3TAlBJh6Q{Lq zrI<>1U$B;;I%QiuKPV*As&SPzw>pjD*vUnGBVsB?FIw7?ap*P5HxRRh;8lCH(wqv^ z>&_D~*PX2FSqcajAWAh{lNh7fyvwjC)GjC|#y6jc)ogJ}P?rQ}t3*n^X!Rx}pIvwU zB_vq)ZUv5vv@k1l8nrW@rS&!Z0^R}S^l4NUTMR)ZM0OgtynfD(>Z|H{ogL;NA}tFHQd(2p`f$sp*%tIYa~MEPS~Egvf{{xOfU%;eD>UE% zl5baEPd;t0_BB;;B% zER=>Ld3wnJaIi$`Kk9eXyKL%@1hv|(e$S!V($v{WZL?FS+??xfo1KQHw=Ca6|JB*a zpeMxN*(vCMdca8?FJUjPZp=%wtFzPQ%7T`SmlVi>d1(&3%#}vEOMOyWitk60l=HEx zVz~Hs_PL6s=}pEBpI{QDpf8qf4va~ySvzQLb@r?hoI&v?&!Rilou_%e8on>5(sExK zQR-Gsh{~6uvuA0AEG(@k_tQzZt~TENhd2A9mCvlod7=6{XP<*!De3VX%O1IdM)7z)kIzk+__rLt2K*)-Y>aJ)Yb=Orm=o1l5RH5r=0q#4 zR;7*S{&qNbtjWW$94bm_jaJ&C35VZ>p*9&XN^2Bh*alBPLUltp7I`E9ks!+Lz>neA zj^7dd4&b*3KP~U4$JM+?kK@VkWO$y!^Aw)AP4m$XJkQ~I4$mN-K|H(h?DpAw)O6Mk zYcNC(O{Ypv#xE1UdH8`#FRj?yvWaTk_uZ&{CSMdpSaPr`=mhL%Ynm6 z{3QI=;a88}M*JSf@AvqD)+wNM3ZBV$CgYieXA+(cJRNx2@wDS9;3?o~!_(HkZ44SU zA-_HN9l`GuKb$&+;K3dT3F9(|dZ>TMsB5dd4oy94|oIRVOhmryD76u0JlNy#Stx*&Ppf zu#OJA?Na!yJM_l!Db?1c34tG)rSX)dAq0n+2+lMS)bxLi&qUxe z+Q28V7oW9yV6bm(PeAaky%eze))E5tP#I$zhY%4CNh=ygVqAs_IvI#j@oD3A*P=qT zhlQY7|6@oowZ2ojwrdx!{ZM-H>bI#Q!Tf}ZmKDZJ>zrsy_@knwAruSIY#GTU(rnBI zvf%BMaEbX8usExnTWMn{G#M)QbVKU<00$LG(t^?=-tA&t^jT(#T?r_a>{aAjAQl7$ zA*E1?_!c)#c1^R~H3xg`cFYWz{YJ1qc0xqz(1!~MaIq!$Qe(?lCJ-<$TSkizKL#U) z(sZx>&3}S4T^}i@qZ;$=m+;jVl^G||NzrJnts5M}oZQ^nnbrb3mGXVIv;Y%D0gjTa zR1>vAk2>F`B{~Ot(jWj1)dts3%(Zw6t6=T0@BzTZY6UiDOv6=(9HAjmI9_0T@1=Zf z2<^o_QDp1V@?AD?7GMo8Q4o%NMICGf1~3)?%Jx5kI8p0| zk@N*ef)JZZQ6@=W4y|?%;vg@Iui6SBu-9UvL5d^&%-vO&LiK_#QqT`3Js5PjM;a9X za1MYWP~yH6fq79*87AP%ia)_u;g=O}qXb8O7`b@c|D<@h5B-R;NT_}>mw84Gg5b5-Ug|J!r>o8CPM1JLg4Z?NHvQ$+rT-P zBtkj*Y>WRpsADg~EA|m9!lD9(EPJ%wS6e#Z)nQNdAg#IVam}LI(ps)3{ejmK@bh+b zqxUKHcJ5GDg?V;?;W}fW&Q8QR23;sE7cC20%0bq9X#(JzB`Y3IHnrhV&}} z#)_R(Of=zAWph4glhy;+24Gvn+&;oI0Du?(#6$$xZUDprAT}bvFAab=0K{DppK1VU zRf6+Df#V|pScsV0TqC!50K`WGm}CG90>Ge%0C5ID0ss=Oh|g!ygpb|%pq=Am2e3V2 zZm$6l^cf6*!4UzTFaQz(kQfnQwE-XkK)fP8cW`_h&IcWYPniQnIwIyqX8nj90-zxg z0R#hJC;)~=1ULa%8aWIDz_2UgL#sfoD#`g^5~oiRfRiHTMrO;%Z8!jiM+8`70E_^@ zh=>5C2Ea%FjJzT~+|^4}vh%@Yj!!axlOyI9Z{#)#0FehE1D^xJqX95FVh(Kvz!(6G zxgtKl1&~&i;(Rbgszz=p08SYI?Cq3m5IGi+V^HKfygf+MQ%srmx%l_Qsl1@c><9q21J^?2x%efaeRW#;-E>MV8qOZ7@3_%X6GXUbaeCDUjV>`hyeQyfQtaQctv#n z03glP?JVw=9x(cHH(b&<_Oo z!~ie{{y>1&4S>jl-yGk+0FdT7;VeF3qC>o3U}irwGGjm|G6N_w0ItjcCK~`I1L)7i zOmyNnIss>Kz(j|5!9a9A#i1Ec=M>P1%m8*809R%Jn+*Vy0YpY;4S+P)X=m|i6CL6O z1JQ9AnRNi2$PC~{1K`RGAkhFY89-!o{5Z*@xz0L^&zk5EFBphUGvGiSjQ`>*jDG{* zUyOePz#RXP(YcGG)8#Dg;_L>zpo)0GKzwd8a)W7AabyPIFaWO108R({F@VVU>;sVI zy67yvXp)C`!9a9=Yh(uNwPIob0|Dv`0Fw<21XyMOn9LwDI!+EW7cG$umLS$!z<&C% z1^?H)w}0j={>()9vxxYd>EgBj90*5d76%M~D>I8f8vrJ=h>UO}fHc>a&f+gkbVz6z zSo`l8nVEvaK!6+r;L6M*)c`P=MPzg?lDfln(ph}cL?^P~a2RmtdZyqo5S_mm09R%f zj~M_avxtn&N{-Ig&f>34bRr85c}8ZY;4l!Ki3Y%xnMJe#U^0uy==|ebqRuzY;%`iJ zA`1?$01nhK1&4v?JZ=D7nOQt!0GP}oGCK1)I-SnqPRG@%cHbQ8AcB6-f(`@!1I=&2_<9e8D77WWnJVJhLiOa2QA)*#NjQv$)d$FquVU zbZ!LD3eDBC!dcuS)}Q0uA8(K+(!UYwKL8)-Cr^mpVbB5icXIx4%vpSl6ZIJ2k3}4j z2hQ?F{tRS^%t8Ka09=`a{L}z2IY{JM+y@}d^@X$e3zMj{M?=XQ?HSI*+E zOmreIJti8NnUcpqbfOJ_D|3)zXE=3C4iXuiHvpu$&Nz$D@XiBnQWbga@iQYgQ}h^! zPqhJXWfoFw0GKQ!GCq?zKHoZvzctAddFf#@GBZVwf#`e=4InCGE@omMaU(K1 zn*pS`&O3|Go9IMdddNm*S6+JDX#iZAgJc*0CI^X(PND%6;v!=GKL)Ub`hCO=cKKhU zK?pM$8G0{hB1fd};v*2N4MQWa3kwhLS6mO0gLrpTM1Z#q0B9=R(GdY&GytHfoNp!R z<=6P`FTJP;xeO7iTVF(EkGL(2#DK`#5eWq5N8>V}{uLSknrh@TKgwT43JGYli%@;& zg5v!Z2*_xvz*ha+TXQTsf1hL7_aY7i{5i+60N*#_+llWb_-^`bj^*j!_6dJYkYt6Y>uVp`5eo5d@Il7SiX<%2k^b= zk2w|#zTZO`?I^>7Z?3fdU)T>nj8l16+7D0rE6O(aUi-z&FX#SvyBkeRYO4f90(17O`s5R+7XP{O8B(Rs94LURa3 z94*0K?$X7V?dHPzS@0FUshl6RdI&71%zeZbC{SrH_iY$y=A|VkG|9;@5`YCqnj_;- z*BR{y-t2fD4&=ZDb_rtcOoLrVn*E^v;L(F84<1R_E1&B+t95iOPfG?wURqMum)3*T ziB7<+7OLZ%fL#?eKjRSgtQYTcgm)+bDWD`r!X7e8fKhNB>@$u5prq>sU;alv&!wT2z}%OUFTMiA*0k6ClI;0MJUdMz}!*##0^_-HNV{l=d2~v z&EQg^Eg$Cb`UV`KnvI5Jc33aKL{-P>k&ZR!E$t1-0S;HfPAsIpKt$NftqutP5n=o< z0RQF-;|alF3tqHceM}Sh{8$15TF}Cg&{UH&2aRCwI@0yAa>SyN!5Kaght{hZhNz_8 zsANPXp;5>5YgD62L9hjD=Laz4d8FB{frB6Mc0G!AJ)`ZRc72RDMQ@`f@CTRKABlF(14Y9rQhus{0LEmL^6Ob&O;q)@kXgu3Md*+hHs(u!~oEupeP!-02r zUM_XUGt|`rEsz{yLQg5jkVYO=50jsvXwuvS~jOv}z*t5^z4{ zfJOVB1E+NL?u|fXheJz;I_H2C6{SoV^FJcRul`L^h~6K7 zb5hN|hlvnoUZt$zFwI4(++E5xR-kenrMi6FFkqUu3`SA;?WJy(h&N?-ifIQ1qZHH< z9*MGw+Huackll?swV)oQ?2nWkb&Vz>hYjHwovKV-BV0?+05Y1g zWQReK_f*RDb95!bk&|b2Df7eW!v9TA`F4MQ%tnPUqn;ukm#->iE6Ad{Lzk0vISFR< zr8-WOfsk(IM?@bY#iJtBRq~MoOzZ9JF{*?iG$9?CRgrbQ$Lj#0-hxB`0)))F$OK#~ zus^TJ4z@gu5S^2_S7|?uWAjmJfn82?vrR)`a|VO%dmNTBH`@b?N+c_*Z(1uT?cIP0 zjCeju8v48-!9Ec>I-Eo+NG3aZl#kNgY)dle=wj2+yg1HGz8-a)W}d4x8|N6>sSUGn z;J#m%9?K5A+n-41&>Tkd1!lpZ$=QRmoqCiMqv+A{A?j@tF$Q=aGwRGnkSs;62*W2j zOlplpY5X9<_bExQ&0j_*22%=iqhvmeLIT5&jFMp_7z<-?r9G7pPxe@z2jY()4nhQa zFv`g|in9}GkUFICY)BY=;{?VuGZTN%=zQgNaWf8UNbeIzbC?G*+9>tMs{DzYm7xEY z;p_$HT&vuyY~=qW&r)wWW*bNf`*9F(=I-; z1x;2`s?AJ-oheS&b0f-RHX2i2@5hPb5Y$ zhS2c89fd&I4pi(V%t^D1|5XHq>~=*jIXLChE+<1HzCfWnh(efBOW5Ek^p@Z5Y!mB$ zg91YopX)AxXIDPilb@Lw>J`)6Z09W;`Z&&ZU907G7dl(3qqSC&xR0|B;lQng?YNOo z21xxm-jv* zkdE;X7%WTPr(?cvmsj>0Cb#SbVb$0`1*KJxmYqcifFzI6P=Nasi35vID? z88Cn92IJ8htKM<5WAx2_kG#zGW-~Btwy?($^Id$wN?L0R;H(tJ z9zVkej;`&gJ!Z`~T6;cP&VE5aOG{a>R4&zKr3bzG`pKn`-#)}l08ak*d#G*fIBq8* z9VP-%X}?4hL7Xz*o{AHE6-cP8u)!F5DSBp~`3AL&cQcYE%K|sL8DU8EiJ@6%d>8Dk zvr#NM@()pvi)F0cJjBIrBg{zN z8K>tT4~)tJZ%gWIj+R6hyD=0cTjTRTL8(kYNd8<&LmbB(4z^Xn4U7j}vSl5Si z7ZUFt#l?IfEl6aJ&t5WGOumWb;iBR;i->BmYw#LcV8fKh!fr!lF@3;`;n(!}Gjx{# zo%4VlvfX(R_p}!|oNsLy&%VZmbA1=)L&ND_K{}ioZx|o2Lj8hCawy?UniSyMb3;S9 z9*Aqr@5aj|w>B*mi2L9s8A2th)$P(hK#F@%Qj#-YSht8hjB?s6S0PVic$YKZvEGWS zS4jMHux-o10E1;^&Wmy~E9Zd3IoUn@m6|p%DU`P(oOe9(sIg%LiRRIRxd1OiAFY56 z0B!j^wdG2< zP#>N~JP+Q&>29VNOKtOaJhi`g!cuo|!u;>`6ua2VV5iE3xOIse;%4d(WdCdx4O{I1 zHY#vjJVuRDi)~@eR3@fC9P^#F5vLIO+2ik*V(gE}n)WyXq!M;jB|isyn*o7&GMhV< zoKAggGVb>)1Mj`o#U{|Jvu)KVEw*+>=1h4AHL;8ByN@#=GAr~`iJ6m2lFKO()27Fl{V z0zv~YnFc|G0oa6`sPz`50X8)37TQUOC>6KGLPb|VIyx(KbmF!_zO`@8R&@GoPDMp* z&SvT3igc2RLkcVhrY;8^{*K~iCE2X-6$ zDtfLl-sE)Vy)4xWS4b6r1KEzOMM?5tPK4=H&$7T&a+QFsCE$Wyu<|5AvD<{L);J^N zE`%t%9dn##QHu`gPahLy3OxJyLbbPdEZTvsYrNuPPOSEzaq%69->{rwZ%0*kX z?E7)IS6}r*<9tP<12-At;1!U)V|sD7T-4~gg!dWKi!aHyG`f25e$Dja9(h8eOUJuo zda*7iDEZyCteWoXU4dIov&ujL-e5B^wvP&C@mm15?PX_ z50M?&IeLC~R(W^zDU261i?L&v-W>Qw)7|VZd(hxYFsAyx#@HE4B-@M|npG}3O65`zE7G}dwz*zuRVXG6qns7Oy4J;&U8rEW#eBOeM+Mv#m8j2 zqKGYILuDK48l~*hCWt9h*bzL?^Oe*P!(>BoYzpFr7GygR<<+HFbqaWInv!`HKe;uA zJ%@0<)K#aDS6{F;+^a=PzbM)Sd81tLqvUrjDMYHrh1=1=|1mv{CDAy^;9rn)%!BOXr1hB zM-TG2s5o^q1+q6NITfn;IF!p{gRJKB25FGE*q`yxIxrlupyVFf-0TyYo3Qwh;@L>V zBI$!Dhh}>$QdZrQ#q*Wy`YSyD?@}MAr0Ta+ql>>gM-=gSwe6YHip| zF87!dt)p=YLIt@yMV*tXmZqt5lGO!xpQJ9B?EeDz8HK=GHV_CE^bVwkGXE|FWNm&M zImwQ!%?Icys1>PdMOt8u&-)albuH)y`{3Jk*x2iZ9&(? zs6}G`3edllCC8#8R@g9gch^|e3LAC`aVc04=Pu3``0ieJSSz)6?V`nxETqq|`ES6R z7N;!`+*(Ka8MuV$++~$=;59{)yGd`H#P!9>m*bnijP1<3*;b3o!45hPJTye>sGUAY zj?weaWgMOD{Gj?wR(ViPftw=##~^zBp*5%ZRX>J^=6?iX&^qF_=a=!OmD-$#F$&7h z`JbcDaw;vo*}ALwVhoD%rEDhMGx-Ix@?xi;*tM0>!~nb5d-IIy`L zSh=7pQo)^hQ*1VgvV?4K9b`swVHdPEn*uN+;p=|TyPxnimReD%NCI5wp)^AxbLZ~Sy<-?-h}HztnUdj@#sG6?L^@FQ~leA4I& z>__M;hPG=A6iNhGndA{(Ky$A{57}OH7sh^8jKz{|ku$f2Ssi&u+2aTT+-Rv;U6)O+CQ>JwH`Ig!HuFMs5J$? z$3aXZAhJipS(BqEQxQWb(T>S56vVj*CyT*FPz!UReFqZvU74&x#|<$mtCsCB#mev| ziZl)zAfxqF0{eESuGg)wcagbb6SRKt9DsBEW&#wkbve3ESS+Ojz`bIr^4yVbc;zdp zoagg?f;a59vbT?sd`RJ{cM|4TpLYuWQ`hrO#*6B0r~kfB=>N}2G7Cv*uaGpQ?jYf5 z#m1ip`NMaLKe%ACVxyNtm=znJBY|SY#y^qh@YJLHYIZH(<}0$b@sgszGL37QRo^*) zXmh)bo1)t|QX_4arZ$c&NCd#+KvW;@Lf67R?-70YJiPfh;^=#YM&GkzeFU=^HiNpS zhjP$|=OGFZ*?3^Gr>Xt|MCs5}uTLYQu?936jZJI0-j3v#s0SG9L#zWmwI9RT4fMj) z!q|ydSEf09_@%d(xJx;SZ_4k^_`6HJhhg4>FSJmXz{C%a0?W5jx)!((T&@nL*Rl#Y z9K#lLaqmjgSaOEe=gC;-VR8SEkTH!8>X&MdSi_a+0~M6Z80=MOoboKi#<{W_B?#H#oD}wlYDs( zJLsA)%=|$pT#~d8*`@@G1%<+@X#W_|ymAGBldd*(9mPm7f5p(o1#xv?|Pr^AIpWfblqpm6Ny zW1-11fDAk|Wi7HxL%CRSqePeOpmuw~>~TR0)(0ha zvHlLcgtC2$9Yd5q1tA81)TbRm3ux1ppzr7(0KW-a-a`QK%8_8&6sdopA3%hl)smOY zz_mL)w5HOB-)VG6?91#)H2PMuXAz>%6BqF)NdQ~X50KOYJ3X2t*&`@8BzL&~M@5q_ z$4EsNg^RBLS4D%|K#6+?36-l4rz&e5q+8Rct)wAx4E~crrf+en} zW`ozyQP(Gd-f0FgwXsA@{jmBlQQt$g#HJ5@6G_zdRjm*2LFdbDefV~K=e8AimW{*R zDudYx6mBR_S9$c|E0KxG$7cb)(#~25fF=R>J}g*C!on7E%z}4))k;=A39)7h%ZJ${}WOJDc{+1`~^CYQe#%srx8&wfj&kZ@BpXqd=Fj93khKub?FXBa-Gt2 z(3z8OS{0F$Zkh*4QE}WK=VpIL;xdRL5|MMh4cjgOsP*52daEp>6D3t4yn-5slrI}l z5HE+MoBQoRjAC$uR25NvJej(aQysBBhQm9Ay-iV$W=yEB5_Mw?}1D(8dX?>DuC)@olY$# z#g(#eNlY+lUTU;bDkVf18W(9-BJIRdw%}R>I%je}I!Sfb$ASh*vuQn18 z_1QS2a+`^PQ$~gWY5SE%>?djusr)LB9gKfJPa+Z}m%of;NqMqvvJx z!czSM&-3)$>OuSA<0<|@BMBdm^ABoS{R3qtK_D#+sI4@Bq!G$Y0)+onZpb{2Fo#`RX-6FYp&bHZq)v4A51#nvAm0 z?A_A@)Y9nPQ~USsK}HhfgzM$7flr_PdN&sQfjIUb2qy{Akg1eYjxBH8J;#DwBnV4% z=L;6LYI#sC?+8Q{)XqwkEd z;Z~`l?kKg~9!Nk4xyitHbjIGCg2vd==`CXYKM~|B-teX+8#=qU#ClrFL;t1(OTFXr7)Z5-GV4A-9_(51YBgtwM)doEdKZK zFMgrjPYu_ZZ_gIJuNn~9{@3sla;NR5k`Ns&dY`}>xkYU6QTD*8H{9&Nt!j6|E)ayT z|4Bix8YQHfew<~6-6<_mSfZ3)DBVQ(jZ|2y=uJfVUHNuP6Bbkj@iq3qysYwb@{GEB zy7f09OB?b>;1&wJHogWz4i`uQ*O+OB))jF#je#Tz!N7zPb#EY!Lh%9efqLG372}6Qyc@V^)tM){1E#l!r&;+TSHB+uKTD3C9vm3x}cKU%3H0|HV zP&v>g1aiRH-sbW9`^F1AJ{o?vac9jD9{=ZX{0OC$;|YP(EHHDNG@3p4hyFcgFAhx7 zB4V7U7Q~`$lziAy(f)ypjo?BI%}vlhWLn+#Ukc!0ng@$Udjvw)1|_BfRaLEG^b7Fd2f4RpAR3kV?++ z6vskk3x+O!IAPJGQOA;lTxg$Fv~qAOoJ} zFQY3nSq@g+W!hlwOw^Xh6IolclBKzxXGHWk3wE1aY*ZD1P!f-phv`#e+5Eqt5dN*E z#0JJ_wpfBRJa!sI&xl1aY6WfCj;Onr_ZcY7)CxfzuU4ce3moA!6$k|x; zv{{H{78&Y^}auqxu`Q)Wd(H4;b`Q(gI z@M)y<^x&G=JJY5r7kaS2^v$Xpd{bBXmi*PBC7`{OAKNVQ+ny*ivz9BcXkhXh-uXuo zJ_0W-e-z3H>VU~;$`)j6>&me~gJfL!DP^EMcy<#q_Au>b9eX&QWWCC+S2P zpO_feLXg~&$GuG1e((H~6KADqO7gZUC5W~PPi(h}_?)Lut34rO#^==#CM zd=uiNc1CLkWh&6-Ja#YQb^S|jfluDQ4me77I|Ndtg_l33n9hQd$yw8RL-` zAXqBz^lYcjIQT3&BP?ROefdwJ-5O}BF$`QPvnx}Vy4gx_OIrJj^(41!FSy(1(b(xugI!- zSF9gI^v*v(d6VIjmg!pr*N;xffIB&0&$?)>r!Bvonj)~6@L|M{x$?`c6}9{9d|j_b zDO??p>f6LI`c5B=lx2dYc3E1jiITD`6X+Q@4Y7XU+*N%@&u`C~NflbR8J2ncO}VEX z+KTl*zzSc3%UE46mNfCkP~pNTs{kT_o7C6fqQt@uFZ;B1J<}|JQhlukc01GhW_s~08vVGUC#{=g@LiJ=Zv@482(jy@iTU#{u;krz})=m|8PZv!VNnSZO%4L})*v^@AM7UVX}h!4;So zeimtzho0==B`5>A4dNwJP#K$xM)!Ddld8vse{2gO1{is^rz#iU!w|i_5HW4Gw0Q*Z zeE=vk=TWw0(ijiDup2`8;%Yv==Q5l^$yy2)!9nU0GR$O9wd<*Bv{uSEnwvHDj~GT` z{olbn)UnUwy)obMTk_CN_vDG*&BRouWmERQLpx%rqShP%M_8jzn?Zq(@jzTj0DH6J zuyIqqw&C5`Rb9${fT*lk=_`EPrhk{+i%+atOYu-+^$$at7W2?hX4F&AmSkBOla=2t zB~qR>N7VdwjY`mJjs%|etU>j+uOu2&fT5ImYUz6&eJ`MIv-gb$;&j29JW1tMM7T6u^EO z`lOZfeP7ec+09y@`g5~!_tO7IVU*%#$I+s0wx9ohwUXXvEujCu!n^0AuW$zQbuwz& zowk*FJ70%`f4%t(v!CC5cnz< zJW3jeBu;xq(@V6mG=$wdDK+dU(V)YW^|mMp)-{Jq**njp1Si&DEjN`ToHY)1%58xkRwlD=&h!3<4ONlNf0}gjcC;}nR9rcT zmEn~*T-G>wGABkUd-EOy(HI(8$}avMX<2eO6`B1esIvlC&$m;Q*=tOmwF$_!Alpea z4#PEb`;4k7vn=v3=pHB%`3sdp*h0Pl{chM3J+~c)h`Eq@h$pQBi(B-kcb~Rc%0pVs z0rK9>Tof(r&e+3#ja38WJ%{F+bcg0jyMtl3hm@HE#61#Xp-*1us_gMhMj;^`b4Ye| z`Eaz#&>jP763MP4wd&GzWnJ22SgSatw8^Wkht#Us|El6dB#Eu-{tCHuq#?KF{Z(Ld zQM5)c6?h4}N@$1F&>_J!qiMN@RXjNNS{va2K3Htm?yzSS3$k%vaZ5?@>(q?`385Ec zVpne$)Lff-zkOk`Yf)Kw*ZRa4sxBc2uDVhlMlT)E7CL{c(X<-><2V72*T2 z_|13VpF0)*mQGnAZivHg0fIJ^B4|Sef*x3rjekFM;oqud__w|a5%(f&!vhH0uo__- zHdOhx)gn*doAiIKyj4S)P}t#c7e^yStvLrY(5;|P*IJ&$={sDpu`hj}a!jPhlUgw2 zLOSk05b2>EO!!c~4F@K)ca>usaEp!ej5I9$3>lnXi=F~Lg4Ec>#T>C3c33z~U_G3U zo0U#kkEDMDor5Ny(rP*YaQ0aM21=a!~rf&(V1nb_{c)&?x1LQ)!SgNDWo?O5H7ER0d2oJ!7UDj`)0?mfy6mOQmc z+XLR0YH@9T>aO&I()i-d8BOAEDYl%U?zqloJC%qa+@F*mT-=r4jZd4D$d;f=U}Iz$ zO#6CR?6M~jw3V(VFm1DNQVNHhEjWbbAr%3p`X|8Gwi0*2z>F>@e78UkN*PxJLe1XvPO`rp-Aj$ zZomr>@#@+d(E{VBeVnX^1HdPK6o2Q`&D)+ApB%W3135s}K2T*geE*FqcKD>Pp5q-4 zV}x$(mF7)>Cci@$MDJO|Z?W+%Ce|Mz1P4z>htj4!g}&#pWLqrF$3$zP{dLl-loab9 z81XzwDDQZez(28HYeXu~Hc*oq`69HZFxLPmbkwfRwaD?xGFuN1QPyDF=N>u_0%gDl zJP0}geD?7`AL{0WBf7aZoceqE9^bqnr;2@rNidZ?`)kN0*cjh!*ZGKW#t`f(oALu8 ziI0x0X;dTZ2vfK^lj;X$!QPfFlkf)XX&Od29PaFpr{a2)`d{ z#KGpuZ;=gGelUiVxxNLMl5J{^;JXbkf|_Gfmj5P??_s_18|p8O1-XrTdYbE-&{Pfc zb8+xvjg5CP49SyShbFiLemPaIRT?LRgG@{vWMHa&m|hHyHnRDosO`Iqqn*)N*3*361|Aqx&fmH!w({08y6CII z=ByCpT!X*Cal&XCjX~eMD9+$E5reyi*u|*8c%>N(t~c_jO;o`?JZ^!2+*B~T1vYJi zpsppsApPAaTu+Mo1jL^{2WXnSi|oz1ZGmszP2GylnQVnte$JP2QN@(In5QpysAYnB zyNx)??E?Ng%LKW!ab6s_$S~p}aW+LyeJG{2W*Bji{eAeuC|};DJZ*mf{__#%@GZEE zdDL^H5p0fgn&v?nU*65#s-Vt`>IQ$qF~Wic0n%arAQKPKcO%iU+GT57A}>h~r!ko=|#dLU|X{h7@bk zJF#~{na?K_9y5U0m{36P)tuR`7LY6PTiIciA(wG{!UYzc^V!d6Yl)QVk2!sB$A2L- zvw^I!CUcIpDL@Eh<;?lrH_>3Vhffo8&f;W!m&l4ZRKC4W51b%w zGjQb3+mx4~Pka~GaTpmR)5_Ee@Xft*ynDenaUJoU=WW`cbjUqtLj4cJeLZBFrSO1I z&$ZsS*_^it@)C?CK7wkl(e~2-$L&R#=#6UmsfkM(@>KkKL^%WZE@I-JkPw)R(PMyZ z$KL`*20=eT9i+}1LW3T>Q0}lEq;ZX)n=!1f!YH8`>t$X&jN$KsRwHfl@~5H?@#GAG!&Qz=*|^xBG*l={33z1gL& zvgyO0#nk;KHpU>5i}e@LG8s(`^)YndHQU)%eXh~_H9%Xv6p)HTEjRc!pGJVVZI8Ik z>fEz>M0#_!%lG(Timyz)gO$F{7gM>7)?A#GIReRNmEGs;pkE%Dq*)^XI|Nz7XpClp%=igW+kAZ94L*bwlSZIfs8LD*3OF^>uB5< zL)iv~&;iWh6qHIq$f#jsERic9wAS+%3srSzz->^m&3hdAJPnL9_Fl)|wVzXnuRf7d zSr7Q`N>wh7T5YeLmxxjh*6vCTh{}agt5Iws$~ahiGPS`Gu(nz(InMpnyU=Ya$Sm#q zzaq0VEbJE1!tP;crrC^tlG<$xzY1thl{8k_u+jok@YNm|<_OsQcnX>hm~*h-0JsH) z6b?uyh9Sjj4;)uGr}6uOz6*nr(NMLoK~qBT5$k#kn+mv4_yyp%5Rf{@qj6{?18851 z7<3Lx;HMfKYYb-ZL#-83sbF=5z4sD2NjCnMPz#O~(BrYICXd5?^LPa*pho&5%A_+} z`4~eAyi5gVC_R_tp?yJ~2T0(k($h`%2X)}OqTa~B?!G6`wAS9KBDGI{<@RzgWo66J zCRo*Xs<^VU2a|sbdljvT33@Y-;f_4Awr@gZ#rnqxIcUe5rp)ZXYxRjbon4~M`zk=z z>3;sc9Pi?*ZCIDS#3=`3F@%uAE=Z9yE@(((AczlAUv3J{$*hJ+3r+xI6w-Y9kUeW5 z6CWnan%bJLk^O$W`Q+1$iS>KM`d#=Cw-sQp?DhW+?|rhFf^6WT1liobcM3GKzW;iL z8c9AL_zn(EVVq48x6OjH7aSzdZ*Q#Kh)5yNITpD^&2a`-orLD40Ndpj`5@X2n@Ujj z;bc0ryT_~jS{qPn%O&ds3%eH+RAsE1Yxm_keCp%K=dKmnO*t#nDLL8Mx5;OJ{f~VaRB( zEQ3@1m^fA7`K)QwM?^2(>p&Ym<}r+==t>v_NuD({2s777 z10^GqF?wM?=ghT<^>jUoClzxpYxsi31iT%H^#urgtpT`U#Ap}KKBr8yz;0h)bee=b zJ$*ny{6LgK#yD723R9#|qV+Z%1%;fbp1_ z#ItJ#H_l5=X=tcT%|(RYh@jC_1CEtU9Hyb6VFJZ{sq+yVMtc|^$zjBI{|6yHmPU7p z{-LQP1&Odl3uFfo?`R2JO*?I=br+55mav~vD)ypEDq0xgTgYm@9gE3!mi0cEj50G1 z(i0}T%1g|RwkvCkp#WeNR_hoC{~vyXf{vEr$n*mH-PY`wt~ zC|GHO`!_*ubF-oJ$@l2FIv#|aMqQ51BEg_cdxWYBr5d#N_#oIuOf`r zj}5fqHERe^`7v&C8-(#{ez$NukTq2}hO$IKz331cWa_3Mf>5Ku^8XuJFrjY41|#-9 zZUOokZrmu=B~2=Y<$p8vO3Zj;+$ez?my}dK9#*v)=dc+&_9#jy7ks%$``+0{nXWqI~lOV-zbuETsY4AMyKfI9skIke1)3WLR%o+HxL>!sLT8Zm_H5ig+8=zNK>s~ zDNDxt8GnZ|y~IuDfzM=(U0IG*LS_lP8qO*8Y(lXfYVQ(#coPaoeu6raKmx-JTVW-* zMjq4_LqWu}v&xg!Uxo9>*fyA(nJnSV-CJ@eycfaJEWNqVnV(b>m96F{Z6SErcZl_W zMCMLdEIx}T9pUnlQyy4IW|gPHLl<05+0^`0ZQ2_s9Gdv@v=Vg4BseZ3yEfb30gK@; zr-Ic4asPMyqX<}^TiKp*Xi--e@~^(dmv^f$(}JbS>S3%M2D#d9Ft(I7`0K>10beIW z$gmbc@og5O5p;6cI3ULx=U_u9*3UwQa0hVC@I*$NvnY!ss?SlfHjQXfQeps*Wd-oq zgGDNtTAI})6tSMqD@7^dqrXR#E~mi3S>KfYO$c_rwLafK{|uoItwnqY z{Sj;ep??mJ1}fWP34Mr5fzd9uVB6(2fH48C4g_Ehj(WhTo6zUjnQeGgNeM8LZ6zgv zSOXCP1Z7k8;oDIq=qTNW{DHLW&tOJS7nb?Uc++5)p@aM}@YYF!Xw$E$NOD74ngV*OFnumr+*5{~pcsNL8v0E$gM&>U^>o;K}ePEb%Au?9hb3;c@3 zWpw(<#&rEmCD^_?6U}5a>IjyA2~O*VnchFz;y6{>I90M<89)_&)(e0yqp~HXtolzh zbE1$SCUyQ&yQ0H@58yZ$+*b0U&6AI5%J zV6;g}WEQft(T7uoG|=mu6i>8#A0kT`77(bdJu5$%?8|rYpjO{K?U=o`hFjPUF~64>CF?h{YxW3Gm8@*KSfK{4Uke$OQg%q2TDy)IA_5 z-GO?wr52KLgez0Qe$bq+q1^D9$YR>2a1v#FYJ~qDAuxiNDES(q;x>khgCh&L zcO!36D34CoaC5hc#n}Al%&})nvC3KjN0{`vwlxXbkcMcTJZ1*$FyO|%$|{l^$Z`sr z_}CUb)|jcufM^)B)$p$gZ*>fvA2J2D-^s*g!wJ($h@3k@DC*EYl5SQuKzBG9g-68( z(i^Q1?zH`^2I+E%^X2bhroXG{hx>IYw@%zjmp%rdM z6J#(N0zMy|yjQtr1$D3O5w4Tq=E+OE#*v|7=7FUXX!*ZGlH zUyirNVXBXxJoFi75#In>4L?7P|MiE&`tiUgSJ^=T@E}2F4#kZL2y5ImId#UO)kDOc zyTqNfG|?*UB6tq?4wj-j>ziLfP-R?m5J&dCk0Y70S$yPA7-6_r{n8jf`{=w4$h!#- z^%YLKSLnE-Y8-uhrGvi{G5xEM4CTPyU%E|UR_VKt38bqF3#H-s61}B}R(u36_-_xt z+x)kN-=q8)c(3Cy;1w2sS~x1&pN#j~3*V6^5g=*$+#w%re&E?rrtmykK&z94`{|lZWz4y(K z=K>8ma+xbFti9lo@@g-vl#**NtQNiBN4QZRGQRcSNnp;E@`&6}paXdD*79c|%D~^@ zzdrn)>Ysr3M(d34%42JP95q!QnQJBmfkH`p;w+XK864nd4)7ciS>J$0GV=Zh0W25m z->2t&V*NYxd;qp`^pwT=9rRoy*1tl}MPmJ*@Kj%+-VkzYA#U5{QeWAF05%lbJ#fNC zFZH}K=_<|lGQqtmYv0$)3=#U&vM@iD)*hD zdY?gn=qp44ZC+|CT9^8t!)F&~5K};3iLAOjq$ksN9SruVLnHlybF~A2;-3inT zXO4|QBPWM)EWznF0{0r=Wo5NHXq4Y8r=Sz}0rC*gvM`YIdU;5wO;AaUe(L8Xek&T( zs0uZk>LX1fJW8>L#QJSu3+UeUDz!Py4cm~=aDj9!VEeT>SR@+ZK0r3|dEHjr>U|Gc zO=Uf;V(t#pv_a1^I7U< zVr5!T`*?k`fj;1y~F>2CC|IPKlqtSORP_i_Hi|F=d> z$qvk#y8_{{+1bu_<#XwrFC7d9CUJ(GM&~V5<;sBo<&(ZoJv#p`|62Q;ba54Mq;i z4w}uEu_Nl2P)i61)Kl7-(d@%tRVuko8YY_{clh~p-uHaZYaHm!Epw5||OHwA>yeK?z7 zkVwb!Z2b&o;mi z(Ya3=n!XQ^0=w~CZnw_08&MklA1u&v|;SB*no9Of&)N|BtQ#4b_`lHn2s zw?f=Xd*idzrO8f4$FUK zMY08U^vVjsQWj1F&tN!-1Q*M8DJ`qUA&uAMB)E|t&whm4(zvIYkZUjXN1N zz{yA5FvY#Yw{4<4LgkejxMw*sx9Mm}RAG&@WO#Ek|UDH2}s_70aaFz$F z7mzuFn|=RxARCBQqtw9k;-VCfOLkhiY9f^9 zwg;wK=^tSm+yw*Vz;&>y6n9#%-#MFhocU>^>&P^vAo+G653Ww=Q?J1`oHqyE3|dS7 z8JeH4HrLwHTR0v3-0LQVKrtBT(_@~!8hwQp;tn?9fw1}mOZp<HW9lNr-NdZL1 z+~^FDfDdY3PzDFOF8pJ$@wmD0(}&IT^#6;1x`pyiT?LRU<7H2xL_c3xxg@fNU|M%QXTP7 zg{`+?NQTb^!X-8KVd$hGKmY1Xxl{|0UW&)D7nmcCto|DRL}fL)b)}WP1sMeufN#C2 zM!?@F0AmYI8xBA%f`~DQsJlq}%VK>F-aY>xd+!2QWtA`dKd?6!xwx@V@!Iy9h8m@o z2o}giEKw8zDQ_qsAOgYOUOJ-KmSVGIYG&%4oYTzIX_}gQbIEL`Or!(aWy125rBl{8 z&BJU@Lz5{cx!><^J$r99Xr2E5_x-%*{e0fgwV(B@`&!Ss{MPRpgWiij#+ty?zPObQ ztw4{sN(~fqHrh}ce5-W#rC6nV^T4-Bg9iWhkcU>YcY21t$`q%9$z?tN3hm$`a1YvL z*xHyRlwk28aV|ci%0M4EXGIEaOx`>vU!TE?93y+_2q7-J)twF4woDkohXO#T+H$Z)9RZ`ZW`E!5P}ykcVTiO!48XQohm(+{rY zx8Wkn^BOJ&*%JT~&L4%V$v#;Rrv_S-pr3&V8CX?}QB_wO^`IKcY8xUlh6WCLDbz5) zIzdNIPfN<7isI`Jdwo^~?%$wpe-f z3_Yf%r)BqA(vwbw+QN6khmk{wXJ{w!t{?Lq{SBF;vs%*CKMwgQ_EZO)n1ly|l%`!kW_t_T*0)qT6=Kx>Zv zkZyd_xvmzeGyWA*b_~!((qYTJPsoZ5ZfQ&*Tm*cK?Mm%Yg1t!dsD`*lp~%xnl%tyh z(zK997F$IKFz?cepq3UXXerlF85eJEgWMyEcPzVCl7XqMdYh9kZFDutr*vrypE0$C z@HLfWE>*RwMJAG|HSrl4AZMdYF3X;w#5-P){;cF$ZDCRN9)o?r;T za*v3GbqP!by%5*)XVvTQUdG@N*)KUlC8s<>l z3a*I0v(y=vlHyQ(4)#7uoiQmXyVRVyB>j8l>-TRRtIMTEps#;-s4!_qK>u!){-qG3 zI<#Gqv|QE^S*KYSJf79^d6JF3(21aDB&F$3sw;zyKA75r)Os;~>4B-;=)qHLd$ljl zJeQwemKO~Yu%#PDt)(q3Qf_>!!t3R@T66NtgzwM0a51vB_&uQ5`TQXm1 z0o$TX&&XFzdT-||?kEEyHD*2`D~*M1^xjy|>KjKe2JTy_qeZd`@z(Q4+Me-f|5Dylc#Q7+Yxcr}UE7}V9(`j3FS zm0p`30jdxEDgdb7Xyny%RB;eClm^cq@EibC&nE#u^%Q?P1E^j^VP6xdJ~@Jo=E^|z zj2QzAXWIzD43&fPg{hYpi1dZE!VYph~?k`AA^Pfqd zFxlnTN;*=%)1N?TlGb-B3VE!%(@;m6++Q831$Cr@&7zL9Jx8~w&aCQng`GZr@a}5MDGMQ;=WSQ_u1jWoJ5XeVAU-FC+v={pNI^3pZrX-JIGSr84G(&bzJ@zJdPf}pNO4E%Z>*J} zkU|_Us3zIMa`o>6E){|ik}IKbV!u=AXA%&iym5cBg>dWNGFE?p5-NtRR+AD?>zSXc zY3XY3`X4j~qVUM}P_6bIpfYa!ZzOJu-Ts5TG}DCo zC`v{r!U%nC|6FT~T$tJrc2SpQXXy5;lU}K{IC{dikJ=f&>w+?rGekET*)Uwu-t{iu zTW`(WPdfJHMrXz&0S6sJlucC5K z!&#HIxtN>Z-gs2pp2->Ya=x=(q`~!X3p@zZm!)wLtq~1(>gJ1*X^9XZ_eR1&_w1dT zaYnL^$V0)#o&H>+v9=?V%Oa!=L3W{@>=u9I1wtv(c4NM-3n)qNt`%XXj9yc#i1&j9 zg>wotYCJMd;jVW_z2hy)ZBfA_biGTHN4?A4CzUa~? z$SQ8feX?tSY9mX9X6mU(zWQCVJSkZO|Hh{?@?@I(GA;CGqH-fsoXS+IGQlz^nc^HB zI_OFxQyfLcN~Xh&Z)}g>X>L5Sc@#)AQ)@-oUY1kP;wWc+T+rgtUOZys8#_PSPNKSh zkfGxY_SdWqdWU+!D|DF$W#hU&-EGGKus!3lnekR+!de z0&B+`ytt$VEOVaQd@6{h{57&3jBQYI?X#m)@+ifMTF~X%M zcW2CeYFgVgtPdq!DND$>G9%v!r4w-o$AMLNp74tf0B;ha}p2 zv3`OXgd`vtF+KooPAaiK=KY!iMxhz2S05<}+}#F(B0F5Rm4c*KAYzGKvn`|aF?Xmf z#hGBN<@rx|w-k2*Qk`r%=B5$ucU?!DLz{xruCvW_q0Z2rXE zs_ON2Uz9IIZMLvb8WPd3nv$B75mkHEKQ=6RqZu%ee!zE~6KC1|4o{b|iY82ochVST z5)Hk73j3O)|0|U-bzYEkSW@i)Z!GB9%~$Dn5@Vn}i2ot+6+s31ch399v&}wBpQDn< z$?QHMxi4-RsZS(K_1TLu$@;PCIWt*IqoSb>^I{nPmGAo&xam>SE|>v(^~1mlh5BDt zNUh_g$R;MuaH1`ZOPHII$dQoc-3mo>47gJJDB5*3~eCH%Wv}ldxSTQ3% zvPZ93%D@;ZP#2V?gAB~qn{$lmVw+i(tOB+okQUPVYfFWhmR6v0@z;1H9o&v&1 zH#tttiR=+ysN+;vWZ)4s3!XGlakZ=cK>>U(Wfm}>m3EI_%bpn=NV_gA0;^!Te>{G; z%v#dmM~JlCcQvn$`q<%?`vxgrT$1HJ0k$0Vqw#&Fd@&#s-1u6|KM?T5mDTIg;#;}Z zyeN=IeTJ)jl0YciLb4o(dsEPX((VUPu5P(kfSA3m_XR+)GzyC{wL9d7`>;{?7|Z?N zlF$suRo;@Y+}FP_4t{}le1*NPUc)J6hgcA4xi_8y02W#ndn$_WM3awfjO^*Hd(U5t zmj|ZJv3}AM+>##lkG&YR=K#UR*j|U8iADNl9F#k`VjhQXcD0HDsPewe^b}OY<>cz+ zB;er5h-JSoFdbHB#8N%i8yeRybe@FvxH3i)YU;WAUHv~&y+?IxqH&n{F}R73s%fqc z>pu^S9PNwbxV|tj((@x@Yv5=$Q5TCuXC+_#ZkD6o8syA4NmHE}$6OhZm^x_+l4Z_} zS8XGSg~L{eLbhQo8L#H(O|(tUSLlpqRcl=xt~cE=`giku>zK_eva@fUpgM+A1x8q1 zAF_syib)g?vww4utF2YX%-dn?EJ8rUvf{b)pfP6nNl+8w7dvpZq0TW##1xQK4qnQo zKf8CSVvkpQzYa~|nDlHSZE9yt_E`31Ur=`H-NPu=)OIdqOpI-iWnY5`NWpa2c17t0 z1LJwSN7lG^tCQo9tz22eD*&28v=rE+&`XoHN39dja3TJ*7FuvhVYA={Jut+lFH^Exb7=XztP=TWm{C(43K| z^grY_&m97{!JIhzF1n5qx^Z+7(9++>Sm9NN z*ZGWIw~9Vy#IU8wwc8f#L~sT^;6LQ{Ck?&#Lp6~aWJ+KG@T~BFvUMM4bQ9}i(WhM> z8@&ApzY}*t-8_@qJeK=*%Z?z&bH8Aqf_4lS;LfrSWqdh$S;c(Ax2K6M;-tAjUnnQ>P-dh^9A3&qT}piFm;H zU)OLk0qKH_Sm9rC&uR`!-fD|ZUZH}*C1@ORG2)3pH?I9^lG_KXROaLrcJl(M;y2P9 z0bdklmN9?D@@&-NbFF4uxaGhrj;;jvsPnBR>a*R@{7OStQNx81gkxQ?%j#%7l9U}= zH-biob0e<3)^1LEdGk14%}RG2YMA1dpc~YxRMnXT*oUO3aa?DHI_f^CC#!7yzB|G;bly0544yY$9%1vel)XXr5PcrZ z+R#;C5h%NUo`~Ko0^mXz%Zqqeny-feK zQI6Iq)4SY>KHhjR%5*5{a9wy}qTTFj^%8+2P?*L5d$loKWH=ocL(mkB`d6p`U4K^r z5b55-<668{)SYBUITx?xqT{##m>}YHuIM>b?3$M>zei0MlHRVLxbaAoYsX4QF}$m; zc`AC0M9(+9PlJ-)-2S1c=|UNO@w{)LcFR7fBeU2+QSR0A9`L>IlJ~{)zU_P8E$_F? z`>yZ3QQl|FYw^9`BkyD9-S2z9SKbHD`;NSaQGPS;p3TK5;(4>sxue(}CY@aBc~c}y zoSAFcsnreGC%PG;-`oy)2yOKMzL16mqa$dmM=2(Go-JrWz8+M-4vF3ojG`l@|Mv>> zf&$$RYCtU7I_K>nAA)zU7$xMo=jZF4`9vO1Lrr&~?FPsu`T|mu77h*DVo{=I{Y-60 zy!ubM+`Vdx0V+0qDx%8Ic~pz^D4;gSBx#)HxE3L=InaTb2Yj|d`qAc5#$WZ1Xrg11 zG)18!Pb=UVRY3@)AIx`#x&Dc!mJX4p)lf2K8^nR+$Otz(uV$4r+%nz{DTp=uyYY~e zG6`}gd%|F*30EMrGcT%nEL#*jPgAmtbMR_UnDG0<5uY8^9O}u4Y6{9!%3)%5pMc?? z+_KVkL0UUm=zG^lH8xQ{(=cZ|Grij#EefCMhS0?_dXIL&$kkDrc#UmvN=hl>!WWFkKV7rsD$4aI zz9aW&PfAWfE~@#?dJG{d+w~Z#;x4VOqiwcvBD^P;p|hkCF*<==di7QS6;Z6oo`lK* zo#FtrRwDj}#`8A&J)WVbC8%3$dO~UUp!7aMz>kaab1uF;C5PLBelyju@M(4wnjub+ zqbq~N)R$kNZ>du2+8ZGVRCRO8-yiejeC>ez6d*s?UT1O1Rv;>PJIqO!YCloRNAmZ_ zNhDN9P2lZ8Oc6aEFDpt-O)p#rJj$X< zdL_SL`<+SAqOrU#*VQVv);{s_&%jEpES#Czp_K;NW{V1<1RtZsnD|$0F$-l@sW{z994*wV{K-kTs!f?b+dkeqNF#mHKQ2R8 zyD;{&O5-q-n*Hxe8>-r|5g4IAgYw-zPLs>K3eHaQn#rp)JFk##{Ofn=AA}j;%8q2uMxy&jgL?joIx-E|H z5qvI_v0drdflOK=E&NX@oD+s!L4}R(8;je~m7~TY#JTFE+r(%Hz^YH75J&w<^vbrZ zj)D_MIxdCUM)4v&akQtn??Jz_%YKRqe#?Ga*rqakgo^qg$6!yq(a_dQ@OrnN3Naz% zc^YGd9{aWhUGeNBN|FE2eeCdhSw5hR-EO*L#{&s*2J+V?&UvnWhI*qTb@mg&CRzw zdspbpHwuu_ifcRv<`~D9QHXwk1!VMnBS79Ow&>{{W`|pFuaC=_VDQvhJ200 zw(OKCyEfqr4tE93e8bBbOxFX<0l3UvO}ZhwVcUiAV_p%t*{QWivNqdm2RA)D+()>*S!)5oTMEWt)*@ zo2CD!*S=&aj47TZ1G!w~Ps6D*!6uQeZ`*>-a+`vV0d>-wAqMlPvU zDc#zcWJ>@9n#Lh_%{+}|c5 zKiJkL9kMi9j0`fC)Q1V527HYj2;)X?2LL*MbV7FA?ju`a-aRX65Ng1x&slutUD<1E)rU&ac2$%nr_F`vb+|C(Qy)@5Tf#h9G3odL9z{$Ca# zy5b*}B2>0yc1CdXPwQ(4>HrPQ*g)VF!yDNizu}E+4+>#V!|M1mkl`q0V3}RhX8~H+ zUJIL@kM7N{|Pi(L@G-ZF$Q1V4qQ#Q8U8>s4BE1XfkVP-Hw!M3|9f*r4W z3aI9LZ^&AK|3OC?>j?cV@ZY=UILKhjCibsRIC4G*t`>$eX#Y1gt?JdEgYSVX?3^Q! zWFKiq1BH&94#~$SsTd|_8&Ky>`dp)M-hX#4!$SiSXaJV7!nEhK6ee1IjF6^JP3W+P z49Z3W+92>y88H(&l3H!)Frk7;p9+?M$9Ud?jW-_hW(EyK9NNvtv}arjau1i)9>hFX z^I#BR!EO`V$N7vHjb(&fCli=Pq@bzV()biFK>E+oPf%wRr=?hSi_!z}HrTcx0Nl7+ zr8xub@?LQIAew?V4|epJHz%O4$=kBPxG{8c4-~XeUA-^o>|%u{8dY z2JWiY|MABcHG84-yz3+(?6=ZRVFn|q<=%C&UH6zR_sPEPt$&-Ze#>r_n~+hcsMrXk z88Bowf`QPwLew=%p9Fh`Ht!NY_7NmJ0a3D0|M9JAM^xnHV!Ad@`f4AYTMMV)FY zsYV~?@JQ3WTN2Gi<5EldJk{y9PW0t(Ait?6vHr}?6HIb3(dz{d zWjV+s51QB?tIGwb6L?Wb*1678oL)iCub zjIZP!ST(Bj_GbO8^wz-ZDdQXclxmD%Akp;UM^Ov?0IIaEAI+rCClXhIv7Li*53tYO zUhMNnOhM+5(2XbiF_e1ZT(x*eYuWvhq{!}e6xDpuwI9C_1ZU`fPVSGd4{B0f4J=(y zz&pDd$pW5HSTbhlPY%fS3QI!Kz5Ci^nv&sq53Ok7Sth!ke#}`=%YpyT^S;>jzTS8>Nvi8)cQc zKB1V>q?(x8D1NWn5?X4iiFMzEe^_k-zj0fxBV?p|qUG7Vo|dTKbE!~6qA9~Ea6 zS*ih9ssUL>1A4!r0llTLS(PGBIj$6h%phvDG>T#Pz)yjQ)*6rq7_&MRoFx&6t*I}H zzFUDB14;U_@AQ%Mg_dK?9MO$)oN;BLDo%%bKsyAN5>aOzFmoQ=D!HQ|r6BPvU%_A= zP+Gxd=EUod&+R{RIvTxmpjYN)kIYLo2i%*4NX&5`uh$I7^5B zbLn#+_@v6$xW=+CbmkEwU`>D0l7WKA2yfQrw!x%%#2sUNtAnMHhrKg~%$8>lTK08j zo=cxgkya^E^tlgj9%g*sPi2ZhLMx3tlu6Rkq*i-ODO?8+s*kdk_272;zCo{Tk57N6 z4d~S42f=!Ay^Se5D0|rRyNTx_Y0%TSGO}%oP}B8tYrsqeI}Rd+!#SG$ zB89u^PsuwBSV(r&$Q84spUH|}QKVo2PD%MId13W|lY4#hjEYk$5H0%zW(Kx8T3AelZ{?OJ#NVg#ZH-K&Ri~5Qw+$h>AXi^kER{wjy>Bnd z@2@W3b)3LOiSr%+2Z|OueFLO2?x>bVLB|0biTsqD=|&e6-fbo%$|zcJ+rzTCl090b zvxORIPlf=j%r6uifmQ)iN^rC--;<#huEC#Bdq=gz0aK zl+Gp2=C0*B4v92e9|T{dkN`mb$0og37Zd*};z_3WbM;;8RR> zm^xtbuy6?X>a)46D@?^O6!jCDqC&6nhT3juy%?O+GV5V|Heb8&acP7!|61O-jI^8c z_0cLKnFu~>p^%Jxh|3By!q!d{F$y&Q86(MuQzbkusiBpGRK%^oB-3Q*5tvbqD${U5kE14fxZcB}2%Hmw!V_aFXGz$m7n0KWLej2h86L%6c)T!6Mo%P6Tb|8= zsszMPD^2J?Q%$JDGo@AsZ;F9atJGO6b&kB3mZogwqCQMj-RWlQ3$(?jPh>pxda1VT zsTE`!M}dWpzr+&t&Jh;uVcs5pRen0<=NLbI$URrIkl*qXbId>zpKLrAWdbvGFe<6N z_6)=@Q+D|3q}I9+iDnpAL7$N_^x0BPTZOeOMcTT*fioTSmRJN6Qq0B3or_>>Z>tEF zYx8(qIIcLp)t;{ZiErbzRAv{XUsn-l*a2zcWAfu(>)Eo>^`^c`R!BFR+FYUTJ6vrJ z-C|3|kgY8~3$2arS+Zt^G3Y+2xM*6y!gv4bfHdzh`Ed_BAboyJe%uiUq}Pwhj~92C zqu<54V0S850i$3KL+tEHK4D_UX&Y+BzW)$i6+NxJj*H>82s&pAaa@F+ z>skU`@tAYLTEbC8x}?~d-Py6Acn`%$$P9zN=*&LpidH1Xp+ZkIy_}eRvVMds`=pQ> z3sySQhhP@+r2B;a4j<7O8VH?9Dc5+m6z6*KP8GUGFtVWGz^rLD?CR)Kz)Tjlskr_P z8{*3FwJH>Io6ZHrGY{%_?DWk^*}_jU4t-PLalP+#>C!&7?#Ip$Zs<3>z!iC}VBXnA z^Obf(XaX^8^-Rrh=WI#HM^TJoe(Hz{3{i#1HAZPi=3cYALk395B|09Zu`mvucwtIo zVO*caLMwL-dyLXp7}ck-FwUT@Sa$!M%sY~IdAfn@w#*9DR(KWs1+dlK0 zr~_1u2eclYxGJD0u-xcRf@KgnSamipNJ4MjSW)8_Bm9qSYH|AOBoM~?IN+s|97ELR z2Yp2=-q0v2g~aly4Lxl~fjv-e+EQ|c?aWYk7k!JLmmTnkW(wnHRT7KGK2a&W6V9K0 zfcY@CF7%~bNo$=jlL@+c7b_CI?0OOPA^58os%MnGR6S$#h3Xlrr>bWhAUf%8(r;9+ zR{c8loT!gi&nfyy^_-?(rJf0TgnG`>gVl4i{&}KQGe`fYdM4|C=jnJU#j`m@e_sWq z>u;-Pmj1eWF48;HGe>_>J(ugxs^<#**Xp@ae^Ncy>OWG?V*PvSS*G8wo|XE2>RF@j zR8O0}MLoCb)#|xj-@sFvpiGeIg%Y}NHxo`@th`O)O;cW3?fM+$eL%d^mG?pMj#u7? z#XDSi9}#bq@;)lwAm#nBc+UaIc=kOdUN>IHOVfNx35<_oo9zls3Q>xd*^&;Eq!8xi zq>$vvX`oJW^nEI1b9mD16cv)M|5!o>6tV+8;-rvtZ=QRMQeu)qvb-TRMo4T@$RcmZ zY9k~L@{TuTkr6UFDP*}fWUdinO$u4z4Vj`sHcw2Ny;6nb>%%03*63L~Sv!Yasg+3~ zW$>KXVq8O&{`;jhP>pYcdcii}Ke>QzV3mbNtS)V^%KmeP7h_?dhKJuRLlp~$FlKoT zR@pdMWjkTGGS6RPN3*HzGpt>ZF;M1Zia++3#dRBC8Wrj43)&DWQukH5)5DY{ghxath z$;lBzHVd`p;E01LOgt21`MG%_#wFRQJk6nmVOJ=~^0;|o-6t)XjG)yg*aIEBZ~Hei z#FUvqxv{Z`_F>P^SheNbLxfrvp*R?S2P`YnHE7>#U_Sd90%r9U1O)5Kh9~hjmh&P|3WZT) zo0P5jb`_L?cK42qV`RiUfx3ho*R8+@fS{R4=Xcz>>ykPla$C}G@~S?)UXPJ#UILLh z$E(kFdI49-lO8Nqh!s(IYlK#e4(Tqz!R6?axG!N6JK*$?8xFLA)7V3`9d9w;Bx+7i z>nm2sG=Po{&Nr|)8oSfA>Q$}`W2B5xy@q7M+##Qo|$)HtE@xQFm!Zb^+LiXia$ z8WLTOBD3ZU>VpqOZHh(TJk>Xnat|=b{@j^`FQIm{zJgeVt@Kj$x7*b4fs(iaOMvA7 zs9DFU6b=NnOQ3rMl>#AWfg-&~0)u|2jG;w|er+TMK1CPL#m$fu!Z{Iwo!GKevhBvZ z38Qkr&%lD4N9nIlQKy8D()UT7Y6_@24}? z8MP}c6sLq@Ndm9bwMn*f;B)B>mwVa2RGLWvX-430$&Ol@Tfov6?a#TG8iS46*fz)* z%lTw!1a=h^3D}(%oI&pAd|>$9md12qg#BlOJTF@AUBgql_3K>KtuB$4@MC-eX5)4> z8({uPEuR=W^8~(d zd{O7$MRnb~n(JlPo33}Hp4Eq93^i1aGov|cWhbh*v;ytf&b-s;ni{1apG+s&0$$*0 z%u2a#HbKS|tjVWr0m{sM?X~)^Na2jrALOAXjdu?3ze`OV$+eVRYEKe%Rl=6h$9mJ8 zg-*U8B(ckkQWpEGb64#8oB`gsB?k&{(wmmu;go_oB1@wOuwVGcZ*ESR7<{m^rzztS z{CZgx=0e-(EKJ)RI^T(Uxe<;JFFEs=@euURcI{$@7!meydN0$27JtaYxCk2nQK->k z_bk_;qdGgz4WJx(fQpWmHk_tMYMykmXT%B}| zkC+@+7}o7QL*o=QV%hyKq`){Y+T-`s;6;~!<-QFP78ZnkQsk=7X^<<0zCgX)M2E4_ zv09>GDQrlLu!l5+W3sHldJ$`cYzw9~>=7a>l62hSkn=(aPeI8eExUikOEQ+dnoMb_ z46f2UPx>sllZUsY!nYY=k4Tl6c2fj~9Wr9vn>-olvl5_1NUrD=2yC}K!dR)0UNZn? zG~2C)5O|g?wxQk=VSBFK6zvLwK8yvl5ci~{Rc9eCOT?oHw0bH;F_vE2BU3a+1)E8q zCfk~5msr5Wa}RZe8NE-DKEiw797NmEbO37rAAmku8l{IKxp|o9?ah(!2ZW||2@f)e zCQKJ0d)H&$c{P>S4!+&8FEXX!xdvc*&89bBZQ1v-<$zOO;YE$s^CtNANcY{Tknhc= ziEFIACy%Pm3H$7iEQSMX| zCC@ierj#Pp6Jh%;YFJ90CYdZfGFilP0na2?SbKWtXZ-q|ad{bkFE!bFd( z-dIA>^+g#S=85SN{f7(9QaUpmIwhB!4*vHnw80DU#9?od> zGD_bw)(^|dEYP1WSJR0(=)sdVp;oex zQ!b6JXjM7}7yd23C9iuKe1K!5VT_wI)S!ZXTBjKyAK^&#$L2EzhaPrY>oRq zsqG*QVvN~B)@`^OPF*%dTt?p{rG6v0t7%0Y>aFhf@OqfLTVDpCuAPG zD}wshW@(f}UJ3d@0`xK2x%@KabXr||(6v}jb>8canj$Sg*z7nJ!&bN>T+w?NXf=3! zcPL-RgaH|s_sjUemuEcBb{tsasD2rn`ZDg9+S~R^QEmGrylsCR^U&CA^!L=jg*(H& zP1D~T7;v~Rpki9tqp~?4FBP5*=c>c-ZNf+m|1i9pU@R)a^zGaEz;G7m-y<8;6k#{f zQ}7G6-K(7GA!3MZive?26sz8KiJpI7 z>Cx=PQo3`DknFNn~Y(Ec1bqFM-_z;yu@*Q`F8XrVhDswAt1;OJ~zSS9r?Gyg5}KFrb}S_9bPCSu(MFlsC?ca}&JMOR#Bx z@6x}op2zja725{HY`o`SzEXi^H~& ztgYdex%N3^8Y!6=*&vSR5K@L^k};wFGl`EQ{&Pr-g##lKR~;6?%F~tup`cx+MQ|64|rei3!^eVvQG(Ga2@=87wF|#01lT;tW zmAS*xc!3U1;-=!0qvwJKFG7c3l>VBQ`;;iXU)>LvmUht`^mGo>-yP|F87(U!BdX=T zct&h*%Nmao(m#)2{F=-PB&>Q|ZAi+yFrr-_6W{52w`GCHFCOoDyZj9ioj0|c+FEu8 zPkO}T5k*6wL^d7_i$7$_h)c?g+Zl?y;01TEsT;gB43iDC+kuF8zs8Qatg%d-C|85% z4{##_hu4ZXse4DdU}gwB&b>*e@@MDtLR;RQ2?PsG`;8 z^T^@#_ul7ku8C#{NssoYVaCUhBR&+hF5$znd9aU5*1s+dbl)lv{4)uc1i+8jYDqVtr=zDO^$+OKKtIRdA!EmPjC!*|`(wk>AT5`mw+V99cbj^K z!V%AQxiuwZ~t6%cA;}|OuV_dLPNn5 z-zQf<;{^S1G6jc$Kq%aM{sPvy%j86))&i76=|7i9Y7O68*8Pb`YW*otZLX@eB3V-D zO(a3R6_Y)_$GO_-yd_XC0M{e0NsQ1G@Hw9?q*Iul^umdbGt>HRJVM74HYO~Fd?K(% z#_7a)hh-VesR-IH7P+{w!qq_-`N-6u{!KhVpzSbM znA}|kwGl21p-8fCH%l|8f`SRIJtY>uTQWYjy^W%e-EY`Bn+Tm4>*IFJkW$-*<)n>!2KtK5k|;`F}Ylg z;Np1813z#?4{PT|4ODI8i5)2h@873j*J@#d8HEBuEOyQ~&49f`lSlVi>|B$^ zz!KEQ0|UHn7=VTWij(Cjie(a?QkUqHLJFl)0i4lAfHq>e7{vka`C9`6&A;rJA5i~I z-uj2>zxoUH_kserqn~lflk*2Q!KI8#lJEwx2C9np)oU9rLB~{sMD2+&od9?X^t@)u z5nAUWJg$u6gC7AoYnaDDg(B=0VSP>fI|n`;vM^YH%&=1t6M zo`gU&T2GQh$KlBlBjaLo#7GcFg3~I&NJ)TXdK^|fqH^`Q!)Z*{M%k-b3nKU^-7p5x zi;NgLjfH^HixalNxq1ZY3v{#O!q{+2$zeY+_STUB1auG?iks(A62i^n%7Z>*RXja( zg&K&D($}gpFz!m+JaWDF-rQGF1SAqJ`fpyQ2~N1Ngd2K>{!p}^{Gsl&&nas56*R#K zcZ*Oje460iLKEDI0hzZOnccU`y+11k#X3U;DZo`E>lFp+SIc7-;Z!_a~; zX7@p;EA5C}5&Dj+Jw8M-peyS9aY6C)y?fQREk%GG8I9MOrKPUsAFgQoofpQd6sxWzqki6YJ4x(T0>NS2w2|1B~`7oSI zcgN@!y6YuEsjo1zKpos*GTc?z!7iW)JwDkA!!baVzuj7-7t>MD$ z%M!=h7Prhg0Lz#MsG^WcV5ZFIbqY1q8P|0WpBm;SdF@`%GHZY0SOjxuK1v`eD$KAQ zOX2!znxpRGwmkP@)y@yemvpf{`271V3xc_5@41OQ9L1hnEsl5|ITdDa-toS?mfcqo zA&MiS_B)3-3(XvY&Jn7UGDcfn_FiyBdgl-}X3zlo5;`g)&Ty{;JFwpyUZ7vY7^*qu zyUQ+DgyUFnRM&7hKmxbhJ=*f@(JP3r zd?NBQ|0H>&Avlw!1ZQk_G$ct|j<`4VYfH=R9zLMxtfV#HzHeY_FqRr@&sHtFtRop&8mo5i3MqHU-8z%A*~Ws;MFp-04I&Klxd?z^~vbc_Cl z5p3p5_2*-l$Ayxnz}Kk71CZ!@g)rOc%Uxl)V`m{I-V4}KoBa=9=OTHs?6>VW^MA4Auyx85#gT|nw zqdrEnT?Kuv&z$oFD7J=;ArtW0mCjVe2dGQ9ufMOV@@X3t{dfSxC%mL{2xDyL`hUDD z*Ebz;Oo-=sV%6JLsKhrpl%6Bbb$G(dM2XSlRPgYEo9Q0)xuvO{jcjK)d>i*Zk55^} z$`w(WWcCcO|FHn0iZlYT?Sl}R8H$kq7G9TOM5Rn?P&|A3P?myli}f^mxU!j(KCLyQ zA|}|?&cVp89Ik&sKNvA-+D&_<_N}2i-)ua$<#oQLlFbg+g$1qUGQsu}A95RbxaHTJd9@b-$2wapzsw9sIOzqf9RES32;H$D_uCS}nVUMMYY2 z{~wtz^llEFG{hyc_Cl7Oa0v5gB(zV}KQl{TrFi`dysmD8UcxzH@19^ktAM(5EElRe zX2sai5^L~v1RIJ*TA~t~Q{l}huB?Om@N3O z`e+faay1I1MF?1^R5c--w_Bv7yf9)S0Gt5_woqQ+a{tX@RbC+UWtoK54WhdWi^~P|b4msn<~l z7xZq+zAz(XI3cW`1u&j)qx%%CN8Ps9;?eQxeonFMdO<8jpHq77P$7pzh5n1+{sY#T ztvaYW<-Ru29%VbZi%X_(?!38rRo60FCI_o``tD=;P%CgEv}^5`pl+TcKhWVS5mI|! zBy&4};}%ef0k#_agd2@Y1dhw~iJ#2@b~4ll*Z1b?=LEpwN(N_Vq^qvi@$n!egWV<& z%RU6qBnCXO{uDPtpdby+eV(By>SEz)*SArA8&`6KM7W}v^+8~#!7c^s`K!gFCj725 zQ|L?IJ^ozCqo5*!@{uQr(BFQLbo%I56)+fHF-#wex6nw!%~}*$Ih9pOXUj>s>pUdJ z9-TiB^ORo8*)NC{?b1CD^{7Y78TI&qoHM0+8ihzuy5|WI=`7vzOEL2J$o0sehN#)71^Z;r*F-4B&A*Gaj0M z*Ap_8xS9B%#<91lnTVvfA+zQ`oaHK~l$d@8po~?)Rjv(Hr5}O`2wbY6x2ybtOO=M$ z*yPU2$vl@SoTHDQ%y%tB7*J%GZ$x@@WJ z#?s|Jk$Xmj)ZW2U;s;ALXlhVAkZ$$nlbSW?#{J<9|KV)#6oMQt$j^nu8+pW*Et_zC z)U&C-yJMeK_|_*!oNf-YT??oB1US_l9YJ&dRvS9^FI$3xKbqTtuMJ=O+*9^QM^}*j z=*%N?PuT`N$A|XR2A_Y&)q1B~uU_>`is7^Q{`UMTSo<;VdEBeG&6X!n)JAva9Ox~+&Wwh?rz+7aQ}wekL$#Ji2El_ zLv6=Zxa)8?;!<#naVv3^xUIN*aNoi`ihCN@jys7vgS&)__-(Le#U*NYqcd+NZ=#odNmi5vJ= z^+2k&=F?PdQpkMGg!JKEX#Dj3&HmPW?V4|=`d<5lo?MTLb+`CGU!HSvb2<3@~##5|IwOB$Xr|H1aa(~67p z`ejtC_^;uaf}7|KZ@@pz>jzV(%@IGluW(wXc>Qp{XjxvrRIuFVr_hx?KWPI0q#fd< zOe;?67r*Z*{uFPR_#1Fir}zV%g!#gy9X`MEc+>Lm|5@LkuPw)I#XXGc#HDNif$;Hs zEv|dMHX3KeO~g&ZCE(`Zl5r`xsK_*JBJTH5Y1%Pd&hRvC1#Ts7Ev^_>iA(2lpMkUC z-;Qg*W%Ay{^AX&mxTkPW;}#IUpXWi`OSo5Y$8aZcr*Yl5v$$SdcwCy6Mfw4MZo_5aUyfUgyPfw%xNKZ8@0Ga4_?O^jT)$Gg6@HV9s`b^yB{|jQRko5^ zM(cJhLtCaT=68`cQ@c@{p{3(nq{cD5q<`AX%DT+jk`j$)Ziy|wsM4w0y+UoTsb#q< zR>Hp-TCrBiKju!b$s7_I7CtB`$Wf!mjJ?J>Zv2FalO|s~ zW$JbD)27e3KH-KNXU>{^)6H`d=O!i3yXDrD)cI-Y8JP>R7T$LIqU^;>a-h;J&&w}Z zQFzClD_5;vv$km6`r?w(4Q1sO8!I>GEld~c$T)R0Tp&$p5 zPpeg?uok|yY%PD9wzXy}f8nIi!nE*k9^w3xSM?X#&~R_Vt_CVF-P>^QJq@uTA=J>= z*a-bGA)z6sp^4N}uAP%p&_I4F$;oLVqjgF{Qv)29L)35wmdzGzx zQ;D^twzj&~ije?Fo68q1$;~xFqVokI9>aHwbS6fuO&05a< ztgS6sZ>!ukBO<4=q^PdMy1u%~Rdz#it;*ZQC(fhrgfFuti|@)@~REi zDvH`H%^+2IRk^LasIvTSReZ#9Nnc)7EFCT(r*)lu1Mk*t)%M!Jw$89Fv{^~Isk+W) ztt{VILg~_-+LAhZrOjGhYF%GcSt*q#H}_lvkD%Tc=#VzOr_zwbWja}F8#hy`dw9RvzAm<+c%V1>uQSD(_fOaSc@wA z4OrlK-7sLh{CzJmKHjqB#(3MxXmoYe`jQ!6e>|ifXUeCngVYNvY{q4FTQS$ZmySk>NO4dPLHRDiRvblVH zKw9q@m29nH-7sThWcpU78s{6-_}Hn@n`RT!uH05uTf$0Y7Hk}t{>#V3o2j_SRz!27 zjZYr1EY+f~ExF5HQYVd(S-#A>!SDe&bvBi=pqO;>y=A)FtL$|$fLn_GH!gX`K0%G) z73Y0P6<@8oYEwy-jXp?PHHKGATU5KjE(z;=-!wt3r&2aCn=IHGIZORJkrW}@TZx*% znwDL@zP7rqy3}UPFIT-xi;wq@$0B3XV|&sfSPwI1%+Qi+YO6QYvYS;Eu_N27Hda+{ zsk()4=pDig*`vorL~w9Qvbk3B&S0;QGG?qRvZ?3#O~vY2vb98>^2af8P*_;x4_@Dc z0$a)7{|NB6;P;(3FmwC;qU+rk{y=~qjcxvLXs&+$cLV%JE&uQST7i1||C<6FVf^bK ze&x3;Ad0X0w_CgXU;p&Gw7!3Ws`xtp0!z3@>-!fB-2Qd``SM%;-~I6Zo?rf5%l=Q# zfS_Iee?edK&-Vr8c~4N^zu=&+_s^IA^4|Y{4f(HDfYcPrKVOaV*5{AWzGjEL$p)tX zdcEyS6{Gd}4POwq0!?e!b&q3rzkg=mv-_X>!yli2;lPUrTMxCp^zZE* zhmXAc%B!#a>GjT|Z@hWzKi+!#_&e{OIQi%I-aqxhhkrT!*N^_Dcm3Uc=089FNB1ZH z{PgT+=g#+B`21oow&wnqFH&EAQTkdJssHZ&|998_zdZi`)kXHpzq|igS#vjS znkzSbO)KVanl+n0t9Z8Yr)6hfUtD}W9{zH&H7ykf{(*a#LQ`mqt2g`oOStVwaBfKr zS4ty6s(pjK4(R7Ld!;JlHeg*TE48M!T<{3lMYZe8cxINYtF?2{l(^f9s+5;{qoq#Rro`L;^Suv@KXCt7$AA32|04eR{_&jy>i^<} z{_z9rAMxT>$1gb8KVHO}0{e5QwSWA;@>d?}A3v~tn_lW457De&`LDDa@lxma@PFAj zj_K%M&cJq@I@~{gU^{;Ja{u^&`9qHNj}L6;kKanw_TvJ_WnlTYAMYPOu>AS&_4RFO zfdBY_@i3q2OBWO1A6VZH|JuL4Jptt;ccp46xUXvONO%AEf#3b-|Jgr&V1NGfk1vmZ z`JbuUN4UWH2j(9eG{0Yb;CM^K8FT;mf%#X4e|h}4G4r+AIQjFAwt?qSRlR(D@$dd-sLQ^BWrK{~j9ZJ5Y_l z+ojcl`Ubg~3-keUU(@~(yr`sTbIE1l9|d`#5rwRRgCjT#jHDtRY2Ps|vsLC*Z2_>+ zexSk)lF^Tqe_4s~_6I2;y%tbr`L>#pRPCg^XF_esDYBI<1~bNHwj`La+-)EkH!X*H zky@7tx?FtPh@ec0Ppho1E74-`8^Oz~`w}CP77)Hju;9LA+8C8Pvm8Q@gi%wq)Sgzo zX;V>E@uKo7g3>fqdKLkdCE9fJavxUHPtW?P%43I=QRM^Xv|G%1RiMhrQk=1MJ-{oY zNEM0@H`EMC?MtJN1~05jFM+nOp`*l>SB9kiP4S%O|#0FNE=d%i%ABw zJTdvWseRZ9*Ypj@)f7C@(HaD=Vs9 zUY#wt_4(VVc%;eiUtaA?p#3En>yrk4<-2|z} zJBYFmYuZoL+Q==xn^|&u#46 z1KE_C+7cL@!Cn1Ch*71|-#?KJ8Uro|&>7TAz zU1zaSp$NG+jUKC(S}V$R!thcq^6`6G@EeMuE|m1Q>F^@ zt?=d*Sz(tIw!KR5E44-H&bz!?Sds~|Rc~Y*CP^FR?rhy6NEoB71VJMvKjTykKCHa7 zyrfo*lZ0D+OoEyq_Uh_{UDznBP8cDQ z)+zk(p{82v3^Z$)<)2BKd%twy?6YWCKi<6N(j@M;gROrOwJ? zzAc!-DU?>XvAm`R9?f-v?3=0;)os#BAW!qq;6JOW zYsqHh4TNtLCelfDYNALRtqkE@a#QVWRVrYxVyf5 z6Cb6xd_%bn?sf0nkTJMI9xDq?<}h=4Ly36qykQ!(u9Eqt8e~-AotJ?@{&^{tss*jM zNo7?_UF7eZ!@NtlwXUq%URf;dTqkVl#Mu%Kn>`scns)H@#IVXz(V{TRZQTnRGU>Q97h4B`pf0s;d2oNhOy8cO=&4HjMR zpk|k|>e)iZPa7|M_~WNi5qq1jU3?wt8b3vSfowHCTVTJoZYtj@xn%86U~eh5PLRF0 zwg|i|o1>aGBH$pHJ>g-IaW(P@in)d@ifmt+f$wav`mZaoZIStGd>H>+P!+QKR!)Zn z9gIBbjZJG^2?$y#n>9(ZN*P#r2^UY)}!`ZkyVg&(>I)I)QvV2sZsJ}R$=|-vf7PUk;U44`-Ztz-^OT+MLE(F zMU|C`zec`-D);jFr4sumqx9|i>r}bMHXs{+mvM` zZi3MT?_S1CnN-I~%~PG%Q~0$~jCByHC)LemsSGmq9c!9_&lhQ z%>=jk&Wa0d; zu2JWqb)Iz+Olj-N5eD=6)n;2>C&!E$Pa~pk8Y{1u#V4r}W>xpkBS)*sP7d~lno2}& zij@S1tq5^}FPUc|;UV#y4M;pee?w}@m=HCPa%6Eyf04D}5R~(<#7KANq~cZ7lP7dL zL%IS8tM3adct`|<)Z$>g{X$mKAY<**r%CjPS+QRA-&g--;nJ^3#cCWbUnc>u3~RNd zs_qK#PTzJV`<;>OvhHfCe(A8ZiayiZnmec8uxbV#>%4gqq#1(~5dd?s?Go%&rYy+A$ADJ58 zQmosC?AlfXLP1W8T^wycMF!wh2L+}Yz-gKvxItW|+L|C~@&xLUUCUaxZPU8yO4Ud& z_$Kjb3T_t(wj!&n4dmn4qUVZ{hVu+^8nf|hZ1lQ@H0zMBd%upE$aY>=2FyFtMM*5 z0^e);0!lW^6p+Zut;|A+s4ksr%~j*v2dn$S^9%_Zqhk5c0b}S(<2w>m5aOq)2FMJh$x}aKO4PQhwd6VmglM!s=^p3m4ZI{?j0;u z=>Nu;3bE0@lX&|ib2$)Zl=gj{bTYxG_%_9gNlA7kWkv3;L{iILhD%D6>pHTVhdlJb zMbpG>lctuP){iOBv=1#%6zD@x6s>_2s4En$ADki&eQ-Zew183c_doaEnc3A+qDVGE z4A0{1nYnYnKh8bpo^$W4DN?#wN$gCGwq178Oa9m~Hvh39vaxz1DY-LUB-0_+&!p@P zRIs#XZ77(E_u%QNu%WE3s20y=^3W@JZWgvf9?(ZnA*JvaG$0w;?W)wJA+`>x{2= zGe-;e2nRaAOv`?~qRdo$^^hLXO|^1YJ}QX+VPH^oJ}4bcL&@c%f%j}6j~ZA;sU1@S zqh~~b(A7|GFH1Jc18MziAnwrP#YRSx$);mVsflW&@wTE1?R7X(ms2a9{T+xk4Rjx^ zFcENJ!VX_mDzB-kjiWGb$m9+PAdWV>C%jF3p}nVcDdx`@DT#{h+#0iGO)mb%P5iOq z*X2xFVs-^A!}8q_y8}BRhQTrRRx&Y9>SKB!jFa!eQjO}}`PGJ9M^&46sJmaQ|JdqH zmAwgA)59-oyo5OS*I8(46|I-uELYWXxx`lSu&tE6pXANuzrbo8MVVhgCg``0qv%u7 z7+&1g8rtgv+#Gsb|BgrR2aErZXn-KCsWoa2D;`||H1x=Hdp%dgMvgkuW^);N`RuR2 zO(Zo*n2=S)Iaw|t_`srak9o?>O%;SPUr}eV`hwR{j@7zioViu`j>Emfh;I~jk8(DYFSv~?&dyY3v~XwTx;kHk za1@IYwb+u6h(0&7r2YPj^uQ7Y$HRe6ts2GsO-(sqVs>eM=4zGA|HNZ!R}#Cm3!hrI z%_z;!ftS?UBzA{4ZJE$g(tcufvd~J7zMQ5(>nk@|5n#Xt*3s2V804D8qkigM{h(}H z>PwJ!$yzf=M6&2iNF3E?_Te+@weic83#(tuP7Tu46}iXqp_x0n^9Ls-c2bLO(DuAlAN@QtM)>re7#)B zF^*$Ze9T&XV{8s_Kwexa&<6F8_$U-z?H$OLqaf!5YOUEoMy^U2; zac2lLjM~!b;*y$-Rz`f&5eTV2&3?XrL`&}sM(+Yk-ye*;LL-&1jN*wNqznC_jAOQ5 z7>2*=ch=|QuB78@vbBn#yq;`i=PG!EY`8Y>rOnG-VNJ4C8f{MGVd8C`tCOCT8J>v_ zCS94Pt~m3=57RI|kEF^MF;~swEMdHZRNwKL4pmMjR@Yyk)n55wE5X()8rDx52f7j2 zhWANI;zFG$mgrZgOUG;6+f(2-TQc?uo9*hh8h>H6=Jx@Ha3s94%+f%bipWh@MIt$J zTQ*3P)NdLMe6MO(hjG}tgDQdpLfo{i{@+ku4iza;+s4FY3W`N3&N*{N&n26CQMQFCj-P2?5kI2j2rAbReGWv}9X&K|lXsp! zwPw-mRlk84P`kmKgp%fTQj=8yyfu2VLyxza`h_kQ3YPRu@6btT=-V;}z{IOdDj-WH zl&&?8a}|+%wtN%CiPQ2S3+%H3C_~0_CGmUhZlLXnd!+{JNpJTr@`*@%AwT)V=*dgU z!UZtpAN5`{QQc$Yc^$Z&5acwsPwhW+prik>fn2mtd;YbrJPHgBED-0>EX_%Tp#5B1 zlV*Egf8vMn*~$yMi%chhSYiuqL@6b;&Z^70Q|V4Y+^8RS(nGdA;SyVfwMouS*=iZW zY0afqzzBly)f%+$hANdV$0=jUDE6*+LC(#rwdv=^VC&1#-)qi;APM zjI2y$g6~5U?{xDmvy3da$LX|InFwvz9*7s%1@AR>nq~_|D3SV&KB?0zt`_GwxT-!k zi}VBCG3A~`5S@1V=Ed?NyF%jqI>QO$0Y(OEtRiTBA{`Qi@o~VE9cwV={g%jh$*?W0#@B7eny8!*%Ie#$YcnHtl@8?GH1L#Q6lPc`w zw7j}>rY2oFVDtO=_M+kGsS^|2^yfYH?tvG?5XV+AoH35m&9G*Ylw+KO5ysDsMN`w0 z(RuxG*j`i zI!;zD6sD$!^MzBVr>0K~Bl1cAI0g(0^N0CQbOx9PP6EL_jqhwjeDjBUkK)bjnq=E# z@nJ<2pF7xd@D~pD#QS?V5bRKQH-eX}i(0v>JQ&(6=h$gjR--Y;=Vn*uq1#s0`@AzA zNqO+`B+ucLuvl5(ee<*GNpMytIWTCrZMc`MmD~c3FXZ}Z-14u4CDDnk z#tjza%ry9u(j&Y2sauLTz-E6pP%L<>pp8o7)+)c_>`$_y8_2;RU^3Sc}?Eq`_ATd#N zS;uVQAg$XmS)S)a7E7(t5i|AHZ5~_Czjp6jYI$F>$Wm&SNQ!}aXFw|P;f1Qw#bL~W4Q^wRIdxl zvAc$AQcgh;RJ-D)vP><6q-y?(XS~uholB9_{Lufy>n=VTTvyvB{XP*Lu2oddwpe zN3YsvjE+2Jf5UcX{}Z}B!}+G@$X0Yeow+}0=81lshCDos^m8L|zmxmmebqo*Y@%;) zqzTks`!Ju8Ju7PkI#deDtDeqF+ZRvHWrF{+)jE=P-HtktaUpt6iMVD&QDnWtPl7<^=kmuXsO$Sbf~@{q;2eQ*$V2SrIVp zK26rD%bMy!`v+9lTM2tU?lHcIn=R3T}J#@!EO3y0`>?V2C*p%vk&+m4{GnLJc&yG*HVEwo{ z(8+Rm)X{1o5DP8dJwIV$wz_HWk4fniR_^ya=2Dk2sj-4?t z=sEnvU|+d!u5aO5wXfE9{ki;b-$>unV}0WjXQukj_MPiHudNH|I=ZyaAudS(ybJoP zMxMh>n5Msmw=v$hdk^+2wx(%&9Ea!YntV$!ozwYt?WH^?1jf(1s^f&_m7KrA`H}I% zJ=nwq>~73BrjWgVruV24ECd|4Y+WU#a`3}rmM_MH=#b0>4@M_ zF8LS`@f1c!#=X5z)^-Sc@WW^}Sm{Q+am4M9D-?#0PkX<-@0DwzFg-Gsgl}ArZb#wN z#1n*PYv0U!d-gUQk*&P%cZo}xqJB7FFD(H zGZI~BlT@nreSBCWzPKmW{d)eSf6#lH(R2c}*@e_^*6x8!c;%$bNuyhjf<$p*6fbUGB& zEp{b~6xx!*lsruMAOMd-Ea?|^yFH9kpeNG?bdvDDM?FlotCL-BLBHc*1yK#jNg;2bjnnux$Dzf7<-^ppc{oZ zt=_p(s{Yr@r}itM19V%J@Hc{{{T`}kXVQqj^M3oMJoAXXkbSMDY0r7OlFP8jot50<)ax8rS@S}jOfy@J8akVe-THY$bEXzVlM5Ofl;(34Sf#+j!pb_BjoJnw*$Pn@Cr>V9Pt`)Xz><)LwqFkVg61>oBio? zrh`q5&*O&L-;)3v|9kiD-Mu&Z*lzy0`?#?C?E>#?@eA*TIf#1ij-Le{1vdOsIECKf z!f!ZSNFn5a;%;Pwzn}fw)d4ot{yxa6?`rl`2Cu~^C{f`lWlHwUkEcXf3mFI4MTTFS4?;-ZBaHeM0A!Y?dUBPrH( zZ~IUE{bK#vOm$&C4*nRvYvUBiYI{_Mfg?bG2syY?{{W!4G!-qT;Z}}+jVc;i$ z9|Il%J_7s%AkIfvZo-^-*M{QgD&w`aU)o$@Df@47ahmvG%$JY~1SJ9Y$DuqvqO zQhZr2O|LCI`T5}bpawQt1LOZP_@{sPi9a6>{?)%^cdScp!naKN3qBqU|AYS?l0t>P z8*qpzJO{j<`+Dym)PIO4;1K>{;`qbA@ri8uErx&g^)}tZAg^<|1SIjdzP2d?4>_9k zKbGk-w*JQ&A9BXGG5K{m0M0Iv*6lCXZOp{OkLpLfD_s6G>w-IX?S37`?ea@EUwX{f z=v3q^j|BL_YoCZDq&IbJ-_x}GF|9^Y? zo!g&`zJL1%w=b+8{|m2Lh%+#nXYPFdP8=2PEZph2+yD38`lq-5hx2A%a5?+yZ@O>5 z<-O1UFPi@4eIW&x&{m(7&*r@Lel|_xyWbd!CTTJ)0 z#Z*+lbPJOca|C-x`Yk6L=T+SKtT0JHXq(Tfm#ZcY!|xz74zq zd>wcN_zLhc@N2-AfiD5KfaifVU)WO)|20eltsBj9_$zXN;mcL*2*Dn1O(b}4+g*ErKRFvE8+gZl?~durhS0Q^%e A>Hq)$ diff --git a/src/org/satochip/applet/sha512_fast.javap b/src/org/satochip/applet/sha512_fast.javap deleted file mode 100644 index 7cca15a..0000000 --- a/src/org/satochip/applet/sha512_fast.javap +++ /dev/null @@ -1,634 +0,0 @@ -package org.satochip.applet; - -import javacard.framework.JCSystem; -import javacard.framework.Util; - -// build the java file using a C preprocessor such as mcpp (http://mcpp.sourceforge.net/) -// build command: mcpp Sha512.javap Sha512.java -P - -#define add_carry(x, offsetx, y, offsety)\ - akku = 0;\ - posy = (short)((offsety)+3);\ - posx = (short)((offsetx)+3);\ - addx=x[posx]; addy=y[posy];\ - x[posx] = (short)(addx+addy+akku);\ - akku= (short)(( ((addx&addy)|((addx|addy) & ~x[posx])) >>15)&1);\ - posy--; posx--;\ - addx=x[posx]; addy=y[posy];\ - x[posx] = (short)(addx+addy+akku);\ - akku= (short)(( ((addx&addy)|((addx|addy) & ~x[posx])) >>15)&1);\ - posy--; posx--;\ - addx=x[posx]; addy=y[posy];\ - x[posx] = (short)(addx+addy+akku);\ - akku= (short)(( ((addx&addy)|((addx|addy) & ~x[posx])) >>15)&1);\ - posy--; posx--;\ - addx=x[posx]; addy=y[posy];\ - x[posx] = (short)(addx+addy+akku) - -// add shorts and shorts back to first shorts -#define add_carry_fast(x, y, tmp)\ - tmp##0= x##3;\ - x##3 = (short)((x##3)+(y##3));\ - tmp##2= (short)(( ((tmp##0&(y##3))|((tmp##0|(y##3)) & ~(x##3))) >>15)&1);\ - tmp##0=x##2;\ - x##2 = (short)((x##2)+(y##2)+tmp##2);\ - tmp##2= (short)(( ((tmp##0&(y##2))|((tmp##0|(y##2)) & ~(x##2))) >>15)&1);\ - tmp##0=x##1;\ - x##1 = (short)((x##1)+(y##1)+tmp##2);\ - tmp##2= (short)(( ((tmp##0&(y##1))|((tmp##0|(y##1)) & ~(x##1))) >>15)&1);\ - x##0 = (short)((x##0)+(y##0)+tmp##2) - -// add short-array and short-array to shorts -#define add_carry_fast2(x, offsetx, y, offsety, dst, tmp)\ - tmp##0=x[(short)((offsetx)+3)];\ - tmp##1=y[(short)((offsety)+3)];\ - dst##3= (short)(tmp##0+tmp##1);\ - tmp##2= (short)(( ((tmp##0&tmp##1)|((tmp##0|tmp##1) & ~dst##3)) >>15)&1);\ - tmp##0=x[(short)((offsetx)+2)];\ - tmp##1=y[(short)((offsety)+2)];\ - dst##2= (short)(tmp##0+tmp##1+tmp##2);\ - tmp##2= (short)(( ((tmp##0&tmp##1)|((tmp##0|tmp##1) & ~dst##2)) >>15)&1);\ - tmp##0=x[(short)((offsetx)+1)];\ - tmp##1=y[(short)((offsety)+1)];\ - dst##1= (short)(tmp##0+tmp##1+tmp##2);\ - tmp##2= (short)(( ((tmp##0&tmp##1)|((tmp##0|tmp##1) & ~dst##1)) >>15)&1);\ - dst##0= (short)(x[(offsetx)]+y[(offsety)]+tmp##2);\ - -// add short-array and shorts back to short-array -#define add_carry_fast3(x, offsetx, y, tmp)\ - tmp##0=(short)((offsetx)+3);\ - tmp##1=x[tmp##0];\ - x[tmp##0] = (short)(tmp##1+y##3);\ - tmp##2= (short)(( ((tmp##1&y##3)|((tmp##1|y##3) & ~x[tmp##0])) >>15)&1);\ - tmp##0=(short)((offsetx)+2);\ - tmp##1=x[tmp##0];\ - x[tmp##0] = (short)(tmp##1+y##2+tmp##2);\ - tmp##2= (short)(( ((tmp##1&y##2)|((tmp##1|y##2) & ~x[tmp##0])) >>15)&1);\ - tmp##0=(short)((offsetx)+1);\ - tmp##1=x[tmp##0];\ - x[tmp##0] = (short)(tmp##1+y##1+tmp##2);\ - tmp##2= (short)(( ((tmp##1&y##1)|((tmp##1|y##1) & ~x[tmp##0])) >>15)&1);\ - tmp##0=(short)(offsetx);\ - tmp##1=x[tmp##0];\ - x[tmp##0] = (short)(tmp##1+y##0+tmp##2);\ - -#define Ch_fast(x, xOff, y, yOff, z, zOff, dst, tmp)\ - tmp##0= x[xOff];\ - dst##0= (short) ((tmp##0 & y[yOff]) ^ ((~tmp##0) & z[zOff]));\ - tmp##0= x[(short)(xOff+1)];\ - dst##1= (short) ((tmp##0 & y[(short)(yOff+1)]) ^ ((~tmp##0) & z[(short)(zOff+1)]));\ - tmp##0= x[(short)(xOff+2)];\ - dst##2= (short) ((tmp##0 & y[(short)(yOff+2)]) ^ ((~tmp##0) & z[(short)(zOff+2)]));\ - tmp##0= x[(short)(xOff+3)];\ - dst##3= (short) ((tmp##0 & y[(short)(yOff+3)]) ^ ((~tmp##0) & z[(short)(zOff+3)])); - -#define Maj_fast(x, xOff, y, yOff, z, zOff, dst, tmp)\ - tmp##0=x[xOff];\ - tmp##1=y[yOff];\ - tmp##2=z[zOff];\ - dst##0= (short) ((tmp##0 & tmp##1) ^ (tmp##0 & tmp##2) ^ (tmp##1 & tmp##2));\ - tmp##0=x[(short)(xOff+1)];\ - tmp##1=y[(short)(yOff+1)];\ - tmp##2=z[(short)(zOff+1)];\ - dst##1= (short) ((tmp##0 & tmp##1) ^ (tmp##0 & tmp##2) ^ (tmp##1 & tmp##2));\ - tmp##0=x[(short)(xOff+2)];\ - tmp##1=y[(short)(yOff+2)];\ - tmp##2=z[(short)(zOff+2)];\ - dst##2= (short) ((tmp##0 & tmp##1) ^ (tmp##0 & tmp##2) ^ (tmp##1 & tmp##2));\ - tmp##0=x[(short)(xOff+3)];\ - tmp##1=y[(short)(yOff+3)];\ - tmp##2=z[(short)(zOff+3)];\ - dst##3= (short) ((tmp##0 & tmp##1) ^ (tmp##0 & tmp##2) ^ (tmp##1 & tmp##2));\ - -/* - Based on https://github.com/LedgerHQ/ledger-javacard/blob/master/src-preprocessed/com/ledger/wallet/SHA512.javap - Unrolled versions of - - #define r0(h,l,n) \ - (short) \ - ( (h>>>n) & ~(((short)0xFFFF<<(16-n)) ) | \ - ((short)(l<<(16-n))) ) -*/ - #define r0_6(h,l) \ - ( (h>>>6) & (short)1023 | \ - ((short)(l<<(10))) ) - -#define r0_7(h,l) \ - ( (h>>>7) & (short)511 | \ - ((short)(l<<(9))) ) - -#define r0_3(h,l) \ - ( (h>>>3) & (short)8191 | \ - ((short)(l<<(13))) ) - -#define r0_13(h,l) \ - ( (h>>>13) & (short)7 | \ - ((short)(l<<(3))) ) - -#define r0_1(h,l) \ - ( (h>>>1) & (short)32767 | \ - ((short)(l<<(15))) ) - -#define r0_8(h,l) \ - ( (h>>>8) & (short)255 | \ - ((short)(l<<(8))) ) - -#define r0_14(h,l) \ - ( (h>>>14) & (short)3 | \ - ((short)(l<<(2))) ) - -#define r0_2(h,l) \ - ( (h>>>2) & (short)16383 | \ - ((short)(l<<(14))) ) - -#define r0_9(h,l) \ - ( (h>>>9) & (short)127 | \ - ((short)(l<<(7))) ) - -#define r0_12(h,l) \ - ( (h>>>12) & (short)15 | \ - ((short)(l<<(4))) ) - -/* - Based on https://github.com/LedgerHQ/ledger-javacard/blob/master/src-preprocessed/com/ledger/wallet/SHA512.javap - Unrolled versions of - - #define s0(h,n) \ - (short) \ - ((h>>>n) & ~(((short)0xFFFF<<(16-n)) )) -*/ - -#define s0_6(h) \ - ((h>>>6) & (short)1023) - -#define s0_7(h) \ - ((h>>>7) & (short)511) - -#define E0_fast(x, xOff, dst, tmp)\ - tmp##0= x[xOff];\ - tmp##1= x[(short)(xOff+1)];\ - tmp##2= x[(short)(xOff+2)];\ - tmp##3= x[(short)(xOff+3)];\ - dst##0= (short) (r0_12(tmp##3,tmp##2) ^ r0_2(tmp##2,tmp##1) ^ r0_7(tmp##2,tmp##1));\ - dst##1= (short) (r0_12(tmp##0,tmp##3) ^ r0_2(tmp##3,tmp##2) ^ r0_7(tmp##3,tmp##2));\ - dst##2= (short) (r0_12(tmp##1,tmp##0) ^ r0_2(tmp##0,tmp##3) ^ r0_7(tmp##0,tmp##3));\ - dst##3= (short) (r0_12(tmp##2,tmp##1) ^ r0_2(tmp##1,tmp##0) ^ r0_7(tmp##1,tmp##0)) - -#define E1_fast(x, xOff, dst, tmp)\ - tmp##0= x[xOff];\ - tmp##1= x[(short)(xOff+1)];\ - tmp##2= x[(short)(xOff+2)];\ - tmp##3= x[(short)(xOff+3)];\ - dst##0= (short) (r0_14(tmp##0,tmp##3) ^ r0_2(tmp##3,tmp##2) ^ r0_9(tmp##2,tmp##1));\ - dst##1= (short) (r0_14(tmp##1,tmp##0) ^ r0_2(tmp##0,tmp##3) ^ r0_9(tmp##3,tmp##2));\ - dst##2= (short) (r0_14(tmp##2,tmp##1) ^ r0_2(tmp##1,tmp##0) ^ r0_9(tmp##0,tmp##3));\ - dst##3= (short) (r0_14(tmp##3,tmp##2) ^ r0_2(tmp##2,tmp##1) ^ r0_9(tmp##1,tmp##0)) - -#define Sig0_fast(x, xOff, dst, tmp)\ - tmp##0= x[xOff];\ - tmp##1= x[(short)(xOff+1)];\ - tmp##2= x[(short)(xOff+2)];\ - tmp##3= x[(short)(xOff+3)];\ - dst##0= (short) (r0_1(tmp##0,tmp##3) ^ r0_8(tmp##0,tmp##3) ^ s0_7(tmp##0));\ - dst##1= (short) (r0_1(tmp##1,tmp##0) ^ r0_8(tmp##1,tmp##0) ^ r0_7(tmp##1,tmp##0));\ - dst##2= (short) (r0_1(tmp##2,tmp##1) ^ r0_8(tmp##2,tmp##1) ^ r0_7(tmp##2,tmp##1));\ - dst##3= (short) (r0_1(tmp##3,tmp##2) ^ r0_8(tmp##3,tmp##2) ^ r0_7(tmp##3,tmp##2)) - -#define Sig1_fast(x, xOff, dst, tmp)\ - tmp##0= x[xOff];\ - tmp##1= x[(short)(xOff+1)];\ - tmp##2= x[(short)(xOff+2)];\ - tmp##3= x[(short)(xOff+3)];\ - dst##0= (short) (r0_3(tmp##3,tmp##2) ^ r0_13(tmp##1,tmp##0) ^ s0_6(tmp##0));\ - dst##1= (short) (r0_3(tmp##0,tmp##3) ^ r0_13(tmp##2,tmp##1) ^ r0_6(tmp##1,tmp##0));\ - dst##2= (short) (r0_3(tmp##1,tmp##0) ^ r0_13(tmp##3,tmp##2) ^ r0_6(tmp##2,tmp##1));\ - dst##3= (short) (r0_3(tmp##2,tmp##1) ^ r0_13(tmp##0,tmp##3) ^ r0_6(tmp##3,tmp##2)) - -#define getK_fast(round, dst)\ - dst##0=K_SHORT[(short)(4*(round))];\ - dst##1=K_SHORT[(short)(4*(round)+1)];\ - dst##2=K_SHORT[(short)(4*(round)+2)];\ - dst##3=K_SHORT[(short)(4*(round)+3)] - -#define InitW(msgBlock, msgOff)\ - for (short dstOff=0; dstOff<64; dstOff++){\ - w_short[dstOff]= Util.getShort(msgBlock, (short)((msgOff)+2*dstOff));\ - } - -#define updateW_fast(w, wOff, dstcpy, dstnew, tmp)\ - off1=(short)(((short)(wOff+56))%64);\ - off2=(short)(((short)(wOff+36))%64);\ - off3=(short)(((short)(wOff+4))%64);\ - MessageSchedule_fast(w, off1, w, off2, w, off3, w, wOff, dstcpy, dstnew, tmp);\ - dstcpy##0= w[wOff];\ - dstcpy##1= w[(short)(wOff+1)];\ - dstcpy##2= w[(short)(wOff+2)];\ - dstcpy##3= w[(short)(wOff+3)];\ - w[wOff]= dstnew##0;\ - w[(short)(wOff+1)]= dstnew##1;\ - w[(short)(wOff+2)]= dstnew##2;\ - w[(short)(wOff+3)]= dstnew##3;\ - wOff=(short)(((short)(wOff+4))%64) - -// dstcpy is used as temporary variable and dstnew stores updated messageschedule value -// values in w, x, y,z are left unchanged here -#define MessageSchedule_fast(w, wOff, x, xOff, y, yOff, z, zOff, dstcpy, dstnew, tmp)\ - Sig1_fast(w, wOff, dstnew, tmp);\ - dstcpy##0= x[xOff]; dstcpy##1= x[(short)(xOff+1)]; dstcpy##2=x[(short)(xOff+2)]; dstcpy##3=x[(short)(xOff+3)];\ - add_carry_fast(dstnew, dstcpy, tmp);\ - Sig0_fast(y, yOff, dstcpy, tmp);\ - add_carry_fast(dstnew, dstcpy, tmp);\ - dstcpy##0= z[zOff]; dstcpy##1= z[(short)(zOff+1)]; dstcpy##2=z[(short)(zOff+2)]; dstcpy##3=z[(short)(zOff+3)];\ - add_carry_fast(dstnew, dstcpy, tmp) - -public class Sha512 { - - public static final short[] H_INIT_SHORT={ - (short) 0x6a09, (short) 0xe667, (short) 0xf3bc, (short) 0xc908, - (short) 0xbb67, (short) 0xae85, (short) 0x84ca, (short) 0xa73b, - (short) 0x3c6e, (short) 0xf372, (short) 0xfe94, (short) 0xf82b, - (short) 0xa54f, (short) 0xf53a, (short) 0x5f1d, (short) 0x36f1, - (short) 0x510e, (short) 0x527f, (short) 0xade6, (short) 0x82d1, - (short) 0x9b05, (short) 0x688c, (short) 0x2b3e, (short) 0x6c1f, - (short) 0x1f83, (short) 0xd9ab, (short) 0xfb41, (short) 0xbd6b, - (short) 0x5be0, (short) 0xcd19, (short) 0x137e, (short) 0x2179 - }; - - public static final short[] K_SHORT={ - (short) 0x428a,(short) 0x2f98,(short) 0xd728,(short) 0xae22, - (short) 0x7137,(short) 0x4491,(short) 0x23ef,(short) 0x65cd, - (short) 0xb5c0,(short) 0xfbcf,(short) 0xec4d,(short) 0x3b2f, - (short) 0xe9b5,(short) 0xdba5,(short) 0x8189,(short) 0xdbbc, - (short) 0x3956,(short) 0xc25b,(short) 0xf348,(short) 0xb538, - (short) 0x59f1,(short) 0x11f1,(short) 0xb605,(short) 0xd019, - (short) 0x923f,(short) 0x82a4,(short) 0xaf19,(short) 0x4f9b, - (short) 0xab1c,(short) 0x5ed5,(short) 0xda6d,(short) 0x8118, - (short) 0xd807,(short) 0xaa98,(short) 0xa303,(short) 0x0242, - (short) 0x1283,(short) 0x5b01,(short) 0x4570,(short) 0x6fbe, - (short) 0x2431,(short) 0x85be,(short) 0x4ee4,(short) 0xb28c, - (short) 0x550c,(short) 0x7dc3,(short) 0xd5ff,(short) 0xb4e2, - (short) 0x72be,(short) 0x5d74,(short) 0xf27b,(short) 0x896f, - (short) 0x80de,(short) 0xb1fe,(short) 0x3b16,(short) 0x96b1, - (short) 0x9bdc,(short) 0x06a7,(short) 0x25c7,(short) 0x1235, - (short) 0xc19b,(short) 0xf174,(short) 0xcf69,(short) 0x2694, - (short) 0xe49b,(short) 0x69c1,(short) 0x9ef1,(short) 0x4ad2, - (short) 0xefbe,(short) 0x4786,(short) 0x384f,(short) 0x25e3, - (short) 0x0fc1,(short) 0x9dc6,(short) 0x8b8c,(short) 0xd5b5, - (short) 0x240c,(short) 0xa1cc,(short) 0x77ac,(short) 0x9c65, - (short) 0x2de9,(short) 0x2c6f,(short) 0x592b,(short) 0x0275, - (short) 0x4a74,(short) 0x84aa,(short) 0x6ea6,(short) 0xe483, - (short) 0x5cb0,(short) 0xa9dc,(short) 0xbd41,(short) 0xfbd4, - (short) 0x76f9,(short) 0x88da,(short) 0x8311,(short) 0x53b5, - (short) 0x983e,(short) 0x5152,(short) 0xee66,(short) 0xdfab, - (short) 0xa831,(short) 0xc66d,(short) 0x2db4,(short) 0x3210, - (short) 0xb003,(short) 0x27c8,(short) 0x98fb,(short) 0x213f, - (short) 0xbf59,(short) 0x7fc7,(short) 0xbeef,(short) 0x0ee4, - (short) 0xc6e0,(short) 0x0bf3,(short) 0x3da8,(short) 0x8fc2, - (short) 0xd5a7,(short) 0x9147,(short) 0x930a,(short) 0xa725, - (short) 0x06ca,(short) 0x6351,(short) 0xe003,(short) 0x826f, - (short) 0x1429,(short) 0x2967,(short) 0x0a0e,(short) 0x6e70, - (short) 0x27b7,(short) 0x0a85,(short) 0x46d2,(short) 0x2ffc, - (short) 0x2e1b,(short) 0x2138,(short) 0x5c26,(short) 0xc926, - (short) 0x4d2c,(short) 0x6dfc,(short) 0x5ac4,(short) 0x2aed, - (short) 0x5338,(short) 0x0d13,(short) 0x9d95,(short) 0xb3df, - (short) 0x650a,(short) 0x7354,(short) 0x8baf,(short) 0x63de, - (short) 0x766a,(short) 0x0abb,(short) 0x3c77,(short) 0xb2a8, - (short) 0x81c2,(short) 0xc92e,(short) 0x47ed,(short) 0xaee6, - (short) 0x9272,(short) 0x2c85,(short) 0x1482,(short) 0x353b, - (short) 0xa2bf,(short) 0xe8a1,(short) 0x4cf1,(short) 0x0364, - (short) 0xa81a,(short) 0x664b,(short) 0xbc42,(short) 0x3001, - (short) 0xc24b,(short) 0x8b70,(short) 0xd0f8,(short) 0x9791, - (short) 0xc76c,(short) 0x51a3,(short) 0x0654,(short) 0xbe30, - (short) 0xd192,(short) 0xe819,(short) 0xd6ef,(short) 0x5218, - (short) 0xd699,(short) 0x0624,(short) 0x5565,(short) 0xa910, - (short) 0xf40e,(short) 0x3585,(short) 0x5771,(short) 0x202a, - (short) 0x106a,(short) 0xa070,(short) 0x32bb,(short) 0xd1b8, - (short) 0x19a4,(short) 0xc116,(short) 0xb8d2,(short) 0xd0c8, - (short) 0x1e37,(short) 0x6c08,(short) 0x5141,(short) 0xab53, - (short) 0x2748,(short) 0x774c,(short) 0xdf8e,(short) 0xeb99, - (short) 0x34b0,(short) 0xbcb5,(short) 0xe19b,(short) 0x48a8, - (short) 0x391c,(short) 0x0cb3,(short) 0xc5c9,(short) 0x5a63, - (short) 0x4ed8,(short) 0xaa4a,(short) 0xe341,(short) 0x8acb, - (short) 0x5b9c,(short) 0xca4f,(short) 0x7763,(short) 0xe373, - (short) 0x682e,(short) 0x6ff3,(short) 0xd6b2,(short) 0xb8a3, - (short) 0x748f,(short) 0x82ee,(short) 0x5def,(short) 0xb2fc, - (short) 0x78a5,(short) 0x636f,(short) 0x4317,(short) 0x2f60, - (short) 0x84c8,(short) 0x7814,(short) 0xa1f0,(short) 0xab72, - (short) 0x8cc7,(short) 0x0208,(short) 0x1a64,(short) 0x39ec, - (short) 0x90be,(short) 0xfffa,(short) 0x2363,(short) 0x1e28, - (short) 0xa450,(short) 0x6ceb,(short) 0xde82,(short) 0xbde9, - (short) 0xbef9,(short) 0xa3f7,(short) 0xb2c6,(short) 0x7915, - (short) 0xc671,(short) 0x78f2,(short) 0xe372,(short) 0x532b, - (short) 0xca27,(short) 0x3ece,(short) 0xea26,(short) 0x619c, - (short) 0xd186,(short) 0xb8c7,(short) 0x21c0,(short) 0xc207, - (short) 0xeada,(short) 0x7dd6,(short) 0xcde0,(short) 0xeb1e, - (short) 0xf57d,(short) 0x4f7f,(short) 0xee6e,(short) 0xd178, - (short) 0x06f0,(short) 0x67aa,(short) 0x7217,(short) 0x6fba, - (short) 0x0a63,(short) 0x7dc5,(short) 0xa2c8,(short) 0x98a6, - (short) 0x113f,(short) 0x9804,(short) 0xbef9,(short) 0x0dae, - (short) 0x1b71,(short) 0x0b35,(short) 0x131c,(short) 0x471b, - (short) 0x28db,(short) 0x77f5,(short) 0x2304,(short) 0x7d84, - (short) 0x32ca,(short) 0xab7b,(short) 0x40c7,(short) 0x2493, - (short) 0x3c9e,(short) 0xbe0a,(short) 0x15c9,(short) 0xbebc, - (short) 0x431d,(short) 0x67c4,(short) 0x9c10,(short) 0x0d4c, - (short) 0x4cc5,(short) 0xd4be,(short) 0xcb3e,(short) 0x42b6, - (short) 0x597f,(short) 0x299c,(short) 0xfc65,(short) 0x7e2a, - (short) 0x5fcb,(short) 0x6fab,(short) 0x3ad6,(short) 0xfaec, - (short) 0x6c44,(short) 0x198c,(short) 0x4a47,(short) 0x5817 - }; - - public static short[] h_short; - public static short[] w_short; - - public static short[] hashState; - public static byte[] buffer; - public static short bufferOff; - public static short bufferLeft; - - // used in reset(), update() & doFinal() to store size of msg to hash - // public static byte[] dataSize; - // public static final short MSGSIZE=0; - // public static final short CHUNKSIZE=4; - - public static void init(){ - - w_short= JCSystem.makeTransientShortArray((short) (64), JCSystem.CLEAR_ON_DESELECT); - h_short= JCSystem.makeTransientShortArray((short) (32), JCSystem.CLEAR_ON_DESELECT); - - hashState= JCSystem.makeTransientShortArray((short) (32), JCSystem.CLEAR_ON_DESELECT); - buffer= JCSystem.makeTransientByteArray((short) (128), JCSystem.CLEAR_ON_DESELECT); - - // used in reset(), update() & doFinal() to store size of msg to hash - //dataSize= JCSystem.makeTransientByteArray((short) (8), JCSystem.CLEAR_ON_DESELECT); - } - - // simplified method to hash exactly 2 blocks, i.e. >128 bytes and <=240 bytes, as required for Bip32 - public static short resetUpdateDoFinal(byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset){ - - // variable declaration for inline additions - short akku,posy,posx,addx,addy; - - /* Reset */ - bufferOff=0; - bufferLeft=128; - - /* Update */ - - // perform function compression on first (complete) 1024 blocks - // fulfil buffer - Util.arrayCopyNonAtomic(inBuff, inOffset, buffer, bufferOff, bufferLeft); - inOffset+=bufferLeft; - bufferLeft=128; - bufferOff=0; - - // apply compression function - for (short i=0; i<32; i++){ - hashState[i]= H_INIT_SHORT[i]; - h_short[i]=hashState[i]; - } - CompressionFunction(h_short, (short)0, buffer, (short)0); - //add result back in hashState - for (short i=0; i<32; i+=4){ - add_carry(hashState, i, h_short, i); - } - - // save remaining msg in buffer - short remainingBytes= (short)(inLength-(short)128); - Util.arrayCopyNonAtomic(inBuff, inOffset, buffer, bufferOff, remainingBytes); - bufferLeft-=remainingBytes; - bufferOff+=remainingBytes; - - /* DoFinal */ - - // pad remaining bytes in the buffer - buffer[bufferOff]=(byte)0x80; - bufferLeft--; - bufferOff++; - Util.arrayFillNonAtomic(buffer, bufferOff, bufferLeft, (byte)0x00); - - // message size (in bits) - buffer[(short)(buffer.length-2)]=(byte)(((short)(8*inLength)>>8)&0xff); - buffer[(short)(buffer.length-1)]=(byte)((8*inLength) &0xff); - - // apply compression function on last block - for (short i=0; i<32; i++){ - h_short[i]=hashState[i]; - } - CompressionFunction(h_short, (short)0, buffer, (short)0); - //add result back in hashState - for (short i=0; i<32; i+=4){ - add_carry(hashState, i, h_short, i); - } - - // copy final state back - for (short i=0; i<32; i++){ - outBuff[outOffset]=(byte)((hashState[i]>>8)&0xff); - outOffset++; - outBuff[outOffset]=(byte)(hashState[i]&0xff); - outOffset++; - } - - return (short)64; - } - - public static void CompressionFunction(short[] state, short stateOff, byte[] msgBlock, short msgOff){ - - // temporary value for inline method - short off1, off2, off3; - short regA0, regA1, regA2, regA3; - short regB0, regB1, regB2, regB3; - short tmpA0, tmpA1, tmpA2, tmpA3; - - // stateOff => 32 short state (512 bits) - // msgBlock => 128 byte data (1024 bits) - InitW(msgBlock, msgOff); - - short hOff=0, wOff=0; - for (short round=0; round<80; round++){ - - // update W and get wCurrent in regB - updateW_fast(w_short, wOff, regB, regA, tmpA); - - // regA= h+K - off1= (short)(((short)(hOff+28))%32); - off2= (short)(4*(round)); - add_carry_fast2(state, off1, K_SHORT, off2, regA, tmpA); - - // regA= h+K+wCurrent - add_carry_fast(regA, regB, tmpA); - - // regB= Ch(e,f,g) - off1=(short)(((short)(hOff+16))%32); - off2=(short)(((short)(hOff+20))%32); - off3=(short)(((short)(hOff+24))%32); - Ch_fast(state, off1, state, off2, state, off3, regB, tmpA); - - // regA= h+K+wCurrent+Ch(e,f,g) - add_carry_fast(regA, regB, tmpA); - - // regB= E1(e) - off1=(short)(((short)(hOff+16))%32); - E1_fast(state, off1, regB, tmpA); - - // regA= h+K+wCurrent+Ch(e,f,g)+E1 - add_carry_fast(regA, regB, tmpA); - - // d= d+regA - off1= (short)(((short)(hOff+12))%32); - add_carry_fast3(state, off1, regA, tmpA) - - // regB= Maj(a,b,c) - //Maj(state, hOff, state, (short)(((short)(hOff+4))%32), state, (short)(((short)(hOff+8))%32), tmp, REG2); - off1= (short)(((short)(hOff+4))%32); - off2= (short)(((short)(hOff+8))%32); - Maj_fast(state, hOff, state, off1, state, off2, regB, tmpA) - - // regA= regA+Maj(a,b,c) - add_carry_fast(regA, regB, tmpA); - - // regB= E0(a) - E0_fast(state, hOff, regB, tmpA); - - // regA= regA+E0(a) - add_carry_fast(regA, regB, tmpA); - - //update state(h) with regA - state[(short)(((short)(hOff+28))%32)]= regA0; - state[(short)(((short)(hOff+29))%32)]= regA1; - state[(short)(((short)(hOff+30))%32)]= regA2; - state[(short)(((short)(hOff+31))%32)]= regA3; - - // update offset - hOff= (short)(((short)(32+hOff-4))%32); - - }// end for - } - - - /* - public static void reset(){ - bufferOff=0; - bufferLeft=128; - for (short i=0; i<32; i++){ - hashState[i]= H_INIT_SHORT[i]; - } - for (short i=0; i<8; i++){ - dataSize[i]=(byte)0; - } - } - - public static void update(byte[] inBuff, short inOffset, short inLength){ - - // for additions - short akku,posy,posx,addx,addy; - - // update msg size in bits - dataSize[6]=(byte)(((short)(8*inLength)>>8)&0xff); - dataSize[7]=(byte)((8*inLength) &0xff); - add_carry_byte(dataSize, MSGSIZE, dataSize, CHUNKSIZE, (short)4); - - // perform function compression on complete 1024 blocks - while (inLength>=bufferLeft){ - - // fulfill buffer - //System.arraycopy(inBuff, inOffset, buffer, bufferOff, bufferLeft); - Util.arrayCopyNonAtomic(inBuff, inOffset, buffer, bufferOff, bufferLeft); - inOffset+=bufferLeft; - inLength-=bufferLeft; - bufferLeft=128; - bufferOff=0; - - // apply compression function - for (short i=0; i<32; i++){ - h_short[i]=hashState[i]; - } - CompressionFunction(h_short, (short)0, buffer, (short)0); - - //add result back in hashState - for (short i=0; i<32; i+=4){ - add_carry(hashState, (short)i, h_short, (short)i); - } - - } - // at this point, bufferLeft>inLength - - // save remaining msg in buffer - Util.arrayCopyNonAtomic(inBuff, inOffset, buffer, bufferOff, inLength); - inOffset+=inLength; - bufferLeft-=inLength; - bufferOff+=inLength; - } - - - - public static short doFinal(byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset){ - - // for additions - short akku,posy,posx,addx,addy; - - // perform update first - update(inBuff, inOffset, inLength); - - // padd remaining bytes in the buffer - buffer[bufferOff]=(byte)0x80; - bufferLeft--; - bufferOff++; - Util.arrayFillNonAtomic(buffer, bufferOff, bufferLeft, (byte)0x00); - - if (bufferLeft<16){ // needs an additional block - // apply compression function - for (short i=0; i<32; i++){ - h_short[i]=hashState[i]; - } - CompressionFunction(h_short, (short)0, buffer, (short)0); - //add result back in hashState - for (short i=0; i<32; i+=4){ - add_carry(hashState, (short)i, h_short, (short)i); - } - // reset buffer - bufferOff=0; - bufferLeft=128; - Util.arrayFillNonAtomic(buffer, bufferOff, bufferLeft, (byte)0x00); - } - // message size (in bits) - Util.arrayCopyNonAtomic(dataSize, MSGSIZE, buffer, (short)(buffer.length-4), (short)4); - - // apply compression function on last block - for (short i=0; i<32; i++){ - h_short[i]=hashState[i]; - } - CompressionFunction(h_short, (short)0, buffer, (short)0); - //add result back in hashState - for (short i=0; i<32; i+=4){ - add_carry(hashState, (short)i, h_short, (short)i); - } - - // copy final state back and reset - for (short i=0; i<32; i++){ - outBuff[outOffset]=(byte)((hashState[i]>>8)&0xff); - outOffset++; - outBuff[outOffset]=(byte)(hashState[i]&0xff); - outOffset++; - } - reset(); - - return (short)64; - } - - public static boolean add_carry_byte(byte[] x, short offsetx, byte[] y, short offsety, short size){ - short digit_mask = 0xff; - short digit_len = 8; - short akku = 0; - short j = (short)(offsetx+size-1); - for(short i = (short)(offsety+size-1); i >= offsety; i--, j--) { - akku = (short)(akku + (x[j] & digit_mask) + (y[i] & digit_mask)); - - x[j] = (byte)(akku & digit_mask); - akku = (short)((akku >>> digit_len) & digit_mask); - } - return akku != 0; - } - */ - -} - From d8f3dd59424d5fe8f8fcf2f1cd76c984597c2437 Mon Sep 17 00:00:00 2001 From: Toporin Date: Wed, 20 May 2020 21:40:27 +0200 Subject: [PATCH 6/6] Satochip applet v0.11-0.1: support (mandatory) secure channel Major protocol revision with Secure Channel support. In addition, some code cleanup: - remove deprecated instruction (sign_short_message) - improved error message in case of wrong PIN --- src/org/satochip/applet/CardEdge.java | 797 ++++++++++++++++-------- src/org/satochip/applet/HmacSha160.java | 6 +- src/org/satochip/applet/HmacSha512.java | 9 +- 3 files changed, 527 insertions(+), 285 deletions(-) diff --git a/src/org/satochip/applet/CardEdge.java b/src/org/satochip/applet/CardEdge.java index e3df5ed..037dedb 100644 --- a/src/org/satochip/applet/CardEdge.java +++ b/src/org/satochip/applet/CardEdge.java @@ -58,7 +58,6 @@ import javacard.security.AESKey; import javacard.security.ECPrivateKey; import javacard.security.ECPublicKey; -//import javacard.security.HMACKey; import javacard.security.CryptoException; import javacard.security.Key; import javacard.security.KeyAgreement; @@ -66,13 +65,12 @@ import javacard.security.Signature; import javacard.security.MessageDigest; import javacard.security.RandomData; -import javacardx.apdu.ExtendedLength; //debugXL //TODO: remove import javacardx.crypto.Cipher; /** * Implements MUSCLE's Card Edge Specification. */ -public class CardEdge extends javacard.framework.Applet implements ExtendedLength { +public class CardEdge extends javacard.framework.Applet { /* constants declaration */ @@ -99,10 +97,11 @@ public class CardEdge extends javacard.framework.Applet implements ExtendedLengt // 0.10-0.2: support for native sha512 // 0.10-0.3: ECkey recovery optimisation: support ALG_EC_SVDP_DH_PLAIN_XY // 0.10-0.4: Cleanup: optimised code only, removed legacy sha512 implementation and slow pubkey recovery + // 0.11-0.1: support (mandatory) secure channel private final static byte PROTOCOL_MAJOR_VERSION = (byte) 0; - private final static byte PROTOCOL_MINOR_VERSION = (byte) 10; + private final static byte PROTOCOL_MINOR_VERSION = (byte) 11; private final static byte APPLET_MAJOR_VERSION = (byte) 0; - private final static byte APPLET_MINOR_VERSION = (byte) 4; + private final static byte APPLET_MINOR_VERSION = (byte) 1; // Maximum number of keys handled by the Cardlet private final static byte MAX_NUM_KEYS = (byte) 16; @@ -160,20 +159,20 @@ public class CardEdge extends javacard.framework.Applet implements ExtendedLengt private final static byte INS_CRYPT_TRANSACTION_2FA = (byte) 0x76; private final static byte INS_SET_2FA_KEY = (byte) 0x79; private final static byte INS_RESET_2FA_KEY = (byte) 0x78; + private final static byte INS_SIGN_TRANSACTION_HASH= (byte) 0x7A; - private final static byte INS_SIGN_TRANSACTION_HASH= (byte) 0x7A; - - // debug - private final static byte INS_TEST_SHA1 = (byte) 0x80; - private final static byte INS_COMPUTE_SHA512 = (byte) 0x6A; - private final static byte INS_COMPUTE_HMAC= (byte) 0x6B; - private final static byte INS_BIP32_SET_EXTENDED_KEY= (byte) 0x70; + // secure channel + private final static byte INS_INIT_SECURE_CHANNEL = (byte) 0x81; + private final static byte INS_PROCESS_SECURE_CHANNEL = (byte) 0x82; + + /**************************************** + * Error codes * + ****************************************/ - - /** There have been memory problems on the card */ - private final static short SW_NO_MEMORY_LEFT = Bip32ObjectManager.SW_NO_MEMORY_LEFT; /** Entered PIN is not correct */ - private final static short SW_AUTH_FAILED = (short) 0x9C02; + private final static short SW_PIN_FAILED = (short)0x63C0;// includes number of tries remaining + ///** DEPRECATED - Entered PIN is not correct */ + //private final static short SW_AUTH_FAILED = (short) 0x9C02; /** Required operation is not allowed in actual circumstances */ private final static short SW_OPERATION_NOT_ALLOWED = (short) 0x9C03; /** Required setup is not not done */ @@ -181,13 +180,16 @@ public class CardEdge extends javacard.framework.Applet implements ExtendedLengt /** Required setup is already done */ private final static short SW_SETUP_ALREADY_DONE = (short) 0x9C07; /** Required feature is not (yet) supported */ - private final static short SW_UNSUPPORTED_FEATURE = (short) 0x9C05; + final static short SW_UNSUPPORTED_FEATURE = (short) 0x9C05; /** Required operation was not authorized because of a lack of privileges */ private final static short SW_UNAUTHORIZED = (short) 0x9C06; /** Algorithm specified is not correct */ private final static short SW_INCORRECT_ALG = (short) 0x9C09; - /** Required object is missing */ - private final static short SW_OBJECT_NOT_FOUND= (short) 0x9C07; + + /** There have been memory problems on the card */ + private final static short SW_NO_MEMORY_LEFT = Bip32ObjectManager.SW_NO_MEMORY_LEFT; + ///** DEPRECATED - Required object is missing */ + //private final static short SW_OBJECT_NOT_FOUND= (short) 0x9C07; /** Incorrect P1 parameter */ private final static short SW_INCORRECT_P1 = (short) 0x9C10; @@ -213,8 +215,8 @@ public class CardEdge extends javacard.framework.Applet implements ExtendedLengt private final static short SW_BIP32_UNINITIALIZED_SEED = (short) 0x9C14; /** Bip32 seed is already initialized (must be reset before change)*/ private final static short SW_BIP32_INITIALIZED_SEED = (short) 0x9C17; - /** Bip32 authentikey pubkey is not initialized*/ - private final static short SW_BIP32_UNINITIALIZED_AUTHENTIKEY_PUBKEY= (short) 0x9C16; + //** DEPRECATED - Bip32 authentikey pubkey is not initialized*/ + //private final static short SW_BIP32_UNINITIALIZED_AUTHENTIKEY_PUBKEY= (short) 0x9C16; /** Incorrect transaction hash */ private final static short SW_INCORRECT_TXHASH = (short) 0x9C15; @@ -223,6 +225,19 @@ public class CardEdge extends javacard.framework.Applet implements ExtendedLengt /** 2FA uninitialized*/ private final static short SW_2FA_UNINITIALIZED_KEY = (short) 0x9C19; + /** HMAC errors */ + static final short SW_HMAC_UNSUPPORTED_KEYSIZE = (short) 0x9c1E; + static final short SW_HMAC_UNSUPPORTED_MSGSIZE = (short) 0x9c1F; + + /** Secure channel */ + private final static short SW_SECURE_CHANNEL_REQUIRED = (short) 0x9C20; + private final static short SW_SECURE_CHANNEL_UNINITIALIZED = (short) 0x9C21; + private final static short SW_SECURE_CHANNEL_WRONG_IV= (short) 0x9C22; + private final static short SW_SECURE_CHANNEL_WRONG_MAC= (short) 0x9C23; + + /** For instructions that have been deprecated*/ + private final static short SW_INS_DEPRECATED = (short) 0x9C26; + /** For debugging purposes 2 */ private final static short SW_DEBUG_FLAG = (short) 0x9FFF; @@ -361,6 +376,24 @@ public class CardEdge extends javacard.framework.Applet implements ExtendedLengt private Cipher aes128_cbc; private AESKey key_2FA; + // secure channel + private static final byte[] CST_SC = {'s','c','_','k','e','y', 's','c','_','m','a','c'}; + private boolean needs_secure_channel= true; + private boolean initialized_secure_channel= false; + private ECPrivateKey sc_ephemeralkey; + private AESKey sc_sessionkey; + private Cipher sc_aes128_cbc; + private byte[] sc_buffer; + private static final byte OFFSET_SC_IV=0; + private static final byte OFFSET_SC_IV_RANDOM=OFFSET_SC_IV; + private static final byte OFFSET_SC_IV_COUNTER=12; + private static final byte OFFSET_SC_MACKEY=16; + private static final byte SIZE_SC_MACKEY=20; + private static final byte SIZE_SC_IV= 16; + private static final byte SIZE_SC_IV_RANDOM=12; + private static final byte SIZE_SC_IV_COUNTER=SIZE_SC_IV-SIZE_SC_IV_RANDOM; + private static final byte SIZE_SC_BUFFER=SIZE_SC_MACKEY+SIZE_SC_IV; + // additional options private short option_flags; @@ -383,6 +416,45 @@ private CardEdge(byte[] bArray, short bOffset, byte bLength) { pins[0] = new OwnerPIN((byte) 3, (byte) PIN_INIT_VALUE.length); pins[0].update(PIN_INIT_VALUE, (short) 0, (byte) PIN_INIT_VALUE.length); + // Temporary working arrays + try { + tmpBuffer = JCSystem.makeTransientByteArray((short) TMP_BUFFER_SIZE, JCSystem.CLEAR_ON_DESELECT); + } catch (SystemException e) { + tmpBuffer = new byte[TMP_BUFFER_SIZE]; + } + // Initialize the extended APDU buffer + try { + // Try to allocate the extended APDU buffer on RAM memory + recvBuffer = JCSystem.makeTransientByteArray((short) EXT_APDU_BUFFER_SIZE, JCSystem.CLEAR_ON_DESELECT); + } catch (SystemException e) { + // Allocate the extended APDU buffer on EEPROM memory + // This is the fallback method, but its usage is really not + // recommended as after ~ 100000 writes it will kill the EEPROM cells... + recvBuffer = new byte[EXT_APDU_BUFFER_SIZE]; + } + + // common cryptographic objects + randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); + sigECDSA= Signature.getInstance(ALG_ECDSA_SHA_256, false); + HmacSha160.init(tmpBuffer); + try { + keyAgreement = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN_XY, false); + } catch (CryptoException e) { + ISOException.throwIt(SW_UNSUPPORTED_FEATURE);// unsupported feature => use a more recent card! + } + + //secure channel objects + try { + sc_buffer = JCSystem.makeTransientByteArray((short) SIZE_SC_BUFFER, JCSystem.CLEAR_ON_DESELECT); + } catch (SystemException e) { + sc_buffer = new byte[SIZE_SC_BUFFER]; + } + //sc_IV= new byte[(short)16];//todo make transient and combine? + //sc_mackey= new byte[(short)20]; + sc_sessionkey= (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); // todo: make transient? + sc_ephemeralkey= (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, LENGTH_EC_FP_256, false); + sc_aes128_cbc= Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); + // debug register(); } // end of constructor @@ -396,6 +468,10 @@ public boolean select() { * Application has been selected: Do session cleanup operation */ LogOutAll(); + + //todo: clear secure channel values? + initialized_secure_channel=false; + return true; } @@ -414,7 +490,7 @@ public void process(APDU apdu) { // The interface javacard.framework.ISO7816 // declares constants to denote the offset of // these bytes in the APDU buffer - + if (selectingApplet()) ISOException.throwIt(ISO7816.SW_NO_ERROR); @@ -429,107 +505,153 @@ public void process(APDU apdu) { ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); byte ins = buffer[ISO7816.OFFSET_INS]; + + // prepare APDU buffer + if (ins != (byte) INS_GET_STATUS){ + short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); + if (bytesLeft != apdu.setIncomingAndReceive()) + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + + // only 3 commands are allowed, the others must be wrapped in a secure channel command + // the 3 commands are: get_status, initialize_secure_channel & process_secure_channel + short sizeout=(short)0; + if (ins == (byte) INS_GET_STATUS){ + sizeout= GetStatus(apdu, buffer); + apdu.setOutgoingAndSend((short) 0, sizeout); + return; + } + else if (ins == (byte) INS_INIT_SECURE_CHANNEL){ + sizeout= InitiateSecureChannel(apdu, buffer); + apdu.setOutgoingAndSend((short) 0, sizeout); + return; + } + else if (ins == (byte) INS_PROCESS_SECURE_CHANNEL){ + sizeout= ProcessSecureChannel(apdu, buffer); + //todo: check if sizeout and buffer[ISO7816.OFFSET_LC] matches... + //if sizeout>4, buffer[ISO7816.OFFSET_LC] should be equal to (sizeout-5) + //todo: remove padding ? (it is actually not used) + } + else if (needs_secure_channel){ + ISOException.throwIt(SW_SECURE_CHANNEL_REQUIRED); + } + + // at this point, the encrypted content has been deciphered in the buffer + ins = buffer[ISO7816.OFFSET_INS]; + // check setup status if (!setupDone && (ins != (byte) INS_SETUP)) ISOException.throwIt(SW_SETUP_NOT_DONE); if (setupDone && (ins == (byte) INS_SETUP)) ISOException.throwIt(SW_SETUP_ALREADY_DONE); - + switch (ins) { case INS_SETUP: - setup(apdu, buffer); + sizeout= setup(apdu, buffer); break; case INS_IMPORT_KEY: - ImportKey(apdu, buffer); + sizeout= ImportKey(apdu, buffer); break; case INS_RESET_KEY: - ResetKey(apdu, buffer); + sizeout= ResetKey(apdu, buffer); break; case INS_GET_PUBLIC_FROM_PRIVATE: - getPublicKeyFromPrivate(apdu, buffer); + sizeout= getPublicKeyFromPrivate(apdu, buffer); break; case INS_VERIFY_PIN: - VerifyPIN(apdu, buffer); + sizeout= VerifyPIN(apdu, buffer); break; case INS_CREATE_PIN: - CreatePIN(apdu, buffer); + sizeout= CreatePIN(apdu, buffer); break; case INS_CHANGE_PIN: - ChangePIN(apdu, buffer); + sizeout= ChangePIN(apdu, buffer); break; case INS_UNBLOCK_PIN: - UnblockPIN(apdu, buffer); + sizeout= UnblockPIN(apdu, buffer); break; case INS_LOGOUT_ALL: - LogOutAll(); + sizeout= LogOutAll(); break; case INS_LIST_PINS: - ListPINs(apdu, buffer); + sizeout= ListPINs(apdu, buffer); break; case INS_GET_STATUS: - GetStatus(apdu, buffer); + sizeout= GetStatus(apdu, buffer); break; case INS_BIP32_IMPORT_SEED: - importBIP32Seed(apdu, buffer); + sizeout= importBIP32Seed(apdu, buffer); break; case INS_BIP32_RESET_SEED: - resetBIP32Seed(apdu, buffer); + sizeout= resetBIP32Seed(apdu, buffer); break; case INS_BIP32_GET_AUTHENTIKEY: - getBIP32AuthentiKey(apdu, buffer); + sizeout= getBIP32AuthentiKey(apdu, buffer); break; case INS_BIP32_SET_AUTHENTIKEY_PUBKEY: - setBIP32AuthentikeyPubkey(apdu, buffer); + sizeout= setBIP32AuthentikeyPubkey(apdu, buffer); break; case INS_BIP32_GET_EXTENDED_KEY: - getBIP32ExtendedKey(apdu, buffer); + sizeout= getBIP32ExtendedKey(apdu, buffer); break; case INS_BIP32_SET_EXTENDED_PUBKEY: - setBIP32ExtendedPubkey(apdu, buffer); + sizeout= setBIP32ExtendedPubkey(apdu, buffer); break; case INS_SIGN_MESSAGE: - signMessage(apdu, buffer); + sizeout= signMessage(apdu, buffer); break; case INS_SIGN_SHORT_MESSAGE: - signShortMessage(apdu, buffer); + //sizeout= signShortMessage(apdu, buffer); + ISOException.throwIt(SW_INS_DEPRECATED); break; case INS_SIGN_TRANSACTION: - SignTransaction(apdu, buffer); + sizeout= SignTransaction(apdu, buffer); break; case INS_SIGN_TRANSACTION_HASH: - SignTransactionHash(apdu, buffer); + sizeout= SignTransactionHash(apdu, buffer); break; case INS_PARSE_TRANSACTION: - ParseTransaction(apdu, buffer); + sizeout= ParseTransaction(apdu, buffer); break; case INS_SET_2FA_KEY: - set2FAKey(apdu, buffer); + sizeout= set2FAKey(apdu, buffer); break; case INS_RESET_2FA_KEY: - reset2FAKey(apdu, buffer); + sizeout= reset2FAKey(apdu, buffer); break; case INS_CRYPT_TRANSACTION_2FA: - CryptTransaction2FA(apdu, buffer); + sizeout= CryptTransaction2FA(apdu, buffer); break; - // only for debugging purpose - case INS_BIP32_SET_EXTENDED_KEY: - ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); // only for debug purpose - //setBIP32ExtendedKey(apdu, buffer); - break; - case INS_COMPUTE_SHA512: - ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); // only for debug purpose - //computeSha512(apdu, buffer); - break; - case INS_COMPUTE_HMAC: - ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); // only for debug purpose - //computeHmac(apdu, buffer); - break; - case INS_TEST_SHA1: - ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); // only for debug purpose - //testSha512(apdu, buffer) - break; default: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + }//end of switch + + // Prepare buffer for return + if (sizeout==0){ + return; + } + else if ((ins == (byte) INS_GET_STATUS) || (ins == (byte) INS_INIT_SECURE_CHANNEL)) { + apdu.setOutgoingAndSend((short) 0, sizeout); } + else if (needs_secure_channel) { // encrypt response + // buffer contains the data (sizeout) + // for encryption, data is padded with PKCS#7 + short blocksize=(short)16; + short padsize= (short) (blocksize - (sizeout%blocksize)); + + Util.arrayCopy(buffer, (short)0, tmpBuffer, (short)0, sizeout); + Util.arrayFillNonAtomic(tmpBuffer, sizeout, padsize, (byte)padsize);//padding + Util.arrayCopy(sc_buffer, OFFSET_SC_IV, buffer, (short)0, SIZE_SC_IV); + sc_aes128_cbc.init(sc_sessionkey, Cipher.MODE_ENCRYPT, sc_buffer, OFFSET_SC_IV, SIZE_SC_IV); + short sizeoutCrypt=sc_aes128_cbc.doFinal(tmpBuffer, (short)0, (short)(sizeout+padsize), buffer, (short) (18)); + Util.setShort(buffer, (short)16, sizeoutCrypt); + sizeout= (short)(18+sizeoutCrypt); + //send back + apdu.setOutgoingAndSend((short) 0, sizeout); + } + else { + apdu.setOutgoingAndSend((short) 0, sizeout); + } + } // end of process method /** @@ -560,11 +682,8 @@ public void process(APDU apdu) { * * return: none */ - private void setup(APDU apdu, byte[] buffer) { + private short setup(APDU apdu, byte[] buffer) { short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (bytesLeft != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); - short base = (short) (ISO7816.OFFSET_CDATA); byte numBytes = buffer[base++]; @@ -574,12 +693,12 @@ private void setup(APDU apdu, byte[] buffer) { if (!CheckPINPolicy(buffer, base, numBytes)) ISOException.throwIt(SW_INVALID_PARAMETER); - - if (pin.getTriesRemaining() == (byte) 0x00) + + byte triesRemaining = pin.getTriesRemaining(); + if (triesRemaining == (byte) 0x00) ISOException.throwIt(SW_IDENTITY_BLOCKED); - if (!pin.check(buffer, base, numBytes)) - ISOException.throwIt(SW_AUTH_FAILED); + ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining - 1)); base += numBytes; bytesLeft-=numBytes; @@ -649,30 +768,7 @@ private void setup(APDU apdu, byte[] buffer) { eckeys = new Key[MAX_NUM_KEYS]; logged_ids = 0x0000; // No identities logged in - // Initialize the extended APDU buffer - try { - // Try to allocate the extended APDU buffer on RAM memory - recvBuffer = JCSystem.makeTransientByteArray((short) EXT_APDU_BUFFER_SIZE, JCSystem.CLEAR_ON_DESELECT); - } catch (SystemException e) { - // Allocate the extended APDU buffer on EEPROM memory - // This is the fallback method, but its usage is really not - // recommended as after ~ 100000 writes it will kill the EEPROM cells... - recvBuffer = new byte[EXT_APDU_BUFFER_SIZE]; - } - // temporary buffer - try { - tmpBuffer = JCSystem.makeTransientByteArray((short) TMP_BUFFER_SIZE, JCSystem.CLEAR_ON_DESELECT); - } catch (SystemException e) { - tmpBuffer = new byte[TMP_BUFFER_SIZE]; - } - // shared cryptographic objects - try { - keyAgreement = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN_XY, false); - } catch (CryptoException e) { - ISOException.throwIt(SW_UNSUPPORTED_FEATURE);// unsupported feature => use a more recent card! - } - sigECDSA= Signature.getInstance(ALG_ECDSA_SHA_256, false); aes128= Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, false); // HD wallet @@ -700,7 +796,6 @@ private void setup(APDU apdu, byte[] buffer) { // Transaction signing Transaction.init(); - HmacSha160.init(tmpBuffer); transactionData= new byte[OFFSET_TRANSACTION_SIZE]; // parse options @@ -719,8 +814,7 @@ private void setup(APDU apdu, byte[] buffer) { base+=(short)8; bytesLeft-=(short)8; // set 2FA variables - randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); - aes128_cbc= Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); + aes128_cbc= Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); key_2FA= (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); // hmac derivation for id_2FA & key_2FA HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, CST_2FA, (short)0, (short)6, data2FA, OFFSET_2FA_ID); @@ -732,6 +826,7 @@ private void setup(APDU apdu, byte[] buffer) { } setupDone = true; + return (short)0;//nothing to return } /********** UTILITY FUNCTIONS **********/ @@ -793,7 +888,7 @@ private boolean CheckPINPolicy(byte[] pin_buffer, short pin_offset, byte pin_siz * data: [key_encoding(1) | key_type(1) | key_size(2) | RFU(6) | key_blob | (option)HMAC-2FA(20b)] * return: none */ - private void ImportKey(APDU apdu, byte[] buffer) { + private short ImportKey(APDU apdu, byte[] buffer) { // check that PIN[0] has been entered previously if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); @@ -805,8 +900,6 @@ private void ImportKey(APDU apdu, byte[] buffer) { ISOException.throwIt(SW_INCORRECT_P1); short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (bytesLeft != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); /*** Start reading key blob header***/ // blob header= [ key_encoding(1) | key_type(1) | key_size(2) | RFU(6)] @@ -875,6 +968,7 @@ private void ImportKey(APDU apdu, byte[] buffer) { // set key from secret value & set flag ec_prv_key.setS(buffer, dataOffset, blob_size); eckeys_flag |= (short) (0x0001 << key_nb);// set corresponding bit flag; + return (short)0; } /** @@ -887,7 +981,7 @@ private void ImportKey(APDU apdu, byte[] buffer) { * data: [ (option)HMAC-2FA(20b)] * return: none */ - private void ResetKey(APDU apdu, byte[] buffer) { + private short ResetKey(APDU apdu, byte[] buffer) { // check that PIN[0] has been entered previously if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); @@ -906,8 +1000,6 @@ private void ResetKey(APDU apdu, byte[] buffer) { // check 2FA if required if (needs_2FA){ short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (bytesLeft != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if (bytesLeft < (short)20) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); @@ -926,6 +1018,8 @@ private void ResetKey(APDU apdu, byte[] buffer) { // clear key & reset flag key.clearKey(); eckeys_flag &= (short) ~(0x0001 << key_nb);// reset corresponding bit flag; + + return (short)0; } /** @@ -938,16 +1032,14 @@ private void ResetKey(APDU apdu, byte[] buffer) { * data: none * return(SECP256K1): [coordx_size(2b) | pubkey_coordx | sig_size(2b) | sig] */ - private void getPublicKeyFromPrivate(APDU apdu, byte[] buffer) { + private short getPublicKeyFromPrivate(APDU apdu, byte[] buffer) { // check that PIN[0] has been entered previously if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); - short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (bytesLeft != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + byte key_nb = buffer[ISO7816.OFFSET_P1]; if ((key_nb < 0) || (key_nb >= MAX_NUM_KEYS)) ISOException.throwIt(SW_INCORRECT_P1); @@ -978,7 +1070,7 @@ private void getPublicKeyFromPrivate(APDU apdu, byte[] buffer) { // return x-coordinate of public key+signature // the client can recover full public-key from the signature or // by guessing the compression value () and verifying the signature... - apdu.setOutgoingAndSend((short) 0, (short)(2+coordx_size+2+sign_size)); + return (short)(2+coordx_size+2+sign_size); } /** @@ -993,7 +1085,7 @@ private void getPublicKeyFromPrivate(APDU apdu, byte[] buffer) { * data: [PIN_size(1b) | PIN | UBLK_size(1b) | UBLK] * return: none */ - private void CreatePIN(APDU apdu, byte[] buffer) { + private short CreatePIN(APDU apdu, byte[] buffer) { // check that PIN[0] has been entered previously if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); @@ -1004,19 +1096,17 @@ private void CreatePIN(APDU apdu, byte[] buffer) { if ((pin_nb < 0) || (pin_nb >= MAX_NUM_PINS) || (pins[pin_nb] != null)) ISOException.throwIt(SW_INCORRECT_P1); /* Allow pin lengths > 127 (useful at all ?) */ - short avail = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (apdu.setIncomingAndReceive() != avail) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); // At least 1 character for PIN and 1 for unblock code (+ lengths) - if (avail < 4) + if (bytesLeft < 4) ISOException.throwIt(SW_INVALID_PARAMETER); byte pin_size = buffer[ISO7816.OFFSET_CDATA]; - if (avail < (short) (1 + pin_size + 1)) + if (bytesLeft < (short) (1 + pin_size + 1)) ISOException.throwIt(SW_INVALID_PARAMETER); if (!CheckPINPolicy(buffer, (short) (ISO7816.OFFSET_CDATA + 1), pin_size)) ISOException.throwIt(SW_INVALID_PARAMETER); byte ucode_size = buffer[(short) (ISO7816.OFFSET_CDATA + 1 + pin_size)]; - if (avail != (short) (1 + pin_size + 1 + ucode_size)) + if (bytesLeft != (short) (1 + pin_size + 1 + ucode_size)) ISOException.throwIt(SW_INVALID_PARAMETER); if (!CheckPINPolicy(buffer, (short) (ISO7816.OFFSET_CDATA + 1 + pin_size + 1), ucode_size)) ISOException.throwIt(SW_INVALID_PARAMETER); @@ -1026,6 +1116,8 @@ private void CreatePIN(APDU apdu, byte[] buffer) { // Recycle variable pin_size pin_size = (byte) (ISO7816.OFFSET_CDATA + 1 + pin_size + 1); ublk_pins[pin_nb].update(buffer, pin_size, ucode_size); + + return (short)0; } /** @@ -1040,7 +1132,7 @@ private void CreatePIN(APDU apdu, byte[] buffer) { * data: [PIN] * return: none (throws an exception in case of wrong PIN) */ - private void VerifyPIN(APDU apdu, byte[] buffer) { + private short VerifyPIN(APDU apdu, byte[] buffer) { byte pin_nb = buffer[ISO7816.OFFSET_P1]; if ((pin_nb < 0) || (pin_nb >= MAX_NUM_PINS)) ISOException.throwIt(SW_INCORRECT_P1); @@ -1049,24 +1141,26 @@ private void VerifyPIN(APDU apdu, byte[] buffer) { ISOException.throwIt(SW_INCORRECT_P1); if (buffer[ISO7816.OFFSET_P2] != 0x00) ISOException.throwIt(SW_INCORRECT_P2); - short numBytes = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); + short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); /* * Here I suppose the PIN code is small enough to enter in the buffer * TODO: Verify the assumption and eventually adjust code to support * reading PIN in multiple read()s */ - if (numBytes != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); - if (!CheckPINPolicy(buffer, ISO7816.OFFSET_CDATA, (byte) numBytes)) + if (!CheckPINPolicy(buffer, ISO7816.OFFSET_CDATA, (byte) bytesLeft)) ISOException.throwIt(SW_INVALID_PARAMETER); - if (pin.getTriesRemaining() == (byte) 0x00) + byte triesRemaining = pin.getTriesRemaining(); + if (triesRemaining == (byte) 0x00) ISOException.throwIt(SW_IDENTITY_BLOCKED); - if (!pin.check(buffer, (short) ISO7816.OFFSET_CDATA, (byte) numBytes)) { + if (!pin.check(buffer, (short) ISO7816.OFFSET_CDATA, (byte) bytesLeft)) { LogoutIdentity(pin_nb); - ISOException.throwIt(SW_AUTH_FAILED); + ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining - 1)); } + // Actually register that PIN has been successfully verified. logged_ids |= (short) (0x0001 << pin_nb); + + return (short)0; } @@ -1080,7 +1174,7 @@ private void VerifyPIN(APDU apdu, byte[] buffer) { * data: [PIN_size(1b) | old_PIN | PIN_size(1b) | new_PIN ] * return: none (throws an exception in case of wrong PIN) */ - private void ChangePIN(APDU apdu, byte[] buffer) { + private short ChangePIN(APDU apdu, byte[] buffer) { /* * Here I suppose the PIN code is small enough that 2 of them enter in * the buffer TODO: Verify the assumption and eventually adjust code to @@ -1094,31 +1188,34 @@ private void ChangePIN(APDU apdu, byte[] buffer) { ISOException.throwIt(SW_INCORRECT_P1); if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); - short avail = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (apdu.setIncomingAndReceive() != avail) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); // At least 1 character for each PIN code - if (avail < 4) + if (bytesLeft < 4) ISOException.throwIt(SW_INVALID_PARAMETER); byte pin_size = buffer[ISO7816.OFFSET_CDATA]; - if (avail < (short) (1 + pin_size + 1)) + if (bytesLeft < (short) (1 + pin_size + 1)) ISOException.throwIt(SW_INVALID_PARAMETER); if (!CheckPINPolicy(buffer, (short) (ISO7816.OFFSET_CDATA + 1), pin_size)) ISOException.throwIt(SW_INVALID_PARAMETER); byte new_pin_size = buffer[(short) (ISO7816.OFFSET_CDATA + 1 + pin_size)]; - if (avail < (short) (1 + pin_size + 1 + new_pin_size)) + if (bytesLeft < (short) (1 + pin_size + 1 + new_pin_size)) ISOException.throwIt(SW_INVALID_PARAMETER); if (!CheckPINPolicy(buffer, (short) (ISO7816.OFFSET_CDATA + 1 + pin_size + 1), new_pin_size)) ISOException.throwIt(SW_INVALID_PARAMETER); - if (pin.getTriesRemaining() == (byte) 0x00) + + byte triesRemaining = pin.getTriesRemaining(); + if (triesRemaining == (byte) 0x00) ISOException.throwIt(SW_IDENTITY_BLOCKED); if (!pin.check(buffer, (short) (ISO7816.OFFSET_CDATA + 1), pin_size)) { LogoutIdentity(pin_nb); - ISOException.throwIt(SW_AUTH_FAILED); + ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining - 1)); } + pin.update(buffer, (short) (ISO7816.OFFSET_CDATA + 1 + pin_size + 1), new_pin_size); // JC specifies this resets the validated flag. So we do. logged_ids &= (short) ((short) 0xFFFF ^ (0x01 << pin_nb)); + + return (short)0; } /** @@ -1131,7 +1228,7 @@ private void ChangePIN(APDU apdu, byte[] buffer) { * data: [PUK] * return: none (throws an exception in case of wrong PUK) */ - private void UnblockPIN(APDU apdu, byte[] buffer) { + private short UnblockPIN(APDU apdu, byte[] buffer) { byte pin_nb = buffer[ISO7816.OFFSET_P1]; if ((pin_nb < 0) || (pin_nb >= MAX_NUM_PINS)) ISOException.throwIt(SW_INCORRECT_P1); @@ -1146,27 +1243,33 @@ private void UnblockPIN(APDU apdu, byte[] buffer) { ISOException.throwIt(SW_OPERATION_NOT_ALLOWED); if (buffer[ISO7816.OFFSET_P2] != 0x00) ISOException.throwIt(SW_INCORRECT_P2); - short numBytes = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); + short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); /* * Here I suppose the PIN code is small enough to fit into the buffer * TODO: Verify the assumption and eventually adjust code to support * reading PIN in multiple read()s */ - if (numBytes != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); - if (!CheckPINPolicy(buffer, ISO7816.OFFSET_CDATA, (byte) numBytes)) + if (!CheckPINPolicy(buffer, ISO7816.OFFSET_CDATA, (byte) bytesLeft)) ISOException.throwIt(SW_INVALID_PARAMETER); - if (!ublk_pin.check(buffer, ISO7816.OFFSET_CDATA, (byte) numBytes)) - ISOException.throwIt(SW_AUTH_FAILED); + byte triesRemaining = ublk_pin.getTriesRemaining(); + if (triesRemaining == (byte) 0x00) + ISOException.throwIt(SW_IDENTITY_BLOCKED); + if (!ublk_pin.check(buffer, ISO7816.OFFSET_CDATA, (byte) bytesLeft)) + ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining - 1)); + pin.resetAndUnblock(); + + return (short)0; } - private void LogOutAll() { + private short LogOutAll() { logged_ids = (short) 0x0000; // Nobody is logged in byte i; for (i = (byte) 0; i < MAX_NUM_PINS; i++) if (pins[i] != null) pins[i].reset(); + + return (short)0; } /** @@ -1179,7 +1282,7 @@ private void LogOutAll() { * data: none * return: [RFU(1b) | PIN_mask(1b)] */ - private void ListPINs(APDU apdu, byte[] buffer) { + private short ListPINs(APDU apdu, byte[] buffer) { // check that PIN[0] has been entered previously if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); @@ -1201,7 +1304,7 @@ private void ListPINs(APDU apdu, byte[] buffer) { // Fill the buffer Util.setShort(buffer, (short) 0, mask); // Send response - apdu.setOutgoingAndSend((short) 0, (short) 2); + return (short)2; } /** @@ -1213,9 +1316,9 @@ private void ListPINs(APDU apdu, byte[] buffer) { * p1: 0x00 * p2: 0x00 * data: none - * return: [versions(4b) | PIN0-PUK0-PIN1-PUK1 tries (4b) | needs2FA (1b) | is_seeded(1b) ] + * return: [versions(4b) | PIN0-PUK0-PIN1-PUK1 tries (4b) | needs2FA (1b) | is_seeded(1b) | setupDone(1b) | needs_secure_channel(1b)] */ - private void GetStatus(APDU apdu, byte[] buffer) { + private short GetStatus(APDU apdu, byte[] buffer) { // check that PIN[0] has been entered previously //if (!pins[0].isValidated()) // ISOException.throwIt(SW_UNAUTHORIZED); @@ -1224,16 +1327,24 @@ private void GetStatus(APDU apdu, byte[] buffer) { ISOException.throwIt(SW_INCORRECT_P1); if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); + short pos = (short) 0; buffer[pos++] = (byte) PROTOCOL_MAJOR_VERSION; // Major Card Edge Protocol version n. buffer[pos++] = (byte) PROTOCOL_MINOR_VERSION; // Minor Card Edge Protocol version n. buffer[pos++] = (byte) APPLET_MAJOR_VERSION; // Major Applet version n. buffer[pos++] = (byte) APPLET_MINOR_VERSION; // Minor Applet version n. // PIN/PUK remaining tries available - buffer[pos++] = pins[0].getTriesRemaining(); - buffer[pos++] = ublk_pins[0].getTriesRemaining(); - buffer[pos++] = pins[1].getTriesRemaining(); - buffer[pos++] = ublk_pins[1].getTriesRemaining(); + if (setupDone){ + buffer[pos++] = pins[0].getTriesRemaining(); + buffer[pos++] = ublk_pins[0].getTriesRemaining(); + buffer[pos++] = pins[1].getTriesRemaining(); + buffer[pos++] = ublk_pins[1].getTriesRemaining(); + } else { + buffer[pos++] = (byte) 0; + buffer[pos++] = (byte) 0; + buffer[pos++] = (byte) 0; + buffer[pos++] = (byte) 0; + } if (needs_2FA) buffer[pos++] = (byte)0x01; else @@ -1242,7 +1353,16 @@ private void GetStatus(APDU apdu, byte[] buffer) { buffer[pos++] = (byte)0x01; else buffer[pos++] = (byte)0x00; - apdu.setOutgoingAndSend((short) 0, pos); + if (setupDone) + buffer[pos++] = (byte)0x01; + else + buffer[pos++] = (byte)0x00; + if (needs_secure_channel) + buffer[pos++] = (byte)0x01; + else + buffer[pos++] = (byte)0x00; + + return pos; } /** @@ -1260,7 +1380,7 @@ private void GetStatus(APDU apdu, byte[] buffer) { * data: [seed_data (seed_size)] * return: [coordx_size(2b) | coordx | sig_size(2b) | sig] */ - private void importBIP32Seed(APDU apdu, byte[] buffer){ + private short importBIP32Seed(APDU apdu, byte[] buffer){ // check that PIN[0] has been entered previously if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); @@ -1271,8 +1391,6 @@ private void importBIP32Seed(APDU apdu, byte[] buffer){ ISOException.throwIt(SW_BIP32_INITIALIZED_SEED); short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (bytesLeft != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); // get seed bytesize (max 64 bytes) byte bip32_seedsize = buffer[ISO7816.OFFSET_P1]; @@ -1314,7 +1432,7 @@ private void importBIP32Seed(APDU apdu, byte[] buffer){ // the client can recover full public-key from the signature or // by guessing the compression value () and verifying the signature... // buffer= [coordx_size(2) | coordx | sigsize(2) | sig] - apdu.setOutgoingAndSend((short) 0, (short)(2+coordx_size+2+sign_size)); + return (short)(2+coordx_size+2+sign_size); } /** @@ -1328,11 +1446,9 @@ private void importBIP32Seed(APDU apdu, byte[] buffer){ * data: [PIN | optional-hmac(20b)] * return: (none) */ - private void resetBIP32Seed(APDU apdu, byte[] buffer){ + private short resetBIP32Seed(APDU apdu, byte[] buffer){ short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (bytesLeft != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); // check provided PIN byte pin_size= buffer[ISO7816.OFFSET_P1]; @@ -1341,11 +1457,13 @@ private void resetBIP32Seed(APDU apdu, byte[] buffer){ ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if (!CheckPINPolicy(buffer, ISO7816.OFFSET_CDATA, pin_size)) ISOException.throwIt(SW_INVALID_PARAMETER); - if (pin.getTriesRemaining() == (byte) 0x00) + + byte triesRemaining = pin.getTriesRemaining(); + if (triesRemaining == (byte) 0x00) ISOException.throwIt(SW_IDENTITY_BLOCKED); if (!pin.check(buffer, (short) ISO7816.OFFSET_CDATA, (byte) pin_size)) { LogoutIdentity((byte)0x00); - ISOException.throwIt(SW_AUTH_FAILED); + ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining - 1)); } // check if seeded @@ -1378,7 +1496,7 @@ private void resetBIP32Seed(APDU apdu, byte[] buffer){ Secp256k1.setCommonCurveParameters(bip32_authentikey);// keep public params! Util.arrayFillNonAtomic(authentikey_pubkey, (short)0, (short)(2*BIP32_KEY_SIZE+1), (byte)0x00); LogOutAll(); - return; + return (short)0; } /** @@ -1392,7 +1510,7 @@ private void resetBIP32Seed(APDU apdu, byte[] buffer){ * data: none * return: [coordx_size(2b) | coordx | sig_size(2b) | sig] */ - private void getBIP32AuthentiKey(APDU apdu, byte[] buffer){ + private short getBIP32AuthentiKey(APDU apdu, byte[] buffer){ // check that PIN[0] has been entered previously if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); @@ -1415,7 +1533,7 @@ private void getBIP32AuthentiKey(APDU apdu, byte[] buffer){ // the client can recover full public-key from the signature or // by guessing the compression value () and verifying the signature... // buffer= [coordx_size(2) | coordx | sigsize(2) | sig] - apdu.setOutgoingAndSend((short) 0, (short)(coordx_size+sign_size+4)); + return (short)(coordx_size+sign_size+4); } /** @@ -1433,7 +1551,7 @@ private void getBIP32AuthentiKey(APDU apdu, byte[] buffer){ * * returns: none */ - private void setBIP32AuthentikeyPubkey(APDU apdu, byte[] buffer){ + private short setBIP32AuthentikeyPubkey(APDU apdu, byte[] buffer){ // check that PIN[0] has been entered previously if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); @@ -1443,7 +1561,7 @@ private void setBIP32AuthentikeyPubkey(APDU apdu, byte[] buffer){ pos += (short) 2; Util.setShort(buffer, pos, bip32_om.nb_elem_used); // number of slot used pos += (short) 2; - apdu.setOutgoingAndSend((short) 0, pos); + return pos; }// end of setBIP32AuthentikeyPubkey /** @@ -1464,7 +1582,7 @@ private void setBIP32AuthentikeyPubkey(APDU apdu, byte[] buffer){ * returns: [chaincode(32b) | coordx_size(2b) | coordx | sig_size(2b) | sig | sig_size(2b) | sig2] * * */ - private void getBIP32ExtendedKey(APDU apdu, byte[] buffer){ + private short getBIP32ExtendedKey(APDU apdu, byte[] buffer){ // check that PIN[0] has been entered previously if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); @@ -1475,8 +1593,6 @@ private void getBIP32ExtendedKey(APDU apdu, byte[] buffer){ // input short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (bytesLeft != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); byte bip32_depth = buffer[ISO7816.OFFSET_P1]; if ((bip32_depth < 0) || (bip32_depth > MAX_BIP32_DEPTH) ) @@ -1623,7 +1739,7 @@ private void getBIP32ExtendedKey(APDU apdu, byte[] buffer){ // return x-coordinate of public key+signatures // the client can recover full public-key by guessing the compression value () and verifying the signature... // buffer=[chaincode(32) | coordx_size(2) | coordx | sign_size(2) | self-sign | sign_size(2) | auth_sign] - apdu.setOutgoingAndSend((short) 0, (short)(BIP32_KEY_SIZE+coordx_size+sign_size+sign_size2+6)); + return (short)(BIP32_KEY_SIZE+coordx_size+sign_size+sign_size2+6); }// end of getBip32ExtendedKey() @@ -1642,7 +1758,7 @@ private void getBIP32ExtendedKey(APDU apdu, byte[] buffer){ * [ coordy_size(2b) | coordy] * returns: none */ - private void setBIP32ExtendedPubkey(APDU apdu, byte[] buffer){ + private short setBIP32ExtendedPubkey(APDU apdu, byte[] buffer){ // check that PIN[0] has been entered previously if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); @@ -1652,7 +1768,7 @@ private void setBIP32ExtendedPubkey(APDU apdu, byte[] buffer){ pos += (short) 2; Util.setShort(buffer, pos, bip32_om.nb_elem_used); // number of slot used pos += (short) 2; - apdu.setOutgoingAndSend((short) 0, pos); + return pos; }// end of setBIP32ExtendedPubkey /** @@ -1669,7 +1785,7 @@ private void setBIP32ExtendedPubkey(APDU apdu, byte[] buffer){ * return(finalize): [sig] * */ - private void signMessage(APDU apdu, byte[] buffer){ + private short signMessage(APDU apdu, byte[] buffer){ // check that PIN[0] has been entered previously if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); @@ -1682,8 +1798,6 @@ private void signMessage(APDU apdu, byte[] buffer){ if (p2 <= (byte) 0x00 || p2 > (byte) 0x03) ISOException.throwIt(SW_INCORRECT_P2); short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (bytesLeft != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); // check whether the seed is initialized if (key_nb==(byte)0xFF && !bip32_seeded) @@ -1722,7 +1836,7 @@ else if (bytesLeft==(short)4){ sha256.reset(); sha256.update(recvBuffer, (short) 0, recvOffset); sign_flag= true; // set flag - break; + return (short)0; // update (optionnal) case OP_PROCESS: @@ -1734,7 +1848,7 @@ else if (bytesLeft==(short)4){ chunk_size=Util.getShort(buffer, offset); offset+=2; sha256.update(buffer, (short) offset, chunk_size); - break; + return (short)0; // final case OP_FINALIZE: @@ -1777,12 +1891,16 @@ else if (bytesLeft==(short)4){ sigECDSA.init(key, Signature.MODE_SIGN); } short sign_size= sigECDSA.sign(recvBuffer, (short)0, (short)32, buffer, (short)0); - apdu.setOutgoingAndSend((short) 0, sign_size); - break; - } + return sign_size; + + } //end switch + + return (short)0; } /** + * DEPRECATED - the generic signMessage() should be used instead! + * * This function signs short Bitcoin message using std or Bip32 extended key in 1 APDU * * ins: 0x72 @@ -1793,71 +1911,69 @@ else if (bytesLeft==(short)4){ * return: [sig] * */ - private void signShortMessage(APDU apdu, byte[] buffer){ - // check that PIN[0] has been entered previously - if (!pins[0].isValidated()) - ISOException.throwIt(SW_UNAUTHORIZED); - - byte key_nb = buffer[ISO7816.OFFSET_P1]; - if ( (key_nb!=(byte)0xFF) && ((key_nb < 0)||(key_nb >= MAX_NUM_KEYS)) ) // debug!! - ISOException.throwIt(SW_INCORRECT_P1); - if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) - ISOException.throwIt(SW_INCORRECT_P2); - short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (bytesLeft != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); - - // check whether the seed is initialized - if (key_nb==(byte)0xFF && !bip32_seeded) - ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED); - - // copy message header to tmp buffer - Util.arrayCopyNonAtomic(BITCOIN_SIGNED_MESSAGE_HEADER, (short)0, recvBuffer, (short)0, (short)BITCOIN_SIGNED_MESSAGE_HEADER.length); - short recvOffset= (short)BITCOIN_SIGNED_MESSAGE_HEADER.length; - - // buffer data = [2-byte size | n-byte message to sign] - short offset= (short)ISO7816.OFFSET_CDATA; - short msgSize= Util.getShort(buffer, offset); - recvOffset+= Biginteger.encodeShortToVarInt(msgSize, recvBuffer, recvOffset); - offset+=2; - bytesLeft-=2; - Util.arrayCopyNonAtomic(buffer, offset, recvBuffer, recvOffset, msgSize); - offset+= msgSize; - recvOffset+= msgSize; - bytesLeft-= msgSize; - - // hash SHA-256 - sha256.reset(); - sha256.doFinal(recvBuffer, (short) 0, recvOffset, recvBuffer, (short) 0); - - // check 2FA if required - if(needs_2FA){ - if (bytesLeft<(short)20) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); - // hmac of 64-bytes msg: (sha256(btcheader+msg) | 32bytes 0xBB-padding) - Util.arrayFillNonAtomic(recvBuffer, (short)32, (short)32, (byte)0xBB); - HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64); - if (Util.arrayCompare(buffer, offset, recvBuffer, (short)64, (short)20)!=0) - ISOException.throwIt(SW_SIGNATURE_INVALID); - } - - // set key & sign - if (key_nb==(byte)0xFF) - sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN); - else{ - Key key= eckeys[key_nb]; - // check type and size - if ((key == null) || !key.isInitialized()) - ISOException.throwIt(SW_INCORRECT_P1); - if (key.getType() != KeyBuilder.TYPE_EC_FP_PRIVATE) - ISOException.throwIt(SW_INCORRECT_ALG); - if (key.getSize()!= LENGTH_EC_FP_256) - ISOException.throwIt(SW_INCORRECT_ALG); - sigECDSA.init(key, Signature.MODE_SIGN); - } - short sign_size= sigECDSA.sign(recvBuffer, (short)0, (short)32, buffer, (short)0); - apdu.setOutgoingAndSend((short) 0, sign_size); - } +// private short signShortMessage(APDU apdu, byte[] buffer){ +// // check that PIN[0] has been entered previously +// if (!pins[0].isValidated()) +// ISOException.throwIt(SW_UNAUTHORIZED); +// +// byte key_nb = buffer[ISO7816.OFFSET_P1]; +// if ( (key_nb!=(byte)0xFF) && ((key_nb < 0)||(key_nb >= MAX_NUM_KEYS)) ) +// ISOException.throwIt(SW_INCORRECT_P1); +// if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) +// ISOException.throwIt(SW_INCORRECT_P2); +// short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); +// +// // check whether the seed is initialized +// if (key_nb==(byte)0xFF && !bip32_seeded) +// ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED); +// +// // copy message header to tmp buffer +// Util.arrayCopyNonAtomic(BITCOIN_SIGNED_MESSAGE_HEADER, (short)0, recvBuffer, (short)0, (short)BITCOIN_SIGNED_MESSAGE_HEADER.length); +// short recvOffset= (short)BITCOIN_SIGNED_MESSAGE_HEADER.length; +// +// // buffer data = [2-byte size | n-byte message to sign] +// short offset= (short)ISO7816.OFFSET_CDATA; +// short msgSize= Util.getShort(buffer, offset); +// recvOffset+= Biginteger.encodeShortToVarInt(msgSize, recvBuffer, recvOffset); +// offset+=2; +// bytesLeft-=2; +// Util.arrayCopyNonAtomic(buffer, offset, recvBuffer, recvOffset, msgSize); +// offset+= msgSize; +// recvOffset+= msgSize; +// bytesLeft-= msgSize; +// +// // hash SHA-256 +// sha256.reset(); +// sha256.doFinal(recvBuffer, (short) 0, recvOffset, recvBuffer, (short) 0); +// +// // check 2FA if required +// if(needs_2FA){ +// if (bytesLeft<(short)20) +// ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); +// // hmac of 64-bytes msg: (sha256(btcheader+msg) | 32bytes 0xBB-padding) +// Util.arrayFillNonAtomic(recvBuffer, (short)32, (short)32, (byte)0xBB); +// HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64); +// if (Util.arrayCompare(buffer, offset, recvBuffer, (short)64, (short)20)!=0) +// ISOException.throwIt(SW_SIGNATURE_INVALID); +// } +// +// // set key & sign +// if (key_nb==(byte)0xFF) +// sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN); +// else{ +// Key key= eckeys[key_nb]; +// // check type and size +// if ((key == null) || !key.isInitialized()) +// ISOException.throwIt(SW_INCORRECT_P1); +// if (key.getType() != KeyBuilder.TYPE_EC_FP_PRIVATE) +// ISOException.throwIt(SW_INCORRECT_ALG); +// if (key.getSize()!= LENGTH_EC_FP_256) +// ISOException.throwIt(SW_INCORRECT_ALG); +// sigECDSA.init(key, Signature.MODE_SIGN); +// } +// short sign_size= sigECDSA.sign(recvBuffer, (short)0, (short)32, buffer, (short)0); +// return sign_size; +// } /** * This function parses a raw transaction and returns the corresponding double SHA-256 @@ -1873,7 +1989,7 @@ private void signShortMessage(APDU apdu, byte[] buffer){ * where: * needs_confirm is 0x01 if a hmac-sha1 of the hash must be provided for tx signing */ - private void ParseTransaction(APDU apdu, byte[] buffer){ + private short ParseTransaction(APDU apdu, byte[] buffer){ // check that PIN[0] has been entered previously if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); @@ -1932,8 +2048,7 @@ else if (result == Transaction.RESULT_MORE) { // offset+=2; // } - apdu.setOutgoingAndSend((short)0, offset); - return; + return offset; } else if (result == Transaction.RESULT_FINISHED) { @@ -2003,10 +2118,10 @@ else if (result == Transaction.RESULT_FINISHED) { // buffer= [tx_hash(32) | sign_size(2) | signature | tx context(20 - 46)] //deprecated // buffer= [(hash_size+2)(2b) | tx_hash(32b) | need2fa(2b) | sig_size(2b) | sig(sig_size) | txcontext] Transaction.resetTransaction(); - apdu.setOutgoingAndSend((short)0, offset); + return offset; } - return; + return 0; //should not happen! } /** @@ -2023,7 +2138,7 @@ else if (result == Transaction.RESULT_FINISHED) { * return: [sig ] * */ - private void SignTransaction(APDU apdu, byte[] buffer){ + private short SignTransaction(APDU apdu, byte[] buffer){ // check that PIN[0] has been entered previously if (!pins[0].isValidated()) ISOException.throwIt(SW_UNAUTHORIZED); @@ -2033,8 +2148,6 @@ private void SignTransaction(APDU apdu, byte[] buffer){ ISOException.throwIt(SW_INCORRECT_P1); short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); - if (bytesLeft != apdu.setIncomingAndReceive()) - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if (bytesLeftBLOCKSIZE || key_length<0){ - ISOException.throwIt(SW_UNSUPPORTED_KEYSIZE); // don't accept keys bigger than block size + ISOException.throwIt(CardEdge.SW_HMAC_UNSUPPORTED_KEYSIZE); // don't accept keys bigger than block size } if (message_length>MAXMSGSIZE || message_length<0){ - ISOException.throwIt(SW_UNSUPPORTED_MSGSIZE); + ISOException.throwIt(CardEdge.SW_HMAC_UNSUPPORTED_MSGSIZE); } // compute inner hash diff --git a/src/org/satochip/applet/HmacSha512.java b/src/org/satochip/applet/HmacSha512.java index 18797c4..c57b8fa 100644 --- a/src/org/satochip/applet/HmacSha512.java +++ b/src/org/satochip/applet/HmacSha512.java @@ -31,9 +31,6 @@ public class HmacSha512 { public static final short BLOCKSIZE=128; // 128 bytes public static final short HASHSIZE=64; - private static final short SW_UNSUPPORTED_KEYSIZE = (short) 0x9c0E; - private static final short SW_UNSUPPORTED_MSGSIZE = (short) 0x9c0F; - private static final short SW_UNSUPPORTED_FEATURE = (short) 0x9c05; private static byte[] data; private static MessageDigest sha512; @@ -43,7 +40,7 @@ public static void init(byte[] tmp){ try { sha512 = MessageDigest.getInstance(MessageDigest.ALG_SHA_512, false); } catch (CryptoException e) { - ISOException.throwIt(SW_UNSUPPORTED_FEATURE);// unsupported feature => use a more recent card! + ISOException.throwIt(CardEdge.SW_UNSUPPORTED_FEATURE); // unsupported feature => use a more recent card! } } @@ -52,10 +49,10 @@ public static short computeHmacSha512(byte[] key, short key_offset, short key_le byte[] mac, short mac_offset){ if (key_length>BLOCKSIZE || key_length<0){ - ISOException.throwIt(SW_UNSUPPORTED_KEYSIZE); // don't accept keys bigger than block size + ISOException.throwIt(CardEdge.SW_HMAC_UNSUPPORTED_KEYSIZE); // don't accept keys bigger than block size } if (message_length>HASHSIZE || message_length<0){ - ISOException.throwIt(SW_UNSUPPORTED_MSGSIZE); // don't accept message bigger than block size (should be sufficient for BIP32) + ISOException.throwIt(CardEdge.SW_HMAC_UNSUPPORTED_MSGSIZE); // don't accept message bigger than block size (should be sufficient for BIP32) } // compute inner hash