Commit 6dc617aa authored by Cypres TAC's avatar Cypres TAC
Browse files

Merge branch 'js-compatibility' into 'master'

init JS

See merge request !6
parents f5d51eb1 f7621464
Pipeline #226367 passed with stages
in 3 minutes and 20 seconds
#include <stdbool.h>
#include "ecies.h"
#include "hash/sha256.h"
#include "libec.h"
......@@ -7,7 +8,7 @@ static const ec_str_params *secp256r1;
static ec_params curve;
static prj_pt Q;
static void compress_point(prj_pt *P, uint8_t *buf);
static void compress_point(prj_pt *P, uint8_t *buf, bool x_coord);
static void kdf(uint8_t *in, uint8_t len, uint8_t *out, uint8_t out_len);
void ecies_init(void)
......@@ -19,7 +20,7 @@ void ecies_init(void)
#include <stdio.h>
int32_t ecies_encode(uint8_t *key, uint8_t *msg, uint8_t clear_len, uint8_t crypt_len)
{
#define KDF_INPUT_SIZE (2 * (32 + 1) + 4) // 2 compressed points + 1 32-bit integer
#define KDF_INPUT_SIZE (2 * 32 + 1 + 4) // 1 compressed point + 1 X coordinate + 1 32-bit integer
nn r;
prj_pt C0, S;
uint8_t buf[255]; // Must contain at least crypt_len bytes
......@@ -67,8 +68,8 @@ int32_t ecies_encode(uint8_t *key, uint8_t *msg, uint8_t clear_len, uint8_t cryp
prj_pt_unique(&S, &S);
// Compute K = KDF(E(C0) | E(S))
compress_point(&C0, buf);
compress_point(&S, &(buf[33]));
compress_point(&C0, buf, false);
compress_point(&S, &(buf[33]), true);
kdf(buf, KDF_INPUT_SIZE, K, sizeof(K));
// append compressed C0 at the end of the output buffer
......@@ -92,20 +93,29 @@ int32_t ecies_encode(uint8_t *key, uint8_t *msg, uint8_t clear_len, uint8_t cryp
return 0;
}
static void compress_point(prj_pt *P, uint8_t *buf)
static void compress_point(prj_pt *P, uint8_t *buf, bool x_coord)
{
uint8_t tmp[64];
uint8_t i;
uint8_t i, offset;
prj_pt_export_to_aff_buf(P, tmp, 2 * BYTECEIL(curve.ec_fp.p_bitlen));
for(i = 0; i < sizeof(tmp) / 2; i++)
if(x_coord)
{
offset = 0;
}
else
{
buf[i + 1] = tmp[i];
// ANSI X9.62 encoding
buf[0] = (tmp[sizeof(tmp) - 1] % 2) | 0x2;
offset = 1;
}
// ANSI X9.62 encoding
buf[0] = (tmp[sizeof(tmp) - 1] % 2) | 0x2;
for(i = 0; i < sizeof(tmp) / 2; i++)
{
buf[i + offset] = tmp[i];
}
}
static void kdf(uint8_t *in, uint8_t len, uint8_t *out, uint8_t out_len)
......
......@@ -171,7 +171,8 @@ public class CleaEciesEncoder {
/* Generate secret S */
ECPoint S_Q = ecPublicKey.getQ().multiply(r); // S = r * D(PK_HA)
byte S[] = S_Q.getEncoded(true); // E(S)
//byte S[] = S_Q.getEncoded(true); // E(S)
byte S[] = S_Q.normalize().getAffineXCoord().getEncoded(); // S.X
/* Generate AES key using KDF1 */
KDF1BytesGenerator kdf = new KDF1BytesGenerator(new SHA256Digest());
......@@ -218,8 +219,9 @@ public class CleaEciesEncoder {
/* Generate secret S */
ECPoint S_Q = C0_Q.multiply(privateKey.getD()); // S = x * D(C0)
byte S[] = S_Q.getEncoded(true); // E(S)
//byte S[] = S_Q.getEncoded(true); // E(S)
byte S[] = S_Q.normalize().getAffineXCoord().getEncoded(); // S.X
/* Generate AES key using KDF1 */
KDF1BytesGenerator kdf = new KDF1BytesGenerator(new SHA256Digest());
byte[] key = new byte[32]; // 256-bits AES key
......
......@@ -13,6 +13,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import java.util.UUID;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.util.encoders.Hex;
......@@ -68,6 +69,7 @@ class LocationSpecificPartTest {
assertThat(decrypted).containsExactly(cleaEciesEncoder.concat(header, message));
}
@Test
public void testEncodinsAndDecodingOfALocationMessage() throws CleaEncryptionException {
......@@ -114,7 +116,8 @@ class LocationSpecificPartTest {
assertThat(encryptedLocationSpecificPart).isNotNull();
assertThat(encryptedLocationSpecificPart).isEqualTo(0); // TODO replace by expected value
}
@Test
public void testEncodingAndDecodingOfALocationSpecificPart() throws CleaEncryptionException {
int periodStartTime = TimeUtils.hourRoundedCurrentTimeTimestamp32();
......@@ -148,6 +151,7 @@ class LocationSpecificPartTest {
assertThat(lsp.getEncryptedLocationContactMessage()).isNotNull();
}
@Test
public void testEncodingAndDecodingOfALocationSpecificPartWithoutLocationContact() throws CleaEncryptionException {
int periodStartTime = TimeUtils.hourRoundedCurrentTimeTimestamp32();
......@@ -175,15 +179,16 @@ class LocationSpecificPartTest {
assertThat(decodedLsp).isEqualTo(lsp);
}
@Disabled("compute new String values with c lib")
@Test
public void testDecryptionFromMessageEncryptedByCleaCLibrary() throws NoSuchAlgorithmException, InvalidKeySpecException, IllegalStateException, InvalidCipherTextException, IOException {
/* EC private key from C package */
final String privateKey = "7422c9883c3f6c5ac70c0a08a24b5d524f36edefa04f599e316fa23ef74a4a0f";
final String privateKey = "34af7f978c5a17772867d929e0b800dd2db74608322d73f2f0cfd19cdcaeccc8";
/* message encrypted, from C package */
final String cipherText_S = "2f1376e97378d2e19a5d3b15cf4ff1802f971e4dc357f727098b5e0ba114318f3cdc0af35728c2ccf24641f879110fbc63f7dc9c002247d9073fc46f4e6bb2a85ae19c8e2db9de9071b1887bfe0388c333e8b8d89271f70406a86bfab0d44fc54641f1dda61292a5fe32ce128eb4";
final String cipherText_S = "7d1bbfb6cad6c2e862a7aead7da27fb814cff9dbda33b7277d4bf507f04e5b901d7f4e09ff48b0f2ba8aebb4640d074f1daef17524b6f319f30e6548277e0575039d2560cfcf45d6873e4bf33a03c7c598bb6f1b7c3c4c5873ad35ff5be33e18a815af751d8cde256c7bd8318f04";
/* message in plain text, from C package */
final String plainText_S = "2f1376e97378d2e19a5d3b15cf4ff1802ffd9be701f74d6f15d4a3f22fbcd296eb6bdf21ba03410a01f816b91cb2c74d709544912811e8a867dd44b8d6";
final String plainText_S = "7D1BBFB6CAD6C2E862A7AEAD7DA27FB8149F7213093CEDBBE66356550296A37DD18077E8646185EA2EA0EAFE88630F8C861A2E05F35BB2D863A28841CF";
/* String -> bytes array */
byte[] cipherText = Hex.decode(cipherText_S);
......@@ -195,7 +200,8 @@ class LocationSpecificPartTest {
assertThat(decryptedMessage).containsExactly(plainTextMessage);
}
@Test
public void testQrCodeGeneration() throws Exception {
int periodStartTime = TimeUtils.hourRoundedCurrentTimeTimestamp32();
......@@ -243,27 +249,37 @@ class LocationSpecificPartTest {
// TODO: I do not understand this test. Result of the decoding is not used
System.out.println("Qrcode size C=" + lsp64C.length() + " Java=" + lsp64J.length());
}
@Test
public void testDecodingOfLocationSpecificPartInBase64() throws CleaEncryptionException {
final String lsp_base64 = "AJi9dKxk4aXcRhF9lIIiGchvIbwtd2BE72nelq4/+uF0T0hE/GA0hFpEpuhVi+Xla8irZbGRmcDIfMqs0e8j/eChcYTeHo+bjWyN2GsHo+F5F46o0cM0IWuw/1MgctXYFCUw53zPL2Cs1ERN3HTpxnL9us2y//P+r8qV39YnmjFUj61Rlrosk2r81NO6BQImmg5sSV31rOTWXNrUwNQSTmXki0E+hfLgi9aMeWMnXQ==";
final String lsp_base64 = "APK57CdeWirVk+5bqzle+Fr9ieDlSpYfYkbgTC8zxsGx0bTy9VQq9GzOEqK7YSx5DdZsh5tZ9DT0OLH6MMveQXUky80xLbXkV3glHS+4m1bF2YTnzHNUppxMcb/IpKVoSnxY4pCA5l13gl9Jlkvkl5HIHl5Lv3FmWBZ6ScK4Zuj/VJFSjKp3/+CignJcHwNR6Lz5l+R6EgDugCueAyh9HuiyKrb4ntnGEMeit59btg==";
final String servertAuthoritySecretKey = "34af7f978c5a17772867d929e0b800dd2db74608322d73f2f0cfd19cdcaeccc8";
// final String SK_MCTA = "3108f08b1485adb6f72cfba1b55c7484c906a2a3a0a027c78dcd991ca64c97bd";
LocationSpecificPartDecoder decoder = new LocationSpecificPartDecoder(servertAuthoritySecretKey);
LocationSpecificPart lsp = decoder.decrypt(lsp_base64);
// TODO: add assertions
// assertThat(lsp.getCountryCode()).isEqualTo(??);
assertThat(lsp.isStaff()).isEqualTo(false);
assertThat(lsp.getCountryCode()).isEqualTo(492);
assertThat(lsp.getLocationTemporaryPublicId()).isEqualTo(UUID.fromString("f2b9ec27-5e5a-2ad5-93ee-5bab395ef85a"));
assertThat(lsp.getCompressedPeriodStartTime()).isEqualTo(1062479);
// TODO: PROBLEM TO SOLVE
// assertThat(lsp.getQrCodeValidityStartTime()).isEqualTo(3824926044);
assertThat(lsp.getPeriodDuration()).isEqualTo(3);
assertThat(lsp.getQrCodeRenewalIntervalExponentCompact()).isEqualTo(5);
assertThat(lsp.getVenueType()).isEqualTo(12);
assertThat(lsp.getVenueCategory1()).isEqualTo(0);
assertThat(lsp.getVenueCategory2()).isEqualTo(0);
System.out.println(lsp);
}
@Test
public void testLocationSpecificPartBase64EciesDecryption() throws NoSuchAlgorithmException, InvalidKeySpecException, IllegalStateException, InvalidCipherTextException, IOException {
/* EC private key from C package */
final String privateKey = "34af7f978c5a17772867d929e0b800dd2db74608322d73f2f0cfd19cdcaeccc8";
final String privateKey = "3108f08b1485adb6f72cfba1b55c7484c906a2a3a0a027c78dcd991ca64c97bd";
/* message encrypted, from C package */
final String cipherTextBase64 = "AA3sinpPVKOpedLxzpMbgS3G4d4Up1vDcNQ28fZKB8cBnBjNYV0bPGoaBxnFFab/iM56uzSoDQ0i+N5B9shw5bBmutRONcWQBhNr1ug/0sZ62UaiWZjqfYDmpANJHfv0Kao3DUJvPLHep7N9uNlOywOhHISXoFsCNvikOv8o3hN9j7vbtW6xjILpbQP01gNtiNqliKDGWCSo/g4xlFjlbiWo4E1bL5UiWHAS9KYj8g==";
final String cipherTextBase64 = "AHHp6U8wrVQuWDomdZfDS0BHC45n72pzlmAhqE7AZp3hTWt2cuUOJ78nNeZSJCrpjpl3glMI49yjLEoIi73wqsSbja1sMH0XzuNoAssCV53wTItE3Nxg+J3FI78/W6uWD8IU+dn0YEroJwH2y1g=";
/* String -> bytes array */
byte[] cipherText = Base64.getDecoder().decode(cipherTextBase64);
......
var t_periodStart = get_ntp_utc(true);
var LTId = new Uint8Array(16);
var LTKey = new Uint8Array(32);
// RPG
var ct_periodStart;
var t_qrStart;
async function clea_start_new_period(config) {
t_periodStart = get_ntp_utc(true);
// Compute LTKey
var tmp = new Uint8Array(64);
tmp.set(config.SK_L, 0);
tmp[60] = (t_periodStart >> 24) & 0xFF;
tmp[61] = (t_periodStart >> 16) & 0xFF;
tmp[62] = (t_periodStart >> 8) & 0xFF;
tmp[63] = t_periodStart;
LTKey = await crypto.subtle.digest("SHA-256", tmp)
// Compute LTId
var one = new Uint8Array(1);
one[0] = 0x31; // '1'
var key = await crypto.subtle.importKey(
"raw",
LTKey, {
name: "HMAC",
hash: "SHA-512"
},
true,
["sign"]
);
LTId = new Uint8Array(await crypto.subtle.sign("HMAC", key, one), 0, 16); // HMAC-SHA256-128
return clea_renew_qrcode(config);
}
async function clea_renew_qrcode(config) {
const CLEAR_HEADER_SIZE = 17;
const MSG_SIZE = 44;
const LOC_MSG_SIZE = 16;
const TAG_AND_KEY = 49;
t_qrStart = get_ntp_utc(false);
ct_periodStart = t_periodStart / 3600;
var header = new Uint8Array(CLEAR_HEADER_SIZE);
var msg = new Uint8Array(MSG_SIZE + (config.locContactMsg ? LOC_MSG_SIZE + TAG_AND_KEY : 0));
var loc_msg = new Uint8Array(LOC_MSG_SIZE);
// Fill header
header[0] = ((config.version & 0x7) << 5) | ((config.qrType & 0x7) << 2);
header.set(LTId, 1);
// Fill message
msg[0] = ((config.staff & 0x1) << 7) | (config.locContactMsg ? 0x40 : 0) | ((config.countryCode & 0xFC0) >>> 6);
msg[1] = ((config.countryCode & 0x3F) << 2) | ((config.CRIexp & 0x18) >> 3);
msg[2] = ((config.CRIexp & 0x7) << 5) | (config.venueType & 0x1F);
msg[3] = ((config.venueCategory1 & 0xF) << 4) | (config.venueCategory2 & 0xF);
msg[4] = config.periodDuration;
msg[5] = (ct_periodStart >> 16) & 0xFF; // multi-byte numbers are stored with the big endian convention as required by the specification
msg[6] = (ct_periodStart >> 8) & 0xFF;
msg[7] = ct_periodStart & 0xFF;
msg[8] = (t_qrStart >> 24) & 0xFF;
msg[9] = (t_qrStart >> 16) & 0xFF;
msg[10] = (t_qrStart >> 8) & 0xFF;
msg[11] = t_qrStart & 0xFF;
msg.set(LTKey, 12);
if (config.locContactMsg) {
const phone = parse_bcd(config.locContactMsg.locationPhone, 8);
loc_msg.set(phone, 0);
const pin = parse_bcd(config.locContactMsg.locationPin, 4);
loc_msg.set(pin, 8);
encrypted_loc_msg = await encrypt(new Uint8Array(0), loc_msg, config.PK_MCTA);
msg.set(new Uint8Array(encrypted_loc_msg), 44);
}
output = await encrypt(header, msg, config.PK_SA);
// Convert output to Base64
return btoa((Array.from(new Uint8Array(output))).map(ch => String.fromCharCode(ch)).join(''));
}
async function encrypt(header, message, publicKey) {
// Step 1: Import the publicKey Q
var ECPubKey = await crypto.subtle.importKey(
"raw",
publicKey, {
name: "ECDH",
namedCurve: "P-256"
},
true,
[]
);
// Step 2: Generate a transient EC key pair (r, C0 = rG)
var EcKeyPair = await crypto.subtle.generateKey({
name: "ECDH",
namedCurve: "P-256"
},
true,
["deriveKey", "deriveBits"]
);
// Step 3: Export C0 compressed
const C0 = await crypto.subtle.exportKey(
"raw",
EcKeyPair.publicKey
);
C0_Q = ecdh_raw_pubkey_compressed(new Uint8Array(C0));
print_buf("C0", C0_Q);
// Step 4: Generate a shared secret (S = rQ)
var S = await crypto.subtle.deriveBits({
name: "ECDH",
namedCurve: "P-256",
public: ECPubKey
},
EcKeyPair.privateKey,
256
);
print_buf("S", S);
// Step5: Compute the AES encryption key K = KDF1(C0 | S)
var tmp = concatBuffer(concatBuffer(C0_Q, S), new ArrayBuffer(4));
var kdf1 = await crypto.subtle.digest("SHA-256", tmp)
print_buf("C0 | S | 0x00000000", tmp);
print_buf("KDF1", kdf1)
var derivedKey = await crypto.subtle.importKey(
"raw",
kdf1,
"AES-GCM",
false,
["encrypt"]
);
// Step6: Encrypt the data
const iv = new Uint8Array([0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb]);
var encoded = await crypto.subtle.encrypt({
name: "AES-GCM",
iv: iv,
additionalData: header,
tagLength: 128
},
derivedKey,
message);
// Step7: Concatenation
const out_msg = concatBuffer(header, encoded);
const output = concatBuffer(out_msg, C0_Q);
print_buf("output", output);
return output;
}
function get_ntp_utc(round) {
const ONE_HOUR_IN_MS = 3600000;
var t = Date.now();
if (round) {
var th = Math.floor(t / ONE_HOUR_IN_MS); // Number of hours since the epoch
var rem = t % ONE_HOUR_IN_MS; // Number of ms since the last round hour
// Round the hour, i.e. if we are closer to the next round
// hour than the last one, round to the next hour
if (rem > ONE_HOUR_IN_MS / 2) {
th++;
}
t = th * 3600;
} else {
t /= 1000;
}
// Convert to hour and add the shift from UNIX epoch to NTP UTC
return Math.floor(t) + 2208988800;
}
/**
* Creates a new Uint8Array based on two different ArrayBuffers
*
* @private
* @param {ArrayBuffers} buf1 The first buffer.
* @param {ArrayBuffers} buf2 The second buffer.
* @return {ArrayBuffers} The new ArrayBuffer created out of the two.
*/
function concatBuffer(buf1, buf2) {
var out = new Uint8Array(buf1.byteLength + buf2.byteLength);
out.set(new Uint8Array(buf1), 0);
out.set(new Uint8Array(buf2), buf1.byteLength);
return out.buffer;
}
function ecdh_raw_pubkey_compressed(ec_raw_pubkey) {
const u8full = new Uint8Array(ec_raw_pubkey)
const len = u8full.byteLength
const u8 = u8full.slice(0, 1 + len >>> 1) // drop `y`
u8[0] = 0x2 | (u8full[len - 1] & 0x01) // encode sign of `y` in first bit
return u8.buffer
}
function print_buf(name, b) {
console.log(name + " = " + Array.from(new Uint8Array(b)).map(u => ("0" + u.toString(16)).slice(-2)).join(""))
}
function getInt64Bytes(x) {
var bytes = [];
var i = 8;
do {
bytes[--i] = x & (255);
x = x >> 8;
} while (i)
return bytes;
}
function parse_bcd(string, size) {
var i = 0, ip, k;
var array = new Uint8Array(size);
for (i = 0; i < string.length; i++) {
digit = string.charAt(i) - '0';
if (i % 2 == 0) {
ip = i / 2;
array[ip] = (digit << 4) | 0x0F;
} else {
ip = (i / 2) - 0.5;
array[ip] &= (0xF0 | digit);
}
}
for (k = ip + 1; k < size; k++) {
array[k] = 0xFF;
}
return array;
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Cléa example</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Cléa example</h1>
<div id="qrcode"></div>
<div class="config">
<div>
<label for="sk_l">Location secret key (SK_L):</label>
<input type="text" id="sk_l" name="sk_l" value="23c9b8f36ac1c0cddaf869c3733b771c3dc409416a9695df40397cea53e7f39e21f76925fc0c74ca6ee7c7eafad92473fd85758bab8f45fe01aac504"/>
</div>
<div>
<label for="pk_sa">Backend server public key (PK_SA):</label>
<input type="text" id="pk_sa" name="pk_sa" value="04c14d9db89a3dd8da8a366cf26cd67f1de468fb5dc15f240b0d2b96dbdb5f39af962cb0bdc0bafcc9e523bf5cd4eba420c51758f987457954d32f1003bbaaf1c5"/>
</div>
<div>
<label for="pk_mcta">Manual contact tracing authority public key (PK_MCTA):</label>
<input type="text" id="pk_mcta" name="pk_mcta" value="04c14d9db89a3dd8da8a366cf26cd67f1de468fb5dc15f240b0d2b96dbdb5f39af962cb0bdc0bafcc9e523bf5cd4eba420c51758f987457954d32f1003bbaaf1c5"/>
</div>
<div>
<label for="staff">Staff:</label>
<input type="checkbox" id="staff" name="staff"/>
</div>
<div>
<label for="CRI">Renewal interval (in min):</label>
<input type="text" id="CRI" name="CRI" value="1024"/>
</div>
<div>
<label for="venueType">Venue type:</label>
<select id="venueType">
<option value="0">Wedding</option>
<option value="1">Concert hall</option>
<option value="2">Cinema</option>
<option value="3">Restaurant</option>
<option value="4">Bar</option>
</select>
</div>
<div>
<label for="venueCategory1">Venue category:</label>
<select id="venueCategory1">
<option value="0">C11</option>
<option value="1">C12</option>
<option value="2">C13</option>
<option value="3">C14</option>
<option value="4">C15</option>
</select>
</div>
<div>
<label for="venueCategory2">Venue sub-category:</label>
<select id="venueCategory2">
<option value="0">C21</option>
<option value="1">C22</option>
<option value="2">C23</option>
<option value="3">C24</option>
<option value="4">C25</option>
</select>
</div>
<div>
<label for="countryCode">Country code:</label>
<select id="countryCode">
<option value="056">Belgium</option>
<option value="250">France</option>
<option value="274">Germany</option>
<option value="380">Italy</option>
<option value="724">Spain</option>
<option value="826">United Kindom of Great Britain and Northern Ireland</option>
<option value="840">United States of America</option>
</select>
</div>
<div>
<label for="periodDuration">Period duration (in h):</label>
<input type="text" id="periodDuration" name="periodDuration" value="3"/>
</div>
<div>
<label for="locationPhone">Location phone (optional):</label>
<input type="text" id="locationPhone" name="locationPhone"/>
</div>
<div>
<label for="locationPin">Location PIN (mandatory if location phone is present):</label>
<input type="text" id="locationPin" name="locationPin"/>
</div>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="qrcode.min.js"></script>
<script src="clea.js"></script>
<script src="test.js"></script>
</html>
var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c<a.length&&0==a[c];)c++;this.num=new Array(a.length-c+b);for(var d=0;d<a.length-c;d++)this.num[d]=a[d+c]}function j(a,b){this.totalCount=a,this.dataCount=b}function k(){this.buffer=[],this.length=0}function m(){return"undefined"!=typeof CanvasRenderingContext2D}function n(){var a=!1,b=navigator.userAgent;return/android/i.test(b)&&(a=!0,aMat=b.toString().match(/android ([0-9]\.[0-9])/i),aMat&&aMat[1]&&(a=parseFloat(aMat[1]))),a}function r(a,b){for(var c=1,e=s(a),f=0,g=l.length;g>=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d<this.moduleCount;d++){this.modules[d]=new Array(this.moduleCount);for(var e=0;e<this.moduleCount;e++)this.modules[d][e]=null}this.setupPositionProbePattern(0,0),this.setupPositionProbePattern(this.moduleCount-7,0),this.setupPositionProbePattern(0,this.moduleCount-7),this.setupPositionAdjustPattern(),this.setupTimingPattern(),this.setupTypeInfo(a,c),this.typeNumber>=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f<this.modules.length;f++)for(var g=f*e,h=0;h<this.modules[f].length;h++){var i=h*e,j=this.modules[f][h];j&&(d.beginFill(0,100),d.moveTo(i,g),d.lineTo(i+e,g),d.lineTo(i+e,g+e),d.lineTo(i,g+e),d.endFill())}return d},setupTimingPattern:function(){for(var a=8;a<this.moduleCount-8;a++)null==this.modules[a][6]&&(this.modules[a][6]=0==a%2);for(var b=8;b<this.moduleCount-8;b++)null==this.modules[6][b]&&(this.modules[6][b]=0==b%2)},setupPositionAdjustPattern:function(){for(var a=f.getPatternPosition(this.typeNumber),b=0;b<a.length;b++)for(var c=0;c<a.length;c++){var d=a[b],e=a[c];if(null==this.modules[d][e])for(var g=-2;2>=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g<a.length&&(j=1==(1&a[g]>>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h<d.length;h++){var i=d[h];g.put(i.mode,4),g.put(i.getLength(),f.getLengthInBits(i.mode,a)),i.write(g)}for(var l=0,h=0;h<e.length;h++)l+=e[h].dataCount;if(g.getLengthInBits()>8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j<b.length;j++){var k=b[j].dataCount,l=b[j].totalCount-k;d=Math.max(d,k),e=Math.max(e,l),g[j]=new Array(k);for(var m=0;m<g[j].length;m++)g[j][m]=255&a.buffer[m+c];c+=k;var n=f.getErrorCorrectPolynomial(l),o=new i(g[j],n.getLength()-1),p=o.mod(n);h[j]=new Array(n.getLength()-1);for(var m=0;m<h[j].length;m++){var q=m+p.getLength()-h[j].length;h[j][m]=q>=0?p.get(q):0}}for(var r=0,m=0;m<b.length;m++)r+=b[m].totalCount;for(var s=new Array(r),t=0,m=0;d>m;m++)for(var j=0;j<b.length;j++)m<g[j].length&&(s[t++]=g[j][m]);for(var m=0;e>m;m++)for(var j=0;j<b.length;j++)m<h[j].length&&(s[t++]=h[j][m]);return s};for(var c={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},d={L:1,M:0,Q:3,H:2},e={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},f={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(a){for(var b=a<<10;f.getBCHDigit(b)-f.getBCHDigit(f.G15)>=0;)b^=f.G15<<f.getBCHDigit(b)-f.getBCHDigit(f.G15);return(a<<10|b)^f.G15_MASK},getBCHTypeNumber:function(a){for(var b=a<<12;f.getBCHDigit(b)-f.getBCHDigit(f.G18)>=0;)b^=f.G18<<f.getBCHDigit(b)-f.getBCHDigit(f.G18);return a<<12|b},getBCHDigit